FileDocCategorySizeDatePackage
AppRunner.javaAPI DocAndroid 1.5 API38042Wed May 06 22:41:16 BST 2009com.vladium.emma.rt

AppRunner

public final class AppRunner extends com.vladium.emma.Processor implements com.vladium.emma.IAppErrorCodes
author
Vlad Roubtsov, (C) 2003

Fields Summary
private final ClassLoader
m_delegate
private String
m_appClassName
private String[]
m_appArgs
private File[]
m_coveragePath
private boolean
m_canonical
private boolean
m_scanCoveragePath
private com.vladium.emma.filter.IInclExclFilter
m_coverageFilter
private boolean
m_dumpSessionData
private File
m_sdataOutFile
private Boolean
m_sdataOutMerge
private com.vladium.emma.report.IReportGenerator[]
m_reportGenerators
private File[]
m_sourcePath
private static final boolean
INIT_AT_LOAD_TIME
private static final boolean
SET_CURRENT_CONTEXT_LOADER
private static final boolean
USE_SOFT_CACHE
private static final int
INIT_CACHE_CAPACITY
private static final int
SOFT_CACHE_READ_CHK_FREQUENCY
private static final int
SOFT_CACHE_WRITE_CHK_FREQUENCY
private static final String[]
FORCED_DELEGATION_FILTER_SPECS
private static final Class[]
MAIN_TYPE
private static final Class[]
EXPECTED_FAILURES
protected static final String
COMMA_DELIMITERS
protected static final String
PATH_DELIMITERS
Constructors Summary
private AppRunner(ClassLoader delegate)

        m_delegate = delegate;
        m_coveragePath = IConstants.EMPTY_FILE_ARRAY;
    
