com.vladium.emma.report.html.ReportGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.vladium.emma.report.html.ReportGenerator.java

Source

/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
 * 
 * This program and the accompanying materials are made available under
 * the terms of the Common Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/cpl-v10.html
 * 
 * $Id: ReportGenerator.java,v 1.2.2.1 2004/07/16 23:32:04 vlad_r Exp $
 */
package com.vladium.emma.report.html;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;

import com.vladium.util.Descriptors;
import com.vladium.util.Files;
import com.vladium.util.IProperties;
import com.vladium.util.IntObjectMap;
import com.vladium.util.IntVector;
import com.vladium.util.ObjectIntMap;
import com.vladium.util.Property;
import com.vladium.util.asserts.$assert;
import com.vladium.emma.IAppConstants;
import com.vladium.emma.IAppErrorCodes;
import com.vladium.emma.EMMAProperties;
import com.vladium.emma.EMMARuntimeException;
import com.vladium.emma.data.ICoverageData;
import com.vladium.emma.data.IMetaData;
import com.vladium.emma.report.AbstractReportGenerator;
import com.vladium.emma.report.AllItem;
import com.vladium.emma.report.ClassItem;
import com.vladium.emma.report.IItem;
import com.vladium.emma.report.IItemAttribute;
import com.vladium.emma.report.IItemMetadata;
import com.vladium.emma.report.ItemComparator;
import com.vladium.emma.report.MethodItem;
import com.vladium.emma.report.PackageItem;
import com.vladium.emma.report.SourcePathCache;
import com.vladium.emma.report.SrcFileItem;
import com.vladium.emma.report.html.doc.*;

// for JSON output
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

// ----------------------------------------------------------------------------
/**
 * @author Vlad Roubtsov, (C) 2003
 */
public final class ReportGenerator extends AbstractReportGenerator implements IAppErrorCodes {
    // public: ................................................................

    // TODO: make sure relative file names are converted to relative URLs in all anchors/hrefs

    public ReportGenerator() {
        m_format = (DecimalFormat) NumberFormat.getPercentInstance(); // TODO: locale
        m_fieldPosition = new FieldPosition(DecimalFormat.INTEGER_FIELD);

        m_format.setMaximumFractionDigits(0);
    }

    // IReportGenerator:

    public final String getType() {
        return TYPE;
    }

    public void process(final IMetaData mdata, final ICoverageData cdata, final SourcePathCache cache,
            final IProperties properties) throws EMMARuntimeException {
        initialize(mdata, cdata, cache, properties);

        m_pageTitle = null;
        m_footerBottom = null;

        File outDir = m_settings.getOutDir();
        if ((outDir == null) /* this should never happen */ || (outDir
                .equals(new File(Property.getSystemProperty("user.dir", ""))))) {
            outDir = new File("coverage");
            m_settings.setOutDir(outDir);
        }

        long start = 0, end;
        final boolean trace1 = m_log.atTRACE1();

        if (trace1)
            start = System.currentTimeMillis();

        {
            m_queue = new LinkedList();
            m_reportIDNamespace = new IDGenerator(mdata.size());

            for (m_queue.add(m_view.getRoot()); !m_queue.isEmpty();) {
                final IItem head = (IItem) m_queue.removeFirst();

                head.accept(this, null);
            }

            m_reportIDNamespace = null;
        }

        if (trace1) {
            end = System.currentTimeMillis();

            m_log.trace1("process", "[" + getType() + "] report generated in " + (end - start) + " ms");
        }
    }

    public void cleanup() {
        m_queue = null;
        m_reportIDNamespace = null;

        super.cleanup();
    }

    // IItemVisitor:

    public Object visit(final AllItem item, final Object ctx) {
        HTMLWriter out = null;
        try {
            File outFile = m_settings.getOutFile();
            if (outFile == null) {
                outFile = new File("index".concat(FILE_EXTENSION));
                m_settings.setOutFile(outFile);
            }

            final File fullOutFile = Files.newFile(m_settings.getOutDir(), outFile);

            m_log.info("writing [" + getType() + "] report to [" + fullOutFile.getAbsolutePath() + "] ...");

            out = openOutFile(fullOutFile, m_settings.getOutEncoding(), true);

            final int[] columns = m_settings.getColumnOrder();
            final StringBuffer buf = new StringBuffer();

            final String title;
            {
                final StringBuffer _title = new StringBuffer(REPORT_HEADER_TITLE);

                _title.append(" (generated ");
                _title.append(new Date(EMMAProperties.getTimeStamp()));
                _title.append(')');

                title = _title.toString();
            }

            final HTMLDocument page = createPage(title);
            {
                final IItem[] path = getParentPath(item);

                addPageHeader(page, item, path);
                addPageFooter(page, item, path);
            }

            // [all] coverage summary table:

            page.addH(1, "OVERALL COVERAGE SUMMARY", null);

            final HTMLTable summaryTable = new HTMLTable("100%", null, null, "0");
            {
                // header row:
                final HTMLTable.IRow header = summaryTable.newTitleRow();
                // coverage row:
                final HTMLTable.IRow coverage = summaryTable.newRow();

                for (int c = 0; c < columns.length; ++c) {
                    final int attrID = columns[c];
                    final IItemAttribute attr = item.getAttribute(attrID, m_settings.getUnitsType());

                    final HTMLTable.ICell headercell = header.newCell();
                    headercell.setText(attr.getName(), true);

                    if (attr != null) {
                        boolean fail = (m_metrics[attrID] > 0) && !attr.passes(item, m_metrics[attrID]);

                        buf.setLength(0);
                        attr.format(item, buf);

                        final HTMLTable.ICell cell = coverage.newCell();
                        cell.setText(buf.toString(), true);
                        if (fail)
                            cell.setClass(CSS_DATA_HIGHLIGHT);
                    }
                }
            }
            page.add(summaryTable);

            // [all] stats summary table ([all] only):

            page.addH(2, "OVERALL STATS SUMMARY", null);

            final HTMLTable statsTable = new HTMLTable(null, null, null, "0");
            statsTable.setClass(CSS_INVISIBLE_TABLE);
            {
                HTMLTable.IRow row = statsTable.newRow();
                row.newCell().setText("total packages:", true);
                row.newCell().setText("" + item.getChildCount(), false);

                if (m_srcView && m_hasSrcFileInfo) {
                    row = statsTable.newRow();
                    row.newCell().setText("total executable files:", true);
                    row.newCell().setText("" + item.getAggregate(IItem.TOTAL_SRCFILE_COUNT), false);
                }

                row = statsTable.newRow();
                row.newCell().setText("total classes:", true);
                row.newCell().setText("" + item.getAggregate(IItem.TOTAL_CLASS_COUNT), true);
                row = statsTable.newRow();
                row.newCell().setText("total methods:", true);
                row.newCell().setText("" + item.getAggregate(IItem.TOTAL_METHOD_COUNT), true);

                if (m_srcView && m_hasSrcFileInfo && m_hasLineNumberInfo) {
                    row = statsTable.newRow();
                    row.newCell().setText("total executable lines:", true);
                    row.newCell().setText("" + item.getAggregate(IItem.TOTAL_LINE_COUNT), true);
                }
            }
            /*
            {
            final HTMLTable.IRow first = statsTable.newRow (); // stats always available
                
            first.newCell ().setText ("total packages: " + item.getChildCount (), true);
            first.newCell ().setText ("total classes: " + item.getAggregate (IItem.TOTAL_CLASS_COUNT), true);
            first.newCell ().setText ("total methods: " + item.getAggregate (IItem.TOTAL_METHOD_COUNT), true);
                
            if (m_srcView && m_hasSrcFileInfo)
            {
                final HTMLTable.IRow second = statsTable.newRow ();
                    
                final HTMLTable.ICell cell1 = second.newCell ();
                cell1.setText ("total source files: " + item.getAggregate (IItem.TOTAL_SRCFILE_COUNT), true);
                
                if (m_hasLineNumberInfo)
                {
                    final HTMLTable.ICell cell2 = second.newCell ();
                        
                    cell2.setText ("total executable source lines: " + item.getAggregate (IItem.TOTAL_LINE_COUNT), true);
                    cell2.getAttributes ().set (Attribute.COLSPAN, "2");
                }
                else
                {
                    cell1.getAttributes ().set (Attribute.COLSPAN, "3");
                }
            }
            }
            */
            page.add(statsTable);

            final boolean deeper = (m_settings.getDepth() > item.getMetadata().getTypeID());

            // render package summary tables on the same page:

            page.addH(2, "COVERAGE BREAKDOWN BY PACKAGE", null);

            final HTMLTable childSummaryTable = new HTMLTable("100%", null, null, "0");
            {
                int[] headerColumns = null;

                boolean odd = true;
                final ItemComparator order = m_typeSortComparators[PackageItem.getTypeMetadata().getTypeID()];
                for (Iterator packages = item.getChildren(order); packages.hasNext(); odd = !odd) {
                    final IItem pkg = (IItem) packages.next();

                    if (headerColumns == null) {
                        // header row:
                        headerColumns = addHeaderRow(pkg, childSummaryTable, columns);
                    }

                    // coverage row:
                    String childHREF = null;
                    if (deeper) {
                        childHREF = getItemHREF(item, pkg);
                    }
                    addItemRow(pkg, odd, childSummaryTable, headerColumns, childHREF, false);

                    if (deeper)
                        m_queue.addLast(pkg);
                }
            }
            page.add(childSummaryTable);

            page.emit(out);
            out.flush();
        } finally {
            if (out != null)
                out.close();
            out = null;
        }

        return ctx;
    }

