Squashed 'lib/' content from commit 25c3423

git-subtree-dir: lib
git-subtree-split: 25c34231861721918079749db731f8af500eb371
This commit is contained in:
Charlie
2014-07-31 11:37:54 -04:00
commit 2af8c8dbf2
46 changed files with 3570 additions and 0 deletions

13
core/pom.xml Normal file
View File

@@ -0,0 +1,13 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.sk89q</groupId>
<artifactId>command-framework-parent</artifactId>
<version>0.5-SNAPSHOT</version>
</parent>
<artifactId>command-framework-core</artifactId>
<packaging>jar</packaging>
<version>0.5-SNAPSHOT</version>
<name>Sk89q Command Framework Core</name>
<description>Core classes for the sk89q command framework.</description>
</project>

View File

@@ -0,0 +1,252 @@
package com.sk89q.minecraft.util.commands;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
/**
* All supported color values for chat
*
* Class provided as part of the Bukkit project with slight modifications to
* reduce dependencies.
*
* @see https://github.com/Bukkit/Bukkit/blob/master/src/main/java/org/bukkit/ChatColor.java
*/
public enum ChatColor {
/**
* Represents black
*/
BLACK('0', 0x00),
/**
* Represents dark blue
*/
DARK_BLUE('1', 0x1),
/**
* Represents dark green
*/
DARK_GREEN('2', 0x2),
/**
* Represents dark blue (aqua)
*/
DARK_AQUA('3', 0x3),
/**
* Represents dark red
*/
DARK_RED('4', 0x4),
/**
* Represents dark purple
*/
DARK_PURPLE('5', 0x5),
/**
* Represents gold
*/
GOLD('6', 0x6),
/**
* Represents gray
*/
GRAY('7', 0x7),
/**
* Represents dark gray
*/
DARK_GRAY('8', 0x8),
/**
* Represents blue
*/
BLUE('9', 0x9),
/**
* Represents green
*/
GREEN('a', 0xA),
/**
* Represents aqua
*/
AQUA('b', 0xB),
/**
* Represents red
*/
RED('c', 0xC),
/**
* Represents light purple
*/
LIGHT_PURPLE('d', 0xD),
/**
* Represents yellow
*/
YELLOW('e', 0xE),
/**
* Represents white
*/
WHITE('f', 0xF),
/**
* Represents magical characters that change around randomly
*/
MAGIC('k', 0x10, true),
/**
* Makes the text bold.
*/
BOLD('l', 0x11, true),
/**
* Makes a line appear through the text.
*/
STRIKETHROUGH('m', 0x12, true),
/**
* Makes the text appear underlined.
*/
UNDERLINE('n', 0x13, true),
/**
* Makes the text italic.
*/
ITALIC('o', 0x14, true),
/**
* Resets all previous chat colors or formats.
*/
RESET('r', 0x15);
/**
* The special character which prefixes all chat colour codes. Use this if you need to dynamically
* convert colour codes from your custom format.
*/
public static final char COLOR_CHAR = '\u00A7';
private static final Pattern STRIP_COLOR_PATTERN = Pattern.compile("(?i)" + String.valueOf(COLOR_CHAR) + "[0-9A-FK-OR]");
private final int intCode;
private final char code;
private final boolean isFormat;
private final String toString;
private final static Map<Integer, ChatColor> BY_ID = new HashMap<Integer, ChatColor>();
private final static Map<Character, ChatColor> BY_CHAR = new HashMap<Character, ChatColor>();
private ChatColor(char code, int intCode) {
this(code, intCode, false);
}
private ChatColor(char code, int intCode, boolean isFormat) {
this.code = code;
this.intCode = intCode;
this.isFormat = isFormat;
this.toString = new String(new char[] {COLOR_CHAR, code});
}
/**
* Gets the char value associated with this color
*
* @return A char value of this color code
*/
public char getChar() {
return this.code;
}
@Override
public String toString() {
return this.toString;
}
/**
* Checks if this code is a format code as opposed to a color code.
*/
public boolean isFormat() {
return this.isFormat;
}
/**
* Checks if this code is a color code as opposed to a format code.
*/
public boolean isColor() {
return !this.isFormat && this != RESET;
}
/**
* Gets the color represented by the specified color code
*
* @param code Code to check
* @return Associative {@link org.bukkit.ChatColor} with the given code, or null if it doesn't exist
*/
public static ChatColor getByChar(char code) {
return BY_CHAR.get(code);
}
/**
* Gets the color represented by the specified color code
*
* @param code Code to check
* @return Associative {@link org.bukkit.ChatColor} with the given code, or null if it doesn't exist
*/
public static ChatColor getByChar(String code) {
if (code == null) throw new NullPointerException("Code cannot be null");
if (code.isEmpty()) throw new IllegalArgumentException("Code must have at least one char");
return BY_CHAR.get(code.charAt(0));
}
/**
* Strips the given message of all color codes
*
* @param input String to strip of color
* @return A copy of the input string, without any coloring
*/
public static String stripColor(final String input) {
if (input == null) {
return null;
}
return STRIP_COLOR_PATTERN.matcher(input).replaceAll("");
}
/**
* Translates a string using an alternate color code character into a string that uses the internal
* ChatColor.COLOR_CODE color code character. The alternate color code character will only be replaced
* if it is immediately followed by 0-9, A-F, a-f, K-O, k-o, R or r.
*
* @param altColorChar The alternate color code character to replace. Ex: &
* @param textToTranslate Text containing the alternate color code character.
* @return Text containing the ChatColor.COLOR_CODE color code character.
*/
public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) {
char[] b = textToTranslate.toCharArray();
for (int i = 0; i < b.length - 1; i++) {
if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i+1]) > -1) {
b[i] = ChatColor.COLOR_CHAR;
b[i+1] = Character.toLowerCase(b[i+1]);
}
}
return new String(b);
}
/**
* Gets the ChatColors used at the end of the given input string.
*
* @param input Input string to retrieve the colors from.
* @return Any remaining ChatColors to pass onto the next line.
*/
public static String getLastColors(String input) {
String result = "";
int length = input.length();
// Search backwards from the end as it is faster
for (int index = length - 1; index > -1; index--) {
char section = input.charAt(index);
if (section == COLOR_CHAR && index < length - 1) {
char c = input.charAt(index + 1);
ChatColor color = getByChar(c);
if (color != null) {
result = color.toString() + result;
// Once we find a color or reset we can stop searching
if (color.isColor() || color.equals(RESET)) {
break;
}
}
}
}
return result;
}
static {
for (ChatColor color : values()) {
BY_ID.put(color.intCode, color);
BY_CHAR.put(color.code, color);
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* This annotation indicates a command. Methods should be marked with this
* annotation to tell {@link CommandsManager} that the method is a command.
* Note that the method name can actually be anything.
*
* @author sk89q
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface Command {
/**
* A list of aliases for the command. The first alias is the most
* important -- it is the main name of the command. (The method name
* is never used for anything).
*
* @return Aliases for a command
*/
String[] aliases();
/**
* Usage instruction. Example text for usage could be
* <code>[-h harps] [name] [message]</code>.
*
* @return Usage instructions for a command
*/
String usage() default "";
/**
* @return A short description for the command.
*/
String desc();
/**
* The minimum number of arguments. This should be 0 or above.
*
* @return the minimum number of arguments
*/
int min() default 0;
/**
* The maximum number of arguments. Use -1 for an unlimited number
* of arguments.
*
* @return the maximum number of arguments
*/
int max() default -1;
/**
* Flags allow special processing for flags such as -h in the command,
* allowing users to easily turn on a flag. This is a string with
* each character being a flag. Use A-Z and a-z as possible flags.
* Appending a flag with a : makes the flag character before a value flag,
* meaning that if it is given it must have a value
*
* @return Flags matching a-zA-Z
*/
String flags() default "";
/**
* @return A long description for the command.
*/
String help() default "";
/**
*
*
* @return Whether any flag can be provided to the command, even if it is not in {@link #flags()}
*/
boolean anyFlags() default false;
}

View File

@@ -0,0 +1,41 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Any command with this annotation will run the raw command as shown in the
* thing, as long as it is registered in the current {@link CommandsManager}.
* Mostly to move commands around without breaking things.
*
* @author zml2008
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface CommandAlias {
/**
*
* @return Raw {@link CommandsManager}-formatted command arg array to run
*/
String[] value();
}

View File

@@ -0,0 +1,338 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class CommandContext {
protected final String command;
protected final List<String> parsedArgs;
protected final List<Integer> originalArgIndices;
protected final String[] originalArgs;
protected final Set<Character> booleanFlags = new HashSet<Character>();
protected final Map<Character, String> valueFlags = new HashMap<Character, String>();
protected final SuggestionContext suggestionContext;
protected final CommandLocals locals;
public static String[] split(String args) {
return args.split(" ", -1);
}
public CommandContext(String args) throws CommandException {
this(args.split(" ", -1), null);
}
public CommandContext(String[] args) throws CommandException {
this(args, null);
}
public CommandContext(String args, Set<Character> valueFlags) throws CommandException {
this(args.split(" ", -1), valueFlags);
}
public CommandContext(String args, Set<Character> valueFlags, boolean allowHangingFlag)
throws CommandException {
this(args.split(" ", -1), valueFlags, allowHangingFlag, new CommandLocals());
}
public CommandContext(String[] args, Set<Character> valueFlags) throws CommandException {
this(args, valueFlags, false, null);
}
/**
* Parse the given array of arguments.
*
* <p>Empty arguments are removed from the list of arguments.</p>
*
* @param args an array with arguments
* @param valueFlags a set containing all value flags (pass null to disable value flag parsing)
* @param allowHangingFlag true if hanging flags are allowed
* @param locals the locals, null to create empty one
* @throws CommandException thrown on a parsing error
*/
public CommandContext(String[] args, Set<Character> valueFlags,
boolean allowHangingFlag, CommandLocals locals) throws CommandException {
if (valueFlags == null) {
valueFlags = Collections.emptySet();
}
originalArgs = args;
command = args[0];
this.locals = locals != null ? locals : new CommandLocals();
boolean isHanging = false;
SuggestionContext suggestionContext = SuggestionContext.hangingValue();
// Eliminate empty args and combine multiword args first
List<Integer> argIndexList = new ArrayList<Integer>(args.length);
List<String> argList = new ArrayList<String>(args.length);
for (int i = 1; i < args.length; ++i) {
isHanging = false;
String arg = args[i];
if (arg.length() == 0) {
isHanging = true;
continue;
}
argIndexList.add(i);
switch (arg.charAt(0)) {
case '\'':
case '"':
final StringBuilder build = new StringBuilder();
final char quotedChar = arg.charAt(0);
int endIndex;
for (endIndex = i; endIndex < args.length; ++endIndex) {
final String arg2 = args[endIndex];
if (arg2.charAt(arg2.length() - 1) == quotedChar && arg2.length() > 1) {
if (endIndex != i) build.append(' ');
build.append(arg2.substring(endIndex == i ? 1 : 0, arg2.length() - 1));
break;
} else if (endIndex == i) {
build.append(arg2.substring(1));
} else {
build.append(' ').append(arg2);
}
}
if (endIndex < args.length) {
arg = build.toString();
i = endIndex;
}
// In case there is an empty quoted string
if (arg.length() == 0) {
continue;
}
// else raise exception about hanging quotes?
}
argList.add(arg);
}
// Then flags
this.originalArgIndices = new ArrayList<Integer>(argIndexList.size());
this.parsedArgs = new ArrayList<String>(argList.size());
for (int nextArg = 0; nextArg < argList.size(); ) {
// Fetch argument
String arg = argList.get(nextArg++);
suggestionContext = SuggestionContext.hangingValue();
// Not a flag?
if (arg.charAt(0) != '-' || arg.length() == 1 || !arg.matches("^-[a-zA-Z]+$")) {
if (!isHanging) {
suggestionContext = SuggestionContext.lastValue();
}
originalArgIndices.add(argIndexList.get(nextArg - 1));
parsedArgs.add(arg);
continue;
}
// Handle flag parsing terminator --
if (arg.equals("--")) {
while (nextArg < argList.size()) {
originalArgIndices.add(argIndexList.get(nextArg));
parsedArgs.add(argList.get(nextArg++));
}
break;
}
// Go through the flag characters
for (int i = 1; i < arg.length(); ++i) {
char flagName = arg.charAt(i);
if (valueFlags.contains(flagName)) {
if (this.valueFlags.containsKey(flagName)) {
throw new CommandException("Value flag '" + flagName + "' already given");
}
if (nextArg >= argList.size()) {
if (allowHangingFlag) {
suggestionContext = SuggestionContext.flag(flagName);
break;
} else {
throw new CommandException("No value specified for the '-" + flagName + "' flag.");
}
}
// If it is a value flag, read another argument and add it
this.valueFlags.put(flagName, argList.get(nextArg++));
if (!isHanging) {
suggestionContext = SuggestionContext.flag(flagName);
}
} else {
booleanFlags.add(flagName);
}
}
}
this.suggestionContext = suggestionContext;
}
public SuggestionContext getSuggestionContext() {
return suggestionContext;
}
public String getCommand() {
return command;
}
public boolean matches(String command) {
return this.command.equalsIgnoreCase(command);
}
public String getString(int index) {
return parsedArgs.get(index);
}
public String getString(int index, String def) {
return index < parsedArgs.size() ? parsedArgs.get(index) : def;
}
public String getJoinedStrings(int initialIndex) {
initialIndex = originalArgIndices.get(initialIndex);
StringBuilder buffer = new StringBuilder(originalArgs[initialIndex]);
for (int i = initialIndex + 1; i < originalArgs.length; ++i) {
buffer.append(" ").append(originalArgs[i]);
}
return buffer.toString();
}
public String getRemainingString(int start) {
return getString(start, parsedArgs.size() - 1);
}
public String getString(int start, int end) {
StringBuilder buffer = new StringBuilder(parsedArgs.get(start));
for (int i = start + 1; i < end + 1; ++i) {
buffer.append(" ").append(parsedArgs.get(i));
}
return buffer.toString();
}
public int getInteger(int index) throws NumberFormatException {
return Integer.parseInt(parsedArgs.get(index));
}
public int getInteger(int index, int def) throws NumberFormatException {
return index < parsedArgs.size() ? Integer.parseInt(parsedArgs.get(index)) : def;
}
public double getDouble(int index) throws NumberFormatException {
return Double.parseDouble(parsedArgs.get(index));
}
public double getDouble(int index, double def) throws NumberFormatException {
return index < parsedArgs.size() ? Double.parseDouble(parsedArgs.get(index)) : def;
}
public String[] getSlice(int index) {
String[] slice = new String[originalArgs.length - index];
System.arraycopy(originalArgs, index, slice, 0, originalArgs.length - index);
return slice;
}
public String[] getPaddedSlice(int index, int padding) {
String[] slice = new String[originalArgs.length - index + padding];
System.arraycopy(originalArgs, index, slice, padding, originalArgs.length - index);
return slice;
}
public String[] getParsedSlice(int index) {
String[] slice = new String[parsedArgs.size() - index];
System.arraycopy(parsedArgs.toArray(new String[parsedArgs.size()]), index, slice, 0, parsedArgs.size() - index);
return slice;
}
public String[] getParsedPaddedSlice(int index, int padding) {
String[] slice = new String[parsedArgs.size() - index + padding];
System.arraycopy(parsedArgs.toArray(new String[parsedArgs.size()]), index, slice, padding, parsedArgs.size() - index);
return slice;
}
public boolean hasFlag(char ch) {
return booleanFlags.contains(ch) || valueFlags.containsKey(ch);
}
public Set<Character> getFlags() {
return booleanFlags;
}
public Map<Character, String> getValueFlags() {
return valueFlags;
}
public String getFlag(char ch) {
return valueFlags.get(ch);
}
public String getFlag(char ch, String def) {
final String value = valueFlags.get(ch);
if (value == null) {
return def;
}
return value;
}
public int getFlagInteger(char ch) throws NumberFormatException {
return Integer.parseInt(valueFlags.get(ch));
}
public int getFlagInteger(char ch, int def) throws NumberFormatException {
final String value = valueFlags.get(ch);
if (value == null) {
return def;
}
return Integer.parseInt(value);
}
public double getFlagDouble(char ch) throws NumberFormatException {
return Double.parseDouble(valueFlags.get(ch));
}
public double getFlagDouble(char ch, double def) throws NumberFormatException {
final String value = valueFlags.get(ch);
if (value == null) {
return def;
}
return Double.parseDouble(value);
}
public int argsLength() {
return parsedArgs.size();
}
public CommandLocals getLocals() {
return locals;
}
}

View File

@@ -0,0 +1,72 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class CommandException extends Exception {
private static final long serialVersionUID = 870638193072101739L;
private List<String> commandStack = new ArrayList<String>();
public CommandException() {
super();
}
public CommandException(String message) {
super(message);
}
public CommandException(String message, Throwable t) {
super(message, t);
}
public CommandException(Throwable t) {
super(t);
}
public void prependStack(String name) {
commandStack.add(name);
}
public String toStackString(String prefix, String spacedSuffix) {
StringBuilder builder = new StringBuilder();
if (prefix != null) {
builder.append(prefix);
}
ListIterator<String> li = commandStack.listIterator(commandStack.size());
while (li.hasPrevious()) {
if (li.previousIndex() != commandStack.size() - 1) {
builder.append(" ");
}
builder.append(li.previous());
}
if (spacedSuffix != null) {
if (builder.length() > 0) {
builder.append(" ");
}
builder.append(spacedSuffix);
}
return builder.toString();
}
}

View File

@@ -0,0 +1,50 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
import java.util.HashMap;
import java.util.Map;
public class CommandLocals {
private final Map<Object, Object> locals = new HashMap<Object, Object>();
public boolean containsKey(Object key) {
return locals.containsKey(key);
}
public boolean containsValue(Object value) {
return locals.containsValue(value);
}
public Object get(Object key) {
return locals.get(key);
}
@SuppressWarnings("unchecked")
public <T> T get(Class<T> key) {
return (T) locals.get(key);
}
public Object put(Object key, Object value) {
return locals.put(key, value);
}
}

View File

@@ -0,0 +1,37 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Indicates a list of permissions that should be checked.
*
* @author sk89q
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface CommandPermissions {
/**
* A list of permissions. Only one permission has to be met
* for the command to be permitted.
*/
String[] value();
}

View File

@@ -0,0 +1,29 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
/**
* Thrown when not enough permissions are satisfied.
*
* @author sk89q
*/
public class CommandPermissionsException extends CommandException {
private static final long serialVersionUID = -602374621030168291L;
}

View File

@@ -0,0 +1,35 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
public class CommandUsageException extends CommandException {
private static final long serialVersionUID = -6761418114414516542L;
protected String usage;
public CommandUsageException(String message, String usage) {
super(message);
this.usage = usage;
}
public String getUsage() {
return usage;
}
}

View File

@@ -0,0 +1,594 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sk89q.util.StringUtil;
/**
* <p>Manager for handling commands. This allows you to easily process commands,
* including nested commands, by correctly annotating methods of a class.</p>
*
* <p>To use this, it is merely a matter of registering classes containing
* the commands (as methods with the proper annotations) with the
* manager. When you want to process a command, use one of the
* <code>execute</code> methods. If something is wrong, such as incorrect
* usage, insufficient permissions, or a missing command altogether, an
* exception will be raised for upstream handling.</p>
*
* <p>Methods of a class to be registered can be static, but if an injector
* is registered with the class, the instances of the command classes
* will be created automatically and methods will be called non-statically.</p>
*
* <p>To mark a method as a command, use {@link Command}. For nested commands,
* see {@link NestedCommand}. To handle permissions, use
* {@link CommandPermissions}.</p>
*
* <p>This uses Java reflection extensively, but to reduce the overhead of
* reflection, command lookups are completely cached on registration. This
* allows for fast command handling. Method invocation still has to be done
* with reflection, but this is quite fast in that of itself.</p>
*
* @author sk89q
* @param <T> command sender class
*/
public abstract class CommandsManager<T> {
/**
* Logger for general errors.
*/
protected static final Logger logger =
Logger.getLogger(CommandsManager.class.getCanonicalName());
/**
* Mapping of commands (including aliases) with a description. Root
* commands are stored under a key of null, whereas child commands are
* cached under their respective {@link Method}. The child map has
* the key of the command name (one for each alias) with the
* method.
*/
protected Map<Method, Map<String, Method>> commands = new HashMap<Method, Map<String, Method>>();
/**
* Used to store the instances associated with a method.
*/
protected Map<Method, Object> instances = new HashMap<Method, Object>();
/**
* Mapping of commands (not including aliases) with a description. This
* is only for top level commands.
*/
protected Map<String, String> descs = new HashMap<String, String>();
/**
* Stores the injector used to getInstance.
*/
protected Injector injector;
/**
* Mapping of commands (not including aliases) with a description. This
* is only for top level commands.
*/
protected Map<String, String> helpMessages = new HashMap<String, String>();
/**
* Register an class that contains commands (denoted by {@link Command}.
* If no dependency injector is specified, then the methods of the
* class will be registered to be called statically. Otherwise, new
* instances will be created of the command classes and methods will
* not be called statically.
*
* @param cls
*/
public void register(Class<?> cls) {
registerMethods(cls, null);
}
/**
* Register an class that contains commands (denoted by {@link Command}.
* If no dependency injector is specified, then the methods of the
* class will be registered to be called statically. Otherwise, new
* instances will be created of the command classes and methods will
* not be called statically. A List of {@link Command} annotations from
* registered commands is returned.
*
* @param cls
* @return A List of {@link Command} annotations from registered commands,
* for use in eg. a dynamic command registration system.
*/
public List<Command> registerAndReturn(Class<?> cls) {
return registerMethods(cls, null);
}
/**
* Register the methods of a class. This will automatically construct
* instances as necessary.
*
* @param cls
* @param parent
* @return Commands Registered
*/
public List<Command> registerMethods(Class<?> cls, Method parent) {
try {
if (getInjector() == null) {
return registerMethods(cls, parent, null);
} else {
Object obj = getInjector().getInstance(cls);
return registerMethods(cls, parent, obj);
}
} catch (InvocationTargetException e) {
logger.log(Level.SEVERE, "Failed to register commands", e);
} catch (IllegalAccessException e) {
logger.log(Level.SEVERE, "Failed to register commands", e);
} catch (InstantiationException e) {
logger.log(Level.SEVERE, "Failed to register commands", e);
}
return null;
}
/**
* Register the methods of a class.
*
* @param cls
* @param parent
* @param obj
* @return
*/
private List<Command> registerMethods(Class<?> cls, Method parent, Object obj) {
Map<String, Method> map;
List<Command> registered = new ArrayList<Command>();
// Make a new hash map to cache the commands for this class
// as looking up methods via reflection is fairly slow
if (commands.containsKey(parent)) {
map = commands.get(parent);
} else {
map = new HashMap<String, Method>();
commands.put(parent, map);
}
for (Method method : cls.getMethods()) {
if (!method.isAnnotationPresent(Command.class)) {
continue;
}
boolean isStatic = Modifier.isStatic(method.getModifiers());
Command cmd = method.getAnnotation(Command.class);
// Cache the aliases too
for (String alias : cmd.aliases()) {
map.put(alias, method);
}
// We want to be able invoke with an instance
if (!isStatic) {
// Can't register this command if we don't have an instance
if (obj == null) {
continue;
}
instances.put(method, obj);
}
// Build a list of commands and their usage details, at least for
// root level commands
if (parent == null) {
final String commandName = cmd.aliases()[0];
final String desc = cmd.desc();
final String usage = cmd.usage();
if (usage.length() == 0) {
descs.put(commandName, desc);
} else {
descs.put(commandName, usage + " - " + desc);
}
String help = cmd.help();
if (help.length() == 0) {
help = desc;
}
final CharSequence arguments = getArguments(cmd);
for (String alias : cmd.aliases()) {
final String helpMessage = "/" + alias + " " + arguments + "\n\n" + help;
final String key = alias.replaceAll("/", "");
String previous = helpMessages.put(key, helpMessage);
if (previous != null && !previous.replaceAll("^/[^ ]+ ", "").equals(helpMessage.replaceAll("^/[^ ]+ ", ""))) {
helpMessages.put(key, previous + "\n\n" + helpMessage);
}
}
}
// Add the command to the registered command list for return
registered.add(cmd);
// Look for nested commands -- if there are any, those have
// to be cached too so that they can be quickly looked
// up when processing commands
if (method.isAnnotationPresent(NestedCommand.class)) {
NestedCommand nestedCmd = method.getAnnotation(NestedCommand.class);
for (Class<?> nestedCls : nestedCmd.value()) {
registerMethods(nestedCls, method);
}
}
}
if (cls.getSuperclass() != null) {
registerMethods(cls.getSuperclass(), parent, obj);
}
return registered;
}
/**
* Checks to see whether there is a command named such at the root level.
* This will check aliases as well.
*
* @param command
* @return
*/
public boolean hasCommand(String command) {
return commands.get(null).containsKey(command.toLowerCase());
}
/**
* Get a list of command descriptions. This is only for root commands.
*
* @return
*/
public Map<String, String> getCommands() {
return descs;
}
public Map<Method, Map<String, Method>> getMethods() {
return commands;
}
/**
* Get a map from command name to help message. This is only for root commands.
*
* @return
*/
public Map<String, String> getHelpMessages() {
return helpMessages;
}
/**
* Get the usage string for a command.
*
* @param args
* @param level
* @param cmd
* @return
*/
protected String getUsage(String[] args, int level, Command cmd) {
final StringBuilder command = new StringBuilder();
command.append('/');
for (int i = 0; i <= level; ++i) {
command.append(args[i]);
command.append(' ');
}
command.append(getArguments(cmd));
final String help = cmd.help();
if (help.length() > 0) {
command.append("\n\n");
command.append(help);
}
return command.toString();
}
protected CharSequence getArguments(Command cmd) {
final String flags = cmd.flags();
final StringBuilder command2 = new StringBuilder();
if (flags.length() > 0) {
String flagString = flags.replaceAll(".:", "");
if (flagString.length() > 0) {
command2.append("[-");
for (int i = 0; i < flagString.length(); ++i) {
command2.append(flagString.charAt(i));
}
command2.append("] ");
}
}
command2.append(cmd.usage());
return command2;
}
/**
* Get the usage string for a nested command.
*
* @param args
* @param level
* @param method
* @param player
* @return
* @throws CommandException
*/
protected String getNestedUsage(String[] args, int level,
Method method, T player) throws CommandException {
StringBuilder command = new StringBuilder();
command.append("/");
for (int i = 0; i <= level; ++i) {
command.append(args[i] + " ");
}
Map<String, Method> map = commands.get(method);
boolean found = false;
command.append("<");
Set<String> allowedCommands = new HashSet<String>();
for (Map.Entry<String, Method> entry : map.entrySet()) {
Method childMethod = entry.getValue();
found = true;
if (hasPermission(childMethod, player)) {
Command childCmd = childMethod.getAnnotation(Command.class);
allowedCommands.add(childCmd.aliases()[0]);
}
}
if (allowedCommands.size() > 0) {
command.append(StringUtil.joinString(allowedCommands, "|", 0));
} else {
if (!found) {
command.append("?");
} else {
//command.append("action");
throw new CommandPermissionsException();
}
}
command.append(">");
return command.toString();
}
/**
* Attempt to execute a command. This version takes a separate command
* name (for the root command) and then a list of following arguments.
*
* @param cmd command to run
* @param args arguments
* @param player command source
* @param methodArgs method arguments
* @throws CommandException
*/
public void execute(String cmd, String[] args, T player,
Object... methodArgs) throws CommandException {
String[] newArgs = new String[args.length + 1];
System.arraycopy(args, 0, newArgs, 1, args.length);
newArgs[0] = cmd;
Object[] newMethodArgs = new Object[methodArgs.length + 1];
System.arraycopy(methodArgs, 0, newMethodArgs, 1, methodArgs.length);
executeMethod(null, newArgs, player, newMethodArgs, 0);
}
/**
* Attempt to execute a command.
*
* @param args
* @param player
* @param methodArgs
* @throws CommandException
*/
public void execute(String[] args, T player,
Object... methodArgs) throws CommandException {
Object[] newMethodArgs = new Object[methodArgs.length + 1];
System.arraycopy(methodArgs, 0, newMethodArgs, 1, methodArgs.length);
executeMethod(null, args, player, newMethodArgs, 0);
}
/**
* Attempt to execute a command.
*
* @param parent
* @param args
* @param player
* @param methodArgs
* @param level
* @throws CommandException
*/
public void executeMethod(Method parent, String[] args,
T player, Object[] methodArgs, int level) throws CommandException {
String cmdName = args[level];
Map<String, Method> map = commands.get(parent);
Method method = map.get(cmdName.toLowerCase());
if (method == null) {
if (parent == null) { // Root
throw new UnhandledCommandException();
} else {
throw new MissingNestedCommandException("Unknown command: " + cmdName,
getNestedUsage(args, level - 1, parent, player));
}
}
checkPermission(player, method);
int argsCount = args.length - 1 - level;
// checks if we need to execute the body of the nested command method (false)
// or display the help what commands are available (true)
// this is all for an args count of 0 if it is > 0 and a NestedCommand Annotation is present
// it will always handle the methods that NestedCommand points to
// e.g.:
// - /cmd - @NestedCommand(executeBody = true) will go into the else loop and execute code in that method
// - /cmd <arg1> <arg2> - @NestedCommand(executeBody = true) will always go to the nested command class
// - /cmd <arg1> - @NestedCommand(executeBody = false) will always go to the nested command class not matter the args
boolean executeNested = method.isAnnotationPresent(NestedCommand.class)
&& (argsCount > 0 || !method.getAnnotation(NestedCommand.class).executeBody());
if (executeNested) {
if (argsCount == 0) {
throw new MissingNestedCommandException("Sub-command required.",
getNestedUsage(args, level, method, player));
} else {
executeMethod(method, args, player, methodArgs, level + 1);
}
} else if (method.isAnnotationPresent(CommandAlias.class)) {
CommandAlias aCmd = method.getAnnotation(CommandAlias.class);
executeMethod(parent, aCmd.value(), player, methodArgs, level);
} else {
Command cmd = method.getAnnotation(Command.class);
String[] newArgs = new String[args.length - level];
System.arraycopy(args, level, newArgs, 0, args.length - level);
final Set<Character> valueFlags = new HashSet<Character>();
char[] flags = cmd.flags().toCharArray();
Set<Character> newFlags = new HashSet<Character>();
for (int i = 0; i < flags.length; ++i) {
if (flags.length > i + 1 && flags[i + 1] == ':') {
valueFlags.add(flags[i]);
++i;
}
newFlags.add(flags[i]);
}
CommandContext context = new CommandContext(newArgs, valueFlags);
if (context.argsLength() < cmd.min()) {
throw new CommandUsageException("Too few arguments.", getUsage(args, level, cmd));
}
if (cmd.max() != -1 && context.argsLength() > cmd.max()) {
throw new CommandUsageException("Too many arguments.", getUsage(args, level, cmd));
}
if (!cmd.anyFlags()) {
for (char flag : context.getFlags()) {
if (!newFlags.contains(flag)) {
throw new CommandUsageException("Unknown flag: " + flag, getUsage(args, level, cmd));
}
}
}
methodArgs[0] = context;
Object instance = instances.get(method);
invokeMethod(parent, args, player, method, instance, methodArgs, argsCount);
}
}
protected void checkPermission(T player, Method method) throws CommandException {
if (!hasPermission(method, player)) {
throw new CommandPermissionsException();
}
}
public void invokeMethod(Method parent, String[] args, T player, Method method,
Object instance, Object[] methodArgs, int level) throws CommandException {
try {
method.invoke(instance, methodArgs);
} catch (IllegalArgumentException e) {
logger.log(Level.SEVERE, "Failed to execute command", e);
} catch (IllegalAccessException e) {
logger.log(Level.SEVERE, "Failed to execute command", e);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof CommandException) {
throw (CommandException) e.getCause();
}
throw new WrappedCommandException(e.getCause());
}
}
/**
* Returns whether a player has access to a command.
*
* @param method
* @param player
* @return
*/
protected boolean hasPermission(Method method, T player) {
CommandPermissions perms = method.getAnnotation(CommandPermissions.class);
if (perms == null) {
return true;
}
for (String perm : perms.value()) {
if (hasPermission(player, perm)) {
return true;
}
}
return false;
}
/**
* Returns whether a player permission..
*
* @param player
* @param perm
* @return
*/
public abstract boolean hasPermission(T player, String perm);
/**
* Get the injector used to create new instances. This can be
* null, in which case only classes will be registered statically.
*/
public Injector getInjector() {
return injector;
}
/**
* Set the injector for creating new instances.
*
* @param injector injector or null
*/
public void setInjector(Injector injector) {
this.injector = injector;
}
}

View File

@@ -0,0 +1,32 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* This annotation indicates that a command can be used from the console.
*
* @author sk89q
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface Console {
}

View File

@@ -0,0 +1,41 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
import java.lang.reflect.InvocationTargetException;
/**
* Constructs new instances.
*/
public interface Injector {
/**
* Constructs a new instance of the given class.
*
* @param cls class
* @return object
* @throws IllegalAccessException
* @throws InstantiationException
* @throws InvocationTargetException
*/
public Object getInstance(Class<?> cls) throws InvocationTargetException,
IllegalAccessException, InstantiationException;
}

View File

@@ -0,0 +1,66 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//$Id$
package com.sk89q.minecraft.util.commands;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Indicates how the affected blocks should be hinted at in the log.
*
* @author sk89q
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface Logging {
public enum LogMode {
/**
* Player position
*/
POSITION,
/**
* Region selection
*/
REGION,
/**
* Player orientation and region selection
*/
ORIENTATION_REGION,
/**
* Either the player position or pos1, depending on the placeAtPos1 flag
*/
PLACEMENT,
/**
* Log all information available
*/
ALL
}
/**
* Log mode. Can be either POSITION, REGION, ORIENTATION_REGION, PLACEMENT or ALL.
*/
LogMode value();
}

View File

@@ -0,0 +1,29 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
public class MissingNestedCommandException extends CommandUsageException {
private static final long serialVersionUID = -4382896182979285355L;
public MissingNestedCommandException(String message, String usage) {
super(message, usage);
}
}

View File

@@ -0,0 +1,46 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Indicates a nested command. Mark methods with this annotation to tell
* {@link CommandsManager} that a method is merely a shell for child
* commands. Note that the body of a method marked with this annotation
* will never called. Additionally, not all fields of {@link Command} apply
* when it is used in conjunction with this annotation, although both
* are still required.
*
* @author sk89q
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface NestedCommand {
/**
* A list of classes with the child commands.
*/
Class<?>[] value();
/**
* If set to true it will execute the body of the tagged method.
*/
boolean executeBody() default false;
}

View File

@@ -0,0 +1,62 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.logging.Logger;
public class SimpleInjector implements Injector {
private static final Logger logger = Logger.getLogger(SimpleInjector.class.getCanonicalName());
private Object[] args;
private Class<?>[] argClasses;
public SimpleInjector(Object... args) {
this.args = args;
argClasses = new Class[args.length];
for (int i = 0; i < args.length; ++i) {
argClasses[i] = args[i].getClass();
}
}
public Object getInstance(Class<?> clazz) {
try {
Constructor<?> ctr = clazz.getConstructor(argClasses);
ctr.setAccessible(true);
return ctr.newInstance(args);
} catch (NoSuchMethodException e) {
logger.severe("Error initializing commands class " + clazz + ": ");
e.printStackTrace();
return null;
} catch (InvocationTargetException e) {
logger.severe("Error initializing commands class " + clazz + ": ");
e.printStackTrace();
return null;
} catch (InstantiationException e) {
logger.severe("Error initializing commands class " + clazz + ": ");
e.printStackTrace();
return null;
} catch (IllegalAccessException e) {
logger.severe("Error initializing commands class " + clazz + ": ");
e.printStackTrace();
return null;
}
}
}

View File

@@ -0,0 +1,68 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
public class SuggestionContext {
private static final SuggestionContext FOR_LAST = new SuggestionContext(null, true);
private static final SuggestionContext FOR_HANGING = new SuggestionContext(null, false);
private final Character flag;
private final boolean forLast;
private SuggestionContext(Character flag, boolean forLast) {
this.flag = flag;
this.forLast = forLast;
}
public boolean forHangingValue() {
return flag == null && !forLast;
}
public boolean forLastValue() {
return flag == null && forLast;
}
public boolean forFlag() {
return flag != null;
}
public Character getFlag() {
return flag;
}
@Override
public String toString() {
return forFlag() ? ("-" + getFlag()) : (forHangingValue() ? "hanging" : "last");
}
public static SuggestionContext flag(Character flag) {
return new SuggestionContext(flag, false);
}
public static SuggestionContext lastValue() {
return FOR_LAST;
}
public static SuggestionContext hangingValue() {
return FOR_HANGING;
}
}

View File

@@ -0,0 +1,25 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
public class UnhandledCommandException extends CommandException {
private static final long serialVersionUID = 3370887306593968091L;
}

View File

@@ -0,0 +1,28 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.commands;
public class WrappedCommandException extends CommandException {
private static final long serialVersionUID = -4075721444847778918L;
public WrappedCommandException(Throwable t) {
super(t);
}
}

View File

@@ -0,0 +1,22 @@
package com.sk89q.minecraft.util.commands;
public interface WrappedCommandSender {
String getName();
void sendMessage(String message);
void sendMessage(String[] messages);
boolean hasPermission(String permission);
Type getType();
Object getCommandSender();
public static enum Type {
CONSOLE,
PLAYER,
BLOCK,
UNKNOWN
}
}

View File

@@ -0,0 +1,8 @@
package com.sk89q.minecraft.util.commands;
public class WrappedCommandsManager extends CommandsManager<WrappedCommandSender> {
@Override
public boolean hasPermission(WrappedCommandSender player, String perm) {
return player.hasPermission(perm);
}
}

View File

@@ -0,0 +1,71 @@
/*
* CommandBook
* Copyright (C) 2011 sk89q <http://www.sk89q.com>
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.minecraft.util.pagination;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.sk89q.minecraft.util.commands.CommandException;
import com.sk89q.minecraft.util.commands.WrappedCommandSender;
/**
* Commands that wish to display a paginated list of results can use this class to do
* the actual pagination, giving a list of items, a page number, and basic formatting information.
*/
public abstract class PaginatedResult<T> {
protected final int resultsPerPage;
public PaginatedResult() {
this(6);
}
public PaginatedResult(int resultsPerPage) {
assert resultsPerPage > 0;
this.resultsPerPage = resultsPerPage;
}
public void display(WrappedCommandSender sender, Collection<? extends T> results, int page) throws CommandException {
this.display(sender, new ArrayList<T>(results), page);
}
public void display(WrappedCommandSender sender, List<? extends T> results, int page) throws CommandException {
if (results.size() == 0) throw new CommandException("No results match!");
int maxPages = results.size() / this.resultsPerPage + 1;
// If the content divides perfectly, eg (18 entries, and 9 per page)
// we end up with a blank page this handles this case
if (results.size() % this.resultsPerPage == 0) {
maxPages--;
}
if (page <= 0 || page > maxPages) throw new CommandException("Unknown page selected! " + maxPages + " total pages.");
sender.sendMessage(this.formatHeader(page, maxPages));
for (int i = this.resultsPerPage * (page - 1); i < this.resultsPerPage * page && i < results.size(); i++) {
sender.sendMessage(this.format(results.get(i), i));
}
}
public abstract String formatHeader(int page, int maxPages);
public abstract String format(T entry, int index);
}

View File

@@ -0,0 +1,23 @@
package com.sk89q.minecraft.util.pagination;
import com.sk89q.minecraft.util.commands.ChatColor;
public abstract class SimplePaginatedResult<T> extends PaginatedResult<T> {
protected final String header;
public SimplePaginatedResult(String header) {
super();
this.header = header;
}
public SimplePaginatedResult(String header, int resultsPerPage) {
super(resultsPerPage);
this.header = header;
}
@Override
public String formatHeader(int page, int maxPages) {
return ChatColor.YELLOW + this.header + " (page " + page + "/" + maxPages + ")";
}
}

View File

@@ -0,0 +1,48 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.util;
import java.lang.reflect.Field;
/**
* @author zml2008
*/
public final class ReflectionUtil {
private ReflectionUtil() {
}
@SuppressWarnings("unchecked")
public static <T> T getField(Object from, String name) {
Class<?> checkClass = from.getClass();
do {
try {
Field field = checkClass.getDeclaredField(name);
field.setAccessible(true);
return (T) field.get(from);
} catch (NoSuchFieldException e) {
} catch (IllegalAccessException e) {
}
} while (checkClass.getSuperclass() != Object.class && ((checkClass = checkClass.getSuperclass()) != null));
return null;
}
}

View File

@@ -0,0 +1,311 @@
/*
* WorldEdit, a Minecraft world manipulation toolkit
* Copyright (C) sk89q <http://www.sk89q.com>
* Copyright (C) WorldEdit team and contributors
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sk89q.util;
import java.util.Collection;
import java.util.Map;
/**
* String utilities.
*
* @author sk89q
*/
public final class StringUtil {
private StringUtil() {
}
/**
* Trim a string if it is longer than a certain length.
*
* @param str
* @param len
* @return
*/
public static String trimLength(String str, int len) {
if (str.length() > len) {
return str.substring(0, len);
}
return str;
}
/**
* Join an array of strings into a string.
*
* @param str
* @param delimiter
* @param initialIndex
* @return
*/
public static String joinString(String[] str, String delimiter,
int initialIndex) {
if (str.length == 0) {
return "";
}
StringBuilder buffer = new StringBuilder(str[initialIndex]);
for (int i = initialIndex + 1; i < str.length; ++i) {
buffer.append(delimiter).append(str[i]);
}
return buffer.toString();
}
/**
* Join an array of strings into a string.
*
* @param str
* @param delimiter
* @param initialIndex
* @param quote
* @return
*/
public static String joinQuotedString(String[] str, String delimiter,
int initialIndex, String quote) {
if (str.length == 0) {
return "";
}
StringBuilder buffer = new StringBuilder();
buffer.append(quote);
buffer.append(str[initialIndex]);
buffer.append(quote);
for (int i = initialIndex + 1; i < str.length; ++i) {
buffer.append(delimiter).append(quote).append(str[i]).append(quote);
}
return buffer.toString();
}
/**
* Join an array of strings into a string.
*
* @param str
* @param delimiter
* @return
*/
public static String joinString(String[] str, String delimiter) {
return joinString(str, delimiter, 0);
}
/**
* Join an array of strings into a string.
*
* @param str
* @param delimiter
* @param initialIndex
* @return
*/
public static String joinString(Object[] str, String delimiter,
int initialIndex) {
if (str.length == 0) {
return "";
}
StringBuilder buffer = new StringBuilder(str[initialIndex].toString());
for (int i = initialIndex + 1; i < str.length; ++i) {
buffer.append(delimiter).append(str[i].toString());
}
return buffer.toString();
}
/**
* Join an array of strings into a string.
*
* @param str
* @param delimiter
* @param initialIndex
* @return
*/
public static String joinString(int[] str, String delimiter,
int initialIndex) {
if (str.length == 0) {
return "";
}
StringBuilder buffer = new StringBuilder(Integer.toString(str[initialIndex]));
for (int i = initialIndex + 1; i < str.length; ++i) {
buffer.append(delimiter).append(Integer.toString(str[i]));
}
return buffer.toString();
}
/**
* Join an list of strings into a string.
*
* @param str
* @param delimiter
* @param initialIndex
* @return
*/
public static String joinString(Collection<?> str, String delimiter,
int initialIndex) {
if (str.size() == 0) {
return "";
}
StringBuilder buffer = new StringBuilder();
int i = 0;
for (Object o : str) {
if (i >= initialIndex) {
if (i > 0) {
buffer.append(delimiter);
}
buffer.append(o.toString());
}
++i;
}
return buffer.toString();
}
/**
* <p>Find the Levenshtein distance between two Strings.</p>
*
* <p>This is the number of changes needed to change one String into
* another, where each change is a single character modification (deletion,
* insertion or substitution).</p>
*
* <p>The previous implementation of the Levenshtein distance algorithm
* was from <a href="http://www.merriampark.com/ld.htm">http://www.merriampark.com/ld.htm</a></p>
*
* <p>Chas Emerick has written an implementation in Java, which avoids an OutOfMemoryError
* which can occur when my Java implementation is used with very large strings.<br>
* This implementation of the Levenshtein distance algorithm
* is from <a href="http://www.merriampark.com/ldjava.htm">http://www.merriampark.com/ldjava.htm</a></p>
*
* <pre>
* StringUtil.getLevenshteinDistance(null, *) = IllegalArgumentException
* StringUtil.getLevenshteinDistance(*, null) = IllegalArgumentException
* StringUtil.getLevenshteinDistance("","") = 0
* StringUtil.getLevenshteinDistance("","a") = 1
* StringUtil.getLevenshteinDistance("aaapppp", "") = 7
* StringUtil.getLevenshteinDistance("frog", "fog") = 1
* StringUtil.getLevenshteinDistance("fly", "ant") = 3
* StringUtil.getLevenshteinDistance("elephant", "hippo") = 7
* StringUtil.getLevenshteinDistance("hippo", "elephant") = 7
* StringUtil.getLevenshteinDistance("hippo", "zzzzzzzz") = 8
* StringUtil.getLevenshteinDistance("hello", "hallo") = 1
* </pre>
*
* @param s the first String, must not be null
* @param t the second String, must not be null
* @return result distance
* @throws IllegalArgumentException if either String input <code>null</code>
*/
public static int getLevenshteinDistance(String s, String t) {
if (s == null || t == null) {
throw new IllegalArgumentException("Strings must not be null");
}
/*
* The difference between this impl. and the previous is that, rather
* than creating and retaining a matrix of size s.length()+1 by
* t.length()+1, we maintain two single-dimensional arrays of length
* s.length()+1. The first, d, is the 'current working' distance array
* that maintains the newest distance cost counts as we iterate through
* the characters of String s. Each time we increment the index of
* String t we are comparing, d is copied to p, the second int[]. Doing
* so allows us to retain the previous cost counts as required by the
* algorithm (taking the minimum of the cost count to the left, up one,
* and diagonally up and to the left of the current cost count being
* calculated). (Note that the arrays aren't really copied anymore, just
* switched...this is clearly much better than cloning an array or doing
* a System.arraycopy() each time through the outer loop.)
*
* Effectively, the difference between the two implementations is this
* one does not cause an out of memory condition when calculating the LD
* over two very large strings.
*/
int n = s.length(); // length of s
int m = t.length(); // length of t
if (n == 0) {
return m;
} else if (m == 0) {
return n;
}
int p[] = new int[n + 1]; // 'previous' cost array, horizontally
int d[] = new int[n + 1]; // cost array, horizontally
int _d[]; // placeholder to assist in swapping p and d
// indexes into strings s and t
int i; // iterates through s
int j; // iterates through t
char tj; // jth character of t
int cost; // cost
for (i = 0; i <= n; ++i) {
p[i] = i;
}
for (j = 1; j <= m; ++j) {
tj = t.charAt(j - 1);
d[0] = j;
for (i = 1; i <= n; ++i) {
cost = s.charAt(i - 1) == tj ? 0 : 1;
// minimum of cell to the left+1, to the top+1, diagonally left
// and up +cost
d[i] = Math.min(Math.min(d[i - 1] + 1, p[i] + 1), p[i - 1]
+ cost);
}
// copy current distance counts to 'previous row' distance counts
_d = p;
p = d;
d = _d;
}
// our last action in the above loop was to switch d and p, so p now
// actually has the most recent cost counts
return p[n];
}
public static <T extends Enum<?>> T lookup(Map<String, T> lookup, String name, boolean fuzzy) {
String testName = name.replaceAll("[ _]", "").toLowerCase();
T type = lookup.get(testName);
if (type != null) {
return type;
}
if (!fuzzy) {
return null;
}
int minDist = -1;
for (Map.Entry<String, T> entry : lookup.entrySet()) {
final String key = entry.getKey();
if (key.charAt(0) != testName.charAt(0)) {
continue;
}
int dist = getLevenshteinDistance(key, testName);
if ((dist < minDist || minDist == -1) && dist < 2) {
minDist = dist;
type = entry.getValue();
}
}
return type;
}
}