Methods Summary
protected void_run(com.vladium.util.IProperties toolProperties)

        final Logger log = m_log;
        
        final boolean verbose = log.atVERBOSE ();
        if (verbose)
        {
            log.verbose (IAppConstants.APP_VERBOSE_BUILD_ID);
            
            // [assertion: m_coveragePath != null]
            log.verbose ("coverage path:");
            log.verbose ("{");
            for (int p = 0; p < m_coveragePath.length; ++ p)
            {
                final File f = m_coveragePath [p];
                final String nonexistent = f.exists () ? "" : "{nonexistent} ";
                
                log.verbose ("  " + nonexistent + f.getAbsolutePath ());
            }
            log.verbose ("}");
            
            if ((m_sourcePath == null) || (m_sourcePath.length == 0))
            {
                log.verbose ("source path not set");
            }
            else
            {
                log.verbose ("source path:");
                log.verbose ("{");
                for (int p = 0; p < m_sourcePath.length; ++ p)
                {
                    final File f = m_sourcePath [p];
                    final String nonexistent = f.exists () ? "" : "{nonexistent} ";
                    
                    log.verbose ("  " + nonexistent + f.getAbsolutePath ());
                }
                log.verbose ("}");
            }
        }
        
        // get the data out settings [note: this is not conditioned on m_dumpRawData]:
        File sdataOutFile = m_sdataOutFile;
        Boolean sdataOutMerge = m_sdataOutMerge;
        {
            if (sdataOutFile == null)
                sdataOutFile = new File (toolProperties.getProperty (EMMAProperties.PROPERTY_SESSION_DATA_OUT_FILE,
                                                                     EMMAProperties.DEFAULT_SESSION_DATA_OUT_FILE));
            
            if (sdataOutMerge == null)
            {
                final String _dataOutMerge = toolProperties.getProperty (EMMAProperties.PROPERTY_SESSION_DATA_OUT_MERGE,
                                                                         EMMAProperties.DEFAULT_SESSION_DATA_OUT_MERGE.toString ());
                sdataOutMerge = Property.toBoolean (_dataOutMerge) ? Boolean.TRUE : Boolean.FALSE;
            } 
        }
        
        if (verbose && m_dumpSessionData)
        {
            log.verbose ("session data output file: " + sdataOutFile.getAbsolutePath ());
            log.verbose ("session data output merge mode: " + sdataOutMerge);
        }
        
        // get instr class loader delegation filter settings:
        final IInclExclFilter forcedDelegationFilter
            = IInclExclFilter.Factory.create (toolProperties.getProperty (InstrClassLoader.PROPERTY_FORCED_DELEGATION_FILTER),
                                              COMMA_DELIMITERS, FORCED_DELEGATION_FILTER_SPECS);
        final IInclExclFilter throughDelegationFilter
            = IInclExclFilter.Factory.create (toolProperties.getProperty (InstrClassLoader.PROPERTY_THROUGH_DELEGATION_FILTER),
                                              COMMA_DELIMITERS, null);
        

        // TODO: consider injecting Runtime straight into appLoader namespace...
        // TODO: create a thread group for all exit hooks?


        // get a handle to exit hook manager singleton:
        ExitHookManager runnerExitHookManager = null;
        try
        {
            runnerExitHookManager = ExitHookManager.getSingleton (); // can throw
        }
        catch (Exception e)
        {
            // TODO: log/handle/warn
            e.printStackTrace (System.out);
        }

        AppRunnerExitHook runnerExitHook = null;
        RuntimeException failure = null;
        
        try
        { 
            SourcePathCache srcpathCache = null;
            if (m_sourcePath != null) srcpathCache = new SourcePathCache (m_sourcePath, true); // ignore non-existent source dirs
            
            // create session data containers:
            ICoverageData cdata = RT.getCoverageData ();
            if ($assert.ENABLED) $assert.ASSERT (cdata != null, "cdata is null");
            
            IMetaData mdata = DataFactory.newMetaData (CoverageOptionsFactory.create (toolProperties));
            
            runnerExitHook = new AppRunnerExitHook (log, m_dumpSessionData, sdataOutFile, sdataOutMerge.booleanValue (), mdata, cdata, m_reportGenerators, srcpathCache, toolProperties);
            
            if (runnerExitHookManager != null)
                runnerExitHookManager.addExitHook (runnerExitHook);
            
            // --------------[ start of exit hook-protected section ]--------------
            
            Map classIOCache = null;
            
            // scan the classpath to populate the initial metadata:
            if (m_scanCoveragePath)
            {
                if (USE_SOFT_CACHE)
                    classIOCache = new SoftValueMap (INIT_CACHE_CAPACITY, 0.75F, SOFT_CACHE_READ_CHK_FREQUENCY, SOFT_CACHE_WRITE_CHK_FREQUENCY);
                else
                    classIOCache = new HashMap (INIT_CACHE_CAPACITY, 0.75F);
                    
                final ClassPathProcessorST processor = new ClassPathProcessorST (m_coveragePath, m_canonical, mdata, m_coverageFilter, classIOCache);
                
                // with a bit of work [ClassPathProcessorST needs to lock on the
                // metadata, etc] this could be run concurrently with the app
                // itself to improve perceived performance, however, I am not
                // going to invest time in this; 
                
                // populate 'cache' [optional] and 'mdata':
                processor.run ();
                
                if (log.atTRACE1 ())
                {
                    log.trace1 ("run", "class cache size after cp scan: " + classIOCache.size ());
                    log.trace1 ("run", "metadata size after cp scan: " + mdata.size ());
                }
            }
            
            
            // app runner does not need these handles anymore [only the exit hook runner maintains them]:
            srcpathCache = null;
            cdata = null;
            
            final ClassLoader appLoader;
            {
                final IClassLoadHook loadHook = new InstrClassLoadHook (m_coverageFilter, mdata);
                 
                try
                {
                    appLoader = new InstrClassLoader (m_delegate, m_coveragePath, forcedDelegationFilter, throughDelegationFilter, loadHook, classIOCache);
                }
                catch (SecurityException se)
                {
                    throw new EMMARuntimeException (SECURITY_RESTRICTION, new String [] {IAppConstants.APP_NAME}, se);
                }
                catch (MalformedURLException mue)
                {
                    throw new EMMARuntimeException (mue); 
                }
            }
            
            // app runner does not need these handles anymore:
            mdata = null;
            classIOCache = null;

            
            final ClassLoader contextLoader;
            boolean contextLoaderSet = false;
            if (SET_CURRENT_CONTEXT_LOADER)
            {
                try
                {
                    final Thread currentThread = Thread.currentThread (); 
                    
                    // TODO: rethink if this is the right place to do this
                    contextLoader = currentThread.getContextClassLoader ();
                    currentThread.setContextClassLoader (appLoader);
                    
                    contextLoaderSet = true;
                }
                catch (SecurityException se)
                {
                    throw new EMMARuntimeException (SECURITY_RESTRICTION, new String [] {IAppConstants.APP_NAME}, se);
                }
            }
            
            
            ThreadGroup appThreadGroup = null;
            try
            {
                // load [and possibly initialize] the app class:
                
                final Class appClass;
                try
                {
                    // load [and force early initialization if INIT_AT_LOAD_TIME is 'true']:
                    appClass = Class.forName (m_appClassName, INIT_AT_LOAD_TIME, appLoader);
                }
                catch (ClassNotFoundException cnfe)
                {
                    // TODO: dump the classloader tree into the error message as well
                    throw new EMMARuntimeException (MAIN_CLASS_NOT_FOUND, new String [] {m_appClassName}, cnfe);
                }
                catch (ExceptionInInitializerError eiie) // this should not happen for INIT_AT_LOAD_TIME=false
                {
                    final Throwable cause = eiie.getException ();
                    
                    throw new EMMARuntimeException (MAIN_CLASS_LOAD_FAILURE, new String [] {m_appClassName, cause.toString ()}, cause);
                }
                catch (Throwable t)
                {
                    throw new EMMARuntimeException (MAIN_CLASS_NOT_FOUND, new String [] {m_appClassName}, t);
                }
                
                // ensure that the app is bootstrapped using appLoader:
                {
                    final ClassLoader actualLoader = appClass.getClassLoader ();
                    if (actualLoader != appLoader)
                    {
                        final String loaderName = actualLoader != null ?  actualLoader.getClass ().getName () : "<PRIMORDIAL>";
                        
                        throw new EMMARuntimeException (MAIN_CLASS_BAD_DELEGATION, new String [] {IAppConstants.APP_NAME, m_appClassName, loaderName});
                    }
                }
    
                // run the app's main():
                
                final Method appMain;
                try
                {
                    // this causes initialization on some non-Sun-compatible JVMs [ignore]:
                    appMain = appClass.getMethod ("main", MAIN_TYPE); // Sun JVMs do not seem to require the method to be declared
                }
                catch (Throwable t)
                {
                    throw new EMMARuntimeException (MAIN_METHOD_NOT_FOUND, new String [] {m_appClassName}, t);
                }            
                
                Invoker invoker = new Invoker (appMain, null, new Object [] {m_appArgs});
                
                appThreadGroup = new ThreadGroup (IAppConstants.APP_NAME + " thread group [" + m_appClassName + "]");
                appThreadGroup.setDaemon (true);
                
                Thread appThread = new Thread (appThreadGroup, invoker, IAppConstants.APP_NAME + " main() thread");
                appThread.setContextClassLoader (appLoader);
                
                // --- [app start] ----
                
                appThread.start ();
                
                try {appThread.join (); } catch (InterruptedException ignore) {}
                appThread = null;
                
                joinNonDeamonThreads (appThreadGroup);
                
                // --- [app end] ----
                
                if (log.atTRACE1 ())
                {
                    if (appLoader instanceof InstrClassLoader) ((InstrClassLoader) appLoader).debugDump (log.getWriter ());
                }
                
                final Throwable mainFailure = invoker.getFailure ();
                invoker = null;
                
                if (mainFailure != null)
                {
                    if (mainFailure instanceof InvocationTargetException)
                    {
                        final Throwable cause = ((InvocationTargetException) mainFailure).getTargetException ();
                        
                        throw new EMMARuntimeException (MAIN_METHOD_FAILURE, new String [] {m_appClassName, cause.toString ()}, cause);
                    }
                    else if (mainFailure instanceof ExceptionInInitializerError)
                    {
                        // this catch block is never entered if INIT_AT_LOAD_TIME is 'true'
                        final Throwable cause = ((ExceptionInInitializerError) mainFailure).getException ();
                        
                        throw new EMMARuntimeException (MAIN_METHOD_FAILURE, new String [] {m_appClassName, cause.toString ()}, cause);
                    }
                    else if ((mainFailure instanceof IllegalAccessException)   ||
                             (mainFailure instanceof IllegalArgumentException) ||
                             (mainFailure instanceof NullPointerException))
                    {
                        throw new EMMARuntimeException (MAIN_METHOD_NOT_FOUND, new String [] {m_appClassName}, mainFailure);
                    }
                    else
                    {
                        throw new EMMARuntimeException (MAIN_METHOD_FAILURE, new String [] {m_appClassName, mainFailure.toString ()}, mainFailure);
                    }
                }                
            }
            catch (SecurityException se)
            {
                throw new EMMARuntimeException (SECURITY_RESTRICTION, new String [] {IAppConstants.APP_NAME}, se);
            }
            finally
            {
                if (SET_CURRENT_CONTEXT_LOADER && contextLoaderSet)
                {
                    try
                    {
                        Thread.currentThread ().setContextClassLoader (contextLoader);
                    }
                    catch (Throwable ignore) {} 
                }

                if ((appThreadGroup != null) && ! appThreadGroup.isDestroyed ())
                try
                {
                    appThreadGroup.destroy ();
                    appThreadGroup = null;
                }
                catch (Throwable ignore) {}                
            }
        }
        catch (RuntimeException re)
        {
            failure = re; // should be EMMARuntimeException only if there are no errors above
        }
        finally
        {
            RT.reset (false, false);
        }
        
        if ($assert.ENABLED) $assert.ASSERT (runnerExitHook != null, "reportExitHook = null");
        runnerExitHook.run (); // that this may be a noop (if the shutdown sequence got there first)
        
        // [assertion: the report exit hook is done]
                
        if (runnerExitHookManager != null)
        {
            runnerExitHookManager.removeExitHook (runnerExitHook); // Ok if this fails
            runnerExitHookManager = null;
        }
        
        // ---------------[ end of exit hook-protected section ]---------------
        
        
        final Throwable exitHookDataDumpFailure = runnerExitHook.getDataDumpFailure ();
        final List /* Throwable */ exitHookReportFailures = runnerExitHook.getReportFailures ();
        runnerExitHook = null;        
        
        if (failure != null) // 'failure' takes precedence over any possible exit hook's problems
        {
            throw wrapFailure (failure);
        }
        else if ((exitHookDataDumpFailure != null) || (exitHookReportFailures != null))
        {
            if (exitHookDataDumpFailure != null)
                log.log (Logger.SEVERE, "exception while persisting raw session data:", exitHookDataDumpFailure);
            
            Throwable firstReportFailure = null;
            if (exitHookReportFailures != null)
            {
                for (Iterator i = exitHookReportFailures.iterator (); i.hasNext (); )
                {
                    final Throwable reportFailure = (Throwable) i.next ();                
                    if (firstReportFailure == null) firstReportFailure = reportFailure;
                    
                    log.log (Logger.SEVERE, "exception while creating a report:", reportFailure);
                }
            }
            
            if (exitHookDataDumpFailure != null)
                throw wrapFailure (exitHookDataDumpFailure);
            else if (firstReportFailure != null) // redundant check
                throw wrapFailure (firstReportFailure);
        }

    
