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

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

Renamed Frame.getItems() to Frame.getSortedItems() to better represent its functionality.

-> org.apollo.ApolloGestureActions
-> org.apollo.ApolloSystem
-> org.expeditee.actions.Actions
-> org.expeditee.actions.Debug
-> org.expeditee.actions.ExploratorySearchActions
-> org.expeditee.actions.JfxBrowserActions
-> org.expeditee.actions.Misc
-> org.expeditee.actions.Navigation
-> org.expeditee.actions.ScriptBase
-> org.expeditee.actions.Simple
-> org.expeditee.agents.ComputeTree
-> org.expeditee.agents.CopyTree
-> org.expeditee.agents.DisplayComet
-> org.expeditee.agents.DisplayTree
-> org.expeditee.agents.DisplayTreeLeaves
-> org.expeditee.agents.GraphFramesetLinks
-> org.expeditee.agents.TreeProcessor
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gui.DisplayController
-> org.expeditee.gui.FrameCreator
-> org.expeditee.gui.FrameIO
-> org.expeditee.io.DefaultTreeWriter
-> org.expeditee.io.JavaWriter
-> org.expeditee.io.PDF2Writer
-> org.expeditee.io.TXTWriter
-> org.expeditee.io.WebParser
-> org.expeditee.io.flowlayout.XGroupItem
-> org.expeditee.items.Dot
-> org.expeditee.items.Item
-> org.expeditee.items.ItemUtils
-> org.expeditee.network.FrameShare
-> org.expeditee.stats.TreeStats


Created ItemsList class to wrap ArrayList<Item>. Frames now use this new class to store its body list (used for display) as well as its primaryBody and surrogateBody.

-> org.expeditee.agents.Format
-> org.expeditee.agents.HFormat
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gui.Frame
-> org.expeditee.gui.FrameUtils


Refactorted Frame.setResort(bool) to Frame.invalidateSorted() to better function how it is intended to with a more accurate name.

-> org.expeditee.agents.Sort


When writing out .exp files and getting attributes to respond to LEFT + RIGHT click, boolean items are by default true. This has always been the case. An ammendment to this is that defaults can now be established.
Also added 'EnterClick' functionality. If cursored over a item with this property and you press enter, it acts as if you have clicked on it instead.

-> org.expeditee.assets.resources-public.framesets.authentication.1.exp to 6.exp
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gio.input.KBMInputEvent
-> org.expeditee.gio.javafx.JavaFXConversions
-> org.expeditee.gio.swing.SwingConversions
-> org.expeditee.gui.AttributeUtils
-> org.expeditee.io.Conversion
-> org.expeditee.io.DefaultFrameWriter
-> org.expeditee.items.Item


Fixed a bug caused by calling Math.abs on Integer.MIN_VALUE returning unexpected result. Due to zero being a thing, you cannot represent Math.abs(Integer.MIN_VALUE) in a Integer object. The solution is to use Integer.MIN_VALUE + 1 instead of Integer.MIN_VALUE.

-> org.expeditee.core.bounds.CombinationBounds
-> org.expeditee.io.flowlayout.DimensionExtent


Recoded the contains function in EllipticalBounds so that intersection tests containing circles work correctly.

-> org.expeditee.core.bounds.EllipticalBounds


Added toString() to PolygonBounds to allow for useful printing during debugging.

-> org.expeditee.core.bounds.PolygonBounds

Implemented Surrogate Mode!

-> org.expeditee.encryption.io.EncryptedExpReader
-> org.expeditee.encryption.io.EncryptedExpWriter
-> org.expeditee.encryption.items.surrogates.EncryptionDetail
-> org.expeditee.encryption.items.surrogates.Label
-> org.expeditee.gui.FrameUtils
-> org.expeditee.gui.ItemsList
-> org.expeditee.items.Item
-> org.expeditee.items.Text


???? Use Integer.MAX_VALUE cast to a float instead of Float.MAX_VALUE. This fixed some bug which I cannot remember.

-> org.expeditee.gio.TextLayoutManager
-> org.expeditee.gio.swing.SwingTextLayoutManager


Improved solution for dealing with the F10 key taking focus away from Expeditee due to it being a assessibility key.

-> org.expeditee.gio.swing.SwingInputManager


Renamed variable visibleItems in FrameGraphics.paintFrame to itemsToPaintCanditates to better represent functional intent.

-> org.expeditee.gui.FrameGraphics


Improved checking for if personal resources exist before recreating them

-> org.expeditee.gui.FrameIO


Repeated messages to message bay now have a visual feedback instead of just a beep. This visual feedback is in the form of a count of the amount of times it has repeated.

-> org.expeditee.gui.MessageBay


Updated comment on the Vector class to explain what vectors are.

-> org.expeditee.gui.Vector


Added constants to represent all of the property keys in DefaultFrameReader and DefaultFrameWriter.

-> org.expeditee.io.DefaultFrameReader
-> org.expeditee.io.DefaultFrameWriter


Updated the KeyList setting to be more heirarcial with how users will store their Secrets.

-> org.expeditee.settings.identity.secrets.KeyList

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.getSortedItems(), "@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.getSortedItems(), "@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.