1 Commits

342 changed files with 4207 additions and 15883 deletions

View File

@@ -1,63 +0,0 @@
name: Bug inside BungeeCord
description: Create a bug report about a problem inside BungeeCord.
body:
- type: markdown
attributes:
value: |
#### Report a bug inside bungeecord
Issues happening with forks of BungeeCord should **not** be reported here.
- type: input
id: bungee-version
attributes:
label: Bungeecord version
description: The output of the /bungee command (or just the bungee build number) (execute in bungeecord console for easy text copy)
placeholder: e.g. git:BungeeCord-Bootstrap:1.xx-SNAPSHOT:xxxxxxx:xxxx
validations:
required: true
- type: input
id: server-version
attributes:
label: Server version
description: The output of the /version command (execute in server console for easy text copy)
placeholder: "e.g. git-Spigot-xxxxxxx-xxxxxxx (MC: 1.x.x)"
- type: input
id: client-version
attributes:
label: Client version
description: Minecraft Client Version
placeholder: e.g. 1.18.2
- type: textarea
id: bungee-plugins
attributes:
label: Bungeecord plugins
description: Please list all BungeeCord plugins you are using.
validations:
required: true
- type: textarea
id: the-bug
attributes:
label: The bug
description: Please describe the bug. Include **details** you find neccessary. If you just have a question, please ask it in [SpigotMC Forums](https://www.spigotmc.org) and not here.
validations:
required: true
- type: textarea
id: logs
attributes:
label: Log output (links)
description: Please put your log output inbetween three backticks (```` ``` ````). Upload your log files to [gist.github.com](https://gist.github.com) and put them in here.
placeholder: |
```
log output
```
- type: checkboxes
id: checkboxes
attributes:
label: Checking
options:
- label: I am using BungeeCord and **not a fork**. Issues with forks should not be reported here.
required: true
- label: I think this is **not** an issue with a bungeecord plugin.
required: true
- label: I have not read these checkboxes and therefore I just ticked them all.
- label: This is not a question or plugin creation help request.
required: true

View File

@@ -1,14 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Configuration help
url: https://www.spigotmc.org/forums/bungeecord-help.70/create-thread
about: Help for configuring bungeecord will only be answered in spigotmc.org forums.
- name: I have a problem with a bungee plugin
url: https://www.spigotmc.org/forums/bungeecord-plugin-help.71/create-thread
about: Help about plugins can be recieved in spigotmc.org forums.
- name: Questions and discussions
url: https://www.spigotmc.org/forums/bungeecord-discussion.21/create-thread
about: spigotmc.org forums are the best place to ask your questions regarding bungeecord.
- name: Plugin creation help
url: https://www.spigotmc.org/forums/bungeecord-plugin-development.23/create-thread
about: Plugin creation help for bungee plugins can be recieved in spigotmc.org forums.

View File

@@ -1,36 +0,0 @@
name: Feature request
description: Suggest a feature which bungeecord should include.
body:
- type: textarea
id: the-feature
attributes:
label: Feature description
description: Please describe your feature or improvement. Please include **details**.
validations:
required: true
- type: textarea
id: goal
attributes:
label: Goal of the feature
description: What is the goal of your feature?
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Unfitting alternatives
description: What alternatives have you considered and why are they not sufficient for your use case?
validations:
required: true
- type: checkboxes
id: checkboxes
attributes:
label: Checking
options:
- label: This is not a question or plugin creation help request.
required: true
- label: This is a **feature or improvement request**.
required: true
- label: I have not read these checkboxes and therefore I just ticked them all.
- label: I did not use this form to report a bug.
required: true

View File

@@ -1,27 +0,0 @@
version: 2
updates:
- package-ecosystem: "maven"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 50
ignore:
# Synchronised with Minecraft
- dependency-name: "com.google.code.gson:gson"
# 9.x has performance issues (see, eg, checkstyle/checkstyle#10934) and 10.x is incompatible
- dependency-name: "com.puppycrawl.tools:checkstyle"
# Newer versions have issues, see #1909 and #2050
- dependency-name: "jline:jline"
# Needs to be synchronised with maven-resolver-provider dependencies
- dependency-name: "org.apache.maven.resolver:maven-resolver-connector-basic"
- dependency-name: "org.apache.maven.resolver:maven-resolver-transport-http"
# Used with maven-resolver dependencies; 2.0 update breaks other providers
- dependency-name: "org.slf4j:slf4j-api"
update-types: ["version-update:semver-major"]
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 50

View File

@@ -4,20 +4,18 @@ on: [push, pull_request]
jobs: jobs:
build: build:
runs-on: ubuntu-24.04 runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false
matrix: matrix:
java: [8, 11, 17, 21, 25-ea] java: [8, 11]
name: Java ${{ matrix.java }} name: Java ${{ matrix.java }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- uses: actions/setup-java@v4 - uses: actions/setup-java@v1
with: with:
distribution: zulu
java-version: ${{ matrix.java }} java-version: ${{ matrix.java }}
- run: java -version && mvn --version - run: java -version && mvn --version
- run: mvn --activate-profiles dist --no-transfer-progress package - run: mvn --activate-profiles dist --no-transfer-progress package

6
.gitmodules vendored
View File

@@ -1,6 +0,0 @@
[submodule "native/mbedtls"]
path = native/mbedtls
url = https://github.com/ARMmbed/mbedtls.git
[submodule "native/zlib"]
path = native/zlib
url = https://github.com/cloudflare/zlib.git

View File

@@ -23,4 +23,4 @@ Binaries
-------- --------
Precompiled binaries are available for end users on [Jenkins](https://www.spigotmc.org/go/bungeecord-dl). Precompiled binaries are available for end users on [Jenkins](https://www.spigotmc.org/go/bungeecord-dl).
(c) 2012-2025 SpigotMC Pty. Ltd. (c) 2012-2020 SpigotMC Pty. Ltd.

View File

@@ -6,13 +6,13 @@
<parent> <parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId> <artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.4-SNAPSHOT</version> <version>1.16-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId> <artifactId>bungeecord-api</artifactId>
<version>1.21-R0.4-SNAPSHOT</version> <version>1.16-R0.4-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>BungeeCord-API</name> <name>BungeeCord-API</name>
@@ -31,12 +31,6 @@
<version>${project.version}</version> <version>${project.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-dialog</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-event</artifactId> <artifactId>bungeecord-event</artifactId>
@@ -52,33 +46,13 @@
<dependency> <dependency>
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-transport-native-unix-common</artifactId> <artifactId>netty-transport-native-unix-common</artifactId>
<version>${netty.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-resolver-provider</artifactId>
<version>3.9.6</version>
<!-- not part of the API proper -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-connector-basic</artifactId>
<version>1.9.18</version>
<!-- not part of the API proper -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-transport-http</artifactId>
<version>1.9.18</version>
<!-- not part of the API proper -->
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>org.yaml</groupId> <groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId> <artifactId>snakeyaml</artifactId>
<version>2.2</version> <version>1.26</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -7,7 +7,6 @@ import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Locale;
import java.util.UUID; import java.util.UUID;
/** /**
@@ -69,17 +68,6 @@ public class Util
return String.format( "0x%02X", i ); return String.format( "0x%02X", i );
} }
/**
* Formats an char as a unicode value.
*
* @param c the character to format
* @return the unicode representation of the character
*/
public static String unicode(char c)
{
return "\\u" + String.format( "%04x", (int) c ).toUpperCase( Locale.ROOT );
}
/** /**
* Constructs a pretty one line version of a {@link Throwable}. Useful for * Constructs a pretty one line version of a {@link Throwable}. Useful for
* debugging. * debugging.
@@ -88,24 +76,11 @@ public class Util
* @return a string representing information about the {@link Throwable} * @return a string representing information about the {@link Throwable}
*/ */
public static String exception(Throwable t) public static String exception(Throwable t)
{
return exception( t, true );
}
/**
* Constructs a pretty one line version of a {@link Throwable}. Useful for
* debugging.
*
* @param t the {@link Throwable} to format.
* @param includeLineNumbers whether to include line numbers
* @return a string representing information about the {@link Throwable}
*/
public static String exception(Throwable t, boolean includeLineNumbers)
{ {
// TODO: We should use clear manually written exceptions // TODO: We should use clear manually written exceptions
StackTraceElement[] trace = t.getStackTrace(); StackTraceElement[] trace = t.getStackTrace();
return t.getClass().getSimpleName() + " : " + t.getMessage() return t.getClass().getSimpleName() + " : " + t.getMessage()
+ ( ( includeLineNumbers && trace.length > 0 ) ? " @ " + t.getStackTrace()[0].getClassName() + ":" + t.getStackTrace()[0].getLineNumber() : "" ); + ( ( trace.length > 0 ) ? " @ " + t.getStackTrace()[0].getClassName() + ":" + t.getStackTrace()[0].getLineNumber() : "" );
} }
public static String csv(Iterable<?> objects) public static String csv(Iterable<?> objects)
@@ -113,16 +88,6 @@ public class Util
return format( objects, ", " ); return format( objects, ", " );
} }
/**
* Returns a string of objects, each separated by a separator.
*
* @param objects the objects to join
* @param separators the separator
* @return joined string
* @see String#join(java.lang.CharSequence, java.lang.Iterable)
* @deprecated use {@link String} join methods
*/
@Deprecated
public static String format(Iterable<?> objects, String separators) public static String format(Iterable<?> objects, String separators)
{ {
return Joiner.on( separators ).join( objects ); return Joiner.on( separators ).join( objects );

View File

@@ -28,13 +28,18 @@ public abstract class AbstractReconnectHandler implements ReconnectHandler
public static ServerInfo getForcedHost(PendingConnection con) public static ServerInfo getForcedHost(PendingConnection con)
{ {
String forced = ( con.getVirtualHost() == null ) ? null : con.getListener().getForcedHosts().get( con.getVirtualHost().getHostString() ); if ( con.getVirtualHost() == null )
{
return null;
}
String forced = con.getListener().getForcedHosts().get( con.getVirtualHost().getHostString() );
if ( forced == null && con.getListener().isForceDefault() ) if ( forced == null && con.getListener().isForceDefault() )
{ {
forced = con.getListener().getDefaultServer(); forced = con.getListener().getDefaultServer();
} }
return ( forced == null ) ? null : ProxyServer.getInstance().getServerInfo( forced ); return ProxyServer.getInstance().getServerInfo( forced );
} }
protected abstract ServerInfo getStoredServer(ProxiedPlayer player); protected abstract ServerInfo getStoredServer(ProxiedPlayer player);

View File

@@ -1,10 +1,9 @@
package net.md_5.bungee.api; package net.md_5.bungee.api;
import com.google.common.base.Preconditions;
import com.google.common.io.BaseEncoding; import com.google.common.io.BaseEncoding;
import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapter;
import com.google.gson.internal.bind.TypeAdapters;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter; import com.google.gson.stream.JsonWriter;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@@ -27,26 +26,13 @@ public class Favicon
@Override @Override
public void write(JsonWriter out, Favicon value) throws IOException public void write(JsonWriter out, Favicon value) throws IOException
{ {
if ( value == null ) TypeAdapters.STRING.write( out, value == null ? null : value.getEncoded() );
{
out.nullValue();
} else
{
out.value( value.getEncoded() );
}
} }
@Override @Override
public Favicon read(JsonReader in) throws IOException public Favicon read(JsonReader in) throws IOException
{ {
JsonToken peek = in.peek(); String enc = TypeAdapters.STRING.read( in );
if ( peek == JsonToken.NULL )
{
in.nextNull();
return null;
}
String enc = in.nextString();
return enc == null ? null : create( enc ); return enc == null ? null : create( enc );
} }
}; };
@@ -73,7 +59,6 @@ public class Favicon
*/ */
public static Favicon create(BufferedImage image) public static Favicon create(BufferedImage image)
{ {
Preconditions.checkArgument( image != null, "image is null" );
// check size // check size
if ( image.getWidth() != 64 || image.getHeight() != 64 ) if ( image.getWidth() != 64 || image.getHeight() != 64 )
{ {

View File

@@ -16,7 +16,6 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager; import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.api.scheduler.TaskScheduler; import net.md_5.bungee.api.scheduler.TaskScheduler;
import net.md_5.bungee.protocol.channel.BungeeChannelInitializer;
public abstract class ProxyServer public abstract class ProxyServer
{ {
@@ -312,56 +311,4 @@ public abstract class ProxyServer
*/ */
public abstract Title createTitle(); public abstract Title createTitle();
/**
* Get the unsafe methods of this class.
*
* @return the unsafe method interface
*/
public abstract Unsafe unsafe();
public interface Unsafe
{
/**
* Gets the frontend channel initializer
*
* @return the frontend channel initializer
*/
BungeeChannelInitializer getFrontendChannelInitializer();
/**
* Set the frontend channel initializer of this proxy
*
* @param channelInitializer the frontend channelInitializer to set
*/
void setFrontendChannelInitializer(BungeeChannelInitializer channelInitializer);
/**
* Gets the backend channel initializer
*
* @return the backend channel initializer
*/
BungeeChannelInitializer getBackendChannelInitializer();
/**
* Set the backend channel initializer of this proxy
*
* @param channelInitializer the backend channelInitializer to set
*/
void setBackendChannelInitializer(BungeeChannelInitializer channelInitializer);
/**
* Gets the server info channel initializer
*
* @return the server info channel initializer
*/
BungeeChannelInitializer getServerInfoChannelInitializer();
/**
* Set the server info channel initializer of this proxy
*
* @param channelInitializer the server info channelInitializer to set
*/
void setServerInfoChannelInitializer(BungeeChannelInitializer channelInitializer);
}
} }

View File

@@ -105,13 +105,13 @@ public class ServerPing
@Deprecated @Deprecated
public ServerPing(Protocol version, Players players, String description, String favicon) public ServerPing(Protocol version, Players players, String description, String favicon)
{ {
this( version, players, TextComponent.fromLegacy( description ), favicon == null ? null : Favicon.create( favicon ) ); this( version, players, new TextComponent( TextComponent.fromLegacyText( description ) ), favicon == null ? null : Favicon.create( favicon ) );
} }
@Deprecated @Deprecated
public ServerPing(Protocol version, Players players, String description, Favicon favicon) public ServerPing(Protocol version, Players players, String description, Favicon favicon)
{ {
this( version, players, TextComponent.fromLegacy( description ), favicon ); this( version, players, new TextComponent( TextComponent.fromLegacyText( description ) ), favicon );
} }
@Deprecated @Deprecated
@@ -139,7 +139,7 @@ public class ServerPing
@Deprecated @Deprecated
public void setDescription(String description) public void setDescription(String description)
{ {
this.description = TextComponent.fromLegacy( description ); this.description = new TextComponent( TextComponent.fromLegacyText( description ) );
} }
@Deprecated @Deprecated

View File

@@ -84,15 +84,5 @@ public interface Connection
* @param packet the packet to send * @param packet the packet to send
*/ */
void sendPacket(DefinedPacket packet); void sendPacket(DefinedPacket packet);
/**
* Queue a packet to this connection.
* If the packet is not registered for the connections current encoder protocol, it will be queued until it is,
* otherwise it will be sent immediately.
*
* @param packet the packet to be queued
* @throws UnsupportedOperationException if used for a PendingConnection
*/
void sendPacketQueued(DefinedPacket packet);
} }
} }

View File

@@ -2,9 +2,7 @@ package net.md_5.bungee.api.connection;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.config.ListenerInfo;
import org.jetbrains.annotations.ApiStatus;
/** /**
* Represents a user attempting to log into the proxy. * Represents a user attempting to log into the proxy.
@@ -91,40 +89,4 @@ public interface PendingConnection extends Connection
* @return Whether the client is using a legacy client. * @return Whether the client is using a legacy client.
*/ */
boolean isLegacy(); boolean isLegacy();
/**
* Gets if this connection has been transferred from another server.
*
* @return true if the connection has been transferred
*/
@ApiStatus.Experimental
boolean isTransferred();
/**
* Retrieves a cookie from this pending connection.
*
* @param cookie the resource location of the cookie, for example
* "bungeecord:my_cookie"
* @return a {@link CompletableFuture} that will be completed when the
* Cookie response is received. If the cookie is not set in the client, the
* {@link CompletableFuture} will complete with a null value
* @throws IllegalStateException if the player's version is not at least
* 1.20.5
*/
@ApiStatus.Experimental
CompletableFuture<byte[]> retrieveCookie(String cookie);
/**
* Sends a login payload request to the client.
*
* @param channel the channel to send this data via
* @param data the data to send
* @return a {@link CompletableFuture} that will be completed when the Login
* Payload response is received. If the Vanilla client doesn't know the
* channel, the {@link CompletableFuture} will complete with a null value
* @throws IllegalStateException if the player's version is not at least
* 1.13
*/
@ApiStatus.Experimental
CompletableFuture<byte[]> sendData(String channel, byte[] data);
} }

View File

@@ -3,7 +3,6 @@ package net.md_5.bungee.api.connection;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import net.md_5.bungee.api.Callback; import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.ChatMessageType; import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
@@ -12,13 +11,11 @@ import net.md_5.bungee.api.SkinConfiguration;
import net.md_5.bungee.api.Title; import net.md_5.bungee.api.Title;
import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.dialog.Dialog;
import net.md_5.bungee.api.event.ServerConnectEvent; import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.score.Scoreboard; import net.md_5.bungee.api.score.Scoreboard;
import org.jetbrains.annotations.ApiStatus;
/** /**
* Represents a player whose connection is being connected to somewhere else, * Represents a player who's connection is being connected to somewhere else,
* whether it be a remote or embedded server. * whether it be a remote or embedded server.
*/ */
public interface ProxiedPlayer extends Connection, CommandSender public interface ProxiedPlayer extends Connection, CommandSender
@@ -60,7 +57,8 @@ public interface ProxiedPlayer extends Connection, CommandSender
String getDisplayName(); String getDisplayName();
/** /**
* Sets this player's display name to be used by proxy commands and plugins. * Sets this players display name to be used as their nametag and tab list
* name.
* *
* @param name the name to set * @param name the name to set
*/ */
@@ -337,77 +335,6 @@ public interface ProxiedPlayer extends Connection, CommandSender
* Get the {@link Scoreboard} that belongs to this player. * Get the {@link Scoreboard} that belongs to this player.
* *
* @return this player's {@link Scoreboard} * @return this player's {@link Scoreboard}
* @deprecated for internal use only, setters will not have the expected
* effect, will not update client state, and may corrupt proxy state
*/ */
@Deprecated
Scoreboard getScoreboard(); Scoreboard getScoreboard();
/**
* Retrieves a cookie from this player.
*
* @param cookie the resource location of the cookie, for example
* "bungeecord:my_cookie"
* @return a {@link CompletableFuture} that will be completed when the
* Cookie response is received. If the cookie is not set in the client, the
* {@link CompletableFuture} will complete with a null value
* @throws IllegalStateException if the player's version is not at least
* 1.20.5
*/
@ApiStatus.Experimental
CompletableFuture<byte[]> retrieveCookie(String cookie);
/**
* Stores a cookie in this player's client.
*
* @param cookie the resource location of the cookie, for example
* "bungeecord:my_cookie"
* @param data the data to store in the cookie
* @throws IllegalStateException if the player's version is not at least
* 1.20.5
*/
@ApiStatus.Experimental
void storeCookie(String cookie, byte[] data);
/**
* Requests this player to connect to a different server specified by host
* and port.
*
* This is a client-side transfer - host and port should not specify a
* BungeeCord backend server.
*
* @param host the host of the server to transfer to
* @param port the port of the server to transfer to
* @throws IllegalStateException if the players version is not at least
* 1.20.5
*/
@ApiStatus.Experimental
void transfer(String host, int port);
/**
* Gets the client brand of this player.
* If the player has not sent a brand packet yet, it will return null.
*
* @return the brand of the client, or null if not received yet
*/
String getClientBrand();
/**
* Clear the player's open dialog.
*
* @throws IllegalStateException if the players version is not at least
* 1.21.6
*/
@ApiStatus.Experimental
void clearDialog();
/**
* Show a dialog to the player.
*
* @param dialog the dialog to show
* @throws IllegalStateException if the players version is not at least
* 1.21.6
*/
@ApiStatus.Experimental
void showDialog(Dialog dialog);
} }

View File

@@ -0,0 +1,157 @@
package net.md_5.bungee.api.event;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Setter;
import lombok.ToString;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.Connection;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.api.plugin.TabExecutor;
/**
* Event called when a downstream server (on 1.13+) sends the command structure
* to a player, but before BungeeCord adds the dummy command nodes of
* registered commands.
* <p>
* BungeeCord will not overwrite the modifications made by the listeners.
*
* <h2>Usage example</h2>
* Here is a usage example of this event, to declare a command structure.
* This illustrates the commands /server and /send of Bungee.
* <pre>
* event.getRoot().addChild( LiteralArgumentBuilder.&lt;CommandSender&gt;literal( "server" )
* .requires( sender -&gt; sender.hasPermission( "bungeecord.command.server" ) )
* .executes( a -&gt; 0 )
* .then( RequiredArgumentBuilder.argument( "serverName", StringArgumentType.greedyString() )
* .suggests( SuggestionRegistry.ASK_SERVER )
* )
* .build()
* );
* event.getRoot().addChild( LiteralArgumentBuilder.&lt;CommandSender&gt;literal( "send" )
* .requires( sender -&gt; sender.hasPermission( "bungeecord.command.send" ) )
* .then( RequiredArgumentBuilder.argument( "playerName", StringArgumentType.word() )
* .suggests( SuggestionRegistry.ASK_SERVER )
* .then( RequiredArgumentBuilder.argument( "serverName", StringArgumentType.greedyString() )
* .suggests( SuggestionRegistry.ASK_SERVER )
* )
* )
* .build()
* );
* </pre>
*
* <h2>Flag a {@link CommandNode} as executable or not</h2>
* The implementation of a {@link com.mojang.brigadier.Command Command} used in
* {@link ArgumentBuilder#executes(com.mojang.brigadier.Command)} will never be
* executed. This will only tell to the client if the current node is
* executable or not.
* <ul>
* <li>
* {@code builder.executes(null)} (default) to mark the node as not
* executable.
* </li>
* <li>
* {@code builder.executes(a -> 0)}, or any non null argument, to mark
* the node as executable (the child arguments are displayed as
* optional).
* </li>
* </ul>
*
* <h2>{@link CommandNode}s suggestions management</h2>
* The implementation of a SuggestionProvider used in
* {@link RequiredArgumentBuilder#suggests(SuggestionProvider)} will never be
* executed. This will only tell to the client how to deal with the
* auto-completion of the argument.
* <ul>
* <li>
* {@code builder.suggests(null)} (default) to disable auto-completion
* for this argument.
* </li>
* <li>
* {@code builder.suggests(SuggestionRegistry.ALL_RECIPES)} to suggest
* Minecrafts recipes.
* </li>
* <li>
* {@code builder.suggests(SuggestionRegistry.AVAILABLE_SOUNDS)} to
* suggest Minecrafts default sound identifiers.
* </li>
* <li>
* {@code builder.suggests(SuggestionRegistry.SUMMONABLE_ENTITIES)} to
* suggest Minecrafts default summonable entities identifiers.
* </li>
* <li>
* {@code builder.suggests(SuggestionRegistry.ASK_SERVER)}, or any
* other non null argument, to make the Minecraft client ask
* auto-completion to the server. Any specified implementation of
* {@link SuggestionProvider} will never be executed.
* </li>
* </ul>
*
* <h2>Argument types</h2>
* When building a new argument command node using
* {@link RequiredArgumentBuilder#argument(String, ArgumentType)}, you have to
* specify an {@link ArgumentType}. You can use all subclasses of
* {@link ArgumentType} provided with brigadier (for instance,
* {@link StringArgumentType} or {@link IntegerArgumentType}), or call any
* {@code ArgumentRegistry.minecraft*()} methods to use a {@code minecraft:*}
* argument type.
*
* <h2>Limitations with brigadier API</h2>
* This event is only used for the client to show command syntax, suggest
* sub-commands and color the arguments in the chat box. The command execution
* needs to be implemented using {@link PluginManager#registerCommand(Plugin,
* Command)} and the server-side tab-completion using {@link TabCompleteEvent}
* or {@link TabExecutor}.
*/
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class CommandsDeclareEvent extends TargetedEvent
{
/**
* Wether or not the command tree is modified by this event.
*
* If this value is set to true, BungeeCord will ensure that the
* modifications made in the command tree, will be sent to the player.
* If this is false, the modifications may not be taken into account.
*
* When calling {@link #getRoot()}, this value is automatically set
* to true.
*/
@Setter(value = AccessLevel.NONE)
private boolean modified = false;
/**
* The root command node of the command structure that will be send to the
* player.
*/
private final RootCommandNode<CommandSender> root;
public CommandsDeclareEvent(Connection sender, Connection receiver, RootCommandNode<CommandSender> root)
{
super( sender, receiver );
this.root = root;
}
/**
* The root command node of the command structure that will be send to the
* player.
* @return The root command node
*/
public RootCommandNode<CommandSender> getRoot()
{
modified = true;
return root;
}
}

View File

@@ -1,39 +0,0 @@
package net.md_5.bungee.api.event;
import com.google.gson.JsonElement;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Cancellable;
import net.md_5.bungee.api.plugin.Event;
import org.jetbrains.annotations.ApiStatus;
/**
* Called after a {@link ProxiedPlayer} runs a custom action from a chat event
* or form submission.
*/
@Data
@ToString(callSuper = false)
@EqualsAndHashCode(callSuper = false)
@ApiStatus.Experimental
public class CustomClickEvent extends Event implements Cancellable
{
/**
* Player who clicked.
*/
private final ProxiedPlayer player;
/**
* Custom action ID.
*/
private final String id;
/**
* The data as submitted.
*/
private final JsonElement data;
/**
* Cancelled state.
*/
private boolean cancelled;
}

View File

@@ -1,7 +1,9 @@
package net.md_5.bungee.api.event; package net.md_5.bungee.api.event;
import lombok.AccessLevel;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import net.md_5.bungee.api.Callback; import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.BaseComponent;
@@ -25,7 +27,8 @@ public class LoginEvent extends AsyncEvent<LoginEvent> implements Cancellable
/** /**
* Message to use when kicking if this event is canceled. * Message to use when kicking if this event is canceled.
*/ */
private BaseComponent reason; @Setter(AccessLevel.NONE)
private BaseComponent[] cancelReasonComponents;
/** /**
* Connection attempting to login. * Connection attempting to login.
*/ */
@@ -39,44 +42,28 @@ public class LoginEvent extends AsyncEvent<LoginEvent> implements Cancellable
/** /**
* @return reason to be displayed * @return reason to be displayed
* @deprecated use component methods instead * @deprecated Use component methods instead.
*/ */
@Deprecated @Deprecated
public String getCancelReason() public String getCancelReason()
{ {
return TextComponent.toLegacyText( getReason() ); return BaseComponent.toLegacyText( getCancelReasonComponents() );
} }
/** /**
* @param cancelReason reason to be displayed * @param cancelReason reason to be displayed
* @deprecated use component methods instead * @deprecated Use
* {@link #setCancelReason(net.md_5.bungee.api.chat.BaseComponent...)}
* instead.
*/ */
@Deprecated @Deprecated
public void setCancelReason(String cancelReason) public void setCancelReason(String cancelReason)
{ {
setReason( TextComponent.fromLegacy( cancelReason ) ); setCancelReason( TextComponent.fromLegacyText( cancelReason ) );
} }
/**
* @return reason to be displayed
* @deprecated use single component methods instead
*/
@Deprecated
public BaseComponent[] getCancelReasonComponents()
{
return new BaseComponent[]
{
getReason()
};
}
/**
* @param cancelReason reason to be displayed
* @deprecated use single component methods instead
*/
@Deprecated
public void setCancelReason(BaseComponent... cancelReason) public void setCancelReason(BaseComponent... cancelReason)
{ {
setReason( TextComponent.fromArray( cancelReason ) ); this.cancelReasonComponents = cancelReason;
} }
} }

View File

@@ -3,9 +3,8 @@ package net.md_5.bungee.api.event;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Event;
/** /**
* Event called as soon as a connection has a {@link ProxiedPlayer} and is ready * Event called as soon as a connection has a {@link ProxiedPlayer} and is ready
@@ -14,22 +13,11 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
@Data @Data
@ToString(callSuper = false) @ToString(callSuper = false)
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
public class PostLoginEvent extends AsyncEvent<PostLoginEvent> public class PostLoginEvent extends Event
{ {
/** /**
* The player involved with this event. * The player involved with this event.
*/ */
private final ProxiedPlayer player; private final ProxiedPlayer player;
/**
* The server to which the player will initially be connected.
*/
private ServerInfo target;
public PostLoginEvent(ProxiedPlayer player, ServerInfo target, Callback<PostLoginEvent> done)
{
super( done );
this.player = player;
this.target = target;
}
} }

View File

@@ -1,7 +1,9 @@
package net.md_5.bungee.api.event; package net.md_5.bungee.api.event;
import lombok.AccessLevel;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import net.md_5.bungee.api.Callback; import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.BaseComponent;
@@ -30,7 +32,8 @@ public class PreLoginEvent extends AsyncEvent<PreLoginEvent> implements Cancella
/** /**
* Message to use when kicking if this event is canceled. * Message to use when kicking if this event is canceled.
*/ */
private BaseComponent reason; @Setter(AccessLevel.NONE)
private BaseComponent[] cancelReasonComponents;
/** /**
* Connection attempting to login. * Connection attempting to login.
*/ */
@@ -44,44 +47,28 @@ public class PreLoginEvent extends AsyncEvent<PreLoginEvent> implements Cancella
/** /**
* @return reason to be displayed * @return reason to be displayed
* @deprecated use component methods instead * @deprecated Use component methods instead.
*/ */
@Deprecated @Deprecated
public String getCancelReason() public String getCancelReason()
{ {
return BaseComponent.toLegacyText( getReason() ); return BaseComponent.toLegacyText( getCancelReasonComponents() );
} }
/** /**
* @param cancelReason reason to be displayed * @param cancelReason reason to be displayed
* @deprecated Use component methods instead * @deprecated Use
* {@link #setCancelReason(net.md_5.bungee.api.chat.BaseComponent...)}
* instead.
*/ */
@Deprecated @Deprecated
public void setCancelReason(String cancelReason) public void setCancelReason(String cancelReason)
{ {
setReason( TextComponent.fromLegacy( cancelReason ) ); setCancelReason( TextComponent.fromLegacyText( cancelReason ) );
} }
/**
* @return reason to be displayed
* @deprecated use single component methods instead
*/
@Deprecated
public BaseComponent[] getCancelReasonComponents()
{
return new BaseComponent[]
{
getReason()
};
}
/**
* @param cancelReason reason to be displayed
* @deprecated use single component methods instead
*/
@Deprecated
public void setCancelReason(BaseComponent... cancelReason) public void setCancelReason(BaseComponent... cancelReason)
{ {
setReason( TextComponent.fromArray( cancelReason ) ); this.cancelReasonComponents = cancelReason;
} }
} }

View File

@@ -8,7 +8,7 @@ import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.connection.PendingConnection; import net.md_5.bungee.api.connection.PendingConnection;
/** /**
* Called when the proxy is queried for status from the server list. * Called when the proxy is pinged with packet 0xFE from the server list.
*/ */
@Data @Data
@ToString(callSuper = false) @ToString(callSuper = false)

View File

@@ -3,7 +3,6 @@ package net.md_5.bungee.api.event;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString;
import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Event; import net.md_5.bungee.api.plugin.Event;
@@ -11,7 +10,6 @@ import net.md_5.bungee.api.plugin.Event;
* Called when somebody reloads BungeeCord * Called when somebody reloads BungeeCord
*/ */
@Getter @Getter
@ToString(callSuper = false)
@AllArgsConstructor @AllArgsConstructor
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)
public class ProxyReloadEvent extends Event public class ProxyReloadEvent extends Event

View File

@@ -9,13 +9,6 @@ import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Event; import net.md_5.bungee.api.plugin.Event;
/**
* Called when the player is disconnected from a server, for example during
* server switching.
*
* If the player is kicked from a server, {@link ServerKickEvent} will be called
* instead.
*/
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@ToString(callSuper = false) @ToString(callSuper = false)

View File

@@ -35,7 +35,7 @@ public class ServerKickEvent extends Event implements Cancellable
/** /**
* Kick reason. * Kick reason.
*/ */
private BaseComponent reason; private BaseComponent[] kickReasonComponent;
/** /**
* Server to send player to if this event is cancelled. * Server to send player to if this event is cancelled.
*/ */
@@ -63,61 +63,24 @@ public class ServerKickEvent extends Event implements Cancellable
this( player, player.getServer().getInfo(), kickReasonComponent, cancelServer, state ); this( player, player.getServer().getInfo(), kickReasonComponent, cancelServer, state );
} }
@Deprecated
public ServerKickEvent(ProxiedPlayer player, ServerInfo kickedFrom, BaseComponent[] kickReasonComponent, ServerInfo cancelServer, State state) public ServerKickEvent(ProxiedPlayer player, ServerInfo kickedFrom, BaseComponent[] kickReasonComponent, ServerInfo cancelServer, State state)
{
this( player, kickedFrom, TextComponent.fromArray( kickReasonComponent ), cancelServer, state );
}
public ServerKickEvent(ProxiedPlayer player, ServerInfo kickedFrom, BaseComponent reason, ServerInfo cancelServer, State state)
{ {
this.player = player; this.player = player;
this.kickedFrom = kickedFrom; this.kickedFrom = kickedFrom;
this.reason = reason; this.kickReasonComponent = kickReasonComponent;
this.cancelServer = cancelServer; this.cancelServer = cancelServer;
this.state = state; this.state = state;
} }
/**
* @return the kick reason
* @deprecated use component methods instead
*/
@Deprecated @Deprecated
public String getKickReason() public String getKickReason()
{ {
return BaseComponent.toLegacyText( getReason() ); return BaseComponent.toLegacyText( kickReasonComponent );
} }
/**
* @param reason the kick reason
* @deprecated use component methods instead
*/
@Deprecated @Deprecated
public void setKickReason(String reason) public void setKickReason(String reason)
{ {
this.setReason( TextComponent.fromLegacy( reason ) ); kickReasonComponent = TextComponent.fromLegacyText( reason );
}
/**
* @return the kick reason
* @deprecated use single component methods instead
*/
@Deprecated
public BaseComponent[] getKickReasonComponent()
{
return new BaseComponent[]
{
getReason()
};
}
/**
* @param kickReasonComponent the kick reason
* @deprecated use single component methods instead
*/
@Deprecated
public void setKickReasonComponent(BaseComponent[] kickReasonComponent)
{
this.setReason( TextComponent.fromArray( kickReasonComponent ) );
} }
} }

View File

@@ -1,129 +0,0 @@
package net.md_5.bungee.api.plugin;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.collection.CollectRequest;
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
import org.eclipse.aether.graph.Dependency;
import org.eclipse.aether.impl.DefaultServiceLocator;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.DependencyRequest;
import org.eclipse.aether.resolution.DependencyResolutionException;
import org.eclipse.aether.resolution.DependencyResult;
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
import org.eclipse.aether.spi.connector.transport.TransporterFactory;
import org.eclipse.aether.transfer.AbstractTransferListener;
import org.eclipse.aether.transfer.TransferCancelledException;
import org.eclipse.aether.transfer.TransferEvent;
import org.eclipse.aether.transport.http.HttpTransporterFactory;
class LibraryLoader
{
private static final String REPOSITORY_PROPERTY = "net.md_5.bungee.api.plugin.centralURL";
private final Logger logger;
private final RepositorySystem repository;
private final DefaultRepositorySystemSession session;
private final List<RemoteRepository> repositories;
public LibraryLoader(Logger logger)
{
this.logger = logger;
DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
locator.addService( RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class );
locator.addService( TransporterFactory.class, HttpTransporterFactory.class );
this.repository = locator.getService( RepositorySystem.class );
this.session = MavenRepositorySystemUtils.newSession();
session.setChecksumPolicy( RepositoryPolicy.CHECKSUM_POLICY_FAIL );
session.setLocalRepositoryManager( repository.newLocalRepositoryManager( session, new LocalRepository( "libraries" ) ) );
session.setTransferListener( new AbstractTransferListener()
{
@Override
public void transferStarted(TransferEvent event) throws TransferCancelledException
{
logger.log( Level.INFO, "Downloading {0}", event.getResource().getRepositoryUrl() + event.getResource().getResourceName() );
}
} );
// SPIGOT-7638: Add system properties,
// since JdkVersionProfileActivator needs 'java.version' when a profile has the 'jdk' element
// otherwise it will silently fail and not resolves the dependencies in the affected pom.
session.setSystemProperties( System.getProperties() );
session.setReadOnly();
this.repositories = repository.newResolutionRepositories( session, Arrays.asList( new RemoteRepository.Builder( "central", "default", System.getProperty( REPOSITORY_PROPERTY, "https://repo.maven.apache.org/maven2" ) ).build() ) );
}
public ClassLoader createLoader(PluginDescription desc)
{
if ( desc.getLibraries().isEmpty() )
{
return null;
}
logger.log( Level.INFO, "[{0}] Loading {1} libraries... please wait", new Object[]
{
desc.getName(), desc.getLibraries().size()
} );
List<Dependency> dependencies = new ArrayList<>();
for ( String library : desc.getLibraries() )
{
Artifact artifact = new DefaultArtifact( library );
Dependency dependency = new Dependency( artifact, null );
dependencies.add( dependency );
}
DependencyResult result;
try
{
result = repository.resolveDependencies( session, new DependencyRequest( new CollectRequest( (Dependency) null, dependencies, repositories ), null ) );
} catch ( DependencyResolutionException ex )
{
throw new RuntimeException( "Error resolving libraries", ex );
}
List<URL> jarFiles = new ArrayList<>();
for ( ArtifactResult artifact : result.getArtifactResults() )
{
File file = artifact.getArtifact().getFile();
URL url;
try
{
url = file.toURI().toURL();
} catch ( MalformedURLException ex )
{
throw new AssertionError( ex );
}
jarFiles.add( url );
logger.log( Level.INFO, "[{0}] Loaded library {1}", new Object[]
{
desc.getName(), file
} );
}
URLClassLoader loader = new URLClassLoader( jarFiles.toArray( new URL[ 0 ] ) );
return loader;
}
}

View File

@@ -1,23 +1,12 @@
package net.md_5.bungee.api.plugin; package net.md_5.bungee.api.plugin;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.io.ByteStreams;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import lombok.ToString;
import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ProxyServer;
@ToString(of = "desc")
final class PluginClassloader extends URLClassLoader final class PluginClassloader extends URLClassLoader
{ {
@@ -25,10 +14,6 @@ final class PluginClassloader extends URLClassLoader
// //
private final ProxyServer proxy; private final ProxyServer proxy;
private final PluginDescription desc; private final PluginDescription desc;
private final JarFile jar;
private final Manifest manifest;
private final URL url;
private final ClassLoader libraryLoader;
// //
private Plugin plugin; private Plugin plugin;
@@ -37,18 +22,11 @@ final class PluginClassloader extends URLClassLoader
ClassLoader.registerAsParallelCapable(); ClassLoader.registerAsParallelCapable();
} }
public PluginClassloader(ProxyServer proxy, PluginDescription desc, File file, ClassLoader libraryLoader) throws IOException public PluginClassloader(ProxyServer proxy, PluginDescription desc, URL[] urls)
{ {
super( new URL[] super( urls );
{
file.toURI().toURL()
} );
this.proxy = proxy; this.proxy = proxy;
this.desc = desc; this.desc = desc;
this.jar = new JarFile( file );
this.manifest = jar.getManifest();
this.url = file.toURI().toURL();
this.libraryLoader = libraryLoader;
allLoaders.add( this ); allLoaders.add( this );
} }
@@ -56,34 +34,17 @@ final class PluginClassloader extends URLClassLoader
@Override @Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{ {
return loadClass0( name, resolve, true, true ); return loadClass0( name, resolve, true );
} }
private Class<?> loadClass0(String name, boolean resolve, boolean checkOther, boolean checkLibraries) throws ClassNotFoundException private Class<?> loadClass0(String name, boolean resolve, boolean checkOther) throws ClassNotFoundException
{ {
try try
{ {
Class<?> result = super.loadClass( name, resolve ); return super.loadClass( name, resolve );
// SPIGOT-6749: Library classes will appear in the above, but we don't want to return them to other plugins
if ( checkOther || result.getClassLoader() == this )
{
return result;
}
} catch ( ClassNotFoundException ex ) } catch ( ClassNotFoundException ex )
{ {
} }
if ( checkLibraries && libraryLoader != null )
{
try
{
return libraryLoader.loadClass( name );
} catch ( ClassNotFoundException ex )
{
}
}
if ( checkOther ) if ( checkOther )
{ {
for ( PluginClassloader loader : allLoaders ) for ( PluginClassloader loader : allLoaders )
@@ -92,81 +53,16 @@ final class PluginClassloader extends URLClassLoader
{ {
try try
{ {
return loader.loadClass0( name, resolve, false, proxy.getPluginManager().isTransitiveDepend( desc, loader.desc ) ); return loader.loadClass0( name, resolve, false );
} catch ( ClassNotFoundException ex ) } catch ( ClassNotFoundException ex )
{ {
} }
} }
} }
} }
throw new ClassNotFoundException( name ); throw new ClassNotFoundException( name );
} }
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException
{
String path = name.replace( '.', '/' ).concat( ".class" );
JarEntry entry = jar.getJarEntry( path );
if ( entry != null )
{
byte[] classBytes;
try ( InputStream is = jar.getInputStream( entry ) )
{
classBytes = ByteStreams.toByteArray( is );
} catch ( IOException ex )
{
throw new ClassNotFoundException( name, ex );
}
int dot = name.lastIndexOf( '.' );
if ( dot != -1 )
{
String pkgName = name.substring( 0, dot );
if ( getPackage( pkgName ) == null )
{
try
{
if ( manifest != null )
{
definePackage( pkgName, manifest, url );
} else
{
definePackage( pkgName, null, null, null, null, null, null, null );
}
} catch ( IllegalArgumentException ex )
{
if ( getPackage( pkgName ) == null )
{
throw new IllegalStateException( "Cannot find package " + pkgName );
}
}
}
}
CodeSigner[] signers = entry.getCodeSigners();
CodeSource source = new CodeSource( url, signers );
return defineClass( name, classBytes, 0, classBytes.length, source );
}
return super.findClass( name );
}
@Override
public void close() throws IOException
{
try
{
super.close();
} finally
{
jar.close();
}
}
void init(Plugin plugin) void init(Plugin plugin)
{ {
Preconditions.checkArgument( plugin != null, "plugin" ); Preconditions.checkArgument( plugin != null, "plugin" );

View File

@@ -2,8 +2,6 @@ package net.md_5.bungee.api.plugin;
import java.io.File; import java.io.File;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set; import java.util.Set;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@@ -50,8 +48,4 @@ public class PluginDescription
* Optional description. * Optional description.
*/ */
private String description = null; private String description = null;
/**
* Optional libraries.
*/
private List<String> libraries = new LinkedList<>();
} }

View File

@@ -4,12 +4,10 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import com.google.common.graph.GraphBuilder;
import com.google.common.graph.Graphs;
import com.google.common.graph.MutableGraph;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@@ -23,10 +21,6 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.Stack; import java.util.Stack;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.logging.Level; import java.util.logging.Level;
@@ -37,7 +31,6 @@ import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.event.EventBus; import net.md_5.bungee.event.EventBus;
import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventHandler;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor; import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.introspector.PropertyUtils; import org.yaml.snakeyaml.introspector.PropertyUtils;
@@ -56,40 +49,24 @@ public final class PluginManager
private final Yaml yaml; private final Yaml yaml;
private final EventBus eventBus; private final EventBus eventBus;
private final Map<String, Plugin> plugins = new LinkedHashMap<>(); private final Map<String, Plugin> plugins = new LinkedHashMap<>();
private final MutableGraph<String> dependencyGraph = GraphBuilder.directed().build();
private final LibraryLoader libraryLoader;
private final Map<String, Command> commandMap = new HashMap<>(); private final Map<String, Command> commandMap = new HashMap<>();
private Map<String, PluginDescription> toLoad = new HashMap<>(); private Map<String, PluginDescription> toLoad = new HashMap<>();
private final Multimap<Plugin, Command> commandsByPlugin = ArrayListMultimap.create(); private final Multimap<Plugin, Command> commandsByPlugin = ArrayListMultimap.create();
private final Multimap<Plugin, Listener> listenersByPlugin = ArrayListMultimap.create(); private final Multimap<Plugin, Listener> listenersByPlugin = ArrayListMultimap.create();
private final ReadWriteLock commandsLock = new ReentrantReadWriteLock();
private final Lock listenersLock = new ReentrantLock();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public PluginManager(ProxyServer proxy) public PluginManager(ProxyServer proxy)
{ {
this.proxy = proxy; this.proxy = proxy;
// Ignore unknown entries in the plugin descriptions // Ignore unknown entries in the plugin descriptions
Constructor yamlConstructor = new Constructor( new LoaderOptions() ); Constructor yamlConstructor = new Constructor();
PropertyUtils propertyUtils = yamlConstructor.getPropertyUtils(); PropertyUtils propertyUtils = yamlConstructor.getPropertyUtils();
propertyUtils.setSkipMissingProperties( true ); propertyUtils.setSkipMissingProperties( true );
yamlConstructor.setPropertyUtils( propertyUtils ); yamlConstructor.setPropertyUtils( propertyUtils );
yaml = new Yaml( yamlConstructor ); yaml = new Yaml( yamlConstructor );
eventBus = new EventBus( proxy.getLogger() ); eventBus = new EventBus( proxy.getLogger() );
LibraryLoader libraryLoader = null;
try
{
libraryLoader = new LibraryLoader( proxy.getLogger() );
} catch ( NoClassDefFoundError ex )
{
// Provided depends were not added back
proxy.getLogger().warning( "Could not initialize LibraryLoader (missing dependencies?)" );
}
this.libraryLoader = libraryLoader;
} }
/** /**
@@ -100,19 +77,12 @@ public final class PluginManager
*/ */
public void registerCommand(Plugin plugin, Command command) public void registerCommand(Plugin plugin, Command command)
{ {
commandsLock.writeLock().lock(); commandMap.put( command.getName().toLowerCase( Locale.ROOT ), command );
try for ( String alias : command.getAliases() )
{ {
commandMap.put( command.getName().toLowerCase( Locale.ROOT ), command ); commandMap.put( alias.toLowerCase( Locale.ROOT ), command );
for ( String alias : command.getAliases() )
{
commandMap.put( alias.toLowerCase( Locale.ROOT ), command );
}
commandsByPlugin.put( plugin, command );
} finally
{
commandsLock.writeLock().unlock();
} }
commandsByPlugin.put( plugin, command );
} }
/** /**
@@ -122,15 +92,8 @@ public final class PluginManager
*/ */
public void unregisterCommand(Command command) public void unregisterCommand(Command command)
{ {
commandsLock.writeLock().lock(); while ( commandMap.values().remove( command ) );
try commandsByPlugin.values().remove( command );
{
while ( commandMap.values().remove( command ) );
commandsByPlugin.values().remove( command );
} finally
{
commandsLock.writeLock().unlock();
}
} }
/** /**
@@ -140,18 +103,11 @@ public final class PluginManager
*/ */
public void unregisterCommands(Plugin plugin) public void unregisterCommands(Plugin plugin)
{ {
commandsLock.writeLock().lock(); for ( Iterator<Command> it = commandsByPlugin.get( plugin ).iterator(); it.hasNext(); )
try
{ {
for ( Iterator<Command> it = commandsByPlugin.get( plugin ).iterator(); it.hasNext(); ) Command command = it.next();
{ while ( commandMap.values().remove( command ) );
Command command = it.next(); it.remove();
while ( commandMap.values().remove( command ) );
it.remove();
}
} finally
{
commandsLock.writeLock().unlock();
} }
} }
@@ -165,14 +121,7 @@ public final class PluginManager
return null; return null;
} }
commandsLock.readLock().lock(); return commandMap.get( commandLower );
try
{
return commandMap.get( commandLower );
} finally
{
commandsLock.readLock().unlock();
}
} }
/** /**
@@ -360,7 +309,6 @@ public final class PluginManager
status = false; status = false;
} }
dependencyGraph.putEdge( plugin.getName(), dependName );
if ( !status ) if ( !status )
{ {
break; break;
@@ -372,7 +320,10 @@ public final class PluginManager
{ {
try try
{ {
URLClassLoader loader = new PluginClassloader( proxy, plugin, plugin.getFile(), ( libraryLoader != null ) ? libraryLoader.createLoader( plugin ) : null ); URLClassLoader loader = new PluginClassloader( proxy, plugin, new URL[]
{
plugin.getFile().toURI().toURL()
} );
Class<?> main = loader.loadClass( plugin.getMain() ); Class<?> main = loader.loadClass( plugin.getMain() );
Plugin clazz = (Plugin) main.getDeclaredConstructor().newInstance(); Plugin clazz = (Plugin) main.getDeclaredConstructor().newInstance();
@@ -384,7 +335,7 @@ public final class PluginManager
} ); } );
} catch ( Throwable t ) } catch ( Throwable t )
{ {
proxy.getLogger().log( Level.WARNING, "Error loading plugin " + plugin.getName(), t ); proxy.getLogger().log( Level.WARNING, "Error enabling plugin " + plugin.getName(), t );
} }
} }
@@ -469,20 +420,13 @@ public final class PluginManager
*/ */
public void registerListener(Plugin plugin, Listener listener) public void registerListener(Plugin plugin, Listener listener)
{ {
listenersLock.lock(); for ( Method method : listener.getClass().getDeclaredMethods() )
try
{ {
for ( Method method : listener.getClass().getDeclaredMethods() ) Preconditions.checkArgument( !method.isAnnotationPresent( Subscribe.class ),
{
Preconditions.checkArgument( !method.isAnnotationPresent( Subscribe.class ),
"Listener %s has registered using deprecated subscribe annotation! Please update to @EventHandler.", listener ); "Listener %s has registered using deprecated subscribe annotation! Please update to @EventHandler.", listener );
}
eventBus.register( listener );
listenersByPlugin.put( plugin, listener );
} finally
{
listenersLock.unlock();
} }
eventBus.register( listener );
listenersByPlugin.put( plugin, listener );
} }
/** /**
@@ -492,15 +436,8 @@ public final class PluginManager
*/ */
public void unregisterListener(Listener listener) public void unregisterListener(Listener listener)
{ {
listenersLock.lock(); eventBus.unregister( listener );
try listenersByPlugin.values().remove( listener );
{
eventBus.unregister( listener );
listenersByPlugin.values().remove( listener );
} finally
{
listenersLock.unlock();
}
} }
/** /**
@@ -510,17 +447,10 @@ public final class PluginManager
*/ */
public void unregisterListeners(Plugin plugin) public void unregisterListeners(Plugin plugin)
{ {
listenersLock.lock(); for ( Iterator<Listener> it = listenersByPlugin.get( plugin ).iterator(); it.hasNext(); )
try
{ {
for ( Iterator<Listener> it = listenersByPlugin.get( plugin ).iterator(); it.hasNext(); ) eventBus.unregister( it.next() );
{ it.remove();
eventBus.unregister( it.next() );
it.remove();
}
} finally
{
listenersLock.unlock();
} }
} }
@@ -531,28 +461,6 @@ public final class PluginManager
*/ */
public Collection<Map.Entry<String, Command>> getCommands() public Collection<Map.Entry<String, Command>> getCommands()
{ {
commandsLock.readLock().lock(); return Collections.unmodifiableCollection( commandMap.entrySet() );
try
{
return Collections.unmodifiableCollection( commandMap.entrySet() );
} finally
{
commandsLock.readLock().unlock();
}
}
boolean isTransitiveDepend(PluginDescription plugin, PluginDescription depend)
{
Preconditions.checkArgument( plugin != null, "plugin" );
Preconditions.checkArgument( depend != null, "depend" );
if ( dependencyGraph.nodes().contains( plugin.getName() ) )
{
if ( Graphs.reachableNodes( dependencyGraph, plugin.getName() ).contains( depend.getName() ) )
{
return true;
}
}
return false;
} }
} }

View File

@@ -1,37 +1,22 @@
package net.md_5.bungee.util; package net.md_5.bungee.util;
import it.unimi.dsi.fastutil.Hash; import gnu.trove.strategy.HashingStrategy;
import java.util.Locale; import java.util.Locale;
class CaseInsensitiveHashingStrategy implements Hash.Strategy<String> class CaseInsensitiveHashingStrategy implements HashingStrategy
{ {
static final CaseInsensitiveHashingStrategy INSTANCE = new CaseInsensitiveHashingStrategy(); static final CaseInsensitiveHashingStrategy INSTANCE = new CaseInsensitiveHashingStrategy();
@Override @Override
public int hashCode(String object) public int computeHashCode(Object object)
{ {
if ( object == null ) return ( (String) object ).toLowerCase( Locale.ROOT ).hashCode();
{
return 0;
}
return object.toLowerCase( Locale.ROOT ).hashCode();
} }
@Override @Override
public boolean equals(String o1, String o2) public boolean equals(Object o1, Object o2)
{ {
if ( o1 == o2 ) return o1.equals( o2 ) || ( o1 instanceof String && o2 instanceof String && ( (String) o1 ).toLowerCase( Locale.ROOT ).equals( ( (String) o2 ).toLowerCase( Locale.ROOT ) ) );
{
return true;
}
if ( o1 == null || o2 == null )
{
return false;
}
return o1.equals( o2 ) || o1.toLowerCase( Locale.ROOT ).equals( o2.toLowerCase( Locale.ROOT ) );
} }
} }

View File

@@ -1,9 +1,9 @@
package net.md_5.bungee.util; package net.md_5.bungee.util;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; import gnu.trove.map.hash.TCustomHashMap;
import java.util.Map; import java.util.Map;
public class CaseInsensitiveMap<V> extends Object2ObjectOpenCustomHashMap<String, V> public class CaseInsensitiveMap<V> extends TCustomHashMap<String, V>
{ {
public CaseInsensitiveMap() public CaseInsensitiveMap()
@@ -13,6 +13,6 @@ public class CaseInsensitiveMap<V> extends Object2ObjectOpenCustomHashMap<String
public CaseInsensitiveMap(Map<? extends String, ? extends V> map) public CaseInsensitiveMap(Map<? extends String, ? extends V> map)
{ {
super( map, CaseInsensitiveHashingStrategy.INSTANCE ); super( CaseInsensitiveHashingStrategy.INSTANCE, map );
} }
} }

View File

@@ -1,9 +1,9 @@
package net.md_5.bungee.util; package net.md_5.bungee.util;
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet; import gnu.trove.set.hash.TCustomHashSet;
import java.util.Collection; import java.util.Collection;
public class CaseInsensitiveSet extends ObjectOpenCustomHashSet<String> public class CaseInsensitiveSet extends TCustomHashSet<String>
{ {
public CaseInsensitiveSet() public CaseInsensitiveSet()
@@ -13,6 +13,6 @@ public class CaseInsensitiveSet extends ObjectOpenCustomHashSet<String>
public CaseInsensitiveSet(Collection<? extends String> collection) public CaseInsensitiveSet(Collection<? extends String> collection)
{ {
super( collection, CaseInsensitiveHashingStrategy.INSTANCE ); super( CaseInsensitiveHashingStrategy.INSTANCE, collection );
} }
} }

View File

@@ -1,13 +1,12 @@
package net.md_5.bungee.api; package net.md_5.bungee.api;
import static org.junit.jupiter.api.Assertions.*;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.Collection; import java.util.Collection;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.ServerConnectEvent; import net.md_5.bungee.api.event.ServerConnectEvent;
import org.junit.jupiter.api.Test; import org.junit.Test;
public class ServerConnectRequestTest public class ServerConnectRequestTest
{ {
@@ -79,15 +78,15 @@ public class ServerConnectRequestTest
} }
}; };
@Test @Test(expected = NullPointerException.class)
public void testNullTarget() public void testNullTarget()
{ {
assertThrows( NullPointerException.class, () -> ServerConnectRequest.builder().target( null ).reason( ServerConnectEvent.Reason.JOIN_PROXY ).build() ); ServerConnectRequest.builder().target( null ).reason( ServerConnectEvent.Reason.JOIN_PROXY ).build();
} }
@Test @Test(expected = NullPointerException.class)
public void testNullReason() public void testNullReason()
{ {
assertThrows( NullPointerException.class, () -> ServerConnectRequest.builder().target( DUMMY_INFO ).reason( null ).build() ); ServerConnectRequest.builder().target( DUMMY_INFO ).reason( null ).build();
} }
} }

View File

@@ -1,38 +1,63 @@
package net.md_5.bungee.util; package net.md_5.bungee.util;
import static org.junit.jupiter.api.Assertions.*;
import io.netty.channel.unix.DomainSocketAddress; import io.netty.channel.unix.DomainSocketAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.stream.Stream; import java.util.Arrays;
import java.util.Collection;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.md_5.bungee.Util; import net.md_5.bungee.Util;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.Assert;
import org.junit.jupiter.params.provider.Arguments; import org.junit.Test;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RequiredArgsConstructor @RequiredArgsConstructor
@RunWith(Parameterized.class)
public class AddressParseTest public class AddressParseTest
{ {
public static Stream<Arguments> data() @Parameters
public static Collection<Object[]> data()
{ {
return Stream.of( return Arrays.asList( new Object[][]
Arguments.of( "127.0.0.1", "127.0.0.1", Util.DEFAULT_PORT ), {
Arguments.of( "127.0.0.1:1337", "127.0.0.1", 1337 ), {
Arguments.of( "[::1]", "0:0:0:0:0:0:0:1", Util.DEFAULT_PORT ), "127.0.0.1", "127.0.0.1", Util.DEFAULT_PORT
Arguments.of( "[0:0:0:0::1]", "0:0:0:0:0:0:0:1", Util.DEFAULT_PORT ), },
Arguments.of( "[0:0:0:0:0:0:0:1]", "0:0:0:0:0:0:0:1", Util.DEFAULT_PORT ), {
Arguments.of( "[::1]:1337", "0:0:0:0:0:0:0:1", 1337 ), "127.0.0.1:1337", "127.0.0.1", 1337
Arguments.of( "[0:0:0:0::1]:1337", "0:0:0:0:0:0:0:1", 1337 ), },
Arguments.of( "[0:0:0:0:0:0:0:1]:1337", "0:0:0:0:0:0:0:1", 1337 ), {
Arguments.of( "unix:///var/run/bungee.sock", "/var/run/bungee.sock", -1 ) "[::1]", "0:0:0:0:0:0:0:1", Util.DEFAULT_PORT
); },
{
"[0:0:0:0::1]", "0:0:0:0:0:0:0:1", Util.DEFAULT_PORT
},
{
"[0:0:0:0:0:0:0:1]", "0:0:0:0:0:0:0:1", Util.DEFAULT_PORT
},
{
"[::1]:1337", "0:0:0:0:0:0:0:1", 1337
},
{
"[0:0:0:0::1]:1337", "0:0:0:0:0:0:0:1", 1337
},
{
"[0:0:0:0:0:0:0:1]:1337", "0:0:0:0:0:0:0:1", 1337
},
{
"unix:///var/run/bungee.sock", "/var/run/bungee.sock", -1
}
} );
} }
private final String line;
private final String host;
private final int port;
@ParameterizedTest @Test
@MethodSource("data") public void test()
public void test(String line, String host, int port)
{ {
SocketAddress parsed = Util.getAddr( line ); SocketAddress parsed = Util.getAddr( line );
@@ -40,14 +65,14 @@ public class AddressParseTest
{ {
InetSocketAddress tcp = (InetSocketAddress) parsed; InetSocketAddress tcp = (InetSocketAddress) parsed;
assertEquals( host, tcp.getHostString() ); Assert.assertEquals( host, tcp.getHostString() );
assertEquals( port, tcp.getPort() ); Assert.assertEquals( port, tcp.getPort() );
} else if ( parsed instanceof DomainSocketAddress ) } else if ( parsed instanceof DomainSocketAddress )
{ {
DomainSocketAddress unix = (DomainSocketAddress) parsed; DomainSocketAddress unix = (DomainSocketAddress) parsed;
assertEquals( host, unix.path() ); Assert.assertEquals( host, unix.path() );
assertEquals( -1, port ); Assert.assertEquals( -1, port );
} else } else
{ {
throw new AssertionError( "Unknown socket " + parsed ); throw new AssertionError( "Unknown socket " + parsed );

View File

@@ -1,7 +1,7 @@
package net.md_5.bungee.util; package net.md_5.bungee.util;
import static org.junit.jupiter.api.Assertions.*; import org.junit.Assert;
import org.junit.jupiter.api.Test; import org.junit.Test;
public class CaseInsensitiveTest public class CaseInsensitiveTest
{ {
@@ -13,12 +13,12 @@ public class CaseInsensitiveTest
CaseInsensitiveMap<Object> map = new CaseInsensitiveMap<>(); CaseInsensitiveMap<Object> map = new CaseInsensitiveMap<>();
map.put( "FOO", obj ); map.put( "FOO", obj );
assertTrue( map.containsKey( "foo" ) ); // Assert that contains is case insensitive Assert.assertTrue( map.contains( "foo" ) ); // Assert that contains is case insensitive
assertTrue( map.entrySet().iterator().next().getKey().equals( "FOO" ) ); // Assert that case is preserved Assert.assertTrue( map.entrySet().iterator().next().getKey().equals( "FOO" ) ); // Assert that case is preserved
// Assert that remove is case insensitive // Assert that remove is case insensitive
map.remove( "FoO" ); map.remove( "FoO" );
assertFalse( map.containsKey( "foo" ) ); Assert.assertFalse( map.contains( "foo" ) );
} }
@Test @Test
@@ -27,8 +27,8 @@ public class CaseInsensitiveTest
CaseInsensitiveSet set = new CaseInsensitiveSet(); CaseInsensitiveSet set = new CaseInsensitiveSet();
set.add( "FOO" ); set.add( "FOO" );
assertTrue( set.contains( "foo" ) ); // Assert that contains is case insensitive Assert.assertTrue( set.contains( "foo" ) ); // Assert that contains is case insensitive
set.remove( "FoO" ); set.remove( "FoO" );
assertFalse( set.contains( "foo" ) ); // Assert that remove is case insensitive Assert.assertFalse( set.contains( "foo" ) ); // Assert that remove is case insensitive
} }
} }

View File

@@ -1,9 +1,9 @@
package net.md_5.bungee.util; package net.md_5.bungee.util;
import static org.junit.jupiter.api.Assertions.*;
import java.util.UUID; import java.util.UUID;
import net.md_5.bungee.Util; import net.md_5.bungee.Util;
import org.junit.jupiter.api.Test; import org.junit.Assert;
import org.junit.Test;
public class UUIDTest public class UUIDTest
{ {
@@ -13,7 +13,7 @@ public class UUIDTest
{ {
UUID uuid = UUID.fromString( "af74a02d-19cb-445b-b07f-6866a861f783" ); UUID uuid = UUID.fromString( "af74a02d-19cb-445b-b07f-6866a861f783" );
UUID uuid1 = Util.getUUID( "af74a02d19cb445bb07f6866a861f783" ); UUID uuid1 = Util.getUUID( "af74a02d19cb445bb07f6866a861f783" );
assertEquals( uuid, uuid1 ); Assert.assertEquals( uuid, uuid1 );
} }
@Test @Test
@@ -23,7 +23,7 @@ public class UUIDTest
{ {
UUID expected = UUID.randomUUID(); UUID expected = UUID.randomUUID();
UUID actual = Util.getUUID( expected.toString().replace( "-", "" ) ); UUID actual = Util.getUUID( expected.toString().replace( "-", "" ) );
assertEquals( expected, actual, "Could not parse UUID " + expected ); Assert.assertEquals( "Could not parse UUID " + expected, expected, actual );
} }
} }
} }

View File

@@ -6,13 +6,13 @@
<parent> <parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId> <artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.4-SNAPSHOT</version> <version>1.16-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-bootstrap</artifactId> <artifactId>bungeecord-bootstrap</artifactId>
<version>1.21-R0.4-SNAPSHOT</version> <version>1.16-R0.4-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>BungeeCord-Bootstrap</name> <name>BungeeCord-Bootstrap</name>
@@ -41,14 +41,13 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>maven-jar-plugin</artifactId>
<version>3.4.1</version> <version>3.2.0</version>
<configuration> <configuration>
<archive> <archive>
<manifestEntries> <manifestEntries>
<Main-Class>net.md_5.bungee.Bootstrap</Main-Class> <Main-Class>net.md_5.bungee.Bootstrap</Main-Class>
<Implementation-Version>${describe}</Implementation-Version> <Implementation-Version>${describe}</Implementation-Version>
<Specification-Version>${maven.build.timestamp}</Specification-Version> <Specification-Version>${maven.build.timestamp}</Specification-Version>
<Enable-Native-Access>ALL-UNNAMED</Enable-Native-Access>
</manifestEntries> </manifestEntries>
</archive> </archive>
</configuration> </configuration>
@@ -56,7 +55,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version> <version>3.2.3</version>
<executions> <executions>
<execution> <execution>
<phase>package</phase> <phase>package</phase>
@@ -80,34 +79,4 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
<profiles>
<profile>
<id>jdk-9-release</id>
<activation>
<jdk>[9,)</jdk>
</activation>
<properties>
<maven.compiler.release>6</maven.compiler.release>
</properties>
</profile>
<profile>
<id>jdk-12-release</id>
<activation>
<jdk>[12,)</jdk>
</activation>
<properties>
<maven.compiler.release>7</maven.compiler.release>
</properties>
</profile>
<profile>
<id>jdk-20-release</id>
<activation>
<jdk>[20,)</jdk>
</activation>
<properties>
<maven.compiler.release>8</maven.compiler.release>
</properties>
</profile>
</profiles>
</project> </project>

View File

@@ -6,13 +6,13 @@
<parent> <parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId> <artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.4-SNAPSHOT</version> <version>1.16-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-chat</artifactId> <artifactId>bungeecord-chat</artifactId>
<version>1.21-R0.4-SNAPSHOT</version> <version>1.16-R0.4-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>BungeeCord-Chat</name> <name>BungeeCord-Chat</name>
@@ -22,7 +22,7 @@
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>
<version>2.11.0</version> <version>2.8.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -244,7 +244,7 @@ public final class ChatColor
public static ChatColor of(String string) public static ChatColor of(String string)
{ {
Preconditions.checkArgument( string != null, "string cannot be null" ); Preconditions.checkArgument( string != null, "string cannot be null" );
if ( string.length() == 7 && string.charAt( 0 ) == '#' ) if ( string.startsWith( "#" ) && string.length() == 7 )
{ {
int rgb; int rgb;
try try
@@ -300,7 +300,7 @@ public final class ChatColor
@Deprecated @Deprecated
public static ChatColor[] values() public static ChatColor[] values()
{ {
return BY_CHAR.values().toArray( new ChatColor[ 0 ] ); return BY_CHAR.values().toArray( new ChatColor[ BY_CHAR.values().size() ] );
} }
/** /**

View File

@@ -1,10 +1,8 @@
package net.md_5.bungee.api.chat; package net.md_5.bungee.api.chat;
import java.awt.Color;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@@ -22,10 +20,38 @@ public abstract class BaseComponent
BaseComponent parent; BaseComponent parent;
/** /**
* The component's style. * The color of this component and any child components (unless overridden)
*/ */
@Getter private ChatColor color;
private ComponentStyle style = new ComponentStyle(); /**
* The font of this component and any child components (unless overridden)
*/
private String font;
/**
* Whether this component and any child components (unless overridden) is
* bold
*/
private Boolean bold;
/**
* Whether this component and any child components (unless overridden) is
* italic
*/
private Boolean italic;
/**
* Whether this component and any child components (unless overridden) is
* underlined
*/
private Boolean underlined;
/**
* Whether this component and any child components (unless overridden) is
* strikethrough
*/
private Boolean strikethrough;
/**
* Whether this component and any child components (unless overridden) is
* obfuscated
*/
private Boolean obfuscated;
/** /**
* The text to insert into the chat when this component (and child * The text to insert into the chat when this component (and child
* components) are clicked while pressing the shift key * components) are clicked while pressing the shift key
@@ -52,12 +78,6 @@ public abstract class BaseComponent
@Getter @Getter
private HoverEvent hoverEvent; private HoverEvent hoverEvent;
/**
* Whether this component rejects previous formatting
*/
@Getter
private transient boolean reset;
/** /**
* Default constructor. * Default constructor.
* *
@@ -127,35 +147,31 @@ public abstract class BaseComponent
} }
if ( retention == FormatRetention.FORMATTING || retention == FormatRetention.ALL ) if ( retention == FormatRetention.FORMATTING || retention == FormatRetention.ALL )
{ {
if ( replace || !style.hasColor() ) if ( replace || color == null )
{ {
setColor( component.getColorRaw() ); setColor( component.getColorRaw() );
} }
if ( replace || !style.hasShadowColor() ) if ( replace || font == null )
{
setShadowColor( component.getShadowColorRaw() );
}
if ( replace || !style.hasFont() )
{ {
setFont( component.getFontRaw() ); setFont( component.getFontRaw() );
} }
if ( replace || style.isBoldRaw() == null ) if ( replace || bold == null )
{ {
setBold( component.isBoldRaw() ); setBold( component.isBoldRaw() );
} }
if ( replace || style.isItalicRaw() == null ) if ( replace || italic == null )
{ {
setItalic( component.isItalicRaw() ); setItalic( component.isItalicRaw() );
} }
if ( replace || style.isUnderlinedRaw() == null ) if ( replace || underlined == null )
{ {
setUnderlined( component.isUnderlinedRaw() ); setUnderlined( component.isUnderlinedRaw() );
} }
if ( replace || style.isStrikethroughRaw() == null ) if ( replace || strikethrough == null )
{ {
setStrikethrough( component.isStrikethroughRaw() ); setStrikethrough( component.isStrikethroughRaw() );
} }
if ( replace || style.isObfuscatedRaw() == null ) if ( replace || obfuscated == null )
{ {
setObfuscated( component.isObfuscatedRaw() ); setObfuscated( component.isObfuscatedRaw() );
} }
@@ -181,7 +197,6 @@ public abstract class BaseComponent
if ( retention == FormatRetention.EVENTS || retention == FormatRetention.NONE ) if ( retention == FormatRetention.EVENTS || retention == FormatRetention.NONE )
{ {
setColor( null ); setColor( null );
setShadowColor( null );
setBold( null ); setBold( null );
setItalic( null ); setItalic( null );
setUnderlined( null ); setUnderlined( null );
@@ -245,32 +260,6 @@ public abstract class BaseComponent
return builder.toString(); return builder.toString();
} }
/**
* Set the {@link ComponentStyle} for this component.
* <p>
* Unlike {@link #applyStyle(ComponentStyle)}, this method will overwrite
* all style values on this component.
*
* @param style the style to set, or null to set all style values to default
*/
public void setStyle(ComponentStyle style)
{
this.style = ( style != null ) ? style.clone() : new ComponentStyle();
}
/**
* Set this component's color.
* <p>
* <b>Warning: This should be a color, not formatting code (ie,
* {@link ChatColor#color} should not be null).</b>
*
* @param color the component color, or null to use the default
*/
public void setColor(ChatColor color)
{
this.style.setColor( color );
}
/** /**
* Returns the color of this component. This uses the parent's color if this * Returns the color of this component. This uses the parent's color if this
* component doesn't have one. {@link net.md_5.bungee.api.ChatColor#WHITE} * component doesn't have one. {@link net.md_5.bungee.api.ChatColor#WHITE}
@@ -280,7 +269,7 @@ public abstract class BaseComponent
*/ */
public ChatColor getColor() public ChatColor getColor()
{ {
if ( !style.hasColor() ) if ( color == null )
{ {
if ( parent == null ) if ( parent == null )
{ {
@@ -288,7 +277,7 @@ public abstract class BaseComponent
} }
return parent.getColor(); return parent.getColor();
} }
return style.getColor(); return color;
} }
/** /**
@@ -299,57 +288,7 @@ public abstract class BaseComponent
*/ */
public ChatColor getColorRaw() public ChatColor getColorRaw()
{ {
return style.getColor(); return color;
}
/**
* Set this component's shadow color.
*
* @param color the component shadow color, or null to use the default
*/
public void setShadowColor(Color color)
{
this.style.setShadowColor( color );
}
/**
* Returns the shadow color of this component. This uses the parent's shadow color if this
* component doesn't have one. null is returned if no shadow color is found.
*
* @return the shadow color of this component
*/
public Color getShadowColor()
{
if ( !style.hasShadowColor() )
{
if ( parent == null )
{
return null;
}
return parent.getShadowColor();
}
return style.getShadowColor();
}
/**
* Returns the shadow color of this component without checking the parents
* shadow color. May return null
*
* @return the shadow color of this component
*/
public Color getShadowColorRaw()
{
return style.getShadowColor();
}
/**
* Set this component's font.
*
* @param font the font to set, or null to use the default
*/
public void setFont(String font)
{
this.style.setFont( font );
} }
/** /**
@@ -360,7 +299,7 @@ public abstract class BaseComponent
*/ */
public String getFont() public String getFont()
{ {
if ( !style.hasFont() ) if ( font == null )
{ {
if ( parent == null ) if ( parent == null )
{ {
@@ -368,7 +307,7 @@ public abstract class BaseComponent
} }
return parent.getFont(); return parent.getFont();
} }
return style.getFont(); return font;
} }
/** /**
@@ -379,17 +318,7 @@ public abstract class BaseComponent
*/ */
public String getFontRaw() public String getFontRaw()
{ {
return style.getFont(); return font;
}
/**
* Set whether or not this component is bold.
*
* @param bold the new bold state, or null to use the default
*/
public void setBold(Boolean bold)
{
this.style.setBold( bold );
} }
/** /**
@@ -401,11 +330,11 @@ public abstract class BaseComponent
*/ */
public boolean isBold() public boolean isBold()
{ {
if ( style.isBoldRaw() == null ) if ( bold == null )
{ {
return parent != null && parent.isBold(); return parent != null && parent.isBold();
} }
return style.isBold(); return bold;
} }
/** /**
@@ -416,17 +345,7 @@ public abstract class BaseComponent
*/ */
public Boolean isBoldRaw() public Boolean isBoldRaw()
{ {
return style.isBoldRaw(); return bold;
}
/**
* Set whether or not this component is italic.
*
* @param italic the new italic state, or null to use the default
*/
public void setItalic(Boolean italic)
{
this.style.setItalic( italic );
} }
/** /**
@@ -438,11 +357,11 @@ public abstract class BaseComponent
*/ */
public boolean isItalic() public boolean isItalic()
{ {
if ( style.isItalicRaw() == null ) if ( italic == null )
{ {
return parent != null && parent.isItalic(); return parent != null && parent.isItalic();
} }
return style.isItalic(); return italic;
} }
/** /**
@@ -453,17 +372,7 @@ public abstract class BaseComponent
*/ */
public Boolean isItalicRaw() public Boolean isItalicRaw()
{ {
return style.isItalicRaw(); return italic;
}
/**
* Set whether or not this component is underlined.
*
* @param underlined the new underlined state, or null to use the default
*/
public void setUnderlined(Boolean underlined)
{
this.style.setUnderlined( underlined );
} }
/** /**
@@ -475,11 +384,11 @@ public abstract class BaseComponent
*/ */
public boolean isUnderlined() public boolean isUnderlined()
{ {
if ( style.isUnderlinedRaw() == null ) if ( underlined == null )
{ {
return parent != null && parent.isUnderlined(); return parent != null && parent.isUnderlined();
} }
return style.isUnderlined(); return underlined;
} }
/** /**
@@ -490,18 +399,7 @@ public abstract class BaseComponent
*/ */
public Boolean isUnderlinedRaw() public Boolean isUnderlinedRaw()
{ {
return style.isUnderlinedRaw(); return underlined;
}
/**
* Set whether or not this component is strikethrough.
*
* @param strikethrough the new strikethrough state, or null to use the
* default
*/
public void setStrikethrough(Boolean strikethrough)
{
this.style.setStrikethrough( strikethrough );
} }
/** /**
@@ -513,11 +411,11 @@ public abstract class BaseComponent
*/ */
public boolean isStrikethrough() public boolean isStrikethrough()
{ {
if ( style.isStrikethroughRaw() == null ) if ( strikethrough == null )
{ {
return parent != null && parent.isStrikethrough(); return parent != null && parent.isStrikethrough();
} }
return style.isStrikethrough(); return strikethrough;
} }
/** /**
@@ -528,17 +426,7 @@ public abstract class BaseComponent
*/ */
public Boolean isStrikethroughRaw() public Boolean isStrikethroughRaw()
{ {
return style.isStrikethroughRaw(); return strikethrough;
}
/**
* Set whether or not this component is obfuscated.
*
* @param obfuscated the new obfuscated state, or null to use the default
*/
public void setObfuscated(Boolean obfuscated)
{
this.style.setObfuscated( obfuscated );
} }
/** /**
@@ -550,11 +438,11 @@ public abstract class BaseComponent
*/ */
public boolean isObfuscated() public boolean isObfuscated()
{ {
if ( style.isObfuscatedRaw() == null ) if ( obfuscated == null )
{ {
return parent != null && parent.isObfuscated(); return parent != null && parent.isObfuscated();
} }
return style.isObfuscated(); return obfuscated;
} }
/** /**
@@ -565,52 +453,7 @@ public abstract class BaseComponent
*/ */
public Boolean isObfuscatedRaw() public Boolean isObfuscatedRaw()
{ {
return style.isObfuscatedRaw(); return obfuscated;
}
/**
* Apply the style from the given {@link ComponentStyle} to this component.
* <p>
* Any style values that have been explicitly set in the style will be
* applied to this component. If a value is not set in the style, it will
* not override the style set in this component.
*
* @param style the style to apply
*/
public void applyStyle(ComponentStyle style)
{
if ( style.hasColor() )
{
setColor( style.getColor() );
}
if ( style.hasShadowColor() )
{
setShadowColor( style.getShadowColor() );
}
if ( style.hasFont() )
{
setFont( style.getFont() );
}
if ( style.isBoldRaw() != null )
{
setBold( style.isBoldRaw() );
}
if ( style.isItalicRaw() != null )
{
setItalic( style.isItalicRaw() );
}
if ( style.isUnderlinedRaw() != null )
{
setUnderlined( style.isUnderlinedRaw() );
}
if ( style.isStrikethroughRaw() != null )
{
setStrikethrough( style.isStrikethroughRaw() );
}
if ( style.isObfuscatedRaw() != null )
{
setObfuscated( style.isObfuscatedRaw() );
}
} }
public void setExtra(List<BaseComponent> components) public void setExtra(List<BaseComponent> components)
@@ -649,16 +492,6 @@ public abstract class BaseComponent
extra.add( component ); extra.add( component );
} }
/**
* Returns whether the component has any styling applied to it.
*
* @return Whether any styling is applied
*/
public boolean hasStyle()
{
return !style.isEmpty();
}
/** /**
* Returns whether the component has any formatting or events applied to it * Returns whether the component has any formatting or events applied to it
* *
@@ -666,8 +499,10 @@ public abstract class BaseComponent
*/ */
public boolean hasFormatting() public boolean hasFormatting()
{ {
return hasStyle() || insertion != null return color != null || font != null || bold != null
|| hoverEvent != null || clickEvent != null; || italic != null || underlined != null
|| strikethrough != null || obfuscated != null
|| insertion != null || hoverEvent != null || clickEvent != null;
} }
/** /**
@@ -678,11 +513,11 @@ public abstract class BaseComponent
public String toPlainText() public String toPlainText()
{ {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
toPlainText( new LimitedStringVisitor( builder, Short.MAX_VALUE ) ); toPlainText( builder );
return builder.toString(); return builder.toString();
} }
void toPlainText(StringVisitor builder) void toPlainText(StringBuilder builder)
{ {
if ( extra != null ) if ( extra != null )
{ {
@@ -702,11 +537,11 @@ public abstract class BaseComponent
public String toLegacyText() public String toLegacyText()
{ {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
toLegacyText( new LimitedStringVisitor( builder, Short.MAX_VALUE ) ); toLegacyText( builder );
return builder.toString(); return builder.toString();
} }
void toLegacyText(StringVisitor builder) void toLegacyText(StringBuilder builder)
{ {
if ( extra != null ) if ( extra != null )
{ {
@@ -717,7 +552,7 @@ public abstract class BaseComponent
} }
} }
void addFormat(StringVisitor builder) void addFormat(StringBuilder builder)
{ {
builder.append( getColor() ); builder.append( getColor() );
if ( isBold() ) if ( isBold() )
@@ -741,35 +576,4 @@ public abstract class BaseComponent
builder.append( ChatColor.MAGIC ); builder.append( ChatColor.MAGIC );
} }
} }
@FunctionalInterface
protected static interface StringVisitor
{
void append(String s);
default void append(Object obj)
{
append( String.valueOf( obj ) );
}
}
@Data
protected static class LimitedStringVisitor implements StringVisitor
{
private final StringBuilder builder;
private final int maxLength;
@Override
public void append(String s)
{
if ( builder.length() >= maxLength )
{
throw new IllegalArgumentException( "String exceeded maximum length " + maxLength );
}
builder.append( s );
}
}
} }

View File

@@ -4,14 +4,12 @@ import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.ToString; import lombok.ToString;
import org.jetbrains.annotations.ApiStatus;
@Getter @Getter
@ToString @ToString
@EqualsAndHashCode @EqualsAndHashCode
@RequiredArgsConstructor @RequiredArgsConstructor
@ApiStatus.NonExtendable public final class ClickEvent
public class ClickEvent
{ {
/** /**
@@ -54,19 +52,11 @@ public class ClickEvent
* {@link net.md_5.bungee.api.chat.ClickEvent#value} in a book. * {@link net.md_5.bungee.api.chat.ClickEvent#value} in a book.
*/ */
CHANGE_PAGE, CHANGE_PAGE,
/**
* Must use subclass ShowDialogClickEvent.
*/
SHOW_DIALOG,
/** /**
* Copy the string given by * Copy the string given by
* {@link net.md_5.bungee.api.chat.ClickEvent#value} into the player's * {@link net.md_5.bungee.api.chat.ClickEvent#value} into the player's
* clipboard. * clipboard.
*/ */
COPY_TO_CLIPBOARD, COPY_TO_CLIPBOARD
/**
* Must use subclass {@link ClickEventCustom}.
*/
CUSTOM,
} }
} }

View File

@@ -1,30 +0,0 @@
package net.md_5.bungee.api.chat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
/**
* Click event which sends a custom payload to the server.
*/
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ClickEventCustom extends ClickEvent
{
/**
* The custom payload.
*/
private final String payload;
/**
* @param id identifier for the event (lower case, no special characters)
* @param payload custom payload
*/
public ClickEventCustom(String id, String payload)
{
super( ClickEvent.Action.CUSTOM, id );
this.payload = payload;
}
}

View File

@@ -57,7 +57,7 @@ public final class ComponentBuilder
*/ */
public ComponentBuilder(ComponentBuilder original) public ComponentBuilder(ComponentBuilder original)
{ {
this( original.parts.toArray( new BaseComponent[ 0 ] ) ); this( original.parts.toArray( new BaseComponent[ original.parts.size() ] ) );
} }
/** /**
@@ -161,7 +161,7 @@ public final class ComponentBuilder
previous = dummy; previous = dummy;
dummy = null; dummy = null;
} }
if ( previous != null && !component.isReset() ) if ( previous != null )
{ {
component.copyFormatting( previous, retention, false ); component.copyFormatting( previous, retention, false );
} }
@@ -204,33 +204,6 @@ public final class ComponentBuilder
return this; return this;
} }
/**
* Appends the {@link TranslationProvider} object to the builder and makes
* the last element the current target for formatting. The components will
* have all the formatting from previous part.
*
* @param translatable the translatable object to append
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder append(TranslationProvider translatable)
{
return append( translatable, FormatRetention.ALL );
}
/**
* Appends the {@link TranslationProvider} object to the builder and makes
* the last element the current target for formatting. You can specify the
* amount of formatting retained from previous part.
*
* @param translatable the translatable object to append
* @param retention the formatting to retain
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder append(TranslationProvider translatable, FormatRetention retention)
{
return append( translatable.asTranslatableComponent(), retention );
}
/** /**
* Appends the text to the builder and makes it the current target for * Appends the text to the builder and makes it the current target for
* formatting. The text will have all the formatting from previous part. * formatting. The text will have all the formatting from previous part.
@@ -423,18 +396,6 @@ public final class ComponentBuilder
return this; return this;
} }
/**
* Applies the provided {@link ComponentStyle} to the current part.
*
* @param style the style to apply
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder style(ComponentStyle style)
{
getCurrentComponent().applyStyle( style );
return this;
}
/** /**
* Sets the insertion text for the current part. * Sets the insertion text for the current part.
* *
@@ -493,32 +454,9 @@ public final class ComponentBuilder
return this; return this;
} }
/**
* Returns the component built by this builder. If this builder is empty, an
* empty text component will be returned.
*
* @return the component
*/
public BaseComponent build()
{
TextComponent base = new TextComponent();
if ( !parts.isEmpty() )
{
List<BaseComponent> cloned = new ArrayList<>( parts );
cloned.replaceAll( BaseComponent::duplicate );
base.setExtra( cloned );
}
return base;
}
/** /**
* Returns the components needed to display the message created by this * Returns the components needed to display the message created by this
* builder.git * builder.git
* <p>
* <strong>NOTE:</strong> {@link #build()} is preferred as it will
* consolidate all components into a single BaseComponent with extra
* contents as opposed to an array of components which is non-standard and
* may result in unexpected behavior.
* *
* @return the created components * @return the created components
*/ */

View File

@@ -1,263 +0,0 @@
package net.md_5.bungee.api.chat;
import java.awt.Color;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.md_5.bungee.api.ChatColor;
/**
* Represents a style that may be applied to a {@link BaseComponent}.
*/
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
public final class ComponentStyle implements Cloneable
{
/**
* The color of this style.
* <p>
* <b>Warning: This should be a color, not formatting code (ie,
* {@link ChatColor#color} should not be null).</b>
*/
private ChatColor color;
/**
* The shadow color of this style.
*/
private Color shadowColor;
/**
* The font of this style.
*/
private String font;
/**
* Whether this style is bold.
*/
private Boolean bold;
/**
* Whether this style is italic.
*/
private Boolean italic;
/**
* Whether this style is underlined.
*/
private Boolean underlined;
/**
* Whether this style is strikethrough.
*/
private Boolean strikethrough;
/**
* Whether this style is obfuscated.
*/
private Boolean obfuscated;
/**
* Returns the color of this style. May return null.
*
* @return the color of this style, or null if default color
*/
public ChatColor getColor()
{
return color;
}
/**
* Returns whether or not this style has a color set.
*
* @return whether a color is set
*/
public boolean hasColor()
{
return ( color != null );
}
/**
* Returns the shadow color of this style. May return null.
*
* @return the shadow color of this style, or null if default color
*/
public Color getShadowColor()
{
return shadowColor;
}
/**
* Returns whether or not this style has a shadow color set.
*
* @return whether a shadow color is set
*/
public boolean hasShadowColor()
{
return ( shadowColor != null );
}
/**
* Returns the font of this style. May return null.
*
* @return the font of this style, or null if default font
*/
public String getFont()
{
return font;
}
/**
* Returns whether or not this style has a font set.
*
* @return whether a font is set
*/
public boolean hasFont()
{
return ( font != null );
}
/**
* Returns whether this style is bold.
*
* @return whether the style is bold
*/
public boolean isBold()
{
return ( bold != null ) && bold.booleanValue();
}
/**
* Returns whether this style is bold. May return null.
*
* @return whether the style is bold, or null if not set
*/
public Boolean isBoldRaw()
{
return bold;
}
/**
* Returns whether this style is italic. May return null.
*
* @return whether the style is italic
*/
public boolean isItalic()
{
return ( italic != null ) && italic.booleanValue();
}
/**
* Returns whether this style is italic. May return null.
*
* @return whether the style is italic, or null if not set
*/
public Boolean isItalicRaw()
{
return italic;
}
/**
* Returns whether this style is underlined.
*
* @return whether the style is underlined
*/
public boolean isUnderlined()
{
return ( underlined != null ) && underlined.booleanValue();
}
/**
* Returns whether this style is underlined. May return null.
*
* @return whether the style is underlined, or null if not set
*/
public Boolean isUnderlinedRaw()
{
return underlined;
}
/**
* Returns whether this style is strikethrough
*
* @return whether the style is strikethrough
*/
public boolean isStrikethrough()
{
return ( strikethrough != null ) && strikethrough.booleanValue();
}
/**
* Returns whether this style is strikethrough. May return null.
*
* @return whether the style is strikethrough, or null if not set
*/
public Boolean isStrikethroughRaw()
{
return strikethrough;
}
/**
* Returns whether this style is obfuscated.
*
* @return whether the style is obfuscated
*/
public boolean isObfuscated()
{
return ( obfuscated != null ) && obfuscated.booleanValue();
}
/**
* Returns whether this style is obfuscated. May return null.
*
* @return whether the style is obfuscated, or null if not set
*/
public Boolean isObfuscatedRaw()
{
return obfuscated;
}
/**
* Returns whether this style has no formatting explicitly set.
*
* @return true if no value is set, false if at least one is set
*/
public boolean isEmpty()
{
return color == null && shadowColor == null && font == null && bold == null
&& italic == null && underlined == null
&& strikethrough == null && obfuscated == null;
}
@Override
public ComponentStyle clone()
{
return new ComponentStyle( color, shadowColor, font, bold, italic, underlined, strikethrough, obfuscated );
}
/**
* Get a new {@link ComponentStyleBuilder}.
*
* @return the builder
*/
public static ComponentStyleBuilder builder()
{
return new ComponentStyleBuilder();
}
/**
* Get a new {@link ComponentStyleBuilder} with values initialized to the
* style values of the supplied {@link ComponentStyle}.
*
* @param other the component style whose values to copy into the builder
* @return the builder
*/
public static ComponentStyleBuilder builder(ComponentStyle other)
{
return new ComponentStyleBuilder()
.color( other.color )
.shadowColor( other.shadowColor )
.font( other.font )
.bold( other.bold )
.italic( other.italic )
.underlined( other.underlined )
.strikethrough( other.strikethrough )
.obfuscated( other.obfuscated );
}
}

View File

@@ -1,140 +0,0 @@
package net.md_5.bungee.api.chat;
import java.awt.Color;
import net.md_5.bungee.api.ChatColor;
/**
* <p>
* ComponentStyleBuilder simplifies creating component styles by allowing the
* use of a chainable builder.
* </p>
* <pre>
* ComponentStyle style = ComponentStyle.builder()
* .color(ChatColor.RED)
* .font("custom:font")
* .bold(true).italic(true).create();
*
* BaseComponent component = new ComponentBuilder("Hello world").style(style).create();
* // Or it can be used directly on a component
* TextComponent text = new TextComponent("Hello world");
* text.applyStyle(style);
* </pre>
*
* @see ComponentStyle#builder()
* @see ComponentStyle#builder(ComponentStyle)
*/
public final class ComponentStyleBuilder
{
private ChatColor color;
private Color shadowColor;
private String font;
private Boolean bold, italic, underlined, strikethrough, obfuscated;
/**
* Set the style color.
*
* @param color the color to set, or null to use the default
* @return this ComponentStyleBuilder for chaining
*/
public ComponentStyleBuilder color(ChatColor color)
{
this.color = color;
return this;
}
/**
* Set the style shadow color.
*
* @param shadowColor the shadow color to set, or null to use the default
* @return this ComponentStyleBuilder for chaining
*/
public ComponentStyleBuilder shadowColor(Color shadowColor)
{
this.shadowColor = shadowColor;
return this;
}
/**
* Set the style font.
*
* @param font the font key to set, or null to use the default
* @return this ComponentStyleBuilder for chaining
*/
public ComponentStyleBuilder font(String font)
{
this.font = font;
return this;
}
/**
* Set the style's bold property.
*
* @param bold the bold value to set, or null to use the default
* @return this ComponentStyleBuilder for chaining
*/
public ComponentStyleBuilder bold(Boolean bold)
{
this.bold = bold;
return this;
}
/**
* Set the style's italic property.
*
* @param italic the italic value to set, or null to use the default
* @return this ComponentStyleBuilder for chaining
*/
public ComponentStyleBuilder italic(Boolean italic)
{
this.italic = italic;
return this;
}
/**
* Set the style's underlined property.
*
* @param underlined the underlined value to set, or null to use the default
* @return this ComponentStyleBuilder for chaining
*/
public ComponentStyleBuilder underlined(Boolean underlined)
{
this.underlined = underlined;
return this;
}
/**
* Set the style's strikethrough property.
*
* @param strikethrough the strikethrough value to set, or null to use the
* default
* @return this ComponentStyleBuilder for chaining
*/
public ComponentStyleBuilder strikethrough(Boolean strikethrough)
{
this.strikethrough = strikethrough;
return this;
}
/**
* Set the style's obfuscated property.
*
* @param obfuscated the obfuscated value to set, or null to use the default
* @return this ComponentStyleBuilder for chaining
*/
public ComponentStyleBuilder obfuscated(Boolean obfuscated)
{
this.obfuscated = obfuscated;
return this;
}
/**
* Build the {@link ComponentStyle} using the values set in this builder.
*
* @return the created ComponentStyle
*/
public ComponentStyle build()
{
return new ComponentStyle( color, shadowColor, font, bold, italic, underlined, strikethrough, obfuscated );
}
}

View File

@@ -13,7 +13,7 @@ import net.md_5.bungee.api.chat.hover.content.Content;
import net.md_5.bungee.api.chat.hover.content.Entity; import net.md_5.bungee.api.chat.hover.content.Entity;
import net.md_5.bungee.api.chat.hover.content.Item; import net.md_5.bungee.api.chat.hover.content.Item;
import net.md_5.bungee.api.chat.hover.content.Text; import net.md_5.bungee.api.chat.hover.content.Text;
import org.jetbrains.annotations.ApiStatus; import net.md_5.bungee.chat.ComponentSerializer;
@Getter @Getter
@ToString @ToString
@@ -34,7 +34,6 @@ public final class HoverEvent
* Returns whether this hover event is prior to 1.16 * Returns whether this hover event is prior to 1.16
*/ */
@Setter @Setter
@ApiStatus.Internal
private boolean legacy = false; private boolean legacy = false;
/** /**
@@ -72,6 +71,22 @@ public final class HoverEvent
this.legacy = true; this.legacy = true;
} }
@Deprecated
public BaseComponent[] getValue()
{
Content content = contents.get( 0 );
if ( content instanceof Text && ( (Text) content ).getValue() instanceof BaseComponent[] )
{
return (BaseComponent[]) ( (Text) content ).getValue();
}
TextComponent component = new TextComponent( ComponentSerializer.toString( content ) );
return new BaseComponent[]
{
component
};
}
/** /**
* Adds a content to this hover event. * Adds a content to this hover event.
* *

View File

@@ -43,7 +43,7 @@ public final class ItemTag
private final int level; private final int level;
private final int id; private final int id;
} }
*/ */
private ItemTag(String nbt) private ItemTag(String nbt)
{ {

View File

@@ -50,14 +50,14 @@ public final class KeybindComponent extends BaseComponent
} }
@Override @Override
protected void toPlainText(StringVisitor builder) protected void toPlainText(StringBuilder builder)
{ {
builder.append( getKeybind() ); builder.append( getKeybind() );
super.toPlainText( builder ); super.toPlainText( builder );
} }
@Override @Override
protected void toLegacyText(StringVisitor builder) protected void toLegacyText(StringBuilder builder)
{ {
addFormat( builder ); addFormat( builder );
builder.append( getKeybind() ); builder.append( getKeybind() );

View File

@@ -85,14 +85,14 @@ public final class ScoreComponent extends BaseComponent
} }
@Override @Override
protected void toPlainText(StringVisitor builder) protected void toPlainText(StringBuilder builder)
{ {
builder.append( this.value ); builder.append( this.value );
super.toPlainText( builder ); super.toPlainText( builder );
} }
@Override @Override
protected void toLegacyText(StringVisitor builder) protected void toLegacyText(StringBuilder builder)
{ {
addFormat( builder ); addFormat( builder );
builder.append( this.value ); builder.append( this.value );

View File

@@ -33,13 +33,6 @@ public final class SelectorComponent extends BaseComponent
*/ */
private String selector; private String selector;
/**
* The separator of multiple selected entities.
* <br>
* The default is {@code {"color": "gray", "text": ", "}}.
*/
private BaseComponent separator;
/** /**
* Creates a selector component from the original to clone it. * Creates a selector component from the original to clone it.
* *
@@ -49,17 +42,6 @@ public final class SelectorComponent extends BaseComponent
{ {
super( original ); super( original );
setSelector( original.getSelector() ); setSelector( original.getSelector() );
setSeparator( original.getSeparator() );
}
/**
* Creates a selector component from the selector
*
* @param selector the selector as a String
*/
public SelectorComponent(String selector)
{
setSelector( selector );
} }
@Override @Override
@@ -69,14 +51,14 @@ public final class SelectorComponent extends BaseComponent
} }
@Override @Override
protected void toPlainText(StringVisitor builder) protected void toPlainText(StringBuilder builder)
{ {
builder.append( this.selector ); builder.append( this.selector );
super.toPlainText( builder ); super.toPlainText( builder );
} }
@Override @Override
protected void toLegacyText(StringVisitor builder) protected void toLegacyText(StringBuilder builder)
{ {
addFormat( builder ); addFormat( builder );
builder.append( this.selector ); builder.append( this.selector );

View File

@@ -2,7 +2,6 @@ package net.md_5.bungee.api.chat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.function.Consumer;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@@ -28,41 +27,6 @@ public final class TextComponent extends BaseComponent
* @param message the text to convert * @param message the text to convert
* @return the components needed to print the message to the client * @return the components needed to print the message to the client
*/ */
public static BaseComponent fromLegacy(String message)
{
return fromLegacy( message, ChatColor.WHITE );
}
/**
* Converts the old formatting system that used
* {@link net.md_5.bungee.api.ChatColor#COLOR_CHAR} into the new json based
* system.
*
* @param message the text to convert
* @param defaultColor color to use when no formatting is to be applied
* (i.e. after ChatColor.RESET).
* @return the components needed to print the message to the client
*/
public static BaseComponent fromLegacy(String message, ChatColor defaultColor)
{
ComponentBuilder componentBuilder = new ComponentBuilder();
populateComponentStructure( message, defaultColor, componentBuilder::append );
return componentBuilder.build();
}
/**
* Converts the old formatting system that used
* {@link net.md_5.bungee.api.ChatColor#COLOR_CHAR} into the new json based
* system.
*
* @param message the text to convert
* @return the components needed to print the message to the client
* @deprecated {@link #fromLegacy(String)} is preferred as it will
* consolidate all components into a single BaseComponent with extra
* contents as opposed to an array of components which is non-standard and
* may result in unexpected behavior.
*/
@Deprecated
public static BaseComponent[] fromLegacyText(String message) public static BaseComponent[] fromLegacyText(String message)
{ {
return fromLegacyText( message, ChatColor.WHITE ); return fromLegacyText( message, ChatColor.WHITE );
@@ -77,21 +41,10 @@ public final class TextComponent extends BaseComponent
* @param defaultColor color to use when no formatting is to be applied * @param defaultColor color to use when no formatting is to be applied
* (i.e. after ChatColor.RESET). * (i.e. after ChatColor.RESET).
* @return the components needed to print the message to the client * @return the components needed to print the message to the client
* @deprecated {@link #fromLegacy(String, ChatColor)} is preferred as it
* will consolidate all components into a single BaseComponent with extra
* contents as opposed to an array of components which is non-standard and
* may result in unexpected behavior.
*/ */
@Deprecated
public static BaseComponent[] fromLegacyText(String message, ChatColor defaultColor) public static BaseComponent[] fromLegacyText(String message, ChatColor defaultColor)
{ {
ArrayList<BaseComponent> components = new ArrayList<>(); ArrayList<BaseComponent> components = new ArrayList<BaseComponent>();
populateComponentStructure( message, defaultColor, components::add );
return components.toArray( new BaseComponent[ 0 ] );
}
private static void populateComponentStructure(String message, ChatColor defaultColor, Consumer<BaseComponent> appender)
{
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
TextComponent component = new TextComponent(); TextComponent component = new TextComponent();
Matcher matcher = url.matcher( message ); Matcher matcher = url.matcher( message );
@@ -141,7 +94,7 @@ public final class TextComponent extends BaseComponent
component = new TextComponent( old ); component = new TextComponent( old );
old.setText( builder.toString() ); old.setText( builder.toString() );
builder = new StringBuilder(); builder = new StringBuilder();
appender.accept( old ); components.add( old );
} }
if ( format == ChatColor.BOLD ) if ( format == ChatColor.BOLD )
{ {
@@ -158,15 +111,15 @@ public final class TextComponent extends BaseComponent
} else if ( format == ChatColor.MAGIC ) } else if ( format == ChatColor.MAGIC )
{ {
component.setObfuscated( true ); component.setObfuscated( true );
} else } else if ( format == ChatColor.RESET )
{
format = defaultColor;
component = new TextComponent();
component.setColor( format );
} else
{ {
if ( format == ChatColor.RESET )
{
format = defaultColor;
}
component = new TextComponent(); component = new TextComponent();
component.setColor( format ); component.setColor( format );
component.setReset( true );
} }
continue; continue;
} }
@@ -184,7 +137,7 @@ public final class TextComponent extends BaseComponent
component = new TextComponent( old ); component = new TextComponent( old );
old.setText( builder.toString() ); old.setText( builder.toString() );
builder = new StringBuilder(); builder = new StringBuilder();
appender.accept( old ); components.add( old );
} }
TextComponent old = component; TextComponent old = component;
@@ -193,7 +146,7 @@ public final class TextComponent extends BaseComponent
component.setText( urlString ); component.setText( urlString );
component.setClickEvent( new ClickEvent( ClickEvent.Action.OPEN_URL, component.setClickEvent( new ClickEvent( ClickEvent.Action.OPEN_URL,
urlString.startsWith( "http" ) ? urlString : "http://" + urlString ) ); urlString.startsWith( "http" ) ? urlString : "http://" + urlString ) );
appender.accept( component ); components.add( component );
i += pos - i - 1; i += pos - i - 1;
component = old; component = old;
continue; continue;
@@ -202,29 +155,9 @@ public final class TextComponent extends BaseComponent
} }
component.setText( builder.toString() ); component.setText( builder.toString() );
appender.accept( component ); components.add( component );
}
/** return components.toArray( new BaseComponent[ components.size() ] );
* Internal compatibility method to transform an array of components to a
* single component.
*
* @param components array
* @return single component
*/
public static BaseComponent fromArray(BaseComponent... components)
{
if ( components == null )
{
return null;
}
if ( components.length == 1 )
{
return components[0];
}
return new TextComponent( components );
} }
/** /**
@@ -280,14 +213,14 @@ public final class TextComponent extends BaseComponent
} }
@Override @Override
protected void toPlainText(StringVisitor builder) protected void toPlainText(StringBuilder builder)
{ {
builder.append( text ); builder.append( text );
super.toPlainText( builder ); super.toPlainText( builder );
} }
@Override @Override
protected void toLegacyText(StringVisitor builder) protected void toLegacyText(StringBuilder builder)
{ {
addFormat( builder ); addFormat( builder );
builder.append( text ); builder.append( text );
@@ -297,6 +230,6 @@ public final class TextComponent extends BaseComponent
@Override @Override
public String toString() public String toString()
{ {
return "TextComponent{text=" + text + ", " + super.toString() + '}'; return String.format( "TextComponent{text=%s, %s}", text, super.toString() );
} }
} }

View File

@@ -19,7 +19,7 @@ import net.md_5.bungee.chat.TranslationRegistry;
public final class TranslatableComponent extends BaseComponent public final class TranslatableComponent extends BaseComponent
{ {
private static final Pattern FORMAT = Pattern.compile( "%(?:(\\d+)\\$)?([A-Za-z%]|$)" ); private final Pattern format = Pattern.compile( "%(?:(\\d+)\\$)?([A-Za-z%]|$)" );
/** /**
* The key into the Minecraft locale files to use for the translation. The * The key into the Minecraft locale files to use for the translation. The
@@ -30,10 +30,6 @@ public final class TranslatableComponent extends BaseComponent
* The components to substitute into the translation * The components to substitute into the translation
*/ */
private List<BaseComponent> with; private List<BaseComponent> with;
/**
* The fallback, if the translation is not found
*/
private String fallback;
/** /**
* Creates a translatable component from the original to clone it. * Creates a translatable component from the original to clone it.
@@ -44,11 +40,10 @@ public final class TranslatableComponent extends BaseComponent
{ {
super( original ); super( original );
setTranslate( original.getTranslate() ); setTranslate( original.getTranslate() );
setFallback( original.getFallback() );
if ( original.getWith() != null ) if ( original.getWith() != null )
{ {
List<BaseComponent> temp = new ArrayList<>(); List<BaseComponent> temp = new ArrayList<BaseComponent>();
for ( BaseComponent baseComponent : original.getWith() ) for ( BaseComponent baseComponent : original.getWith() )
{ {
temp.add( baseComponent.duplicate() ); temp.add( baseComponent.duplicate() );
@@ -87,21 +82,6 @@ public final class TranslatableComponent extends BaseComponent
} }
} }
/**
* Creates a translatable component with the passed substitutions
*
* @param translatable the translatable object
* @param with the {@link java.lang.String}s and
* {@link net.md_5.bungee.api.chat.BaseComponent}s to use into the
* translation
* @see #translate
* @see #setWith(java.util.List)
*/
public TranslatableComponent(TranslationProvider translatable, Object... with)
{
this( translatable.getTranslationKey(), with );
}
/** /**
* Creates a duplicate of this TranslatableComponent. * Creates a duplicate of this TranslatableComponent.
* *
@@ -156,29 +136,24 @@ public final class TranslatableComponent extends BaseComponent
} }
@Override @Override
protected void toPlainText(StringVisitor builder) protected void toPlainText(StringBuilder builder)
{ {
convert( builder, false ); convert( builder, false );
super.toPlainText( builder ); super.toPlainText( builder );
} }
@Override @Override
protected void toLegacyText(StringVisitor builder) protected void toLegacyText(StringBuilder builder)
{ {
convert( builder, true ); convert( builder, true );
super.toLegacyText( builder ); super.toLegacyText( builder );
} }
private void convert(StringVisitor builder, boolean applyFormat) private void convert(StringBuilder builder, boolean applyFormat)
{ {
String trans = TranslationRegistry.INSTANCE.translate( translate ); String trans = TranslationRegistry.INSTANCE.translate( translate );
if ( trans.equals( translate ) && fallback != null ) Matcher matcher = format.matcher( trans );
{
trans = fallback;
}
Matcher matcher = FORMAT.matcher( trans );
int position = 0; int position = 0;
int i = 0; int i = 0;
while ( matcher.find( position ) ) while ( matcher.find( position ) )

View File

@@ -1,38 +0,0 @@
package net.md_5.bungee.api.chat;
/**
* An object capable of being translated by the client in a
* {@link TranslatableComponent}.
*/
public interface TranslationProvider
{
/**
* Get the translation key.
*
* @return the translation key
*/
String getTranslationKey();
/**
* Get this translatable object as a {@link TranslatableComponent}.
*
* @return the translatable component
*/
default TranslatableComponent asTranslatableComponent()
{
return asTranslatableComponent( (Object[]) null );
}
/**
* Get this translatable object as a {@link TranslatableComponent}.
*
* @param with the {@link String Strings} and
* {@link BaseComponent BaseComponents} to use in the translation
* @return the translatable component
*/
default TranslatableComponent asTranslatableComponent(Object... with)
{
return new TranslatableComponent( this, with );
}
}

View File

@@ -0,0 +1,40 @@
package net.md_5.bungee.api.chat.hover.content;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
import net.md_5.bungee.api.chat.BaseComponent;
public class EntitySerializer implements JsonSerializer<Entity>, JsonDeserializer<Entity>
{
@Override
public Entity deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
{
JsonObject value = element.getAsJsonObject();
return new Entity(
( value.has( "type" ) ) ? value.get( "type" ).getAsString() : null,
value.get( "id" ).getAsString(),
( value.has( "name" ) ) ? context.deserialize( value.get( "name" ), BaseComponent.class ) : null
);
}
@Override
public JsonElement serialize(Entity content, Type type, JsonSerializationContext context)
{
JsonObject object = new JsonObject();
object.addProperty( "type", ( content.getType() != null ) ? content.getType() : "minecraft:pig" );
object.addProperty( "id", content.getId() );
if ( content.getName() != null )
{
object.add( "name", context.serialize( content.getName() ) );
}
return object;
}
}

View File

@@ -23,15 +23,6 @@ public class Text extends Content
this.value = value; this.value = value;
} }
public Text(BaseComponent value)
{
// For legacy serialization reasons, this has to be an array of components
this( new BaseComponent[]
{
value
} );
}
public Text(String value) public Text(String value)
{ {
this.value = value; this.value = value;

View File

@@ -0,0 +1,207 @@
package net.md_5.bungee.chat;
import com.google.common.base.Preconditions;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Locale;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.hover.content.Content;
public class BaseComponentSerializer
{
protected void deserialize(JsonObject object, BaseComponent component, JsonDeserializationContext context)
{
if ( object.has( "color" ) )
{
component.setColor( ChatColor.of( object.get( "color" ).getAsString() ) );
}
if ( object.has( "font" ) )
{
component.setFont( object.get( "font" ).getAsString() );
}
if ( object.has( "bold" ) )
{
component.setBold( object.get( "bold" ).getAsBoolean() );
}
if ( object.has( "italic" ) )
{
component.setItalic( object.get( "italic" ).getAsBoolean() );
}
if ( object.has( "underlined" ) )
{
component.setUnderlined( object.get( "underlined" ).getAsBoolean() );
}
if ( object.has( "strikethrough" ) )
{
component.setStrikethrough( object.get( "strikethrough" ).getAsBoolean() );
}
if ( object.has( "obfuscated" ) )
{
component.setObfuscated( object.get( "obfuscated" ).getAsBoolean() );
}
if ( object.has( "insertion" ) )
{
component.setInsertion( object.get( "insertion" ).getAsString() );
}
if ( object.has( "extra" ) )
{
component.setExtra( Arrays.asList( context.<BaseComponent[]>deserialize( object.get( "extra" ), BaseComponent[].class ) ) );
}
//Events
if ( object.has( "clickEvent" ) )
{
JsonObject event = object.getAsJsonObject( "clickEvent" );
component.setClickEvent( new ClickEvent(
ClickEvent.Action.valueOf( event.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) ),
( event.has( "value" ) ) ? event.get( "value" ).getAsString() : "" ) );
}
if ( object.has( "hoverEvent" ) )
{
JsonObject event = object.getAsJsonObject( "hoverEvent" );
HoverEvent hoverEvent = null;
HoverEvent.Action action = HoverEvent.Action.valueOf( event.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) );
for ( String type : Arrays.asList( "value", "contents" ) )
{
if ( !event.has( type ) )
{
continue;
}
JsonElement contents = event.get( type );
try
{
// Plugins previously had support to pass BaseComponent[] into any action.
// If the GSON is possible to be parsed as BaseComponent, attempt to parse as so.
BaseComponent[] components;
if ( contents.isJsonArray() )
{
components = context.deserialize( contents, BaseComponent[].class );
} else
{
components = new BaseComponent[]
{
context.deserialize( contents, BaseComponent.class )
};
}
hoverEvent = new HoverEvent( action, components );
} catch ( JsonParseException ex )
{
Content[] list;
if ( contents.isJsonArray() )
{
list = context.deserialize( contents, HoverEvent.getClass( action, true ) );
} else
{
list = new Content[]
{
context.deserialize( contents, HoverEvent.getClass( action, false ) )
};
}
hoverEvent = new HoverEvent( action, new ArrayList<>( Arrays.asList( list ) ) );
}
// stop the loop as soon as either one is found
break;
}
if ( hoverEvent != null )
{
component.setHoverEvent( hoverEvent );
}
}
}
protected void serialize(JsonObject object, BaseComponent component, JsonSerializationContext context)
{
boolean first = false;
if ( ComponentSerializer.serializedComponents.get() == null )
{
first = true;
ComponentSerializer.serializedComponents.set( Collections.newSetFromMap( new IdentityHashMap<BaseComponent, Boolean>() ) );
}
try
{
Preconditions.checkArgument( !ComponentSerializer.serializedComponents.get().contains( component ), "Component loop" );
ComponentSerializer.serializedComponents.get().add( component );
if ( component.getColorRaw() != null )
{
object.addProperty( "color", component.getColorRaw().getName() );
}
if ( component.getFontRaw() != null )
{
object.addProperty( "font", component.getFontRaw() );
}
if ( component.isBoldRaw() != null )
{
object.addProperty( "bold", component.isBoldRaw() );
}
if ( component.isItalicRaw() != null )
{
object.addProperty( "italic", component.isItalicRaw() );
}
if ( component.isUnderlinedRaw() != null )
{
object.addProperty( "underlined", component.isUnderlinedRaw() );
}
if ( component.isStrikethroughRaw() != null )
{
object.addProperty( "strikethrough", component.isStrikethroughRaw() );
}
if ( component.isObfuscatedRaw() != null )
{
object.addProperty( "obfuscated", component.isObfuscatedRaw() );
}
if ( component.getInsertion() != null )
{
object.addProperty( "insertion", component.getInsertion() );
}
if ( component.getExtra() != null )
{
object.add( "extra", context.serialize( component.getExtra() ) );
}
//Events
if ( component.getClickEvent() != null )
{
JsonObject clickEvent = new JsonObject();
clickEvent.addProperty( "action", component.getClickEvent().getAction().toString().toLowerCase( Locale.ROOT ) );
clickEvent.addProperty( "value", component.getClickEvent().getValue() );
object.add( "clickEvent", clickEvent );
}
if ( component.getHoverEvent() != null )
{
JsonObject hoverEvent = new JsonObject();
hoverEvent.addProperty( "action", component.getHoverEvent().getAction().toString().toLowerCase( Locale.ROOT ) );
if ( component.getHoverEvent().isLegacy() )
{
hoverEvent.add( "value", context.serialize( component.getHoverEvent().getContents().get( 0 ) ) );
} else
{
hoverEvent.add( "contents", context.serialize( ( component.getHoverEvent().getContents().size() == 1 )
? component.getHoverEvent().getContents().get( 0 ) : component.getHoverEvent().getContents() ) );
}
object.add( "hoverEvent", hoverEvent );
}
} finally
{
ComponentSerializer.serializedComponents.get().remove( component );
if ( first )
{
ComponentSerializer.serializedComponents.set( null );
}
}
}
}

View File

@@ -0,0 +1,109 @@
package net.md_5.bungee.chat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import java.lang.reflect.Type;
import java.util.Set;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ItemTag;
import net.md_5.bungee.api.chat.KeybindComponent;
import net.md_5.bungee.api.chat.ScoreComponent;
import net.md_5.bungee.api.chat.SelectorComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import net.md_5.bungee.api.chat.hover.content.Entity;
import net.md_5.bungee.api.chat.hover.content.EntitySerializer;
import net.md_5.bungee.api.chat.hover.content.Item;
import net.md_5.bungee.api.chat.hover.content.ItemSerializer;
import net.md_5.bungee.api.chat.hover.content.Text;
import net.md_5.bungee.api.chat.hover.content.TextSerializer;
public class ComponentSerializer implements JsonDeserializer<BaseComponent>
{
private static final JsonParser JSON_PARSER = new JsonParser();
private static final Gson gson = new GsonBuilder().
registerTypeAdapter( BaseComponent.class, new ComponentSerializer() ).
registerTypeAdapter( TextComponent.class, new TextComponentSerializer() ).
registerTypeAdapter( TranslatableComponent.class, new TranslatableComponentSerializer() ).
registerTypeAdapter( KeybindComponent.class, new KeybindComponentSerializer() ).
registerTypeAdapter( ScoreComponent.class, new ScoreComponentSerializer() ).
registerTypeAdapter( SelectorComponent.class, new SelectorComponentSerializer() ).
registerTypeAdapter( Entity.class, new EntitySerializer() ).
registerTypeAdapter( Text.class, new TextSerializer() ).
registerTypeAdapter( Item.class, new ItemSerializer() ).
registerTypeAdapter( ItemTag.class, new ItemTag.Serializer() ).
create();
public static final ThreadLocal<Set<BaseComponent>> serializedComponents = new ThreadLocal<Set<BaseComponent>>();
public static BaseComponent[] parse(String json)
{
JsonElement jsonElement = JSON_PARSER.parse( json );
if ( jsonElement.isJsonArray() )
{
return gson.fromJson( jsonElement, BaseComponent[].class );
} else
{
return new BaseComponent[]
{
gson.fromJson( jsonElement, BaseComponent.class )
};
}
}
public static String toString(Object object)
{
return gson.toJson( object );
}
public static String toString(BaseComponent component)
{
return gson.toJson( component );
}
public static String toString(BaseComponent... components)
{
if ( components.length == 1 )
{
return gson.toJson( components[0] );
} else
{
return gson.toJson( new TextComponent( components ) );
}
}
@Override
public BaseComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
if ( json.isJsonPrimitive() )
{
return new TextComponent( json.getAsString() );
}
JsonObject object = json.getAsJsonObject();
if ( object.has( "translate" ) )
{
return context.deserialize( json, TranslatableComponent.class );
}
if ( object.has( "keybind" ) )
{
return context.deserialize( json, KeybindComponent.class );
}
if ( object.has( "score" ) )
{
return context.deserialize( json, ScoreComponent.class );
}
if ( object.has( "selector" ) )
{
return context.deserialize( json, SelectorComponent.class );
}
return context.deserialize( json, TextComponent.class );
}
}

View File

@@ -13,23 +13,17 @@ import net.md_5.bungee.api.chat.KeybindComponent;
public class KeybindComponentSerializer extends BaseComponentSerializer implements JsonSerializer<KeybindComponent>, JsonDeserializer<KeybindComponent> public class KeybindComponentSerializer extends BaseComponentSerializer implements JsonSerializer<KeybindComponent>, JsonDeserializer<KeybindComponent>
{ {
public KeybindComponentSerializer(VersionedComponentSerializer serializer)
{
super( serializer );
}
@Override @Override
public KeybindComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException public KeybindComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{ {
JsonObject object = json.getAsJsonObject(); JsonObject object = json.getAsJsonObject();
JsonElement keybind = object.get( "keybind" ); if ( !object.has( "keybind" ) )
if ( keybind == null )
{ {
throw new JsonParseException( "Could not parse JSON: missing 'keybind' property" ); throw new JsonParseException( "Could not parse JSON: missing 'keybind' property" );
} }
KeybindComponent component = new KeybindComponent(); KeybindComponent component = new KeybindComponent();
deserialize( object, component, context ); deserialize( object, component, context );
component.setKeybind( keybind.getAsString() ); component.setKeybind( object.get( "keybind" ).getAsString() );
return component; return component;
} }

View File

@@ -13,38 +13,26 @@ import net.md_5.bungee.api.chat.ScoreComponent;
public class ScoreComponentSerializer extends BaseComponentSerializer implements JsonSerializer<ScoreComponent>, JsonDeserializer<ScoreComponent> public class ScoreComponentSerializer extends BaseComponentSerializer implements JsonSerializer<ScoreComponent>, JsonDeserializer<ScoreComponent>
{ {
public ScoreComponentSerializer(VersionedComponentSerializer serializer)
{
super( serializer );
}
@Override @Override
public ScoreComponent deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException public ScoreComponent deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
{ {
JsonObject json = element.getAsJsonObject(); JsonObject json = element.getAsJsonObject();
JsonObject score = json.getAsJsonObject( "score" ); if ( !json.has( "score" ) )
if ( score == null )
{ {
throw new JsonParseException( "Could not parse JSON: missing 'score' property" ); throw new JsonParseException( "Could not parse JSON: missing 'score' property" );
} }
JsonElement nameJson = score.get( "name" ); JsonObject score = json.get( "score" ).getAsJsonObject();
if ( nameJson == null ) if ( !score.has( "name" ) || !score.has( "objective" ) )
{
throw new JsonParseException( "A score component needs at least a name (and an objective)" );
}
JsonElement objectiveJson = score.get( "objective" );
if ( objectiveJson == null )
{ {
throw new JsonParseException( "A score component needs at least a name and an objective" ); throw new JsonParseException( "A score component needs at least a name and an objective" );
} }
String name = nameJson.getAsString(); String name = score.get( "name" ).getAsString();
String objective = objectiveJson.getAsString(); String objective = score.get( "objective" ).getAsString();
ScoreComponent component = new ScoreComponent( name, objective ); ScoreComponent component = new ScoreComponent( name, objective );
JsonElement value = score.get( "value" ); if ( score.has( "value" ) && !score.get( "value" ).getAsString().isEmpty() )
if ( value != null && !value.getAsString().isEmpty() )
{ {
component.setValue( value.getAsString() ); component.setValue( score.get( "value" ).getAsString() );
} }
deserialize( json, component, context ); deserialize( json, component, context );

View File

@@ -13,28 +13,15 @@ import net.md_5.bungee.api.chat.SelectorComponent;
public class SelectorComponentSerializer extends BaseComponentSerializer implements JsonSerializer<SelectorComponent>, JsonDeserializer<SelectorComponent> public class SelectorComponentSerializer extends BaseComponentSerializer implements JsonSerializer<SelectorComponent>, JsonDeserializer<SelectorComponent>
{ {
public SelectorComponentSerializer(VersionedComponentSerializer serializer)
{
super( serializer );
}
@Override @Override
public SelectorComponent deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException public SelectorComponent deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
{ {
JsonObject object = element.getAsJsonObject(); JsonObject object = element.getAsJsonObject();
JsonElement selector = object.get( "selector" ); if ( !object.has( "selector" ) )
if ( selector == null )
{ {
throw new JsonParseException( "Could not parse JSON: missing 'selector' property" ); throw new JsonParseException( "Could not parse JSON: missing 'selector' property" );
} }
SelectorComponent component = new SelectorComponent( selector.getAsString() ); SelectorComponent component = new SelectorComponent( object.get( "selector" ).getAsString() );
JsonElement separator = object.get( "separator" );
if ( separator != null )
{
component.setSeparator( serializer.deserialize( separator.getAsString() ) );
}
deserialize( object, component, context ); deserialize( object, component, context );
return component; return component;
} }
@@ -45,11 +32,6 @@ public class SelectorComponentSerializer extends BaseComponentSerializer impleme
JsonObject object = new JsonObject(); JsonObject object = new JsonObject();
serialize( object, component, context ); serialize( object, component, context );
object.addProperty( "selector", component.getSelector() ); object.addProperty( "selector", component.getSelector() );
if ( component.getSeparator() != null )
{
object.addProperty( "separator", serializer.toString( component.getSeparator() ) );
}
return object; return object;
} }
} }

View File

@@ -8,26 +8,23 @@ import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer; import com.google.gson.JsonSerializer;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.List;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
public class TextComponentSerializer extends BaseComponentSerializer implements JsonSerializer<TextComponent>, JsonDeserializer<TextComponent> public class TextComponentSerializer extends BaseComponentSerializer implements JsonSerializer<TextComponent>, JsonDeserializer<TextComponent>
{ {
public TextComponentSerializer(VersionedComponentSerializer serializer)
{
super( serializer );
}
@Override @Override
public TextComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException public TextComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{ {
TextComponent component = new TextComponent(); TextComponent component = new TextComponent();
JsonObject object = json.getAsJsonObject(); JsonObject object = json.getAsJsonObject();
JsonElement text = object.get( "text" ); if ( !object.has( "text" ) )
if ( text != null )
{ {
component.setText( text.getAsString() ); throw new JsonParseException( "Could not parse JSON: missing 'text' property" );
} }
component.setText( object.get( "text" ).getAsString() );
deserialize( object, component, context ); deserialize( object, component, context );
return component; return component;
} }
@@ -35,9 +32,13 @@ public class TextComponentSerializer extends BaseComponentSerializer implements
@Override @Override
public JsonElement serialize(TextComponent src, Type typeOfSrc, JsonSerializationContext context) public JsonElement serialize(TextComponent src, Type typeOfSrc, JsonSerializationContext context)
{ {
List<BaseComponent> extra = src.getExtra();
JsonObject object = new JsonObject(); JsonObject object = new JsonObject();
serialize( object, src, context );
object.addProperty( "text", src.getText() ); object.addProperty( "text", src.getText() );
if ( src.hasFormatting() || ( extra != null && !extra.isEmpty() ) )
{
serialize( object, src, context );
}
return object; return object;
} }
} }

View File

@@ -15,32 +15,20 @@ import net.md_5.bungee.api.chat.TranslatableComponent;
public class TranslatableComponentSerializer extends BaseComponentSerializer implements JsonSerializer<TranslatableComponent>, JsonDeserializer<TranslatableComponent> public class TranslatableComponentSerializer extends BaseComponentSerializer implements JsonSerializer<TranslatableComponent>, JsonDeserializer<TranslatableComponent>
{ {
public TranslatableComponentSerializer(VersionedComponentSerializer serializer)
{
super( serializer );
}
@Override @Override
public TranslatableComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException public TranslatableComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{ {
TranslatableComponent component = new TranslatableComponent(); TranslatableComponent component = new TranslatableComponent();
JsonObject object = json.getAsJsonObject(); JsonObject object = json.getAsJsonObject();
deserialize( object, component, context ); deserialize( object, component, context );
JsonElement translate = object.get( "translate" ); if ( !object.has( "translate" ) )
if ( translate == null )
{ {
throw new JsonParseException( "Could not parse JSON: missing 'translate' property" ); throw new JsonParseException( "Could not parse JSON: missing 'translate' property" );
} }
component.setTranslate( translate.getAsString() ); component.setTranslate( object.get( "translate" ).getAsString() );
JsonElement with = object.get( "with" ); if ( object.has( "with" ) )
if ( with != null )
{ {
component.setWith( Arrays.asList( context.deserialize( with, BaseComponent[].class ) ) ); component.setWith( Arrays.asList( context.<BaseComponent[]>deserialize( object.get( "with" ), BaseComponent[].class ) ) );
}
JsonElement fallback = object.get( "fallback" );
if ( fallback != null )
{
component.setFallback( fallback.getAsString() );
} }
return component; return component;
} }
@@ -55,10 +43,6 @@ public class TranslatableComponentSerializer extends BaseComponentSerializer imp
{ {
object.add( "with", context.serialize( src.getWith() ) ); object.add( "with", context.serialize( src.getWith() ) );
} }
if ( src.getFallback() != null )
{
object.addProperty( "fallback", src.getFallback() );
}
return object; return object;
} }
} }

View File

@@ -1,11 +1,11 @@
package net.md_5.bungee.chat; package net.md_5.bungee.chat;
import com.google.common.base.Charsets;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@@ -102,7 +102,7 @@ public final class TranslationRegistry
public JsonProvider(String resourcePath) throws IOException public JsonProvider(String resourcePath) throws IOException
{ {
try ( InputStreamReader rd = new InputStreamReader( JsonProvider.class.getResourceAsStream( resourcePath ), StandardCharsets.UTF_8 ) ) try ( InputStreamReader rd = new InputStreamReader( JsonProvider.class.getResourceAsStream( resourcePath ), Charsets.UTF_8 ) )
{ {
JsonObject obj = new Gson().fromJson( rd, JsonObject.class ); JsonObject obj = new Gson().fromJson( rd, JsonObject.class );
for ( Map.Entry<String, JsonElement> entries : obj.entrySet() ) for ( Map.Entry<String, JsonElement> entries : obj.entrySet() )

View File

@@ -0,0 +1,579 @@
package net.md_5.bungee.api.chat;
import java.awt.Color;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.hover.content.Item;
import net.md_5.bungee.api.chat.hover.content.Text;
import net.md_5.bungee.chat.ComponentSerializer;
import org.junit.Assert;
import org.junit.Test;
public class ComponentsTest
{
public static void testDissembleReassemble(BaseComponent[] components)
{
String json = ComponentSerializer.toString( components );
BaseComponent[] parsed = ComponentSerializer.parse( json );
Assert.assertEquals( TextComponent.toLegacyText( parsed ), TextComponent.toLegacyText( components ) );
}
public static void testDissembleReassemble(String json)
{
BaseComponent[] parsed = ComponentSerializer.parse( json );
Assert.assertEquals( json, ComponentSerializer.toString( parsed ) );
}
@Test
public void testItemParse()
{
// Declare all commonly used variables for reuse.
BaseComponent[] components;
TextComponent textComponent;
String json;
textComponent = new TextComponent( "Test" );
textComponent.setHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_ITEM, new BaseComponent[]
{
new TextComponent( "{id:\"minecraft:netherrack\",Count:47b}" )
} ) );
testDissembleReassemble( new BaseComponent[]
{
textComponent
} );
json = "{\"text\":\"Test\",\"hoverEvent\":{\"action\":\"show_item\",\"value\":[{\"text\":\"{id:\\\"minecraft:netherrack\\\",Count:47b}\"}]}}";
testDissembleReassemble( json );
//////////
String hoverVal = "{\"text\":\"{id:\\\"minecraft:dirt\\\",Count:1b}\"}";
json = "{\"extra\":[{\"text\":\"[\"},{\"extra\":[{\"translate\":\"block.minecraft.dirt\"}],\"text\":\"\"},{\"text\":\"]\"}],\"hoverEvent\":{\"action\":\"show_item\",\"value\":[" + hoverVal + "]},\"text\":\"\"}";
components = ComponentSerializer.parse( json );
Text contentText = ( (Text) components[0].getHoverEvent().getContents().get( 0 ) );
Assert.assertEquals( hoverVal, ComponentSerializer.toString( (BaseComponent[]) contentText.getValue() ) );
testDissembleReassemble( components );
//////////
TextComponent component1 = new TextComponent( "HoverableText" );
String nbt = "{display:{Name:{text:Hello},Lore:[{text:Line_1},{text:Line_2}]},ench:[{id:49,lvl:5}],Unbreakable:1}}";
Item contentItem = new Item( "minecraft:wood", 1, ItemTag.ofNbt( nbt ) );
HoverEvent hoverEvent = new HoverEvent( HoverEvent.Action.SHOW_ITEM, contentItem );
component1.setHoverEvent( hoverEvent );
json = ComponentSerializer.toString( component1 );
components = ComponentSerializer.parse( json );
Item parsedContentItem = ( (Item) components[0].getHoverEvent().getContents().get( 0 ) );
Assert.assertEquals( contentItem, parsedContentItem );
Assert.assertEquals( contentItem.getCount(), parsedContentItem.getCount() );
Assert.assertEquals( contentItem.getId(), parsedContentItem.getId() );
Assert.assertEquals( nbt, parsedContentItem.getTag().getNbt() );
}
@Test
public void testEmptyComponentBuilder()
{
ComponentBuilder builder = new ComponentBuilder();
BaseComponent[] parts = builder.create();
Assert.assertEquals( parts.length, 0 );
for ( int i = 0; i < 3; i++ )
{
builder.append( "part:" + i );
parts = builder.create();
Assert.assertEquals( parts.length, i + 1 );
}
}
@Test
public void testDummyRetaining()
{
ComponentBuilder builder = new ComponentBuilder();
Assert.assertNotNull( builder.getCurrentComponent() );
builder.color( ChatColor.GREEN );
builder.append( "test ", ComponentBuilder.FormatRetention.ALL );
Assert.assertEquals( builder.getCurrentComponent().getColor(), ChatColor.GREEN );
}
@Test(expected = IndexOutOfBoundsException.class)
public void testComponentGettingExceptions()
{
ComponentBuilder builder = new ComponentBuilder();
builder.getComponent( -1 );
builder.getComponent( 0 );
builder.getComponent( 1 );
BaseComponent component = new TextComponent( "Hello" );
builder.append( component );
Assert.assertEquals( builder.getComponent( 0 ), component );
builder.getComponent( 1 );
}
@Test
public void testComponentParting()
{
ComponentBuilder builder = new ComponentBuilder();
TextComponent apple = new TextComponent( "apple" );
builder.append( apple );
Assert.assertEquals( builder.getCurrentComponent(), apple );
Assert.assertEquals( builder.getComponent( 0 ), apple );
TextComponent mango = new TextComponent( "mango" );
TextComponent orange = new TextComponent( "orange" );
builder.append( mango );
builder.append( orange );
builder.removeComponent( 1 ); // Removing mango
Assert.assertEquals( builder.getComponent( 0 ), apple );
Assert.assertEquals( builder.getComponent( 1 ), orange );
}
@Test
public void testToLegacyFromLegacy()
{
String text = "§a§lHello §f§kworld§7!";
Assert.assertEquals( text, TextComponent.toLegacyText( TextComponent.fromLegacyText( text ) ) );
}
@Test(expected = IndexOutOfBoundsException.class)
public void testComponentBuilderCursorInvalidPos()
{
ComponentBuilder builder = new ComponentBuilder();
builder.append( new TextComponent( "Apple, " ) );
builder.append( new TextComponent( "Orange, " ) );
builder.setCursor( -1 );
builder.setCursor( 2 );
}
@Test
public void testComponentBuilderCursor()
{
TextComponent t1, t2, t3;
ComponentBuilder builder = new ComponentBuilder();
Assert.assertEquals( builder.getCursor(), -1 );
builder.append( t1 = new TextComponent( "Apple, " ) );
Assert.assertEquals( builder.getCursor(), 0 );
builder.append( t2 = new TextComponent( "Orange, " ) );
builder.append( t3 = new TextComponent( "Mango, " ) );
Assert.assertEquals( builder.getCursor(), 2 );
builder.setCursor( 0 );
Assert.assertEquals( builder.getCurrentComponent(), t1 );
// Test that appending new components updates the position to the new list size
// after having previously set it to 0 (first component)
builder.append( new TextComponent( "and Grapefruit" ) );
Assert.assertEquals( builder.getCursor(), 3 );
builder.setCursor( 0 );
builder.resetCursor();
Assert.assertEquals( builder.getCursor(), 3 );
}
@Test
public void testLegacyComponentBuilderAppend()
{
String text = "§a§lHello §r§kworld§7!";
BaseComponent[] components = TextComponent.fromLegacyText( text );
BaseComponent[] builderComponents = new ComponentBuilder().append( components ).create();
Assert.assertArrayEquals( components, builderComponents );
}
/*
@Test
public void testItemTag()
{
TextComponent component = new TextComponent( "Hello world" );
HoverEvent.ContentItem content = new HoverEvent.ContentItem();
content.setId( "minecraft:diamond_sword" );
content.setCount( 1 );
content.setTag( ItemTag.builder()
.ench( new ItemTag.Enchantment( 5, 16 ) )
.name( new TextComponent( "Sharp Sword" ) )
.unbreakable( true )
.lore( new ComponentBuilder( "Line1" ).create() )
.lore( new ComponentBuilder( "Line2" ).create() )
.build() );
HoverEvent event = new HoverEvent( HoverEvent.Action.SHOW_ITEM, content );
component.setHoverEvent( event );
String serialised = ComponentSerializer.toString( component );
BaseComponent[] deserialised = ComponentSerializer.parse( serialised );
Assert.assertEquals( TextComponent.toLegacyText( deserialised ), TextComponent.toLegacyText( component ) );
}
*/
@Test
public void testModernShowAdvancement()
{
String advancement = "achievement.openInventory";
// First do the text using the newer contents system
HoverEvent hoverEvent = new HoverEvent(
HoverEvent.Action.SHOW_TEXT,
new Text( advancement )
);
TextComponent component = new TextComponent( "test" );
component.setHoverEvent( hoverEvent );
Assert.assertEquals( component.getHoverEvent().getContents().size(), 1 );
Assert.assertTrue( component.getHoverEvent().getContents().get( 0 ) instanceof Text );
Assert.assertEquals( ( (Text) component.getHoverEvent().getContents().get( 0 ) ).getValue(), advancement );
}
@Test
public void testHoverEventContents()
{
// First do the text using the newer contents system
HoverEvent hoverEvent = new HoverEvent(
HoverEvent.Action.SHOW_TEXT,
new Text( new ComponentBuilder( "First" ).create() ),
new Text( new ComponentBuilder( "Second" ).create() )
);
TextComponent component = new TextComponent( "Sample text" );
component.setHoverEvent( hoverEvent );
Assert.assertEquals( hoverEvent.getContents().size(), 2 );
Assert.assertFalse( hoverEvent.isLegacy() );
String serialized = ComponentSerializer.toString( component );
BaseComponent[] deserialized = ComponentSerializer.parse( serialized );
Assert.assertEquals( component.getHoverEvent(), deserialized[0].getHoverEvent() );
// check the test still works with the value method
hoverEvent = new HoverEvent( HoverEvent.Action.SHOW_TEXT, new ComponentBuilder( "Sample text" ).create() );
Assert.assertEquals( hoverEvent.getContents().size(), 1 );
Assert.assertTrue( hoverEvent.isLegacy() );
serialized = ComponentSerializer.toString( component );
deserialized = ComponentSerializer.parse( serialized );
Assert.assertEquals( component.getHoverEvent(), deserialized[0].getHoverEvent() );
// Test single content:
String json = "{\"italic\":true,\"color\":\"gray\",\"translate\":\"chat.type.admin\",\"with\":[{\"text\":\"@\"}"
+ ",{\"translate\":\"commands.give.success.single\",\"with\":[\"1\",{\"color\":\"white\""
+ ",\"hoverEvent\":{\"action\":\"show_item\",\"contents\":{\"id\":\"minecraft:diamond_sword\",\"tag\":\""
+ "{Damage:0,display:{Lore:['\\\"test lore'!\\\"'],Name:'\\\"test\\\"'}}\"}},"
+ "\"extra\":[{\"italic\":true,\"extra\":[{\"text\":\"test\"}],\"text\":\"\"},{\"text\":\"]\"}],"
+ "\"text\":\"[\"},{\"insertion\":\"Name\",\"clickEvent\":{\"action\":\"suggest_command\",\"value\":"
+ "\"/tell Name \"},\"hoverEvent\":{\"action\":\"show_entity\",\"contents\":"
+ "{\"type\":\"minecraft:player\",\"id\":\"00000000-0000-0000-0000-00000000000000\",\"name\":"
+ "{\"text\":\"Name\"}}},\"text\":\"Name\"}]}]}";
testDissembleReassemble( ComponentSerializer.parse( json ) );
}
@Test
public void testFormatRetentionCopyFormatting()
{
TextComponent first = new TextComponent( "Hello" );
first.setBold( true );
first.setColor( ChatColor.RED );
first.setClickEvent( new ClickEvent( ClickEvent.Action.RUN_COMMAND, "test" ) );
first.setHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, new ComponentBuilder( "Test" ).create() ) );
TextComponent second = new TextComponent( " world" );
second.copyFormatting( first, ComponentBuilder.FormatRetention.ALL, true );
Assert.assertEquals( first.isBold(), second.isBold() );
Assert.assertEquals( first.getColor(), second.getColor() );
Assert.assertEquals( first.getClickEvent(), second.getClickEvent() );
Assert.assertEquals( first.getHoverEvent(), second.getHoverEvent() );
}
@Test
public void testBuilderClone()
{
ComponentBuilder builder = new ComponentBuilder( "Hello " ).color( ChatColor.RED ).append( "world" ).color( ChatColor.DARK_RED );
ComponentBuilder cloned = new ComponentBuilder( builder );
Assert.assertEquals( TextComponent.toLegacyText( builder.create() ), TextComponent.toLegacyText( cloned.create() ) );
}
@Test
public void testBuilderAppendMixedComponents()
{
ComponentBuilder builder = new ComponentBuilder( "Hello " );
TextComponent textComponent = new TextComponent( "world " );
TranslatableComponent translatableComponent = new TranslatableComponent( "item.swordGold.name" );
// array based BaseComponent append
builder.append( new BaseComponent[]
{
textComponent,
translatableComponent
} );
ScoreComponent scoreComponent = new ScoreComponent( "myscore", "myobjective" );
builder.append( scoreComponent ); // non array based BaseComponent append
BaseComponent[] components = builder.create();
Assert.assertEquals( "Hello ", components[0].toPlainText() );
Assert.assertEquals( textComponent.toPlainText(), components[1].toPlainText() );
Assert.assertEquals( translatableComponent.toPlainText(), components[2].toPlainText() );
Assert.assertEquals( scoreComponent.toPlainText(), components[3].toPlainText() );
}
@Test
public void testScore()
{
BaseComponent[] component = ComponentSerializer.parse( "{\"score\":{\"name\":\"@p\",\"objective\":\"TEST\",\"value\":\"hello\"}}" );
String text = ComponentSerializer.toString( component );
BaseComponent[] reparsed = ComponentSerializer.parse( text );
Assert.assertArrayEquals( component, reparsed );
}
@Test
public void testBuilderAppend()
{
ClickEvent clickEvent = new ClickEvent( ClickEvent.Action.RUN_COMMAND, "/help " );
HoverEvent hoverEvent = new HoverEvent( HoverEvent.Action.SHOW_TEXT, new ComponentBuilder( "Hello world" ).create() );
ComponentBuilder builder = new ComponentBuilder( "Hello " ).color( ChatColor.YELLOW );
builder.append( new ComponentBuilder( "world!" ).color( ChatColor.GREEN ).event( hoverEvent ).event( clickEvent ).create() );
BaseComponent[] components = builder.create();
Assert.assertEquals( components[1].getHoverEvent(), hoverEvent );
Assert.assertEquals( components[1].getClickEvent(), clickEvent );
Assert.assertEquals( "Hello world!", BaseComponent.toPlainText( components ) );
Assert.assertEquals( ChatColor.YELLOW + "Hello " + ChatColor.GREEN + "world!", BaseComponent.toLegacyText( components ) );
}
@Test
public void testBuilderAppendLegacy()
{
ComponentBuilder builder = new ComponentBuilder( "Hello " ).color( ChatColor.YELLOW );
builder.appendLegacy( "§aworld!" );
BaseComponent[] components = builder.create();
Assert.assertEquals( "Hello world!", BaseComponent.toPlainText( components ) );
Assert.assertEquals( ChatColor.YELLOW + "Hello " + ChatColor.GREEN + "world!", BaseComponent.toLegacyText( components ) );
}
@Test
public void testBasicComponent()
{
TextComponent textComponent = new TextComponent( "Hello world" );
textComponent.setColor( ChatColor.RED );
Assert.assertEquals( "Hello world", textComponent.toPlainText() );
Assert.assertEquals( ChatColor.RED + "Hello world", textComponent.toLegacyText() );
}
@Test
public void testLegacyConverter()
{
BaseComponent[] test1 = TextComponent.fromLegacyText( ChatColor.AQUA + "Aqua " + ChatColor.RED + ChatColor.BOLD + "RedBold" );
Assert.assertEquals( "Aqua RedBold", BaseComponent.toPlainText( test1 ) );
Assert.assertEquals( ChatColor.AQUA + "Aqua " + ChatColor.RED + ChatColor.BOLD + "RedBold", BaseComponent.toLegacyText( test1 ) );
BaseComponent[] test2 = TextComponent.fromLegacyText( "Text http://spigotmc.org " + ChatColor.GREEN + "google.com/test" );
Assert.assertEquals( "Text http://spigotmc.org google.com/test", BaseComponent.toPlainText( test2 ) );
//The extra ChatColor instances are sometimes inserted when not needed but it doesn't change the result
Assert.assertEquals( ChatColor.WHITE + "Text " + ChatColor.WHITE + "http://spigotmc.org" + ChatColor.WHITE
+ " " + ChatColor.GREEN + "google.com/test" + ChatColor.GREEN, BaseComponent.toLegacyText( test2 ) );
ClickEvent url1 = test2[1].getClickEvent();
Assert.assertNotNull( url1 );
Assert.assertTrue( url1.getAction() == ClickEvent.Action.OPEN_URL );
Assert.assertEquals( "http://spigotmc.org", url1.getValue() );
ClickEvent url2 = test2[3].getClickEvent();
Assert.assertNotNull( url2 );
Assert.assertTrue( url2.getAction() == ClickEvent.Action.OPEN_URL );
Assert.assertEquals( "http://google.com/test", url2.getValue() );
}
@Test
public void testTranslateComponent()
{
TranslatableComponent item = new TranslatableComponent( "item.swordGold.name" );
item.setColor( ChatColor.AQUA );
TranslatableComponent translatableComponent = new TranslatableComponent( "commands.give.success",
item, "5",
"thinkofdeath" );
Assert.assertEquals( "Given Golden Sword * 5 to thinkofdeath", translatableComponent.toPlainText() );
Assert.assertEquals( ChatColor.WHITE + "Given " + ChatColor.AQUA + "Golden Sword" + ChatColor.WHITE
+ " * " + ChatColor.WHITE + "5" + ChatColor.WHITE + " to " + ChatColor.WHITE + "thinkofdeath",
translatableComponent.toLegacyText() );
TranslatableComponent positional = new TranslatableComponent( "book.pageIndicator", "5", "50" );
Assert.assertEquals( "Page 5 of 50", positional.toPlainText() );
Assert.assertEquals( ChatColor.WHITE + "Page " + ChatColor.WHITE + "5" + ChatColor.WHITE + " of " + ChatColor.WHITE + "50", positional.toLegacyText() );
TranslatableComponent one_four_two = new TranslatableComponent( "filled_map.buried_treasure" );
Assert.assertEquals( "Buried Treasure Map", one_four_two.toPlainText() );
}
@Test
public void testBuilder()
{
BaseComponent[] components = new ComponentBuilder( "Hello " ).color( ChatColor.RED ).
append( "World" ).bold( true ).color( ChatColor.BLUE ).
append( "!" ).color( ChatColor.YELLOW ).create();
Assert.assertEquals( "Hello World!", BaseComponent.toPlainText( components ) );
Assert.assertEquals( ChatColor.RED + "Hello " + ChatColor.BLUE + ChatColor.BOLD
+ "World" + ChatColor.YELLOW + ChatColor.BOLD + "!", BaseComponent.toLegacyText( components ) );
}
@Test
public void testBuilderReset()
{
BaseComponent[] components = new ComponentBuilder( "Hello " ).color( ChatColor.RED )
.append( "World" ).reset().create();
Assert.assertEquals( components[0].getColor(), ChatColor.RED );
Assert.assertEquals( components[1].getColor(), ChatColor.WHITE );
}
@Test
public void testBuilderFormatRetention()
{
BaseComponent[] noneRetention = new ComponentBuilder( "Hello " ).color( ChatColor.RED )
.append( "World", ComponentBuilder.FormatRetention.NONE ).create();
Assert.assertEquals( noneRetention[0].getColor(), ChatColor.RED );
Assert.assertEquals( noneRetention[1].getColor(), ChatColor.WHITE );
HoverEvent testEvent = new HoverEvent( HoverEvent.Action.SHOW_TEXT, new ComponentBuilder( "test" ).create() );
BaseComponent[] formattingRetention = new ComponentBuilder( "Hello " ).color( ChatColor.RED )
.event( testEvent ).append( "World", ComponentBuilder.FormatRetention.FORMATTING ).create();
Assert.assertEquals( formattingRetention[0].getColor(), ChatColor.RED );
Assert.assertEquals( formattingRetention[0].getHoverEvent(), testEvent );
Assert.assertEquals( formattingRetention[1].getColor(), ChatColor.RED );
Assert.assertNull( formattingRetention[1].getHoverEvent() );
ClickEvent testClickEvent = new ClickEvent( ClickEvent.Action.OPEN_URL, "http://www.example.com" );
BaseComponent[] eventRetention = new ComponentBuilder( "Hello " ).color( ChatColor.RED )
.event( testEvent ).event( testClickEvent ).append( "World", ComponentBuilder.FormatRetention.EVENTS ).create();
Assert.assertEquals( eventRetention[0].getColor(), ChatColor.RED );
Assert.assertEquals( eventRetention[0].getHoverEvent(), testEvent );
Assert.assertEquals( eventRetention[0].getClickEvent(), testClickEvent );
Assert.assertEquals( eventRetention[1].getColor(), ChatColor.WHITE );
Assert.assertEquals( eventRetention[1].getHoverEvent(), testEvent );
Assert.assertEquals( eventRetention[1].getClickEvent(), testClickEvent );
}
@Test(expected = IllegalArgumentException.class)
public void testLoopSimple()
{
TextComponent component = new TextComponent( "Testing" );
component.addExtra( component );
ComponentSerializer.toString( component );
}
@Test(expected = IllegalArgumentException.class)
public void testLoopComplex()
{
TextComponent a = new TextComponent( "A" );
TextComponent b = new TextComponent( "B" );
b.setColor( ChatColor.AQUA );
TextComponent c = new TextComponent( "C" );
c.setColor( ChatColor.RED );
a.addExtra( b );
b.addExtra( c );
c.addExtra( a );
ComponentSerializer.toString( a );
}
@Test
public void testRepeated()
{
TextComponent a = new TextComponent( "A" );
TextComponent b = new TextComponent( "B" );
b.setColor( ChatColor.AQUA );
a.addExtra( b );
a.addExtra( b );
ComponentSerializer.toString( a );
}
@Test(expected = IllegalArgumentException.class)
public void testRepeatedError()
{
TextComponent a = new TextComponent( "A" );
TextComponent b = new TextComponent( "B" );
b.setColor( ChatColor.AQUA );
TextComponent c = new TextComponent( "C" );
c.setColor( ChatColor.RED );
a.addExtra( b );
a.addExtra( c );
c.addExtra( a );
a.addExtra( b );
ComponentSerializer.toString( a );
}
@Test
public void testInvalidColorCodes()
{
StringBuilder allInvalidColorCodes = new StringBuilder();
// collect all invalid color codes (e.g. §z, §g, ...)
for ( char alphChar : "0123456789abcdefghijklmnopqrstuvwxyz".toCharArray() )
{
if ( ChatColor.ALL_CODES.indexOf( alphChar ) == -1 )
{
allInvalidColorCodes.append( ChatColor.COLOR_CHAR );
allInvalidColorCodes.append( alphChar );
}
}
// last char is a single '§'
allInvalidColorCodes.append( ChatColor.COLOR_CHAR );
String invalidColorCodesLegacyText = fromAndToLegacyText( allInvalidColorCodes.toString() );
String emptyLegacyText = fromAndToLegacyText( "" );
// all invalid color codes and the trailing '§' should be ignored
Assert.assertEquals( emptyLegacyText, invalidColorCodesLegacyText );
}
@Test
public void testFormattingOnlyTextConversion()
{
String text = "§a";
BaseComponent[] converted = TextComponent.fromLegacyText( text );
Assert.assertEquals( ChatColor.GREEN, converted[0].getColor() );
String roundtripLegacyText = BaseComponent.toLegacyText( converted );
// color code should not be lost during conversion
Assert.assertEquals( text, roundtripLegacyText );
}
@Test
public void testEquals()
{
TextComponent first = new TextComponent( "Hello, " );
first.addExtra( new TextComponent( "World!" ) );
TextComponent second = new TextComponent( "Hello, " );
second.addExtra( new TextComponent( "World!" ) );
Assert.assertEquals( first, second );
}
@Test
public void testNotEquals()
{
TextComponent first = new TextComponent( "Hello, " );
first.addExtra( new TextComponent( "World." ) );
TextComponent second = new TextComponent( "Hello, " );
second.addExtra( new TextComponent( "World!" ) );
Assert.assertNotEquals( first, second );
}
@Test
public void testLegacyHack()
{
BaseComponent[] hexColored = new ComponentBuilder().color( ChatColor.of( Color.GRAY ) ).append( "Test" ).create();
String legacy = TextComponent.toLegacyText( hexColored );
BaseComponent[] reColored = TextComponent.fromLegacyText( legacy );
Assert.assertArrayEquals( hexColored, reColored );
}
private String fromAndToLegacyText(String legacyText)
{
return BaseComponent.toLegacyText( TextComponent.fromLegacyText( legacyText ) );
}
}

View File

@@ -0,0 +1,28 @@
package net.md_5.bungee.api.chat;
import net.md_5.bungee.chat.ComponentSerializer;
import org.junit.Assert;
import org.junit.Test;
public class TranslatableComponentTest
{
@Test
public void testMissingPlaceholdersAdded()
{
TranslatableComponent testComponent = new TranslatableComponent( "Test string with %s placeholders: %s", 2, "aoeu" );
Assert.assertEquals( "Test string with 2 placeholders: aoeu", testComponent.toPlainText() );
Assert.assertEquals( "§fTest string with §f2§f placeholders: §faoeu", testComponent.toLegacyText() );
}
@Test
public void testJsonSerialisation()
{
TranslatableComponent testComponent = new TranslatableComponent( "Test string with %s placeholder", "a" );
String jsonString = ComponentSerializer.toString( testComponent );
BaseComponent[] baseComponents = ComponentSerializer.parse( jsonString );
Assert.assertEquals( "Test string with a placeholder", TextComponent.toPlainText( baseComponents ) );
Assert.assertEquals( "§fTest string with §fa§f placeholder", TextComponent.toLegacyText( baseComponents ) );
}
}

View File

@@ -33,9 +33,9 @@
<!-- See http://checkstyle.sourceforge.net/config_filters.html --> <!-- See http://checkstyle.sourceforge.net/config_filters.html -->
<module name="SuppressionCommentFilter"/> <module name="SuppressionCommentFilter"/>
<module name="SuppressWarningsHolder"/>
<!-- See http://checkstyle.sourceforge.net/config_imports.html --> <!-- See http://checkstyle.sourceforge.net/config_imports.html -->
<module name="AvoidStarImport"/>
<module name="ImportOrder"> <module name="ImportOrder">
<property name="option" value="above"/> <property name="option" value="above"/>
<property name="ordered" value="true"/> <property name="ordered" value="true"/>
@@ -54,11 +54,11 @@
<module name="OperatorWrap"/> <module name="OperatorWrap"/>
<module name="ParenPad"> <module name="ParenPad">
<property name="option" value="nospace"/> <property name="option" value="nospace"/>
<property name="tokens" value="ANNOTATION, CTOR_DEF, METHOD_DEF, LAMBDA"/> <property name="tokens" value="ANNOTATION, CTOR_DEF, METHOD_DEF"/>
</module> </module>
<module name="ParenPad"> <module name="ParenPad">
<property name="option" value="space"/> <property name="option" value="space"/>
<property name="tokens" value="ANNOTATION_FIELD_DEF, CTOR_CALL, DOT, ENUM_CONSTANT_DEF, EXPR, LITERAL_CATCH, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW, LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_WHILE, METHOD_CALL, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL, RECORD_DEF"/> <property name="tokens" value="ANNOTATION_FIELD_DEF, CTOR_CALL, DOT, ENUM_CONSTANT_DEF, EXPR, LITERAL_CATCH, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW, LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_WHILE, METHOD_CALL, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL, LAMBDA"/>
</module> </module>
<module name="SingleSpaceSeparator"/> <module name="SingleSpaceSeparator"/>
<module name="TypecastParenPad"/> <module name="TypecastParenPad"/>
@@ -84,6 +84,4 @@
<module name="Indentation"/> <module name="Indentation"/>
<module name="UpperEll"/> <module name="UpperEll"/>
</module> </module>
<module name="SuppressWarningsFilter"/>
</module> </module>

View File

@@ -6,13 +6,13 @@
<parent> <parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId> <artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.4-SNAPSHOT</version> <version>1.16-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath> <relativePath>../pom.xml</relativePath>
</parent> </parent>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-config</artifactId> <artifactId>bungeecord-config</artifactId>
<version>1.21-R0.4-SNAPSHOT</version> <version>1.16-R0.4-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>BungeeCord-Config</name> <name>BungeeCord-Config</name>
@@ -22,14 +22,14 @@
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>
<version>2.11.0</version> <version>2.8.0</version>
<scope>compile</scope> <scope>compile</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.yaml</groupId> <groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId> <artifactId>snakeyaml</artifactId>
<version>2.2</version> <version>1.26</version>
<scope>compile</scope> <scope>compile</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>

View File

@@ -1,5 +1,6 @@
package net.md_5.bungee.config; package net.md_5.bungee.config;
import com.google.common.base.Charsets;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
@@ -15,7 +16,6 @@ import java.io.OutputStreamWriter;
import java.io.Reader; import java.io.Reader;
import java.io.Writer; import java.io.Writer;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import lombok.AccessLevel; import lombok.AccessLevel;
@@ -37,7 +37,7 @@ public class JsonConfiguration extends ConfigurationProvider
@Override @Override
public void save(Configuration config, File file) throws IOException public void save(Configuration config, File file) throws IOException
{ {
try ( Writer writer = new OutputStreamWriter( new FileOutputStream( file ), StandardCharsets.UTF_8 ) ) try ( Writer writer = new OutputStreamWriter( new FileOutputStream( file ), Charsets.UTF_8 ) )
{ {
save( config, writer ); save( config, writer );
} }
@@ -91,7 +91,7 @@ public class JsonConfiguration extends ConfigurationProvider
@Override @Override
public Configuration load(InputStream is, Configuration defaults) public Configuration load(InputStream is, Configuration defaults)
{ {
return load( new InputStreamReader( is, StandardCharsets.UTF_8 ), defaults ); return load( new InputStreamReader( is, Charsets.UTF_8 ), defaults );
} }
@Override @Override

View File

@@ -1,5 +1,6 @@
package net.md_5.bungee.config; package net.md_5.bungee.config;
import com.google.common.base.Charsets;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@@ -8,13 +9,11 @@ import java.io.InputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.Reader; import java.io.Reader;
import java.io.Writer; import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor; import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.Node;
@@ -30,10 +29,7 @@ public class YamlConfiguration extends ConfigurationProvider
@Override @Override
protected Yaml initialValue() protected Yaml initialValue()
{ {
DumperOptions options = new DumperOptions(); Representer representer = new Representer()
options.setDefaultFlowStyle( DumperOptions.FlowStyle.BLOCK );
Representer representer = new Representer( options )
{ {
{ {
representers.put( Configuration.class, new Represent() representers.put( Configuration.class, new Represent()
@@ -47,14 +43,17 @@ public class YamlConfiguration extends ConfigurationProvider
} }
}; };
return new Yaml( new Constructor( new LoaderOptions() ), representer, options ); DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle( DumperOptions.FlowStyle.BLOCK );
return new Yaml( new Constructor(), representer, options );
} }
}; };
@Override @Override
public void save(Configuration config, File file) throws IOException public void save(Configuration config, File file) throws IOException
{ {
try ( Writer writer = new OutputStreamWriter( new FileOutputStream( file ), StandardCharsets.UTF_8 ) ) try ( Writer writer = new OutputStreamWriter( new FileOutputStream( file ), Charsets.UTF_8 ) )
{ {
save( config, writer ); save( config, writer );
} }

View File

@@ -1,138 +1,148 @@
package net.md_5.bungee.config; package net.md_5.bungee.config;
import static org.junit.jupiter.api.Assertions.*;
import java.io.StringReader; import java.io.StringReader;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.Assert;
import org.junit.jupiter.params.provider.Arguments; import org.junit.Test;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RequiredArgsConstructor @RequiredArgsConstructor
@RunWith(Parameterized.class)
public class CompoundConfigurationTest public class CompoundConfigurationTest
{ {
public static Stream<Arguments> data() @Parameters(name = "{0}")
public static Iterable<Object[]> data()
{ {
return Stream.of( // CHECKSTYLE:OFF
Arguments.of( return Arrays.asList( new Object[][]
// provider {
YamlConfiguration.class, {
// testDocument // provider
"" YamlConfiguration.class,
+ "receipt: Oz-Ware Purchase Invoice\n" // testDocument
+ "date: 2012-08-06\n" ""
+ "customer:\n" + "receipt: Oz-Ware Purchase Invoice\n"
+ " given: Dorothy\n" + "date: 2012-08-06\n"
+ " family: Gale\n" + "customer:\n"
+ "\n" + " given: Dorothy\n"
+ "items:\n" + " family: Gale\n"
+ " - part_no: A4786\n" + "\n"
+ " descrip: Water Bucket (Filled)\n" + "items:\n"
+ " price: 1.47\n" + " - part_no: A4786\n"
+ " quantity: 4\n" + " descrip: Water Bucket (Filled)\n"
+ "\n" + " price: 1.47\n"
+ " - part_no: E1628\n" + " quantity: 4\n"
+ " descrip: High Heeled \"Ruby\" Slippers\n" + "\n"
+ " size: 8\n" + " - part_no: E1628\n"
+ " price: 100.27\n" + " descrip: High Heeled \"Ruby\" Slippers\n"
+ " quantity: 1\n" + " size: 8\n"
+ "\n" + " price: 100.27\n"
+ "bill-to: &id001\n" + " quantity: 1\n"
+ " street: |\n" + "\n"
+ " 123 Tornado Alley\n" + "bill-to: &id001\n"
+ " Suite 16\n" + " street: |\n"
+ " city: East Centerville\n" + " 123 Tornado Alley\n"
+ " state: KS\n" + " Suite 16\n"
+ "\n" + " city: East Centerville\n"
+ "ship-to: *id001\n" + " state: KS\n"
+ "\n" + "\n"
+ "specialDelivery: >\n" + "ship-to: *id001\n"
+ " Follow the Yellow Brick\n" + "\n"
+ " Road to the Emerald City.\n" + "specialDelivery: >\n"
+ " Pay no attention to the\n" + " Follow the Yellow Brick\n"
+ " man behind the curtain.", + " Road to the Emerald City.\n"
// numberTest + " Pay no attention to the\n"
"" + " man behind the curtain.",
+ "someKey:\n" // numberTest
+ " 1: 1\n" ""
+ " 2: 2\n" + "someKey:\n"
+ " 3: 3\n" + " 1: 1\n"
+ " 4: 4", + " 2: 2\n"
// nullTest + " 3: 3\n"
"" + " 4: 4",
+ "null:\n" // nullTest
+ " null: object\n" ""
+ " object: null\n" + "null:\n"
), + " null: object\n"
Arguments.of( + " object: null\n"
// provider },
JsonConfiguration.class, {
// testDocument // provider
"" JsonConfiguration.class,
+ "{\n" // testDocument
+ " \"customer\": {\n" ""
+ " \"given\": \"Dorothy\", \n" + "{\n"
+ " \"family\": \"Gale\"\n" + " \"customer\": {\n"
+ " }, \n" + " \"given\": \"Dorothy\", \n"
+ " \"ship-to\": {\n" + " \"family\": \"Gale\"\n"
+ " \"city\": \"East Centerville\", \n" + " }, \n"
+ " \"state\": \"KS\", \n" + " \"ship-to\": {\n"
+ " \"street\": \"123 Tornado Alley\\nSuite 16\\n\"\n" + " \"city\": \"East Centerville\", \n"
+ " }, \n" + " \"state\": \"KS\", \n"
+ " \"bill-to\": {\n" + " \"street\": \"123 Tornado Alley\\nSuite 16\\n\"\n"
+ " \"city\": \"East Centerville\", \n" + " }, \n"
+ " \"state\": \"KS\", \n" + " \"bill-to\": {\n"
+ " \"street\": \"123 Tornado Alley\\nSuite 16\\n\"\n" + " \"city\": \"East Centerville\", \n"
+ " }, \n" + " \"state\": \"KS\", \n"
+ " \"date\": \"2012-08-06\", \n" + " \"street\": \"123 Tornado Alley\\nSuite 16\\n\"\n"
+ " \"items\": [\n" + " }, \n"
+ " {\n" + " \"date\": \"2012-08-06\", \n"
+ " \"part_no\": \"A4786\", \n" + " \"items\": [\n"
+ " \"price\": 1.47, \n" + " {\n"
+ " \"descrip\": \"Water Bucket (Filled)\", \n" + " \"part_no\": \"A4786\", \n"
+ " \"quantity\": 4\n" + " \"price\": 1.47, \n"
+ " }, \n" + " \"descrip\": \"Water Bucket (Filled)\", \n"
+ " {\n" + " \"quantity\": 4\n"
+ " \"part_no\": \"E1628\", \n" + " }, \n"
+ " \"descrip\": \"High Heeled \\\"Ruby\\\" Slippers\", \n" + " {\n"
+ " \"price\": 100.27, \n" + " \"part_no\": \"E1628\", \n"
+ " \"quantity\": 1, \n" + " \"descrip\": \"High Heeled \\\"Ruby\\\" Slippers\", \n"
+ " \"size\": 8\n" + " \"price\": 100.27, \n"
+ " }\n" + " \"quantity\": 1, \n"
+ " ], \n" + " \"size\": 8\n"
+ " \"receipt\": \"Oz-Ware Purchase Invoice\", \n" + " }\n"
+ " \"specialDelivery\": \"Follow the Yellow Brick Road to the Emerald City. Pay no attention to the man behind the curtain.\"\n" + " ], \n"
+ "}", + " \"receipt\": \"Oz-Ware Purchase Invoice\", \n"
// numberTest + " \"specialDelivery\": \"Follow the Yellow Brick Road to the Emerald City. Pay no attention to the man behind the curtain.\"\n"
"" + "}",
+ "{\n" // numberTest
+ " \"someKey\": {\n" ""
+ " \"1\": 1, \n" + "{\n"
+ " \"2\": 2, \n" + " \"someKey\": {\n"
+ " \"3\": 3, \n" + " \"1\": 1, \n"
+ " \"4\": 4\n" + " \"2\": 2, \n"
+ " }\n" + " \"3\": 3, \n"
+ "}", + " \"4\": 4\n"
// nullTest + " }\n"
"" + "}",
+ "{\n" // nullTest
+ " \"null\": {\n" ""
+ " \"null\": \"object\", \n" + "{\n"
+ " \"object\": null\n" + " \"null\": {\n"
+ " }\n" + " \"null\": \"object\", \n"
+ "}" + " \"object\": null\n"
) + " }\n"
); + "}"
}
} );
// CHECKSTYLE:ON
} }
//
private final Class<? extends ConfigurationProvider> provider;
private final String testDocument;
private final String numberTest;
private final String nullTest;
@ParameterizedTest @Test
@MethodSource("data") public void testConfig() throws Exception
public void testConfig(Class<? extends ConfigurationProvider> provider, String testDocument, String numberTest, String nullTest) throws Exception
{ {
Configuration conf = ConfigurationProvider.getProvider( provider ).load( testDocument ); Configuration conf = ConfigurationProvider.getProvider( provider ).load( testDocument );
testSection( conf ); testSection( conf );
@@ -141,7 +151,7 @@ public class CompoundConfigurationTest
ConfigurationProvider.getProvider( provider ).save( conf, sw ); ConfigurationProvider.getProvider( provider ).save( conf, sw );
// Check nulls were saved, see #1094 // Check nulls were saved, see #1094
assertFalse( sw.toString().contains( "null" ), "Config contains null" ); Assert.assertFalse( "Config contains null", sw.toString().contains( "null" ) );
conf = ConfigurationProvider.getProvider( provider ).load( new StringReader( sw.toString() ) ); conf = ConfigurationProvider.getProvider( provider ).load( new StringReader( sw.toString() ) );
conf.set( "receipt", "Oz-Ware Purchase Invoice" ); // Add it back conf.set( "receipt", "Oz-Ware Purchase Invoice" ); // Add it back
@@ -150,38 +160,37 @@ public class CompoundConfigurationTest
private void testSection(Configuration conf) private void testSection(Configuration conf)
{ {
assertEquals( "Oz-Ware Purchase Invoice", conf.getString( "receipt" ), "receipt" ); Assert.assertEquals( "receipt", "Oz-Ware Purchase Invoice", conf.getString( "receipt" ) );
// assertEquals( "2012-08-06", conf.get( "date" ).toString(), "date" ); // Assert.assertEquals( "date", "2012-08-06", conf.get( "date" ).toString() );
Configuration customer = conf.getSection( "customer" ); Configuration customer = conf.getSection( "customer" );
assertEquals( "Dorothy", customer.getString( "given" ), "customer.given" ); Assert.assertEquals( "customer.given", "Dorothy", customer.getString( "given" ) );
assertEquals( "Dorothy", conf.getString( "customer.given" ), "customer.given" ); Assert.assertEquals( "customer.given", "Dorothy", conf.getString( "customer.given" ) );
List items = conf.getList( "items" ); List items = conf.getList( "items" );
Map item = (Map) items.get( 0 ); Map item = (Map) items.get( 0 );
assertEquals( "A4786", item.get( "part_no" ), "items[0].part_no" ); Assert.assertEquals( "items[0].part_no", "A4786", item.get( "part_no" ) );
conf.set( "receipt", null ); conf.set( "receipt", null );
assertEquals( null, conf.get( "receipt" ) ); Assert.assertEquals( null, conf.get( "receipt" ) );
assertEquals( "foo", conf.get( "receipt", "foo" ) ); Assert.assertEquals( "foo", conf.get( "receipt", "foo" ) );
Configuration newSection = conf.getSection( "new.section" ); Configuration newSection = conf.getSection( "new.section" );
newSection.set( "value", "foo" ); newSection.set( "value", "foo" );
assertEquals( "foo", conf.get( "new.section.value" ) ); Assert.assertEquals( "foo", conf.get( "new.section.value" ) );
conf.set( "other.new.section", "bar" ); conf.set( "other.new.section", "bar" );
assertEquals( "bar", conf.get( "other.new.section" ) ); Assert.assertEquals( "bar", conf.get( "other.new.section" ) );
assertTrue( conf.contains( "customer.given" ) ); Assert.assertTrue( conf.contains( "customer.given" ) );
assertTrue( customer.contains( "given" ) ); Assert.assertTrue( customer.contains( "given" ) );
assertFalse( conf.contains( "customer.foo" ) ); Assert.assertFalse( conf.contains( "customer.foo" ) );
assertFalse( customer.contains( "foo" ) ); Assert.assertFalse( customer.contains( "foo" ) );
} }
@ParameterizedTest @Test
@MethodSource("data") public void testNumberedKeys()
public void testNumberedKeys(Class<? extends ConfigurationProvider> provider, String testDocument, String numberTest, String nullTest)
{ {
Configuration conf = ConfigurationProvider.getProvider( provider ).load( numberTest ); Configuration conf = ConfigurationProvider.getProvider( provider ).load( numberTest );
@@ -192,31 +201,29 @@ public class CompoundConfigurationTest
} }
} }
@ParameterizedTest @Test
@MethodSource("data") public void testNull()
public void testNull(Class<? extends ConfigurationProvider> provider, String testDocument, String numberTest, String nullTest)
{ {
Configuration conf = ConfigurationProvider.getProvider( provider ).load( nullTest ); Configuration conf = ConfigurationProvider.getProvider( provider ).load( nullTest );
assertEquals( "object", conf.get( "null.null" ) ); Assert.assertEquals( "object", conf.get( "null.null" ) );
assertEquals( "object", conf.getSection( "null" ).get( "null" ) ); Assert.assertEquals( "object", conf.getSection( "null" ).get( "null" ) );
assertEquals( null, conf.get( "null.object" ) ); Assert.assertEquals( null, conf.get( "null.object" ) );
assertEquals( "", conf.getString( "null.object" ) ); Assert.assertEquals( "", conf.getString( "null.object" ) );
} }
@ParameterizedTest @Test
@MethodSource("data") public void testMapAddition()
public void testMapAddition(Class<? extends ConfigurationProvider> provider, String testDocument, String numberTest, String nullTest)
{ {
Configuration conf = ConfigurationProvider.getProvider( provider ).load( testDocument ); Configuration conf = ConfigurationProvider.getProvider( provider ).load( testDocument );
conf.set( "addition", Collections.singletonMap( "foo", "bar" ) ); conf.set( "addition", Collections.singletonMap( "foo", "bar" ) );
// Order matters // Order matters
assertEquals( "bar", conf.getSection( "addition" ).getString( "foo" ) ); Assert.assertEquals( "bar", conf.getSection( "addition" ).getString( "foo" ) );
assertEquals( "bar", conf.getString( "addition.foo" ) ); Assert.assertEquals( "bar", conf.getString( "addition.foo" ) );
assertTrue( conf.get( "addition" ) instanceof Configuration ); Assert.assertTrue( conf.get( "addition" ) instanceof Configuration );
} }
} }

View File

@@ -1,7 +1,7 @@
package net.md_5.bungee.config; package net.md_5.bungee.config;
import static org.junit.jupiter.api.Assertions.*; import org.junit.Assert;
import org.junit.jupiter.api.Test; import org.junit.Test;
public class DefaultConfigurationTest public class DefaultConfigurationTest
{ {
@@ -16,8 +16,8 @@ public class DefaultConfigurationTest
Configuration actualConfig = new Configuration( defaultConfig ); Configuration actualConfig = new Configuration( defaultConfig );
assertEquals( 10, actualConfig.getInt( "setting" ) ); Assert.assertEquals( 10, actualConfig.getInt( "setting" ) );
assertEquals( 11, actualConfig.getInt( "nested.setting" ) ); Assert.assertEquals( 11, actualConfig.getInt( "nested.setting" ) );
assertEquals( 12, actualConfig.getInt( "double.nested.setting" ) ); Assert.assertEquals( 12, actualConfig.getInt( "double.nested.setting" ) );
} }
} }

View File

@@ -1,28 +0,0 @@
BSD 3-Clause License
Copyright (c) 2025, SpigotMC Pty. Ltd.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,38 +0,0 @@
BungeeCord-Dialog
=================
Highly experimental API, subject to breakage. All contributions welcome, including major refactors/design changes.
Sample Plugin
-------------
```java
private class TestCommand extends Command
{
public TestCommand()
{
super( "btest" );
}
@Override
public void execute(CommandSender sender, String[] args)
{
ProxiedPlayer player = (ProxiedPlayer) sender;
Dialog notice = new NoticeDialog( new DialogBase( new ComponentBuilder( "Hello" ).color( ChatColor.RED ).build() ) );
player.showDialog( notice );
notice = new NoticeDialog(
new DialogBase( new ComponentBuilder( "Hello" ).color( ChatColor.RED ).build() )
.inputs(
Arrays.asList( new TextInput( "first", new ComponentBuilder( "First" ).build() ),
new TextInput( "second", new ComponentBuilder( "Second" ).build() )
)
) )
.action( new ActionButton( new ComponentBuilder( "Submit Button" ).build(), new CustomClickAction( "customform" ) ) );
player.sendMessage( new ComponentBuilder( "click me" ).event( new ShowDialogClickEvent( notice ) ).build() );
}
}
```

View File

@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-shared-configuration>
<!--
This file contains additional configuration written by modules in the NetBeans IDE.
The configuration is intended to be shared among all the users of project and
therefore it is assumed to be part of version control checkout.
Without this configuration present, some functionality in the IDE may be limited or fail altogether.
-->
<properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
<!--
Properties that influence various parts of the IDE, especially code formatting and the like.
You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
That way multiple projects can share the same settings (useful for formatting rules for example).
Any value defined here will override the pom.xml file value but is only applicable to the current project.
-->
<org-netbeans-modules-editor-indent.CodeStyle.usedProfile>project</org-netbeans-modules-editor-indent.CodeStyle.usedProfile>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinMethodCallParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinMethodCallParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSwitchParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSwitchParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinCatchParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinCatchParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinTryParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinTryParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSynchronizedParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSynchronizedParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinArrayInitBrackets>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinArrayInitBrackets>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinWhileParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinWhileParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinIfParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinIfParens>
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinForParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinForParens>
</properties>
</project-shared-configuration>

View File

@@ -1,36 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.4-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-dialog</artifactId>
<version>1.21-R0.4-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Dialog</name>
<description>Minecraft dialog API intended for use with BungeeCord</description>
<licenses>
<license>
<name>BSD-3-Clause</name>
<url>https://github.com/SpigotMC/BungeeCord/blob/master/dialog/LICENSE</url>
<distribution>repo</distribution>
</license>
</licenses>
<dependencies>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-chat</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,39 +0,0 @@
package net.md_5.bungee.api.dialog;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.dialog.action.ActionButton;
/**
* Represents a simple dialog with text and two actions at the bottom (default:
* "yes", "no").
*/
@Data
@ToString
@EqualsAndHashCode
@AllArgsConstructor
@Accessors(fluent = true)
public final class ConfirmationDialog implements Dialog
{
@NonNull
@Accessors(fluent = false)
private DialogBase base;
/**
* The "yes" click action / bottom (appears on the left).
*/
private ActionButton yes;
/**
* The "no" click action / bottom (appears on the right).
*/
private ActionButton no;
public ConfirmationDialog(@NonNull DialogBase base)
{
this( base, null, null );
}
}

View File

@@ -1,29 +0,0 @@
package net.md_5.bungee.api.dialog;
import org.jetbrains.annotations.ApiStatus;
/**
* Represents a dialog GUI.
*/
public interface Dialog
{
/**
* Gets the dialog base which contains the dialog title and other options
* common to all types of dialogs.
*
* @return mutable reference to the dialog base
*/
DialogBase getBase();
/**
* Sets the dialog base.
* <br>
* For internal use only as this is mandatory and should be specified in the
* constructor.
*
* @param base the new dialog base
*/
@ApiStatus.Internal
void setBase(DialogBase base);
}

View File

@@ -1,84 +0,0 @@
package net.md_5.bungee.api.dialog;
import com.google.gson.annotations.SerializedName;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.dialog.body.DialogBody;
import net.md_5.bungee.api.dialog.input.DialogInput;
/**
* Represents the title and other options common to all dialogs.
*/
@Data
@AllArgsConstructor
@Accessors(fluent = true)
public final class DialogBase
{
/**
* The mandatory dialog title.
*/
@NonNull
private BaseComponent title;
/**
* The name which is used for any buttons leading to this dialog (eg from a
* {@link DialogListDialog}). Otherwise defaults to {@link #title}.
*/
@SerializedName("external_title")
private BaseComponent externalTitle;
/**
* The inputs to the dialog.
*/
private List<DialogInput> inputs;
/**
* The body elements which make up this dialog.
*/
private List<DialogBody> body;
/**
* Whether this dialog can be closed with the escape key (default: true).
*/
@SerializedName("can_close_with_escape")
private Boolean canCloseWithEscape;
/**
* Whether this dialog should pause the game in single-player mode (default:
* true).
*/
private Boolean pause;
/**
* Action to take after the a click or submit action is performed on the
* dialog (default: close).
*/
@SerializedName("after_action")
private AfterAction afterAction;
public DialogBase(@NonNull BaseComponent title)
{
this( title, null, null, null, null, null, null );
}
/**
* Types of action which may be taken after the dialog.
*/
public enum AfterAction
{
/**
* Close the dialog.
*/
@SerializedName("close")
CLOSE,
/**
* Do nothing.
*/
@SerializedName("none")
NONE,
/**
* Show a waiting for response screen.
*/
@SerializedName("wait_for_response")
WAIT_FOR_RESPONSE;
}
}

View File

@@ -1,73 +0,0 @@
package net.md_5.bungee.api.dialog;
import com.google.common.base.Preconditions;
import com.google.gson.annotations.SerializedName;
import java.util.Arrays;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.dialog.action.ActionButton;
/**
* Represents a dialog which contains buttons that link to other dialogs.
*/
@Data
@ToString
@EqualsAndHashCode
@Accessors(fluent = true)
public final class DialogListDialog implements Dialog
{
@NonNull
@Accessors(fluent = false)
private DialogBase base;
/**
* The child dialogs behind each button.
*/
private List<Dialog> dialogs;
/**
* The {@link ActionButton} activated when the dialog is exited.
*/
@SerializedName("exit_action")
private ActionButton exitAction;
/**
* The number of columns for the dialog buttons (default: 2).
*/
private Integer columns;
/**
* The width of the dialog buttons (default: 150, minimum: 1, maximum: 1024).
*/
@SerializedName("button_width")
private Integer buttonWidth;
public DialogListDialog(@NonNull DialogBase base, Dialog... dialogs)
{
this( base, Arrays.asList( dialogs ), null, null, null );
}
public DialogListDialog(@NonNull DialogBase base, List<Dialog> dialogs, ActionButton exitAction, Integer columns, Integer buttonWidth)
{
this.base = base;
this.dialogs = dialogs;
this.exitAction = exitAction;
columns( columns );
buttonWidth( buttonWidth );
}
public DialogListDialog columns(Integer columns)
{
Preconditions.checkArgument( columns == null || columns > 0, "At least one column is required" );
this.columns = columns;
return this;
}
public DialogListDialog buttonWidth(Integer buttonWidth)
{
Preconditions.checkArgument( buttonWidth == null || ( buttonWidth >= 1 && buttonWidth <= 1024 ), "buttonWidth must be between 1 and 1024" );
this.buttonWidth = buttonWidth;
return this;
}
}

View File

@@ -1,64 +0,0 @@
package net.md_5.bungee.api.dialog;
import com.google.common.base.Preconditions;
import com.google.gson.annotations.SerializedName;
import java.util.Arrays;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.dialog.action.ActionButton;
/**
* Represents a dialog with text a list of action buttons grouped into columns
* and scrollable if necessary.
*/
@Data
@ToString
@EqualsAndHashCode
@Accessors(fluent = true)
public final class MultiActionDialog implements Dialog
{
@NonNull
@Accessors(fluent = false)
private DialogBase base;
/**
* The action buttons in the dialog. At least one must be provided.
*/
@NonNull
private List<ActionButton> actions;
/**
* The number of columns for the dialog buttons (default: 2).
*/
private Integer columns;
/**
* The {@link ActionButton} activated when the dialog is exited.
*/
@SerializedName("exit_action")
private ActionButton exitAction;
public MultiActionDialog(@NonNull DialogBase base, @NonNull ActionButton... actions)
{
this( base, Arrays.asList( actions ), null, null );
}
public MultiActionDialog(@NonNull DialogBase base, @NonNull List<ActionButton> actions, Integer columns, ActionButton exitAction)
{
Preconditions.checkArgument( !actions.isEmpty(), "At least one action must be provided" );
this.base = base;
this.actions = actions;
columns( columns );
this.exitAction = exitAction;
}
public MultiActionDialog columns(Integer columns)
{
Preconditions.checkArgument( columns == null || columns > 0, "At least one column is required" );
this.columns = columns;
return this;
}
}

View File

@@ -1,35 +0,0 @@
package net.md_5.bungee.api.dialog;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.dialog.action.ActionButton;
/**
* Represents a simple dialog with text and one action at the bottom (default:
* "OK").
*/
@Data
@ToString
@EqualsAndHashCode
@AllArgsConstructor
@Accessors(fluent = true)
public final class NoticeDialog implements Dialog
{
@NonNull
@Accessors(fluent = false)
private DialogBase base;
/**
* The "OK" action button for the dialog.
*/
private ActionButton action;
public NoticeDialog(DialogBase base)
{
this( base, null );
}
}

View File

@@ -1,71 +0,0 @@
package net.md_5.bungee.api.dialog;
import com.google.common.base.Preconditions;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.dialog.action.ActionButton;
/**
* Represents a dialog which shows the links configured/sent from the server.
*/
@Data
@ToString
@EqualsAndHashCode
@Accessors(fluent = true)
public final class ServerLinksDialog implements Dialog
{
@NonNull
@Accessors(fluent = false)
private DialogBase base;
/**
* The optional {@link ActionButton} for this dialog.
*/
@SerializedName("action")
private ActionButton action;
/**
* The {@link ActionButton} activated when the dialog is exited.
*/
@SerializedName("exit_action")
private ActionButton exitAction;
/**
* The number of columns for the dialog buttons (default: 2).
*/
private Integer columns;
/**
* The width of the dialog buttons (default: 150, minimum: 1, maximum: 1024).
*/
@SerializedName("button_width")
private Integer buttonWidth;
public ServerLinksDialog(@NonNull DialogBase base)
{
this( base, null, null, null );
}
public ServerLinksDialog(@NonNull DialogBase base, ActionButton action, Integer columns, Integer buttonWidth)
{
this.base = base;
this.action = action;
columns( columns );
buttonWidth( buttonWidth );
}
public ServerLinksDialog columns(Integer columns)
{
Preconditions.checkArgument( columns == null || columns > 0, "At least one column is required" );
this.columns = columns;
return this;
}
public ServerLinksDialog buttonWidth(Integer buttonWidth)
{
Preconditions.checkArgument( buttonWidth == null || ( buttonWidth >= 1 && buttonWidth <= 1024 ), "buttonWidth must be between 1 and 1024" );
this.buttonWidth = buttonWidth;
return this;
}
}

View File

@@ -1,6 +0,0 @@
package net.md_5.bungee.api.dialog.action;
public interface Action
{
}

View File

@@ -1,54 +0,0 @@
package net.md_5.bungee.api.dialog.action;
import com.google.common.base.Preconditions;
import lombok.Data;
import lombok.NonNull;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.chat.BaseComponent;
/**
* Represents a dialog action which will usually appear as a button.
*/
@Data
@Accessors(fluent = true)
public class ActionButton
{
/**
* The text label of the button, mandatory.
*/
@NonNull
private BaseComponent label;
/**
* The hover tooltip of the button.
*/
private BaseComponent tooltip;
/**
* The width of the button (default: 150, minimum: 1, maximum: 1024).
*/
private Integer width;
/**
* The action to take.
*/
@NonNull
private Action action;
public ActionButton(@NonNull BaseComponent label, BaseComponent tooltip, Integer width, @NonNull Action action)
{
this.label = label;
this.tooltip = tooltip;
setWidth( width );
this.action = action;
}
public ActionButton(@NonNull BaseComponent label, @NonNull Action action)
{
this( label, null, null, action );
}
public void setWidth(Integer width)
{
Preconditions.checkArgument( width == null || ( width >= 1 && width <= 1024 ), "width must be between 1 and 1024" );
this.width = width;
}
}

View File

@@ -1,27 +0,0 @@
package net.md_5.bungee.api.dialog.action;
import com.google.gson.JsonElement;
import lombok.Data;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
/**
* Submits the dialog with the given ID and values as a payload.
*/
@Data
@Accessors(fluent = true)
@ToString(callSuper = true)
public class CustomClickAction implements Action
{
/**
* The namespaced key of the submission.
*/
@NonNull
private String id;
/**
* Fields to be added to the submission payload.
*/
private JsonElement additions;
}

View File

@@ -1,25 +0,0 @@
package net.md_5.bungee.api.dialog.action;
import lombok.Data;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
/**
* Executes a command. If the command requires a permission
* higher than 0, a confirmation dialog will be shown by the client.
*/
@Data
@Accessors(fluent = true)
@ToString(callSuper = true)
public class RunCommandAction implements Action
{
/**
* The template to be applied, where variables of the form
* <code>$(key)</code> will be replaced by their
* {@link net.md_5.bungee.api.dialog.input.DialogInput#key} value.
*/
@NonNull
private String template;
}

View File

@@ -1,20 +0,0 @@
package net.md_5.bungee.api.dialog.action;
import lombok.Data;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.chat.ClickEvent;
/**
* Represents a static dialog action.
*/
@Data
@Accessors(fluent = true)
@ToString(callSuper = true)
public class StaticAction implements Action
{
@NonNull
private ClickEvent clickEvent;
}

View File

@@ -1,4 +0,0 @@
/**
* Contains the different actions/buttons for a {@link net.md_5.bungee.api.dialog.Dialog}.
*/
package net.md_5.bungee.api.dialog.action;

View File

@@ -1,20 +0,0 @@
package net.md_5.bungee.api.dialog.body;
import lombok.Data;
import lombok.NonNull;
import org.jetbrains.annotations.ApiStatus;
/**
* Represents the body content of a {@link net.md_5.bungee.api.dialog.Dialog}.
*/
@Data
public abstract class DialogBody
{
/**
* The internal body type.
*/
@NonNull
@ApiStatus.Internal
private final String type;
}

View File

@@ -1,49 +0,0 @@
package net.md_5.bungee.api.dialog.body;
import com.google.common.base.Preconditions;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.chat.BaseComponent;
/**
* Represents a dialog body which consists of text constrained to a certain
* width.
*/
@Data
@Accessors(fluent = true)
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class PlainMessageBody extends DialogBody
{
/**
* The text body.
*/
@NonNull
private BaseComponent contents;
/**
* The maximum width (default: 200, minimum: 1, maximum: 1024).
*/
private Integer width;
public PlainMessageBody(@NonNull BaseComponent contents)
{
this( contents, null );
}
public PlainMessageBody(@NonNull BaseComponent contents, Integer width)
{
super( "minecraft:plain_message" );
this.contents = contents;
width( width );
}
public void width(Integer width)
{
Preconditions.checkArgument( width == null || ( width >= 1 && width <= 1024 ), "width must be between 1 and 1024" );
this.width = width;
}
}

View File

@@ -1,5 +0,0 @@
/**
* Contains the different {@link net.md_5.bungee.api.dialog.Dialog} body content
* types.
*/
package net.md_5.bungee.api.dialog.body;

View File

@@ -1,42 +0,0 @@
package net.md_5.bungee.api.dialog.chat;
import lombok.Data;
import lombok.EqualsAndHashCode;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.dialog.Dialog;
/**
* Click event which displays either a pre-existing dialog by key or a custom
* dialog.
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class ShowDialogClickEvent extends ClickEvent
{
/**
* Key for a pre-existing dialog to show.
*/
private String reference;
/**
* Dialog to show.
*/
private Dialog dialog;
public ShowDialogClickEvent(String reference)
{
this( reference, null );
}
public ShowDialogClickEvent(Dialog dialog)
{
this( null, dialog );
}
private ShowDialogClickEvent(String reference, Dialog dialog)
{
super( Action.SHOW_DIALOG, null );
this.reference = reference;
this.dialog = dialog;
}
}

View File

@@ -1,4 +0,0 @@
/**
* Contains dialog extensions to the chat API.
*/
package net.md_5.bungee.api.dialog.chat;

View File

@@ -1,54 +0,0 @@
package net.md_5.bungee.api.dialog.input;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.chat.BaseComponent;
/**
* Represents a checkbox input control.
*/
@Data
@Accessors(fluent = true)
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class BooleanInput extends DialogInput
{
/**
* The input label.
*/
@NonNull
private BaseComponent label;
/**
* The initial value (default: false/unchecked).
*/
private Boolean initial;
/**
* The string value to be submitted when true/checked (default: "true").
*/
@SerializedName("on_true")
private String onTrue;
/**
* The string value to be submitted when false/unchecked (default: "false").
*/
@SerializedName("on_false")
private String onFalse;
public BooleanInput(@NonNull String key, @NonNull BaseComponent label)
{
this( key, label, null, "true", "false" );
}
public BooleanInput(@NonNull String key, @NonNull BaseComponent label, Boolean initial, String onTrue, String onFalse)
{
super( "minecraft:boolean", key );
this.label = label;
this.initial = initial;
this.onTrue = onTrue;
this.onFalse = onFalse;
}
}

View File

@@ -1,29 +0,0 @@
package net.md_5.bungee.api.dialog.input;
import lombok.Data;
import lombok.NonNull;
import lombok.experimental.Accessors;
import org.jetbrains.annotations.ApiStatus;
/**
* Represents a type of input which may be displayed/submitted with a form
* dialog.
*/
@Data
@Accessors(fluent = true)
public class DialogInput
{
/**
* The internal input type.
*/
@NonNull
@ApiStatus.Internal
private final String type;
/**
* The key corresponding to this input and associated with the value
* submitted.
*/
@NonNull
private final String key;
}

View File

@@ -1,39 +0,0 @@
package net.md_5.bungee.api.dialog.input;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.chat.BaseComponent;
/**
* Represents an option choice which may form part of a
* {@link SingleOptionInput}.
*/
@Data
@AllArgsConstructor
@Accessors(fluent = true)
public class InputOption
{
/**
* The string value associated with this option, to be submitted when
* selected.
*/
@NonNull
private String id;
/**
* The text to display for this option.
*/
private BaseComponent display;
/**
* Whether this option is the one initially selected. Only one option may
* have this value as true (default: first option).
*/
private Boolean initial;
public InputOption(@NonNull String id)
{
this( id, null, null );
}
}

View File

@@ -1,103 +0,0 @@
package net.md_5.bungee.api.dialog.input;
import com.google.common.base.Preconditions;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.chat.BaseComponent;
/**
* Represents a number slider input.
*/
@Data
@Accessors(fluent = true)
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class NumberRangeInput extends DialogInput
{
/**
* The width of the input (default: 200, minimum: 1, maximum: 1024).
*/
private Integer width;
/**
* The label of the slider.
*/
@NonNull
private BaseComponent label;
/**
* A translate key used to display the label value (default:
* options.generic_value).
*/
private String labelFormat;
/**
* The start position of the slider (leftmost position).
*/
private float start;
/**
* The end position of the slider (rightmost position).
*/
private float end;
/**
* The steps in which the input will be increased or decreased, or null if
* no specific steps.
*/
private Float step;
/**
* The initial value of number input, or null to fall back to the middle.
*/
private Float initial;
public NumberRangeInput(@NonNull String key, @NonNull BaseComponent label, float start, float end)
{
this( key, null, label, "options.generic_value", start, end, null, null );
}
public NumberRangeInput(@NonNull String key, @NonNull BaseComponent label, float start, float end, Float step)
{
this( key, null, label, "options.generic_value", start, end, step, null );
}
public NumberRangeInput(@NonNull String key, @NonNull BaseComponent label, float start, float end, Float step, Float initial)
{
this( key, null, label, "options.generic_value", start, end, step, initial );
}
public NumberRangeInput(@NonNull String key, Integer width, @NonNull BaseComponent label, String labelFormat, float start, float end, Float step, Float initial)
{
super( "minecraft:number_range", key );
width( width );
this.label = label;
this.labelFormat = labelFormat;
this.start = start;
this.end = end;
step( step );
initial( initial );
}
public NumberRangeInput width(Integer width)
{
Preconditions.checkArgument( width == null || ( width >= 1 && width <= 1024 ), "with must be between 1 and 1024" );
this.width = width;
return this;
}
public NumberRangeInput step(Float step)
{
Preconditions.checkArgument( step == null || step > 0, "step must be null or greater than zero" );
this.step = step;
return this;
}
public NumberRangeInput initial(Float initial)
{
// we need to calculate if the initial value is between start and end, regardless of the order
float min = Math.min( start, end );
float max = Math.max( start, end );
Preconditions.checkArgument( initial == null || ( initial >= min && initial <= max ), "initial must be null or between start and end" );
this.initial = initial;
return this;
}
}

View File

@@ -1,66 +0,0 @@
package net.md_5.bungee.api.dialog.input;
import com.google.common.base.Preconditions;
import com.google.gson.annotations.SerializedName;
import java.util.Arrays;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.ToString;
import lombok.experimental.Accessors;
import net.md_5.bungee.api.chat.BaseComponent;
/**
* Represents a single option (dropdown) input.
*/
@Data
@Accessors(fluent = true)
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class SingleOptionInput extends DialogInput
{
/**
* The width of the input (default: 200, minimum: 1, maximum: 1024).
*/
private Integer width;
/**
* The input label.
*/
@NonNull
private BaseComponent label;
/**
* Whether the label is visible (default: true).
*/
@SerializedName("label_visible")
private Boolean labelVisible;
/**
* The non-empty list of options to be selected from.
*/
@NonNull
private List<InputOption> options;
public SingleOptionInput(@NonNull String key, @NonNull BaseComponent label, @NonNull InputOption... options)
{
this( key, null, label, null, Arrays.asList( options ) );
}
public SingleOptionInput(@NonNull String key, Integer width, @NonNull BaseComponent label, Boolean labelVisible, @NonNull List<InputOption> options)
{
super( "minecraft:single_option", key );
Preconditions.checkArgument( !options.isEmpty(), "At least one option must be provided" );
width( width );
this.label = label;
this.labelVisible = labelVisible;
this.options = options;
}
public SingleOptionInput width(Integer width)
{
Preconditions.checkArgument( width == null || ( width >= 1 && width <= 1024 ), "width must be between 1 and 1024" );
this.width = width;
return this;
}
}

Some files were not shown because too many files have changed in this diff Show More