001/* ----------------------------------------------------------------------------
002   The Kiwi Toolkit - A Java Class Library
003   Copyright (C) 1998-2004 Mark A. Lindner
004
005   This library is free software; you can redistribute it and/or
006   modify it under the terms of the GNU General Public License as
007   published by the Free Software Foundation; either version 2 of the
008   License, or (at your option) any later version.
009
010   This library is distributed in the hope that it will be useful,
011   but WITHOUT ANY WARRANTY; without even the implied warranty of
012   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013   General Public License for more details.
014
015   You should have received a copy of the GNU General Public License
016   along with this library; if not, write to the Free Software
017   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
018   02111-1307, USA.
019 
020   The author may be contacted at: mark_a_lindner@yahoo.com
021   ----------------------------------------------------------------------------
022   $Log: CommandDispatcher.java,v $
023   Revision 1.5  2004/05/05 21:22:45  markl
024   Comment header updates.
025
026   Revision 1.4  2003/01/19 09:42:38  markl
027   Javadoc & comment header updates.
028
029   Revision 1.3  2001/03/12 02:57:38  markl
030   Source code cleanup.
031
032   Revision 1.2  1999/01/10 03:41:46  markl
033   added GPL header & RCS tag
034   ----------------------------------------------------------------------------
035*/
036
037package kiwi.util;
038
039import java.util.*;
040import java.lang.reflect.*;
041
042/** A class that makes use of the reflection API to implement a generic,
043  * reusable, and flexible command processor.
044  * <p>
045  * The <code>CommandDispatcher</code> analyzes a helper class (which
046  * implements the <code>CommandProcessor</code> interface), searching for
047  * methods whose names begin with the prefix <code>cmd_</code>. This
048  * information is used to build a command dictionary that maps command names
049  * to their associated processing methods.
050  * <p>
051  * The general idea is that the <code>CommandDispatcher</code> will be fed
052  * command strings that consist of a command name followed by a whitespace-
053  * separated list of arguments. This string is tokenized into words. The first
054  * word is the command name and is used to look up an associated method in the
055  * command dictionary. For example, if the command is <i>hello</i>, the
056  * associated method in the helper class would be <code>cmd_hello()</code>. If
057  * a matching method is not found, the helper object is messaged with an
058  * <code>unknownCommandError()</code>.
059  * <p>
060  * If the command is found, then the remaining tokens are counted to determine
061  * the number of arguments that the command is being invoked with. Some
062  * commands may have more than one form, or may have optional arguments. In
063  * this case, the associated command processing method in the helper class may
064  * be overloaded to handle all of the separate (legal) forms. The argument
065  * count is used to select an appropriate command handler. Note that no two
066  * command processing methods of the same name may have the same number of
067  * arguments, as this would produce a parsing ambiguity. Thus it is not valid
068  * to have <code>void cmd_hello(String s, int i)</code> and
069  * <code>void cmd_hello(int a, int b)</code>. If an appriate method cannot be
070  * found, the helper object is messaged with an
071  * <code>argumentCountError()</code>.
072  * <p>
073  * The arguments to the command (which are string tokens) are cast to the
074  * appropriate data types for the method. Thus if the command processor method
075  * for <i>hello</i> is declared as <code>void cmd_hello(String s, int i)
076  * </code>, then the first argument is left as a string and the second
077  * argument is converted to an <code>Integer</code> object. The following
078  * object types are supported for arguments: <code>String</code>,
079  * <code>int</code>, <code>Integer</code>, <code>boolean</code>,
080  * <code>Boolean</code>, <code>short</code>, <code>Short</code>,
081  * <code>long</code>, <code>Long</code>, <code>float</code>,
082  * <code>Float</code>, <code>double</code>, <code>Double</code>. If an error
083  * occurrs during argument conversion, the helper object is messaged with an
084  * <code>arugmentFormatError()</code>.
085  * <p>
086  * Once argument conversion is complete, the command processor method in the
087  * helper object is invoked with the argument list. If an error occurrs during
088  * the invocation, the helper object is messaged with an
089  * <code>invocationError()</code>.
090  *
091  * @author Mark Lindner
092  */
093
094public class CommandDispatcher
095  {
096  private CommandProcessor processor;
097  private Hashtable cmds;
098  private static final Class argtypes[]
099    = { Integer.class, Float.class, Double.class, Short.class, Long.class,
100        java.lang.String.class, Boolean.class };
101  private static final Class argtypes2[]
102    = { int.class, float.class, double.class, short.class, Long.class,
103        java.lang.String.class, boolean.class };
104  private static final Class stringConstructor[] = { java.lang.String.class };
105
106  /** Construct a new <code>CommandDispatcher</code> for the given helper
107    * class.
108    *
109    * @param processor The helper class that serves as the command processor
110    * and error handler.
111    */
112
113  public CommandDispatcher(CommandProcessor processor)
114    {
115    this.processor = processor;
116    cmds = new Hashtable();
117
118    // analyze the processor class and build a command dictionary
119
120    Method methods[] = processor.getClass().getMethods();
121
122      NEXTCMD:
123    for(int i = 0; i < methods.length; i++)
124      {
125      String nm = methods[i].getName();
126
127      // ignore methods whose names don't begin with "cmd_"
128
129      if(!(nm.startsWith("cmd_"))) continue;
130
131      // check all the method's parameter types to ensure that they are of
132      // supported types; ignore methods that don't pass this test
133
134      Class args[] = methods[i].getParameterTypes();
135      for(int j = 0; j < args.length; j++)
136        if(!isSupported(args[j])) continue NEXTCMD;
137
138      // strip off the "cmd_" prefix and try to look up the vector for this
139      // method name in the hash table
140
141      String c = nm.substring(4);
142      Vector v = (Vector)cmds.get(c);
143
144      // if this is the first method of this name that we've encountered,
145      // create the vector in the hash table
146
147      if(v == null)
148        {
149        v = new Vector();
150        cmds.put(c, v);
151        }
152
153      // add the method object to the vector
154
155      v.addElement(methods[i]);
156      }
157    }
158
159  /** Dispatch a command string to the appropriate processing method.
160    *
161    * @param s The command string to be parsed and passed to its associated
162    * command processing method.
163    */
164
165  public void dispatch(String s)
166    {
167    StringTokenizer st = new StringTokenizer(s);
168
169    // if there are no tokens on the line, ignore the line
170
171    if(!st.hasMoreTokens()) return;
172
173    // pull the first token off the string; this is the command line
174    // try to find the vector for this command in the hashtable
175
176    String cmd = st.nextToken();
177    Vector v = (Vector)cmds.get(cmd);
178    if(v == null)
179      {
180      // if we can't find one, message an error & abort
181
182      processor.unknownCommandError(cmd);
183      return;
184      }
185
186    // enumerate through the vector, examining each overloaded method in turn
187
188    Enumeration e = v.elements();
189    Method m = null;
190    Class params[] = null;
191    boolean found = false;
192    Constructor cons = null;
193
194    while(e.hasMoreElements())
195      {
196      m = (Method)e.nextElement();
197      params = m.getParameterTypes();
198
199      // if the parameter count matches the number of tokens remaining
200      // (arguments) then we've found our method
201
202      if(params.length == st.countTokens())
203        {
204        found = true;
205        break;
206        }
207      }
208
209    if(!found)
210      {
211      // if we didn't find a method, message an error & abort
212
213      processor.argumentCountError(cmd);
214      return;
215      }
216
217    // construct the argument list to pass to the method
218
219    Object args[] = new Object[params.length];
220
221    for(int i = 0; i < params.length; i++)
222      {
223      // get a wrapper class for the nth parameter, and try to locate a
224      // constructor for the wrapper class that takes only a single string as
225      // an argument
226
227      Class clazz = wrapperClass(params[i]);
228      String arg = st.nextToken();
229
230      try
231        {
232        cons = clazz.getConstructor(stringConstructor);
233        }
234      catch(NoSuchMethodException ex)
235        {
236        // this shouldn't happen, but if it does, we should abort
237
238        processor.argumentFormatError(cmd, arg);
239        return;
240        }
241
242      // instantiate the class with the next token as the argument
243
244      try
245        {
246        args[i] = cons.newInstance(new Object[] { arg });
247        }
248      catch(Exception ex)
249        {
250        // if we get an instantiation error, then the format of the argument is
251        // incorrect
252
253        processor.argumentFormatError(cmd, arg);
254        return;
255        }
256      }
257
258    // now invoke the method with the argument list, messaging an error and
259    // aborting if we get an exception
260
261    try
262      {
263      m.invoke(processor, args);
264      }
265    catch(Exception ex)
266      {
267      processor.invocationError(cmd, ex);
268      return;
269      }
270    }
271  
272  /* Look up the wrapper class for a primitive type class; if we can't find a
273   * wrapper, just return the class that was passed in.
274   */
275
276  private Class wrapperClass(Class clazz)
277    {
278    for(int i = 0; i < argtypes.length; i++)
279      if(clazz.equals(argtypes2[i]))
280        return(argtypes[i]);
281
282    return(clazz);
283    }
284  
285  /* Determine if the specified class is a supported argument type.
286   */
287
288  private boolean isSupported(Class type)
289    {
290    for(int i = 0; i < argtypes.length; i++)
291      if(type.equals(argtypes[i]) || type.equals(argtypes2[i]))
292        return(true);
293
294    return(false);
295    }
296  
297  }
298
299/* end of source file */