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 }