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: InstrProcessorST.java,v 1.1.1.1.2.3 2004/07/16 23:32:28 vlad_r Exp $ 8 */ 9package com.vladium.emma.instr; 10 11import java.io.File; 12import java.io.FileInputStream; 13import java.io.FileNotFoundException; 14import java.io.FileOutputStream; 15import java.io.IOException; 16import java.io.InputStream; 17import java.io.OutputStream; 18import java.io.RandomAccessFile; 19import java.util.Date; 20import java.util.jar.Attributes; 21import java.util.jar.JarFile; 22import java.util.jar.JarInputStream; 23import java.util.jar.JarOutputStream; 24import java.util.jar.Manifest; 25import java.util.zip.CRC32; 26import java.util.zip.ZipEntry; 27import java.util.zip.ZipInputStream; 28import java.util.zip.ZipOutputStream; 29 30import com.vladium.jcd.cls.ClassDef; 31import com.vladium.jcd.compiler.ClassWriter; 32import com.vladium.jcd.parser.ClassDefParser; 33import com.vladium.logging.Logger; 34import com.vladium.util.ByteArrayOStream; 35import com.vladium.util.Descriptors; 36import com.vladium.util.Files; 37import com.vladium.util.IPathEnumerator; 38import com.vladium.util.IProperties; 39//import com.vladium.util.Profiler; 40import com.vladium.util.Property; 41import com.vladium.util.asserts.$assert; 42import com.vladium.util.exception.Exceptions; 43//import com.vladium.utils.ObjectSizeProfiler; 44import com.vladium.emma.IAppConstants; 45import com.vladium.emma.IAppErrorCodes; 46import com.vladium.emma.EMMAProperties; 47import com.vladium.emma.EMMARuntimeException; 48import com.vladium.emma.data.CoverageOptions; 49import com.vladium.emma.data.CoverageOptionsFactory; 50import com.vladium.emma.data.DataFactory; 51import com.vladium.emma.data.IMetaData; 52 53// ---------------------------------------------------------------------------- 54/** 55 * @author Vlad Roubtsov, (C) 2003 56 */ 57final class InstrProcessorST extends InstrProcessor 58 implements IAppErrorCodes 59{ 60 // public: ................................................................ 61 62 // TODO: performance of 'copy' mode could be improved by pushing dir filtering 63 // all the way to the path enumerator [although dir listing is reasonably fast] 64 65 66 // IPathEnumerator.IPathHandler: 67 68 69 public final void handleArchiveStart (final File parentDir, final File archive, final Manifest manifest) 70 { 71 final Logger log = m_log; 72 if (log.atTRACE2 ()) log.trace2 ("handleArchiveStart", "[" + parentDir + "] [" + archive + "]"); 73 74 // TODO: pass manifest into this callback, if any 75 // TODO: detect if manifest corresonds to a previously intrumented archive already ? 76 77 if (DO_DEPENDS_CHECKING) 78 { 79 final File fullArchiveFile = Files.newFile (parentDir, archive); 80 m_currentArchiveTS = fullArchiveFile.lastModified (); 81 82 if ($assert.ENABLED) $assert.ASSERT (m_currentArchiveTS > 0, "invalid ts: " + m_currentArchiveTS); 83 } 84 85 if ((m_outMode == OutMode.OUT_MODE_FULLCOPY) || (m_outMode == OutMode.OUT_MODE_OVERWRITE)) 86 { 87 final Manifest outManifest = manifest != null 88 ? new Manifest (manifest) // shallow copy 89 : new Manifest (); 90 91 // set some basic main attributes: 92 93 final Attributes mainAttrs = outManifest.getMainAttributes (); 94 if (manifest == null) mainAttrs.put (Attributes.Name.MANIFEST_VERSION, "1.0"); 95 mainAttrs.put (new Attributes.Name ("Created-By"), IAppConstants.APP_NAME + " v" + IAppConstants.APP_VERSION_WITH_BUILD_ID_AND_TAG); 96 97 // note: Manifest makes these 72-char-safe 98 99 mainAttrs.put (Attributes.Name.IMPLEMENTATION_TITLE, "instrumented version of [" + archive.getAbsolutePath () + "]"); 100 mainAttrs.put (Attributes.Name.SPECIFICATION_TITLE, "instrumented on " + new Date (m_timeStamp) + " [" + Property.getSystemFingerprint () + "]"); 101 102 // TODO: remove entries related to signing 103 104 if (m_outMode == OutMode.OUT_MODE_FULLCOPY) 105 { 106 // create an identically named artive in outdir/lib [the stream is 107 // closed in the archive end event handler]: 108 109 try 110 { 111 final OutputStream out = new FileOutputStream (getFullOutFile (parentDir, archive, IN_LIB)); 112 113 m_archiveOut = outManifest != null ? new JarOutputStream (out, outManifest) : new JarOutputStream (out); 114 } 115 catch (IOException ioe) 116 { 117 // TODO: error code 118 throw new EMMARuntimeException (ioe); 119 } 120 } 121 else if (m_outMode == OutMode.OUT_MODE_OVERWRITE) 122 { 123 // create a temp file in the same dir [moved into the original one 124 // in the archive end event handler]: 125 126 m_origArchiveFile = Files.newFile (parentDir, archive); 127 128 // length > 3: 129 final String archiveName = Files.getFileName (archive) + IAppConstants.APP_NAME_LC; 130 final String archiveExt = EMMAProperties.PROPERTY_TEMP_FILE_EXT; 131 132 try 133 { 134 m_tempArchiveFile = Files.createTempFile (parentDir, archiveName, archiveExt); 135 if (log.atTRACE2 ()) log.trace2 ("handleArchiveStart", "created temp archive [" + m_tempArchiveFile.getAbsolutePath () + "]"); 136 137 final OutputStream out = new FileOutputStream (m_tempArchiveFile); 138 139 m_archiveOut = outManifest != null ? new JarOutputStream (out, outManifest) : new JarOutputStream (out); 140 } 141 catch (IOException ioe) 142 { 143 // TODO: error code 144 throw new EMMARuntimeException (ioe); 145 } 146 } 147 } 148 } 149 150 public final void handleArchiveEntry (final JarInputStream in, final ZipEntry entry) 151 { 152 final Logger log = m_log; 153 if (log.atTRACE2 ()) log.trace2 ("handleArchiveEntry", "[" + entry.getName () + "]"); 154 155 final String name = entry.getName (); 156 final String lcName = name.toLowerCase (); 157 158 final boolean notcopymode = (m_outMode == OutMode.OUT_MODE_FULLCOPY) || (m_outMode == OutMode.OUT_MODE_OVERWRITE); 159 160 boolean copyEntry = false; 161 162 if (lcName.endsWith (".class")) 163 { 164 final String className = name.substring (0, name.length () - 6).replace ('/', '.'); 165 166 // it is possible that a class with this name has already been processed; 167 // however, we can't skip it here because there is no guarantee that 168 // the runtime classpath will be identical to the instrumentation path 169 170 // [the metadata will still contain only a single entry for a class with 171 // this name: it is the responsibility of the user to ensure that both 172 // files represent the same class; in the future I might use a more 173 // robust internal strategy that uses something other than a class name 174 // as a metadata key] 175 176 if ((m_coverageFilter == null) || m_coverageFilter.included (className)) 177 { 178 InputStream clsin = null; 179 try 180 { 181 File outFile = null; 182 File fullOutFile = null; 183 184 if (DO_DEPENDS_CHECKING) 185 { 186 // in 'copy' mode 187 188 if (m_outMode == OutMode.OUT_MODE_COPY) 189 { 190 outFile = new File (className.replace ('.', File.separatorChar).concat (".class")); 191 fullOutFile = getFullOutFile (null, outFile, IN_CLASSES); 192 193 // if we already processed this class name within this instrumentor 194 // run, skip duplicates in copy mode: 195 196 if (m_mdata.hasDescriptor (Descriptors.javaNameToVMName (className))) 197 return; 198 199 // BUG_SF989071: using outFile here instead resulted in 200 // a zero result regardless of whether the target existed or not 201 final long outTimeStamp = fullOutFile.lastModified (); // 0 if 'fullOutFile' does not exist or if an I/O error occurs 202 203 if (outTimeStamp > 0) 204 { 205 long inTimeStamp = entry.getTime (); // can return -1 206 if (inTimeStamp < 0) inTimeStamp = m_currentArchiveTS; // default to the archive file timestamp 207 208 if ($assert.ENABLED) $assert.ASSERT (inTimeStamp > 0); 209 210 if (inTimeStamp <= outTimeStamp) 211 { 212 if (log.atVERBOSE ()) log.verbose ("destination file [" + outFile + "] skipped: more recent than the source"); 213 return; 214 } 215 } 216 } 217 } 218 219 readZipEntry (in, entry); 220 221 final ClassDef clsDef = ClassDefParser.parseClass (m_readbuf, m_readpos); 222 223 m_visitor.process (clsDef, m_outMode == OutMode.OUT_MODE_OVERWRITE, true, true, m_instrResult); 224 if (m_instrResult.m_instrumented) 225 { 226 if ($assert.ENABLED) $assert.ASSERT (m_instrResult.m_descriptor != null, "no descriptor created for an instrumented class"); 227 228 ++ m_classInstrs; 229 230 // update metadata [if this class has not been seen before]: 231 232 m_mdata.add (m_instrResult.m_descriptor, false); 233 234 // class def modified: write it to an array and submit a write job 235 236 m_baos.reset (); 237 ClassWriter.writeClassTable (clsDef, m_baos); 238 239 if (notcopymode) 240 { 241 // [destination is a zip entry] 242 243 entry.setTime (m_timeStamp); 244 addJob (new EntryWriteJob (m_archiveOut, m_baos.copyByteArray (), entry, false)); 245 } 246 else // copy mode 247 { 248 // [destination is a file] 249 250 if (! DO_DEPENDS_CHECKING) // this block is just a complement to the one above (where fullOutFile is inited) 251 { 252 outFile = new File (className.replace ('.', File.separatorChar).concat (".class")); 253 fullOutFile = getFullOutFile (null, outFile, IN_CLASSES); 254 } 255 256 addJob (new FileWriteJob (fullOutFile, m_baos.copyByteArray (), true)); 257 } 258 } 259 else if (notcopymode) 260 { 261 // original class def already read into m_readbuf: 262 // clone the array and submit an entry write job 263 264 final byte [] data = new byte [m_readpos]; 265 System.arraycopy (m_readbuf, 0, data, 0, data.length); 266 ++ m_classCopies; 267 268 entry.setTime (m_timeStamp); 269 addJob (new EntryWriteJob (m_archiveOut, data, entry, true)); 270 } 271 } 272 catch (FileNotFoundException fnfe) 273 { 274 // ignore: this should never happen 275 if ($assert.ENABLED) 276 { 277 fnfe.printStackTrace (System.out); 278 } 279 } 280 catch (IOException ioe) 281 { 282 // TODO: error code 283 throw new EMMARuntimeException (ioe); 284 } 285 finally 286 { 287 if (clsin != null) 288 try 289 { 290 clsin.close (); 291 } 292 catch (Exception e) 293 { 294 // TODO: error code 295 throw new EMMARuntimeException (e); 296 } 297 } 298 } 299 else 300 { 301 // copy excluded .class entries in full copy and overwrite modes: 302 copyEntry = notcopymode; 303 } 304 } 305 else 306 { 307 // copy non-.class entries in full copy and overwrite modes: 308 copyEntry = notcopymode; 309 310 // skipping these entries here is important: this is done as a complement 311 // to Sun jar API workarounds as detailed in PathEnumerator.enumeratePathArchive(): 312 313 if (copyEntry && name.equalsIgnoreCase ("META-INF/")) 314 copyEntry = false; 315 if (copyEntry && name.equalsIgnoreCase (JarFile.MANIFEST_NAME)) 316 copyEntry = false; 317 318 // TODO: skip signature-related entries (.SF and .RSA/.DSA/.PGP) 319 } 320 321 if (copyEntry) 322 { 323 try 324 { 325 readZipEntry (in, entry); 326 327 final byte [] data = new byte [m_readpos]; 328 System.arraycopy (m_readbuf, 0, data, 0, data.length); 329 ++ m_classCopies; 330 331 entry.setTime (m_timeStamp); 332 addJob (new EntryWriteJob (m_archiveOut, data, entry, true)); 333 } 334 catch (IOException ioe) 335 { 336 // TODO: error code 337 throw new EMMARuntimeException (ioe); 338 } 339 } 340 } 341 342 public final void handleArchiveEnd (final File parentDir, final File archive) 343 { 344 final Logger log = m_log; 345 if (log.atTRACE2 ()) log.trace2 ("handleArchiveEnd", "[" + parentDir + "] [" + archive + "]"); 346 347 m_currentArchiveTS = Long.MAX_VALUE; 348 349 if ((m_outMode == OutMode.OUT_MODE_FULLCOPY) || (m_outMode == OutMode.OUT_MODE_OVERWRITE)) 350 { 351 try 352 { 353 drainJobQueue (); // drain the queue before closing the archive 354 355 m_archiveOut.flush (); 356 m_archiveOut.close (); 357 m_archiveOut = null; 358 } 359 catch (IOException ioe) 360 { 361 // TODO: error code 362 throw new EMMARuntimeException (ioe); 363 } 364 365 // in overwrite mode replace the original archive with the temp archive: 366 367 if (m_outMode == OutMode.OUT_MODE_OVERWRITE) 368 { 369 if (! Files.renameFile (m_tempArchiveFile, m_origArchiveFile, true)) // overwrite the original archive 370 { 371 // TODO: disable temp file cleanup in this case so that the user 372 // could do it manually later? 373 374 // error code 375 throw new EMMARuntimeException ("could not rename temporary file [" + m_tempArchiveFile + "] to [" + m_origArchiveFile + "]: make sure the original file is not locked and can be deleted"); 376 } 377 else 378 { 379 if (log.atTRACE2 ()) log.trace2 ("handleArchiveEnd", "renamed temp archive [" + m_tempArchiveFile.getAbsolutePath () + "] to [" + m_origArchiveFile + "]"); 380 m_origArchiveFile = m_tempArchiveFile = null; 381 } 382 } 383 } 384 } 385 386 387 public final void handleDirStart (final File pathDir, final File dir) 388 { 389 final Logger log = m_log; 390 if (log.atTRACE2 ()) log.trace2 ("handleDirStart", "[" + pathDir + "] [" + dir + "]"); 391 392 // in full copy mode, create all dirs here; in copy mode, do it as part 393 // of writing each individual file: 394 395 if (m_outMode == OutMode.OUT_MODE_FULLCOPY) 396 { 397 final File saveDir = new File (getFullOutDir (pathDir, IN_CLASSES), dir.getPath ()); 398 createDir (saveDir, true); 399 } 400 } 401 402 public final void handleFile (final File pathDir, final File file) 403 { 404 final Logger log = m_log; 405 if (log.atTRACE2 ()) log.trace2 ("handleFile", "[" + pathDir + "] [" + file + "]"); 406 407 final String name = file.getPath (); 408 final String lcName = name.toLowerCase (); 409 410 final boolean fullcopymode = (m_outMode == OutMode.OUT_MODE_FULLCOPY); 411 final boolean mkdir = (m_outMode == OutMode.OUT_MODE_COPY); 412 413 414 boolean copyFile = false; 415 416 if (lcName.endsWith (".class")) 417 { 418 final String className = name.substring (0, name.length () - 6).replace (File.separatorChar, '.'); 419 420 // it is possible that a class with this name has already been processed; 421 // however, we can't skip it here because there is no guarantee that 422 // the runtime classpath will be identical to the instrumentation path 423 424 // [the metadata will still contain only a single entry for a class with 425 // this name: it is the responsibility of the user to ensure that both 426 // files represent the same class; in the future I might use a more 427 // robust internal strategy that uses something other than a class name 428 // as a metadata key] 429 430 if ((m_coverageFilter == null) || m_coverageFilter.included (className)) 431 { 432 InputStream clsin = null; 433 try 434 { 435 final File inFile = Files.newFile (pathDir, file.getPath ()); 436 final File fullOutFile = getFullOutFile (pathDir, file, IN_CLASSES); 437 438 if (DO_DEPENDS_CHECKING) 439 { 440 if (m_outMode == OutMode.OUT_MODE_COPY) 441 { 442 // if we already processed this class name within this instrumentor 443 // run, skip duplicates in copy mode: 444 445 if (m_mdata.hasDescriptor (Descriptors.javaNameToVMName (className))) 446 return; 447 448 // otherwise, instrument only if the dest file is out of date 449 // wrt to the source file: 450 451 final long outTimeStamp = fullOutFile.lastModified (); // 0 if 'fullOutFile' does not exist or if an I/O error occurs 452 453 if (outTimeStamp > 0) 454 { 455 final long inTimeStamp = inFile.lastModified (); 456 457 if (inTimeStamp <= outTimeStamp) 458 { 459 if (log.atVERBOSE ()) log.verbose ("destination file [" + fullOutFile + "] skipped: more recent that the source file"); 460 return; 461 } 462 } 463 } 464 } 465 466 readFile (inFile); 467 468 ClassDef clsDef = ClassDefParser.parseClass (m_readbuf, m_readpos); 469 470 // in non-overwrite modes, bail if src file already instrumented: 471 m_visitor.process (clsDef, m_outMode == OutMode.OUT_MODE_OVERWRITE, true, true, m_instrResult); 472 if (m_instrResult.m_instrumented) 473 { 474 if ($assert.ENABLED) $assert.ASSERT (m_instrResult.m_descriptor != null, "no descriptor created for an instrumented class"); 475 476 ++ m_classInstrs; 477 478 // update metadata [if this class has not been seen before]: 479 480// ObjectSizeProfiler.SizeProfile profile = ObjectSizeProfiler.profile (m_instrResult.m_descriptor, true); 481// System.out.println (clsDef.getName () + " metadata:"); 482// System.out.println (profile.root ().dump (0.2)); 483 484 m_mdata.add (m_instrResult.m_descriptor, false); 485 486 // class def modified: write it to an array and submit a write job 487 488 m_baos.reset (); 489 ClassWriter.writeClassTable (clsDef, m_baos); 490 clsDef = null; 491 492 final byte [] outdata = m_baos.copyByteArray (); 493 494 addJob (new FileWriteJob (fullOutFile, outdata, mkdir)); 495 } 496 else if (fullcopymode) 497 { 498 // original class def already read into m_readbuf: 499 // clone the array and submit a file write job 500 501 clsDef = null; 502 503 final byte [] outdata = new byte [m_readpos]; 504 System.arraycopy (m_readbuf, 0, outdata, 0, m_readpos); 505 ++ m_classCopies; 506 507 addJob (new FileWriteJob (fullOutFile, outdata, mkdir)); 508 } 509 } 510 catch (FileNotFoundException fnfe) 511 { 512 // ignore: this should never happen 513 if ($assert.ENABLED) 514 { 515 fnfe.printStackTrace (System.out); 516 } 517 } 518 catch (IOException ioe) 519 { 520 // TODO: error code 521 throw new EMMARuntimeException (ioe); 522 } 523 finally 524 { 525 if (clsin != null) 526 try 527 { 528 clsin.close (); 529 } 530 catch (Exception e) 531 { 532 // TODO: error code 533 throw new EMMARuntimeException (e); 534 } 535 } 536 } 537 else 538 { 539 // copy excluded .class files in full copy mode: 540 copyFile = fullcopymode; 541 } 542 } 543 else 544 { 545 // copy non-.class files in full copy mode: 546 copyFile = fullcopymode; 547 } 548 549 if (copyFile) 550 { 551 try 552 { 553 final File inFile = Files.newFile (pathDir, file.getPath ()); 554 readFile (inFile); 555 556 final byte [] data = new byte [m_readpos]; 557 System.arraycopy (m_readbuf, 0, data, 0, data.length); 558 ++ m_classCopies; 559 560 final File outFile = getFullOutFile (pathDir, file, IN_CLASSES); 561 562 addJob (new FileWriteJob (outFile, data, mkdir)); 563 } 564 catch (IOException ioe) 565 { 566 // TODO: error code 567 throw new EMMARuntimeException (ioe); 568 } 569 } 570 } 571 572 public final void handleDirEnd (final File pathDir, final File dir) 573 { 574 final Logger log = m_log; 575 if (log.atTRACE2 ()) log.trace2 ("handleDirEnd", "[" + pathDir + "] [" + dir + "]"); 576 577 // in overwrite mode, flush the job queue before going to the next directory: 578 579 if (m_outMode == OutMode.OUT_MODE_OVERWRITE) 580 { 581 try 582 { 583 drainJobQueue (); 584 } 585 catch (IOException ioe) 586 { 587 // TODO: error code 588 throw new EMMARuntimeException (ioe); 589 } 590 } 591 } 592 593 // protected: ............................................................. 594 595 596 protected void reset () 597 { 598 m_visitor = null; 599 m_mdata = null; 600 m_readbuf = null; 601 m_baos = null; 602 603 for (int j = 0; j < m_jobs.length; ++ j) m_jobs [j] = null; 604 605 if (CLEANUP_TEMP_ARCHIVE_ON_ERRORS) 606 { 607 if (m_archiveOut != null) 608 try { m_archiveOut.close (); } catch (Exception ignore) {} // unlock the file descriptor for deletion 609 610 if (m_tempArchiveFile != null) 611 m_tempArchiveFile.delete (); 612 } 613 614 m_archiveOut = null; 615 m_origArchiveFile = null; 616 m_tempArchiveFile = null; 617 618 super.reset (); 619 } 620 621 protected void _run (final IProperties toolProperties) 622 { 623 final Logger log = m_log; 624 625 final boolean verbose = log.atVERBOSE (); 626 if (verbose) 627 { 628 log.verbose (IAppConstants.APP_VERBOSE_BUILD_ID); 629 630 // [assertion: m_instrPath != null] 631 log.verbose ("instrumentation path:"); 632 log.verbose ("{"); 633 for (int p = 0; p < m_instrPath.length; ++ p) 634 { 635 final File f = m_instrPath [p]; 636 final String nonexistent = f.exists () ? "" : "{nonexistent} "; 637 638 log.verbose (" " + nonexistent + f.getAbsolutePath ()); 639 } 640 log.verbose ("}"); 641 642 // [assertion: m_outMode != null] 643 log.verbose ("instrumentation output mode: " + m_outMode); 644 } 645 else 646 { 647 log.info ("processing instrumentation path ..."); 648 } 649 650 RuntimeException failure = null; 651 try 652 { 653 long start = System.currentTimeMillis (); 654 m_timeStamp = start; 655 656 // construct instr path enumerator [throws on illegal input only]: 657 final IPathEnumerator enumerator = IPathEnumerator.Factory.create (m_instrPath, m_canonical, this); 658 659 // create out dir(s): 660 { 661 if (m_outMode != OutMode.OUT_MODE_OVERWRITE) createDir (m_outDir, true); 662 663 if ((m_outMode == OutMode.OUT_MODE_FULLCOPY)) 664 { 665 final File classesDir = Files.newFile (m_outDir, CLASSES); 666 createDir (classesDir, false); // note: not using mkdirs() here 667 668 final File libDir = Files.newFile (m_outDir, LIB); 669 createDir (libDir, false); // note: not using mkdirs() here 670 } 671 } 672 673 // get the data out settings [note: this is not conditioned on m_dumpRawData]: 674 File mdataOutFile = m_mdataOutFile; 675 Boolean mdataOutMerge = m_mdataOutMerge; 676 { 677 if (mdataOutFile == null) 678 mdataOutFile = new File (toolProperties.getProperty (EMMAProperties.PROPERTY_META_DATA_OUT_FILE, 679 EMMAProperties.DEFAULT_META_DATA_OUT_FILE)); 680 681 if (mdataOutMerge == null) 682 { 683 final String _dataOutMerge = toolProperties.getProperty (EMMAProperties.PROPERTY_META_DATA_OUT_MERGE, 684 EMMAProperties.DEFAULT_META_DATA_OUT_MERGE.toString ()); 685 mdataOutMerge = Property.toBoolean (_dataOutMerge) ? Boolean.TRUE : Boolean.FALSE; 686 } 687 } 688 689 if (verbose) 690 { 691 log.verbose ("metadata output file: " + mdataOutFile.getAbsolutePath ()); 692 log.verbose ("metadata output merge mode: " + mdataOutMerge); 693 } 694 695 // TODO: can also register an exit hook to clean up temp files, but this is low value 696 697 // allocate I/O buffers: 698 m_readbuf = new byte [BUF_SIZE]; // don't reuse this across run() calls to reset it to the original size 699 m_readpos = 0; 700 m_baos = new ByteArrayOStream (BUF_SIZE); // don't reuse this across run() calls to reset it to the original size 701 702 // reset job queue position: 703 m_jobPos = 0; 704 705 m_currentArchiveTS = Long.MAX_VALUE; 706 707 final CoverageOptions options = CoverageOptionsFactory.create (toolProperties); 708 m_visitor = new InstrVisitor (options); // TODO: reuse this? 709 710 m_mdata = DataFactory.newMetaData (options); 711 712 // actual work is driven by the path enumerator: 713 try 714 { 715 enumerator.enumerate (); 716 drainJobQueue (); 717 } 718 catch (IOException ioe) 719 { 720 throw new EMMARuntimeException (INSTR_IO_FAILURE, ioe); 721 } 722 723 if (log.atINFO ()) 724 { 725 final long end = System.currentTimeMillis (); 726 727 log.info ("instrumentation path processed in " + (end - start) + " ms"); 728 log.info ("[" + m_classInstrs + " class(es) instrumented, " + m_classCopies + " resource(s) copied]"); 729 } 730 731 // persist metadata: 732 try 733 { 734 // TODO: create an empty file earlier to catch any errors sooner? [to avoid scenarios where a user waits throught the entire instr run to find out the file could not be written to] 735 736 if ($assert.ENABLED) $assert.ASSERT (mdataOutFile != null, "m_metadataOutFile is null"); 737 738 if (verbose) 739 { 740 if (m_mdata != null) 741 { 742 log.verbose ("metadata contains " + m_mdata.size () + " entries"); 743 } 744 } 745 746 if (m_mdata.isEmpty ()) 747 { 748 log.info ("no output created: metadata is empty"); 749 } 750 else 751 { 752 start = System.currentTimeMillis (); 753 DataFactory.persist (m_mdata, mdataOutFile, mdataOutMerge.booleanValue ()); 754 final long end = System.currentTimeMillis (); 755 756 if (log.atINFO ()) 757 { 758 log.info ("metadata " + (mdataOutMerge.booleanValue () ? "merged into" : "written to") + " [" + mdataOutFile.getAbsolutePath () + "] {in " + (end - start) + " ms}"); 759 } 760 } 761 } 762 catch (IOException ioe) 763 { 764 throw new EMMARuntimeException (OUT_IO_FAILURE, new Object [] {mdataOutFile.getAbsolutePath ()}, ioe); 765 } 766 } 767 catch (SecurityException se) 768 { 769 failure = new EMMARuntimeException (SECURITY_RESTRICTION, new String [] {IAppConstants.APP_NAME}, se); 770 } 771 catch (RuntimeException re) 772 { 773 failure = re; 774 } 775 finally 776 { 777 reset (); 778 } 779 780 if (failure != null) 781 { 782 if (Exceptions.unexpectedFailure (failure, EXPECTED_FAILURES)) 783 { 784 throw new EMMARuntimeException (UNEXPECTED_FAILURE, 785 new Object [] {failure.toString (), IAppConstants.APP_BUG_REPORT_LINK}, 786 failure); 787 } 788 else 789 throw failure; 790 } 791 } 792 793 // package: ............................................................... 794 795 796 InstrProcessorST () 797 { 798 m_jobs = new Job [JOB_QUEUE_SIZE]; 799 m_instrResult = new InstrVisitor.InstrResult (); 800 } 801 802 803 static void writeFile (final byte [] data, final File outFile, final boolean mkdirs) 804 throws IOException 805 { 806 RandomAccessFile raf = null; 807 try 808 { 809 if (mkdirs) 810 { 811 final File parent = outFile.getParentFile (); 812 if (parent != null) parent.mkdirs (); // no error checking here [errors will be throw below] 813 } 814 815 raf = new RandomAccessFile (outFile, "rw"); 816 if (DO_RAF_EXTENSION) raf.setLength (data.length); 817 818 raf.write (data); 819 } 820 finally 821 { 822 if (raf != null) raf.close (); // note: intentionally letting the exception percolate up 823 } 824 } 825 826 static void writeZipEntry (final byte [] data, final ZipOutputStream out, final ZipEntry entry, final boolean isCopy) 827 throws IOException 828 { 829 if (isCopy) 830 { 831 out.putNextEntry (entry); // reusing ' entry' is ok here because we are not changing the data 832 try 833 { 834 out.write (data); 835 } 836 finally 837 { 838 out.closeEntry (); 839 } 840 } 841 else 842 { 843 // need to compute the checksum which slows things down quite a bit: 844 845 final ZipEntry entryCopy = new ZipEntry (entry.getName ()); 846 entryCopy.setTime (entry.getTime ()); // avoid repeated calls to System.currentTimeMillis() inside the zip stream 847 entryCopy.setMethod (ZipOutputStream.STORED); 848 // [directory status is implicit in the name] 849 entryCopy.setSize (data.length); 850 entryCopy.setCompressedSize (data.length); 851 852 final CRC32 crc = new CRC32 (); 853 crc.update (data); 854 entryCopy.setCrc (crc.getValue ()); 855 856 out.putNextEntry (entryCopy); 857 try 858 { 859 out.write (data); 860 } 861 finally 862 { 863 out.closeEntry (); 864 } 865 } 866 } 867 868 // private: ............................................................... 869 870 871 private static abstract class Job 872 { 873 protected abstract void run () throws IOException; 874 875 } // end of nested class 876 877 878 private static final class FileWriteJob extends Job 879 { 880 protected void run () throws IOException 881 { 882 writeFile (m_data, m_outFile, m_mkdirs); 883 m_data = null; 884 } 885 886 FileWriteJob (final File outFile, final byte [] data, final boolean mkdirs) 887 { 888 m_outFile = outFile; 889 m_data = data; 890 m_mkdirs = mkdirs; 891 } 892 893 894 final File m_outFile; 895 final boolean m_mkdirs; 896 byte [] m_data; 897 898 } // end of nested class 899 900 901 private static final class EntryWriteJob extends Job 902 { 903 protected void run () throws IOException 904 { 905 writeZipEntry (m_data, m_out, m_entry, m_isCopy); 906 m_data = null; 907 } 908 909 EntryWriteJob (final ZipOutputStream out, final byte [] data, final ZipEntry entry, final boolean isCopy) 910 { 911 m_out = out; 912 m_data = data; 913 m_entry = entry; 914 m_isCopy = isCopy; 915 } 916 917 918 final ZipOutputStream m_out; 919 byte [] m_data; 920 final ZipEntry m_entry; 921 final boolean m_isCopy; 922 923 } // end of nested class 924 925 926 private void addJob (final Job job) 927 throws FileNotFoundException, IOException 928 { 929 if (m_jobPos == JOB_QUEUE_SIZE) drainJobQueue (); 930 931 m_jobs [m_jobPos ++] = job; 932 } 933 934 private void drainJobQueue () 935 throws IOException 936 { 937 for (int j = 0; j < m_jobPos; ++ j) 938 { 939 final Job job = m_jobs [j]; 940 if (job != null) // a guard just in case 941 { 942 m_jobs [j] = null; 943 job.run (); 944 } 945 } 946 947 m_jobPos = 0; 948 } 949 950 /* 951 * Reads into m_readbuf (m_readpos is updated correspondingly) 952 */ 953 private void readFile (final File file) 954 throws IOException 955 { 956 final int length = (int) file.length (); 957 958 ensureReadCapacity (length); 959 960 InputStream in = null; 961 try 962 { 963 in = new FileInputStream (file); 964 965 int totalread = 0; 966 for (int read; 967 (totalread < length) && (read = in.read (m_readbuf, totalread, length - totalread)) >= 0; 968 totalread += read); 969 m_readpos = totalread; 970 } 971 finally 972 { 973 if (in != null) try { in.close (); } catch (Exception ignore) {} 974 } 975 } 976 977 /* 978 * Reads into m_readbuf (m_readpos is updated correspondingly) 979 */ 980 private void readZipEntry (final ZipInputStream in, final ZipEntry entry) 981 throws IOException 982 { 983 final int length = (int) entry.getSize (); // can be -1 if unknown 984 985 if (length >= 0) 986 { 987 ensureReadCapacity (length); 988 989 int totalread = 0; 990 for (int read; 991 (totalread < length) && (read = in.read (m_readbuf, totalread, length - totalread)) >= 0; 992 totalread += read); 993 m_readpos = totalread; 994 } 995 else 996 { 997 ensureReadCapacity (BUF_SIZE); 998 999 m_baos.reset (); 1000 for (int read; (read = in.read (m_readbuf)) >= 0; m_baos.write (m_readbuf, 0, read)); 1001 1002 m_readbuf = m_baos.copyByteArray (); 1003 m_readpos = m_readbuf.length; 1004 } 1005 } 1006 1007 private void ensureReadCapacity (final int capacity) 1008 { 1009 if (m_readbuf.length < capacity) 1010 { 1011 final int readbuflen = m_readbuf.length; 1012 m_readbuf = null; 1013 m_readbuf = new byte [Math.max (readbuflen << 1, capacity)]; 1014 } 1015 } 1016 1017 1018 // internal run()-scoped state: 1019 1020 private final Job [] m_jobs; 1021 private final InstrVisitor.InstrResult m_instrResult; 1022 1023 private InstrVisitor m_visitor; 1024 private IMetaData m_mdata; 1025 private byte [] m_readbuf; 1026 private int m_readpos; 1027 private ByteArrayOStream m_baos; // TODO: code to guard this from becoming too large 1028 private int m_jobPos; 1029 private long m_currentArchiveTS; 1030 private File m_origArchiveFile, m_tempArchiveFile; 1031 private JarOutputStream m_archiveOut; 1032 private long m_timeStamp; 1033 1034 1035 private static final int BUF_SIZE = 32 * 1024; 1036 private static final int JOB_QUEUE_SIZE = 128; // a reasonable size chosen empirically after testing a few SCSI/IDE machines 1037 private static final boolean CLEANUP_TEMP_ARCHIVE_ON_ERRORS = true; 1038 private static final boolean DO_RAF_EXTENSION = true; 1039 1040 private static final boolean DO_DEPENDS_CHECKING = true; 1041 private static final Class [] EXPECTED_FAILURES; // set in <clinit> 1042 1043 static 1044 { 1045 EXPECTED_FAILURES = new Class [] 1046 { 1047 EMMARuntimeException.class, 1048 IllegalArgumentException.class, 1049 IllegalStateException.class, 1050 }; 1051 } 1052 1053} // end of class 1054// ----------------------------------------------------------------------------