Home | History | Annotate | Download | only in html
      1 /* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
      2  *
      3  * This program and the accompanying materials are made available under
      4  * the terms of the Common Public License v1.0 which accompanies this distribution,
      5  * and is available at http://www.eclipse.org/legal/cpl-v10.html
      6  *
      7  * $Id: ReportGenerator.java,v 1.2.2.1 2004/07/16 23:32:04 vlad_r Exp $
      8  */
      9 package com.vladium.emma.report.html;
     10 
     11 import java.io.BufferedReader;
     12 import java.io.BufferedWriter;
     13 import java.io.File;
     14 import java.io.FileOutputStream;
     15 import java.io.FileReader;
     16 import java.io.IOException;
     17 import java.io.OutputStreamWriter;
     18 import java.io.UnsupportedEncodingException;
     19 import java.text.DecimalFormat;
     20 import java.text.FieldPosition;
     21 import java.text.NumberFormat;
     22 import java.util.Date;
     23 import java.util.Iterator;
     24 import java.util.LinkedList;
     25 
     26 import com.vladium.util.Descriptors;
     27 import com.vladium.util.Files;
     28 import com.vladium.util.IProperties;
     29 import com.vladium.util.IntObjectMap;
     30 import com.vladium.util.IntVector;
     31 import com.vladium.util.ObjectIntMap;
     32 import com.vladium.util.Property;
     33 import com.vladium.util.asserts.$assert;
     34 import com.vladium.emma.IAppConstants;
     35 import com.vladium.emma.IAppErrorCodes;
     36 import com.vladium.emma.EMMAProperties;
     37 import com.vladium.emma.EMMARuntimeException;
     38 import com.vladium.emma.data.ICoverageData;
     39 import com.vladium.emma.data.IMetaData;
     40 import com.vladium.emma.report.AbstractReportGenerator;
     41 import com.vladium.emma.report.AllItem;
     42 import com.vladium.emma.report.ClassItem;
     43 import com.vladium.emma.report.IItem;
     44 import com.vladium.emma.report.IItemAttribute;
     45 import com.vladium.emma.report.IItemMetadata;
     46 import com.vladium.emma.report.ItemComparator;
     47 import com.vladium.emma.report.MethodItem;
     48 import com.vladium.emma.report.PackageItem;
     49 import com.vladium.emma.report.SourcePathCache;
     50 import com.vladium.emma.report.SrcFileItem;
     51 import com.vladium.emma.report.html.doc.*;
     52 
     53 // ----------------------------------------------------------------------------
     54 /**
     55  * @author Vlad Roubtsov, (C) 2003
     56  */
     57 public
     58 final class ReportGenerator extends AbstractReportGenerator
     59                             implements IAppErrorCodes
     60 {
     61     // public: ................................................................
     62 
     63     // TODO: make sure relative file names are converted to relative URLs in all anchors/hrefs
     64 
     65     public ReportGenerator ()
     66     {
     67         m_format = (DecimalFormat) NumberFormat.getPercentInstance (); // TODO: locale
     68         m_fieldPosition = new FieldPosition (DecimalFormat.INTEGER_FIELD);
     69 
     70         m_format.setMaximumFractionDigits (0);
     71     }
     72 
     73 
     74     // IReportGenerator:
     75 
     76     public final String getType ()
     77     {
     78         return TYPE;
     79     }
     80 
     81     public void process (final IMetaData mdata, final ICoverageData cdata,
     82                          final SourcePathCache cache, final IProperties properties)
     83         throws EMMARuntimeException
     84     {
     85         initialize (mdata, cdata, cache, properties);
     86 
     87         m_pageTitle = null;
     88         m_footerBottom = null;
     89 
     90         File outDir = m_settings.getOutDir ();
     91         if ((outDir == null) /* this should never happen */ || (outDir.equals (new File (Property.getSystemProperty ("user.dir", "")))))
     92         {
     93             outDir = new File ("coverage");
     94             m_settings.setOutDir (outDir);
     95         }
     96 
     97         long start = 0, end;
     98         final boolean trace1 = m_log.atTRACE1 ();
     99 
    100         if (trace1) start = System.currentTimeMillis ();
    101 
    102         {
    103             m_queue = new LinkedList ();
    104             m_reportIDNamespace = new IDGenerator (mdata.size ());
    105 
    106             for (m_queue.add (m_view.getRoot ()); ! m_queue.isEmpty (); )
    107             {
    108                 final IItem head = (IItem) m_queue.removeFirst ();
    109 
    110                 head.accept (this, null);
    111             }
    112 
    113             m_reportIDNamespace = null;
    114         }
    115 
    116         if (trace1)
    117         {
    118             end = System.currentTimeMillis ();
    119 
    120             m_log.trace1 ("process", "[" + getType () + "] report generated in " + (end - start) + " ms");
    121         }
    122     }
    123 
    124     public void cleanup ()
    125     {
    126         m_queue = null;
    127         m_reportIDNamespace = null;
    128 
    129         super.cleanup ();
    130     }
    131 
    132 
    133     // IItemVisitor:
    134 
    135     public Object visit (final AllItem item, final Object ctx)
    136     {
    137         HTMLWriter out = null;
    138         try
    139         {
    140             File outFile = m_settings.getOutFile ();
    141             if (outFile == null)
    142             {
    143                 outFile = new File ("index".concat (FILE_EXTENSION));
    144                 m_settings.setOutFile (outFile);
    145             }
    146 
    147             final File fullOutFile = Files.newFile (m_settings.getOutDir (), outFile);
    148 
    149             m_log.info ("writing [" + getType () + "] report to [" + fullOutFile.getAbsolutePath () + "] ...");
    150 
    151             out = openOutFile (fullOutFile, m_settings.getOutEncoding (), true);
    152 
    153             final int [] columns = m_settings.getColumnOrder ();
    154             final StringBuffer buf = new StringBuffer ();
    155 
    156             final String title;
    157             {
    158                 final StringBuffer _title = new StringBuffer (REPORT_HEADER_TITLE);
    159 
    160                 _title.append (" (generated ");
    161                 _title.append (new Date (EMMAProperties.getTimeStamp ()));
    162                 _title.append (')');
    163 
    164                 title = _title.toString ();
    165             }
    166 
    167             final HTMLDocument page = createPage (title);
    168             {
    169                 final IItem [] path = getParentPath (item);
    170 
    171                 addPageHeader (page, item, path);
    172                 addPageFooter (page, item, path);
    173             }
    174 
    175             // [all] coverage summary table:
    176 
    177             page.addH (1, "OVERALL COVERAGE SUMMARY", null);
    178 
    179             final HTMLTable summaryTable = new HTMLTable ("100%", null, null, "0");
    180             {
    181                 // header row:
    182                 final HTMLTable.IRow header = summaryTable.newTitleRow ();
    183                 // coverage row:
    184                 final HTMLTable.IRow coverage = summaryTable.newRow ();
    185 
    186                 for (int c = 0; c < columns.length; ++ c)
    187                 {
    188                     final int attrID = columns [c];
    189                     final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
    190 
    191                     final HTMLTable.ICell headercell = header.newCell ();
    192                     headercell.setText (attr.getName (), true);
    193 
    194                     if (attr != null)
    195                     {
    196                         boolean fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]);
    197 
    198                         buf.setLength (0);
    199                         attr.format (item, buf);
    200 
    201                         final HTMLTable.ICell cell = coverage.newCell ();
    202                         cell.setText (buf.toString (), true);
    203                         if (fail) cell.setClass (CSS_DATA_HIGHLIGHT);
    204                     }
    205                 }
    206             }
    207             page.add (summaryTable);
    208 
    209             // [all] stats summary table ([all] only):
    210 
    211             page.addH (2, "OVERALL STATS SUMMARY", null);
    212 
    213             final HTMLTable statsTable = new HTMLTable (null, null, null, "0");
    214             statsTable.setClass (CSS_INVISIBLE_TABLE);
    215             {
    216                 HTMLTable.IRow row = statsTable.newRow ();
    217                 row.newCell ().setText ("total packages:", true);
    218                 row.newCell ().setText ("" + item.getChildCount (), false);
    219 
    220                 if (m_srcView && m_hasSrcFileInfo)
    221                 {
    222                     row = statsTable.newRow ();
    223                     row.newCell ().setText ("total executable files:", true);
    224                     row.newCell ().setText ("" + item.getAggregate (IItem.TOTAL_SRCFILE_COUNT), false);
    225                 }
    226 
    227                 row = statsTable.newRow ();
    228                 row.newCell ().setText ("total classes:", true);
    229                 row.newCell ().setText ("" + item.getAggregate (IItem.TOTAL_CLASS_COUNT), true);
    230                 row = statsTable.newRow ();
    231                 row.newCell ().setText ("total methods:", true);
    232                 row.newCell ().setText ("" + item.getAggregate (IItem.TOTAL_METHOD_COUNT), true);
    233 
    234                 if (m_srcView && m_hasSrcFileInfo && m_hasLineNumberInfo)
    235                 {
    236                     row = statsTable.newRow ();
    237                     row.newCell ().setText ("total executable lines:", true);
    238                     row.newCell ().setText ("" + item.getAggregate (IItem.TOTAL_LINE_COUNT), true);
    239                 }
    240             }
    241             /*
    242             {
    243                 final HTMLTable.IRow first = statsTable.newRow (); // stats always available
    244 
    245                 first.newCell ().setText ("total packages: " + item.getChildCount (), true);
    246                 first.newCell ().setText ("total classes: " + item.getAggregate (IItem.TOTAL_CLASS_COUNT), true);
    247                 first.newCell ().setText ("total methods: " + item.getAggregate (IItem.TOTAL_METHOD_COUNT), true);
    248 
    249                 if (m_srcView && m_hasSrcFileInfo)
    250                 {
    251                     final HTMLTable.IRow second = statsTable.newRow ();
    252 
    253                     final HTMLTable.ICell cell1 = second.newCell ();
    254                     cell1.setText ("total source files: " + item.getAggregate (IItem.TOTAL_SRCFILE_COUNT), true);
    255 
    256                     if (m_hasLineNumberInfo)
    257                     {
    258                         final HTMLTable.ICell cell2 = second.newCell ();
    259 
    260                         cell2.setText ("total executable source lines: " + item.getAggregate (IItem.TOTAL_LINE_COUNT), true);
    261                         cell2.getAttributes ().set (Attribute.COLSPAN, "2");
    262                     }
    263                     else
    264                     {
    265                         cell1.getAttributes ().set (Attribute.COLSPAN, "3");
    266                     }
    267                 }
    268             }
    269             */
    270             page.add (statsTable);
    271 
    272             final boolean deeper = (m_settings.getDepth () > item.getMetadata ().getTypeID ());
    273 
    274             // render package summary tables on the same page:
    275 
    276             page.addH (2, "COVERAGE BREAKDOWN BY PACKAGE", null);
    277 
    278             final HTMLTable childSummaryTable = new HTMLTable ("100%", null, null, "0");
    279             {
    280                 int [] headerColumns = null;
    281 
    282                 boolean odd = true;
    283                 final ItemComparator order = m_typeSortComparators [PackageItem.getTypeMetadata ().getTypeID ()];
    284                 for (Iterator packages = item.getChildren (order); packages.hasNext (); odd = ! odd)
    285                 {
    286                     final IItem pkg = (IItem) packages.next ();
    287 
    288                     if (headerColumns == null)
    289                     {
    290                         // header row:
    291                         headerColumns = addHeaderRow (pkg, childSummaryTable, columns);
    292                     }
    293 
    294                     // coverage row:
    295                     String childHREF = null;
    296                     if (deeper)
    297                     {
    298                         childHREF = getItemHREF (item, pkg);
    299                     }
    300                     addItemRow (pkg, odd, childSummaryTable, headerColumns, childHREF, false);
    301 
    302                     if (deeper) m_queue.addLast (pkg);
    303                 }
    304             }
    305             page.add (childSummaryTable);
    306 
    307 
    308             page.emit (out);
    309             out.flush ();
    310         }
    311         finally
    312         {
    313             if (out != null) out.close ();
    314             out = null;
    315         }
    316 
    317         return ctx;
    318     }
    319 
    320     public Object visit (final PackageItem item, final Object ctx)
    321     {
    322         HTMLWriter out = null;
    323         try
    324         {
    325             if (m_verbose) m_log.verbose ("  report: processing package [" + item.getName () + "] ...");
    326 
    327             final File outFile = getItemFile (NESTED_ITEMS_PARENT_DIR, m_reportIDNamespace.getID (getItemKey (item)));
    328 
    329             out = openOutFile (Files.newFile (m_settings.getOutDir (), outFile), m_settings.getOutEncoding (), true);
    330 
    331             final int [] columns = m_settings.getColumnOrder ();
    332             final StringBuffer buf = new StringBuffer ();
    333 
    334             // TODO: set title [from a prop?]
    335             final HTMLDocument page = createPage (REPORT_HEADER_TITLE);
    336             {
    337                 final IItem [] path = getParentPath (item);
    338 
    339                 addPageHeader (page, item, path);
    340                 addPageFooter (page, item, path);
    341             }
    342 
    343             // summary table:
    344 
    345             {
    346                 final IElement itemname = IElement.Factory.create (Tag.SPAN);
    347                 itemname.setText (item.getName (), true);
    348                 itemname.setClass (CSS_ITEM_NAME);
    349 
    350                 final IElementList title = new ElementList ();
    351                 title.add (new Text ("COVERAGE SUMMARY FOR PACKAGE [", true));
    352                 title.add (itemname);
    353                 title.add (new Text ("]", true));
    354 
    355                 page.addH (1, title, null);
    356             }
    357 
    358             final HTMLTable summaryTable = new HTMLTable ("100%", null, null, "0");
    359             {
    360                 // header row:
    361                 final HTMLTable.IRow header = summaryTable.newTitleRow ();
    362                 // coverage row:
    363                 final HTMLTable.IRow coverage = summaryTable.newRow ();
    364 
    365                 for (int c = 0; c < columns.length; ++ c)
    366                 {
    367                     final int attrID = columns [c];
    368                     final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
    369 
    370                     final HTMLTable.ICell headercell = header.newCell ();
    371                     headercell.setText (attr.getName (), true);
    372 
    373                     if (attr != null)
    374                     {
    375                         boolean fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]);
    376 
    377                         buf.setLength (0);
    378                         attr.format (item, buf);
    379 
    380                         final HTMLTable.ICell cell = coverage.newCell ();
    381                         cell.setText (buf.toString (), true);
    382                         if (fail) cell.setClass (CSS_DATA_HIGHLIGHT);
    383                     }
    384                 }
    385             }
    386             page.add (summaryTable);
    387 
    388             final boolean deeper = (m_settings.getDepth () > item.getMetadata ().getTypeID ());
    389 
    390             // render child summary tables on the same page:
    391 
    392             final String summaryTitle = m_srcView ? "COVERAGE BREAKDOWN BY SOURCE FILE" : "COVERAGE BREAKDOWN BY CLASS";
    393             page.addH (2, summaryTitle, null);
    394 
    395             final HTMLTable childSummaryTable = new HTMLTable ("100%", null, null, "0");
    396             {
    397                 int [] headerColumns = null;
    398 
    399                 boolean odd = true;
    400                 final ItemComparator order = m_typeSortComparators [m_srcView ? SrcFileItem.getTypeMetadata ().getTypeID () : ClassItem.getTypeMetadata ().getTypeID ()];
    401                 for (Iterator srcORclsFiles = item.getChildren (order); srcORclsFiles.hasNext (); odd = ! odd)
    402                 {
    403                     final IItem srcORcls = (IItem) srcORclsFiles.next ();
    404 
    405                     if (headerColumns == null)
    406                     {
    407                         // header row:
    408                         headerColumns = addHeaderRow (srcORcls, childSummaryTable, columns);
    409                     }
    410 
    411                     // coverage row:
    412                     String childHREF = null;
    413                     if (deeper)
    414                     {
    415                         childHREF = getItemHREF (item, srcORcls);
    416                     }
    417                     addItemRow (srcORcls, odd, childSummaryTable, headerColumns, childHREF, false);
    418 
    419                     if (deeper) m_queue.addLast (srcORcls);
    420                 }
    421             }
    422             page.add (childSummaryTable);
    423 
    424 
    425             page.emit (out);
    426             out.flush ();
    427         }
    428         finally
    429         {
    430             if (out != null) out.close ();
    431             out = null;
    432         }
    433 
    434         return ctx;
    435     }
    436 
    437     public Object visit (final SrcFileItem item, final Object ctx)
    438     {
    439         // this visit only takes place in src views
    440 
    441         HTMLWriter out = null;
    442         try
    443         {
    444             final File outFile = getItemFile (NESTED_ITEMS_PARENT_DIR, m_reportIDNamespace.getID (getItemKey (item)));
    445 
    446             out = openOutFile (Files.newFile (m_settings.getOutDir (), outFile), m_settings.getOutEncoding (), true);
    447 
    448             final int [] columns = m_settings.getColumnOrder ();
    449             final StringBuffer buf = new StringBuffer ();
    450 
    451             // TODO: set title [from a prop?]
    452             final HTMLDocument page = createPage (REPORT_HEADER_TITLE);
    453             {
    454                 final IItem [] path = getParentPath (item);
    455 
    456                 addPageHeader (page, item, path);
    457                 addPageFooter (page, item, path);
    458             }
    459 
    460             // summary table:
    461 
    462             {
    463                 final IElement itemname = IElement.Factory.create (Tag.SPAN);
    464                 itemname.setText (item.getName (), true);
    465                 itemname.setClass (CSS_ITEM_NAME);
    466 
    467                 final IElementList title = new ElementList ();
    468                 title.add (new Text ("COVERAGE SUMMARY FOR SOURCE FILE [", true));
    469                 title.add (itemname);
    470                 title.add (new Text ("]", true));
    471 
    472                 page.addH (1, title, null);
    473             }
    474 
    475             final HTMLTable summaryTable = new HTMLTable ("100%", null, null, "0");
    476             {
    477                 // header row:
    478                 final HTMLTable.IRow header = summaryTable.newTitleRow ();
    479                 // coverage row:
    480                 final HTMLTable.IRow coverage = summaryTable.newRow ();
    481 
    482                 for (int c = 0; c < columns.length; ++ c)
    483                 {
    484                     final int attrID = columns [c];
    485                     final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
    486 
    487                     final HTMLTable.ICell headercell = header.newCell ();
    488                     headercell.setText (attr.getName (), true);
    489 
    490                     if (attr != null)
    491                     {
    492                         boolean fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]);
    493 
    494                         buf.setLength (0);
    495                         attr.format (item, buf);
    496 
    497                         final HTMLTable.ICell cell = coverage.newCell ();
    498                         cell.setText (buf.toString (), true);
    499                         if (fail) cell.setClass (CSS_DATA_HIGHLIGHT);
    500                     }
    501                 }
    502             }
    503             page.add (summaryTable);
    504 
    505             final boolean deeper = (m_settings.getDepth () > ClassItem.getTypeMetadata ().getTypeID ());
    506             final boolean embedSrcFile = deeper && srcFileAvailable (item, m_cache);
    507             final boolean createAnchors = embedSrcFile && m_hasLineNumberInfo;
    508 
    509             final IDGenerator pageIDNamespace = createAnchors ? new IDGenerator () : null;
    510 
    511             // child summary table is special for srcfile items:
    512 
    513             page.addH (2, "COVERAGE BREAKDOWN BY CLASS AND METHOD", null);
    514 
    515             final IntObjectMap lineAnchorIDMap = embedSrcFile ? new IntObjectMap () : null;
    516             final HTMLTable childSummaryTable = new HTMLTable ("100%", null, null, "0");
    517 
    518             childSummaryTable.setClass (CSS_CLS_NOLEFT);
    519 
    520             {
    521                 int [] headerColumns = null;
    522 
    523                 final ItemComparator order = m_typeSortComparators [ClassItem.getTypeMetadata ().getTypeID ()];
    524                 int clsIndex = 0;
    525                 for (Iterator classes = item.getChildren (order); classes.hasNext (); ++ clsIndex)
    526                 {
    527                     final ClassItem cls = (ClassItem) classes.next ();
    528 
    529                     if (headerColumns == null)
    530                     {
    531                         // header row:
    532                         headerColumns = addHeaderRow (cls, childSummaryTable, columns);
    533                     }
    534 
    535                     String HREFname = null;
    536 
    537                     // special class subheader:
    538                     if (createAnchors)
    539                     {
    540                         if ($assert.ENABLED)
    541                         {
    542                             $assert.ASSERT (lineAnchorIDMap != null);
    543                             $assert.ASSERT (pageIDNamespace != null);
    544                         }
    545 
    546                         final String childKey = getItemKey (cls);
    547 
    548                         HREFname = addLineAnchorID (cls.getFirstLine (), pageIDNamespace.getID (childKey), lineAnchorIDMap);
    549                     }
    550 
    551                     addClassRow (cls, clsIndex, childSummaryTable, headerColumns, HREFname, createAnchors);
    552 
    553 //                    // row to separate this class's methods:
    554 //                    final HTMLTable.IRow subheader = childSummaryTable.newTitleRow ();
    555 //                    final HTMLTable.ICell cell = subheader.newCell ();
    556 //                    // TODO: cell.setColspan (???)
    557 //                    cell.setText ("class " + child.getName () + " methods:", true);
    558 
    559                     boolean odd = false;
    560                     final ItemComparator order2 = m_typeSortComparators [MethodItem.getTypeMetadata ().getTypeID ()];
    561                     for (Iterator methods = cls.getChildren (order2); methods.hasNext (); odd = ! odd)
    562                     {
    563                         final MethodItem method = (MethodItem) methods.next ();
    564 
    565                         HREFname = null;
    566 
    567                         if (createAnchors)
    568                         {
    569                             if ($assert.ENABLED)
    570                             {
    571                                 $assert.ASSERT (lineAnchorIDMap != null);
    572                                 $assert.ASSERT (pageIDNamespace != null);
    573                             }
    574 
    575                             final String child2Key = getItemKey (method);
    576 
    577                             HREFname = addLineAnchorID (method.getFirstLine (), pageIDNamespace.getID (child2Key), lineAnchorIDMap);
    578                         }
    579 
    580                         addClassItemRow (method, odd, childSummaryTable, headerColumns, HREFname, createAnchors);
    581                     }
    582                 }
    583             }
    584             page.add (childSummaryTable);
    585 
    586 
    587             // embed source file:
    588 
    589             if (deeper)
    590             {
    591                 //page.addHR (1);
    592                 page.addEmptyP ();
    593                 {
    594                     embedSrcFile (item, page, lineAnchorIDMap, m_cache);
    595                 }
    596                 //page.addHR (1);
    597             }
    598 
    599 
    600             page.emit (out);
    601             out.flush ();
    602         }
    603         finally
    604         {
    605             if (out != null) out.close ();
    606             out = null;
    607         }
    608 
    609         return ctx;
    610     }
    611 
    612     public Object visit (final ClassItem item, final Object ctx)
    613     {
    614         // this visit only takes place in class views
    615 
    616         HTMLWriter out = null;
    617         try
    618         {
    619             final File outFile = getItemFile (NESTED_ITEMS_PARENT_DIR, m_reportIDNamespace.getID (getItemKey (item)));
    620 
    621             // TODO: deal with overwrites
    622             out = openOutFile (Files.newFile (m_settings.getOutDir (), outFile), m_settings.getOutEncoding (), true);
    623 
    624             final int [] columns = m_settings.getColumnOrder ();
    625             final StringBuffer buf = new StringBuffer ();
    626 
    627             // TODO: set title [from a prop?]
    628             final HTMLDocument page = createPage (REPORT_HEADER_TITLE);
    629             {
    630                 final IItem [] path = getParentPath (item);
    631 
    632                 addPageHeader (page, item, path);
    633                 addPageFooter (page, item, path);
    634             }
    635 
    636 
    637             // summary table:
    638 
    639             {
    640                 final IElement itemname = IElement.Factory.create (Tag.SPAN);
    641                 itemname.setText (item.getName (), true);
    642                 itemname.setClass (CSS_ITEM_NAME);
    643 
    644                 final IElementList title = new ElementList ();
    645                 title.add (new Text ("COVERAGE SUMMARY FOR CLASS [", true));
    646                 title.add (itemname);
    647                 title.add (new Text ("]", true));
    648 
    649                 page.addH (1, title, null);
    650             }
    651 
    652             final HTMLTable summaryTable = new HTMLTable ("100%", null, null, "0");
    653             {
    654                 // header row:
    655                 final HTMLTable.IRow header = summaryTable.newTitleRow ();
    656                 // coverage row:
    657                 final HTMLTable.IRow coverage = summaryTable.newRow ();
    658 
    659                 for (int c = 0; c < columns.length; ++ c)
    660                 {
    661                     final int attrID = columns [c];
    662                     final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
    663 
    664                     final HTMLTable.ICell headercell = header.newCell ();
    665                     headercell.setText (attr.getName (), true);
    666 
    667                     if (attr != null)
    668                     {
    669                         boolean fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]);
    670 
    671                         buf.setLength (0);
    672                         attr.format (item, buf);
    673 
    674                         final HTMLTable.ICell cell = coverage.newCell ();
    675                         cell.setText (buf.toString (), true);
    676                         if (fail) cell.setClass (CSS_DATA_HIGHLIGHT);
    677                     }
    678                 }
    679             }
    680             page.add (summaryTable);
    681 
    682 
    683             // child summary table:
    684 
    685             page.addH (2, "COVERAGE BREAKDOWN BY METHOD", null);
    686 
    687             final HTMLTable childSummaryTable = new HTMLTable ("100%", null, null, "0");
    688             {
    689                 int [] headerColumns = null;
    690 
    691                 boolean odd = true;
    692                 final ItemComparator order = m_typeSortComparators [MethodItem.getTypeMetadata ().getTypeID ()];
    693                 for (Iterator methods = item.getChildren (order); methods.hasNext (); odd = ! odd)
    694                 {
    695                     final MethodItem method = (MethodItem) methods.next ();
    696 
    697                     if (headerColumns == null)
    698                     {
    699                         // header row:
    700                         headerColumns = addHeaderRow (method, childSummaryTable, columns);
    701                     }
    702 
    703                     addItemRow (method, odd, childSummaryTable, headerColumns, null, false);
    704                 }
    705             }
    706             page.add (childSummaryTable);
    707 
    708 
    709             page.emit (out);
    710             out.flush ();
    711         }
    712         finally
    713         {
    714             if (out != null) out.close ();
    715             out = null;
    716         }
    717 
    718         return ctx;
    719     }
    720 
    721     // protected: .............................................................
    722 
    723     // package: ...............................................................
    724 
    725     // private: ...............................................................
    726 
    727 
    728     private static final class IDGenerator
    729     {
    730         IDGenerator ()
    731         {
    732             m_namespace = new ObjectIntMap (101);
    733             m_out = new int [1];
    734         }
    735 
    736         IDGenerator (final int initialCapacity)
    737         {
    738             m_namespace = new ObjectIntMap (initialCapacity);
    739             m_out = new int [1];
    740         }
    741 
    742         String getID (final String key)
    743         {
    744             final int [] out = m_out;
    745             final int ID;
    746 
    747             if (m_namespace.get (key, out))
    748                 ID = out [0];
    749             else
    750             {
    751                 ID = m_namespace.size ();
    752                 m_namespace.put (key, ID);
    753             }
    754 
    755             return Integer.toHexString (ID);
    756         }
    757 
    758         private final ObjectIntMap /* key:String->ID */ m_namespace;
    759         private final int [] m_out;
    760 
    761     } // end of nested class
    762 
    763 
    764     private HTMLDocument createPage (final String title)
    765     {
    766         final HTMLDocument page = new HTMLDocument (title, m_settings.getOutEncoding ());
    767         page.addStyle (CSS); // TODO: split by visit type
    768 
    769         return page;
    770     }
    771 
    772     private IElement addPageHeader (final HTMLDocument page, final IItem item, final IItem [] path)
    773     {
    774         // TODO: merge header and footer in the same method
    775 
    776         if ($assert.ENABLED)
    777         {
    778             $assert.ASSERT (page != null);
    779         }
    780 
    781         final HTMLTable header = new HTMLTable ("100%", null, null, "0");
    782         header.setClass (CSS_HEADER_FOOTER);
    783 
    784         // header row:
    785         addPageHeaderTitleRow (header);
    786 
    787         // nav row:
    788         {
    789             final HTMLTable.IRow navRow = header.newRow ();
    790 
    791             final HTMLTable.ICell cell = navRow.newCell ();
    792             cell.setClass (CSS_NAV);
    793 
    794             final int lLimit = path.length > 1 ? path.length - 1 : path.length;
    795             for (int l = 0; l < lLimit; ++ l)
    796             {
    797                 cell.add (LEFT_BRACKET);
    798 
    799                 final String name = path [l].getName ();
    800                 final String HREF = getItemHREF (item, path [l]);
    801                 cell.add (new HyperRef (HREF, name, true));
    802 
    803                 cell.add (RIGHT_BRACKET);
    804             }
    805         }
    806 
    807         page.setHeader (header);
    808 
    809         return header;
    810     }
    811 
    812     private IElement addPageFooter (final HTMLDocument page, final IItem item, final IItem [] path)
    813     {
    814         if ($assert.ENABLED)
    815         {
    816             $assert.ASSERT (page != null);
    817         }
    818 
    819         final HTMLTable footerTable = new HTMLTable ("100%", null, null, "0");
    820         footerTable.setClass (CSS_HEADER_FOOTER);
    821 
    822         // nav row:
    823         {
    824             final HTMLTable.IRow navRow = footerTable.newRow ();
    825 
    826             final HTMLTable.ICell cell = navRow.newCell ();
    827             cell.setClass (CSS_NAV);
    828 
    829             final int lLimit = path.length > 1 ? path.length - 1 : path.length;
    830             for (int l = 0; l < lLimit; ++ l)
    831             {
    832                 cell.add (LEFT_BRACKET);
    833 
    834                 final String name = path [l].getName ();
    835                 final String HREF = getItemHREF (item, path [l]);
    836                 cell.add (new HyperRef (HREF, name, true));
    837 
    838                 cell.add (RIGHT_BRACKET);
    839             }
    840         }
    841 
    842         // title row:
    843         {
    844             final HTMLTable.IRow titleRow = footerTable.newRow ();
    845 
    846             final HTMLTable.ICell cell = titleRow.newCell ();
    847             cell.setClass (CSS_TITLE);
    848 
    849             cell.add (getFooterBottom ());
    850         }
    851 
    852         final ElementList footer = new ElementList ();
    853 
    854         footer.add (IElement.Factory.create (Tag.P)); // spacer
    855         footer.add (footerTable);
    856 
    857         page.setFooter (footer);
    858 
    859         return footerTable;
    860     }
    861 
    862     private int [] addHeaderRow (final IItem item, final HTMLTable table, final int [] columns)
    863     {
    864         if ($assert.ENABLED)
    865         {
    866             $assert.ASSERT (item != null, "null input: item");
    867             $assert.ASSERT (table != null, "null input: table");
    868             $assert.ASSERT (columns != null, "null input: columns");
    869         }
    870 
    871         // header row:
    872         final HTMLTable.IRow header = table.newTitleRow ();
    873 
    874         // determine the set of columns actually present in the header [may be narrower than 'columns']:
    875         final IntVector headerColumns = new IntVector (columns.length);
    876 
    877         for (int c = 0; c < columns.length; ++ c)
    878         {
    879             final int attrID = columns [c];
    880             final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
    881 
    882             if (attr != null)
    883             {
    884                 final HTMLTable.ICell cell = header.newCell ();
    885 
    886                 cell.setText (attr.getName (), true);//.getAttributes ().set (Attribute.WIDTH, "20%");
    887                 cell.setClass (headerCellStyle (c));
    888                 headerColumns.add (attrID);
    889             }
    890 
    891             // note: by design this does not create columns for nonexistent attribute types
    892         }
    893 
    894         return headerColumns.values ();
    895     }
    896 
    897     /*
    898      * No header row, just data rows.
    899      */
    900     private void addItemRow (final IItem item, final boolean odd, final HTMLTable table, final int [] columns, final String nameHREF, final boolean anchor)
    901     {
    902         if ($assert.ENABLED)
    903         {
    904             $assert.ASSERT (item != null, "null input: item");
    905             $assert.ASSERT (table != null, "null input: table");
    906             $assert.ASSERT (columns != null, "null input: columns");
    907         }
    908 
    909         final HTMLTable.IRow row = table.newRow ();
    910         if (odd) row.setClass (CSS_ODDROW);
    911 
    912         final StringBuffer buf = new StringBuffer (11); // TODO: reuse a buffer
    913 
    914         for (int c = 0; c < columns.length; ++ c)
    915         {
    916             final int attrID = columns [c];
    917             final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
    918 
    919             if (attr != null)
    920             {
    921                 final HTMLTable.ICell cell = row.newCell ();
    922 
    923                 if ((nameHREF != null) && (attrID == IItemAttribute.ATTRIBUTE_NAME_ID))
    924                 {
    925                     buf.setLength (0);
    926                     attr.format (item, buf);
    927 
    928                     trimForDisplay (buf);
    929 
    930                     final String fullHREFName = anchor ? "#".concat (nameHREF) : nameHREF;
    931 
    932                     cell.add (new HyperRef (fullHREFName, buf.toString (), true));
    933                 }
    934                 else
    935                 {
    936                     final boolean fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]);
    937 
    938                     buf.setLength (0);
    939                     attr.format (item, buf);
    940 
    941                     trimForDisplay (buf);
    942 
    943                     cell.setText (buf.toString (), true);
    944                     if (fail) cell.setClass (CSS_DATA_HIGHLIGHT);
    945                 }
    946             }
    947             else
    948             {
    949                 // note: by design this puts empty cells for nonexistent attribute types
    950 
    951                 final HTMLTable.ICell cell = row.newCell ();
    952                 cell.setText (" ", true);
    953             }
    954         }
    955     }
    956 
    957     private void addClassRow (final ClassItem item, final int clsIndex, final HTMLTable table, final int [] columns,
    958                               final String itemHREF, final boolean isAnchor)
    959     {
    960         if ($assert.ENABLED)
    961         {
    962             $assert.ASSERT (item != null, "null input: item");
    963             $assert.ASSERT (table != null, "null input: table");
    964             $assert.ASSERT (columns != null, "null input: columns");
    965         }
    966 
    967         final HTMLTable.IRow blank = table.newRow ();
    968 
    969         final HTMLTable.IRow row = table.newRow ();
    970         row.setClass (CSS_CLASS_ITEM_SPECIAL);
    971 
    972         final StringBuffer buf = new StringBuffer (11);
    973 
    974         for (int c = 0; c < columns.length; ++ c)
    975         {
    976             final int attrID = columns [c];
    977             final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
    978 
    979             if (attr != null)
    980             {
    981                 buf.setLength (0);
    982                 attr.format (item, buf);
    983 
    984                 final HTMLTable.ICell blankcell = blank.newCell ();
    985                 blankcell.setClass (clsIndex == 0 ? CSS_BLANK : CSS_BOTTOM);
    986                 blankcell.setText (" ", true);
    987 
    988                 final HTMLTable.ICell cell = row.newCell ();
    989 
    990                 boolean fail = false;
    991                 if (attrID == IItemAttribute.ATTRIBUTE_NAME_ID)
    992                 {
    993                     if (itemHREF != null)
    994                     {
    995                         final String fullItemHREF = isAnchor ? "#".concat (itemHREF) : itemHREF;
    996 
    997                         cell.add (new Text ("class ", true));
    998                         cell.add (new HyperRef (fullItemHREF, buf.toString (), true));
    999                     }
   1000                     else
   1001                     {
   1002                         cell.setText ("class " + buf.toString (), true);
   1003                     }
   1004                 }
   1005                 else
   1006                 {
   1007                     fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]);
   1008 
   1009                     cell.setText (buf.toString (), true);
   1010                 }
   1011 
   1012                 cell.setClass (dataCellStyle (c, fail));
   1013             }
   1014             else
   1015             {
   1016                 final HTMLTable.ICell cell = row.newCell ();
   1017                 cell.setText (" ", true);
   1018                 cell.setClass (dataCellStyle (c, false));
   1019             }
   1020         }
   1021     }
   1022 
   1023 
   1024     private void addClassItemRow (final IItem item, final boolean odd, final HTMLTable table, final int [] columns, final String nameHREF, final boolean anchor)
   1025     {
   1026         if ($assert.ENABLED)
   1027         {
   1028             $assert.ASSERT (item != null, "null input: item");
   1029             $assert.ASSERT (table != null, "null input: table");
   1030             $assert.ASSERT (columns != null, "null input: columns");
   1031         }
   1032 
   1033         final HTMLTable.IRow row = table.newRow ();
   1034         if (odd) row.setClass (CSS_ODDROW);
   1035 
   1036         final StringBuffer buf = new StringBuffer (11); // TODO: reuse a buffer
   1037 
   1038         for (int c = 0; c < columns.length; ++ c)
   1039         {
   1040             final int attrID = columns [c];
   1041             final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
   1042 
   1043             if (attr != null)
   1044             {
   1045                 final HTMLTable.ICell cell = row.newCell ();
   1046 
   1047                 boolean fail = false;
   1048                 if ((nameHREF != null) && (attrID == IItemAttribute.ATTRIBUTE_NAME_ID))
   1049                 {
   1050                     buf.setLength (0);
   1051                     attr.format (item, buf);
   1052 
   1053                     trimForDisplay (buf);
   1054 
   1055                     final String fullHREFName = anchor ? "#".concat (nameHREF) : nameHREF;
   1056 
   1057                     cell.add (new HyperRef (fullHREFName, buf.toString (), true));
   1058                 }
   1059                 else
   1060                 {
   1061                     fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]);
   1062 
   1063                     buf.setLength (0);
   1064                     attr.format (item, buf);
   1065 
   1066                     trimForDisplay (buf);
   1067 
   1068                     cell.setText (buf.toString (), true);
   1069                 }
   1070 
   1071                 cell.setClass (dataCellStyle (c, fail));
   1072             }
   1073             else
   1074             {
   1075                 // note: by design this puts empty cells for nonexistent attribute types
   1076 
   1077                 final HTMLTable.ICell cell = row.newCell ();
   1078                 cell.setText (" ", true);
   1079                 cell.setClass (dataCellStyle (c, false));
   1080             }
   1081         }
   1082     }
   1083 
   1084 
   1085     private boolean srcFileAvailable (final SrcFileItem item, final SourcePathCache cache)
   1086     {
   1087         if (cache == null) return false;
   1088 
   1089         if ($assert.ENABLED) $assert.ASSERT (item != null, "null input: item");
   1090 
   1091         final String fileName = item.getName ();
   1092         if ($assert.ENABLED) $assert.ASSERT (fileName.endsWith (".java"), "cache only handles .java extensions");
   1093 
   1094         // TODO: should I keep VM names in package items?
   1095         final String packageVMName = ((PackageItem) item.getParent ()).getVMName ();
   1096 
   1097         return (cache.find (packageVMName, fileName) != null);
   1098     }
   1099 
   1100 //    private boolean srcFileAvailable (final ClassItem item, final SourcePathCache cache)
   1101 //    {
   1102 //        if (cache == null) return false;
   1103 //
   1104 //        if ($assert.ENABLED) $assert.ASSERT (item != null, "null input: item");
   1105 //
   1106 //        final String fileName = item.getSrcFileName ();
   1107 //        if ($assert.ENABLED) $assert.ASSERT (fileName.endsWith (".java"), "cache only handles .java extensions");
   1108 //
   1109 //        // TODO: should I keep VM names in package items?
   1110 //        final String packageVMName = ((PackageItem) item.getParent ()).getVMName ();
   1111 //
   1112 //        return (cache.find (packageVMName, fileName) != null);
   1113 //    }
   1114 
   1115     private void embedSrcFile (final SrcFileItem item, final HTMLDocument page,
   1116                                final IntObjectMap /* line num:int->anchor name:String */anchorMap,
   1117                                final SourcePathCache cache)
   1118     {
   1119         if ($assert.ENABLED)
   1120         {
   1121             $assert.ASSERT (item != null, "null input: item");
   1122             $assert.ASSERT (page != null, "null input: page");
   1123         }
   1124 
   1125         final String fileName = item.getName ();
   1126         if ($assert.ENABLED) $assert.ASSERT (fileName.endsWith (".java"), "cache only handles .java extensions");
   1127 
   1128         // TODO: should I keep VM names in package items?
   1129         final String packageVMName = ((PackageItem) item.getParent ()).getVMName ();
   1130 
   1131         boolean success = false;
   1132 
   1133         final HTMLTable srcTable = new HTMLTable ("100%", null, null, "0");
   1134 
   1135         if (cache != null) // TODO: do this check earlier, in outer scope
   1136         {
   1137             srcTable.setClass (CSS_SOURCE);
   1138             final File srcFile = cache.find (packageVMName, fileName);
   1139 
   1140             if (srcFile != null)
   1141             {
   1142                 BufferedReader in = null;
   1143                 try
   1144                 {
   1145                     in = new BufferedReader (new FileReader (srcFile), IO_BUF_SIZE);
   1146 
   1147                     final boolean markupCoverage = m_hasLineNumberInfo;
   1148 
   1149                     final int unitsType = m_settings.getUnitsType ();
   1150                     IntObjectMap /* line num:int -> SrcFileItem.LineCoverageData */ lineCoverageMap = null;
   1151                     StringBuffer tooltipBuffer = null;
   1152 
   1153 
   1154                     if (markupCoverage)
   1155                     {
   1156                         lineCoverageMap = item.getLineCoverage ();
   1157                         $assert.ASSERT (lineCoverageMap != null, "null: lineCoverageMap");
   1158 
   1159                         tooltipBuffer = new StringBuffer (64);
   1160                     }
   1161 
   1162                     int l = 1;
   1163                     for (String line; (line = in.readLine ()) != null; ++ l)
   1164                     {
   1165                         final HTMLTable.IRow srcline = srcTable.newRow ();
   1166                         final HTMLTable.ICell lineNumCell = srcline.newCell ();
   1167                         lineNumCell.setClass (CSS_LINENUM);
   1168 
   1169                         if (anchorMap != null)
   1170                         {
   1171                             final int adjustedl = l < SRC_LINE_OFFSET ? l : l + SRC_LINE_OFFSET;
   1172 
   1173                             final String anchor = (String) anchorMap.get (adjustedl);
   1174                             if (anchor != null)
   1175                             {
   1176                                 final IElement a = IElement.Factory.create (Tag.A);
   1177                                 //a.getAttributes ().set (Attribute.ID, anchor); ID anchoring does not work in NS 4.0
   1178                                 a.getAttributes ().set (Attribute.NAME, anchor);
   1179 
   1180                                 a.setText (Integer.toString (l), true);
   1181 
   1182                                 lineNumCell.add (a);
   1183                             }
   1184                             else
   1185                             {
   1186                                 lineNumCell.setText (Integer.toString (l), true);
   1187                             }
   1188                         }
   1189                         else
   1190                         {
   1191                             lineNumCell.setText (Integer.toString (l), true);
   1192                         }
   1193 
   1194                         final HTMLTable.ICell lineTxtCell = srcline.newCell ();
   1195                         lineTxtCell.setText (line.length () > 0 ? line : " ", true);
   1196 
   1197                         if (markupCoverage)
   1198                         {
   1199                             final SrcFileItem.LineCoverageData lCoverageData = (SrcFileItem.LineCoverageData) lineCoverageMap.get (l);
   1200 
   1201                             if (lCoverageData != null)
   1202                             {
   1203                                 switch (lCoverageData.m_coverageStatus)
   1204                                 {
   1205                                     case SrcFileItem.LineCoverageData.LINE_COVERAGE_ZERO:
   1206                                         srcline.setClass (CSS_COVERAGE_ZERO);
   1207                                     break;
   1208 
   1209                                     case SrcFileItem.LineCoverageData.LINE_COVERAGE_PARTIAL:
   1210                                     {
   1211                                         srcline.setClass (CSS_COVERAGE_PARTIAL);
   1212 
   1213                                         if (USE_LINE_COVERAGE_TOOLTIPS)
   1214                                         {
   1215                                             tooltipBuffer.setLength (0);
   1216 
   1217                                             final int [] coverageRatio = lCoverageData.m_coverageRatio [unitsType];
   1218 
   1219                                             final int d = coverageRatio [0];
   1220                                             final int n = coverageRatio [1];
   1221 
   1222                                             m_format.format ((double) n / d, tooltipBuffer, m_fieldPosition);
   1223 
   1224                                             tooltipBuffer.append (" line coverage (");
   1225                                             tooltipBuffer.append (n);
   1226                                             tooltipBuffer.append (" out of ");
   1227                                             tooltipBuffer.append (d);
   1228 
   1229                                             switch (unitsType)
   1230                                             {
   1231                                                 case IItemAttribute.UNITS_COUNT:
   1232                                                     tooltipBuffer.append (" basic blocks)");
   1233                                                 break;
   1234 
   1235                                                 case IItemAttribute.UNITS_INSTR:
   1236                                                     tooltipBuffer.append (" instructions)");
   1237                                                 break;
   1238                                             }
   1239 
   1240                                             // [Opera does not display TITLE tooltios on <TR> elements]
   1241 
   1242                                             lineNumCell.getAttributes ().set (Attribute.TITLE, tooltipBuffer.toString ());
   1243                                             lineTxtCell.getAttributes ().set (Attribute.TITLE, tooltipBuffer.toString ());
   1244                                         }
   1245                                     }
   1246                                     break;
   1247 
   1248                                     case SrcFileItem.LineCoverageData.LINE_COVERAGE_COMPLETE:
   1249                                         srcline.setClass (CSS_COVERAGE_COMPLETE);
   1250                                     break;
   1251 
   1252                                     default: $assert.ASSERT (false, "invalid line coverage status: " + lCoverageData.m_coverageStatus);
   1253 
   1254                                 } // end of switch
   1255                             }
   1256                         }
   1257                     }
   1258 
   1259                     success = true;
   1260                 }
   1261                 catch (Throwable t)
   1262                 {
   1263                     t.printStackTrace (System.out); // TODO: logging
   1264                     success = false;
   1265                 }
   1266                 finally
   1267                 {
   1268                     if (in != null) try { in.close (); } catch (Throwable ignore) {}
   1269                     in = null;
   1270                 }
   1271             }
   1272         }
   1273 
   1274         if (! success)
   1275         {
   1276             srcTable.setClass (CSS_INVISIBLE_TABLE);
   1277 
   1278             final HTMLTable.IRow row = srcTable.newTitleRow ();
   1279             row.newCell ().setText ("[source file '" + Descriptors.combineVMName (packageVMName, fileName) + "' not found in sourcepath]", false);
   1280         }
   1281 
   1282         page.add (srcTable);
   1283     }
   1284 
   1285 
   1286     private static String addLineAnchorID (final int line, final String anchorID,
   1287                                            final IntObjectMap /* line num:int->anchorID:String */lineAnchorIDMap)
   1288     {
   1289         if (line > 0)
   1290         {
   1291             final String _anchorID = (String) lineAnchorIDMap.get (line);
   1292             if (_anchorID != null)
   1293                 return _anchorID;
   1294             else
   1295             {
   1296                 lineAnchorIDMap.put (line, anchorID);
   1297 
   1298                 return anchorID;
   1299             }
   1300         }
   1301 
   1302         return null;
   1303     }
   1304 
   1305     /*
   1306      * Always includes AllItem
   1307      */
   1308     private IItem [] getParentPath (IItem item)
   1309     {
   1310         final LinkedList /* IItem */ _result = new LinkedList ();
   1311 
   1312         for ( ; item != null; item = item.getParent ())
   1313         {
   1314             _result.add (item);
   1315         }
   1316 
   1317         final IItem [] result = new IItem [_result.size ()];
   1318         int j = result.length - 1;
   1319         for (Iterator i = _result.iterator (); i.hasNext (); -- j)
   1320         {
   1321             result [j] = (IItem) i.next ();
   1322         }
   1323 
   1324         return result;
   1325     }
   1326 
   1327     /*
   1328      *
   1329      */
   1330     private String getItemHREF (final IItem base, final IItem item)
   1331     {
   1332         final String itemHREF;
   1333         if (item instanceof AllItem)
   1334             itemHREF = m_settings.getOutFile ().getName (); // note that this is always a simple filename [no parent path]
   1335         else
   1336             itemHREF = m_reportIDNamespace.getID (getItemKey (item)).concat (FILE_EXTENSION);
   1337 
   1338         final String fullHREF;
   1339 
   1340         if (base == null)
   1341             fullHREF = itemHREF;
   1342         else
   1343         {
   1344             final int nesting = NESTING [base.getMetadata ().getTypeID ()] [item.getMetadata ().getTypeID ()];
   1345             if (nesting == 1)
   1346                 fullHREF = NESTED_ITEMS_PARENT_DIRNAME.concat ("/").concat (itemHREF);
   1347             else if (nesting == -1)
   1348                 fullHREF = "../".concat (itemHREF);
   1349             else
   1350                 fullHREF = itemHREF;
   1351         }
   1352 
   1353         return fullHREF;
   1354     }
   1355 
   1356 
   1357     private IContent getPageTitle ()
   1358     {
   1359         IContent title = m_pageTitle;
   1360         if (title == null)
   1361         {
   1362             final IElementList _title = new ElementList ();
   1363 
   1364             _title.add (new HyperRef (IAppConstants.APP_HOME_SITE_LINK, IAppConstants.APP_NAME, true));
   1365 
   1366             final StringBuffer s = new StringBuffer (" Coverage Report (generated ");
   1367             s.append (new Date (EMMAProperties.getTimeStamp ()));
   1368             s.append (')');
   1369 
   1370             _title.add (new Text (s.toString (), true));
   1371 
   1372             m_pageTitle = title = _title;
   1373         }
   1374 
   1375         return title;
   1376     }
   1377 
   1378     private IContent getFooterBottom ()
   1379     {
   1380         IContent bottom = m_footerBottom;
   1381         if (bottom == null)
   1382         {
   1383             final IElementList _bottom = new ElementList ();
   1384 
   1385             _bottom.add (new HyperRef (IAppConstants.APP_BUG_REPORT_LINK, IAppConstants.APP_NAME + " " + IAppConstants.APP_VERSION_WITH_BUILD_ID_AND_TAG, true));
   1386             _bottom.add (new Text (" " + IAppConstants.APP_COPYRIGHT, true));
   1387 
   1388             m_footerBottom = bottom = _bottom;
   1389         }
   1390 
   1391         return bottom;
   1392     }
   1393 
   1394     private void addPageHeaderTitleRow (final HTMLTable header)
   1395     {
   1396         final HTMLTable.IRow titleRow = header.newTitleRow ();
   1397 
   1398         final HTMLTable.ICell cell = titleRow.newCell ();
   1399         cell.setClass (CSS_TITLE);
   1400         cell.add (getPageTitle ());
   1401     }
   1402 
   1403     private static void trimForDisplay (final StringBuffer buf)
   1404     {
   1405         if (buf.length () > MAX_DISPLAY_NAME_LENGTH)
   1406         {
   1407             buf.setLength (MAX_DISPLAY_NAME_LENGTH - 3);
   1408             buf.append ("...");
   1409         }
   1410     }
   1411 
   1412     /*
   1413      * Assumes relative pathnames.
   1414      */
   1415     private static File getItemFile (final File parentDir, final String itemKey)
   1416     {
   1417         if (parentDir == null)
   1418             return new File (itemKey.concat (FILE_EXTENSION));
   1419         else
   1420             return new File (parentDir, itemKey.concat (FILE_EXTENSION));
   1421     }
   1422 
   1423     private static String getItemKey (IItem item)
   1424     {
   1425         final StringBuffer result = new StringBuffer ();
   1426 
   1427         for ( ; item != null; item = item.getParent ())
   1428         {
   1429             result.append (item.getName ());
   1430             result.append (':');
   1431         }
   1432 
   1433         return result.toString ();
   1434     }
   1435 
   1436     private static HTMLWriter openOutFile (final File file, final String encoding, final boolean mkdirs)
   1437     {
   1438         BufferedWriter out = null;
   1439         try
   1440         {
   1441             if (mkdirs)
   1442             {
   1443                 final File parent = file.getParentFile ();
   1444                 if (parent != null) parent.mkdirs ();
   1445             }
   1446 
   1447             out = new BufferedWriter (new OutputStreamWriter (new FileOutputStream (file), encoding), IO_BUF_SIZE);
   1448         }
   1449         catch (UnsupportedEncodingException uee)
   1450         {
   1451             // TODO: error code
   1452             throw new EMMARuntimeException (uee);
   1453         }
   1454         // note: in J2SDK 1.3 FileOutputStream constructor's throws clause
   1455         // was narrowed to FileNotFoundException:
   1456         catch (IOException fnfe) // FileNotFoundException
   1457         {
   1458             // TODO: error code
   1459             throw new EMMARuntimeException (fnfe);
   1460         }
   1461 
   1462         return new HTMLWriter (out);
   1463     }
   1464 
   1465     private static String dataCellStyle (final int column, final boolean highlight)
   1466     {
   1467         if (column == 0)
   1468             return highlight ? CSS_DATA_HIGHLIGHT_FIRST : CSS_DATA_FIRST;
   1469         else
   1470             return highlight ? CSS_DATA_HIGHLIGHT : CSS_DATA;
   1471     }
   1472 
   1473     private static String headerCellStyle (final int column)
   1474     {
   1475         return (column == 0) ? CSS_HEADER_FIRST : CSS_HEADER;
   1476     }
   1477 
   1478 
   1479     private final DecimalFormat m_format;
   1480     private final FieldPosition m_fieldPosition;
   1481 
   1482     private LinkedList /* IITem */ m_queue;
   1483     private IDGenerator m_reportIDNamespace;
   1484 
   1485     private IContent m_pageTitle, m_footerBottom;
   1486 
   1487     private static final boolean USE_LINE_COVERAGE_TOOLTIPS = true;
   1488 
   1489     private static final String TYPE = "html";
   1490     private static final String REPORT_HEADER_TITLE = IAppConstants.APP_NAME + " Coverage Report";
   1491     private static final IContent LEFT_BRACKET = new Text ("[", false);
   1492     private static final IContent RIGHT_BRACKET = new Text ("]", false);
   1493 
   1494     private static final int MAX_DISPLAY_NAME_LENGTH = 80;
   1495     private static final int SRC_LINE_OFFSET = 4;
   1496 
   1497 
   1498     private static final String CSS_HEADER_FOOTER       = "hdft";
   1499     private static final String CSS_TITLE               = "tl";
   1500     private static final String CSS_NAV                 = "nv";
   1501 
   1502     private static final String CSS_COVERAGE_ZERO       = "z";
   1503     private static final String CSS_COVERAGE_PARTIAL    = "p";
   1504     private static final String CSS_COVERAGE_COMPLETE   = "c";
   1505 
   1506     private static final String DARKER_BACKGROUND   = "#F0F0F0";
   1507     private static final String TITLE_BACKGROUND    = "#6699CC";
   1508     private static final String NAV_BACKGROUND      = "#6633DD";
   1509 
   1510     private static final String CSS_INVISIBLE_TABLE     = "it";
   1511 
   1512     private static final String CSS_ITEM_NAME           = "in";
   1513 
   1514     private static final String CSS_CLASS_ITEM_SPECIAL  = "cis";
   1515 
   1516     private static final String CSS_SOURCE              = "s";
   1517     private static final String CSS_LINENUM             = "l";
   1518 
   1519     private static final String CSS_BOTTOM              = "bt";
   1520     private static final String CSS_ODDROW              = "o";
   1521     private static final String CSS_BLANK               = "b";
   1522 
   1523     private static final String CSS_DATA = "";
   1524     private static final String CSS_DATA_HIGHLIGHT = CSS_DATA + "h";
   1525     private static final String CSS_DATA_FIRST = CSS_DATA + "f";
   1526     private static final String CSS_DATA_HIGHLIGHT_FIRST = CSS_DATA + "hf";
   1527     private static final String CSS_HEADER = "";
   1528     private static final String CSS_HEADER_FIRST = CSS_HEADER + "f";
   1529 
   1530     private static final String CSS_CLS_NOLEFT          = "cn";
   1531 
   1532     // TODO: optimize this
   1533 
   1534     private static final String CSS =
   1535         " TABLE,TD,TH {border-style:solid; border-color:black;}" +
   1536         " TD,TH {background:white;margin:0;line-height:100%;padding-left:0.5em;padding-right:0.5em;}" +
   1537 
   1538         " TD {border-width:0 1px 0 0;}" +
   1539         " TH {border-width:1px 1px 1px 0;}" +
   1540         " TR TD." + CSS_DATA_HIGHLIGHT + " {color:red;}" +
   1541 
   1542         " TABLE {border-spacing:0; border-collapse:collapse;border-width:0 0 1px 1px;}" +
   1543 
   1544         " P,H1,H2,H3,TH {font-family:verdana,arial,sans-serif;font-size:10pt;}" +
   1545         " TD {font-family:courier,monospace;font-size:10pt;}" +
   1546 
   1547         " TABLE." + CSS_HEADER_FOOTER + " {border-spacing:0;border-collapse:collapse;border-style:none;}" +
   1548         " TABLE." + CSS_HEADER_FOOTER + " TH,TABLE." + CSS_HEADER_FOOTER + " TD {border-style:none;line-height:normal;}" +
   1549 
   1550         " TABLE." + CSS_HEADER_FOOTER + " TH." + CSS_TITLE + ",TABLE." + CSS_HEADER_FOOTER + " TD." + CSS_TITLE + " {background:" + TITLE_BACKGROUND + ";color:white;}" +
   1551         " TABLE." + CSS_HEADER_FOOTER + " TD." + CSS_NAV + " {background:" + NAV_BACKGROUND + ";color:white;}" +
   1552 
   1553         " ." + CSS_NAV + " A:link {color:white;}" +
   1554         " ." + CSS_NAV + " A:visited {color:white;}" +
   1555         " ." + CSS_NAV + " A:active {color:yellow;}" +
   1556 
   1557         " TABLE." + CSS_HEADER_FOOTER + " A:link {color:white;}" +
   1558         " TABLE." + CSS_HEADER_FOOTER + " A:visited {color:white;}" +
   1559         " TABLE." + CSS_HEADER_FOOTER + " A:active {color:yellow;}" +
   1560 
   1561         //" ." + CSS_ITEM_NAME + " {color:#6633FF;}" +
   1562         //" ." + CSS_ITEM_NAME + " {color:#C000E0;}" +
   1563         " ." + CSS_ITEM_NAME + " {color:#356085;}" +
   1564 
   1565         //" A:hover {color:#0066FF; text-decoration:underline; font-style:italic}" +
   1566 
   1567         " TABLE." + CSS_SOURCE + " TD {padding-left:0.25em;padding-right:0.25em;}" +
   1568         " TABLE." + CSS_SOURCE + " TD." + CSS_LINENUM + " {padding-left:0.25em;padding-right:0.25em;text-align:right;background:" + DARKER_BACKGROUND + ";}" +
   1569         " TABLE." + CSS_SOURCE + " TR." + CSS_COVERAGE_ZERO + " TD {background:#FF9999;}" +
   1570         " TABLE." + CSS_SOURCE + " TR." + CSS_COVERAGE_PARTIAL + " TD {background:#FFFF88;}" +
   1571         " TABLE." + CSS_SOURCE + " TR." + CSS_COVERAGE_COMPLETE + " TD {background:#CCFFCC;}" +
   1572 
   1573         " A:link {color:#0000EE;text-decoration:none;}" +
   1574         " A:visited {color:#0000EE;text-decoration:none;}" +
   1575         " A:hover {color:#0000EE;text-decoration:underline;}" +
   1576 
   1577         " TABLE." + CSS_CLS_NOLEFT + " {border-width:0 0 1px 0;}" +
   1578         " TABLE." + CSS_SOURCE + " {border-width:1px 0 1px 1px;}" +
   1579 
   1580 //        " TD {border-width: 0px 1px 0px 0px; }" +
   1581         " TD." + CSS_DATA_HIGHLIGHT + " {color:red;border-width:0 1px 0 0;}" +
   1582         " TD." + CSS_DATA_FIRST + " {border-width:0 1px 0 1px;}" +
   1583         " TD." + CSS_DATA_HIGHLIGHT_FIRST + " {color:red;border-width:0 1px 0 1px;}" +
   1584 
   1585 //        " TH {border-width: 1px 1px 1px 0px; }" +
   1586         " TH." + CSS_HEADER_FIRST + " {border-width:1px 1px 1px 1px;}" +
   1587 
   1588         " TR." + CSS_CLASS_ITEM_SPECIAL + " TD {background:" + DARKER_BACKGROUND + ";}" +
   1589         " TR." + CSS_CLASS_ITEM_SPECIAL + " TD {border-width:1px 1px 1px 0;}" +
   1590         " TR." + CSS_CLASS_ITEM_SPECIAL + " TD." + CSS_DATA_HIGHLIGHT + " {color:red;border-width:1px 1px 1px 0;}" +
   1591         " TR." + CSS_CLASS_ITEM_SPECIAL + " TD." + CSS_DATA_FIRST + " {border-width:1px 1px 1px 1px;}" +
   1592         " TR." + CSS_CLASS_ITEM_SPECIAL + " TD." + CSS_DATA_HIGHLIGHT_FIRST + " {color:red;border-width:1px 1px 1px 1px;}" +
   1593 
   1594         " TD." + CSS_BLANK + " {border-style:none;background:transparent;line-height:50%;} " +
   1595         " TD." + CSS_BOTTOM + " {border-width:1px 0 0 0;background:transparent;line-height:50%;}" +
   1596         " TR." + CSS_ODDROW + " TD {background:" + DARKER_BACKGROUND + ";}" +
   1597 
   1598         "TABLE." + CSS_INVISIBLE_TABLE + " {border-style:none;}" +
   1599         "TABLE." + CSS_INVISIBLE_TABLE + " TD,TABLE." + CSS_INVISIBLE_TABLE + " TH {border-style:none;}" +
   1600 
   1601         "";
   1602 
   1603     private static final String NESTED_ITEMS_PARENT_DIRNAME = "_files";
   1604     private static final File NESTED_ITEMS_PARENT_DIR = new File (NESTED_ITEMS_PARENT_DIRNAME);
   1605     private static final int [][] NESTING; // set in <clinit>; this reflects the dir structure for the report
   1606 
   1607     private static final String FILE_EXTENSION = ".html";
   1608     private static final int IO_BUF_SIZE = 32 * 1024;
   1609 
   1610     private static final long [] ATTRIBUTE_SETS; // set in <clinit>
   1611 
   1612     static
   1613     {
   1614         final IItemMetadata [] allTypes = IItemMetadata.Factory.getAllTypes ();
   1615 
   1616         ATTRIBUTE_SETS = new long [allTypes.length];
   1617         for (int t = 0; t < allTypes.length; ++ t)
   1618         {
   1619             ATTRIBUTE_SETS [allTypes [t].getTypeID ()] = allTypes [t].getAttributeIDs ();
   1620         }
   1621 
   1622         NESTING = new int [4][4];
   1623 
   1624         int base = AllItem.getTypeMetadata().getTypeID ();
   1625         NESTING [base][PackageItem.getTypeMetadata ().getTypeID ()] = 1;
   1626         NESTING [base][SrcFileItem.getTypeMetadata ().getTypeID ()] = 1;
   1627         NESTING [base][ClassItem.getTypeMetadata ().getTypeID ()] = 1;
   1628 
   1629         base = PackageItem.getTypeMetadata().getTypeID ();
   1630         NESTING [base][AllItem.getTypeMetadata ().getTypeID ()] = -1;
   1631 
   1632         base = SrcFileItem.getTypeMetadata().getTypeID ();
   1633         NESTING [base][AllItem.getTypeMetadata ().getTypeID ()] = -1;
   1634 
   1635         base = ClassItem.getTypeMetadata().getTypeID ();
   1636         NESTING [base][AllItem.getTypeMetadata ().getTypeID ()] = -1;
   1637     }
   1638 
   1639 } // end of class
   1640 // ----------------------------------------------------------------------------
   1641