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
RevLine 
[919]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
[4]19package org.expeditee.actions;
20
21import java.lang.reflect.Constructor;
22import java.lang.reflect.Method;
23import java.lang.reflect.Modifier;
[313]24import java.rmi.UnexpectedException;
[987]25import java.util.ArrayList;
[80]26import java.util.Collection;
[975]27import java.util.Comparator;
[4]28import java.util.HashMap;
29import java.util.LinkedList;
30import java.util.List;
[296]31import java.util.Set;
[4]32
33import org.expeditee.agents.Agent;
[1102]34import org.expeditee.gio.EcosystemManager;
35import org.expeditee.gui.DisplayController;
[4]36import org.expeditee.gui.Frame;
37import org.expeditee.gui.FrameIO;
38import org.expeditee.gui.FrameUtils;
[156]39import org.expeditee.gui.FreeItems;
[121]40import org.expeditee.gui.MessageBay;
[4]41import org.expeditee.io.Conversion;
42import org.expeditee.items.Item;
43import org.expeditee.items.ItemUtils;
[990]44import org.expeditee.items.Text;
[570]45import org.expeditee.reflection.PackageLoader;
[286]46import org.expeditee.simple.SString;
[419]47import org.expeditee.stats.Logger;
[4]48
49/**
[80]50 * The Action class is used to launch Actions and Agents.
[4]51 *
[975]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)
[80]56 *
[975]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>
[4]65 */
66public class Actions {
67
[570]68 private static final String INVALID_PARAMETERS_ERROR = "Invalid parameters for agent: "; //$NON-NLS-1$
[86]69
[570]70 // the currently running agent (if there is one)
71 private static Agent _Agent = null;
[4]72
[570]73 // maps lower case method names to the method
74 private static HashMap<String, Method> _Actions = new HashMap<String, Method>();
[4]75
[570]76 // map lower case fonts to capitalized fonts
77 private static HashMap<String, String> _Fonts = new HashMap<String, String>();
[108]78
[570]79 // maps lower case JAG class names to capitalized JAG full class names
80 private static HashMap<String, String> _JAGs = new HashMap<String, String>();
[4]81
[570]82 // maps lower case IW class names to capitalized IW names
83 private static HashMap<String, String> _IWs = new HashMap<String, String>();
[286]84
[570]85 public static final String ROOT_PACKAGE = "org.expeditee.";
[72]86
[570]87 // Package and class file locations
[725]88 public static final String ACTIONS_PACKAGE = ROOT_PACKAGE + "actions.";
[4]89
[725]90 public static final String AGENTS_PACKAGE = ROOT_PACKAGE + "agents.";
[72]91
[725]92 public static final String WIDGET_PACKAGE = ROOT_PACKAGE + "items.widgets.";
[286]93
[975]94 public static final String CHARTS_PACKAGE = ROOT_PACKAGE
95 + "items.widgets.charts.";
[286]96
[975]97 public static final String NAVIGATIONS_CLASS = ROOT_PACKAGE
98 + "actions.NavigationActions";
[72]99
[570]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 // }
[492]208
[570]209 /**
[975]210 * Clears out the Action and JAG Hashtables and refills them. Normally this
211 * is only called once when the system starts.
[570]212 *
[975]213 * @return a warning message if there were any problems loading agents or
214 * actions.
[570]215 */
[1102]216 public static List<String> Init() {
[492]217
[1102]218 List<String> warnings = new LinkedList<String>();
[570]219 List<Class<?>> classes;
[492]220
[570]221 try {
222 classes = PackageLoader.getClassesNew(AGENTS_PACKAGE);
[492]223
[570]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 }
[492]229
[570]230 classes = PackageLoader.getClassesNew(WIDGET_PACKAGE);
[492]231
[570]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 }
[492]237
[570]238 classes = PackageLoader.getClassesNew(CHARTS_PACKAGE);
[492]239
[570]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 }
[492]253
[570]254 try {
255 classes = PackageLoader.getClassesNew(ACTIONS_PACKAGE);
[492]256
[570]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 }
[492]270 }
[570]271 } catch (Exception e) {
272 warnings.add(e.getMessage());
[492]273 }
[570]274 return warnings;
[492]275 }
276
[570]277 /**
[975]278 * Temporary, if a plugin system is devised then this would porbably become
279 * redundant. For now this allows external agents to be included.
[570]280 *
281 * @param fullClassNames
[975]282 * A set of full class names, that is, the class package and
283 * name. For example" "org.myplugin.agents.SerializedSearch"
[570]284 *
[975]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.
[570]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");
[492]296
[570]297 List<String> omittedAgents = new LinkedList<String>();
[492]298
[570]299 for (String fullName : fullClassNames) {
[492]300
[570]301 if (fullName == null || fullName.length() == 0)
302 continue;
[492]303
[570]304 boolean didAdd = false;
[492]305
[570]306 try {
307 // Does the class even exist?
308 Class<?> c = Class.forName(fullName);
[492]309
[570]310 String name = c.getSimpleName().toLowerCase();
[492]311
[570]312 if (!_JAGs.containsKey(name)) {
[4]313
[570]314 _JAGs.put(name, fullName);
315 didAdd = true;
[492]316
[570]317 }
[492]318
[570]319 } catch (ClassNotFoundException e) { // Nope it does not exist
320 e.printStackTrace();
[4]321 }
[492]322
[570]323 if (!didAdd)
324 omittedAgents.add(fullName);
[492]325
[570]326 }
[492]327
[570]328 return omittedAgents;
[4]329 }
330
[570]331 /**
[975]332 * Loads all the Methods that meet the requirements checked by MethodCheck
333 * into the hashtable.
[570]334 *
335 * @param c
336 * The Class to load the Methods from.
337 */
338 public static void LoadMethods(Class<?> c) {
339 assert (c != null);
[108]340
[570]341 // list of methods to test
342 Method[] toLoad = c.getMethods();
[86]343
[570]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++;
[4]354
[570]355 _Actions.put(lowercaseName + i, m);
356 }
[286]357
[570]358 }
359 }
360 }
[286]361
[570]362 /**
[975]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.
[570]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();
[286]373
[570]374 // check the method is declared (not inherited)
375 if ((mods & Method.DECLARED) != Method.DECLARED)
376 return false;
[286]377
[570]378 // check the method is public
379 if ((mods & Modifier.PUBLIC) != Modifier.PUBLIC)
380 return false;
[286]381
[570]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;
[480]388 }
389
[570]390 /**
[975]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
[1195]393 * does not have to be the items parent frame.
[570]394 *
[975]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 *
[570]399 * @param source
[975]400 * The frame that the action should apply to
[570]401 * @param launcher
[975]402 * The item that has the action assigned to it
[570]403 * @param command
404 * The action to perform
[975]405 * @return
406 * @throws Exception
[570]407 */
[1197]408 static Object LegacyPerformAction(final Frame source, final Item launcher,
[975]409 final String command) throws Exception {
[1195]410 MessageBay.displayMessage("Using Legacy code to execute action: " + command);
[975]411 final String actionName = getName(command);
412 final String parameters = command.substring(actionName.length()).trim();
[480]413
[975]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);
[4]420 }
421
[975]422 // Find all canditates. Sort by quantity of arguments.
423 final String lowercaseName = actionName.toLowerCase();
424 final Method firstCanditate = _Actions.get(lowercaseName);
[1195]425 // if this is not the name of a method, it may be the name of an agent
[975]426 if (firstCanditate == null) {
[990]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);
[570]431 return null;
432 }
[975]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)) {
[1102]436 FrameIO.SaveFrame(DisplayController.getCurrentFrame());
[570]437 }
[975]438 final List<Method> canditates = new LinkedList<Method>();
439 canditates.add(firstCanditate);
[570]440 int i = 0;
[975]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) {
[976]446 final int parameterCountDifference = m2.getParameterCount() - m1.getParameterCount();
447 if(parameterCountDifference == 0) {
448 final Class<?>[] m1ParamTypes = m1.getParameterTypes();
449 final Class<?>[] m2ParamTypes = m2.getParameterTypes();
[987]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++) {
[976]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;
[975]472 }
473 });
[305]474
[975]475 // Find best candidate. Start searching with the candidate with most
476 // arguments.
477 for (final Method canditate : canditates) {
[1195]478 final Object[] paramObjects = LegacyCreateObjects(canditate, source,
[975]479 launcher, parameters);
[570]480 try {
[975]481 if (paramObjects != null)
482 return canditate.invoke(null, paramObjects);
483 } catch (final Exception e) {
[570]484 Logger.Log(e);
485 e.printStackTrace();
486 }
[480]487 }
[975]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());
[296]495 }
[1195]496
[305]497
[1197]498 static Object PerformAction(final Frame source, final Item launcher, final String command, final Item actionItem) throws Exception {
[1195]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
[975]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
[570]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 */
[975]681 private static void LaunchAgent(String name, String parameters,
682 Frame source, Item clicked) throws Exception {
[570]683 // Use the correct case version for printing error messages
684 String nameWithCorrectCase = name;
685 name = name.toLowerCase();
[282]686
[570]687 String fullClassName = AGENTS_PACKAGE + name;
[4]688
[570]689 try {
690 // check for stored capitalisation
691 if (_JAGs.containsKey(name)) {
692 fullClassName = _JAGs.get(name);
693 } else if (name.endsWith("tree")) {
[975]694 parameters = name.substring(0, name.length() - "tree".length())
695 + " " + parameters;
[570]696 fullClassName = AGENTS_PACKAGE + "writetree";
[4]697
[570]698 } else if (name.endsWith("frame")) {
[975]699 parameters = name
700 .substring(0, name.length() - "frame".length())
701 + " "
702 + parameters;
[570]703 fullClassName = AGENTS_PACKAGE + "writeframe";
704 }
[4]705
[570]706 // load the JAG class
707 Class<?> agentClass = Class.forName(fullClassName);
[480]708
[570]709 // get the constructor for the JAG class
710 Constructor<?> con = null;
711 Constructor<?>[] constructors = agentClass.getConstructors();
712 Object[] params = null;
[4]713
[570]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 /**
[975]723 * Any extra parameters will be treated as the rest of the
724 * string if the last param is a string
[570]725 */
726 if (paramCount > paramStrings.length) {
727 continue;
728 }
[4]729
[570]730 /**
[975]731 * If there are extra parameters the last param must be a
732 * String
[570]733 */
734 int lastParam = paramTypes.length - 1;
[4]735
[975]736 if (paramCount < paramStrings.length
737 && !paramTypes[lastParam].equals(String.class)) {
[570]738 continue;
739 }
[4]740
[570]741 try {
742 for (int i = 0; i < paramCount; i++) {
743 SString nextParam = new SString(paramStrings[i]);
744 params[i] = null;
[975]745 if (paramTypes[i].equals(int.class)
746 || paramTypes[i].equals(Integer.class)) {
[570]747 params[i] = nextParam.integerValue().intValue();
[975]748 } else if (paramTypes[i].equals(long.class)
749 || paramTypes[i].equals(Long.class)) {
[570]750 params[i] = nextParam.integerValue();
[975]751 } else if (paramTypes[i].equals(double.class)
752 || paramTypes[i].equals(Double.class)) {
[570]753 params[i] = nextParam.doubleValue();
[975]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)) {
[570]760 params[i] = nextParam.booleanValue();
761 } else if (paramTypes[i].equals(String.class)) {
762 params[i] = nextParam.stringValue();
763 } else {
[975]764 throw new UnexpectedException(
765 "Unexpected type "
766 + paramTypes[i].getClass()
767 .toString());
[570]768 }
769 }
770 } catch (Exception e) {
771 continue;
772 }
[4]773
[570]774 if (paramCount < paramStrings.length) {
[480]775
[570]776 /**
[975]777 * Append extra params on the end of the last string
778 * param
[570]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 }
[480]786
[570]787 con = c;
788 break;
789 } else if (c.getParameterTypes().length == 0 && con == null) {
790 con = c;
791 params = null;
792 }
793 }
[4]794
[570]795 // if there is no constructor, return
796 if (con == null) {
[975]797 throw new RuntimeException(INVALID_PARAMETERS_ERROR
798 + nameWithCorrectCase);
[570]799 }
[72]800
[570]801 // create the JAG
802 Agent toLaunch = (Agent) con.newInstance(params);
[4]803
[570]804 LaunchAgent(toLaunch, source, clicked);
[4]805
[570]806 } catch (ClassNotFoundException cnf) {
807 _Agent = null;
[975]808 throw new RuntimeException("'" + nameWithCorrectCase
809 + "' is not an action or agent.");
[570]810 }
[480]811 }
[4]812
[975]813 public static void LaunchAgent(String name, String parameters, Frame source)
814 throws Exception {
[570]815 LaunchAgent(name, parameters, source, null);
[480]816 }
[4]817
[570]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) {
[4]834
[570]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");
[4]840
[570]841 String nameWithCorrectCase = agent.getClass().getSimpleName();
[4]842
[570]843 try {
[4]844
[570]845 // create the JAG
846 _Agent = agent;
[305]847
[830]848 Thread.UncaughtExceptionHandler h = new Thread.UncaughtExceptionHandler() {
[975]849 public void uncaughtException(Thread th, Throwable ex) {
[830]850
[975]851 MessageBay.errorMessage("Error occurred in Action: "
852 + th.getName());
853 ex.printStackTrace();
854
855 stopAgent();
856 _Agent = null;
857 }
[830]858 };
[975]859
860 Thread t = new Thread(_Agent, nameWithCorrectCase);
861
[570]862 t.setPriority(Thread.MIN_PRIORITY);
[830]863 t.setUncaughtExceptionHandler(h);
[975]864
[570]865 if (FreeItems.textOnlyAttachedToCursor()) {
866 itemParam = FreeItems.getItemAttachedToCursor();
867 }
[305]868
[570]869 // check for errors during initialisation
870 if (!_Agent.initialise(source, itemParam)) {
871 _Agent = null;
[975]872 throw new RuntimeException("Error initialising agent: "
873 + nameWithCorrectCase);
[570]874 }
[4]875
[570]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
[975]879 if (!nameWithCorrectCase.equalsIgnoreCase("format")
880 && !nameWithCorrectCase.equalsIgnoreCase("sort")) {
[570]881 FrameUtils.LeavingFrame(source);
[4]882 }
883
[570]884 if (_Agent.hasResultString()) {
885 // Just run the agent on this thread... dont run it in the
886 // background
887 t.run();
[830]888 if (_Agent != null) {
[975]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
[570]897 }
898 } else {
899 t.start();
[830]900 if (_Agent != null && _Agent.hasResultFrame()) {
[570]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 }
[480]906 }
[570]907 } catch (Exception e) {
908 _Agent = null;
909 e.printStackTrace();
[975]910 throw new RuntimeException("Error creating Agent: '"
911 + nameWithCorrectCase + "'");
[305]912 }
[1102]913 DisplayController.requestRefresh(false);
[570]914 }
[305]915
[570]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();
[480]924
[570]925 return false;
926 }
[480]927
[570]928 /**
[975]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.
[570]932 */
933 public static void stopAgent() {
934 if (_Agent != null && _Agent.isRunning()) {
935 MessageBay.errorMessage("Stopping Agent...");
936 _Agent.stop();
937 }
938 }
[480]939
[570]940 public static void interruptAgent() {
941 if (_Agent != null) {
942 _Agent.interrupt();
943 }
[479]944 }
945
[570]946 /**
947 * Converts the given String of values into an array of Objects
948 *
949 * @param launcher
[975]950 * The Item used to launch the action, it may be required as a
951 * parameter
[570]952 * @param values
953 * A list of space separated String values to convert to objects
954 * @return The created array of Objects
955 */
[1195]956 public static Object[] LegacyCreateObjects(Method method, Frame source,
[975]957 Item launcher, String values) {
[570]958 // The parameter types that should be created from the given String
959 Class<?>[] paramTypes = method.getParameterTypes();
[305]960
[570]961 int paramCount = paramTypes.length;
962 // if the method has no parameters
963 if (paramCount == 0)
964 return new Object[0];
[305]965
[570]966 Object[] objects = new Object[paramCount];
967 int ind = 0;
[305]968
[570]969 /*
[975]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
[570]972 */
973 if (paramTypes[ind] == Frame.class) {
974 objects[ind] = source;
975 ind++;
976 }
[305]977
[570]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 }
[305]986
[570]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 }
[1192]1000
[570]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 }
[4]1015
[570]1016 return objects;
1017 }
[1195]1018
[305]1019
[1195]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.
[1286]1044 //if (paramCount > ind && actionItem.getClass().isAssignableFrom(paramTypes[ind])) {
1045 if (paramCount > ind && paramTypes[ind].isAssignableFrom(actionItem.getClass())) {
1046 objects[ind++] = actionItem;
1047 }
[1195]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
[570]1078 /**
[975]1079 * Returns a string containing the remaining params after ignoring the first
1080 * one.
[570]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;
[305]1089
[570]1090 // remove leading and trailing spaces
1091 params = params.trim();
[305]1092
[570]1093 // if there are no more parameters, we are done
1094 if (params.indexOf(" ") < 0) {
1095 return "";
1096 }
[480]1097
[570]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 }
[4]1106 }
1107
[570]1108 return params.substring(params.indexOf(" ")).trim();
[4]1109 }
1110
[570]1111 /**
[975]1112 * Returns the first value in the space separated String of parameters
1113 * passed in. Strings are enclosed in double quotes.
[570]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;
[4]1122
[570]1123 // remove leading and trailing spaces
1124 String param = params.trim();
[4]1125
[570]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 }
[4]1132
[570]1133 // if there are no more parameters, we are done
1134 if (param.indexOf(" ") < 0) {
1135 return param;
1136 }
[4]1137
[570]1138 return param.substring(0, param.indexOf(" "));
[480]1139 }
[4]1140
[570]1141 /**
[975]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.
[570]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) {
[975]1150 if (command.startsWith("@"))
1151 command = command.substring(1);
[990]1152 command = command.trim();
[570]1153 if (command.indexOf(" ") < 0)
1154 return command;
[4]1155
[570]1156 return command.substring(0, command.indexOf(" "));
[4]1157 }
1158
[570]1159 /**
[975]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.
[570]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());
[480]1172 }
[4]1173
[570]1174 /**
1175 * Initialise the fontsList if it has not been done already
1176 */
1177 private static void initFonts() {
1178 if (_Fonts.size() == 0) {
[1102]1179 String[] availableFonts = EcosystemManager.getFontManager().getRegisteredFontFamilies();
[570]1180 for (String s : availableFonts) {
1181 _Fonts.put(s.toLowerCase(), s);
1182 }
1183 }
[4]1184 }
1185
[570]1186 public static HashMap<String, String> getFonts() {
1187 initFonts();
1188 return _Fonts;
[480]1189 }
[4]1190
[1195]1191 public static Object LegacyPerformActionCatchErrors(Frame current, Item launcher,
[975]1192 String command) {
[570]1193 try {
[1195]1194 return LegacyPerformAction(current, launcher, command);
[570]1195 } catch (RuntimeException e) {
1196 e.printStackTrace();
1197 MessageBay.errorMessage("Action failed: " + e.getMessage());
1198 } catch (Exception e) {
1199 e.printStackTrace();
[975]1200 MessageBay.errorMessage("Action failed: "
1201 + e.getClass().getSimpleName());
[570]1202 }
1203 return null;
[4]1204 }
[1195]1205
[4]1206
[1195]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
[570]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());
[115]1229 }
[181]1230
[570]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 }
[286]1241
[570]1242 return actionNames;
[286]1243 }
[306]1244
[570]1245 static List<String> getAgents() {
1246 List<String> agentNames = new LinkedList<String>();
[306]1247
[570]1248 for (String s : _JAGs.values()) {
1249 agentNames.add(s.substring(s.lastIndexOf('.') + 1));
1250 }
[306]1251
[570]1252 return agentNames;
[306]1253 }
[4]1254}
Note: See TracBrowser for help on using the repository browser.