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 6f8c9736..5aa7e68e 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 @@ -9,6 +9,7 @@ import net.md_5.bungee.api.ChatColor; import java.util.ArrayList; import java.util.List; import lombok.ToString; +import net.md_5.bungee.api.chat.ComponentBuilder.FormatRetention; @Setter @ToString(exclude = "parent") @@ -62,13 +63,13 @@ public abstract class BaseComponent private List extra; /** - * The action to preform when this component (and child components) are + * The action to perform when this component (and child components) are * clicked */ @Getter private ClickEvent clickEvent; /** - * The action to preform when this component (and child components) are + * The action to perform when this component (and child components) are * hovered over */ @Getter @@ -76,29 +77,118 @@ public abstract class BaseComponent BaseComponent(BaseComponent old) { - copyFormatting( old ); - } + copyFormatting( old, FormatRetention.ALL, true ); - public void copyFormatting(BaseComponent component) - { - setColor( component.getColorRaw() ); - setBold( component.isBoldRaw() ); - setItalic( component.isItalicRaw() ); - setUnderlined( component.isUnderlinedRaw() ); - setStrikethrough( component.isStrikethroughRaw() ); - setObfuscated( component.isObfuscatedRaw() ); - setInsertion( component.getInsertion() ); - setClickEvent( component.getClickEvent() ); - setHoverEvent( component.getHoverEvent() ); - if ( component.getExtra() != null ) + if ( old.getExtra() != null ) { - for ( BaseComponent extra : component.getExtra() ) + for ( BaseComponent extra : old.getExtra() ) { addExtra( extra.duplicate() ); } } } + /** + * Copies the events and formatting of a BaseComponent. Already set + * formatting will be replaced. + * + * @param component the component to copy from + */ + public void copyFormatting(BaseComponent component) + { + copyFormatting( component, FormatRetention.ALL, true ); + } + + /** + * Copies the events and formatting of a BaseComponent. + * + * @param component the component to copy from + * @param replace if already set formatting should be replaced by the new + * component + */ + public void copyFormatting(BaseComponent component, boolean replace) + { + copyFormatting( component, FormatRetention.ALL, replace ); + } + + /** + * Copies the specified formatting of a BaseComponent. + * + * @param component the component to copy from + * @param retention the formatting to copy + * @param replace if already set formatting should be replaced by the new + * component + */ + public void copyFormatting(BaseComponent component, FormatRetention retention, boolean replace) + { + if ( retention == FormatRetention.EVENTS || retention == FormatRetention.ALL ) + { + if ( replace || clickEvent == null ) + { + setClickEvent( component.getClickEvent() ); + } + if ( replace || hoverEvent == null ) + { + setHoverEvent( component.getHoverEvent() ); + } + } + if ( retention == FormatRetention.FORMATTING || retention == FormatRetention.ALL ) + { + if ( replace || color == null ) + { + setColor( component.getColorRaw() ); + } + if ( replace || bold == null ) + { + setBold( component.isBoldRaw() ); + } + if ( replace || italic == null ) + { + setItalic( component.isItalicRaw() ); + } + if ( replace || underlined == null ) + { + setUnderlined( component.isUnderlinedRaw() ); + } + if ( replace || strikethrough == null ) + { + setStrikethrough( component.isStrikethroughRaw() ); + } + if ( replace || obfuscated == null ) + { + setObfuscated( component.isObfuscatedRaw() ); + } + if ( replace || insertion == null ) + { + setInsertion( component.getInsertion() ); + } + } + } + + /** + * Retains only the specified formatting. + * + * @param retention the formatting to retain + */ + public void retain(FormatRetention retention) + { + if ( retention == FormatRetention.FORMATTING || retention == FormatRetention.NONE ) + { + setClickEvent( null ); + setHoverEvent( null ); + } + if ( retention == FormatRetention.EVENTS || retention == FormatRetention.NONE ) + { + setColor( null ); + setBold( null ); + setItalic( null ); + setUnderlined( null ); + setStrikethrough( null ); + setObfuscated( null ); + setInsertion( null ); + } + } + /** * Clones the BaseComponent and returns the clone. * @@ -110,8 +200,15 @@ public abstract class BaseComponent * Clones the BaseComponent without formatting and returns the clone. * * @return The duplicate of this BaseComponent + * @deprecated API use discouraged, use traditional duplicate */ - public abstract BaseComponent duplicateWithoutFormatting(); + @Deprecated + public BaseComponent duplicateWithoutFormatting() + { + BaseComponent component = duplicate(); + component.retain( FormatRetention.NONE ); + return component; + } /** * Converts the components to a string that uses the old formatting codes diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/ComponentBuilder.java b/chat/src/main/java/net/md_5/bungee/api/chat/ComponentBuilder.java index 2985908b..e1e96b68 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/ComponentBuilder.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/ComponentBuilder.java @@ -55,8 +55,51 @@ public final class ComponentBuilder } /** - * Appends the components to the builder and makes it the current target for - * formatting. The text will have all the formatting from the previous part. + * Creates a ComponentBuilder with the given component as the first part. + * + * @param component the first component element + */ + public ComponentBuilder(BaseComponent component) + { + current = component.duplicate(); + } + + /** + * Appends a component to the builder and makes it the current target for + * formatting. The component will have all the formatting from previous + * part. + * + * @param component the component to append + * @return this ComponentBuilder for chaining + */ + public ComponentBuilder append(BaseComponent component) + { + return append( component, FormatRetention.ALL ); + } + + /** + * Appends a component to the builder and makes it the current target for + * formatting. You can specify the amount of formatting retained from + * previous part. + * + * @param component the component to append + * @param retention the formatting to retain + * @return this ComponentBuilder for chaining + */ + public ComponentBuilder append(BaseComponent component, FormatRetention retention) + { + parts.add( current ); + + BaseComponent previous = current; + current = component.duplicate(); + current.copyFormatting( previous, retention, false ); + return this; + } + + /** + * Appends the components to the builder and makes the last element the + * current target for formatting. The components will have all the + * formatting from previous part. * * @param components the components to append * @return this ComponentBuilder for chaining @@ -67,8 +110,9 @@ public final class ComponentBuilder } /** - * Appends the components to the builder and makes it the current target for - * formatting. You can specify the amount of formatting retained. + * Appends the components 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 components the components to append * @param retention the formatting to retain @@ -82,8 +126,9 @@ public final class ComponentBuilder { parts.add( current ); + BaseComponent previous = current; current = component.duplicate(); - retain( retention ); + current.copyFormatting( previous, retention, false ); } return this; @@ -91,7 +136,7 @@ public final class ComponentBuilder /** * Appends the text to the builder and makes it the current target for - * formatting. The text will have all the formatting from the previous part. + * formatting. The text will have all the formatting from previous part. * * @param text the text to append * @return this ComponentBuilder for chaining @@ -103,7 +148,8 @@ public final class ComponentBuilder /** * Appends the text to the builder and makes it the current target for - * formatting. You can specify the amount of formatting retained. + * formatting. You can specify the amount of formatting retained from + * previous part. * * @param text the text to append * @param retention the formatting to retain @@ -115,8 +161,7 @@ public final class ComponentBuilder BaseComponent old = current; current = new TextComponent( text ); - current.copyFormatting( old ); - retain( retention ); + current.copyFormatting( old, retention, false ); return this; } @@ -247,27 +292,7 @@ public final class ComponentBuilder */ public ComponentBuilder retain(FormatRetention retention) { - BaseComponent previous = current; - - switch ( retention ) - { - case NONE: - current = current.duplicateWithoutFormatting(); - break; - case ALL: - // No changes are required - break; - case EVENTS: - current = current.duplicateWithoutFormatting(); - current.setInsertion( previous.getInsertion() ); - current.setClickEvent( previous.getClickEvent() ); - current.setHoverEvent( previous.getHoverEvent() ); - break; - case FORMATTING: - current.setClickEvent( null ); - current.setHoverEvent( null ); - break; - } + current.retain( retention ); return this; } diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/KeybindComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/KeybindComponent.java index 1fc12600..0bcb06e4 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/KeybindComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/KeybindComponent.java @@ -48,12 +48,6 @@ public final class KeybindComponent extends BaseComponent return new KeybindComponent( this ); } - @Override - public BaseComponent duplicateWithoutFormatting() - { - return new KeybindComponent( keybind ); - } - @Override protected void toPlainText(StringBuilder builder) { diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/ScoreComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/ScoreComponent.java index 591a8fd4..48ef24aa 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/ScoreComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/ScoreComponent.java @@ -82,12 +82,6 @@ public final class ScoreComponent extends BaseComponent return new ScoreComponent( this ); } - @Override - public ScoreComponent duplicateWithoutFormatting() - { - return new ScoreComponent( this.name, this.objective, this.value ); - } - @Override protected void toLegacyText(StringBuilder builder) { diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/SelectorComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/SelectorComponent.java index 1977bd81..3140b322 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/SelectorComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/SelectorComponent.java @@ -48,12 +48,6 @@ public final class SelectorComponent extends BaseComponent return new SelectorComponent( this ); } - @Override - public SelectorComponent duplicateWithoutFormatting() - { - return new SelectorComponent( this.selector ); - } - @Override protected void toLegacyText(StringBuilder builder) { 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 16c90fb3..2f808a85 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 @@ -180,12 +180,6 @@ public final class TextComponent extends BaseComponent return new TextComponent( this ); } - @Override - public BaseComponent duplicateWithoutFormatting() - { - return new TextComponent( this.text ); - } - @Override protected void toPlainText(StringBuilder builder) { 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 958ebbdf..53ee9b0d 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 @@ -94,12 +94,6 @@ public final class TranslatableComponent extends BaseComponent return new TranslatableComponent( this ); } - @Override - public BaseComponent duplicateWithoutFormatting() - { - return new TranslatableComponent( this.translate, this.with ); - } - /** * Sets the translation substitutions to be used in this component. Removes * any previously set substitutions 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 4d94c79e..0b7cf3b4 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 @@ -7,6 +7,7 @@ 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 net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.KeybindComponent; import net.md_5.bungee.api.chat.ScoreComponent; @@ -20,6 +21,7 @@ import java.util.HashSet; public class ComponentSerializer implements JsonDeserializer { + private final static JsonParser JSON_PARSER = new JsonParser(); private final static Gson gson = new GsonBuilder(). registerTypeAdapter( BaseComponent.class, new ComponentSerializer() ). registerTypeAdapter( TextComponent.class, new TextComponentSerializer() ). @@ -33,14 +35,18 @@ public class ComponentSerializer implements JsonDeserializer public static BaseComponent[] parse(String json) { - if ( json.startsWith( "[" ) ) - { //Array - return gson.fromJson( json, BaseComponent[].class ); - } - return new BaseComponent[] + JsonElement jsonElement = JSON_PARSER.parse( json ); + + if ( jsonElement.isJsonArray() ) { - gson.fromJson( json, BaseComponent.class ) - }; + return gson.fromJson( jsonElement, BaseComponent[].class ); + } else + { + return new BaseComponent[] + { + gson.fromJson( jsonElement, BaseComponent.class ) + }; + } } public static String toString(BaseComponent component) @@ -50,7 +56,13 @@ public class ComponentSerializer implements JsonDeserializer public static String toString(BaseComponent... components) { - return gson.toJson( new TextComponent( components ) ); + if ( components.length == 1 ) + { + return gson.toJson( components[0] ); + } else + { + return gson.toJson( new TextComponent( components ) ); + } } @Override diff --git a/chat/src/test/java/net/md_5/bungee/api/chat/ComponentsTest.java b/chat/src/test/java/net/md_5/bungee/api/chat/ComponentsTest.java index 43420f2b..ce56c7f7 100644 --- a/chat/src/test/java/net/md_5/bungee/api/chat/ComponentsTest.java +++ b/chat/src/test/java/net/md_5/bungee/api/chat/ComponentsTest.java @@ -8,6 +8,23 @@ import org.junit.Test; public class ComponentsTest { + @Test + public void testComponentFormatRetention() + { + 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() { @@ -17,6 +34,25 @@ public class ComponentsTest 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" ); + builder.append( new BaseComponent[] { // array based BaseComponent append + 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 testBuilderAppend() {