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

Last change on this file since 21 was 21, checked in by ra33, 16 years ago
File size: 17.7 KB
Line 
1package org.expeditee.actions;
2
3import java.io.File;
4import java.lang.reflect.Constructor;
5import java.lang.reflect.Method;
6import java.lang.reflect.Modifier;
7import java.net.URL;
8import java.util.ArrayList;
9import java.util.Enumeration;
10import java.util.HashMap;
11import java.util.LinkedList;
12import java.util.List;
13import java.util.jar.JarFile;
14import java.util.zip.ZipEntry;
15
16import org.expeditee.agents.Agent;
17import org.expeditee.gui.DisplayIO;
18import org.expeditee.gui.Frame;
19import org.expeditee.gui.FrameGraphics;
20import org.expeditee.gui.FrameIO;
21import org.expeditee.gui.FrameUtils;
22import org.expeditee.io.Conversion;
23import org.expeditee.io.Logger;
24import org.expeditee.items.Item;
25import org.expeditee.items.ItemUtils;
26
27/**
28 * The Action class is used to launch KMS Actions as well as launching JAGs.
29 *
30 * This class checks all class files in the same directory, and reads in and
31 * adds all the methods from them. The methods are stored in a Hashtable so that
32 * the lowercase method names can be mapped to the correctly capatilized method
33 * names (to provide case-insensitivity)
34 */
35public class Actions {
36
37 // the currently running agent (if there is one)
38 private static Agent _Agent = null;
39
40 // maps lower case method names to the method
41 private static HashMap<String, Method> _Actions = new HashMap<String, Method>();
42
43 // maps lower case JAG class names to capitalized JAG names
44 private static HashMap<String, String> _JAGs = new HashMap<String, String>();
45
46 public static final String ROOT_PACKAGE = "org.expeditee.";
47
48 // Package and class file locations
49 private static final String ACTIONS_PACKAGE = ROOT_PACKAGE + "actions.";
50
51 private static final String AGENTS_PACKAGE = ROOT_PACKAGE + "agents.";
52
53 private static final String NAVIGATIONS_CLASS = ROOT_PACKAGE + "actions.NavigationActions";
54
55 public static Class[] getClasses(String pckgname)
56 throws ClassNotFoundException {
57 ArrayList<Class> classes = new ArrayList<Class>();
58 // Get a File object for the package
59 File directory = null;
60 String path = pckgname.replace('.', '/');
61 try {
62 ClassLoader cld = Thread.currentThread().getContextClassLoader();
63 if (cld == null) {
64 throw new ClassNotFoundException("Can't get class loader.");
65 }
66 URL resource = cld.getResource(path);
67 if (resource == null) {
68 throw new ClassNotFoundException("No resource for " + path);
69 }
70 directory = new File(resource.getFile());
71 } catch (NullPointerException x) {
72 throw new ClassNotFoundException(pckgname + " (" + directory
73 + ") does not appear to be a valid package");
74 }
75
76 int splitPoint = directory.getPath().indexOf('!');
77 if (splitPoint > 0) {
78 try {
79 String jarName = directory.getPath().substring(
80 "file:".length(), splitPoint);
81 // Windows HACK
82 if (jarName.indexOf(":") >= 0)
83 jarName = jarName.substring(1);
84
85 if (jarName.indexOf("%20") > 0) {
86 jarName = jarName.replace("%20", " ");
87 }
88
89 JarFile jarFile = new JarFile(jarName);
90
91 Enumeration entries = jarFile.entries();
92 while (entries.hasMoreElements()) {
93 ZipEntry entry = (ZipEntry) entries.nextElement();
94 String className = entry.getName();
95 if (className.startsWith(path)) {
96 if (className.endsWith(".class")
97 && !className.contains("$")) {
98 classes.add(Class.forName(className.substring(0,
99 className.length() - 6).replace('/', '.')));
100 }
101 }
102 }
103 jarFile.close();
104
105 } catch (Exception e) {
106 e.printStackTrace();
107 }
108
109 } else {
110
111 if (directory.exists()) {
112 // Get the list of the files contained in the package
113 String[] files = directory.list();
114 for (int i = 0; i < files.length; i++) {
115 // we are only interested in .class files
116 if (files[i].endsWith(".class") && !files[i].contains("$")
117 && !files[i].equals("Actions.class")) {
118 // removes the .class extension
119 classes
120 .add(Class.forName(pckgname
121 + files[i].substring(0, files[i]
122 .length() - 6)));
123 }
124 }
125 } else {
126 throw new ClassNotFoundException(pckgname + " (" + directory
127 + ") does not appear to be a valid package");
128 }
129 }
130 Class[] classesA = new Class[classes.size()];
131 classes.toArray(classesA);
132 return classesA;
133 }
134
135 /**
136 * Clears out the Action and JAG Hashtables and refills them. Normally this
137 * is only called once when the system starts.
138 */
139 public static void Init() {
140 Class[] classes;
141 try {
142 classes = getClasses(AGENTS_PACKAGE);
143
144 for (int i = 0; i < classes.length; i++) {
145 String name = classes[i].getSimpleName();
146 // maps lower case name to correct capitalised name
147 _JAGs.put(name.toLowerCase(), name);
148 }
149 } catch (Exception e) {
150 System.out
151 .println("You must have Java 1.5 or higher to run Expeditee");
152 System.out.println(e.getMessage());
153 }
154 try {
155 classes = getClasses(ACTIONS_PACKAGE);
156
157 for (int i = 0; i < classes.length; i++) {
158 String name = classes[i].getSimpleName();
159 // read in all the methods from the class
160 try {
161 // System.out.println(name);
162 LoadMethods(Class.forName(ACTIONS_PACKAGE + name));
163 } catch (ClassNotFoundException e) {
164 Logger.Log(e);
165 e.printStackTrace();
166 }
167 }
168 } catch (Exception e) {
169 System.out.println(e.getMessage());
170 }
171 }
172
173 /**
174 * Loads all the Methods that meet the requirements checked by MethodCheck
175 * into the hashtable.
176 *
177 * @param c
178 * The Class to load the Methods from.
179 */
180 private static void LoadMethods(Class c) {
181 // list of methods to test
182 Method[] toLoad = c.getMethods();
183
184 for (Method m : toLoad) {
185 // only allow methods with the right modifiers
186 if (MethodCheck(m)) {
187 if (!(_Actions.containsKey(m.getName().toLowerCase())))
188 _Actions.put(m.getName().toLowerCase(), m);
189 else {
190 int i = 0;
191 while (_Actions.containsKey(m.getName().toLowerCase() + i))
192 i++;
193
194 _Actions.put(m.getName().toLowerCase() + i, m);
195 }
196
197 }
198 }
199 }
200
201 /**
202 * Checks if the given Method corresponds to the restrictions of Action
203 * commands, namely: Declared (not inherited), Public, and Static, with a
204 * void return type.
205 *
206 * @param m
207 * The Method to check
208 * @return True if the Method meets the above conditions, false otherwise.
209 */
210 private static boolean MethodCheck(Method m) {
211 // the return type must be void
212 if (m.getReturnType() != void.class)
213 return false;
214
215 int mods = m.getModifiers();
216
217 // check the method is declared (not inherited)
218 if ((mods & Method.DECLARED) != Method.DECLARED)
219 return false;
220
221 // check the method is public
222 if ((mods & Modifier.PUBLIC) != Modifier.PUBLIC)
223 return false;
224
225 // check the method is static
226 if ((mods & Modifier.STATIC) != Modifier.STATIC)
227 return false;
228
229 // if we have not returned yet, then the tests have all passed
230 return true;
231 }
232
233 /**
234 * Performs the given KMS action command. The source Frame and Item are
235 * given because they are required by some actions. Note that the source
236 * frame does not have to be the Item's parent Frame.
237 *
238 * @param source
239 * The Frame that the action should apply to
240 * @param launcher
241 * The Item that has the action assigned to it
242 * @param command
243 * The action to perform
244 */
245 public static void PerformAction(Frame source, Item launcher, String command) {
246 FrameIO.SaveFrame(source, false);
247
248 // separate method name and parameter names
249 String mname = getName(command);
250 if (command.length() > mname.length())
251 command = command.substring(mname.length() + 1);
252 else
253 command = "";
254
255 // Strip off the @ from annotation items
256 if (mname.startsWith("@"))
257 mname = mname.substring(1);
258
259 mname = mname.trim().toLowerCase();
260
261 // check for protection on frame
262 if (ItemUtils.ContainsTag(source.getItems(), "@No" + mname)) {
263 FrameGraphics.DisplayMessage("Frame is protected by @No" + mname
264 + " tag.");
265 return;
266 }
267
268 // retrieve methods that match the name
269 Method toRun = _Actions.get(mname);
270
271 // if this is not the name of a method, it may be the name of an agent
272 if (toRun == null) {
273 LaunchAgent(mname, command, source);
274 return;
275 }
276
277 // Need to save the frame if we are navigating away from it so we dont
278 // loose changes
279 if (toRun.getDeclaringClass().getName().equals(
280 NAVIGATIONS_CLASS)) {
281 FrameIO.SaveFrame(DisplayIO.getCurrentFrame());
282 }
283
284 // if there are duplicate methods with the same name
285 List<Method> possibles = new LinkedList<Method>();
286 possibles.add(toRun);
287 int i = 0;
288 while (_Actions.containsKey(mname + i)) {
289 possibles.add(_Actions.get(mname + i));
290 i++;
291 }
292
293 for (Method possible : possibles) {
294
295 // try first with the launching item as a parameter
296
297 // run method
298 try {
299 // convert parameters to objects and get the method to invoke
300 Object[] parameters = CreateObjects(possible, source, launcher,
301 command);
302
303 possible.invoke(null, parameters);
304 return;
305 } catch (Exception e) {
306 Logger.Log(e);
307 e.printStackTrace();
308 }
309 }
310 }
311
312 /**
313 * Launches an agent with the given name, and passes in the given parameters
314 *
315 * @param name
316 * The name of the JAG to load
317 * @param parameters
318 * The parameters to pass to the JAG
319 * @param source
320 * The starting Frame that the JAG is being launched on
321 */
322 private static void LaunchAgent(String name, String parameters, Frame source) {
323 // save the current frame (if necesssary)
324 FrameUtils.LeavingFrame(source);
325
326 try {
327 // check for stored capitalisation
328 if (_JAGs.containsKey(name.toLowerCase())) {
329 name = _JAGs.get(name.toLowerCase());
330 } else if (name.toLowerCase().endsWith("tree")) {
331 parameters = name.substring(0, name.length() - "tree".length())
332 + " " + parameters;
333 name = "WriteTree";
334 } else if (name.toLowerCase().endsWith("frame")) {
335 parameters = name
336 .substring(0, name.length() - "frame".length())
337 + " " + parameters;
338 name = "WriteFrame";
339 }
340
341 // load the JAG class
342 Class agentClass = Class.forName(AGENTS_PACKAGE + name);
343
344 // get the constructor for the JAG class
345 Constructor con = null;
346 Constructor constructors[] = agentClass.getConstructors();
347 Object[] params = null;
348
349 // determine correct parameters for constructor
350 for (Constructor c : constructors) {
351 if (parameters.length() > 0
352 && c.getParameterTypes().length == 1) {
353 con = c;
354 params = new String[1];
355 params[0] = parameters;
356 break;
357 } else if (c.getParameterTypes().length == 0 && con == null)
358 con = c;
359 }
360
361 // if there is no constructor, return
362 if (con == null) {
363 FrameGraphics.DisplayMessage("Invalid parametres for agent.");
364 //System.out.println("Constructor not found...");
365 return;
366 }
367
368 // create the JAG
369 _Agent = (Agent) con.newInstance(params);
370
371 Thread t = new Thread(_Agent);
372 t.setPriority(Thread.MIN_PRIORITY);
373
374 // check for errors during initialisation
375 if (!_Agent.initialise(source)) {
376 FrameGraphics.ErrorMessage("Error initialising agent: " + name);
377 return;
378 }
379
380 _Agent.setStartFrame(source);
381
382 t.start();
383
384 // if there is a completion frame, then display it to the user
385 if (_Agent.hasResultFrame()) {
386 Frame next = _Agent.getResultFrame();
387 FrameUtils.DisplayFrame(next, true);
388 }
389
390 } catch (ClassNotFoundException cnf) {
391 FrameGraphics.ErrorMessage("Error: '" + name
392 + "' is not an action statement or Agent.");
393 } catch (Exception e) {
394 FrameGraphics.ErrorMessage("Error creating Agent: '" + name + "'");
395 System.out.println("Agent set to Null.");
396 _Agent = null;
397 e.printStackTrace();
398 Logger.Log(e);
399 }
400
401 return;
402 }
403
404 /**
405 * Used to determine if the previously launched agent is still executing.
406 *
407 * @return True if the last Agent is still executing, False otherwise.
408 */
409 public static boolean isAgentRunning() {
410 if (_Agent != null)
411 return _Agent.isRunning();
412
413 return false;
414 }
415
416 /**
417 * Stops the currently running Agent (If there is one) by calling
418 * Agent.stop(). Note: This may not stop the Agent immediately, but the
419 * Agent should terminate as soon as it is safe to do so.
420 */
421 public static void stopAgent() {
422 if (_Agent != null) {
423 FrameGraphics.DisplayMessage("Stopping Agent...");
424 _Agent.stop();
425
426 // while (_Agent.isRunning())
427 // ;
428
429 // FrameGraphics.DisplayMessage("Agent Stopped.");
430 }
431 }
432
433 public static void interruptAgent() {
434 if (_Agent != null) {
435 _Agent.interrupt();
436 }
437 }
438
439 /**
440 * Takes a list of Methods and finds one that accepts the given list of
441 * objects as parameters
442 *
443 * @param possibleMethods
444 * The list of Methods to search through
445 * @param parameters
446 * The array of parameters that the Method should accept
447 * @return The Method found if one was found, otherwise null
448 */
449 /*
450 * private static Method FindCompatibleMethods(List<Method>
451 * possibleMethods, Object[] parameters){ Method toRun = null;
452 *
453 * for(Method m : possibleMethods){ //the number of parameters must be the
454 * same if(parameters.length == m.getParameterTypes().length){ //if no
455 * methods are required, there is nothing else to check if(parameters.length ==
456 * 0) toRun = m; else //check that the list of parameters are of the right
457 * types for(int i = 0; i < parameters.length; i++){
458 * if(m.getParameterTypes()[i].isInstance(parameters[i])) toRun = m; else
459 * toRun = null; }
460 *
461 * //if a method has been found already, there is no need to keep searching
462 * if(toRun != null) break; } }
463 *
464 * return toRun; }
465 */
466
467 /**
468 * Converts the given String of values into an array of Objects
469 *
470 * @param launcher
471 * The Item used to launch the action, it may be required as a
472 * parameter
473 * @param values
474 * A list of space separated String values to convert to objects
475 * @return The created array of Objects
476 */
477 public static Object[] CreateObjects(Method method, Frame source,
478 Item launcher, String values) {
479 // The parameter types that should be created from the given String
480 Class[] paramTypes = method.getParameterTypes();
481
482 // if the method has no parameters
483 if (paramTypes.length == 0)
484 return new Object[0];
485
486 Object[] objects = new Object[paramTypes.length];
487 int ind = 0;
488
489 // if the first class in the list is a frame or item, it is the source
490 // or launcher
491 // length must be at least one if we are still running
492 if (paramTypes[0] == Frame.class) {
493 objects[0] = source;
494 ind = 1;
495 } else if (paramTypes[0] == Item.class) {
496 objects[0] = launcher;
497 ind = 1;
498 }
499
500 // if the first class was a frame or item, the second class might be the
501 // other
502 if (paramTypes.length > 1 && objects[0] != null) {
503 if (paramTypes[1] == Frame.class) {
504 objects[1] = source;
505 ind = 2;
506 } else if (paramTypes[1] == Item.class) {
507 objects[1] = launcher;
508 ind = 2;
509 }
510 }
511
512 String param;
513 // convert the rest of the objects
514 for (; ind < objects.length; ind++) {
515 // strip off the next value
516 param = ParseValue(values);
517 values = RemainingParams(values);
518
519 // check if its the last param and combine
520 if (values.length() > 0 && ind == objects.length - 1)
521 param = param.trim() + ' ' + values.trim();
522
523 // convert the value to an object
524 Object o = Conversion.Convert(paramTypes[ind], param);
525 objects[ind] = o;
526 }
527
528 return objects;
529
530 // strip off the first parameter
531
532 /*
533 * //the list of objects to return List<Object> returnParams = new
534 * ArrayList<Object>(); if(launcher != null)
535 * returnParams.add(launcher);
536 *
537 * while(param != null){ /* //the parameter is a String
538 * if(param.charAt(0) == '"') returnParams.add(param.substring(1,
539 * param.length() - 1)); else{ //try to parse an int try{ int val =
540 * Integer.parseInt(param); returnParams.add(val);
541 * }catch(NumberFormatException nfe){ System.out.println("Type not found
542 * for: " + param); } }
543 *
544 * if(param.length() < values.length()) values =
545 * values.substring(param.length() + 1); else values = "";
546 */
547
548 // returnParams.add(KMSConversion.Convert(type, value))
549 // get the next value
550 /*
551 * param = ParseValue(values); }
552 *
553 * return returnParams.toArray();
554 */
555 }
556
557 /**
558 * Returns a string containing the remaining params after ignoring the first
559 * one.
560 *
561 * @param params
562 * a space sparated list of N parameters
563 * @return the remaining N - 1 parameters
564 */
565 private static String RemainingParams(String params) {
566 if (params.length() == 0)
567 return null;
568
569 // remove leading and trailing spaces
570 params = params.trim();
571
572 // if there are no more parameters, we are done
573 if (params.indexOf(" ") < 0) {
574 return "";
575 }
576
577 // Check if we have a string parameter
578 if (params.charAt(0) == '"') {
579 return params.substring(params.indexOf('"', 1) + 1);
580 }
581
582 return params.substring(params.indexOf(" "));
583 }
584
585 /**
586 * Returns the first value in the space separated String of parameters
587 * passed in. Strings are enclosed in double quotes.
588 *
589 * @param params
590 * The String of space separated values
591 * @return The first value in the String
592 */
593 private static String ParseValue(String params) {
594 if (params.length() == 0)
595 return null;
596
597 // remove leading and trailing spaces
598 String param = params.trim();
599
600 // if there are no more parameters, we are done
601 if (param.indexOf(" ") < 0) {
602 return param;
603 }
604
605 // Check if we have a string parameter
606 if (param.charAt(0) == '"') {
607 return param.substring(1, param.indexOf('"', 1));
608 }
609
610 return param.substring(0, param.indexOf(" "));
611 }
612
613 /**
614 * Separates the name of the given command from any parameters and returns
615 * them
616 *
617 * @param command
618 * The String to separate out the Action or Agent name from
619 * @return The name of the Action of Agent with parameters stripped off
620 */
621 private static String getName(String command) {
622 if (command.indexOf(" ") < 0)
623 return command;
624
625 return command.substring(0, command.indexOf(" "));
626 }
627}
Note: See TracBrowser for help on using the repository browser.