FileDocCategorySizeDatePackage
Command.javaAPI DocExample8850Sat Jan 24 10:44:30 GMT 2004je3.reflect

Command.java

/*
 * Copyright (c) 2004 David Flanagan.  All rights reserved.
 * This code is from the book Java Examples in a Nutshell, 3nd Edition.
 * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
 * You may study, use, and modify it for any non-commercial purpose,
 * including teaching and use in open-source projects.
 * You may distribute it non-commercially as long as you retain this notice.
 * For a commercial use license, or to purchase the book, 
 * please visit http://www.davidflanagan.com/javaexamples3.
 */
package je3.reflect;
import java.awt.event.*;
import java.beans.*;
import java.lang.reflect.*;
import java.io.*;
import java.util.*;
import je3.classes.Tokenizer;
import je3.classes.CharSequenceTokenizer;

/**
 * This class represents a Method, the list of arguments to be passed
 * to that method, and the object on which the method is to be invoked.
 * The invoke() method invokes the method.  The actionPerformed() method
 * does the same thing, allowing this class to implement ActionListener
 * and be used to respond to ActionEvents generated in a GUI or elsewhere. 
 * The static parse() method parses a string representation of a method
 * and its arguments.
 **/
public class Command implements ActionListener, InvocationHandler {
    Method m;       // The method to be invoked
    Object target;  // The object to invoke it on
    Object[] args;  // The arguments to pass to the method

    // An empty array; used for methods with no arguments at all.
    static final Object[] nullargs = new Object[] {};
    
    /** This constructor creates a Command object for a no-arg method */
    public Command(Object target, Method m) { this(target, m, nullargs); }

    /** 
     * This constructor creates a Command object for a method that takes the
     * specified array of arguments.  Note that the parse() method provides
     * another way to create a Command object
     **/
    public Command(Object target, Method m, Object[] args) { 
	this.target = target;
	this.m = m;
	if (args == null) args = nullargs;
	this.args = args;
    }

    /** 
     * This construct specifies the method to call by name.  It looks for a 
     * method of the target object with the specified name and specified number
     * of arguments.  It does not attempt to verify the types of the arguments,
     * since wrapper object (Integer, Boolean, etc) in the args[] array could
     * represent reference or primitive arguments.
     */
    public Command(Object target, String methodName, Object[] args) {
	this.target = target;
	if (args == null) args = nullargs;
	this.args = args;
	
	Method[] methods = target.getClass().getMethods();
	for(int i = 0; i < methods.length; i++) {
	    Method m = methods[i];
	    if (m.getParameterTypes().length == args.length && 
		m.getName().equals(methodName)) {
		this.m = m;
		break;
	    }
	}
	
	// If we didn't find a method, throw an exceptoin
	if (this.m == null)
	    throw new IllegalArgumentException("Unknown method " + methodName);

    }

    /**
     * Invoke the Command by calling the method on its target, and passing
     * the arguments.  See also actionPerformed() which does not throw the
     * checked exceptions that this method does.
     **/
    public void invoke()
	throws IllegalAccessException, InvocationTargetException
    {
	m.invoke(target, args);  // Use reflection to invoke the method
    }

    /**
     * This method implements the ActionListener interface.  It is like
     * invoke() except that it catches the exceptions thrown by that method
     * and rethrows them as an unchecked RuntimeException
     **/
    public void actionPerformed(ActionEvent e) {
	try {
	    invoke();                          // Call the invoke method
	}
	catch (InvocationTargetException ex) { // But convert to unchecked
	    throw new RuntimeException(ex);    // exceptions.  Note that we
	}                                      // chain to the original 
	catch (IllegalAccessException ex) {    // exception.
	    throw new RuntimeException(ex);
	}
    }

    /**
     * This method implements the InvocationHandler interface, so that a
     * Command object can be used with Proxy objects.  Note that it simply
     * calls the no-argument invoke() method, ignoring its arguments and 
     * returning null. This means that it is only useful for proxying
     * interfaces that define a single no-arg void method.
     **/
    public Object invoke(Object p, Method m, Object[] a) throws Throwable {
	invoke();
	return null;
    }