    public Object visit(final PackageItem item, final Object ctx) {
        HTMLWriter out = null;
        try {
            if (m_verbose)
                m_log.verbose("  report: processing package [" + item.getName() + "] ...");

            final File outFile = getItemFile(NESTED_ITEMS_PARENT_DIR, m_reportIDNamespace.getID(getItemKey(item)));

            out = openOutFile(Files.newFile(m_settings.getOutDir(), outFile), m_settings.getOutEncoding(), true);

            final int[] columns = m_settings.getColumnOrder();
            final StringBuffer buf = new StringBuffer();

            // TODO: set title [from a prop?]
            final HTMLDocument page = createPage(REPORT_HEADER_TITLE);
            {
                final IItem[] path = getParentPath(item);

                addPageHeader(page, item, path);
                addPageFooter(page, item, path);
            }

            // summary table:

            {
                final IElement itemname = IElement.Factory.create(Tag.SPAN);
                itemname.setText(item.getName(), true);
                itemname.setClass(CSS_ITEM_NAME);

                final IElementList title = new ElementList();
                title.add(new Text("COVERAGE SUMMARY FOR PACKAGE [", true));
                title.add(itemname);
                title.add(new Text("]", true));

                page.addH(1, title, null);
            }

            final HTMLTable summaryTable = new HTMLTable("100%", null, null, "0");
            {
                // header row:
                final HTMLTable.IRow header = summaryTable.newTitleRow();
                // coverage row:
                final HTMLTable.IRow coverage = summaryTable.newRow();

                for (int c = 0; c < columns.length; ++c) {
                    final int attrID = columns[c];
                    final IItemAttribute attr = item.getAttribute(attrID, m_settings.getUnitsType());

                    final HTMLTable.ICell headercell = header.newCell();
                    headercell.setText(attr.getName(), true);

                    if (attr != null) {
                        boolean fail = (m_metrics[attrID] > 0) && !attr.passes(item, m_metrics[attrID]);

                        buf.setLength(0);
                        attr.format(item, buf);

                        final HTMLTable.ICell cell = coverage.newCell();
                        cell.setText(buf.toString(), true);
                        if (fail)
                            cell.setClass(CSS_DATA_HIGHLIGHT);
                    }
                }
            }
            page.add(summaryTable);

            final boolean deeper = (m_settings.getDepth() > item.getMetadata().getTypeID());

            // render child summary tables on the same page:

            final String summaryTitle = m_srcView ? "COVERAGE BREAKDOWN BY SOURCE FILE"
                    : "COVERAGE BREAKDOWN BY CLASS";
            page.addH(2, summaryTitle, null);

            final HTMLTable childSummaryTable = new HTMLTable("100%", null, null, "0");
            {
                int[] headerColumns = null;

                boolean odd = true;
                final ItemComparator order = m_typeSortComparators[m_srcView
                        ? SrcFileItem.getTypeMetadata().getTypeID()
                        : ClassItem.getTypeMetadata().getTypeID()];
                for (Iterator srcORclsFiles = item.getChildren(order); srcORclsFiles.hasNext(); odd = !odd) {
                    final IItem srcORcls = (IItem) srcORclsFiles.next();

                    if (headerColumns == null) {
                        // header row:
                        headerColumns = addHeaderRow(srcORcls, childSummaryTable, columns);
                    }

                    // coverage row:
                    String childHREF = null;
                    if (deeper) {
                        childHREF = getItemHREF(item, srcORcls);
                    }
                    addItemRow(srcORcls, odd, childSummaryTable, headerColumns, childHREF, false);

                    if (deeper)
                        m_queue.addLast(srcORcls);
                }
            }
            page.add(childSummaryTable);

            page.emit(out);
            out.flush();
        } finally {
            if (out != null)
                out.close();
            out = null;
        }

        return ctx;
    }

