/***************************************************************************
* Copyright 2009-2012 by Christian Ihle *
* kontakt@usikkert.net *
* *
* This file is part of KouInject. *
* *
* KouInject 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 3 of *
* the License, or (at your option) any later version. *
* *
* KouInject 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 KouInject. *
* If not, see <http://www.gnu.org/licenses/>. *
***************************************************************************/
package net.usikkert.kouinject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import javax.inject.Provider;
import net.usikkert.kouinject.beandata.BeanData;
import net.usikkert.kouinject.beandata.BeanKey;
import net.usikkert.kouinject.beandata.ConstructorData;
import net.usikkert.kouinject.beandata.InjectionPoint;
import net.usikkert.kouinject.factory.FactoryContext;
import net.usikkert.kouinject.factory.FactoryContextImpl;
import net.usikkert.kouinject.factory.FactoryPoint;
import net.usikkert.kouinject.factory.FactoryPointHandler;
import net.usikkert.kouinject.factory.FactoryPointMap;
import net.usikkert.kouinject.generics.TypeLiteral;
import org.apache.commons.lang.Validate;
/**
* Default implementation of the {@link BeanLoader}.
*
* <p>It's recommended to use the {@link DefaultInjector} instead of using this class directly,
* unless you have special requirements.</p>
*
* @author Christian Ihle
*/
public class DefaultBeanLoader implements BeanLoader {
private static final Logger LOG = Logger.getLogger(DefaultBeanLoader.class.getName());
private final SingletonMap singletonMap;
private final BeanDataMap beanDataMap;
private final BeansInCreation beansInCreation;
private final BeanDataHandler beanDataHandler;
private final BeanLocator beanLocator;
private final FactoryPointMap factoryPointMap;
private final FactoryPointHandler factoryPointHandler;
/**
* Constructs a new instance of this {@link BeanLoader} with the specified {@link BeanDataHandler},
* {@link BeanLocator} and {@link FactoryPointHandler}. Detected beans will be scanned and prepared,
* but not instantiated.
*
* @param beanDataHandler The handler to use for finding the meta-data required to instantiate beans.
* @param beanLocator The locator to use for getting the beans to load.
* @param factoryPointHandler The handler to use for finding the meta-data required for using factories.
*/
public DefaultBeanLoader(final BeanDataHandler beanDataHandler, final BeanLocator beanLocator,
final FactoryPointHandler factoryPointHandler) {
Validate.notNull(beanDataHandler, "Bean-data handler can not be null");
Validate.notNull(beanLocator, "Bean locator can not be null");
Validate.notNull(factoryPointHandler, "Factory point handler can not be null");
this.beanDataHandler = beanDataHandler;
this.beanLocator = beanLocator;
this.factoryPointHandler = factoryPointHandler;
this.singletonMap = new SingletonMap();
this.beanDataMap = new BeanDataMap();
this.beansInCreation = new BeansInCreation();
this.factoryPointMap = new FactoryPointMap();
loadBeanData();
}
/**
* Finds all registered beans, and prepares them for being instantiated.
*/
private void loadBeanData() {
final Set<BeanKey> detectedBeans = beanLocator.findBeans();
LOG.fine("Beans found: " + detectedBeans.size());
final long start = System.currentTimeMillis();
for (final BeanKey bean : detectedBeans) {
LOG.finest("Loading bean-data for: " + bean);
final BeanData beanData = beanDataHandler.getBeanData(bean, false);
beanDataMap.addBeanData(beanData);
final List<FactoryPoint<?>> factoryPoints = factoryPointHandler.getFactoryPoints(bean);
factoryPointMap.addFactoryPoints(factoryPoints);
}
final long stop = System.currentTimeMillis();
LOG.fine("All bean-data loaded in: " + (stop - start) + " ms");
}
/**
* {@inheritDoc}
*/
@Override
public <T> T getBean(final Class<T> beanClass) {
return getBean(beanClass, null);
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public <T> T getBean(final Class<T> beanClass, final String qualifier) {
return (T) getBean(new BeanKey(beanClass, qualifier));
}
/**
* {@inheritDoc}
*/
@Override
public <T> T getBean(final TypeLiteral<T> beanType) {
return getBean(beanType, null);
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public <T> T getBean(final TypeLiteral<T> beanType, final String qualifier) {
return (T) getBean(new BeanKey(beanType, qualifier));
}
@SuppressWarnings("unchecked")
private <T> T getBean(final BeanKey beanKey) {
LOG.finer("Requesting: " + beanKey);
if (!beanCanBeCreated(beanKey)) {
throw new IllegalArgumentException("No registered bean-data for: " + beanKey);
}
return (T) findOrCreateBean(beanKey);
}
/**
* {@inheritDoc}
*/
@Override
public <T> Collection<T> getBeans(final Class<T> beanClass) {
return getBeans(beanClass, null);
}
/**
* {@inheritDoc}
*/
@Override
public <T> Collection<T> getBeans(final Class<T> beanClass, final String qualifier) {
return getBeans(new BeanKey(beanClass, qualifier));
}
/**
* {@inheritDoc}
*/
@Override
public <T> Collection<T> getBeans(final TypeLiteral<T> beanType) {
return getBeans(beanType, null);
}
/**
* {@inheritDoc}
*/
@Override
public <T> Collection<T> getBeans(final TypeLiteral<T> beanType, final String qualifier) {
return getBeans(new BeanKey(beanType, qualifier));
}
@SuppressWarnings("unchecked")
private <T> Collection<T> getBeans(final BeanKey beanKey) {
LOG.finer("Requesting: " + beanKey);
final Collection<BeanKey> beanKeys = new ArrayList<BeanKey>();
beanKeys.addAll(beanDataMap.findBeanKeys(beanKey));
beanKeys.addAll(factoryPointMap.findFactoryPointKeys(beanKey));
if (beanKeys.isEmpty()) {
throw new IllegalArgumentException("No registered bean-data for: " + beanKey);
}
final Collection<T> beans = new ArrayList<T>();
for (final BeanKey key : beanKeys) {
beans.add((T) findOrCreateBean(key));
}
return beans;
}
private boolean beanCanBeCreated(final BeanKey dependency) {
final boolean isStandaloneBean = beanDataMap.containsBeanData(dependency);
final boolean isFactoryCreatedBean = factoryPointMap.containsFactoryPoint(dependency);
if (isStandaloneBean && isFactoryCreatedBean) {
throw new IllegalStateException("Requested bean is both a standalone bean and a factory created bean: " + dependency
+ "\n standalone: " + beanDataMap.getBeanData(dependency)
+ "\n factory: " + factoryPointMap.getFactoryPoint(dependency));
}
return isStandaloneBean || isFactoryCreatedBean;
}
private Object findOrCreateBean(final BeanKey dependency) {
final Object bean = singletonMap.getSingleton(dependency, false);
if (bean != null) {
LOG.finer("Mapping " + dependency + " to existing singleton " + bean.getClass());
return bean;
}
return createBean(dependency);
}
private void addSingleton(final Object beanToAdd, final BeanKey beanKey) {
Validate.notNull(beanToAdd, "Bean can not be null");
Validate.notNull(beanKey, "Bean key can not be null");
LOG.finer("Adding singleton: " + beanKey);
singletonMap.addSingleton(beanKey, beanToAdd);
LOG.fine("Singleton added: " + beanKey);
}
private Object createBean(final BeanKey dependency) {
LOG.finer("Checking bean before creation: " + dependency);
beansInCreation.addBean(dependency);
final CreatedBean createdBean = createBeanUsingFactoryOrInjector(dependency);
if (createdBean.isSingleton()) {
addSingleton(createdBean.getInstance(), createdBean.getBeanKey());
}
beansInCreation.removeBean(dependency);
return createdBean.getInstance();
}
private CreatedBean createBeanUsingFactoryOrInjector(final BeanKey dependency) {
if (factoryPointMap.containsFactoryPoint(dependency)) {
return createBeanUsingFactory(dependency);
}
else {
return createBeanUsingInjector(dependency);
}
}
private CreatedBean createBeanUsingInjector(final BeanKey dependency) {
final BeanData beanData = beanDataMap.getBeanData(dependency);
final BeanKey beanKeyForBeanData = beanData.getBeanKey();
LOG.finer("Mapping " + dependency + " to " + beanKeyForBeanData);
final Object beanInstance = instantiateBean(beanData);
return new CreatedBean(beanInstance, beanData.isSingleton(), beanKeyForBeanData);
}
private CreatedBean createBeanUsingFactory(final BeanKey dependency) {
final FactoryPoint<?> factoryPoint = factoryPointMap.getFactoryPoint(dependency);
final BeanKey returnType = factoryPoint.getReturnType();
LOG.finer("Mapping " + dependency + " to " + returnType);
final Object factoryInstance = getBean(factoryPoint.getFactoryKey());
final Object beanInstance = invokeFactoryPoint(factoryPoint, factoryInstance, dependency);
return new CreatedBean(beanInstance, factoryPoint.isSingleton(), returnType);
}
private Object instantiateBean(final BeanData beanData) {
final Object instance = instantiateConstructor(beanData);
autowireBean(beanData, instance);
LOG.fine("Created bean: " + instance.getClass());
return instance;
}
private Object instantiateConstructor(final BeanData beanData) {
final ConstructorData constructor = beanData.getConstructor();
LOG.finer("Invoking constructor: " + constructor);
final List<BeanKey> dependencies = constructor.getDependencies();
final Object[] beansForConstructor = new Object[dependencies.size()];
for (int i = 0; i < dependencies.size(); i++) {
final BeanKey dependency = dependencies.get(i);
final Object bean = findBeanOrCreateProvider(dependency);
beansForConstructor[i] = bean;
}
final Object instance = constructor.createInstance(beansForConstructor);
LOG.finer("Constructor invoked: " + constructor);
return instance;
}
private Object invokeFactoryPoint(final FactoryPoint<?> factoryPoint, final Object factoryInstance,
final BeanKey dependency) {
LOG.finer("Invoking factory point: " + factoryPoint);
final List<BeanKey> parameters = factoryPoint.getParameters();
final Object[] beansForFactoryPoint = new Object[parameters.size()];
for (int i = 0; i < parameters.size(); i++) {
final BeanKey parameter = parameters.get(i);
final Object bean = getBeanOrFactoryContext(parameter, dependency);
beansForFactoryPoint[i] = bean;
}
final Object instance = factoryPoint.create(factoryInstance, beansForFactoryPoint);
LOG.finer("Factory point invoked: " + factoryPoint);
return instance;
}
private Object getBeanOrFactoryContext(final BeanKey parameter, final BeanKey dependency) {
if (parameter.getBeanType().equals(FactoryContext.class)) {
return new FactoryContextImpl(dependency.getQualifier());
}
else {
return findBeanOrCreateProvider(parameter);
}
}
private void autowireBean(final BeanData beanData, final Object instance) {
final List<InjectionPoint> injectionPoints = beanData.getInjectionPoints();
for (final InjectionPoint injectionPoint : injectionPoints) {
LOG.finer("Autowiring injection point: " + injectionPoint);
final List<BeanKey> dependencies = injectionPoint.getDependencies();
final Object[] beansForInjectionPoint = new Object[dependencies.size()];
for (int i = 0; i < dependencies.size(); i++) {
final BeanKey dependency = dependencies.get(i);
final Object bean = findBeanOrCreateProvider(dependency);
beansForInjectionPoint[i] = bean;
}
LOG.finer("Injection point autowired: " + injectionPoint);
injectionPoint.inject(instance, beansForInjectionPoint);
}
}
private Object findBeanOrCreateProvider(final BeanKey dependency) {
if (dependency.isProvider()) {
return new Provider<Object>() {
@Override
public Object get() {
return getBean(dependency);
}
};
}
if (dependency.isCollection()) {
final BeanKey actualBeanKey = dependency.getActualBeanKey();
if (factoryPointMap.containsFactoryPoint(actualBeanKey)) {
// The default handling of this collection is overridden by a factory
return getBean(actualBeanKey);
}
else {
return getBeans(dependency);
}
}
if (dependency.isCollectionProvider()) {
return new CollectionProvider() {
@Override
public Collection<?> get() {
return getBeans(dependency);
}
};
}
return getBean(dependency);
}
}