Move chat API into own submodule.

This commit is contained in:
md_5
2014-08-31 18:56:03 +10:00
parent 4bb3850b40
commit faa284c8fc
15 changed files with 357 additions and 3 deletions

31
chat/nb-configuration.xml Normal file
View File

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

29
chat/pom.xml Normal file
View File

@@ -0,0 +1,29 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-chat</artifactId>
<version>1.7-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Chat</name>
<description>Minecraft JSON chat API intended for use with BungeeCord</description>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.4</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,191 @@
package net.md_5.bungee.api;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import lombok.Getter;
/**
* Simplistic enumeration of all supported color values for chat.
*/
public enum ChatColor
{
/**
* Represents black.
*/
BLACK( '0', "black" ),
/**
* Represents dark blue.
*/
DARK_BLUE( '1', "dark_blue" ),
/**
* Represents dark green.
*/
DARK_GREEN( '2', "dark_green" ),
/**
* Represents dark blue (aqua).
*/
DARK_AQUA( '3', "dark_aqua" ),
/**
* Represents dark red.
*/
DARK_RED( '4', "dark_red" ),
/**
* Represents dark purple.
*/
DARK_PURPLE( '5', "dark_purple" ),
/**
* Represents gold.
*/
GOLD( '6', "gold" ),
/**
* Represents gray.
*/
GRAY( '7', "gray" ),
/**
* Represents dark gray.
*/
DARK_GRAY( '8', "dark_gray" ),
/**
* Represents blue.
*/
BLUE( '9', "blue" ),
/**
* Represents green.
*/
GREEN( 'a', "green" ),
/**
* Represents aqua.
*/
AQUA( 'b', "aqua" ),
/**
* Represents red.
*/
RED( 'c', "red" ),
/**
* Represents light purple.
*/
LIGHT_PURPLE( 'd', "light_purple" ),
/**
* Represents yellow.
*/
YELLOW( 'e', "yellow" ),
/**
* Represents white.
*/
WHITE( 'f', "white" ),
/**
* Represents magical characters that change around randomly.
*/
MAGIC( 'k', "obfuscated" ),
/**
* Makes the text bold.
*/
BOLD( 'l', "bold" ),
/**
* Makes a line appear through the text.
*/
STRIKETHROUGH( 'm', "strikethrough" ),
/**
* Makes the text appear underlined.
*/
UNDERLINE( 'n', "underline" ),
/**
* Makes the text italic.
*/
ITALIC( 'o', "italic" ),
/**
* Resets all previous chat colors or formats.
*/
RESET( 'r', "reset" );
/**
* The special character which prefixes all chat colour codes. Use this if
* you need to dynamically convert colour codes from your custom format.
*/
public static final char COLOR_CHAR = '\u00A7';
public static final String ALL_CODES = "0123456789AaBbCcDdEeFfKkLlMmNnOoRr";
/**
* Pattern to remove all colour codes.
*/
public static final Pattern STRIP_COLOR_PATTERN = Pattern.compile( "(?i)" + String.valueOf( COLOR_CHAR ) + "[0-9A-FK-OR]" );
/**
* Colour instances keyed by their active character.
*/
private static final Map<Character, ChatColor> BY_CHAR = new HashMap<>();
/**
* The code appended to {@link #COLOR_CHAR} to make usable colour.
*/
private final char code;
/**
* This colour's colour char prefixed by the {@link #COLOR_CHAR}.
*/
private final String toString;
@Getter
private final String name;
static
{
for ( ChatColor colour : values() )
{
BY_CHAR.put( colour.code, colour );
}
}
private ChatColor(char code, String name)
{
this.code = code;
this.name = name;
this.toString = new String( new char[]
{
COLOR_CHAR, code
} );
}
@Override
public String toString()
{
return toString;
}
/**
* Strips the given message of all color codes
*
* @param input String to strip of color
* @return A copy of the input string, without any coloring
*/
public static String stripColor(final String input)
{
if ( input == null )
{
return null;
}
return STRIP_COLOR_PATTERN.matcher( input ).replaceAll( "" );
}
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate)
{
char[] b = textToTranslate.toCharArray();
for ( int i = 0; i < b.length - 1; i++ )
{
if ( b[i] == altColorChar && ALL_CODES.indexOf( b[i + 1] ) > -1 )
{
b[i] = ChatColor.COLOR_CHAR;
b[i + 1] = Character.toLowerCase( b[i + 1] );
}
}
return new String( b );
}
/**
* Get the colour represented by the specified code.
*
* @param code the code to search for
* @return the mapped colour, or null if non exists
*/
public static ChatColor getByChar(char code)
{
return BY_CHAR.get( code );
}
}