    /**
     * This static method creates a Command using the specified target object,
     * and the specified string.  The string should contain method name
     * followed by an optional parenthesized comma-separated argument list and
     * a semicolon.  The arguments may be boolean, integer or double literals,
     * or double-quoted strings.  The parser is lenient about missing commas,
     * semicolons and quotes, but throws an IOException if it cannot parse the
     * string.
     **/
    public static Command parse(Object target, String text) throws IOException
    {
	String methodname;                 // The name of the method
	ArrayList args = new ArrayList();  // Hold arguments as we parse them.
	ArrayList types = new ArrayList(); // Hold argument types.

	Tokenizer t = new CharSequenceTokenizer(text);
	t.skipSpaces(true).tokenizeWords(true).tokenizeNumbers(true);
	t.quotes("\"'", "\"'");

	if (t.next() != Tokenizer.WORD)
	    throw new IOException("Missing method name for command");
	methodname = t.tokenText();
	t.next();
	if (t.tokenType() == '(') {
	    t.next(); 
	    for(;;) { // Loop through all arguments 
		int c = t.tokenType();
		if (c == ')') {
		    // Consume closing paren
		    t.next();
		    break;  // and break out of list
		}
		
		if (c == Tokenizer.WORD) {
		    String word = t.tokenText();
		    if (word.equals("true")) {
			args.add(Boolean.TRUE);
			types.add(boolean.class);
		    }
		    else if (word.equals("false")) {
			args.add(Boolean.FALSE);
			types.add(boolean.class);
		    }
		    else {  // Treat unquoted identifiers as strings...
			args.add(word);
			types.add(String.class);
		    }
		}
		else if (c == '"') {  // double-quoted string
		    args.add(t.tokenText());
		    types.add(String.class);
		}
		else if (c == '\'') { // single-quoted character
		    args.add(new Character(t.tokenText().charAt(0)));
		    types.add(char.class);
		}
		else if (c == Tokenizer.NUMBER) {  // An integer
		    args.add(new Integer(Integer.parseInt(t.tokenText())));
		    types.add(int.class);
		}
		else {  // Anything else is a syntax error
		    throw new IOException("Unexpected token " + t.tokenText() +
					  " in argument list of " +
					  methodname + "().");
		}
		
		// Consume the token we just parse, and then consume an
		// optional comma.  
		if (t.next() == ',') t.next();
	    }
	}

	// Consume optional semicolon after method name or argument list
	if (t.tokenType() == ';') t.next();


	// We've parsed the argument list.
	// Next, convert the lists of argument values and types to arrays
	Object[] argValues = args.toArray();
	Class[] argtypes = (Class[])types.toArray(new Class[argValues.length]);

	// At this point, we've got a method name, and arrays of argument
	// values and types.  Use reflection on the class of the target object
	// to find a method with the given name and argument types.  Throw
	// an exception if we can't find the named method.
	Method method;
	try { method = target.getClass().getMethod(methodname, argtypes); }
	catch (Exception e) {
	    throw new IOException("No such method found, or wrong argument " +
				  "types: " + methodname);
	}

	// Finally, create and return a Command object, using the target object
	// passed to this method, the Method object we obtained above, and
	// the array of argument values we parsed from the string.
	return new Command(target, method, argValues);
    }


    /**
     * This simple program demonstrates how a Command object can be parsed from
     * a string and used as an ActionListener object in a Swing application.
     **/
    static class Test {
	public static void main(String[] args) throws IOException {
	    javax.swing.JFrame f = new javax.swing.JFrame("Command Test");
	    javax.swing.JButton b1 = new javax.swing.JButton("Tick");
	    javax.swing.JButton b2 = new javax.swing.JButton("Tock");
	    javax.swing.JLabel label = new javax.swing.JLabel("Hello world");
	    java.awt.Container pane = f.getContentPane();

	    pane.add(b1, java.awt.BorderLayout.WEST);
	    pane.add(b2, java.awt.BorderLayout.EAST);
	    pane.add(label, java.awt.BorderLayout.NORTH);

	    b1.addActionListener(Command.parse(label, "setText(\"tick\");"));
	    b2.addActionListener(Command.parse(label, "setText(\"tock\");"));
	    
	    f.pack();
	    f.show();
	}
    }
}