    public Object visit(final SrcFileItem item, final Object ctx) {
        // this visit only takes place in src views

        HTMLWriter out = null;
        try {
            final File outFile = getItemFile(NESTED_ITEMS_PARENT_DIR, m_reportIDNamespace.getID(getItemKey(item)));

            out = openOutFile(Files.newFile(m_settings.getOutDir(), outFile), m_settings.getOutEncoding(), true);

            final int[] columns = m_settings.getColumnOrder();
            final StringBuffer buf = new StringBuffer();

            // TODO: set title [from a prop?]
            final HTMLDocument page = createPage(REPORT_HEADER_TITLE);
            {
                final IItem[] path = getParentPath(item);

                addPageHeader(page, item, path);
                addPageFooter(page, item, path);
            }

            // summary table:

            {
                final IElement itemname = IElement.Factory.create(Tag.SPAN);
                itemname.setText(item.getName(), true);
                itemname.setClass(CSS_ITEM_NAME);

                final IElementList title = new ElementList();
                title.add(new Text("COVERAGE SUMMARY FOR SOURCE FILE [", true));
                title.add(itemname);
                title.add(new Text("]", true));

                page.addH(1, title, null);
            }

            final HTMLTable summaryTable = new HTMLTable("100%", null, null, "0");
            {
                // header row:
                final HTMLTable.IRow header = summaryTable.newTitleRow();
                // coverage row:
                final HTMLTable.IRow coverage = summaryTable.newRow();

                for (int c = 0; c < columns.length; ++c) {
                    final int attrID = columns[c];
                    final IItemAttribute attr = item.getAttribute(attrID, m_settings.getUnitsType());

                    final HTMLTable.ICell headercell = header.newCell();
                    headercell.setText(attr.getName(), true);

                    if (attr != null) {
                        boolean fail = (m_metrics[attrID] > 0) && !attr.passes(item, m_metrics[attrID]);

                        buf.setLength(0);
                        attr.format(item, buf);

                        final HTMLTable.ICell cell = coverage.newCell();
                        cell.setText(buf.toString(), true);
                        if (fail)
                            cell.setClass(CSS_DATA_HIGHLIGHT);
                    }
                }
            }
            page.add(summaryTable);

            final boolean deeper = (m_settings.getDepth() > ClassItem.getTypeMetadata().getTypeID());
            final boolean embedSrcFile = deeper && srcFileAvailable(item, m_cache);
            final boolean createAnchors = embedSrcFile && m_hasLineNumberInfo;

            final IDGenerator pageIDNamespace = createAnchors ? new IDGenerator() : null;

            // child summary table is special for srcfile items:

            page.addH(2, "COVERAGE BREAKDOWN BY CLASS AND METHOD", null);

            final IntObjectMap lineAnchorIDMap = embedSrcFile ? new IntObjectMap() : null;
            final HTMLTable childSummaryTable = new HTMLTable("100%", null, null, "0");

            childSummaryTable.setClass(CSS_CLS_NOLEFT);

            {
                int[] headerColumns = null;

                final ItemComparator order = m_typeSortComparators[ClassItem.getTypeMetadata().getTypeID()];
                int clsIndex = 0;
                for (Iterator classes = item.getChildren(order); classes.hasNext(); ++clsIndex) {
                    final ClassItem cls = (ClassItem) classes.next();

                    if (headerColumns == null) {
                        // header row:
                        headerColumns = addHeaderRow(cls, childSummaryTable, columns);
                    }

                    String HREFname = null;

                    // special class subheader:
                    if (createAnchors) {
                        if ($assert.ENABLED) {
                            $assert.ASSERT(lineAnchorIDMap != null);
                            $assert.ASSERT(pageIDNamespace != null);
                        }

                        final String childKey = getItemKey(cls);

                        HREFname = addLineAnchorID(cls.getFirstLine(), pageIDNamespace.getID(childKey),
                                lineAnchorIDMap);
                    }

                    addClassRow(cls, clsIndex, childSummaryTable, headerColumns, HREFname, createAnchors);

                    //                    // row to separate this class's methods:
                    //                    final HTMLTable.IRow subheader = childSummaryTable.newTitleRow ();
                    //                    final HTMLTable.ICell cell = subheader.newCell ();
                    //                    // TODO: cell.setColspan (???)
                    //                    cell.setText ("class " + child.getName () + " methods:", true);

                    boolean odd = false;
                    final ItemComparator order2 = m_typeSortComparators[MethodItem.getTypeMetadata().getTypeID()];
                    for (Iterator methods = cls.getChildren(order2); methods.hasNext(); odd = !odd) {
                        final MethodItem method = (MethodItem) methods.next();

                        HREFname = null;

                        if (createAnchors) {
                            if ($assert.ENABLED) {
                                $assert.ASSERT(lineAnchorIDMap != null);
                                $assert.ASSERT(pageIDNamespace != null);
                            }

                            final String child2Key = getItemKey(method);

                            HREFname = addLineAnchorID(method.getFirstLine(), pageIDNamespace.getID(child2Key),
                                    lineAnchorIDMap);
                        }

                        addClassItemRow(method, odd, childSummaryTable, headerColumns, HREFname, createAnchors);
                    }
                }
            }
            page.add(childSummaryTable);

            // embed source file:

            if (deeper) {
                //page.addHR (1);
                page.addEmptyP();
                {
                    embedSrcFileJson(item, m_cache);
                    embedSrcFile(item, page, lineAnchorIDMap, m_cache);
                }
                //page.addHR (1);
            }

            page.emit(out);
            out.flush();
        } finally {
            if (out != null)
                out.close();
            out = null;
        }

        return ctx;
    }

    public Object visit(final ClassItem item, final Object ctx) {
        // this visit only takes place in class views

        HTMLWriter out = null;
        try {
            final File outFile = getItemFile(NESTED_ITEMS_PARENT_DIR, m_reportIDNamespace.getID(getItemKey(item)));

            // TODO: deal with overwrites
            out = openOutFile(Files.newFile(m_settings.getOutDir(), outFile), m_settings.getOutEncoding(), true);

            final int[] columns = m_settings.getColumnOrder();
            final StringBuffer buf = new StringBuffer();

            // TODO: set title [from a prop?]
            final HTMLDocument page = createPage(REPORT_HEADER_TITLE);
            {
                final IItem[] path = getParentPath(item);

                addPageHeader(page, item, path);
                addPageFooter(page, item, path);
            }

            // summary table:

            {
                final IElement itemname = IElement.Factory.create(Tag.SPAN);
                itemname.setText(item.getName(), true);
                itemname.setClass(CSS_ITEM_NAME);

                final IElementList title = new ElementList();
                title.add(new Text("COVERAGE SUMMARY FOR CLASS [", true));
                title.add(itemname);
                title.add(new Text("]", true));

                page.addH(1, title, null);
            }

            final HTMLTable summaryTable = new HTMLTable("100%", null, null, "0");
            {
                // header row:
                final HTMLTable.IRow header = summaryTable.newTitleRow();
                // coverage row:
                final HTMLTable.IRow coverage = summaryTable.newRow();

                for (int c = 0; c < columns.length; ++c) {
                    final int attrID = columns[c];
                    final IItemAttribute attr = item.getAttribute(attrID, m_settings.getUnitsType());

                    final HTMLTable.ICell headercell = header.newCell();
                    headercell.setText(attr.getName(), true);

                    if (attr != null) {
                        boolean fail = (m_metrics[attrID] > 0) && !attr.passes(item, m_metrics[attrID]);

                        buf.setLength(0);
                        attr.format(item, buf);

                        final HTMLTable.ICell cell = coverage.newCell();
                        cell.setText(buf.toString(), true);
                        if (fail)
                            cell.setClass(CSS_DATA_HIGHLIGHT);
                    }
                }
            }
            page.add(summaryTable);

            // child summary table:

            page.addH(2, "COVERAGE BREAKDOWN BY METHOD", null);

            final HTMLTable childSummaryTable = new HTMLTable("100%", null, null, "0");
            {
                int[] headerColumns = null;

                boolean odd = true;
                final ItemComparator order = m_typeSortComparators[MethodItem.getTypeMetadata().getTypeID()];
                for (Iterator methods = item.getChildren(order); methods.hasNext(); odd = !odd) {
                    final MethodItem method = (MethodItem) methods.next();

                    if (headerColumns == null) {
                        // header row:
                        headerColumns = addHeaderRow(method, childSummaryTable, columns);
                    }

                    addItemRow(method, odd, childSummaryTable, headerColumns, null, false);
                }
            }
            page.add(childSummaryTable);

            page.emit(out);
            out.flush();
        } finally {
            if (out != null)
                out.close();
            out = null;
        }

        return ctx;
    }

