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 "anonymous" <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 }