/* 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.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.*; // ---------------------------------------------------------------------------- /** * @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 (); { 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 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 ; 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 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; } } // end of class // ----------------------------------------------------------------------------