Command.java Source code

Java tutorial

Introduction

Here is the source code for Command.java

Source

/*
 * Copyright (c) 2000 David Flanagan.  All rights reserved.
 * This code is from the book Java Examples in a Nutshell, 2nd 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.
 * You may distribute it non-commercially as long as you retain this notice.
 * For a commercial use license, or to purchase the book (recommended),
 * visit http://www.davidflanagan.com/javaexamples2.
 */

import java.awt.event.*;
import java.beans.*;
import java.lang.reflect.*;
import java.io.*;
import java.util.*;

/**
 * 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 {
    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;
        this.args = args;
    }

    /**
     * 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 handle the exceptions
            throw new RuntimeException("Command: " + ex.getTargetException().toString());
        } catch (IllegalAccessException ex) {
            throw new RuntimeException("Command: " + ex.toString());
        }
    }

    /**
     * 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.

        // Convert the string into a character stream, and use the
        // StreamTokenizer class to convert it into a stream of tokens
        StreamTokenizer t = new StreamTokenizer(new StringReader(text));

        // The first token must be the method name
        int c = t.nextToken(); // read a token
        if (c != t.TT_WORD) // check the token type
            throw new IOException("Missing method name for command");
        methodname = t.sval; // Remember the method name

        // Now we either need a semicolon or a open paren
        c = t.nextToken();
        if (c == '(') { // If we see an open paren, then parse an arg list
            for (;;) { // Loop 'till end of arglist
                c = t.nextToken(); // Read next token

                if (c == ')') { // See if we're done parsing arguments.
                    c = t.nextToken(); // If so, parse an optional semicolon
                    if (c != ';')
                        t.pushBack();
                    break; // Now stop the loop.
                }

                // Otherwise, the token is an argument; figure out its type
                if (c == t.TT_WORD) {
                    // If the token is an identifier, parse boolean literals,
                    // and treat any other tokens as unquoted string literals.
                    if (t.sval.equals("true")) { // Boolean literal
                        args.add(Boolean.TRUE);
                        types.add(boolean.class);
                    } else if (t.sval.equals("false")) { // Boolean literal
                        args.add(Boolean.FALSE);
                        types.add(boolean.class);
                    } else { // Assume its a string
                        args.add(t.sval);
                        types.add(String.class);
                    }
                } else if (c == '"') { // If the token is a quoted string
                    args.add(t.sval);
                    types.add(String.class);
                } else if (c == t.TT_NUMBER) { // If the token is a number
                    int i = (int) t.nval;
                    if (i == t.nval) { // Check if its an integer
                        // Note: this code treats a token like "2.0" as an int!
                        args.add(new Integer(i));
                        types.add(int.class);
                    } else { // Otherwise, its a double
                        args.add(new Double(t.nval));
                        types.add(double.class);
                    }
                } else { // Any other token is an error
                    throw new IOException(
                            "Unexpected token " + t.sval + " in argument list of " + methodname + "().");
                }

                // Next should be a comma, but we don't complain if its not
                c = t.nextToken();
                if (c != ',')
                    t.pushBack();
            }
        } else if (c != ';') { // if a method name is not followed by a paren
            t.pushBack(); // then allow a semi-colon but don't require it.
        }

        // 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);
    }

    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();
    }
}