diff --git a/src/main/java/com/sk89q/minecraft/util/commands/CommandsManager.java b/src/main/java/com/sk89q/minecraft/util/commands/CommandsManager.java
index 77ee41716..f5db52105 100644
--- a/src/main/java/com/sk89q/minecraft/util/commands/CommandsManager.java
+++ b/src/main/java/com/sk89q/minecraft/util/commands/CommandsManager.java
@@ -21,66 +21,111 @@ package com.sk89q.minecraft.util.commands;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.lang.reflect.Modifier;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
import com.sk89q.util.StringUtil;
/**
- * Manager for handling commands. This allows you to easily process commands,
- * including nested commands, by correctly annotating methods of a class.
- * The commands are thus declaratively defined, and it's easy to spot
- * how permissions and commands work out, and it decreases the opportunity
- * for errors because the consistency would cause any odd balls to show.
- * The manager also handles some boilerplate code such as number of arguments
- * checking and printing usage.
+ *
Manager for handling commands. This allows you to easily process commands,
+ * including nested commands, by correctly annotating methods of a class.
*
* 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
* execute
methods. If something is wrong, such as incorrect
* usage, insufficient permissions, or a missing command altogether, an
- * exception will be raised for upstream handling.
+ * exception will be raised for upstream handling.
+ *
+ * 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.
*
* To mark a method as a command, use {@link Command}. For nested commands,
* see {@link NestedCommand}. To handle permissions, use
- * {@link CommandPermissions}.
+ * {@link CommandPermissions}.
*
* 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.
+ * with reflection, but this is quite fast in that of itself.
*
* @author sk89q
* @param command sender class
*/
public abstract class CommandsManager {
+
+ /**
+ * 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}.
+ * 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> commands
= new HashMap>();
+
+ /**
+ * Used to store the instances associated with a method.
+ */
+ protected Map instances = new HashMap();
/**
- * Mapping of commands (not including aliases) with a description.
+ * Mapping of commands (not including aliases) with a description. This
+ * is only for top level commands.
*/
protected Map descs = new HashMap();
+
+ /**
+ * Stores the injector used to getInstance.
+ */
+ protected Injector injector;
/**
- * Register an object that contains commands (denoted by
- * {@link Command}. The methods are
- * cached into a map for later usage and it reduces the overhead of
- * reflection (method lookup via reflection is relatively slow).
+ * 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 the methods of a class. This will automatically construct
+ * instances as necessary.
+ *
+ * @param cls
+ * @param parent
+ */
+ private void registerMethods(Class> cls, Method parent) {
+ try {
+ if (getInjector() == null) {
+ registerMethods(cls, parent, null);
+ } else {
+ Object obj = null;
+ 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.
@@ -88,7 +133,7 @@ public abstract class CommandsManager {
* @param cls
* @param parent
*/
- private void registerMethods(Class> cls, Method parent) {
+ private void registerMethods(Class> cls, Method parent, Object obj) {
Map map;
// Make a new hash map to cache the commands for this class
@@ -105,12 +150,24 @@ public abstract class CommandsManager {
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
@@ -338,13 +395,15 @@ public abstract class CommandsManager {
}
methodArgs[0] = context;
+
+ Object instance = instances.get(method);
try {
- method.invoke(null, methodArgs);
+ method.invoke(instance, methodArgs);
} catch (IllegalArgumentException e) {
- e.printStackTrace();
+ logger.log(Level.SEVERE, "Failed to execute command", e);
} catch (IllegalAccessException e) {
- e.printStackTrace();
+ logger.log(Level.SEVERE, "Failed to execute command", e);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof CommandException) {
throw (CommandException) e.getCause();
@@ -385,4 +444,21 @@ public abstract class CommandsManager {
* @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;
+ }
}
diff --git a/src/main/java/com/sk89q/minecraft/util/commands/Injector.java b/src/main/java/com/sk89q/minecraft/util/commands/Injector.java
new file mode 100644
index 000000000..0374b6df4
--- /dev/null
+++ b/src/main/java/com/sk89q/minecraft/util/commands/Injector.java
@@ -0,0 +1,28 @@
+// $Id$
+/*
+ * Copyright (C) 2010 sk89q
+ * All rights reserved.
+*/
+
+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;
+
+}