Permalink
Browse files

Added a mechanism that lets users register their own converters. Rele…

…vant to #308. Not integrated to master because of shading/jarjar issues related to xstream. Needs to be fixed.
  • Loading branch information...
1 parent f388f5e commit 5f8d015dff72ae24e8b8f102bdb889a64774fabc @aslakhellesoy aslakhellesoy committed Apr 26, 2012
Showing with 177 additions and 61 deletions.
  1. +1 −1 clojure/src/main/clj/cucumber/runtime/clj.clj
  2. +6 −1 core/src/main/java/cucumber/runtime/Backend.java
  3. +11 −17 core/src/main/java/cucumber/runtime/Runtime.java
  4. +5 −1 core/src/main/java/cucumber/runtime/StepDefinitionMatch.java
  5. +16 −2 core/src/main/java/cucumber/runtime/converters/LocalizedXStreams.java
  6. +5 −1 core/src/test/java/cucumber/runtime/StepDefinitionMatchTest.java
  7. +2 −1 core/src/test/java/cucumber/runtime/UndefinedStepsTrackerTest.java
  8. +2 −1 groovy/src/main/java/cucumber/runtime/groovy/GroovyBackend.java
  9. +2 −1 ioke/src/main/java/cucumber/runtime/ioke/IokeBackend.java
  10. +24 −11 java/src/main/java/cucumber/runtime/java/{ClasspathMethodScanner.java → GlueScanner.java}
  11. +8 −6 java/src/main/java/cucumber/runtime/java/JavaBackend.java
  12. +7 −0 java/src/main/java/cucumber/runtime/java/ObjectMapper.java
  13. +6 −6 java/src/test/java/cucumber/runtime/java/{ClasspathMethodScannerTest.java → GlueScannerTest.java}
  14. +9 −4 java/src/test/java/cucumber/runtime/java/JavaBackendTest.java
  15. +1 −1 java/src/test/java/cucumber/runtime/java/JavaHookTest.java
  16. +2 −1 java/src/test/java/cucumber/runtime/java/JavaStepDefinitionTest.java
  17. +2 −1 jruby/src/main/java/cucumber/runtime/jruby/JRubyBackend.java
  18. +2 −1 junit/src/test/java/cucumber/runtime/stub/StubBackend.java
  19. +2 −1 jython/src/main/java/cucumber/runtime/jython/JythonBackend.java
  20. +9 −0 picocontainer/src/main/java/cucumber/runtime/java/picocontainer/Thing.java
  21. +11 −0 picocontainer/src/test/java/cucumber/runtime/java/picocontainer/MyObjectMapper.java
  22. +15 −0 picocontainer/src/test/java/cucumber/runtime/java/picocontainer/ThingConverter.java
  23. +20 −0 picocontainer/src/test/java/cucumber/runtime/java/picocontainer/ThingSteps.java
  24. +4 −0 ...iner/src/test/resources/cucumber/runtime/java/picocontainer/globally_registered_converter.feature
  25. +1 −1 pom.xml
  26. +2 −1 rhino/src/main/java/cucumber/runtime/rhino/RhinoBackend.java
  27. +2 −1 scala/src/main/scala/cucumber/runtime/ScalaBackend.scala
