#2363: Chat Component API Improvements

- duplicateWithoutFormatting deprecated and now works to include extra. Less maintenance required for any component implementations.
- Improved copyFormatting API to allow for retention copying.
- API to append a single BaseComponent in a ComponentBuilder, previously had to wrap a
component in its own array to do this.
- BaseComponent retain API that functions the same as from
ComponentBuilder.
This commit is contained in:
Joe 2018-03-04 20:34:25 +00:00 committed by md_5
parent 74e077e0fb
commit 7653a5f0f8
9 changed files with 226 additions and 86 deletions

View File

@ -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<BaseComponent> 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)
if ( old.getExtra() != null )
{
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 )
{
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

View File

@ -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;
}

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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

View File

@ -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<BaseComponent>
{
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,15 +35,19 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
public static BaseComponent[] parse(String json)
{
if ( json.startsWith( "[" ) )
{ //Array
return gson.fromJson( json, BaseComponent[].class );
}
JsonElement jsonElement = JSON_PARSER.parse( json );
if ( jsonElement.isJsonArray() )
{
return gson.fromJson( jsonElement, BaseComponent[].class );
} else
{
return new BaseComponent[]
{
gson.fromJson( json, BaseComponent.class )
gson.fromJson( jsonElement, BaseComponent.class )
};
}
}
public static String toString(BaseComponent component)
{
@ -49,9 +55,15 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
}
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

View File

@ -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()
{