Home | History | Annotate | Download | only in data
      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  */
      9 package com.vladium.emma.data;
     10 
     11 import java.io.File;
     12 import java.io.IOException;
     13 
     14 import com.vladium.logging.Logger;
     15 import com.vladium.util.Files;
     16 import com.vladium.util.IConstants;
     17 import com.vladium.util.IProperties;
     18 import com.vladium.util.asserts.$assert;
     19 import com.vladium.util.exception.Exceptions;
     20 import com.vladium.emma.IAppConstants;
     21 import com.vladium.emma.IAppErrorCodes;
     22 import com.vladium.emma.EMMAProperties;
     23 import com.vladium.emma.EMMARuntimeException;
     24 import 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  */
     34 public
     35 final 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 // ----------------------------------------------------------------------------