View Javadoc

1   /*
2    * Copyright 2006-2016 The JGUIraffe Team.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.jguiraffe.gui.builder.action;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.Map;
24  import java.util.NoSuchElementException;
25  import java.util.Set;
26  import java.util.concurrent.ConcurrentHashMap;
27  
28  /**
29   * <p>
30   * A class for maintaining action objects.
31   * </p>
32   * <p>
33   * This class provides access to the actions available in an application or for
34   * a certain component. Actions can be queried by their name, or lists of all
35   * existing actions can be requested. It is also possible to organize actions in
36   * groups. Then for example all actions that belong to a group can be disabled
37   * at once.
38   * </p>
39   * <p>
40   * An <code>ActionStore</code> object also holds a reference to a parent
41   * store. If defined, all actions in the parent store can be accessed by this
42   * store, too. This provides for a hierarchical action architecture. E.g. there
43   * may be a central <code>ActionStore</code> holding the global actions of the
44   * application. Sub components of the application (e.g. internal frames or
45   * dialogs) can define their own <code>ActionStore</code> with their specific
46   * set of actions, but through the parent reference have also access to the
47   * global actions.
48   * </p>
49   * <p>
50   * Note: The operations of this class are thread-safe.
51   * </p>
52   *
53   * @author Oliver Heger
54   * @version $Id: ActionStore.java 205 2012-01-29 18:29:57Z oheger $
55   */
56  public class ActionStore
57  {
58      /** Stores the defined actions in this store. */
59      private final Map<String, FormAction> actions;
60  
61      /** Holds information about the defined action groups. */
62      private final Map<String, Set<String>> groups;
63  
64      /** Stores the reference to this store's parent. */
65      private volatile ActionStore parent;
66  
67      /**
68       * Creates a new empty instance of <code>ActionStore</code>.
69       */
70      public ActionStore()
71      {
72          this(null);
73      }
74  
75      /**
76       * Creates a new instance of <code>ActionStore</code> and initializes the
77       * parent reference.
78       *
79       * @param parent this action store's parent
80       */
81      public ActionStore(ActionStore parent)
82      {
83          actions = new ConcurrentHashMap<String, FormAction>();
84          groups = new HashMap<String, Set<String>>();
85          setParent(parent);
86      }
87  
88      /**
89       * Adds the specified action to this store.
90       *
91       * @param action the action to add (must not be <b>null</b>)
92       */
93      public void addAction(FormAction action)
94      {
95          if (action == null)
96          {
97              throw new IllegalArgumentException("Action must not be null!");
98          }
99          if (action.getName() == null)
100         {
101             throw new IllegalArgumentException(
102                     "The action's name must not be null!");
103         }
104 
105         actions.put(action.getName(), action);
106     }
107 
108     /**
109      * Removes the action with the specified name from this store. This method
110      * only removes actions that belong to this store; actions in the parent
111      * store are not removed.
112      *
113      * @param name the name of the action to remove
114      * @return the removed action or <b>null</b> if it was not found
115      */
116     public FormAction removeAction(String name)
117     {
118         return actions.remove(name);
119     }
120 
121     /**
122      * Returns the action with the given name. If this action does not exist, an
123      * exception will be thrown.
124      *
125      * @param name the name of the desired action
126      * @return the action with this name
127      * @throws NoSuchElementException if no such action exists
128      */
129     public FormAction getAction(String name)
130     {
131         FormAction action = (name != null) ? actions.get(name) : null;
132         if (action != null)
133         {
134             return action;
135         }
136         else
137         {
138             if (getParent() == null)
139             {
140                 throw new NoSuchElementException("Action " + name
141                         + " could not be found!");
142             }
143             else
144             {
145                 return getParent().getAction(name);
146             }
147         }
148     }
149 
150     /**
151      * Returns a flag whether the specified action is contained in this store or
152      * in the parent store.
153      *
154      * @param name the action's name
155      * @return a flag whether this action exists
156      */
157     public boolean hasAction(String name)
158     {
159         return (name != null) && (actions.containsKey(name)
160                 || (getParent() != null && getParent().hasAction(name)));
161     }
162 
163     /**
164      * Returns a collection with the names of the actions stored in this
165      * <code>ActionStore</code>. The returned collection will not contain the
166      * names of the actions that are stored in the parent
167      * <code>ActionStore</code>.
168      *
169      * @return a collection with the names of the actions directly stored in
170      * this <code>ActionStore</code>
171      */
172     public Set<String> getActionNames()
173     {
174         return actions.keySet();
175     }
176 
177     /**
178      * Returns a collection with the names of all actions stored in this
179      * <code>ActionStore</code> or in one of its parents. With this method
180      * really all names can be found out that can be passed to the
181      * {@link #getAction(String)} method. Note: the set returned
182      * by this method is a snapshot reflecting the state of this action time at
183      * the time it was created. It is not connected to this action store, so
184      * later updates are not visible.
185      *
186      * @return a collection with all defined action names
187      */
188     public Set<String> getAllActionNames()
189     {
190         Set<String> result = new HashSet<String>(getActionNames());
191         ActionStore store = getParent();
192 
193         while (store != null)
194         {
195             result.addAll(store.getAllActionNames());
196             store = store.getParent();
197         }
198 
199         return result;
200     }
201 
202     /**
203      * Returns all action objects whose names are specified in the given
204      * collection. This is a convenience method for easily accessing groups of
205      * actions. If one of the requested actions does not exist, a
206      * <code>NoSuchElementException</code> exception will be thrown. If an
207      * action cannot be found in this store, the parent store (if it is
208      * defined), is also searched.
209      *
210      * @param names a collection with the names of the desired actions
211      * @return the corresponding actions
212      * @throws NoSuchElementException if one of the actions cannot be resolved
213      */
214     public Collection<FormAction> getActions(Collection<String> names)
215     {
216         if (names == null)
217         {
218             return Collections.emptyList();
219         }
220 
221         Collection<FormAction> result = new ArrayList<FormAction>(names.size());
222         for (String name : names)
223         {
224             result.add(getAction(name));
225         }
226 
227         return result;
228     }
229 
230     /**
231      * Adds the specified action to the given group. This establishes a logical
232      * connection between this action and the group. If no group with this name
233      * exists, it is created now. If the action is unknown in this store or in
234      * the parent stores, a <code>NoSuchElementException</code> exception will
235      * be thrown.
236      *
237      * @param actionName the name of the action
238      * @param groupName the name of the group (must not be <b>null</b>)
239      */
240     public void addActionToGroup(String actionName, String groupName)
241     {
242         if (!hasAction(actionName))
243         {
244             throw new NoSuchElementException("Unknown action " + actionName);
245         }
246         if (groupName == null)
247         {
248             throw new IllegalArgumentException("Group name must not be null!");
249         }
250 
251         synchronized (groups)
252         {
253             Set<String> groupSet = groups.get(groupName);
254             if (groupSet == null)
255             {
256                 groupSet = new HashSet<String>();
257                 groups.put(groupName, groupSet);
258             }
259 
260             groupSet.add(actionName);
261         }
262     }
263 
264     /**
265      * Removes the specified action from the given group. If the group becomes
266      * empty after this operation, it is removed itself.
267      *
268      * @param actionName the action's name
269      * @param groupName the group's name
270      * @return a flag if the action was removed (<b>false</b> if either the
271      * action did not belong to this group or the group does not exist)
272      */
273     public boolean removeActionFromGroup(String actionName, String groupName)
274     {
275         synchronized (groups)
276         {
277             Set<String> group = groups.get(groupName);
278             boolean found = (group == null) ? false : group.remove(actionName);
279             if (found && group.isEmpty())
280             {
281                 // remove empty group
282                 groups.remove(groupName);
283             }
284             return found;
285         }
286     }
287 
288     /**
289      * Checks if the specified action belongs to the given group.
290      *
291      * @param actionName the action's name
292      * @param groupName the group's name
293      * @return a flag if the action belongs to this group
294      */
295     public boolean isActionInGroup(String actionName, String groupName)
296     {
297         synchronized (groups)
298         {
299             Set<String> group = groups.get(groupName);
300             return group != null && group.contains(actionName);
301         }
302     }
303 
304     /**
305      * Removes the group with the specified name. This does not affect any
306      * actions in this group; only the actions' associations to this group are
307      * removed.
308      *
309      * @param groupName the name of the group to remove
310      * @return a flag if the group existed
311      */
312     public boolean removeGroup(String groupName)
313     {
314         synchronized (groups)
315         {
316             return groups.remove(groupName) != null;
317         }
318     }
319 
320     /**
321      * Returns a set with the names of all actions that belong to the given
322      * group. If the group does not exist, the returned set is empty. The
323      * returned set is only a copy, so modifications won't have any effect on
324      * the groups of this store.
325      *
326      * @param groupName the name of the group
327      * @return a set with the names of all actions in this group
328      */
329     public Set<String> getActionNamesForGroup(String groupName)
330     {
331         Set<String> result = new HashSet<String>();
332         synchronized (groups)
333         {
334             Set<String> group = groups.get(groupName);
335             if (group != null)
336             {
337                 result.addAll(group);
338             }
339         }
340         return result;
341     }
342 
343     /**
344      * Returns the names of all defined groups. Groups are a means for logically
345      * grouping actions. With the set returned here a client can iterate over
346      * all existing groups. Note that groups defined in one
347      * <code>ActionStore</code> are completely independent on the parent's
348      * groups, i.e. this method won't return any groups defined in the parent
349      * store.
350      *
351      * @return a collection with the names of the defined action groups
352      */
353     public Set<String> getGroupNames()
354     {
355         synchronized (groups)
356         {
357             return groups.keySet();
358         }
359     }
360 
361     /**
362      * Sets the enabled flag for all actions in the specified group. If the
363      * group cannot be found, this method has no effect.
364      *
365      * @param groupName the name of the group
366      * @param enabled the value of the enabled flag
367      */
368     public void enableGroup(String groupName, boolean enabled)
369     {
370         ActionHelper.enableActions(ActionHelper.fetchActionsInGroup(this,
371                 groupName), enabled);
372     }
373 
374     /**
375      * Returns the parent store.
376      *
377      * @return the parent store (can be <b>null</b>)
378      */
379     public ActionStore getParent()
380     {
381         return parent;
382     }
383 
384     /**
385      * Sets the parent store. Some of the methods also include a parent store in
386      * look up operations.
387      *
388      * @param parent the parent store
389      */
390     public void setParent(ActionStore parent)
391     {
392         this.parent = parent;
393     }
394 }