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

Last change on this file since 492 was 492, checked in by davidb, 11 years ago

Replaced getClasses() with getClassesNew(), an alternative that is compatible with use in an Applet. After a bit more testing, this should be renamed and become the main one used in Expeditee

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