public static com.vladium.emma.rt.AppRunnercreate(java.lang.ClassLoader delegate)

        return new AppRunner (delegate);
    
private static voidjoinNonDeamonThreads(java.lang.ThreadGroup group)

        if (group == null) throw new IllegalArgumentException ("null input: group");
        
        final List threads = new ArrayList ();
        while (true)
        {
            threads.clear ();
            
            // note: group.activeCount() is only an estimate as more threads
            // could get created while we are doing this [if 'aliveThreads'
            // array is too short, the extra threads are silently ignored]:
            
            Thread [] aliveThreads;
            final int aliveCount;
            
            // enumerate [recursively] all threads in 'group':
            synchronized (group)
            {
                aliveThreads = new Thread [group.activeCount () << 1];
                aliveCount = group.enumerate (aliveThreads, true);
            }
            
            for (int t = 0; t < aliveCount; t++)
            {
                if (! aliveThreads [t].isDaemon ())
                    threads.add (aliveThreads [t]);
            }            
            aliveThreads = null;
            
            if (threads.isEmpty ())
                break; // note: this logic does not work if daemon threads are spawning non-daemon ones
            else
            {
                for (Iterator i = threads.iterator (); i.hasNext (); )
                {
                    try
                    {
                        ((Thread) i.next ()).join (); 
                    }
                    catch (InterruptedException ignore) {}
                }
            }
        }
    
