Skip to content

Commit

Permalink
Minor tweaks to needle module. Closes cucumber#496, cucumber#500
Browse files Browse the repository at this point in the history
  • Loading branch information
aslakhellesoy committed Aug 14, 2013
2 parents c4a7d9e + c6f3111 commit 09848fa
Show file tree
Hide file tree
Showing 42 changed files with 1,264 additions and 5 deletions.
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ nexus.properties
pom.xml.releaseBackup
release.properties
*.ser
dependency-reduced-pom.xml
*~
libpeerconnection.log

# OS generated files
.DS_Store*
ehthumbs.db
Icon?
Thumbs.db
dependency-reduced-pom.xml
*~
libpeerconnection.log
1 change: 1 addition & 0 deletions History.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## [Git master](https://github.com/cucumber/cucumber-jvm/compare/v1.1.4...master)

* [Java,Needle] New DI engine: Needle. ([#496](https://github.com/cucumber/cucumber-jvm/issues/496), [#500](https://github.com/cucumber/cucumber-jvm/pull/500) Jan Galinski)
* [Core] Bugfix: StringIndexOutOfBoundsException when optional argument not present. ([#394](https://github.com/cucumber/cucumber-jvm/issues/394), [#558](https://github.com/cucumber/cucumber-jvm/pull/558) Guy Burton)
* [Java, Jython] New `--snippet [underscore|camelcase]` option for more control over snippet style. ([#561](https://github.com/cucumber/cucumber-jvm/pull/561), [302](https://github.com/cucumber/cucumber-jvm/pull/302) Márton Mészáros, Aslak Hellesøy)
* [Windows] Use uri instead of path in CucumberFeature ([#562](https://github.com/cucumber/cucumber-jvm/pull/562) Björn Rasmusson)
Expand Down
30 changes: 28 additions & 2 deletions java/src/main/java/cucumber/runtime/java/ObjectFactory.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,37 @@
package cucumber.runtime.java;

/**
* Minimal facade for Dependency Injection containers
*/
public interface ObjectFactory {

/**
* Instantiate glue code <b>before</b> scenario execution. Called once per scenario.
*/
void start();

/**
* Dispose glue code <b>after</b> scenario execution. Called once per scenario.
*/
void stop();

void addClass(Class<?> clazz);
/**
* Collects glue classes in the classpath. Called once on init.
*
* @param glueClass
* Glue class containing cucumber.api annotations (Before, Given, When, ...)
*/
void addClass(Class<?> glueClass);

<T> T getInstance(Class<T> type);
/**
* Provides the glue instances used to execute the current scenario. The instance can be prepared in
* {@link #start()}.
*
* @param glueClass
* type of instance to be created.
* @param <T>
* type of Glue class
* @return new Glue instance of type <T>
*/
<T> T getInstance(Class<T> glueClass);
}
8 changes: 8 additions & 0 deletions needle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# cucumber-needle

Implements an ObjectFactory that instantiates glue code via [needle](http://needle.spree.de).

This allows easy configurable dependency injection without relying on a full blown DI
container and automatic mocking of all not explicitly specified dependencies.

See [needle tutorial](http://needle.spree.de/tutorial) for more information.
82 changes: 82 additions & 0 deletions needle/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>info.cukes</groupId>
<artifactId>cucumber-jvm</artifactId>
<relativePath>../pom.xml</relativePath>
<version>1.1.5-SNAPSHOT</version>
</parent>

<artifactId>cucumber-needle</artifactId>
<packaging>jar</packaging>
<name>Cucumber-JVM: Needle</name>

<build>
<defaultGoal>clean install</defaultGoal>
</build>

<dependencies>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-jvm-deps</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>gherkin</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.cobertura</groupId>
<artifactId>cobertura</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.akquinet.jbosscc</groupId>
<artifactId>jbosscc-needle</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package cucumber.api.needle;

import cucumber.runtime.java.needle.NeedleFactory;
import de.akquinet.jbosscc.needle.NeedleTestcase;
import de.akquinet.jbosscc.needle.injection.InjectionProvider;

import java.util.Set;

/**
* <a href="http://javadocs.techempower.com/jdk18/api/java/util/function/Supplier.html">Supplies</a> a Set of
* InjectionProvider instances that are created outside the {@link NeedleFactory} lifecycle.
*/
public interface InjectionProviderInstancesSupplier {

/**
* <a href="http://javadocs.techempower.com/jdk18/api/java/util/function/Supplier.html">Supplies</a> a Set of
* InjectionProvider instances that are created outside the {@link NeedleFactory} lifecycle.
*
* @return InjectionProviders that can be added to {@link NeedleTestcase}
*/
Set<InjectionProvider<?>> get();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package cucumber.api.needle;

import de.akquinet.jbosscc.needle.injection.InjectionProvider;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation to mark InjectionProviders in the cucumber glue or cucumber steps. <br/>
* Should be placed on fields of type {@link InjectionProvider} or an array of those.
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NeedleInjectionProvider {
// Nothing here
}
10 changes: 10 additions & 0 deletions needle/src/main/java/cucumber/api/needle/package.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<body>
<p>
By including the <code>cucumber-needle</code> jar
on your <code>CLASSPATH</code> your Glue classes will be instantiated
by <a href="http://needle.spree.de/overview">needle</a>.

You can use the InjectionProviderInstanceSupplier and NeedleInjectionProvider to configure the behavior of the
needle injection.
</p>
</body>
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package cucumber.runtime.java.needle;

import cucumber.runtime.java.ObjectFactory;
import cucumber.runtime.java.needle.config.CollectInjectionProvidersFromStepsInstance;
import cucumber.runtime.java.needle.config.CreateInstanceByDefaultConstructor;
import cucumber.runtime.java.needle.config.CucumberNeedleConfiguration;
import de.akquinet.jbosscc.needle.NeedleTestcase;
import de.akquinet.jbosscc.needle.injection.InjectionProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.LinkedHashMap;
import java.util.Map;

import static cucumber.runtime.java.needle.config.CucumberNeedleConfiguration.RESOURCE_CUCUMBER_NEEDLE;
import static java.lang.String.format;

/**
* Needle factory for object resolution inside of cucumber tests.
*/
public class NeedleFactory extends NeedleTestcase implements ObjectFactory {

private final Map<Class<?>, Object> cachedStepsInstances = new LinkedHashMap<Class<?>, Object>();
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final CreateInstanceByDefaultConstructor createInstanceByDefaultConstructor = CreateInstanceByDefaultConstructor.INSTANCE;
private final CollectInjectionProvidersFromStepsInstance collectInjectionProvidersFromStepsInstance = CollectInjectionProvidersFromStepsInstance.INSTANCE;

public NeedleFactory() {
super(setUpInjectionProviders(RESOURCE_CUCUMBER_NEEDLE));
}

@Override
public <T> T getInstance(final Class<T> type) {
logger.trace("getInstance: " + type.getCanonicalName());
assertTypeHasBeenAdded(type);
return nullSafeGetInstance(type);
}

@Override
public void start() {
logger.trace("start()");
for (final Class<?> stepDefinitionType : cachedStepsInstances.keySet()) {
cachedStepsInstances.put(stepDefinitionType, createStepsInstance(stepDefinitionType));
}
}

@Override
public void stop() {
logger.trace("stop()");
for (final Class<?> stepDefinitionType : cachedStepsInstances.keySet()) {
cachedStepsInstances.put(stepDefinitionType, null);
}
}

@Override
public void addClass(final Class<?> type) {
logger.trace("addClass(): " + type.getCanonicalName());

// build up cache keys ...
if (!cachedStepsInstances.containsKey(type)) {
cachedStepsInstances.put(type, null);
}
}

private void assertTypeHasBeenAdded(final Class<?> type) {
if (!cachedStepsInstances.containsKey(type)) {
throw new IllegalStateException(format("%s was not added during addClass()", type.getSimpleName()));
}
}

@SuppressWarnings("unchecked")
private <T> T nullSafeGetInstance(final Class<T> type) {
final Object instance = cachedStepsInstances.get(type);
if (instance == null) {
throw new IllegalStateException(format("instance of type %s has not been initialized in start()!",
type.getSimpleName()));
}
return (T) instance;
}

private <T> T createStepsInstance(final Class<T> type) {
logger.trace("createInstance(): " + type.getCanonicalName());
try {
final T stepsInstance = createInstanceByDefaultConstructor.apply(type);
addInjectionProvider(collectInjectionProvidersFromStepsInstance.apply(stepsInstance));
initTestcase(stepsInstance);
return stepsInstance;
} catch (final Exception e) {
throw new IllegalStateException(e);
}
}

static InjectionProvider<?>[] setUpInjectionProviders(final String resourceName) {
return new CucumberNeedleConfiguration(resourceName).getInjectionProviders();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package cucumber.runtime.java.needle.config;

import cucumber.api.needle.InjectionProviderInstancesSupplier;
import cucumber.api.needle.NeedleInjectionProvider;
import cucumber.runtime.java.needle.NeedleFactory;
import de.akquinet.jbosscc.needle.injection.InjectionProvider;
import de.akquinet.jbosscc.needle.reflection.ReflectionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;

/**
* Collects {@link InjectionProvider} instances.
*/
public enum CollectInjectionProvidersFromStepsInstance {
/**
* stateless Singleton
*/
INSTANCE;

/**
* Logger for the factory.
*/
private final Logger logger = LoggerFactory.getLogger(NeedleFactory.class);

/**
* Collect providers direct in the step definition.
*
* @param instance step definition instance
* @return collected injection providers.
*/
public final <T> InjectionProvider<?>[] apply(final T instance) {
final Set<InjectionProvider<?>> providers = new LinkedHashSet<InjectionProvider<?>>();
for (final Field field : ReflectionUtil.getAllFieldsWithAnnotation(instance, NeedleInjectionProvider.class)) {
field.setAccessible(true);
try {
final Object value = field.get(instance);
if (value instanceof InjectionProvider<?>[]) {
providers.addAll(Arrays.asList((InjectionProvider<?>[]) value));
} else if (value instanceof InjectionProvider) {
providers.add((InjectionProvider<?>) value);
} else if (value instanceof InjectionProviderInstancesSupplier) {
providers.addAll(((InjectionProviderInstancesSupplier) value).get());
} else {
throw new IllegalStateException("Fields annotated with NeedleInjectionProviders must be of type "
+ "InjectionProviderInstancesSupplier, InjectionProvider " + "or InjectionProvider[]");
}
} catch (final Exception e) {
throw new IllegalStateException(e);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Adding {} InjectionProvider instances.", providers.size());
}

return providers.toArray(new InjectionProvider<?>[providers.size()]);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package cucumber.runtime.java.needle.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Instantiates new java object by default constructor
*/
public enum CreateInstanceByDefaultConstructor {
/**
* Singleton
*/
INSTANCE;

private final Logger logger = LoggerFactory.getLogger(this.getClass());

public final <T> T apply(final Class<T> type) {
try {
final T newInstance = type.getConstructor().newInstance();
logger.debug("newInstance by DefaultConstructor: " + newInstance);
return newInstance;
} catch (final Exception e) {
throw new IllegalStateException("Can not instantiate Instance by Default Constructor.", e);
}
}

}
Loading

0 comments on commit 09848fa

Please sign in to comment.