/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved. * * This program and the accompanying materials are made available under * the terms of the Common Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/cpl-v10.html * * $Id: MergeProcessor.java,v 1.1.1.1.2.2 2004/07/16 23:32:29 vlad_r Exp $ */ package com.vladium.emma.data; import java.io.File; import java.io.IOException; import com.vladium.logging.Logger; import com.vladium.util.Files; import com.vladium.util.IConstants; import com.vladium.util.IProperties; import com.vladium.util.asserts.$assert; import com.vladium.util.exception.Exceptions; import com.vladium.emma.IAppConstants; import com.vladium.emma.IAppErrorCodes; import com.vladium.emma.EMMAProperties; import com.vladium.emma.EMMARuntimeException; import com.vladium.emma.Processor; // ---------------------------------------------------------------------------- /* * This class was not meant to be public by design. It is made to to work around * access bugs in reflective invocations. */ /** * @author Vlad Roubtsov, (C) 2003 */ public final class MergeProcessor extends Processor implements IAppErrorCodes { // public: ................................................................ public static MergeProcessor create () { return new MergeProcessor (); } /** * * @param path [null is equivalent to an empty array] */ public synchronized final void setDataPath (final String [] path) { if ((path == null) || (path.length == 0)) m_dataPath = IConstants.EMPTY_FILE_ARRAY; else m_dataPath = Files.pathToFiles (path, true); } /** * NOTE: there is no setter for merge attribute because this processor * always overwrites the out file [to ensure compaction] * * @param fileName [null unsets the previous override setting] */ public synchronized final void setSessionOutFile (final String fileName) { if (fileName == null) m_sdataOutFile = null; else { final File _file = new File (fileName); if (_file.exists () && ! _file.isFile ()) throw new IllegalArgumentException ("not a file: [" + _file.getAbsolutePath () + "]"); m_sdataOutFile = _file; } } // protected: ............................................................. protected void validateState () { super.validateState (); if (m_dataPath == null) throw new IllegalStateException ("data path not set"); // [m_sdataOutFile can be null] // [m_propertyOverrides can be null] } protected void _run (final IProperties toolProperties) { final Logger log = m_log; final boolean verbose = m_log.atVERBOSE (); if (verbose) { log.verbose (IAppConstants.APP_VERBOSE_BUILD_ID); // [assertion: m_dataPath != null] log.verbose ("input data path:"); log.verbose ("{"); for (int p = 0; p < m_dataPath.length; ++ p) { final File f = m_dataPath [p]; final String nonexistent = f.exists () ? "" : "{nonexistent} "; log.verbose (" " + nonexistent + f.getAbsolutePath ()); } log.verbose ("}"); } else { log.info ("processing input files ..."); } // get the data out settings: File sdataOutFile = m_sdataOutFile; { if (sdataOutFile == null) sdataOutFile = new File (toolProperties.getProperty (EMMAProperties.PROPERTY_SESSION_DATA_OUT_FILE, EMMAProperties.DEFAULT_SESSION_DATA_OUT_FILE)); } RuntimeException failure = null; try { IMetaData mdata = null; ICoverageData cdata = null; // merge all data files: try { final long start = log.atINFO () ? System.currentTimeMillis () : 0; for (int f = 0; f < m_dataPath.length; ++ f) { final File dataFile = m_dataPath [f]; if (verbose) log.verbose ("processing input file [" + dataFile.getAbsolutePath () + "] ..."); final IMergeable [] fileData = DataFactory.load (dataFile); final IMetaData _mdata = (IMetaData) fileData [DataFactory.TYPE_METADATA]; if (_mdata != null) { if (verbose) log.verbose (" loaded " + _mdata.size () + " metadata entries"); if (mdata == null) mdata = _mdata; else mdata = (IMetaData) mdata.merge (_mdata); // note: later datapath entries override earlier ones } final ICoverageData _cdata = (ICoverageData) fileData [DataFactory.TYPE_COVERAGEDATA]; if (_cdata != null) { if (verbose) log.verbose (" loaded " + _cdata.size () + " coverage data entries"); if (cdata == null) cdata = _cdata; else cdata = (ICoverageData) cdata.merge (_cdata); // note: later datapath entries override earlier ones } ++ m_dataFileCount; } if (log.atINFO ()) { final long end = System.currentTimeMillis (); log.info (m_dataFileCount + " file(s) read and merged in " + (end - start) + " ms"); } if (((mdata == null) || mdata.isEmpty ()) && ((cdata == null) || cdata.isEmpty ())) { log.warning ("nothing to do: no metadata or coverage data found in any of the input files"); // TODO: throw exception or exit quietly? return; } } catch (IOException ioe) { // TODO: handle ioe.printStackTrace (System.out); } if (verbose) { if (mdata != null) { log.verbose (" merged metadata contains " + mdata.size () + " entries"); } if (cdata != null) { log.verbose (" merged coverage data contains " + cdata.size () + " entries"); } } // write merged data into output file: { $assert.ASSERT (sdataOutFile != null, "sdataOutFile not null"); // the case of the output file being one of the input files is // supported; however, for safety reasons we create output in // a temp file and rename it only when the data is safely persisted: boolean rename = false; File tempDataOutFile = null; final File canonicalDataOutFile = Files.canonicalizeFile (sdataOutFile); for (int f = 0; f < m_dataPath.length; ++ f) { final File canonicalDataFile = Files.canonicalizeFile (m_dataPath [f]); if (canonicalDataOutFile.equals (canonicalDataFile)) { rename = true; break; } } if (rename) // create a temp out file { File tempFileDir = canonicalDataOutFile.getParentFile (); if (tempFileDir == null) tempFileDir = new File (""); // length > 3: final String tempFileName = Files.getFileName (canonicalDataOutFile) + IAppConstants.APP_NAME_LC; final String tempFileExt = EMMAProperties.PROPERTY_TEMP_FILE_EXT; try { tempDataOutFile = Files.createTempFile (tempFileDir, tempFileName, tempFileExt); } catch (IOException ioe) { // TODO: error code throw new EMMARuntimeException (ioe); } log.warning ("the specified output file is one of the input files [" + canonicalDataOutFile + "]"); log.warning ("all merged data will be written to a temp file first [" + tempDataOutFile.getAbsolutePath () + "]"); } // persist merged session data: { final long start = log.atINFO () ? System.currentTimeMillis () : 0; File persistFile = null; try { persistFile = tempDataOutFile != null ? tempDataOutFile : canonicalDataOutFile; // TODO: the persister API is ugly, redesign if ((mdata == null) || mdata.isEmpty ()) DataFactory.persist (cdata, persistFile, false); // never merge to enforce compaction behavior else if ((cdata == null) || cdata.isEmpty ()) DataFactory.persist (mdata, persistFile, false); // never merge to enforce compaction behavior else DataFactory.persist (new SessionData (mdata, cdata), persistFile, false); // never merge to enforce compaction behavior } catch (IOException ioe) { if (persistFile != null) persistFile.delete (); // TODO: error code throw new EMMARuntimeException (ioe); } catch (Error e) { if (persistFile != null) persistFile.delete (); throw e; // re-throw } if (rename) // rename-with-delete temp out file into the desired out file { if (! Files.renameFile (tempDataOutFile, canonicalDataOutFile, true)) // overwrite the original archive { // error code throw new EMMARuntimeException ("could not rename temporary file [" + tempDataOutFile.getAbsolutePath () + "] to [" + canonicalDataOutFile + "]: make sure the original file is not locked and can be deleted"); } } if (log.atINFO ()) { final long end = System.currentTimeMillis (); log.info ("merged/compacted data written to [" + canonicalDataOutFile + "] {in " + (end - start) + " ms}"); } } } } catch (SecurityException se) { failure = new EMMARuntimeException (SECURITY_RESTRICTION, new String [] {IAppConstants.APP_NAME}, se); } catch (RuntimeException re) { failure = re; } finally { reset (); } if (failure != null) { if (Exceptions.unexpectedFailure (failure, EXPECTED_FAILURES)) { throw new EMMARuntimeException (UNEXPECTED_FAILURE, new Object [] {failure.toString (), IAppConstants.APP_BUG_REPORT_LINK}, failure); } else throw failure; } } // package: ............................................................... // private: ............................................................... private MergeProcessor () { m_dataPath = IConstants.EMPTY_FILE_ARRAY; } private void reset () { m_dataFileCount = 0; } // caller-settable state [scoped to this runner instance]: private File [] m_dataPath; // required to be non-null for run() [is set to canonicalized form] private File m_sdataOutFile; // user override; can be null for run() // internal run()-scoped state: private int m_dataFileCount; private static final Class [] EXPECTED_FAILURES; // set in <clinit> static { EXPECTED_FAILURES = new Class [] { EMMARuntimeException.class, IllegalArgumentException.class, IllegalStateException.class, }; } } // end of class // ----------------------------------------------------------------------------