public synchronized voidrun()

        validateState ();
        
        // disable Runtime's own exit hook:
        RTSettings.setStandaloneMode (false); // an optimization to disable RT's static init code [this line must precede any reference to RT]
        RT.reset (true, false); // reset RT [RT creates 'cdata' and loads app properties]

        // load tool properties:
        final IProperties toolProperties;
        {
            IProperties appProperties = RT.getAppProperties (); // try to use app props consistent with RT's view of them
            if (appProperties == null) appProperties = EMMAProperties.getAppProperties (); // don't use combine()
            
            toolProperties = IProperties.Factory.combine (m_propertyOverrides, appProperties);
        }
        if ($assert.ENABLED) $assert.ASSERT (toolProperties != null, "toolProperties is null"); // can be empty, though

        final Logger current = Logger.getLogger ();
        final Logger log = AppLoggers.create (m_appName, toolProperties, current);
        
        if (log.atTRACE1 ())
        {
            log.trace1 ("run", "complete tool properties:");
            toolProperties.list (log.getWriter ());
        }
        
        try
        {
            Logger.push (log);
            m_log = log;
        
            _run (toolProperties);
        }
        finally
        {
            if (m_log != null)
            {
                Logger.pop (m_log);
                m_log = null;
            }
        }
    
public synchronized voidsetAppClass(java.lang.String className, java.lang.String[] args)

