/**
* AnalyzerBeans
* Copyright (C) 2014 Neopost - Customer Information Management
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.eobjects.analyzer.result.html;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import org.eobjects.analyzer.beans.api.Renderer;
import org.eobjects.analyzer.configuration.AnalyzerBeansConfiguration;
import org.eobjects.analyzer.descriptors.ComponentDescriptor;
import org.eobjects.analyzer.job.ComponentJob;
import org.eobjects.analyzer.result.AnalysisResult;
import org.eobjects.analyzer.result.AnalysisResultWriter;
import org.eobjects.analyzer.result.AnalyzerResult;
import org.eobjects.analyzer.result.renderer.HtmlRenderingFormat;
import org.eobjects.analyzer.result.renderer.RendererFactory;
import org.eobjects.analyzer.util.ComponentJobComparator;
import org.eobjects.analyzer.util.LabelUtils;
import org.eobjects.analyzer.util.StringUtils;
import org.apache.metamodel.util.Predicate;
import org.apache.metamodel.util.Ref;
import org.apache.metamodel.util.TruePredicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link AnalysisResultWriter} which writes an analysis result as a HTML page.
*/
public class HtmlAnalysisResultWriter implements AnalysisResultWriter {
private static final Logger logger = LoggerFactory.getLogger(HtmlAnalysisResultWriter.class);
private final boolean _tabs;
private final boolean _headers;
private final Predicate<Entry<ComponentJob, AnalyzerResult>> _jobInclusionPredicate;
public HtmlAnalysisResultWriter() {
this(true);
}
public HtmlAnalysisResultWriter(boolean tabs) {
this(tabs, new TruePredicate<Entry<ComponentJob, AnalyzerResult>>());
}
public HtmlAnalysisResultWriter(boolean tabs, Predicate<Entry<ComponentJob, AnalyzerResult>> jobInclusionPredicate) {
this(tabs, jobInclusionPredicate, true);
}
public HtmlAnalysisResultWriter(boolean tabs, Predicate<Entry<ComponentJob, AnalyzerResult>> jobInclusionPredicate,
boolean headers) {
_tabs = tabs;
_jobInclusionPredicate = jobInclusionPredicate;
_headers = headers;
}
@Override
public void write(AnalysisResult result, AnalyzerBeansConfiguration configuration, Ref<Writer> writerRef,
Ref<OutputStream> outputStreamRef) throws IOException {
final Writer writer = writerRef.get();
write(result, configuration, writer);
}
public void write(AnalysisResult result, AnalyzerBeansConfiguration configuration, Writer writer)
throws IOException {
final HtmlRenderingContext context = new DefaultHtmlRenderingContext();
final RendererFactory rendererFactory = new RendererFactory(configuration);
final Map<ComponentJob, HtmlFragment> htmlFragments = new LinkedHashMap<ComponentJob, HtmlFragment>();
final Map<ComponentJob, AnalyzerResult> resultMap = new TreeMap<ComponentJob, AnalyzerResult>(
new ComponentJobComparator());
resultMap.putAll(result.getResultMap());
for (Entry<ComponentJob, AnalyzerResult> entry : resultMap.entrySet()) {
final ComponentJob componentJob = entry.getKey();
final AnalyzerResult analyzerResult = entry.getValue();
if (_jobInclusionPredicate.eval(entry)) {
final Renderer<? super AnalyzerResult, ? extends HtmlFragment> renderer = rendererFactory.getRenderer(
analyzerResult, HtmlRenderingFormat.class);
if (renderer == null) {
throw new IllegalStateException("No HTML renderer found for result: " + analyzerResult);
}
final HtmlRenderingContext localContext = new ComponentHtmlRenderingContext(context, componentJob);
try {
final HtmlFragment htmlFragment = renderer.render(analyzerResult);
htmlFragment.initialize(localContext);
htmlFragments.put(componentJob, htmlFragment);
} catch (Exception e) {
logger.error("Error while rendering analyzer result: " + analyzerResult, e);
writeRenderingError(writer, componentJob, analyzerResult, e);
}
} else {
logger.debug("Skipping job {} / result {} because predicate evaluated false", componentJob,
analyzerResult);
}
}
writeHtmlBegin(writer, context);
writeHead(writer, htmlFragments, context);
writeBody(writer, htmlFragments, context);
writeHtmlEnd(writer, context);
}
private void writeMaterializationError(Writer writer, ComponentJob componentJob, Exception e) throws IOException {
writeGenericError(writer, componentJob, null, e);
}
private void writeRenderingError(Writer writer, ComponentJob componentJob, AnalyzerResult analyzerResult,
Exception e) throws IOException {
writeGenericError(writer, componentJob, analyzerResult, e);
}
private void writeGenericError(Writer writer, ComponentJob componentJob, AnalyzerResult analyzerResult, Exception e)
throws IOException {
writer.write("<div class=\"error\">");
writer.write("<p>Component job: " + LabelUtils.getLabel(componentJob) + "</p>");
if (analyzerResult != null) {
writer.write("<p>Analyzer result: " + analyzerResult + "</p>");
}
writer.write("<pre>");
PrintWriter printWriter = new PrintWriter(writer);
e.printStackTrace(printWriter);
writer.write("</pre>");
writer.write("</div>");
}
protected void writeHtmlBegin(Writer writer, HtmlRenderingContext context) throws IOException {
writer.write("<!DOCTYPE html>\n");
writer.write("<html>\n");
}
protected void writeHtmlEnd(Writer writer, HtmlRenderingContext context) throws IOException {
writer.write("</html>");
}
protected void writeHead(final Writer writer, final Map<ComponentJob, HtmlFragment> htmlFragments,
HtmlRenderingContext context) throws IOException {
final Set<HeadElement> allHeadElements = new HashSet<HeadElement>();
writeHeadBegin(writer);
// add base element no matter what
{
final HeadElement baseHeadElement = createBaseHeadElement();
writeHeadElement(writer, null, baseHeadElement, context);
allHeadElements.add(baseHeadElement);
}
for (Entry<ComponentJob, HtmlFragment> entry : htmlFragments.entrySet()) {
final HtmlFragment htmlFragment = entry.getValue();
final List<HeadElement> headElements = htmlFragment.getHeadElements();
for (HeadElement headElement : headElements) {
if (!allHeadElements.contains(headElement)) {
final ComponentJob componentJob = entry.getKey();
writeHeadElement(writer, componentJob, headElement, context);
allHeadElements.add(headElement);
}
}
}
writeHeadEnd(writer);
}
protected HeadElement createBaseHeadElement() {
return new BaseHeadElement();
}
protected void writeHeadBegin(Writer writer) throws IOException {
writer.write("<head>\n");
writer.write(" <title>Analysis result</title>");
}
protected void writeHeadEnd(Writer writer) throws IOException {
writer.write("</head>");
}
protected void writeHeadElement(Writer writer, ComponentJob componentJob, HeadElement headElement,
HtmlRenderingContext context) throws IOException {
final HtmlRenderingContext localContext = new ComponentHtmlRenderingContext(context, componentJob);
writer.write(" ");
try {
String html = headElement.toHtml(localContext);
writer.write(html);
} catch (Exception e) {
writeMaterializationError(writer, componentJob, e);
}
writer.write('\n');
}
protected void writeBody(final Writer writer, final Map<ComponentJob, HtmlFragment> htmlFragments,
final HtmlRenderingContext context) throws IOException {
final Set<Entry<ComponentJob, HtmlFragment>> htmlFragmentSet = htmlFragments.entrySet();
writeBodyBegin(writer, context);
writer.write("<div class=\"analysisResultHeader\">");
if (_tabs) {
// write a <ul> with all descriptors in it (a TOC)
{
writer.write("<ul class=\"analysisResultToc\">");
ComponentDescriptor<?> lastDescriptor = null;
for (Entry<ComponentJob, HtmlFragment> entry : htmlFragmentSet) {
final ComponentJob componentJob = entry.getKey();
final ComponentDescriptor<?> descriptor = componentJob.getDescriptor();
if (!descriptor.equals(lastDescriptor)) {
final String styleName = toStyleName(descriptor.getDisplayName());
writer.write("<li class=\"" + styleName + "\"><a href=\"#analysisResultDescriptorGroup_"
+ styleName + "\">");
writer.write(context.escapeHtml(descriptor.getDisplayName()));
writer.write("</a></li>");
lastDescriptor = descriptor;
}
}
writer.write("</ul>");
}
}
writer.write("</div>");
// write all descriptor groups
{
boolean descriptorGroupBegin = false;
ComponentDescriptor<?> lastDescriptor = null;
for (Entry<ComponentJob, HtmlFragment> entry : htmlFragmentSet) {
final ComponentJob componentJob = entry.getKey();
final ComponentDescriptor<?> descriptor = componentJob.getDescriptor();
final HtmlFragment htmlFragment = entry.getValue();
if (!descriptor.equals(lastDescriptor)) {
if (descriptorGroupBegin) {
writer.write("</div>\n");
}
final String styleName = toStyleName(descriptor.getDisplayName());
writer.write("<div id=\"analysisResultDescriptorGroup_" + styleName
+ "\" class=\"analysisResultDescriptorGroup " + toStyleName(descriptor.getDisplayName())
+ "\">");
lastDescriptor = descriptor;
descriptorGroupBegin = true;
}
writeBodyHtmlFragment(writer, componentJob, htmlFragment, context);
}
if (descriptorGroupBegin) {
writer.write("</div>\n");
}
}
writeBodyEnd(writer, context);
}
protected void writeBodyBegin(Writer writer, HtmlRenderingContext context) throws IOException {
writer.write("<body>\n");
writer.write("<div class=\"analysisResultContainer\">\n");
}
protected void writeBodyEnd(Writer writer, HtmlRenderingContext context) throws IOException {
writer.write("</div>\n");
writer.write("</body>");
}
protected void writeBodyHtmlFragment(Writer writer, ComponentJob componentJob, HtmlFragment htmlFragment,
final HtmlRenderingContext context) throws IOException {
final String displayName = componentJob.getDescriptor().getDisplayName();
final String styleName = toStyleName(displayName);
writer.write("<div class=\"analyzerResult " + styleName + "\">");
if (_headers) {
writeHeader(writer, componentJob, context, htmlFragment);
}
writer.write("<div class=\"analyzerResultContent\">\n");
final List<BodyElement> bodyElements = htmlFragment.getBodyElements();
for (BodyElement bodyElement : bodyElements) {
writeBodyElement(writer, componentJob, htmlFragment, bodyElement, context);
}
writer.write("</div>");
writer.write("<div class=\"analyzerResultFooter\"></div>");
writer.write("</div>\n");
}
protected void writeHeader(Writer writer, ComponentJob componentJob, HtmlRenderingContext context,
HtmlFragment htmlFragment) throws IOException {
final String label = LabelUtils.getLabel(componentJob);
writer.write("<div class=\"analyzerResultHeader\">");
writer.write("<h2>" + context.escapeHtml(label) + "</h2>");
writer.write("</div>");
}
protected String toStyleName(String displayName) {
final String camelCase = StringUtils.toCamelCase(displayName);
final String cleaned = camelCase.replaceAll("/", "_").replaceAll("&", "_");
return cleaned;
}
protected void writeBodyElement(Writer writer, ComponentJob componentJob, HtmlFragment htmlFragment,
BodyElement bodyElement, final HtmlRenderingContext context) throws IOException {
final HtmlRenderingContext localContext = new ComponentHtmlRenderingContext(context, componentJob);
writer.write(" ");
try {
String html = bodyElement.toHtml(localContext);
writer.write(html);
} catch (Exception e) {
writeMaterializationError(writer, componentJob, e);
}
writer.write('\n');
}
}