    // protected: .............................................................

    // package: ...............................................................

    // private: ...............................................................

    private static final class IDGenerator {
        IDGenerator() {
            m_namespace = new ObjectIntMap(101);
            m_out = new int[1];
        }

        IDGenerator(final int initialCapacity) {
            m_namespace = new ObjectIntMap(initialCapacity);
            m_out = new int[1];
        }

        String getID(final String key) {
            final int[] out = m_out;
            final int ID;

            if (m_namespace.get(key, out))
                ID = out[0];
            else {
                ID = m_namespace.size();
                m_namespace.put(key, ID);
            }

            return Integer.toHexString(ID);
        }

        private final ObjectIntMap /* key:String->ID */ m_namespace;
        private final int[] m_out;

    } // end of nested class

    private HTMLDocument createPage(final String title) {
        final HTMLDocument page = new HTMLDocument(title, m_settings.getOutEncoding());
        page.addStyle(CSS); // TODO: split by visit type

        return page;
    }

    private IElement addPageHeader(final HTMLDocument page, final IItem item, final IItem[] path) {
        // TODO: merge header and footer in the same method

        if ($assert.ENABLED) {
            $assert.ASSERT(page != null);
        }

        final HTMLTable header = new HTMLTable("100%", null, null, "0");
        header.setClass(CSS_HEADER_FOOTER);

        // header row:        
        addPageHeaderTitleRow(header);

        // nav row:
        {
            final HTMLTable.IRow navRow = header.newRow();

            final HTMLTable.ICell cell = navRow.newCell();
            cell.setClass(CSS_NAV);

            final int lLimit = path.length > 1 ? path.length - 1 : path.length;
            for (int l = 0; l < lLimit; ++l) {
                cell.add(LEFT_BRACKET);

                final String name = path[l].getName();
                final String HREF = getItemHREF(item, path[l]);
                cell.add(new HyperRef(HREF, name, true));

                cell.add(RIGHT_BRACKET);
            }
        }

        page.setHeader(header);

        return header;
    }

    private IElement addPageFooter(final HTMLDocument page, final IItem item, final IItem[] path) {
        if ($assert.ENABLED) {
            $assert.ASSERT(page != null);
        }

        final HTMLTable footerTable = new HTMLTable("100%", null, null, "0");
        footerTable.setClass(CSS_HEADER_FOOTER);

        // nav row:
        {
            final HTMLTable.IRow navRow = footerTable.newRow();

            final HTMLTable.ICell cell = navRow.newCell();
            cell.setClass(CSS_NAV);

            final int lLimit = path.length > 1 ? path.length - 1 : path.length;
            for (int l = 0; l < lLimit; ++l) {
                cell.add(LEFT_BRACKET);

                final String name = path[l].getName();
                final String HREF = getItemHREF(item, path[l]);
                cell.add(new HyperRef(HREF, name, true));

                cell.add(RIGHT_BRACKET);
            }
        }

        // title row:
        {
            final HTMLTable.IRow titleRow = footerTable.newRow();

            final HTMLTable.ICell cell = titleRow.newCell();
            cell.setClass(CSS_TITLE);

            cell.add(getFooterBottom());
        }

        final ElementList footer = new ElementList();

        footer.add(IElement.Factory.create(Tag.P)); // spacer
        footer.add(footerTable);

        page.setFooter(footer);

        return footerTable;
    }

    private int[] addHeaderRow(final IItem item, final HTMLTable table, final int[] columns) {
        if ($assert.ENABLED) {
            $assert.ASSERT(item != null, "null input: item");
            $assert.ASSERT(table != null, "null input: table");
            $assert.ASSERT(columns != null, "null input: columns");
        }

        // header row:
        final HTMLTable.IRow header = table.newTitleRow();

        // determine the set of columns actually present in the header [may be narrower than 'columns']:
        final IntVector headerColumns = new IntVector(columns.length);

        for (int c = 0; c < columns.length; ++c) {
            final int attrID = columns[c];
            final IItemAttribute attr = item.getAttribute(attrID, m_settings.getUnitsType());

            if (attr != null) {
                final HTMLTable.ICell cell = header.newCell();

                cell.setText(attr.getName(), true);//.getAttributes ().set (Attribute.WIDTH, "20%");
                cell.setClass(headerCellStyle(c));
                headerColumns.add(attrID);
            }

            // note: by design this does not create columns for nonexistent attribute types
        }

        return headerColumns.values();
    }

    /*
     * No header row, just data rows.
     */
    private void addItemRow(final IItem item, final boolean odd, final HTMLTable table, final int[] columns,
            final String nameHREF, final boolean anchor) {
        if ($assert.ENABLED) {
            $assert.ASSERT(item != null, "null input: item");
            $assert.ASSERT(table != null, "null input: table");
            $assert.ASSERT(columns != null, "null input: columns");
        }

        final HTMLTable.IRow row = table.newRow();
        if (odd)
            row.setClass(CSS_ODDROW);

        final StringBuffer buf = new StringBuffer(11); // TODO: reuse a buffer

        for (int c = 0; c < columns.length; ++c) {
            final int attrID = columns[c];
            final IItemAttribute attr = item.getAttribute(attrID, m_settings.getUnitsType());

            if (attr != null) {
                final HTMLTable.ICell cell = row.newCell();

                if ((nameHREF != null) && (attrID == IItemAttribute.ATTRIBUTE_NAME_ID)) {
                    buf.setLength(0);
                    attr.format(item, buf);

                    trimForDisplay(buf);

                    final String fullHREFName = anchor ? "#".concat(nameHREF) : nameHREF;

                    cell.add(new HyperRef(fullHREFName, buf.toString(), true));
                } else {
                    final boolean fail = (m_metrics[attrID] > 0) && !attr.passes(item, m_metrics[attrID]);

                    buf.setLength(0);
                    attr.format(item, buf);

                    trimForDisplay(buf);

                    cell.setText(buf.toString(), true);
                    if (fail)
                        cell.setClass(CSS_DATA_HIGHLIGHT);
                }
            } else {
                // note: by design this puts empty cells for nonexistent attribute types

                final HTMLTable.ICell cell = row.newCell();
                cell.setText(" ", true);
            }
        }
    }

