From 653e37ad24cacf8fdfa62a7849d2c3a3e79881c2 Mon Sep 17 00:00:00 2001 From: aPunch Date: Wed, 25 Jan 2012 09:29:54 -0600 Subject: [PATCH] initial command commit, needs lots of work --- lib/CitizensAPI.jar | Bin 17758 -> 17711 bytes src/net/citizensnpcs/Citizens.java | 127 +++++- src/net/citizensnpcs/EventListen.java | 28 +- .../citizensnpcs/command/CommandContext.java | 118 +++++ .../command/CommandIdentifier.java | 48 ++ .../citizensnpcs/command/CommandManager.java | 411 ++++++++++++++++++ src/net/citizensnpcs/command/Injector.java | 15 + .../command/annotation/Command.java | 40 ++ .../command/annotation/NestedCommand.java | 29 ++ .../command/annotation/Permission.java | 29 ++ .../command/annotation/Requirements.java | 12 + .../command/annotation/ServerCommand.java | 8 + .../command/command/NPCCommands.java | 42 ++ .../command/exception/CommandException.java | 36 ++ .../exception/CommandUsageException.java | 35 ++ .../MissingNestedCommandException.java | 28 ++ .../exception/NoPermissionsException.java | 24 + .../RequirementMissingException.java | 10 + .../exception/ServerCommandException.java | 5 + .../exception/UnhandledCommandException.java | 24 + .../exception/WrappedCommandException.java | 28 ++ src/net/citizensnpcs/npc/CitizensNPC.java | 10 +- .../citizensnpcs/npc/CitizensNPCManager.java | 24 +- src/net/citizensnpcs/util/Messaging.java | 4 + src/net/citizensnpcs/util/StringHelper.java | 59 +++ 25 files changed, 1154 insertions(+), 40 deletions(-) create mode 100644 src/net/citizensnpcs/command/CommandContext.java create mode 100644 src/net/citizensnpcs/command/CommandIdentifier.java create mode 100644 src/net/citizensnpcs/command/CommandManager.java create mode 100644 src/net/citizensnpcs/command/Injector.java create mode 100644 src/net/citizensnpcs/command/annotation/Command.java create mode 100644 src/net/citizensnpcs/command/annotation/NestedCommand.java create mode 100644 src/net/citizensnpcs/command/annotation/Permission.java create mode 100644 src/net/citizensnpcs/command/annotation/Requirements.java create mode 100644 src/net/citizensnpcs/command/annotation/ServerCommand.java create mode 100644 src/net/citizensnpcs/command/command/NPCCommands.java create mode 100644 src/net/citizensnpcs/command/exception/CommandException.java create mode 100644 src/net/citizensnpcs/command/exception/CommandUsageException.java create mode 100644 src/net/citizensnpcs/command/exception/MissingNestedCommandException.java create mode 100644 src/net/citizensnpcs/command/exception/NoPermissionsException.java create mode 100644 src/net/citizensnpcs/command/exception/RequirementMissingException.java create mode 100644 src/net/citizensnpcs/command/exception/ServerCommandException.java create mode 100644 src/net/citizensnpcs/command/exception/UnhandledCommandException.java create mode 100644 src/net/citizensnpcs/command/exception/WrappedCommandException.java create mode 100644 src/net/citizensnpcs/util/StringHelper.java diff --git a/lib/CitizensAPI.jar b/lib/CitizensAPI.jar index cf8a213dd2388a22222d2a2e401992e8cb5c118d..94097ea3b148e13b9dad3920df95bdbeb2eddce6 100644 GIT binary patch delta 2448 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 characterManager = DefaultInstanceFactory.create(); - private static final InstanceFactory traitManager = DefaultInstanceFactory.create(); + private static Storage saves; + + private CitizensNPCManager npcManager; + private final InstanceFactory characterManager = new DefaultInstanceFactory(); + private final InstanceFactory traitManager = new DefaultInstanceFactory(); + private final CommandManager cmdManager = new CommandManager(); private Settings config; - private Storage saves; @Override public boolean onCommand(CommandSender sender, Command cmd, String cmdName, String[] args) { - if (args[0].equals("spawn")) { - NPC npc = npcManager.createNPC(ChatColor.GREEN + "aPunch"); - npc.spawn(((Player) sender).getLocation()); - ((CitizensNPC) npc).save(saves); - } else if (args[0].equals("despawn")) { - for (NPC npc : npcManager.getSpawnedNPCs()) - npc.remove(); + Player player = null; + if (sender instanceof Player) + player = (Player) sender; + + try { + // must put command into split. + String[] split = new String[args.length + 1]; + System.arraycopy(args, 0, split, 1, args.length); + split[0] = cmd.getName().toLowerCase(); + + String modifier = ""; + if (args.length > 0) + modifier = args[0]; + + // No command found! + if (!cmdManager.hasCommand(split[0], modifier)) { + if (!modifier.isEmpty()) { + boolean value = handleMistake(sender, split[0], modifier); + return value; + } + } + + NPC npc = null; + if (player != null && npcManager.hasNPCSelected(player)) + npc = npcManager.getSelectedNPC(player); + + try { + cmdManager.execute(split, player, player == null ? sender : player, npc); + } catch (ServerCommandException ex) { + sender.sendMessage("You must be in-game to execute that command."); + } catch (NoPermissionsException ex) { + Messaging.sendError(player, "You don't have permission to execute that command."); + } catch (MissingNestedCommandException ex) { + Messaging.sendError(player, ex.getUsage()); + } catch (CommandUsageException ex) { + Messaging.sendError(player, ex.getMessage()); + Messaging.sendError(player, ex.getUsage()); + } catch (RequirementMissingException ex) { + Messaging.sendError(player, ex.getMessage()); + } catch (WrappedCommandException e) { + throw e.getCause(); + } catch (UnhandledCommandException e) { + return false; + } + } catch (NumberFormatException e) { + Messaging.sendError(player, "That is not a valid number."); + } catch (Throwable excp) { + excp.printStackTrace(); + Messaging.sendError(player, "Please report this error: [See console]"); + Messaging.sendError(player, excp.getClass().getName() + ": " + excp.getMessage()); } return true; } @@ -81,6 +140,8 @@ public class Citizens extends JavaPlugin { // Register events getServer().getPluginManager().registerEvents(new EventListen(npcManager), this); + // Register commands and permissions + registerCommands(); registerPermissions(); Messaging.log("v" + getDescription().getVersion() + " enabled."); @@ -102,16 +163,20 @@ public class Citizens extends JavaPlugin { } } + public static Storage getNPCStorage() { + return saves; + } + private void saveNPCs() { for (NPC npc : npcManager.getAllNPCs()) - ((CitizensNPC) npc).save(saves); - saves.save(); + ((CitizensNPC) npc).save(); + getNPCStorage().save(); } private void setupNPCs() throws NPCLoadException { traitManager.register("location", SpawnLocation.class); - for (DataKey key : saves.getKey("npc").getIntegerSubKeys()) { + for (DataKey key : getNPCStorage().getKey("npc").getIntegerSubKeys()) { int id = Integer.parseInt(key.name()); if (!key.keyExists("name")) throw new NPCLoadException("Could not find a name for the NPC with ID '" + id + "'."); @@ -148,9 +213,43 @@ public class Citizens extends JavaPlugin { private void registerPermissions() { Map children = new HashMap(); + children.put("citizens.npc.spawn", true); + children.put("citizens.npc.despawn", true); children.put("citizens.npc.select", true); Permission perm = new Permission("citizens.*", PermissionDefault.OP, children); getServer().getPluginManager().addPermission(perm); } + + private void registerCommands() { + cmdManager.register(NPCCommands.class); + } + + private boolean handleMistake(CommandSender sender, String command, String modifier) { + String[] modifiers = cmdManager.getAllCommandModifiers(command); + Map values = new TreeMap(); + int i = 0; + for (String string : modifiers) { + values.put(StringHelper.getLevenshteinDistance(modifier, string), modifiers[i]); + ++i; + } + int best = 0; + boolean stop = false; + Set possible = new HashSet(); + for (Entry entry : values.entrySet()) { + if (!stop) { + best = entry.getKey(); + stop = true; + } else if (entry.getKey() > best) + break; + possible.add(entry.getValue()); + } + if (possible.size() > 0) { + sender.sendMessage(ChatColor.GRAY + "Unknown command. Did you mean:"); + for (String string : possible) + sender.sendMessage(StringHelper.wrap(" /") + command + " " + StringHelper.wrap(string)); + return true; + } + return false; + } } \ No newline at end of file diff --git a/src/net/citizensnpcs/EventListen.java b/src/net/citizensnpcs/EventListen.java index a963213a3..d63a64929 100644 --- a/src/net/citizensnpcs/EventListen.java +++ b/src/net/citizensnpcs/EventListen.java @@ -25,10 +25,10 @@ import org.bukkit.event.world.ChunkUnloadEvent; public class EventListen implements Listener { private final List toRespawn = new ArrayList(); - private final CitizensNPCManager manager; + private final CitizensNPCManager npcManager; - public EventListen(CitizensNPCManager manager) { - this.manager = manager; + public EventListen(CitizensNPCManager npcManager) { + this.npcManager = npcManager; } /* @@ -38,7 +38,7 @@ public class EventListen implements Listener { public void onChunkLoad(ChunkLoadEvent event) { Iterator itr = toRespawn.iterator(); while (itr.hasNext()) { - NPC npc = manager.getNPC(itr.next()); + NPC npc = npcManager.getNPC(itr.next()); npc.spawn(npc.getTrait(SpawnLocation.class).getLocation()); itr.remove(); } @@ -49,7 +49,7 @@ public class EventListen implements Listener { if (event.isCancelled()) return; - for (NPC npc : manager.getSpawnedNPCs()) { + for (NPC npc : npcManager.getSpawnedNPCs()) { Location loc = npc.getBukkitEntity().getLocation(); if (event.getWorld().equals(loc.getWorld()) && event.getChunk().getX() == loc.getChunk().getX() && event.getChunk().getZ() == loc.getChunk().getZ()) { @@ -65,14 +65,14 @@ public class EventListen implements Listener { */ @EventHandler public void onEntityDamage(EntityDamageEvent event) { - if (!manager.isNPC(event.getEntity())) + if (!npcManager.isNPC(event.getEntity())) return; event.setCancelled(true); // TODO implement damage handlers if (event instanceof EntityDamageByEntityEvent) { EntityDamageByEntityEvent e = (EntityDamageByEntityEvent) event; if (e.getDamager() instanceof Player) { - NPC npc = manager.getNPC(event.getEntity()); + NPC npc = npcManager.getNPC(event.getEntity()); if (npc.getCharacter() != null) npc.getCharacter().onLeftClick(npc, (Player) e.getDamager()); } @@ -81,15 +81,15 @@ public class EventListen implements Listener { @EventHandler public void onEntityTarget(EntityTargetEvent event) { - if (manager.isNPC(event.getTarget())) - if (event.isCancelled() || !manager.isNPC(event.getEntity()) || !(event.getTarget() instanceof Player)) + if (npcManager.isNPC(event.getTarget())) + if (event.isCancelled() || !npcManager.isNPC(event.getEntity()) || !(event.getTarget() instanceof Player)) return; - NPC npc = manager.getNPC(event.getEntity()); + NPC npc = npcManager.getNPC(event.getEntity()); Player player = (Player) event.getTarget(); - if (!manager.hasSelected(player, npc)) { - if (manager.canSelect(player, npc)) { - manager.selectNPC(player, npc); + if (!npcManager.npcIsSelectedByPlayer(player, npc)) { + if (npcManager.canSelectNPC(player, npc)) { + npcManager.selectNPC(player, npc); Messaging.sendWithNPC(player, Setting.SELECTION_MESSAGE.getString(), npc); if (!Setting.QUICK_SELECT.getBoolean()) return; @@ -104,7 +104,7 @@ public class EventListen implements Listener { */ @EventHandler public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { - if (!manager.isNPC(event.getRightClicked())) + if (!npcManager.isNPC(event.getRightClicked())) return; Bukkit.getPluginManager().callEvent( diff --git a/src/net/citizensnpcs/command/CommandContext.java b/src/net/citizensnpcs/command/CommandContext.java new file mode 100644 index 000000000..8bb461302 --- /dev/null +++ b/src/net/citizensnpcs/command/CommandContext.java @@ -0,0 +1,118 @@ +// $Id$ +/* + * Copyright (C) 2010 sk89q + * + * 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 . + */ + +package net.citizensnpcs.command; + +import java.util.HashSet; +import java.util.Set; + +import com.google.common.base.Joiner; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; + +public class CommandContext { + protected String[] args; + protected Set flags = new HashSet(); + + public CommandContext(String args) { + this(args.split(" ")); + } + + public CommandContext(String[] args) { + int i = 1; + for (; i < args.length; i++) { + if (args[i].length() == 0) { + // Ignore this + } else if (args[i].charAt(0) == '-' && args[i].matches("^-[a-zA-Z]+$")) { + for (int k = 1; k < args[i].length(); k++) + flags.add(args[i].charAt(k)); + args[i] = ""; + } + } + this.args = Iterables.toArray(Splitter.on(" ").omitEmptyStrings().split(Joiner.on(" ").skipNulls().join(args)), + String.class); + } + + public String getCommand() { + return args[0]; + } + + public boolean matches(String command) { + return args[0].equalsIgnoreCase(command); + } + + public String getString(int index) { + return args[index + 1]; + } + + public String getString(int index, String def) { + return index + 1 < args.length ? args[index + 1] : def; + } + + public String getJoinedStrings(int initialIndex) { + initialIndex = initialIndex + 1; + StringBuilder buffer = new StringBuilder(args[initialIndex]); + for (int i = initialIndex + 1; i < args.length; i++) + buffer.append(" ").append(args[i]); + return buffer.toString(); + } + + public int getInteger(int index) throws NumberFormatException { + return Integer.parseInt(args[index + 1]); + } + + public int getInteger(int index, int def) throws NumberFormatException { + return index + 1 < args.length ? Integer.parseInt(args[index + 1]) : def; + } + + public double getDouble(int index) throws NumberFormatException { + return Double.parseDouble(args[index + 1]); + } + + public double getDouble(int index, double def) throws NumberFormatException { + return index + 1 < args.length ? Double.parseDouble(args[index + 1]) : def; + } + + public String[] getSlice(int index) { + String[] slice = new String[args.length - index]; + System.arraycopy(args, index, slice, 0, args.length - index); + return slice; + } + + public String[] getPaddedSlice(int index, int padding) { + String[] slice = new String[args.length - index + padding]; + System.arraycopy(args, index, slice, padding, args.length - index); + return slice; + } + + public boolean hasFlag(char ch) { + return flags.contains(ch); + } + + public Set getFlags() { + return flags; + } + + public int length() { + return args.length; + } + + public int argsLength() { + return args.length - 1; + } +} \ No newline at end of file diff --git a/src/net/citizensnpcs/command/CommandIdentifier.java b/src/net/citizensnpcs/command/CommandIdentifier.java new file mode 100644 index 000000000..66260e1da --- /dev/null +++ b/src/net/citizensnpcs/command/CommandIdentifier.java @@ -0,0 +1,48 @@ +package net.citizensnpcs.command; + +import com.google.common.base.Objects; + +public class CommandIdentifier { + private final String modifier; + private final String command; + + public CommandIdentifier(String command, String modifier) { + this.command = command; + this.modifier = modifier; + } + + @Override + public int hashCode() { + return Objects.hashCode(command, modifier); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + CommandIdentifier other = (CommandIdentifier) obj; + if (command == null) { + if (other.command != null) + return false; + } else if (!command.equals(other.command)) + return false; + if (modifier == null) { + if (other.modifier != null) + return false; + } else if (!modifier.equals(other.modifier)) + return false; + return true; + } + + public String getModifier() { + return modifier; + } + + public String getCommand() { + return command; + } +} \ No newline at end of file diff --git a/src/net/citizensnpcs/command/CommandManager.java b/src/net/citizensnpcs/command/CommandManager.java new file mode 100644 index 000000000..15e5ef7e5 --- /dev/null +++ b/src/net/citizensnpcs/command/CommandManager.java @@ -0,0 +1,411 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * 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 . + */ + +package net.citizensnpcs.command; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import net.citizensnpcs.command.annotation.Command; +import net.citizensnpcs.command.annotation.Requirements; +import net.citizensnpcs.command.annotation.NestedCommand; +import net.citizensnpcs.command.annotation.Permission; +import net.citizensnpcs.command.annotation.ServerCommand; +import net.citizensnpcs.command.exception.CommandException; +import net.citizensnpcs.command.exception.CommandUsageException; +import net.citizensnpcs.command.exception.MissingNestedCommandException; +import net.citizensnpcs.command.exception.NoPermissionsException; +import net.citizensnpcs.command.exception.ServerCommandException; +import net.citizensnpcs.command.exception.UnhandledCommandException; +import net.citizensnpcs.command.exception.WrappedCommandException; +import net.citizensnpcs.util.Messaging; + +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; + +public class CommandManager { + + // Logger for general errors. + private static final Logger logger = Logger.getLogger(CommandManager.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. + */ + private Map> commands = new HashMap>(); + + // Used to store the instances associated with a method. + private Map instances = new HashMap(); + + /* + * Mapping of commands (not including aliases) with a description. This is + * only for top level commands. + */ + private Map descs = new HashMap(); + + // Stores the injector used to getInstance. + private Injector injector; + + private Map requirements = new HashMap(); + + private Map serverCommands = new HashMap(); + + /* + * 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. + */ + public void register(Class cls) { + registerMethods(cls, null); + } + + /* + * Register the methods of a class. This will automatically construct + * instances as necessary. + */ + private void registerMethods(Class cls, Method parent) { + try { + if (getInjector() == null) + registerMethods(cls, parent, null); + else { + Object obj = getInjector().getInstance(cls); + 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); + } + } + + // Register the methods of a class. + private void registerMethods(Class cls, Method parent, Object obj) { + Map map; + + // 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(); + 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); + String[] modifiers = cmd.modifiers(); + + // Cache the aliases too + for (String alias : cmd.aliases()) + for (String modifier : modifiers) + map.put(new CommandIdentifier(alias, modifier), method); + + Requirements cmdRequirements = null; + if (method.getDeclaringClass().isAnnotationPresent(Requirements.class)) + cmdRequirements = method.getDeclaringClass().getAnnotation(Requirements.class); + + if (method.isAnnotationPresent(Requirements.class)) + cmdRequirements = method.getAnnotation(Requirements.class); + + if (requirements != null) + requirements.put(method, cmdRequirements); + + ServerCommand serverCommand = null; + if (method.isAnnotationPresent(ServerCommand.class)) + serverCommand = method.getAnnotation(ServerCommand.class); + + if (serverCommand != null) + serverCommands.put(method, serverCommand); + + // 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); + Messaging.log("Put instance."); + } + + // Build a list of commands and their usage details, at least for + // root level commands + if (parent == null) + if (cmd.usage().length() == 0) + descs.put(new CommandIdentifier(cmd.aliases()[0], cmd.modifiers()[0]), cmd.desc()); + else + descs.put(new CommandIdentifier(cmd.aliases()[0], cmd.modifiers()[0]), + cmd.usage() + " - " + cmd.desc()); + + // 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); + } + } + } + + /* + * Checks to see whether there is a command named such at the root level. + * This will check aliases as well. + */ + public boolean hasCommand(String command, String modifier) { + return commands.get(null).containsKey(new CommandIdentifier(command.toLowerCase(), modifier.toLowerCase())) + || commands.get(null).containsKey(new CommandIdentifier(command.toLowerCase(), "*")); + } + + // Get a list of command descriptions. This is only for root commands. + public Map getCommands() { + return descs; + } + + // Get the usage string for a command. + private String getUsage(String[] args, int level, Command cmd) { + StringBuilder command = new StringBuilder(); + + command.append("/"); + + for (int i = 0; i <= level; i++) + command.append(args[i] + " "); + + // removed arbitrary positioning of flags. + command.append(cmd.usage()); + + return command.toString(); + } + + // Get the usage string for a nested command. + private String getNestedUsage(String[] args, int level, Method method, Player player) throws CommandException { + StringBuilder command = new StringBuilder(); + + command.append("/"); + + for (int i = 0; i <= level; i++) + command.append(args[i] + " "); + + Map map = commands.get(method); + boolean found = false; + + command.append("<"); + + Set allowedCommands = new HashSet(); + + for (Map.Entry 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(joinString(allowedCommands, "|", 0)); + else { + if (!found) + command.append("?"); + else + throw new NoPermissionsException(); + } + + command.append(">"); + + return command.toString(); + } + + 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(); + } + + /* + * Attempt to execute a command. This version takes a separate command name + * (for the root command) and then a list of following arguments. + */ + public void execute(String cmd, String[] args, Player 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. + public void execute(String[] args, Player 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. + public void executeMethod(Method parent, String[] args, Player player, Object[] methodArgs, int level) + throws CommandException { + String cmdName = args[level]; + String modifier = ""; + if (args.length > level + 1) + modifier = args[level + 1]; + + Map map = commands.get(parent); + Method method = map.get(new CommandIdentifier(cmdName.toLowerCase(), modifier.toLowerCase())); + if (method == null) + method = map.get(new CommandIdentifier(cmdName.toLowerCase(), "*")); + + if (method != null && methodArgs != null && serverCommands.get(method) == null + && methodArgs[1] instanceof ConsoleCommandSender) + throw new ServerCommandException(); + + if (method == null) + if (parent == null) + throw new UnhandledCommandException(); + else + throw new MissingNestedCommandException("Unknown command: " + cmdName, getNestedUsage(args, level - 1, + parent, player)); + + if (methodArgs[1] instanceof Player) + if (!hasPermission(method, player)) + throw new NoPermissionsException(); + + int argsCount = args.length - 1 - level; + + if (method.isAnnotationPresent(NestedCommand.class)) + if (argsCount == 0) + throw new MissingNestedCommandException("Sub-command required.", getNestedUsage(args, level, method, + player)); + else + executeMethod(method, args, player, methodArgs, level + 1); + else if (methodArgs[1] instanceof Player) { + Requirements cmdRequirements = requirements.get(method); + + // TODO add requirements + if (cmdRequirements != null) + Messaging.debug(""); + else + Messaging.debug("No annotation present."); + } + + Command cmd = method.getAnnotation(Command.class); + + String[] newArgs = new String[args.length - level]; + System.arraycopy(args, level, newArgs, 0, args.length - level); + + CommandContext context = new CommandContext(newArgs); + + 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)); + + for (char flag : context.getFlags()) + if (cmd.flags().indexOf(String.valueOf(flag)) == -1) + throw new CommandUsageException("Unknown flag: " + flag, getUsage(args, level, cmd)); + + methodArgs[0] = context; + Object instance = instances.get(method); + 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. + private boolean hasPermission(Method method, Player player) { + Permission permission = method.getAnnotation(Permission.class); + if (permission == null) + return true; + + if (hasPermission(player, permission.value())) + return true; + + return false; + } + + /* + * 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. + public void setInjector(Injector injector) { + this.injector = injector; + } + + // Returns whether a player has permission. + private boolean hasPermission(Player player, String perm) { + return ((Player) player).hasPermission("citizens." + perm); + } + + public String[] getAllCommandModifiers(String command) { + Set cmds = new HashSet(); + for (Map enclosing : commands.values()) { + for (CommandIdentifier identifier : enclosing.keySet()) { + if (identifier.getCommand().equals(command)) { + cmds.add(identifier.getModifier()); + } + } + } + return cmds.toArray(new String[cmds.size()]); + } +} \ No newline at end of file diff --git a/src/net/citizensnpcs/command/Injector.java b/src/net/citizensnpcs/command/Injector.java new file mode 100644 index 000000000..76e82c73d --- /dev/null +++ b/src/net/citizensnpcs/command/Injector.java @@ -0,0 +1,15 @@ +// $Id$ +/* + * Copyright (C) 2010 sk89q + * All rights reserved. + */ + +package net.citizensnpcs.command; + +import java.lang.reflect.InvocationTargetException; + +public interface Injector { + + public Object getInstance(Class cls) throws InvocationTargetException, IllegalAccessException, + InstantiationException; +} \ No newline at end of file diff --git a/src/net/citizensnpcs/command/annotation/Command.java b/src/net/citizensnpcs/command/annotation/Command.java new file mode 100644 index 000000000..22fadd2a6 --- /dev/null +++ b/src/net/citizensnpcs/command/annotation/Command.java @@ -0,0 +1,40 @@ +// $Id$ +/* + * Copyright (C) 2010 sk89q + * + * 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 . + */ + +package net.citizensnpcs.command.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Command { + + String[] aliases(); + + String usage() default ""; + + String desc(); + + String[] modifiers() default ""; + + int min() default 0; + + int max() default -1; + + String flags() default ""; +} \ No newline at end of file diff --git a/src/net/citizensnpcs/command/annotation/NestedCommand.java b/src/net/citizensnpcs/command/annotation/NestedCommand.java new file mode 100644 index 000000000..4fce20bc8 --- /dev/null +++ b/src/net/citizensnpcs/command/annotation/NestedCommand.java @@ -0,0 +1,29 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * 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 . + */ + +package net.citizensnpcs.command.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface NestedCommand { + + Class[] value(); +} \ No newline at end of file diff --git a/src/net/citizensnpcs/command/annotation/Permission.java b/src/net/citizensnpcs/command/annotation/Permission.java new file mode 100644 index 000000000..894117bcf --- /dev/null +++ b/src/net/citizensnpcs/command/annotation/Permission.java @@ -0,0 +1,29 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010 sk89q + * + * 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 . + */ + +package net.citizensnpcs.command.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Permission { + + String value(); +} \ No newline at end of file diff --git a/src/net/citizensnpcs/command/annotation/Requirements.java b/src/net/citizensnpcs/command/annotation/Requirements.java new file mode 100644 index 000000000..455bf6f0f --- /dev/null +++ b/src/net/citizensnpcs/command/annotation/Requirements.java @@ -0,0 +1,12 @@ +package net.citizensnpcs.command.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface Requirements { + + boolean requireSelected() default false; + + boolean requireOwnership() default false; +} \ No newline at end of file diff --git a/src/net/citizensnpcs/command/annotation/ServerCommand.java b/src/net/citizensnpcs/command/annotation/ServerCommand.java new file mode 100644 index 000000000..8fa4b32e1 --- /dev/null +++ b/src/net/citizensnpcs/command/annotation/ServerCommand.java @@ -0,0 +1,8 @@ +package net.citizensnpcs.command.annotation; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface ServerCommand { +} \ No newline at end of file diff --git a/src/net/citizensnpcs/command/command/NPCCommands.java b/src/net/citizensnpcs/command/command/NPCCommands.java new file mode 100644 index 000000000..3717e8709 --- /dev/null +++ b/src/net/citizensnpcs/command/command/NPCCommands.java @@ -0,0 +1,42 @@ +package net.citizensnpcs.command.command; + +import org.bukkit.entity.Player; + +import net.citizensnpcs.api.CitizensAPI; +import net.citizensnpcs.api.npc.NPC; +import net.citizensnpcs.command.CommandContext; +import net.citizensnpcs.command.annotation.Command; +import net.citizensnpcs.command.annotation.Permission; +import net.citizensnpcs.npc.CitizensNPC; +import net.citizensnpcs.npc.CitizensNPCManager; + +// TODO add requirements +public class NPCCommands { + + @Command( + aliases = { "npc" }, + usage = "spawn [name]", + desc = "Spawn an NPC", + modifiers = { "spawn", "create" }, + min = 2, + max = 2) + @Permission("npc.spawn") + public static void spawnNPC(CommandContext args, Player player, NPC npc) { + CitizensNPC create = (CitizensNPC) CitizensAPI.getNPCManager().createNPC(args.getString(1)); + create.spawn(player.getLocation()); + create.save(); + } + + @Command( + aliases = { "npc" }, + usage = "despawn", + desc = "Despawn an NPC", + modifiers = { "despawn" }, + min = 1, + max = 1) + @Permission("npc.despawn") + public static void despawnNPC(CommandContext args, Player player, NPC npc) { + CitizensNPC despawn = (CitizensNPC) ((CitizensNPCManager) CitizensAPI.getNPCManager()).getSelectedNPC(player); + despawn.despawn(); + } +} \ No newline at end of file diff --git a/src/net/citizensnpcs/command/exception/CommandException.java b/src/net/citizensnpcs/command/exception/CommandException.java new file mode 100644 index 000000000..e04d80a92 --- /dev/null +++ b/src/net/citizensnpcs/command/exception/CommandException.java @@ -0,0 +1,36 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * 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 . + */ + +package net.citizensnpcs.command.exception; + +public class CommandException extends Exception { + private static final long serialVersionUID = 870638193072101739L; + + public CommandException() { + super(); + } + + public CommandException(String message) { + super(message); + } + + public CommandException(Throwable t) { + super(t); + } +} \ No newline at end of file diff --git a/src/net/citizensnpcs/command/exception/CommandUsageException.java b/src/net/citizensnpcs/command/exception/CommandUsageException.java new file mode 100644 index 000000000..aa4a2be2c --- /dev/null +++ b/src/net/citizensnpcs/command/exception/CommandUsageException.java @@ -0,0 +1,35 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * 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 . + */ + +package net.citizensnpcs.command.exception; + +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; + } +} \ No newline at end of file diff --git a/src/net/citizensnpcs/command/exception/MissingNestedCommandException.java b/src/net/citizensnpcs/command/exception/MissingNestedCommandException.java new file mode 100644 index 000000000..1ba15d5b0 --- /dev/null +++ b/src/net/citizensnpcs/command/exception/MissingNestedCommandException.java @@ -0,0 +1,28 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * 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 . + */ + +package net.citizensnpcs.command.exception; + +public class MissingNestedCommandException extends CommandUsageException { + private static final long serialVersionUID = -4382896182979285355L; + + public MissingNestedCommandException(String message, String usage) { + super(message, usage); + } +} \ No newline at end of file diff --git a/src/net/citizensnpcs/command/exception/NoPermissionsException.java b/src/net/citizensnpcs/command/exception/NoPermissionsException.java new file mode 100644 index 000000000..7e09553f0 --- /dev/null +++ b/src/net/citizensnpcs/command/exception/NoPermissionsException.java @@ -0,0 +1,24 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * 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 . + */ + +package net.citizensnpcs.command.exception; + +public class NoPermissionsException extends CommandException { + private static final long serialVersionUID = -602374621030168291L; +} \ No newline at end of file diff --git a/src/net/citizensnpcs/command/exception/RequirementMissingException.java b/src/net/citizensnpcs/command/exception/RequirementMissingException.java new file mode 100644 index 000000000..e11fa34df --- /dev/null +++ b/src/net/citizensnpcs/command/exception/RequirementMissingException.java @@ -0,0 +1,10 @@ +package net.citizensnpcs.command.exception; + +public class RequirementMissingException extends CommandException { + + private static final long serialVersionUID = -4299721983654504028L; + + public RequirementMissingException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/net/citizensnpcs/command/exception/ServerCommandException.java b/src/net/citizensnpcs/command/exception/ServerCommandException.java new file mode 100644 index 000000000..2e0399b82 --- /dev/null +++ b/src/net/citizensnpcs/command/exception/ServerCommandException.java @@ -0,0 +1,5 @@ +package net.citizensnpcs.command.exception; + +public class ServerCommandException extends CommandException { + private static final long serialVersionUID = 9120268556899197316L; +} \ No newline at end of file diff --git a/src/net/citizensnpcs/command/exception/UnhandledCommandException.java b/src/net/citizensnpcs/command/exception/UnhandledCommandException.java new file mode 100644 index 000000000..6e6c908c6 --- /dev/null +++ b/src/net/citizensnpcs/command/exception/UnhandledCommandException.java @@ -0,0 +1,24 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * 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 . + */ + +package net.citizensnpcs.command.exception; + +public class UnhandledCommandException extends CommandException { + private static final long serialVersionUID = 3370887306593968091L; +} \ No newline at end of file diff --git a/src/net/citizensnpcs/command/exception/WrappedCommandException.java b/src/net/citizensnpcs/command/exception/WrappedCommandException.java new file mode 100644 index 000000000..82aacea5d --- /dev/null +++ b/src/net/citizensnpcs/command/exception/WrappedCommandException.java @@ -0,0 +1,28 @@ +// $Id$ +/* + * WorldEdit + * Copyright (C) 2010, 2011 sk89q + * + * 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 . + */ + +package net.citizensnpcs.command.exception; + +public class WrappedCommandException extends CommandException { + private static final long serialVersionUID = -4075721444847778918L; + + public WrappedCommandException(Throwable t) { + super(t); + } +} \ No newline at end of file diff --git a/src/net/citizensnpcs/npc/CitizensNPC.java b/src/net/citizensnpcs/npc/CitizensNPC.java index d072810fc..8f49306e0 100644 --- a/src/net/citizensnpcs/npc/CitizensNPC.java +++ b/src/net/citizensnpcs/npc/CitizensNPC.java @@ -1,5 +1,6 @@ package net.citizensnpcs.npc; +import net.citizensnpcs.Citizens; import net.citizensnpcs.Settings.Setting; import net.citizensnpcs.api.DataKey; import net.citizensnpcs.api.event.NPCDespawnEvent; @@ -10,7 +11,6 @@ import net.citizensnpcs.api.npc.trait.Trait; import net.citizensnpcs.api.npc.trait.trait.SpawnLocation; import net.citizensnpcs.npc.ai.CitizensNavigator; import net.citizensnpcs.resources.lib.CraftNPC; -import net.citizensnpcs.storage.Storage; import net.citizensnpcs.util.Messaging; import org.bukkit.Bukkit; @@ -31,7 +31,7 @@ public class CitizensNPC extends AbstractNPC { @Override public void despawn() { if (!isSpawned()) { - Messaging.debug("The NPC is already despawned."); + Messaging.debug("The NPC with the ID '" + getId() + "' is already despawned."); return; } @@ -72,7 +72,7 @@ public class CitizensNPC extends AbstractNPC { @Override public void spawn(Location loc) { if (isSpawned()) { - Messaging.debug("The NPC is already spawned."); + Messaging.debug("The NPC with the ID '" + getId() + "' is already spawned."); return; } @@ -101,8 +101,8 @@ public class CitizensNPC extends AbstractNPC { Messaging.log(formatted); } - public void save(Storage saves) { - DataKey key = saves.getKey("npc." + getId()); + public void save() { + DataKey key = Citizens.getNPCStorage().getKey("npc." + getId()); key.setString("name", getFullName()); if (!key.keyExists("spawned")) key.setBoolean("spawned", true); diff --git a/src/net/citizensnpcs/npc/CitizensNPCManager.java b/src/net/citizensnpcs/npc/CitizensNPCManager.java index 1d26dd6a3..6c9e3db24 100644 --- a/src/net/citizensnpcs/npc/CitizensNPCManager.java +++ b/src/net/citizensnpcs/npc/CitizensNPCManager.java @@ -7,10 +7,10 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import net.citizensnpcs.Settings.Setting; +import net.citizensnpcs.api.CitizensAPI; import net.citizensnpcs.api.npc.NPC; import net.citizensnpcs.api.npc.NPCManager; import net.citizensnpcs.api.npc.trait.Character; -import net.citizensnpcs.api.npc.trait.Trait; import net.citizensnpcs.api.npc.trait.trait.SpawnLocation; import net.citizensnpcs.resources.lib.CraftNPC; import net.citizensnpcs.storage.Storage; @@ -89,10 +89,11 @@ public class CitizensNPCManager implements NPCManager { } @Override - public Collection getNPCs(Class trait) { + public Collection getNPCs(Class character) { List npcs = new ArrayList(); for (NPC npc : getAllNPCs()) { - if (npc.hasTrait(trait)) + if (npc.getCharacter() != null + && CitizensAPI.getCharacterManager().getInstance(npc.getCharacter().getName()) != null) npcs.add(npc); } return npcs; @@ -143,16 +144,25 @@ public class CitizensNPCManager implements NPCManager { selected.put(player.getName(), npc.getId()); } - public boolean hasSelected(Player player, NPC npc) { + public boolean npcIsSelectedByPlayer(Player player, NPC npc) { if (!selected.containsKey(player.getName())) return false; return selected.get(player.getName()) == npc.getId(); } - public boolean canSelect(Player player, NPC npc) { - if (player.hasPermission("citizens.npc.select")) { + public boolean hasNPCSelected(Player player) { + return selected.containsKey(player.getName()); + } + + public boolean canSelectNPC(Player player, NPC npc) { + if (player.hasPermission("citizens.npc.select")) return player.getItemInHand().getTypeId() == Setting.SELECTION_ITEM.getInt(); - } return false; } + + public NPC getSelectedNPC(Player player) { + if (!selected.containsKey(player.getName())) + return null; + return getNPC(selected.get(player.getName())); + } } \ No newline at end of file diff --git a/src/net/citizensnpcs/util/Messaging.java b/src/net/citizensnpcs/util/Messaging.java index 55f516785..1a4a95697 100644 --- a/src/net/citizensnpcs/util/Messaging.java +++ b/src/net/citizensnpcs/util/Messaging.java @@ -45,4 +45,8 @@ public class Messaging { send(player, send); } + + public static void sendError(Player player, Object msg) { + send(player, "" + ChatColor.RED + msg); + } } \ No newline at end of file diff --git a/src/net/citizensnpcs/util/StringHelper.java b/src/net/citizensnpcs/util/StringHelper.java new file mode 100644 index 000000000..34a737d5e --- /dev/null +++ b/src/net/citizensnpcs/util/StringHelper.java @@ -0,0 +1,59 @@ +package net.citizensnpcs.util; + +import org.bukkit.ChatColor; + +public class StringHelper { + + public static int getLevenshteinDistance(String s, String t) { + if (s == null || t == null) + throw new IllegalArgumentException("Strings must not be null"); + + 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 t_j; // jth character of t + + int cost; // cost + + for (i = 0; i <= n; i++) + p[i] = i; + + for (j = 1; j <= m; j++) { + t_j = t.charAt(j - 1); + d[0] = j; + + for (i = 1; i <= n; i++) { + cost = s.charAt(i - 1) == t_j ? 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 String wrap(Object string) { + return ChatColor.YELLOW + string.toString() + ChatColor.GREEN; + } +} \ No newline at end of file