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:29 vlad_r Exp $
8 */
9package com.vladium.emma.report.xml;
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.io.Writer;
18import java.util.Date;
19import java.util.Iterator;
20
21import com.vladium.util.Files;
22import com.vladium.util.IConstants;
23import com.vladium.util.IProperties;
24import com.vladium.util.Strings;
25import com.vladium.emma.IAppConstants;
26import com.vladium.emma.IAppErrorCodes;
27import com.vladium.emma.EMMAProperties;
28import com.vladium.emma.EMMARuntimeException;
29import com.vladium.emma.data.ICoverageData;
30import com.vladium.emma.data.IMetaData;
31import com.vladium.emma.report.AbstractReportGenerator;
32import com.vladium.emma.report.AllItem;
33import com.vladium.emma.report.ClassItem;
34import com.vladium.emma.report.IItem;
35import com.vladium.emma.report.IItemAttribute;
36import com.vladium.emma.report.IItemMetadata;
37import com.vladium.emma.report.ItemComparator;
38import com.vladium.emma.report.MethodItem;
39import com.vladium.emma.report.PackageItem;
40import com.vladium.emma.report.SourcePathCache;
41import com.vladium.emma.report.SrcFileItem;
42
43// ----------------------------------------------------------------------------
44/**
45 * @author Vlad Roubtsov, (C) 2003
46 */
47public
48final class ReportGenerator extends AbstractReportGenerator
49                            implements IAppErrorCodes
50{
51    // public: ................................................................
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_view.getRoot ().accept (this, null);
73            close ();
74        }
75
76        if (trace1)
77        {
78            end = System.currentTimeMillis ();
79
80            m_log.trace1 ("process", "[" + getType () + "] report generated in " + (end - start) + " ms");
81        }
82    }
83
84    public void cleanup ()
85    {
86        close ();
87
88        super.cleanup ();
89    }
90
91
92    // IItemVisitor:
93
94    public Object visit (final AllItem item, final Object ctx)
95    {
96        try
97        {
98            File outFile = m_settings.getOutFile ();
99            if (outFile == null)
100            {
101                outFile = new File ("coverage.xml");
102                m_settings.setOutFile (outFile);
103            }
104
105            final File fullOutFile = Files.newFile (m_settings.getOutDir (), outFile);
106
107            m_log.info ("writing [" + getType () + "] report to [" + fullOutFile.getAbsolutePath () + "] ...");
108
109            openOutFile (fullOutFile, m_settings.getOutEncoding (), true);
110
111            // XML header:
112            m_out.write ("<?xml version=\"1.0\" encoding=\"" + m_settings.getOutEncoding () + "\"?>");
113
114            // build ID stamp:
115            try
116            {
117                final StringBuffer label = new StringBuffer (101);
118
119                label.append ("<!-- ");
120                label.append (IAppConstants.APP_NAME);
121                label.append (" v"); label.append (IAppConstants.APP_VERSION_WITH_BUILD_ID_AND_TAG);
122                label.append (" report, generated ");
123                label.append (new Date (EMMAProperties.getTimeStamp ()));
124                label.append (" -->");
125
126                m_out.write (label.toString ());
127                m_out.newLine ();
128
129                m_out.flush ();
130            }
131            catch (IOException ioe)
132            {
133                throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
134            }
135
136            eol ();
137            openElementTag ("report");
138            closeElementTag (false);
139            m_out.incIndent ();
140
141            // stats summary section:
142            eol ();
143            openElementTag ("stats");
144            closeElementTag (false);
145            m_out.incIndent ();
146            {
147                emitStatsCount ("packages", item.getChildCount ());
148                emitStatsCount ("classes", item.getAggregate (IItem.TOTAL_CLASS_COUNT));
149                emitStatsCount ("methods", item.getAggregate (IItem.TOTAL_METHOD_COUNT));
150
151                if (m_srcView && m_hasSrcFileInfo)
152                {
153                    emitStatsCount ("srcfiles", item.getAggregate (IItem.TOTAL_SRCFILE_COUNT));
154
155                    if (m_hasLineNumberInfo)
156                        emitStatsCount ("srclines", item.getAggregate (IItem.TOTAL_LINE_COUNT));
157                }
158            }
159            m_out.decIndent ();
160            eol ();
161            endElement ("stats");
162
163            // actual coverage data:
164            eol ();
165            openElementTag ("data");
166            closeElementTag (false);
167            m_out.incIndent ();
168            {
169                final ItemComparator childrenOrder = m_typeSortComparators [PackageItem.getTypeMetadata ().getTypeID ()];
170                emitItem (item, childrenOrder);
171            }
172            m_out.decIndent ();
173            eol ();
174            endElement ("data");
175
176            m_out.decIndent ();
177            eol ();
178            endElement ("report");
179        }
180        catch (IOException ioe)
181        {
182            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
183        }
184
185        return ctx;
186    }
187
188
189    public Object visit (final PackageItem item, final Object ctx)
190    {
191        if (m_verbose) m_log.verbose ("  report: processing package [" + item.getName () + "] ...");
192
193        try
194        {
195            final ItemComparator childrenOrder = m_typeSortComparators [m_srcView ? SrcFileItem.getTypeMetadata ().getTypeID () : ClassItem.getTypeMetadata ().getTypeID ()];
196            emitItem (item, childrenOrder);
197        }
198        catch (IOException ioe)
199        {
200            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
201        }
202
203        return ctx;
204    }
205
206
207    public Object visit (final SrcFileItem item, final Object ctx)
208    {
209        try
210        {
211            final ItemComparator childrenOrder = m_typeSortComparators [ClassItem.getTypeMetadata ().getTypeID ()];
212            emitItem (item, childrenOrder);
213        }
214        catch (IOException ioe)
215        {
216            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
217        }
218
219        return ctx;
220    }
221
222    public Object visit (final ClassItem item, final Object ctx)
223    {
224        try
225        {
226            final ItemComparator childrenOrder = m_typeSortComparators [MethodItem.getTypeMetadata ().getTypeID ()];
227            emitItem (item, childrenOrder);
228        }
229        catch (IOException ioe)
230        {
231            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
232        }
233
234        return ctx;
235    }
236
237    public Object visit (final MethodItem item, final Object ctx)
238    {
239        try
240        {
241            emitItem (item, null);
242        }
243        catch (IOException ioe)
244        {
245            throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
246        }
247
248        return ctx;
249    }
250
251    // protected: .............................................................
252
253    // package: ...............................................................
254
255    // private: ...............................................................
256
257
258    private static final class IndentingWriter extends BufferedWriter
259    {
260        public void newLine () throws IOException
261        {
262            m_state = 0;
263            super.write (IConstants.EOL, 0, IConstants.EOL.length ());
264        }
265
266        public void write (final char [] cbuf, final int off, final int len) throws IOException
267        {
268            indent ();
269            super.write (cbuf, off, len);
270        }
271
272        public void write (int c) throws IOException
273        {
274            indent ();
275            super.write (c);
276        }
277
278        public void write (final String s, final int off, final int len) throws IOException
279        {
280            indent ();
281            super.write (s, off, len);
282        }
283
284
285        IndentingWriter (final Writer out, final int buffer, final int indent)
286        {
287            super (out, buffer);
288            m_indent = indent;
289        }
290
291
292        void incIndent (final int delta)
293        {
294            if (delta < 0) throw new IllegalArgumentException ("delta be non-negative: " + delta);
295
296            m_indent += delta;
297        }
298
299        void incIndent ()
300        {
301            incIndent (INDENT_INCREMENT);
302        }
303
304        void decIndent (final int delta)
305        {
306            if (delta < 0) throw new IllegalArgumentException ("delta be non-negative: " + delta);
307            if (delta > m_indent) throw new IllegalArgumentException ("delta = " + delta + ", current indent = " + m_indent);
308
309            m_indent -= delta;
310        }
311
312        void decIndent ()
313        {
314            decIndent (INDENT_INCREMENT);
315        }
316
317        String getIndent ()
318        {
319            if (m_indent <= 0)
320                return "";
321            else
322            {
323                if ((m_sindent == null) || (m_sindent.length () < m_indent))
324                {
325                    final char [] ca = new char [m_indent];
326
327                    for (int i = 0; i < m_indent; ++ i) ca [i] = ' ';
328                    m_sindent = new String (ca);
329
330                    return m_sindent;
331                }
332                else
333                {
334                    return m_sindent.substring (0, m_indent);
335                }
336            }
337        }
338
339
340        private void indent ()
341            throws IOException
342        {
343            if (m_state == 0)
344            {
345                final String indent = getIndent ();
346                super.write (indent, 0, indent.length ());
347
348                m_state = 1;
349            }
350        }
351
352
353        private int m_indent;
354        private int m_state;
355        private transient String m_sindent;
356
357        private static final int INDENT_INCREMENT = 2;
358
359    } // end of nested class
360
361
362    private void emitStatsCount (final String name, final int value)
363        throws IOException
364    {
365        eol ();
366        openElementTag (name);
367        m_out.write (" value=\"" + value);
368        m_out.write ('"');
369        closeElementTag (true);
370    }
371
372    private void emitItem (final IItem item, final ItemComparator childrenOrder)
373        throws IOException
374    {
375        final IItemMetadata metadata = item.getMetadata ();
376        final int [] columns = m_settings.getColumnOrder ();
377        final String tag = metadata.getTypeName ();
378
379        eol ();
380
381        // emit opening tag with name attribute:
382        {
383            openElementTag (tag);
384
385            m_out.write (" name=\"");
386            m_out.write (Strings.HTMLEscape (item.getName ()));
387            m_out.write ('"');
388
389            closeElementTag (false);
390        }
391
392        eol ();
393
394        m_out.incIndent ();
395
396        emitItemCoverage (item, columns);
397
398        final boolean deeper = (childrenOrder != null) && (m_settings.getDepth () > metadata.getTypeID ()) && (item.getChildCount () > 0);
399
400        if (deeper)
401        {
402            for (Iterator packages = item.getChildren (childrenOrder); packages.hasNext (); )
403            {
404                ((IItem) packages.next ()).accept (this, null);
405            }
406
407            eol ();
408        }
409
410        m_out.decIndent ();
411
412        // emit closing tag:
413        {
414            endElement (tag);
415        }
416    }
417
418    /*
419     * No header row, just data rows.
420     */
421    private void emitItemCoverage (final IItem item, final int [] columns)
422        throws IOException
423    {
424        final StringBuffer buf = new StringBuffer (64);
425
426        for (int c = 0, cLimit = columns.length; c < cLimit; ++ c)
427        {
428            final int attrID = columns [c];
429
430            if (attrID != IItemAttribute.ATTRIBUTE_NAME_ID)
431            {
432                final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ());
433
434                if (attr != null)
435                {
436                    openElementTag ("coverage");
437
438                    m_out.write (" type=\"");
439                    m_out.write (Strings.HTMLEscape (attr.getName ()));
440                    m_out.write ("\" value=\"");
441                    attr.format (item, buf);
442                    m_out.write (Strings.HTMLEscape (buf.toString ()));
443                    m_out.write ('"');
444                    buf.setLength (0);
445
446                    closeElementTag (true);
447
448                    eol ();
449                }
450            }
451        }
452
453    }
454
455    private void openElementTag (final String tag)
456        throws IOException
457    {
458        m_out.write ('<');
459        m_out.write (tag);
460    }
461
462    private void closeElementTag (final boolean simple)
463        throws IOException
464    {
465        if (simple)
466            m_out.write ("/>");
467        else
468            m_out.write ('>');
469    }
470
471    private void endElement (final String tag)
472        throws IOException
473    {
474        m_out.write ("</");
475        m_out.write (tag);
476        m_out.write ('>');
477    }
478
479    private void eol ()
480        throws IOException
481    {
482        m_out.newLine ();
483    }
484
485    private void close ()
486    {
487        if (m_out != null)
488        {
489            try
490            {
491                m_out.flush ();
492                m_out.close ();
493            }
494            catch (IOException ioe)
495            {
496                throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe);
497            }
498            finally
499            {
500                m_out = null;
501            }
502        }
503    }
504
505    private void openOutFile (final File file, final String encoding, final boolean mkdirs)
506    {
507        try
508        {
509            if (mkdirs)
510            {
511                final File parent = file.getParentFile ();
512                if (parent != null) parent.mkdirs ();
513            }
514
515            m_out = new IndentingWriter (new OutputStreamWriter (new FileOutputStream (file), encoding), IO_BUF_SIZE, 0);
516        }
517        catch (UnsupportedEncodingException uee)
518        {
519            // TODO: error code
520            throw new EMMARuntimeException (uee);
521        }
522        // note: in J2SDK 1.3 FileOutputStream constructor's throws clause
523        // was narrowed to FileNotFoundException:
524        catch (IOException fnfe) // FileNotFoundException
525        {
526            // TODO: error code
527            throw new EMMARuntimeException (fnfe);
528        }
529    }
530
531
532    private IndentingWriter m_out;
533
534    private static final String TYPE = "xml";
535    private static final int IO_BUF_SIZE = 64 * 1024;
536
537} // end of class
538// ----------------------------------------------------------------------------