GuiceAnnotationBuilder.java

package org.jbehave.core.configuration.guice;

import java.util.ArrayList;
import java.util.List;

import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.TypeLiteral;
import com.google.inject.util.Modules;

import org.jbehave.core.annotations.guice.UsingGuice;
import org.jbehave.core.configuration.AnnotationBuilder;
import org.jbehave.core.configuration.AnnotationFinder;
import org.jbehave.core.configuration.AnnotationMonitor;
import org.jbehave.core.configuration.AnnotationRequired;
import org.jbehave.core.configuration.Configuration;
import org.jbehave.core.configuration.PrintStreamAnnotationMonitor;
import org.jbehave.core.io.ResourceLoader;
import org.jbehave.core.model.TableTransformers;
import org.jbehave.core.steps.CompositeStepsFactory;
import org.jbehave.core.steps.InjectableStepsFactory;
import org.jbehave.core.steps.ParameterConverters;
import org.jbehave.core.steps.ParameterConverters.ParameterConverter;
import org.jbehave.core.steps.guice.GuiceStepsFactory;

/**
 * Extends {@link AnnotationBuilder} to provide Guice-based dependency injection
 * if {@link UsingGuice} annotation is present.
 * 
 * @author Cristiano GaviĆ£o
 * @author Mauro Talevi
 */
public class GuiceAnnotationBuilder extends AnnotationBuilder {

    public Injector injector;

    public GuiceAnnotationBuilder(Class<?> annotatedClass) {
        this(annotatedClass, new PrintStreamAnnotationMonitor());
    }

    public GuiceAnnotationBuilder(Class<?> annotatedClass, AnnotationMonitor annotationMonitor) {
        super(annotatedClass, annotationMonitor);
    }

    @Override
    public Configuration buildConfiguration() throws AnnotationRequired {

        AnnotationFinder finder = annotationFinder();
        if (finder.isAnnotationPresent(UsingGuice.class)) {
            @SuppressWarnings("rawtypes")
            List<Class> moduleClasses = finder.getAnnotatedValues(UsingGuice.class, Class.class, "modules");
            List<Module> modules = new ArrayList<>();
            for (Class<Module> moduleClass : moduleClasses) {
                try {
                    modules.add(moduleClass.newInstance());
                } catch (Exception e) {
                    annotationMonitor().elementCreationFailed(moduleClass, e);
                }
            }
            // creating injector with any modules found
            if (modules.size() > 0) {
                injector = createInjector(modules);
            }
        } else {
            annotationMonitor().annotationNotFound(UsingGuice.class, annotatedClass());
        }
        return super.buildConfiguration();
    }

    @Override
    public InjectableStepsFactory buildStepsFactory(Configuration configuration) {
        InjectableStepsFactory factoryUsingSteps = super.buildStepsFactory(configuration);
        if (injector != null) {
            return new CompositeStepsFactory(new GuiceStepsFactory(configuration, injector), factoryUsingSteps);
        }
        return factoryUsingSteps;
    }

    @Override
    protected ParameterConverters parameterConverters(AnnotationFinder annotationFinder, ResourceLoader resourceLoader,
            TableTransformers tableTransformers) {
        ParameterConverters converters = super.parameterConverters(annotationFinder, resourceLoader, tableTransformers);
        if (injector != null) {
            return converters.addConverters(findConverters(injector));
        }
        return converters;
    }

    /**
     * Finds any {@link ParameterConverter} defined in the given injector and,
     * if none found, recurses to its parent.
     * 
     * @param injector
     *            the Injector
     * @return A List of ParameterConverter instances
     */
    private List<ParameterConverter> findConverters(Injector injector) {
        List<Binding<ParameterConverter>> bindingsByType = injector
                .findBindingsByType(new TypeLiteral<ParameterConverter>() {
                });
        if (bindingsByType.isEmpty() && injector.getParent() != null) {
            return findConverters(injector.getParent());
        }
        List<ParameterConverter> converters = new ArrayList<>();
        for (Binding<ParameterConverter> binding : bindingsByType) {
            converters.add(binding.getProvider().get());
        }
        return converters;
    }

    @Override
    protected <T, V extends T> T instanceOf(final Class<T> type, final Class<V> ofClass) {
        if (injector != null) {
            if (!type.equals(Object.class)) {
                try {
                    boolean bindingFound = findBinding(injector, type);
                    if (bindingFound) {
                        // when binding found, just get the instance associated
                        return injector.getInstance(type);
                    } else {
                        // when binding not found, need to explicitly bind type
                        // + ofClass
                        Module module = new AbstractModule() {

                            @Override
                            protected void configure() {
                                if (!type.equals(ofClass)) {
                                    bind(type).to(ofClass);
                                } else {
                                    // when type and oFClass are
                                    // binding the ofClass
                                    bind(ofClass);
                                }
                            }
                        };

                        injector = injector.createChildInjector(module);
                        return injector.getInstance(type);
                    }
                } catch (Exception e) {
                    // fall back on getting instance ofClass
                    return injector.getInstance(ofClass);
                }
            } else {
                return injector.getBinding(ofClass).getProvider().get();
            }
        }
        return super.instanceOf(type, ofClass);
    }

    /**
     * Finds binding for a type in the given injector and, if not found,
     * recurses to its parent
     * 
     * @param injector
     *            the current Injector
     * @param type
     *            the Class representing the type
     * @return A boolean flag, <code>true</code> if binding found
     */
    private boolean findBinding(Injector injector, Class<?> type) {
        boolean found = false;
        for (Key<?> key : injector.getBindings().keySet()) {
            if (key.getTypeLiteral().getRawType().equals(type)) {
                found = true;
                break;
            }
        }
        if (!found && injector.getParent() != null) {
            return findBinding(injector.getParent(), type);
        }

        return found;
    }

    protected Injector createInjector(List<Module> modules) {
        if (injector != null) {
            return injector;
        }
        Injector root = Guice.createInjector(new AbstractModule() {        
            @Override
            protected void configure() {
        
            }
        });
        return root.createChildInjector(Modules.combine(modules));
    }

    protected Injector injector() {
        return injector;
    }
}