TemplateableOutput.java

  1. package org.jbehave.core.reporters;

  2. import static org.jbehave.core.steps.StepCreator.PARAMETER_TABLE_END;
  3. import static org.jbehave.core.steps.StepCreator.PARAMETER_TABLE_START;
  4. import static org.jbehave.core.steps.StepCreator.PARAMETER_VALUE_END;
  5. import static org.jbehave.core.steps.StepCreator.PARAMETER_VALUE_START;
  6. import static org.jbehave.core.steps.StepCreator.PARAMETER_VERBATIM_END;
  7. import static org.jbehave.core.steps.StepCreator.PARAMETER_VERBATIM_START;

  8. import java.io.File;
  9. import java.io.FileWriter;
  10. import java.io.Writer;
  11. import java.text.MessageFormat;
  12. import java.util.ArrayList;
  13. import java.util.HashMap;
  14. import java.util.List;
  15. import java.util.Map;
  16. import java.util.Set;
  17. import java.util.regex.Matcher;
  18. import java.util.regex.Pattern;

  19. import org.apache.commons.lang3.StringUtils;
  20. import org.jbehave.core.annotations.AfterScenario.Outcome;
  21. import org.jbehave.core.annotations.Scope;
  22. import org.jbehave.core.configuration.Keywords;
  23. import org.jbehave.core.embedder.MetaFilter;
  24. import org.jbehave.core.model.ExamplesTable;
  25. import org.jbehave.core.model.GivenStories;
  26. import org.jbehave.core.model.Lifecycle;
  27. import org.jbehave.core.model.Meta;
  28. import org.jbehave.core.model.Narrative;
  29. import org.jbehave.core.model.OutcomesTable;
  30. import org.jbehave.core.model.Scenario;
  31. import org.jbehave.core.model.Story;
  32. import org.jbehave.core.model.StoryDuration;
  33. import org.jbehave.core.model.Verbatim;
  34. import org.jbehave.core.steps.StepCollector;
  35. import org.jbehave.core.steps.StepCreator.PendingStep;
  36. import org.jbehave.core.steps.Timing;

  37. import freemarker.ext.beans.BeansWrapper;
  38. import freemarker.template.TemplateHashModel;
  39. import freemarker.template.TemplateModelException;

  40. /**
  41.  * <p>
  42.  * Story reporter that outputs to a template.
  43.  * </p>
  44.  */
  45. public class TemplateableOutput extends NullStoryReporter {

  46.     private final File file;
  47.     private final Keywords keywords;
  48.     private final TemplateProcessor processor;
  49.     private final String templatePath;
  50.     private OutputStory outputStory = new OutputStory();
  51.     private OutputScenario outputScenario = new OutputScenario();
  52.     private OutputStep failedStep;
  53.     private OutputStep pendingStep;
  54.     private Scope scope;
  55.     private StepCollector.Stage stage;

  56.     public TemplateableOutput(File file, Keywords keywords, TemplateProcessor processor, String templatePath) {
  57.         this.file = file;
  58.         this.keywords = keywords;
  59.         this.processor = processor;
  60.         this.templatePath = templatePath;
  61.     }

  62.     @Override
  63.     public void storyExcluded(Story story, String filter) {
  64.         this.outputStory.excludedBy = filter;
  65.     }

  66.     @Override
  67.     public void beforeStory(Story story, boolean givenStory) {
  68.         if (!givenStory) {
  69.             this.outputStory = new OutputStory();
  70.             this.outputStory.description = story.getDescription().asString();
  71.             this.outputStory.path = story.getPath();
  72.             this.scope = Scope.STORY;
  73.             this.stage = StepCollector.Stage.BEFORE;
  74.         }
  75.         if (!story.getMeta().isEmpty()) {
  76.             this.outputStory.meta = new OutputMeta(story.getMeta());
  77.         }
  78.     }

  79.     @Override
  80.     public void narrative(Narrative narrative) {
  81.         if (!narrative.isEmpty()) {
  82.             this.outputStory.narrative = new OutputNarrative(narrative);
  83.         }
  84.     }

  85.     @Override
  86.     public void lifecycle(Lifecycle lifecycle) {
  87.         if (!lifecycle.isEmpty()) {
  88.             this.outputStory.lifecycle = new OutputLifecycle(lifecycle);            
  89.         }
  90.     }

  91.     @Override
  92.     public void scenarioExcluded(Scenario scenario, String filter) {
  93.         this.outputScenario.excludedBy = filter;
  94.     }

  95.     @Override
  96.     public void beforeScenario(Scenario scenario) {
  97.         if (this.outputScenario.currentExample == null) {
  98.             this.outputScenario = new OutputScenario();
  99.         }
  100.         this.outputScenario.title = scenario.getTitle();
  101.         this.scope = Scope.SCENARIO;

  102.         Meta meta = scenario.getMeta();
  103.         if (!meta.isEmpty()) {
  104.             this.outputScenario.meta = new OutputMeta(meta);
  105.         }
  106.     }

  107.     private void addStep(OutputStep outputStep) {
  108.         if (scope == Scope.STORY) {
  109.             if (stage == StepCollector.Stage.BEFORE) {
  110.                 this.outputStory.addBeforeStep(outputStep);
  111.             } else {
  112.                 this.outputStory.addAfterStep(outputStep);
  113.             }
  114.         } else {
  115.             this.outputScenario.addStep(outputStep);
  116.         }
  117.     }

  118.     @Override
  119.     public void successful(String step) {
  120.         addStep(new OutputStep(step, "successful"));
  121.     }

  122.     @Override
  123.     public void ignorable(String step) {
  124.         addStep(new OutputStep(step, "ignorable"));
  125.     }

  126.     @Override
  127.     public void comment(String step) {
  128.         addStep(new OutputStep(step, "comment"));
  129.     }

  130.     @Override
  131.     public void pending(PendingStep step) {
  132.         this.pendingStep = new OutputStep(step.stepAsString(), "pending");
  133.         pendingStep.pendingMethod = step.getPendingMethod();
  134.         addStep(pendingStep);
  135.     }

  136.     @Override
  137.     public void notPerformed(String step) {
  138.         addStep(new OutputStep(step, "notPerformed"));
  139.     }

  140.     @Override
  141.     public void failed(String step, Throwable storyFailure) {
  142.         this.failedStep = new OutputStep(step, "failed");
  143.         failedStep.failure = storyFailure;
  144.         addStep(failedStep);
  145.     }

  146.     @Override
  147.     public void failedOutcomes(String step, OutcomesTable table) {
  148.         failed(step, table.failureCause());
  149.         this.failedStep.outcomes = table;
  150.     }

  151.     @Override
  152.     public void givenStories(GivenStories givenStories) {
  153.         if (!givenStories.getStories().isEmpty()) {
  154.             this.outputScenario.givenStories = givenStories;
  155.         }
  156.     }

  157.     @Override
  158.     public void givenStories(List<String> storyPaths) {
  159.         givenStories(new GivenStories(StringUtils.join(storyPaths, ",")));
  160.     }

  161.     @Override
  162.     public void beforeExamples(List<String> steps, ExamplesTable table) {
  163.         this.outputScenario.examplesSteps = steps;
  164.         this.outputScenario.examplesTable = table;
  165.     }

  166.     @Override
  167.     public void example(Map<String, String> parameters, int exampleIndex) {
  168.         this.outputScenario.examples.add(parameters);
  169.         this.outputScenario.currentExample = parameters;
  170.     }

  171.     @Override
  172.     public void afterExamples() {
  173.         this.outputScenario.currentExample = null;
  174.     }

  175.     @Override
  176.     public void afterScenario(Timing timing) {
  177.         if (this.outputScenario.currentExample == null) {
  178.             this.outputStory.scenarios.add(outputScenario);
  179.         }
  180.         this.scope = Scope.STORY;
  181.         this.stage = StepCollector.Stage.AFTER;
  182.     }

  183.     @Override
  184.     public void pendingMethods(List<String> methods) {
  185.         this.outputStory.pendingMethods = methods;
  186.     }

  187.     @Override
  188.     public void restarted(String step, Throwable cause) {
  189.         addStep(new OutputRestart(step, cause.getMessage()));
  190.     }
  191.    
  192.     @Override
  193.     public void restartedStory(Story story, Throwable cause) {
  194.         addStep(new OutputRestart(story.getName(), cause.getMessage()));
  195.     }

  196.     @Override
  197.     public void storyCancelled(Story story, StoryDuration storyDuration) {
  198.         this.outputStory.cancelled = true;
  199.         this.outputStory.storyDuration = storyDuration;
  200.     }

  201.     @Override
  202.     public void afterStory(boolean givenStory) {
  203.         if (!givenStory) {
  204.             Map<String, Object> model = newDataModel();
  205.             model.put("story", outputStory);
  206.             model.put("keywords", new OutputKeywords(keywords));

  207.             TemplateHashModel enumModels = BeansWrapper.getDefaultInstance().getEnumModels();
  208.             TemplateHashModel escapeEnums;
  209.             try {
  210.                 String escapeModeEnum = EscapeMode.class.getCanonicalName();
  211.                 escapeEnums = (TemplateHashModel) enumModels.get(escapeModeEnum);
  212.                 model.put("EscapeMode", escapeEnums);  
  213.             } catch (TemplateModelException e) {
  214.                 throw new IllegalArgumentException(e);
  215.             }  

  216.             write(file, templatePath, model);
  217.         }
  218.     }

  219.     private File write(File file, String resource, Map<String, Object> dataModel) {
  220.         try {
  221.             file.getParentFile().mkdirs();
  222.             Writer writer = new FileWriter(file);
  223.             processor.process(resource, dataModel, writer);
  224.             writer.close();
  225.             return file;
  226.         } catch (Exception e) {
  227.             throw new RuntimeException(resource, e);
  228.         }
  229.     }

  230.     private Map<String, Object> newDataModel() {
  231.         return new HashMap<>();
  232.     }

  233.     public static class OutputKeywords {

  234.         private final Keywords keywords;

  235.         public OutputKeywords(Keywords keywords) {
  236.             this.keywords = keywords;
  237.         }

  238.         public String getLifecycle() {
  239.             return keywords.lifecycle();
  240.         }

  241.         public String getScope() {
  242.             return keywords.scope();
  243.         }

  244.         public String getScopeScenario() {
  245.             return keywords.scopeScenario();
  246.         }

  247.         public String getScopeStory() {
  248.             return keywords.scopeStory();
  249.         }

  250.         public String getBefore() {
  251.             return keywords.before();
  252.         }

  253.         public String getAfter() {
  254.             return keywords.after();
  255.         }

  256.         public String getMeta() {
  257.             return keywords.meta();
  258.         }

  259.         public String getMetaProperty() {
  260.             return keywords.metaProperty();
  261.         }

  262.         public String getNarrative() {
  263.             return keywords.narrative();
  264.         }

  265.         public String getInOrderTo() {
  266.             return keywords.inOrderTo();
  267.         }

  268.         public String getAsA() {
  269.             return keywords.asA();
  270.         }

  271.         public String getiWantTo() {
  272.             return keywords.iWantTo();
  273.         }

  274.         public String getSoThat() {
  275.             return keywords.soThat();
  276.         }

  277.         public String getScenario() {
  278.             return keywords.scenario();
  279.         }

  280.         public String getGivenStories() {
  281.             return keywords.givenStories();
  282.         }

  283.         public String getExamplesTable() {
  284.             return keywords.examplesTable();
  285.         }

  286.         public String getExamplesTableRow() {
  287.             return keywords.examplesTableRow();
  288.         }

  289.         public String getExamplesTableHeaderSeparator() {
  290.             return keywords.examplesTableHeaderSeparator();
  291.         }

  292.         public String getExamplesTableValueSeparator() {
  293.             return keywords.examplesTableValueSeparator();
  294.         }

  295.         public String getExamplesTableIgnorableSeparator() {
  296.             return keywords.examplesTableIgnorableSeparator();
  297.         }

  298.         public String getGiven() {
  299.             return keywords.given();
  300.         }

  301.         public String getWhen() {
  302.             return keywords.when();
  303.         }

  304.         public String getThen() {
  305.             return keywords.then();
  306.         }

  307.         public String getAnd() {
  308.             return keywords.and();
  309.         }

  310.         public String getIgnorable() {
  311.             return keywords.ignorable();
  312.         }

  313.         public String getPending() {
  314.             return keywords.pending();
  315.         }

  316.         public String getNotPerformed() {
  317.             return keywords.notPerformed();
  318.         }

  319.         public String getFailed() {
  320.             return keywords.failed();
  321.         }

  322.         public String getDryRun() {
  323.             return keywords.dryRun();
  324.         }

  325.         public String getStoryCancelled() {
  326.             return keywords.storyCancelled();
  327.         }

  328.         public String getDuration() {
  329.             return keywords.duration();
  330.         }

  331.         public String getOutcome() {
  332.             return keywords.outcome();
  333.         }
  334.        
  335.         public String getMetaFilter() {
  336.             return keywords.metaFilter();
  337.         }
  338.        
  339.         public String getYes() {
  340.             return keywords.yes();
  341.         }

  342.         public String getNo() {
  343.             return keywords.no();
  344.         }
  345.     }

  346.     public static class OutputStory {
  347.         private String description;
  348.         private String path;
  349.         private OutputMeta meta;
  350.         private OutputNarrative narrative;
  351.         private OutputLifecycle lifecycle;
  352.         private String excludedBy;
  353.         private List<String> pendingMethods;
  354.         private List<OutputStep> beforeSteps = new ArrayList<>();
  355.         private List<OutputStep> afterSteps = new ArrayList<>();
  356.         private List<OutputScenario> scenarios = new ArrayList<>();
  357.         private boolean cancelled;
  358.         private StoryDuration storyDuration;

  359.         public String getDescription() {
  360.             return description;
  361.         }

  362.         public String getPath() {
  363.             return path;
  364.         }

  365.         public OutputMeta getMeta() {
  366.             return meta;
  367.         }

  368.         public OutputNarrative getNarrative() {
  369.             return narrative;
  370.         }

  371.         public OutputLifecycle getLifecycle() {
  372.             return lifecycle;
  373.         }

  374.         public String getExcludedBy() {
  375.             return excludedBy;
  376.         }

  377.         public void addBeforeStep(OutputStep outputStep) {
  378.             this.beforeSteps.add(outputStep);
  379.         }

  380.         public void addAfterStep(OutputStep outputStep) {
  381.             this.afterSteps.add(outputStep);
  382.         }

  383.         public List<OutputStep> getBeforeSteps() {
  384.             return beforeSteps;
  385.         }

  386.         public List<OutputStep> getAfterSteps() {
  387.             return afterSteps;
  388.         }

  389.         public List<String> getPendingMethods() {
  390.             return pendingMethods;
  391.         }

  392.         public List<OutputScenario> getScenarios() {
  393.             return scenarios;
  394.         }

  395.         public boolean isCancelled() {
  396.             return cancelled;
  397.         }

  398.         public StoryDuration getStoryDuration() {
  399.             return storyDuration;
  400.         }
  401.     }

  402.     public static class OutputMeta {

  403.         private final Meta meta;

  404.         public OutputMeta(Meta meta) {
  405.             this.meta = meta;
  406.         }

  407.         public Map<String, String> getProperties() {
  408.             Map<String, String> properties = new HashMap<>();
  409.             for (String name : meta.getPropertyNames()) {
  410.                 properties.put(name, meta.getProperty(name));
  411.             }
  412.             return properties;
  413.         }

  414.     }

  415.     public static class OutputNarrative {
  416.         private final Narrative narrative;

  417.         public OutputNarrative(Narrative narrative) {
  418.             this.narrative = narrative;
  419.         }

  420.         public String getInOrderTo() {
  421.             return narrative.inOrderTo();
  422.         }

  423.         public String getAsA() {
  424.             return narrative.asA();
  425.         }

  426.         public String getiWantTo() {
  427.             return narrative.iWantTo();
  428.         }
  429.        
  430.         public String getSoThat() {
  431.             return narrative.soThat();
  432.         }
  433.        
  434.         public boolean isAlternative() {
  435.             return narrative.isAlternative();
  436.         }

  437.     }

  438.     public static class OutputLifecycle {
  439.         private final Lifecycle lifecycle;

  440.         public OutputLifecycle(Lifecycle lifecycle) {
  441.             this.lifecycle = lifecycle;
  442.         }

  443.         public Set<Scope> getScopes() {
  444.             return lifecycle.getScopes();
  445.         }

  446.         public boolean hasBeforeSteps() {
  447.             return lifecycle.hasBeforeSteps();
  448.         }

  449.         public List<String> getBeforeSteps() {
  450.             return lifecycle.getBeforeSteps();
  451.         }

  452.         public List<String> getBeforeSteps(Scope scope) {
  453.             return lifecycle.getBeforeSteps(scope);
  454.         }

  455.         public boolean hasAfterSteps() {
  456.             return lifecycle.hasAfterSteps();
  457.         }

  458.         public List<String> getAfterSteps() {
  459.             return lifecycle.getAfterSteps();
  460.         }

  461.         public List<String> getAfterSteps(Scope scope) {
  462.             return lifecycle.getAfterSteps(scope);
  463.         }

  464.         public List<String> getAfterSteps(Scope scope, Outcome outcome) {
  465.             return lifecycle.getAfterSteps(scope, outcome);
  466.         }

  467.         public List<String> getAfterSteps(Outcome outcome) {
  468.             return lifecycle.getAfterSteps(outcome);
  469.         }

  470.         public List<String> getAfterSteps(Outcome outcome, Meta meta) {
  471.             return lifecycle.getAfterSteps(outcome, meta);
  472.         }

  473.         public Set<Outcome> getOutcomes() {
  474.             return lifecycle.getOutcomes();
  475.         }

  476.         public MetaFilter getMetaFilter(Outcome outcome) {
  477.             return lifecycle.getMetaFilter(outcome);
  478.         }

  479.     }

  480.     public static class OutputScenario {
  481.         private String title;
  482.         private List<OutputStep> steps = new ArrayList<>();
  483.         private OutputMeta meta;
  484.         private GivenStories givenStories;
  485.         private String excludedBy;
  486.         private List<String> examplesSteps;
  487.         private ExamplesTable examplesTable;
  488.         private Map<String, String> currentExample;
  489.         private List<Map<String, String>> examples = new ArrayList<>();
  490.         private Map<Map<String, String>, List<OutputStep>> stepsByExample = new HashMap<>();

  491.         public String getTitle() {
  492.             return title;
  493.         }

  494.         public void addStep(OutputStep outputStep) {
  495.             if (examplesTable == null) {
  496.                 steps.add(outputStep);
  497.             } else {
  498.                 List<OutputStep> currentExampleSteps = stepsByExample.get(currentExample);
  499.                 if (currentExampleSteps == null) {
  500.                     currentExampleSteps = new ArrayList<>();
  501.                     stepsByExample.put(currentExample, currentExampleSteps);
  502.                 }
  503.                 currentExampleSteps.add(outputStep);
  504.             }
  505.         }

  506.         public List<OutputStep> getSteps() {
  507.             return steps;
  508.         }

  509.         public List<OutputStep> getStepsByExample(Map<String, String> example) {
  510.             List<OutputStep> steps = stepsByExample.get(example);
  511.             if (steps == null) {
  512.                 return new ArrayList<>();
  513.             }
  514.             return steps;
  515.         }

  516.         public OutputMeta getMeta() {
  517.             return meta;
  518.         }

  519.         public GivenStories getGivenStories() {
  520.             return givenStories;
  521.         }

  522.         public String getExcludedBy() {
  523.             return excludedBy;
  524.         }

  525.         public List<String> getExamplesSteps() {
  526.             return examplesSteps;
  527.         }

  528.         public ExamplesTable getExamplesTable() {
  529.             return examplesTable;
  530.         }

  531.         public List<Map<String, String>> getExamples() {
  532.             return examples;
  533.         }
  534.     }

  535.     public static class OutputRestart extends OutputStep {

  536.         public OutputRestart(String step, String outcome) {
  537.             super(step, outcome);
  538.         }

  539.     }

  540.     public static class OutputStep {
  541.         private final String step;
  542.         private final String outcome;
  543.         private Throwable failure;
  544.         private OutcomesTable outcomes;
  545.         private List<OutputParameter> parameters;
  546.         private String stepPattern;
  547.         private String tableAsString;
  548.         private ExamplesTable table;
  549.         private String verbatimAsString;
  550.         private Verbatim verbatim;
  551.         private String pendingMethod;

  552.         public OutputStep(String step, String outcome) {
  553.             this.step = step;
  554.             this.outcome = outcome;
  555.             parseTableAsString();
  556.             parseVerbatimAsString();
  557.             parseParameters();
  558.             createStepPattern();
  559.         }

  560.         public String getStep() {
  561.             return step;
  562.         }

  563.         public String getStepPattern() {
  564.             return stepPattern;
  565.         }

  566.         public List<OutputParameter> getParameters() {
  567.             return parameters;
  568.         }

  569.         public String getOutcome() {
  570.             return outcome;
  571.         }

  572.         public Throwable getFailure() {
  573.             return failure;
  574.         }

  575.         public String getPendingMethod() {
  576.             return pendingMethod;
  577.         }

  578.         public String getFailureCause() {
  579.             if (failure != null) {
  580.                 return new StackTraceFormatter(true).stackTrace(failure);
  581.             }
  582.             return "";
  583.         }

  584.         public ExamplesTable getTable() {
  585.             return table;
  586.         }

  587.         public Verbatim getVerbatim() {
  588.             return verbatim;
  589.         }

  590.         public OutcomesTable getOutcomes() {
  591.             return outcomes;
  592.         }

  593.         public String getOutcomesFailureCause() {
  594.             if (outcomes.failureCause() != null) {
  595.                 return new StackTraceFormatter(true).stackTrace(outcomes.failureCause());
  596.             }
  597.             return "";
  598.         }

  599.         public String getFormattedStep(String parameterPattern) {
  600.             return getFormattedStep(EscapeMode.NONE, parameterPattern);
  601.         }

  602.         public String getFormattedStep(EscapeMode escapeMode, String parameterPattern) {
  603.             // note that escaping the stepPattern string only works
  604.             // because placeholders for parameters do not contain
  605.             // special chars (the placeholder is {0} etc)
  606.             String escapedStep = escapeMode.escapeString(stepPattern);
  607.             if (!parameters.isEmpty()) {
  608.                 try {
  609.                     return MessageFormat.format(escapedStep, formatParameters(escapeMode, parameterPattern));
  610.                 } catch (RuntimeException e) {
  611.                     throw new StepFormattingFailed(stepPattern, parameterPattern, parameters, e);
  612.                 }
  613.             }
  614.             return escapedStep;
  615.         }

  616.         private Object[] formatParameters(EscapeMode escapeMode, String parameterPattern) {
  617.             Object[] arguments = new Object[parameters.size()];
  618.             for (int a = 0; a < parameters.size(); a++) {
  619.                 arguments[a] = MessageFormat.format(parameterPattern,
  620.                         escapeMode.escapeString(parameters.get(a).getValue()));
  621.             }
  622.             return arguments;
  623.         }

  624.         private void parseParameters() {
  625.             // first, look for parameterized scenarios
  626.             parameters = findParameters(PARAMETER_VALUE_START + PARAMETER_VALUE_START, PARAMETER_VALUE_END
  627.                     + PARAMETER_VALUE_END);
  628.             // second, look for normal scenarios
  629.             if (parameters.isEmpty()) {
  630.                 parameters = findParameters(PARAMETER_VALUE_START, PARAMETER_VALUE_END);
  631.             }
  632.         }

  633.         private List<OutputParameter> findParameters(String start, String end) {
  634.             List<OutputParameter> parameters = new ArrayList<>();
  635.             Matcher matcher = Pattern.compile("(" + start + ".*?" + end + ")(\\W|\\Z)",
  636.                     Pattern.DOTALL).matcher(step);
  637.             while (matcher.find()) {
  638.                 parameters.add(new OutputParameter(step, matcher.start(), matcher.end()));
  639.             }
  640.             return parameters;
  641.         }

  642.         private void parseTableAsString() {
  643.             if (step.contains(PARAMETER_TABLE_START) && step.contains(PARAMETER_TABLE_END)) {
  644.                 tableAsString = StringUtils.substringBetween(step, PARAMETER_TABLE_START, PARAMETER_TABLE_END);
  645.                 table = new ExamplesTable(tableAsString);
  646.             }
  647.         }

  648.         private void parseVerbatimAsString() {
  649.             if (step.contains(PARAMETER_VERBATIM_START) && step.contains(PARAMETER_VERBATIM_END)) {
  650.                 verbatimAsString = StringUtils.substringBetween(step, PARAMETER_VERBATIM_START, PARAMETER_VERBATIM_END);
  651.                 verbatim = new Verbatim(verbatimAsString);
  652.             }
  653.         }

  654.         private void createStepPattern() {
  655.             this.stepPattern = step;
  656.             if (tableAsString != null) {
  657.                 this.stepPattern = StringUtils.replaceOnce(stepPattern, PARAMETER_TABLE_START + tableAsString
  658.                         + PARAMETER_TABLE_END, "");
  659.             }
  660.             if (verbatimAsString != null) {
  661.                 this.stepPattern = StringUtils.replaceOnce(stepPattern, PARAMETER_VERBATIM_START + verbatimAsString
  662.                         + PARAMETER_VERBATIM_END, "");
  663.             }
  664.             for (int count = 0; count < parameters.size(); count++) {
  665.                 String value = parameters.get(count).toString();
  666.                 this.stepPattern = stepPattern.replace(value, "{" + count + "}");
  667.             }
  668.         }

  669.         @SuppressWarnings("serial")
  670.         public static class StepFormattingFailed extends RuntimeException {

  671.             public StepFormattingFailed(String stepPattern, String parameterPattern, List<OutputParameter> parameters,
  672.                     RuntimeException cause) {
  673.                 super("Failed to format step '" + stepPattern + "' with parameter pattern '" + parameterPattern
  674.                         + "' and parameters: " + parameters, cause);
  675.             }

  676.         }

  677.     }

  678.     public static class OutputParameter {
  679.         private final String parameter;

  680.         public OutputParameter(String pattern, int start, int end) {
  681.             this.parameter = pattern.substring(start, end).trim();
  682.         }

  683.         public String getValue() {
  684.             String value = StringUtils.remove(parameter, PARAMETER_VALUE_START);
  685.             value = StringUtils.remove(value, PARAMETER_VALUE_END);
  686.             return value;
  687.         }

  688.         @Override
  689.         public String toString() {
  690.             return parameter;
  691.         }
  692.     }

  693. }