/*
* ============================================================================
* GNU Lesser General Public License
* ============================================================================
*
* Beanlet - JSE Application Container.
* Copyright (C) 2006 Leon van Zantvoort
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* Leon van Zantvoort
* 243 Acalanes Drive #11
* Sunnyvale, CA 94086
* USA
*
* zantvoort@users.sourceforge.net
* http://beanlet.org
*/
package org.beanlet.common;
import com.sun.org.apache.bcel.internal.generic.RETURN;
import com.sun.tools.internal.xjc.model.nav.EagerNClass;
import org.beanlet.BeanletMetaData;
import org.beanlet.Provider;
import org.beanlet.plugin.Injectant;
import org.beanlet.plugin.DependencyInjection;
import org.beanlet.plugin.BeanletConfiguration;
import org.beanlet.plugin.DependencyInjectionFactory;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.beanlet.BeanletWiringException;
import org.beanlet.annotation.AnnotationDeclaration;
import org.beanlet.annotation.AnnotationDomain;
import org.beanlet.annotation.AnnotationTypeElement;
import org.beanlet.annotation.ConstructorElement;
import org.beanlet.annotation.ConstructorParameterElement;
import org.beanlet.annotation.Element;
import org.beanlet.annotation.ElementAnnotation;
import org.beanlet.annotation.FieldElement;
import org.beanlet.annotation.MethodElement;
import org.beanlet.annotation.MethodParameterElement;
import org.beanlet.annotation.TypeElement;
import org.jargo.ComponentContext;
import sun.tools.tree.CaseStatement;
/**
* Abstract dependency injection factory implementation. It is advised that all dependency injection factories extends
* this class.
*
* This class adds generic support for {@code Provider}.
*
* @author Leon van Zantvoort
*/
public abstract class AbstractDependencyInjectionFactory<T extends Annotation>
implements DependencyInjectionFactory {
private static final Object DUMMY = new Object();
private final BeanletConfiguration<?> configuration;
private final AnnotationDomain domain;
public AbstractDependencyInjectionFactory(BeanletConfiguration<?> configuration) {
this.configuration = configuration;
this.domain = configuration.getAnnotationDomain();
}
protected abstract Class<T> annotationType();
protected abstract boolean isSupported(
ElementAnnotation<? extends Element, T> ea);
protected abstract boolean isOptional(
ElementAnnotation<? extends Element, T> ea);
protected abstract Set<String> getDependencies(
ElementAnnotation<? extends Element, T> ea)
throws BeanletWiringException;
/**
* May return {@code null} if at runtime it is detected that no injectant can be delivered.
* @throws BeanletWiringException
*/
protected abstract Injectant getInjectant(
ElementAnnotation<? extends Element, T> ea, ComponentContext<?> ctx)
throws BeanletWiringException;
/**
* Extracts name from specified {@code annotation}, possible an empty
* string.
*
* This method allows sub classes to assiciate a name with the given {@code annotation}.
*/
protected String getName(T annotation) {
return "";
}
/**
* Extracts the type from the specified {@code annotation}, returns
* {@code Object.class} by default.
*
* This method allows sub classes to associate a type with the given {@code annotation}.
*/
protected Class<?> getType(T annotation) {
return Object.class;
}
/**
* @return possibly an empty string.
*/
protected final String getName(ElementAnnotation<? extends Element, T> ea) {
String name = getName(ea.getAnnotation());
if (name.length() == 0) {
name = getName(ea.getElement());
}
assert name != null;
return name;
}
/**
* Extracts the type from the specified element annotation. The type is inferred from the element, unless
* sub classes associate a type with the annotation by overriding {@code getType(T annotation)}.
*/
protected final Class<?> getType(ElementAnnotation<? extends Element, T> ea) {
Class<?> type = getType(ea.getAnnotation());
if (Object.class.equals(type)) {
type = getType(ea.getElement());
}
assert type != null;
return type;
}
/**
* Helper method.
*/
protected final Class<?> getTypeClass(Type t) {
if (t instanceof GenericArrayType) {
return (Class) ((GenericArrayType) t).getGenericComponentType();
}
if (t instanceof ParameterizedType) {
return (Class) ((ParameterizedType) t).getRawType();
}
if (t instanceof TypeVariable) {
return (Class) ((TypeVariable) t).getBounds()[0];
}
if (t instanceof WildcardType) {
return getTypeClass(((WildcardType) t).getUpperBounds()[0]);
} else {
return (Class) t;
}
}
/**
* Returns the type of the injection signature. If type is {@code Provider}, the parameterized type of provider
* is returned.
*
* @return possibly {@code Object.class}.
*/
protected final Class<?> getType(Element element) {
final Class<?> type;
Class<?> tmp;
switch (element.getElementType()) {
case ANNOTATION_TYPE:
type = ((AnnotationTypeElement) element).getAnnotationType();
break;
case CONSTRUCTOR:
Constructor constructor = ((ConstructorElement) element).
getConstructor();
if (constructor.getParameterTypes().length == 1) {
tmp = constructor.getParameterTypes()[0];
if (Provider.class.isAssignableFrom(tmp)) {
if (constructor.getGenericParameterTypes()[0] instanceof ParameterizedType) {
type = getTypeClass(((ParameterizedType) constructor.getGenericParameterTypes()[0]).
getActualTypeArguments()[0]);
} else {
type = tmp;
}
} else {
type = tmp;
}
} else {
type = Object.class;
}
break;
case FIELD:
Field field = ((FieldElement) element).getField();
tmp = field.getType();
if (Provider.class.isAssignableFrom(tmp)) {
if (field.getGenericType() instanceof ParameterizedType) {
type = getTypeClass(((ParameterizedType) field.getGenericType()).
getActualTypeArguments()[0]);
} else {
type = tmp;
}
} else {
type = tmp;
}
break;
case LOCAL_VARIABLE:
type = Object.class;
break;
case METHOD:
Method method = ((MethodElement) element).getMethod();
if (method.getParameterTypes().length == 1) {
tmp = method.getParameterTypes()[0];
if (Provider.class.isAssignableFrom(tmp)) {
if (method.getGenericParameterTypes()[0] instanceof ParameterizedType) {
type = getTypeClass(((ParameterizedType) method.getGenericParameterTypes()[0]).
getActualTypeArguments()[0]);
} else {
type = tmp;
}
} else {
type = tmp;
}
} else {
type = Object.class;
}
break;
case PACKAGE:
type = Object.class;
break;
case PARAMETER:
if (element instanceof ConstructorParameterElement) {
ConstructorParameterElement cpe = (ConstructorParameterElement) element;
tmp = cpe.getConstructor().getParameterTypes()[cpe.getParameter()];
if (Provider.class.isAssignableFrom(tmp)) {
if (cpe.getConstructor().getGenericParameterTypes()[cpe.getParameter()] instanceof ParameterizedType) {
type = getTypeClass(((ParameterizedType) cpe.getConstructor().getGenericParameterTypes()[cpe.getParameter()]).
getActualTypeArguments()[0]);
} else {
type = tmp;
}
} else {
type = tmp;
}
} else if (element instanceof MethodParameterElement) {
MethodParameterElement mpe = (MethodParameterElement) element;
tmp = mpe.getMethod().getParameterTypes()[mpe.getParameter()];
if (Provider.class.isAssignableFrom(tmp)) {
if (mpe.getMethod().getGenericParameterTypes()[mpe.getParameter()] instanceof ParameterizedType) {
type = getTypeClass(((ParameterizedType) mpe.getMethod().
getGenericParameterTypes()[mpe.getParameter()]).
getActualTypeArguments()[0]);
} else {
type = tmp;
}
} else {
type = tmp;
}
} else {
type = Object.class;
}
break;
case TYPE:
type = ((TypeElement) element).getType();
break;
default:
type = Object.class;
break;
}
return type;
}
/**
* @return possibly an empty string.
*/
protected final String getName(Element element) {
final String name;
switch (element.getElementType()) {
case ANNOTATION_TYPE:
name = "";
break;
case CONSTRUCTOR:
name = "";
break;
case FIELD:
name = ((FieldElement) element).getField().getName();
break;
case LOCAL_VARIABLE:
name = "";
break;
case METHOD:
Method method = ((MethodElement) element).getMethod();
if (method.getName().startsWith("set")) {
name = Character.toLowerCase(method.getName().charAt(3)) +
method.getName().substring(4);
} else {
name = method.getName();
}
break;
case PACKAGE:
name = "";
break;
case PARAMETER:
name = "";
break;
case TYPE:
name = "";
break;
default:
name = "";
break;
}
return name;
}
public final List<DependencyInjection> getConstructorDependencyInjections(
Class<?> cls) {
List<DependencyInjection> list = new ArrayList<DependencyInjection>();
AnnotationDeclaration<T> ad = domain.getDeclaration(annotationType());
for (final ElementAnnotation<ConstructorElement, T> ea :
ad.getTypedElements(ConstructorElement.class)) {
if (ea.getElement().isElementOfSubclass(cls)) {
if (isSupported(ea)) {
list.add(new DependencyInjectionAdapter<T>(configuration.getComponentName(), ea,
new DelegatingDependencyInjection(ea)));
}
}
}
for (final ElementAnnotation<ConstructorParameterElement, T> ea :
ad.getTypedElements(ConstructorParameterElement.class)) {
if (ea.getElement().isElementOfSubclass(cls)) {
if (isSupported(ea)) {
list.add(new DependencyInjectionAdapter<T>(configuration.getComponentName(), ea,
new DelegatingDependencyInjection(ea)));
}
}
}
if (configuration.getType().isAssignableFrom(cls)) {
// Factory (static) method / (static) field injection only
// supported for beanlet type (or any sub class), not for
// interceptors.
for (final ElementAnnotation<MethodElement, T> ea :
ad.getTypedElements(MethodElement.class)) {
Method method = ea.getElement().getMethod();
if (Modifier.isStatic(method.getModifiers()) &&
!method.getReturnType().equals(Void.TYPE)) {
// No isPartOf check, because factory method can return any type.
if (isSupported(ea)) {
list.add(new DependencyInjectionAdapter<T>(configuration.getComponentName(), ea,
new DelegatingDependencyInjection(ea)));
}
}
}
for (final ElementAnnotation<MethodParameterElement, T> ea :
ad.getTypedElements(MethodParameterElement.class)) {
Method method = ea.getElement().getMethod();
if (Modifier.isStatic(method.getModifiers()) &&
!method.getReturnType().equals(Void.TYPE)) {
// No isPartOf check, because factory method can return any type.
if (isSupported(ea)) {
list.add(new DependencyInjectionAdapter<T>(configuration.getComponentName(), ea,
new DelegatingDependencyInjection(ea)));
}
}
}
for (final ElementAnnotation<FieldElement, T> ea :
ad.getTypedElements(FieldElement.class)) {
Field field = ea.getElement().getField();
if (Modifier.isStatic(field.getModifiers())) {
// No isPartOf check, because factory field can return any type.
if (isSupported(ea)) {
list.add(new DependencyInjectionAdapter<T>(configuration.getComponentName(), ea,
new DelegatingDependencyInjection(ea)));
}
}
}
}
return Collections.unmodifiableList(list);
}
public final List<DependencyInjection> getSetterDependencyInjections(
Class<?> cls) {
List<DependencyInjection> list = new ArrayList<DependencyInjection>();
AnnotationDeclaration<T> ad = domain.getDeclaration(annotationType());
for (final ElementAnnotation<FieldElement, T> ea :
ad.getTypedElements(FieldElement.class, cls)) {
if (Modifier.isStatic(ea.getElement().getField().getModifiers())) {
continue;
}
if (isSupported(ea)) {
list.add(new DependencyInjectionAdapter<T>(configuration.getComponentName(), ea,
new DelegatingDependencyInjection(ea)));
}
}
for (final ElementAnnotation<MethodElement, T> ea :
ad.getTypedElements(MethodElement.class, cls)) {
if (Modifier.isStatic(ea.getElement().getMethod().getModifiers())) {
continue;
}
if (isSupported(ea)) {
list.add(new DependencyInjectionAdapter<T>(configuration.getComponentName(), ea,
new DelegatingDependencyInjection(ea)));
}
}
for (final ElementAnnotation<MethodParameterElement, T> ea :
ad.getTypedElements(MethodParameterElement.class, cls)) {
if (Modifier.isStatic(ea.getElement().getMethod().getModifiers())) {
continue;
}
if (isSupported(ea)) {
list.add(new DependencyInjectionAdapter<T>(configuration.getComponentName(), ea,
new DelegatingDependencyInjection(ea)));
}
}
return Collections.unmodifiableList(list);
}
public final List<DependencyInjection> getFactoryDependencyInjections(
Class<?> cls, String factoryMethod) {
List<DependencyInjection> list = new ArrayList<DependencyInjection>();
AnnotationDeclaration<T> ad = domain.getDeclaration(annotationType());
for (final ElementAnnotation<MethodElement, T> ea :
ad.getTypedElements(MethodElement.class, cls)) {
Method method = ea.getElement().getMethod();
if (!Modifier.isStatic(method.getModifiers()) &&
!method.getReturnType().equals(Void.TYPE) &&
method.getName().equals(factoryMethod)) {
if (isSupported(ea)) {
list.add(new DependencyInjectionAdapter<T>(configuration.getComponentName(), ea,
new DelegatingDependencyInjection(ea)));
}
}
}
for (final ElementAnnotation<MethodParameterElement, T> ea :
ad.getTypedElements(MethodParameterElement.class, cls)) {
Method method = ea.getElement().getMethod();
if (!Modifier.isStatic(method.getModifiers()) &&
!method.getReturnType().equals(Void.TYPE) &&
method.getName().equals(factoryMethod)) {
if (isSupported(ea)) {
list.add(new DependencyInjectionAdapter<T>(configuration.getComponentName(), ea,
new DelegatingDependencyInjection(ea)));
}
}
}
return Collections.unmodifiableList(list);
}
protected final boolean isProviderElement(Element element) {
final Class<?> type;
switch (element.getElementType()) {
case ANNOTATION_TYPE:
type = ((AnnotationTypeElement) element).getAnnotationType();
break;
case CONSTRUCTOR:
Constructor constructor = ((ConstructorElement) element).getConstructor();
type = constructor.getParameterTypes()[0];
break;
case FIELD:
Field field = ((FieldElement) element).getField();
type = field.getType();
break;
case LOCAL_VARIABLE:
type = Object.class;
break;
case METHOD:
Method method = ((MethodElement) element).getMethod();
type = method.getParameterTypes()[0];
break;
case PACKAGE:
type = Object.class;
break;
case PARAMETER:
if (element instanceof ConstructorParameterElement) {
ConstructorParameterElement cpe = (ConstructorParameterElement) element;
type = cpe.getConstructor().getParameterTypes()[cpe.getParameter()];
} else if (element instanceof MethodParameterElement) {
MethodParameterElement mpe = (MethodParameterElement) element;
type = mpe.getMethod().getParameterTypes()[mpe.getParameter()];
} else {
type = Object.class;
}
break;
case TYPE:
type = ((TypeElement) element).getType();
break;
default:
type = Object.class;
break;
}
return Provider.class.isAssignableFrom(type);
}
private final class DelegatingDependencyInjection implements DependencyInjection {
private final ElementAnnotation<? extends Element,T> ea;
public DelegatingDependencyInjection(ElementAnnotation<? extends Element,T> ea) {
this.ea = ea;
}
public Element getTarget() {
return ea.getElement();
}
public Set<String> getDependencies() {
final Set<String> dependencies;
if (isProviderElement(ea.getElement())) {
dependencies = Collections.emptySet();
} else {
dependencies = AbstractDependencyInjectionFactory.this.getDependencies(ea);
}
return dependencies;
}
public boolean isOptional() {
return AbstractDependencyInjectionFactory.this.isOptional(ea);
}
public Injectant<?> getInjectant(ComponentContext<?> ctx) {
final Injectant<?> injectant;
if (isProviderElement(ea.getElement())) {
injectant = new InjectantImpl<Object>(new ProviderImpl(ea, ctx), false);
} else {
injectant = AbstractDependencyInjectionFactory.this.getInjectant(ea, ctx);
}
return injectant;
}
}
private final class ProviderImpl implements Provider<Object> {
private final ElementAnnotation<? extends Element,T> ea;
private final ComponentContext<?> ctx;
private final AtomicReference<Object> result;
public ProviderImpl(ElementAnnotation<? extends Element,T> ea, ComponentContext<?> ctx) {
this.ea = ea;
this.ctx = ctx;
this.result = new AtomicReference<Object>();
}
public Object get() {
Object o = result.get();
if (o == null) {
Injectant<?> injectant = AbstractDependencyInjectionFactory.this.getInjectant(ea, ctx);
if (injectant != null) {
o = injectant.getObject();
if (injectant.isCacheable()) {
result.compareAndSet(null, o == null ? DUMMY : o);
o = result.get();
}
}
}
return o == DUMMY ? null : o;
}
}
private static final class DependencyInjectionAdapter<T extends Annotation> implements DependencyInjection {
private final String beanletName;
private final DependencyInjection target;
private final ElementAnnotation<? extends Element, T> ea;
public DependencyInjectionAdapter(String beanletName,
ElementAnnotation<? extends Element, T> ea,
DependencyInjection target) {
this.ea = ea;
this.beanletName = beanletName;
this.target = target;
}
public Element getTarget() {
return target.getTarget();
}
public boolean isOptional() {
return target.isOptional();
}
public Set<String> getDependencies() throws BeanletWiringException {
try {
return target.getDependencies();
} catch (Exception e) {
throw wrapException(e);
}
}
public Injectant<?> getInjectant(ComponentContext<?> ctx) throws
BeanletWiringException {
try {
final Injectant injectant = target.getInjectant(ctx);
if (injectant == null) {
return null;
} else {
return new Injectant<Object>() {
public boolean isCacheable() {
return injectant.isCacheable();
}
public boolean isStatic() {
return injectant.isStatic();
}
public Object getObject() {
try {
return injectant.getObject();
} catch (Exception e) {
// Although getObject should not throw any exceptions, catch them anyway.
throw wrapException(e);
}
}
};
}
} catch (Exception e) {
throw wrapException(e);
}
}
public BeanletWiringException wrapException(Exception e) {
try {
throw e;
} catch (BeanletWiringException e2) {
return e2;
} catch (Exception e2) {
return new BeanletWiringException(beanletName,
ea.getElement().getMember(), e2);
}
}
}
}