/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved. * * This program and the accompanying materials are made available under * the terms of the Common Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/cpl-v10.html * * $Id: AppRunner.java,v 1.1.1.1.2.2 2004/07/16 23:32:03 vlad_r Exp $ */ package com.vladium.emma.rt; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.util.HashMap; import java.util.Iterator; import java.util.ArrayList; import java.util.List; import java.util.Map; import com.vladium.logging.Logger; import com.vladium.util.Files; import com.vladium.util.IConstants; import com.vladium.util.IProperties; import com.vladium.util.Property; import com.vladium.util.SoftValueMap; import com.vladium.util.Strings; import com.vladium.util.asserts.$assert; import com.vladium.util.exception.Exceptions; import com.vladium.util.exit.ExitHookManager; import com.vladium.emma.AppLoggers; import com.vladium.emma.IAppConstants; import com.vladium.emma.IAppErrorCodes; import com.vladium.emma.EMMAProperties; import com.vladium.emma.EMMARuntimeException; import com.vladium.emma.Processor; import com.vladium.emma.filter.IInclExclFilter; import com.vladium.emma.data.CoverageOptionsFactory; import com.vladium.emma.data.IMetaData; import com.vladium.emma.data.ICoverageData; import com.vladium.emma.data.DataFactory; import com.vladium.emma.data.ISessionData; import com.vladium.emma.data.SessionData; import com.vladium.emma.report.AbstractReportGenerator; import com.vladium.emma.report.IReportGenerator; import com.vladium.emma.report.SourcePathCache; // ---------------------------------------------------------------------------- /** * @author Vlad Roubtsov, (C) 2003 */ public final class AppRunner extends Processor implements IAppErrorCodes { // public: ................................................................ public static AppRunner create (final ClassLoader delegate) { return new AppRunner (delegate); } 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; } } } /** * @param path [null is equivalent to empty array] * @param canonical */ public synchronized void setCoveragePath (String [] path, final 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 setScanCoveragePath (final boolean scan) { m_scanCoveragePath = scan; } /** * @param path [null is equivalent to no source path] */ public synchronized void setSourcePath (final String [] path) { if (path == null) m_sourcePath = null; else m_sourcePath = Files.pathToFiles (path, true); // always canonicalize source path } /** * * @param specs [null is equivalent to no filtering (everything is included)] */ public synchronized final void setInclExclFilter (final String [] specs) { if (specs == null) m_coverageFilter = null; else m_coverageFilter = IInclExclFilter.Factory.create (specs); } /** * * @param className [may not be null or empty] * @param args [null is equivalent to an empty array] */ public synchronized void setAppClass (final String className, final 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 setDumpSessionData (final boolean dump) { m_dumpSessionData = dump; } /** * * @param fileName [null unsets the previous override setting] */ public synchronized final void setSessionOutFile (final 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; } } /** * * @param merge [null unsets the previous override setting] */ public synchronized final void setSessionOutMerge (final Boolean merge) { m_sdataOutMerge = merge; } /** * * @param types [may not be null] */ public synchronized void setReportTypes (final 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; } // protected: ............................................................. 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] } protected void _run (final 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 () : ""; 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); } } // package: ............................................................... // private: ............................................................... private static final class Invoker implements Runnable { Invoker (final Method method, final Object target, final Object [] args) { if (method == null) throw new IllegalArgumentException ("null input: method"); if (args == null) throw new IllegalArgumentException ("null input: args"); m_method = method; m_target = target; m_args = args; } public void run () { try { m_method.invoke (m_target, m_args); } catch (Throwable t) { m_failure = t; } } Throwable getFailure () { return m_failure; } private final Method m_method; private final Object m_target; private final Object [] m_args; private Throwable m_failure; } // end of nested class private static final class AppRunnerExitHook implements Runnable { public synchronized void run () { try { if (! m_done) { // grab data snapshots: final IMetaData mdataSnashot = m_mdata.shallowCopy (); m_mdata = null; final ICoverageData cdataSnapshot = m_cdata.shallowCopy (); m_cdata = null; if (mdataSnashot.isEmpty ()) { m_log.warning ("no metadata collected at runtime [no reports generated]"); return; } if (cdataSnapshot.isEmpty ()) { m_log.warning ("no coverage data collected at runtime [all reports will be empty]"); } final ISessionData sdata = new SessionData (mdataSnashot, cdataSnapshot); // if requested, dump raw data before running report generators: // [note that the raw dumps and reports will be consistent wrt // the session data they represent] if (m_dumpRawData && (m_sdataOutFile != null)) { try { final boolean info = m_log.atINFO (); final long start = info ? System.currentTimeMillis () : 0; { DataFactory.persist (sdata, m_sdataOutFile, m_sdataOutMerge); } if (info) { final long end = System.currentTimeMillis (); m_log.info ("raw session data " + (m_sdataOutMerge ? "merged into" : "written to") + " [" + m_sdataOutFile.getAbsolutePath () + "] {in " + (end - start) + " ms}"); } } catch (Throwable t) { m_dataDumpFailure = t; } } for (int g = 0; g < m_generators.length; ++ g) { final IReportGenerator generator = m_generators [g]; if (generator != null) { try { generator.process (mdataSnashot, cdataSnapshot, m_cache, m_properties); } catch (Throwable t) { if (m_reportFailures == null) m_reportFailures = new ArrayList (); m_reportFailures.add (t); continue; } finally { try { generator.cleanup (); } catch (Throwable ignore) {} m_generators [g] = null; } } } } } finally { m_generators = null; m_mdata = null; m_cdata = null; m_properties = null; m_cache = null; m_done = true; } } // note: because ExitHookManager is a lazily created static singleton the // correct thing to do is to pass an explicit Logger into each exit hook runner // instead of relying on thread inheritance: AppRunnerExitHook (final Logger log, final boolean dumpRawData, final File sdataOutFile, final boolean sdataOutMerge, final IMetaData mdata, final ICoverageData cdata, final IReportGenerator [] generators, final SourcePathCache cache, final IProperties properties) { if (log == null) throw new IllegalArgumentException ("null input: log"); if ((generators == null) || (generators.length == 0)) throw new IllegalArgumentException ("null/empty input: generators"); if (mdata == null) throw new IllegalArgumentException ("null input: mdata"); if (cdata == null) throw new IllegalArgumentException ("null input: cdata"); if (properties == null) throw new IllegalArgumentException ("null input: properties"); m_log = log; m_dumpRawData = dumpRawData; m_sdataOutFile = sdataOutFile; m_sdataOutMerge = sdataOutMerge; m_generators = (IReportGenerator []) generators.clone (); m_mdata = mdata; m_cdata = cdata; m_cache = cache; m_properties = properties; } synchronized Throwable getDataDumpFailure () { return m_dataDumpFailure; } synchronized List /* Throwable */ getReportFailures () { return m_reportFailures; } private final Logger m_log; private final boolean m_dumpRawData; private final File m_sdataOutFile; private final boolean m_sdataOutMerge; private IReportGenerator [] m_generators; private IMetaData m_mdata; private ICoverageData m_cdata; private SourcePathCache m_cache; private IProperties m_properties; private boolean m_done; private Throwable m_dataDumpFailure; private List /* Throwable */ m_reportFailures; } // end of nested class private AppRunner (final ClassLoader delegate) { m_delegate = delegate; m_coveragePath = IConstants.EMPTY_FILE_ARRAY; } private static void joinNonDeamonThreads (final 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) {} } } } } private static RuntimeException wrapFailure (final 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); } // caller-settable state [scoped to this runner instance]: private final ClassLoader m_delegate; private String m_appClassName; // required to be non-null for run() private String [] m_appArgs; // required to be non-null for run() private File [] m_coveragePath; // required to be non-null/non-empty for run() private boolean m_canonical; private boolean m_scanCoveragePath; private IInclExclFilter m_coverageFilter; // can be null for run() private boolean m_dumpSessionData; private File m_sdataOutFile; // user override; can be null for run() private Boolean m_sdataOutMerge; // user override; can be null for run() private IReportGenerator [] m_reportGenerators; // required to be non-null for run() private File [] m_sourcePath; // can be null/empty for run() // it is attractive to detect errors at load time, but this may allow // threads created by code to escape; on the other hand, classes // that do not override main() will not get initialized this way and will // not register with our runtime [which seems a minor problem at this point]: private static final boolean INIT_AT_LOAD_TIME = false; // setting the context loader on AppRunner's thread should not // be necessary since the app is run in a dedicated thread group; // however, if INIT_AT_LOAD_TIME=true the app's code // should run with an adjusted context loader: private static final boolean SET_CURRENT_CONTEXT_LOADER = INIT_AT_LOAD_TIME; // a soft cache is ideal for managing class definitions that are read during // the initial classpath scan; however, the default LRU policy parameters for // clearing SoftReferences in Sun's J2SDK 1.3+ render them next to useless // in the client HotSpot JVM (in which this tool will probably run most often); // using a hard cache guarantees 100% cache hit rate but can also raise the // memory requirements significantly beyond the needs of the original app. // [see bug refs 4471453, 4806720, 4888056, 4239645] // // resolution for now: use a soft cache anyway and doc that to make it useful // for non-trivial apps the user should use -Xms or -XX:SoftRefLRUPolicyMSPerMB // JVM options or use a server HotSpot JVM private static final boolean USE_SOFT_CACHE = true; private static final int INIT_CACHE_CAPACITY = 2003; // prime private static final int SOFT_CACHE_READ_CHK_FREQUENCY = 100; private static final int SOFT_CACHE_WRITE_CHK_FREQUENCY = 100; private static final String [] FORCED_DELEGATION_FILTER_SPECS; // set in private static final Class [] MAIN_TYPE = new Class [] {String [].class}; private static final Class [] EXPECTED_FAILURES; // set in protected static final String COMMA_DELIMITERS = "," + Strings.WHITE_SPACE; protected static final String PATH_DELIMITERS = ",".concat (File.pathSeparator); static { EXPECTED_FAILURES = new Class [] { EMMARuntimeException.class, IllegalArgumentException.class, IllegalStateException.class, }; FORCED_DELEGATION_FILTER_SPECS = new String [] {"+" + IAppConstants.APP_PACKAGE + ".*"}; } } // end of class // ----------------------------------------------------------------------------