KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > vladium > emma > rt > AppRunner


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 JavaDoc;
12 import java.lang.reflect.InvocationTargetException JavaDoc;
13 import java.lang.reflect.Method JavaDoc;
14 import java.net.MalformedURLException JavaDoc;
15 import java.util.HashMap JavaDoc;
16 import java.util.Iterator JavaDoc;
17 import java.util.ArrayList JavaDoc;
18 import java.util.List JavaDoc;
19 import java.util.Map JavaDoc;
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 JavaDoc 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 JavaDoc [] 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 JavaDoc [] 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 JavaDoc [] 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 JavaDoc className, final String JavaDoc [] args)
158     {
159         if ((className == null) || (className.length () == 0))
160             throw new IllegalArgumentException JavaDoc ("null/empty input: className");
161         
162         if (args != null)
163         {
164             final String JavaDoc [] _args = (String JavaDoc []) args.clone ();
165              
166             for (int a = 0; a < _args.length; ++ a)
167                 if (_args [a] == null) throw new IllegalArgumentException JavaDoc ("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 JavaDoc fileName)
189     {
190         if (fileName == null)
191             m_sdataOutFile = null;
192         else
193         {
194             final File JavaDoc _file = new File JavaDoc (fileName);
195                 
196             if (_file.exists () && ! _file.isFile ())
197                 throw new IllegalArgumentException JavaDoc ("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 JavaDoc 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 JavaDoc [] types)
217     {
218         if (types == null) throw new IllegalArgumentException JavaDoc ("null input: types");
219         
220         final String JavaDoc [] reportTypes = Strings.removeDuplicates (types, true);
221         if (reportTypes.length == 0) throw new IllegalArgumentException JavaDoc ("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 JavaDoc ("application class name not set");
244         
245         if (m_appArgs == null)
246             throw new IllegalStateException JavaDoc ("application arguments not set");
247
248         if (m_coveragePath == null)
249             throw new IllegalStateException JavaDoc ("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 JavaDoc ("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 JavaDoc f = m_coveragePath [p];
280                 final String JavaDoc 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 JavaDoc f = m_sourcePath [p];
297                     final String JavaDoc 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 JavaDoc sdataOutFile = m_sdataOutFile;
307         Boolean JavaDoc sdataOutMerge = m_sdataOutMerge;
308         {
309             if (sdataOutFile == null)
310                 sdataOutFile = new File JavaDoc (toolProperties.getProperty (EMMAProperties.PROPERTY_SESSION_DATA_OUT_FILE,
311                                                                      EMMAProperties.DEFAULT_SESSION_DATA_OUT_FILE));
312             
313             if (sdataOutMerge == null)
314             {
315                 final String JavaDoc _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 JavaDoc e)
347         {
348             // TODO: log/handle/warn
349
e.printStackTrace (System.out);
350         }
351
352         AppRunnerExitHook runnerExitHook = null;
353         RuntimeException JavaDoc 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 JavaDoc 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 JavaDoc (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 JavaDoc 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 JavaDoc se)
414                 {
415                     throw new EMMARuntimeException (SECURITY_RESTRICTION, new String JavaDoc [] {IAppConstants.APP_NAME}, se);
416                 }
417                 catch (MalformedURLException JavaDoc 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 JavaDoc contextLoader;
429             boolean contextLoaderSet = false;
430             if (SET_CURRENT_CONTEXT_LOADER)
431             {
432                 try
433                 {
434                     final Thread JavaDoc 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 JavaDoc se)
443                 {
444                     throw new EMMARuntimeException (SECURITY_RESTRICTION, new String JavaDoc [] {IAppConstants.APP_NAME}, se);
445                 }
446             }
447             
448             
449             ThreadGroup JavaDoc appThreadGroup = null;
450             try
451             {
452                 // load [and possibly initialize] the app class:
453

454                 final Class JavaDoc 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 JavaDoc cnfe)
461                 {
462                     // TODO: dump the classloader tree into the error message as well
463
throw new EMMARuntimeException (MAIN_CLASS_NOT_FOUND, new String JavaDoc [] {m_appClassName}, cnfe);
464                 }
465                 catch (ExceptionInInitializerError JavaDoc eiie) // this should not happen for INIT_AT_LOAD_TIME=false
466
{
467                     final Throwable JavaDoc cause = eiie.getException ();
468                     
469                     throw new EMMARuntimeException (MAIN_CLASS_LOAD_FAILURE, new String JavaDoc [] {m_appClassName, cause.toString ()}, cause);
470                 }
471                 catch (Throwable JavaDoc t)
472                 {
473                     throw new EMMARuntimeException (MAIN_CLASS_NOT_FOUND, new String JavaDoc [] {m_appClassName}, t);
474                 }
475                 
476                 // ensure that the app is bootstrapped using appLoader:
477
{
478                     final ClassLoader JavaDoc actualLoader = appClass.getClassLoader ();
479                     if (actualLoader != appLoader)
480                     {
481                         final String JavaDoc loaderName = actualLoader != null ? actualLoader.getClass ().getName () : "<PRIMORDIAL>";
482                         
483                         throw new EMMARuntimeException (MAIN_CLASS_BAD_DELEGATION, new String JavaDoc [] {IAppConstants.APP_NAME, m_appClassName, loaderName});
484                     }
485                 }
486     
487                 // run the app's main():
488

489                 final Method JavaDoc 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 JavaDoc t)
496                 {
497                     throw new EMMARuntimeException (MAIN_METHOD_NOT_FOUND, new String JavaDoc [] {m_appClassName}, t);
498                 }
499                 
500                 Invoker invoker = new Invoker (appMain, null, new Object JavaDoc [] {m_appArgs});
501                 
502                 appThreadGroup = new ThreadGroup JavaDoc (IAppConstants.APP_NAME + " thread group [" + m_appClassName + "]");
503                 appThreadGroup.setDaemon (true);
504                 
505                 Thread JavaDoc appThread = new Thread JavaDoc (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 JavaDoc 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 JavaDoc mainFailure = invoker.getFailure ();
525                 invoker = null;
526                 
527                 if (mainFailure != null)
528                 {
529                     if (mainFailure instanceof InvocationTargetException JavaDoc)
530                     {
531                         final Throwable JavaDoc cause = ((InvocationTargetException JavaDoc) mainFailure).getTargetException ();
532                         
533                         throw new EMMARuntimeException (MAIN_METHOD_FAILURE, new String JavaDoc [] {m_appClassName, cause.toString ()}, cause);
534                     }
535                     else if (mainFailure instanceof ExceptionInInitializerError JavaDoc)
536                     {
537                         // this catch block is never entered if INIT_AT_LOAD_TIME is 'true'
538
final Throwable JavaDoc cause = ((ExceptionInInitializerError JavaDoc) mainFailure).getException ();
539                         
540                         throw new EMMARuntimeException (MAIN_METHOD_FAILURE, new String JavaDoc [] {m_appClassName, cause.toString ()}, cause);
541                     }
542                     else if ((mainFailure instanceof IllegalAccessException JavaDoc) ||
543                              (mainFailure instanceof IllegalArgumentException JavaDoc) ||
544                              (mainFailure instanceof NullPointerException JavaDoc))
545                     {
546                         throw new EMMARuntimeException (MAIN_METHOD_NOT_FOUND, new String JavaDoc [] {m_appClassName}, mainFailure);
547                     }
548                     else
549                     {
550                         throw new EMMARuntimeException (MAIN_METHOD_FAILURE, new String JavaDoc [] {m_appClassName, mainFailure.toString ()}, mainFailure);
551                     }
552                 }
553             }
554             catch (SecurityException JavaDoc se)
555             {
556                 throw new EMMARuntimeException (SECURITY_RESTRICTION, new String JavaDoc [] {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 JavaDoc ignore) {}
567                 }
568
569                 if ((appThreadGroup != null) && ! appThreadGroup.isDestroyed ())
570                 try
571                 {
572                     appThreadGroup.destroy ();
573                     appThreadGroup = null;
574                 }
575                 catch (Throwable JavaDoc ignore) {}
576             }
577         }
578         catch (RuntimeException JavaDoc 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 JavaDoc exitHookDataDumpFailure = runnerExitHook.getDataDumpFailure ();
602         final List JavaDoc /* 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 JavaDoc firstReportFailure = null;
615             if (exitHookReportFailures != null)
616             {
617                 for (Iterator JavaDoc i = exitHookReportFailures.iterator (); i.hasNext (); )
618                 {
619                     final Throwable JavaDoc reportFailure = (Throwable JavaDoc) 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 JavaDoc
640     {
641         Invoker (final Method JavaDoc method, final Object JavaDoc target, final Object JavaDoc [] args)
642         {
643             if (method == null) throw new IllegalArgumentException JavaDoc ("null input: method");
644             if (args == null) throw new IllegalArgumentException JavaDoc ("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 JavaDoc t)
658             {
659                 m_failure = t;
660             }
661         }
662         
663         Throwable JavaDoc getFailure ()
664         {
665             return m_failure;
666         }
667         
668         
669         private final Method JavaDoc m_method;
670         private final Object JavaDoc m_target;
671         private final Object JavaDoc [] m_args;
672         private Throwable JavaDoc m_failure;
673         
674     } // end of nested class
675

676     
677     private static final class AppRunnerExitHook implements Runnable JavaDoc
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 JavaDoc 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 JavaDoc t)
744                             {
745                                 if (m_reportFailures == null) m_reportFailures = new ArrayList JavaDoc ();
746                                 m_reportFailures.add (t);
747                                 
748                                 continue;
749                             }
750                             finally
751                             {
752                                 try { generator.cleanup (); } catch (Throwable JavaDoc 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 JavaDoc 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 JavaDoc ("null input: log");
782             if ((generators == null) || (generators.length == 0)) throw new IllegalArgumentException JavaDoc ("null/empty input: generators");
783             if (mdata == null) throw new IllegalArgumentException JavaDoc ("null input: mdata");
784             if (cdata == null) throw new IllegalArgumentException JavaDoc ("null input: cdata");
785             if (properties == null) throw new IllegalArgumentException JavaDoc ("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 JavaDoc getDataDumpFailure ()
802         {
803             return m_dataDumpFailure;
804         }
805         
806         synchronized List JavaDoc /* 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 JavaDoc 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 JavaDoc m_dataDumpFailure;
824         private List JavaDoc /* Throwable */ m_reportFailures;
825         
826     } // end of nested class
827

828     
829     private AppRunner (final ClassLoader JavaDoc delegate)
830     {
831         m_delegate = delegate;
832         m_coveragePath = IConstants.EMPTY_FILE_ARRAY;
833     }
834     
835     
836     private static void joinNonDeamonThreads (final ThreadGroup JavaDoc group)
837     {
838         if (group == null) throw new IllegalArgumentException JavaDoc ("null input: group");
839         
840         final List JavaDoc threads = new ArrayList JavaDoc ();
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 JavaDoc [] aliveThreads;
850             final int aliveCount;
851             
852             // enumerate [recursively] all threads in 'group':
853
synchronized (group)
854             {
855                 aliveThreads = new Thread JavaDoc [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 JavaDoc i = threads.iterator (); i.hasNext (); )
871                 {
872                     try
873                     {
874                         ((Thread JavaDoc) i.next ()).join ();
875                     }
876                     catch (InterruptedException JavaDoc ignore) {}
877                 }
878             }
879         }
880     }
881     
882     private static RuntimeException JavaDoc wrapFailure (final Throwable JavaDoc t)
883     {
884         if (Exceptions.unexpectedFailure (t, EXPECTED_FAILURES))
885             return new EMMARuntimeException (UNEXPECTED_FAILURE,
886                                             new Object JavaDoc [] {t.toString (), IAppConstants.APP_BUG_REPORT_LINK},
887                                             t);
888         else if (t instanceof RuntimeException JavaDoc)
889             return (RuntimeException JavaDoc) 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 JavaDoc m_delegate;
898     
899     private String JavaDoc m_appClassName; // required to be non-null for run()
900
private String JavaDoc [] m_appArgs; // required to be non-null for run()
901

902     private File JavaDoc [] 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 JavaDoc m_sdataOutFile; // user override; can be null for run()
909
private Boolean JavaDoc 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 JavaDoc [] 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 JavaDoc [] FORCED_DELEGATION_FILTER_SPECS; // set in <clinit>
944
private static final Class JavaDoc [] MAIN_TYPE = new Class JavaDoc [] {String JavaDoc [].class};
945     
946     private static final Class JavaDoc [] EXPECTED_FAILURES; // set in <clinit>
947

948     protected static final String JavaDoc COMMA_DELIMITERS = "," + Strings.WHITE_SPACE;
949     protected static final String JavaDoc PATH_DELIMITERS = ",".concat (File.pathSeparator);
950     
951     static
952     {
953         EXPECTED_FAILURES = new Class JavaDoc []
954         {
955             EMMARuntimeException.class,
956             IllegalArgumentException JavaDoc.class,
957             IllegalStateException JavaDoc.class,
958         };
959                 
960         FORCED_DELEGATION_FILTER_SPECS = new String JavaDoc [] {"+" + IAppConstants.APP_PACKAGE + ".*"};
961     }
962     
963 } // end of class
964
// ----------------------------------------------------------------------------
Popular Tags