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 */
9package com.vladium.emma.report.html;
10
11import java.io.BufferedReader;
12import java.io.BufferedWriter;
13import java.io.File;
14import java.io.FileOutputStream;
15import java.io.FileReader;
16import java.io.IOException;
17import java.io.OutputStreamWriter;
18import java.io.UnsupportedEncodingException;
19import java.text.DecimalFormat;
20import java.text.FieldPosition;
21import java.text.NumberFormat;
22import java.util.Date;
23import java.util.Iterator;
24import java.util.LinkedList;
25
26import com.vladium.util.Descriptors;
27import com.vladium.util.Files;
28import com.vladium.util.IProperties;
29import com.vladium.util.IntObjectMap;
30import com.vladium.util.IntVector;
31import com.vladium.util.ObjectIntMap;
32import com.vladium.util.Property;
33import com.vladium.util.asserts.$assert;
34import com.vladium.emma.IAppConstants;
35import com.vladium.emma.IAppErrorCodes;
36import com.vladium.emma.EMMAProperties;
37import com.vladium.emma.EMMARuntimeException;
38import com.vladium.emma.data.ICoverageData;
39import com.vladium.emma.data.IMetaData;
40import com.vladium.emma.report.AbstractReportGenerator;
41import com.vladium.emma.report.AllItem;
42import com.vladium.emma.report.ClassItem;
43import com.vladium.emma.report.IItem;
44import com.vladium.emma.report.IItemAttribute;
45import com.vladium.emma.report.IItemMetadata;
46import com.vladium.emma.report.ItemComparator;
47import com.vladium.emma.report.MethodItem;
48import com.vladium.emma.report.PackageItem;
49import com.vladium.emma.report.SourcePathCache;
50import com.vladium.emma.report.SrcFileItem;
51import com.vladium.emma.report.html.doc.*;
52
53// ----------------------------------------------------------------------------
54/**
55 * @author Vlad Roubtsov, (C) 2003
56 */
57public
58final 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