AnnotationBuilder.java

  1. package org.jbehave.core.configuration;

  2. import java.io.ByteArrayInputStream;
  3. import java.io.IOException;
  4. import java.lang.reflect.Constructor;
  5. import java.util.ArrayList;
  6. import java.util.List;
  7. import java.util.Properties;

  8. import com.thoughtworks.paranamer.Paranamer;

  9. import org.apache.commons.lang3.StringUtils;
  10. import org.jbehave.core.ConfigurableEmbedder;
  11. import org.jbehave.core.Embeddable;
  12. import org.jbehave.core.annotations.Configure;
  13. import org.jbehave.core.annotations.UsingEmbedder;
  14. import org.jbehave.core.annotations.UsingPaths;
  15. import org.jbehave.core.annotations.UsingSteps;
  16. import org.jbehave.core.embedder.Embedder;
  17. import org.jbehave.core.embedder.EmbedderControls;
  18. import org.jbehave.core.embedder.StoryControls;
  19. import org.jbehave.core.failures.FailureStrategy;
  20. import org.jbehave.core.failures.PendingStepStrategy;
  21. import org.jbehave.core.io.PathCalculator;
  22. import org.jbehave.core.io.ResourceLoader;
  23. import org.jbehave.core.io.StoryFinder;
  24. import org.jbehave.core.io.StoryLoader;
  25. import org.jbehave.core.io.StoryPathResolver;
  26. import org.jbehave.core.model.TableTransformers;
  27. import org.jbehave.core.parsers.StepPatternParser;
  28. import org.jbehave.core.parsers.StoryParser;
  29. import org.jbehave.core.reporters.StepdocReporter;
  30. import org.jbehave.core.reporters.StoryReporter;
  31. import org.jbehave.core.reporters.StoryReporterBuilder;
  32. import org.jbehave.core.reporters.ViewGenerator;
  33. import org.jbehave.core.steps.CandidateSteps;
  34. import org.jbehave.core.steps.InjectableStepsFactory;
  35. import org.jbehave.core.steps.InstanceStepsFactory;
  36. import org.jbehave.core.steps.ParameterControls;
  37. import org.jbehave.core.steps.ParameterConverters;
  38. import org.jbehave.core.steps.ParameterConverters.ParameterConverter;
  39. import org.jbehave.core.steps.ScanningStepsFactory;
  40. import org.jbehave.core.steps.StepCollector;
  41. import org.jbehave.core.steps.StepFinder;
  42. import org.jbehave.core.steps.StepMonitor;

  43. /**
  44.  * Allows the building of {@link Configuration}, {@link CandidateSteps} and
  45.  * {@link Embedder} from an annotated class.
  46.  *
  47.  * @author Cristiano GaviĆ£o
  48.  * @author Mauro Talevi
  49.  */
  50. public class AnnotationBuilder {

  51.     private final AnnotationMonitor annotationMonitor;

  52.     private final Class<?> annotatedClass;
  53.     private final AnnotationFinder finder;

  54.     public AnnotationBuilder(Class<?> annotatedClass) {
  55.         this(annotatedClass, new PrintStreamAnnotationMonitor());
  56.     }

  57.     public AnnotationBuilder(Class<?> annotatedClass, AnnotationMonitor annotationMonitor) {
  58.         this.annotationMonitor = annotationMonitor;
  59.         this.annotatedClass = annotatedClass;
  60.         this.finder = new AnnotationFinder(annotatedClass);
  61.     }

  62.     public Class<?> annotatedClass() {
  63.         return annotatedClass;
  64.     }

  65.     /**
  66.      * Builds a Configuration instance based on annotation {@link Configure}
  67.      * found in the annotated object instance
  68.      *
  69.      * @return A Configuration instance
  70.      */
  71.     public Configuration buildConfiguration() throws AnnotationRequired {

  72.         if (!finder.isAnnotationPresent(Configure.class)) {
  73.             // not using annotation configuration, default to most useful
  74.             // configuration
  75.             return new MostUsefulConfiguration();
  76.         }

  77.         Configuration configuration = configurationElement(finder, "using", Configuration.class);
  78.         configuration.useKeywords(configurationElement(finder, "keywords", Keywords.class));
  79.         configuration.useFailureStrategy(configurationElement(finder, "failureStrategy", FailureStrategy.class));
  80.         configuration.usePendingStepStrategy(configurationElement(finder, "pendingStepStrategy",
  81.                 PendingStepStrategy.class));
  82.         configuration.useParanamer(configurationElement(finder, "paranamer", Paranamer.class));
  83.         configuration.useStoryControls(configurationElement(finder, "storyControls", StoryControls.class));
  84.         configuration.useStepCollector(configurationElement(finder, "stepCollector", StepCollector.class));
  85.         configuration.useStepdocReporter(configurationElement(finder, "stepdocReporter", StepdocReporter.class));
  86.         configuration.useStepFinder(configurationElement(finder, "stepFinder", StepFinder.class));
  87.         configuration.useStepMonitor(configurationElement(finder, "stepMonitor", StepMonitor.class));
  88.         configuration.useStepPatternParser(configurationElement(finder, "stepPatternParser", StepPatternParser.class));
  89.         configuration.useStoryLoader(configurationElement(finder, "storyLoader", StoryLoader.class));
  90.         configuration.useStoryParser(configurationElement(finder, "storyParser", StoryParser.class));
  91.         configuration.useStoryPathResolver(configurationElement(finder, "storyPathResolver", StoryPathResolver.class));
  92.         configuration
  93.                 .useDefaultStoryReporter(configurationElement(finder, "defaultStoryReporter", StoryReporter.class));
  94.         configuration.useStoryReporterBuilder(configurationElement(finder, "storyReporterBuilder",
  95.                 StoryReporterBuilder.class));
  96.         configuration.useViewGenerator(configurationElement(finder, "viewGenerator", ViewGenerator.class));
  97.         configuration.useTableTransformers(configurationElement(finder, "tableTransformers", TableTransformers.class));
  98.         configuration.useParameterConverters(
  99.                 parameterConverters(finder, configuration.storyLoader(), configuration.tableTransformers()));
  100.         configuration.useParameterControls(configurationElement(finder, "parameterControls", ParameterControls.class));
  101.         configuration.usePathCalculator(configurationElement(finder, "pathCalculator", PathCalculator.class));
  102.         return configuration;
  103.     }

  104.     /**
  105.      * Builds CandidateSteps using annotation {@link UsingSteps} found in the
  106.      * annotated object instance and using the configuration build by
  107.      * {@link #buildConfiguration()}
  108.      *
  109.      * @return A List of CandidateSteps instances
  110.      */
  111.     public List<CandidateSteps> buildCandidateSteps() {
  112.         return buildCandidateSteps(buildConfiguration());
  113.     }

  114.     /**
  115.      * Builds CandidateSteps using annotation {@link UsingSteps} found in the
  116.      * annotated object instance and the configuration provided
  117.      *
  118.      * @param configuration the Configuration
  119.      * @return A List of CandidateSteps instances
  120.      */
  121.     public List<CandidateSteps> buildCandidateSteps(Configuration configuration) {
  122.         return buildStepsFactory(configuration).createCandidateSteps();
  123.     }

  124.     /**
  125.      * Builds the {@link InjectableStepsFactory} using annotation
  126.      * {@link UsingSteps} found in the annotated object instance and the
  127.      * configuration provided
  128.      *
  129.      * @param configuration the Configuration
  130.      * @return A {@link InjectableStepsFactory}
  131.      */
  132.     public InjectableStepsFactory buildStepsFactory(Configuration configuration) {
  133.         List<Object> stepsInstances = new ArrayList<>();
  134.         InjectableStepsFactory factory = null;
  135.         if (finder.isAnnotationPresent(UsingSteps.class)) {
  136.             List<Class<Object>> stepsClasses = finder.getAnnotatedClasses(
  137.                     UsingSteps.class, Object.class, "instances");
  138.             if (!stepsClasses.isEmpty()) {
  139.                 for (Class<Object> stepsClass : stepsClasses) {
  140.                     stepsInstances.add(instanceOf(Object.class, stepsClass));
  141.                 }
  142.                 factory = new InstanceStepsFactory(configuration,
  143.                         stepsInstances);
  144.             }
  145.             List<String> packages = finder.getAnnotatedValues(UsingSteps.class,
  146.                     String.class, "packages");
  147.             if (!packages.isEmpty()) {
  148.                 String matchingNames = finder.getAnnotatedValue(UsingSteps.class,
  149.                         String.class, "matchingNames");
  150.                 String notMatchingNames = finder.getAnnotatedValue(UsingSteps.class,
  151.                         String.class, "notMatchingNames");
  152.                 factory = new ScanningStepsFactory(configuration,
  153.                         packages.toArray(new String[packages.size()]))
  154.                         .matchingNames(matchingNames).notMatchingNames(notMatchingNames);
  155.             }
  156.         } else {
  157.             annotationMonitor.annotationNotFound(UsingSteps.class, annotatedClass);
  158.         }

  159.         if (factory == null) {
  160.             factory = new InstanceStepsFactory(configuration);
  161.         }
  162.         return factory;
  163.     }

  164.     public Embedder buildEmbedder() {
  165.         if (!finder.isAnnotationPresent(UsingEmbedder.class)) {
  166.             return defaultEmbedder();
  167.         }

  168.         boolean batch = control(finder, "batch");
  169.         boolean skip = control(finder, "skip");
  170.         boolean generateViewAfterStories = control(finder, "generateViewAfterStories");
  171.         boolean ignoreFailureInStories = control(finder, "ignoreFailureInStories");
  172.         boolean ignoreFailureInView = control(finder, "ignoreFailureInView");
  173.         boolean verboseFailures = control(finder, "verboseFailures");
  174.         boolean verboseFiltering = control(finder, "verboseFiltering");
  175.         String storyTimeouts = finder.getAnnotatedValue(UsingEmbedder.class, String.class, "storyTimeouts");
  176.         boolean failOnStoryTimeout = control(finder, "failOnStoryTimeout");
  177.         int threads = finder.getAnnotatedValue(UsingEmbedder.class, Integer.class, "threads");
  178.         Embedder embedder = embedder();
  179.         EmbedderControls embedderControls = embedder.embedderControls();
  180.         embedderControls.doBatch(batch).doSkip(skip).doGenerateViewAfterStories(generateViewAfterStories)
  181.                 .doIgnoreFailureInStories(ignoreFailureInStories).doIgnoreFailureInView(ignoreFailureInView)
  182.                 .doVerboseFailures(verboseFailures).doVerboseFiltering(verboseFiltering)
  183.                 .doFailOnStoryTimeout(failOnStoryTimeout).useThreads(threads);
  184.         if (StringUtils.isNotBlank(storyTimeouts)) {
  185.             embedderControls.useStoryTimeouts(storyTimeouts);
  186.         }
  187.         Configuration configuration = buildConfiguration();
  188.         embedder.useConfiguration(configuration);
  189.         embedder.useStepsFactory(buildStepsFactory(configuration));

  190.         List<String> metaFilters = finder.getAnnotatedValues(UsingEmbedder.class, String.class, "metaFilters");
  191.         if (!metaFilters.isEmpty()) {
  192.             embedder.useMetaFilters(metaFilters);
  193.         }
  194.         Properties systemProperties = loadProperties(finder.getAnnotatedValue(UsingEmbedder.class, String.class,
  195.                 "systemProperties"));
  196.         if (!systemProperties.isEmpty()) {
  197.             embedder.useSystemProperties(systemProperties);
  198.         }
  199.         return embedder;
  200.     }

  201.     @SuppressWarnings("unchecked")
  202.     protected Embedder embedder() {
  203.         return instanceOf(Embedder.class,
  204.                 (Class<? extends Embedder>) finder.getAnnotatedValue(UsingEmbedder.class, Class.class, "embedder"));
  205.     }
  206.    
  207.     protected Embedder defaultEmbedder() {
  208.         return new Embedder();
  209.     }

  210.     public AnnotationFinder finder() {
  211.         return finder;
  212.     }
  213.    
  214.     public List<String> findPaths() {
  215.         if (!finder.isAnnotationPresent(UsingPaths.class)) {
  216.             return new ArrayList<>();
  217.         }

  218.         String searchIn = finder.getAnnotatedValue(UsingPaths.class, String.class, "searchIn");
  219.         List<String> includes = finder.getAnnotatedValues(UsingPaths.class, String.class, "includes");
  220.         List<String> excludes = finder.getAnnotatedValues(UsingPaths.class, String.class, "excludes");
  221.         return storyFinder().findPaths(searchIn, includes, excludes);
  222.     }

  223.     @SuppressWarnings("unchecked")
  224.     private StoryFinder storyFinder() {
  225.         return instanceOf(StoryFinder.class,
  226.                 (Class<? extends StoryFinder>) finder.getAnnotatedValue(UsingPaths.class, Class.class, "storyFinder"));
  227.     }

  228.     private boolean control(AnnotationFinder finder, String name) {
  229.         return finder.getAnnotatedValue(UsingEmbedder.class, Boolean.class, name);
  230.     }

  231.     private <T> T configurationElement(AnnotationFinder finder, String name, Class<T> type) {
  232.         Class<T> implementation = elementImplementation(finder, name);
  233.         return instanceOf(type, implementation);
  234.     }

  235.     @SuppressWarnings("unchecked")
  236.     protected <T> Class<T> elementImplementation(AnnotationFinder finder, String name) {
  237.         return finder.getAnnotatedValue(Configure.class, Class.class, name);
  238.     }

  239.     private Properties loadProperties(String systemPropertiesCSV) {
  240.         Properties properties = new Properties();
  241.         try {
  242.             properties.load(new ByteArrayInputStream(systemPropertiesCSV.replace(",", "\n").getBytes()));
  243.         } catch (IOException e) {
  244.             // return empty map
  245.         }
  246.         return properties;
  247.     }

  248.     protected ParameterConverters parameterConverters(AnnotationFinder annotationFinder,
  249.             ResourceLoader resourceLoader, TableTransformers tableTransformers) {
  250.         ParameterConverters parameterConverters = new ParameterConverters(resourceLoader, tableTransformers);
  251.         for (Class<ParameterConverter> converterClass : annotationFinder.getAnnotatedClasses(Configure.class,
  252.                 ParameterConverter.class, "parameterConverters")) {
  253.             parameterConverters.addConverters(instanceOf(ParameterConverter.class, converterClass));
  254.         }
  255.         return parameterConverters;
  256.     }

  257.     protected <T, V extends T> T instanceOf(Class<T> type, Class<V> ofClass) {
  258.         try {
  259.             // by classloader constructor
  260.             try {
  261.                 Constructor<V> constructor =
  262.                         ofClass.getConstructor(ClassLoader.class);
  263.                 return constructor.newInstance(annotatedClass.getClassLoader());
  264.             } catch (NoSuchMethodException ns) {
  265.                 // fall-back to another approach of object construction
  266.             }
  267.             // by class constructor
  268.             try {
  269.                 Constructor<V> constructor =
  270.                         ofClass.getConstructor(Class.class);
  271.                 return constructor.newInstance(annotatedClass);
  272.             } catch (NoSuchMethodException ns) {
  273.                 // fall-back to another approach of object construction
  274.             }                
  275.             // by class instance
  276.             return ofClass.newInstance();
  277.         } catch (Exception e) {
  278.             annotationMonitor.elementCreationFailed(ofClass, e);
  279.             throw new InstantiationFailed(ofClass, type, e);
  280.         }
  281.     }

  282.     protected AnnotationMonitor annotationMonitor() {
  283.         return annotationMonitor;
  284.     }

  285.     protected AnnotationFinder annotationFinder() {
  286.         return finder;
  287.     }

  288.     public Object embeddableInstance() {
  289.         return injectEmbedder(buildEmbedder(), annotatedClass);
  290.     }

  291.     protected Object injectEmbedder(Embedder embedder, Class<?> annotatedClass) {
  292.         try {
  293.             Object instance = annotatedClass.newInstance();
  294.             if (instance instanceof Embeddable) {
  295.                 Embeddable embeddable = (Embeddable) instance;
  296.                 embeddable.useEmbedder(embedder);
  297.             }
  298.             if (instance instanceof ConfigurableEmbedder) {
  299.                 ConfigurableEmbedder configurableEmbedder = (ConfigurableEmbedder) instance;
  300.                 configurableEmbedder.useConfiguration(embedder.configuration());
  301.                 configurableEmbedder.useStepsFactory(embedder.stepsFactory());
  302.             }
  303.             return instance;
  304.         } catch (Exception e) {
  305.             annotationMonitor.elementCreationFailed(annotatedClass, e);
  306.             throw new InstantiationFailed(annotatedClass, e);
  307.         }
  308.     }

  309.     @SuppressWarnings("serial")
  310.     public static class InstantiationFailed extends RuntimeException {

  311.         public InstantiationFailed(Class<?> ofClass, Class<?> type, Throwable cause) {
  312.             super("Failed to instantiate " + ofClass + " of type " + type, cause);
  313.         }

  314.         public InstantiationFailed(Class<?> ofClass, Throwable cause) {
  315.             super("Failed to instantiate " + ofClass, cause);
  316.         }

  317.     }

  318. }