Added support for using instances (created using a specified dependency injector) in CommandsManager.

This commit is contained in:
sk89q 2011-06-18 03:56:16 -07:00
parent 1784b29f02
commit 86ecd49b89
2 changed files with 128 additions and 24 deletions

View File

@ -21,60 +21,80 @@ package com.sk89q.minecraft.util.commands;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.HashMap; import java.lang.reflect.Modifier;
import java.util.HashSet; import java.util.*;
import java.util.Map; import java.util.logging.Level;
import java.util.Set; import java.util.logging.Logger;
import com.sk89q.util.StringUtil; import com.sk89q.util.StringUtil;
/** /**
* Manager for handling commands. This allows you to easily process commands, * <p>Manager for handling commands. This allows you to easily process commands,
* including nested commands, by correctly annotating methods of a class. * including nested commands, by correctly annotating methods of a class.</p>
* 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.
* *
* <p>To use this, it is merely a matter of registering classes containing * <p>To use this, it is merely a matter of registering classes containing
* the commands (as methods with the proper annotations) with the * the commands (as methods with the proper annotations) with the
* manager. When you want to process a command, use one of the * manager. When you want to process a command, use one of the
* <code>execute</code> methods. If something is wrong, such as incorrect * <code>execute</code> methods. If something is wrong, such as incorrect
* usage, insufficient permissions, or a missing command altogether, an * usage, insufficient permissions, or a missing command altogether, an
* exception will be raised for upstream handling. * 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, * <p>To mark a method as a command, use {@link Command}. For nested commands,
* see {@link NestedCommand}. To handle permissions, use * see {@link NestedCommand}. To handle permissions, use
* {@link CommandPermissions}. * {@link CommandPermissions}.</p>
* *
* <p>This uses Java reflection extensively, but to reduce the overhead of * <p>This uses Java reflection extensively, but to reduce the overhead of
* reflection, command lookups are completely cached on registration. This * reflection, command lookups are completely cached on registration. This
* allows for fast command handling. Method invocation still has to be done * 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.</p>
* *
* @author sk89q * @author sk89q
* @param <T> command sender class * @param <T> command sender class
*/ */
public abstract class CommandsManager<T> { 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 * Mapping of commands (including aliases) with a description. Root
* commands are stored under a key of null, whereas child commands are * 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<Method, Map<String, Method>> commands protected Map<Method, Map<String, Method>> commands
= new HashMap<Method, Map<String, Method>>(); = new HashMap<Method, Map<String, Method>>();
/** /**
* Mapping of commands (not including aliases) with a description. * 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>(); protected Map<String, String> descs = new HashMap<String, String>();
/** /**
* Register an object that contains commands (denoted by * Stores the injector used to getInstance.
* {@link Command}. The methods are */
* cached into a map for later usage and it reduces the overhead of protected Injector injector;
* 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 * @param cls
*/ */
@ -83,12 +103,37 @@ public abstract class CommandsManager<T> {
} }
/** /**
* Register the methods of a class. * Register the methods of a class. This will automatically construct
* instances as necessary.
* *
* @param cls * @param cls
* @param parent * @param parent
*/ */
private void registerMethods(Class<?> cls, Method 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.
*
* @param cls
* @param parent
*/
private void registerMethods(Class<?> cls, Method parent, Object obj) {
Map<String, Method> map; Map<String, Method> map;
// Make a new hash map to cache the commands for this class // Make a new hash map to cache the commands for this class
@ -105,6 +150,8 @@ public abstract class CommandsManager<T> {
continue; continue;
} }
boolean isStatic = Modifier.isStatic(method.getModifiers());
Command cmd = method.getAnnotation(Command.class); Command cmd = method.getAnnotation(Command.class);
// Cache the aliases too // Cache the aliases too
@ -112,6 +159,16 @@ public abstract class CommandsManager<T> {
map.put(alias, method); 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 // Build a list of commands and their usage details, at least for
// root level commands // root level commands
if (parent == null) { if (parent == null) {
@ -339,12 +396,14 @@ public abstract class CommandsManager<T> {
methodArgs[0] = context; methodArgs[0] = context;
Object instance = instances.get(method);
try { try {
method.invoke(null, methodArgs); method.invoke(instance, methodArgs);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
e.printStackTrace(); logger.log(Level.SEVERE, "Failed to execute command", e);
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
e.printStackTrace(); logger.log(Level.SEVERE, "Failed to execute command", e);
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
if (e.getCause() instanceof CommandException) { if (e.getCause() instanceof CommandException) {
throw (CommandException) e.getCause(); throw (CommandException) e.getCause();
@ -385,4 +444,21 @@ public abstract class CommandsManager<T> {
* @return * @return
*/ */
public abstract boolean hasPermission(T player, String perm); 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,28 @@
// $Id$
/*
* Copyright (C) 2010 sk89q <http://www.sk89q.com>
* 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;
}