    private void addClassRow(final ClassItem item, final int clsIndex, final HTMLTable table, final int[] columns,
            final String itemHREF, final boolean isAnchor) {
        if ($assert.ENABLED) {
            $assert.ASSERT(item != null, "null input: item");
            $assert.ASSERT(table != null, "null input: table");
            $assert.ASSERT(columns != null, "null input: columns");
        }

        final HTMLTable.IRow blank = table.newRow();

        final HTMLTable.IRow row = table.newRow();
        row.setClass(CSS_CLASS_ITEM_SPECIAL);

        final StringBuffer buf = new StringBuffer(11);

        for (int c = 0; c < columns.length; ++c) {
            final int attrID = columns[c];
            final IItemAttribute attr = item.getAttribute(attrID, m_settings.getUnitsType());

            if (attr != null) {
                buf.setLength(0);
                attr.format(item, buf);

                final HTMLTable.ICell blankcell = blank.newCell();
                blankcell.setClass(clsIndex == 0 ? CSS_BLANK : CSS_BOTTOM);
                blankcell.setText(" ", true);

                final HTMLTable.ICell cell = row.newCell();

                boolean fail = false;
                if (attrID == IItemAttribute.ATTRIBUTE_NAME_ID) {
                    if (itemHREF != null) {
                        final String fullItemHREF = isAnchor ? "#".concat(itemHREF) : itemHREF;

                        cell.add(new Text("class ", true));
                        cell.add(new HyperRef(fullItemHREF, buf.toString(), true));
                    } else {
                        cell.setText("class " + buf.toString(), true);
                    }
                } else {
                    fail = (m_metrics[attrID] > 0) && !attr.passes(item, m_metrics[attrID]);

                    cell.setText(buf.toString(), true);
                }

                cell.setClass(dataCellStyle(c, fail));
            } else {
                final HTMLTable.ICell cell = row.newCell();
                cell.setText(" ", true);
                cell.setClass(dataCellStyle(c, false));
            }
        }
    }

    private void addClassItemRow(final IItem item, final boolean odd, final HTMLTable table, final int[] columns,
            final String nameHREF, final boolean anchor) {
        if ($assert.ENABLED) {
            $assert.ASSERT(item != null, "null input: item");
            $assert.ASSERT(table != null, "null input: table");
            $assert.ASSERT(columns != null, "null input: columns");
        }

        final HTMLTable.IRow row = table.newRow();
        if (odd)
            row.setClass(CSS_ODDROW);

        final StringBuffer buf = new StringBuffer(11); // TODO: reuse a buffer

        for (int c = 0; c < columns.length; ++c) {
            final int attrID = columns[c];
            final IItemAttribute attr = item.getAttribute(attrID, m_settings.getUnitsType());

            if (attr != null) {
                final HTMLTable.ICell cell = row.newCell();

                boolean fail = false;
                if ((nameHREF != null) && (attrID == IItemAttribute.ATTRIBUTE_NAME_ID)) {
                    buf.setLength(0);
                    attr.format(item, buf);

                    trimForDisplay(buf);

                    final String fullHREFName = anchor ? "#".concat(nameHREF) : nameHREF;

                    cell.add(new HyperRef(fullHREFName, buf.toString(), true));
                } else {
                    fail = (m_metrics[attrID] > 0) && !attr.passes(item, m_metrics[attrID]);

                    buf.setLength(0);
                    attr.format(item, buf);

                    trimForDisplay(buf);

                    cell.setText(buf.toString(), true);
                }

                cell.setClass(dataCellStyle(c, fail));
            } else {
                // note: by design this puts empty cells for nonexistent attribute types

                final HTMLTable.ICell cell = row.newCell();
                cell.setText(" ", true);
                cell.setClass(dataCellStyle(c, false));
            }
        }
    }

    private boolean srcFileAvailable(final SrcFileItem item, final SourcePathCache cache) {
        if (cache == null)
            return false;

        if ($assert.ENABLED)
            $assert.ASSERT(item != null, "null input: item");

        final String fileName = item.getName();
        if ($assert.ENABLED)
            $assert.ASSERT(fileName.endsWith(".java"), "cache only handles .java extensions");

        // TODO: should I keep VM names in package items?
        final String packageVMName = ((PackageItem) item.getParent()).getVMName();

        return (cache.find(packageVMName, fileName) != null);
    }

    //    private boolean srcFileAvailable (final ClassItem item, final SourcePathCache cache)
    //    {
    //        if (cache == null) return false;
    //        
    //        if ($assert.ENABLED) $assert.ASSERT (item != null, "null input: item");
    //        
    //        final String fileName = item.getSrcFileName ();
    //        if ($assert.ENABLED) $assert.ASSERT (fileName.endsWith (".java"), "cache only handles .java extensions");
    //        
    //        // TODO: should I keep VM names in package items?
    //        final String packageVMName = ((PackageItem) item.getParent ()).getVMName ();
    //        
    //        return (cache.find (packageVMName, fileName) != null);
    //    }

