KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > jodd > petite > PetiteContainer


1 // Copyright (c) 2003-2007, Jodd Team (jodd.sf.net). All Rights Reserved.
2

3 package jodd.petite;
4
5 import jodd.petite.scope.Scope;
6 import jodd.petite.scope.SingletonScope;
7 import jodd.petite.scope.DefaultScope;
8 import jodd.petite.meta.PetiteBean;
9 import jodd.petite.meta.PetiteBeanRef;
10 import jodd.util.StringUtil;
11 import jodd.introspector.DefaultIntrospector;
12 import jodd.introspector.ClassDescriptor;
13 import jodd.bean.BeanUtil;
14
15 import java.util.*;
16 import java.lang.reflect.Field JavaDoc;
17 import java.lang.reflect.Constructor JavaDoc;
18 import java.lang.annotation.Annotation JavaDoc;
19
20 /**
21  * Petite Container.
22  */

23 public class PetiteContainer {
24
25     // ---------------------------------------------------------------- get beans
26

27     /**
28      * Returns Petite bean instance.
29      * Parameter is name of registered petite bean. Bean can be registered with
30      * explicit name using {@link PetiteContainer#register(String, Class, Class)}
31      * or by named generated from its class {@link PetiteContainer#register(Class, Class)}
32      * (from optional annotation or simple type name).
33      * <p>
34      * Petite container will find the bean in corresponding scope and all its dependecies,
35      * either by constructor or property injection. When using constructor injection, cyclic dependecies
36      * can not be prevented, but they are at least detected.
37      *
38      * @see PetiteContainer#create(Class)
39      */

40     public Object JavaDoc getBean(String JavaDoc name) {
41         return getBean(name, new HashMap<String JavaDoc, Object JavaDoc>());
42     }
43
44     /**
45      * Returns petite bean instance.
46      */

47     protected Object JavaDoc getBean(String JavaDoc name, Map<String JavaDoc, Object JavaDoc> acquiredBeans) {
48
49         // First check if bean is already acquired within this call.
50
// This prevents cyclic dependencies problem. It is expected than single
51
// object tree path contains less elements, therefore, this search is faster
52
// then the next one.
53
Object JavaDoc bean = acquiredBeans.get(name);
54         if (bean != null) {
55             if (bean == Void.TYPE) {
56                 throw new PetiteException("Cycle dependecies on constructor injection detected!");
57             }
58             return bean;
59         }
60
61         // Lookup for registered bean definition.
62
BeanDef def = beanNames.get(name);
63         if (def == null) {
64             return null;
65         }
66
67         // Find the bean in its scope
68
bean = def.scopeLookup();
69         if (bean == null) {
70             // Create a new bean in scope
71
bean = newBeanInstance(def.name, def.type, acquiredBeans);
72             wire(bean, acquiredBeans);
73             def.scopeRegister(bean);
74         }
75         return bean;
76
77     }
78
79
80     // ---------------------------------------------------------------- create
81

82     /**
83      * Creates a new intance of specified type and wires it with the container.
84      * <p>
85      * If type is actually a petite bean (has annotation), {@link #getBean(String)} will be invoked.
86      * <p>
87      * Otherwise, a new instance will be created and then wired with container beans.
88      * Since in this case is not possible to check if specified type is registered in container,
89      * newly created instance is <b>not</b> in the petite scopes! Thats important for cyclic dependencies
90      * cases, where the same type is acquired later in the bean-graph/
91      */

92     @SuppressWarnings JavaDoc({"unchecked"})
93     public Object JavaDoc create(Class JavaDoc type) {
94         PetiteBean ann = (PetiteBean) type.getAnnotation(PetiteBean.class);
95         if (ann != null) {
96             return getBean(ann.value().trim());
97         }
98         Map<String JavaDoc, Object JavaDoc> acquiredBeans = new HashMap<String JavaDoc, Object JavaDoc>();
99         Object JavaDoc bean = newBeanInstance(null, type, acquiredBeans);
100         wire(bean, acquiredBeans);
101         return bean;
102     }
103
104     // ---------------------------------------------------------------- new instance
105

106     /**
107      * Cache for constructors per type.
108      */

109     protected Map<Class JavaDoc, Object JavaDoc[]> beanCtors = new HashMap<Class JavaDoc, Object JavaDoc[]>();
110
111     /**
112      * Creates new petite bean or type instance and performs constructor injection.
113      * This method may be called from {@link #create(Class)} and {@link #getBean(String)}, therefore
114      * the frist argument (bean name) also indicates if the bean is inside the container (not null).
115      * <p>
116      * After creating a bean, it will be added to acquired beans (if it is from the container).
117      *
118      * @param name optional bean name, if set then it is a name under which petite bean was registered
119      * @param type petite bean type
120      * @param acquiredBeans acquired beans until this moment
121      */

122     protected Object JavaDoc newBeanInstance(String JavaDoc name, Class JavaDoc type, Map<String JavaDoc, Object JavaDoc> acquiredBeans) {
123         Object JavaDoc[] ctorData = beanCtors.get(type);
124         if (ctorData == null) {
125             ClassDescriptor cd = DefaultIntrospector.lookup(type);
126             Constructor JavaDoc[] allCtors = cd.getAllCtors(true);
127             boolean founded = false;
128             List<Annotation JavaDoc> annotations = new ArrayList<Annotation JavaDoc>();
129             nextCtor:
130             for (Constructor JavaDoc actor : allCtors) {
131                 annotations.clear();
132                 Annotation JavaDoc[][] paramAnnotations = actor.getParameterAnnotations();
133                 for (Annotation JavaDoc[] anns : paramAnnotations) {
134                     boolean has = false;
135                     for (Annotation JavaDoc a : anns) {
136                         if (a.annotationType() == PetiteBeanRef.class) {
137                             annotations.add(a);
138                             has = true;
139                             break;
140                         }
141                     }
142                     if (has == false) {
143                         continue nextCtor;
144                     }
145                 }
146                 // suitable constructor found
147
Annotation JavaDoc[] beanRefAnnotations = annotations.toArray(new Annotation JavaDoc[annotations.size()]);
148                 ctorData = new Object JavaDoc[] {actor, actor.getParameterTypes(), beanRefAnnotations};
149                 Object JavaDoc[] previous = beanCtors.get(type);
150                 boolean addOk = false;
151                 if (previous == null) {
152                     addOk = true;
153                 } else {
154                     if (((Class JavaDoc[]) previous[1]).length == 0) { // previous was default ctor
155
addOk = true;
156                     } else if (((Class JavaDoc[]) ctorData[1]).length == 0) { // current is a default ctor, ignore
157
ctorData = previous;
158                         continue;
159                     }
160                 }
161                 if (addOk == true) {
162                     beanCtors.put(type, ctorData);
163                 } else {
164                     throw new PetiteException("Type '" + type.getName() + "' contains more than one suitable constructor.");
165                 }
166                 founded = true;
167             }
168             if (founded == false) {
169                 throw new PetiteException("Type '" + type.getName() + "' has no default or suitable constructor.");
170             }
171         }
172
173         Class JavaDoc[] args = (Class JavaDoc[]) ctorData[1];
174         if (args.length == 0) {
175             Object JavaDoc bean;
176             try {
177                 bean = type.newInstance();
178             } catch (Exception JavaDoc ex) {
179                 throw new PetiteException("Unable to create new bean instance '" + type + "' using default constructor.", ex);
180             }
181             if (name != null) {
182                 acquiredBeans.put(name, bean);
183             }
184             return bean;
185         }
186
187         if (name != null) {
188             acquiredBeans.put(name, Void.TYPE); // puts a dummy marker for cyclic dependency check
189
}
190         Constructor JavaDoc c = (Constructor JavaDoc) ctorData[0];
191         Object JavaDoc[] argObjs = new Object JavaDoc[args.length];
192         for (int i = 0; i < args.length; i++) {
193             Class JavaDoc arg = args[i];
194             PetiteBeanRef pbr = (PetiteBeanRef) ((Annotation JavaDoc[]) ctorData[2])[i];
195             String JavaDoc beanRef = pbr.value();
196             if (beanRef.length() == 0) {
197                 beanRef = StringUtil.uncapitalize(arg.getSimpleName());
198             }
199             argObjs[i] = getBean(beanRef, acquiredBeans);
200             if (argObjs[i] == null) {
201                 throw new PetiteException("Reference '" + beanRef + "' not found for constructor '" + c + "'.");
202             }
203         }
204         Object JavaDoc bean;
205         try {
206             bean = c.newInstance(argObjs);
207         } catch (Exception JavaDoc ex) {
208             throw new PetiteException("Unable to create new bean instance '" + type + "' using constructor: '" + c + "'.", ex);
209         }
210         if (name != null) {
211             acquiredBeans.put(name, bean);
212         }
213         return bean;
214     }
215
216     // ---------------------------------------------------------------- wire
217

218     /**
219      * Cache for references fields per type.
220      */

221     protected Map<Class JavaDoc<? extends Object JavaDoc>, Field JavaDoc[]> beanReferences = new HashMap<Class JavaDoc<? extends Object JavaDoc>, Field JavaDoc[]>();
222
223     /**
224      * Wires beans by injecting instances in properties marked with {@link PetiteBeanRef} annotation.
225      */

226     public void wire(Object JavaDoc bean) {
227         wire(bean, new HashMap<String JavaDoc, Object JavaDoc>());
228     }
229
230     /**
231      * Wires beans by injecting instances in properties marked with {@link PetiteBeanRef} annotation.
232      */

233     protected void wire(Object JavaDoc bean, Map<String JavaDoc, Object JavaDoc> acquiredBeans) {
234         Class JavaDoc<? extends Object JavaDoc> type = bean.getClass();
235         Field JavaDoc[] fields = beanReferences.get(type);
236         if (fields == null) {
237             ClassDescriptor cd = DefaultIntrospector.lookup(type);
238             ArrayList<Field JavaDoc> list = new ArrayList<Field JavaDoc>();
239             Field JavaDoc[] allFields = cd.getAllFields(true);
240             for (Field JavaDoc field : allFields) {
241                 PetiteBeanRef ref = field.getAnnotation(PetiteBeanRef.class);
242                 if (ref == null) {
243                     continue;
244                 }
245                 list.add(field);
246             }
247             fields = list.toArray(new Field JavaDoc[list.size()]);
248             beanReferences.put(type, fields);
249         }
250
251         for (Field JavaDoc field : fields) {
252             PetiteBeanRef ref = field.getAnnotation(PetiteBeanRef.class);
253             String JavaDoc refName = ref.value().trim();
254             if (refName.length() == 0) {
255                 refName = field.getName();
256             }
257             Object JavaDoc value = getBean(refName, acquiredBeans);
258             if (value == null) {
259                 throw new PetiteException("Reference '" + refName + "' not found for property '"+ type.toString().substring(6) + '#' + field.getName() + "'.");
260             }
261             BeanUtil.setDeclaredProperty(bean, field.getName(), value);
262         }
263     }
264
265     // ---------------------------------------------------------------- scopes
266

267     protected Class JavaDoc<? extends Scope> defaultScope = SingletonScope.class;
268
269     /**
270      * Returns default scope type.
271      */

272     public Class JavaDoc<? extends Scope> getDefaultScope() {
273         return defaultScope;
274     }
275
276     /**
277      * Sets default scope type.
278      */

279     public void setDefaultScope(Class JavaDoc<? extends Scope> defaultScope) {
280         if (defaultScope == DefaultScope.class) {
281             throw new PetiteException("Default Petite bean scope must be a concrete scope implementation.");
282         }
283         this.defaultScope = defaultScope;
284     }
285
286     // ---------------------------------------------------------------- registration
287

288     /**
289      * Map of all beans definitions.
290      */

291     protected Map<String JavaDoc, BeanDef> beanNames = new HashMap<String JavaDoc, BeanDef>();
292
293     /**
294      * Map of all bean scopes.
295      */

296     protected Map<Class JavaDoc<? extends Scope>, Scope> scopes = new HashMap<Class JavaDoc<? extends Scope>, Scope>();
297
298
299     /**
300      * Registers petite bean class. If class is not annotate, it will be registered with default scope.
301      * @see PetiteContainer#register(Class, Class)
302      * @see PetiteContainer#register(String, Class, Class)
303      */

304     public void register(Class JavaDoc type) {
305         register(type, defaultScope);
306     }
307
308     /**
309      * Registers petite bean class within specified scope. Class may be annotated, but this is not required.
310      * If annotation is omitted, bean will be registered with uncapitalized simple type name and defaults.
311      * @see PetiteContainer#register(String, Class, Class)
312      */

313     @SuppressWarnings JavaDoc({"unchecked"})
314     public void register(Class JavaDoc type, Class JavaDoc<? extends Scope> scopeType) {
315         PetiteBean petiteBean = (PetiteBean) type.getAnnotation(PetiteBean.class);
316         String JavaDoc name = null;
317         if (petiteBean != null) {
318             name = petiteBean.value().trim();
319             scopeType = petiteBean.scope();
320         }
321         if ((name == null) || (name.length() == 0)) {
322             name = StringUtil.uncapitalize(type.getSimpleName());
323         }
324         register(name, type, scopeType);
325     }
326
327
328     /**
329      * Registers any class as petite bean class with default scope.
330      * @see PetiteContainer#register(String, Class, Class)
331      */

332     public void register(String JavaDoc name, Class JavaDoc type) {
333         register(name, type, defaultScope);
334     }
335
336     /**
337      * Registers any class as petite bean class. {@link PetiteBean} annotation is not required
338      * and is ignored during this registration. Bean name must be unique.
339      */

340     public void register(String JavaDoc name, Class JavaDoc type, Class JavaDoc<? extends Scope> scopeType) {
341         if (scopeType == DefaultScope.class) {
342             scopeType = defaultScope;
343         }
344         Scope scope = scopes.get(scopeType);
345         if (scope == null) {
346             try {
347                 scope = scopeType.newInstance();
348                 scopes.put(scopeType, scope);
349             } catch (Exception JavaDoc ex) {
350                 throw new PetiteException("Unable to create the Petite scope: '" + scopeType + "'.", ex);
351             }
352         }
353         if (beanNames.put(name, new BeanDef(name, type, scope)) != null) {
354             throw new PetiteException("Petite bean already registered with name '" + name + "'.");
355         }
356     }
357
358     // ---------------------------------------------------------------- remove
359

360     /**
361      * Removes bean definition from the container. Returns <code>true</code> if bean specified by provided name
362      * was removed from the container, otherwise it returns <code>false</code>.
363      * @see PetiteContainer#remove(Class)
364      */

365     public boolean remove(String JavaDoc name) {
366         return beanNames.remove(name) != null;
367     }
368
369     /**
370      * Removes petite bean from the container. Returns <code>true</code> if bean specified by provided name
371      * was removed from the container, otherwise it returns <code>false</code>.
372      * @see PetiteContainer#remove(String)
373      */

374     @SuppressWarnings JavaDoc({"unchecked"})
375     public boolean remove(Class JavaDoc type) {
376         PetiteBean petiteBean = (PetiteBean) type.getAnnotation(PetiteBean.class);
377         String JavaDoc name = null;
378         if (petiteBean != null) {
379             name = petiteBean.value().trim();
380         }
381         if ((name == null) || (name.length() == 0)) {
382             name = StringUtil.uncapitalize(type.getSimpleName());
383         }
384         return beanNames.remove(name) != null;
385     }
386
387
388     // ---------------------------------------------------------------- stats
389

390     /**
391      * Returns total number of registered beans.
392      */

393     public int getTotalBeans() {
394         return beanNames.size();
395     }
396
397     /**
398      * Return total number of used scopes.
399      */

400     public int getTotalScopes() {
401         return scopes.size();
402     }
403
404 }
405
Popular Tags