package cucumber.runtime.groovy; import cucumber.runtime.Backend; import cucumber.runtime.ClassFinder; import cucumber.runtime.CucumberException; import cucumber.runtime.Glue; import cucumber.runtime.UnreportedStepExecutor; import cucumber.runtime.io.Resource; import cucumber.runtime.io.ResourceLoader; import cucumber.runtime.io.ResourceLoaderClassFinder; import cucumber.runtime.snippets.FunctionNameGenerator; import cucumber.runtime.snippets.SnippetGenerator; import gherkin.TagExpression; import gherkin.formatter.model.Step; import groovy.lang.Binding; import groovy.lang.Closure; import groovy.lang.GroovyShell; import groovy.lang.Script; import org.codehaus.groovy.control.CompilerConfiguration; import org.codehaus.groovy.runtime.DefaultGroovyMethods; import org.codehaus.groovy.runtime.InvokerInvocationException; import java.io.IOException; import java.io.InputStreamReader; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import static cucumber.runtime.io.MultiLoader.packageName; public class GroovyBackend implements Backend { public static ThreadLocal<GroovyBackend> instanceThreadLocal = new ThreadLocal<GroovyBackend>(); private final Set<Class> scripts = new HashSet<Class>(); private final SnippetGenerator snippetGenerator = new SnippetGenerator(new GroovySnippet()); private final ResourceLoader resourceLoader; private final GroovyShell shell; private final ClassFinder classFinder; private Collection<Closure> worldClosures = new LinkedList<Closure>(); private GroovyWorld world; private Glue glue; public static GroovyBackend getInstance(){ return instanceThreadLocal.get(); } private static GroovyShell createShell() { CompilerConfiguration compilerConfig = new CompilerConfiguration(); // Probably not needed: // compilerConfig.addCompilationCustomizers(new ASTTransformationCustomizer(ThreadInterrupt.class)); return new GroovyShell(Thread.currentThread().getContextClassLoader(), new Binding(), compilerConfig); } public GroovyBackend(ResourceLoader resourceLoader) { this(createShell(), resourceLoader); } public GroovyBackend(GroovyShell shell, ResourceLoader resourceLoader) { this.shell = shell; this.resourceLoader = resourceLoader; instanceThreadLocal.set(this); classFinder = new ResourceLoaderClassFinder(resourceLoader, shell.getClassLoader()); } @Override public void loadGlue(Glue glue, List<String> gluePaths) { this.glue = glue; final Binding context = shell.getContext(); for (String gluePath : gluePaths) { // Load sources for (Resource resource : resourceLoader.resources(gluePath, ".groovy")) { Script script = parse(resource); runIfScript(context, script); } // Load compiled scripts for (Class<? extends Script> glueClass : classFinder.getDescendants(Script.class, packageName(gluePath))) { try { Script script = glueClass.getConstructor(Binding.class).newInstance(context); runIfScript(context, script); } catch (Exception e) { throw new CucumberException(e); } } } } private void runIfScript(Binding context, Script script) { Class scriptClass = script.getMetaClass().getTheClass(); if (isScript(script) && !scripts.contains(scriptClass)) { script.setBinding(context); script.run(); scripts.add(scriptClass); } } @Override public void setUnreportedStepExecutor(UnreportedStepExecutor executor) { //Not used yet } @Override public void buildWorld() { world = new GroovyWorld(); for (Closure closure : worldClosures) { world.registerWorld(closure.call()); } } private Script parse(Resource resource) { try { return shell.parse(new InputStreamReader(resource.getInputStream(), "UTF-8"), resource.getAbsolutePath()); } catch (IOException e) { throw new CucumberException(e); } } private boolean isScript(Script script) { return DefaultGroovyMethods.asBoolean(script.getMetaClass().respondsTo(script, "main")); } @Override public void disposeWorld() { this.world = null; } @Override public String getSnippet(Step step, FunctionNameGenerator functionNameGenerator) { return snippetGenerator.getSnippet(step, null); } public void addStepDefinition(Pattern regexp, long timeoutMillis, Closure body) { glue.addStepDefinition(new GroovyStepDefinition(regexp, timeoutMillis, body, currentLocation(), this)); } public void registerWorld(Closure closure) { worldClosures.add(closure); } public void addBeforeHook(TagExpression tagExpression, long timeoutMillis, int order, Closure body) { glue.addBeforeHook(new GroovyHookDefinition(tagExpression, timeoutMillis, order, body, currentLocation(), this)); } public void addAfterHook(TagExpression tagExpression, long timeoutMillis, int order, Closure body) { glue.addAfterHook(new GroovyHookDefinition(tagExpression, timeoutMillis, order, body, currentLocation(), this)); } public void invoke(Closure body, Object[] args) throws Throwable { body.setResolveStrategy(Closure.DELEGATE_FIRST); body.setDelegate(world); try { body.call(args); } catch (InvokerInvocationException e) { throw e.getCause(); } } GroovyWorld getGroovyWorld() { return world; } private static StackTraceElement currentLocation() { Throwable t = new Throwable(); StackTraceElement[] stackTraceElements = t.getStackTrace(); for (StackTraceElement stackTraceElement : stackTraceElements) { if (isGroovyFile(stackTraceElement.getFileName())) { return stackTraceElement; } } throw new RuntimeException("Couldn't find location for step definition"); } private static boolean isGroovyFile(String fileName) { return fileName != null && fileName.endsWith(".groovy"); } }