ResourceHelper.java
001 /*
002  * ResourceHelper.java
003  
004  * Copyright (c) 2013, The University of Sheffield. See the file COPYRIGHT.txt
005  * in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
006  
007  * This file is part of GATE (see http://gate.ac.uk/), and is free software,
008  * licenced under the GNU Library General Public License, Version 2, June 1991
009  * (in the distribution as file licence.html, and also available at
010  * http://gate.ac.uk/gate/licence.html).
011  
012  * Mark A. Greenwood, 01/08/2013
013  */
014 
015 package gate.gui;
016 
017 import gate.Gate;
018 import gate.Resource;
019 import gate.creole.AbstractResource;
020 import gate.creole.ResourceInstantiationException;
021 import gate.event.CreoleEvent;
022 import gate.event.CreoleListener;
023 
024 import java.lang.reflect.InvocationTargetException;
025 import java.lang.reflect.Method;
026 import java.lang.reflect.Modifier;
027 import java.util.HashMap;
028 import java.util.List;
029 import java.util.Map;
030 
031 import javax.swing.Action;
032 
033 public abstract class ResourceHelper extends AbstractResource implements
034   CreoleListener {
035 
036   private static final long serialVersionUID = 1657147709821774423L;
037 
038   // We cache the actions so we don't keep recreating them every time. If you
039   // don't want this behaviour (i.e. you want completely dynamic menus) then
040   // you'll need to override the getActions method
041   Map<Object, List<Action>> actions = new HashMap<Object, List<Action>>();
042 
043   @Override
044   public Resource init() throws ResourceInstantiationException{
045     // we need to listen for unload events so we register ourselves with the
046     // creole register
047     Gate.getCreoleRegister().addCreoleListener(this);
048 
049     // nothing else to do so just return ourself
050     return this;
051   }
052 
053   /**
054    * Get the right-click menu items that this tool will add to the specified
055    * resource. Note that this implementation uses a cache so that the same items
056    * will be returned each time the menu is displayed. For a fully dynamic
057    * approach a subclass should override this method to simply call @{link
058    {@link #buildActions(NameBearerHandle)}.
059    
060    @param handle
061    *          the {@link gate.gui.NameBearerHandle} instance we are wanting to
062    *          add new menu items to
063    @return a list of {@link javax.swing.Action} instances which will be added
064    *         to the right click menu of the specified handle
065    */
066   public List<Action> getActions(NameBearerHandle handle) {
067 
068     if(!actions.containsKey(handle.getTarget())) {
069       // if we haven't seen this resource before then build the actions
070       actions.put(handle.getTarget(), buildActions(handle));
071     }
072 
073     // return the actions from the cache
074     return actions.get(handle.getTarget());
075   }
076 
077   /**
078    * Build the {@link javax.swing.Action} instances that should be used to
079    * enhance the right-click menu of the specified
080    {@link gate.gui.NameBearerHandle}.
081    
082    @param handle
083    *          the {@link gate.gui.NameBearerHandle} instance we are adding to
084    @return a list of {@link javax.swing.Action} instances that will be added
085    *         to the right-click menu of the resource.
086    */
087   protected abstract List<Action> buildActions(NameBearerHandle handle);
088 
089   /**
090    * Allows for the calling of methods defined within ResourceHelper instances
091    * which aren't part of the core API and so which can only be called via
092    * reflection.
093    
094    @param action
095    *          the name of the method to call (method must take a Resource
096    *          instance as it's first parameter)
097    @param resource
098    *          the Resource instance that you want to help
099    @param params
100    *          parameters to the method you are trying to call
101    @return the return value from the method you are calling, or null if the
102    *         method has a void return type
103    */
104   public Object call(String action, Resource resource, Object... params)
105     throws NoSuchMethodException, IllegalArgumentException,
106     IllegalAccessException, InvocationTargetException {
107 
108     // get all the methods defined for this instance of the helper
109     Method[] methods = this.getClass().getMethods();
110 
111     outer: for(Method method : methods) {
112       // for each method....
113 
114       // skip over methods which aren't public
115       if(!Modifier.isPublic(method.getModifiers())) continue;
116 
117       // if the method name doesn't match then skip onto the next method
118       if(!method.getName().equals(action)) continue;
119 
120       // get the types of the methods params
121       Class<?>[] paramTypes = method.getParameterTypes();
122 
123       // if the method doesn't have the right number of params then skip to the
124       // next method
125       if(paramTypes.length != params.length + 1continue;
126 
127       // check the param types and skip to the next method if they aren't
128       // compatible
129       if(!paramTypes[0].isAssignableFrom((resource.getClass()))) continue;
130       for(int i = 0; i < params.length; ++i) {
131         if(!paramTypes[i + 1].isAssignableFrom(params[i].getClass())) continue outer;
132       }
133 
134       // if we got to here then we have found a method we can call so....
135 
136       // copy the params into a single array
137       Object[] parameters = new Object[params.length + 1];
138       parameters[0= resource;
139       System.arraycopy(params, 0, parameters, 1, params.length);
140 
141       // and finally call the method
142       return method.invoke(this, parameters);
143 
144     }
145 
146     throw new NoSuchMethodException("we can't find what you are looking for");
147   }
148 
149   @Override
150   public void cleanup() {
151     // do the normal cleanup
152     super.cleanup();
153 
154     // stop listening for creole events so that we can get fully unloaded
155     Gate.getCreoleRegister().removeCreoleListener(this);
156   }
157 
158   @Override
159   public void resourceUnloaded(CreoleEvent e) {
160     // remove any cached menus for the resource that is being unloaded to help
161     // with memory consumption etc.
162     actions.remove(e.getResource());
163   }
164 
165   @Override
166   public void resourceLoaded(CreoleEvent e) {
167     // we don't care about this event
168   }
169 
170   @Override
171   public void datastoreOpened(CreoleEvent e) {
172     // we don't care about this event
173   }
174 
175   @Override
176   public void datastoreCreated(CreoleEvent e) {
177     // we don't care about this event
178   }
179 
180   @Override
181   public void datastoreClosed(CreoleEvent e) {
182     // we don't care about this event
183   }
184 
185   @Override
186   public void resourceRenamed(Resource resource, String oldName, String newName) {
187     // we don't care about this event
188   }
189 }