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

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

Fixed up a few bugs with Saving of frames when actions are performed and with Comet and Tree stats.

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