001: // Copyright (c) 2003-2007, Jodd Team (jodd.sf.net). All Rights Reserved.
002:
003: package jodd.petite;
004:
005: import jodd.petite.scope.Scope;
006: import jodd.petite.scope.SingletonScope;
007: import jodd.petite.scope.DefaultScope;
008: import jodd.petite.scope.ScopeReplacer;
009: import jodd.petite.meta.PetiteBean;
010: import jodd.petite.meta.PetiteInject;
011: import jodd.util.StringUtil;
012: import jodd.introspector.DefaultIntrospector;
013: import jodd.introspector.ClassDescriptor;
014: import jodd.bean.BeanUtil;
015:
016: import java.util.ArrayList;
017: import java.util.HashMap;
018: import java.util.Iterator;
019: import java.util.List;
020: import java.util.Map;
021: import java.lang.reflect.Field;
022: import java.lang.reflect.Constructor;
023: import java.lang.annotation.Annotation;
024:
025: /**
026: * Petite Container.
027: */
028: public class PetiteContainer {
029:
030: // ---------------------------------------------------------------- get beans
031:
032: /**
033: * Returns Petite bean instance.
034: * Parameter is name of registered petite bean. Bean can be registered with
035: * explicit name using {@link PetiteContainer#register(String, Class, Class)}
036: * or by named generated from its class {@link PetiteContainer#register(Class, Class)}
037: * (from optional annotation or simple type name).
038: * <p>
039: * Petite container will find the bean in corresponding scope and all its dependecies,
040: * either by constructor or property injection. When using constructor injection, cyclic dependecies
041: * can not be prevented, but they are at least detected.
042: *
043: * @see PetiteContainer#create(Class)
044: */
045: public Object getBean(String name) {
046: return getBean(name, new HashMap<String, Object>());
047: }
048:
049: /**
050: * Returns petite bean instance.
051: */
052: protected Object getBean(String name,
053: Map<String, Object> acquiredBeans) {
054:
055: // First check if bean is already acquired within this call.
056: // This prevents cyclic dependencies problem. It is expected than single
057: // object tree path contains less elements, therefore, this search is faster
058: // then the next one.
059: Object bean = acquiredBeans.get(name);
060: if (bean != null) {
061: if (bean == Void.TYPE) {
062: throw new PetiteException(
063: "Cycle dependecies on constructor injection detected!");
064: }
065: return bean;
066: }
067:
068: // Lookup for registered bean definition.
069: BeanDef def = beanNames.get(name);
070: if (def == null) {
071: return null;
072: }
073:
074: // Find the bean in its scope
075: bean = def.scopeLookup();
076: if (bean == null) {
077: // Create a new bean in the scope
078: bean = newBeanInstance(def.name, def.type, acquiredBeans);
079: wire(bean, acquiredBeans);
080: def.scopeRegister(bean);
081: }
082: return bean;
083:
084: }
085:
086: // ---------------------------------------------------------------- create
087:
088: /**
089: * Creates a new intance of specified type and wires it with the container.
090: * <p>
091: * If type is actually a petite bean (has annotation), {@link #getBean(String)} will be invoked.
092: * <p>
093: * Otherwise, a new instance will be created and then wired with container beans.
094: * Since in this case is not possible to check if specified type is registered in container,
095: * newly created instance is <b>not</b> present in the petite scopes!
096: * That is important for cyclic dependencies cases, where the same type is acquired
097: * later in the bean-graph.
098: */
099: @SuppressWarnings({"unchecked"})
100: public Object create(Class type) {
101: PetiteBean ann = (PetiteBean) type
102: .getAnnotation(PetiteBean.class);
103: if (ann != null) {
104: return getBean(ann.value().trim());
105: }
106: Map<String, Object> acquiredBeans = new HashMap<String, Object>();
107: Object bean = newBeanInstance(null, type, acquiredBeans);
108: wire(bean, acquiredBeans);
109: return bean;
110: }
111:
112: // ---------------------------------------------------------------- new instance
113:
114: /**
115: * Cache for constructors per type.
116: */
117: protected Map<Class, Object[]> beanCtors = new HashMap<Class, Object[]>();
118:
119: /**
120: * Creates new petite bean or type instance and performs constructor injection.
121: * This method may be called from {@link #create(Class)} and {@link #getBean(String)}, therefore
122: * the frist argument (bean name) also indicates if the bean is inside the container (when is not null).
123: * <p>
124: * After creating a bean, it will be added to acquired beans (if it is from the container).
125: *
126: * @param name optional bean name, if set then it is a name under which petite bean was registered
127: * @param type petite bean type
128: * @param acquiredBeans acquired beans until this moment
129: */
130: protected Object newBeanInstance(String name, Class type,
131: Map<String, Object> acquiredBeans) {
132: Object[] ctorData = beanCtors.get(type);
133: if (ctorData == null) {
134: ClassDescriptor cd = DefaultIntrospector.lookup(type);
135: Constructor[] allCtors = cd.getAllCtors(true);
136: boolean founded = false;
137: List<Annotation> annotations = new ArrayList<Annotation>();
138: nextCtor: for (Constructor actor : allCtors) {
139: annotations.clear();
140: Annotation[][] paramAnnotations = actor
141: .getParameterAnnotations();
142: for (Annotation[] anns : paramAnnotations) {
143: boolean has = false;
144: for (Annotation a : anns) {
145: if (a.annotationType() == PetiteInject.class) {
146: annotations.add(a);
147: has = true;
148: break;
149: }
150: }
151: if (has == false) {
152: continue nextCtor;
153: }
154: }
155: // suitable constructor found
156: Annotation[] beanRefAnnotations = annotations
157: .toArray(new Annotation[annotations.size()]);
158: ctorData = new Object[] { actor,
159: actor.getParameterTypes(), beanRefAnnotations };
160: Object[] previous = beanCtors.get(type);
161: boolean addOk = false;
162: if (previous == null) {
163: addOk = true;
164: } else {
165: if (((Class[]) previous[1]).length == 0) { // previous was default ctor
166: addOk = true;
167: } else if (((Class[]) ctorData[1]).length == 0) { // current is a default ctor, ignore
168: ctorData = previous;
169: continue;
170: }
171: }
172: if (addOk == true) {
173: beanCtors.put(type, ctorData);
174: } else {
175: throw new PetiteException(
176: "Type '"
177: + type.getName()
178: + "' contains more than one suitable constructor.");
179: }
180: founded = true;
181: }
182: if (founded == false) {
183: throw new PetiteException("Type '" + type.getName()
184: + "' has no default or suitable constructor.");
185: }
186: }
187:
188: Class[] args = (Class[]) ctorData[1];
189: // default ctors
190: if (args.length == 0) {
191: Object bean;
192: try {
193: bean = type.newInstance();
194: } catch (Exception ex) {
195: throw new PetiteException(
196: "Unable to create new bean instance '" + type
197: + "' using default constructor.", ex);
198: }
199: if (name != null) {
200: acquiredBeans.put(name, bean);
201: }
202: return bean;
203: }
204:
205: // other ctors
206: if (name != null) {
207: acquiredBeans.put(name, Void.TYPE); // puts a dummy marker for cyclic dependency check
208: }
209: Constructor c = (Constructor) ctorData[0];
210: Object[] argObjs = new Object[args.length];
211: for (int i = 0; i < args.length; i++) {
212: Class arg = args[i];
213: PetiteInject pbr = (PetiteInject) ((Annotation[]) ctorData[2])[i];
214: String beanRef = pbr.value();
215: if (beanRef.length() == 0) {
216: beanRef = StringUtil.uncapitalize(arg.getSimpleName());
217: }
218: argObjs[i] = getBean(beanRef, acquiredBeans);
219: if (argObjs[i] == null) {
220: throw new PetiteException("Reference '" + beanRef
221: + "' not found for constructor '" + c + "'.");
222: }
223: }
224: Object bean;
225: try {
226: bean = c.newInstance(argObjs);
227: } catch (Exception ex) {
228: throw new PetiteException(
229: "Unable to create new bean instance '" + type
230: + "' using constructor: '" + c + "'.", ex);
231: }
232: if (name != null) {
233: acquiredBeans.put(name, bean);
234: }
235: return bean;
236: }
237:
238: // ---------------------------------------------------------------- wire
239:
240: protected boolean trackBeanWiring;
241:
242: public boolean isTrackBeanWiring() {
243: return trackBeanWiring;
244: }
245:
246: /**
247: * Stores additional information about targets where bean was wired in.
248: * This will lower performance and raise memory usage. This data are needed
249: * for run-time bean replacing.
250: */
251: public void setTrackBeanWiring(boolean trackBeanWiring) {
252: this .trackBeanWiring = trackBeanWiring;
253: }
254:
255: /**
256: * Cache of reference (i.e. annotated) fields for one type.
257: */
258: protected Map<Class, Field[]> beanReferences = new HashMap<Class, Field[]>();
259:
260: /**
261: * Wires beans by injecting instances in properties marked with {@link PetiteInject} annotation.
262: * @see #wire(Object, java.util.Map)
263: */
264: public void wire(Object bean) {
265: wire(bean, new HashMap<String, Object>());
266: }
267:
268: /**
269: * Wires beans by injecting instances in properties marked with {@link PetiteInject} annotation.
270: */
271: protected void wire(Object bean, Map<String, Object> acquiredBeans) {
272: Class type = bean.getClass();
273: Field[] fields = beanReferences.get(type);
274: if (fields == null) {
275: ClassDescriptor cd = DefaultIntrospector.lookup(type);
276: ArrayList<Field> list = new ArrayList<Field>();
277: Field[] allFields = cd.getAllFields(true);
278: for (Field field : allFields) {
279: PetiteInject ref = field
280: .getAnnotation(PetiteInject.class);
281: if (ref == null) {
282: continue;
283: }
284: list.add(field);
285: }
286: fields = list.toArray(new Field[list.size()]);
287: beanReferences.put(type, fields);
288: }
289:
290: for (Field field : fields) {
291: PetiteInject ref = field.getAnnotation(PetiteInject.class);
292: String refName = ref.value().trim();
293: if (refName.length() == 0) {
294: refName = field.getName();
295: }
296: Object value = getBean(refName, acquiredBeans);
297: if (value == null) {
298: throw new PetiteException("Reference '" + refName
299: + "' not found for property '"
300: + type.toString().substring(6) + '#'
301: + field.getName() + "'.");
302: }
303: if (trackBeanWiring == true) { // track wirings, slow
304: BeanDef bd = beanNames.get(refName);
305: bd.injectedTo(resolveBeanName(type), field.getName());
306: }
307: BeanUtil.setDeclaredProperty(bean, field.getName(), value);
308: }
309: }
310:
311: // ---------------------------------------------------------------- scopes
312:
313: protected Class<? extends Scope> defaultScope = SingletonScope.class;
314:
315: /**
316: * Returns default scope type.
317: */
318: public Class<? extends Scope> getDefaultScope() {
319: return defaultScope;
320: }
321:
322: /**
323: * Sets default scope type.
324: */
325: public void setDefaultScope(Class<? extends Scope> defaultScope) {
326: if (defaultScope == DefaultScope.class) {
327: throw new PetiteException(
328: "Default Petite bean scope must be a concrete scope implementation.");
329: }
330: this .defaultScope = defaultScope;
331: }
332:
333: // ---------------------------------------------------------------- registration
334:
335: /**
336: * Map of all beans definitions.
337: */
338: protected Map<String, BeanDef> beanNames = new HashMap<String, BeanDef>();
339:
340: /**
341: * Map of all bean scopes.
342: */
343: protected Map<Class<? extends Scope>, Scope> scopes = new HashMap<Class<? extends Scope>, Scope>();
344:
345: /**
346: * Registers petite bean class. If class is not annotated, it will be registered with default scope.
347: * @see PetiteContainer#register(Class, Class)
348: * @see PetiteContainer#register(String, Class, Class)
349: */
350: public void register(Class type) {
351: register(type, defaultScope);
352: }
353:
354: /**
355: * Registers petite bean class within specified scope. Class may be annotated, but this is not required.
356: * If annotation is omitted, bean will be registered with uncapitalized simple type name and defaults.
357: * @see PetiteContainer#register(String, Class, Class)
358: */
359: @SuppressWarnings({"unchecked"})
360: public void register(Class type, Class<? extends Scope> scopeType) {
361: PetiteBean petiteBean = (PetiteBean) type
362: .getAnnotation(PetiteBean.class);
363: String name = null;
364: if (petiteBean != null) {
365: name = petiteBean.value().trim();
366: scopeType = petiteBean.scope();
367: }
368: if ((name == null) || (name.length() == 0)) {
369: name = StringUtil.uncapitalize(type.getSimpleName());
370: }
371: register(name, type, scopeType);
372: }
373:
374: /**
375: * Registers any class as petite bean class with default scope.
376: * @see PetiteContainer#register(String, Class, Class)
377: */
378: public void register(String name, Class type) {
379: register(name, type, defaultScope);
380: }
381:
382: /**
383: * Registers any class as petite bean class. {@link PetiteBean} annotation is not required
384: * and is ignored during this registration. Bean names must be unique. This is the actual method
385: * that performs the registration.
386: */
387: public void register(String name, Class type,
388: Class<? extends Scope> scopeType) {
389: if (scopeType == DefaultScope.class) {
390: scopeType = defaultScope;
391: }
392: Scope scope = scopes.get(scopeType);
393: if (scope == null) {
394: try {
395: scope = scopeType.newInstance();
396: scopes.put(scopeType, scope);
397: } catch (Exception ex) {
398: throw new PetiteException(
399: "Unable to create the Petite scope: '"
400: + scopeType + "' for bean '" + name
401: + "'.", ex);
402: }
403: }
404: BeanDef existing = beanNames.put(name, new BeanDef(name, type,
405: scope));
406: if (existing != null) {
407: throw new PetiteException("Unable to register class '"
408: + type.getSimpleName() + "'. Petite bean class '"
409: + existing.type.getSimpleName()
410: + "' already registered with the name '" + name
411: + "'.");
412: }
413: }
414:
415: // ---------------------------------------------------------------- remove
416:
417: /**
418: * Removes bean definition from the container.
419: * Returns <code>true</code> if bean specified by provided name
420: * was removed from the container, otherwise it returns <code>false</code>.
421: * @see PetiteContainer#remove(Class)
422: */
423: public boolean remove(String name) {
424: BeanDef bd = beanNames.remove(name);
425: if (bd == null) {
426: return false;
427: }
428: beanCtors.remove(bd.type);
429: beanReferences.remove(bd.type);
430: bd.scopeRemove();
431: return true;
432: }
433:
434: /**
435: * Removes petite bean from the container. Returns <code>true</code> if bean specified by provided name
436: * was removed from the container, otherwise it returns <code>false</code>.
437: * @see PetiteContainer#remove(String)
438: */
439: public boolean remove(Class type) {
440: String name = resolveBeanName(type);
441: return remove(name);
442: }
443:
444: /**
445: * Resolves bean name from bean annotation or class name.
446: */
447: @SuppressWarnings({"unchecked"})
448: protected String resolveBeanName(Class type) {
449: PetiteBean petiteBean = (PetiteBean) type
450: .getAnnotation(PetiteBean.class);
451: String name = null;
452: if (petiteBean != null) {
453: name = petiteBean.value().trim();
454: }
455: if ((name == null) || (name.length() == 0)) {
456: name = StringUtil.uncapitalize(type.getSimpleName());
457: }
458: return name;
459: }
460:
461: // ---------------------------------------------------------------- replace class
462:
463: /**
464: * Replaces a class in the container. It will find all targets where original class
465: * has been injected and will replace existing instances, while storing them in
466: * appropriate scopes.
467: */
468: public void replace(final Class newType) {
469: final String newName = resolveBeanName(newType);
470: final BeanDef bd = beanNames.get(newName);
471: if (bd == null) {
472: register(newType); // new types are simply registered
473: return;
474: }
475: beanCtors.remove(bd.type);
476: beanReferences.remove(bd.type);
477: bd.scopeRemove();
478: bd.type = newType;
479:
480: if (bd.injectectionTargets == null) {
481: return;
482: }
483: for (final BeanDef.InjectionTarget ref : bd.injectectionTargets) {
484: for (Scope scope : scopes.values()) {
485: scope.replaceIn(ref.beanName, new ScopeReplacer() {
486: public Object replace(Object target) {
487: Object oldObj = BeanUtil.getDeclaredProperty(
488: target, ref.fieldName);
489: Map<String, Object> acquiredBeans = new HashMap<String, Object>();
490: Object newObj = newBeanInstance(bd.name,
491: bd.type, acquiredBeans);
492: // copy fields since it is not possible to wire bean to all scopes, since
493: // maybe some scope depends on current thread, request, etc.
494: BeanUtil.copyFields(oldObj, newObj, true);
495: BeanUtil.setProperty(target, ref.fieldName,
496: newObj);
497: return newObj;
498: }
499:
500: public String getBeanName() {
501: return newName;
502: }
503: });
504: }
505: }
506: }
507:
508: // ---------------------------------------------------------------- stats and misc
509:
510: /**
511: * Returns total number of registered beans.
512: */
513: public int getTotalBeans() {
514: return beanNames.size();
515: }
516:
517: /**
518: * Return total number of used scopes.
519: */
520: public int getTotalScopes() {
521: return scopes.size();
522: }
523:
524: /**
525: * Returns iterator over all registered classes.
526: */
527: public Iterator<Class> classIterator() {
528: final Iterator<BeanDef> it = beanNames.values().iterator();
529: return new Iterator<Class>() {
530: public boolean hasNext() {
531: return it.hasNext();
532: }
533:
534: public Class next() {
535: return it.next().type;
536: }
537:
538: public void remove() {
539: it.remove();
540: }
541: };
542: }
543:
544: }
|