From 1acaddf677322a05af0af8fcd72e643756af589b Mon Sep 17 00:00:00 2001 From: mrapple Date: Sat, 6 Sep 2014 19:38:31 -0500 Subject: [PATCH 01/24] Derploy --- pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index 38975e8701f..8e5872034b0 100644 --- a/pom.xml +++ b/pom.xml @@ -29,6 +29,16 @@ repo + + + overcast-deployment + https://repo.oc.tc/content/repositories/releases + + + overcast-deployment + https://repo.oc.tc/content/repositories/snapshots + + From 05b98db5c3bb0227c941fdbf18f1e4a4df51859d Mon Sep 17 00:00:00 2001 From: mrapple Date: Thu, 11 Sep 2014 23:33:52 -0500 Subject: [PATCH 02/24] Disable security manager --- proxy/src/main/java/net/md_5/bungee/BungeeCord.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java index 8fc2f88263e..d3b9e52b24a 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -171,7 +171,8 @@ public BungeeCord() throws IOException // Java uses ! to indicate a resource inside of a jar/zip/other container. Running Bungee from within a directory that has a ! will cause this to muck up. Preconditions.checkState( new File( "." ).getAbsolutePath().indexOf( '!' ) == -1, "Cannot use BungeeCord in directory with ! in path." ); - System.setSecurityManager( new BungeeSecurityManager() ); + // Overcast - disable security manager + // System.setSecurityManager( new BungeeSecurityManager() ); try { From 15cf3e206bc922a30843aa56306d769d5f3e35d5 Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Wed, 19 Nov 2014 04:07:51 -0500 Subject: [PATCH 03/24] Log stderr at WARNING instead of SEVERE --- proxy/src/main/java/net/md_5/bungee/BungeeCord.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java index d3b9e52b24a..470957fc696 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -197,7 +197,9 @@ public BungeeCord() throws IOException consoleReader.setExpandEvents( false ); logger = new BungeeLogger( this ); - System.setErr( new PrintStream( new LoggingOutputStream( logger, Level.SEVERE ), true ) ); + + // Overcast - stderr gets a lot of non-error output, so log it at WARNING level instead of SEVERE + System.setErr( new PrintStream( new LoggingOutputStream( logger, Level.WARNING ), true ) ); System.setOut( new PrintStream( new LoggingOutputStream( logger, Level.INFO ), true ) ); if ( !Boolean.getBoolean( "net.md_5.bungee.native.disable" ) ) From 84b87fef63cf94b2bed8fcb5a6756cf23d4441a4 Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Thu, 27 Nov 2014 17:40:43 -0500 Subject: [PATCH 04/24] Postpone PluginManager creation so that EventBus gets the right logger --- .../main/java/net/md_5/bungee/BungeeCord.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java index 470957fc696..07a707114e7 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -122,7 +122,7 @@ public class BungeeCord extends ProxyServer * Plugin manager. */ @Getter - public final PluginManager pluginManager = new PluginManager( this ); + public final PluginManager pluginManager; @Getter @Setter private ReconnectHandler reconnectHandler; @@ -148,18 +148,6 @@ public class BungeeCord extends ProxyServer private ConnectionThrottle connectionThrottle; private final ModuleManager moduleManager = new ModuleManager(); - - { - // TODO: Proper fallback when we interface the manager - getPluginManager().registerCommand( null, new CommandReload() ); - getPluginManager().registerCommand( null, new CommandEnd() ); - getPluginManager().registerCommand( null, new CommandIP() ); - getPluginManager().registerCommand( null, new CommandBungee() ); - getPluginManager().registerCommand( null, new CommandPerms() ); - - registerChannel( "BungeeCord" ); - } - public static BungeeCord getInstance() { return (BungeeCord) ProxyServer.getInstance(); @@ -219,6 +207,17 @@ public BungeeCord() throws IOException logger.info( "Using standard Java compressor. To enable zero copy compression, run on 64 bit Linux" ); } } + + pluginManager = new PluginManager( this ); + + // TODO: Proper fallback when we interface the manager + getPluginManager().registerCommand( null, new CommandReload() ); + getPluginManager().registerCommand( null, new CommandEnd() ); + getPluginManager().registerCommand( null, new CommandIP() ); + getPluginManager().registerCommand( null, new CommandBungee() ); + getPluginManager().registerCommand( null, new CommandPerms() ); + + registerChannel( "BungeeCord" ); } /** From c5f3e5b82ff3c6301799c6b07f0440a209d9247a Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Thu, 27 Nov 2014 17:41:15 -0500 Subject: [PATCH 05/24] Log event exceptions at SEVERE level --- event/src/main/java/net/md_5/bungee/event/EventBus.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/event/src/main/java/net/md_5/bungee/event/EventBus.java b/event/src/main/java/net/md_5/bungee/event/EventBus.java index cd4057f9609..7f1a3b1daa2 100644 --- a/event/src/main/java/net/md_5/bungee/event/EventBus.java +++ b/event/src/main/java/net/md_5/bungee/event/EventBus.java @@ -53,7 +53,7 @@ public void post(Object event) throw new Error( "Method rejected target/argument: " + event, ex ); } catch ( InvocationTargetException ex ) { - logger.log( Level.WARNING, MessageFormat.format( "Error dispatching event {0} to listener {1}", event, method.getListener() ), ex.getCause() ); + logger.log( Level.SEVERE, MessageFormat.format( "Error dispatching event {0} to listener {1}", event, method.getListener() ), ex.getCause() ); } } } From f1c572e1840cdb65970828836a491786d7f139d7 Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Thu, 27 Nov 2014 21:18:59 -0500 Subject: [PATCH 06/24] Improve logging of packet decoding errors --- .../java/net/md_5/bungee/protocol/BadPacketException.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/BadPacketException.java b/protocol/src/main/java/net/md_5/bungee/protocol/BadPacketException.java index 6c0ef4dfa00..d3ee377d8b4 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/BadPacketException.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/BadPacketException.java @@ -1,6 +1,8 @@ package net.md_5.bungee.protocol; -public class BadPacketException extends RuntimeException +import io.netty.handler.codec.DecoderException; + +public class BadPacketException extends DecoderException { public BadPacketException(String message) From a227309ff1910151a2b8c24406ee7623b5c0997d Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Sun, 30 Nov 2014 14:44:38 -0500 Subject: [PATCH 07/24] Guard against negative packet IDs --- protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java b/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java index 128d3934919..d55e3899ef9 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java @@ -122,7 +122,7 @@ public class DirectionData public boolean hasPacket(int id) { - return id < MAX_PACKET_ID && packetConstructors[id] != null; + return 0 <= id && id < MAX_PACKET_ID && packetConstructors[id] != null; } public final DefinedPacket createPacket(int id) From e5cba2a04cec143b63155a0922be524b9b3229c9 Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Sun, 30 Nov 2014 15:02:58 -0500 Subject: [PATCH 08/24] A few more packet ID range checks --- .../java/net/md_5/bungee/protocol/Protocol.java | 4 ++-- .../java/net/md_5/bungee/entitymap/EntityMap.java | 14 +++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java b/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java index d55e3899ef9..1d6568911e3 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java @@ -100,7 +100,7 @@ public enum Protocol } }; /*========================================================================*/ - public static final int MAX_PACKET_ID = 0xFF; + public static final int MAX_PACKET_ID = 0x100; public static List supportedVersions = Arrays.asList( ProtocolConstants.MINECRAFT_1_7_2, ProtocolConstants.MINECRAFT_1_7_6, @@ -127,7 +127,7 @@ public boolean hasPacket(int id) public final DefinedPacket createPacket(int id) { - if ( id > MAX_PACKET_ID ) + if ( id < 0 || id >= MAX_PACKET_ID ) { throw new BadPacketException( "Packet with id " + id + " outside of range " ); } diff --git a/proxy/src/main/java/net/md_5/bungee/entitymap/EntityMap.java b/proxy/src/main/java/net/md_5/bungee/entitymap/EntityMap.java index 56067db8cc8..47f96b1cd71 100644 --- a/proxy/src/main/java/net/md_5/bungee/entitymap/EntityMap.java +++ b/proxy/src/main/java/net/md_5/bungee/entitymap/EntityMap.java @@ -3,6 +3,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.netty.buffer.ByteBuf; import net.md_5.bungee.protocol.DefinedPacket; +import net.md_5.bungee.protocol.Protocol; import net.md_5.bungee.protocol.ProtocolConstants; /** @@ -105,12 +106,15 @@ private static void rewrite(ByteBuf packet, int oldId, int newId, boolean[] ints int packetId = DefinedPacket.readVarInt( packet ); int packetIdLength = packet.readerIndex() - readerIndex; - if ( ints[ packetId ] ) + if ( 0 <= packetId && packetId < Protocol.MAX_PACKET_ID) { - rewriteInt( packet, oldId, newId, readerIndex + packetIdLength ); - } else if ( varints[ packetId ] ) - { - rewriteVarInt( packet, oldId, newId, readerIndex + packetIdLength ); + if ( ints[ packetId ] ) + { + rewriteInt( packet, oldId, newId, readerIndex + packetIdLength ); + } else if ( varints[ packetId ] ) + { + rewriteVarInt(packet, oldId, newId, readerIndex + packetIdLength); + } } packet.readerIndex( readerIndex ); } From 9404b2c87d865b9085e9157b9870fd5542cc50a8 Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Sat, 13 Dec 2014 15:16:04 -0500 Subject: [PATCH 09/24] Ignore a common exception that appears to be harmless --- .../main/java/net/md_5/bungee/netty/ChannelWrapper.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java b/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java index 06d19c3c1b1..bb08aff2cff 100644 --- a/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java +++ b/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java @@ -12,6 +12,8 @@ import net.md_5.bungee.protocol.MinecraftEncoder; import net.md_5.bungee.protocol.Protocol; +import java.util.NoSuchElementException; + public class ChannelWrapper { @@ -78,7 +80,11 @@ public void setCompressionThreshold(int compressionThreshold) { if ( ch.pipeline().get( PacketCompressor.class ) == null && compressionThreshold != -1 ) { - addBefore( PipelineUtils.PACKET_ENCODER, "compress", new PacketCompressor() ); + try { + addBefore(PipelineUtils.PACKET_ENCODER, "compress", new PacketCompressor()); + } catch(NoSuchElementException ignored) { + // Sometimes packet-encoder is not in the pipeline, probably when the client disconnects soon after connecting + } } if ( compressionThreshold != -1 ) { From 8379dec5617ae6dd9bf3d4ecebc59b7808b56dca Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Sun, 14 Dec 2014 02:58:43 -0500 Subject: [PATCH 10/24] Fix NPE caused by the other fix --- proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java b/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java index bb08aff2cff..955e7c9fcab 100644 --- a/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java +++ b/proxy/src/main/java/net/md_5/bungee/netty/ChannelWrapper.java @@ -82,8 +82,9 @@ public void setCompressionThreshold(int compressionThreshold) { try { addBefore(PipelineUtils.PACKET_ENCODER, "compress", new PacketCompressor()); - } catch(NoSuchElementException ignored) { + } catch(NoSuchElementException ex) { // Sometimes packet-encoder is not in the pipeline, probably when the client disconnects soon after connecting + return; } } if ( compressionThreshold != -1 ) From 1d25cf2e2f47cf33c4e90e74ba74b69005c464c3 Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Mon, 22 Dec 2014 12:37:59 -0500 Subject: [PATCH 11/24] Remove startup delay for outdated build --- bootstrap/src/main/java/net/md_5/bungee/BungeeCordLauncher.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/bootstrap/src/main/java/net/md_5/bungee/BungeeCordLauncher.java b/bootstrap/src/main/java/net/md_5/bungee/BungeeCordLauncher.java index 68f104b8072..625a18b1d3f 100644 --- a/bootstrap/src/main/java/net/md_5/bungee/BungeeCordLauncher.java +++ b/bootstrap/src/main/java/net/md_5/bungee/BungeeCordLauncher.java @@ -43,8 +43,6 @@ public static void main(String[] args) throws Exception System.err.println( "*** Warning, this build is outdated ***" ); System.err.println( "*** Please download a new build from http://ci.md-5.net/job/BungeeCord ***" ); System.err.println( "*** You will get NO support regarding this build ***" ); - System.err.println( "*** Server will start in 10 seconds ***" ); - Thread.sleep( TimeUnit.SECONDS.toMillis( 10 ) ); } } From d9edd25c058e0ab5ed693b60da58f6d96d23391e Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Tue, 6 Jan 2015 11:24:09 -0500 Subject: [PATCH 12/24] Add the capability to simulate username changes --- .../java/net/md_5/bungee/api/event/ServerConnectEvent.java | 1 + proxy/src/main/java/net/md_5/bungee/ServerConnector.java | 4 +++- proxy/src/main/java/net/md_5/bungee/UserConnection.java | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/net/md_5/bungee/api/event/ServerConnectEvent.java b/api/src/main/java/net/md_5/bungee/api/event/ServerConnectEvent.java index 17cfccd0a0e..51824022bd2 100644 --- a/api/src/main/java/net/md_5/bungee/api/event/ServerConnectEvent.java +++ b/api/src/main/java/net/md_5/bungee/api/event/ServerConnectEvent.java @@ -34,6 +34,7 @@ public class ServerConnectEvent extends Event implements Cancellable * Cancelled state. */ private boolean cancelled; + private String fakeUsername; public ServerConnectEvent(ProxiedPlayer player, ServerInfo target) { diff --git a/proxy/src/main/java/net/md_5/bungee/ServerConnector.java b/proxy/src/main/java/net/md_5/bungee/ServerConnector.java index 5282033a8d3..3740bf2e82a 100644 --- a/proxy/src/main/java/net/md_5/bungee/ServerConnector.java +++ b/proxy/src/main/java/net/md_5/bungee/ServerConnector.java @@ -35,6 +35,7 @@ import net.md_5.bungee.protocol.packet.Handshake; import net.md_5.bungee.protocol.packet.Kick; import net.md_5.bungee.protocol.packet.Login; +import net.md_5.bungee.protocol.packet.LoginRequest; import net.md_5.bungee.protocol.packet.LoginSuccess; import net.md_5.bungee.protocol.packet.PluginMessage; import net.md_5.bungee.protocol.packet.Respawn; @@ -50,6 +51,7 @@ public class ServerConnector extends PacketHandler private final UserConnection user; private final BungeeServerInfo target; private State thisState = State.LOGIN_SUCCESS; + private final String fakeUsername; @Getter private ForgeServerHandler handshakeHandler; @@ -102,7 +104,7 @@ else if ( !user.getExtraDataInHandshake().isEmpty() ) channel.write( copiedHandshake ); channel.setProtocol( Protocol.LOGIN ); - channel.write( user.getPendingConnection().getLoginRequest() ); + channel.write(new LoginRequest(fakeUsername != null ? fakeUsername : user.getPendingConnection().getName())); } @Override diff --git a/proxy/src/main/java/net/md_5/bungee/UserConnection.java b/proxy/src/main/java/net/md_5/bungee/UserConnection.java index fb437c57660..5f0484c4d00 100644 --- a/proxy/src/main/java/net/md_5/bungee/UserConnection.java +++ b/proxy/src/main/java/net/md_5/bungee/UserConnection.java @@ -227,6 +227,7 @@ public void connect(ServerInfo info, final Callback callback, final boo } final BungeeServerInfo target = (BungeeServerInfo) event.getTarget(); // Update in case the event changed target + final String fakeUsername = event.getFakeUsername(); if ( getServer() != null && Objects.equal( getServer().getInfo(), target ) ) { @@ -249,7 +250,7 @@ protected void initChannel(Channel ch) throws Exception PipelineUtils.BASE.initChannel( ch ); ch.pipeline().addAfter( PipelineUtils.FRAME_DECODER, PipelineUtils.PACKET_DECODER, new MinecraftDecoder( Protocol.HANDSHAKE, false, getPendingConnection().getVersion() ) ); ch.pipeline().addAfter( PipelineUtils.FRAME_PREPENDER, PipelineUtils.PACKET_ENCODER, new MinecraftEncoder( Protocol.HANDSHAKE, false, getPendingConnection().getVersion() ) ); - ch.pipeline().get( HandlerBoss.class ).setHandler( new ServerConnector( bungee, UserConnection.this, target ) ); + ch.pipeline().get( HandlerBoss.class ).setHandler( new ServerConnector( bungee, UserConnection.this, target, fakeUsername ) ); } }; ChannelFutureListener listener = new ChannelFutureListener() From af0d74aceaf701f3c7a8a1d40fd95899d1ace47f Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Fri, 13 Feb 2015 02:28:52 -0500 Subject: [PATCH 13/24] Properly serialize all BaseComponent subclasses --- .../main/java/net/md_5/bungee/chat/ComponentSerializer.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chat/src/main/java/net/md_5/bungee/chat/ComponentSerializer.java b/chat/src/main/java/net/md_5/bungee/chat/ComponentSerializer.java index 5756fc4afd8..8fbd4bcfdc0 100644 --- a/chat/src/main/java/net/md_5/bungee/chat/ComponentSerializer.java +++ b/chat/src/main/java/net/md_5/bungee/chat/ComponentSerializer.java @@ -18,9 +18,9 @@ public class ComponentSerializer implements JsonDeserializer { private final static Gson gson = new GsonBuilder(). - registerTypeAdapter( BaseComponent.class, new ComponentSerializer() ). - registerTypeAdapter( TextComponent.class, new TextComponentSerializer() ). - registerTypeAdapter( TranslatableComponent.class, new TranslatableComponentSerializer() ). + registerTypeHierarchyAdapter( BaseComponent.class, new ComponentSerializer() ). + registerTypeHierarchyAdapter( TextComponent.class, new TextComponentSerializer() ). + registerTypeHierarchyAdapter( TranslatableComponent.class, new TranslatableComponentSerializer() ). create(); public final static ThreadLocal> serializedComponents = new ThreadLocal>(); From d2b8c6fc069c659b7889d160fca12b194990fc11 Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Mon, 31 Aug 2015 03:28:07 -0400 Subject: [PATCH 14/24] Don't set the default log level to ALL --- proxy/src/main/java/net/md_5/bungee/log/BungeeLogger.java | 1 - 1 file changed, 1 deletion(-) diff --git a/proxy/src/main/java/net/md_5/bungee/log/BungeeLogger.java b/proxy/src/main/java/net/md_5/bungee/log/BungeeLogger.java index 94e924b6491..3f86e6429b3 100644 --- a/proxy/src/main/java/net/md_5/bungee/log/BungeeLogger.java +++ b/proxy/src/main/java/net/md_5/bungee/log/BungeeLogger.java @@ -23,7 +23,6 @@ public class BungeeLogger extends Logger public BungeeLogger(BungeeCord bungee) { super( "BungeeCord", null ); - setLevel( Level.ALL ); try { From 9c1884adafbc56755506060ed4e3a7ead39dc7c4 Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Sat, 5 Sep 2015 09:12:04 -0400 Subject: [PATCH 15/24] Allow commands to dynamically decide if they should be handled or passed upstream --- .../md_5/bungee/api/plugin/CommandBypassException.java | 8 ++++++++ .../java/net/md_5/bungee/api/plugin/PluginManager.java | 2 ++ 2 files changed, 10 insertions(+) create mode 100644 api/src/main/java/net/md_5/bungee/api/plugin/CommandBypassException.java diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/CommandBypassException.java b/api/src/main/java/net/md_5/bungee/api/plugin/CommandBypassException.java new file mode 100644 index 00000000000..0c3419c8251 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/plugin/CommandBypassException.java @@ -0,0 +1,8 @@ +package net.md_5.bungee.api.plugin; + +/** + * Thrown from inside a command to tell the proxy to pass the command upstream + */ +public class CommandBypassException extends RuntimeException { + +} diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java b/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java index 91ab95adb2a..5da8bf40e49 100644 --- a/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java +++ b/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java @@ -167,6 +167,8 @@ public boolean dispatchCommand(CommandSender sender, String commandLine, List Date: Wed, 30 Sep 2015 20:43:35 -0400 Subject: [PATCH 16/24] Fix NPE from duplicating TranslatableComponent --- .../java/net/md_5/bungee/api/chat/TranslatableComponent.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java index c00b7fa94a9..24e329496bf 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java @@ -41,10 +41,12 @@ public TranslatableComponent(TranslatableComponent original) { super( original ); setTranslate( original.getTranslate() ); + List temp = new ArrayList(); for ( BaseComponent baseComponent : original.getWith() ) { - with.add( baseComponent.duplicate() ); + temp.add( baseComponent.duplicate() ); } + setWith( temp ); } /** From 32e2bd2908824887eb94c46f0dd2b61374f5dfde Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Sat, 3 Oct 2015 20:36:09 -0400 Subject: [PATCH 17/24] Fix another NPE from duplicating TranslatableComponent --- .../md_5/bungee/api/chat/TranslatableComponent.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java index 24e329496bf..5e5bac0169a 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java @@ -41,12 +41,16 @@ public TranslatableComponent(TranslatableComponent original) { super( original ); setTranslate( original.getTranslate() ); - List temp = new ArrayList(); - for ( BaseComponent baseComponent : original.getWith() ) + + if ( original.getWith() != null ) { - temp.add( baseComponent.duplicate() ); + List temp = new ArrayList(); + for ( BaseComponent baseComponent : original.getWith() ) + { + temp.add( baseComponent.duplicate() ); + } + setWith( temp ); } - setWith( temp ); } /** From 552596985db3f8237d4cb8121c9d1066a10f3695 Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Sat, 3 Oct 2015 06:26:47 -0400 Subject: [PATCH 18/24] Improve component cycle detection --- .../md_5/bungee/api/chat/BaseComponent.java | 96 ++++++++++++++++++- .../md_5/bungee/api/chat/TextComponent.java | 9 +- .../api/chat/TranslatableComponent.java | 17 +++- .../bungee/chat/BaseComponentSerializer.java | 14 +-- .../md_5/bungee/chat/ComponentSerializer.java | 6 +- .../chat/TranslatableComponentSerializer.java | 8 +- 6 files changed, 127 insertions(+), 23 deletions(-) diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java index 4ae1654d2f2..60189cc167e 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java @@ -1,5 +1,6 @@ package net.md_5.bungee.api.chat; +import com.google.common.base.Joiner; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -7,11 +8,13 @@ import net.md_5.bungee.api.ChatColor; import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashSet; import java.util.List; -import lombok.ToString; +import java.util.Map; +import java.util.Set; @Setter -@ToString(exclude = "parent") @NoArgsConstructor public abstract class BaseComponent { @@ -388,4 +391,93 @@ void toLegacyText(StringBuilder builder) } } } + + protected static final ChatColor[] DECORATIONS = { + ChatColor.BOLD, + ChatColor.ITALIC, + ChatColor.UNDERLINE, + ChatColor.STRIKETHROUGH, + ChatColor.MAGIC + }; + + protected static final Joiner JOINER = Joiner.on(", "); + + public Boolean hasFormatRaw(ChatColor format) { + switch(format) { + case BOLD: return isBoldRaw(); + case ITALIC: return isItalicRaw(); + case UNDERLINE: return isUnderlinedRaw(); + case STRIKETHROUGH: return isStrikethroughRaw(); + case MAGIC: return isObfuscatedRaw(); + case RESET: return null; + } + + if(getColorRaw() == null) { + return null; + } else { + return getColorRaw() == format; + } + } + + public Map getFormatsRaw() { + EnumMap formats = new EnumMap(ChatColor.class); + + if(getColorRaw() != null) formats.put(getColorRaw(), true); + + for(ChatColor format : DECORATIONS) { + final Boolean flag = hasFormatRaw(format); + if(flag != null) formats.put(format, flag); + } + + return formats; + } + + protected void toStringTerminal(List fields) { + if(getColorRaw() != null) { + fields.add("color=\"" + getColorRaw().name().toLowerCase() + "\""); + } + + for(ChatColor format : DECORATIONS) { + final Boolean flag = hasFormatRaw(format); + if(flag != null) { + fields.add(format.name().toLowerCase() + "=" + flag); + } + } + + if(getClickEvent() != null) { + fields.add("clickEvent=" + getClickEvent()); + } + } + + protected void toStringRecursive(List fields) { + if(getHoverEvent() != null) { + fields.add("hoverEvent=" + getHoverEvent()); + } + + if(getExtra() != null && !getExtra().isEmpty()) { + fields.add("extra=[" + Joiner.on(", ").join(getExtra()) + "]"); + } + } + + private static final ThreadLocal> visited = new ThreadLocal>() { + @Override protected Set initialValue() { + return new HashSet(); + } + }; + + @Override + public String toString() { + List fields = new ArrayList(); + toStringTerminal(fields); + try { + if(visited.get().add(this)) { + toStringRecursive(fields); + } else { + fields.add("... (cycle)"); + } + } finally { + visited.get().remove(this); + } + return getClass().getSimpleName() + "{" + JOINER.join(fields) + "}"; + } } diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java index 49214f8716a..9961c423168 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java @@ -5,9 +5,11 @@ import lombok.NoArgsConstructor; import lombok.Setter; import net.md_5.bungee.api.ChatColor; +import org.apache.commons.lang.StringEscapeUtils; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -205,9 +207,8 @@ protected void toLegacyText(StringBuilder builder) super.toLegacyText( builder ); } - @Override - public String toString() - { - return String.format( "TextComponent{text=%s, %s}", text, super.toString() ); + @Override protected void toStringTerminal(List fields) { + fields.add("text=\"" + StringEscapeUtils.escapeJava(getText()) + "\""); + super.toStringTerminal(fields); } } diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java index 5e5bac0169a..e2235630962 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java @@ -10,11 +10,10 @@ import java.util.ResourceBundle; import java.util.regex.Matcher; import java.util.regex.Pattern; -import lombok.ToString; +import org.apache.commons.lang.StringEscapeUtils; @Getter @Setter -@ToString @NoArgsConstructor public class TranslatableComponent extends BaseComponent { @@ -246,4 +245,18 @@ private void addFormat(StringBuilder builder) builder.append( ChatColor.MAGIC ); } } + + @Override + protected void toStringTerminal(List fields) { + fields.add("translate=\"" + StringEscapeUtils.escapeJava(getTranslate()) + "\""); + super.toStringTerminal(fields); + } + + @Override + protected void toStringRecursive(List fields) { + if(getWith() != null && !getWith().isEmpty()) { + fields.add("with=[" + JOINER.join(getWith()) + "]"); + } + super.toStringRecursive(fields); + } } diff --git a/chat/src/main/java/net/md_5/bungee/chat/BaseComponentSerializer.java b/chat/src/main/java/net/md_5/bungee/chat/BaseComponentSerializer.java index cb1d24a25d9..2420940945e 100644 --- a/chat/src/main/java/net/md_5/bungee/chat/BaseComponentSerializer.java +++ b/chat/src/main/java/net/md_5/bungee/chat/BaseComponentSerializer.java @@ -10,7 +10,6 @@ import net.md_5.bungee.api.chat.HoverEvent; import java.util.Arrays; -import java.util.HashSet; public class BaseComponentSerializer { @@ -74,16 +73,9 @@ protected void deserialize(JsonObject object, BaseComponent component, JsonDeser protected void serialize(JsonObject object, BaseComponent component, JsonSerializationContext context) { - boolean first = false; - if ( ComponentSerializer.serializedComponents.get() == null ) - { - first = true; - ComponentSerializer.serializedComponents.set( new HashSet() ); - } try { - Preconditions.checkArgument( !ComponentSerializer.serializedComponents.get().contains( component ), "Component loop" ); - ComponentSerializer.serializedComponents.get().add( component ); + Preconditions.checkArgument( ComponentSerializer.serializedComponents.get().add( component ), "Component loop: " + component ); if ( component.getColorRaw() != null ) { object.addProperty( "color", component.getColorRaw().getName() ); @@ -132,10 +124,6 @@ protected void serialize(JsonObject object, BaseComponent component, JsonSeriali } finally { ComponentSerializer.serializedComponents.get().remove( component ); - if ( first ) - { - ComponentSerializer.serializedComponents.set( null ); - } } } } diff --git a/chat/src/main/java/net/md_5/bungee/chat/ComponentSerializer.java b/chat/src/main/java/net/md_5/bungee/chat/ComponentSerializer.java index 8fbd4bcfdc0..4acb09b7d5d 100644 --- a/chat/src/main/java/net/md_5/bungee/chat/ComponentSerializer.java +++ b/chat/src/main/java/net/md_5/bungee/chat/ComponentSerializer.java @@ -23,7 +23,11 @@ public class ComponentSerializer implements JsonDeserializer registerTypeHierarchyAdapter( TranslatableComponent.class, new TranslatableComponentSerializer() ). create(); - public final static ThreadLocal> serializedComponents = new ThreadLocal>(); + public final static ThreadLocal> serializedComponents = new ThreadLocal>() { + @Override protected HashSet initialValue() { + return new HashSet(); + } + }; public static BaseComponent[] parse(String json) { diff --git a/chat/src/main/java/net/md_5/bungee/chat/TranslatableComponentSerializer.java b/chat/src/main/java/net/md_5/bungee/chat/TranslatableComponentSerializer.java index c01af26864c..213fb574d0b 100644 --- a/chat/src/main/java/net/md_5/bungee/chat/TranslatableComponentSerializer.java +++ b/chat/src/main/java/net/md_5/bungee/chat/TranslatableComponentSerializer.java @@ -38,7 +38,13 @@ public JsonElement serialize(TranslatableComponent src, Type typeOfSrc, JsonSeri object.addProperty( "translate", src.getTranslate() ); if ( src.getWith() != null ) { - object.add( "with", context.serialize( src.getWith() ) ); + try { + ComponentSerializer.serializedComponents.get().add( src ); + object.add( "with", context.serialize( src.getWith() ) ); + } + finally { + ComponentSerializer.serializedComponents.get().remove( src ); + } } return object; } From 2adbb86f13800a55f4ff3f6d5b5c886d001f3660 Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Sun, 4 Oct 2015 01:53:42 -0400 Subject: [PATCH 19/24] Remove parent field from BaseComponent --- .../java/net/md_5/bungee/api/ChatColor.java | 9 + .../md_5/bungee/api/ChatStringBuilder.java | 106 ++++++++++++ .../md_5/bungee/api/chat/BaseComponent.java | 155 ++++++++++-------- .../md_5/bungee/api/chat/TextComponent.java | 27 +-- .../api/chat/TranslatableComponent.java | 51 ++---- .../net/md_5/bungee/chat/ComponentsTest.java | 4 +- 6 files changed, 219 insertions(+), 133 deletions(-) create mode 100644 chat/src/main/java/net/md_5/bungee/api/ChatStringBuilder.java diff --git a/chat/src/main/java/net/md_5/bungee/api/ChatColor.java b/chat/src/main/java/net/md_5/bungee/api/ChatColor.java index cd88bd47000..85291a8806a 100644 --- a/chat/src/main/java/net/md_5/bungee/api/ChatColor.java +++ b/chat/src/main/java/net/md_5/bungee/api/ChatColor.java @@ -105,6 +105,15 @@ public enum ChatColor */ public static final char COLOR_CHAR = '\u00A7'; public static final String ALL_CODES = "0123456789AaBbCcDdEeFfKkLlMmNnOoRr"; + + public static final ChatColor[] DECORATIONS = { + BOLD, + ITALIC, + UNDERLINE, + STRIKETHROUGH, + MAGIC + }; + /** * Pattern to remove all colour codes. */ diff --git a/chat/src/main/java/net/md_5/bungee/api/ChatStringBuilder.java b/chat/src/main/java/net/md_5/bungee/api/ChatStringBuilder.java new file mode 100644 index 00000000000..a3b0a2be537 --- /dev/null +++ b/chat/src/main/java/net/md_5/bungee/api/ChatStringBuilder.java @@ -0,0 +1,106 @@ +package net.md_5.bungee.api; + +import java.util.EnumSet; +import java.util.Set; + +public class ChatStringBuilder { + + private final StringBuilder builder; + private ChatColor oldColor, newColor; + private final Set oldDecorations, newDecorations; + private boolean formatChanged = true; + + public ChatStringBuilder(String initial, ChatColor color, Set decorations) { + builder = initial != null ? new StringBuilder(initial) : new StringBuilder(); + + oldColor = newColor = color; + + oldDecorations = decorations != null ? EnumSet.copyOf(decorations) : EnumSet.noneOf(ChatColor.class); + newDecorations = EnumSet.noneOf(ChatColor.class); + } + + public ChatStringBuilder() { + this(null, ChatColor.RESET, null); + } + + @Override + public String toString() { + return builder.toString(); + } + + private void refreshComplete() { + builder.append(oldColor = newColor); + + oldDecorations.clear(); + for(ChatColor deco : newDecorations) { + oldDecorations.add(deco); + builder.append(deco); + } + } + + private void refreshIfChanged() { + if(!formatChanged) return; + + // If color changed, a complete refresh is required + if(oldColor != newColor) { + refreshComplete(); + return; + } + + // If any decorations were removed, a complete refresh is required + for(ChatColor deco : oldDecorations) { + if(!newDecorations.contains(deco)) { + refreshComplete(); + return; + } + } + + // If the only change is added decorations, they can just be appended + for(ChatColor deco : newDecorations) { + if(oldDecorations.add(deco)) { + builder.append(deco); + } + } + } + + public void append(String text) { + if(!text.isEmpty()) { + refreshIfChanged(); + builder.append(text); + } + } + + public void append(Object thing) { + append(String.valueOf(thing)); + } + + public void color(ChatColor color) { + if(newColor != color) { + formatChanged = true; + newColor = color; + } + } + + public void decoration(ChatColor decoration, boolean on) { + if(on) { + if(newDecorations.add(decoration)) { + formatChanged = true; + } + } else { + if(newDecorations.remove(decoration)) { + formatChanged = true; + } + } + } + + public void decorations(Set decorations) { + for(ChatColor deco : ChatColor.DECORATIONS) { + decoration(deco, decorations.contains(deco)); + } + } + + public void format(ChatColor color, Set decorations) { + color(color); + decorations(decorations); + } +} diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java index 60189cc167e..28d2fa6400f 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/BaseComponent.java @@ -1,14 +1,16 @@ package net.md_5.bungee.api.chat; import com.google.common.base.Joiner; -import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ChatStringBuilder; import java.util.ArrayList; +import java.util.Collections; import java.util.EnumMap; +import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -19,9 +21,6 @@ public abstract class BaseComponent { - @Setter(AccessLevel.NONE) - BaseComponent parent; - /** * The color of this component and any child components (unless overridden) */ @@ -139,15 +138,7 @@ public static String toPlainText(BaseComponent... components) */ public ChatColor getColor() { - if ( color == null ) - { - if ( parent == null ) - { - return ChatColor.WHITE; - } - return parent.getColor(); - } - return color; + return color != null ? color : ChatColor.WHITE; } /** @@ -161,6 +152,10 @@ public ChatColor getColorRaw() return color; } + public ChatColor getColor(ChatColor def) { + return color != null ? color : def; + } + /** * Returns whether this component is bold. This uses the parent's setting if * this component hasn't been set. false is returned if none of the parent @@ -170,11 +165,7 @@ public ChatColor getColorRaw() */ public boolean isBold() { - if ( bold == null ) - { - return parent != null && parent.isBold(); - } - return bold; + return bold != null && bold; } /** @@ -197,11 +188,7 @@ public Boolean isBoldRaw() */ public boolean isItalic() { - if ( italic == null ) - { - return parent != null && parent.isItalic(); - } - return italic; + return italic != null && italic; } /** @@ -224,11 +211,7 @@ public Boolean isItalicRaw() */ public boolean isUnderlined() { - if ( underlined == null ) - { - return parent != null && parent.isUnderlined(); - } - return underlined; + return underlined != null && underlined; } /** @@ -251,11 +234,7 @@ public Boolean isUnderlinedRaw() */ public boolean isStrikethrough() { - if ( strikethrough == null ) - { - return parent != null && parent.isStrikethrough(); - } - return strikethrough; + return strikethrough != null && strikethrough; } /** @@ -278,11 +257,7 @@ public Boolean isStrikethroughRaw() */ public boolean isObfuscated() { - if ( obfuscated == null ) - { - return parent != null && parent.isObfuscated(); - } - return obfuscated; + return obfuscated != null && obfuscated; } /** @@ -298,10 +273,6 @@ public Boolean isObfuscatedRaw() public void setExtra(List components) { - for ( BaseComponent component : components ) - { - component.parent = this; - } extra = components; } @@ -328,7 +299,6 @@ public void addExtra(BaseComponent component) { extra = new ArrayList(); } - component.parent = this; extra.add( component ); } @@ -376,60 +346,107 @@ void toPlainText(StringBuilder builder) */ public String toLegacyText() { - StringBuilder builder = new StringBuilder(); - toLegacyText( builder ); + ChatStringBuilder builder = new ChatStringBuilder(); + toLegacyText(builder, ChatColor.WHITE, Collections.emptySet()); return builder.toString(); } - void toLegacyText(StringBuilder builder) + protected void toLegacyText(ChatStringBuilder builder, ChatColor color, Set decorations) { + color = getColor(color); + decorations = getDecorations(decorations); + + toLegacyTextContent(builder, color, decorations); + if ( extra != null ) { for ( BaseComponent e : extra ) { - e.toLegacyText( builder ); + e.toLegacyText( builder, color, decorations ); } } } - protected static final ChatColor[] DECORATIONS = { - ChatColor.BOLD, - ChatColor.ITALIC, - ChatColor.UNDERLINE, - ChatColor.STRIKETHROUGH, - ChatColor.MAGIC - }; + protected void toLegacyTextContent(ChatStringBuilder builder, ChatColor color, Set decorations) { + } protected static final Joiner JOINER = Joiner.on(", "); - public Boolean hasFormatRaw(ChatColor format) { - switch(format) { + public Boolean getDecoration(ChatColor decoration) { + switch(decoration) { case BOLD: return isBoldRaw(); case ITALIC: return isItalicRaw(); case UNDERLINE: return isUnderlinedRaw(); case STRIKETHROUGH: return isStrikethroughRaw(); case MAGIC: return isObfuscatedRaw(); - case RESET: return null; + default: return null; } + } - if(getColorRaw() == null) { - return null; + public boolean getDecoration(ChatColor decoration, boolean def) { + Boolean flag = getDecoration(decoration); + if(flag != null) { + return flag; } else { - return getColorRaw() == format; + return def; } } - public Map getFormatsRaw() { - EnumMap formats = new EnumMap(ChatColor.class); + public void setDecoration(ChatColor decoration, Boolean flag) { + switch(decoration) { + case BOLD: setBold(flag); return; + case ITALIC: setItalic(flag); return; + case UNDERLINE: setUnderlined(flag); return; + case STRIKETHROUGH: setStrikethrough(flag); return; + case MAGIC: setObfuscated(flag); return; + } + } - if(getColorRaw() != null) formats.put(getColorRaw(), true); + public Map getDecorations() { + EnumMap decos = new EnumMap(ChatColor.class); + for(ChatColor deco : ChatColor.DECORATIONS) { + final Boolean flag = getDecoration(deco); + if(flag != null) decos.put(deco, flag); + } + return decos; + } - for(ChatColor format : DECORATIONS) { - final Boolean flag = hasFormatRaw(format); - if(flag != null) formats.put(format, flag); + public Set getDecorations(Set def) { + EnumSet decos = EnumSet.noneOf(ChatColor.class); + for(ChatColor deco : ChatColor.DECORATIONS) { + if(getDecoration(deco, def.contains(deco))) { + decos.add(deco); + } } + return decos; + } + + public void mergeDecorations(BaseComponent from) { + for(ChatColor deco : ChatColor.DECORATIONS) { + Boolean flag = from.getDecoration(deco); + if(flag != null) setDecoration(deco, flag); + } + } + + public void mergeColor(BaseComponent from) { + if(from.getColorRaw() != null) { + setColor(from.getColorRaw()); + } + } + + public void mergeEvents(BaseComponent from) { + if(from.getClickEvent() != null) { + setClickEvent(from.getClickEvent()); + } + if(from.getHoverEvent() != null) { + setHoverEvent(from.getHoverEvent()); + } + } - return formats; + public void mergeFormatting(BaseComponent from) { + mergeDecorations(from); + mergeColor(from); + mergeEvents(from); } protected void toStringTerminal(List fields) { @@ -437,8 +454,8 @@ protected void toStringTerminal(List fields) { fields.add("color=\"" + getColorRaw().name().toLowerCase() + "\""); } - for(ChatColor format : DECORATIONS) { - final Boolean flag = hasFormatRaw(format); + for(ChatColor format : ChatColor.DECORATIONS) { + final Boolean flag = getDecoration(format); if(flag != null) { fields.add(format.name().toLowerCase() + "=" + flag); } diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java index 9961c423168..b35ea7086ac 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java @@ -5,11 +5,13 @@ import lombok.NoArgsConstructor; import lombok.Setter; import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ChatStringBuilder; import org.apache.commons.lang.StringEscapeUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -180,31 +182,10 @@ protected void toPlainText(StringBuilder builder) } @Override - protected void toLegacyText(StringBuilder builder) + protected void toLegacyTextContent(ChatStringBuilder builder, ChatColor color, Set decorations) { - builder.append( getColor() ); - if ( isBold() ) - { - builder.append( ChatColor.BOLD ); - } - if ( isItalic() ) - { - builder.append( ChatColor.ITALIC ); - } - if ( isUnderlined() ) - { - builder.append( ChatColor.UNDERLINE ); - } - if ( isStrikethrough() ) - { - builder.append( ChatColor.STRIKETHROUGH ); - } - if ( isObfuscated() ) - { - builder.append( ChatColor.MAGIC ); - } + builder.format(color, decorations); builder.append( text ); - super.toLegacyText( builder ); } @Override protected void toStringTerminal(List fields) { diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java index e2235630962..e31b7b18b6e 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java @@ -3,13 +3,17 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import net.md_5.bungee.api.ChatColor; + import java.util.ArrayList; import java.util.List; import java.util.MissingResourceException; import java.util.ResourceBundle; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ChatStringBuilder; import org.apache.commons.lang.StringEscapeUtils; @Getter @@ -98,10 +102,6 @@ public BaseComponent duplicate() */ public void setWith(List components) { - for ( BaseComponent component : components ) - { - component.parent = this; - } with = components; } @@ -128,7 +128,6 @@ public void addWith(BaseComponent component) { with = new ArrayList(); } - component.parent = this; with.add( component ); } @@ -176,12 +175,12 @@ protected void toPlainText(StringBuilder builder) } @Override - protected void toLegacyText(StringBuilder builder) + protected void toLegacyTextContent(ChatStringBuilder builder, ChatColor color, Set decorations) { try { String trans = locales.getString( translate ); - Matcher matcher = format.matcher( trans ); + Matcher matcher = this.format.matcher(trans ); int position = 0; int i = 0; while ( matcher.find( position ) ) @@ -189,7 +188,7 @@ protected void toLegacyText(StringBuilder builder) int pos = matcher.start(); if ( pos != position ) { - addFormat( builder ); + builder.format(color, decorations); builder.append( trans.substring( position, pos ) ); } position = matcher.end(); @@ -200,50 +199,24 @@ protected void toLegacyText(StringBuilder builder) case 's': case 'd': String withIndex = matcher.group( 1 ); - with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ ).toLegacyText( builder ); + with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ ).toLegacyText( builder, color, decorations ); break; case '%': - addFormat( builder ); + builder.format(color, decorations); builder.append( '%' ); break; } } if ( trans.length() != position ) { - addFormat( builder ); + builder.format(color, decorations); builder.append( trans.substring( position, trans.length() ) ); } } catch ( MissingResourceException e ) { - addFormat( builder ); + builder.format(color, decorations); builder.append( translate ); } - super.toLegacyText( builder ); - } - - private void addFormat(StringBuilder builder) - { - builder.append( getColor() ); - if ( isBold() ) - { - builder.append( ChatColor.BOLD ); - } - if ( isItalic() ) - { - builder.append( ChatColor.ITALIC ); - } - if ( isUnderlined() ) - { - builder.append( ChatColor.UNDERLINE ); - } - if ( isStrikethrough() ) - { - builder.append( ChatColor.STRIKETHROUGH ); - } - if ( isObfuscated() ) - { - builder.append( ChatColor.MAGIC ); - } } @Override diff --git a/proxy/src/test/java/net/md_5/bungee/chat/ComponentsTest.java b/proxy/src/test/java/net/md_5/bungee/chat/ComponentsTest.java index b5ea883386c..4e840c08de0 100644 --- a/proxy/src/test/java/net/md_5/bungee/chat/ComponentsTest.java +++ b/proxy/src/test/java/net/md_5/bungee/chat/ComponentsTest.java @@ -60,13 +60,13 @@ public void testTranslateComponent() 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", + + " * 5 to 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() ); + Assert.assertEquals( ChatColor.WHITE + "Page 5 of 50", positional.toLegacyText() ); } @Test From f377cea3244828498a9c4e6d262a4e9122e96a43 Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Sun, 4 Oct 2015 09:40:12 -0400 Subject: [PATCH 20/24] Seems Apache Commons is not available at runtime --- chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java | 3 +-- .../java/net/md_5/bungee/api/chat/TranslatableComponent.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java index b35ea7086ac..4b1a266ec3e 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java @@ -6,7 +6,6 @@ import lombok.Setter; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatStringBuilder; -import org.apache.commons.lang.StringEscapeUtils; import java.util.ArrayList; import java.util.Arrays; @@ -189,7 +188,7 @@ protected void toLegacyTextContent(ChatStringBuilder builder, ChatColor color, S } @Override protected void toStringTerminal(List fields) { - fields.add("text=\"" + StringEscapeUtils.escapeJava(getText()) + "\""); + fields.add("text=\"" + getText() + "\""); super.toStringTerminal(fields); } } diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java index e31b7b18b6e..59d17677677 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/TranslatableComponent.java @@ -14,7 +14,6 @@ import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatStringBuilder; -import org.apache.commons.lang.StringEscapeUtils; @Getter @Setter @@ -221,7 +220,7 @@ protected void toLegacyTextContent(ChatStringBuilder builder, ChatColor color, S @Override protected void toStringTerminal(List fields) { - fields.add("translate=\"" + StringEscapeUtils.escapeJava(getTranslate()) + "\""); + fields.add("translate=\"" + getTranslate() + "\""); super.toStringTerminal(fields); } From 708c01e8bc68593b7667d1bc8a2cbf8b011723b1 Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Sat, 10 Oct 2015 18:38:52 -0400 Subject: [PATCH 21/24] Register PluginLoggers with the LogManager --- .../net/md_5/bungee/api/plugin/Plugin.java | 2 +- .../md_5/bungee/api/plugin/PluginLogger.java | 19 +++++++++++++++++++ .../net/md_5/bungee/log/BungeeLogger.java | 1 - 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/Plugin.java b/api/src/main/java/net/md_5/bungee/api/plugin/Plugin.java index 96405b0062d..834d9a64552 100644 --- a/api/src/main/java/net/md_5/bungee/api/plugin/Plugin.java +++ b/api/src/main/java/net/md_5/bungee/api/plugin/Plugin.java @@ -85,7 +85,7 @@ final void init(ProxyServer proxy, PluginDescription description) this.proxy = proxy; this.description = description; this.file = description.getFile(); - this.logger = new PluginLogger( this ); + this.logger = PluginLogger.get( this ); } // diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/PluginLogger.java b/api/src/main/java/net/md_5/bungee/api/plugin/PluginLogger.java index b304ec0d663..8b52dd8d423 100644 --- a/api/src/main/java/net/md_5/bungee/api/plugin/PluginLogger.java +++ b/api/src/main/java/net/md_5/bungee/api/plugin/PluginLogger.java @@ -1,10 +1,29 @@ package net.md_5.bungee.api.plugin; +import java.util.logging.LogManager; import java.util.logging.LogRecord; import java.util.logging.Logger; public class PluginLogger extends Logger { + public static PluginLogger get(Plugin context) { + LogManager lm = LogManager.getLogManager(); + Logger logger = lm.getLogger(context.getClass().getCanonicalName()); + + if(logger instanceof PluginLogger) { + return (PluginLogger) logger; + } else { + PluginLogger pluginLogger = new PluginLogger(context); + + // Register the logger under the plugin's name, unless some other logger is already using the name + if(logger == null) { + lm.addLogger(pluginLogger); + pluginLogger.setParent(context.getProxy().getLogger()); // addLogger changes this, change it back + } + + return pluginLogger; + } + } private final String pluginName; diff --git a/proxy/src/main/java/net/md_5/bungee/log/BungeeLogger.java b/proxy/src/main/java/net/md_5/bungee/log/BungeeLogger.java index 3f86e6429b3..8b95f91f4e5 100644 --- a/proxy/src/main/java/net/md_5/bungee/log/BungeeLogger.java +++ b/proxy/src/main/java/net/md_5/bungee/log/BungeeLogger.java @@ -31,7 +31,6 @@ public BungeeLogger(BungeeCord bungee) addHandler( fileHandler ); ColouredWriter consoleHandler = new ColouredWriter( bungee.getConsoleReader() ); - consoleHandler.setLevel( Level.INFO ); consoleHandler.setFormatter( formatter ); addHandler( consoleHandler ); } catch ( IOException ex ) From fbbac1594b9d454c069e7fc21311b88be116b9c4 Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Sun, 11 Oct 2015 04:45:26 -0400 Subject: [PATCH 22/24] Register root logger with LogManager as well --- .../main/java/net/md_5/bungee/BungeeCord.java | 2 +- .../net/md_5/bungee/log/BungeeLogger.java | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java index 07a707114e7..0fbc84debb2 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -184,7 +184,7 @@ public BungeeCord() throws IOException consoleReader = new ConsoleReader(); consoleReader.setExpandEvents( false ); - logger = new BungeeLogger( this ); + logger = BungeeLogger.get( this ); // Overcast - stderr gets a lot of non-error output, so log it at WARNING level instead of SEVERE System.setErr( new PrintStream( new LoggingOutputStream( logger, Level.WARNING ), true ) ); diff --git a/proxy/src/main/java/net/md_5/bungee/log/BungeeLogger.java b/proxy/src/main/java/net/md_5/bungee/log/BungeeLogger.java index 8b95f91f4e5..4451ba5bafa 100644 --- a/proxy/src/main/java/net/md_5/bungee/log/BungeeLogger.java +++ b/proxy/src/main/java/net/md_5/bungee/log/BungeeLogger.java @@ -4,13 +4,24 @@ import java.io.IOException; import java.util.logging.FileHandler; import java.util.logging.Formatter; -import java.util.logging.Level; +import java.util.logging.LogManager; import java.util.logging.LogRecord; import java.util.logging.Logger; import net.md_5.bungee.BungeeCord; public class BungeeLogger extends Logger { + private static final String NAME = "BungeeCord"; + + public static BungeeLogger get(BungeeCord bungee) { + final LogManager lm = LogManager.getLogManager(); + Logger logger = lm.getLogger(NAME); + if(!(logger instanceof BungeeLogger)) { + logger = new BungeeLogger(bungee); + lm.addLogger(logger); + } + return (BungeeLogger) logger; + } private final Formatter formatter = new ConciseFormatter(); private final LogDispatcher dispatcher = new LogDispatcher( this ); @@ -20,9 +31,11 @@ public class BungeeLogger extends Logger "CallToPrintStackTrace", "CallToThreadStartDuringObjectConstruction" }) @SuppressFBWarnings("SC_START_IN_CTOR") - public BungeeLogger(BungeeCord bungee) + private BungeeLogger(BungeeCord bungee) { - super( "BungeeCord", null ); + super( NAME, null ); + setParent(Logger.getLogger("")); + setUseParentHandlers(false); try { From 0b7b5e3e438ca8e8b20230fe8f16506e4c9293d2 Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Sat, 16 Jan 2016 06:59:23 -0500 Subject: [PATCH 23/24] Allow Configuration objects to be cloned --- .../src/main/java/net/md_5/bungee/config/Configuration.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/src/main/java/net/md_5/bungee/config/Configuration.java b/config/src/main/java/net/md_5/bungee/config/Configuration.java index 1366f449165..d333c8144aa 100644 --- a/config/src/main/java/net/md_5/bungee/config/Configuration.java +++ b/config/src/main/java/net/md_5/bungee/config/Configuration.java @@ -28,6 +28,11 @@ public Configuration(Configuration defaults) this( new LinkedHashMap(), defaults ); } + public Configuration(Configuration values, Configuration defaults) + { + this( values.self, defaults ); + } + private Configuration getSectionFor(String path) { int index = path.indexOf( SEPARATOR ); From b1f3b47c142bf3a418d82f64f7eb75cc51862416 Mon Sep 17 00:00:00 2001 From: Jedediah Smith Date: Sat, 23 Jan 2016 16:50:09 -0500 Subject: [PATCH 24/24] A proper mechanism for plugins to register servers dynamically --- .../java/net/md_5/bungee/api/ProxyServer.java | 31 +++++ .../bungee/util/ConcurrentAggregateMap.java | 121 ++++++++++++++++++ .../main/java/net/md_5/bungee/BungeeCord.java | 17 ++- 3 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 api/src/main/java/net/md_5/bungee/util/ConcurrentAggregateMap.java diff --git a/api/src/main/java/net/md_5/bungee/api/ProxyServer.java b/api/src/main/java/net/md_5/bungee/api/ProxyServer.java index 4c191f25075..aa4aa26c8e9 100644 --- a/api/src/main/java/net/md_5/bungee/api/ProxyServer.java +++ b/api/src/main/java/net/md_5/bungee/api/ProxyServer.java @@ -96,6 +96,37 @@ public static void setInstance(ProxyServer instance) */ public abstract Map getServers(); + /** + * Register the given map to be used to enumerate servers, and resolve them by name. + * + * The map returned from {@link #getServers()} is a live superset view of statically + * configured servers plus all the maps registered through this method. + * + * The master map will synchronize on each sub-map while calling any method on it, or + * iterating over any of its views. The master map will also be locked when that + * happens, so it must not be accessed while holding a lock on any of its sub-maps, or a + * deadlock may occur. The master map itself is entirely safe for concurrent access. + * Its collection views are immutable snapshots. + * + * The master map does not transform keys in any way, so if case-insensitivity is + * desired, it must be implemented by the sub-map. + * + * If multiple sub-maps contain entries with the same key, all entries will be included + * in the {@link Map#entrySet} and {@link Map#values} views of the master map, + * while the {@link Map#keySet} view will de-duplicate the keys. An arbitrary entry + * will be chosen when looking up a single duplicated key. + * + * @return true if the sub-map was added, false if it was already registered. + */ + public abstract boolean addServers(Map servers); + + /** + * Unregister the given server sub-map. + * + * @return true if the sub-map was unregistered, false if it was not registered to begin with. + */ + public abstract boolean removeServers(Map servers); + /** * Gets the server info of a server. * diff --git a/api/src/main/java/net/md_5/bungee/util/ConcurrentAggregateMap.java b/api/src/main/java/net/md_5/bungee/util/ConcurrentAggregateMap.java new file mode 100644 index 00000000000..f5487ff9996 --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/util/ConcurrentAggregateMap.java @@ -0,0 +1,121 @@ +package net.md_5.bungee.util; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +public class ConcurrentAggregateMap implements Map { + + private final Set> maps = new HashSet<>(); + + synchronized public boolean addMap(Map map) { + return maps.add(map); + } + + synchronized public boolean removeMap(Map map) { + return maps.remove(map); + } + + @Override + synchronized public int size() { + int n = 0; + for(Map map : maps) { + synchronized(map) { + n += map.size(); + } + } + return n; + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + synchronized public boolean containsKey(Object key) { + for(Map map : maps) { + synchronized(map) { + if(map.containsKey(key)) return true; + } + } + return false; + } + + @Override + synchronized public boolean containsValue(Object value) { + for(Map map : maps) { + synchronized(map) { + if(map.containsValue(value)) return true; + } + } + return false; + } + + @Override + synchronized public V get(Object key) { + for(Map map : maps) { + synchronized(map) { + if(map.containsKey(key)) return map.get(key); + } + } + return null; + } + + @Override + synchronized public Set keySet() { + final ImmutableSet.Builder builder = ImmutableSet.builder(); + for(Map map : maps) { + synchronized(map) { + builder.addAll(map.keySet()); + } + } + return builder.build(); + } + + @Override + synchronized public Collection values() { + final ImmutableList.Builder builder = ImmutableList.builder(); + for(Map map : maps) { + synchronized(map) { + builder.addAll(map.values()); + } + } + return builder.build(); + } + + @Override + synchronized public Set> entrySet() { + final ImmutableSet.Builder> builder = ImmutableSet.builder(); + for(Map map : maps) { + synchronized(map) { + builder.addAll(map.entrySet()); + } + } + return builder.build(); + } + + @Override + public V put(K key, V value) { + throw new UnsupportedOperationException("This container cannot be directly modified"); + } + + @Override + public V remove(Object key) { + throw new UnsupportedOperationException("This container cannot be directly modified"); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException("This container cannot be directly modified"); + } + + @Override + public void clear() { + throw new UnsupportedOperationException("This container cannot be directly modified"); + } +} diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java index 0fbc84debb2..80c46828b41 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -43,6 +43,7 @@ import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; +import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.UUID; @@ -80,6 +81,7 @@ import net.md_5.bungee.protocol.packet.PluginMessage; import net.md_5.bungee.query.RemoteQuery; import net.md_5.bungee.util.CaseInsensitiveMap; +import net.md_5.bungee.util.ConcurrentAggregateMap; import org.fusesource.jansi.AnsiConsole; /** @@ -97,6 +99,8 @@ public class BungeeCord extends ProxyServer */ @Getter public final Configuration config = new Configuration(); + + private final ConcurrentAggregateMap servers = new ConcurrentAggregateMap<>(); /** * Localization bundle. */ @@ -248,6 +252,7 @@ public void start() throws Exception pluginManager.loadPlugins(); config.load(); + addServers(config.getServers()); registerChannel( ForgeConstants.FML_TAG ); registerChannel( ForgeConstants.FML_HANDSHAKE_TAG ); @@ -536,7 +541,7 @@ public ProxiedPlayer getPlayer(UUID uuid) @Override public Map getServers() { - return config.getServers(); + return servers; } @Override @@ -545,6 +550,16 @@ public ServerInfo getServerInfo(String name) return getServers().get( name ); } + @Override + public boolean addServers(Map servers) { + return this.servers.addMap(servers); + } + + @Override + public boolean removeServers(Map servers) { + return this.servers.removeMap(servers); + } + @Override @Synchronized("pluginChannels") public void registerChannel(String channel)