org.tyrannyofheaven.bukkit.util.ToHMessageUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.tyrannyofheaven.bukkit.util.ToHMessageUtils.java

Source

/*
 * Copyright 2011 ZerothAngel <zerothangel@tyrannyofheaven.org>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.tyrannyofheaven.bukkit.util;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Server;
import org.bukkit.command.CommandSender;
import org.bukkit.conversations.Conversable;
import org.bukkit.conversations.Conversation;
import org.bukkit.conversations.ConversationFactory;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.util.ChatPaginator;

import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;

/**
 * Convenience methods mainly for displaying info via {@link CommandSender#sendMessage(String)}.
 * 
 * @author zerothangel
 */
public class ToHMessageUtils {

    // Number of lines per page
    private static final int LINES_PER_PAGE = ChatPaginator.CLOSED_CHAT_PAGE_HEIGHT;

    // Cache for colorize(). Feeling kinda iffy, but Strings are immutable...
    private static final ConcurrentMap<String, String> colorizeCache = new ConcurrentHashMap<>();

    private ToHMessageUtils() {
        throw new AssertionError("Don't instantiate me!");
    }

    /**
     * Convenience method for sending messages. Supports {@link String#format(String, Object...)}
     * formatting and multiple lines.
     * 
     * @param sender the receiver of the message
     * @param format the message format string
     * @param args format arguments
     */
    public static void sendMessage(CommandSender sender, String format, Object... args) {
        String message = String.format(format, args);
        for (String line : message.split("\n")) {
            sender.sendMessage(line);
        }
    }

    /**
     * Sends a plugin message to BungeeCord, signing it with zPermissions as 
     * the channel and any information to write to it.
     * 
     * @param plugin plugin to send with
     * @param channel sub channel to send to
     * @param objects objects to write
     */
    public static void sendPluginMessage(Plugin plugin, String channel, String... objects) {
        ByteArrayDataOutput out = ByteStreams.newDataOutput();
        out.writeUTF("zPermissions");
        out.writeUTF(channel);

        try (ByteArrayOutputStream msgbytes = new ByteArrayOutputStream();
                DataOutputStream msgout = new DataOutputStream(msgbytes)) {
            for (String s : objects) {
                msgout.writeUTF(s);
            }

            byte[] byteArray = msgbytes.toByteArray();
            out.writeShort(byteArray.length);
            out.write(byteArray);
            for (Player player : Bukkit.getOnlinePlayers()) {
                player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray());
                return;
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // For colorize()
    private static enum ColorizeState {
        TEXT, COLOR_OPEN, COLOR_NAME, COLOR_CLOSE, COLOR_ESCAPE;
    }

    /**
     * Scans text for color tokens and replaces with the appropriate color
     * code. Might be more convenient than string concatenation? Tokens
     * consist of the color name enclosed in curly-braces. Braces may be
     * escaped by doubling.
     * 
     * @param text the text to colorize
     * @return the colorized result
     */
    public static String colorize(String text) {
        if (text == null)
            return null;

        // Works best with interned strings
        String cacheResult = colorizeCache.get(text);
        if (cacheResult != null)
            return cacheResult;

        StringBuilder out = new StringBuilder();

        ColorizeState state = ColorizeState.TEXT;
        StringBuilder color = null;

        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);

            if (state == ColorizeState.TEXT) {
                if (c == '{') {
                    state = ColorizeState.COLOR_OPEN;
                } else if (c == '}') {
                    state = ColorizeState.COLOR_CLOSE;
                } else if (c == '`') {
                    state = ColorizeState.COLOR_ESCAPE;
                } else {
                    out.append(c);
                }
            } else if (state == ColorizeState.COLOR_OPEN) {
                if (c == '{') { // Escaped bracket
                    out.append('{');
                    state = ColorizeState.TEXT;
                } else if (Character.isUpperCase(c)) {
                    // First character of color name
                    color = new StringBuilder();
                    color.append(c);
                    state = ColorizeState.COLOR_NAME;
                } else {
                    // Invalid
                    throw new IllegalArgumentException("Invalid color name");
                }
            } else if (state == ColorizeState.COLOR_NAME) {
                if (Character.isUpperCase(c) || c == '_') {
                    color.append(c);
                } else if (c == '}') {
                    ChatColor chatColor = ChatColor.valueOf(color.toString());
                    out.append(chatColor);
                    state = ColorizeState.TEXT;
                } else {
                    // Invalid
                    throw new IllegalArgumentException("Invalid color name");
                }
            } else if (state == ColorizeState.COLOR_CLOSE) {
                // Optional, but for sanity's sake, to keep brackets matched
                if (c == '}') {
                    out.append('}'); // Collapse to single bracket
                } else {
                    out.append('}');
                    out.append(c);
                }
                state = ColorizeState.TEXT;
            } else if (state == ColorizeState.COLOR_ESCAPE) {
                out.append(decodeColor(c));
                state = ColorizeState.TEXT;
            } else
                throw new AssertionError("Unknown ColorizeState");
        }

        // End of string
        if (state == ColorizeState.COLOR_CLOSE) {
            out.append('}');
        } else if (state != ColorizeState.TEXT) {
            // Was in the middle of color name
            throw new IllegalArgumentException("Invalid color name");
        }

        cacheResult = out.toString();
        colorizeCache.putIfAbsent(text, cacheResult);

        return cacheResult;
    }

