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