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: ReportProcessor.java,v 1.1.1.1.2.2 2004/07/16 23:32:29 vlad_r Exp $
8 */
9package com.vladium.emma.report;
10
11import java.io.File;
12import java.io.IOException;
13
14import com.vladium.logging.Logger;
15import com.vladium.util.Files;
16import com.vladium.util.IConstants;
17import com.vladium.util.IProperties;
18import com.vladium.util.Strings;
19import com.vladium.util.asserts.$assert;
20import com.vladium.util.exception.Exceptions;
21import com.vladium.emma.IAppConstants;
22import com.vladium.emma.IAppErrorCodes;
23import com.vladium.emma.EMMARuntimeException;
24import com.vladium.emma.Processor;
25import com.vladium.emma.data.DataFactory;
26import com.vladium.emma.data.ICoverageData;
27import com.vladium.emma.data.IMergeable;
28import com.vladium.emma.data.IMetaData;
29
30// ----------------------------------------------------------------------------
31/*
32 * This class was not meant to be public by design. It is made to to work around
33 * access bugs in reflective invocations.
34 */
35/**
36 * @author Vlad Roubtsov, (C) 2003
37 */
38public
39final class ReportProcessor extends Processor
40                            implements IAppErrorCodes
41{
42    // public: ................................................................
43
44    public static ReportProcessor create ()
45    {
46        return new ReportProcessor ();
47    }
48
49    /**
50     *
51     * @param path [null is equivalent to an empty array]
52     */
53    public synchronized final void setDataPath (final String [] path)
54    {
55        if ((path == null) || (path.length == 0))
56            m_dataPath = IConstants.EMPTY_FILE_ARRAY;
57        else
58            m_dataPath = Files.pathToFiles (path, true);
59    }
60
61    /**
62     * @param path [null is equivalent to no source path]
63     */
64    public synchronized void setSourcePath (final String [] path)
65    {
66        if (path == null)
67            m_sourcePath = null;
68        else
69            m_sourcePath = Files.pathToFiles (path, true); // always canonicalize source path
70    }
71
72    /**
73     *
74     * @param types [may not be null]
75     */
76    public synchronized void setReportTypes (final String [] types)
77    {
78        if (types == null) throw new IllegalArgumentException ("null input: types");
79
80        final String [] reportTypes = Strings.removeDuplicates (types, true);
81        if (reportTypes.length == 0) throw new IllegalArgumentException ("empty input: types");
82
83        if ($assert.ENABLED) $assert.ASSERT (reportTypes != null && reportTypes.length  > 0);
84
85
86        final IReportGenerator [] reportGenerators = new IReportGenerator [reportTypes.length];
87        for (int t = 0; t < reportTypes.length; ++ t)
88        {
89            reportGenerators [t] = AbstractReportGenerator.create (reportTypes [t]);
90        }
91
92        m_reportGenerators = reportGenerators;
93    }
94
95    // protected: .............................................................
96
97
98    protected void validateState ()
99    {
100        super.validateState ();
101
102        if (m_dataPath == null)
103            throw new IllegalStateException ("data path not set");
104
105        // [m_sourcePath can be null]
106
107        if ((m_reportGenerators == null) || (m_reportGenerators.length == 0))
108            throw new IllegalStateException ("report types not set");
109
110        // [m_propertyOverrides can be null]
111    }
112
113
114    protected void _run (final IProperties toolProperties)
115    {
116        final Logger log = m_log;
117
118        final boolean verbose = m_log.atVERBOSE ();
119        if (verbose)
120        {
121            log.verbose (IAppConstants.APP_VERBOSE_BUILD_ID);
122
123            // [assertion: m_dataPath != null]
124            log.verbose ("input data path:");
125            log.verbose ("{");
126            for (int p = 0; p < m_dataPath.length; ++ p)
127            {
128                final File f = m_dataPath [p];
129                final String nonexistent = f.exists () ? "" : "{nonexistent} ";
130
131                log.verbose ("  " + nonexistent + f.getAbsolutePath ());
132            }
133            log.verbose ("}");
134
135
136            if ((m_sourcePath == null) || (m_sourcePath.length == 0))
137            {
138                log.verbose ("source path not set");
139            }
140            else
141            {
142                log.verbose ("source path:");
143                log.verbose ("{");
144                for (int p = 0; p < m_sourcePath.length; ++ p)
145                {
146                    final File f = m_sourcePath [p];
147                    final String nonexistent = f.exists () ? "" : "{nonexistent} ";
148
149                    log.verbose ("  " + nonexistent + f.getAbsolutePath ());
150                }
151                log.verbose ("}");
152            }
153        }
154        else
155        {
156            log.info ("processing input files ...");
157        }
158
159        RuntimeException failure = null;
160        try
161        {
162            final long start = log.atINFO () ? System.currentTimeMillis () : 0;
163
164            IMetaData mdata = null;
165            ICoverageData cdata = null;
166
167            // merge all data files:
168            try
169            {
170                for (int f = 0; f < m_dataPath.length; ++ f)
171                {
172                    final File dataFile = m_dataPath [f];
173                    if (verbose) log.verbose ("processing input file [" + dataFile.getAbsolutePath () + "] ...");
174
175                    final IMergeable [] fileData = DataFactory.load (dataFile);
176
177                    final IMetaData _mdata = (IMetaData) fileData [DataFactory.TYPE_METADATA];
178                    if (_mdata != null)
179                    {
180                        if (verbose) log.verbose ("  loaded " + _mdata.size () + " metadata entries");
181
182                        if (mdata == null)
183                            mdata = _mdata;
184                        else
185                            mdata = (IMetaData) mdata.merge (_mdata); // note: later datapath entries override earlier ones
186                    }
187
188                    final ICoverageData _cdata = (ICoverageData) fileData [DataFactory.TYPE_COVERAGEDATA];
189                    if (_cdata != null)
190                    {
191                        if (verbose) log.verbose ("  loaded " + _cdata.size () + " coverage data entries");
192
193                        if (cdata == null)
194                            cdata = _cdata;
195                        else
196                            cdata = (ICoverageData) cdata.merge (_cdata); // note: later datapath entries override earlier ones
197                    }
198
199                    ++ m_dataFileCount;
200                }
201
202                if (log.atINFO ())
203                {
204                    final long end = System.currentTimeMillis ();
205
206                    log.info (m_dataFileCount + " file(s) read and merged in " + (end - start) + " ms");
207                }
208
209                if ((mdata == null) || mdata.isEmpty ())
210                {
211                    log.warning ("nothing to do: no metadata found in any of the data files");
212
213                    return;
214                }
215
216                if (cdata == null)
217                {
218                    log.warning ("nothing to do: no runtime coverage data found in any of the data files");
219
220                    return;
221                }
222
223                if (cdata.isEmpty ())
224                {
225                    log.warning ("no collected coverage data found in any of the data files [all reports will be empty]");
226                }
227
228
229                if (verbose)
230                {
231                    if (mdata != null)
232                    {
233                        log.verbose ("  merged metadata contains " + mdata.size () + " entries");
234                    }
235
236                    if (cdata != null)
237                    {
238                        log.verbose ("  merged coverage data contains " + cdata.size () + " entries");
239                    }
240                }
241
242                SourcePathCache srcpathCache = null;
243                if (m_sourcePath != null) srcpathCache = new SourcePathCache (m_sourcePath, true); // ignore non-existent source dirs
244
245                for (int g = 0; g < m_reportGenerators.length; ++ g)
246                {
247                    final IReportGenerator generator = m_reportGenerators [g];
248
249                    try
250                    {
251                        // no shallow copies of 'mdata' or 'cdata' are needed here
252                        // because this command never runs in a concurrent situation
253
254                        generator.process (mdata, cdata, srcpathCache, toolProperties);
255                    }
256                    catch (Throwable t)
257                    {
258                        // TODO: handle and continue
259                        t.printStackTrace (System.out);
260
261                        // TODO: continue here
262                        break;
263                    }
264                    finally
265                    {
266                        try { generator.cleanup (); } catch (Throwable ignore) {}
267                    }
268                }
269            }
270            catch (IOException ioe)
271            {
272                // TODO: handle
273                ioe.printStackTrace (System.out);
274            }
275        }
276        catch (SecurityException se)
277        {
278            failure = new EMMARuntimeException (SECURITY_RESTRICTION, new String [] {IAppConstants.APP_NAME}, se);
279        }
280        catch (RuntimeException re)
281        {
282            failure = re;
283        }
284        finally
285        {
286            reset ();
287        }
288
289        if (failure != null)
290        {
291            if (Exceptions.unexpectedFailure (failure, EXPECTED_FAILURES))
292            {
293                throw new EMMARuntimeException (UNEXPECTED_FAILURE,
294                                                new Object [] {failure.toString (), IAppConstants.APP_BUG_REPORT_LINK},
295                                                failure);
296            }
297            else
298                throw failure;
299        }
300    }
301
302    // package: ...............................................................
303
304    // private: ...............................................................
305
306
307    private ReportProcessor ()
308    {
309        m_dataPath = IConstants.EMPTY_FILE_ARRAY;
310    }
311
312    private void reset ()
313    {
314        m_dataFileCount = 0;
315    }
316
317
318    // caller-settable state [scoped to this runner instance]:
319
320    private File [] m_dataPath;     // required to be non-null for run()
321    private File [] m_sourcePath;   // can be null/empty for run()
322    private IReportGenerator [] m_reportGenerators; // required to be non-null for run()
323
324    // internal run()-scoped state:
325
326    private int m_dataFileCount;
327
328    private static final Class [] EXPECTED_FAILURES; // set in <clinit>
329
330    static
331    {
332        EXPECTED_FAILURES = new Class []
333        {
334            EMMARuntimeException.class,
335            IllegalArgumentException.class,
336            IllegalStateException.class,
337        };
338    }
339
340} // end of class
341// ----------------------------------------------------------------------------