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// ----------------------------------------------------------------------------