    private void embedSrcFile(final SrcFileItem item, final HTMLDocument page,
            final IntObjectMap /* line num:int->anchor name:String */ anchorMap, final SourcePathCache cache) {
        if ($assert.ENABLED) {
            $assert.ASSERT(item != null, "null input: item");
            $assert.ASSERT(page != null, "null input: page");
        }

        final String fileName = item.getName();
        if ($assert.ENABLED)
            $assert.ASSERT(fileName.endsWith(".java"), "cache only handles .java extensions");

        // TODO: should I keep VM names in package items?
        final String packageVMName = ((PackageItem) item.getParent()).getVMName();

        boolean success = false;

        final HTMLTable srcTable = new HTMLTable("100%", null, null, "0");

        if (cache != null) // TODO: do this check earlier, in outer scope
        {
            srcTable.setClass(CSS_SOURCE);
            final File srcFile = cache.find(packageVMName, fileName);

            if (srcFile != null) {
                BufferedReader in = null;
                try {
                    in = new BufferedReader(new FileReader(srcFile), IO_BUF_SIZE);

                    final boolean markupCoverage = m_hasLineNumberInfo;

                    final int unitsType = m_settings.getUnitsType();
                    IntObjectMap /* line num:int -> SrcFileItem.LineCoverageData */ lineCoverageMap = null;
                    StringBuffer tooltipBuffer = null;

                    if (markupCoverage) {
                        lineCoverageMap = item.getLineCoverage();
                        $assert.ASSERT(lineCoverageMap != null, "null: lineCoverageMap");

                        tooltipBuffer = new StringBuffer(64);
                    }

                    int l = 1;
                    for (String line; (line = in.readLine()) != null; ++l) {
                        final HTMLTable.IRow srcline = srcTable.newRow();
                        final HTMLTable.ICell lineNumCell = srcline.newCell();
                        lineNumCell.setClass(CSS_LINENUM);

                        if (anchorMap != null) {
                            final int adjustedl = l < SRC_LINE_OFFSET ? l : l + SRC_LINE_OFFSET;

                            final String anchor = (String) anchorMap.get(adjustedl);
                            if (anchor != null) {
                                final IElement a = IElement.Factory.create(Tag.A);
                                //a.getAttributes ().set (Attribute.ID, anchor); ID anchoring does not work in NS 4.0
                                a.getAttributes().set(Attribute.NAME, anchor);

                                a.setText(Integer.toString(l), true);

                                lineNumCell.add(a);
                            } else {
                                lineNumCell.setText(Integer.toString(l), true);
                            }
                        } else {
                            lineNumCell.setText(Integer.toString(l), true);
                        }

                        final HTMLTable.ICell lineTxtCell = srcline.newCell();
                        lineTxtCell.setText(line.length() > 0 ? line : " ", true);

                        if (markupCoverage) {
                            final SrcFileItem.LineCoverageData lCoverageData = (SrcFileItem.LineCoverageData) lineCoverageMap
                                    .get(l);

                            if (lCoverageData != null) {
                                switch (lCoverageData.m_coverageStatus) {
                                case SrcFileItem.LineCoverageData.LINE_COVERAGE_ZERO:
                                    srcline.setClass(CSS_COVERAGE_ZERO);
                                    break;

                                case SrcFileItem.LineCoverageData.LINE_COVERAGE_PARTIAL: {
                                    srcline.setClass(CSS_COVERAGE_PARTIAL);

                                    if (USE_LINE_COVERAGE_TOOLTIPS) {
                                        tooltipBuffer.setLength(0);

                                        final int[] coverageRatio = lCoverageData.m_coverageRatio[unitsType];

                                        final int d = coverageRatio[0];
                                        final int n = coverageRatio[1];

                                        m_format.format((double) n / d, tooltipBuffer, m_fieldPosition);

                                        tooltipBuffer.append(" line coverage (");
                                        tooltipBuffer.append(n);
                                        tooltipBuffer.append(" out of ");
                                        tooltipBuffer.append(d);

                                        switch (unitsType) {
                                        case IItemAttribute.UNITS_COUNT:
                                            tooltipBuffer.append(" basic blocks)");
                                            break;

                                        case IItemAttribute.UNITS_INSTR:
                                            tooltipBuffer.append(" instructions)");
                                            break;
                                        }

                                        // [Opera does not display TITLE tooltios on <TR> elements]

                                        lineNumCell.getAttributes().set(Attribute.TITLE, tooltipBuffer.toString());
                                        lineTxtCell.getAttributes().set(Attribute.TITLE, tooltipBuffer.toString());
                                    }
                                }
                                    break;

                                case SrcFileItem.LineCoverageData.LINE_COVERAGE_COMPLETE:
                                    srcline.setClass(CSS_COVERAGE_COMPLETE);
                                    break;

                                default:
                                    $assert.ASSERT(false,
                                            "invalid line coverage status: " + lCoverageData.m_coverageStatus);

                                } // end of switch
                            }
                        }
                    }

                    success = true;
                } catch (Throwable t) {
                    t.printStackTrace(System.out); // TODO: logging
                    success = false;
                } finally {
                    if (in != null)
                        try {
                            in.close();
                        } catch (Throwable ignore) {
                        }
                    in = null;
                }
            }
        }

        if (!success) {
            srcTable.setClass(CSS_INVISIBLE_TABLE);

            final HTMLTable.IRow row = srcTable.newTitleRow();
            row.newCell().setText("[source file '" + Descriptors.combineVMName(packageVMName, fileName)
                    + "' not found in sourcepath]", false);
        }

        page.add(srcTable);
    }

    private static String addLineAnchorID(final int line, final String anchorID,
            final IntObjectMap /* line num:int->anchorID:String */ lineAnchorIDMap) {
        if (line > 0) {
            final String _anchorID = (String) lineAnchorIDMap.get(line);
            if (_anchorID != null)
                return _anchorID;
            else {
                lineAnchorIDMap.put(line, anchorID);

                return anchorID;
            }
        }

        return null;
    }

    /*
     * Always includes AllItem
     */
    private IItem[] getParentPath(IItem item) {
        final LinkedList /* IItem */ _result = new LinkedList();

        for (; item != null; item = item.getParent()) {
            _result.add(item);
        }

        final IItem[] result = new IItem[_result.size()];
        int j = result.length - 1;
        for (Iterator i = _result.iterator(); i.hasNext(); --j) {
            result[j] = (IItem) i.next();
        }

        return result;
    }

    /*
     * 
     */
    private String getItemHREF(final IItem base, final IItem item) {
        final String itemHREF;
        if (item instanceof AllItem)
            itemHREF = m_settings.getOutFile().getName(); // note that this is always a simple filename [no parent path]
        else
            itemHREF = m_reportIDNamespace.getID(getItemKey(item)).concat(FILE_EXTENSION);

        final String fullHREF;

        if (base == null)
            fullHREF = itemHREF;
        else {
            final int nesting = NESTING[base.getMetadata().getTypeID()][item.getMetadata().getTypeID()];
            if (nesting == 1)
                fullHREF = NESTED_ITEMS_PARENT_DIRNAME.concat("/").concat(itemHREF);
            else if (nesting == -1)
                fullHREF = "../".concat(itemHREF);
            else
                fullHREF = itemHREF;
        }

        return fullHREF;
    }

    private IContent getPageTitle() {
        IContent title = m_pageTitle;
        if (title == null) {
            final IElementList _title = new ElementList();

            _title.add(new HyperRef(IAppConstants.APP_HOME_SITE_LINK, IAppConstants.APP_NAME, true));

            final StringBuffer s = new StringBuffer(" Coverage Report (generated ");
            s.append(new Date(EMMAProperties.getTimeStamp()));
            s.append(')');

            _title.add(new Text(s.toString(), true));

            m_pageTitle = title = _title;
        }

        return title;
    }

    private IContent getFooterBottom() {
        IContent bottom = m_footerBottom;
        if (bottom == null) {
            final IElementList _bottom = new ElementList();

            _bottom.add(new HyperRef(IAppConstants.APP_BUG_REPORT_LINK,
                    IAppConstants.APP_NAME + " " + IAppConstants.APP_VERSION_WITH_BUILD_ID_AND_TAG, true));
            _bottom.add(new Text(" " + IAppConstants.APP_COPYRIGHT, true));

            m_footerBottom = bottom = _bottom;
        }

        return bottom;
    }

    private void addPageHeaderTitleRow(final HTMLTable header) {
        final HTMLTable.IRow titleRow = header.newTitleRow();

        final HTMLTable.ICell cell = titleRow.newCell();
        cell.setClass(CSS_TITLE);
        cell.add(getPageTitle());
    }

