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: MergeProcessor.java,v 1.1.1.1.2.2 2004/07/16 23:32:29 vlad_r Exp $
8 */
9package com.vladium.emma.data;
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.asserts.$assert;
19import com.vladium.util.exception.Exceptions;
20import com.vladium.emma.IAppConstants;
21import com.vladium.emma.IAppErrorCodes;
22import com.vladium.emma.EMMAProperties;
23import com.vladium.emma.EMMARuntimeException;
24import com.vladium.emma.Processor;
25
26// ----------------------------------------------------------------------------
27/*
28 * This class was not meant to be public by design. It is made to to work around
29 * access bugs in reflective invocations.
30 */
31/**
32 * @author Vlad Roubtsov, (C) 2003
33 */
34public
35final class MergeProcessor extends Processor
36                           implements IAppErrorCodes
37{
38    // public: ................................................................
39
40    public static MergeProcessor create ()
41    {
42        return new MergeProcessor ();
43    }
44
45    /**
46     *
47     * @param path [null is equivalent to an empty array]
48     */
49    public synchronized final void setDataPath (final String [] path)
50    {
51        if ((path == null) || (path.length == 0))
52            m_dataPath = IConstants.EMPTY_FILE_ARRAY;
53        else
54            m_dataPath = Files.pathToFiles (path, true);
55    }
56
57    /**
58     * NOTE: there is no setter for merge attribute because this processor
59     * always overwrites the out file [to ensure compaction]
60     *
61     * @param fileName [null unsets the previous override setting]
62     */
63    public synchronized final void setSessionOutFile (final String fileName)
64    {
65        if (fileName == null)
66            m_sdataOutFile = null;
67        else
68        {
69            final File _file = new File (fileName);
70
71            if (_file.exists () && ! _file.isFile ())
72                throw new IllegalArgumentException ("not a file: [" + _file.getAbsolutePath () + "]");
73
74            m_sdataOutFile = _file;
75        }
76    }
77
78    // protected: .............................................................
79
80
81    protected void validateState ()
82    {
83        super.validateState ();
84
85        if (m_dataPath == null)
86            throw new IllegalStateException ("data path not set");
87
88        // [m_sdataOutFile can be null]
89
90        // [m_propertyOverrides can be null]
91    }
92
93
94    protected void _run (final IProperties toolProperties)
95    {
96        final Logger log = m_log;
97
98        final boolean verbose = m_log.atVERBOSE ();
99        if (verbose)
100        {
101            log.verbose (IAppConstants.APP_VERBOSE_BUILD_ID);
102
103            // [assertion: m_dataPath != null]
104            log.verbose ("input data path:");
105            log.verbose ("{");
106            for (int p = 0; p < m_dataPath.length; ++ p)
107            {
108                final File f = m_dataPath [p];
109                final String nonexistent = f.exists () ? "" : "{nonexistent} ";
110
111                log.verbose ("  " + nonexistent + f.getAbsolutePath ());
112            }
113            log.verbose ("}");
114        }
115        else
116        {
117            log.info ("processing input files ...");
118        }
119
120        // get the data out settings:
121        File sdataOutFile = m_sdataOutFile;
122        {
123            if (sdataOutFile == null)
124                sdataOutFile = new File (toolProperties.getProperty (EMMAProperties.PROPERTY_SESSION_DATA_OUT_FILE,
125                                                                     EMMAProperties.DEFAULT_SESSION_DATA_OUT_FILE));
126        }
127
128        RuntimeException failure = null;
129        try
130        {
131            IMetaData mdata = null;
132            ICoverageData cdata = null;
133
134            // merge all data files:
135            try
136            {
137                final long start = log.atINFO () ? System.currentTimeMillis () : 0;
138
139                for (int f = 0; f < m_dataPath.length; ++ f)
140                {
141                    final File dataFile = m_dataPath [f];
142                    if (verbose) log.verbose ("processing input file [" + dataFile.getAbsolutePath () + "] ...");
143
144                    final IMergeable [] fileData = DataFactory.load (dataFile);
145
146                    final IMetaData _mdata = (IMetaData) fileData [DataFactory.TYPE_METADATA];
147                    if (_mdata != null)
148                    {
149                        if (verbose) log.verbose ("  loaded " + _mdata.size () + " metadata entries");
150
151                        if (mdata == null)
152                            mdata = _mdata;
153                        else
154                            mdata = (IMetaData) mdata.merge (_mdata); // note: later datapath entries override earlier ones
155                    }
156
157                    final ICoverageData _cdata = (ICoverageData) fileData [DataFactory.TYPE_COVERAGEDATA];
158                    if (_cdata != null)
159                    {
160                        if (verbose) log.verbose ("  loaded " + _cdata.size () + " coverage data entries");
161
162                        if (cdata == null)
163                            cdata = _cdata;
164                        else
165                            cdata = (ICoverageData) cdata.merge (_cdata); // note: later datapath entries override earlier ones
166                    }
167
168                    ++ m_dataFileCount;
169                }
170
171                if (log.atINFO ())
172                {
173                    final long end = System.currentTimeMillis ();
174
175                    log.info (m_dataFileCount + " file(s) read and merged in " + (end - start) + " ms");
176                }
177
178                if (((mdata == null) || mdata.isEmpty ()) && ((cdata == null) || cdata.isEmpty ()))
179                {
180                    log.warning ("nothing to do: no metadata or coverage data found in any of the input files");
181
182                    // TODO: throw exception or exit quietly?
183                    return;
184                }
185            }
186            catch (IOException ioe)
187            {
188                // TODO: handle
189                ioe.printStackTrace (System.out);
190            }
191
192
193            if (verbose)
194            {
195                if (mdata != null)
196                {
197                    log.verbose ("  merged metadata contains " + mdata.size () + " entries");
198                }
199
200                if (cdata != null)
201                {
202                    log.verbose ("  merged coverage data contains " + cdata.size () + " entries");
203                }
204            }
205
206            // write merged data into output file:
207            {
208                $assert.ASSERT (sdataOutFile != null, "sdataOutFile not null");
209
210                // the case of the output file being one of the input files is
211                // supported; however, for safety reasons we create output in
212                // a temp file and rename it only when the data is safely persisted:
213
214                boolean rename = false;
215                File tempDataOutFile = null;
216
217                final File canonicalDataOutFile = Files.canonicalizeFile (sdataOutFile);
218
219                for (int f = 0; f < m_dataPath.length; ++ f)
220                {
221                    final File canonicalDataFile = Files.canonicalizeFile (m_dataPath [f]);
222                    if (canonicalDataOutFile.equals (canonicalDataFile))
223                    {
224                        rename = true;
225                        break;
226                    }
227                }
228
229                if (rename) // create a temp out file
230                {
231                    File tempFileDir = canonicalDataOutFile.getParentFile ();
232                    if (tempFileDir == null) tempFileDir = new File ("");
233
234                    // length > 3:
235                    final String tempFileName = Files.getFileName (canonicalDataOutFile) + IAppConstants.APP_NAME_LC;
236                    final String tempFileExt = EMMAProperties.PROPERTY_TEMP_FILE_EXT;
237
238                    try
239                    {
240                        tempDataOutFile = Files.createTempFile (tempFileDir, tempFileName, tempFileExt);
241                    }
242                    catch (IOException ioe)
243                    {
244                        // TODO: error code
245                        throw new EMMARuntimeException (ioe);
246                    }
247
248                    log.warning ("the specified output file is one of the input files [" + canonicalDataOutFile + "]");
249                    log.warning ("all merged data will be written to a temp file first [" + tempDataOutFile.getAbsolutePath ()  + "]");
250                }
251
252                // persist merged session data:
253                {
254                    final long start = log.atINFO () ? System.currentTimeMillis () : 0;
255
256                    File persistFile = null;
257                    try
258                    {
259                        persistFile = tempDataOutFile != null ? tempDataOutFile : canonicalDataOutFile;
260
261                        // TODO: the persister API is ugly, redesign
262
263                        if ((mdata == null) || mdata.isEmpty ())
264                            DataFactory.persist (cdata, persistFile, false); // never merge to enforce compaction behavior
265                        else if ((cdata == null) || cdata.isEmpty ())
266                            DataFactory.persist (mdata, persistFile, false); // never merge to enforce compaction behavior
267                        else
268                            DataFactory.persist (new SessionData (mdata, cdata), persistFile, false); // never merge to enforce compaction behavior
269                    }
270                    catch (IOException ioe)
271                    {
272                        if (persistFile != null) persistFile.delete ();
273
274                        // TODO: error code
275                        throw new EMMARuntimeException (ioe);
276                    }
277                    catch (Error e)
278                    {
279                        if (persistFile != null) persistFile.delete ();
280
281                        throw e; // re-throw
282                    }
283
284                    if (rename) // rename-with-delete temp out file into the desired out file
285                    {
286                        if (! Files.renameFile (tempDataOutFile, canonicalDataOutFile, true)) // overwrite the original archive
287                        {
288                            // error code
289                            throw new EMMARuntimeException ("could not rename temporary file [" + tempDataOutFile.getAbsolutePath () + "] to [" + canonicalDataOutFile + "]: make sure the original file is not locked and can be deleted");
290                        }
291                    }
292
293                    if (log.atINFO ())
294                    {
295                        final long end = System.currentTimeMillis ();
296
297                        log.info ("merged/compacted data written to [" + canonicalDataOutFile + "] {in " + (end - start) + " ms}");
298                    }
299                }
300            }
301        }
302        catch (SecurityException se)
303        {
304            failure = new EMMARuntimeException (SECURITY_RESTRICTION, new String [] {IAppConstants.APP_NAME}, se);
305        }
306        catch (RuntimeException re)
307        {
308            failure = re;
309        }
310        finally
311        {
312            reset ();
313        }
314
315        if (failure != null)
316        {
317            if (Exceptions.unexpectedFailure (failure, EXPECTED_FAILURES))
318            {
319                throw new EMMARuntimeException (UNEXPECTED_FAILURE,
320                                                new Object [] {failure.toString (), IAppConstants.APP_BUG_REPORT_LINK},
321                                                failure);
322            }
323            else
324                throw failure;
325        }
326    }
327
328
329    // package: ...............................................................
330
331    // private: ...............................................................
332
333
334    private MergeProcessor ()
335    {
336        m_dataPath = IConstants.EMPTY_FILE_ARRAY;
337    }
338
339
340    private void reset ()
341    {
342        m_dataFileCount = 0;
343    }
344
345
346    // caller-settable state [scoped to this runner instance]:
347
348    private File [] m_dataPath; // required to be non-null for run() [is set to canonicalized form]
349    private File m_sdataOutFile; // user override; can be null for run()
350
351    // internal run()-scoped state:
352
353    private int m_dataFileCount;
354
355    private static final Class [] EXPECTED_FAILURES; // set in <clinit>
356
357    static
358    {
359        EXPECTED_FAILURES = new Class []
360        {
361            EMMARuntimeException.class,
362            IllegalArgumentException.class,
363            IllegalStateException.class,
364        };
365    }
366
367} // end of class
368// ----------------------------------------------------------------------------