NeedleStepsFactory.java
package org.jbehave.core.steps.needle;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jbehave.core.annotations.AsParameterConverter;
import org.jbehave.core.configuration.Configuration;
import org.jbehave.core.configuration.MostUsefulConfiguration;
import org.jbehave.core.steps.AbstractStepsFactory;
import org.jbehave.core.steps.CandidateSteps;
import org.jbehave.core.steps.InjectableStepsFactory;
import org.jbehave.core.steps.ParameterConverters.MethodReturningConverter;
import org.jbehave.core.steps.ParameterConverters.ParameterConverter;
import org.jbehave.core.steps.Steps;
import org.jbehave.core.steps.needle.configuration.CollectInjectionProvidersFromStepsInstance;
import org.jbehave.core.steps.needle.configuration.CreateInstanceByDefaultConstructor;
import org.jbehave.core.steps.needle.configuration.JBehaveNeedleConfiguration;
import org.needle4j.NeedleTestcase;
import org.needle4j.injection.InjectionProvider;
import org.needle4j.reflection.ReflectionUtil;
/**
* An {@link InjectableStepsFactory} that uses a Needle {@link InjectionProvider} for the composition and instantiation
* of all components that contain JBehave annotated methods.
* @author Simon Zambrovski (simon.zambrovski@holisticon.de)
* @author Jan Galinski (jan.galinski@holisticon.de)
*/
public class NeedleStepsFactory extends NeedleTestcase implements InjectableStepsFactory {
private final Map<Class<?>, Object> cachedTypeInstances = new LinkedHashMap<>();
private Configuration configuration;
private Class<?>[] steps;
/**
* Creates factory with given configuration and step instances.
* @param configuration
* JBehave configuration
* @param steps
* step classes
*/
public NeedleStepsFactory(final Configuration configuration, final Class<?>... steps) {
this(configuration, null, steps);
}
/**
* Creates factory with given configuration, injection providers and step instances.
* @param configuration
* JBehave configuration
* @param injectionProviders
* injection providers.
* @param steps
* step classes
*/
public NeedleStepsFactory(final Configuration configuration, final Set<InjectionProvider<?>> injectionProviders,
final Class<?>... steps) {
super(setUpInjectionProviders(JBehaveNeedleConfiguration.RESOURCE_JBEHAVE_NEEDLE));
if (injectionProviders != null) {
addInjectionProvider(toArray(injectionProviders));
}
if (this.configuration == null) {
this.configuration = new MostUsefulConfiguration();
} else {
this.configuration = configuration;
}
this.steps = steps;
}
/**
* {@inheritDoc}
*/
@Override
public List<CandidateSteps> createCandidateSteps() {
final List<CandidateSteps> result = new ArrayList<>();
for (final Class<?> type : steps) {
if (hasAnnotatedMethods(type)) {
configuration.parameterConverters().addConverters(methodReturningConverters(type));
result.add(new Steps(configuration, type, this));
}
}
return result;
}
@Override
public Object createInstanceOfType(final Class<?> type) {
final Object instance = cachedTypeInstances.get(type);
if (instance == null) {
try {
final Object stepsInstance = createInstanceUsingNeedleTestCase(type);
final InjectionProvider<?>[] foundProviders = CollectInjectionProvidersFromStepsInstance.INSTANCE.apply(
stepsInstance);
addInjectionProvider(foundProviders);
initTestcase(stepsInstance);
cachedTypeInstances.put(type, stepsInstance);
return stepsInstance;
} catch (final Exception e) {
throw new IllegalStateException(e);
}
}
return instance;
}
/**
* Uses private instantiation methods of NeedleTestCase via
* {@link ReflectionUtil#invokeMethod(Object, String, Object...)}. First tries to create new instance with
* constructor injection, then falls back to default constructor. If creation fails, an IllegalStateException is
* thrown.
*
* @param type type of instance to create
* @return new instance of type. Never <code>null</code>
* @throws IllegalStateException when creation fails.
*/
private Object createInstanceUsingNeedleTestCase(final Class<?> type) throws IllegalStateException {
try {
Object instance = ReflectionUtil.invokeMethod(this, "getInstanceByConstructorInjection", type);
if (instance == null) {
instance = CreateInstanceByDefaultConstructor.INSTANCE.apply(type);
}
if (instance == null) {
throw new IllegalStateException("failed to create instance of type " + type.getCanonicalName());
}
return instance;
} catch (final Exception e) {
throw new IllegalStateException(e);
}
}
/**
* Create parameter converters from methods annotated with @AsParameterConverter
* @see AbstractStepsFactory
*/
private List<ParameterConverter> methodReturningConverters(final Class<?> type) {
final List<ParameterConverter> converters = new ArrayList<>();
for (final Method method : type.getMethods()) {
if (method.isAnnotationPresent(AsParameterConverter.class)) {
converters.add(new MethodReturningConverter(method, type, this));
}
}
return converters;
}
/**
* Add injection providers.
* @param providers
* add injection providers after factory construction.
*/
public void addInjectionProviders(final Set<InjectionProvider<?>> providers) {
if (providers != null) {
addInjectionProvider(toArray(providers));
}
}
/**
* Determines if the given type is a {@link Class} containing at least one method annotated with annotations from
* package "org.jbehave.core.annotations".
* @param type
* the Type of the steps instance
* @return A boolean, <code>true</code> if at least one annotated method is found.
* @see AbstractStepsFactory
*/
static boolean hasAnnotatedMethods(final Type type) {
if (type instanceof Class<?>) {
for (final Method method : ((Class<?>)type).getMethods()) {
for (final Annotation annotation : method.getAnnotations()) {
if (annotation.annotationType().getName().startsWith("org.jbehave.core.annotations")) {
return true;
}
}
}
}
return false;
}
/**
* Read injection providers configuration from a resource.
* @param resourceName
* resource name
* @return injection providers.
*/
static InjectionProvider<?>[] setUpInjectionProviders(final String resourceName) {
return new JBehaveNeedleConfiguration(resourceName).getInjectionProviders();
}
/**
* Set to array.
* @param injectionProviders
* set of providers
* @return array of providers
*/
static InjectionProvider<?>[] toArray(final Set<InjectionProvider<?>> injectionProviders) {
return injectionProviders.toArray(new InjectionProvider<?>[injectionProviders.size()]);
}
}