View File

@@ -0,0 +1,391 @@
package net.md_5.bungee.api.chat;
import lombok.AccessLevel;
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 lombok.ToString;
@Setter
@ToString
@NoArgsConstructor
public abstract class BaseComponent
{
@Setter(AccessLevel.NONE)
BaseComponent parent;
/**
* The color of this component and any child components (unless overridden)
*/
private ChatColor color;
/**
* Whether this component and any child components (unless overridden) is
* bold
*/
private Boolean bold;
/**
* Whether this component and any child components (unless overridden) is
* italic
*/
private Boolean italic;
/**
* Whether this component and any child components (unless overridden) is
* underlined
*/
private Boolean underlined;
/**
* Whether this component and any child components (unless overridden) is
* strikethrough
*/
private Boolean strikethrough;
/**
* Whether this component and any child components (unless overridden) is
* obfuscated
*/
private Boolean obfuscated;
/**
* Appended components that inherit this component's formatting and events
*/
@Getter
private List<BaseComponent> extra;
/**
* The action to preform when this component (and child components) are
* clicked
*/
@Getter
private ClickEvent clickEvent;
/**
* The action to preform when this component (and child components) are
* hovered over
*/
@Getter
private HoverEvent hoverEvent;
BaseComponent(BaseComponent old)
{
setColor( old.getColorRaw() );
setBold( old.isBoldRaw() );
setItalic( old.isItalicRaw() );
setUnderlined( old.isUnderlinedRaw() );
setStrikethrough( old.isStrikethroughRaw() );
setObfuscated( old.isObfuscatedRaw() );
setClickEvent( old.getClickEvent() );
setHoverEvent( old.getHoverEvent() );
if ( extra != null )
{
for ( BaseComponent component : extra )
{
addExtra( component.duplicate() );
}
}
}
/**
* Clones the BaseComponent and returns the clone.
*
* @return The duplicate of this BaseComponent
*/
public abstract BaseComponent duplicate();
/**
* Converts the components to a string that uses the old formatting codes
* ({@link net.md_5.bungee.api.ChatColor#COLOR_CHAR}
*
* @param components the components to convert
* @return the string in the old format
*/
public static String toLegacyText(BaseComponent... components)
{
StringBuilder builder = new StringBuilder();
for ( BaseComponent msg : components )
{
builder.append( msg.toLegacyText() );
}
return builder.toString();
}
/**
* Converts the components into a string without any formatting
*
* @param components the components to convert
* @return the string as plain text
*/
public static String toPlainText(BaseComponent... components)
{
StringBuilder builder = new StringBuilder();
for ( BaseComponent msg : components )
{
builder.append( msg.toPlainText() );
}
return builder.toString();
}
/**
* Returns the color of this component. This uses the parent's color if this
* component doesn't have one. {@link net.md_5.bungee.api.ChatColor#WHITE}
* is returned if no color is found.
*
* @return the color of this component
*/
public ChatColor getColor()
{
if ( color == null )
{
if ( parent == null )
{
return ChatColor.WHITE;
}
return parent.getColor();
}
return color;
}
/**
* Returns the color of this component without checking the parents color.
* May return null
*
* @return the color of this component
*/
public ChatColor getColorRaw()
{
return color;
}
/**
* 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
* chain has been set.
*
* @return whether the component is bold
*/
public boolean isBold()
{
if ( bold == null )
{
return parent != null && parent.isBold();
}
return bold;
}
/**
* Returns whether this component is bold without checking the parents
* setting. May return null
*
* @return whether the component is bold
*/
public Boolean isBoldRaw()
{
return bold;
}
/**
* Returns whether this component is italic. This uses the parent's setting
* if this component hasn't been set. false is returned if none of the
* parent chain has been set.
*
* @return whether the component is italic
*/
public boolean isItalic()
{
if ( italic == null )
{
return parent != null && parent.isItalic();
}
return italic;
}
/**
* Returns whether this component is italic without checking the parents
* setting. May return null
*
* @return whether the component is italic
*/
public Boolean isItalicRaw()
{
return italic;
}
/**
* Returns whether this component is underlined. This uses the parent's
* setting if this component hasn't been set. false is returned if none of
* the parent chain has been set.
*
* @return whether the component is underlined
*/
public boolean isUnderlined()
{
if ( underlined == null )
{
return parent != null && parent.isUnderlined();
}
return underlined;
}
/**
* Returns whether this component is underlined without checking the parents
* setting. May return null
*
* @return whether the component is underlined
*/
public Boolean isUnderlinedRaw()
{
return underlined;
}
/**
* Returns whether this component is strikethrough. This uses the parent's
* setting if this component hasn't been set. false is returned if none of
* the parent chain has been set.
*
* @return whether the component is strikethrough
*/
public boolean isStrikethrough()
{
if ( strikethrough == null )
{
return parent != null && parent.isStrikethrough();
}
return strikethrough;
}
/**
* Returns whether this component is strikethrough without checking the
* parents setting. May return null
*
* @return whether the component is strikethrough
*/
public Boolean isStrikethroughRaw()
{
return strikethrough;
}
/**
* Returns whether this component is obfuscated. This uses the parent's
* setting if this component hasn't been set. false is returned if none of
* the parent chain has been set.
*
* @return whether the component is obfuscated
*/
public boolean isObfuscated()
{
if ( obfuscated == null )
{
return parent != null && parent.isObfuscated();
}
return obfuscated;
}
/**
* Returns whether this component is obfuscated without checking the parents
* setting. May return null
*
* @return whether the component is obfuscated
*/
public Boolean isObfuscatedRaw()
{
return obfuscated;
}
public void setExtra(List<BaseComponent> components)
{
for ( BaseComponent component : components )
{
component.parent = this;
}
extra = components;
}
/**
* Appends a text element to the component. The text will inherit this
* component's formatting
*
* @param text the text to append
*/
public void addExtra(String text)
{
addExtra( new TextComponent( text ) );
}
/**
* Appends a component to the component. The text will inherit this
* component's formatting
*
* @param component the component to append
*/
public void addExtra(BaseComponent component)
{
if ( extra == null )
{
extra = new ArrayList<>();
}
component.parent = this;
extra.add( component );
}
/**
* Returns whether the component has any formatting or events applied to it
*
* @return Whether any formatting or events are applied
*/
public boolean hasFormatting()
{
return color != null || bold != null
|| italic != null || underlined != null
|| strikethrough != null || obfuscated != null
|| hoverEvent != null || clickEvent != null;
}
/**
* Converts the component into a string without any formatting
*
* @return the string as plain text
*/
public String toPlainText()
{
StringBuilder builder = new StringBuilder();
toPlainText( builder );
return builder.toString();
}
void toPlainText(StringBuilder builder)
{
if ( extra != null )
{
for ( BaseComponent e : extra )
{
e.toPlainText( builder );
}
}
}
/**
* Converts the component to a string that uses the old formatting codes
* ({@link net.md_5.bungee.api.ChatColor#COLOR_CHAR}
*
* @return the string in the old format
*/
public String toLegacyText()
{
StringBuilder builder = new StringBuilder();
toLegacyText( builder );
return builder.toString();
}
void toLegacyText(StringBuilder builder)
{
if ( extra != null )
{
for ( BaseComponent e : extra )
{
e.toLegacyText( builder );
}
}
}
}

