AppRunnerpublic final class AppRunner extends com.vladium.emma.Processor implements com.vladium.emma.IAppErrorCodes
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.AppRunner | create(java.lang.ClassLoader delegate)
return new AppRunner (delegate);
| private static void | joinNonDeamonThreads(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 void | run()
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 void | setAppClass(java.lang.String className, java.lang.String[] args)
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 void | setCoveragePath(java.lang.String[] path, boolean 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 void | setDumpSessionData(boolean dump)
m_dumpSessionData = dump;
| public final synchronized void | setInclExclFilter(java.lang.String[] specs)
if (specs == null)
m_coverageFilter = null;
else
m_coverageFilter = IInclExclFilter.Factory.create (specs);
| public synchronized void | setReportTypes(java.lang.String[] types)
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 void | setScanCoveragePath(boolean scan)
m_scanCoveragePath = scan;
| public final synchronized void | setSessionOutFile(java.lang.String fileName)
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 void | setSessionOutMerge(java.lang.Boolean merge)
m_sdataOutMerge = merge;
| public synchronized void | setSourcePath(java.lang.String[] path)
if (path == null)
m_sourcePath = null;
else
m_sourcePath = Files.pathToFiles (path, true); // always canonicalize source path
| protected void | validateState()
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.RuntimeException | wrapFailure(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);
|
|