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.1.1.1.2.1 2004/07/16 23:32:30 vlad_r Exp $
8 */
9package com.vladium.emma.report.txt;
10
11import java.io.BufferedWriter;
12import java.io.File;
13import java.io.FileOutputStream;
14import java.io.IOException;
15import java.io.OutputStreamWriter;
16import java.io.UnsupportedEncodingException;
17import java.util.Date;
18import java.util.Iterator;
19import java.util.LinkedList;
20
21import com.vladium.util.Files;
22import com.vladium.util.IProperties;
23import com.vladium.util.asserts.$assert;
24import com.vladium.emma.IAppConstants;
25import com.vladium.emma.IAppErrorCodes;
26import com.vladium.emma.EMMAProperties;
27import com.vladium.emma.EMMARuntimeException;
28import com.vladium.emma.data.ICoverageData;
29import com.vladium.emma.data.IMetaData;
30import com.vladium.emma.report.AbstractReportGenerator;
31import com.vladium.emma.report.AllItem;
32import com.vladium.emma.report.ClassItem;
33import com.vladium.emma.report.IItem;
34import com.vladium.emma.report.IItemAttribute;
35import com.vladium.emma.report.ItemComparator;
36import com.vladium.emma.report.MethodItem;
37import com.vladium.emma.report.PackageItem;
38import com.vladium.emma.report.SourcePathCache;
39import com.vladium.emma.report.SrcFileItem;
40
41// ----------------------------------------------------------------------------
42/**
43 * @author Vlad Roubtsov, (C) 2003
44 */
45public
46final class ReportGenerator extends AbstractReportGenerator
47                            implements IAppErrorCodes
48{
49    // public: ................................................................
50
51    // TODO: this is prototype quality, needs major cleanup
52
53    // IReportGenerator:
54
55    public String getType ()
56    {
57        return TYPE;
58    }
59
60    public void process (final IMetaData mdata, final ICoverageData cdata,
61                         final SourcePathCache cache, final IProperties properties)
62        throws EMMARuntimeException
63    {
64        initialize (mdata, cdata, cache, properties);
65
66        long start = 0, end;
67        final boolean trace1 = m_log.atTRACE1 ();
68
69        if (trace1) start = System.currentTimeMillis ();
70
71        {
72            m_queue = new LinkedList ();
73            for (m_queue.add (m_view.getRoot ()); ! m_queue.isEmpty (); )
74            {
75                final IItem head = (IItem) m_queue.removeFirst ();
76
77                head.accept (this, null);
78            }
79            line ();
80
81            close ();
82        }
83
84        if (trace1)
85        {
86            end = System.currentTimeMillis ();
87
88            m_log.trace1 ("process", "[" + getType () + "] report generated in " + (end - start) + " ms");
89        }
90    }
91
92    public void cleanup ()
93    {
94        m_queue = null;
95        close ();
96
97        super.cleanup ();
98    }
99
100
101    // IItemVisitor:
102
103    public Object visit (final AllItem item, final Object ctx)
104    {
105        File outFile = m_settings.getOutFile ();
106        if (outFile == null)
107        {
108            outFile = new File ("coverage.txt");
109            m_settings.setOutFile (outFile);
110        }
111
112        final File fullOutFile = Files.newFile (m_settings.getOutDir (), outFile);
113
114        m_log.info ("writing [" + getType () + "] report to [" + fullOutFile.getAbsolutePath () + "] ...");
115
116        openOutFile (fullOutFile, m_settings.getOutEncoding (), true);
117
118        // build ID stamp:
119        try
120        {
121            final StringBuffer label = new StringBuffer (101);
122
123            label.append ("[");
124            label.append (IAppConstants.APP_NAME);
125            label.append (" v"); label.append (IAppConstants.APP_VERSION_WITH_BUILD_ID_AND_TAG);
126            label.append (" report, generated ");
127            label.append (new Date (EMMAProperties.getTimeStamp ()));
128            label.append ("]");
129
130            m_out.write (label.toString ());
131            m_out.newLine ();
132
133            m_out.flush ();
134        }
135        catch (IOException ioe)
136        {
137            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
138        }
139
140        final int [] columns = m_settings.getColumnOrder ();
141
142        line ();
143
144        // [all] coverage summary row:
145        addTitleRow ("OVERALL COVERAGE SUMMARY", 0, 1);
146        {
147            // header row:
148            addHeaderRow (item, columns);
149
150            // coverage row:
151            addItemRow (item, columns);
152        }
153
154        // [all] stats summary table ([all] only):
155        addTitleRow ("OVERALL STATS SUMMARY", 1, 1);
156        {
157            row ("total packages:" + m_separator + item.getChildCount ());
158            row ("total classes:" + m_separator + item.getAggregate (IItem.TOTAL_CLASS_COUNT));
159            row ("total methods:" + m_separator + item.getAggregate (IItem.TOTAL_METHOD_COUNT));
160
161            if (m_srcView && m_hasSrcFileInfo)
162            {
163                row ("total executable files:" + m_separator + item.getAggregate (IItem.TOTAL_SRCFILE_COUNT));
164
165                if (m_hasLineNumberInfo)
166                    row ("total executable lines:" + m_separator + item.getAggregate (IItem.TOTAL_LINE_COUNT));
167            }
168        }
169
170        final boolean deeper = (m_settings.getDepth () > item.getMetadata ().getTypeID ());
171
172        // render package summary rows:
173        addTitleRow ("COVERAGE BREAKDOWN BY PACKAGE", 1, 1);
174        {
175            boolean headerDone = false;
176            final ItemComparator order = m_typeSortComparators [PackageItem.getTypeMetadata ().getTypeID ()];
177            for (Iterator packages = item.getChildren (order); packages.hasNext (); )
178            {
179                final IItem pkg = (IItem) packages.next ();
180
181                if (! headerDone)
182                {
183                    // header row:
184                    addHeaderRow (pkg, columns);
185                    headerDone = true;
186                }
187
188                // coverage row:
189                addItemRow (pkg, columns);
190
191                if (deeper) m_queue.addLast (pkg);
192            }
193        }
194
195        return ctx;
196    }
197
198    public Object visit (final PackageItem item, final Object ctx)
199    {
200        if (m_verbose) m_log.verbose ("  report: processing package [" + item.getName () + "] ...");
201
202        final int [] columns = m_settings.getColumnOrder ();
203
204        line ();
205
206        // coverage summary row:
207        addTitleRow ("COVERAGE SUMMARY FOR PACKAGE [".concat (item.getName ()).concat ("]"), 0, 1);
208        {
209            // header row:
210            addHeaderRow (item, columns);
211
212            // coverage row:
213            addItemRow (item, columns);
214        }
215
216
217        final boolean deeper = (m_settings.getDepth () > item.getMetadata ().getTypeID ());
218
219        // render child summary rows:
220
221        final String summaryTitle = m_srcView ? "COVERAGE BREAKDOWN BY SOURCE FILE" : "COVERAGE BREAKDOWN BY CLASS";
222        addTitleRow (summaryTitle, 1, 1);
223        {
224            boolean headerDone = false;
225            final ItemComparator order = m_typeSortComparators [m_srcView ? SrcFileItem.getTypeMetadata ().getTypeID () : ClassItem.getTypeMetadata ().getTypeID ()];
226            for (Iterator srcORclsFiles = item.getChildren (order); srcORclsFiles.hasNext (); )
227            {
228                final IItem srcORcls = (IItem) srcORclsFiles.next ();
229
230                if (! headerDone)
231                {
232                    // header row:
233                    addHeaderRow (srcORcls, columns);
234                    headerDone = true;
235                }
236
237                // coverage row:
238                addItemRow (srcORcls, columns);
239
240                if (deeper) m_queue.addLast (srcORcls);
241            }
242        }
243
244        return ctx;
245    }
246
247    public Object visit (final SrcFileItem item, final Object ctx)
248    {
249        final int [] columns = m_settings.getColumnOrder ();
250
251        line ();
252
253        // coverage summary row:
254        addTitleRow ("COVERAGE SUMMARY FOR SOURCE FILE [".concat (item.getName ()).concat ("]"), 0, 1);
255        {
256            // header row:
257            addHeaderRow (item, columns);
258
259            // coverage row:
260            addItemRow (item, columns);
261        }
262
263        // render child summary rows:
264        addTitleRow ("COVERAGE BREAKDOWN BY CLASS AND METHOD", 1, 1);
265        {
266            boolean headerDone = false;
267            final ItemComparator order = m_typeSortComparators [ClassItem.getTypeMetadata ().getTypeID ()];
268            for (Iterator classes = item.getChildren (order); classes.hasNext (); )
269            {
270                final IItem cls = (IItem) classes.next ();
271
272                if (! headerDone)
273                {
274                    // header row:
275                    addHeaderRow (cls, columns);
276                    headerDone = true;
277                }
278
279                // coverage row:
280                //addItemRow (child, columns);
281
282                addTitleRow ("class [" + cls.getName () + "] methods", 0, 0);
283
284                // TODO: select the right comparator here
285                final ItemComparator order2 = m_typeSortComparators [MethodItem.getTypeMetadata ().getTypeID ()];
286                for (Iterator methods = cls.getChildren (order2); methods.hasNext (); )
287                {
288                    final MethodItem method = (MethodItem) methods.next ();
289
290                    addItemRow (method, columns);
291                }
292            }
293        }
294
295        return ctx;
296    }
297
298    public Object visit (final ClassItem item, final Object ctx)
299    {
300        final int [] columns = m_settings.getColumnOrder ();
301
302        line ();
303
304        // coverage summary row:
305        addTitleRow ("COVERAGE SUMMARY FOR CLASS [".concat (item.getName ()).concat ("]"), 0, 1);
306        {
307            // header row:
308            addHeaderRow (item, columns);
309
310            // coverage row:
311            addItemRow (item, columns);
312        }
313
314        // render child summary rows:
315        addTitleRow ("COVERAGE BREAKDOWN BY METHOD", 1, 1);
316        {
317            final ItemComparator order = m_typeSortComparators [MethodItem.getTypeMetadata ().getTypeID ()];
318            for (Iterator methods = item.getChildren (order); methods.hasNext (); )
319            {
320                final IItem method = (IItem) methods.next ();
321
322                // coverage row:
323                addItemRow (method, columns);
324            }
325        }
326
327        return ctx;
328    }
329
330    // protected: .............................................................
331
332    // package: ...............................................................
333
334    // private: ...............................................................
335
336
337    private void addTitleRow (final String text, final int hlines, final int flines)
338    {
339        for (int i = 0; i < hlines; ++ i) eol ();
340        row (new StringBuffer (text).append (":"));
341        for (int i = 0; i < flines; ++ i) eol ();
342    }
343
344    private void addHeaderRow (final IItem item, final int [] columns)
345    {
346        if ($assert.ENABLED)
347        {
348            $assert.ASSERT (item != null, "null input: item");
349            $assert.ASSERT (columns != null, "null input: columns");
350        }
351
352        // header row:
353        final StringBuffer buf = new StringBuffer ();
354
355        for (int c = 0, cLimit = columns.length; c < cLimit; ++ c)
356        {
357            final int attrID = columns [c];
358            final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
359
360            if (attr != null)
361            {
362                buf.append ('[');
363                buf.append (attr.getName ());
364                buf.append (']');
365            }
366            if (c != cLimit - 1) buf.append (m_separator);
367        }
368
369        row (buf);
370    }
371
372
373    /*
374     * No header row, just data rows.
375     */
376    private void addItemRow (final IItem item, final int [] columns)
377    {
378        if ($assert.ENABLED)
379        {
380            $assert.ASSERT (item != null, "null input: item");
381            $assert.ASSERT (columns != null, "null input: columns");
382        }
383
384        final StringBuffer buf = new StringBuffer (11); // TODO: reuse a buffer
385
386        for (int c = 0, cLimit = columns.length; c < cLimit; ++ c)
387        {
388            final int attrID = columns [c];
389            final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
390
391            if (attr != null)
392            {
393                boolean fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]);
394
395                if (fail)
396                {
397                    //buf.append ('(');
398                    //buf.append ("! ");
399                    attr.format (item, buf);
400                    buf.append ('!');
401                    //buf.append (')');
402                }
403                else
404                {
405                    attr.format (item, buf);
406                }
407            }
408            if (c != cLimit - 1) buf.append (m_separator);
409        }
410
411        row (buf);
412    }
413
414
415    private void row (final StringBuffer str)
416    {
417        if ($assert.ENABLED) $assert.ASSERT (str != null, "str = null");
418
419        try
420        {
421            m_out.write (str.toString ());
422            m_out.newLine ();
423        }
424        catch (IOException ioe)
425        {
426            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
427        }
428    }
429
430    private void row (final String str)
431    {
432        if ($assert.ENABLED) $assert.ASSERT (str != null, "str = null");
433
434        try
435        {
436            m_out.write (str);
437            m_out.newLine ();
438        }
439        catch (IOException ioe)
440        {
441            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
442        }
443    }
444
445    private void line ()
446    {
447        try
448        {
449            m_out.write (LINE);
450            m_out.newLine ();
451        }
452        catch (IOException ioe)
453        {
454            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
455        }
456    }
457
458    private void eol ()
459    {
460        try
461        {
462            m_out.newLine ();
463        }
464        catch (IOException ioe)
465        {
466            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
467        }
468    }
469
470    private void close ()
471    {
472        if (m_out != null)
473        {
474            try
475            {
476                m_out.flush ();
477                m_out.close ();
478            }
479            catch (IOException ioe)
480            {
481                throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
482            }
483            finally
484            {
485                m_out = null;
486            }
487        }
488    }
489
490    private void openOutFile (final File file, final String encoding, final boolean mkdirs)
491    {
492        try
493        {
494            if (mkdirs)
495            {
496                final File parent = file.getParentFile ();
497                if (parent != null) parent.mkdirs ();
498            }
499
500            m_out = new BufferedWriter (new OutputStreamWriter (new FileOutputStream (file), encoding), IO_BUF_SIZE);
501        }
502        catch (UnsupportedEncodingException uee)
503        {
504            // TODO: error code
505            throw new EMMARuntimeException (uee);
506        }
507        // note: in J2SDK 1.3 FileOutputStream constructor's throws clause
508        // was narrowed to FileNotFoundException:
509        catch (IOException fnfe) // FileNotFoundException
510        {
511            // TODO: error code
512            throw new EMMARuntimeException (fnfe);
513        }
514    }
515
516
517    private char m_separator = '\t'; // TODO: set this
518
519    private LinkedList /* IITem */ m_queue;
520    private BufferedWriter m_out;
521
522    private static final String TYPE = "txt";
523    private static final String LINE = "-------------------------------------------------------------------------------";
524
525    private static final int IO_BUF_SIZE = 32 * 1024;
526
527} // end of class
528// ----------------------------------------------------------------------------