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.components;
17
18 import java.util.Collections;
19 import java.util.LinkedHashMap;
20 import java.util.LinkedHashSet;
21 import java.util.Map;
22 import java.util.NoSuchElementException;
23 import java.util.Set;
24
25 import net.sf.jguiraffe.di.BeanContext;
26 import net.sf.jguiraffe.gui.forms.ComponentHandler;
27
28 import org.apache.commons.jelly.JellyContext;
29
30 /**
31 * <p>
32 * This class represents a group of components.
33 * </p>
34 * <p>
35 * The form builder library supports adding components to logical groups.
36 * Logical in this context means that these groups do not have a direct
37 * representation on the generated GUI. They exist only during the builder
38 * process and can be used to reference components. Examples include
39 * constructing radio groups or composite component handlers.
40 * </p>
41 * <p>
42 * A <code>ComponentGroup</code> instance does not contain the components
43 * themselves, but rather their names. For obtaining the corresponding
44 * components access to a {@link ComponentBuilderData} object is required.
45 * </p>
46 *
47 * @author Oliver Heger
48 * @version $Id: ComponentGroup.java 205 2012-01-29 18:29:57Z oheger $
49 */
50 public class ComponentGroup
51 {
52 /**
53 * Constant for the prefix under which groups are stored in the jelly
54 * context.
55 */
56 private static final String GROUP_PREFIX = "group/";
57
58 /** Stores the names of the components that belong to this group. */
59 private final Set<String> componentNames;
60
61 /**
62 * Creates a new instance of <code>ComponentGroup</code>.
63 */
64 public ComponentGroup()
65 {
66 componentNames = new LinkedHashSet<String>();
67 }
68
69 /**
70 * Returns a set with the names of all components contained in this group.
71 * The order of elements in the set corresponds to the order in which the
72 * components have been added to this group.
73 *
74 * @return a set with the names of all contained components
75 */
76 public Set<String> getComponentNames()
77 {
78 return Collections.unmodifiableSet(componentNames);
79 }
80
81 /**
82 * Adds a component to this group.
83 *
84 * @param name the name of the component to add
85 */
86 public void addComponent(String name)
87 {
88 componentNames.add(name);
89 }
90
91 /**
92 * Returns a map with all components this group refers to. This method looks
93 * up all component names in the specified {@code ComponentBuilderData}
94 * object. The resulting map contains the names of the components as keys
95 * and the corresponding component objects as values. If a component name
96 * cannot be resolved, a {@code FormBuilderException} exception is thrown.
97 *
98 * @param data the {@code ComponentBuilderData} object (must not be
99 * <b>null</b>)
100 * @return a map with all components that belong to this group
101 * @throws FormBuilderException if a component cannot be resolved
102 * @throws IllegalArgumentException if the {@code ComponentBuilderData}
103 * object is <b>null</b>
104 */
105 public Map<String, Object> getComponents(ComponentBuilderData data)
106 throws FormBuilderException
107 {
108 if (data == null)
109 {
110 throw new IllegalArgumentException(
111 "ComponentBuilderData must not be null!");
112 }
113 Map<String, Object> comps = new LinkedHashMap<String, Object>();
114
115 for (String name : componentNames)
116 {
117 Object comp = data.getComponent(name);
118 if (comp == null)
119 {
120 throw new FormBuilderException("Component cannot be resolved: "
121 + name);
122 }
123 comps.put(name, comp);
124 }
125
126 return comps;
127 }
128
129 /**
130 * Returns a map with the {@code ComponentHandler} objects of the components
131 * this group refers to. This method works like
132 * {@link #getComponents(ComponentBuilderData)}, but the resulting map
133 * contains the {@code ComponentHandler}s rather than the components
134 * themselves.
135 *
136 * @param data the {@code ComponentBuilderData} object (must not be
137 * <b>null</b>)
138 * @return a map with all component handlers that belong to this group
139 * @throws FormBuilderException if a component cannot be resolved
140 * @throws IllegalArgumentException if the {@code ComponentBuilderData}
141 * object is <b>null</b>
142 */
143 public Map<String, ComponentHandler<?>> getComponentHandlers(
144 ComponentBuilderData data) throws FormBuilderException
145 {
146 if (data == null)
147 {
148 throw new IllegalArgumentException(
149 "ComponentBuilderData must not be null!");
150 }
151 Map<String, ComponentHandler<?>> handlers =
152 new LinkedHashMap<String, ComponentHandler<?>>();
153
154 for (String name : componentNames)
155 {
156 ComponentHandler<?> handler = data.getComponentHandler(name);
157 if (handler == null)
158 {
159 throw new FormBuilderException(
160 "ComponentHandler cannot be resolved: " + name);
161 }
162 handlers.put(name, handler);
163 }
164
165 return handlers;
166 }
167
168 /**
169 * Changes the enabled state of this {@code ComponentGroup}. This method
170 * obtains the {@code ComponentHandler} objects for all components that
171 * belong to this group. Then it calls the {@code setEnabled()} method on
172 * all these handlers.
173 *
174 * @param data the {@code ComponentBuilderData} object (must not be
175 * <b>null</b>)
176 * @param enabled the new enabled flag
177 * @see #getComponentHandlers(ComponentBuilderData)
178 * @throws FormBuilderException if a component cannot be resolved
179 * @throws IllegalArgumentException if the {@code ComponentBuilderData}
180 * object is <b>null</b>
181 */
182 public void enableGroup(ComponentBuilderData data, boolean enabled)
183 throws FormBuilderException
184 {
185 for (ComponentHandler<?> handler : getComponentHandlers(data).values())
186 {
187 handler.setEnabled(enabled);
188 }
189 }
190
191 /**
192 * Fetches the group with the given name from the specified jelly context.
193 * If the group does not exist, an exception will be thrown.
194 *
195 * @param context the jelly context (must not be <b>null</b>
196 * @param name the name of the desired group
197 * @return the group with this name
198 * @throws IllegalArgumentException if the context is <b>null</b>
199 * @throws NoSuchElementException if there is no such group
200 */
201 public static ComponentGroup fromContext(JellyContext context, String name)
202 throws NoSuchElementException
203 {
204 ComponentGroup result = fetchGroup(context, name);
205 if (result == null)
206 {
207 throw new NoSuchElementException("No group with name '" + name
208 + "' found!");
209 }
210 return result;
211 }
212
213 /**
214 * Tests whether a group with the specified name exists in the given jelly
215 * context. Note that {@code ComponentGroup} objects are not stored under
216 * their name in the context, but a specific prefix is used. So always this
217 * method has to be used to check the existence of a group rather than
218 * testing the context directly.
219 *
220 * @param context the jelly context
221 * @param name the name of the group
222 * @return <b>true </b> if there is such a group, <b>false </b> otherwise
223 * @throws IllegalArgumentException if the context is <b>null</b>
224 */
225 public static boolean groupExists(JellyContext context, String name)
226 {
227 return fetchGroup(context, name) != null;
228 }
229
230 /**
231 * Stores a component group in the jelly context under a given name.
232 *
233 * @param context the context
234 * @param name the group's name
235 * @param group the group to store (if <b>null </b>, the group will be
236 * removed if it exists)
237 */
238 public static void storeGroup(JellyContext context, String name,
239 ComponentGroup group)
240 {
241 checkContext(context);
242 if (group == null)
243 {
244 context.removeVariable(GROUP_PREFIX + name);
245 }
246 else
247 {
248 context.setVariable(GROUP_PREFIX + name, group);
249 }
250 }
251
252 /**
253 * Creates a new {@code ComponentGroup} instance and stores it in the
254 * specified context. This is a convenience method which performs the
255 * following steps:
256 * <ol>
257 * <li>It checks whether already a group with the specified name exists in
258 * the context. If this is the case, an exception is thrown.</li>
259 * <li>Otherwise a new {@code ComponentGroup} instance is created.</li>
260 * <li>The new instance is stored in the context under the specified name.</li>
261 * <li>The newly created instance is returned.</li>
262 * </ol>
263 *
264 * @param context the Jelly context (must not be <b>null</b>
265 * @param name the name of the new {@code ComponentGroup} (must not be
266 * <b>null</b>)
267 * @return the newly created {@code ComponentGroup} instance
268 * @throws FormBuilderException if a group with this name already exists
269 * @throws IllegalArgumentException if the group name or the context is
270 * <b>null</b>
271 */
272 public static ComponentGroup createGroup(JellyContext context, String name)
273 throws FormBuilderException
274 {
275 if (name == null)
276 {
277 throw new IllegalArgumentException("Group name must not be null!");
278 }
279 if (groupExists(context, name))
280 {
281 throw new FormBuilderException(String.format(
282 "A group with the name %s already exists!", name));
283 }
284
285 ComponentGroup group = new ComponentGroup();
286 storeGroup(context, name, group);
287 return group;
288 }
289
290 /**
291 * Obtains the {@code ComponentGroup} with the specified name from the given
292 * {@code BeanContext}. This method is similar to
293 * {@link #fromContext(JellyContext, String)}, but the {@code
294 * ComponentGroup} is resolved from a {@code BeanContext} object. This can
295 * be useful if the group is to be obtained after a builder operation. In
296 * this case, the Jelly context may not be available directly, but it can be
297 * accessed through the {@code BeanContext} returned by the builder.
298 *
299 * @param context the {@code BeanContext}
300 * @param groupName the name of the group to be obtained
301 * @return the corresponding {@code ComponentGroup} instance
302 * @throws IllegalArgumentException if the {@code BeanContext} is
303 * <b>null</b>
304 * @throws net.sf.jguiraffe.di.InjectionException if the group cannot be resolved
305 */
306 public static ComponentGroup fromBeanContext(BeanContext context,
307 String groupName)
308 {
309 checkContext(context);
310 return (ComponentGroup) context.getBean(GROUP_PREFIX + groupName);
311 }
312
313 /**
314 * Tests whether a group with the specified name exists in the given {@code
315 * BeanContext}. Works like {@link #groupExists(JellyContext, String)}, but
316 * checks the given {@code BeanContext}.
317 *
318 * @param context the {@code BeanContext}
319 * @param groupName the name of the group in question
320 * @return a flag whether this {@code ComponentGroup} can be found in this
321 * {@code BeanContext}
322 * @throws IllegalArgumentException if the {@code BeanContext} is
323 * <b>null</b>
324 */
325 public static boolean groupExistsInBeanContext(BeanContext context,
326 String groupName)
327 {
328 checkContext(context);
329 return context.containsBean(GROUP_PREFIX + groupName);
330 }
331
332 /**
333 * Helper method for fetching a component group from the jelly context.
334 *
335 * @param context the context
336 * @param name the group's name
337 * @return the found group or <b>null </b> if it does not exist
338 * @throws IllegalArgumentException if the context is <b>null</b>
339 */
340 private static ComponentGroup fetchGroup(JellyContext context, String name)
341 {
342 checkContext(context);
343 return (ComponentGroup) context.getVariable(GROUP_PREFIX + name);
344 }
345
346 /**
347 * Tests the passed in context object. This method throws an exception if
348 * the context is <b>null</b>.
349 *
350 * @param context the context
351 * @throws IllegalArgumentException if no context is passed
352 */
353 private static void checkContext(Object context)
354 {
355 if (context == null)
356 {
357 throw new IllegalArgumentException("Context must not be null!");
358 }
359 }
360 }