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.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 }