Coverage Report - org.jbehave.core.reporters.FreemarkerViewGenerator
 
Classes in this File Line Coverage Branch Coverage Complexity
FreemarkerViewGenerator
100%
124/124
100%
20/20
1.795
FreemarkerViewGenerator$1
100%
6/6
100%
8/8
1.795
FreemarkerViewGenerator$Report
90%
29/32
83%
5/6
1.795
FreemarkerViewGenerator$ReportCreationFailed
100%
2/2
N/A
1.795
FreemarkerViewGenerator$ReportsTable
90%
30/33
87%
7/8
1.795
FreemarkerViewGenerator$ViewGenerationFailedForTemplate
100%
2/2
N/A
1.795
 
 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.StoryMaps;
 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}, which uses the configured FTL
 42  
  * templates for the views. The default view properties are overridable via the 
 43  
  * method {@link Properties} parameter.  To override, specify the path to the 
 44  
  * new template under the appropriate key:
 45  
  * <p>
 46  
  * The view generator provides the following resources:
 47  
  * 
 48  
  * <pre>
 49  
  * resources.setProperty(&quot;views&quot;, &quot;ftl/jbehave-views.ftl&quot;);
 50  
  * resources.setProperty(&quot;maps&quot;, &quot;ftl/jbehave-maps.ftl&quot;);
 51  
  * resources.setProperty(&quot;navigator&quot;, &quot;ftl/jbehave-navigator.ftl&quot;);
 52  
  * resources.setProperty(&quot;reports&quot;, &quot;ftl/jbehave-reports-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  
  * </p>
 60  
  * 
 61  
  * @author Mauro Talevi
 62  
  */
 63  
 public class FreemarkerViewGenerator implements ViewGenerator {
 64  
 
 65  
     private final Configuration configuration;
 66  
     private Properties viewProperties;
 67  485
     private List<Report> reports = new ArrayList<Report>();
 68  
     private final StoryNameResolver nameResolver;
 69  
 
 70  
     public FreemarkerViewGenerator() {
 71  485
         this(new UnderscoredToCapitalized());
 72  485
     }
 73  
 
 74  485
     public FreemarkerViewGenerator(StoryNameResolver nameResolver) {
 75  485
         this.nameResolver = nameResolver;
 76  485
         this.configuration = configure();
 77  485
     }
 78  
 
 79  
     public static Properties defaultViewProperties() {
 80  279
         Properties properties = new Properties();
 81  279
         properties.setProperty("views", "ftl/jbehave-views.ftl");
 82  279
         properties.setProperty("maps", "ftl/jbehave-maps.ftl");
 83  279
         properties.setProperty("navigator", "ftl/jbehave-navigator.ftl");
 84  279
         properties.setProperty("reports", "ftl/jbehave-reports-with-totals.ftl");
 85  279
         properties.setProperty("decorated", "ftl/jbehave-report-decorated.ftl");
 86  279
         properties.setProperty("nonDecorated", "ftl/jbehave-report-non-decorated.ftl");
 87  279
         properties.setProperty("decorateNonHtml", "true");
 88  279
         properties.setProperty("defaultFormats", "stats");
 89  279
         properties.setProperty("viewDirectory", "view");
 90  279
         return properties;
 91  
     }
 92  
 
 93  
     private Properties mergeWithDefault(Properties properties) {
 94  17
         Properties merged = defaultViewProperties();
 95  17
         merged.putAll(properties);
 96  17
         return merged;
 97  
     }
 98  
 
 99  
     private void generateViewsIndex(File outputDirectory) {
 100  16
         String outputName = templateResource("viewDirectory") + "/index.html";
 101  16
         String viewsTemplate = templateResource("views");        
 102  16
         Map<String, Object> dataModel = newDataModel();
 103  16
         dataModel.put("date", new Date());
 104  16
         write(outputDirectory, outputName, viewsTemplate, dataModel);        
 105  16
     }
 106  
 
 107  
     public void generateMapsView(File outputDirectory, StoryMaps storyMaps, Properties viewProperties) {
 108  1
         this.viewProperties = mergeWithDefault(viewProperties);
 109  1
         String outputName = templateResource("viewDirectory") + "/maps.html";
 110  1
         String mapsTemplate = templateResource("maps");
 111  1
         Map<String, Object> dataModel = newDataModel();
 112  1
         dataModel.put("storyLanes", new StoryLanes(storyMaps, nameResolver));
 113  1
         dataModel.put("date", new Date());
 114  1
         write(outputDirectory, outputName, mapsTemplate, dataModel);
 115  1
         generateViewsIndex(outputDirectory);
 116  1
     }
 117  
 
 118  
     public void generateReportsView(File outputDirectory, List<String> formats, Properties viewProperties) {
 119  16
         this.viewProperties = mergeWithDefault(viewProperties);
 120  16
         String outputName = templateResource("viewDirectory") + "/reports.html";
 121  16
         String reportsTemplate = templateResource("reports");
 122  16
         List<String> mergedFormats = mergeFormatsWithDefaults(formats);
 123  16
         reports = createReports(readReportFiles(outputDirectory, outputName, mergedFormats));
 124  16
         Map<String, Object> dataModel = newDataModel();
 125  16
         dataModel.put("reportsTable", new ReportsTable(reports, nameResolver));
 126  16
         dataModel.put("date", new Date());
 127  16
         write(outputDirectory, outputName, reportsTemplate, dataModel);
 128  15
         generateViewsIndex(outputDirectory);
 129  15
     }
 130  
 
 131  
     public ReportsCount getReportsCount() {
 132  13
         int stories = reports.size();
 133  13
         int storiesNotAllowed = count("notAllowed", reports);
 134  13
         int storiesPending = count("pending", reports);
 135  13
         int scenarios = count("scenarios", reports);
 136  13
         int scenariosFailed = count("scenariosFailed", reports);
 137  13
         int scenariosNotAllowed = count("scenariosNotAllowed", reports);
 138  13
         int scenariosPending = count("scenariosPending", reports);
 139  13
         int stepsFailed = count("stepsFailed", reports);
 140  13
         return new ReportsCount(stories, storiesNotAllowed, storiesPending, scenarios, scenariosFailed, scenariosNotAllowed, scenariosPending, stepsFailed);
 141  
     }
 142  
 
 143  
     int count(String event, Collection<Report> reports) {
 144  93
         int count = 0;
 145  93
         for (Report report : reports) {
 146  93
             Properties stats = report.asProperties("stats");
 147  93
             if (stats.containsKey(event)) {
 148  1
                 count = count + Integer.parseInt((String) stats.get(event));
 149  
             }
 150  93
         }
 151  93
         return count;
 152  
     }
 153  
 
 154  
     private List<String> mergeFormatsWithDefaults(List<String> formats) {
 155  16
         List<String> merged = new ArrayList<String>();
 156  16
         merged.addAll(asList(templateResource("defaultFormats").split(",")));
 157  16
         merged.addAll(formats);
 158  16
         return merged;
 159  
     }
 160  
 
 161  
     List<Report> createReports(Map<String, List<File>> reportFiles) {
 162  
         try {
 163  17
             String decoratedTemplate = templateResource("decorated");
 164  16
             String nonDecoratedTemplate = templateResource("nonDecorated");
 165  16
             String viewDirectory = templateResource("viewDirectory");
 166  16
             boolean decorateNonHtml = Boolean.valueOf(templateResource("decorateNonHtml"));
 167  16
             List<Report> reports = new ArrayList<Report>();
 168  16
             for (String name : reportFiles.keySet()) {
 169  15
                 Map<String, File> filesByFormat = new HashMap<String, File>();
 170  15
                 for (File file : reportFiles.get(name)) {
 171  17
                     String fileName = file.getName();
 172  17
                     String format = FilenameUtils.getExtension(fileName);
 173  17
                     Map<String, Object> dataModel = newDataModel();
 174  17
                     dataModel.put("name", name);
 175  17
                     dataModel.put("body", IOUtils.toString(new FileReader(file)));
 176  17
                     dataModel.put("format", format);
 177  17
                     File outputDirectory = file.getParentFile();
 178  17
                     String outputName = viewDirectory + "/" + fileName;
 179  17
                     String template = decoratedTemplate;
 180  17
                     if (!format.equals("html")) {
 181  15
                         if (decorateNonHtml) {
 182  14
                             outputName = outputName + ".html";
 183  
                         } else {
 184  1
                             template = nonDecoratedTemplate;
 185  
                         }
 186  
                     }
 187  17
                     File written = write(outputDirectory, outputName, template, dataModel);
 188  17
                     filesByFormat.put(format, written);
 189  17
                 }
 190  15
                 reports.add(new Report(name, filesByFormat));
 191  15
             }
 192  16
             return reports;
 193  1
         } catch (Exception e) {
 194  1
             throw new ReportCreationFailed(reportFiles, e);
 195  
         }
 196  
     }
 197  
 
 198  
     SortedMap<String, List<File>> readReportFiles(File outputDirectory, final String outputName,
 199  
             final List<String> formats) {
 200  19
         SortedMap<String, List<File>> reportFiles = new TreeMap<String, List<File>>();
 201  19
         if (outputDirectory == null || !outputDirectory.exists()) {
 202  2
             return reportFiles;
 203  
         }
 204  17
         String[] fileNames = outputDirectory.list(new FilenameFilter() {
 205  
             public boolean accept(File dir, String name) {
 206  128
                 return !name.equals(outputName) && hasFormats(name, formats);
 207  
             }
 208  
 
 209  
             private boolean hasFormats(String name, List<String> formats) {
 210  127
                 for (String format : formats) {
 211  165
                     if (name.endsWith(format)) {
 212  21
                         return true;
 213  
                     }
 214  
                 }
 215  106
                 return false;
 216  
             }
 217  
         });
 218  38
         for (String fileName : fileNames) {
 219  21
             String name = FilenameUtils.getBaseName(fileName);
 220  21
             List<File> filesByName = reportFiles.get(name);
 221  21
             if (filesByName == null) {
 222  17
                 filesByName = new ArrayList<File>();
 223  17
                 reportFiles.put(name, filesByName);
 224  
             }
 225  21
             filesByName.add(new File(outputDirectory, fileName));
 226  
         }
 227  17
         return reportFiles;
 228  
     }
 229  
 
 230  
     private File write(File outputDirectory, String outputName, String resource, Map<String, Object> dataModel) {
 231  
         try {
 232  50
             File file = new File(outputDirectory, outputName);
 233  50
             file.getParentFile().mkdirs();
 234  50
             Writer writer = new FileWriter(file);
 235  50
             process(resource, dataModel, writer);
 236  49
             return file;
 237  1
         } catch (Exception e) {
 238  1
             throw new ViewGenerationFailedForTemplate(resource, e);
 239  
         }
 240  
     }
 241  
 
 242  
     private Configuration configure() {
 243  485
         Configuration configuration = new Configuration();
 244  485
         configuration.setClassForTemplateLoading(FreemarkerViewGenerator.class, "/");
 245  485
         configuration.setObjectWrapper(ObjectWrapper.BEANS_WRAPPER);
 246  485
         return configuration;
 247  
     }
 248  
 
 249  
     private void process(String resource, Map<String, Object> dataModel, Writer writer) throws TemplateException,
 250  
             IOException {
 251  50
         Template template = configuration.getTemplate(resource);
 252  49
         template.process(dataModel, writer);
 253  49
     }
 254  
 
 255  
     private String templateResource(String format) {
 256  147
         return viewProperties.getProperty(format);
 257  
     }
 258  
 
 259  
     private Map<String, Object> newDataModel() {
 260  50
         return new HashMap<String, Object>();
 261  
     }
 262  
 
 263  
     @SuppressWarnings("serial")
 264  
     public static class ReportCreationFailed extends RuntimeException {
 265  
 
 266  
         public ReportCreationFailed(Map<String, List<File>> reportFiles, Exception cause) {
 267  1
             super("Report creation failed from file " + reportFiles, cause);
 268  1
         }
 269  
     }
 270  
 
 271  
     @SuppressWarnings("serial")
 272  
     public static class ViewGenerationFailedForTemplate extends RuntimeException {
 273  
 
 274  
         public ViewGenerationFailedForTemplate(String resource, Exception cause) {
 275  1
             super(resource, cause);
 276  1
         }
 277  
 
 278  
     }
 279  
 
 280  
     public static class ReportsTable {
 281  
 
 282  16
         private final Map<String, Report> reports = new HashMap<String, Report>();
 283  
         private final StoryNameResolver nameResolver;
 284  
 
 285  16
         public ReportsTable(List<Report> reports, StoryNameResolver nameResolver) {
 286  16
             this.nameResolver = nameResolver;
 287  16
             index(reports);
 288  16
             addTotalsReport();
 289  16
         }
 290  
 
 291  
         private void index(List<Report> reports) {
 292  16
             for (Report report : reports) {
 293  15
                 report.nameAs(nameResolver.resolveName(report.getPath()));
 294  15
                 this.reports.put(report.getName(), report);
 295  
             }
 296  16
         }
 297  
 
 298  
         private void addTotalsReport() {
 299  16
             Report report = totals(reports.values());
 300  16
             report.nameAs(nameResolver.resolveName(report.getPath()));
 301  16
             reports.put(report.getName(), report);
 302  16
         }
 303  
 
 304  
         private Report totals(Collection<Report> values) {
 305  16
             Map<String, Integer> totals = new HashMap<String, Integer>();
 306  16
             for (Report report : values) {
 307  15
                 Map<String, Integer> stats = report.getStats();
 308  15
                 for (String key : stats.keySet()) {
 309  234
                     Integer total = totals.get(key);
 310  234
                     if (total == null) {
 311  234
                         total = 0;
 312  
                     }
 313  234
                     total = total + stats.get(key);
 314  234
                     totals.put(key, total);
 315  234
                 }
 316  15
             }
 317  16
             return new Report("Totals", new HashMap<String, File>(), totals);
 318  
         }
 319  
 
 320  
         public List<Report> getReports() {
 321  0
             List<Report> list = new ArrayList<Report>(reports.values());
 322  0
             Collections.sort(list);
 323  0
             return list;
 324  
         }
 325  
 
 326  
         public List<String> getReportNames() {
 327  15
             List<String> list = new ArrayList<String>(reports.keySet());
 328  15
             Collections.sort(list);
 329  15
             return list;
 330  
         }
 331  
 
 332  
         public Report getReport(String name) {
 333  45
             return reports.get(name);
 334  
         }
 335  
     }
 336  
 
 337  0
     public static class Report implements Comparable<Report> {
 338  
 
 339  
         private final String path;
 340  
         private final Map<String, File> filesByFormat;
 341  
         private Map<String, Integer> stats;
 342  
         private String name;
 343  
 
 344  
         public Report(String path, Map<String, File> filesByFormat) {
 345  16
             this(path, filesByFormat, null);
 346  16
         }
 347  
 
 348  32
         public Report(String path, Map<String, File> filesByFormat, Map<String, Integer> stats) {
 349  32
             this.path = path;
 350  32
             this.filesByFormat = filesByFormat;
 351  32
             this.stats = stats;
 352  32
         }
 353  
 
 354  
         public String getPath() {
 355  31
             return path;
 356  
         }
 357  
 
 358  
         public String getName() {
 359  46
             return name != null ? name : path;
 360  
         }
 361  
 
 362  
         public void nameAs(String name) {
 363  31
             this.name = name;
 364  31
         }
 365  
 
 366  
         public Map<String, File> getFilesByFormat() {
 367  15
             return filesByFormat;
 368  
         }
 369  
 
 370  
         public Properties asProperties(String format) {
 371  107
             Properties p = new Properties();
 372  107
             File stats = filesByFormat.get(format);
 373  
             try {
 374  107
                 p.load(new FileInputStream(stats));
 375  3
             } catch (Exception e) {
 376  
                 // return empty map
 377  104
             }
 378  107
             return p;
 379  
         }
 380  
 
 381  
         public Map<String, Integer> getStats() {
 382  45
             if (stats == null) {
 383  15
                 Properties p = asProperties("stats");
 384  15
                 stats = new HashMap<String, Integer>();
 385  15
                 for (Enumeration<?> e = p.propertyNames(); e.hasMoreElements();) {
 386  234
                     String key = (String) e.nextElement();
 387  234
                     stats.put(key, valueOf(key, p));
 388  234
                 }
 389  
             }
 390  45
             return stats;
 391  
         }
 392  
 
 393  
         private Integer valueOf(String key, Properties p) {
 394  
             try {
 395  234
                 return Integer.valueOf(p.getProperty(key));
 396  234
             } catch (NumberFormatException e) {
 397  234
                 return 0;
 398  
             }
 399  
         }
 400  
 
 401  
         public int compareTo(Report that) {
 402  0
             return CompareToBuilder.reflectionCompare(this.getName(), that.getName());
 403  
         }
 404  
 
 405  
         @Override
 406  
         public String toString() {
 407  0
             return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append(path).toString();
 408  
         }
 409  
     }
 410  
 
 411  
 }