AllStepCandidates.java

package org.jbehave.core.embedder;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collector;
import java.util.stream.Collector.Characteristics;
import java.util.stream.Collectors;

import org.jbehave.core.annotations.Conditional;
import org.jbehave.core.annotations.ScenarioType;
import org.jbehave.core.condition.StepConditionMatcher;
import org.jbehave.core.steps.BeforeOrAfterStep;
import org.jbehave.core.steps.CandidateSteps;
import org.jbehave.core.steps.ConditionalStepCandidate;
import org.jbehave.core.steps.StepCandidate;

public class AllStepCandidates {
    private final List<BeforeOrAfterStep> beforeStoriesSteps = new ArrayList<>();
    private final List<BeforeOrAfterStep> afterStoriesSteps = new ArrayList<>();

    private final List<BeforeOrAfterStep> beforeGivenStorySteps = new ArrayList<>();
    private final List<BeforeOrAfterStep> afterGivenStorySteps = new ArrayList<>();

    private final List<BeforeOrAfterStep> beforeStorySteps = new ArrayList<>();
    private final List<BeforeOrAfterStep> afterStorySteps = new ArrayList<>();

    private final Map<ScenarioType, List<BeforeOrAfterStep>> beforeScenarioSteps = new EnumMap<>(ScenarioType.class);
    private final Map<ScenarioType, List<BeforeOrAfterStep>> afterScenarioSteps = new EnumMap<>(ScenarioType.class);

    private final List<StepCandidate> regularSteps;

    public AllStepCandidates(StepConditionMatcher stepConditionMatcher, List<CandidateSteps> candidateSteps) {

        for (ScenarioType type : ScenarioType.values()) {
            beforeScenarioSteps.put(type, new ArrayList<>());
            afterScenarioSteps.put(type, new ArrayList<>());
        }

        for (CandidateSteps candidateStep : candidateSteps) {
            beforeStoriesSteps.addAll(candidateStep.listBeforeStories());
            afterStoriesSteps.addAll(candidateStep.listAfterStories());

            beforeGivenStorySteps.addAll(candidateStep.listBeforeStory(true));
            afterGivenStorySteps.addAll(candidateStep.listAfterStory(true));

            beforeStorySteps.addAll(candidateStep.listBeforeStory(false));
            afterStorySteps.addAll(candidateStep.listAfterStory(false));

            candidateStep.listBeforeScenario().forEach(
                    (scenarioType, steps) -> beforeScenarioSteps.get(scenarioType).addAll(steps));
            candidateStep.listAfterScenario().forEach(
                    (scenarioType, steps) -> afterScenarioSteps.get(scenarioType).addAll(steps));
        }

        this.regularSteps = candidateSteps.stream()
                .map(CandidateSteps::listCandidates)
                .flatMap(List::stream)
                .collect(stepCandidateCollector())
                .entrySet()
                .stream()
                .map(e -> {
                    List<StepCandidate> candidates = e.getValue();
                    boolean allCandidatesConditional = areAllCandidatesConditional(candidates);
                    if (allCandidatesConditional) {
                        return ConditionalStepCandidate.from(stepConditionMatcher, candidates);
                    }
                    if (candidates.size() == 1) {
                        return candidates.get(0);
                    }
                    throw new DuplicateCandidateFound(e.getKey());
                })
                .collect(Collectors.toList());

        sortBeforeSteps(beforeStoriesSteps);
        sortAfterSteps(afterStoriesSteps);

        sortBeforeSteps(beforeGivenStorySteps);
        sortAfterSteps(afterGivenStorySteps);

        sortBeforeSteps(beforeStorySteps);
        sortAfterSteps(afterStorySteps);

        beforeScenarioSteps.values().forEach(this::sortBeforeSteps);
        afterScenarioSteps.values().forEach(this::sortAfterSteps);
    }

    private void sortBeforeSteps(List<BeforeOrAfterStep> beforeSteps) {
        sortSteps(beforeSteps, Comparator.reverseOrder());
    }

    private void sortAfterSteps(List<BeforeOrAfterStep> afterSteps) {
        sortSteps(afterSteps, Comparator.naturalOrder());
    }

    private void sortSteps(List<BeforeOrAfterStep> steps, Comparator<Integer> comparator) {
        steps.sort(Comparator.comparing(BeforeOrAfterStep::getOrder, comparator));
    }

    public List<BeforeOrAfterStep> getBeforeStoriesSteps() {
        return beforeStoriesSteps;
    }

    public List<BeforeOrAfterStep> getAfterStoriesSteps() {
        return afterStoriesSteps;
    }

    public List<BeforeOrAfterStep> getBeforeStorySteps(boolean givenStory) {
        return givenStory ? beforeGivenStorySteps : beforeStorySteps;
    }

    public List<BeforeOrAfterStep> getAfterStorySteps(boolean givenStory) {
        return givenStory ? afterGivenStorySteps : afterStorySteps;
    }

    public List<BeforeOrAfterStep> getBeforeScenarioSteps(ScenarioType scenarioType) {
        return beforeScenarioSteps.get(scenarioType);
    }

    public List<BeforeOrAfterStep> getAfterScenarioSteps(ScenarioType scenarioType) {
        return afterScenarioSteps.get(scenarioType);
    }

    public List<StepCandidate> getRegularSteps() {
        return regularSteps;
    }

    private static Collector<StepCandidate, Map<String, List<StepCandidate>>, Map<String, List<StepCandidate>>>
        stepCandidateCollector() {
        return Collector.of(HashMap::new, (map, candidate) -> {

            String candidateWording = candidate.getStartingWord() + " " + candidate.getPatternAsString();

            Optional<String> candidateKey = map.keySet().stream()
                    .filter(k -> candidate.matches(k) && map.get(k).stream().allMatch(c -> c.matches(candidateWording)))
                    .findFirst();

            List<StepCandidate> candidates;
            if (candidateKey.isPresent()) {
                candidates = map.get(candidateKey.get());
            } else {
                candidates = new ArrayList<>();
                map.put(candidateWording, candidates);
            }
            candidates.add(candidate);
        }, (l, r) -> l, Characteristics.IDENTITY_FINISH);
    }

    private boolean areAllCandidatesConditional(Collection<StepCandidate> candidates) {
        return candidates.stream()
                         .map(StepCandidate::getMethod)
                         .allMatch(m -> m != null
                             && (m.isAnnotationPresent(Conditional.class)
                                     || m.getDeclaringClass().isAnnotationPresent(Conditional.class)));
    }

}