param
className [may not be null or empty]
param
args [null is equivalent to an empty array]

        if ((className == null) || (className.length () == 0))
            throw new IllegalArgumentException ("null/empty input: className");
        
        if (args != null)
        {
            final String [] _args = (String []) args.clone ();
             
            for (int a = 0; a < _args.length; ++ a)
                if (_args [a] == null) throw new IllegalArgumentException ("null input: args[" + a + "]");
                
            m_appArgs = _args;
        }
        else
        {
            m_appArgs = IConstants.EMPTY_STRING_ARRAY;
        }
        
        m_appClassName = className;
    
public synchronized voidsetCoveragePath(java.lang.String[] path, boolean canonical)

param
path [null is equivalent to empty array]
param
canonical

        if ((path == null) || (path.length == 0))
            m_coveragePath = IConstants.EMPTY_FILE_ARRAY;
        else
            m_coveragePath = Files.pathToFiles (path, canonical);
        
        m_canonical = canonical;
    
public synchronized voidsetDumpSessionData(boolean dump)

        m_dumpSessionData = dump;    
    
public final synchronized voidsetInclExclFilter(java.lang.String[] specs)

param
specs [null is equivalent to no filtering (everything is included)]

        if (specs == null)
            m_coverageFilter = null;
        else
            m_coverageFilter = IInclExclFilter.Factory.create (specs);
    
public synchronized voidsetReportTypes(java.lang.String[] types)

param
types [may not be null]

        if (types == null) throw new IllegalArgumentException ("null input: types");
        
        final String [] reportTypes = Strings.removeDuplicates (types, true);
        if (reportTypes.length == 0) throw new IllegalArgumentException ("empty input: types");
        
        if ($assert.ENABLED) $assert.ASSERT (reportTypes != null && reportTypes.length  > 0);
        
        
        final IReportGenerator [] reportGenerators = new IReportGenerator [reportTypes.length];
        for (int t = 0; t < reportTypes.length; ++ t)
        {
            reportGenerators [t] = AbstractReportGenerator.create (reportTypes [t]);
        }
        
        m_reportGenerators = reportGenerators;
    
public synchronized voidsetScanCoveragePath(boolean scan)

        m_scanCoveragePath = scan;
    
public final synchronized voidsetSessionOutFile(java.lang.String fileName)

param
fileName [null unsets the previous override setting]

        if (fileName == null)
            m_sdataOutFile = null;
        else
        {
            final File _file = new File (fileName);
                
            if (_file.exists () && ! _file.isFile ())
                throw new IllegalArgumentException ("not a file: [" + _file.getAbsolutePath () + "]");
                
            m_sdataOutFile = _file;
        }
    
public final synchronized voidsetSessionOutMerge(java.lang.Boolean merge)

param
merge [null unsets the previous override setting]

        m_sdataOutMerge = merge;
    
public synchronized voidsetSourcePath(java.lang.String[] path)

param
path [null is equivalent to no source path]

        if (path == null)
            m_sourcePath = null;
        else
            m_sourcePath = Files.pathToFiles (path, true); // always canonicalize source path
    
protected voidvalidateState()

        super.validateState ();
        
        if ((m_appClassName == null) || (m_appClassName.length () == 0))
            throw new IllegalStateException ("application class name not set");
        
        if (m_appArgs == null)
            throw new IllegalStateException ("application arguments not set");

        if (m_coveragePath == null)
            throw new IllegalStateException ("coverage path not set");
        
        // [m_coverageFilter can be null]
        
        // [m_sdataOutFile can be null]
        // [m_sdataOutMerge can be null]
        
        if ((m_reportGenerators == null) || (m_reportGenerators.length == 0))
            throw new IllegalStateException ("report types not set");

        // [m_sourcePath can be null/empty]
        
        // [m_propertyOverrides can be null]
    
private static java.lang.RuntimeExceptionwrapFailure(java.lang.Throwable t)

        if (Exceptions.unexpectedFailure (t, EXPECTED_FAILURES))
            return new EMMARuntimeException (UNEXPECTED_FAILURE,
                                            new Object [] {t.toString (), IAppConstants.APP_BUG_REPORT_LINK},
                                            t);
        else if (t instanceof RuntimeException)
            return (RuntimeException) t;
        else
            return new EMMARuntimeException (t);