Ignore:
Timestamp:
12/04/13 17:52:41 (11 years ago)
Author:
jts21
Message:

Add settings package which uses reflection to allow changing settings without hard coding the code to change every setting.

  • Currently only works for String/Int/Boolean/Double values
  • Supports default values by looking for fields with the prefix 'default', and will reset unset values to their default if a default exists.
  • For more complex settings (e.g. proxy settings), there is a "onParsed()" callback which can be used to run additional code, and (in the case of the password widget) parse abnormal items.
  • Some of the settings code in FrameUtils.ParseProfile has already been commented out since it's handled by default with the new Settings package. The rest could be moved over to UserSettings.onParsed(). Given the number of settings that remain to be moved, it may be a good idea to add the possibility for setting-specific onParsed() callbacks (could be done quite easily, similarly to how default values are handled).
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/expeditee/actions/Actions.java

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