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

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

Fixing bugs for Rob

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