source: trunk/src/org/expeditee/actions/Actions.java@ 86

Last change on this file since 86 was 86, checked in by ra33, 16 years ago

Added DebugFrame action
Added GetCometStats
Lots of bug fixes

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