blob: dbae3d02c57c2b42c25f67fbcdf57d783023c78b [file] [log] [blame]
/* 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
// ----------------------------------------------------------------------------