@@ -46,7 +46,7 @@
(defn- -init [resource-loader]
[[] (atom {:resource-loader resource-loader})])
-(defn -loadGlue [cljb a-glue glue-paths]
+(defn -loadGlue [cljb a-glue glue-paths, localized-xstreams]
(reset! glue a-glue)
(doseq [path glue-paths
resource (.resources (:resource-loader @(.state cljb)) path ".clj")]
@@ -1,14 +1,19 @@
package cucumber.runtime;
+import cucumber.runtime.converters.LocalizedXStreams;
import gherkin.formatter.model.Step;
import java.util.List;
public interface Backend {
/**
* Invoked once before all features. This is where stepdefs and hooks should be loaded.
+ *
+ * @param glue where hooks and stepdefs should be registered
+ * @param gluePaths where stepdefs and hooks can be loaded from. Typically a path or a package name
+ * @param localizedXStreams
*/
- void loadGlue(Glue glue, List<String> gluePaths);
+ void loadGlue(Glue glue, List<String> gluePaths, LocalizedXStreams localizedXStreams);
/**
* invoked once, handing the backend a reference to a step executor
@@ -39,12 +39,12 @@
private final List<Throwable> errors = new ArrayList<Throwable>();
private final Collection<? extends Backend> backends;
private final ResourceLoader resourceLoader;
+ private final ClassLoader classLoader;
//TODO: These are really state machine variables, and I'm not sure the runtime is the best place for this state machine
//They really should be created each time a scenario is run, not in here
private boolean skipNextStep = false;
private ScenarioResultImpl scenarioResult = null;
- private ClassLoader classLoader;
public Runtime(ResourceLoader resourceLoader, ClassLoader classLoader, RuntimeOptions runtimeOptions) {
this(resourceLoader, classLoader, loadBackends(resourceLoader, classLoader), runtimeOptions);
@@ -57,10 +57,11 @@ public Runtime(ResourceLoader resourceLoader, ClassLoader classLoader, Collectio
throw new CucumberException("No backends were found. Please make sure you have a backend module on your CLASSPATH.");
}
this.backends = backends;
- glue = new RuntimeGlue(undefinedStepsTracker, new LocalizedXStreams(classLoader));
+ LocalizedXStreams localizedXStreams = new LocalizedXStreams(classLoader);
+ this.glue = new RuntimeGlue(undefinedStepsTracker, localizedXStreams);
for (Backend backend : backends) {
- backend.loadGlue(glue, runtimeOptions.glue);
+ backend.loadGlue(glue, runtimeOptions.glue, localizedXStreams);
backend.setUnreportedStepExecutor(this);
}
this.runtimeOptions = runtimeOptions;
@@ -127,32 +128,25 @@ public byte exitStatus() {
return result;
}
- private boolean hasUndefinedOrPendingStepsAndIsStrict()
- {
+ private boolean hasUndefinedOrPendingStepsAndIsStrict() {
return runtimeOptions.strict && hasUndefinedOrPendingSteps();
}
- private boolean hasUndefinedOrPendingSteps()
- {
+ private boolean hasUndefinedOrPendingSteps() {
return hasUndefinedSteps() || hasPendingSteps();
}
- private boolean hasUndefinedSteps()
- {
+ private boolean hasUndefinedSteps() {
return undefinedStepsTracker.hasUndefinedSteps();
}
- private boolean hasPendingSteps()
- {
+ private boolean hasPendingSteps() {
return !errors.isEmpty() && !hasErrors();
}
- private boolean hasErrors()
- {
- for (Throwable error : errors)
- {
- if (!(error instanceof PendingException))
- {
+ private boolean hasErrors() {
+ for (Throwable error : errors) {
+ if (!(error instanceof PendingException)) {
return true;
}
}
@@ -101,7 +101,11 @@ public void runStep(I18n i18n) throws Throwable {
"Try writing your own converter:\n" +
"\n" +
"@%s(%sConverter.class)\n" +
- "public class %s {}\n",
+ "public class %s {}\n" +
+ "\n" +
+ "Alternatively, leave out the annotation and\n" +
+ "register it in your own implementation of\n" +
+ "ObjectMapper.\n",
a.getVal(),
parameterType.getParameterClass().getName(),
XStreamConverter.class.getName(),
@@ -1,6 +1,7 @@
package cucumber.runtime.converters;
import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.ConverterLookup;
import com.thoughtworks.xstream.converters.ConverterRegistry;
import com.thoughtworks.xstream.converters.SingleValueConverter;
@@ -16,6 +17,7 @@
public class LocalizedXStreams {
private final Map<I18n, LocalizedXStream> xStreams = new HashMap<I18n, LocalizedXStream>();
private final ClassLoader classLoader;
+ private final List<SingleValueConverter> converters = new ArrayList<SingleValueConverter>();
public LocalizedXStreams(ClassLoader classLoader) {
this.classLoader = classLoader;
@@ -30,14 +32,22 @@ public LocalizedXStream get(I18n i18n) {
return xStream;
}
+ /**
+ * Registers a custom converter.
+ * @param converter
+ */
+ public void registerConverter(SingleValueConverter converter) {
+ converters.add(converter);
+ }
+
private LocalizedXStream newXStream(Locale locale) {
DefaultConverterLookup lookup = new DefaultConverterLookup();
return new LocalizedXStream(classLoader, lookup, lookup, locale);
}
- public static class LocalizedXStream extends XStream {
+ public class LocalizedXStream extends XStream {
private final Locale locale;
- private static List<TimeConverter> timeConverters = new ArrayList<TimeConverter>();
+ private final List<TimeConverter> timeConverters = new ArrayList<TimeConverter>();
public LocalizedXStream(ClassLoader classLoader, ConverterLookup converterLookup, ConverterRegistry converterRegistry, Locale locale) {
super(null, null, classLoader, null, converterLookup, converterRegistry);
@@ -54,6 +64,10 @@ public LocalizedXStream(ClassLoader classLoader, ConverterLookup converterLookup
register(converterRegistry, new FloatConverter(locale));
register(converterRegistry, new IntegerConverter(locale));
register(converterRegistry, new LongConverter(locale));
+
+ for (SingleValueConverter converter : converters) {
+ registerConverter(converter);
+ }
}
private void register(ConverterRegistry lookup, SingleValueConverter converter) {
@@ -109,7 +109,11 @@ public void gives_nice_error_message_when_conversion_fails() throws Throwable {
"Try writing your own converter:\n" +
"\n" +
"@com.thoughtworks.xstream.annotations.XStreamConverter(ThangConverter.class)\n" +
- "public class Thang {}\n",
+ "public class Thang {}\n" +
+ "\n" +
+ "Alternatively, leave out the annotation and\n" +
+ "register it in your own implementation of\n" +
+ "ObjectMapper.\n",
expected.getMessage()
);
}
@@ -1,5 +1,6 @@
package cucumber.runtime;
+import cucumber.runtime.converters.LocalizedXStreams;
import cucumber.runtime.snippets.Snippet;
import cucumber.runtime.snippets.SnippetGenerator;
import gherkin.I18n;
@@ -80,7 +81,7 @@ public void snippets_are_generated_for_correct_locale() throws Exception {
private class TestBackend implements Backend {
@Override
- public void loadGlue(Glue glue, List<String> gluePaths) {
+ public void loadGlue(Glue glue, List<String> gluePaths, LocalizedXStreams localizedXStreams) {
throw new UnsupportedOperationException();
}
@@ -7,6 +7,7 @@
import cucumber.runtime.CucumberException;
import cucumber.runtime.Glue;
import cucumber.runtime.UnreportedStepExecutor;
+import cucumber.runtime.converters.LocalizedXStreams;
import cucumber.runtime.snippets.SnippetGenerator;
import gherkin.TagExpression;
import gherkin.formatter.model.Step;
@@ -51,7 +52,7 @@ public GroovyBackend(GroovyShell shell, ResourceLoader resourceLoader) {
}
@Override
- public void loadGlue(Glue glue, List<String> gluePaths) {
+ public void loadGlue(Glue glue, List<String> gluePaths, LocalizedXStreams localizedXStreams) {
this.glue = glue;
final Binding context = shell.getContext();
@@ -6,6 +6,7 @@
import cucumber.runtime.CucumberException;
import cucumber.runtime.Glue;
import cucumber.runtime.UnreportedStepExecutor;
+import cucumber.runtime.converters.LocalizedXStreams;
import cucumber.runtime.snippets.SnippetGenerator;
import cucumber.table.DataTable;
import gherkin.formatter.model.Step;
@@ -44,7 +45,7 @@ public IokeBackend(ResourceLoader resourceLoader) {
}
@Override
- public void loadGlue(Glue glue, List<String> gluePaths) {
+ public void loadGlue(Glue glue, List<String> gluePaths, LocalizedXStreams localizedXStreams) {
this.glue = glue;
for (String gluePath : gluePaths) {
@@ -6,19 +6,20 @@
import cucumber.io.ClasspathResourceLoader;
import cucumber.runtime.CucumberException;
import cucumber.runtime.Utils;
+import cucumber.runtime.converters.LocalizedXStreams;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
-class ClasspathMethodScanner {
+class GlueScanner {
- private final ClasspathResourceLoader resourceLoader;
+ private final ClasspathResourceLoader classpathResourceLoader;
private final Collection<Class<? extends Annotation>> cucumberAnnotationClasses;
- public ClasspathMethodScanner(ClasspathResourceLoader resourceLoader) {
- this.resourceLoader = resourceLoader;
+ public GlueScanner(ClasspathResourceLoader classpathResourceLoader) {
+ this.classpathResourceLoader = classpathResourceLoader;
cucumberAnnotationClasses = findCucumberAnnotationClasses();
}
@@ -30,11 +31,11 @@ public ClasspathMethodScanner(ClasspathResourceLoader resourceLoader) {
*/
public void scan(JavaBackend javaBackend, List<String> gluePaths) {
for (String gluePath : gluePaths) {
- if(gluePath.contains("/") || gluePath.contains("\\")) {
+ if (gluePath.contains("/") || gluePath.contains("\\")) {
throw new CucumberException("Java glue must be a Java package name - not a path: " + gluePath);
}
// We can be fairly confident that gluePath is a package name at this point
- for (Class<?> glueCodeClass : resourceLoader.getDescendants(Object.class, gluePath)) {
+ for (Class<?> glueCodeClass : classpathResourceLoader.getDescendants(Object.class, gluePath)) {
while (glueCodeClass != null && glueCodeClass != Object.class && !Utils.isInstantiable(glueCodeClass)) {
// those can't be instantiated without container class present.
glueCodeClass = glueCodeClass.getSuperclass();
@@ -48,21 +49,33 @@ public void scan(JavaBackend javaBackend, List<String> gluePaths) {
}
}
+ public void configureXStream(LocalizedXStreams localizedXStreams, List<String> gluePaths) {
+ for (String gluePath : gluePaths) {
+ if (gluePath.contains("/") || gluePath.contains("\\")) {
+ throw new CucumberException("Java glue must be a Java package name - not a path: " + gluePath);
+ }
+ // We can be fairly confident that gluePath is a package name at this point
+ for (ObjectMapper objectMapper : classpathResourceLoader.instantiateSubclasses(ObjectMapper.class, gluePath, new Class[0], new Object[0])) {
+ objectMapper.configure(localizedXStreams);
+ }
+ }
+ }
+
/**
* Registers step definitions and hooks.
*
- * @param javaBackend the backend where stepdefs and hooks will be registered
- * @param method a candidate for being a stepdef or hook
+ * @param javaBackend the backend where stepdefs and hooks will be registered
+ * @param method a candidate for being a stepdef or hook
* @param glueCodeClass
*/
public void scan(JavaBackend javaBackend, Method method, Class<?> glueCodeClass) {
for (Class<? extends Annotation> cucumberAnnotationClass : cucumberAnnotationClasses) {
Annotation annotation = method.getAnnotation(cucumberAnnotationClass);
if (annotation != null && !annotation.annotationType().equals(Order.class)) {
- if(!method.getDeclaringClass().isAssignableFrom(glueCodeClass)) {
+ if (!method.getDeclaringClass().isAssignableFrom(glueCodeClass)) {
throw new CucumberException(String.format("%s isn't assignable from %s", method.getDeclaringClass(), glueCodeClass));
}
- if(!glueCodeClass.equals(method.getDeclaringClass())) {
+ if (!glueCodeClass.equals(method.getDeclaringClass())) {
throw new CucumberException(String.format("You're not allowed to extend classes that define Step Definitions or hooks. %s extends %s", glueCodeClass, method.getDeclaringClass()));
}
if (isHookAnnotation(annotation)) {
@@ -75,7 +88,7 @@ public void scan(JavaBackend javaBackend, Method method, Class<?> glueCodeClass)
}
private Collection<Class<? extends Annotation>> findCucumberAnnotationClasses() {
- return resourceLoader.getAnnotations("cucumber.annotation");
+ return classpathResourceLoader.getAnnotations("cucumber.annotation");
}
private boolean isHookAnnotation(Annotation annotation) {
@@ -11,6 +11,7 @@
import cucumber.runtime.Glue;
import cucumber.runtime.UnreportedStepExecutor;
import cucumber.runtime.Utils;
+import cucumber.runtime.converters.LocalizedXStreams;
import cucumber.runtime.snippets.SnippetGenerator;
import gherkin.formatter.model.Step;
@@ -23,18 +24,18 @@
private final SnippetGenerator snippetGenerator = new SnippetGenerator(new JavaSnippet());
private final ObjectFactory objectFactory;
private final ClasspathResourceLoader classpathResourceLoader;
- private final ClasspathMethodScanner classpathMethodScanner;
+ private final GlueScanner glueScanner;
private Glue glue;
public JavaBackend(ResourceLoader ignored) {
classpathResourceLoader = new ClasspathResourceLoader(Thread.currentThread().getContextClassLoader());
- classpathMethodScanner = new ClasspathMethodScanner(classpathResourceLoader);
+ glueScanner = new GlueScanner(classpathResourceLoader);
objectFactory = loadObjectFactory();
}
public JavaBackend(ObjectFactory objectFactory) {
classpathResourceLoader = new ClasspathResourceLoader(Thread.currentThread().getContextClassLoader());
- classpathMethodScanner = new ClasspathMethodScanner(classpathResourceLoader);
+ glueScanner = new GlueScanner(classpathResourceLoader);
this.objectFactory = objectFactory;
}
@@ -53,9 +54,10 @@ private ObjectFactory loadObjectFactory() {
}
@Override
- public void loadGlue(Glue glue, List<String> gluePaths) {
+ public void loadGlue(Glue glue, List<String> gluePaths, LocalizedXStreams localizedXStreams) {
this.glue = glue;
- classpathMethodScanner.scan(this, gluePaths);
+ glueScanner.configureXStream(localizedXStreams, gluePaths);
+ glueScanner.scan(this, gluePaths);
}
/**
@@ -68,7 +70,7 @@ public void loadGlue(Glue glue, List<String> gluePaths) {
*/
public void loadGlue(Glue glue, Method method, Class<?> glueCodeClass) {
this.glue = glue;
- classpathMethodScanner.scan(this, method, glueCodeClass);
+ glueScanner.scan(this, method, glueCodeClass);
}
@Override
@@ -0,0 +1,7 @@
+package cucumber.runtime.java;
+
+import cucumber.runtime.converters.LocalizedXStreams;
+
+public interface ObjectMapper {
+ void configure(LocalizedXStreams localizedXStreams);
+}
Oops, something went wrong.

0 comments on commit 5f8d015

Please sign in to comment.