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