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

Last change on this file since 1286 was 1286, checked in by bln4, 5 years ago

Fixed bug with running getItemsFromChildFrame, and supposidly other actions, were the actionItem was not being parsed to parameters correctly.

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