/*
* Copyright (c) 2009-2016, b3log.org & hacpai.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.b3log.latke.ioc.bean;
import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyObject;
import org.b3log.latke.ioc.BeanManager;
import org.b3log.latke.ioc.LatkeBeanManager;
import org.b3log.latke.ioc.annotated.*;
import org.b3log.latke.ioc.annotated.AnnotatedType;
import org.b3log.latke.ioc.config.Configurator;
import org.b3log.latke.ioc.context.CreationalContext;
import org.b3log.latke.ioc.inject.Inject;
import org.b3log.latke.ioc.inject.Named;
import org.b3log.latke.ioc.inject.Provider;
import org.b3log.latke.ioc.literal.NamedLiteral;
import org.b3log.latke.ioc.point.FieldInjectionPoint;
import org.b3log.latke.ioc.point.InjectionPoint;
import org.b3log.latke.ioc.point.ParameterInjectionPoint;
import org.b3log.latke.ioc.provider.FieldProvider;
import org.b3log.latke.ioc.provider.ParameterProvider;
import org.b3log.latke.logging.Level;
import org.b3log.latke.logging.Logger;
import org.b3log.latke.util.Reflections;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
/**
* Latke bean implementation.
*
* @param <T> the declaring type
* @author <a href="http://88250.b3log.org">Liang Ding</a>
* @version 1.0.0.8, Sep 29, 2013
*/
public class BeanImpl<T> implements LatkeBean<T> {
/**
* Logger.
*/
private static final Logger LOGGER = Logger.getLogger(BeanImpl.class.getName());
/**
* Stereo types.
*/
private final Set<Class<? extends Annotation>> stereotypes;
/**
* Bean manager.
*/
private LatkeBeanManager beanManager;
/**
* Bean configurator.
*/
private Configurator configurator;
/**
* Bean name.
*/
private String name;
/**
* Bean scope.
*/
private Class<? extends Annotation> scope;
/**
* Bean qualifiers.
*/
private Set<Annotation> qualifiers;
/**
* Bean class.
*/
private Class<T> beanClass;
/**
* Proxy class.
*/
private Class<T> proxyClass;
/**
* Javassist method handler.
*/
private JavassistMethodHandler javassistMethodHandler;
/**
* Bean types.
*/
private Set<Type> types;
/**
* Annotated type of this bean.
*/
private AnnotatedType<T> annotatedType;
/**
* Field injection points.
*/
private Set<FieldInjectionPoint> fieldInjectionPoints;
/**
* Constructor parameter injection points.
*/
private Map<AnnotatedConstructor<T>, List<ParameterInjectionPoint>> constructorParameterInjectionPoints;
/**
* Method parameter injection points.
*/
private Map<AnnotatedMethod<?>, List<ParameterInjectionPoint>> methodParameterInjectionPoints;
/**
* Constructor parameter providers.
*/
private List<ParameterProvider<?>> constructorParameterProviders;
/**
* Field provider.
*/
private Set<FieldProvider<?>> fieldProviders;
/**
* Method parameter providers.
*/
private Map<AnnotatedMethod<?>, List<ParameterProvider<?>>> methodParameterProviders;
/**
* Constructs a Latke bean.
*
* @param beanManager the specified bean manager
* @param name the specified bean name
* @param scope the specified bean scope
* @param qualifiers the specified bean qualifiers
* @param beanClass the specified bean class
* @param types the specified bean types
* @param stereotypes the specified stereo types
*/
public BeanImpl(final LatkeBeanManager beanManager, final String name, final Class<? extends Annotation> scope,
final Set<Annotation> qualifiers, final Class<T> beanClass, final Set<Type> types,
final Set<Class<? extends Annotation>> stereotypes) {
this.beanManager = beanManager;
this.name = name;
this.scope = scope;
this.qualifiers = qualifiers;
this.beanClass = beanClass;
this.types = types;
this.stereotypes = stereotypes;
this.configurator = beanManager.getConfigurator();
javassistMethodHandler = new JavassistMethodHandler(beanManager);
final ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setSuperclass(beanClass);
proxyFactory.setFilter(javassistMethodHandler.getMethodFilter());
proxyClass = proxyFactory.createClass();
annotatedType = new AnnotatedTypeImpl<T>(beanClass);
constructorParameterInjectionPoints = new HashMap<AnnotatedConstructor<T>, List<ParameterInjectionPoint>>();
constructorParameterProviders = new ArrayList<ParameterProvider<?>>();
methodParameterInjectionPoints = new HashMap<AnnotatedMethod<?>, List<ParameterInjectionPoint>>();
methodParameterProviders = new HashMap<AnnotatedMethod<?>, List<ParameterProvider<?>>>();
fieldInjectionPoints = new HashSet<FieldInjectionPoint>();
fieldProviders = new HashSet<FieldProvider<?>>();
initFieldInjectionPoints();
initConstructorInjectionPoints();
initMethodInjectionPoints();
}
/**
* Resolves dependencies for the specified reference.
*
* @param reference the specified reference
* @throws Exception exception
*/
private void resolveDependencies(final Object reference) throws Exception {
final Class<?> superclass = reference.getClass().getSuperclass().getSuperclass(); // Proxy -> Orig -> Super
resolveSuperclassFieldDependencies(reference, superclass);
resolveSuperclassMethodDependencies(reference, superclass);
resolveCurrentclassFieldDependencies(reference);
resolveCurrentclassMethodDependencies(reference);
}
/**
* Constructs the bean object with dependencies resolved.
*
* @return bean object
* @throws Exception exception
*/
private T instantiateReference() throws Exception {
T ret;
if (constructorParameterInjectionPoints.size() == 1) {
// only one constructor allow to be annotated with @Inject
// instantiate an instance by the constructor annotated with @Inject
final AnnotatedConstructor<T> annotatedConstructor = constructorParameterInjectionPoints.keySet().iterator().next();
final List<ParameterInjectionPoint> paraInjectionPoints = constructorParameterInjectionPoints.get(annotatedConstructor);
final Object[] args = new Object[paraInjectionPoints.size()];
int i = 0;
for (final ParameterInjectionPoint paraInjectionPoint : paraInjectionPoints) {
Object arg = beanManager.getInjectableReference(paraInjectionPoint, null);
if (arg == null) {
for (final ParameterProvider<?> provider : constructorParameterProviders) {
if (provider.getAnnotated().equals(paraInjectionPoint.getAnnotated())) {
arg = provider;
break;
}
}
}
args[i++] = arg;
}
final Constructor<T> oriBeanConstructor = annotatedConstructor.getJavaMember();
final Constructor<T> constructor = proxyClass.getConstructor(oriBeanConstructor.getParameterTypes());
ret = constructor.newInstance(args);
} else {
ret = proxyClass.newInstance();
}
((ProxyObject) ret).setHandler(javassistMethodHandler);
LOGGER.log(Level.TRACE, "Uses Javassist method handler for bean[class={0}]", beanClass.getName());
return ret;
}
/**
* Resolves current class field dependencies for the specified reference.
*
* @param reference the specified reference
*/
private void resolveCurrentclassFieldDependencies(final Object reference) {
for (final FieldInjectionPoint injectionPoint : fieldInjectionPoints) {
Object injection = beanManager.getInjectableReference(injectionPoint, null);
if (injection == null) {
for (final FieldProvider<?> provider : fieldProviders) {
if (provider.getAnnotated().equals(injectionPoint.getAnnotated())) {
injection = provider;
break;
}
}
}
final Field field = injectionPoint.getAnnotated().getJavaMember();
try {
final Field declaredField = proxyClass.getDeclaredField(field.getName());
if (declaredField.isAnnotationPresent(Inject.class)) {
try {
declaredField.set(reference, injection);
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
} catch (final NoSuchFieldException ex) {
try {
field.set(reference, injection);
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
}
}
/**
* Resolves current class method dependencies for the specified reference.
*
* @param reference the specified reference
*/
private void resolveCurrentclassMethodDependencies(final Object reference) {
for (final Map.Entry<AnnotatedMethod<?>, List<ParameterInjectionPoint>> methodParameterInjectionPoint
: methodParameterInjectionPoints.entrySet()) {
final List<ParameterInjectionPoint> paraSet = methodParameterInjectionPoint.getValue();
final Object[] args = new Object[paraSet.size()];
int i = 0;
for (final ParameterInjectionPoint paraInjectionPoint : paraSet) {
Object arg = beanManager.getInjectableReference(paraInjectionPoint, null);
if (arg == null) {
for (final ParameterProvider<?> provider : methodParameterProviders.get(methodParameterInjectionPoint.getKey())) {
if (provider.getAnnotated().equals(paraInjectionPoint.getAnnotated())) {
arg = provider;
break;
}
}
}
args[i++] = arg;
}
final AnnotatedMethod<?> annotatedMethod = methodParameterInjectionPoint.getKey();
final Method method = annotatedMethod.getJavaMember();
try {
final Method declaredMethod = proxyClass.getDeclaredMethod(method.getName(), method.getParameterTypes());
try {
declaredMethod.setAccessible(true);
declaredMethod.invoke(reference, args);
} catch (final Exception e) {
throw new RuntimeException(e);
}
} catch (final NoSuchMethodException ex) {
try {
method.setAccessible(true);
method.invoke(reference, args);
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
}
}
/**
* Resolves super class field dependencies for the specified reference.
*
* @param reference the specified reference
* @param clazz the super class of the specified reference
* @throws Exception exception
*/
private void resolveSuperclassFieldDependencies(final Object reference, final Class<?> clazz) throws Exception {
if (clazz.equals(Object.class)) {
return;
}
final Class<?> superclass = clazz.getSuperclass();
resolveSuperclassFieldDependencies(reference, superclass);
if (Modifier.isAbstract(clazz.getModifiers()) || Modifier.isInterface(clazz.getModifiers())) {
return;
}
final BeanImpl<?> bean = (BeanImpl<?>) beanManager.getBean(clazz);
final Set<FieldInjectionPoint> injectionPoints = bean.fieldInjectionPoints;
for (final FieldInjectionPoint injectionPoint : injectionPoints) {
Object injection = beanManager.getInjectableReference(injectionPoint, null);
if (injection == null) {
for (final FieldProvider<?> provider : bean.fieldProviders) {
if (provider.getAnnotated().equals(injectionPoint.getAnnotated())) {
injection = provider;
break;
}
}
}
final Field field = injectionPoint.getAnnotated().getJavaMember();
try {
final Field declaredField = proxyClass.getDeclaredField(field.getName());
if (!Reflections.matchInheritance(declaredField, field)) { // Hide
try {
field.set(reference, injection);
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
} catch (final NoSuchFieldException ex) {
throw new RuntimeException(ex);
}
}
}
/**
* Resolves super class method dependencies for the specified reference.
*
* @param reference the specified reference
* @param clazz the super class of the specified reference
* @throws Exception exception
*/
private void resolveSuperclassMethodDependencies(final Object reference, final Class<?> clazz) throws Exception {
if (clazz.equals(Object.class)) {
return;
}
final Class<?> superclass = clazz.getSuperclass();
resolveSuperclassMethodDependencies(reference, superclass);
if (Modifier.isAbstract(clazz.getModifiers()) || Modifier.isInterface(clazz.getModifiers())) {
return;
}
final BeanImpl<?> superBean = (BeanImpl<?>) beanManager.getBean(clazz);
for (final Map.Entry<AnnotatedMethod<?>, List<ParameterInjectionPoint>> methodParameterInjectionPoint
: superBean.methodParameterInjectionPoints.entrySet()) {
final List<ParameterInjectionPoint> paraSet = methodParameterInjectionPoint.getValue();
final Object[] args = new Object[paraSet.size()];
int i = 0;
for (final ParameterInjectionPoint paraInjectionPoint : paraSet) {
Object arg = beanManager.getInjectableReference(paraInjectionPoint, null);
if (arg == null) {
for (final ParameterProvider<?> provider : superBean.methodParameterProviders.get(methodParameterInjectionPoint.getKey())) {
if (provider.getAnnotated().equals(paraInjectionPoint.getAnnotated())) {
arg = provider;
break;
}
}
}
args[i++] = arg;
}
final AnnotatedMethod<?> superAnnotatedMethod = methodParameterInjectionPoint.getKey();
final Method superMethod = superAnnotatedMethod.getJavaMember();
final Method overrideMethod = Reflections.getOverrideMethod(superMethod, proxyClass);
if (superMethod.equals(overrideMethod)) {
try {
superMethod.invoke(reference, args);
} catch (final Exception e) {
throw new RuntimeException(e);
}
return;
}
}
}
@Override
public Set<Class<? extends Annotation>> getStereotypes() {
return stereotypes;
}
@Override
public boolean isAlternative() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public boolean isNullable() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void destroy(final T instance, final CreationalContext<T> creationalContext) {
LOGGER.log(Level.DEBUG, "Destroy bean [name={0}]", name);
}
@Override
public BeanManager getBeanManager() {
return beanManager;
}
@Override
public Class<?> getBeanClass() {
return beanClass;
}
@Override
public Set<InjectionPoint> getInjectionPoints() {
final Set<InjectionPoint> ret = new HashSet<InjectionPoint>();
for (final List<ParameterInjectionPoint> constructorParameterInjectionPointList : constructorParameterInjectionPoints.values()) {
ret.addAll(constructorParameterInjectionPointList);
}
ret.addAll(fieldInjectionPoints);
for (final List<ParameterInjectionPoint> methodParameterInjectionPointList : methodParameterInjectionPoints.values()) {
ret.addAll(methodParameterInjectionPointList);
}
return ret;
}
@Override
public String getName() {
return name;
}
@Override
public Set<Annotation> getQualifiers() {
return qualifiers;
}
@Override
public Class<? extends Annotation> getScope() {
return scope;
}
/**
* Sets the scope with the specified scope.
*
* @param scope the specified scope
*/
private void setScope(final Class<? extends Annotation> scope) {
this.scope = scope;
}
@Override
public Set<Type> getTypes() {
return types;
}
@Override
public T create(final CreationalContext<T> creationalContext) {
T ret = null;
try {
ret = instantiateReference();
resolveDependencies(ret);
} catch (final Exception ex) {
LOGGER.log(Level.ERROR, ex.getMessage(), ex);
}
return ret;
}
@Override
public LatkeBean<T> named(final String name) {
final Named namedQualifier = new NamedLiteral(name);
addQualifier(namedQualifier);
return this;
}
@Override
public LatkeBean<T> qualified(final Annotation qualifier,
final Annotation... qualifiers) {
addQualifier(qualifier);
for (final Annotation q : qualifiers) {
addQualifier(q);
}
return this;
}
@Override
public LatkeBean<T> scoped(final Class<? extends Annotation> scope) {
this.setScope(scope);
return this;
}
@Override
public String toString() {
return "[name=" + name + ", scope=" + scope.getName() + ", qualifiers=" + qualifiers + ", class=" + beanClass.getName() + ", types="
+ types + "]";
}
/**
* Initializes constructor injection points.
*/
private void initConstructorInjectionPoints() {
final Set<AnnotatedConstructor<T>> annotatedConstructors = annotatedType.getConstructors();
for (final AnnotatedConstructor annotatedConstructor : annotatedConstructors) {
final List<AnnotatedParameter<?>> parameters = annotatedConstructor.getParameters();
final List<ParameterInjectionPoint> paraInjectionPointArrayList = new ArrayList<ParameterInjectionPoint>();
for (final AnnotatedParameter<?> annotatedParameter : parameters) {
Type type = annotatedParameter.getBaseType();
if (type instanceof ParameterizedType) {
type = ((ParameterizedType) type).getRawType();
}
if (type.equals(Provider.class)) {
final ParameterProvider<T> provider = new ParameterProvider<T>(beanManager, annotatedParameter);
constructorParameterProviders.add(provider);
}
final ParameterInjectionPoint parameterInjectionPoint = new ParameterInjectionPoint(this, annotatedParameter);
paraInjectionPointArrayList.add(parameterInjectionPoint);
}
constructorParameterInjectionPoints.put(annotatedConstructor, paraInjectionPointArrayList);
}
}
/**
* Initializes method injection points.
*/
@SuppressWarnings("unchecked")
private void initMethodInjectionPoints() {
final Set<AnnotatedMethod<? super T>> annotatedMethods = annotatedType.getMethods();
for (final AnnotatedMethod annotatedMethod : annotatedMethods) {
final List<AnnotatedParameter<?>> parameters = annotatedMethod.getParameters();
final List<ParameterInjectionPoint> paraInjectionPointArrayList = new ArrayList<ParameterInjectionPoint>();
final List<ParameterProvider<?>> paraProviders = new ArrayList<ParameterProvider<?>>();
for (final AnnotatedParameter<?> annotatedParameter : parameters) {
Type type = annotatedParameter.getBaseType();
if (type instanceof ParameterizedType) {
type = ((ParameterizedType) type).getRawType();
}
if (type.equals(Provider.class)) {
final ParameterProvider<T> provider = new ParameterProvider<T>(beanManager, annotatedParameter);
paraProviders.add(provider);
}
final ParameterInjectionPoint parameterInjectionPoint = new ParameterInjectionPoint(this, annotatedParameter);
paraInjectionPointArrayList.add(parameterInjectionPoint);
}
methodParameterProviders.put(annotatedMethod, paraProviders);
methodParameterInjectionPoints.put(annotatedMethod, paraInjectionPointArrayList);
}
}
/**
* Initializes field injection points.
*/
private void initFieldInjectionPoints() {
final Set<AnnotatedField<? super T>> annotatedFields = annotatedType.getFields();
for (final AnnotatedField<? super T> annotatedField : annotatedFields) {
final Field field = annotatedField.getJavaMember();
if (field.getType().equals(Provider.class)) { // by provider
final FieldProvider<T> provider = new FieldProvider<T>(beanManager, annotatedField);
fieldProviders.add(provider);
final FieldInjectionPoint fieldInjectionPoint = new FieldInjectionPoint(this, annotatedField);
fieldInjectionPoints.add(fieldInjectionPoint);
} else { // by qualifier
final FieldInjectionPoint fieldInjectionPoint = new FieldInjectionPoint(this, annotatedField);
fieldInjectionPoints.add(fieldInjectionPoint);
}
}
}
/**
* Adds a qualifier with the specified qualifier.
*
* @param qualifier the specified qualifier
*/
private void addQualifier(final Annotation qualifier) {
if (qualifier.getClass().equals(NamedLiteral.class)) {
final NamedLiteral namedQualifier = (NamedLiteral) getNamedQualifier();
final NamedLiteral newNamedQualifier = (NamedLiteral) qualifier;
if (!namedQualifier.value().equals(newNamedQualifier.value())) {
setNamedQualifier(newNamedQualifier);
}
} else {
qualifiers.add(qualifier);
}
configurator.addClassQualifierBinding(beanClass, qualifier);
}
/**
* Gets the named qualifier.
*
* @return named aualifier
*/
private Annotation getNamedQualifier() {
for (final Annotation qualifier : qualifiers) {
if (qualifier.annotationType().equals(Named.class)) {
return qualifier;
}
}
throw new RuntimeException("A bean has one qualifier(Named) at least!");
}
/**
* Sets the named qualifier with the specified named qualifier.
*
* @param namedQualifier the specified named qualifier
*/
private void setNamedQualifier(final Annotation namedQualifier) {
for (final Annotation qualifier : qualifiers) {
if (qualifier.annotationType().equals(Named.class)) {
qualifiers.remove(qualifier);
qualifiers.add(namedQualifier);
name = ((Named) namedQualifier).value();
}
}
}
}