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

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

Fixed bug preventing it from working in windows... and added comment to stop that happening again.

Added right click and drag to a line...

File size: 18.5 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
296 // separate method name and parameter names
297 String mname = getName(command);
298 if (command.length() > mname.length())
299 command = command.substring(mname.length() + 1);
300 else
301 command = "";
302
303 // Strip off the @ from annotation items
304 if (mname.startsWith("@"))
305 mname = mname.substring(1);
306
307 mname = mname.trim();
308 String lowercaseName = mname.toLowerCase();
309 // check for protection on frame
310 if (ItemUtils.ContainsTag(source.getItems(), "@No" + mname)) {
311 FrameGraphics.DisplayMessage("Frame is protected by @No" + mname
312 + " tag.");
313 return;
314 }
315
316 // retrieve methods that match the name
317 Method toRun = _Actions.get(lowercaseName);
318
319 // if this is not the name of a method, it may be the name of an agent
320 if (toRun == null) {
321 LaunchAgent(mname, command, source);
322 return;
323 }
324
325 // Need to save the frame if we are navigating away from it so we dont
326 // loose changes
327 if (toRun.getDeclaringClass().getName().equals(NAVIGATIONS_CLASS)) {
328 FrameIO.SaveFrame(DisplayIO.getCurrentFrame());
329 }
330
331 // if there are duplicate methods with the same name
332 List<Method> possibles = new LinkedList<Method>();
333 possibles.add(toRun);
334 int i = 0;
335 while (_Actions.containsKey(lowercaseName + i)) {
336 possibles.add(_Actions.get(lowercaseName + i));
337 i++;
338 }
339
340 for (Method possible : possibles) {
341 // try first with the launching item as a parameter
342
343 // run method
344 try {
345 // convert parameters to objects and get the method to invoke
346 Object[] parameters = CreateObjects(possible, source, launcher,
347 command);
348 // Check that there are the same amount of params
349 if (parameters == null) {
350 continue;
351 }
352
353 possible.invoke(null, parameters);
354 return;
355 } catch (Exception e) {
356 Logger.Log(e);
357 e.printStackTrace();
358 }
359 }
360 if (possibles.size() > 0) {
361 FrameGraphics.ErrorMessage("Incorrect parameters for " + mname);
362 } else {
363 assert (false);
364 FrameGraphics.ErrorMessage(mname + " action not found");
365 }
366 }
367
368 /**
369 * Launches an agent with the given name, and passes in the given parameters
370 *
371 * @param name
372 * The name of the JAG to load
373 * @param parameters
374 * The parameters to pass to the JAG
375 * @param source
376 * The starting Frame that the JAG is being launched on
377 */
378 private static void LaunchAgent(String name, String parameters, Frame source) {
379 // Use the correct case version for printing error messages
380 String nameWithCorrectCase = name;
381 name = name.toLowerCase();
382 // save the current frame (if necesssary)
383 FrameUtils.LeavingFrame(source);
384
385 try {
386 // check for stored capitalisation
387 if (_JAGs.containsKey(name.toLowerCase())) {
388 name = _JAGs.get(name.toLowerCase());
389 } else if (name.toLowerCase().endsWith("tree")) {
390 parameters = name.substring(0, name.length() - "tree".length())
391 + " " + parameters;
392 name = "WriteTree";
393 } else if (name.toLowerCase().endsWith("frame")) {
394 parameters = name
395 .substring(0, name.length() - "frame".length())
396 + " " + parameters;
397 name = "WriteFrame";
398 }
399
400 // load the JAG class
401 Class agentClass = Class.forName(AGENTS_PACKAGE + name);
402
403 // get the constructor for the JAG class
404 Constructor con = null;
405 Constructor[] constructors = agentClass.getConstructors();
406 Object[] params = null;
407
408 // determine correct parameters for constructor
409 for (Constructor c : constructors) {
410 if (parameters.length() > 0
411 && c.getParameterTypes().length == 1) {
412 con = c;
413 params = new String[1];
414 params[0] = parameters;
415 break;
416 } else if (c.getParameterTypes().length == 0 && con == null) {
417 con = c;
418 }
419 }
420
421 // if there is no constructor, return
422 if (con == null) {
423 FrameGraphics.DisplayMessage(INVALID_PARAMETERS_ERROR
424 + nameWithCorrectCase);
425 // System.out.println("Constructor not found...");
426 return;
427 }
428
429 // create the JAG
430 _Agent = (Agent) con.newInstance(params);
431
432 Thread t = new Thread(_Agent);
433 t.setPriority(Thread.MIN_PRIORITY);
434
435 // check for errors during initialisation
436 if (!_Agent.initialise(source)) {
437 FrameGraphics.ErrorMessage("Error initialising agent: "
438 + nameWithCorrectCase);
439 return;
440 }
441
442 _Agent.setStartFrame(source);
443
444 t.start();
445
446 // if there is a completion frame, then display it to the user
447 if (_Agent.hasResultFrame()) {
448 Frame next = _Agent.getResultFrame();
449 FrameUtils.DisplayFrame(next, true);
450 }
451
452 } catch (ClassNotFoundException cnf) {
453 FrameGraphics.ErrorMessage(nameWithCorrectCase
454 + "' is not an action or agent.");
455 } catch (Exception e) {
456 FrameGraphics.ErrorMessage("Error creating Agent: '"
457 + nameWithCorrectCase + "'");
458 System.out.println("Agent set to Null.");
459 _Agent = null;
460 e.printStackTrace();
461 Logger.Log(e);
462 }
463
464 return;
465 }
466
467 /**
468 * Used to determine if the previously launched agent is still executing.
469 *
470 * @return True if the last Agent is still executing, False otherwise.
471 */
472 public static boolean isAgentRunning() {
473 if (_Agent != null)
474 return _Agent.isRunning();
475
476 return false;
477 }
478
479 /**
480 * Stops the currently running Agent (If there is one) by calling
481 * Agent.stop(). Note: This may not stop the Agent immediately, but the
482 * Agent should terminate as soon as it is safe to do so.
483 */
484 public static void stopAgent() {
485 if (_Agent != null && _Agent.isRunning()) {
486 FrameGraphics.DisplayMessage("Stopping Agent...");
487 _Agent.stop();
488 }
489 }
490
491 public static void interruptAgent() {
492 if (_Agent != null) {
493 _Agent.interrupt();
494 }
495 }
496
497 /**
498 * Converts the given String of values into an array of Objects
499 *
500 * @param launcher
501 * The Item used to launch the action, it may be required as a
502 * parameter
503 * @param values
504 * A list of space separated String values to convert to objects
505 * @return The created array of Objects
506 */
507 public static Object[] CreateObjects(Method method, Frame source,
508 Item launcher, String values) {
509 // The parameter types that should be created from the given String
510 Class[] paramTypes = method.getParameterTypes();
511
512 // if the method has no parameters
513 if (paramTypes.length == 0)
514 return new Object[0];
515
516 Object[] objects = new Object[paramTypes.length];
517 int ind = 0;
518
519 // if the first class in the list is a frame or item, it is the source
520 // or launcher
521 // length must be at least one if we are still running
522 if (paramTypes[0] == Frame.class) {
523 objects[0] = source;
524 ind = 1;
525 } else if (paramTypes[0] == Item.class) {
526 objects[0] = launcher;
527 ind = 1;
528 }
529
530 // if the first class was a frame or item, the second class might be the
531 // other
532 if (paramTypes.length > 1 && objects[0] != null) {
533 if (paramTypes[1] == Frame.class) {
534 objects[1] = source;
535 ind = 2;
536 } else if (paramTypes[1] == Item.class) {
537 objects[1] = launcher;
538 ind = 2;
539 }
540 }
541
542 String param = values;
543 // convert the rest of the objects
544 for (; ind < objects.length; ind++) {
545 // check if its the last param and combine
546 if (values.length() > 0 && ind == objects.length - 1) {
547 param = values.trim();
548 // check if its a string
549 if (param.length() > 0 && param.charAt(0) == '"') {
550 int endOfString = param.indexOf('"', 1);
551 if (endOfString > 0) {
552 param = param.substring(0, endOfString);
553 }
554 }
555 } else {// strip off the next value
556 param = ParseValue(values);
557 values = RemainingParams(values);
558 }
559 // convert the value to an object
560 try {
561 Object o = Conversion.Convert(paramTypes[ind], param);
562 if (o == null)
563 return null;
564 objects[ind] = o;
565 } catch (Exception e) {
566 return null;
567 }
568 }
569
570 return objects;
571 }
572
573 /**
574 * Returns a string containing the remaining params after ignoring the first
575 * one.
576 *
577 * @param params
578 * a space sparated list of N parameters
579 * @return the remaining N - 1 parameters
580 */
581 public static String RemainingParams(String params) {
582 if (params.length() == 0)
583 return null;
584
585 // remove leading and trailing spaces
586 params = params.trim();
587
588 // if there are no more parameters, we are done
589 if (params.indexOf(" ") < 0) {
590 return "";
591 }
592
593 // Check if we have a string parameter
594 if (params.charAt(0) == '"') {
595 int endOfString = params.indexOf('"', 1);
596 if (endOfString > 0) {
597 if (endOfString > params.length())
598 return "";
599 return params.substring(endOfString + 1).trim();
600 }
601 }
602
603 return params.substring(params.indexOf(" ")).trim();
604 }
605
606 /**
607 * Returns the first value in the space separated String of parameters
608 * passed in. Strings are enclosed in double quotes.
609 *
610 * @param params
611 * The String of space separated values
612 * @return The first value in the String
613 */
614 public static String ParseValue(String params) {
615 if (params.length() == 0)
616 return null;
617
618 // remove leading and trailing spaces
619 String param = params.trim();
620
621 // Check if we have a string parameter
622 if (param.charAt(0) == '"') {
623 int endOfString = param.indexOf('"', 1);
624 if (endOfString > 0)
625 return param.substring(1, endOfString);
626 }
627
628 // if there are no more parameters, we are done
629 if (param.indexOf(" ") < 0) {
630 return param;
631 }
632
633 return param.substring(0, param.indexOf(" "));
634 }
635
636 /**
637 * Separates the name of the given command from any parameters and returns
638 * them
639 *
640 * @param command
641 * The String to separate out the Action or Agent name from
642 * @return The name of the Action of Agent with parameters stripped off
643 */
644 private static String getName(String command) {
645 if (command.indexOf(" ") < 0)
646 return command;
647
648 return command.substring(0, command.indexOf(" "));
649 }
650}
Note: See TracBrowser for help on using the repository browser.