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.di;
17  
18  import java.util.HashMap;
19  import java.util.Map;
20  import java.util.NoSuchElementException;
21  import java.util.Set;
22  import java.util.concurrent.atomic.AtomicInteger;
23  
24  import net.sf.jguiraffe.di.BeanProvider;
25  import net.sf.jguiraffe.di.BeanStore;
26  import net.sf.jguiraffe.di.ClassLoaderProvider;
27  import net.sf.jguiraffe.di.InvocationHelper;
28  import net.sf.jguiraffe.di.MutableBeanStore;
29  import net.sf.jguiraffe.di.impl.DefaultBeanStore;
30  import net.sf.jguiraffe.di.impl.DefaultClassLoaderProvider;
31  
32  import org.apache.commons.jelly.JellyContext;
33  
34  /**
35   * <p>
36   * A data class for maintaining all information required for a DI builder
37   * operation.
38   * </p>
39   * <p>
40   * An instance of this class will be present in the Jelly context when a Jelly
41   * script for populating a <code>BeanContext</code> is running. The specific tag
42   * handler classes can access the information they need for adding newly created
43   * beans to the correct <code>BeanStore</code> or for accessing required helper
44   * objects.
45   * </p>
46   * <p>
47   * One important task of this class is dealing with <code>BeanStore</code>
48   * objects: One root bean store must be provided, to which bean definitions are
49   * added per default. In a builder script, it is possible to create an arbitrary
50   * number of further bean stores below this root store. In a bean definition the
51   * target bean store (where the bean should be stored) can be specified.
52   * </p>
53   * <p>
54   * This class also maintains the current {@link InvocationHelper} instance which
55   * is responsible for reflection operations and (indirectly) for data type
56   * conversions required by the current builder script. Either an instance of
57   * {@link InvocationHelper} can be set explicitly using the
58   * {@link #setInvocationHelper(InvocationHelper)} method or a default instance
59   * will be created. If the root {@link BeanStore} is created by this object, the
60   * {@link net.sf.jguiraffe.di.ConversionHelper ConversionHelper} will be set
61   * automatically. If the root {@link BeanStore} is set manually using the
62   * {@link #initRootBeanStore(MutableBeanStore)} method, the caller is
63   * responsible for initializing the root store with the correct
64   * {@link net.sf.jguiraffe.di.ConversionHelper ConversionHelper} instance.
65   * </p>
66   * <p>
67   * Implementation note: This class is not thread-safe. It is intended to be used
68   * inside a Jelly script, which runs in a single thread.
69   * </p>
70   *
71   * @author Oliver Heger
72   * @version $Id: DIBuilderData.java 205 2012-01-29 18:29:57Z oheger $
73   */
74  public class DIBuilderData
75  {
76      /** Constant for the key for storing an instance in the Jelly context. */
77      private static final String CTX_KEY = DIBuilderData.class.getName();
78  
79      /** A counter for generating unique indices for anonymous bean providers. */
80      private static final AtomicInteger ANONYMOUS_COUNTER = new AtomicInteger();
81  
82      /** Stores the <code>ClassLoaderProvider</code> to be used. */
83      private ClassLoaderProvider classLoaderProvider;
84  
85      /** Stores a map with the known bean stores. */
86      private Map<String, MutableBeanStore> beanStores;
87  
88      /** Stores the root bean store. */
89      private MutableBeanStore rootBeanStore;
90  
91      /** The current invocation helper. */
92      private InvocationHelper invocationHelper;
93  
94      /**
95       * Creates a new instance of <code>DIBuilderData</code>.
96       */
97      public DIBuilderData()
98      {
99          beanStores = new HashMap<String, MutableBeanStore>();
100     }
101 
102     /**
103      * Returns the <code>ClassLoaderProvider</code> to be used. This method
104      * never returns <b>null</b>. If no specific
105      * <code>ClassLoaderProvider</code> has been set so far (by using the
106      * {@link #setClassLoaderProvider(ClassLoaderProvider)}
107      * method), a new default provider is created and returned, which does not
108      * contain any specific registered class loaders.
109      *
110      * @return the <code>ClassLoaderProvider</code> to be used
111      */
112     public ClassLoaderProvider getClassLoaderProvider()
113     {
114         if (classLoaderProvider == null)
115         {
116             // create default instance
117             classLoaderProvider = new DefaultClassLoaderProvider();
118         }
119         return classLoaderProvider;
120     }
121 
122     /**
123      * Sets the <code>ClassLoaderProvider</code> to be used. This object is
124      * especially required for resolving <code>ClassDescription</code> objects.
125      *
126      * @param classLoaderProvider the <code>ClassLoaderProvider</code> to be
127      *        used
128      */
129     public void setClassLoaderProvider(ClassLoaderProvider classLoaderProvider)
130     {
131         this.classLoaderProvider = classLoaderProvider;
132     }
133 
134     /**
135      * Returns the current {@link InvocationHelper}. This object can be used by
136      * all components involved in the builder process if reflection operations
137      * or data type conversions are needed. If custom type converters are
138      * needed, they have to be registered at conversion helper managed by this
139      * instance.
140      *
141      * @return the {@link InvocationHelper}
142      * @see #setInvocationHelper(InvocationHelper)
143      */
144     public InvocationHelper getInvocationHelper()
145     {
146         if (invocationHelper == null)
147         {
148             invocationHelper = new InvocationHelper();
149         }
150         return invocationHelper;
151     }
152 
153     /**
154      * Sets the {@link InvocationHelper} to be used by this object. Using this
155      * method a specific {@link InvocationHelper} instance can be set which will
156      * then be returned by {@link #getInvocationHelper()}. If
157      * {@link #getInvocationHelper()} is called and no helper object has been
158      * set, a new default instance is created automatically.
159      *
160      * @param invocationHelper the {@link InvocationHelper} to be used
161      */
162     public void setInvocationHelper(InvocationHelper invocationHelper)
163     {
164         this.invocationHelper = invocationHelper;
165     }
166 
167     /**
168      * Returns the root bean store. This method returns never <b>null</b>; if no
169      * root bean store has been set so far, a default one is created now.
170      *
171      * @return the root bean store
172      */
173     public BeanStore getRootBeanStore()
174     {
175         return getBeanStore(null);
176     }
177 
178     /**
179      * Sets the root bean store. This is the default bean store, to which all
180      * beans are added unless a specific bean store is specified. Using the
181      * <code>addBeanStore()</code> method it is possible to create further bean
182      * stores below this root store.
183      *
184      * @param rootBeanStore the root bean store
185      */
186     public void initRootBeanStore(MutableBeanStore rootBeanStore)
187     {
188         this.rootBeanStore = rootBeanStore;
189     }
190 
191     /**
192      * Returns the <code>BeanStore</code> with the specified name. The root
193      * store can be obtained by passing in <b>null</b> for the name. If a name
194      * cannot be resolved, an exception is thrown.
195      *
196      * @param name the name of the desired bean store
197      * @return the bean store with this name
198      * @throws NoSuchElementException if the name cannot be resolved
199      */
200     public BeanStore getBeanStore(String name)
201     {
202         return internalGetBeanStore(name);
203     }
204 
205     /**
206      * Returns a flag whether the bean store with the given name is present.
207      *
208      * @param name the name of the bean store in question
209      * @return a flag whether this bean store is present
210      */
211     public boolean hasBeanStore(String name)
212     {
213         return name == null || beanStores.containsKey(name);
214     }
215 
216     /**
217      * Returns a set with the names of the existing bean stores. These are the
218      * bean stores that have been added using the <code>addBeanStore()</code>
219      * method. Note that the root bean store (which is identified by the name
220      * <b>null</b>) is not contained in this set.
221      *
222      * @return a set with the names of the existing bean stores
223      */
224     public Set<String> getBeanStoreNames()
225     {
226         return beanStores.keySet();
227     }
228 
229     /**
230      * Creates a <code>BeanStore</code> with the specified name and adds it to
231      * the internal list of existing bean stores. The new store is added as a
232      * child of the specified parent bean store. If <b>null</b> is passed in for
233      * the parent, the new store will become a child of the root bean store.
234      * Note that the names of bean stores must be unique, even if they are at
235      * different levels of the hierarchy.
236      *
237      * @param name the name of the bean store to be added (must not be
238      *        <b>null</b>)
239      * @param parentName the name of the parent bean store
240      * @throws NoSuchElementException if the parent name cannot be resolved
241      * @throws IllegalArgumentException if the name is <b>null</b> or is already
242      *         used by an existing bean store
243      */
244     public void addBeanStore(String name, String parentName)
245     {
246         if (name == null)
247         {
248             throw new IllegalArgumentException(
249                     "Name of bean store must not be null!");
250         }
251         if (hasBeanStore(name))
252         {
253             throw new IllegalArgumentException(
254                     "A bean store with this name already exists: " + name);
255         }
256 
257         DefaultBeanStore store =
258                 new DefaultBeanStore(name, internalGetBeanStore(parentName));
259         beanStores.put(name, store);
260     }
261 
262     /**
263      * Adds a <code>BeanProvider</code> to a <code>BeanStore</code>. The bean
264      * store with the specified name is resolved (<b>null</b> selects the root
265      * bean store), and the provided <code>BeanProvider</code> is added to it
266      * under the given name.
267      *
268      * @param storeName the name of the bean store
269      * @param beanName the name of the bean provider (must not be <b>null</b>)
270      * @param bean the bean provider to be added (must not be <b>null</b>)
271      * @throws NoSuchElementException if the bean store cannot be resolved
272      * @throws IllegalArgumentException if the bean name or the bean provider is
273      *         <b>null</b>
274      */
275     public void addBeanProvider(String storeName, String beanName,
276             BeanProvider bean)
277     {
278         if (beanName == null)
279         {
280             throw new IllegalArgumentException(
281                     "Name of bean provider must not be null!");
282         }
283         if (bean == null)
284         {
285             throw new IllegalArgumentException(
286                     "Bean provider must not be null!");
287         }
288 
289         internalGetBeanStore(storeName).addBeanProvider(beanName, bean);
290     }
291 
292     /**
293      * Adds an &quot;anonymous&quot; <code>BeanProvider</code> to a
294      * <code>BeanStore</code>. This method can be used for beans that are
295      * visible in a local context only. It works similar to
296      * <code>addBeanProvider()</code>, but the bean store's
297      * <code>addAnonymousBeanProvider()</code> method will be called. This class
298      * will keep a counter for generating indices for anonymous bean providers.
299      * This ensures that the generated names used internally for these beans are
300      * unique.
301      *
302      * @param storeName the name of the bean store
303      * @param bean the bean provider to be added (must not be <b>null</b>)
304      * @return the name generated for the bean provider added
305      * @throws IllegalArgumentException if no provider is specified
306      */
307     public String addAnonymousBeanProvider(String storeName, BeanProvider bean)
308     {
309         if (bean == null)
310         {
311             throw new IllegalArgumentException(
312                     "Bean provider must not be null!");
313         }
314 
315         return internalGetBeanStore(storeName).addAnonymousBeanProvider(
316                 nextAnonymousIndex(), bean);
317     }
318 
319     /**
320      * Stores this instance in the specified context. It is stored there under a
321      * default key.
322      *
323      * @param context the Jelly context
324      */
325     public void put(JellyContext context)
326     {
327         context.setVariable(CTX_KEY, this);
328     }
329 
330     /**
331      * Obtains an instance of this class from the specified Jelly context. The
332      * instance is looked up under a default key, the same that is also used by
333      * the <code>put()</code> method. If no instance can be found in this
334      * context, <b>null</b> is returned.
335      *
336      * @param context the Jelly context
337      * @return the instance found in this context
338      */
339     public static DIBuilderData get(JellyContext context)
340     {
341         return (DIBuilderData) context.getVariable(CTX_KEY);
342     }
343 
344     /**
345      * Returns the internal bean store with the given name. Internally this
346      * class operates on mutable {@link DefaultBeanStore} objects. To its
347      * clients only the immutable {@link BeanStore} interface is exposed.
348      *
349      * @param name the name of the bean store to be retrieved (<b>null</b> means
350      *        the root bean store)
351      * @return the bean store with this name
352      * @throws NoSuchElementException if the bean store cannot be resolved
353      */
354     protected MutableBeanStore internalGetBeanStore(String name)
355     {
356         if (name == null)
357         {
358             // root bean store already created?
359             if (rootBeanStore == null)
360             {
361                 rootBeanStore = createRootStore();
362             }
363             return rootBeanStore;
364         }
365 
366         MutableBeanStore result = beanStores.get(name);
367         if (result == null)
368         {
369             throw new NoSuchElementException("Unknown bean store: " + name);
370         }
371         return result;
372     }
373 
374     /**
375      * Creates the root bean store. This method is called by
376      * <code>internalGetBeanStore()</code> when the root bean store is accessed
377      * for the first time and has not been explicitly initialized.
378      *
379      * @return the new root bean store
380      */
381     protected MutableBeanStore createRootStore()
382     {
383         DefaultBeanStore root = new DefaultBeanStore();
384         root.setConversionHelper(getInvocationHelper().getConversionHelper());
385         return root;
386     }
387 
388     /**
389      * Returns the next index for an anonymous bean provider.
390      *
391      * @return the next index
392      */
393     private static int nextAnonymousIndex()
394     {
395         return ANONYMOUS_COUNTER.incrementAndGet();
396     }
397 }