001 /*
002 * This file is part of muCommander, http://www.mucommander.com
003 * Copyright (C) 2002-2008 Maxence Bernard
004 *
005 * muCommander is free software; you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation; either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * muCommander is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program. If not, see <http://www.gnu.org/licenses/>.
017 */
018
019 package com.mucommander.ui.action;
020
021 import com.mucommander.Debug;
022 import com.mucommander.ui.main.MainFrame;
023
024 import java.lang.reflect.Constructor;
025 import java.util.Hashtable;
026 import java.util.Iterator;
027 import java.util.Vector;
028 import java.util.WeakHashMap;
029
030 /**
031 * ActionManager provides methods to retrieve {@link MuAction} instances and invoke them. It keeps track of all the
032 * action instances it has created and allows them to be reused whithin a {@link MainFrame}.
033 *
034 * <p>MuAction subclasses should not be instanciated directly, <code>getActionInstance</code>
035 * methods should be used instead. Using ActionManager to retrieve a MuAction ensures that only one instance
036 * exists for a given {@link MainFrame}. This is particularly important because actions are stateful and can be used
037 * in several components of a MainFrame at the same time; if an action's state changes, the change must be reflected
038 * everywhere the action is used. It is also important for performance reasons: sharing one action throughout a
039 * {@link com.mucommander.ui.main.MainFrame} saves some memory and also CPU cycles as some actions listen to particular events to change
040 * their state accordingly.</p>
041 *
042 * @see MuAction
043 * @see ActionDescriptor
044 * @see ActionKeymap
045 * @author Maxence Bernard
046 */
047 public class ActionManager {
048
049 /** MuAction class -> constructor map */
050 private static Hashtable actionConstructors = new Hashtable();
051
052 /** MainFrame -> MuAction map */
053 private static WeakHashMap mainFrameActionsMap = new WeakHashMap();
054
055
056 /**
057 * Convenience method that returns an instance of the given MuAction class, and associated with the specified
058 * MainFrame. This method creates an ActionDescriptor with no initial property, passes it to
059 * {@link #getActionInstance(ActionDescriptor, MainFrame)} and returns the MuAction instance.
060 *
061 * @param actionClass the MuAction class to instanciate
062 * @param mainFrame the MainFrame instance the action belongs to
063 * @return a MuAction instance matching the given MuAction Class and MainFrame, <code>null</code> if the
064 * class could not be found or could not be instanciated.
065 */
066 public static MuAction getActionInstance(Class actionClass, MainFrame mainFrame) {
067 return getActionInstance(new ActionDescriptor(actionClass), mainFrame);
068 }
069
070
071 /**
072 * Returns an instance of the MuAction class denoted by the given ActionDescriptor, for the specified MainFrame.
073 * If an existing instance corresponding to the same ActionDescriptor and MainFrame is found, it is simply returned.
074 * If no matching instance could be found, a new instance is created, added to the internal action instances map
075 * (for further use) and returned.
076 * If the MuAction denoted by the specified ActionDescriptor cannot be found or cannot be instanciated,
077 * <code>null</code> is returned.
078 *
079 * @param actionDescriptor a descriptor of the action class to instanciate with initial properties
080 * @param mainFrame the MainFrame instance the action belongs to
081 * @return a MuAction instance matching the given ActionDescriptor and MainFrame, <code>null</code> if the
082 * MuAction class denoted by the ActionDescriptor could not be found or could not be instanciated.
083 */
084 public static MuAction getActionInstance(ActionDescriptor actionDescriptor, MainFrame mainFrame) {
085 // if(Debug.ON) Debug.trace("called, actionDescriptor = "+actionDescriptor, 5);
086
087 Hashtable mainFrameActions = (Hashtable)mainFrameActionsMap.get(mainFrame);
088 if(mainFrameActions==null) {
089 // if(Debug.ON) Debug.trace("creating MainFrame action map");
090
091 mainFrameActions = new Hashtable();
092 mainFrameActionsMap.put(mainFrame, mainFrameActions);
093 }
094
095 // Looks for an existing MuAction instance used by the specified MainFrame
096 MuAction action = (MuAction)mainFrameActions.get(actionDescriptor);
097 if(action==null) {
098 Class actionClass = actionDescriptor.getActionClass();
099
100 try {
101 // Looks for an existing cached Constructor instance
102 Constructor actionConstructor = (Constructor)actionConstructors.get(actionClass);
103 if(actionConstructor==null) {
104 // if(Debug.ON) Debug.trace("creating constructor");
105
106 // Not found, retrieve a Constructor instance and caches it
107 actionConstructor = actionClass.getConstructor(new Class[]{MainFrame.class, Hashtable.class});
108 actionConstructors.put(actionClass, actionConstructor);
109
110 // if(Debug.ON) Debug.trace("nb constructors = "+actionConstructors.size());
111 }
112
113 // if(Debug.ON) Debug.trace("creating instance");
114
115 Hashtable properties = actionDescriptor.getInitProperties();
116 // If no properties hashtable is specified in the action descriptor
117 if(properties==null) {
118 properties = new Hashtable();
119 }
120 // else clone the hashtable to ensure that it doesn't get modified by action instances.
121 // Since cloning is an expensive operation, this is done only if the hashtable is not empty.
122 else if(!properties.isEmpty()) {
123 properties = (Hashtable)properties.clone();
124 }
125
126 // Instanciate the MuAction class
127 action = (MuAction)actionConstructor.newInstance(new Object[]{mainFrame, properties});
128 mainFrameActions.put(actionDescriptor, action);
129
130 // if(Debug.ON) Debug.trace("nb action instances = "+mainFrameActions.size());
131 }
132 catch(Exception e) { // Catches ClassNotFoundException, NoSuchMethodException, InstanciationException, IllegalAccessException, InvocateTargetException
133 if(Debug.ON) {
134 Debug.trace("ERROR: caught exception "+e+" for class "+actionClass);
135 e.printStackTrace();
136 }
137
138 // Class / constructor could not be instanciated, return null
139 return null;
140 }
141 }
142 // else {
143 // if(Debug.ON) Debug.trace("found existing action instance: "+action);
144 // }
145
146 return action;
147 }
148
149
150 /**
151 * Returns a Vector of all MuAction instances matching the specified Class.
152 *
153 * @param muActionClass the MuAction class to compare instances against
154 * @return a Vector of all MuAction instances matching the specified Class
155 */
156 public static Vector getActionInstances(Class muActionClass) {
157 Vector actionInstances = new Vector();
158
159 // Iterate on all MainFrame instances
160 Iterator mainFrameActions = mainFrameActionsMap.values().iterator();
161 while(mainFrameActions.hasNext()) {
162 Iterator actions = ((Hashtable)mainFrameActions.next()).values().iterator();
163 // Iterate on all the MainFrame's actions
164 while(actions.hasNext()) {
165 MuAction action = (MuAction)actions.next();
166 if(action.getClass().equals(muActionClass)) {
167 // Found an action matching the specified class
168 actionInstances.add(action);
169 // Jump to the next MainFrame
170 break;
171 }
172 }
173 }
174
175 // if(Debug.ON) Debug.trace("returning "+actionInstances);
176 return actionInstances;
177 }
178
179
180 /**
181 * Convenience method that retrieves an instance of the MuAction denoted by the given Class and associated
182 * with the given {@link MainFrame} and calls {@link MuAction#performAction()} on it.
183 * Returns <code>true</code> if an instance of the action could be retrieved and performed, <code>false</code>
184 * if the MuAction could not be found or could not be instanciated.
185 *
186 * @param actionClass the class of the MuAction to perform
187 * @param mainFrame the MainFrame the action belongs to
188 * @return true if the action instance could be retrieved and the action performed, false otherwise
189 */
190 public static boolean performAction(Class actionClass, MainFrame mainFrame) {
191 return performAction(new ActionDescriptor(actionClass), mainFrame);
192 }
193
194
195 /**
196 * Convenience method that retrieves an instance of the MuAction denoted by the given {@link ActionDescriptor}
197 * and associated with the given {@link com.mucommander.ui.main.MainFrame} and calls {@link MuAction#performAction()} on it.
198 * Returns <code>true</code> if an instance of the action could be retrieved and performed, <code>false</code>
199 * if the MuAction could not be found or could not be instanciated.
200 *
201 * @param actionDescriptor the ActionDescriptor of the action to perform
202 * @param mainFrame the MainFrame the action belongs to
203 * @return true if the action instance could be retrieved and the action performed, false otherwise
204 */
205 public static boolean performAction(ActionDescriptor actionDescriptor, MainFrame mainFrame) {
206 MuAction action = getActionInstance(actionDescriptor, mainFrame);
207
208 if(action==null)
209 return false;
210
211 action.performAction();
212
213 return true;
214 }
215 }