Home | History | Annotate | Download | only in rt
      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  */
      9 package com.vladium.emma.rt;
     10 
     11 import java.io.File;
     12 import java.lang.reflect.InvocationTargetException;
     13 import java.lang.reflect.Method;
     14 import java.net.MalformedURLException;
     15 import java.util.HashMap;
     16 import java.util.Iterator;
     17 import java.util.ArrayList;
     18 import java.util.List;
     19 import java.util.Map;
     20 
     21 import com.vladium.logging.Logger;
     22 import com.vladium.util.Files;
     23 import com.vladium.util.IConstants;
     24 import com.vladium.util.IProperties;
     25 import com.vladium.util.Property;
     26 import com.vladium.util.SoftValueMap;
     27 import com.vladium.util.Strings;
     28 import com.vladium.util.asserts.$assert;
     29 import com.vladium.util.exception.Exceptions;
     30 import com.vladium.util.exit.ExitHookManager;
     31 import com.vladium.emma.AppLoggers;
     32 import com.vladium.emma.IAppConstants;
     33 import com.vladium.emma.IAppErrorCodes;
     34 import com.vladium.emma.EMMAProperties;
     35 import com.vladium.emma.EMMARuntimeException;
     36 import com.vladium.emma.Processor;
     37 import com.vladium.emma.filter.IInclExclFilter;
     38 import com.vladium.emma.data.CoverageOptionsFactory;
     39 import com.vladium.emma.data.IMetaData;
     40 import com.vladium.emma.data.ICoverageData;
     41 import com.vladium.emma.data.DataFactory;
     42 import com.vladium.emma.data.ISessionData;
     43 import com.vladium.emma.data.SessionData;
     44 import com.vladium.emma.report.AbstractReportGenerator;
     45 import com.vladium.emma.report.IReportGenerator;
     46 import com.vladium.emma.report.SourcePathCache;
     47 
     48 // ----------------------------------------------------------------------------
     49 /**
     50  * @author Vlad Roubtsov, (C) 2003
     51  */
     52 public
     53 final 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 // ----------------------------------------------------------------------------