com.dsh105.powermessage.core.PowerMessage.java Source code

Java tutorial

Introduction

Here is the source code for com.dsh105.powermessage.core.PowerMessage.java

Source

/*
 * This file is part of PowerMessage.
 *
 * PowerMessage is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * PowerMessage is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with PowerMessage.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.dsh105.powermessage.core;

import com.dsh105.commodus.ServerUtil;
import com.dsh105.commodus.paginator.Pageable;
import com.dsh105.commodus.reflection.Reflection;
import com.dsh105.powermessage.exception.InvalidMessageException;
import org.bukkit.Achievement;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.configuration.serialization.ConfigurationSerialization;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.stream.JsonWriter;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;

import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Represents a message that internally manipulates JSON to allow the sending of fancy, interactive messages to players
 */
public class PowerMessage implements MessageBuilder, Pageable, JsonWritable, Cloneable, ConfigurationSerializable,
        Iterable<PowerSnippet> {

    protected static final Pattern COLOUR_PATTERN = Pattern.compile(ChatColor.COLOR_CHAR + "([0-9A-FK-OR])",
            Pattern.CASE_INSENSITIVE);

    private static final String SERIALIZED_SNIPPETS = "snippets";
    private static final JsonParser jsonParser = new JsonParser();

    private static Class<?> CHAT_PACKET_CLASS;
    private static Method CHAT_FROM_JSON;

    static {
        ConfigurationSerialization.registerClass(PowerMessage.class);

        if (ServerUtil.getBukkitVersion().isCompatible("1.7")) {
            Class<?> chatSerialiser;
            if (ServerUtil.getBukkitVersion().isSupported("1.8.1")) {
                chatSerialiser = Reflection.getNMSClass("ChatSerializer");
            } else {
                chatSerialiser = Reflection.getNMSClass("IChatBaseComponent$ChatSerializer");
            }
            for (Method method : chatSerialiser.getDeclaredMethods()) {
                if (method.getReturnType().equals(Reflection.getNMSClass("IChatBaseComponent"))
                        && method.getParameterTypes().length == 1
                        && method.getParameterTypes()[0].equals(String.class)) {
                    CHAT_FROM_JSON = method;
                    break;
                }
            }

            ArrayList<Method> packetMethods = new ArrayList<>();
            ArrayList<Field> packetFields = new ArrayList<>();
            for (Method method : Reflection.getNMSClass("EnumProtocol").getDeclaredMethods()) {
                if (Map.class.isAssignableFrom(method.getReturnType()) && method.getParameterTypes().length == 0) {
                    method.setAccessible(true);
                    packetMethods.add(method);
                }
            }

            Map packetPlayMap;

            if (!packetMethods.isEmpty()) {
                packetPlayMap = (Map) Reflection.invoke(packetMethods.get(0),
                        Reflection.getNMSClass("EnumProtocol").getEnumConstants()[1]);
            } else {
                for (Field field : Reflection.getNMSClass("EnumProtocol").getDeclaredFields()) {
                    if (Map.class.isAssignableFrom(field.getType())) {
                        field.setAccessible(true);
                        packetFields.add(field);
                    }
                }
                packetPlayMap = (Map) Reflection.getFieldValue(packetFields.get(1),
                        Reflection.getNMSClass("EnumProtocol").getEnumConstants()[1]);
            }

            // temporary solution

            String fullName = "net.minecraft.server." + ServerUtil.getServerVersion() + ".EnumProtocolDirection";
            Class enumProtocolDirection = null;
            try {
                enumProtocolDirection = Class.forName(fullName);
            } catch (ClassNotFoundException ignored) {
            }
            if (enumProtocolDirection != null) {
                packetPlayMap = (Map) packetPlayMap.get(enumProtocolDirection.getEnumConstants()[1]);
            }
            CHAT_PACKET_CLASS = (Class<?>) packetPlayMap.get(0x02);

            try {
                CHAT_PACKET_CLASS.getConstructor(Reflection.getNMSClass("IChatBaseComponent"));
            } catch (NoSuchMethodException e) {
                // This is more of a backup
                CHAT_PACKET_CLASS = Reflection.getNMSClass("PacketPlayOutChat");
            }
        }
    }

    private ArrayList<PowerSnippet> snippets = new ArrayList<>();
    private String rawJson;
    private boolean convertedToJson;
    private Group currentGroup;

    /**
     * Constructs a new, empty PowerMessage
     */
    public PowerMessage() {
    }

    /**
     * Constructs a new PowerMessage with a starting text snippet
     *
     * @param firstSnippet First snippet to construct the message with
     */
    public PowerMessage(String firstSnippet) {
        then(firstSnippet);
    }

    @Override
    public String getContent() {
        StringBuilder result = new StringBuilder();
        for (PowerSnippet snippet : getSnippets()) {
            for (ChatColor colour : snippet.getColours()) {
                result.append(colour);
            }
            result.append(snippet.getText());
        }
        return result.toString();
    }

    /**
     * Sends a message to a Bukkit {@link org.bukkit.command.CommandSender}
     *
     * @param sender Whom to send the message to
     * @return This object
     */
    @Override
    public PowerMessage send(CommandSender sender) {
        if (sender instanceof Player) {
            send((Player) sender);
        } else {
            sender.sendMessage(getContent());
        }
        return this;
    }

    /**
     * Sends a message to a group of Bukkit {@link org.bukkit.command.CommandSender}s
     *
     * @param senders Whom to send the message to
     * @return This object
     */
    public PowerMessage send(CommandSender... senders) {
        for (CommandSender sender : senders) {
            send(sender);
        }
        return this;
    }

    /**
     * Sends a message to a Bukkit {@link org.bukkit.entity.Player}
     *
     * @param player Whom to send the message to
     * @return This object
     */
    public PowerMessage send(Player player) {
        if (ServerUtil.getBukkitVersion().isCompatible("1.7")) {
            Object chatComponent = Reflection.invokeStatic(CHAT_FROM_JSON, toJson());
            Object packet = Reflection.newInstance(
                    Reflection.getConstructor(CHAT_PACKET_CLASS, Reflection.getNMSClass("IChatBaseComponent")),
                    chatComponent);
            Object handle = Reflection.invoke(Reflection.getMethod(player.getClass(), "getHandle"), player);
            Object connection = Reflection.getFieldValue(handle, "playerConnection");
            Reflection.invoke(
                    Reflection.getMethod(connection.getClass(), "sendPacket", Reflection.getNMSClass("Packet")),
                    connection, packet);
        } else {
            player.sendMessage(getContent());
        }
        return this;
    }

    /**
     * Sends this message to a group of Bukkit {@link org.bukkit.entity.Player}s
     *
     * @param players Whom to send the message to
     * @return This object
     */
    public PowerMessage send(Player... players) {
        for (Player player : players) {
            send(player);
        }
        return this;
    }

    @Override
    public String getText() {
        return currentGroup.getText();
    }

    public PowerSnippet getSnippet(String content) {
        for (PowerSnippet snippet : getSnippets()) {
            if (snippet.getText().equals(content)) {
                return snippet;
            }
        }
        return null;
    }

    public PowerMessage clear() {
        this.snippets.clear();
        currentGroup = null;
        convertedToJson = false;
        return this;
    }

    @Override
    public PowerMessage edit(String snippetContent) {
        currentGroup.edit(snippetContent);
        return this;
    }

    @Override
    public PowerMessage colour(ChatColor... colours) {
        currentGroup.colour(colours);
        return this;
    }

    @Override
    public PowerMessage file(String relativePath) {
        currentGroup.file(relativePath);
        return this;
    }

    @Override
    public PowerMessage link(String urlLink) {
        currentGroup.link(urlLink);
        return this;
    }

    @Override
    public PowerMessage suggest(String commandToSuggest) {
        currentGroup.suggest(commandToSuggest);
        return this;
    }

    @Override
    public PowerMessage perform(String commandToPerform) {
        currentGroup.perform(commandToPerform);
        return this;
    }

    @Override
    public PowerMessage tooltip(String... content) {
        currentGroup.tooltip(content);
        return this;
    }

    @Override
    public PowerMessage tooltip(PowerMessage powerMessage) {
        currentGroup.tooltip(powerMessage);
        return this;
    }

    @Override
    public PowerMessage achievementTooltip(String achievementName) {
        currentGroup.achievementTooltip(achievementName);
        return this;
    }

    @Override
    public PowerMessage itemTooltip(String itemJson) {
        currentGroup.itemTooltip(itemJson);
        return this;
    }

    @Override
    public PowerMessage itemTooltip(String... itemContent) {
        currentGroup.itemTooltip(itemContent);
        return this;
    }

    @Override
    public PowerMessage itemTooltip(ItemStack itemStack) {
        currentGroup.itemTooltip(itemStack);
        return this;
    }

    @Override
    public PowerMessage achievementTooltip(Achievement which) {
        currentGroup.achievementTooltip(which);
        return this;
    }

    @Override
    public PowerMessage statisticTooltip(Statistic which) {
        currentGroup.statisticTooltip(which);
        return this;
    }

    @Override
    public PowerMessage statisticTooltip(Statistic which, Material item) {
        currentGroup.statisticTooltip(which, item);
        return this;
    }

    @Override
    public PowerMessage statisticTooltip(Statistic which, EntityType entity) {
        currentGroup.statisticTooltip(which, entity);
        return this;
    }

    /**
     * Begins construction of a new message snippet
     *
     * @param snippetContent Content to begin the new snippet with
     * @return This object
     */
    public PowerMessage then(String snippetContent) {
        String content = ChatColor.translateAlternateColorCodes('&', snippetContent);
        int groupCount = 0;
        if (content.length() > 0) {
            ArrayList<ChatColor> colours = new ArrayList<>();
            Matcher colourMatcher = COLOUR_PATTERN.matcher(content);
            int lastEnd = 0;
            while (colourMatcher.find()) {
                if (colourMatcher.start() > lastEnd) {
                    then(new PowerSnippet(content.substring(lastEnd, colourMatcher.start())))
                            .colour(colours.toArray(new ChatColor[0]));
                    groupCount++;
                }

                ChatColor colour = ChatColor.getByChar(colourMatcher.group(1));
                if (colour == ChatColor.RESET) {
                    colours.clear();
                } else {
                    colours.add(colour);
                }
                lastEnd = colourMatcher.end();
            }
            if (lastEnd < (content.length() - 1)) {
                then(new PowerSnippet(content.substring(lastEnd, content.length())))
                        .colour(colours.toArray(new ChatColor[0]));
                groupCount++;
            }
            // Group everything together so that changes can be applied to all of them
            group(groupCount);
        }
        return this;
    }

    /**
     * Begins construction of a new message snippet
     *
     * @param snippetContent Content to begin the new snippet with
     * @return This object
     */
    public PowerMessage then(Object snippetContent) {
        return then(snippetContent.toString());
    }

    /**
     * Adds a new snippet to a PowerMessage
     *
     * @param snippet Snippet to add
     * @return This object
     */
    public PowerMessage then(PowerSnippet snippet) {
        snippets.add(snippet);
        group(1);
        return this;
    }

    /**
     * Gets a copy of the snippets in a PowerMessage
     * <p>
     * Editing this list will not change the content of the original PowerMessage
     *
     * @return List of snippets in a PowerMessage
     */
    public List<PowerSnippet> getSnippets() {
        return Collections.unmodifiableList(snippets);
    }

    /**
     * Gets a snippet at a particular index
     *
     * @param index Index to retrieve
     * @return A particular snippet in a PowerMessage
     */
    public PowerSnippet getSnippet(int index) {
        return snippets.get(index);
    }

    /**
     * Gets a group of all snippets in a PowerMessage so that changes can be applied to them more easily
     *
     * @return A Group representing all snippets in a PowerMessage
     */
    public Group group() {
        this.convertedToJson = false;
        return new Group(this, groupCount());
    }

    /**
     * Gets a group of snippets so that changes can be applied to all more easily
     * <p>
     * The snippets are counted backwards <i>{@code count}</i> times inclusively
     *
     * @param count Number of snippets to include
     * @return A {@link com.dsh105.powermessage.core.Group} representing a certain number of snippets
     */
    public Group group(int count) {
        this.convertedToJson = false;
        this.currentGroup = new Group(this, count);
        return currentGroup;
    }

    /**
     * Gets a group of snippets so that changes can be applied to all more easily
     * <p>
     * The group begins at the specified {@code startIndex} and extends to the snippet at index {@code endIndex} - 1. Thus the number of snippets included is {@code endIndex}-@{code startIndex}
     *
     * @param startIndex The starting index, inclusive
     * @param endIndex The ending index, exclusive
     * @return A {@link com.dsh105.powermessage.core.Group} representing a certain number of snippets
     */
    // Inclusively from startIndex, exclusively
    public Group group(int startIndex, int endIndex) {
        this.convertedToJson = false;
        this.currentGroup = new Group(this, startIndex, endIndex);
        return currentGroup;
    }

    /**
     * Gets the number of snippets in a PowerMessage
     *
     * @return Group count (number of snippets)
     */
    public int groupCount() {
        return snippets.size();
    }

    private PowerSnippet lastSnippet() {
        return getSnippet(snippets.size() - 1);
    }

    private boolean isConvertedToJson() {
        return convertedToJson;
    }

    @Override
    public Iterator<PowerSnippet> iterator() {
        return snippets.iterator();
    }

    @Override
    public Map<String, Object> serialize() {
        Map<String, Object> serialized = new HashMap<>();
        serialized.put(SERIALIZED_SNIPPETS, snippets);
        return serialized;
    }

    public static PowerMessage deserialize(Map<String, Object> serialized) {
        if (!serialized.containsKey(SERIALIZED_SNIPPETS)) {
            throw new IllegalArgumentException("Failed to deserialize PowerMessage from provided data");
        }
        PowerMessage powerMessage = new PowerMessage();
        powerMessage.snippets = (ArrayList<PowerSnippet>) serialized.get(SERIALIZED_SNIPPETS);
        return powerMessage;
    }

    /**
     * Converts a raw JSON string to a PowerMessage object. This JSON string is in the format given by {@link #toJson()}
     * @param json the JSON string which represents a PowerMessage
     * @return A PowerMessage representing the given JSON string
     */
    public static PowerMessage fromJson(String json) {
        JsonArray serialised = jsonParser.parse(json).getAsJsonObject().getAsJsonArray("extra");
        PowerMessage powerMessage = new PowerMessage();

        for (JsonElement serialisedElement : serialised) {
            PowerSnippet snippet = new PowerSnippet("");
            JsonObject message = serialisedElement.getAsJsonObject();
            for (Map.Entry<String, JsonElement> entry : message.entrySet()) {
                String key = entry.getKey();
                JsonElement element = entry.getValue();

                if (key.equals("text")) {
                    snippet.setText(element.getAsString());
                } else if (PowerSnippet.STYLE_TO_NAME_MAP.containsValue(key)) {
                    if (element.getAsBoolean()) {
                        snippet.withColour(PowerSnippet.STYLE_TO_NAME_MAP.inverse().get(key));
                    }
                } else if (key.equals("color")) {
                    snippet.withColour(ChatColor.valueOf(element.getAsString().toUpperCase()));
                } else if (key.equals("clickEvent") || key.equals("hoverEvent")) {
                    JsonObject event = element.getAsJsonObject();
                    snippet.withEvent(key.substring(0, key.indexOf("Event")), event.get("action").getAsString(),
                            event.get("value").getAsString());
                }
            }
            powerMessage.then(snippet);
        }
        return powerMessage;
    }

    /**
     * Converts a PowerMessage to raw JSON, ready to be sent to a player
     *
     * @return Raw JSON to represent a PowerMessage
     */
    public String toJson() {
        if (!convertedToJson || rawJson == null) {
            StringWriter stringWriter = new StringWriter();
            JsonWriter writer = new JsonWriter(stringWriter);

            try {
                writeJson(writer);
            } catch (IOException e) {
                throw new InvalidMessageException("Invalid message", e);
            } finally {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            rawJson = stringWriter.toString();
            convertedToJson = true;
        }

        return rawJson;
    }

    @Override
    public JsonWriter writeJson(JsonWriter writer) throws IOException {
        if (snippets.size() == 1) {
            lastSnippet().writeJson(writer);
        } else {
            writer.beginObject().name("text").value("").name("extra").beginArray();
            for (PowerSnippet snippet : snippets) {
                snippet.writeJson(writer);
            }
            writer.endArray().endObject();
        }
        return writer;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        PowerMessage cloned = (PowerMessage) super.clone();
        for (int i = 0; i < this.getSnippets().size(); i++) {
            cloned.snippets.add(this.getSnippet(i));
        }
        return cloned;
    }
}