Java tutorial
/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved. * * This program and the accompanying materials are made available under * the terms of the Common Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/cpl-v10.html * * $Id: ReportGenerator.java,v 1.2.2.1 2004/07/16 23:32:04 vlad_r Exp $ */ package com.vladium.emma.report.html; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.text.DecimalFormat; import java.text.FieldPosition; import java.text.NumberFormat; import java.util.Date; import java.util.Iterator; import java.util.LinkedList; import com.vladium.util.Descriptors; import com.vladium.util.Files; import com.vladium.util.IProperties; import com.vladium.util.IntObjectMap; import com.vladium.util.IntVector; import com.vladium.util.ObjectIntMap; import com.vladium.util.Property; import com.vladium.util.asserts.$assert; import com.vladium.emma.IAppConstants; import com.vladium.emma.IAppErrorCodes; import com.vladium.emma.EMMAProperties; import com.vladium.emma.EMMARuntimeException; import com.vladium.emma.data.ICoverageData; import com.vladium.emma.data.IMetaData; import com.vladium.emma.report.AbstractReportGenerator; import com.vladium.emma.report.AllItem; import com.vladium.emma.report.ClassItem; import com.vladium.emma.report.IItem; import com.vladium.emma.report.IItemAttribute; import com.vladium.emma.report.IItemMetadata; import com.vladium.emma.report.ItemComparator; import com.vladium.emma.report.MethodItem; import com.vladium.emma.report.PackageItem; import com.vladium.emma.report.SourcePathCache; import com.vladium.emma.report.SrcFileItem; import com.vladium.emma.report.html.doc.*; // for JSON output import org.json.simple.JSONArray; import org.json.simple.JSONObject; // ---------------------------------------------------------------------------- /** * @author Vlad Roubtsov, (C) 2003 */ public final class ReportGenerator extends AbstractReportGenerator implements IAppErrorCodes { // public: ................................................................ // TODO: make sure relative file names are converted to relative URLs in all anchors/hrefs public ReportGenerator() { m_format = (DecimalFormat) NumberFormat.getPercentInstance(); // TODO: locale m_fieldPosition = new FieldPosition(DecimalFormat.INTEGER_FIELD); m_format.setMaximumFractionDigits(0); } // IReportGenerator: public final String getType() { return TYPE; } public void process(final IMetaData mdata, final ICoverageData cdata, final SourcePathCache cache, final IProperties properties) throws EMMARuntimeException { initialize(mdata, cdata, cache, properties); m_pageTitle = null; m_footerBottom = null; File outDir = m_settings.getOutDir(); if ((outDir == null) /* this should never happen */ || (outDir .equals(new File(Property.getSystemProperty("user.dir", ""))))) { outDir = new File("coverage"); m_settings.setOutDir(outDir); } long start = 0, end; final boolean trace1 = m_log.atTRACE1(); if (trace1) start = System.currentTimeMillis(); { m_queue = new LinkedList(); m_reportIDNamespace = new IDGenerator(mdata.size()); for (m_queue.add(m_view.getRoot()); !m_queue.isEmpty();) { final IItem head = (IItem) m_queue.removeFirst(); head.accept(this, null); } m_reportIDNamespace = null; } if (trace1) { end = System.currentTimeMillis(); m_log.trace1("process", "[" + getType() + "] report generated in " + (end - start) + " ms"); } } public void cleanup() { m_queue = null; m_reportIDNamespace = null; super.cleanup(); } // IItemVisitor: public Object visit(final AllItem item, final Object ctx) { HTMLWriter out = null; try { File outFile = m_settings.getOutFile(); if (outFile == null) { outFile = new File("index".concat(FILE_EXTENSION)); m_settings.setOutFile(outFile); } final File fullOutFile = Files.newFile(m_settings.getOutDir(), outFile); m_log.info("writing [" + getType() + "] report to [" + fullOutFile.getAbsolutePath() + "] ..."); out = openOutFile(fullOutFile, m_settings.getOutEncoding(), true); final int[] columns = m_settings.getColumnOrder(); final StringBuffer buf = new StringBuffer(); final String title; { final StringBuffer _title = new StringBuffer(REPORT_HEADER_TITLE); _title.append(" (generated "); _title.append(new Date(EMMAProperties.getTimeStamp())); _title.append(')'); title = _title.toString(); } final HTMLDocument page = createPage(title); { final IItem[] path = getParentPath(item); addPageHeader(page, item, path); addPageFooter(page, item, path); } // [all] coverage summary table: page.addH(1, "OVERALL COVERAGE SUMMARY", null); final HTMLTable summaryTable = new HTMLTable("100%", null, null, "0"); { // header row: final HTMLTable.IRow header = summaryTable.newTitleRow(); // coverage row: final HTMLTable.IRow coverage = summaryTable.newRow(); for (int c = 0; c < columns.length; ++c) { final int attrID = columns[c]; final IItemAttribute attr = item.getAttribute(attrID, m_settings.getUnitsType()); final HTMLTable.ICell headercell = header.newCell(); headercell.setText(attr.getName(), true); if (attr != null) { boolean fail = (m_metrics[attrID] > 0) && !attr.passes(item, m_metrics[attrID]); buf.setLength(0); attr.format(item, buf); final HTMLTable.ICell cell = coverage.newCell(); cell.setText(buf.toString(), true); if (fail) cell.setClass(CSS_DATA_HIGHLIGHT); } } } page.add(summaryTable); // [all] stats summary table ([all] only): page.addH(2, "OVERALL STATS SUMMARY", null); final HTMLTable statsTable = new HTMLTable(null, null, null, "0"); statsTable.setClass(CSS_INVISIBLE_TABLE); { HTMLTable.IRow row = statsTable.newRow(); row.newCell().setText("total packages:", true); row.newCell().setText("" + item.getChildCount(), false); if (m_srcView && m_hasSrcFileInfo) { row = statsTable.newRow(); row.newCell().setText("total executable files:", true); row.newCell().setText("" + item.getAggregate(IItem.TOTAL_SRCFILE_COUNT), false); } row = statsTable.newRow(); row.newCell().setText("total classes:", true); row.newCell().setText("" + item.getAggregate(IItem.TOTAL_CLASS_COUNT), true); row = statsTable.newRow(); row.newCell().setText("total methods:", true); row.newCell().setText("" + item.getAggregate(IItem.TOTAL_METHOD_COUNT), true); if (m_srcView && m_hasSrcFileInfo && m_hasLineNumberInfo) { row = statsTable.newRow(); row.newCell().setText("total executable lines:", true); row.newCell().setText("" + item.getAggregate(IItem.TOTAL_LINE_COUNT), true); } } /* { final HTMLTable.IRow first = statsTable.newRow (); // stats always available first.newCell ().setText ("total packages: " + item.getChildCount (), true); first.newCell ().setText ("total classes: " + item.getAggregate (IItem.TOTAL_CLASS_COUNT), true); first.newCell ().setText ("total methods: " + item.getAggregate (IItem.TOTAL_METHOD_COUNT), true); if (m_srcView && m_hasSrcFileInfo) { final HTMLTable.IRow second = statsTable.newRow (); final HTMLTable.ICell cell1 = second.newCell (); cell1.setText ("total source files: " + item.getAggregate (IItem.TOTAL_SRCFILE_COUNT), true); if (m_hasLineNumberInfo) { final HTMLTable.ICell cell2 = second.newCell (); cell2.setText ("total executable source lines: " + item.getAggregate (IItem.TOTAL_LINE_COUNT), true); cell2.getAttributes ().set (Attribute.COLSPAN, "2"); } else { cell1.getAttributes ().set (Attribute.COLSPAN, "3"); } } } */ page.add(statsTable); final boolean deeper = (m_settings.getDepth() > item.getMetadata().getTypeID()); // render package summary tables on the same page: page.addH(2, "COVERAGE BREAKDOWN BY PACKAGE", null); final HTMLTable childSummaryTable = new HTMLTable("100%", null, null, "0"); { int[] headerColumns = null; boolean odd = true; final ItemComparator order = m_typeSortComparators[PackageItem.getTypeMetadata().getTypeID()]; for (Iterator packages = item.getChildren(order); packages.hasNext(); odd = !odd) { final IItem pkg = (IItem) packages.next(); if (headerColumns == null) { // header row: headerColumns = addHeaderRow(pkg, childSummaryTable, columns); } // coverage row: String childHREF = null; if (deeper) { childHREF = getItemHREF(item, pkg); } addItemRow(pkg, odd, childSummaryTable, headerColumns, childHREF, false); if (deeper) m_queue.addLast(pkg); } } page.add(childSummaryTable); page.emit(out); out.flush(); } finally { if (out != null) out.close(); out = null; } return ctx; } public Object visit(final PackageItem item, final Object ctx) { HTMLWriter out = null; try { if (m_verbose) m_log.verbose(" report: processing package [" + item.getName() + "] ..."); final File outFile = getItemFile(NESTED_ITEMS_PARENT_DIR, m_reportIDNamespace.getID(getItemKey(item))); out = openOutFile(Files.newFile(m_settings.getOutDir(), outFile), m_settings.getOutEncoding(), true); final int[] columns = m_settings.getColumnOrder(); final StringBuffer buf = new StringBuffer(); // TODO: set title [from a prop?] final HTMLDocument page = createPage(REPORT_HEADER_TITLE); { final IItem[] path = getParentPath(item); addPageHeader(page, item, path); addPageFooter(page, item, path); } // summary table: { final IElement itemname = IElement.Factory.create(Tag.SPAN); itemname.setText(item.getName(), true); itemname.setClass(CSS_ITEM_NAME); final IElementList title = new ElementList(); title.add(new Text("COVERAGE SUMMARY FOR PACKAGE [", true)); title.add(itemname); title.add(new Text("]", true)); page.addH(1, title, null); } final HTMLTable summaryTable = new HTMLTable("100%", null, null, "0"); { // header row: final HTMLTable.IRow header = summaryTable.newTitleRow(); // coverage row: final HTMLTable.IRow coverage = summaryTable.newRow(); for (int c = 0; c < columns.length; ++c) { final int attrID = columns[c]; final IItemAttribute attr = item.getAttribute(attrID, m_settings.getUnitsType()); final HTMLTable.ICell headercell = header.newCell(); headercell.setText(attr.getName(), true); if (attr != null) { boolean fail = (m_metrics[attrID] > 0) && !attr.passes(item, m_metrics[attrID]); buf.setLength(0); attr.format(item, buf); final HTMLTable.ICell cell = coverage.newCell(); cell.setText(buf.toString(), true); if (fail) cell.setClass(CSS_DATA_HIGHLIGHT); } } } page.add(summaryTable); final boolean deeper = (m_settings.getDepth() > item.getMetadata().getTypeID()); // render child summary tables on the same page: final String summaryTitle = m_srcView ? "COVERAGE BREAKDOWN BY SOURCE FILE" : "COVERAGE BREAKDOWN BY CLASS"; page.addH(2, summaryTitle, null); final HTMLTable childSummaryTable = new HTMLTable("100%", null, null, "0"); { int[] headerColumns = null; boolean odd = true; final ItemComparator order = m_typeSortComparators[m_srcView ? SrcFileItem.getTypeMetadata().getTypeID() : ClassItem.getTypeMetadata().getTypeID()]; for (Iterator srcORclsFiles = item.getChildren(order); srcORclsFiles.hasNext(); odd = !odd) { final IItem srcORcls = (IItem) srcORclsFiles.next(); if (headerColumns == null) { // header row: headerColumns = addHeaderRow(srcORcls, childSummaryTable, columns); } // coverage row: String childHREF = null; if (deeper) { childHREF = getItemHREF(item, srcORcls); } addItemRow(srcORcls, odd, childSummaryTable, headerColumns, childHREF, false); if (deeper) m_queue.addLast(srcORcls); } } page.add(childSummaryTable); page.emit(out); out.flush(); } finally { if (out != null) out.close(); out = null; } return ctx; } public Object visit(final SrcFileItem item, final Object ctx) { // this visit only takes place in src views HTMLWriter out = null; try { final File outFile = getItemFile(NESTED_ITEMS_PARENT_DIR, m_reportIDNamespace.getID(getItemKey(item))); out = openOutFile(Files.newFile(m_settings.getOutDir(), outFile), m_settings.getOutEncoding(), true); final int[] columns = m_settings.getColumnOrder(); final StringBuffer buf = new StringBuffer(); // TODO: set title [from a prop?] final HTMLDocument page = createPage(REPORT_HEADER_TITLE); { final IItem[] path = getParentPath(item); addPageHeader(page, item, path); addPageFooter(page, item, path); } // summary table: { final IElement itemname = IElement.Factory.create(Tag.SPAN); itemname.setText(item.getName(), true); itemname.setClass(CSS_ITEM_NAME); final IElementList title = new ElementList(); title.add(new Text("COVERAGE SUMMARY FOR SOURCE FILE [", true)); title.add(itemname); title.add(new Text("]", true)); page.addH(1, title, null); } final HTMLTable summaryTable = new HTMLTable("100%", null, null, "0"); { // header row: final HTMLTable.IRow header = summaryTable.newTitleRow(); // coverage row: final HTMLTable.IRow coverage = summaryTable.newRow(); for (int c = 0; c < columns.length; ++c) { final int attrID = columns[c]; final IItemAttribute attr = item.getAttribute(attrID, m_settings.getUnitsType()); final HTMLTable.ICell headercell = header.newCell(); headercell.setText(attr.getName(), true); if (attr != null) { boolean fail = (m_metrics[attrID] > 0) && !attr.passes(item, m_metrics[attrID]); buf.setLength(0); attr.format(item, buf); final HTMLTable.ICell cell = coverage.newCell(); cell.setText(buf.toString(), true); if (fail) cell.setClass(CSS_DATA_HIGHLIGHT); } } } page.add(summaryTable); final boolean deeper = (m_settings.getDepth() > ClassItem.getTypeMetadata().getTypeID()); final boolean embedSrcFile = deeper && srcFileAvailable(item, m_cache); final boolean createAnchors = embedSrcFile && m_hasLineNumberInfo; final IDGenerator pageIDNamespace = createAnchors ? new IDGenerator() : null; // child summary table is special for srcfile items: page.addH(2, "COVERAGE BREAKDOWN BY CLASS AND METHOD", null); final IntObjectMap lineAnchorIDMap = embedSrcFile ? new IntObjectMap() : null; final HTMLTable childSummaryTable = new HTMLTable("100%", null, null, "0"); childSummaryTable.setClass(CSS_CLS_NOLEFT); { int[] headerColumns = null; final ItemComparator order = m_typeSortComparators[ClassItem.getTypeMetadata().getTypeID()]; int clsIndex = 0; for (Iterator classes = item.getChildren(order); classes.hasNext(); ++clsIndex) { final ClassItem cls = (ClassItem) classes.next(); if (headerColumns == null) { // header row: headerColumns = addHeaderRow(cls, childSummaryTable, columns); } String HREFname = null; // special class subheader: if (createAnchors) { if ($assert.ENABLED) { $assert.ASSERT(lineAnchorIDMap != null); $assert.ASSERT(pageIDNamespace != null); } final String childKey = getItemKey(cls); HREFname = addLineAnchorID(cls.getFirstLine(), pageIDNamespace.getID(childKey), lineAnchorIDMap); } addClassRow(cls, clsIndex, childSummaryTable, headerColumns, HREFname, createAnchors); // // row to separate this class's methods: // final HTMLTable.IRow subheader = childSummaryTable.newTitleRow (); // final HTMLTable.ICell cell = subheader.newCell (); // // TODO: cell.setColspan (???) // cell.setText ("class " + child.getName () + " methods:", true); boolean odd = false; final ItemComparator order2 = m_typeSortComparators[MethodItem.getTypeMetadata().getTypeID()]; for (Iterator methods = cls.getChildren(order2); methods.hasNext(); odd = !odd) { final MethodItem method = (MethodItem) methods.next(); HREFname = null; if (createAnchors) { if ($assert.ENABLED) { $assert.ASSERT(lineAnchorIDMap != null); $assert.ASSERT(pageIDNamespace != null); } final String child2Key = getItemKey(method); HREFname = addLineAnchorID(method.getFirstLine(), pageIDNamespace.getID(child2Key), lineAnchorIDMap); } addClassItemRow(method, odd, childSummaryTable, headerColumns, HREFname, createAnchors); } } } page.add(childSummaryTable); // embed source file: if (deeper) { //page.addHR (1); page.addEmptyP(); { embedSrcFileJson(item, m_cache); embedSrcFile(item, page, lineAnchorIDMap, m_cache); } //page.addHR (1); } page.emit(out); out.flush(); } finally { if (out != null) out.close(); out = null; } return ctx; } public Object visit(final ClassItem item, final Object ctx) { // this visit only takes place in class views HTMLWriter out = null; try { final File outFile = getItemFile(NESTED_ITEMS_PARENT_DIR, m_reportIDNamespace.getID(getItemKey(item))); // TODO: deal with overwrites out = openOutFile(Files.newFile(m_settings.getOutDir(), outFile), m_settings.getOutEncoding(), true); final int[] columns = m_settings.getColumnOrder(); final StringBuffer buf = new StringBuffer(); // TODO: set title [from a prop?] final HTMLDocument page = createPage(REPORT_HEADER_TITLE); { final IItem[] path = getParentPath(item); addPageHeader(page, item, path); addPageFooter(page, item, path); } // summary table: { final IElement itemname = IElement.Factory.create(Tag.SPAN); itemname.setText(item.getName(), true); itemname.setClass(CSS_ITEM_NAME); final IElementList title = new ElementList(); title.add(new Text("COVERAGE SUMMARY FOR CLASS [", true)); title.add(itemname); title.add(new Text("]", true)); page.addH(1, title, null); } final HTMLTable summaryTable = new HTMLTable("100%", null, null, "0"); { // header row: final HTMLTable.IRow header = summaryTable.newTitleRow(); // coverage row: final HTMLTable.IRow coverage = summaryTable.newRow(); for (int c = 0; c < columns.length; ++c) { final int attrID = columns[c]; final IItemAttribute attr = item.getAttribute(attrID, m_settings.getUnitsType()); final HTMLTable.ICell headercell = header.newCell(); headercell.setText(attr.getName(), true); if (attr != null) { boolean fail = (m_metrics[attrID] > 0) && !attr.passes(item, m_metrics[attrID]); buf.setLength(0); attr.format(item, buf); final HTMLTable.ICell cell = coverage.newCell(); cell.setText(buf.toString(), true); if (fail) cell.setClass(CSS_DATA_HIGHLIGHT); } } } page.add(summaryTable); // child summary table: page.addH(2, "COVERAGE BREAKDOWN BY METHOD", null); final HTMLTable childSummaryTable = new HTMLTable("100%", null, null, "0"); { int[] headerColumns = null; boolean odd = true; final ItemComparator order = m_typeSortComparators[MethodItem.getTypeMetadata().getTypeID()]; for (Iterator methods = item.getChildren(order); methods.hasNext(); odd = !odd) { final MethodItem method = (MethodItem) methods.next(); if (headerColumns == null) { // header row: headerColumns = addHeaderRow(method, childSummaryTable, columns); } addItemRow(method, odd, childSummaryTable, headerColumns, null, false); } } page.add(childSummaryTable); page.emit(out); out.flush(); } finally { if (out != null) out.close(); out = null; } return ctx; } // protected: ............................................................. // package: ............................................................... // private: ............................................................... private static final class IDGenerator { IDGenerator() { m_namespace = new ObjectIntMap(101); m_out = new int[1]; } IDGenerator(final int initialCapacity) { m_namespace = new ObjectIntMap(initialCapacity); m_out = new int[1]; } String getID(final String key) { final int[] out = m_out; final int ID; if (m_namespace.get(key, out)) ID = out[0]; else { ID = m_namespace.size(); m_namespace.put(key, ID); } return Integer.toHexString(ID); } private final ObjectIntMap /* key:String->ID */ m_namespace; private final int[] m_out; } // end of nested class private HTMLDocument createPage(final String title) { final HTMLDocument page = new HTMLDocument(title, m_settings.getOutEncoding()); page.addStyle(CSS); // TODO: split by visit type return page; } private IElement addPageHeader(final HTMLDocument page, final IItem item, final IItem[] path) { // TODO: merge header and footer in the same method if ($assert.ENABLED) { $assert.ASSERT(page != null); } final HTMLTable header = new HTMLTable("100%", null, null, "0"); header.setClass(CSS_HEADER_FOOTER); // header row: addPageHeaderTitleRow(header); // nav row: { final HTMLTable.IRow navRow = header.newRow(); final HTMLTable.ICell cell = navRow.newCell(); cell.setClass(CSS_NAV); final int lLimit = path.length > 1 ? path.length - 1 : path.length; for (int l = 0; l < lLimit; ++l) { cell.add(LEFT_BRACKET); final String name = path[l].getName(); final String HREF = getItemHREF(item, path[l]); cell.add(new HyperRef(HREF, name, true)); cell.add(RIGHT_BRACKET); } } page.setHeader(header); return header; } private IElement addPageFooter(final HTMLDocument page, final IItem item, final IItem[] path) { if ($assert.ENABLED) { $assert.ASSERT(page != null); } final HTMLTable footerTable = new HTMLTable("100%", null, null, "0"); footerTable.setClass(CSS_HEADER_FOOTER); // nav row: { final HTMLTable.IRow navRow = footerTable.newRow(); final HTMLTable.ICell cell = navRow.newCell(); cell.setClass(CSS_NAV); final int lLimit = path.length > 1 ? path.length - 1 : path.length; for (int l = 0; l < lLimit; ++l) { cell.add(LEFT_BRACKET); final String name = path[l].getName(); final String HREF = getItemHREF(item, path[l]); cell.add(new HyperRef(HREF, name, true)); cell.add(RIGHT_BRACKET); } } // title row: { final HTMLTable.IRow titleRow = footerTable.newRow(); final HTMLTable.ICell cell = titleRow.newCell(); cell.setClass(CSS_TITLE); cell.add(getFooterBottom()); } final ElementList footer = new ElementList(); footer.add(IElement.Factory.create(Tag.P)); // spacer footer.add(footerTable); page.setFooter(footer); return footerTable; } private int[] addHeaderRow(final IItem item, final HTMLTable table, final int[] columns) { if ($assert.ENABLED) { $assert.ASSERT(item != null, "null input: item"); $assert.ASSERT(table != null, "null input: table"); $assert.ASSERT(columns != null, "null input: columns"); } // header row: final HTMLTable.IRow header = table.newTitleRow(); // determine the set of columns actually present in the header [may be narrower than 'columns']: final IntVector headerColumns = new IntVector(columns.length); for (int c = 0; c < columns.length; ++c) { final int attrID = columns[c]; final IItemAttribute attr = item.getAttribute(attrID, m_settings.getUnitsType()); if (attr != null) { final HTMLTable.ICell cell = header.newCell(); cell.setText(attr.getName(), true);//.getAttributes ().set (Attribute.WIDTH, "20%"); cell.setClass(headerCellStyle(c)); headerColumns.add(attrID); } // note: by design this does not create columns for nonexistent attribute types } return headerColumns.values(); } /* * No header row, just data rows. */ private void addItemRow(final IItem item, final boolean odd, final HTMLTable table, final int[] columns, final String nameHREF, final boolean anchor) { if ($assert.ENABLED) { $assert.ASSERT(item != null, "null input: item"); $assert.ASSERT(table != null, "null input: table"); $assert.ASSERT(columns != null, "null input: columns"); } final HTMLTable.IRow row = table.newRow(); if (odd) row.setClass(CSS_ODDROW); final StringBuffer buf = new StringBuffer(11); // TODO: reuse a buffer for (int c = 0; c < columns.length; ++c) { final int attrID = columns[c]; final IItemAttribute attr = item.getAttribute(attrID, m_settings.getUnitsType()); if (attr != null) { final HTMLTable.ICell cell = row.newCell(); if ((nameHREF != null) && (attrID == IItemAttribute.ATTRIBUTE_NAME_ID)) { buf.setLength(0); attr.format(item, buf); trimForDisplay(buf); final String fullHREFName = anchor ? "#".concat(nameHREF) : nameHREF; cell.add(new HyperRef(fullHREFName, buf.toString(), true)); } else { final boolean fail = (m_metrics[attrID] > 0) && !attr.passes(item, m_metrics[attrID]); buf.setLength(0); attr.format(item, buf); trimForDisplay(buf); cell.setText(buf.toString(), true); if (fail) cell.setClass(CSS_DATA_HIGHLIGHT); } } else { // note: by design this puts empty cells for nonexistent attribute types final HTMLTable.ICell cell = row.newCell(); cell.setText(" ", true); } } } private void addClassRow(final ClassItem item, final int clsIndex, final HTMLTable table, final int[] columns, final String itemHREF, final boolean isAnchor) { if ($assert.ENABLED) { $assert.ASSERT(item != null, "null input: item"); $assert.ASSERT(table != null, "null input: table"); $assert.ASSERT(columns != null, "null input: columns"); } final HTMLTable.IRow blank = table.newRow(); final HTMLTable.IRow row = table.newRow(); row.setClass(CSS_CLASS_ITEM_SPECIAL); final StringBuffer buf = new StringBuffer(11); for (int c = 0; c < columns.length; ++c) { final int attrID = columns[c]; final IItemAttribute attr = item.getAttribute(attrID, m_settings.getUnitsType()); if (attr != null) { buf.setLength(0); attr.format(item, buf); final HTMLTable.ICell blankcell = blank.newCell(); blankcell.setClass(clsIndex == 0 ? CSS_BLANK : CSS_BOTTOM); blankcell.setText(" ", true); final HTMLTable.ICell cell = row.newCell(); boolean fail = false; if (attrID == IItemAttribute.ATTRIBUTE_NAME_ID) { if (itemHREF != null) { final String fullItemHREF = isAnchor ? "#".concat(itemHREF) : itemHREF; cell.add(new Text("class ", true)); cell.add(new HyperRef(fullItemHREF, buf.toString(), true)); } else { cell.setText("class " + buf.toString(), true); } } else { fail = (m_metrics[attrID] > 0) && !attr.passes(item, m_metrics[attrID]); cell.setText(buf.toString(), true); } cell.setClass(dataCellStyle(c, fail)); } else { final HTMLTable.ICell cell = row.newCell(); cell.setText(" ", true); cell.setClass(dataCellStyle(c, false)); } } } private void addClassItemRow(final IItem item, final boolean odd, final HTMLTable table, final int[] columns, final String nameHREF, final boolean anchor) { if ($assert.ENABLED) { $assert.ASSERT(item != null, "null input: item"); $assert.ASSERT(table != null, "null input: table"); $assert.ASSERT(columns != null, "null input: columns"); } final HTMLTable.IRow row = table.newRow(); if (odd) row.setClass(CSS_ODDROW); final StringBuffer buf = new StringBuffer(11); // TODO: reuse a buffer for (int c = 0; c < columns.length; ++c) { final int attrID = columns[c]; final IItemAttribute attr = item.getAttribute(attrID, m_settings.getUnitsType()); if (attr != null) { final HTMLTable.ICell cell = row.newCell(); boolean fail = false; if ((nameHREF != null) && (attrID == IItemAttribute.ATTRIBUTE_NAME_ID)) { buf.setLength(0); attr.format(item, buf); trimForDisplay(buf); final String fullHREFName = anchor ? "#".concat(nameHREF) : nameHREF; cell.add(new HyperRef(fullHREFName, buf.toString(), true)); } else { fail = (m_metrics[attrID] > 0) && !attr.passes(item, m_metrics[attrID]); buf.setLength(0); attr.format(item, buf); trimForDisplay(buf); cell.setText(buf.toString(), true); } cell.setClass(dataCellStyle(c, fail)); } else { // note: by design this puts empty cells for nonexistent attribute types final HTMLTable.ICell cell = row.newCell(); cell.setText(" ", true); cell.setClass(dataCellStyle(c, false)); } } } private boolean srcFileAvailable(final SrcFileItem item, final SourcePathCache cache) { if (cache == null) return false; if ($assert.ENABLED) $assert.ASSERT(item != null, "null input: item"); final String fileName = item.getName(); if ($assert.ENABLED) $assert.ASSERT(fileName.endsWith(".java"), "cache only handles .java extensions"); // TODO: should I keep VM names in package items? final String packageVMName = ((PackageItem) item.getParent()).getVMName(); return (cache.find(packageVMName, fileName) != null); } // private boolean srcFileAvailable (final ClassItem item, final SourcePathCache cache) // { // if (cache == null) return false; // // if ($assert.ENABLED) $assert.ASSERT (item != null, "null input: item"); // // final String fileName = item.getSrcFileName (); // if ($assert.ENABLED) $assert.ASSERT (fileName.endsWith (".java"), "cache only handles .java extensions"); // // // TODO: should I keep VM names in package items? // final String packageVMName = ((PackageItem) item.getParent ()).getVMName (); // // return (cache.find (packageVMName, fileName) != null); // } private void embedSrcFile(final SrcFileItem item, final HTMLDocument page, final IntObjectMap /* line num:int->anchor name:String */ anchorMap, final SourcePathCache cache) { if ($assert.ENABLED) { $assert.ASSERT(item != null, "null input: item"); $assert.ASSERT(page != null, "null input: page"); } final String fileName = item.getName(); if ($assert.ENABLED) $assert.ASSERT(fileName.endsWith(".java"), "cache only handles .java extensions"); // TODO: should I keep VM names in package items? final String packageVMName = ((PackageItem) item.getParent()).getVMName(); boolean success = false; final HTMLTable srcTable = new HTMLTable("100%", null, null, "0"); if (cache != null) // TODO: do this check earlier, in outer scope { srcTable.setClass(CSS_SOURCE); final File srcFile = cache.find(packageVMName, fileName); if (srcFile != null) { BufferedReader in = null; try { in = new BufferedReader(new FileReader(srcFile), IO_BUF_SIZE); final boolean markupCoverage = m_hasLineNumberInfo; final int unitsType = m_settings.getUnitsType(); IntObjectMap /* line num:int -> SrcFileItem.LineCoverageData */ lineCoverageMap = null; StringBuffer tooltipBuffer = null; if (markupCoverage) { lineCoverageMap = item.getLineCoverage(); $assert.ASSERT(lineCoverageMap != null, "null: lineCoverageMap"); tooltipBuffer = new StringBuffer(64); } int l = 1; for (String line; (line = in.readLine()) != null; ++l) { final HTMLTable.IRow srcline = srcTable.newRow(); final HTMLTable.ICell lineNumCell = srcline.newCell(); lineNumCell.setClass(CSS_LINENUM); if (anchorMap != null) { final int adjustedl = l < SRC_LINE_OFFSET ? l : l + SRC_LINE_OFFSET; final String anchor = (String) anchorMap.get(adjustedl); if (anchor != null) { final IElement a = IElement.Factory.create(Tag.A); //a.getAttributes ().set (Attribute.ID, anchor); ID anchoring does not work in NS 4.0 a.getAttributes().set(Attribute.NAME, anchor); a.setText(Integer.toString(l), true); lineNumCell.add(a); } else { lineNumCell.setText(Integer.toString(l), true); } } else { lineNumCell.setText(Integer.toString(l), true); } final HTMLTable.ICell lineTxtCell = srcline.newCell(); lineTxtCell.setText(line.length() > 0 ? line : " ", true); if (markupCoverage) { final SrcFileItem.LineCoverageData lCoverageData = (SrcFileItem.LineCoverageData) lineCoverageMap .get(l); if (lCoverageData != null) { switch (lCoverageData.m_coverageStatus) { case SrcFileItem.LineCoverageData.LINE_COVERAGE_ZERO: srcline.setClass(CSS_COVERAGE_ZERO); break; case SrcFileItem.LineCoverageData.LINE_COVERAGE_PARTIAL: { srcline.setClass(CSS_COVERAGE_PARTIAL); if (USE_LINE_COVERAGE_TOOLTIPS) { tooltipBuffer.setLength(0); final int[] coverageRatio = lCoverageData.m_coverageRatio[unitsType]; final int d = coverageRatio[0]; final int n = coverageRatio[1]; m_format.format((double) n / d, tooltipBuffer, m_fieldPosition); tooltipBuffer.append(" line coverage ("); tooltipBuffer.append(n); tooltipBuffer.append(" out of "); tooltipBuffer.append(d); switch (unitsType) { case IItemAttribute.UNITS_COUNT: tooltipBuffer.append(" basic blocks)"); break; case IItemAttribute.UNITS_INSTR: tooltipBuffer.append(" instructions)"); break; } // [Opera does not display TITLE tooltios on <TR> elements] lineNumCell.getAttributes().set(Attribute.TITLE, tooltipBuffer.toString()); lineTxtCell.getAttributes().set(Attribute.TITLE, tooltipBuffer.toString()); } } break; case SrcFileItem.LineCoverageData.LINE_COVERAGE_COMPLETE: srcline.setClass(CSS_COVERAGE_COMPLETE); break; default: $assert.ASSERT(false, "invalid line coverage status: " + lCoverageData.m_coverageStatus); } // end of switch } } } success = true; } catch (Throwable t) { t.printStackTrace(System.out); // TODO: logging success = false; } finally { if (in != null) try { in.close(); } catch (Throwable ignore) { } in = null; } } } if (!success) { srcTable.setClass(CSS_INVISIBLE_TABLE); final HTMLTable.IRow row = srcTable.newTitleRow(); row.newCell().setText("[source file '" + Descriptors.combineVMName(packageVMName, fileName) + "' not found in sourcepath]", false); } page.add(srcTable); } private static String addLineAnchorID(final int line, final String anchorID, final IntObjectMap /* line num:int->anchorID:String */ lineAnchorIDMap) { if (line > 0) { final String _anchorID = (String) lineAnchorIDMap.get(line); if (_anchorID != null) return _anchorID; else { lineAnchorIDMap.put(line, anchorID); return anchorID; } } return null; } /* * Always includes AllItem */ private IItem[] getParentPath(IItem item) { final LinkedList /* IItem */ _result = new LinkedList(); for (; item != null; item = item.getParent()) { _result.add(item); } final IItem[] result = new IItem[_result.size()]; int j = result.length - 1; for (Iterator i = _result.iterator(); i.hasNext(); --j) { result[j] = (IItem) i.next(); } return result; } /* * */ private String getItemHREF(final IItem base, final IItem item) { final String itemHREF; if (item instanceof AllItem) itemHREF = m_settings.getOutFile().getName(); // note that this is always a simple filename [no parent path] else itemHREF = m_reportIDNamespace.getID(getItemKey(item)).concat(FILE_EXTENSION); final String fullHREF; if (base == null) fullHREF = itemHREF; else { final int nesting = NESTING[base.getMetadata().getTypeID()][item.getMetadata().getTypeID()]; if (nesting == 1) fullHREF = NESTED_ITEMS_PARENT_DIRNAME.concat("/").concat(itemHREF); else if (nesting == -1) fullHREF = "../".concat(itemHREF); else fullHREF = itemHREF; } return fullHREF; } private IContent getPageTitle() { IContent title = m_pageTitle; if (title == null) { final IElementList _title = new ElementList(); _title.add(new HyperRef(IAppConstants.APP_HOME_SITE_LINK, IAppConstants.APP_NAME, true)); final StringBuffer s = new StringBuffer(" Coverage Report (generated "); s.append(new Date(EMMAProperties.getTimeStamp())); s.append(')'); _title.add(new Text(s.toString(), true)); m_pageTitle = title = _title; } return title; } private IContent getFooterBottom() { IContent bottom = m_footerBottom; if (bottom == null) { final IElementList _bottom = new ElementList(); _bottom.add(new HyperRef(IAppConstants.APP_BUG_REPORT_LINK, IAppConstants.APP_NAME + " " + IAppConstants.APP_VERSION_WITH_BUILD_ID_AND_TAG, true)); _bottom.add(new Text(" " + IAppConstants.APP_COPYRIGHT, true)); m_footerBottom = bottom = _bottom; } return bottom; } private void addPageHeaderTitleRow(final HTMLTable header) { final HTMLTable.IRow titleRow = header.newTitleRow(); final HTMLTable.ICell cell = titleRow.newCell(); cell.setClass(CSS_TITLE); cell.add(getPageTitle()); } private static void trimForDisplay(final StringBuffer buf) { if (buf.length() > MAX_DISPLAY_NAME_LENGTH) { buf.setLength(MAX_DISPLAY_NAME_LENGTH - 3); buf.append("..."); } } /* * Assumes relative pathnames. */ private static File getItemFile(final File parentDir, final String itemKey) { if (parentDir == null) return new File(itemKey.concat(FILE_EXTENSION)); else return new File(parentDir, itemKey.concat(FILE_EXTENSION)); } private static String getItemKey(IItem item) { final StringBuffer result = new StringBuffer(); for (; item != null; item = item.getParent()) { result.append(item.getName()); result.append(':'); } return result.toString(); } private static HTMLWriter openOutFile(final File file, final String encoding, final boolean mkdirs) { BufferedWriter out = null; try { if (mkdirs) { final File parent = file.getParentFile(); if (parent != null) parent.mkdirs(); } out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), encoding), IO_BUF_SIZE); } catch (UnsupportedEncodingException uee) { // TODO: error code throw new EMMARuntimeException(uee); } // note: in J2SDK 1.3 FileOutputStream constructor's throws clause // was narrowed to FileNotFoundException: catch (IOException fnfe) // FileNotFoundException { // TODO: error code throw new EMMARuntimeException(fnfe); } return new HTMLWriter(out); } private static String dataCellStyle(final int column, final boolean highlight) { if (column == 0) return highlight ? CSS_DATA_HIGHLIGHT_FIRST : CSS_DATA_FIRST; else return highlight ? CSS_DATA_HIGHLIGHT : CSS_DATA; } private static String headerCellStyle(final int column) { return (column == 0) ? CSS_HEADER_FIRST : CSS_HEADER; } private final DecimalFormat m_format; private final FieldPosition m_fieldPosition; private LinkedList /* IITem */ m_queue; private IDGenerator m_reportIDNamespace; private IContent m_pageTitle, m_footerBottom; private static final boolean USE_LINE_COVERAGE_TOOLTIPS = true; private static final String TYPE = "html"; private static final String REPORT_HEADER_TITLE = IAppConstants.APP_NAME + " Coverage Report"; private static final IContent LEFT_BRACKET = new Text("[", false); private static final IContent RIGHT_BRACKET = new Text("]", false); private static final int MAX_DISPLAY_NAME_LENGTH = 80; private static final int SRC_LINE_OFFSET = 4; private static final String CSS_HEADER_FOOTER = "hdft"; private static final String CSS_TITLE = "tl"; private static final String CSS_NAV = "nv"; private static final String CSS_COVERAGE_ZERO = "z"; private static final String CSS_COVERAGE_PARTIAL = "p"; private static final String CSS_COVERAGE_COMPLETE = "c"; private static final String DARKER_BACKGROUND = "#F0F0F0"; private static final String TITLE_BACKGROUND = "#6699CC"; private static final String NAV_BACKGROUND = "#6633DD"; private static final String CSS_INVISIBLE_TABLE = "it"; private static final String CSS_ITEM_NAME = "in"; private static final String CSS_CLASS_ITEM_SPECIAL = "cis"; private static final String CSS_SOURCE = "s"; private static final String CSS_LINENUM = "l"; private static final String CSS_BOTTOM = "bt"; private static final String CSS_ODDROW = "o"; private static final String CSS_BLANK = "b"; private static final String CSS_DATA = ""; private static final String CSS_DATA_HIGHLIGHT = CSS_DATA + "h"; private static final String CSS_DATA_FIRST = CSS_DATA + "f"; private static final String CSS_DATA_HIGHLIGHT_FIRST = CSS_DATA + "hf"; private static final String CSS_HEADER = ""; private static final String CSS_HEADER_FIRST = CSS_HEADER + "f"; private static final String CSS_CLS_NOLEFT = "cn"; // TODO: optimize this private static final String CSS = " TABLE,TD,TH {border-style:solid; border-color:black;}" + " TD,TH {background:white;margin:0;line-height:100%;padding-left:0.5em;padding-right:0.5em;}" + " TD {border-width:0 1px 0 0;}" + " TH {border-width:1px 1px 1px 0;}" + " TR TD." + CSS_DATA_HIGHLIGHT + " {color:red;}" + " TABLE {border-spacing:0; border-collapse:collapse;border-width:0 0 1px 1px;}" + " P,H1,H2,H3,TH {font-family:verdana,arial,sans-serif;font-size:10pt;}" + " TD {font-family:courier,monospace;font-size:10pt;}" + " TABLE." + CSS_HEADER_FOOTER + " {border-spacing:0;border-collapse:collapse;border-style:none;}" + " TABLE." + CSS_HEADER_FOOTER + " TH,TABLE." + CSS_HEADER_FOOTER + " TD {border-style:none;line-height:normal;}" + " TABLE." + CSS_HEADER_FOOTER + " TH." + CSS_TITLE + ",TABLE." + CSS_HEADER_FOOTER + " TD." + CSS_TITLE + " {background:" + TITLE_BACKGROUND + ";color:white;}" + " TABLE." + CSS_HEADER_FOOTER + " TD." + CSS_NAV + " {background:" + NAV_BACKGROUND + ";color:white;}" + " ." + CSS_NAV + " A:link {color:white;}" + " ." + CSS_NAV + " A:visited {color:white;}" + " ." + CSS_NAV + " A:active {color:yellow;}" + " TABLE." + CSS_HEADER_FOOTER + " A:link {color:white;}" + " TABLE." + CSS_HEADER_FOOTER + " A:visited {color:white;}" + " TABLE." + CSS_HEADER_FOOTER + " A:active {color:yellow;}" + //" ." + CSS_ITEM_NAME + " {color:#6633FF;}" + //" ." + CSS_ITEM_NAME + " {color:#C000E0;}" + " ." + CSS_ITEM_NAME + " {color:#356085;}" + //" A:hover {color:#0066FF; text-decoration:underline; font-style:italic}" + " TABLE." + CSS_SOURCE + " TD {padding-left:0.25em;padding-right:0.25em;}" + " TABLE." + CSS_SOURCE + " TD." + CSS_LINENUM + " {padding-left:0.25em;padding-right:0.25em;text-align:right;background:" + DARKER_BACKGROUND + ";}" + " TABLE." + CSS_SOURCE + " TR." + CSS_COVERAGE_ZERO + " TD {background:#FF9999;}" + " TABLE." + CSS_SOURCE + " TR." + CSS_COVERAGE_PARTIAL + " TD {background:#FFFF88;}" + " TABLE." + CSS_SOURCE + " TR." + CSS_COVERAGE_COMPLETE + " TD {background:#CCFFCC;}" + " A:link {color:#0000EE;text-decoration:none;}" + " A:visited {color:#0000EE;text-decoration:none;}" + " A:hover {color:#0000EE;text-decoration:underline;}" + " TABLE." + CSS_CLS_NOLEFT + " {border-width:0 0 1px 0;}" + " TABLE." + CSS_SOURCE + " {border-width:1px 0 1px 1px;}" + // " TD {border-width: 0px 1px 0px 0px; }" + " TD." + CSS_DATA_HIGHLIGHT + " {color:red;border-width:0 1px 0 0;}" + " TD." + CSS_DATA_FIRST + " {border-width:0 1px 0 1px;}" + " TD." + CSS_DATA_HIGHLIGHT_FIRST + " {color:red;border-width:0 1px 0 1px;}" + // " TH {border-width: 1px 1px 1px 0px; }" + " TH." + CSS_HEADER_FIRST + " {border-width:1px 1px 1px 1px;}" + " TR." + CSS_CLASS_ITEM_SPECIAL + " TD {background:" + DARKER_BACKGROUND + ";}" + " TR." + CSS_CLASS_ITEM_SPECIAL + " TD {border-width:1px 1px 1px 0;}" + " TR." + CSS_CLASS_ITEM_SPECIAL + " TD." + CSS_DATA_HIGHLIGHT + " {color:red;border-width:1px 1px 1px 0;}" + " TR." + CSS_CLASS_ITEM_SPECIAL + " TD." + CSS_DATA_FIRST + " {border-width:1px 1px 1px 1px;}" + " TR." + CSS_CLASS_ITEM_SPECIAL + " TD." + CSS_DATA_HIGHLIGHT_FIRST + " {color:red;border-width:1px 1px 1px 1px;}" + " TD." + CSS_BLANK + " {border-style:none;background:transparent;line-height:50%;} " + " TD." + CSS_BOTTOM + " {border-width:1px 0 0 0;background:transparent;line-height:50%;}" + " TR." + CSS_ODDROW + " TD {background:" + DARKER_BACKGROUND + ";}" + "TABLE." + CSS_INVISIBLE_TABLE + " {border-style:none;}" + "TABLE." + CSS_INVISIBLE_TABLE + " TD,TABLE." + CSS_INVISIBLE_TABLE + " TH {border-style:none;}" + ""; private static final String NESTED_ITEMS_PARENT_DIRNAME = "_files"; private static final File NESTED_ITEMS_PARENT_DIR = new File(NESTED_ITEMS_PARENT_DIRNAME); private static final int[][] NESTING; // set in <clinit>; this reflects the dir structure for the report private static final String FILE_EXTENSION = ".html"; private static final int IO_BUF_SIZE = 32 * 1024; private static final long[] ATTRIBUTE_SETS; // set in <clinit> static { final IItemMetadata[] allTypes = IItemMetadata.Factory.getAllTypes(); ATTRIBUTE_SETS = new long[allTypes.length]; for (int t = 0; t < allTypes.length; ++t) { ATTRIBUTE_SETS[allTypes[t].getTypeID()] = allTypes[t].getAttributeIDs(); } NESTING = new int[4][4]; int base = AllItem.getTypeMetadata().getTypeID(); NESTING[base][PackageItem.getTypeMetadata().getTypeID()] = 1; NESTING[base][SrcFileItem.getTypeMetadata().getTypeID()] = 1; NESTING[base][ClassItem.getTypeMetadata().getTypeID()] = 1; base = PackageItem.getTypeMetadata().getTypeID(); NESTING[base][AllItem.getTypeMetadata().getTypeID()] = -1; base = SrcFileItem.getTypeMetadata().getTypeID(); NESTING[base][AllItem.getTypeMetadata().getTypeID()] = -1; base = ClassItem.getTypeMetadata().getTypeID(); NESTING[base][AllItem.getTypeMetadata().getTypeID()] = -1; } private void embedSrcFileJson(final SrcFileItem item, final SourcePathCache cache) { if (cache == null) { return; } final String fileName = item.getName(); final String packageVMName = ((PackageItem) item.getParent()).getVMName(); final File srcFile = cache.find(packageVMName, fileName); if (srcFile == null) { return; } File outDirFull = new File(m_settings.getOutDir(), packageVMName); if (!outDirFull.exists()) { outDirFull.mkdirs(); } File outDir = null; if (packageVMName.isEmpty()) { outDir = new File("."); } else { outDir = new File(packageVMName); } File outFile = new File(outDir, fileName.concat(".json")); final File fullOutFile = Files.newFile(m_settings.getOutDir(), outFile); JSONObject json = new JSONObject(); json.put("file", fileName); json.put("package", packageVMName); JSONArray jsonList = new JSONArray(); BufferedReader in = null; try { in = new BufferedReader(new FileReader(srcFile), IO_BUF_SIZE); IntObjectMap lineCoverageMap = item.getLineCoverage(); int l = 1; final int unitsType = m_settings.getUnitsType(); for (String line; (line = in.readLine()) != null; ++l) { final SrcFileItem.LineCoverageData lCoverageData = (SrcFileItem.LineCoverageData) lineCoverageMap .get(l); String covered = ""; if (lCoverageData != null) { switch (lCoverageData.m_coverageStatus) { case SrcFileItem.LineCoverageData.LINE_COVERAGE_ZERO: // 0 coverage covered = "0"; break; case SrcFileItem.LineCoverageData.LINE_COVERAGE_PARTIAL: final int[] coverageRatio = lCoverageData.m_coverageRatio[unitsType]; final int d = coverageRatio[0]; final int n = coverageRatio[1]; covered = d + "/" + n; // d out of n covered break; case SrcFileItem.LineCoverageData.LINE_COVERAGE_COMPLETE: // total coverage covered = "1"; break; } } else { covered = "-1"; } JSONObject obj = new JSONObject(); obj.put("source", line); obj.put("covered", covered); jsonList.add(obj); } json.put("lines", jsonList); } catch (Throwable t) { t.printStackTrace(System.out); // TODO: logging } finally { if (in != null) try { in.close(); } catch (Throwable ignore) { } in = null; } try { FileWriter out = new FileWriter(fullOutFile); out.write(json.toJSONString()); out.flush(); out.close(); } catch (IOException e) { e.printStackTrace(); } } } // end of class // ----------------------------------------------------------------------------