    private static void trimForDisplay(final StringBuffer buf) {
        if (buf.length() > MAX_DISPLAY_NAME_LENGTH) {
            buf.setLength(MAX_DISPLAY_NAME_LENGTH - 3);
            buf.append("...");
        }
    }

    /*
     * Assumes relative pathnames.
     */
    private static File getItemFile(final File parentDir, final String itemKey) {
        if (parentDir == null)
            return new File(itemKey.concat(FILE_EXTENSION));
        else
            return new File(parentDir, itemKey.concat(FILE_EXTENSION));
    }

    private static String getItemKey(IItem item) {
        final StringBuffer result = new StringBuffer();

        for (; item != null; item = item.getParent()) {
            result.append(item.getName());
            result.append(':');
        }

        return result.toString();
    }

    private static HTMLWriter openOutFile(final File file, final String encoding, final boolean mkdirs) {
        BufferedWriter out = null;
        try {
            if (mkdirs) {
                final File parent = file.getParentFile();
                if (parent != null)
                    parent.mkdirs();
            }

            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), encoding), IO_BUF_SIZE);
        } catch (UnsupportedEncodingException uee) {
            // TODO: error code
            throw new EMMARuntimeException(uee);
        }
        // note: in J2SDK 1.3 FileOutputStream constructor's throws clause
        // was narrowed to FileNotFoundException:
        catch (IOException fnfe) // FileNotFoundException  
        {
            // TODO: error code
            throw new EMMARuntimeException(fnfe);
        }

        return new HTMLWriter(out);
    }

    private static String dataCellStyle(final int column, final boolean highlight) {
        if (column == 0)
            return highlight ? CSS_DATA_HIGHLIGHT_FIRST : CSS_DATA_FIRST;
        else
            return highlight ? CSS_DATA_HIGHLIGHT : CSS_DATA;
    }

    private static String headerCellStyle(final int column) {
        return (column == 0) ? CSS_HEADER_FIRST : CSS_HEADER;
    }

    private final DecimalFormat m_format;
    private final FieldPosition m_fieldPosition;

    private LinkedList /* IITem */ m_queue;
    private IDGenerator m_reportIDNamespace;

    private IContent m_pageTitle, m_footerBottom;

    private static final boolean USE_LINE_COVERAGE_TOOLTIPS = true;

    private static final String TYPE = "html";
    private static final String REPORT_HEADER_TITLE = IAppConstants.APP_NAME + " Coverage Report";
    private static final IContent LEFT_BRACKET = new Text("[", false);
    private static final IContent RIGHT_BRACKET = new Text("]", false);

    private static final int MAX_DISPLAY_NAME_LENGTH = 80;
    private static final int SRC_LINE_OFFSET = 4;

    private static final String CSS_HEADER_FOOTER = "hdft";
    private static final String CSS_TITLE = "tl";
    private static final String CSS_NAV = "nv";

    private static final String CSS_COVERAGE_ZERO = "z";
    private static final String CSS_COVERAGE_PARTIAL = "p";
    private static final String CSS_COVERAGE_COMPLETE = "c";

    private static final String DARKER_BACKGROUND = "#F0F0F0";
    private static final String TITLE_BACKGROUND = "#6699CC";
    private static final String NAV_BACKGROUND = "#6633DD";

    private static final String CSS_INVISIBLE_TABLE = "it";

    private static final String CSS_ITEM_NAME = "in";

    private static final String CSS_CLASS_ITEM_SPECIAL = "cis";

    private static final String CSS_SOURCE = "s";
    private static final String CSS_LINENUM = "l";

    private static final String CSS_BOTTOM = "bt";
    private static final String CSS_ODDROW = "o";
    private static final String CSS_BLANK = "b";

    private static final String CSS_DATA = "";
    private static final String CSS_DATA_HIGHLIGHT = CSS_DATA + "h";
    private static final String CSS_DATA_FIRST = CSS_DATA + "f";
    private static final String CSS_DATA_HIGHLIGHT_FIRST = CSS_DATA + "hf";
    private static final String CSS_HEADER = "";
    private static final String CSS_HEADER_FIRST = CSS_HEADER + "f";

    private static final String CSS_CLS_NOLEFT = "cn";

    // TODO: optimize this

    private static final String CSS = " TABLE,TD,TH {border-style:solid; border-color:black;}"
            + " TD,TH {background:white;margin:0;line-height:100%;padding-left:0.5em;padding-right:0.5em;}" +

            " TD {border-width:0 1px 0 0;}" + " TH {border-width:1px 1px 1px 0;}" + " TR TD." + CSS_DATA_HIGHLIGHT
            + " {color:red;}" +

            " TABLE {border-spacing:0; border-collapse:collapse;border-width:0 0 1px 1px;}" +

            " P,H1,H2,H3,TH {font-family:verdana,arial,sans-serif;font-size:10pt;}"
            + " TD {font-family:courier,monospace;font-size:10pt;}" +

            " TABLE." + CSS_HEADER_FOOTER + " {border-spacing:0;border-collapse:collapse;border-style:none;}"
            + " TABLE." + CSS_HEADER_FOOTER + " TH,TABLE." + CSS_HEADER_FOOTER
            + " TD {border-style:none;line-height:normal;}" +

            " TABLE." + CSS_HEADER_FOOTER + " TH." + CSS_TITLE + ",TABLE." + CSS_HEADER_FOOTER + " TD." + CSS_TITLE
            + " {background:" + TITLE_BACKGROUND + ";color:white;}" + " TABLE." + CSS_HEADER_FOOTER + " TD."
            + CSS_NAV + " {background:" + NAV_BACKGROUND + ";color:white;}" +

            " ." + CSS_NAV + " A:link {color:white;}" + " ." + CSS_NAV + " A:visited {color:white;}" + " ."
            + CSS_NAV + " A:active {color:yellow;}" +

            " TABLE." + CSS_HEADER_FOOTER + " A:link {color:white;}" + " TABLE." + CSS_HEADER_FOOTER
            + " A:visited {color:white;}" + " TABLE." + CSS_HEADER_FOOTER + " A:active {color:yellow;}" +

            //" ." + CSS_ITEM_NAME + " {color:#6633FF;}" + 
            //" ." + CSS_ITEM_NAME + " {color:#C000E0;}" +
            " ." + CSS_ITEM_NAME + " {color:#356085;}" +

            //" A:hover {color:#0066FF; text-decoration:underline; font-style:italic}" + 

            " TABLE." + CSS_SOURCE + " TD {padding-left:0.25em;padding-right:0.25em;}" + " TABLE." + CSS_SOURCE
            + " TD." + CSS_LINENUM + " {padding-left:0.25em;padding-right:0.25em;text-align:right;background:"
            + DARKER_BACKGROUND + ";}" + " TABLE." + CSS_SOURCE + " TR." + CSS_COVERAGE_ZERO
            + " TD {background:#FF9999;}" + " TABLE." + CSS_SOURCE + " TR." + CSS_COVERAGE_PARTIAL
            + " TD {background:#FFFF88;}" + " TABLE." + CSS_SOURCE + " TR." + CSS_COVERAGE_COMPLETE
            + " TD {background:#CCFFCC;}" +

            " A:link {color:#0000EE;text-decoration:none;}" + " A:visited {color:#0000EE;text-decoration:none;}"
            + " A:hover {color:#0000EE;text-decoration:underline;}" +

            " TABLE." + CSS_CLS_NOLEFT + " {border-width:0 0 1px 0;}" + " TABLE." + CSS_SOURCE
            + " {border-width:1px 0 1px 1px;}" +

            //        " TD {border-width: 0px 1px 0px 0px; }" +
            " TD." + CSS_DATA_HIGHLIGHT + " {color:red;border-width:0 1px 0 0;}" + " TD." + CSS_DATA_FIRST
            + " {border-width:0 1px 0 1px;}" + " TD." + CSS_DATA_HIGHLIGHT_FIRST
            + " {color:red;border-width:0 1px 0 1px;}" +

            //        " TH {border-width: 1px 1px 1px 0px; }" +        
            " TH." + CSS_HEADER_FIRST + " {border-width:1px 1px 1px 1px;}" +

            " TR." + CSS_CLASS_ITEM_SPECIAL + " TD {background:" + DARKER_BACKGROUND + ";}" + " TR."
            + CSS_CLASS_ITEM_SPECIAL + " TD {border-width:1px 1px 1px 0;}" + " TR." + CSS_CLASS_ITEM_SPECIAL
            + " TD." + CSS_DATA_HIGHLIGHT + " {color:red;border-width:1px 1px 1px 0;}" + " TR."
            + CSS_CLASS_ITEM_SPECIAL + " TD." + CSS_DATA_FIRST + " {border-width:1px 1px 1px 1px;}" + " TR."
            + CSS_CLASS_ITEM_SPECIAL + " TD." + CSS_DATA_HIGHLIGHT_FIRST
            + " {color:red;border-width:1px 1px 1px 1px;}" +

            " TD." + CSS_BLANK + " {border-style:none;background:transparent;line-height:50%;} " + " TD."
            + CSS_BOTTOM + " {border-width:1px 0 0 0;background:transparent;line-height:50%;}" + " TR." + CSS_ODDROW
            + " TD {background:" + DARKER_BACKGROUND + ";}" +

            "TABLE." + CSS_INVISIBLE_TABLE + " {border-style:none;}" + "TABLE." + CSS_INVISIBLE_TABLE + " TD,TABLE."
            + CSS_INVISIBLE_TABLE + " TH {border-style:none;}" +

            "";

    private static final String NESTED_ITEMS_PARENT_DIRNAME = "_files";
    private static final File NESTED_ITEMS_PARENT_DIR = new File(NESTED_ITEMS_PARENT_DIRNAME);
    private static final int[][] NESTING; // set in <clinit>; this reflects the dir structure for the report

    private static final String FILE_EXTENSION = ".html";
    private static final int IO_BUF_SIZE = 32 * 1024;

    private static final long[] ATTRIBUTE_SETS; // set in <clinit>

    static {
        final IItemMetadata[] allTypes = IItemMetadata.Factory.getAllTypes();

        ATTRIBUTE_SETS = new long[allTypes.length];
        for (int t = 0; t < allTypes.length; ++t) {
            ATTRIBUTE_SETS[allTypes[t].getTypeID()] = allTypes[t].getAttributeIDs();
        }

        NESTING = new int[4][4];

        int base = AllItem.getTypeMetadata().getTypeID();
        NESTING[base][PackageItem.getTypeMetadata().getTypeID()] = 1;
        NESTING[base][SrcFileItem.getTypeMetadata().getTypeID()] = 1;
        NESTING[base][ClassItem.getTypeMetadata().getTypeID()] = 1;

        base = PackageItem.getTypeMetadata().getTypeID();
        NESTING[base][AllItem.getTypeMetadata().getTypeID()] = -1;

        base = SrcFileItem.getTypeMetadata().getTypeID();
        NESTING[base][AllItem.getTypeMetadata().getTypeID()] = -1;

        base = ClassItem.getTypeMetadata().getTypeID();
        NESTING[base][AllItem.getTypeMetadata().getTypeID()] = -1;
    }

    private void embedSrcFileJson(final SrcFileItem item, final SourcePathCache cache) {
        if (cache == null) {
            return;
        }
        final String fileName = item.getName();
        final String packageVMName = ((PackageItem) item.getParent()).getVMName();
        final File srcFile = cache.find(packageVMName, fileName);
        if (srcFile == null) {
            return;
        }

        File outDirFull = new File(m_settings.getOutDir(), packageVMName);
        if (!outDirFull.exists()) {
            outDirFull.mkdirs();
        }

        File outDir = null;

        if (packageVMName.isEmpty()) {
            outDir = new File(".");
        } else {
            outDir = new File(packageVMName);
        }

        File outFile = new File(outDir, fileName.concat(".json"));
        final File fullOutFile = Files.newFile(m_settings.getOutDir(), outFile);

        JSONObject json = new JSONObject();
        json.put("file", fileName);
        json.put("package", packageVMName);

        JSONArray jsonList = new JSONArray();

        BufferedReader in = null;
        try {
            in = new BufferedReader(new FileReader(srcFile), IO_BUF_SIZE);
            IntObjectMap lineCoverageMap = item.getLineCoverage();
            int l = 1;
            final int unitsType = m_settings.getUnitsType();
            for (String line; (line = in.readLine()) != null; ++l) {
                final SrcFileItem.LineCoverageData lCoverageData = (SrcFileItem.LineCoverageData) lineCoverageMap
                        .get(l);
                String covered = "";
                if (lCoverageData != null) {
                    switch (lCoverageData.m_coverageStatus) {
                    case SrcFileItem.LineCoverageData.LINE_COVERAGE_ZERO:
                        // 0 coverage
                        covered = "0";
                        break;
                    case SrcFileItem.LineCoverageData.LINE_COVERAGE_PARTIAL:
                        final int[] coverageRatio = lCoverageData.m_coverageRatio[unitsType];
                        final int d = coverageRatio[0];
                        final int n = coverageRatio[1];
                        covered = d + "/" + n;
                        // d out of n covered
                        break;
                    case SrcFileItem.LineCoverageData.LINE_COVERAGE_COMPLETE:
                        // total coverage
                        covered = "1";
                        break;
                    }
                } else {
                    covered = "-1";
                }
                JSONObject obj = new JSONObject();
                obj.put("source", line);
                obj.put("covered", covered);
                jsonList.add(obj);
            }
            json.put("lines", jsonList);
        } catch (Throwable t) {
            t.printStackTrace(System.out); // TODO: logging
        } finally {
            if (in != null)
                try {
                    in.close();
                } catch (Throwable ignore) {
                }
            in = null;
        }

        try {
            FileWriter out = new FileWriter(fullOutFile);
            out.write(json.toJSONString());
            out.flush();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
} // end of class
  // ----------------------------------------------------------------------------