From ce76e5d5c729ef29d0ba1046c82f2efb69495af7 Mon Sep 17 00:00:00 2001 From: DmitriyMX Date: Tue, 12 May 2020 11:34:40 +0300 Subject: [PATCH] add Text component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit реализация скопирована из mc-core (mc-server, g1/master) --- src/main/java/mc/protocol/text/Text.java | 237 ++++++++++++++++++ src/main/java/mc/protocol/text/TextColor.java | 28 +++ src/main/java/mc/protocol/text/TextStyle.java | 66 +++++ .../java/mc/protocol/text/TextTemplate.java | 146 +++++++++++ src/test/java/mc/protocol/text/TextTest.java | 75 ++++++ 5 files changed, 552 insertions(+) create mode 100644 src/main/java/mc/protocol/text/Text.java create mode 100644 src/main/java/mc/protocol/text/TextColor.java create mode 100644 src/main/java/mc/protocol/text/TextStyle.java create mode 100644 src/main/java/mc/protocol/text/TextTemplate.java create mode 100644 src/test/java/mc/protocol/text/TextTest.java diff --git a/src/main/java/mc/protocol/text/Text.java b/src/main/java/mc/protocol/text/Text.java new file mode 100644 index 0000000..1715d8f --- /dev/null +++ b/src/main/java/mc/protocol/text/Text.java @@ -0,0 +1,237 @@ +package mc.protocol.text; + +import com.google.common.collect.ImmutableList; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.util.*; + +@Getter +public class Text { + private static final Text EMPTY = new Text(); + private static final Text NEW_LINE = new Text("\n", null, null, null); + + private final String content; + private final TextColor color; + private final TextStyle style; + private final ImmutableList children; + + private Text() { + content = ""; + color = null; + style = null; + children = null; + } + + private Text(String content, TextColor color, TextStyle style, ImmutableList children) { + this.content = content; + this.color = color; + this.style = style; + this.children = children; + } + + public boolean isEmpty() { + boolean result = (content == null || content.isEmpty()); + + if (children != null && !children.isEmpty()) { + for (Text child : children) { + result = result && child.isEmpty(); + } + } + + return result; + } + + public String toPlain() { + if (children != null && !children.isEmpty()) { + final StringJoiner sj = new StringJoiner(""); + children.forEach(child -> sj.add(child.toPlain())); + return sj.toString(); + } else { + return content; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Text text = (Text) o; + return Objects.equals(toPlain(), text.toPlain()); + } + + @Override + public int hashCode() { + return Objects.hash(toPlain()); + } + + public static class Builder { + @Getter + private String content; + @Getter + private TextColor color; + @Getter + private TextStyle style; + private List children; + + public Builder() { + this(""); + } + + public Builder(String content) { + this.content = content; + this.color = null; + this.style = null; + this.children = new ArrayList<>(); + } + + public Builder(Object... objects) { + this.children = new ArrayList<>(); + + for(Object obj : objects) { + if (obj instanceof String) { + if (this.content == null) { + this.content = (String) obj; + } else { + this.content = this.content.concat((String) obj); + } + } else if (obj instanceof TextStyle) { + if (this.style == null) { + this.style = TextStyle.none(); + } else { + this.style.merge((TextStyle) obj); + } + } else if (obj instanceof TextColor) { + this.color = (TextColor) obj; + } else if (obj instanceof Text) { + children.add((Text) obj); + } + } + } + + public List getChildren() { + return Collections.unmodifiableList(children); + } + + public Builder color(TextColor color) { + this.color = color; + return this; + } + + public Builder style(TextStyle style) { + if (this.style == null) { + this.style = TextStyle.none(); + } else { + this.style.merge(style); + } + + return this; + } + + public Builder style(TextStyle... styles) { + if (this.style == null) { + this.style = TextStyle.none(); + } + + for(TextStyle style : styles) { + this.style.merge(style); + } + + return this; + } + + public Builder append(String string) { + return append(Text.of(string)); + } + + public Builder append(Text child) { + this.children.add(child); + return this; + } + + public Builder append(Text... children) { + Collections.addAll(this.children, children); + return this; + } + + public Text build() { + if ((children.isEmpty() || (children.size() == 1 && children.get(0) == null)) + && (content == null || content.isEmpty())) { + return Text.EMPTY; + } + + if (children.size() == 1 && children.get(0) != null) { + return children.get(0); + } else { + return new Text( + content, + color, + style, + ImmutableList.copyOf(children) + ); + } + } + } + + public static Builder builder() { + return new Builder(); + } + + public static Builder builder(String content) { + return new Builder(content); + } + + public static Builder builder(Object... objects) { + return new Builder(objects); + } + + public static Text of() { + return EMPTY; + } + + public static Text of(String string) { + if (string == null || string.isEmpty()) { + return EMPTY; + } else if (string.equals("\n")) { + return NEW_LINE; + } else { + return new Text(string, null, null, null); + } + } + + public static Text of(Object... objects) { + TextColor color = null; + TextStyle style = null; + String content = null; + + for(Object obj : objects) { + if (obj instanceof String) { + if (content == null) { + content = (String) obj; + } else { + content = content.concat((String) obj); + } + } else if (obj instanceof TextStyle) { + if (style == null) { + style = (TextStyle) obj; + } else { + style.merge((TextStyle) obj); + } + } else if (obj instanceof TextColor) { + color = (TextColor) obj; + } else if (obj != null){ + if (content == null) { + content = obj.toString(); + } else { + content = content.concat(obj.toString()); + } + } + } + + if (content == null || content.isEmpty()) { + return EMPTY; + } else { + return new Text(content, color, style, null); + } + } +} diff --git a/src/main/java/mc/protocol/text/TextColor.java b/src/main/java/mc/protocol/text/TextColor.java new file mode 100644 index 0000000..d25e187 --- /dev/null +++ b/src/main/java/mc/protocol/text/TextColor.java @@ -0,0 +1,28 @@ +package mc.protocol.text; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum TextColor { + BLACK ("black", '0'), + DARK_BLUE ("dark_blue", '1'), + DARK_GREEN ("dark_green", '2'), + DARK_AQUA ("dark_aqua", '3'), + DARK_RED ("dark_red", '4'), + DARK_PUEPLE("dark_purple", '5'), + GOLD ("gold", '6'), + GRAY ("gray", '7'), + DARK_GRAY ("dark_gray", '8'), + BLUE ("blue", '9'), + GREEN ("green", 'a'), + AQUA ("aqua", 'b'), + RED ("red", 'c'), + PUEPLE ("light_purple",'d'), + YELLOW ("yellow", 'e'), + WHITE ("white", 'f'); + + private final String name; + private final char code; +} diff --git a/src/main/java/mc/protocol/text/TextStyle.java b/src/main/java/mc/protocol/text/TextStyle.java new file mode 100644 index 0000000..7537d4e --- /dev/null +++ b/src/main/java/mc/protocol/text/TextStyle.java @@ -0,0 +1,66 @@ +package mc.protocol.text; + +import lombok.Getter; +import lombok.Setter; + +import javax.annotation.Nullable; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +@Getter +@Setter +public class TextStyle { + public static final TextStyle BOLD = new TextStyle(true, null, null, null, null); + public static final TextStyle ITALIC = new TextStyle(null, true, null, null, null); + public static final TextStyle UNDERLINE = new TextStyle(null, null, true, null, null); + public static final TextStyle STRIKETHOUGH = new TextStyle(null, null, null, true, null); + public static final TextStyle OBFUSCATED = new TextStyle(null, null, null, null, true); + public static final TextStyle RESET = new TextStyle(false, false, false, false, false); + + private static class OptionalBoolean { + private static final Optional TRUE = Optional.of(true); + private static final Optional FALSE = Optional.of(false); + private static final Optional NONE = Optional.empty(); + + static Optional of(boolean bool) { + return bool ? TRUE : FALSE; + } + + static Optional of(@Nullable Boolean bool) { + if (bool != null) { + return of(bool.booleanValue()); + } + return NONE; + } + } + + private Optional bold; + private Optional italic; + private Optional underline; + private Optional strikethrough; + private Optional obfuscated; + + public TextStyle(@Nullable Boolean bold, + @Nullable Boolean italic, + @Nullable Boolean underline, + @Nullable Boolean strikethrough, + @Nullable Boolean obfuscated) { + this.bold = OptionalBoolean.of(bold); + this.italic = OptionalBoolean.of(italic); + this.underline = OptionalBoolean.of(underline); + this.strikethrough = OptionalBoolean.of(strikethrough); + this.obfuscated = OptionalBoolean.of(obfuscated); + } + + public void merge(TextStyle style) { + if (style.bold.isPresent()) this.bold = style.bold; + if (style.italic.isPresent()) this.italic = style.italic; + if (style.underline.isPresent()) this.underline = style.underline; + if (style.strikethrough.isPresent()) this.strikethrough = style.strikethrough; + if (style.obfuscated.isPresent()) this.obfuscated = style.obfuscated; + } + + public static TextStyle none() { + return new TextStyle(null,null,null,null, null); + } +} diff --git a/src/main/java/mc/protocol/text/TextTemplate.java b/src/main/java/mc/protocol/text/TextTemplate.java new file mode 100644 index 0000000..df86c8b --- /dev/null +++ b/src/main/java/mc/protocol/text/TextTemplate.java @@ -0,0 +1,146 @@ +package mc.protocol.text; + +import com.google.common.collect.ImmutableList; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +import java.util.*; + +public class TextTemplate { + private final ImmutableList elements; + + private TextTemplate(ImmutableList elements) { + this.elements = elements; + } + + public Text apply(Object... objects) { + Map variableMap = new HashMap<>((objects.length % 2) == 1 ? (objects.length / 2) + 1 : (objects.length / 2)); + + boolean skipValue = false; + String key = null; + for (Object obj : objects) { + if (skipValue) { + skipValue = false; + continue; + } + + if (key == null) { + if (obj == null || obj.toString().trim().isEmpty()) { + skipValue = true; + continue; + } + + key = obj.toString().trim(); + } else { + variableMap.put(key, obj); + key = null; + } + } + + if (key != null) { + variableMap.put(key, ""); + } + + return apply(variableMap); + } + + public Text apply(Map variables) { + Text.Builder textBuilder = Text.builder(); + + for(Object obj : elements) { + if (obj instanceof Text) { + textBuilder.append((Text) obj); + } else if (obj instanceof Arg) { + Arg arg = (Arg) obj; + if (variables.containsKey(arg.getKey())) { + Object valueObj = variables.get(arg.getKey()); + + if (valueObj instanceof Text) { + textBuilder.append((Text) valueObj); + } else { + textBuilder.append(Text.of(valueObj, arg.getColor(), arg.getStyle())); + } + } else { + textBuilder.append(Text.of(arg.getDefaultValue(), arg.getColor(), arg.getStyle())); + } + } + } + + return textBuilder.build(); + } + + @RequiredArgsConstructor + @Getter + public static class Arg { + private final String key; + private final String defaultValue; + @Setter + private TextColor color; + @Setter + private TextStyle style; + } + + public static class Builder { + private List elements = new ArrayList<>(); + + public Builder append(Text element) { + this.elements.add(element); + return this; + } + + public Builder append(Text... elements) { + Collections.addAll(this.elements, elements); + return this; + } + + public Builder arg(String name) { + this.elements.add(new Arg(name, null)); + return this; + } + + public Builder arg(String name, String defaultValue) { + this.elements.add(new Arg(name, defaultValue)); + return this; + } + + public Builder arg(Object... objects) { + String key = null, + defaultValue = null; + TextColor color = null; + TextStyle style = null; + + for(Object obj : objects) { + if (obj instanceof String) { + if (key == null) { + key = (String) obj; + } else { + defaultValue = (String) obj; + } + } else if (obj instanceof TextColor) { + color = (TextColor) obj; + } else if (obj instanceof TextStyle) { + if (style == null) { + style = TextStyle.none(); + } + style.merge((TextStyle) obj); + } + } + + Arg arg = new Arg(key, defaultValue); + arg.setColor(color); + arg.setStyle(style); + this.elements.add(arg); + + return this; + } + + public TextTemplate build() { + return new TextTemplate(ImmutableList.copyOf(elements)); + } + } + + public static Builder builder() { + return new Builder(); + } +} diff --git a/src/test/java/mc/protocol/text/TextTest.java b/src/test/java/mc/protocol/text/TextTest.java new file mode 100644 index 0000000..0b55fc6 --- /dev/null +++ b/src/test/java/mc/protocol/text/TextTest.java @@ -0,0 +1,75 @@ +package mc.protocol.text; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TextTest { + + @Test + void testToPlain() { + final String m1 = "mes"; + final String m2 = "sage"; + final String message = m1 + m2; + + assertEquals(message, Text.of(message).toPlain()); + assertEquals(message, Text.builder(message).build().toPlain()); + assertEquals(message, Text.builder(Text.of(message)).build().toPlain()); + assertEquals(message, Text.builder().append(message).build().toPlain()); + assertEquals(message, Text.builder().append(Text.of(message)).build().toPlain()); + + assertEquals(message, Text.builder(m1, m2).build().toPlain()); + assertEquals(message, Text.builder(Text.of(m1), Text.of(m2)).build().toPlain()); + assertEquals(message, Text.builder().append(Text.of(m1), Text.of(m2)).build().toPlain()); + assertEquals(message, Text.builder().append(Text.of(m1)).append(Text.of(m2)).build().toPlain()); + } + + @Test + void testEquals() { + assertEquals(Text.of(), Text.of("")); + assertEquals(Text.of(), Text.builder().build()); + assertEquals(Text.of(), Text.builder("").build()); + assertEquals(Text.of(), Text.builder().append().build()); + assertEquals(Text.of(), Text.builder().append("").build()); + + assertNotEquals(Text.of(), Text.of("??")); + assertNotEquals(Text.of(), Text.builder("??").build()); + assertNotEquals(Text.of(), Text.builder().append("??").build()); + + assertEquals(Text.of("message"), Text.builder("message").build()); + assertEquals(Text.of("message"), Text.builder(Text.of("message")).build()); + assertEquals(Text.of("message"), Text.builder().append("message").build()); + assertEquals(Text.of("message"), Text.builder().append(Text.of("message")).build()); + } + + @Test + void testEmpty() { + assertTrue(Text.of().isEmpty()); + assertTrue(Text.of((String) null).isEmpty()); + assertTrue(Text.of((Text) null).isEmpty()); + assertTrue(Text.of("").isEmpty()); + assertTrue(Text.of("", "").isEmpty()); + + assertTrue(Text.builder().build().isEmpty()); + assertTrue(Text.builder((String) null).build().isEmpty()); + assertTrue(Text.builder((Text) null).build().isEmpty()); + assertTrue(Text.builder("").build().isEmpty()); + assertTrue(Text.builder("", "").build().isEmpty()); + assertTrue(Text.builder(Text.of()).build().isEmpty()); + assertTrue(Text.builder(Text.of(), Text.of()).build().isEmpty()); + + assertTrue(Text.builder().append().build().isEmpty()); + assertTrue(Text.builder().append((String) null).build().isEmpty()); + assertTrue(Text.builder().append((Text) null).build().isEmpty()); + assertTrue(Text.builder().append("").build().isEmpty()); + assertTrue(Text.builder().append(Text.of()).build().isEmpty()); + assertTrue(Text.builder().append(Text.of(), Text.of()).build().isEmpty()); + assertTrue(Text.builder().append(Text.of()).append(Text.of()).build().isEmpty()); + + assertFalse(Text.of("??").isEmpty()); + assertFalse(Text.builder("??").build().isEmpty()); + assertFalse(Text.builder(Text.of("??")).build().isEmpty()); + assertFalse(Text.builder().append("??").build().isEmpty()); + assertFalse(Text.builder().append(Text.of("??")).build().isEmpty()); + } +}