0

Merge branch 'feature/text-component' into development

This commit is contained in:
2020-05-13 15:59:12 +03:00
10 changed files with 694 additions and 21 deletions

View File

@@ -2,15 +2,16 @@ package mc.protocol.dto;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRawValue;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import lombok.ToString;
import mc.protocol.text.Text;
import java.util.List;
import java.util.UUID;
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
@ToString(exclude = "faviconBase64")
public class ServerInfo {
private Version version;
@@ -18,8 +19,7 @@ public class ServerInfo {
@JsonProperty("players")
private PlayersInfo playersInfo;
//TODO необходимо реализовать объект типа Chat (см. https://wiki.vg/index.php?title=Chat&oldid=8329)
private JsonNode description;
private Text description;
@JsonProperty("favicon")
private String faviconBase64;

View File

@@ -1,12 +1,11 @@
package mc.protocol.status.server;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import mc.protocol.Packet;
import mc.protocol.dto.ServerInfo;
import mc.protocol.io.NetInputStream;
import mc.protocol.io.NetOutputStream;
import mc.protocol.utils.json.JsonUtils;
/**
* Status server packet, response.
@@ -58,13 +57,8 @@ public class StatusServerResponse implements Packet {
public ServerInfo getServerInfoDto() {
if (serverInfoDto == null) {
try {
ObjectMapper mapper = new ObjectMapper();
serverInfoDto = mapper.readValue(serverInfo, ServerInfo.class);
} catch (JsonProcessingException e) {
e.printStackTrace();
return new ServerInfo();
}
JsonUtils.jsonToObject(serverInfo, ServerInfo.class)
.ifPresent(serverInfoDto -> this.serverInfoDto = serverInfoDto);
}
return serverInfoDto;
@@ -78,13 +72,7 @@ public class StatusServerResponse implements Packet {
@Override
public void writeSelf(NetOutputStream netOutputStream) {
if (serverInfo == null) {
try {
ObjectMapper mapper = new ObjectMapper();
serverInfo = mapper.writeValueAsString(serverInfoDto);
} catch (JsonProcessingException e) {
e.printStackTrace();
serverInfo = "{}";
}
serverInfo = JsonUtils.objectToJson(serverInfo);
}
netOutputStream.writeString(serverInfo);

View File

@@ -0,0 +1,238 @@
package mc.protocol.text;
import com.google.common.collect.ImmutableList;
import lombok.Getter;
import lombok.ToString;
import java.util.*;
@Getter
@ToString
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<Text> children;
private Text() {
content = "";
color = null;
style = null;
children = null;
}
private Text(String content, TextColor color, TextStyle style, ImmutableList<Text> 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<Text> 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<Text> 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);
}
}
}

View File

@@ -0,0 +1,36 @@
package mc.protocol.text;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.stream.Stream;
@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');
public static TextColor valueOfColorName(String name) {
return Stream.of(TextColor.values())
.filter(textColor -> textColor.getName().equals(name))
.findFirst().orElse(null);
}
private final String name;
private final char code;
}

View File

@@ -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<Boolean> TRUE = Optional.of(true);
private static final Optional<Boolean> FALSE = Optional.of(false);
private static final Optional<Boolean> NONE = Optional.empty();
static Optional<Boolean> of(boolean bool) {
return bool ? TRUE : FALSE;
}
static Optional<Boolean> of(@Nullable Boolean bool) {
if (bool != null) {
return of(bool.booleanValue());
}
return NONE;
}
}
private Optional<Boolean> bold;
private Optional<Boolean> italic;
private Optional<Boolean> underline;
private Optional<Boolean> strikethrough;
private Optional<Boolean> 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);
}
}

View File

@@ -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<Object> elements;
private TextTemplate(ImmutableList<Object> elements) {
this.elements = elements;
}
public Text apply(Object... objects) {
Map<String, Object> 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<String, Object> 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<Object> 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();
}
}

View File

@@ -1,4 +1,4 @@
package mc.protocol;
package mc.protocol.utils;
import lombok.experimental.UtilityClass;

View File

@@ -0,0 +1,55 @@
package mc.protocol.utils.json;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import mc.protocol.text.Text;
import mc.protocol.utils.json.serializer.TextDeserializer;
import java.util.Optional;
@Slf4j
@UtilityClass
public class JsonUtils {
private final String EMPTY_OBJECT = "{}";
private ObjectMapper objectMapper;
public String objectToJson(Object object) {
try {
return getObjectMapper().writeValueAsString(object);
} catch (JsonProcessingException e) {
if (log.isDebugEnabled()) {
log.debug("Error serialize object {}", object, e);
}
return EMPTY_OBJECT;
}
}
public <T> Optional<T> jsonToObject(String json, Class<T> returnType) {
Optional<T> result;
try {
result = Optional.of(getObjectMapper().readValue(json, returnType));
} catch (JsonProcessingException e1) {
e1.printStackTrace();
result = Optional.empty();
}
return result;
}
public ObjectMapper getObjectMapper() {
if (objectMapper == null) {
SimpleModule module = new SimpleModule();
module.addDeserializer(Text.class, new TextDeserializer());
objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
}
return objectMapper;
}
}

View File

@@ -0,0 +1,69 @@
package mc.protocol.utils.json.serializer;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import mc.protocol.text.Text;
import mc.protocol.text.TextColor;
import mc.protocol.text.TextStyle;
import java.io.IOException;
import java.util.Optional;
public class TextDeserializer extends StdDeserializer<Text> {
public TextDeserializer() {
this(null);
}
public TextDeserializer(Class<Text> t) {
super(t);
}
@Override
public Text deserialize(JsonParser parser, DeserializationContext ctx) throws IOException {
final Text.Builder builder = Text.builder();
final JsonNode jsonNode = parser.getCodec().readTree(parser);
Optional.ofNullable(jsonNode.get("text"))
.ifPresent(node -> builder.append(node.asText()));
Optional.ofNullable(jsonNode.get("color"))
.ifPresent(node -> builder.color(TextColor.valueOfColorName(node.asText())));
if (jsonNode.get("bold") != null && jsonNode.get("bold").isBoolean()
&& jsonNode.get("bold").asBoolean()) {
builder.style(TextStyle.BOLD);
}
if (jsonNode.get("italic") != null && jsonNode.get("italic").isBoolean()
&& jsonNode.get("italic").asBoolean()) {
builder.style(TextStyle.ITALIC);
}
if (jsonNode.get("obfuscated") != null && jsonNode.get("obfuscated").isBoolean()
&& jsonNode.get("obfuscated").asBoolean()) {
builder.style(TextStyle.OBFUSCATED);
}
if (jsonNode.get("strikethrough") != null && jsonNode.get("strikethrough").isBoolean()
&& jsonNode.get("strikethrough").asBoolean()) {
builder.style(TextStyle.STRIKETHOUGH);
}
if (jsonNode.get("underlined") != null && jsonNode.get("underlined").isBoolean()
&& jsonNode.get("underlined").asBoolean()) {
builder.style(TextStyle.UNDERLINE);
}
if (jsonNode.get("extra") != null && jsonNode.get("extra").isArray()) {
final JsonNode nodeExtra = jsonNode.get("extra");
for (JsonNode node : nodeExtra) {
builder.append(parser.getCodec().treeToValue(node, Text.class));
}
}
return builder.build();
}
}

View File

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