Steps.java

  1. package org.jbehave.core.steps;

  2. import static java.util.stream.Collectors.collectingAndThen;
  3. import static java.util.stream.Collectors.groupingBy;
  4. import static java.util.stream.Collectors.toList;
  5. import static java.util.stream.Collectors.toSet;
  6. import static org.jbehave.core.annotations.AfterScenario.Outcome.ANY;
  7. import static org.jbehave.core.steps.StepType.GIVEN;
  8. import static org.jbehave.core.steps.StepType.THEN;
  9. import static org.jbehave.core.steps.StepType.WHEN;

  10. import java.lang.annotation.Annotation;
  11. import java.lang.reflect.Method;
  12. import java.util.ArrayList;
  13. import java.util.Collection;
  14. import java.util.Collections;
  15. import java.util.EnumMap;
  16. import java.util.LinkedHashMap;
  17. import java.util.List;
  18. import java.util.Map;
  19. import java.util.function.BiPredicate;
  20. import java.util.function.Function;
  21. import java.util.function.Predicate;
  22. import java.util.function.ToIntFunction;

  23. import org.apache.commons.lang3.builder.ToStringBuilder;
  24. import org.apache.commons.lang3.builder.ToStringStyle;
  25. import org.jbehave.core.annotations.AfterScenario;
  26. import org.jbehave.core.annotations.AfterScenario.Outcome;
  27. import org.jbehave.core.annotations.AfterStories;
  28. import org.jbehave.core.annotations.AfterStory;
  29. import org.jbehave.core.annotations.Alias;
  30. import org.jbehave.core.annotations.Aliases;
  31. import org.jbehave.core.annotations.BeforeScenario;
  32. import org.jbehave.core.annotations.BeforeStories;
  33. import org.jbehave.core.annotations.BeforeStory;
  34. import org.jbehave.core.annotations.Composite;
  35. import org.jbehave.core.annotations.Given;
  36. import org.jbehave.core.annotations.ScenarioType;
  37. import org.jbehave.core.annotations.Then;
  38. import org.jbehave.core.annotations.When;
  39. import org.jbehave.core.configuration.Configuration;
  40. import org.jbehave.core.configuration.MostUsefulConfiguration;
  41. import org.jbehave.core.io.ResourceLoader;
  42. import org.jbehave.core.model.AliasVariant;
  43. import org.jbehave.core.parsers.AliasParser;
  44. import org.jbehave.core.parsers.RegexPrefixCapturingPatternParser;
  45. import org.jbehave.core.parsers.StepPatternParser;

  46. /**
  47.  * <p>
  48.  * Default implementation of {@link CandidateSteps} which provides the step
  49.  * candidates that match the steps being run.
  50.  * </p>
  51.  * <p>
  52.  * To provide your step candidate methods, you can:
  53.  * <ul>
  54.  * <li>pass in the steps instance type and the steps factory used to instantiate
  55.  * the instance if any candidate steps are matched (lazy "has-a" relationship)</li>
  56.  * <li>pass in the steps instance, instantiated regardless of whether the
  57.  * candidate steps are matched (eager "has-a" relationship)</li>
  58.  * <li>extend the {@link Steps} class, in which case the instance is the
  59.  * extended {@link Steps} class itself ("is-a" relationship)</li>
  60.  * </ul>
  61.  * <b>The "has-a" design model, in which the steps instance is passed in, is
  62.  * strongly recommended over the "is-a" model as it does not have tie-ins in the
  63.  * {@link Steps} class implementation</b>.
  64.  * </p>
  65.  * <p>
  66.  * You can define the methods that should be run by annotating them with
  67.  * {@link Given @Given}, {@link When @When} or {@link Then @Then}, and providing
  68.  * as a value for each annotation a pattern matches the textual step. The value
  69.  * is interpreted by the {@link StepPatternParser}, which by default is a
  70.  * {@link RegexPrefixCapturingPatternParser} that interprets the words starting
  71.  * with '$' as parameters.
  72.  * </p>
  73.  * <p>
  74.  * For instance, you could define a method as:
  75.  *
  76.  * <pre>
  77.  * <code>@When("I log in as $username with password: $password")
  78.  * public void logIn(String username, String password) { //... }
  79.  * </code>
  80.  * </pre>
  81.  *
  82.  * and this would match the step:
  83.  *
  84.  * <pre>
  85.  * When I log in as Liz with password: Pa55word
  86.  * </pre>
  87.  *
  88.  * </p>
  89.  * <p>
  90.  * When the step is performed, the parameters matched will be passed to the
  91.  * method, so in this case the effect will be to invoke:
  92.  * </p>
  93.  *
  94.  * <pre>
  95.  * logIn(&quot;Liz&quot;, &quot;Pa55word&quot;);
  96.  * </pre>
  97.  * <p>
  98.  * The {@link Configuration} can be used to provide customize the
  99.  * {@link StepCandidate}s that are created, e.g. providing a step monitor or
  100.  * creating them in "dry run" mode.
  101.  * </p>
  102.  */
  103. public class Steps extends AbstractCandidateSteps {

  104.     private final Class<?> type;
  105.     private final InjectableStepsFactory stepsFactory;

  106.     /**
  107.      * Creates Steps with default configuration for a class extending this
  108.      * instance and containing the candidate step methods
  109.      */
  110.     public Steps() {
  111.         this(new MostUsefulConfiguration());
  112.     }

  113.     /**
  114.      * Creates Steps with given custom configuration for a class extending this
  115.      * instance and containing the candidate step methods
  116.      *
  117.      * @param configuration the Configuration
  118.      */
  119.     public Steps(Configuration configuration) {
  120.         super(configuration);
  121.         this.type = this.getClass();
  122.         this.stepsFactory = new InstanceStepsFactory(configuration, this);
  123.     }

  124.     /**
  125.      * Creates Steps with given custom configuration and a steps instance
  126.      * containing the candidate step methods
  127.      *
  128.      * @param configuration the Configuration
  129.      * @param instance the steps instance
  130.      */
  131.     public Steps(Configuration configuration, Object instance) {
  132.         this(configuration, instance.getClass(), new InstanceStepsFactory(configuration, instance));
  133.     }

  134.     /**
  135.      * Creates Steps with given custom configuration and a steps instance type
  136.      * containing the candidate step methods. The steps instance is created
  137.      * using the steps instance factory provided.
  138.      *
  139.      * @param configuration the Configuration
  140.      * @param type the steps instance type
  141.      * @param stepsFactory the {@link InjectableStepsFactory}
  142.      */
  143.     public Steps(Configuration configuration, Class<?> type, InjectableStepsFactory stepsFactory) {
  144.         super(configuration);
  145.         this.type = type;
  146.         this.stepsFactory = stepsFactory;
  147.     }

  148.     public Class<?> type() {
  149.         return type;
  150.     }

  151.     public Object instance() {
  152.         return stepsFactory.createInstanceOfType(type);
  153.     }

  154.     @Override
  155.     public List<StepCandidate> listCandidates() {

  156.         Map<StepType, List<org.jbehave.core.model.Alias>> aliases = findAliases();

  157.         List<StepCandidate> candidates = new ArrayList<>();
  158.         for (Method method : allMethods()) {
  159.             if (method.isAnnotationPresent(Given.class)) {
  160.                 Given annotation = method.getAnnotation(Given.class);
  161.                 String value = annotation.value();
  162.                 int priority = annotation.priority();
  163.                 addCandidatesFromVariants(candidates, method, GIVEN, value, priority);
  164.                 addCandidatesFromAliases(candidates, method, GIVEN, priority);
  165.                 findVariants(GIVEN, value, aliases).forEach(variant -> addCandidatesFromVariants(candidates, method,
  166.                         GIVEN, variant.getValue(), priority));
  167.             }
  168.             if (method.isAnnotationPresent(When.class)) {
  169.                 When annotation = method.getAnnotation(When.class);
  170.                 String value = annotation.value();
  171.                 int priority = annotation.priority();
  172.                 addCandidatesFromVariants(candidates, method, WHEN, value, priority);
  173.                 addCandidatesFromAliases(candidates, method, WHEN, priority);
  174.                 findVariants(WHEN, value, aliases).forEach(variant -> addCandidatesFromVariants(candidates, method,
  175.                         WHEN, variant.getValue(), priority));
  176.             }
  177.             if (method.isAnnotationPresent(Then.class)) {
  178.                 Then annotation = method.getAnnotation(Then.class);
  179.                 String value = annotation.value();
  180.                 int priority = annotation.priority();
  181.                 addCandidatesFromVariants(candidates, method, THEN, value, priority);
  182.                 addCandidatesFromAliases(candidates, method, THEN, priority);
  183.                 findVariants(THEN, value, aliases).forEach(variant -> addCandidatesFromVariants(candidates, method,
  184.                         THEN, variant.getValue(), priority));
  185.             }
  186.         }

  187.         return candidates;
  188.     }

  189.     private static Collection<AliasVariant> findVariants(StepType stepType, String stepValue,
  190.             Map<StepType, List<org.jbehave.core.model.Alias>> aliases) {
  191.         return aliases.getOrDefault(stepType, Collections.emptyList())
  192.                       .stream()
  193.                       .filter(alias -> stepValue.equals(alias.getStepIdentifier()))
  194.                       .map(org.jbehave.core.model.Alias::getVariants)
  195.                       .flatMap(List::stream)
  196.                       .collect(toList());
  197.     }

  198.     private Map<StepType, List<org.jbehave.core.model.Alias>> findAliases() {
  199.         ResourceLoader resourceLoader = configuration().storyLoader();
  200.         AliasParser aliasParser = configuration().aliasParser();

  201.         Collection<org.jbehave.core.model.Alias> aliases = configuration().aliasPaths().stream()
  202.                                            .map(resourceLoader::loadResourceAsText)
  203.                                            .collect(collectingAndThen(toSet(), aliasParser::parse));

  204.         return aliases.stream().collect(groupingBy(org.jbehave.core.model.Alias::getType, toList()));
  205.     }

  206.     private void addCandidatesFromVariants(List<StepCandidate> candidates, Method method, StepType stepType,
  207.             String value, int priority) {
  208.         String[] composedSteps = method.isAnnotationPresent(Composite.class)
  209.                 ? method.getAnnotation(Composite.class).steps() : new String[0];
  210.         addCandidatesFromVariants(candidates, method, stepType, value, priority, type, stepsFactory, composedSteps);
  211.     }

  212.     private void addCandidatesFromAliases(List<StepCandidate> candidates, Method method, StepType stepType,
  213.             int priority) {
  214.         if (method.isAnnotationPresent(Aliases.class)) {
  215.             String[] aliases = method.getAnnotation(Aliases.class).values();
  216.             for (String alias : aliases) {
  217.                 addCandidatesFromVariants(candidates, method, stepType, alias, priority);
  218.             }
  219.         }
  220.         if (method.isAnnotationPresent(Alias.class)) {
  221.             String alias = method.getAnnotation(Alias.class).value();
  222.             addCandidatesFromVariants(candidates, method, stepType, alias, priority);
  223.         }
  224.     }

  225.     @Override
  226.     public List<BeforeOrAfterStep> listBeforeStories() {
  227.         return listSteps(BeforeStories.class, a -> true, BeforeStories::order);
  228.     }

  229.     @Override
  230.     public List<BeforeOrAfterStep> listAfterStories() {
  231.         return listSteps(AfterStories.class, a -> true, AfterStories::order);
  232.     }

  233.     @Override
  234.     public List<BeforeOrAfterStep> listBeforeStory(boolean givenStory) {
  235.         return listSteps(BeforeStory.class, a -> a.uponGivenStory() == givenStory, BeforeStory::order);
  236.     }

  237.     @Override
  238.     public List<BeforeOrAfterStep> listAfterStory(boolean givenStory) {
  239.         return listSteps(AfterStory.class, a -> a.uponGivenStory() == givenStory, AfterStory::order);
  240.     }

  241.     @Override
  242.     public Map<ScenarioType, List<BeforeOrAfterStep>> listBeforeScenario() {
  243.         return listBeforeOrAfterScenarioSteps(BeforeScenario.class, (a, scenarioType) -> a.uponType() == scenarioType,
  244.                 BeforeScenario::order, a -> ANY);
  245.     }

  246.     @Override
  247.     public Map<ScenarioType, List<BeforeOrAfterStep>> listAfterScenario() {
  248.         return listBeforeOrAfterScenarioSteps(AfterScenario.class, (a, scenarioType) -> a.uponType() == scenarioType,
  249.                 AfterScenario::order, AfterScenario::uponOutcome);
  250.     }

  251.     public <T extends Annotation> Map<ScenarioType, List<BeforeOrAfterStep>> listBeforeOrAfterScenarioSteps(
  252.             Class<T> annotationClass, BiPredicate<T, ScenarioType> predicate, ToIntFunction<T> order,
  253.             Function<T, Outcome> outcome) {
  254.         StepCreator stepCreator = createStepCreator(type, stepsFactory);
  255.         Map<Method, T> methods = methodsAnnotatedWith(annotationClass);
  256.         Map<ScenarioType, List<BeforeOrAfterStep>> stepsPerType = new EnumMap<>(ScenarioType.class);
  257.         for (ScenarioType scenarioType : ScenarioType.values()) {
  258.             stepsPerType.put(scenarioType, listSteps(methods, a -> predicate.test(a, scenarioType), order, outcome,
  259.                     stepCreator));
  260.         }
  261.         return stepsPerType;
  262.     }

  263.     private <T extends Annotation> List<BeforeOrAfterStep> listSteps(Class<T> annotationClass, Predicate<T> predicate,
  264.             ToIntFunction<T> order) {
  265.         StepCreator stepCreator = createStepCreator(type, stepsFactory);
  266.         return listSteps(methodsAnnotatedWith(annotationClass), predicate, order, a -> ANY, stepCreator);
  267.     }

  268.     private <T extends Annotation> List<BeforeOrAfterStep> listSteps(Map<Method, T> methods, Predicate<T> predicate,
  269.             ToIntFunction<T> order, Function<T, Outcome> outcome, StepCreator stepCreator) {
  270.         return methods.entrySet()
  271.                 .stream()
  272.                 .filter(e -> predicate.test(e.getValue()))
  273.                 .map(e -> {
  274.                     Method method = e.getKey();
  275.                     T annotation = e.getValue();
  276.                     return new BeforeOrAfterStep(method, order.applyAsInt(annotation), outcome.apply(annotation),
  277.                             stepCreator);
  278.                 })
  279.                 .collect(toList());
  280.     }

  281.     private Method[] allMethods() {
  282.         return type.getMethods();
  283.     }

  284.     private <T extends Annotation> Map<Method, T> methodsAnnotatedWith(Class<T> annotationClass) {
  285.         Map<Method, T> annotated = new LinkedHashMap<>();
  286.         for (Method method : allMethods()) {
  287.             T annotation = method.getAnnotation(annotationClass);
  288.             if (annotation != null) {
  289.                 annotated.put(method, annotation);
  290.             }
  291.         }
  292.         return annotated;
  293.     }

  294.     @Override
  295.     public String toString() {
  296.         return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append(instance()).toString();
  297.     }
  298. }