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