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

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