/**
* Copyright (C) 2010 STMicroelectronics
*
* This file is part of "Mind Compiler" 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.
*
* This program 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 program. If not, see <http://www.gnu.org/licenses/>.
*
* Contact: mind@ow2.org
*
* Authors: Matthieu Leclercq
* Contributors:
*/
package org.ow2.mind.inject;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.google.inject.AbstractModule;
import com.google.inject.Binder;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Scope;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.AnnotatedBindingBuilder;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.binder.ScopedBindingBuilder;
import com.google.inject.spi.Message;
import com.google.inject.util.Providers;
/**
* Extends default Google Guice {@link AbstractModule} and adds API to create
* delegation chains. A delegation chain is a list of objects where each object
* implements the same interface and may delegate its processing to the next
* object in the chain. To do so, each object in the chain (expect the last one)
* contains a field that is injected to the reference of the next object in the
* chain. This field must be annotated with the {@link InjectDelegate}
* annotation.<br>
* This class provides an extension to the <em>Google Guide Binding EDSL</em> to
* define such delegation chain. For example :
*
* <pre>
* bind(MyService.class).toChainStartingWith(MyFirstDelegate.class)
* .followedBy(MySecondDelegate.class).endingWith(MyServiceImpl.class);
* </pre>
*
* Moreover, this {@link AbstractModule} provides a standard implementation of
* the {@link AbstractModule#configure()} method that calls (using reflection)
* every non-static methods whose name starts with <code>configure</code> and
* that has no parameter. So sub-classes can implement the configuration logic
* in several <i>configure</i> methods which can then be overridden
* independently.
*/
public abstract class AbstractMindModule extends AbstractModule {
private static final String CONFIGURE_METHOD_PREFIX = "configure";
@Override
protected void configure() {
Class<?> clazz = this.getClass();
final Set<String> calledMeths = new HashSet<String>();
do {
for (final Method meth : clazz.getDeclaredMethods()) {
if (meth.getName().startsWith(CONFIGURE_METHOD_PREFIX)
&& meth.getName().length() > CONFIGURE_METHOD_PREFIX.length()
&& meth.getParameterTypes().length == 0
&& calledMeths.add(meth.getName())) {
try {
meth.setAccessible(true);
meth.invoke(this);
} catch (final IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (final IllegalAccessException e) {
throw new RuntimeException(e);
} catch (final InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
} while ((clazz = clazz.getSuperclass()) != null);
}
@Override
protected <T> AnnotatedBindingChainBuilder<T> bind(final Class<T> clazz) {
final Binder binder = binder().skipSources(AbstractMindModule.class);
return new AnnotatedBindingChainBuilderImpl<T>(binder, clazz,
binder.bind(clazz));
}
@Override
protected <T> LinkedBindingChainBuilder<T> bind(final Key<T> key) {
return new LinkedBindingChainBuilderImpl<T>(binder().skipSources(
AbstractMindModule.class), key.getTypeLiteral().getRawType(),
super.bind(key));
}
static class AnnotatedBindingChainBuilderImpl<T>
extends
LinkedBindingChainBuilderImpl<T>
implements
AnnotatedBindingChainBuilder<T> {
final AnnotatedBindingBuilder<T> delegate;
AnnotatedBindingChainBuilderImpl(final Binder binder,
final Class<T> bindType, final AnnotatedBindingBuilder<T> delegate) {
super(binder, bindType, delegate);
this.delegate = delegate;
}
public LinkedBindingChainBuilder<T> annotatedWith(
final Class<? extends Annotation> annotationType) {
delegate.annotatedWith(annotationType);
return this;
}
public LinkedBindingChainBuilder<T> annotatedWith(
final Annotation annotation) {
delegate.annotatedWith(annotation);
return this;
}
}
static class LinkedBindingChainBuilderImpl<T>
implements
LinkedBindingChainBuilder<T> {
final Binder binder;
final LinkedBindingBuilder<T> delegate;
final Class<? super T> bindType;
LinkedBindingChainBuilderImpl(final Binder binder,
final Class<? super T> bindType, final LinkedBindingBuilder<T> delegate) {
this.binder = binder;
this.bindType = bindType;
this.delegate = delegate;
}
public ScopedBindingBuilder to(final Class<? extends T> implementation) {
return delegate.to(implementation);
}
public ScopedBindingBuilder to(final TypeLiteral<? extends T> implementation) {
return delegate.to(implementation);
}
public ScopedBindingBuilder to(final Key<? extends T> targetKey) {
return delegate.to(targetKey);
}
public void toInstance(final T instance) {
delegate.toInstance(instance);
}
public ScopedBindingBuilder toProvider(final Provider<? extends T> provider) {
return delegate.toProvider(provider);
}
public ScopedBindingBuilder toProvider(
final Class<? extends Provider<? extends T>> providerType) {
return delegate.toProvider(providerType);
}
public ScopedBindingBuilder toProvider(
final Key<? extends Provider<? extends T>> providerKey) {
return delegate.toProvider(providerKey);
}
public void in(final Class<? extends Annotation> scopeAnnotation) {
delegate.in(scopeAnnotation);
}
public void in(final Scope scope) {
delegate.in(scope);
}
public void asEagerSingleton() {
delegate.asEagerSingleton();
}
public ChainBuilder<T> toChainStartingWith(final Class<? extends T> clazz) {
return new ChainBuilderImpl<T>(binder, bindType, delegate, clazz);
}
public ChainBuilder<T> toChainStartingWith(
final Provider<? extends T> provider) {
return new ChainBuilderImpl<T>(binder, bindType, delegate, provider);
}
public ChainBuilder<T> toChainStartingWith(final T instance) {
return new ChainBuilderImpl<T>(binder, bindType, delegate, instance);
}
}
static class ChainBuilderImpl<T> implements ChainBuilder<T> {
final ScopedBindingBuilder delegate;
final ChainProvider<T> chainProvider;
final Binder binder;
ChainBuilderImpl(final Binder binder, final Class<? super T> chainType,
final LinkedBindingBuilder<T> delegate,
final Provider<? extends T> firstProvider) {
this.binder = binder;
this.chainProvider = new ChainProvider<T>(chainType);
chainProvider.add(firstProvider);
this.delegate = delegate.toProvider(chainProvider);
}
ChainBuilderImpl(final Binder binder, final Class<? super T> chainType,
final LinkedBindingBuilder<T> delegate,
final Class<? extends T> firstClass) {
this(binder, chainType, delegate, binder.getProvider(firstClass));
checkDelegateClass(firstClass);
}
ChainBuilderImpl(final Binder binder, final Class<? super T> chainType,
final LinkedBindingBuilder<T> delegate, final T firstInstance) {
this(binder, chainType, delegate, Providers.of(firstInstance));
checkDelegateClass(firstInstance.getClass());
}
void checkDelegateClass(final Class<?> elemClass) {
boolean delegateFound = false;
Class<?> clazz = elemClass;
topLoop : do {
for (final Field f : clazz.getDeclaredFields()) {
if (f.getAnnotation(InjectDelegate.class) != null
&& f.getType().isAssignableFrom(chainProvider.chainType)) {
delegateFound = true;
break topLoop;
}
}
} while ((clazz = clazz.getSuperclass()) != null);
if (!delegateFound) {
binder.addError(new Message(elemClass,
"Invalid delegate class : the class does not have a field of type "
+ chainProvider.chainType + " annotated with @InjectDelegate"));
}
}
public ChainBuilder<T> followedBy(final Class<? extends T> elemClass) {
checkDelegateClass(elemClass);
chainProvider.add(binder.getProvider(elemClass));
return this;
}
public ChainBuilder<T> followedBy(final Provider<? extends T> elemProvider) {
chainProvider.add(elemProvider);
return this;
}
public ChainBuilder<T> followedBy(final T elem) {
checkDelegateClass(elem.getClass());
chainProvider.add(Providers.of(elem));
return this;
}
public ChainBuilder<T> followedBy(final Key<T> elemKey) {
checkDelegateClass(elemKey.getTypeLiteral().getRawType());
chainProvider.add(binder.getProvider(elemKey));
return this;
}
public ScopedBindingBuilder endingWith(final Class<? extends T> elemClass) {
chainProvider.add(binder.getProvider(elemClass));
return delegate;
}
public ScopedBindingBuilder endingWith(final Provider<T> elemProvider) {
chainProvider.add(elemProvider);
return delegate;
}
public ScopedBindingBuilder endingWith(final T elem) {
chainProvider.add(Providers.of(elem));
return delegate;
}
public ScopedBindingBuilder endingWith(final Key<T> elemKey) {
chainProvider.add(binder.getProvider(elemKey));
return delegate;
}
public void in(final Class<? extends Annotation> scopeAnnotation) {
delegate.in(scopeAnnotation);
}
public void in(final Scope scope) {
delegate.in(scope);
}
public void asEagerSingleton() {
delegate.asEagerSingleton();
}
}
protected static class ChainProvider<T> implements Provider<T> {
final Class<? super T> chainType;
List<Provider<? extends T>> providers = new ArrayList<Provider<? extends T>>();
T head = null;
ChainProvider(final Class<? super T> chainType) {
this.chainType = chainType;
}
public T get() {
if (head == null) {
T prevElement = null;
for (final Provider<? extends T> provider : providers) {
final T elem = provider.get();
if (prevElement != null) {
// inject delegate
Class<?> clazz = prevElement.getClass();
do {
for (final Field f : clazz.getDeclaredFields()) {
if (f.getAnnotation(InjectDelegate.class) != null
&& f.getType().isAssignableFrom(chainType)) {
f.setAccessible(true);
try {
f.set(prevElement, elem);
} catch (final IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (final IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
} while ((clazz = clazz.getSuperclass()) != null);
} else {
head = elem;
}
prevElement = elem;
}
}
return head;
}
void add(final Provider<? extends T> provider) {
providers.add(provider);
}
}
}