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

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

When deciding what action to run Actions.java now considers actions with the same parameter list by emphasising some parameter types over others. Integer -> Double -> Float -> String -> All others

Now doesn't try to map in a item if the item for some reason has a null bounding_rect. A error message is displayed if it finds a null.

File size: 32.8 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.Arrays;
27import java.util.Collection;
28import java.util.Comparator;
29import java.util.HashMap;
30import java.util.LinkedList;
31import java.util.List;
32import java.util.Set;
33
34import org.expeditee.agents.Agent;
35import org.expeditee.gui.DisplayIO;
36import org.expeditee.gui.Frame;
37import org.expeditee.gui.FrameGraphics;
38import org.expeditee.gui.FrameIO;
39import org.expeditee.gui.FrameUtils;
40import org.expeditee.gui.FreeItems;
41import org.expeditee.gui.MessageBay;
42import org.expeditee.io.Conversion;
43import org.expeditee.items.Item;
44import org.expeditee.items.ItemUtils;
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 final String actionName = getName(command);
411 final String parameters = command.substring(actionName.length()).trim();
412
413 // Check for protection on frame.
414 if (ItemUtils.ContainsTag(source.getItems(), "@No" + actionName)) {
415 final String errorMsg = "Frame is protected by @No" + actionName
416 + " tag.";
417 MessageBay.errorMessage(errorMsg);
418 throw new RuntimeException(errorMsg);
419 }
420
421 // Find all canditates. Sort by quantity of arguments.
422 final String lowercaseName = actionName.toLowerCase();
423 final Method firstCanditate = _Actions.get(lowercaseName);
424 // if this is not the name of a method, it may be the name of an agent
425 if (firstCanditate == null) {
426 LaunchAgent(actionName, command, source, launcher);
427 return null;
428 }
429 // need to save the frame if we are navigating away from it so we dont
430 // loose changes
431 if (firstCanditate.getDeclaringClass().getName().equals(NAVIGATIONS_CLASS)) {
432 FrameIO.SaveFrame(DisplayIO.getCurrentFrame());
433 }
434 final List<Method> canditates = new LinkedList<Method>();
435 canditates.add(firstCanditate);
436 int i = 0;
437 while (_Actions.containsKey(lowercaseName + i))
438 canditates.add(_Actions.get(lowercaseName + i++));
439 canditates.sort(new Comparator<Method>() {
440 @Override
441 public int compare(final Method m1, final Method m2) {
442 final int parameterCountDifference = m2.getParameterCount() - m1.getParameterCount();
443 if(parameterCountDifference == 0) {
444 final Class<?>[] m1ParamTypes = m1.getParameterTypes();
445 final Class<?>[] m2ParamTypes = m2.getParameterTypes();
446 final List<Class<?>> typeOrder = Arrays.asList(Integer.TYPE, Double.TYPE, Float.TYPE, String.class);
447 for(int i = 0,o = 0; i < m1ParamTypes.length && o < m2ParamTypes.length; i++,o++) {
448 final Class<?> m1ParamType = m1ParamTypes[i];
449 final Class<?> m2ParamType = m2ParamTypes[o];
450 final int m1ParamTypeIndex = typeOrder.indexOf(m1ParamType) != -1 ?
451 typeOrder.indexOf(m1ParamType) : typeOrder.size();
452 final int m2ParamTypeIndex = typeOrder.indexOf(m2ParamType) != -1 ?
453 typeOrder.indexOf(m2ParamType) : typeOrder.size();
454 final int paramMagnitude = m2ParamTypeIndex - m1ParamTypeIndex;
455 if(paramMagnitude != 0) return paramMagnitude;
456 }
457 }
458 return parameterCountDifference;
459 }
460 });
461
462 // Find best candidate. Start searching with the candidate with most
463 // arguments.
464 for (final Method canditate : canditates) {
465 final Object[] paramObjects = CreateObjects(canditate, source,
466 launcher, parameters);
467 try {
468 if (paramObjects != null)
469 return canditate.invoke(null, paramObjects);
470 } catch (final Exception e) {
471 Logger.Log(e);
472 e.printStackTrace();
473 }
474 }
475 assert (canditates.size() > 0);
476 final String nl = System.getProperty("line.separator");
477 final StringBuilder errorBuilder = new StringBuilder(
478 "Incorrect parameters for " + actionName + nl + "Canditates: ");
479 for (final Method canditate : canditates)
480 errorBuilder.append(canditate + nl);
481 throw new RuntimeException(errorBuilder.toString());
482 }
483
484 // /**
485 // * Performs the given action command. The source Frame and Item are given
486 // because they are required by some actions.
487 // * Note that the source frame does not have to be the Item's parent Frame.
488 // *
489 // * @param source
490 // * The Frame that the action should apply to
491 // * @param launcher
492 // * The Item that has the action assigned to it
493 // * @param command
494 // * The action to perform
495 // */
496 // public static Object PerformAction(Frame source, Item launcher, String
497 // command) throws Exception {
498 // // if (!command.equalsIgnoreCase("Restore"))
499 // // FrameIO.SaveFrame(source, false);
500 // // TODO make restore UNDO the changes made by the last action
501 //
502 // // separate method name and parameter names
503 // String mname = getName(command);
504 // command = command.substring(mname.length()).trim();
505 // // If no params are provided get them from a text item on the cursor
506 // if (command.length() == 0 && launcher instanceof Text &&
507 // launcher.isFloating()) {
508 // command = launcher.getText();
509 // }
510 //
511 // // Strip off the @ from annotation items
512 // if (mname.startsWith("@"))
513 // mname = mname.substring(1);
514 //
515 // mname = mname.trim();
516 // String lowercaseName = mname.toLowerCase();
517 // // check for protection on frame
518 // if (ItemUtils.ContainsTag(source.getItems(), "@No" + mname)) {
519 // throw new RuntimeException("Frame is protected by @No" + mname +
520 // " tag.");
521 // }
522 //
523 // // retrieve methods that match the name
524 // Method toRun = _Actions.get(lowercaseName);
525 //
526 // // if this is not the name of a method, it may be the name of an agent
527 // if (toRun == null) {
528 // LaunchAgent(mname, command, source, launcher);
529 // return null;
530 // }
531 //
532 // // Need to save the frame if we are navigating away from it so we dont
533 // // loose changes
534 // if (toRun.getDeclaringClass().getName().equals(NAVIGATIONS_CLASS)) {
535 // FrameIO.SaveFrame(DisplayIO.getCurrentFrame());
536 // }
537 //
538 // // if there are duplicate methods with the same name
539 // List<Method> possibles = new LinkedList<Method>();
540 // possibles.add(toRun);
541 // int i = 0;
542 // while (_Actions.containsKey(lowercaseName + i)) {
543 // possibles.add(_Actions.get(lowercaseName + i));
544 // i++;
545 // }
546 //
547 // for (Method possible : possibles) {
548 // // try first with the launching item as a parameter
549 //
550 // // run method
551 // try {
552 // // convert parameters to objects and get the method to invoke
553 // Object[] parameters = CreateObjects(possible, source, launcher, command);
554 // // Check that there are the same amount of params
555 // if (parameters == null) {
556 // continue;
557 // }
558 //
559 // return possible.invoke(null, parameters);
560 // } catch (Exception e) {
561 // Logger.Log(e);
562 // e.printStackTrace();
563 // }
564 // }
565 // // If the actions was not found... then it is run as an agent
566 // assert (possibles.size() > 0);
567 // throw new RuntimeException("Incorrect parameters for " + mname);
568 // }
569
570 /**
571 * Launches an agent with the given name, and passes in the given parameters
572 *
573 * @param name
574 * The name of the JAG to load
575 * @param parameters
576 * The parameters to pass to the JAG
577 * @param source
578 * The starting Frame that the JAG is being launched on
579 */
580 private static void LaunchAgent(String name, String parameters,
581 Frame source, Item clicked) throws Exception {
582 // Use the correct case version for printing error messages
583 String nameWithCorrectCase = name;
584 name = name.toLowerCase();
585
586 String fullClassName = AGENTS_PACKAGE + name;
587
588 try {
589 // check for stored capitalisation
590 if (_JAGs.containsKey(name)) {
591 fullClassName = _JAGs.get(name);
592 } else if (name.endsWith("tree")) {
593 parameters = name.substring(0, name.length() - "tree".length())
594 + " " + parameters;
595 fullClassName = AGENTS_PACKAGE + "writetree";
596
597 } else if (name.endsWith("frame")) {
598 parameters = name
599 .substring(0, name.length() - "frame".length())
600 + " "
601 + parameters;
602 fullClassName = AGENTS_PACKAGE + "writeframe";
603 }
604
605 // load the JAG class
606 Class<?> agentClass = Class.forName(fullClassName);
607
608 // get the constructor for the JAG class
609 Constructor<?> con = null;
610 Constructor<?>[] constructors = agentClass.getConstructors();
611 Object[] params = null;
612
613 parameters = parameters.trim();
614 // determine correct parameters for constructor
615 for (Constructor<?> c : constructors) {
616 Class<?>[] paramTypes = c.getParameterTypes();
617 int paramCount = paramTypes.length;
618 if (paramCount > 0 && parameters.length() > 0) {
619 params = new Object[paramCount];
620 String[] paramStrings = parameters.split("\\s+");
621 /**
622 * Any extra parameters will be treated as the rest of the
623 * string if the last param is a string
624 */
625 if (paramCount > paramStrings.length) {
626 continue;
627 }
628
629 /**
630 * If there are extra parameters the last param must be a
631 * String
632 */
633 int lastParam = paramTypes.length - 1;
634
635 if (paramCount < paramStrings.length
636 && !paramTypes[lastParam].equals(String.class)) {
637 continue;
638 }
639
640 try {
641 for (int i = 0; i < paramCount; i++) {
642 SString nextParam = new SString(paramStrings[i]);
643 params[i] = null;
644 if (paramTypes[i].equals(int.class)
645 || paramTypes[i].equals(Integer.class)) {
646 params[i] = nextParam.integerValue().intValue();
647 } else if (paramTypes[i].equals(long.class)
648 || paramTypes[i].equals(Long.class)) {
649 params[i] = nextParam.integerValue();
650 } else if (paramTypes[i].equals(double.class)
651 || paramTypes[i].equals(Double.class)) {
652 params[i] = nextParam.doubleValue();
653 } else if (paramTypes[i].equals(float.class)
654 || paramTypes[i].equals(Float.class)) {
655 params[i] = nextParam.doubleValue()
656 .floatValue();
657 } else if (paramTypes[i].equals(boolean.class)
658 || paramTypes[i].equals(Boolean.class)) {
659 params[i] = nextParam.booleanValue();
660 } else if (paramTypes[i].equals(String.class)) {
661 params[i] = nextParam.stringValue();
662 } else {
663 throw new UnexpectedException(
664 "Unexpected type "
665 + paramTypes[i].getClass()
666 .toString());
667 }
668 }
669 } catch (Exception e) {
670 continue;
671 }
672
673 if (paramCount < paramStrings.length) {
674
675 /**
676 * Append extra params on the end of the last string
677 * param
678 */
679 String s = params[lastParam].toString();
680 for (int i = paramCount; i < paramStrings.length; i++) {
681 s += ' ' + paramStrings[i];
682 }
683 params[lastParam] = s;
684 }
685
686 con = c;
687 break;
688 } else if (c.getParameterTypes().length == 0 && con == null) {
689 con = c;
690 params = null;
691 }
692 }
693
694 // if there is no constructor, return
695 if (con == null) {
696 throw new RuntimeException(INVALID_PARAMETERS_ERROR
697 + nameWithCorrectCase);
698 }
699
700 // create the JAG
701 Agent toLaunch = (Agent) con.newInstance(params);
702
703 LaunchAgent(toLaunch, source, clicked);
704
705 } catch (ClassNotFoundException cnf) {
706 _Agent = null;
707 throw new RuntimeException("'" + nameWithCorrectCase
708 + "' is not an action or agent.");
709 }
710 }
711
712 public static void LaunchAgent(String name, String parameters, Frame source)
713 throws Exception {
714 LaunchAgent(name, parameters, source, null);
715 }
716
717 /**
718 * Launches an agent from an already instantiated object.
719 *
720 * @param agent
721 * The agent to launch. Must not be null.
722 *
723 * @param source
724 * The calling frame that launched it. Must not be null.
725 *
726 * @param itemParam
727 * The item parameter for the agent.
728 *
729 * @throws NullPointerException
730 * if any of the arguments are null.
731 */
732 public static void LaunchAgent(Agent agent, Frame source, Item itemParam) {
733
734 if (agent == null)
735 throw new NullPointerException("agent");
736 if (source == null)
737 throw new NullPointerException("source");
738 // if (itemParam == null) throw new NullPointerException("itemParam");
739
740 String nameWithCorrectCase = agent.getClass().getSimpleName();
741
742 try {
743
744 // create the JAG
745 _Agent = agent;
746
747 Thread.UncaughtExceptionHandler h = new Thread.UncaughtExceptionHandler() {
748 public void uncaughtException(Thread th, Throwable ex) {
749
750 MessageBay.errorMessage("Error occurred in Action: "
751 + th.getName());
752 ex.printStackTrace();
753
754 stopAgent();
755 _Agent = null;
756 }
757 };
758
759 Thread t = new Thread(_Agent, nameWithCorrectCase);
760
761 t.setPriority(Thread.MIN_PRIORITY);
762 t.setUncaughtExceptionHandler(h);
763
764 if (FreeItems.textOnlyAttachedToCursor()) {
765 itemParam = FreeItems.getItemAttachedToCursor();
766 }
767
768 // check for errors during initialisation
769 if (!_Agent.initialise(source, itemParam)) {
770 _Agent = null;
771 throw new RuntimeException("Error initialising agent: "
772 + nameWithCorrectCase);
773 }
774
775 // save the current frame (if necesssary)
776 // TODO make this nicer... ie. make Format an action rather than an
777 // agent and save frames only before running agents
778 if (!nameWithCorrectCase.equalsIgnoreCase("format")
779 && !nameWithCorrectCase.equalsIgnoreCase("sort")) {
780 FrameUtils.LeavingFrame(source);
781 }
782
783 if (_Agent.hasResultString()) {
784 // Just run the agent on this thread... dont run it in the
785 // background
786 t.run();
787 if (_Agent != null) {
788 String result = _Agent.toString();
789 // Attach the result to the cursor
790 if (FreeItems.textOnlyAttachedToCursor()) {
791 Item resultItem = FreeItems.getItemAttachedToCursor();
792 resultItem.setText(result);
793 }
794 // if there is a completion frame, then display it to the
795 // user
796 }
797 } else {
798 t.start();
799 if (_Agent != null && _Agent.hasResultFrame()) {
800 // TODO We want to be able to navigate through the frames as
801 // the results are loading
802 Frame next = _Agent.getResultFrame();
803 FrameUtils.DisplayFrame(next, true, true);
804 }
805 }
806 } catch (Exception e) {
807 _Agent = null;
808 e.printStackTrace();
809 throw new RuntimeException("Error creating Agent: '"
810 + nameWithCorrectCase + "'");
811 }
812 FrameGraphics.refresh(false);
813 }
814
815 /**
816 * Used to determine if the previously launched agent is still executing.
817 *
818 * @return True if the last Agent is still executing, False otherwise.
819 */
820 public static boolean isAgentRunning() {
821 if (_Agent != null)
822 return _Agent.isRunning();
823
824 return false;
825 }
826
827 /**
828 * Stops the currently running Agent (If there is one) by calling
829 * Agent.stop(). Note: This may not stop the Agent immediately, but the
830 * Agent should terminate as soon as it is safe to do so.
831 */
832 public static void stopAgent() {
833 if (_Agent != null && _Agent.isRunning()) {
834 MessageBay.errorMessage("Stopping Agent...");
835 _Agent.stop();
836 }
837 }
838
839 public static void interruptAgent() {
840 if (_Agent != null) {
841 _Agent.interrupt();
842 }
843 }
844
845 /**
846 * Converts the given String of values into an array of Objects
847 *
848 * @param launcher
849 * The Item used to launch the action, it may be required as a
850 * parameter
851 * @param values
852 * A list of space separated String values to convert to objects
853 * @return The created array of Objects
854 */
855 public static Object[] CreateObjects(Method method, Frame source,
856 Item launcher, String values) {
857 // The parameter types that should be created from the given String
858 Class<?>[] paramTypes = method.getParameterTypes();
859
860 int paramCount = paramTypes.length;
861 // if the method has no parameters
862 if (paramCount == 0)
863 return new Object[0];
864
865 Object[] objects = new Object[paramCount];
866 int ind = 0;
867
868 /*
869 * if the first class in the list is a frame or item, it is the source
870 * or launcher length must be at least one if we are still running
871 */
872 if (paramTypes[ind] == Frame.class) {
873 objects[ind] = source;
874 ind++;
875 }
876
877 // Check if the second item is an item
878 if (paramCount > ind && Item.class.isAssignableFrom(paramTypes[ind])) {
879 objects[ind] = launcher;
880 ind++;
881 }// If there is stuff on the cursor use it for the rest of the params
882 else if (launcher != null && launcher.isFloating()) {
883 values = launcher.getText();
884 }
885
886 String param = values;
887 // convert the rest of the objects
888 for (; ind < objects.length; ind++) {
889 // check if its the last param and combine
890 if (values.length() > 0 && ind == objects.length - 1) {
891 param = values.trim();
892 // check if its a string
893 if (param.length() > 0 && param.charAt(0) == '"') {
894 int endOfString = param.indexOf('"', 1);
895 if (endOfString > 0) {
896 param = param.substring(1, endOfString);
897 }
898 }
899 } else {// strip off the next value
900 param = ParseValue(values);
901 values = RemainingParams(values);
902 }
903 // convert the value to an object
904 try {
905 Object o = Conversion.Convert(paramTypes[ind], param);
906 if (o == null)
907 return null;
908 objects[ind] = o;
909 } catch (Exception e) {
910 return null;
911 }
912 }
913
914 return objects;
915 }
916
917 /**
918 * Returns a string containing the remaining params after ignoring the first
919 * one.
920 *
921 * @param params
922 * a space sparated list of N parameters
923 * @return the remaining N - 1 parameters
924 */
925 public static String RemainingParams(String params) {
926 if (params.length() == 0)
927 return null;
928
929 // remove leading and trailing spaces
930 params = params.trim();
931
932 // if there are no more parameters, we are done
933 if (params.indexOf(" ") < 0) {
934 return "";
935 }
936
937 // Check if we have a string parameter
938 if (params.charAt(0) == '"') {
939 int endOfString = params.indexOf('"', 1);
940 if (endOfString > 0) {
941 if (endOfString > params.length())
942 return "";
943 return params.substring(endOfString + 1).trim();
944 }
945 }
946
947 return params.substring(params.indexOf(" ")).trim();
948 }
949
950 /**
951 * Returns the first value in the space separated String of parameters
952 * passed in. Strings are enclosed in double quotes.
953 *
954 * @param params
955 * The String of space separated values
956 * @return The first value in the String
957 */
958 public static String ParseValue(String params) {
959 if (params.length() == 0)
960 return null;
961
962 // remove leading and trailing spaces
963 String param = params.trim();
964
965 // Check if we have a string parameter
966 if (param.charAt(0) == '"') {
967 int endOfString = param.indexOf('"', 1);
968 if (endOfString > 0)
969 return param.substring(1, endOfString);
970 }
971
972 // if there are no more parameters, we are done
973 if (param.indexOf(" ") < 0) {
974 return param;
975 }
976
977 return param.substring(0, param.indexOf(" "));
978 }
979
980 /**
981 * Separates the name of the given command from any parameters and returns
982 * them Also deals with leading '@'s and leading and tailing whitespace.
983 *
984 * @param command
985 * The String to separate out the Action or Agent name from
986 * @return The name of the Action of Agent with parameters stripped off
987 */
988 private static String getName(String command) {
989 command = command.trim();
990 if (command.startsWith("@"))
991 command = command.substring(1);
992 if (command.indexOf(" ") < 0)
993 return command;
994
995 return command.substring(0, command.indexOf(" "));
996 }
997
998 /**
999 * Gets an uncapitalized font name and returns the capitalized font name.
1000 * The capitalized form can be used with the Font.decoded method to get a
1001 * corresponding Font object.
1002 *
1003 * @param fontName
1004 * a font name in mixed case
1005 * @return the correct capitalized form of the font name
1006 */
1007 public static String getCapitalizedFontName(String fontName) {
1008 // Initialize the fonts if they have not already been loaded
1009 initFonts();
1010 return _Fonts.get(fontName.toLowerCase());
1011 }
1012
1013 /**
1014 * Initialise the fontsList if it has not been done already
1015 */
1016 private static void initFonts() {
1017 if (_Fonts.size() == 0) {
1018 String[] availableFonts = GraphicsEnvironment
1019 .getLocalGraphicsEnvironment()
1020 .getAvailableFontFamilyNames();
1021 for (String s : availableFonts) {
1022 _Fonts.put(s.toLowerCase(), s);
1023 }
1024 }
1025 }
1026
1027 public static HashMap<String, String> getFonts() {
1028 initFonts();
1029 return _Fonts;
1030 }
1031
1032 public static Object PerformActionCatchErrors(Frame current, Item launcher,
1033 String command) {
1034 try {
1035 return PerformAction(current, launcher, command);
1036 } catch (RuntimeException e) {
1037 e.printStackTrace();
1038 MessageBay.errorMessage("Action failed: " + e.getMessage());
1039 } catch (Exception e) {
1040 e.printStackTrace();
1041 MessageBay.errorMessage("Action failed: "
1042 + e.getClass().getSimpleName());
1043 }
1044 return null;
1045 }
1046
1047 /**
1048 * Gets the full class path for a widget with a given case insensitive name.
1049 *
1050 * @param widgetName
1051 * @return
1052 */
1053 public static String getClassName(String widgetName) {
1054 return _IWs.get(widgetName.toLowerCase());
1055 }
1056
1057 static List<String> getActions() {
1058 List<String> actionNames = new LinkedList<String>();
1059 for (Method m : _Actions.values()) {
1060 StringBuilder sb = new StringBuilder();
1061 sb.append(m.getName());
1062 for (Class<?> c : m.getParameterTypes()) {
1063 sb.append(" ").append(c.getSimpleName());
1064 }
1065 actionNames.add(sb.toString());
1066 }
1067
1068 return actionNames;
1069 }
1070
1071 static List<String> getAgents() {
1072 List<String> agentNames = new LinkedList<String>();
1073
1074 for (String s : _JAGs.values()) {
1075 agentNames.add(s.substring(s.lastIndexOf('.') + 1));
1076 }
1077
1078 return agentNames;
1079 }
1080}
Note: See TracBrowser for help on using the repository browser.