    // Decode a color escape code. Same mapping as sk89q's plugins.
    private static String decodeColor(char c) {
        switch (c) {
        case '`':
            return "`";
        case '0':
            return ChatColor.BLACK.toString();
        case '1':
            return ChatColor.GRAY.toString();
        case '2':
            return ChatColor.DARK_GRAY.toString();
        case 'b':
            return ChatColor.BLUE.toString();
        case 'B':
            return ChatColor.DARK_BLUE.toString();
        case 'c':
            return ChatColor.AQUA.toString();
        case 'C':
            return ChatColor.DARK_AQUA.toString();
        case 'g':
            return ChatColor.GREEN.toString();
        case 'G':
            return ChatColor.DARK_GREEN.toString();
        case 'p':
            return ChatColor.LIGHT_PURPLE.toString();
        case 'P':
            return ChatColor.DARK_PURPLE.toString();
        case 'r':
            return ChatColor.RED.toString();
        case 'R':
            return ChatColor.DARK_RED.toString();
        case 'w':
            return ChatColor.WHITE.toString();
        case 'y':
            return ChatColor.YELLOW.toString();
        case 'Y':
            return ChatColor.GOLD.toString();
        default:
            throw new IllegalArgumentException("Invalid color code");
        }
    }

    /**
     * Broadcasts the message to every user with the given permission. Supports
     * {@link String#format(String, Object...)} formatting and multiple lines.
     * 
     * @param server the server
     * @param permission the permission required to receive the broadcast
     * @param format the message format
     * @param args format args
     * @return number of users who received the message. In the case of a multi-line
     *   message, this is the number of users who received the last line.
     */
    public static int broadcast(Plugin plugin, String permission, String format, Object... args) {
        String message = String.format(format, args);
        int count = 0;
        for (String line : message.split("\n")) {
            count = plugin.getServer().broadcast(line, permission);
        }
        return count;
    }

    /**
     * Broadcasts the message to all players. Supports {@link String#format(String, Object...)}
     * formatting and multiple lines.
     * 
     * @param server the server
     * @param format the message format
     * @param args format args
     * @return number of users who received the message. In the case of a multi-line
     *   message, this is the number of users who received the last line.
     */
    public static int broadcastMessage(Plugin plugin, String format, Object... args) {
        return broadcast(plugin, Server.BROADCAST_CHANNEL_USERS, format, args);
    }

    /**
     * Broadcasts the message to all admins. Supports {@link String#format(String, Object...)}
     * formatting and multiple lines.
     * 
     * @param server the server
     * @param format the message format
     * @param args format args
     * @return number of users who received the message. In the case of a multi-line
     *   message, this is the number of users who received the last line.
     */
    public static int broadcastAdmin(Plugin plugin, String format, Object... args) {
        return broadcast(plugin, Server.BROADCAST_CHANNEL_ADMINISTRATIVE, format, args);
    }

    /**
     * Display a bunch of lines, automatically paginating if necessary. Only bothers
     * paginating if sender is a Player and the number of lines is greater than
     * the size of a page.
     * 
     * @param plugin the plugin
     * @param sender the CommandSender to display the lines to
     * @param lines the lines to display
     */
    public static void displayLines(Plugin plugin, CommandSender sender, List<String> lines) {
        if (lines.isEmpty())
            return;

        if (sender instanceof Player) {
            // Word wrap long lines
            List<String> outputLines = new ArrayList<>(lines.size());
            for (String line : lines) {
                String[] wrapped = ChatPaginator.wordWrap(line, ChatPaginator.GUARANTEED_NO_WRAP_CHAT_PAGE_WIDTH);
                for (String wrap : wrapped)
                    outputLines.add(wrap);
            }
            lines = outputLines;

            if (lines.size() > LINES_PER_PAGE) {
                Conversation convo = new ConversationFactory(plugin)
                        .withFirstPrompt(new PagerPrompt(lines, LINES_PER_PAGE)).withLocalEcho(false)
                        .buildConversation((Conversable) sender);

                convo.begin();
                return;
            }

            // Fall through...
        }

        // Don't bother with pager
        for (String line : lines) {
            sender.sendMessage(line);
        }
    }

}