View File

@@ -0,0 +1,50 @@
package net.md_5.bungee.api.chat;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import net.md_5.bungee.api.chat.ClickEvent.Action;
@Getter
@ToString
@RequiredArgsConstructor
public final class ClickEvent
{
/**
* The type of action to preform on click
*/
private final Action action;
/**
* Depends on action
*
* @see Action
*/
private final String value;
public enum Action
{
/**
* Open a url at the path given by
* {@link net.md_5.bungee.api.chat.ClickEvent#value}
*/
OPEN_URL,
/**
* Open a file at the path given by
* {@link net.md_5.bungee.api.chat.ClickEvent#value}
*/
OPEN_FILE,
/**
* Run the command given by
* {@link net.md_5.bungee.api.chat.ClickEvent#value}
*/
RUN_COMMAND,
/**
* Inserts the string given by
* {@link net.md_5.bungee.api.chat.ClickEvent#value} into the players
* text box
*/
SUGGEST_COMMAND
}
}

View File

@@ -0,0 +1,178 @@
package net.md_5.bungee.api.chat;
import net.md_5.bungee.api.ChatColor;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* ComponentBuilder simplifies creating basic messages by allowing the use of a
* chainable builder.
* </p>
* <pre>
* new ComponentBuilder("Hello ").color(ChatColor.RED).
* append("World").color(ChatColor.BLUE). append("!").bold(true).create();
* </pre>
* <p>
* All methods (excluding {@link #append(String)} and {@link #create()} work on
* the last part appended to the builder, so in the example above "Hello " would
* be {@link net.md_5.bungee.api.ChatColor#RED} and "World" would be
* {@link net.md_5.bungee.api.ChatColor#BLUE} but "!" would be bold and
* {@link net.md_5.bungee.api.ChatColor#BLUE} because append copies the previous
* part's formatting
* </p>
*/
public class ComponentBuilder
{
private TextComponent current;
private final List<BaseComponent> parts = new ArrayList<>();
/**
* Creates a ComponentBuilder from the other given ComponentBuilder to clone
* it.
*
* @param original the original for the new ComponentBuilder.
*/
public ComponentBuilder(ComponentBuilder original)
{
current = new TextComponent( original.current );
for ( BaseComponent baseComponent : original.parts )
{
parts.add( baseComponent.duplicate() );
}
}
/**
* Creates a ComponentBuilder with the given text as the first part.
*
* @param text the first text element
*/
public ComponentBuilder(String text)
{
current = new TextComponent( text );
}
/**
* 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.
*
* @param text the text to append
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder append(String text)
{
parts.add( current );
current = new TextComponent( current );
current.setText( text );
return this;
}
/**
* Sets the color of the current part.
*
* @param color the new color
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder color(ChatColor color)
{
current.setColor( color );
return this;
}
/**
* Sets whether the current part is bold.
*
* @param bold whether this part is bold
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder bold(boolean bold)
{
current.setBold( bold );
return this;
}
/**
* Sets whether the current part is italic.
*
* @param italic whether this part is italic
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder italic(boolean italic)
{
current.setItalic( italic );
return this;
}
/**
* Sets whether the current part is underlined.
*
* @param underlined whether this part is underlined
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder underlined(boolean underlined)
{
current.setUnderlined( underlined );
return this;
}
/**
* Sets whether the current part is strikethrough.
*
* @param strikethrough whether this part is strikethrough
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder strikethrough(boolean strikethrough)
{
current.setStrikethrough( strikethrough );
return this;
}
/**
* Sets whether the current part is obfuscated.
*
* @param obfuscated whether this part is obfuscated
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder obfuscated(boolean obfuscated)
{
current.setObfuscated( obfuscated );
return this;
}
/**
* Sets the click event for the current part.
*
* @param clickEvent the click event
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder event(ClickEvent clickEvent)
{
current.setClickEvent( clickEvent );
return this;
}
/**
* Sets the hover event for the current part.
*
* @param hoverEvent the hover event
* @return this ComponentBuilder for chaining
*/
public ComponentBuilder event(HoverEvent hoverEvent)
{
current.setHoverEvent( hoverEvent );
return this;
}
/**
* Returns the components needed to display the message created by this
* builder.
*
* @return the created components
*/
public BaseComponent[] create()
{
parts.add( current );
return parts.toArray( new BaseComponent[ parts.size() ] );
}
}

View File

@@ -0,0 +1,23 @@
package net.md_5.bungee.api.chat;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
@Getter
@ToString
@RequiredArgsConstructor
final public class HoverEvent
{
private final Action action;
private final BaseComponent[] value;
public enum Action
{
SHOW_TEXT,
SHOW_ACHIEVEMENT,
SHOW_ITEM
}
}

View File

@@ -0,0 +1,213 @@
package net.md_5.bungee.api.chat;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.md_5.bungee.api.ChatColor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class TextComponent extends BaseComponent
{
private static final Pattern url = Pattern.compile( "^(?:(https?)://)?([-\\w_\\.]{2,}\\.[a-z]{2,4})(/\\S*)?$" );
/**
* Converts the old formatting system that used
* {@link net.md_5.bungee.api.ChatColor#COLOR_CHAR} into the new json based
* system.
*
* @param message the text to convert
* @return the components needed to print the message to the client
*/
public static BaseComponent[] fromLegacyText(String message)
{
ArrayList<BaseComponent> components = new ArrayList<>();
StringBuilder builder = new StringBuilder();
TextComponent component = new TextComponent();
Matcher matcher = url.matcher( message );
for ( int i = 0; i < message.length(); i++ )
{
char c = message.charAt( i );
if ( c == ChatColor.COLOR_CHAR )
{
i++;
c = message.charAt( i );
if ( c >= 'A' && c <= 'Z' )
{
c += 32;
}
ChatColor format = ChatColor.getByChar( c );
if ( format == null )
{
continue;
}
if ( builder.length() > 0 )
{
TextComponent old = component;
component = new TextComponent( old );
old.setText( builder.toString() );
builder = new StringBuilder();
components.add( old );
}
switch ( format )
{
case BOLD:
component.setBold( true );
break;
case ITALIC:
component.setItalic( true );
break;
case UNDERLINE:
component.setUnderlined( true );
break;
case STRIKETHROUGH:
component.setStrikethrough( true );
break;
case MAGIC:
component.setObfuscated( true );
break;
case RESET:
format = ChatColor.WHITE;
default:
component = new TextComponent();
component.setColor( format );
break;
}
continue;
}
int pos = message.indexOf( ' ', i );
if ( pos == -1 )
{
pos = message.length();
}
if ( matcher.region( i, pos ).find() )
{ //Web link handling
if ( builder.length() > 0 )
{
TextComponent old = component;
component = new TextComponent( old );
old.setText( builder.toString() );
builder = new StringBuilder();
components.add( old );
}
TextComponent old = component;
component = new TextComponent( old );
String urlString = message.substring( i, pos );
component.setText( urlString );
component.setClickEvent( new ClickEvent( ClickEvent.Action.OPEN_URL,
urlString.startsWith( "http" ) ? urlString : "http://" + urlString ) );
components.add( component );
i += pos - i - 1;
component = old;
continue;
}
builder.append( c );
}
if ( builder.length() > 0 )
{
component.setText( builder.toString() );
components.add( component );
}
// The client will crash if the array is empty
if ( components.isEmpty() )
{
components.add( new TextComponent( "" ) );
}
return components.toArray( new BaseComponent[ components.size() ] );
}
/**
* The text of the component that will be displayed to the client
*/
private String text;
/**
* Creates a TextComponent with formatting and text from the passed
* component
*
* @param textComponent the component to copy from
*/
public TextComponent(TextComponent textComponent)
{
super( textComponent );
setText( textComponent.getText() );
}
/**
* Creates a TextComponent with blank text and the extras set to the passed
* array
*
* @param extras the extras to set
*/
public TextComponent(BaseComponent... extras)
{
setText( "" );
setExtra( Arrays.asList( extras ) );
}
/**
* Creates a duplicate of this TextComponent.
*
* @return the duplicate of this TextComponent.
*/
@Override
public BaseComponent duplicate()
{
return new TextComponent( this );
}
@Override
protected void toPlainText(StringBuilder builder)
{
builder.append( text );
super.toPlainText( builder );
}
@Override
protected void toLegacyText(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 );
}
builder.append( text );
super.toLegacyText( builder );
}
@Override
public String toString()
{
return String.format( "TextComponent{text=%s, %s}", text, super.toString() );
}
}

View File

@@ -0,0 +1,243 @@
package net.md_5.bungee.api.chat;
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.regex.Matcher;
import java.util.regex.Pattern;
import lombok.ToString;
@Getter
@Setter
@ToString
@NoArgsConstructor
public class TranslatableComponent extends BaseComponent
{
private final ResourceBundle locales = ResourceBundle.getBundle( "mojang-translations/en_US" );
private final Pattern format = Pattern.compile( "%(?:(\\d+)\\$)?([A-Za-z%]|$)" );
/**
* The key into the Minecraft locale files to use for the translation. The
* text depends on the client's locale setting. The console is always en_US
*/
private String translate;
/**
* The components to substitute into the translation
*/
private List<BaseComponent> with;
/**
* Creates a translatable component from the original to clone it.
*
* @param original the original for the new translatable component.
*/
public TranslatableComponent(TranslatableComponent original)
{
super( original );
setTranslate( original.getTranslate() );
for ( BaseComponent baseComponent : original.getWith() )
{
with.add( baseComponent.duplicate() );
}
}
/**
* Creates a translatable component with the passed substitutions
*
* @see #translate
* @see #setWith(java.util.List)
* @param translate the translation key
* @param with the {@link java.lang.String}s and
* {@link net.md_5.bungee.api.chat.BaseComponent}s to use into the
* translation
*/
public TranslatableComponent(String translate, Object... with)
{
setTranslate( translate );
List<BaseComponent> temp = new ArrayList<>();
for ( Object w : with )
{
if ( w instanceof String )
{
temp.add( new TextComponent( (String) w ) );
} else
{
temp.add( (BaseComponent) w );
}
}
setWith( temp );
}
/**
* Creates a duplicate of this TranslatableComponent.
*
* @return the duplicate of this TranslatableComponent.
*/
@Override
public BaseComponent duplicate()
{
return new TranslatableComponent( this );
}
/**
* Sets the translation substitutions to be used in this component. Removes
* any previously set substitutions
*
* @param components the components to substitute
*/
public void setWith(List<BaseComponent> components)
{
for ( BaseComponent component : components )
{
component.parent = this;
}
with = components;
}
/**
* Adds a text substitution to the component. The text will inherit this
* component's formatting
*
* @param text the text to substitute
*/
public void addWith(String text)
{
addWith( new TextComponent( text ) );
}
/**
* Adds a component substitution to the component. The text will inherit
* this component's formatting
*
* @param component the component to substitute
*/
public void addWith(BaseComponent component)
{
if ( with == null )
{
with = new ArrayList<>();
}
component.parent = this;
with.add( component );
}
@Override
protected void toPlainText(StringBuilder builder)
{
try
{
String trans = locales.getString( translate );
Matcher matcher = format.matcher( trans );
int position = 0;
int i = 0;
while ( matcher.find( position ) )
{
int pos = matcher.start();
if ( pos != position )
{
builder.append( trans.substring( position, pos ) );
}
position = matcher.end();
String formatCode = matcher.group( 2 );
switch ( formatCode.charAt( 0 ) )
{
case 's':
case 'd':
String withIndex = matcher.group( 1 );
with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ ).toPlainText( builder );
break;
case '%':
builder.append( '%' );
break;
}
}
if ( trans.length() != position )
{
builder.append( trans.substring( position, trans.length() ) );
}
} catch ( MissingResourceException e )
{
builder.append( translate );
}
super.toPlainText( builder );
}
@Override
protected void toLegacyText(StringBuilder builder)
{
try
{
String trans = locales.getString( translate );
Matcher matcher = format.matcher( trans );
int position = 0;
int i = 0;
while ( matcher.find( position ) )
{
int pos = matcher.start();
if ( pos != position )
{
addFormat( builder );
builder.append( trans.substring( position, pos ) );
}
position = matcher.end();
String formatCode = matcher.group( 2 );
switch ( formatCode.charAt( 0 ) )
{
case 's':
case 'd':
String withIndex = matcher.group( 1 );
with.get( withIndex != null ? Integer.parseInt( withIndex ) - 1 : i++ ).toLegacyText( builder );
break;
case '%':
addFormat( builder );
builder.append( '%' );
break;
}
}
if ( trans.length() != position )
{
addFormat( builder );
builder.append( trans.substring( position, trans.length() ) );
}
} catch ( MissingResourceException e )
{
addFormat( builder );
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 );
}
}
}

View File

@@ -0,0 +1,141 @@
package net.md_5.bungee.chat;
import com.google.common.base.Preconditions;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import java.util.Arrays;
import java.util.HashSet;
public class BaseComponentSerializer
{
protected void deserialize(JsonObject object, BaseComponent component, JsonDeserializationContext context)
{
if ( object.has( "color" ) )
{
component.setColor( ChatColor.valueOf( object.get( "color" ).getAsString().toUpperCase() ) );
}
if ( object.has( "bold" ) )
{
component.setBold( object.get( "bold" ).getAsBoolean() );
}
if ( object.has( "italic" ) )
{
component.setItalic( object.get( "italic" ).getAsBoolean() );
}
if ( object.has( "underlined" ) )
{
component.setUnderlined( object.get( "underlined" ).getAsBoolean() );
}
if ( object.has( "strikethrough" ) )
{
component.setUnderlined( object.get( "strikethrough" ).getAsBoolean() );
}
if ( object.has( "obfuscated" ) )
{
component.setUnderlined( object.get( "obfuscated" ).getAsBoolean() );
}
if ( object.has( "extra" ) )
{
component.setExtra( Arrays.<BaseComponent>asList( context.<BaseComponent[]>deserialize( object.get( "extra" ), BaseComponent[].class ) ) );
}
//Events
if ( object.has( "clickEvent" ) )
{
JsonObject event = object.getAsJsonObject( "clickEvent" );
component.setClickEvent( new ClickEvent(
ClickEvent.Action.valueOf( event.get( "action" ).getAsString().toUpperCase() ),
event.get( "value" ).getAsString() ) );
}
if ( object.has( "hoverEvent" ) )
{
JsonObject event = object.getAsJsonObject( "hoverEvent" );
BaseComponent[] res;
if ( event.get( "value" ).isJsonArray() )
{
res = context.deserialize( event.get( "value" ), BaseComponent[].class );
} else
{
res = new BaseComponent[]
{
context.<BaseComponent>deserialize( event.get( "value" ), BaseComponent.class )
};
}
component.setHoverEvent( new HoverEvent( HoverEvent.Action.valueOf( event.get( "action" ).getAsString().toUpperCase() ), res ) );
}
}
protected void serialize(JsonObject object, BaseComponent component, JsonSerializationContext context)
{
boolean first = false;
if ( ComponentSerializer.serializedComponents.get() == null )
{
first = true;
ComponentSerializer.serializedComponents.set( new HashSet<BaseComponent>() );
}
try
{
Preconditions.checkArgument( !ComponentSerializer.serializedComponents.get().contains( component ), "Component loop" );
ComponentSerializer.serializedComponents.get().add( component );
if ( component.getColorRaw() != null )
{
object.addProperty( "color", component.getColorRaw().getName() );
}
if ( component.isBoldRaw() != null )
{
object.addProperty( "bold", component.isBoldRaw() );
}
if ( component.isItalicRaw() != null )
{
object.addProperty( "italic", component.isItalicRaw() );
}
if ( component.isUnderlinedRaw() != null )
{
object.addProperty( "underlined", component.isUnderlinedRaw() );
}
if ( component.isStrikethroughRaw() != null )
{
object.addProperty( "strikethrough", component.isStrikethroughRaw() );
}
if ( component.isObfuscatedRaw() != null )
{
object.addProperty( "obfuscated", component.isObfuscatedRaw() );
}
if ( component.getExtra() != null )
{
object.add( "extra", context.serialize( component.getExtra() ) );
}
//Events
if ( component.getClickEvent() != null )
{
JsonObject clickEvent = new JsonObject();
clickEvent.addProperty( "action", component.getClickEvent().getAction().toString().toLowerCase() );
clickEvent.addProperty( "value", component.getClickEvent().getValue() );
object.add( "clickEvent", clickEvent );
}
if ( component.getHoverEvent() != null )
{
JsonObject hoverEvent = new JsonObject();
hoverEvent.addProperty( "action", component.getHoverEvent().getAction().toString().toLowerCase() );
hoverEvent.add( "value", context.serialize( component.getHoverEvent().getValue() ) );
object.add( "hoverEvent", hoverEvent );
}
} finally
{
ComponentSerializer.serializedComponents.get().remove( component );
if ( first )
{
ComponentSerializer.serializedComponents.set( null );
}
}
}
}

View File

@@ -0,0 +1,64 @@
package net.md_5.bungee.chat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import java.lang.reflect.Type;
import java.util.HashSet;
public class ComponentSerializer implements JsonDeserializer<BaseComponent>
{
private final static Gson gson = new GsonBuilder().
registerTypeAdapter( BaseComponent.class, new ComponentSerializer() ).
registerTypeAdapter( TextComponent.class, new TextComponentSerializer() ).
registerTypeAdapter( TranslatableComponent.class, new TranslatableComponentSerializer() ).
create();
public final static ThreadLocal<HashSet<BaseComponent>> serializedComponents = new ThreadLocal<>();
public static BaseComponent[] parse(String json)
{
if ( json.startsWith( "[" ) )
{ //Array
return gson.fromJson( json, BaseComponent[].class );
}
return new BaseComponent[]
{
gson.fromJson( json, BaseComponent.class )
};
}
public static String toString(BaseComponent component)
{
return gson.toJson( component );
}
public static String toString(BaseComponent... components)
{
return gson.toJson( new TextComponent( components ) );
}
@Override
public BaseComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
if ( json.isJsonPrimitive() )
{
return new TextComponent( json.getAsString() );
}
JsonObject object = json.getAsJsonObject();
if ( object.has( "translate" ) )
{
return context.deserialize( json, TranslatableComponent.class );
}
return context.deserialize( json, TextComponent.class );
}
}

