StoryReporterBuilder.java

  1. package org.jbehave.core.reporters;

  2. import static java.util.Arrays.asList;

  3. import java.io.File;
  4. import java.net.URL;
  5. import java.util.ArrayList;
  6. import java.util.HashMap;
  7. import java.util.List;
  8. import java.util.Locale;
  9. import java.util.Map;
  10. import java.util.Properties;

  11. import org.jbehave.core.configuration.Configuration;
  12. import org.jbehave.core.configuration.Keywords;
  13. import org.jbehave.core.i18n.LocalizedKeywords;
  14. import org.jbehave.core.io.CodeLocations;
  15. import org.jbehave.core.io.StoryLocation;
  16. import org.jbehave.core.reporters.FilePrintStreamFactory.FileConfiguration;
  17. import org.jbehave.core.reporters.FilePrintStreamFactory.FilePathResolver;

  18. /**
  19.  * <p>
  20.  * A <a href="http://en.wikipedia.org/wiki/Builder_pattern">Builder</a> for
  21.  * {@link StoryReporter}s. It builds a {@link DelegatingStoryReporter} with
  22.  * delegates for a number of formats - mostly file-based ones except
  23.  * {@Format.CONSOLE}. It requires a
  24.  * {@link FilePrintStreamFactory} and provides default delegate instances for
  25.  * each format.
  26.  * </p>
  27.  * <p>
  28.  * To build a reporter for a single story path with default and given formats:
  29.  *
  30.  * <pre>
  31.  * Class&lt;MyStory&gt; storyClass = MyStory.class;
  32.  * StoryPathResolver resolver = new UnderscoredCamelCaseResolver();
  33.  * String storyPath = resolver.resolve(storyClass);
  34.  * StoryReporter reporter = new StoryReporterBuilder()
  35.  *         .withCodeLocation(CodeLocations.codeLocationFromClass(storyClass))
  36.  *         .withDefaultFormats().withFormats(TXT, HTML, XML).build(storyPath);
  37.  * </pre>
  38.  *
  39.  * </p>
  40.  * <p>
  41.  * The builder is configured to build with the {@link Format#STATS} as default
  42.  * format. To change the default formats the user can override the method:
  43.  *
  44.  * <pre>
  45.  * new StoryReporterBuilder() {
  46.  *     protected StoryReporterBuilder withDefaultFormats() {
  47.  *         return withFormats(STATS);
  48.  *     }
  49.  * }
  50.  * </pre>
  51.  *
  52.  * </p>
  53.  * <p>
  54.  * The builder configures the file-based reporters to output to the default file
  55.  * directory {@link FileConfiguration#RELATIVE_DIRECTORY} as relative to the code
  56.  * location. In some case, e.g. with Ant class loader, the code source location
  57.  * from class may not be properly set. In this case, we may specify it from a
  58.  * file:
  59.  *
  60.  * <pre>
  61.  * new StoryReporterBuilder()
  62.  *         .withCodeLocation(
  63.  *                 CodeLocations.codeLocationFromFile(new File(&quot;target/classes&quot;)))
  64.  *         .withDefaultFormats().withFormats(TXT, HTML, XML).build(storyPath);
  65.  * </pre>
  66.  *
  67.  * </p>
  68.  * <p>
  69.  * By default, the reporters will output minimal failure information, the single
  70.  * line describing the failure cause and the outcomes if failures occur. To
  71.  * configure the failure trace to be reported as well:
  72.  *
  73.  * <pre>
  74.  * new StoryReporterBuilder().withFailureTrace(true)
  75.  * </pre>
  76.  *
  77.  * </p>
  78.  * <p>
  79.  * If failure trace is reported, it is with the full stack trace. In some cases,
  80.  * it's useful to have it compressed, eliminating unnecessary lines that are not
  81.  * very informative:
  82.  *
  83.  * <pre>
  84.  * new StoryReporterBuilder().withFailureTraceCompression(true)
  85.  * </pre>
  86.  *
  87.  * </p>
  88.  *
  89.  * <p>
  90.  * To specify the use of keywords for a given locale:
  91.  *
  92.  * <pre>
  93.  * new StoryReporterBuilder().withKeywords(new LocalisedKeywords(Locale.IT)
  94.  * </pre>
  95.  *
  96.  * </p>
  97.  *
  98.  * <p>
  99.  * The builder provides default instances for all reporters, using the default
  100.  * output patterns. To change the reporter for a specific instance, e.g. to
  101.  * report format <b>TXT</b> to <b>.text</b> files and to inject other
  102.  * non-default parameters, such as the custom output patterns:
  103.  *
  104.  * <pre>
  105.  * new StoryReporterBuilder() {
  106.  *   public StoryReporter reporterFor(String storyPath, Format format) {
  107.  *       FilePrintStreamFactory factory = new FilePrintStreamFactory(new StoryLocation(storyPath, codeLocation));
  108.  *       switch (format) {
  109.  *           case TXT:
  110.  *               factory.useConfiguration(new FileConfiguration("text"));
  111.  *               Properties customPatterns = new Properties();
  112.  *               customPatterns.setProperty("successful", "{0}(YEAH!!!)\n");
  113.  *               return new TxtOutput(factory.createPrintStream(), customPatterns, keywords);
  114.  *            default:
  115.  *               return super.reporterFor(format);
  116.  *   }
  117.  * }
  118.  * </pre>
  119.  *
  120.  * </p>
  121.  */
  122. public class StoryReporterBuilder {

  123.     private List<Format> formats = new ArrayList<>();
  124.     protected String relativeDirectory;
  125.     protected FilePathResolver pathResolver;
  126.     protected URL codeLocation;
  127.     protected Properties viewResources;
  128.     protected boolean reportFailureTrace = false;
  129.     protected boolean compressFailureTrace = false;
  130.     protected Keywords keywords;
  131.     protected SGRCodes codes;
  132.     protected SurefireReporter surefireReporter;
  133.     protected boolean multiThreading;
  134.     protected Configuration configuration;
  135.     private FileConfiguration defaultFileConfiguration = new FileConfiguration();

  136.     public StoryReporterBuilder() {
  137.     }

  138.     public StoryReporterBuilder(Configuration configuration) {
  139.         this.configuration = configuration;
  140.     }

  141.     public File outputDirectory() {
  142.         return filePrintStreamFactory("").outputDirectory();
  143.     }

  144.     public String relativeDirectory() {
  145.         if (relativeDirectory == null) {
  146.             relativeDirectory = defaultFileConfiguration.getRelativeDirectory();
  147.         }
  148.         return relativeDirectory;
  149.     }

  150.     public FilePathResolver pathResolver() {
  151.         if (pathResolver == null) {
  152.             pathResolver = defaultFileConfiguration.getPathResolver();
  153.         }
  154.         return pathResolver;
  155.     }

  156.     public URL codeLocation() {
  157.         if (codeLocation == null) {
  158.             codeLocation = CodeLocations.codeLocationFromPath("target/classes");
  159.         }
  160.         return codeLocation;
  161.     }

  162.     public List<org.jbehave.core.reporters.Format> formats() {
  163.         return formats;
  164.     }

  165.     public List<String> formatNames(boolean toLowerCase) {
  166.         Locale locale = Locale.getDefault();
  167.         if (keywords instanceof LocalizedKeywords) {
  168.             locale = ((LocalizedKeywords) keywords).getLocale();
  169.         }
  170.         List<String> names = new ArrayList<>();
  171.         for (org.jbehave.core.reporters.Format format : formats) {
  172.             String name = format.name();
  173.             if (toLowerCase) {
  174.                 name = name.toLowerCase(locale);
  175.             }
  176.             names.add(name);
  177.         }
  178.         return names;
  179.     }

  180.     public Keywords keywords() {
  181.         if (keywords == null) {
  182.             if (configuration != null) {
  183.                 keywords = configuration.keywords();
  184.             }
  185.             keywords = new LocalizedKeywords();
  186.         }
  187.         return keywords;
  188.     }

  189.     public SGRCodes codes() {
  190.         if (codes == null) {
  191.             return new SGRCodes();
  192.         }
  193.         return codes;
  194.     }

  195.     public boolean multiThreading() {
  196.         return multiThreading;
  197.     }

  198.     public boolean reportFailureTrace() {
  199.         return reportFailureTrace;
  200.     }

  201.     public boolean compressFailureTrace() {
  202.         return compressFailureTrace;
  203.     }

  204.     public Properties viewResources() {
  205.         if (viewResources == null) {
  206.             if (configuration != null) {
  207.                 viewResources = configuration.viewGenerator()
  208.                         .defaultViewProperties();
  209.             } else {
  210.                 viewResources = new FreemarkerViewGenerator()
  211.                         .defaultViewProperties();
  212.             }
  213.         }
  214.         return viewResources;
  215.     }

  216.     public StoryReporterBuilder withRelativeDirectory(String relativeDirectory) {
  217.         this.relativeDirectory = relativeDirectory;
  218.         return this;
  219.     }

  220.     public StoryReporterBuilder withPathResolver(FilePathResolver pathResolver) {
  221.         this.pathResolver = pathResolver;
  222.         return this;
  223.     }

  224.     public StoryReporterBuilder withCodeLocation(URL codeLocation) {
  225.         this.codeLocation = codeLocation;
  226.         return this;
  227.     }

  228.     public SurefireReporter surefireReporter() {
  229.         return surefireReporter;
  230.     }

  231.     public boolean hasSurefireReporter() {
  232.         return surefireReporter != null;
  233.     }

  234.     public StoryReporterBuilder withSurefireReporter(SurefireReporter surefireReporter) {
  235.         this.surefireReporter = surefireReporter;
  236.         return this;
  237.     }

  238.     public StoryReporterBuilder withDefaultFormats() {
  239.         return withFormats(Format.STATS);
  240.     }

  241.     public StoryReporterBuilder withFormats(Format... formats) {
  242.         this.formats.addAll(asList(formats));
  243.         return this;
  244.     }

  245.     public StoryReporterBuilder withReporters(StoryReporter... reporters) {
  246.         for (StoryReporter reporter : reporters) {
  247.             this.formats.add(new ProvidedFormat(reporter));
  248.         }
  249.         return this;
  250.     }

  251.     public StoryReporterBuilder withFailureTrace(boolean reportFailureTrace) {
  252.         this.reportFailureTrace = reportFailureTrace;
  253.         return this;
  254.     }

  255.     public StoryReporterBuilder withFailureTraceCompression(
  256.             boolean compressFailureTrace) {
  257.         this.compressFailureTrace = compressFailureTrace;
  258.         return this;
  259.     }

  260.     public StoryReporterBuilder withKeywords(Keywords keywords) {
  261.         this.keywords = keywords;
  262.         return this;
  263.     }

  264.     public StoryReporterBuilder withCodes(SGRCodes codes) {
  265.         this.codes = codes;
  266.         return this;
  267.     }

  268.     public StoryReporterBuilder withMultiThreading(boolean multiThreading) {
  269.         this.multiThreading = multiThreading;
  270.         return this;
  271.     }

  272.     public StoryReporterBuilder withViewResources(Properties resources) {
  273.         this.viewResources = resources;
  274.         return this;
  275.     }

  276.     public StoryReporter build(String storyPath) {
  277.         Map<org.jbehave.core.reporters.Format, StoryReporter> delegates = new HashMap<>();
  278.         for (org.jbehave.core.reporters.Format format : formats) {
  279.             delegates.put(format, reporterFor(storyPath, format));
  280.         }

  281.         return new ConcurrentStoryReporter(new NullStoryReporter(), new ArrayList<>(delegates.values()),
  282.                 multiThreading());
  283.     }

  284.     public Map<String, StoryReporter> build(List<String> storyPaths) {
  285.         Map<String, StoryReporter> reporters = new HashMap<>();
  286.         for (String storyPath : storyPaths) {
  287.             reporters.put(storyPath, build(storyPath));
  288.         }
  289.         reporters.put("*", build("*"));
  290.         return reporters;
  291.     }


  292.     public StoryReporter reporterFor(String storyPath, Format format) {
  293.         FilePrintStreamFactory factory = filePrintStreamFactory(storyPath);
  294.         return format.createStoryReporter(factory, this);
  295.     }

  296.     protected FilePrintStreamFactory filePrintStreamFactory(String storyPath) {
  297.         return new FilePrintStreamFactory(new StoryLocation(codeLocation(),
  298.                 storyPath), fileConfiguration(""));
  299.     }

  300.     public FileConfiguration fileConfiguration(String extension) {
  301.         return new FileConfiguration(relativeDirectory(), extension,
  302.                 pathResolver());
  303.     }

  304.     /**
  305.      * A Format that wraps a StoryReporter instance provided.
  306.      */
  307.     public static class ProvidedFormat extends
  308.             org.jbehave.core.reporters.Format {

  309.         private final StoryReporter reporter;

  310.         public ProvidedFormat(StoryReporter reporter) {
  311.             super(reporter.getClass().getSimpleName());
  312.             this.reporter = reporter;
  313.         }

  314.         @Override
  315.         public StoryReporter createStoryReporter(
  316.                 FilePrintStreamFactory factory,
  317.                 StoryReporterBuilder storyReporterBuilder) {
  318.             return reporter;
  319.         }

  320.     }
  321. }