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    }