View File

@@ -0,0 +1,43 @@
package net.md_5.bungee.chat;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import java.lang.reflect.Type;
import java.util.List;
public class TextComponentSerializer extends BaseComponentSerializer implements JsonSerializer<TextComponent>, JsonDeserializer<TextComponent>
{
@Override
public TextComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
TextComponent component = new TextComponent();
JsonObject object = json.getAsJsonObject();
deserialize( object, component, context );
component.setText( object.get( "text" ).getAsString() );
return component;
}
@Override
public JsonElement serialize(TextComponent src, Type typeOfSrc, JsonSerializationContext context)
{
List<BaseComponent> extra = src.getExtra();
if ( !src.hasFormatting() && ( extra == null || extra.isEmpty() ) )
{
return new JsonPrimitive( src.getText() );
}
JsonObject object = new JsonObject();
serialize( object, src, context );
object.addProperty( "text", src.getText() );
return object;
}
}

View File

@@ -0,0 +1,45 @@
package net.md_5.bungee.chat;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import java.lang.reflect.Type;
import java.util.Arrays;
public class TranslatableComponentSerializer extends BaseComponentSerializer implements JsonSerializer<TranslatableComponent>, JsonDeserializer<TranslatableComponent>
{
@Override
public TranslatableComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
TranslatableComponent component = new TranslatableComponent();
JsonObject object = json.getAsJsonObject();
deserialize( object, component, context );
component.setTranslate( object.get( "translate" ).getAsString() );
if ( object.has( "with" ) )
{
component.setWith( Arrays.asList( (BaseComponent[]) context.deserialize( object.get( "with" ), BaseComponent[].class ) ) );
}
return component;
}
@Override
public JsonElement serialize(TranslatableComponent src, Type typeOfSrc, JsonSerializationContext context)
{
JsonObject object = new JsonObject();
serialize( object, src, context );
object.addProperty( "translate", src.getTranslate() );
if ( src.getWith() != null )
{
object.add( "with", context.serialize( src.getWith() ) );
}
return object;
}
}