FileDocCategorySizeDatePackage
ReportGenerator.javaAPI DocAndroid 1.5 API65203Wed May 06 22:41:16 BST 2009com.vladium.emma.report.html

ReportGenerator

public final class ReportGenerator extends com.vladium.emma.report.AbstractReportGenerator implements com.vladium.emma.IAppErrorCodes
author
Vlad Roubtsov, (C) 2003

Fields Summary
private final DecimalFormat
m_format
private final FieldPosition
m_fieldPosition
private LinkedList
m_queue
private IDGenerator
m_reportIDNamespace
private IContent
m_pageTitle
private IContent
m_footerBottom
private static final boolean
USE_LINE_COVERAGE_TOOLTIPS
private static final String
TYPE
private static final String
REPORT_HEADER_TITLE
private static final IContent
LEFT_BRACKET
private static final IContent
RIGHT_BRACKET
private static final int
MAX_DISPLAY_NAME_LENGTH
private static final int
SRC_LINE_OFFSET
private static final String
CSS_HEADER_FOOTER
private static final String
CSS_TITLE
private static final String
CSS_NAV
private static final String
CSS_COVERAGE_ZERO
private static final String
CSS_COVERAGE_PARTIAL
private static final String
CSS_COVERAGE_COMPLETE
private static final String
DARKER_BACKGROUND
private static final String
TITLE_BACKGROUND
private static final String
NAV_BACKGROUND
private static final String
CSS_INVISIBLE_TABLE
private static final String
CSS_ITEM_NAME
private static final String
CSS_CLASS_ITEM_SPECIAL
private static final String
CSS_SOURCE
private static final String
CSS_LINENUM
private static final String
CSS_BOTTOM
private static final String
CSS_ODDROW
private static final String
CSS_BLANK
private static final String
CSS_DATA
private static final String
CSS_DATA_HIGHLIGHT
private static final String
CSS_DATA_FIRST
private static final String
CSS_DATA_HIGHLIGHT_FIRST
private static final String
CSS_HEADER
private static final String
CSS_HEADER_FIRST
private static final String
CSS_CLS_NOLEFT
private static final String
CSS
private static final String
NESTED_ITEMS_PARENT_DIRNAME
private static final File
NESTED_ITEMS_PARENT_DIR
private static final int[]
NESTING
private static final String
FILE_EXTENSION
private static final int
IO_BUF_SIZE
private static final long[]
ATTRIBUTE_SETS
Constructors Summary
public ReportGenerator()

        m_format = (DecimalFormat) NumberFormat.getPercentInstance (); // TODO: locale
        m_fieldPosition = new FieldPosition (DecimalFormat.INTEGER_FIELD);
        
        m_format.setMaximumFractionDigits (0);
    
Methods Summary
private voidaddClassItemRow(com.vladium.emma.report.IItem item, boolean odd, HTMLTable table, int[] columns, java.lang.String nameHREF, 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 voidaddClassRow(com.vladium.emma.report.ClassItem item, int clsIndex, HTMLTable table, int[] columns, java.lang.String itemHREF, 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 int[]addHeaderRow(com.vladium.emma.report.IItem item, HTMLTable table, 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 ();
    
private voidaddItemRow(com.vladium.emma.report.IItem item, boolean odd, HTMLTable table, int[] columns, java.lang.String nameHREF, 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 static java.lang.StringaddLineAnchorID(int line, java.lang.String anchorID, com.vladium.util.IntObjectMap 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;
    
private IElementaddPageFooter(HTMLDocument page, com.vladium.emma.report.IItem item, com.vladium.emma.report.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 IElementaddPageHeader(HTMLDocument page, com.vladium.emma.report.IItem item, com.vladium.emma.report.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 voidaddPageHeaderTitleRow(HTMLTable header)

        final HTMLTable.IRow titleRow = header.newTitleRow ();
        
        final HTMLTable.ICell cell = titleRow.newCell ();
        cell.setClass (CSS_TITLE);
        cell.add (getPageTitle ());
    
public voidcleanup()

        m_queue = null;
        m_reportIDNamespace = null;
        
        super.cleanup ();
    
private HTMLDocumentcreatePage(java.lang.String title)

        final HTMLDocument page = new HTMLDocument (title, m_settings.getOutEncoding ());
        page.addStyle (CSS); // TODO: split by visit type
        
        return page;
    
private static java.lang.StringdataCellStyle(int column, boolean highlight)

        if (column == 0)
            return highlight ? CSS_DATA_HIGHLIGHT_FIRST : CSS_DATA_FIRST;
        else
            return highlight ? CSS_DATA_HIGHLIGHT : CSS_DATA;
    
private voidembedSrcFile(com.vladium.emma.report.SrcFileItem item, HTMLDocument page, com.vladium.util.IntObjectMap anchorMap, com.vladium.emma.report.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 IContentgetFooterBottom()

 
        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 static java.io.FilegetItemFile(java.io.File parentDir, java.lang.String itemKey)

        if (parentDir == null)
            return new File (itemKey.concat (FILE_EXTENSION));
        else
            return new File (parentDir, itemKey.concat (FILE_EXTENSION));
    
private java.lang.StringgetItemHREF(com.vladium.emma.report.IItem base, com.vladium.emma.report.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 static java.lang.StringgetItemKey(com.vladium.emma.report.IItem item)

        final StringBuffer result = new StringBuffer ();
        
        for ( ; item != null; item = item.getParent ())
        {
            result.append (item.getName ());
            result.append (':");
        }
        
        return result.toString ();
    
private IContentgetPageTitle()

 
        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 com.vladium.emma.report.IItem[]getParentPath(com.vladium.emma.report.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;
    
public final java.lang.StringgetType()

        return TYPE;
    
private static java.lang.StringheaderCellStyle(int column)

        return (column == 0) ? CSS_HEADER_FIRST : CSS_HEADER;
    
private static HTMLWriteropenOutFile(java.io.File file, java.lang.String encoding, 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);
    
public voidprocess(com.vladium.emma.data.IMetaData mdata, com.vladium.emma.data.ICoverageData cdata, com.vladium.emma.report.SourcePathCache cache, com.vladium.util.IProperties properties)

        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");
        }
    
private booleansrcFileAvailable(com.vladium.emma.report.SrcFileItem item, com.vladium.emma.report.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 static voidtrimForDisplay(java.lang.StringBuffer buf)

        if (buf.length () > MAX_DISPLAY_NAME_LENGTH)
        {
            buf.setLength (MAX_DISPLAY_NAME_LENGTH - 3);
            buf.append ("...");
        }
    
public java.lang.Objectvisit(com.vladium.emma.report.AllItem item, java.lang.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 java.lang.Objectvisit(com.vladium.emma.report.PackageItem item, java.lang.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 java.lang.Objectvisit(com.vladium.emma.report.SrcFileItem item, java.lang.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 java.lang.Objectvisit(com.vladium.emma.report.ClassItem item, java.lang.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;