source: trunk/org/expeditee/actions/Actions.java@ 4

Last change on this file since 4 was 4, checked in by davidb, 16 years ago

Starting source code to Expeditee

File size: 17.6 KB
Line 
1package org.expeditee.actions;
2
3import java.io.File;
4import java.lang.reflect.Constructor;
5import java.lang.reflect.Method;
6import java.lang.reflect.Modifier;
7import java.net.URL;
8import java.util.ArrayList;
9import java.util.Enumeration;
10import java.util.HashMap;
11import java.util.LinkedList;
12import java.util.List;
13import java.util.jar.JarFile;
14import java.util.zip.ZipEntry;
15
16import org.expeditee.agents.Agent;
17import org.expeditee.gui.DisplayIO;
18import org.expeditee.gui.Frame;
19import org.expeditee.gui.FrameGraphics;
20import org.expeditee.gui.FrameIO;
21import org.expeditee.gui.FrameUtils;
22import org.expeditee.io.Conversion;
23import org.expeditee.io.Logger;
24import org.expeditee.items.Item;
25import org.expeditee.items.ItemUtils;
26
27/**
28 * The Action class is used to launch KMS Actions as well as launching JAGs.
29 *
30 * This class checks all class files in the same directory, and reads in and
31 * adds all the methods from them. The methods are stored in a Hashtable so that
32 * the lowercase method names can be mapped to the correctly capatilized method
33 * names (to provide case-insensitivity)
34 */
35public class Actions {
36
37 // the currently running agent (if there is one)
38 private static Agent _Agent = null;
39
40 // maps lower case method names to the method
41 private static HashMap<String, Method> _Actions = new HashMap<String, Method>();
42
43 // maps lower case JAG class names to capitalized JAG names
44 private static HashMap<String, String> _JAGs = new HashMap<String, String>();
45
46 public static final String ROOT_PACKAGE = "org.expeditee.";
47
48 // Package and class file locations
49 private static final String ACTIONS_PACKAGE = ROOT_PACKAGE + "actions.";
50
51 private static final String AGENTS_PACKAGE = ROOT_PACKAGE + "agents.";
52
53 private static final String NAVIGATIONS_CLASS = ROOT_PACKAGE + "actions.NavigationActions";
54
55 public static Class[] getClasses(String pckgname)
56 throws ClassNotFoundException {
57 ArrayList<Class> classes = new ArrayList<Class>();
58 // Get a File object for the package
59 File directory = null;
60 String path = pckgname.replace('.', '/');
61 try {
62 ClassLoader cld = Thread.currentThread().getContextClassLoader();
63 if (cld == null) {
64 throw new ClassNotFoundException("Can't get class loader.");
65 }
66 URL resource = cld.getResource(path);
67 if (resource == null) {
68 throw new ClassNotFoundException("No resource for " + path);
69 }
70 directory = new File(resource.getFile());
71 } catch (NullPointerException x) {
72 throw new ClassNotFoundException(pckgname + " (" + directory
73 + ") does not appear to be a valid package");
74 }
75
76 int splitPoint = directory.getPath().indexOf('!');
77 if (splitPoint > 0) {
78 try {
79 String jarName = directory.getPath().substring(
80 "file:".length(), splitPoint);
81 // Windows HACK
82 if (jarName.indexOf(":") >= 0)
83 jarName = jarName.substring(1);
84
85 if (jarName.indexOf("%20") > 0) {
86 jarName = jarName.replace("%20", " ");
87 }
88
89 JarFile jarFile = new JarFile(jarName);
90
91 Enumeration entries = jarFile.entries();
92 while (entries.hasMoreElements()) {
93 ZipEntry entry = (ZipEntry) entries.nextElement();
94 String className = entry.getName();
95 if (className.startsWith(path)) {
96 if (className.endsWith(".class")
97 && !className.contains("$")) {
98 classes.add(Class.forName(className.substring(0,
99 className.length() - 6).replace('/', '.')));
100 }
101 }
102 }
103 jarFile.close();
104
105 } catch (Exception e) {
106 e.printStackTrace();
107 }
108
109 } else {
110
111 if (directory.exists()) {
112 // Get the list of the files contained in the package
113 String[] files = directory.list();
114 for (int i = 0; i < files.length; i++) {
115 // we are only interested in .class files
116 if (files[i].endsWith(".class") && !files[i].contains("$")
117 && !files[i].equals("Actions.class")) {
118 // removes the .class extension
119 classes
120 .add(Class.forName(pckgname
121 + files[i].substring(0, files[i]
122 .length() - 6)));
123 }
124 }
125 } else {
126 throw new ClassNotFoundException(pckgname + " (" + directory
127 + ") does not appear to be a valid package");
128 }
129 }
130 Class[] classesA = new Class[classes.size()];
131 classes.toArray(classesA);
132 return classesA;
133 }
134
135 /**
136 * Clears out the Action and JAG Hashtables and refills them. Normally this
137 * is only called once when the system starts.
138 */
139 public static void Init() {
140 Class[] classes;
141 try {
142 classes = getClasses(AGENTS_PACKAGE);
143
144 for (int i = 0; i < classes.length; i++) {
145 String name = classes[i].getSimpleName();
146 // maps lower case name to correct capitalised name
147 _JAGs.put(name.toLowerCase(), name);
148 }
149 } catch (Exception e) {
150 System.out
151 .println("You must have Java 1.5 or higher to run Expeditee");
152 System.out.println(e.getMessage());
153 }
154 try {
155 classes = getClasses(ACTIONS_PACKAGE);
156
157 for (int i = 0; i < classes.length; i++) {
158 String name = classes[i].getSimpleName();
159 // read in all the methods from the class
160 try {
161 // System.out.println(name);
162 LoadMethods(Class.forName(ACTIONS_PACKAGE + name));
163 } catch (ClassNotFoundException e) {
164 Logger.Log(e);
165 e.printStackTrace();
166 }
167 }
168 } catch (Exception e) {
169 System.out.println(e.getMessage());
170 }
171 }
172
173 /**
174 * Loads all the Methods that meet the requirements checked by MethodCheck
175 * into the hashtable.
176 *
177 * @param c
178 * The Class to load the Methods from.
179 */
180 private static void LoadMethods(Class c) {
181 // list of methods to test
182 Method[] toLoad = c.getMethods();
183
184 for (Method m : toLoad) {
185 // only allow methods with the right modifiers
186 if (MethodCheck(m)) {
187 if (!(_Actions.containsKey(m.getName().toLowerCase())))
188 _Actions.put(m.getName().toLowerCase(), m);
189 else {
190 int i = 0;
191 while (_Actions.containsKey(m.getName().toLowerCase() + i))
192 i++;
193
194 _Actions.put(m.getName().toLowerCase() + i, m);
195 }
196
197 }
198 }
199 }
200
201 /**
202 * Checks if the given Method corresponds to the restrictions of Action
203 * commands, namely: Declared (not inherited), Public, and Static, with a
204 * void return type.
205 *
206 * @param m
207 * The Method to check
208 * @return True if the Method meets the above conditions, false otherwise.
209 */
210 private static boolean MethodCheck(Method m) {
211 // the return type must be void
212 if (m.getReturnType() != void.class)
213 return false;
214
215 int mods = m.getModifiers();
216
217 // check the method is declared (not inherited)
218 if ((mods & Method.DECLARED) != Method.DECLARED)
219 return false;
220
221 // check the method is public
222 if ((mods & Modifier.PUBLIC) != Modifier.PUBLIC)
223 return false;
224
225 // check the method is static
226 if ((mods & Modifier.STATIC) != Modifier.STATIC)
227 return false;
228
229 // if we have not returned yet, then the tests have all passed
230 return true;
231 }
232
233 /**
234 * Performs the given KMS action command. The source Frame and Item are
235 * given because they are required by some actions. Note that the source
236 * frame does not have to be the Item's parent Frame.
237 *
238 * @param source
239 * The Frame that the action should apply to
240 * @param launcher
241 * The Item that has the action assigned to it
242 * @param command
243 * The action to perform
244 */
245 public static void PerformAction(Frame source, Item launcher, String command) {
246 // separate method name and parameter names
247 String mname = getName(command);
248 if (command.length() > mname.length())
249 command = command.substring(mname.length() + 1);
250 else
251 command = "";
252
253 // Strip off the @ from annotation items
254 if (mname.startsWith("@"))
255 mname = mname.substring(1);
256
257 mname = mname.trim().toLowerCase();
258
259 // check for protection on frame
260 if (ItemUtils.ContainsTag(source.getItems(), "@No" + mname)) {
261 FrameGraphics.DisplayMessage("Frame is protected by @No" + mname
262 + " tag.");
263 return;
264 }
265
266 // retrieve methods that match the name
267 Method toRun = _Actions.get(mname);
268
269 // if this is not the name of a method, it may be the name of an agent
270 if (toRun == null) {
271 LaunchAgent(mname, command, source);
272 return;
273 }
274
275 // Need to save the frame if we are navigating away from it so we dont
276 // loose changes
277 if (toRun.getDeclaringClass().getName().equals(
278 NAVIGATIONS_CLASS))
279 FrameIO.SaveFrame(DisplayIO.getCurrentFrame());
280
281 // if there are duplicate methods with the same name
282 List<Method> possibles = new LinkedList<Method>();
283 possibles.add(toRun);
284 int i = 0;
285 while (_Actions.containsKey(mname + i)) {
286 possibles.add(_Actions.get(mname + i));
287 i++;
288 }
289
290 for (Method possible : possibles) {
291
292 // try first with the launching item as a parameter
293
294 // run method
295 try {
296 // convert parameters to objects and get the method to invoke
297 Object[] parameters = CreateObjects(possible, source, launcher,
298 command);
299
300 possible.invoke(null, parameters);
301 return;
302 } catch (Exception e) {
303 Logger.Log(e);
304 // e.printStackTrace();
305 }
306 }
307 }
308
309 /**
310 * Launches an agent with the given name, and passes in the given parameters
311 *
312 * @param name
313 * The name of the JAG to load
314 * @param parameters
315 * The parameters to pass to the JAG
316 * @param source
317 * The starting Frame that the JAG is being launched on
318 */
319 private static void LaunchAgent(String name, String parameters, Frame source) {
320 // save the current frame (if necesssary)
321 FrameUtils.LeavingFrame(source);
322
323 try {
324 // check for stored capitalisation
325 if (_JAGs.containsKey(name.toLowerCase())) {
326 name = _JAGs.get(name.toLowerCase());
327 } else if (name.toLowerCase().endsWith("tree")) {
328 parameters = name.substring(0, name.length() - "tree".length())
329 + " " + parameters;
330 name = "WriteTree";
331 } else if (name.toLowerCase().endsWith("frame")) {
332 parameters = name
333 .substring(0, name.length() - "frame".length())
334 + " " + parameters;
335 name = "WriteFrame";
336 }
337
338 // load the JAG class
339 Class agentClass = Class.forName(AGENTS_PACKAGE + name);
340
341 // get the constructor for the JAG class
342 Constructor con = null;
343 Constructor constructors[] = agentClass.getConstructors();
344 Object[] params = null;
345
346 // determine correct parameters for constructor
347 for (Constructor c : constructors) {
348 if (parameters.length() > 0
349 && c.getParameterTypes().length == 1) {
350 con = c;
351 params = new String[1];
352 params[0] = parameters;
353 break;
354 } else if (c.getParameterTypes().length == 0 && con == null)
355 con = c;
356 }
357
358 // if there is no constructor, return
359 if (con == null) {
360 FrameGraphics.DisplayMessage("Invalid parametres for agent.");
361 //System.out.println("Constructor not found...");
362 return;
363 }
364
365 // create the JAG
366 _Agent = (Agent) con.newInstance(params);
367
368 Thread t = new Thread(_Agent);
369 t.setPriority(Thread.MIN_PRIORITY);
370
371 // check for errors during initialisation
372 if (!_Agent.initialise(source)) {
373 FrameGraphics.ErrorMessage("Error initialising agent: " + name);
374 return;
375 }
376
377 _Agent.setStartFrame(source);
378
379 t.start();
380
381 // if there is a completion frame, then display it to the user
382 if (_Agent.hasResultFrame()) {
383 Frame next = _Agent.getResultFrame();
384 FrameUtils.DisplayFrame(next, true);
385 }
386
387 } catch (ClassNotFoundException cnf) {
388 FrameGraphics.ErrorMessage("Error: '" + name
389 + "' is not an action statement or Agent.");
390 } catch (Exception e) {
391 FrameGraphics.ErrorMessage("Error creating Agent: '" + name + "'");
392 System.out.println("Agent set to Null.");
393 _Agent = null;
394 e.printStackTrace();
395 Logger.Log(e);
396 }
397
398 return;
399 }
400
401 /**
402 * Used to determine if the previously launched agent is still executing.
403 *
404 * @return True if the last Agent is still executing, False otherwise.
405 */
406 public static boolean isAgentRunning() {
407 if (_Agent != null)
408 return _Agent.isRunning();
409
410 return false;
411 }
412
413 /**
414 * Stops the currently running Agent (If there is one) by calling
415 * Agent.stop(). Note: This may not stop the Agent immediately, but the
416 * Agent should terminate as soon as it is safe to do so.
417 */
418 public static void stopAgent() {
419 if (_Agent != null) {
420 FrameGraphics.DisplayMessage("Stopping Agent...");
421 _Agent.stop();
422
423 // while (_Agent.isRunning())
424 // ;
425
426 // FrameGraphics.DisplayMessage("Agent Stopped.");
427 }
428 }
429
430 public static void interruptAgent() {
431 if (_Agent != null) {
432 _Agent.interrupt();
433 }
434 }
435
436 /**
437 * Takes a list of Methods and finds one that accepts the given list of
438 * objects as parameters
439 *
440 * @param possibleMethods
441 * The list of Methods to search through
442 * @param parameters
443 * The array of parameters that the Method should accept
444 * @return The Method found if one was found, otherwise null
445 */
446 /*
447 * private static Method FindCompatibleMethods(List<Method>
448 * possibleMethods, Object[] parameters){ Method toRun = null;
449 *
450 * for(Method m : possibleMethods){ //the number of parameters must be the
451 * same if(parameters.length == m.getParameterTypes().length){ //if no
452 * methods are required, there is nothing else to check if(parameters.length ==
453 * 0) toRun = m; else //check that the list of parameters are of the right
454 * types for(int i = 0; i < parameters.length; i++){
455 * if(m.getParameterTypes()[i].isInstance(parameters[i])) toRun = m; else
456 * toRun = null; }
457 *
458 * //if a method has been found already, there is no need to keep searching
459 * if(toRun != null) break; } }
460 *
461 * return toRun; }
462 */
463
464 /**
465 * Converts the given String of values into an array of Objects
466 *
467 * @param launcher
468 * The Item used to launch the action, it may be required as a
469 * parameter
470 * @param values
471 * A list of space separated String values to convert to objects
472 * @return The created array of Objects
473 */
474 public static Object[] CreateObjects(Method method, Frame source,
475 Item launcher, String values) {
476 // The parameter types that should be created from the given String
477 Class[] paramTypes = method.getParameterTypes();
478
479 // if the method has no parameters
480 if (paramTypes.length == 0)
481 return new Object[0];
482
483 Object[] objects = new Object[paramTypes.length];
484 int ind = 0;
485
486 // if the first class in the list is a frame or item, it is the source
487 // or launcher
488 // length must be at least one if we are still running
489 if (paramTypes[0] == Frame.class) {
490 objects[0] = source;
491 ind = 1;
492 } else if (paramTypes[0] == Item.class) {
493 objects[0] = launcher;
494 ind = 1;
495 }
496
497 // if the first class was a frame or item, the second class might be the
498 // other
499 if (paramTypes.length > 1 && objects[0] != null) {
500 if (paramTypes[1] == Frame.class) {
501 objects[1] = source;
502 ind = 2;
503 } else if (paramTypes[1] == Item.class) {
504 objects[1] = launcher;
505 ind = 2;
506 }
507 }
508
509 String param;
510 // convert the rest of the objects
511 for (; ind < objects.length; ind++) {
512 // strip off the next value
513 param = ParseValue(values);
514 values = RemainingParams(values);
515
516 // check if its the last param and combine
517 if (values.length() > 0 && ind == objects.length - 1)
518 param = param.trim() + ' ' + values.trim();
519
520 // convert the value to an object
521 Object o = Conversion.Convert(paramTypes[ind], param);
522 objects[ind] = o;
523 }
524
525 return objects;
526
527 // strip off the first parameter
528
529 /*
530 * //the list of objects to return List<Object> returnParams = new
531 * ArrayList<Object>(); if(launcher != null)
532 * returnParams.add(launcher);
533 *
534 * while(param != null){ /* //the parameter is a String
535 * if(param.charAt(0) == '"') returnParams.add(param.substring(1,
536 * param.length() - 1)); else{ //try to parse an int try{ int val =
537 * Integer.parseInt(param); returnParams.add(val);
538 * }catch(NumberFormatException nfe){ System.out.println("Type not found
539 * for: " + param); } }
540 *
541 * if(param.length() < values.length()) values =
542 * values.substring(param.length() + 1); else values = "";
543 */
544
545 // returnParams.add(KMSConversion.Convert(type, value))
546 // get the next value
547 /*
548 * param = ParseValue(values); }
549 *
550 * return returnParams.toArray();
551 */
552 }
553
554 /**
555 * Returns a string containing the remaining params after ignoring the first
556 * one.
557 *
558 * @param params
559 * a space sparated list of N parameters
560 * @return the remaining N - 1 parameters
561 */
562 private static String RemainingParams(String params) {
563 if (params.length() == 0)
564 return null;
565
566 // remove leading and trailing spaces
567 params = params.trim();
568
569 // if there are no more parameters, we are done
570 if (params.indexOf(" ") < 0) {
571 return "";
572 }
573
574 // Check if we have a string parameter
575 if (params.charAt(0) == '"') {
576 return params.substring(params.indexOf('"', 1) + 1);
577 }
578
579 return params.substring(params.indexOf(" "));
580 }
581
582 /**
583 * Returns the first value in the space separated String of parameters
584 * passed in. Strings are enclosed in double quotes.
585 *
586 * @param params
587 * The String of space separated values
588 * @return The first value in the String
589 */
590 private static String ParseValue(String params) {
591 if (params.length() == 0)
592 return null;
593
594 // remove leading and trailing spaces
595 String param = params.trim();
596
597 // if there are no more parameters, we are done
598 if (param.indexOf(" ") < 0) {
599 return param;
600 }
601
602 // Check if we have a string parameter
603 if (param.charAt(0) == '"') {
604 return param.substring(1, param.indexOf('"', 1));
605 }
606
607 return param.substring(0, param.indexOf(" "));
608 }
609
610 /**
611 * Separates the name of the given command from any parameters and returns
612 * them
613 *
614 * @param command
615 * The String to separate out the Action or Agent name from
616 * @return The name of the Action of Agent with parameters stripped off
617 */
618 private static String getName(String command) {
619 if (command.indexOf(" ") < 0)
620 return command;
621
622 return command.substring(0, command.indexOf(" "));
623 }
624}
Note: See TracBrowser for help on using the repository browser.