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