package net.glowstone.util;
import lombok.EqualsAndHashCode;
import org.apache.commons.lang.Validate;
import org.bukkit.ChatColor;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* Simple container for chat message structures until more advanced chat
* formatting is implemented.
*/
@EqualsAndHashCode
public final class TextMessage {
/**
* The formatting ChatColors.
*/
private static final ChatColor[] FORMATTING = {
ChatColor.MAGIC, ChatColor.BOLD, ChatColor.STRIKETHROUGH, ChatColor.UNDERLINE, ChatColor.ITALIC
};
/**
* The JSON structure of this text message.
*/
private final JSONObject object;
/**
* Construct a new chat message from a simple text string. Handles style
* and colors in the original string, converting them to the new format.
* @param text The text of the message.
*/
public TextMessage(String text) {
object = convert(text);
}
/**
* Construct a chat message from a JSON structure. No validation occurs.
* @param object The JSON structure of the message.
*/
public TextMessage(JSONObject object) {
Validate.notNull(object, "object must not be null");
this.object = object;
}
/**
* Encode this chat message to its textual JSON representation.
* @return The encoded representation.
*/
public String encode() {
return object.toJSONString();
}
/**
* Attempt to convert the message to its plaintext representation.
* @return The plain text, or the empty string on failure.
*/
public String asPlaintext() {
if (object.containsKey("text")) {
Object obj = object.get("text");
if (obj instanceof String) {
return (String) obj;
}
}
return "";
}
/**
* Flatten this message to an approximate old-style string representation.
* @return The best old-style string representation for this message.
*/
public String flatten() {
StringBuilder builder = new StringBuilder();
flatten(builder, object);
return builder.toString();
}
private static void flatten(StringBuilder dest, JSONObject obj) {
if (obj.containsKey("color")) {
try {
dest.append(ChatColor.valueOf(obj.get("color").toString().toUpperCase()));
} catch (IllegalArgumentException ex) {
// invalid color, ignore
}
}
for (ChatColor format : FORMATTING) {
String name = format == ChatColor.MAGIC ? "obfuscated" : format.name().toLowerCase();
if (obj.containsKey(name) && obj.get(name).equals(true)) {
dest.append(format);
}
}
if (obj.containsKey("text")) {
dest.append(obj.get("text").toString());
}
if (obj.containsKey("extra")) {
JSONArray array = (JSONArray) obj.get("extra");
for (Object o : array) {
if (o instanceof JSONObject) {
flatten(dest, (JSONObject) o);
} else {
dest.append(o);
}
}
}
}
@Override
public String toString() {
return "Message" + encode();
}
/**
* Decode a chat message from its textual JSON representation if possible.
* @param json The encoded representation.
* @return The decoded TextMessage.
*/
public static TextMessage decode(String json) {
JSONParser parser = new JSONParser();
try {
Object o = parser.parse(json);
if (o instanceof JSONObject) {
return new TextMessage((JSONObject) o);
} else {
return new TextMessage(o.toString());
}
} catch (ParseException e) {
return new TextMessage(json);
}
}
/**
* Convert from an old-style to a new-style chat message.
* @param text The The text of the message.
* @return The converted JSON structure.
*/
@SuppressWarnings("unchecked")
private static JSONObject convert(String text) {
// state
final List<JSONObject> items = new LinkedList<>();
final Set<ChatColor> formatting = EnumSet.noneOf(ChatColor.class);
final StringBuilder current = new StringBuilder();
ChatColor color = null;
// work way through text, converting colors
for (int i = 0; i < text.length(); ++i) {
char ch = text.charAt(i);
if (ch != ChatColor.COLOR_CHAR) {
// no special handling
current.append(ch);
continue;
}
if (i == text.length() - 1) {
// ignore color character at end
continue;
}
// handle colors
append(items, current, color, formatting);
ChatColor code = ChatColor.getByChar(text.charAt(++i));
if (code == ChatColor.RESET) {
color = null;
formatting.clear();
} else if (code.isFormat()) {
formatting.add(code);
} else {
color = code;
formatting.clear();
}
}
append(items, current, color, formatting);
// convert list of items into structure
if (items.isEmpty()) {
// no items, return a blank message
JSONObject object = new JSONObject();
object.put("text", "");
return object;
} else if (items.size() == 1) {
// only one item, return it as-is
return items.get(0);
} else {
JSONObject object = items.get(0);
if (object.size() == 1) {
// only contains "text", no formatting, can reuse
object.put("extra", items.subList(1, items.size()));
} else {
// must put everything in the "extra" list
object = new JSONObject();
object.put("text", "");
object.put("extra", items);
}
return object;
}
}
@SuppressWarnings("unchecked")
private static void append(List<JSONObject> items, StringBuilder current, ChatColor color, Set<ChatColor> formatting) {
if (current.length() == 0) {
return;
}
JSONObject object = new JSONObject();
object.put("text", current.toString());
if (color != null) {
object.put("color", color.name().toLowerCase());
}
for (ChatColor format : formatting) {
if (format == ChatColor.MAGIC) {
object.put("obfuscated", true);
} else {
object.put(format.name().toLowerCase(), true);
}
}
current.setLength(0);
items.add(object);
}
}