Coverage Report - org.jbehave.core.reporters.FreemarkerViewGenerator
 
Classes in this File Line Coverage Branch Coverage Complexity
FreemarkerViewGenerator
92%
120/130
96%
25/26
1.906
FreemarkerViewGenerator$1
100%
6/6
100%
8/8
1.906
FreemarkerViewGenerator$Report
89%
26/29
100%
4/4
1.906
FreemarkerViewGenerator$ReportCreationFailed
100%
2/2
N/A
1.906
FreemarkerViewGenerator$ViewGenerationFailedForTemplate
100%
2/2
N/A
1.906
 
 1  
 package org.jbehave.core.reporters;
 2  
 
 3  
 import static java.util.Arrays.asList;
 4  
 
 5  
 import java.io.File;
 6  
 import java.io.FileInputStream;
 7  
 import java.io.FileReader;
 8  
 import java.io.FileWriter;
 9  
 import java.io.FilenameFilter;
 10  
 import java.io.IOException;
 11  
 import java.io.Writer;
 12  
 import java.util.ArrayList;
 13  
 import java.util.Collection;
 14  
 import java.util.Collections;
 15  
 import java.util.Date;
 16  
 import java.util.Enumeration;
 17  
 import java.util.HashMap;
 18  
 import java.util.List;
 19  
 import java.util.Map;
 20  
 import java.util.Properties;
 21  
 import java.util.SortedMap;
 22  
 import java.util.TreeMap;
 23  
 
 24  
 import org.apache.commons.io.FilenameUtils;
 25  
 import org.apache.commons.io.IOUtils;
 26  
 import org.apache.commons.lang.builder.CompareToBuilder;
 27  
 import org.apache.commons.lang.builder.ToStringBuilder;
 28  
 import org.apache.commons.lang.builder.ToStringStyle;
 29  
 import org.jbehave.core.io.StoryNameResolver;
 30  
 import org.jbehave.core.io.UnderscoredToCapitalized;
 31  
 import org.jbehave.core.model.StoryLanes;
 32  
 import org.jbehave.core.model.StoryMap;
 33  
 
 34  
 import freemarker.template.Configuration;
 35  
 import freemarker.template.ObjectWrapper;
 36  
 import freemarker.template.Template;
 37  
 import freemarker.template.TemplateException;
 38  
 
 39  
 /**
 40  
  * <p>
 41  
  * Freemarker-based {@link ViewGenerator}, using the file outputs of the
 42  
  * reporters for the given formats. The FTL templates for the index and single
 43  
  * views are injectable the {@link #generateReportsView(File, List, Properties)}
 44  
  * but defaults are provided. To override, specify the path the new template
 45  
  * under keys "index", "decorated" and "nonDecorated".
 46  
  * </p>
 47  
  * <p>
 48  
  * The view generator provides the following resources:
 49  
  * 
 50  
  * <pre>
 51  
  * resources.setProperty(&quot;maps&quot;, &quot;ftl/jbehave-story-maps.ftl&quot;);
 52  
  * resources.setProperty(&quot;index&quot;, &quot;ftl/jbehave-reports-index-with-totals.ftl&quot;);
 53  
  * resources.setProperty(&quot;decorated&quot;, &quot;ftl/jbehave-report-decorated.ftl&quot;);
 54  
  * resources.setProperty(&quot;nonDecorated&quot;, &quot;ftl/jbehave-report-non-decorated.ftl&quot;);
 55  
  * resources.setProperty(&quot;decorateNonHtml&quot;, &quot;true&quot;);
 56  
  * resources.setProperty(&quot;defaultFormats&quot;, &quot;stats&quot;);
 57  
  * resources.setProperty(&quot;viewDirectory&quot;, &quot;view&quot;);
 58  
  * </pre>
 59  
  * 
 60  
  * </p>
 61  
  * 
 62  
  * @author Mauro Talevi
 63  
  */
 64  
 public class FreemarkerViewGenerator implements ViewGenerator {
 65  
 
 66  
     private final Configuration configuration;
 67  
     private Properties viewProperties;
 68  405
     private Map<String, Report> reports = new HashMap<String, Report>();
 69  405
     private StoryNameResolver nameResolver = new UnderscoredToCapitalized();
 70  
     
 71  405
     public FreemarkerViewGenerator() {
 72  405
         this.configuration = configure();
 73  405
     }
 74  
 
 75  
     public static Properties defaultViewProperties() {
 76  239
         Properties properties = new Properties();
 77  239
         properties.setProperty("maps", "ftl/jbehave-story-maps.ftl");
 78  239
         properties.setProperty("index", "ftl/jbehave-reports-index-with-totals.ftl");
 79  239
         properties.setProperty("decorated", "ftl/jbehave-report-decorated.ftl");
 80  239
         properties.setProperty("nonDecorated", "ftl/jbehave-report-non-decorated.ftl");
 81  239
         properties.setProperty("decorateNonHtml", "true");
 82  239
         properties.setProperty("defaultFormats", "stats");
 83  239
         properties.setProperty("viewDirectory", "view");
 84  239
         return properties;
 85  
     }
 86  
 
 87  
     private Properties mergeWithDefault(Properties properties) {
 88  21
         Properties merged = defaultViewProperties();
 89  21
         merged.putAll(properties);
 90  21
         return merged;
 91  
     }
 92  
 
 93  
     public void generateStoryMapsView(File outputDirectory, List<StoryMap> storyMaps, Properties viewProperties) {
 94  0
         this.viewProperties = mergeWithDefault(viewProperties);
 95  0
         createMaps(outputDirectory, storyMaps);
 96  0
     }
 97  
 
 98  
     private void createMaps(File outputDirectory, List<StoryMap> storyMaps) {
 99  0
         String outputName = templateResource("viewDirectory") + "/maps.html";
 100  0
         String maps = templateResource("maps");
 101  0
         Map<String, Object> dataModel = newDataModel();
 102  0
         dataModel.put("storyLanes", new StoryLanes(storyMaps));
 103  0
         dataModel.put("date", new Date());
 104  0
         write(outputDirectory, outputName, maps, dataModel);
 105  0
     }
 106  
 
 107  
     public void generateReportsView(File outputDirectory, List<String> formats, Properties viewProperties) {
 108  21
         this.viewProperties = mergeWithDefault(viewProperties);
 109  21
         createReportsIndex(outputDirectory, formats);
 110  20
     }
 111  
 
 112  
     public ReportsCount getReportsCount() {
 113  18
         int stories = reports.size() - 1; // don't count the totals reports
 114  18
         int scenarios = count("scenarios", reports.values());
 115  18
         int failedScenarios = count("scenariosFailed", reports.values());
 116  18
         return new ReportsCount(stories, scenarios, failedScenarios);
 117  
     }
 118  
 
 119  
     int count(String event, Collection<Report> collection) {
 120  38
         int count = 0;
 121  38
         for (Report report : collection) {
 122  74
             Properties stats = report.asProperties("stats");
 123  74
             if (stats.containsKey(event)) {
 124  1
                 count = count + Integer.parseInt((String) stats.get(event));
 125  
             }
 126  74
         }
 127  38
         return count;
 128  
     }
 129  
 
 130  
     private void createReportsIndex(File outputDirectory, List<String> formats) {
 131  21
         String outputName = templateResource("viewDirectory") + "/index.html";
 132  21
         String index = templateResource("index");
 133  21
         List<String> mergedFormats = mergeWithDefaults(formats);
 134  21
         reports = toReports(indexedReportFiles(outputDirectory, outputName, mergedFormats));
 135  21
         Map<String, Object> dataModel = newDataModel();
 136  21
         List<Report> reportsAsList = new ArrayList<Report>(reports.values());
 137  21
         Collections.sort(reportsAsList);
 138  21
         dataModel.put("reports", reportsAsList);
 139  21
         addTotalsReport();
 140  21
         dataModel.put("reportsAsMap", reports);
 141  21
         dataModel.put("date", new Date());
 142  21
         write(outputDirectory, outputName, index, dataModel);
 143  20
     }
 144  
 
 145  
     private List<String> mergeWithDefaults(List<String> formats) {
 146  21
         List<String> merged = new ArrayList<String>();
 147  21
         merged.addAll(asList(templateResource("defaultFormats").split(",")));
 148  21
         merged.addAll(formats);
 149  21
         return merged;
 150  
     }
 151  
 
 152  
     SortedMap<String, List<File>> indexedReportFiles(File outputDirectory, final String outputName,
 153  
             final List<String> formats) {
 154  24
         SortedMap<String, List<File>> reportFiles = new TreeMap<String, List<File>>();
 155  24
         if (outputDirectory == null || !outputDirectory.exists()) {
 156  2
             return reportFiles;
 157  
         }
 158  22
         String[] fileNames = outputDirectory.list(new FilenameFilter() {
 159  
             public boolean accept(File dir, String name) {
 160  162
                 return !name.equals(outputName) && hasFormats(name, formats);
 161  
             }
 162  
 
 163  
             private boolean hasFormats(String name, List<String> formats) {
 164  161
                 for (String format : formats) {
 165  198
                     if (name.endsWith(format)) {
 166  26
                         return true;
 167  
                     }
 168  
                 }
 169  135
                 return false;
 170  
             }
 171  
         });
 172  48
         for (String fileName : fileNames) {
 173  26
             String name = FilenameUtils.getBaseName(fileName);
 174  26
             List<File> filesByName = reportFiles.get(name);
 175  26
             if (filesByName == null) {
 176  22
                 filesByName = new ArrayList<File>();
 177  22
                 reportFiles.put(name, filesByName);
 178  
             }
 179  26
             filesByName.add(new File(outputDirectory, fileName));
 180  
         }
 181  22
         return reportFiles;
 182  
     }
 183  
 
 184  
     Map<String, Report> toReports(Map<String, List<File>> reportFiles) {
 185  
         try {
 186  22
             String decoratedTemplate = templateResource("decorated");
 187  21
             String nonDecoratedTemplate = templateResource("nonDecorated");
 188  21
             String viewDirectory = templateResource("viewDirectory");
 189  21
             boolean decorateNonHtml = Boolean.valueOf(templateResource("decorateNonHtml"));
 190  21
             Map<String, Report> reports = new HashMap<String, Report>();
 191  21
             for (String name : reportFiles.keySet()) {
 192  20
                 Map<String, File> filesByFormat = new HashMap<String, File>();
 193  20
                 for (File file : reportFiles.get(name)) {
 194  22
                     String fileName = file.getName();
 195  22
                     String format = FilenameUtils.getExtension(fileName);
 196  22
                     Map<String, Object> dataModel = newDataModel();
 197  22
                     dataModel.put("name", name);
 198  22
                     dataModel.put("body", IOUtils.toString(new FileReader(file)));
 199  22
                     dataModel.put("format", format);
 200  22
                     File outputDirectory = file.getParentFile();
 201  22
                     String outputName = viewDirectory + "/" + fileName;
 202  22
                     String template = decoratedTemplate;
 203  22
                     if (!format.equals("html")) {
 204  20
                         if (decorateNonHtml) {
 205  19
                             outputName = outputName + ".html";
 206  
                         } else {
 207  1
                             template = nonDecoratedTemplate;
 208  
                         }
 209  
                     }
 210  22
                     File written = write(outputDirectory, outputName, template, dataModel);
 211  22
                     filesByFormat.put(format, written);
 212  22
                 }
 213  20
                 reports.put(name, new Report(nameResolver.resolveName(name), filesByFormat));
 214  20
             }
 215  21
             return reports;
 216  1
         } catch (Exception e) {
 217  1
             throw new ReportCreationFailed(reportFiles, e);
 218  
         }
 219  
     }
 220  
 
 221  
     private void addTotalsReport() {
 222  21
         Report totals = totals(reports.values());
 223  21
         reports.put(totals.getName(), totals);
 224  21
     }
 225  
 
 226  
     private Report totals(Collection<Report> values) {
 227  21
         Map<String, Integer> totals = new HashMap<String, Integer>();
 228  21
         for (Report report : values) {
 229  20
             Map<String, Integer> stats = report.getStats();
 230  20
             for (String key : stats.keySet()) {
 231  342
                 Integer total = totals.get(key);
 232  342
                 if (total == null) {
 233  342
                     total = 0;
 234  
                 }
 235  342
                 total = total + stats.get(key);
 236  342
                 totals.put(key, total);
 237  342
             }
 238  20
         }
 239  21
         return new Report("totals", new HashMap<String, File>(), totals);
 240  
     }
 241  
 
 242  
     private File write(File outputDirectory, String outputName, String resource, Map<String, Object> dataModel) {
 243  
         try {
 244  43
             File file = new File(outputDirectory, outputName);
 245  43
             file.getParentFile().mkdirs();
 246  43
             Writer writer = new FileWriter(file);
 247  43
             process(resource, dataModel, writer);
 248  42
             return file;
 249  1
         } catch (Exception e) {
 250  1
             throw new ViewGenerationFailedForTemplate(resource, e);
 251  
         }
 252  
     }
 253  
 
 254  
     private Configuration configure() {
 255  405
         Configuration configuration = new Configuration();
 256  405
         configuration.setClassForTemplateLoading(FreemarkerViewGenerator.class, "/");
 257  405
         configuration.setObjectWrapper(ObjectWrapper.BEANS_WRAPPER);
 258  405
         return configuration;
 259  
     }
 260  
 
 261  
     private void process(String resource, Map<String, Object> dataModel, Writer writer) throws TemplateException,
 262  
             IOException {
 263  43
         Template template = configuration.getTemplate(resource);
 264  42
         template.process(dataModel, writer);
 265  42
     }
 266  
 
 267  
     private String templateResource(String format) {
 268  148
         return viewProperties.getProperty(format);
 269  
     }
 270  
 
 271  
     private Map<String, Object> newDataModel() {
 272  43
         return new HashMap<String, Object>();
 273  
     }
 274  
 
 275  
     @SuppressWarnings("serial")
 276  
     public static class ReportCreationFailed extends RuntimeException {
 277  
 
 278  
         public ReportCreationFailed(Map<String, List<File>> reportFiles, Exception cause) {
 279  1
             super("Report creation failed from file " + reportFiles, cause);
 280  1
         }
 281  
     }
 282  
 
 283  
     @SuppressWarnings("serial")
 284  
     public static class ViewGenerationFailedForTemplate extends RuntimeException {
 285  
 
 286  
         public ViewGenerationFailedForTemplate(String resource, Exception cause) {
 287  1
             super(resource, cause);
 288  1
         }
 289  
 
 290  
     }
 291  
 
 292  0
     public static class Report implements Comparable<Report> {
 293  
 
 294  
         private final String name;
 295  
         private final Map<String, File> filesByFormat;
 296  
         private Map<String, Integer> stats;
 297  
 
 298  
         public Report(String name, Map<String, File> filesByFormat) {
 299  21
             this(name, filesByFormat, null);
 300  21
         }
 301  
 
 302  42
         public Report(String name, Map<String, File> filesByFormat, Map<String, Integer> stats) {
 303  42
             this.name = name;
 304  42
             this.filesByFormat = filesByFormat;
 305  42
             this.stats = stats;
 306  42
         }
 307  
 
 308  
         public String getName() {
 309  41
             return name;
 310  
         }
 311  
 
 312  
         public Map<String, File> getFilesByFormat() {
 313  20
             return filesByFormat;
 314  
         }
 315  
 
 316  
         public Properties asProperties(String format) {
 317  93
             Properties p = new Properties();
 318  93
             File stats = filesByFormat.get(format);
 319  
             try {
 320  93
                 p.load(new FileInputStream(stats));
 321  39
             } catch (Exception e) {
 322  
                 // return empty map
 323  54
             }
 324  93
             return p;
 325  
         }
 326  
 
 327  
         public Map<String, Integer> getStats() {
 328  60
             if (stats == null) {
 329  20
                 Properties p = asProperties("stats");
 330  20
                 stats = new HashMap<String, Integer>();
 331  20
                 for (Enumeration<?> e = p.propertyNames(); e.hasMoreElements();) {
 332  342
                     String key = (String) e.nextElement();
 333  342
                     stats.put(key, valueOf(key, p));
 334  342
                 }
 335  
             }
 336  60
             return stats;
 337  
         }
 338  
 
 339  
         private Integer valueOf(String key, Properties p) {
 340  
             try {
 341  342
                 return Integer.valueOf(p.getProperty(key));
 342  342
             } catch (NumberFormatException e) {
 343  342
                 return 0;
 344  
             }
 345  
         }
 346  
 
 347  
         public int compareTo(Report that) {
 348  0
             return CompareToBuilder.reflectionCompare(this.getName(), that.getName());
 349  
         }
 350  
 
 351  
         @Override
 352  
         public String toString() {
 353  0
             return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append(name).toString();
 354  
         }
 355  
     }
 356  
 
 357  
 }