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: AppRunner.java,v 1.1.1.1.2.2 2004/07/16 23:32:03 vlad_r Exp $ 8 */ 9package com.vladium.emma.rt; 10 11import java.io.File; 12import java.lang.reflect.InvocationTargetException; 13import java.lang.reflect.Method; 14import java.net.MalformedURLException; 15import java.util.HashMap; 16import java.util.Iterator; 17import java.util.ArrayList; 18import java.util.List; 19import java.util.Map; 20 21import com.vladium.logging.Logger; 22import com.vladium.util.Files; 23import com.vladium.util.IConstants; 24import com.vladium.util.IProperties; 25import com.vladium.util.Property; 26import com.vladium.util.SoftValueMap; 27import com.vladium.util.Strings; 28import com.vladium.util.asserts.$assert; 29import com.vladium.util.exception.Exceptions; 30import com.vladium.util.exit.ExitHookManager; 31import com.vladium.emma.AppLoggers; 32import com.vladium.emma.IAppConstants; 33import com.vladium.emma.IAppErrorCodes; 34import com.vladium.emma.EMMAProperties; 35import com.vladium.emma.EMMARuntimeException; 36import com.vladium.emma.Processor; 37import com.vladium.emma.filter.IInclExclFilter; 38import com.vladium.emma.data.CoverageOptionsFactory; 39import com.vladium.emma.data.IMetaData; 40import com.vladium.emma.data.ICoverageData; 41import com.vladium.emma.data.DataFactory; 42import com.vladium.emma.data.ISessionData; 43import com.vladium.emma.data.SessionData; 44import com.vladium.emma.report.AbstractReportGenerator; 45import com.vladium.emma.report.IReportGenerator; 46import com.vladium.emma.report.SourcePathCache; 47 48// ---------------------------------------------------------------------------- 49/** 50 * @author Vlad Roubtsov, (C) 2003 51 */ 52public 53final class AppRunner extends Processor 54 implements IAppErrorCodes 55{ 56 // public: ................................................................ 57 58 59 public static AppRunner create (final ClassLoader delegate) 60 { 61 return new AppRunner (delegate); 62 } 63 64 65 public synchronized void run () 66 { 67 validateState (); 68 69 // disable Runtime's own exit hook: 70 RTSettings.setStandaloneMode (false); // an optimization to disable RT's static init code [this line must precede any reference to RT] 71 RT.reset (true, false); // reset RT [RT creates 'cdata' and loads app properties] 72 73 // load tool properties: 74 final IProperties toolProperties; 75 { 76 IProperties appProperties = RT.getAppProperties (); // try to use app props consistent with RT's view of them 77 if (appProperties == null) appProperties = EMMAProperties.getAppProperties (); // don't use combine() 78 79 toolProperties = IProperties.Factory.combine (m_propertyOverrides, appProperties); 80 } 81 if ($assert.ENABLED) $assert.ASSERT (toolProperties != null, "toolProperties is null"); // can be empty, though 82 83 final Logger current = Logger.getLogger (); 84 final Logger log = AppLoggers.create (m_appName, toolProperties, current); 85 86 if (log.atTRACE1 ()) 87 { 88 log.trace1 ("run", "complete tool properties:"); 89 toolProperties.list (log.getWriter ()); 90 } 91 92 try 93 { 94 Logger.push (log); 95 m_log = log; 96 97 _run (toolProperties); 98 } 99 finally 100 { 101 if (m_log != null) 102 { 103 Logger.pop (m_log); 104 m_log = null; 105 } 106 } 107 } 108 109 110 /** 111 * @param path [null is equivalent to empty array] 112 * @param canonical 113 */ 114 public synchronized void setCoveragePath (String [] path, final boolean canonical) 115 { 116 if ((path == null) || (path.length == 0)) 117 m_coveragePath = IConstants.EMPTY_FILE_ARRAY; 118 else 119 m_coveragePath = Files.pathToFiles (path, canonical); 120 121 m_canonical = canonical; 122 } 123 124 public synchronized void setScanCoveragePath (final boolean scan) 125 { 126 m_scanCoveragePath = scan; 127 } 128 129 /** 130 * @param path [null is equivalent to no source path] 131 */ 132 public synchronized void setSourcePath (final String [] path) 133 { 134 if (path == null) 135 m_sourcePath = null; 136 else 137 m_sourcePath = Files.pathToFiles (path, true); // always canonicalize source path 138 } 139 140 /** 141 * 142 * @param specs [null is equivalent to no filtering (everything is included)] 143 */ 144 public synchronized final void setInclExclFilter (final String [] specs) 145 { 146 if (specs == null) 147 m_coverageFilter = null; 148 else 149 m_coverageFilter = IInclExclFilter.Factory.create (specs); 150 } 151 152 /** 153 * 154 * @param className [may not be null or empty] 155 * @param args [null is equivalent to an empty array] 156 */ 157 public synchronized void setAppClass (final String className, final String [] args) 158 { 159 if ((className == null) || (className.length () == 0)) 160 throw new IllegalArgumentException ("null/empty input: className"); 161 162 if (args != null) 163 { 164 final String [] _args = (String []) args.clone (); 165 166 for (int a = 0; a < _args.length; ++ a) 167 if (_args [a] == null) throw new IllegalArgumentException ("null input: args[" + a + "]"); 168 169 m_appArgs = _args; 170 } 171 else 172 { 173 m_appArgs = IConstants.EMPTY_STRING_ARRAY; 174 } 175 176 m_appClassName = className; 177 } 178 179 public synchronized void setDumpSessionData (final boolean dump) 180 { 181 m_dumpSessionData = dump; 182 } 183 184 /** 185 * 186 * @param fileName [null unsets the previous override setting] 187 */ 188 public synchronized final void setSessionOutFile (final String fileName) 189 { 190 if (fileName == null) 191 m_sdataOutFile = null; 192 else 193 { 194 final File _file = new File (fileName); 195 196 if (_file.exists () && ! _file.isFile ()) 197 throw new IllegalArgumentException ("not a file: [" + _file.getAbsolutePath () + "]"); 198 199 m_sdataOutFile = _file; 200 } 201 } 202 203 /** 204 * 205 * @param merge [null unsets the previous override setting] 206 */ 207 public synchronized final void setSessionOutMerge (final Boolean merge) 208 { 209 m_sdataOutMerge = merge; 210 } 211 212 /** 213 * 214 * @param types [may not be null] 215 */ 216 public synchronized void setReportTypes (final String [] types) 217 { 218 if (types == null) throw new IllegalArgumentException ("null input: types"); 219 220 final String [] reportTypes = Strings.removeDuplicates (types, true); 221 if (reportTypes.length == 0) throw new IllegalArgumentException ("empty input: types"); 222 223 if ($assert.ENABLED) $assert.ASSERT (reportTypes != null && reportTypes.length > 0); 224 225 226 final IReportGenerator [] reportGenerators = new IReportGenerator [reportTypes.length]; 227 for (int t = 0; t < reportTypes.length; ++ t) 228 { 229 reportGenerators [t] = AbstractReportGenerator.create (reportTypes [t]); 230 } 231 232 m_reportGenerators = reportGenerators; 233 } 234 235 // protected: ............................................................. 236 237 238 protected void validateState () 239 { 240 super.validateState (); 241 242 if ((m_appClassName == null) || (m_appClassName.length () == 0)) 243 throw new IllegalStateException ("application class name not set"); 244 245 if (m_appArgs == null) 246 throw new IllegalStateException ("application arguments not set"); 247 248 if (m_coveragePath == null) 249 throw new IllegalStateException ("coverage path not set"); 250 251 // [m_coverageFilter can be null] 252 253 // [m_sdataOutFile can be null] 254 // [m_sdataOutMerge can be null] 255 256 if ((m_reportGenerators == null) || (m_reportGenerators.length == 0)) 257 throw new IllegalStateException ("report types not set"); 258 259 // [m_sourcePath can be null/empty] 260 261 // [m_propertyOverrides can be null] 262 } 263 264 265 protected void _run (final IProperties toolProperties) 266 { 267 final Logger log = m_log; 268 269 final boolean verbose = log.atVERBOSE (); 270 if (verbose) 271 { 272 log.verbose (IAppConstants.APP_VERBOSE_BUILD_ID); 273 274 // [assertion: m_coveragePath != null] 275 log.verbose ("coverage path:"); 276 log.verbose ("{"); 277 for (int p = 0; p < m_coveragePath.length; ++ p) 278 { 279 final File f = m_coveragePath [p]; 280 final String nonexistent = f.exists () ? "" : "{nonexistent} "; 281 282 log.verbose (" " + nonexistent + f.getAbsolutePath ()); 283 } 284 log.verbose ("}"); 285 286 if ((m_sourcePath == null) || (m_sourcePath.length == 0)) 287 { 288 log.verbose ("source path not set"); 289 } 290 else 291 { 292 log.verbose ("source path:"); 293 log.verbose ("{"); 294 for (int p = 0; p < m_sourcePath.length; ++ p) 295 { 296 final File f = m_sourcePath [p]; 297 final String nonexistent = f.exists () ? "" : "{nonexistent} "; 298 299 log.verbose (" " + nonexistent + f.getAbsolutePath ()); 300 } 301 log.verbose ("}"); 302 } 303 } 304 305 // get the data out settings [note: this is not conditioned on m_dumpRawData]: 306 File sdataOutFile = m_sdataOutFile; 307 Boolean sdataOutMerge = m_sdataOutMerge; 308 { 309 if (sdataOutFile == null) 310 sdataOutFile = new File (toolProperties.getProperty (EMMAProperties.PROPERTY_SESSION_DATA_OUT_FILE, 311 EMMAProperties.DEFAULT_SESSION_DATA_OUT_FILE)); 312 313 if (sdataOutMerge == null) 314 { 315 final String _dataOutMerge = toolProperties.getProperty (EMMAProperties.PROPERTY_SESSION_DATA_OUT_MERGE, 316 EMMAProperties.DEFAULT_SESSION_DATA_OUT_MERGE.toString ()); 317 sdataOutMerge = Property.toBoolean (_dataOutMerge) ? Boolean.TRUE : Boolean.FALSE; 318 } 319 } 320 321 if (verbose && m_dumpSessionData) 322 { 323 log.verbose ("session data output file: " + sdataOutFile.getAbsolutePath ()); 324 log.verbose ("session data output merge mode: " + sdataOutMerge); 325 } 326 327 // get instr class loader delegation filter settings: 328 final IInclExclFilter forcedDelegationFilter 329 = IInclExclFilter.Factory.create (toolProperties.getProperty (InstrClassLoader.PROPERTY_FORCED_DELEGATION_FILTER), 330 COMMA_DELIMITERS, FORCED_DELEGATION_FILTER_SPECS); 331 final IInclExclFilter throughDelegationFilter 332 = IInclExclFilter.Factory.create (toolProperties.getProperty (InstrClassLoader.PROPERTY_THROUGH_DELEGATION_FILTER), 333 COMMA_DELIMITERS, null); 334 335 336 // TODO: consider injecting Runtime straight into appLoader namespace... 337 // TODO: create a thread group for all exit hooks? 338 339 340 // get a handle to exit hook manager singleton: 341 ExitHookManager runnerExitHookManager = null; 342 try 343 { 344 runnerExitHookManager = ExitHookManager.getSingleton (); // can throw 345 } 346 catch (Exception e) 347 { 348 // TODO: log/handle/warn 349 e.printStackTrace (System.out); 350 } 351 352 AppRunnerExitHook runnerExitHook = null; 353 RuntimeException failure = null; 354 355 try 356 { 357 SourcePathCache srcpathCache = null; 358 if (m_sourcePath != null) srcpathCache = new SourcePathCache (m_sourcePath, true); // ignore non-existent source dirs 359 360 // create session data containers: 361 ICoverageData cdata = RT.getCoverageData (); 362 if ($assert.ENABLED) $assert.ASSERT (cdata != null, "cdata is null"); 363 364 IMetaData mdata = DataFactory.newMetaData (CoverageOptionsFactory.create (toolProperties)); 365 366 runnerExitHook = new AppRunnerExitHook (log, m_dumpSessionData, sdataOutFile, sdataOutMerge.booleanValue (), mdata, cdata, m_reportGenerators, srcpathCache, toolProperties); 367 368 if (runnerExitHookManager != null) 369 runnerExitHookManager.addExitHook (runnerExitHook); 370 371 // --------------[ start of exit hook-protected section ]-------------- 372 373 Map classIOCache = null; 374 375 // scan the classpath to populate the initial metadata: 376 if (m_scanCoveragePath) 377 { 378 if (USE_SOFT_CACHE) 379 classIOCache = new SoftValueMap (INIT_CACHE_CAPACITY, 0.75F, SOFT_CACHE_READ_CHK_FREQUENCY, SOFT_CACHE_WRITE_CHK_FREQUENCY); 380 else 381 classIOCache = new HashMap (INIT_CACHE_CAPACITY, 0.75F); 382 383 final ClassPathProcessorST processor = new ClassPathProcessorST (m_coveragePath, m_canonical, mdata, m_coverageFilter, classIOCache); 384 385 // with a bit of work [ClassPathProcessorST needs to lock on the 386 // metadata, etc] this could be run concurrently with the app 387 // itself to improve perceived performance, however, I am not 388 // going to invest time in this; 389 390 // populate 'cache' [optional] and 'mdata': 391 processor.run (); 392 393 if (log.atTRACE1 ()) 394 { 395 log.trace1 ("run", "class cache size after cp scan: " + classIOCache.size ()); 396 log.trace1 ("run", "metadata size after cp scan: " + mdata.size ()); 397 } 398 } 399 400 401 // app runner does not need these handles anymore [only the exit hook runner maintains them]: 402 srcpathCache = null; 403 cdata = null; 404 405 final ClassLoader appLoader; 406 { 407 final IClassLoadHook loadHook = new InstrClassLoadHook (m_coverageFilter, mdata); 408 409 try 410 { 411 appLoader = new InstrClassLoader (m_delegate, m_coveragePath, forcedDelegationFilter, throughDelegationFilter, loadHook, classIOCache); 412 } 413 catch (SecurityException se) 414 { 415 throw new EMMARuntimeException (SECURITY_RESTRICTION, new String [] {IAppConstants.APP_NAME}, se); 416 } 417 catch (MalformedURLException mue) 418 { 419 throw new EMMARuntimeException (mue); 420 } 421 } 422 423 // app runner does not need these handles anymore: 424 mdata = null; 425 classIOCache = null; 426 427 428 final ClassLoader contextLoader; 429 boolean contextLoaderSet = false; 430 if (SET_CURRENT_CONTEXT_LOADER) 431 { 432 try 433 { 434 final Thread currentThread = Thread.currentThread (); 435 436 // TODO: rethink if this is the right place to do this 437 contextLoader = currentThread.getContextClassLoader (); 438 currentThread.setContextClassLoader (appLoader); 439 440 contextLoaderSet = true; 441 } 442 catch (SecurityException se) 443 { 444 throw new EMMARuntimeException (SECURITY_RESTRICTION, new String [] {IAppConstants.APP_NAME}, se); 445 } 446 } 447 448 449 ThreadGroup appThreadGroup = null; 450 try 451 { 452 // load [and possibly initialize] the app class: 453 454 final Class appClass; 455 try 456 { 457 // load [and force early initialization if INIT_AT_LOAD_TIME is 'true']: 458 appClass = Class.forName (m_appClassName, INIT_AT_LOAD_TIME, appLoader); 459 } 460 catch (ClassNotFoundException cnfe) 461 { 462 // TODO: dump the classloader tree into the error message as well 463 throw new EMMARuntimeException (MAIN_CLASS_NOT_FOUND, new String [] {m_appClassName}, cnfe); 464 } 465 catch (ExceptionInInitializerError eiie) // this should not happen for INIT_AT_LOAD_TIME=false 466 { 467 final Throwable cause = eiie.getException (); 468 469 throw new EMMARuntimeException (MAIN_CLASS_LOAD_FAILURE, new String [] {m_appClassName, cause.toString ()}, cause); 470 } 471 catch (Throwable t) 472 { 473 throw new EMMARuntimeException (MAIN_CLASS_NOT_FOUND, new String [] {m_appClassName}, t); 474 } 475 476 // ensure that the app is bootstrapped using appLoader: 477 { 478 final ClassLoader actualLoader = appClass.getClassLoader (); 479 if (actualLoader != appLoader) 480 { 481 final String loaderName = actualLoader != null ? actualLoader.getClass ().getName () : "<PRIMORDIAL>"; 482 483 throw new EMMARuntimeException (MAIN_CLASS_BAD_DELEGATION, new String [] {IAppConstants.APP_NAME, m_appClassName, loaderName}); 484 } 485 } 486 487 // run the app's main(): 488 489 final Method appMain; 490 try 491 { 492 // this causes initialization on some non-Sun-compatible JVMs [ignore]: 493 appMain = appClass.getMethod ("main", MAIN_TYPE); // Sun JVMs do not seem to require the method to be declared 494 } 495 catch (Throwable t) 496 { 497 throw new EMMARuntimeException (MAIN_METHOD_NOT_FOUND, new String [] {m_appClassName}, t); 498 } 499 500 Invoker invoker = new Invoker (appMain, null, new Object [] {m_appArgs}); 501 502 appThreadGroup = new ThreadGroup (IAppConstants.APP_NAME + " thread group [" + m_appClassName + "]"); 503 appThreadGroup.setDaemon (true); 504 505 Thread appThread = new Thread (appThreadGroup, invoker, IAppConstants.APP_NAME + " main() thread"); 506 appThread.setContextClassLoader (appLoader); 507 508 // --- [app start] ---- 509 510 appThread.start (); 511 512 try {appThread.join (); } catch (InterruptedException ignore) {} 513 appThread = null; 514 515 joinNonDeamonThreads (appThreadGroup); 516 517 // --- [app end] ---- 518 519 if (log.atTRACE1 ()) 520 { 521 if (appLoader instanceof InstrClassLoader) ((InstrClassLoader) appLoader).debugDump (log.getWriter ()); 522 } 523 524 final Throwable mainFailure = invoker.getFailure (); 525 invoker = null; 526 527 if (mainFailure != null) 528 { 529 if (mainFailure instanceof InvocationTargetException) 530 { 531 final Throwable cause = ((InvocationTargetException) mainFailure).getTargetException (); 532 533 throw new EMMARuntimeException (MAIN_METHOD_FAILURE, new String [] {m_appClassName, cause.toString ()}, cause); 534 } 535 else if (mainFailure instanceof ExceptionInInitializerError) 536 { 537 // this catch block is never entered if INIT_AT_LOAD_TIME is 'true' 538 final Throwable cause = ((ExceptionInInitializerError) mainFailure).getException (); 539 540 throw new EMMARuntimeException (MAIN_METHOD_FAILURE, new String [] {m_appClassName, cause.toString ()}, cause); 541 } 542 else if ((mainFailure instanceof IllegalAccessException) || 543 (mainFailure instanceof IllegalArgumentException) || 544 (mainFailure instanceof NullPointerException)) 545 { 546 throw new EMMARuntimeException (MAIN_METHOD_NOT_FOUND, new String [] {m_appClassName}, mainFailure); 547 } 548 else 549 { 550 throw new EMMARuntimeException (MAIN_METHOD_FAILURE, new String [] {m_appClassName, mainFailure.toString ()}, mainFailure); 551 } 552 } 553 } 554 catch (SecurityException se) 555 { 556 throw new EMMARuntimeException (SECURITY_RESTRICTION, new String [] {IAppConstants.APP_NAME}, se); 557 } 558 finally 559 { 560 if (SET_CURRENT_CONTEXT_LOADER && contextLoaderSet) 561 { 562 try 563 { 564 Thread.currentThread ().setContextClassLoader (contextLoader); 565 } 566 catch (Throwable ignore) {} 567 } 568 569 if ((appThreadGroup != null) && ! appThreadGroup.isDestroyed ()) 570 try 571 { 572 appThreadGroup.destroy (); 573 appThreadGroup = null; 574 } 575 catch (Throwable ignore) {} 576 } 577 } 578 catch (RuntimeException re) 579 { 580 failure = re; // should be EMMARuntimeException only if there are no errors above 581 } 582 finally 583 { 584 RT.reset (false, false); 585 } 586 587 if ($assert.ENABLED) $assert.ASSERT (runnerExitHook != null, "reportExitHook = null"); 588 runnerExitHook.run (); // that this may be a noop (if the shutdown sequence got there first) 589 590 // [assertion: the report exit hook is done] 591 592 if (runnerExitHookManager != null) 593 { 594 runnerExitHookManager.removeExitHook (runnerExitHook); // Ok if this fails 595 runnerExitHookManager = null; 596 } 597 598 // ---------------[ end of exit hook-protected section ]--------------- 599 600 601 final Throwable exitHookDataDumpFailure = runnerExitHook.getDataDumpFailure (); 602 final List /* Throwable */ exitHookReportFailures = runnerExitHook.getReportFailures (); 603 runnerExitHook = null; 604 605 if (failure != null) // 'failure' takes precedence over any possible exit hook's problems 606 { 607 throw wrapFailure (failure); 608 } 609 else if ((exitHookDataDumpFailure != null) || (exitHookReportFailures != null)) 610 { 611 if (exitHookDataDumpFailure != null) 612 log.log (Logger.SEVERE, "exception while persisting raw session data:", exitHookDataDumpFailure); 613 614 Throwable firstReportFailure = null; 615 if (exitHookReportFailures != null) 616 { 617 for (Iterator i = exitHookReportFailures.iterator (); i.hasNext (); ) 618 { 619 final Throwable reportFailure = (Throwable) i.next (); 620 if (firstReportFailure == null) firstReportFailure = reportFailure; 621 622 log.log (Logger.SEVERE, "exception while creating a report:", reportFailure); 623 } 624 } 625 626 if (exitHookDataDumpFailure != null) 627 throw wrapFailure (exitHookDataDumpFailure); 628 else if (firstReportFailure != null) // redundant check 629 throw wrapFailure (firstReportFailure); 630 } 631 632 } 633 634 // package: ............................................................... 635 636 // private: ............................................................... 637 638 639 private static final class Invoker implements Runnable 640 { 641 Invoker (final Method method, final Object target, final Object [] args) 642 { 643 if (method == null) throw new IllegalArgumentException ("null input: method"); 644 if (args == null) throw new IllegalArgumentException ("null input: args"); 645 646 m_method = method; 647 m_target = target; 648 m_args = args; 649 } 650 651 public void run () 652 { 653 try 654 { 655 m_method.invoke (m_target, m_args); 656 } 657 catch (Throwable t) 658 { 659 m_failure = t; 660 } 661 } 662 663 Throwable getFailure () 664 { 665 return m_failure; 666 } 667 668 669 private final Method m_method; 670 private final Object m_target; 671 private final Object [] m_args; 672 private Throwable m_failure; 673 674 } // end of nested class 675 676 677 private static final class AppRunnerExitHook implements Runnable 678 { 679 public synchronized void run () 680 { 681 try 682 { 683 if (! m_done) 684 { 685 // grab data snapshots: 686 687 final IMetaData mdataSnashot = m_mdata.shallowCopy (); 688 m_mdata = null; 689 final ICoverageData cdataSnapshot = m_cdata.shallowCopy (); 690 m_cdata = null; 691 692 if (mdataSnashot.isEmpty ()) 693 { 694 m_log.warning ("no metadata collected at runtime [no reports generated]"); 695 696 return; 697 } 698 699 if (cdataSnapshot.isEmpty ()) 700 { 701 m_log.warning ("no coverage data collected at runtime [all reports will be empty]"); 702 } 703 704 final ISessionData sdata = new SessionData (mdataSnashot, cdataSnapshot); 705 706 // if requested, dump raw data before running report generators: 707 // [note that the raw dumps and reports will be consistent wrt 708 // the session data they represent] 709 710 if (m_dumpRawData && (m_sdataOutFile != null)) 711 { 712 try 713 { 714 final boolean info = m_log.atINFO (); 715 716 final long start = info ? System.currentTimeMillis () : 0; 717 { 718 DataFactory.persist (sdata, m_sdataOutFile, m_sdataOutMerge); 719 } 720 if (info) 721 { 722 final long end = System.currentTimeMillis (); 723 724 m_log.info ("raw session data " + (m_sdataOutMerge ? "merged into" : "written to") + " [" + m_sdataOutFile.getAbsolutePath () + "] {in " + (end - start) + " ms}"); 725 } 726 } 727 catch (Throwable t) 728 { 729 m_dataDumpFailure = t; 730 } 731 } 732 733 for (int g = 0; g < m_generators.length; ++ g) 734 { 735 final IReportGenerator generator = m_generators [g]; 736 737 if (generator != null) 738 { 739 try 740 { 741 generator.process (mdataSnashot, cdataSnapshot, m_cache, m_properties); 742 } 743 catch (Throwable t) 744 { 745 if (m_reportFailures == null) m_reportFailures = new ArrayList (); 746 m_reportFailures.add (t); 747 748 continue; 749 } 750 finally 751 { 752 try { generator.cleanup (); } catch (Throwable ignore) {} 753 m_generators [g] = null; 754 } 755 } 756 } 757 } 758 } 759 finally 760 { 761 m_generators = null; 762 m_mdata = null; 763 m_cdata = null; 764 m_properties = null; 765 m_cache = null; 766 767 m_done = true; 768 } 769 } 770 771 // note: because ExitHookManager is a lazily created static singleton the 772 // correct thing to do is to pass an explicit Logger into each exit hook runner 773 // instead of relying on thread inheritance: 774 775 AppRunnerExitHook (final Logger log, 776 final boolean dumpRawData, final File sdataOutFile, final boolean sdataOutMerge, 777 final IMetaData mdata, final ICoverageData cdata, 778 final IReportGenerator [] generators, 779 final SourcePathCache cache, final IProperties properties) 780 { 781 if (log == null) throw new IllegalArgumentException ("null input: log"); 782 if ((generators == null) || (generators.length == 0)) throw new IllegalArgumentException ("null/empty input: generators"); 783 if (mdata == null) throw new IllegalArgumentException ("null input: mdata"); 784 if (cdata == null) throw new IllegalArgumentException ("null input: cdata"); 785 if (properties == null) throw new IllegalArgumentException ("null input: properties"); 786 787 m_log = log; 788 789 m_dumpRawData = dumpRawData; 790 m_sdataOutFile = sdataOutFile; 791 m_sdataOutMerge = sdataOutMerge; 792 793 m_generators = (IReportGenerator []) generators.clone (); 794 m_mdata = mdata; 795 m_cdata = cdata; 796 m_cache = cache; 797 m_properties = properties; 798 } 799 800 801 synchronized Throwable getDataDumpFailure () 802 { 803 return m_dataDumpFailure; 804 } 805 806 synchronized List /* Throwable */ getReportFailures () 807 { 808 return m_reportFailures; 809 } 810 811 812 private final Logger m_log; 813 private final boolean m_dumpRawData; 814 private final File m_sdataOutFile; 815 private final boolean m_sdataOutMerge; 816 817 private IReportGenerator [] m_generators; 818 private IMetaData m_mdata; 819 private ICoverageData m_cdata; 820 private SourcePathCache m_cache; 821 private IProperties m_properties; 822 private boolean m_done; 823 private Throwable m_dataDumpFailure; 824 private List /* Throwable */ m_reportFailures; 825 826 } // end of nested class 827 828 829 private AppRunner (final ClassLoader delegate) 830 { 831 m_delegate = delegate; 832 m_coveragePath = IConstants.EMPTY_FILE_ARRAY; 833 } 834 835 836 private static void joinNonDeamonThreads (final ThreadGroup group) 837 { 838 if (group == null) throw new IllegalArgumentException ("null input: group"); 839 840 final List threads = new ArrayList (); 841 while (true) 842 { 843 threads.clear (); 844 845 // note: group.activeCount() is only an estimate as more threads 846 // could get created while we are doing this [if 'aliveThreads' 847 // array is too short, the extra threads are silently ignored]: 848 849 Thread [] aliveThreads; 850 final int aliveCount; 851 852 // enumerate [recursively] all threads in 'group': 853 synchronized (group) 854 { 855 aliveThreads = new Thread [group.activeCount () << 1]; 856 aliveCount = group.enumerate (aliveThreads, true); 857 } 858 859 for (int t = 0; t < aliveCount; t++) 860 { 861 if (! aliveThreads [t].isDaemon ()) 862 threads.add (aliveThreads [t]); 863 } 864 aliveThreads = null; 865 866 if (threads.isEmpty ()) 867 break; // note: this logic does not work if daemon threads are spawning non-daemon ones 868 else 869 { 870 for (Iterator i = threads.iterator (); i.hasNext (); ) 871 { 872 try 873 { 874 ((Thread) i.next ()).join (); 875 } 876 catch (InterruptedException ignore) {} 877 } 878 } 879 } 880 } 881 882 private static RuntimeException wrapFailure (final Throwable t) 883 { 884 if (Exceptions.unexpectedFailure (t, EXPECTED_FAILURES)) 885 return new EMMARuntimeException (UNEXPECTED_FAILURE, 886 new Object [] {t.toString (), IAppConstants.APP_BUG_REPORT_LINK}, 887 t); 888 else if (t instanceof RuntimeException) 889 return (RuntimeException) t; 890 else 891 return new EMMARuntimeException (t); 892 } 893 894 895 // caller-settable state [scoped to this runner instance]: 896 897 private final ClassLoader m_delegate; 898 899 private String m_appClassName; // required to be non-null for run() 900 private String [] m_appArgs; // required to be non-null for run() 901 902 private File [] m_coveragePath; // required to be non-null/non-empty for run() 903 private boolean m_canonical; 904 private boolean m_scanCoveragePath; 905 private IInclExclFilter m_coverageFilter; // can be null for run() 906 907 private boolean m_dumpSessionData; 908 private File m_sdataOutFile; // user override; can be null for run() 909 private Boolean m_sdataOutMerge; // user override; can be null for run() 910 911 private IReportGenerator [] m_reportGenerators; // required to be non-null for run() 912 private File [] m_sourcePath; // can be null/empty for run() 913 914 // it is attractive to detect errors at load time, but this may allow 915 // threads created by <clinit> code to escape; on the other hand, classes 916 // that do not override main() will not get initialized this way and will 917 // not register with our runtime [which seems a minor problem at this point]: 918 private static final boolean INIT_AT_LOAD_TIME = false; 919 920 // setting the context loader on AppRunner's thread should not 921 // be necessary since the app is run in a dedicated thread group; 922 // however, if INIT_AT_LOAD_TIME=true the app's <clinit> code 923 // should run with an adjusted context loader: 924 private static final boolean SET_CURRENT_CONTEXT_LOADER = INIT_AT_LOAD_TIME; 925 926 // a soft cache is ideal for managing class definitions that are read during 927 // the initial classpath scan; however, the default LRU policy parameters for 928 // clearing SoftReferences in Sun's J2SDK 1.3+ render them next to useless 929 // in the client HotSpot JVM (in which this tool will probably run most often); 930 // using a hard cache guarantees 100% cache hit rate but can also raise the 931 // memory requirements significantly beyond the needs of the original app. 932 // [see bug refs 4471453, 4806720, 4888056, 4239645] 933 // 934 // resolution for now: use a soft cache anyway and doc that to make it useful 935 // for non-trivial apps the user should use -Xms or -XX:SoftRefLRUPolicyMSPerMB 936 // JVM options or use a server HotSpot JVM 937 private static final boolean USE_SOFT_CACHE = true; 938 939 private static final int INIT_CACHE_CAPACITY = 2003; // prime 940 private static final int SOFT_CACHE_READ_CHK_FREQUENCY = 100; 941 private static final int SOFT_CACHE_WRITE_CHK_FREQUENCY = 100; 942 943 private static final String [] FORCED_DELEGATION_FILTER_SPECS; // set in <clinit> 944 private static final Class [] MAIN_TYPE = new Class [] {String [].class}; 945 946 private static final Class [] EXPECTED_FAILURES; // set in <clinit> 947 948 protected static final String COMMA_DELIMITERS = "," + Strings.WHITE_SPACE; 949 protected static final String PATH_DELIMITERS = ",".concat (File.pathSeparator); 950 951 static 952 { 953 EXPECTED_FAILURES = new Class [] 954 { 955 EMMARuntimeException.class, 956 IllegalArgumentException.class, 957 IllegalStateException.class, 958 }; 959 960 FORCED_DELEGATION_FILTER_SPECS = new String [] {"+" + IAppConstants.APP_PACKAGE + ".*"}; 961 } 962 963} // end of class 964// ----------------------------------------------------------------------------