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

Last change on this file since 975 was 975, checked in by bln4, 9 years ago

When attempting to run a action and finding multiple potential matches (overloaded methods) it now attempts to match the candidate with the most parameters without going over.

For example: running the action "RunJava 100"
-with available actions RunJava() and RunJava(String)
-will now reliably run RunJava(String)

However; some potential confusion still exists; take the same example: running the action "RunJava 100"
-but this time has available actions RunJava(), RunJava(String) and RunJava(Integer)
-whichever it sees first; either the String or the Integer variant will get run
-furthermore it appears nondeterministic over which it will find first. on any given run of Expeditee it will consistently find the same one; however a restart may produce varying results.
-as this behaviour was not noticed for some time my current theory is that it has something to do with the order things get compiled; it was scala code (SpIDER) that first exhibited this problem. Scala compilation does things with inner classes. More investigation needed if we want a answer as to 'why'

File size: 32.0 KB
Line 
1/**
2 * Actions.java
3 * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19package org.expeditee.actions;
20
21import java.awt.GraphicsEnvironment;
22import java.lang.reflect.Constructor;
23import java.lang.reflect.Method;
24import java.lang.reflect.Modifier;
25import java.rmi.UnexpectedException;
26import java.util.Collection;
27import java.util.Comparator;
28import java.util.HashMap;
29import java.util.LinkedList;
30import java.util.List;
31import java.util.Set;
32
33import org.expeditee.agents.Agent;
34import org.expeditee.gui.DisplayIO;
35import org.expeditee.gui.Frame;
36import org.expeditee.gui.FrameGraphics;
37import org.expeditee.gui.FrameIO;
38import org.expeditee.gui.FrameUtils;
39import org.expeditee.gui.FreeItems;
40import org.expeditee.gui.MessageBay;
41import org.expeditee.io.Conversion;
42import org.expeditee.items.Item;
43import org.expeditee.items.ItemUtils;
44import org.expeditee.items.Text;
45import org.expeditee.reflection.PackageLoader;
46import org.expeditee.simple.SString;
47import org.expeditee.stats.Logger;
48
49/**
50 * The Action class is used to launch Actions and Agents.
51 *
52 * This class checks all class files in the same directory, and reads in and
53 * adds all the methods from them. The methods are stored in a Hashtable so that
54 * the lowercase method names can be mapped to the correctly capatilized method
55 * names (to provide case-insensitivity)
56 *
57 * When adding an action to a class in the actions folder the following must be
58 * considered: <li>If the first parameter is of type Frame, the current frame
59 * will be passed as a parameter. <li>If the next param is of type Item the item
60 * on the end of the cursor will be passed or the item that was clicked to
61 * execute the action if nothing is on the end of the cursor. current frame or
62 * item.</li> <li>If there are multiple overloads for the same method they
63 * should be declared in order of the methods with the most parameteres to least
64 * parameters.</li>
65 */
66public class Actions {
67
68 private static final String INVALID_PARAMETERS_ERROR = "Invalid parameters for agent: "; //$NON-NLS-1$
69
70 // the currently running agent (if there is one)
71 private static Agent _Agent = null;
72
73 // maps lower case method names to the method
74 private static HashMap<String, Method> _Actions = new HashMap<String, Method>();
75
76 // map lower case fonts to capitalized fonts
77 private static HashMap<String, String> _Fonts = new HashMap<String, String>();
78
79 // maps lower case JAG class names to capitalized JAG full class names
80 private static HashMap<String, String> _JAGs = new HashMap<String, String>();
81
82 // maps lower case IW class names to capitalized IW names
83 private static HashMap<String, String> _IWs = new HashMap<String, String>();
84
85 public static final String ROOT_PACKAGE = "org.expeditee.";
86
87 // Package and class file locations
88 public static final String ACTIONS_PACKAGE = ROOT_PACKAGE + "actions.";
89
90 public static final String AGENTS_PACKAGE = ROOT_PACKAGE + "agents.";
91
92 public static final String WIDGET_PACKAGE = ROOT_PACKAGE + "items.widgets.";
93
94 public static final String CHARTS_PACKAGE = ROOT_PACKAGE
95 + "items.widgets.charts.";
96
97 public static final String NAVIGATIONS_CLASS = ROOT_PACKAGE
98 + "actions.NavigationActions";
99
100 // public static Class[] getClasses(String pckgname)
101 // throws ClassNotFoundException {
102 // ArrayList<Class> classes = new ArrayList<Class>();
103 // // Get a File object for the package
104 // File directory = null;
105 // // Must be a forward slash for loading resources
106 // String path = pckgname.replace('.', '/');
107 // System.err.println("Get classes: " + path);
108 // try {
109 // ClassLoader cld = Thread.currentThread().getContextClassLoader();
110 // if (cld == null) {
111 // throw new ClassNotFoundException("Can't get class loader.");
112 // }
113 // URL resource = null;
114 // try {
115 // Enumeration<URL> resources = cld.getResources(path);
116 // System.err.println(resources);
117 // while (resources.hasMoreElements()) {
118 // URL url = resources.nextElement();
119 // // Ingore the classes in the test folder when we are running
120 // // the program from Eclipse
121 // // This doesnt apply when running directly from the jar
122 // // because the test classes are not compiled into the jar.
123 // // TODO change this so it is only done when running from
124 // // Eclipse... if it causes problems again!!
125 // // if (!url.toString().toLowerCase().contains("/tests/")) {
126 // resource = url;
127 // // break;
128 // // }
129 // }
130 // } catch (Exception e) {
131 // e.printStackTrace();
132 // }
133 // if (resource == null) {
134 // throw new ClassNotFoundException("No resource for " + path);
135 // }
136 // directory = new File(resource.getFile());
137 // } catch (NullPointerException x) {
138 // x.printStackTrace();
139 // throw new ClassNotFoundException(pckgname + " (" + directory
140 // + ") does not appear to be a valid package");
141 // }
142 // // System.out.println("Path:" + directory.getPath());
143 // int splitPoint = directory.getPath().indexOf('!');
144 // if (splitPoint > 0) {
145 // try {
146 // String jarName = directory.getPath().substring(
147 // "file:".length(), splitPoint);
148 // // Windows HACK
149 // if (jarName.indexOf(":") >= 0)
150 // jarName = jarName.substring(1);
151 //
152 // if (jarName.indexOf("%20") > 0) {
153 // jarName = jarName.replace("%20", " ");
154 // }
155 // // System.out.println("JarName:" + jarName);
156 // JarFile jarFile = new JarFile(jarName);
157 //
158 // Enumeration entries = jarFile.entries();
159 // int classCount = 0;
160 // while (entries.hasMoreElements()) {
161 // ZipEntry entry = (ZipEntry) entries.nextElement();
162 // String className = entry.getName();
163 // if (className.startsWith(path)) {
164 // if (className.endsWith(".class")
165 // && !className.contains("$")) {
166 // classCount++;
167 // // The forward slash below is a forwards slash for
168 // // both windows and linux
169 // classes.add(Class.forName(className.substring(0,
170 // className.length() - 6).replace('/', '.')));
171 // }
172 // }
173 // }
174 // jarFile.close();
175 // // System.out.println("Loaded " + classCount + " classes from "
176 // // + pckgname);
177 //
178 // } catch (Exception e) {
179 // e.printStackTrace();
180 // }
181 //
182 // } else {
183 //
184 // if (directory.exists()) {
185 // // Get the list of the files contained in the package
186 // String[] files = directory.list();
187 // for (int i = 0; i < files.length; i++) {
188 // // we are only interested in .class files
189 // if (files[i].endsWith(".class") && !files[i].contains("$")
190 // && !files[i].equals("Actions.class")) {
191 // // removes the .class extension
192 // classes
193 // .add(Class.forName(pckgname
194 // + files[i].substring(0, files[i]
195 // .length() - 6)));
196 // }
197 // }
198 // } else {
199 // throw new ClassNotFoundException("The package '" + pckgname +
200 // "' in the directory '" + directory
201 // + "' does not appear to be a valid package");
202 // }
203 // }
204 // Class[] classesA = new Class[classes.size()];
205 // classes.toArray(classesA);
206 // return classesA;
207 // }
208
209 /**
210 * Clears out the Action and JAG Hashtables and refills them. Normally this
211 * is only called once when the system starts.
212 *
213 * @return a warning message if there were any problems loading agents or
214 * actions.
215 */
216 public static Collection<String> Init() {
217
218 Collection<String> warnings = new LinkedList<String>();
219 List<Class<?>> classes;
220
221 try {
222 classes = PackageLoader.getClassesNew(AGENTS_PACKAGE);
223
224 for (Class clazz : classes) {
225 String name = clazz.getSimpleName();
226 // maps lower case name to correct capitalised name
227 _JAGs.put(name.toLowerCase(), clazz.getName());
228 }
229
230 classes = PackageLoader.getClassesNew(WIDGET_PACKAGE);
231
232 for (Class clazz : classes) {
233 String name = clazz.getSimpleName();
234 // maps lower case name to correct capitalised name
235 _IWs.put(name.toLowerCase(), WIDGET_PACKAGE + name);
236 }
237
238 classes = PackageLoader.getClassesNew(CHARTS_PACKAGE);
239
240 for (Class clazz : classes) {
241 String name = clazz.getSimpleName();
242 // maps lower case name to correct capitalised name
243 _IWs.put("charts." + name.toLowerCase(), CHARTS_PACKAGE + name);
244 }
245 } catch (ClassNotFoundException e) {
246 System.err.println("ClassNotFoundException");
247 e.printStackTrace();
248 } catch (Exception e) {
249 warnings.add("You must have Java 1.5 or higher to run Expeditee");
250 warnings.add(e.getMessage());
251 e.printStackTrace();
252 }
253
254 try {
255 classes = PackageLoader.getClassesNew(ACTIONS_PACKAGE);
256
257 for (Class clazz : classes) {
258 String name = clazz.getSimpleName();
259 // Ignore the test classes
260 if (name.toLowerCase().contains("test"))
261 continue;
262 // read in all the methods from the class
263 try {
264 // System.out.println(name)
265 LoadMethods(Class.forName(ACTIONS_PACKAGE + name));
266 } catch (ClassNotFoundException e) {
267 Logger.Log(e);
268 e.printStackTrace();
269 }
270 }
271 } catch (Exception e) {
272 warnings.add(e.getMessage());
273 }
274 return warnings;
275 }
276
277 /**
278 * Temporary, if a plugin system is devised then this would porbably become
279 * redundant. For now this allows external agents to be included.
280 *
281 * @param fullClassNames
282 * A set of full class names, that is, the class package and
283 * name. For example" "org.myplugin.agents.SerializedSearch"
284 *
285 * @return A collection of classes their were omitted because either there
286 * was a name clash with existing agents or did not exist. i.e. is
287 * completely successful this will be empty. Never null.
288 *
289 * @throws NullPointerException
290 * If fullClassNames is null.
291 *
292 */
293 public static Collection<String> addAgents(Set<String> fullClassNames) {
294 if (fullClassNames == null)
295 throw new NullPointerException("fullClassNames");
296
297 List<String> omittedAgents = new LinkedList<String>();
298
299 for (String fullName : fullClassNames) {
300
301 if (fullName == null || fullName.length() == 0)
302 continue;
303
304 boolean didAdd = false;
305
306 try {
307 // Does the class even exist?
308 Class<?> c = Class.forName(fullName);
309
310 String name = c.getSimpleName().toLowerCase();
311
312 if (!_JAGs.containsKey(name)) {
313
314 _JAGs.put(name, fullName);
315 didAdd = true;
316
317 }
318
319 } catch (ClassNotFoundException e) { // Nope it does not exist
320 e.printStackTrace();
321 }
322
323 if (!didAdd)
324 omittedAgents.add(fullName);
325
326 }
327
328 return omittedAgents;
329 }
330
331 /**
332 * Loads all the Methods that meet the requirements checked by MethodCheck
333 * into the hashtable.
334 *
335 * @param c
336 * The Class to load the Methods from.
337 */
338 public static void LoadMethods(Class<?> c) {
339 assert (c != null);
340
341 // list of methods to test
342 Method[] toLoad = c.getMethods();
343
344 for (Method m : toLoad) {
345 // only allow methods with the right modifiers
346 if (MethodCheck(m)) {
347 String lowercaseName = m.getName().toLowerCase();
348 if (!(_Actions.containsKey(lowercaseName)))
349 _Actions.put(lowercaseName, m);
350 else {
351 int i = 0;
352 while (_Actions.containsKey(lowercaseName + i))
353 i++;
354
355 _Actions.put(lowercaseName + i, m);
356 }
357
358 }
359 }
360 }
361
362 /**
363 * Checks if the given Method corresponds to the restrictions of Action
364 * commands, namely: Declared (not inherited), Public, and Static, with a
365 * void return type.
366 *
367 * @param m
368 * The Method to check
369 * @return True if the Method meets the above conditions, false otherwise.
370 */
371 private static boolean MethodCheck(Method m) {
372 int mods = m.getModifiers();
373
374 // check the method is declared (not inherited)
375 if ((mods & Method.DECLARED) != Method.DECLARED)
376 return false;
377
378 // check the method is public
379 if ((mods & Modifier.PUBLIC) != Modifier.PUBLIC)
380 return false;
381
382 // check the method is static
383 if ((mods & Modifier.STATIC) != Modifier.STATIC)
384 return false;
385
386 // if we have not returned yet, then the tests have all passed
387 return true;
388 }
389
390 /**
391 * Performs the given action command. The source frame and item are given
392 * because they are required by some actions. Note that the source frame
393 * does not ahve to be the items parent frame.
394 *
395 * If multiple actions exist with the name command.takeWhile(_ != ' ') then
396 * it attempts to find candidate that best matches the arguments passed in.
397 * (arguments are command.dropWhile(_ != ' ') or is floating)
398 *
399 * @param source
400 * The frame that the action should apply to
401 * @param launcher
402 * The item that has the action assigned to it
403 * @param command
404 * The action to perform
405 * @return
406 * @throws Exception
407 */
408 public static Object PerformAction(final Frame source, final Item launcher,
409 final String command) throws Exception {
410 System.err.println("Running action: " + command + " with floating: " + launcher);
411 final String actionName = getName(command);
412 final String parameters = command.substring(actionName.length()).trim();
413
414 // Check for protection on frame.
415 if (ItemUtils.ContainsTag(source.getItems(), "@No" + actionName)) {
416 final String errorMsg = "Frame is protected by @No" + actionName
417 + " tag.";
418 MessageBay.errorMessage(errorMsg);
419 throw new RuntimeException(errorMsg);
420 }
421
422 // Find all canditates. Sort by quantity of arguments.
423 final String lowercaseName = actionName.toLowerCase();
424 final Method firstCanditate = _Actions.get(lowercaseName);
425 // if this is not the name of a method, it may be the name of an agent
426 if (firstCanditate == null) {
427 LaunchAgent(actionName, command, source, launcher);
428 return null;
429 }
430 // need to save the frame if we are navigating away from it so we dont
431 // loose changes
432 if (firstCanditate.getDeclaringClass().getName().equals(NAVIGATIONS_CLASS)) {
433 FrameIO.SaveFrame(DisplayIO.getCurrentFrame());
434 }
435 final List<Method> canditates = new LinkedList<Method>();
436 canditates.add(firstCanditate);
437 int i = 0;
438 while (_Actions.containsKey(lowercaseName + i))
439 canditates.add(_Actions.get(lowercaseName + i++));
440 canditates.sort(new Comparator<Method>() {
441 @Override
442 public int compare(final Method m1, final Method m2) {
443 return m2.getParameterCount() - m1.getParameterCount();
444 }
445 });
446
447 // Find best candidate. Start searching with the candidate with most
448 // arguments.
449 for (final Method canditate : canditates) {
450 final Object[] paramObjects = CreateObjects(canditate, source,
451 launcher, parameters);
452 try {
453 if (paramObjects != null)
454 return canditate.invoke(null, paramObjects);
455 } catch (final Exception e) {
456 Logger.Log(e);
457 e.printStackTrace();
458 }
459 }
460 assert (canditates.size() > 0);
461 final String nl = System.getProperty("line.separator");
462 final StringBuilder errorBuilder = new StringBuilder(
463 "Incorrect parameters for " + actionName + nl + "Canditates: ");
464 for (final Method canditate : canditates)
465 errorBuilder.append(canditate + nl);
466 throw new RuntimeException(errorBuilder.toString());
467 }
468
469 // /**
470 // * Performs the given action command. The source Frame and Item are given
471 // because they are required by some actions.
472 // * Note that the source frame does not have to be the Item's parent Frame.
473 // *
474 // * @param source
475 // * The Frame that the action should apply to
476 // * @param launcher
477 // * The Item that has the action assigned to it
478 // * @param command
479 // * The action to perform
480 // */
481 // public static Object PerformAction(Frame source, Item launcher, String
482 // command) throws Exception {
483 // // if (!command.equalsIgnoreCase("Restore"))
484 // // FrameIO.SaveFrame(source, false);
485 // // TODO make restore UNDO the changes made by the last action
486 //
487 // // separate method name and parameter names
488 // String mname = getName(command);
489 // command = command.substring(mname.length()).trim();
490 // // If no params are provided get them from a text item on the cursor
491 // if (command.length() == 0 && launcher instanceof Text &&
492 // launcher.isFloating()) {
493 // command = launcher.getText();
494 // }
495 //
496 // // Strip off the @ from annotation items
497 // if (mname.startsWith("@"))
498 // mname = mname.substring(1);
499 //
500 // mname = mname.trim();
501 // String lowercaseName = mname.toLowerCase();
502 // // check for protection on frame
503 // if (ItemUtils.ContainsTag(source.getItems(), "@No" + mname)) {
504 // throw new RuntimeException("Frame is protected by @No" + mname +
505 // " tag.");
506 // }
507 //
508 // // retrieve methods that match the name
509 // Method toRun = _Actions.get(lowercaseName);
510 //
511 // // if this is not the name of a method, it may be the name of an agent
512 // if (toRun == null) {
513 // LaunchAgent(mname, command, source, launcher);
514 // return null;
515 // }
516 //
517 // // Need to save the frame if we are navigating away from it so we dont
518 // // loose changes
519 // if (toRun.getDeclaringClass().getName().equals(NAVIGATIONS_CLASS)) {
520 // FrameIO.SaveFrame(DisplayIO.getCurrentFrame());
521 // }
522 //
523 // // if there are duplicate methods with the same name
524 // List<Method> possibles = new LinkedList<Method>();
525 // possibles.add(toRun);
526 // int i = 0;
527 // while (_Actions.containsKey(lowercaseName + i)) {
528 // possibles.add(_Actions.get(lowercaseName + i));
529 // i++;
530 // }
531 //
532 // for (Method possible : possibles) {
533 // // try first with the launching item as a parameter
534 //
535 // // run method
536 // try {
537 // // convert parameters to objects and get the method to invoke
538 // Object[] parameters = CreateObjects(possible, source, launcher, command);
539 // // Check that there are the same amount of params
540 // if (parameters == null) {
541 // continue;
542 // }
543 //
544 // return possible.invoke(null, parameters);
545 // } catch (Exception e) {
546 // Logger.Log(e);
547 // e.printStackTrace();
548 // }
549 // }
550 // // If the actions was not found... then it is run as an agent
551 // assert (possibles.size() > 0);
552 // throw new RuntimeException("Incorrect parameters for " + mname);
553 // }
554
555 /**
556 * Launches an agent with the given name, and passes in the given parameters
557 *
558 * @param name
559 * The name of the JAG to load
560 * @param parameters
561 * The parameters to pass to the JAG
562 * @param source
563 * The starting Frame that the JAG is being launched on
564 */
565 private static void LaunchAgent(String name, String parameters,
566 Frame source, Item clicked) throws Exception {
567 // Use the correct case version for printing error messages
568 String nameWithCorrectCase = name;
569 name = name.toLowerCase();
570
571 String fullClassName = AGENTS_PACKAGE + name;
572
573 try {
574 // check for stored capitalisation
575 if (_JAGs.containsKey(name)) {
576 fullClassName = _JAGs.get(name);
577 } else if (name.endsWith("tree")) {
578 parameters = name.substring(0, name.length() - "tree".length())
579 + " " + parameters;
580 fullClassName = AGENTS_PACKAGE + "writetree";
581
582 } else if (name.endsWith("frame")) {
583 parameters = name
584 .substring(0, name.length() - "frame".length())
585 + " "
586 + parameters;
587 fullClassName = AGENTS_PACKAGE + "writeframe";
588 }
589
590 // load the JAG class
591 Class<?> agentClass = Class.forName(fullClassName);
592
593 // get the constructor for the JAG class
594 Constructor<?> con = null;
595 Constructor<?>[] constructors = agentClass.getConstructors();
596 Object[] params = null;
597
598 parameters = parameters.trim();
599 // determine correct parameters for constructor
600 for (Constructor<?> c : constructors) {
601 Class<?>[] paramTypes = c.getParameterTypes();
602 int paramCount = paramTypes.length;
603 if (paramCount > 0 && parameters.length() > 0) {
604 params = new Object[paramCount];
605 String[] paramStrings = parameters.split("\\s+");
606 /**
607 * Any extra parameters will be treated as the rest of the
608 * string if the last param is a string
609 */
610 if (paramCount > paramStrings.length) {
611 continue;
612 }
613
614 /**
615 * If there are extra parameters the last param must be a
616 * String
617 */
618 int lastParam = paramTypes.length - 1;
619
620 if (paramCount < paramStrings.length
621 && !paramTypes[lastParam].equals(String.class)) {
622 continue;
623 }
624
625 try {
626 for (int i = 0; i < paramCount; i++) {
627 SString nextParam = new SString(paramStrings[i]);
628 params[i] = null;
629 if (paramTypes[i].equals(int.class)
630 || paramTypes[i].equals(Integer.class)) {
631 params[i] = nextParam.integerValue().intValue();
632 } else if (paramTypes[i].equals(long.class)
633 || paramTypes[i].equals(Long.class)) {
634 params[i] = nextParam.integerValue();
635 } else if (paramTypes[i].equals(double.class)
636 || paramTypes[i].equals(Double.class)) {
637 params[i] = nextParam.doubleValue();
638 } else if (paramTypes[i].equals(float.class)
639 || paramTypes[i].equals(Float.class)) {
640 params[i] = nextParam.doubleValue()
641 .floatValue();
642 } else if (paramTypes[i].equals(boolean.class)
643 || paramTypes[i].equals(Boolean.class)) {
644 params[i] = nextParam.booleanValue();
645 } else if (paramTypes[i].equals(String.class)) {
646 params[i] = nextParam.stringValue();
647 } else {
648 throw new UnexpectedException(
649 "Unexpected type "
650 + paramTypes[i].getClass()
651 .toString());
652 }
653 }
654 } catch (Exception e) {
655 continue;
656 }
657
658 if (paramCount < paramStrings.length) {
659
660 /**
661 * Append extra params on the end of the last string
662 * param
663 */
664 String s = params[lastParam].toString();
665 for (int i = paramCount; i < paramStrings.length; i++) {
666 s += ' ' + paramStrings[i];
667 }
668 params[lastParam] = s;
669 }
670
671 con = c;
672 break;
673 } else if (c.getParameterTypes().length == 0 && con == null) {
674 con = c;
675 params = null;
676 }
677 }
678
679 // if there is no constructor, return
680 if (con == null) {
681 throw new RuntimeException(INVALID_PARAMETERS_ERROR
682 + nameWithCorrectCase);
683 }
684
685 // create the JAG
686 Agent toLaunch = (Agent) con.newInstance(params);
687
688 LaunchAgent(toLaunch, source, clicked);
689
690 } catch (ClassNotFoundException cnf) {
691 _Agent = null;
692 throw new RuntimeException("'" + nameWithCorrectCase
693 + "' is not an action or agent.");
694 }
695 }
696
697 public static void LaunchAgent(String name, String parameters, Frame source)
698 throws Exception {
699 LaunchAgent(name, parameters, source, null);
700 }
701
702 /**
703 * Launches an agent from an already instantiated object.
704 *
705 * @param agent
706 * The agent to launch. Must not be null.
707 *
708 * @param source
709 * The calling frame that launched it. Must not be null.
710 *
711 * @param itemParam
712 * The item parameter for the agent.
713 *
714 * @throws NullPointerException
715 * if any of the arguments are null.
716 */
717 public static void LaunchAgent(Agent agent, Frame source, Item itemParam) {
718
719 if (agent == null)
720 throw new NullPointerException("agent");
721 if (source == null)
722 throw new NullPointerException("source");
723 // if (itemParam == null) throw new NullPointerException("itemParam");
724
725 String nameWithCorrectCase = agent.getClass().getSimpleName();
726
727 try {
728
729 // create the JAG
730 _Agent = agent;
731
732 Thread.UncaughtExceptionHandler h = new Thread.UncaughtExceptionHandler() {
733 public void uncaughtException(Thread th, Throwable ex) {
734
735 MessageBay.errorMessage("Error occurred in Action: "
736 + th.getName());
737 ex.printStackTrace();
738
739 stopAgent();
740 _Agent = null;
741 }
742 };
743
744 Thread t = new Thread(_Agent, nameWithCorrectCase);
745
746 t.setPriority(Thread.MIN_PRIORITY);
747 t.setUncaughtExceptionHandler(h);
748
749 if (FreeItems.textOnlyAttachedToCursor()) {
750 itemParam = FreeItems.getItemAttachedToCursor();
751 }
752
753 // check for errors during initialisation
754 if (!_Agent.initialise(source, itemParam)) {
755 _Agent = null;
756 throw new RuntimeException("Error initialising agent: "
757 + nameWithCorrectCase);
758 }
759
760 // save the current frame (if necesssary)
761 // TODO make this nicer... ie. make Format an action rather than an
762 // agent and save frames only before running agents
763 if (!nameWithCorrectCase.equalsIgnoreCase("format")
764 && !nameWithCorrectCase.equalsIgnoreCase("sort")) {
765 FrameUtils.LeavingFrame(source);
766 }
767
768 if (_Agent.hasResultString()) {
769 // Just run the agent on this thread... dont run it in the
770 // background
771 t.run();
772 if (_Agent != null) {
773 String result = _Agent.toString();
774 // Attach the result to the cursor
775 if (FreeItems.textOnlyAttachedToCursor()) {
776 Item resultItem = FreeItems.getItemAttachedToCursor();
777 resultItem.setText(result);
778 }
779 // if there is a completion frame, then display it to the
780 // user
781 }
782 } else {
783 t.start();
784 if (_Agent != null && _Agent.hasResultFrame()) {
785 // TODO We want to be able to navigate through the frames as
786 // the results are loading
787 Frame next = _Agent.getResultFrame();
788 FrameUtils.DisplayFrame(next, true, true);
789 }
790 }
791 } catch (Exception e) {
792 _Agent = null;
793 e.printStackTrace();
794 throw new RuntimeException("Error creating Agent: '"
795 + nameWithCorrectCase + "'");
796 }
797 FrameGraphics.refresh(false);
798 }
799
800 /**
801 * Used to determine if the previously launched agent is still executing.
802 *
803 * @return True if the last Agent is still executing, False otherwise.
804 */
805 public static boolean isAgentRunning() {
806 if (_Agent != null)
807 return _Agent.isRunning();
808
809 return false;
810 }
811
812 /**
813 * Stops the currently running Agent (If there is one) by calling
814 * Agent.stop(). Note: This may not stop the Agent immediately, but the
815 * Agent should terminate as soon as it is safe to do so.
816 */
817 public static void stopAgent() {
818 if (_Agent != null && _Agent.isRunning()) {
819 MessageBay.errorMessage("Stopping Agent...");
820 _Agent.stop();
821 }
822 }
823
824 public static void interruptAgent() {
825 if (_Agent != null) {
826 _Agent.interrupt();
827 }
828 }
829
830 /**
831 * Converts the given String of values into an array of Objects
832 *
833 * @param launcher
834 * The Item used to launch the action, it may be required as a
835 * parameter
836 * @param values
837 * A list of space separated String values to convert to objects
838 * @return The created array of Objects
839 */
840 public static Object[] CreateObjects(Method method, Frame source,
841 Item launcher, String values) {
842 // The parameter types that should be created from the given String
843 Class<?>[] paramTypes = method.getParameterTypes();
844
845 int paramCount = paramTypes.length;
846 // if the method has no parameters
847 if (paramCount == 0)
848 return new Object[0];
849
850 Object[] objects = new Object[paramCount];
851 int ind = 0;
852
853 /*
854 * if the first class in the list is a frame or item, it is the source
855 * or launcher length must be at least one if we are still running
856 */
857 if (paramTypes[ind] == Frame.class) {
858 objects[ind] = source;
859 ind++;
860 }
861
862 // Check if the second item is an item
863 if (paramCount > ind && Item.class.isAssignableFrom(paramTypes[ind])) {
864 objects[ind] = launcher;
865 ind++;
866 }// If there is stuff on the cursor use it for the rest of the params
867 else if (launcher != null && launcher.isFloating()) {
868 values = launcher.getText();
869 }
870
871 String param = values;
872 // convert the rest of the objects
873 for (; ind < objects.length; ind++) {
874 // check if its the last param and combine
875 if (values.length() > 0 && ind == objects.length - 1) {
876 param = values.trim();
877 // check if its a string
878 if (param.length() > 0 && param.charAt(0) == '"') {
879 int endOfString = param.indexOf('"', 1);
880 if (endOfString > 0) {
881 param = param.substring(1, endOfString);
882 }
883 }
884 } else {// strip off the next value
885 param = ParseValue(values);
886 values = RemainingParams(values);
887 }
888 // convert the value to an object
889 try {
890 Object o = Conversion.Convert(paramTypes[ind], param);
891 if (o == null)
892 return null;
893 objects[ind] = o;
894 } catch (Exception e) {
895 return null;
896 }
897 }
898
899 return objects;
900 }
901
902 /**
903 * Returns a string containing the remaining params after ignoring the first
904 * one.
905 *
906 * @param params
907 * a space sparated list of N parameters
908 * @return the remaining N - 1 parameters
909 */
910 public static String RemainingParams(String params) {
911 if (params.length() == 0)
912 return null;
913
914 // remove leading and trailing spaces
915 params = params.trim();
916
917 // if there are no more parameters, we are done
918 if (params.indexOf(" ") < 0) {
919 return "";
920 }
921
922 // Check if we have a string parameter
923 if (params.charAt(0) == '"') {
924 int endOfString = params.indexOf('"', 1);
925 if (endOfString > 0) {
926 if (endOfString > params.length())
927 return "";
928 return params.substring(endOfString + 1).trim();
929 }
930 }
931
932 return params.substring(params.indexOf(" ")).trim();
933 }
934
935 /**
936 * Returns the first value in the space separated String of parameters
937 * passed in. Strings are enclosed in double quotes.
938 *
939 * @param params
940 * The String of space separated values
941 * @return The first value in the String
942 */
943 public static String ParseValue(String params) {
944 if (params.length() == 0)
945 return null;
946
947 // remove leading and trailing spaces
948 String param = params.trim();
949
950 // Check if we have a string parameter
951 if (param.charAt(0) == '"') {
952 int endOfString = param.indexOf('"', 1);
953 if (endOfString > 0)
954 return param.substring(1, endOfString);
955 }
956
957 // if there are no more parameters, we are done
958 if (param.indexOf(" ") < 0) {
959 return param;
960 }
961
962 return param.substring(0, param.indexOf(" "));
963 }
964
965 /**
966 * Separates the name of the given command from any parameters and returns
967 * them Also deals with leading '@'s and leading and tailing whitespace.
968 *
969 * @param command
970 * The String to separate out the Action or Agent name from
971 * @return The name of the Action of Agent with parameters stripped off
972 */
973 private static String getName(String command) {
974 command = command.trim();
975 if (command.startsWith("@"))
976 command = command.substring(1);
977 if (command.indexOf(" ") < 0)
978 return command;
979
980 return command.substring(0, command.indexOf(" "));
981 }
982
983 /**
984 * Gets an uncapitalized font name and returns the capitalized font name.
985 * The capitalized form can be used with the Font.decoded method to get a
986 * corresponding Font object.
987 *
988 * @param fontName
989 * a font name in mixed case
990 * @return the correct capitalized form of the font name
991 */
992 public static String getCapitalizedFontName(String fontName) {
993 // Initialize the fonts if they have not already been loaded
994 initFonts();
995 return _Fonts.get(fontName.toLowerCase());
996 }
997
998 /**
999 * Initialise the fontsList if it has not been done already
1000 */
1001 private static void initFonts() {
1002 if (_Fonts.size() == 0) {
1003 String[] availableFonts = GraphicsEnvironment
1004 .getLocalGraphicsEnvironment()
1005 .getAvailableFontFamilyNames();
1006 for (String s : availableFonts) {
1007 _Fonts.put(s.toLowerCase(), s);
1008 }
1009 }
1010 }
1011
1012 public static HashMap<String, String> getFonts() {
1013 initFonts();
1014 return _Fonts;
1015 }
1016
1017 public static Object PerformActionCatchErrors(Frame current, Item launcher,
1018 String command) {
1019 try {
1020 return PerformAction(current, launcher, command);
1021 } catch (RuntimeException e) {
1022 e.printStackTrace();
1023 MessageBay.errorMessage("Action failed: " + e.getMessage());
1024 } catch (Exception e) {
1025 e.printStackTrace();
1026 MessageBay.errorMessage("Action failed: "
1027 + e.getClass().getSimpleName());
1028 }
1029 return null;
1030 }
1031
1032 /**
1033 * Gets the full class path for a widget with a given case insensitive name.
1034 *
1035 * @param widgetName
1036 * @return
1037 */
1038 public static String getClassName(String widgetName) {
1039 return _IWs.get(widgetName.toLowerCase());
1040 }
1041
1042 static List<String> getActions() {
1043 List<String> actionNames = new LinkedList<String>();
1044 for (Method m : _Actions.values()) {
1045 StringBuilder sb = new StringBuilder();
1046 sb.append(m.getName());
1047 for (Class<?> c : m.getParameterTypes()) {
1048 sb.append(" ").append(c.getSimpleName());
1049 }
1050 actionNames.add(sb.toString());
1051 }
1052
1053 return actionNames;
1054 }
1055
1056 static List<String> getAgents() {
1057 List<String> agentNames = new LinkedList<String>();
1058
1059 for (String s : _JAGs.values()) {
1060 agentNames.add(s.substring(s.lastIndexOf('.') + 1));
1061 }
1062
1063 return agentNames;
1064 }
1065}
Note: See TracBrowser for help on using the repository browser.