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

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

Fixed some repaint issues

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