PrintStreamOutput.java

  1. package org.jbehave.core.reporters;

  2. import static org.apache.commons.lang3.StringUtils.substringBetween;
  3. import static org.jbehave.core.steps.StepCreator.PARAMETER_TABLE_END;
  4. import static org.jbehave.core.steps.StepCreator.PARAMETER_TABLE_START;
  5. import static org.jbehave.core.steps.StepCreator.PARAMETER_VALUE_END;
  6. import static org.jbehave.core.steps.StepCreator.PARAMETER_VALUE_NEWLINE;
  7. import static org.jbehave.core.steps.StepCreator.PARAMETER_VALUE_START;
  8. import static org.jbehave.core.steps.StepCreator.PARAMETER_VERBATIM_END;
  9. import static org.jbehave.core.steps.StepCreator.PARAMETER_VERBATIM_START;

  10. import java.io.ByteArrayOutputStream;
  11. import java.io.OutputStream;
  12. import java.io.PrintStream;
  13. import java.text.MessageFormat;
  14. import java.text.SimpleDateFormat;
  15. import java.util.Date;
  16. import java.util.List;
  17. import java.util.Locale;
  18. import java.util.Map;
  19. import java.util.Properties;
  20. import java.util.stream.Stream;

  21. import org.apache.commons.lang3.StringUtils;
  22. import org.apache.commons.lang3.builder.ToStringBuilder;
  23. import org.apache.commons.lang3.builder.ToStringStyle;
  24. import org.apache.commons.text.CaseUtils;
  25. import org.jbehave.core.annotations.AfterScenario;
  26. import org.jbehave.core.annotations.Scope;
  27. import org.jbehave.core.configuration.Keywords;
  28. import org.jbehave.core.embedder.MetaFilter;
  29. import org.jbehave.core.failures.KnownFailure;
  30. import org.jbehave.core.failures.UUIDExceptionWrapper;
  31. import org.jbehave.core.model.ExamplesTable;
  32. import org.jbehave.core.model.GivenStories;
  33. import org.jbehave.core.model.GivenStory;
  34. import org.jbehave.core.model.Lifecycle;
  35. import org.jbehave.core.model.Meta;
  36. import org.jbehave.core.model.Narrative;
  37. import org.jbehave.core.model.OutcomesTable;
  38. import org.jbehave.core.model.OutcomesTable.Outcome;
  39. import org.jbehave.core.model.Scenario;
  40. import org.jbehave.core.model.Step;
  41. import org.jbehave.core.model.Story;
  42. import org.jbehave.core.model.StoryDuration;
  43. import org.jbehave.core.model.Verbatim;
  44. import org.jbehave.core.steps.StepCollector.Stage;
  45. import org.jbehave.core.steps.StepCreator.PendingStep;
  46. import org.jbehave.core.steps.Timing;

  47. /**
  48.  * <p>
  49.  * Abstract story reporter that outputs to a PrintStream.
  50.  * </p>
  51.  * <p>
  52.  * The output of the reported event is configurable via:
  53.  * <ul>
  54.  * <li>custom output patterns, providing only the patterns that differ from
  55.  * default</li>
  56.  * <li>keywords localised for different languages, providing the formatOutcome Locale</li>
  57.  * <li>flag to report failure trace</li>
  58.  * </ul>
  59.  * </p>
  60.  * <p>
  61.  * Let's look at example of providing custom output patterns, e.g. for the
  62.  * failed event. <br/>
  63.  * we'd need to provide the custom pattern, say we want to have something like
  64.  * "(step being executed) <<< FAILED", keyed on the method name:
  65.  *
  66.  * <pre>
  67.  * Properties patterns = new Properties();
  68.  * patterns.setProperty(&quot;failed&quot;, &quot;{0} &lt;&lt;&lt; {1}&quot;);
  69.  * </pre>
  70.  *
  71.  * The pattern is by default processed and formatted by the
  72.  * {@link MessageFormat}. Both the
  73.  * {@link #format(String key, String defaultPattern, Object... args)} and
  74.  * {@link #lookupPattern(String key, String defaultPattern)} methods are
  75.  * override-able and a different formatter or pattern lookup can be used by
  76.  * subclasses.
  77.  * </p>
  78.  * <p>
  79.  * If the keyword "FAILED" (or any other keyword used by the reporter) needs to
  80.  * be expressed in a different language, all we need to do is to provide an
  81.  * instance of {@link org.jbehave.core.i18n.LocalizedKeywords} using the
  82.  * appropriate {@link Locale}, e.g.
  83.  *
  84.  * <pre>
  85.  * Keywords keywords = new LocalizedKeywords(new Locale(&quot;it&quot;));
  86.  * </pre>
  87.  *
  88.  * </p>
  89.  */
  90. public abstract class PrintStreamOutput extends NullStoryReporter {

  91.     private static final String EMPTY = "";
  92.     public static final String NL = "\n";

  93.     public enum Format {
  94.         TXT {
  95.             @Override
  96.             public Object escapeValue(Object object) {
  97.                 return object;
  98.             }
  99.         },
  100.         HTML {
  101.             @Override
  102.             public Object escapeValue(Object object) {
  103.                 return EscapeMode.HTML.escapeString(asString(object));
  104.             }
  105.         },
  106.         XML {
  107.             @Override
  108.             public Object escapeValue(Object object) {
  109.                 return EscapeMode.XML.escapeString(asString(object));
  110.             }
  111.         },
  112.         JSON {
  113.             @Override
  114.             public Object escapeValue(Object object) {
  115.                 return EscapeMode.JSON.escapeString(asString(object));
  116.             }
  117.         };

  118.         public abstract Object escapeValue(Object object);

  119.         private static String asString(Object object) {
  120.             return object != null ? object.toString() : EMPTY;
  121.         }
  122.     }

  123.     private final Format format;
  124.     private final PrintStream output;
  125.     private final Properties outputPatterns;
  126.     private final Keywords keywords;
  127.     private ThreadLocal<Boolean> reportFailureTrace = new ThreadLocal<>();
  128.     private ThreadLocal<Boolean> compressFailureTrace = new ThreadLocal<>();
  129.     private ThreadLocal<Throwable> cause = new ThreadLocal<>();
  130.     private ThreadLocal<Boolean> dryRun = ThreadLocal.withInitial(() -> false);

  131.     protected PrintStreamOutput(Format format, PrintStream output, Properties defaultPatterns,
  132.             Properties outputPatterns, Keywords keywords) {
  133.         this(format, output, mergePatterns(defaultPatterns, outputPatterns), keywords, false,
  134.                 false);
  135.     }

  136.     protected PrintStreamOutput(Format format, PrintStream output, Properties defaultPatterns,
  137.             Properties outputPatterns, Keywords keywords, boolean reportFailureTrace, boolean compressFailureTrace) {
  138.         this(format, output, mergePatterns(defaultPatterns, outputPatterns), keywords, reportFailureTrace,
  139.                 compressFailureTrace);
  140.     }

  141.     protected PrintStreamOutput(Format format, PrintStream output, Properties outputPatterns, Keywords keywords,
  142.             boolean reportFailureTrace, boolean compressFailureTrace) {
  143.         this.format = format;
  144.         this.output = output;
  145.         this.outputPatterns = outputPatterns;
  146.         this.keywords = keywords;
  147.         doReportFailureTrace(reportFailureTrace);
  148.         doCompressFailureTrace(compressFailureTrace);
  149.     }

  150.     private static Properties mergePatterns(Properties defaultPatterns, Properties outputPatterns) {
  151.         Properties patterns = new Properties();
  152.         patterns.putAll(defaultPatterns);
  153.         // override any default pattern
  154.         patterns.putAll(outputPatterns);
  155.         return patterns;
  156.     }

  157.     @Override
  158.     public void beforeStep(Step step) {
  159.         print(format("beforeStep", "{0}\n", step.getStepAsString()));
  160.     }

  161.     @Override
  162.     public void successful(String step) {
  163.         print(format("successful", "{0}\n", step));
  164.     }

  165.     @Override
  166.     public void ignorable(String step) {
  167.         print(format("ignorable", "{0}\n", step));
  168.     }

  169.     @Override
  170.     public void comment(String step) {
  171.         print(format("comment", "{0}\n", step));
  172.     }

  173.     @Override
  174.     public void pending(PendingStep step) {
  175.         print(format("pending", "{0} ({1})\n({2})\n", step.stepAsString(), keywords.pending(),
  176.                 step.getPendingMethod()));
  177.     }

  178.     @Override
  179.     public void notPerformed(String step) {
  180.         print(format("notPerformed", "{0} ({1})\n", step, keywords.notPerformed()));
  181.     }

  182.     @Override
  183.     public void failed(String step, Throwable storyFailure) {
  184.         // storyFailure be used if a subclass has rewritten the "failed" pattern
  185.         // to have a {3} as WebDriverHtmlOutput (jbehave-web) does.
  186.         if (storyFailure instanceof UUIDExceptionWrapper) {
  187.             this.cause.set(storyFailure.getCause());
  188.             print(format("failed", "{0} ({1})\n({2})\n", step, keywords.failed(), storyFailure.getCause(),
  189.                     ((UUIDExceptionWrapper) storyFailure).getUUID()));
  190.         } else {
  191.             throw new ClassCastException(storyFailure + " should be an instance of UUIDExceptionWrapper");
  192.         }
  193.     }

  194.     @Override
  195.     public void failedOutcomes(String step, OutcomesTable table) {
  196.         failed(step, table.failureCause());
  197.         printOutcomesTable(table);
  198.     }

  199.     private void printOutcomesTable(OutcomesTable table) {
  200.         print(format("outcomesTableStart", NL));
  201.         List<Outcome<?>> rows = table.getOutcomes();
  202.         print(format("outcomesTableHeadStart", "|"));
  203.         for (String field : table.getOutcomeFields()) {
  204.             print(format("outcomesTableHeadCell", "{0}|", field));
  205.         }
  206.         print(format("outcomesTableHeadEnd", NL));
  207.         print(format("outcomesTableBodyStart", EMPTY));
  208.         for (Outcome<?> outcome : rows) {
  209.             print(format("outcomesTableRowStart", "|", outcome.isVerified() ? "verified" : "notVerified"));
  210.             print(format("outcomesTableCell", "{0}|", outcome.getDescription()));
  211.             print(format("outcomesTableCell", "{0}|", renderOutcomeValue(outcome.getValue(), table)));
  212.             print(format("outcomesTableCell", "{0}|", outcome.getMatcher()));
  213.             print(format("outcomesTableCell", "{0}|", (outcome.isVerified() ? keywords.yes() : keywords.no())));
  214.             print(format("outcomesTableRowEnd", NL));
  215.         }
  216.         print(format("outcomesTableBodyEnd", NL));
  217.         print(format("outcomesTableEnd", NL));
  218.     }

  219.     private Object renderOutcomeValue(Object value, OutcomesTable outcomesTable) {
  220.         if (value instanceof Date) {
  221.             return new SimpleDateFormat(outcomesTable.getFormat(Date.class)).format(value);
  222.         } else {
  223.             return value;
  224.         }
  225.     }

  226.     @Override
  227.     public void storyExcluded(Story story, String filter) {
  228.         print(format("filter", "{0}\n", filter));
  229.     }

  230.     @Override
  231.     public void storyCancelled(Story story, StoryDuration storyDuration) {
  232.         print(format("storyCancelled", "{0}: {1} ({2} s)\n", keywords.storyCancelled(), keywords.duration(),
  233.                 storyDuration.getDurationInSecs()));
  234.     }

  235.     @Override
  236.     public void beforeStory(Story story, boolean givenStory) {
  237.         print(format("beforeStory", "{0}\n{1}\n({2})\n", story.getId(), story.getDescription().asString(),
  238.                 story.getPath()));
  239.         if (dryRun.get()) {
  240.             print(format("dryRun", "{0}\n", keywords.dryRun()));
  241.         }
  242.         printMeta(story.getMeta());
  243.     }

  244.     @Override
  245.     public void beforeScenarios() {
  246.         print(format("beforeScenarios", ""));
  247.     }

  248.     @Override
  249.     public void afterScenarios() {
  250.         print(format("afterScenarios", ""));
  251.     }

  252.     @Override
  253.     public void narrative(Narrative narrative) {
  254.         if (!narrative.isEmpty()) {
  255.             if (!narrative.isAlternative()) {
  256.                 print(format("narrative", "{0}\n{1} {2}\n{3} {4}\n{5} {6}\n", keywords.narrative(),
  257.                         keywords.inOrderTo(), narrative.inOrderTo(), keywords.asA(), narrative.asA(),
  258.                         keywords.iWantTo(), narrative.iWantTo()));
  259.             } else {
  260.                 print(format("narrative", "{0}\n{1} {2}\n{3} {4}\n{5} {6}\n", keywords.narrative(), keywords.asA(),
  261.                         narrative.asA(), keywords.iWantTo(), narrative.iWantTo(), keywords.soThat(),
  262.                         narrative.soThat()));
  263.             }
  264.         }
  265.     }

  266.     @Override
  267.     public void lifecycle(Lifecycle lifecycle) {
  268.         if (!lifecycle.isEmpty()) {
  269.             print(format("lifecycleStart", "{0}\n", keywords.lifecycle()));
  270.             ExamplesTable lifecycleExamplesTable = lifecycle.getExamplesTable();
  271.             if (!lifecycleExamplesTable.isEmpty()) {
  272.                 print(formatTable(lifecycleExamplesTable));
  273.             }
  274.             if (lifecycle.hasBeforeSteps()) {
  275.                 print(format("lifecycleBeforeStart", "{0}\n", keywords.before()));
  276.                 for (Scope scope : lifecycle.getScopes()) {
  277.                     printWithScope(lifecycle.getBeforeSteps(scope), scope);
  278.                 }
  279.                 print(format("lifecycleBeforeEnd", NL));
  280.             }
  281.             if (lifecycle.hasAfterSteps()) {
  282.                 print(format("lifecycleAfterStart", "{0}\n", keywords.after()));
  283.                 for (Scope scope : lifecycle.getScopes()) {
  284.                     printOutcomes(lifecycle, scope);
  285.                 }
  286.                 print(format("lifecycleAfterEnd", NL));
  287.             }
  288.             print(format("lifecycleEnd", NL));
  289.         }
  290.     }

  291.     private void printOutcomes(Lifecycle lifecycle, Scope scope) {
  292.         for (AfterScenario.Outcome outcome : lifecycle.getOutcomes()) {
  293.             List<String> afterSteps = lifecycle.getAfterSteps(scope, outcome);
  294.             if (!afterSteps.isEmpty()) {
  295.                 print(format("lifecycleAfterScopeStart", "{0} {1}\n", keywords.scope(), formatScope(scope)));
  296.                 print(format("lifecycleOutcomeStart", "{0} {1}\n", keywords.outcome(), formatOutcome(outcome)));
  297.                 MetaFilter metaFilter = lifecycle.getMetaFilter(outcome);
  298.                 if (!metaFilter.isEmpty()) {
  299.                     print(format("lifecycleMetaFilter", "{0} {1}\n", keywords.metaFilter(), metaFilter.asString()));
  300.                 }
  301.                 printLifecycleSteps(afterSteps);
  302.                 print(format("lifecycleOutcomeEnd", "\n"));
  303.                 print(format("lifecycleAfterScopeEnd", "\n"));
  304.             }
  305.         }
  306.     }

  307.     private void printWithScope(List<String> steps, Scope scope) {
  308.         if (!steps.isEmpty()) {
  309.             print(format("lifecycleBeforeScopeStart", "{0} {1}\n", keywords.scope(), formatScope(scope)));
  310.             printLifecycleSteps(steps);
  311.             print(format("lifecycleBeforeScopeEnd", "\n"));
  312.         }
  313.     }

  314.     private String formatScope(Scope scope) {
  315.         switch (scope) {
  316.             case SCENARIO: return keywords.scopeScenario();
  317.             case STORY: return keywords.scopeStory();
  318.             default: return scope.name();
  319.         }
  320.     }

  321.     private String formatOutcome(AfterScenario.Outcome outcome) {
  322.         switch (outcome) {
  323.             case ANY:
  324.                 return keywords.outcomeAny();
  325.             case SUCCESS:
  326.                 return keywords.outcomeSuccess();
  327.             case FAILURE:
  328.                 return keywords.outcomeFailure();
  329.             default:
  330.                 return outcome.name();
  331.         }
  332.     }

  333.     private void printLifecycleSteps(List<String> steps) {
  334.         for (String step : steps) {
  335.             print(format("lifecycleStep", "{0}\n", step));
  336.         }
  337.     }

  338.     private void printMeta(Meta meta) {
  339.         if (!meta.isEmpty()) {
  340.             print(format("metaStart", "{0}\n", keywords.meta()));
  341.             for (String name : meta.getPropertyNames()) {
  342.                 print(format("metaProperty", "{0}{1} {2}", keywords.metaProperty(), name, meta.getProperty(name)));
  343.             }
  344.             print(format("metaEnd", NL));
  345.         }
  346.     }

  347.     @Override
  348.     public void beforeScenarioSteps(Stage stage, Lifecycle.ExecutionType type) {
  349.         printScenarioSteps("before", stage, type);
  350.     }

  351.     @Override
  352.     public void afterScenarioSteps(Stage stage, Lifecycle.ExecutionType type) {
  353.         printScenarioSteps("after", stage, type);
  354.     }

  355.     private void printScenarioSteps(String stepsStage, Stage stage, Lifecycle.ExecutionType type) {
  356.         printSteps(stepsStage, "Scenario", stage, type);
  357.     }

  358.     @Override
  359.     public void beforeComposedSteps() {
  360.         print(format("beforeComposedSteps", ""));
  361.     }

  362.     @Override
  363.     public void afterComposedSteps() {
  364.         print(format("afterComposedSteps", ""));
  365.     }

  366.     @Override
  367.     public void beforeStoriesSteps(Stage stage) {
  368.         printStoriesSteps("before", stage);
  369.     }

  370.     @Override
  371.     public void afterStoriesSteps(Stage stage) {
  372.         printStoriesSteps("after", stage);
  373.     }

  374.     private void printStoriesSteps(String stepsStage, Stage stage) {
  375.         printSteps(stepsStage, "Stories", stage, null);
  376.     }

  377.     @Override
  378.     public void beforeStorySteps(Stage stage, Lifecycle.ExecutionType type) {
  379.         printStorySteps("before", stage, type);
  380.     }

  381.     @Override
  382.     public void afterStorySteps(Stage stage, Lifecycle.ExecutionType type) {
  383.         printStorySteps("after", stage, type);
  384.     }

  385.     private void printStorySteps(String stepsStage, Stage stage, Lifecycle.ExecutionType type) {
  386.         printSteps(stepsStage, "Story", stage, type);
  387.     }

  388.     private void printSteps(String stepsStage, String parent, Stage stage, Lifecycle.ExecutionType type) {
  389.         String stageName = stage != null ? CaseUtils.toCamelCase(stage.name(), true) : EMPTY;
  390.         String typeName = type != null ? CaseUtils.toCamelCase(type.name(), true) : EMPTY;
  391.         print(format(stepsStage + stageName + typeName + parent + "Steps", ""));
  392.     }

  393.     @Override
  394.     public void afterStory(boolean givenOrRestartingStory) {
  395.         print(format("afterStory", NL));
  396.         // take care not to close System.out
  397.         // which is used for ConsoleOutput
  398.         if (!givenOrRestartingStory && output != System.out) {
  399.             output.close();
  400.         }
  401.     }

  402.     @Override
  403.     public void beforeGivenStories() {
  404.         print(format("beforeGivenStories", ""));
  405.     }

  406.     @Override
  407.     public void givenStories(GivenStories givenStories) {
  408.         print(format("givenStoriesStart", "{0}\n", keywords.givenStories()));
  409.         for (GivenStory givenStory : givenStories.getStories()) {
  410.             print(format("givenStory", "{0}{1}\n", givenStory.asString(),
  411.                     (givenStory.hasAnchor() ? givenStory.getParameters() : "")));
  412.         }
  413.         print(format("givenStoriesEnd", NL));
  414.     }

  415.     @Override
  416.     public void givenStories(List<String> storyPaths) {
  417.         givenStories(new GivenStories(StringUtils.join(storyPaths, ",")));
  418.     }

  419.     @Override
  420.     public void afterGivenStories() {
  421.         print(format("afterGivenStories", ""));
  422.     }

  423.     @Override
  424.     public void scenarioExcluded(Scenario scenario, String filter) {
  425.         print(format("filter", "{0}\n", filter));
  426.     }

  427.     @Override
  428.     public void beforeScenario(Scenario scenario) {
  429.         cause.set(null);
  430.         print(format("beforeScenario", "{0} {1} {2}\n", scenario.getId(), keywords.scenario(), scenario.getTitle()));
  431.         printMeta(scenario.getMeta());
  432.     }

  433.     @Override
  434.     public void afterScenario(Timing timing) {
  435.         print(format("numericParameter", EMPTY, "start", timing.getStart()));
  436.         print(format("numericParameter", EMPTY, "end", timing.getEnd()));
  437.         if (cause.get() != null && !(cause.get() instanceof KnownFailure) && reportFailureTrace()) {
  438.             print(format("afterScenarioWithFailure", "\n{0}\n",
  439.                     new StackTraceFormatter(compressFailureTrace()).stackTrace(cause.get())));
  440.         } else {
  441.             print(format("afterScenario", NL));
  442.         }
  443.     }

  444.     @Override
  445.     public void beforeExamples(List<String> steps, ExamplesTable table) {
  446.         print(format("beforeExamples", "{0}\n", keywords.examplesTable()));
  447.         print(format("examplesStepsStart", EMPTY));
  448.         for (String step : steps) {
  449.             print(format("examplesStep", "{0}\n", step));
  450.         }
  451.         print(format("examplesStepsEnd", EMPTY));
  452.         print(formatTable(table));
  453.         print(format("exampleScenariosStart", EMPTY));
  454.     }

  455.     @Override
  456.     public void example(Map<String, String> tableRow, int exampleIndex) {
  457.         print(format("example", "\n{0} {1}\n", keywords.examplesTableRow(), tableRow));
  458.         print(output, format("beforeExampleParameters", EMPTY));
  459.         tableRow.entrySet().forEach(
  460.                 cell -> print(output, format("exampleParameter", EMPTY, cell.getKey(), cell.getValue())));
  461.         print(output, format("afterExampleParameters", EMPTY));
  462.     }

  463.     @Override
  464.     public void afterExamples() {
  465.         print(format("exampleScenariosEnd", EMPTY));
  466.         print(format("afterExamples", NL));
  467.     }

  468.     @Override
  469.     public void dryRun() {
  470.         dryRun.set(true);
  471.     }

  472.     @Override
  473.     public void restarted(String step, Throwable cause) {
  474.         print(format("restarted", "{0} {1}\n", step, cause.getMessage()));
  475.     }
  476.    
  477.     @Override
  478.     public void restartedStory(Story story, Throwable cause) {
  479.         print(format("restartedStory", "{0} {1}\n", story.getPath(), cause.getMessage()));
  480.     }

  481.     /**
  482.      * Formats event output by key, usually equal to the method name.
  483.      *
  484.      * @param key the event key
  485.      * @param defaultPattern the default pattern to return if a custom pattern
  486.      *            is not found
  487.      * @param args the args used to format output
  488.      * @return A formatted event output
  489.      */
  490.     protected String format(String key, String defaultPattern, Object... args) {
  491.         String escape = escape(defaultPattern);
  492.         String s = lookupPattern(key, escape);
  493.         Object[] objects = escapeAll(args);
  494.         return MessageFormat.format(s, objects);
  495.     }

  496.     protected String formatTable(ExamplesTable table) {
  497.         OutputStream formatted = new ByteArrayOutputStream();
  498.         PrintStream out = new PrintStream(formatted);
  499.         print(out, format("examplesTableStart", NL));
  500.         List<Map<String, String>> rows = table.getRows();
  501.         List<String> headers = table.getHeaders();
  502.         print(out, format("examplesTableHeadStart", "|"));
  503.         for (String header : headers) {
  504.             print(out, format("examplesTableHeadCell", "{0}|", header));
  505.         }
  506.         print(out, format("examplesTableHeadEnd", NL));
  507.         print(out, format("examplesTableBodyStart", EMPTY));
  508.         for (Map<String, String> row : rows) {
  509.             print(out, format("examplesTableRowStart", "|"));
  510.             for (String header : headers) {
  511.                 print(out, format("examplesTableCell", "{0}|", row.get(header)));
  512.             }
  513.             print(out, format("examplesTableRowEnd", NL));
  514.         }
  515.         print(out, format("examplesTableBodyEnd", EMPTY));
  516.         print(out, format("examplesTableEnd", EMPTY));
  517.         return formatted.toString();
  518.     }

  519.     protected String formatVerbatim(Verbatim verbatim) {
  520.         OutputStream formatted = new ByteArrayOutputStream();
  521.         PrintStream out = new PrintStream(formatted);
  522.         print(out, format("verbatimStart", NL));
  523.         print(out, verbatim.getContent());
  524.         print(out, format("verbatimEnd", NL));
  525.         return formatted.toString();
  526.     }

  527.     private Object[] escapeAll(Object... args) {
  528.         return escape(format, args);
  529.     }

  530.     private String escape(String defaultPattern) {
  531.         return (String) escapeAll(defaultPattern)[0];
  532.     }

  533.     /**
  534.      * Escapes args' string values according to format
  535.      *
  536.      * @param format the Format used by the PrintStream
  537.      * @param args the array of args to escape
  538.      * @return The cloned and escaped array of args
  539.      */
  540.     protected Object[] escape(final Format format, Object... args) {
  541.         // Transformer that escapes HTML,XML,JSON strings
  542.         return Stream.of(args).map(format::escapeValue).toArray();
  543.     }

  544.     /**
  545.      * Looks up the format pattern for the event output by key, conventionally
  546.      * equal to the method name. The pattern is used by the
  547.      * {#format(String,String,Object...)} method and by default is formatted
  548.      * using the {@link MessageFormat#format(String, Object...)} method. If no
  549.      * pattern is found for key or needs to be overridden, the default pattern
  550.      * should be returned.
  551.      *
  552.      * @param key the format pattern key
  553.      * @param defaultPattern the default pattern if no pattern is
  554.      * @return The format patter for the given key
  555.      */
  556.     protected String lookupPattern(String key, String defaultPattern) {
  557.         if (outputPatterns.containsKey(key)) {
  558.             return outputPatterns.getProperty(key);
  559.         }
  560.         return defaultPattern;
  561.     }

  562.     public boolean reportFailureTrace() {
  563.         Boolean reportFailure = reportFailureTrace.get();
  564.         if (reportFailure != null) {
  565.             return reportFailure;
  566.         }
  567.         return false;
  568.     }

  569.     public PrintStreamOutput doReportFailureTrace(boolean reportFailureTrace) {
  570.         this.reportFailureTrace.set(reportFailureTrace);
  571.         return this;
  572.     }

  573.     public boolean compressFailureTrace() {
  574.         return compressFailureTrace.get();
  575.     }

  576.     public PrintStreamOutput doCompressFailureTrace(boolean compressFailureTrace) {
  577.         this.compressFailureTrace.set(compressFailureTrace);
  578.         return this;
  579.     }

  580.     protected void overwritePattern(String key, String pattern) {
  581.         outputPatterns.put(key, pattern);
  582.     }

  583.     /**
  584.      * Prints text to output stream, replacing parameter start and end
  585.      * placeholders
  586.      *
  587.      * @param text the String to print
  588.      */
  589.     protected void print(String text) {
  590.         String tableStart = format(PARAMETER_TABLE_START, PARAMETER_TABLE_START);
  591.         String tableEnd = format(PARAMETER_TABLE_END, PARAMETER_TABLE_END);
  592.         boolean containsTable = text.contains(tableStart) && text.contains(tableEnd);
  593.         String verbatimStart = format(PARAMETER_VERBATIM_START, PARAMETER_VERBATIM_START);
  594.         String verbatimEnd = format(PARAMETER_VERBATIM_END, PARAMETER_VERBATIM_END);
  595.         boolean containsVerbatim = text.contains(verbatimStart) && text.contains(verbatimEnd);
  596.         String textToPrint;
  597.         if (containsTable) {
  598.             textToPrint = transformPrintingTable(text, tableStart, tableEnd);
  599.         } else if (containsVerbatim) {
  600.             textToPrint = transformPrintingVerbatim(text, verbatimStart, verbatimEnd);
  601.         } else {
  602.             textToPrint = text;
  603.         }
  604.         print(output, textToPrint
  605.                 .replace(format(PARAMETER_VALUE_START, PARAMETER_VALUE_START),
  606.                         format("parameterValueStart", EMPTY))
  607.                 .replace(format(PARAMETER_VALUE_END, PARAMETER_VALUE_END),
  608.                         format("parameterValueEnd", EMPTY))
  609.                 .replace(format(PARAMETER_VALUE_NEWLINE, PARAMETER_VALUE_NEWLINE),
  610.                         format("parameterValueNewline", NL)));
  611.     }

  612.     protected void print(PrintStream output, String text) {
  613.         output.print(text);
  614.     }

  615.     protected String transformPrintingTable(String text, String tableStart, String tableEnd) {
  616.         String tableAsString = substringBetween(text, tableStart, tableEnd);
  617.         return text
  618.                 .replace(tableAsString, formatTable(new ExamplesTable(tableAsString)))
  619.                 .replace(tableStart, format("parameterValueStart", EMPTY))
  620.                 .replace(tableEnd, format("parameterValueEnd", EMPTY));
  621.     }

  622.     protected String  transformPrintingVerbatim(String text, String verbatimStart, String verbatimEnd) {
  623.         String verbatimAsString = substringBetween(text, verbatimStart, verbatimEnd);
  624.         return text
  625.                 .replace(verbatimAsString, formatVerbatim(new Verbatim(verbatimAsString)))
  626.                 .replace(verbatimStart, format("parameterValueStart", EMPTY))
  627.                 .replace(verbatimEnd, format("parameterValueEnd", EMPTY));
  628.     }

  629.     @Override
  630.     public String toString() {
  631.         return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append(format).append(output).toString();
  632.     }
  633. }