package org.jbehave.core.reporters;
import static java.util.Arrays.asList;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
/**
* <p>
* Freemarker-based {@link ViewGenerator}, using the file outputs of the
* reporters for the given formats. The FTL templates for the index and single
* views are injectable the {@link #generateView(File, List, Properties)} but
* defaults are provided. To override, specify the path the new template under
* keys "index", "decorated" and "nonDecorated".
* </p>
* <p>
* The view generator provides the following resources:
*
* <pre>
* resources.setProperty("index", "ftl/jbehave-reports-index.ftl");
* resources.setProperty("decorated", "ftl/jbehave-report-decorated.ftl");
* resources.setProperty("nonDecorated", "ftl/jbehave-report-non-decorated.ftl");
* resources.setProperty("decorateNonHtml", "true");
* resources.setProperty("defaultFormats", "stats");
* resources.setProperty("viewDirectory", "view");
* </pre>
*
* </p>
*
* @author Mauro Talevi
*/
public class FreemarkerViewGenerator implements ViewGenerator {
private final Configuration configuration;
private Properties viewProperties;
private List<Report> reports = new ArrayList<Report>();
public FreemarkerViewGenerator() {
this.configuration = configure();
}
public static Properties defaultViewProperties() {
Properties properties = new Properties();
properties.setProperty("index", "ftl/jbehave-reports-index.ftl");
properties.setProperty("decorated", "ftl/jbehave-report-decorated.ftl");
properties.setProperty("nonDecorated", "ftl/jbehave-report-non-decorated.ftl");
properties.setProperty("decorateNonHtml", "true");
properties.setProperty("defaultFormats", "stats");
properties.setProperty("viewDirectory", "view");
return properties;
}
private Properties mergeWithDefault(Properties properties) {
Properties merged = defaultViewProperties();
merged.putAll(properties);
return merged;
}
public void generateView(File outputDirectory, List<String> formats, Properties viewProperties) {
this.viewProperties = mergeWithDefault(viewProperties);
createIndex(outputDirectory, formats);
}
public int countStories() {
return reports.size();
}
public int countScenarios() {
return count("scenarios", reports);
}
public int countFailedScenarios() {
return count("scenariosFailed", reports);
}
int count(String event, List<Report> reports) {
int count = 0;
for (Report report : reports) {
Properties stats = report.asProperties("stats");
if (stats.containsKey(event)) {
count = count + Integer.parseInt((String) stats.get(event));
}
}
return count;
}
private void createIndex(File outputDirectory, List<String> formats) {
String outputName = templateResource("viewDirectory") + "/index.html";
String index = templateResource("index");
List<String> mergedFormats = mergeWithDefaults(formats);
reports = toReports(indexedReportFiles(outputDirectory, outputName, mergedFormats));
Map<String, Object> dataModel = newDataModel();
dataModel.put("reports", reports);
dataModel.put("date", new Date());
write(outputDirectory, outputName, index, dataModel);
}
private List<String> mergeWithDefaults(List<String> formats) {
List<String> merged = new ArrayList<String>();
merged.addAll(asList(templateResource("defaultFormats").split(",")));
merged.addAll(formats);
return merged;
}
SortedMap<String, List<File>> indexedReportFiles(File outputDirectory, final String outputName,
final List<String> formats) {
SortedMap<String, List<File>> reports = new TreeMap<String, List<File>>();
if (outputDirectory == null || !outputDirectory.exists()) {
return reports;
}
String[] fileNames = outputDirectory.list(new FilenameFilter() {
public boolean accept(File dir, String name) {
return !name.equals(outputName) && hasFormats(name, formats);
}
private boolean hasFormats(String name, List<String> formats) {
for (String format : formats) {
if (name.endsWith(format)) {
return true;
}
}
return false;
}
});
for (String fileName : fileNames) {
String name = FilenameUtils.getBaseName(fileName);
List<File> filesByName = reports.get(name);
if (filesByName == null) {
filesByName = new ArrayList<File>();
reports.put(name, filesByName);
}
filesByName.add(new File(outputDirectory, fileName));
}
return reports;
}
List<Report> toReports(Map<String, List<File>> reportFiles) {
try {
String decoratedTemplate = templateResource("decorated");
String nonDecoratedTemplate = templateResource("nonDecorated");
String viewDirectory = templateResource("viewDirectory");
boolean decorateNonHtml = Boolean.valueOf(templateResource("decorateNonHtml"));
List<Report> reports = new ArrayList<Report>();
for (String name : reportFiles.keySet()) {
Map<String, File> filesByFormat = new HashMap<String, File>();
for (File file : reportFiles.get(name)) {
String fileName = file.getName();
String format = FilenameUtils.getExtension(fileName);
Map<String, Object> dataModel = newDataModel();
dataModel.put("name", name);
dataModel.put("body", IOUtils.toString(new FileReader(file)));
dataModel.put("format", format);
File outputDirectory = file.getParentFile();
String outputName = viewDirectory + "/" + fileName;
String template = decoratedTemplate;
if (!format.equals("html")) {
if (decorateNonHtml) {
outputName = outputName + ".html";
} else {
template = nonDecoratedTemplate;
}
}
File written = write(outputDirectory, outputName, template, dataModel);
filesByFormat.put(format, written);
}
reports.add(new Report(name, filesByFormat));
}
return reports;
} catch (Exception e) {
throw new ReportCreationFailed(reportFiles, e);
}
}
private File write(File outputDirectory, String outputName, String resource, Map<String, Object> dataModel) {
try {
File file = new File(outputDirectory, outputName);
file.getParentFile().mkdirs();
Writer writer = new FileWriter(file);
process(resource, dataModel, writer);
return file;
} catch (Exception e) {
throw new ViewGenerationFailedForTemplate(resource, e);
}
}
private Configuration configure() {
Configuration configuration = new Configuration();
configuration.setClassForTemplateLoading(FreemarkerViewGenerator.class, "/");
configuration.setObjectWrapper(ObjectWrapper.BEANS_WRAPPER);
return configuration;
}
private void process(String resource, Map<String, Object> dataModel, Writer writer) throws TemplateException,
IOException {
Template template = configuration.getTemplate(resource);
template.process(dataModel, writer);
}
private String templateResource(String format) {
return viewProperties.getProperty(format);
}
private Map<String, Object> newDataModel() {
return new HashMap<String, Object>();
}
@SuppressWarnings("serial")
public static class ReportCreationFailed extends RuntimeException {
public ReportCreationFailed(Map<String, List<File>> reportFiles, Exception cause) {
super("Report creation failed from file " + reportFiles, cause);
}
}
@SuppressWarnings("serial")
public static class ViewGenerationFailedForTemplate extends RuntimeException {
public ViewGenerationFailedForTemplate(String resource, Exception cause) {
super(resource, cause);
}
}
public static class Report {
private final String name;
private final Map<String, File> filesByFormat;
public Report(String name, Map<String, File> filesByFormat) {
this.name = name;
this.filesByFormat = filesByFormat;
}
public String getName() {
return name;
}
public Map<String, File> getFilesByFormat() {
return filesByFormat;
}
public Properties asProperties(String format) {
Properties p = new Properties();
File stats = filesByFormat.get(format);
try {
p.load(new FileInputStream(stats));
} catch (Exception e) {
// return empty map
}
return p;
}
}
}