package org.tyrannyofheaven.bukkit.util.command.reader;
import static org.tyrannyofheaven.bukkit.util.ToHMessageUtils.colorize;
import static org.tyrannyofheaven.bukkit.util.ToHMessageUtils.sendMessage;
import static org.tyrannyofheaven.bukkit.util.ToHStringUtils.delimitedString;
import static org.tyrannyofheaven.bukkit.util.ToHStringUtils.hasText;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
/**
* Utility class to read a file containing commands and execute them.
*
* @author zerothangel
*/
public class CommandReader {
// Used to hold the batch processing abort flag
private static final ThreadLocal<Boolean> abortFlags = new ThreadLocal<>();
/**
* Execute commands from a file. Commands will be echoed back to the sender.
*
* @param server the Server instance
* @param sender who to execute the commands as
* @param file the file to read commands from
* @param plugins Zero or more plugins to restrict the commands to
* @throws IOException upon I/O error
*/
public static boolean read(Server server, CommandSender sender, File file, Plugin... plugins) throws IOException {
return read(server, sender, file, true, plugins);
}
/**
* Execute commands from a stream. Commands will be echoed back to the sender.
*
* @param server the Server instance
* @param sender who to execute the commands as
* @param input InputStream for commands
* @param plugins Zero or more plugins to restrict the commands to
* @throws IOException upon I/O error
*/
public static boolean read(Server server, CommandSender sender, InputStream input, Plugin...plugins) throws IOException {
return read(server, sender, input, true, plugins);
}
/**
* Execute commands from a file.
*
* @param server the Server instance
* @param sender who to execute the commands as
* @param file the file to read commands from
* @param echo true if commands should be echoed back to sender
* @param plugins Zero or more plugins to restrict the commands to
* @return true if all commands executed successfully
* @throws IOException upon I/O error
*/
public static boolean read(Server server, CommandSender sender, File file, boolean echo, Plugin... plugins) throws IOException {
return read(server, sender, new FileInputStream(file), echo, plugins);
}
private static Command getCommand(Server server, String name, Plugin... plugins) {
name = name.toLowerCase();
PluginCommand command = server.getPluginCommand(name);
if (plugins == null || plugins.length == 0) {
// No restrictions
return command;
}
// Find a match among specified plugins
for (Plugin plugin : plugins) {
// Same logic as JavaPlugin#getCommand()
if (command != null && command.getPlugin() != plugin)
command = server.getPluginCommand(String.format("%s:%s", plugin.getDescription().getName(), name));
if (command != null && command.getPlugin() == plugin)
return command;
}
return null;
}
/**
* Execute commands from a stream.
*
* @param server the Server instance
* @param sender who to execute the commands as
* @param input InputStream for commands
* @param echo true if commands should be echoed back to sender
* @param plugins Zero or more plugins to restrict the commands to
* @return true if all commands executed successfully
* @throws IOException upon I/O error
*/
public static boolean read(Server server, CommandSender sender, InputStream input, boolean echo, Plugin... plugins) throws IOException {
List<CommandCall> calls = new ArrayList<>();
// Read entire stream before executing anything
BufferedReader in = new BufferedReader(new InputStreamReader(input));
try {
int lineNo = 0;
String line;
while ((line = in.readLine()) != null) {
lineNo++;
line = line.trim();
if (line.length() == 0 || line.startsWith("#")) {
// Skip comments and blank lines
continue;
}
// Break up into args
String[] args = line.split(" ");
// Strip leading slash if present
String c = args[0].toLowerCase();
if (c.startsWith("/"))
c = c.substring(1);
Command command = getCommand(server, c, plugins);
if (command == null) {
throw new CommandReaderException(String.format("Unknown command at line %d", lineNo));
}
calls.add(new CommandCall(command, c, Arrays.copyOfRange(args, 1, args.length)));
}
}
finally {
in.close();
}
// Set up abort flag
abortFlags.set(Boolean.FALSE);
try {
// Execute each call
for (CommandCall call : calls) {
try {
if (echo) {
sendMessage(sender, colorize("{GRAY}%s%s%s%s"),
(sender instanceof Player ? "/" : ""),
call.getAlias(),
(call.getArgs().length > 0 ? " " : ""),
delimitedString(" ", (Object[])call.getArgs()));
}
if (!call.getCommand().execute(sender, call.getAlias(), call.getArgs()))
return false;
// Check aborting
if (abortFlags.get() != null && abortFlags.get())
return false;
}
catch (Error | RuntimeException e) {
throw e;
}
catch (Throwable t) {
throw new CommandReaderException(t);
}
}
}
finally {
// Remove ThreadLocal to prevent memory leaks
abortFlags.remove();
}
return true;
}
/**
* May be called by command handlers to abort batch processing. Does nothing
* if the handler was not called within {@link #read(Server, CommandSender, InputStream, boolean, Plugin...)}.
*/
public static void abortBatchProcessing() {
if (isBatchProcessing())
abortFlags.set(Boolean.TRUE);
}
/**
* Tests if current thread is currently running batch commands, e.g.
* called within {@link #read(Server, CommandSender, InputStream, boolean, Plugin...)}.
*
* @return true if the current thread is running batch commands
*/
public static boolean isBatchProcessing() {
return abortFlags.get() != null;
}
// Holder for command invocation
private static class CommandCall {
private final Command command;
private final String alias;
private final String[] args;
public CommandCall(Command command, String alias, String[] args) {
if (command == null)
throw new IllegalArgumentException("command cannot be null");
if (!hasText(alias))
throw new IllegalArgumentException("alias must have a value");
if (args == null)
args = new String[0];
this.command = command;
this.alias = alias;
this.args = args;
}
public Command getCommand() {
return command;
}
public String getAlias() {
return alias;
}
public String[] getArgs() {
return Arrays.copyOf(args, args.length);
}
}
}