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

Last change on this file since 1102 was 1102, checked in by davidb, 6 years ago

Reworking of the code-base to separate logic from graphics. This version of Expeditee now supports a JFX graphics as an alternative to SWING

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