package tzatziki.pdf.support;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Section;
import gutenberg.itext.*;
import gutenberg.itext.model.Markdown;
import gutenberg.itext.support.FirstPageRenderer;
import gutenberg.util.Consumer;
import gutenberg.util.KeyValues;
import tzatziki.analysis.exec.model.FeatureExec;
import tzatziki.analysis.exec.model.ResultExec;
import tzatziki.analysis.exec.model.StepExec;
import tzatziki.analysis.exec.support.TagView;
import tzatziki.analysis.exec.support.TagViews;
import tzatziki.analysis.tag.TagDictionary;
import tzatziki.pdf.emitter.FeatureEmitter;
import tzatziki.pdf.model.Steps;
import java.io.File;
import java.io.FileNotFoundException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
/**
* @author <a href="http://twitter.com/aloyer">@aloyer</a>
*/
public class DefaultPdfReportBuilder {
public enum Overview {
FeatureSummary,
TagDictionary,
TagViews
}
//
private Configuration configuration = new Configuration();
private Consumer<PdfReport> configurator;
private SimpleEmitter coverPage;
//
private TagDictionary tagDictionary;
private TagViews tagViews = new TagViews();
private boolean tagViewsFromDictionary = false;
private TagViewsFromDictionaryBuilder tagViewsFromDictionaryBuilder = new TagViewsFromDictionaryBuilder();
//
private List<FeatureExec> features = Lists.newArrayList();
//
private Properties l10n;
private List<Consumer<PdfReport>> fragments = Lists.newArrayList();
public DefaultPdfReportBuilder using(Configuration configuration) {
this.configuration = configuration;
return this;
}
public DefaultPdfReportBuilder configurator(Consumer<PdfReport> configurator) {
this.configurator = configurator;
return this;
}
public DefaultPdfReportBuilder noOverview() {
return this;
}
public DefaultPdfReportBuilder defaultOverview() {
return overview(Overview.values());
}
public DefaultPdfReportBuilder overview(final Overview... overviews) {
fragments.add(new Consumer<PdfReport>() {
@Override
public void consume(PdfReport report) {
emitOverview(
features,
0,
tagViews,
report,
overviews);
}
});
return this;
}
public DefaultPdfReportBuilder overview(final List<FeatureExec> features,
final int hLevelOffset,
final Overview... overviews) {
fragments.add(new Consumer<PdfReport>() {
@Override
public void consume(PdfReport report) {
emitOverview(features, hLevelOffset, tagViews, report, overviews);
}
});
return this;
}
public DefaultPdfReportBuilder overview(final List<FeatureExec> features,
final int hLevelOffset,
final TagViews tagViews,
final Overview... overviews) {
fragments.add(new Consumer<PdfReport>() {
@Override
public void consume(PdfReport report) {
emitOverview(features, hLevelOffset, tagViews, report, overviews);
}
});
return this;
}
public DefaultPdfReportBuilder title(String title) {
configuration.declareProperty(Configuration.TITLE, title);
return this;
}
public DefaultPdfReportBuilder subTitle(String subTitle) {
configuration.declareProperty(Configuration.SUB_TITLE, subTitle);
return this;
}
public DefaultPdfReportBuilder tagDictionary(TagDictionary tagDictionary) {
this.tagDictionary = tagDictionary;
return this;
}
public DefaultPdfReportBuilder coverPage(SimpleEmitter coverPage) {
this.coverPage = coverPage;
return this;
}
public DefaultPdfReportBuilder tagViews(TagView... tagViews) {
this.tagViews.addAll(tagViews);
return this;
}
public DefaultPdfReportBuilder tagViewsFromDictionary() {
this.tagViewsFromDictionary = true;
return this;
}
public DefaultPdfReportBuilder tagViewsFromDictionary(TagViewsFromDictionaryBuilder builder) {
this.tagViewsFromDictionary = true;
this.tagViewsFromDictionaryBuilder = builder;
return this;
}
public DefaultPdfReportBuilder features(final Collection<? extends FeatureExec> features) {
return features(features, 0);
}
public DefaultPdfReportBuilder features(final Collection<? extends FeatureExec> features, final int headerLevelOffset) {
this.features.addAll(features);
this.fragments.add(new Consumer<PdfReport>() {
@Override
public void consume(PdfReport report) {
report.iTextContext().keyValues().declare(FeatureEmitter.FEATURE_HEADER_LEVEL_OFFSET, headerLevelOffset);
for (FeatureExec feature : features) {
emitFeature(report, feature);
}
report.iTextContext().keyValues().declare(FeatureEmitter.FEATURE_HEADER_LEVEL_OFFSET, 0);
}
});
return this;
}
public DefaultPdfReportBuilder sampleSteps() {
this.fragments.add(new Consumer<PdfReport>() {
@Override
public void consume(PdfReport report) {
emitSampleSteps(report);
}
});
return this;
}
public DefaultPdfReportBuilder flushPendingChapter() {
this.fragments.add(new Consumer<PdfReport>() {
@Override
public void consume(PdfReport report) {
report.iTextContext().flushPendingChapter();
}
});
return this;
}
public DefaultPdfReportBuilder markup(final Markdown markdown) {
this.fragments.add(new Consumer<PdfReport>() {
@Override
public void consume(PdfReport report) {
emitMarkup(report, markdown);
}
});
return this;
}
public DefaultPdfReportBuilder emit(final SimpleEmitter emitter) {
this.fragments.add(new Consumer<PdfReport>() {
@Override
public void consume(PdfReport report) {
emitDirect(report, emitter);
}
});
return this;
}
protected String l10n(String key, Object... args) {
if (l10n == null) {
l10n = initL10n();
}
String localized = l10n.getProperty(key);
return MessageFormat.format(localized, args);
}
protected Properties initL10n() {
Properties p = new Properties();
// sample-steps
p.setProperty("sample-steps.section-title", "Sample Steps");
p.setProperty("sample-steps.step-1.keyword", "Given");
p.setProperty("sample-steps.step-1.sentence", "a passed step");
p.setProperty("sample-steps.step-2.keyword", "And");
p.setProperty("sample-steps.step-2.sentence", "a failed step");
p.setProperty("sample-steps.step-3.keyword", "When");
p.setProperty("sample-steps.step-3.sentence", "a pending step");
p.setProperty("sample-steps.step-4.keyword", "But");
p.setProperty("sample-steps.step-4.sentence", "an undefined step");
p.setProperty("sample-steps.step-5.keyword", "Then");
p.setProperty("sample-steps.step-5.sentence", "a skipped step");
// overview
p.setProperty("overview.section-title", "Overview");
p.setProperty("overview.subsection.features.title", "Features");
p.setProperty("overview.subsection.tags.title", "Tags");
p.setProperty("overview.subsection.tagViews.title", "Consolidated tag views");
//
p.setProperty("header.firstPageTemplate", "");
p.setProperty("header.otherPageTemplate", "");
p.setProperty("footer.firstPageTemplate", "");
p.setProperty("footer.otherPageTemplate", "{0} | $'{'chapterTitle'}'");
return p;
}
public void generate(File output) throws FileNotFoundException, DocumentException {
checkForRequiredParameters();
PdfReport report = new PdfReport(configuration);
if (configurator != null)
configurator.consume(report);
report.startReport(output);
HeaderFooter headerFooter = registerHeaderAndFooter(report);
emitCoverPage(report);
report.startContent();
for (Consumer<PdfReport> fragment : fragments) {
fragment.consume(report);
}
report.endReport(new TableOfContentsPostProcessor(headerFooter));
}
protected void checkForRequiredParameters() {
}
private void emitDirect(PdfReport report, SimpleEmitter emitter) {
report.emit(emitter);
}
private void emitSampleSteps(PdfReport report) {
report.emit(new SimpleEmitter() {
@Override
public void emit(ITextContext emitterContext) {
String p = "sample-steps.";
Sections sections = emitterContext.sections();
Section section = sections.newSection(l10n(p + "section-title"), 1, false);
try {
//
List<StepExec> list = Arrays.asList(
new StepExec(l10n(p + "step-1.keyword"), l10n(p + "step-1.sentence")).declareResult(new ResultExec("passed", null, null, 500L)),
new StepExec(l10n(p + "step-2.keyword"), l10n(p + "step-2.sentence")).declareResult(new ResultExec("failed", null, null, 230L)),
new StepExec(l10n(p + "step-3.keyword"), l10n(p + "step-3.sentence")).declareResult(new ResultExec("pending", null, null, null)),
new StepExec(l10n(p + "step-4.keyword"), l10n(p + "step-4.sentence")).declareResult(new ResultExec("undefined", null, null, null)),
new StepExec(l10n(p + "step-5.keyword"), l10n(p + "step-5.sentence")).declareResult(new ResultExec("skipped", null, null, null))
);
emitterContext.emit(Steps.class, new Steps(list));
} finally {
sections.leaveSection(1);
}
emitterContext.append(section);
}
});
}
private void emitFeature(PdfReport report, FeatureExec feature) {
report.emit(feature);
}
private Optional<TagViews> buildTagViewsForDictionary() {
boolean emitTagDictionary = (tagViewsFromDictionary && tagDictionary != null);
if (emitTagDictionary)
return tagViewsFromDictionaryBuilder.build(tagDictionary);
else
return Optional.absent();
}
private void emitOverview(List<FeatureExec> features,
final int hLevelOffset,
final TagViews tagViews,
PdfReport report,
final Overview[] overviews) {
if (overviews.length == 0)
return;
final Optional<TagViews> dictionaryTagViewsOpt = buildTagViewsForDictionary();
tagViews.clear();
for (FeatureExec feature : features) {
tagViews.consolidateView(feature);
if (dictionaryTagViewsOpt.isPresent())
dictionaryTagViewsOpt.get().consolidateView(feature);
}
report.emit(new SimpleEmitter() {
@Override
public void emit(ITextContext emitterContext) {
Sections sections = emitterContext.sections();
Section section = sections.newSection(l10n("overview.section-title"), hLevelOffset + 1);
try {
for (Overview overview : overviews) {
switch (overview) {
case FeatureSummary:
sections.newSection(l10n("overview.subsection.features.title"), hLevelOffset + 2);
emitterContext.emit(new FeatureSummaryListOfSection(DefaultPdfReportBuilder.this.features, hLevelOffset + 3));
sections.leaveSection(hLevelOffset + 2);
break;
case TagDictionary:
sections.newSection(l10n("overview.subsection.tags.title"), hLevelOffset + 2);
emitterContext.emit(tagDictionary);
sections.leaveSection(hLevelOffset + 2);
break;
case TagViews:
sections.newSection(l10n("overview.subsection.tagViews.title"), hLevelOffset + 2);
if (dictionaryTagViewsOpt.isPresent()) {
emitterContext.emit(dictionaryTagViewsOpt.get());
}
emitterContext.emit(tagViews);
sections.leaveSection(hLevelOffset + 2);
break;
}
}
} finally {
sections.leaveSection(hLevelOffset + 1);
}
if (hLevelOffset == 0)
emitterContext.append(section);
}
});
}
private static String orDefault(String value, String fallback) {
return (value != null && !value.trim().isEmpty()) ? value : fallback;
}
private void emitMarkup(PdfReport report, Markdown markdown) {
if (markdown != null)
report.emit(markdown);
}
private void emitCoverPage(PdfReport report) {
if (coverPage != null) {
report.emit(coverPage);
report.newPage();
return;
}
KeyValues kvs = report.iTextContext().keyValues();
String title = kvs.getString(Configuration.TITLE).orNull();
String subTitle = kvs.getString(Configuration.SUB_TITLE).orNull();
if (title != null) {
FirstPageRenderer coverPage = new FirstPageRenderer(title, subTitle);
report.emit(coverPage);
report.newPage();
}
}
private HeaderFooter registerHeaderAndFooter(PdfReport report) {
ITextContext context = report.iTextContext();
PageNumber pageNumber = context.pageNumber();
Styles styles = context.styles();
KeyValues kvs = context.keyValues();
String title = kvs.getString(Configuration.HEADER_TITLE).orNull();
if (title == null)
title = kvs.getString(Configuration.TITLE).orNull();
Function<PageInfos, Phrase> header = HeaderFooter.create(
styles,
HeaderFooter.FOOTER_FONT,
l10n("header.firstPageTemplate", title),
l10n("header.otherPageTemplate", title),
null);
Function<PageInfos, Phrase> footer = HeaderFooter.create(
styles,
HeaderFooter.FOOTER_FONT,
l10n("footer.firstPageTemplate", title),
l10n("footer.otherPageTemplate", title),
null);
HeaderFooter headerFooter = new HeaderFooter(pageNumber, styles, header, footer);
context.getPdfWriter().setPageEvent(headerFooter);
return headerFooter;
}
}