KickJava   Java API By Example, From Geeks To Geeks.

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


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: InstrClassLoader.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.io.IOException JavaDoc;
13 import java.io.InputStream JavaDoc;
14 import java.io.PrintWriter JavaDoc;
15 import java.net.MalformedURLException JavaDoc;
16 import java.net.URL JavaDoc;
17 import java.net.URLClassLoader JavaDoc;
18 import java.security.CodeSource JavaDoc;
19 import java.util.Map JavaDoc;
20
21 import com.vladium.logging.Logger;
22 import com.vladium.util.ByteArrayOStream;
23 import com.vladium.util.asserts.$assert;
24 import com.vladium.emma.IAppConstants;
25 import com.vladium.emma.filter.IInclExclFilter;
26
27 // ----------------------------------------------------------------------------
28
/**
29  * @author Vlad Roubtsov, (C) 2003
30  */

31 public
32 final class InstrClassLoader extends URLClassLoader JavaDoc
33 {
34     // public: ................................................................
35

36     // TODO: proper security [use PrivilegedAction as needed]
37
// TODO: [see above as well] need to keep track of res URLs to support [path] exclusion patterns
38
// TODO: improve error handling so it is clear when errors come from buggy instrumentation
39

40     
41     public static final String JavaDoc PROPERTY_FORCED_DELEGATION_FILTER = "clsload.forced_delegation_filter";
42     public static final String JavaDoc PROPERTY_THROUGH_DELEGATION_FILTER = "clsload.through_delegation_filter";
43         
44
45     public InstrClassLoader (final ClassLoader JavaDoc parent, final File JavaDoc [] classpath,
46                              final IInclExclFilter forcedDelegationFilter,
47                              final IInclExclFilter throughDelegationFilter,
48                              final IClassLoadHook hook, final Map JavaDoc cache)
49         throws MalformedURLException JavaDoc
50     {
51         // setting ClassLoader.parent to null disables the standard delegation
52
// behavior in a few places, including URLClassLoader.getResource():
53

54         super (filesToURLs (classpath), null);
55         
56         // TODO: arg validation
57

58         m_hook = hook;
59         m_cache = cache; // can be null
60

61         m_forcedDelegationFilter = forcedDelegationFilter;
62         m_throughDelegationFilter = throughDelegationFilter;
63          
64         m_parent = parent;
65         m_bufPool = new PoolEntry [BAOS_POOL_SIZE];
66         
67         m_log = Logger.getLogger ();
68     }
69     
70     /**
71      * Overrides java.lang.ClassLoader.loadClass() to change the usual parent-child
72      * delegation rules just enough to be able to 'replace' the parent loader. This
73      * also has the effect of detecting 'system' classes without doing any class
74      * name-based matching.
75      */

76     public synchronized final Class JavaDoc loadClass (final String JavaDoc name, final boolean resolve)
77         throws ClassNotFoundException JavaDoc
78     {
79         final boolean trace1 = m_log.atTRACE1 ();
80         
81         if (trace1) m_log.trace1 ("loadClass", "(" + name + ", " + resolve + "): nest level " + m_nestLevel);
82         
83         Class JavaDoc c = null;
84         
85         // first, check if this class has already been defined by this classloader
86
// instance:
87
c = findLoadedClass (name);
88         
89         if (c == null)
90         {
91             Class JavaDoc parentsVersion = null;
92             if (m_parent != null)
93             {
94                 try
95                 {
96                     parentsVersion = m_parent.loadClass (name); // note: it is important that this does not init the class
97

98                     if ((parentsVersion.getClassLoader () != m_parent) ||
99                         ((m_forcedDelegationFilter == null) || m_forcedDelegationFilter.included (name)))
100                     {
101                         // (a) m_parent itself decided to delegate: use parent's version
102
// (b) the class was on the forced delegation list: use parent's version
103
c = parentsVersion;
104                         if (trace1) m_log.trace1 ("loadClass", "using parent's version for [" + name + "]");
105                     }
106                 }
107                 catch (ClassNotFoundException JavaDoc cnfe)
108                 {
109                     // if the class was on the forced delegation list, error out:
110
if ((m_forcedDelegationFilter == null) || m_forcedDelegationFilter.included (name))
111                         throw cnfe;
112                 }
113             }
114             
115             if (c == null)
116             {
117                 try
118                 {
119                     // either (a) m_parent was null or (b) it could not load 'c'
120
// or (c) it will define 'c' itself if allowed to. In any
121
// of these cases I attempt to define my own version:
122
c = findClass (name);
123                 }
124                 catch (ClassNotFoundException JavaDoc cnfe)
125                 {
126                     // this is a difficult design point unless I resurrect the -lx option
127
// and document how to use it [which will confuse most users anyway]
128

129                     // another alternative would be to see if parent's version is included by
130
// the filter and print a warning; still, it does not help with JAXP etc
131

132                     if (parentsVersion != null)
133                     {
134                         final boolean delegate = (m_throughDelegationFilter == null) || m_throughDelegationFilter.included (name);
135                         
136                         if (delegate)
137                         {
138                             c = parentsVersion;
139                             if (trace1) m_log.trace1 ("loadClass", "[delegation filter] using parent's version for [" + name + "]");
140                         }
141                         else
142                             throw cnfe;
143                     }
144                     else
145                       throw cnfe;
146                 }
147             }
148         }
149         
150         if (c == null) throw new ClassNotFoundException JavaDoc (name);
151         
152         if (resolve) resolveClass (c); // this never happens in J2SE JVMs
153
return c;
154     }
155     
156     // TODO: remove this in the release build
157

158     public final URL JavaDoc getResource (final String JavaDoc name)
159     {
160         final boolean trace1 = m_log.atTRACE1 ();
161         
162         if (trace1) m_log.trace1 ("getResource", "(" + name + "): nest level " + m_nestLevel);
163         
164         final URL JavaDoc result = super.getResource (name);
165         if (trace1 && (result != null)) m_log.trace1 ("loadClass", "[" + name + "] found in " + result);
166         
167         return result;
168     }
169     
170     // protected: .............................................................
171

172     
173     protected final Class JavaDoc findClass (final String JavaDoc name)
174         throws ClassNotFoundException JavaDoc
175     {
176         final boolean trace1 = m_log.atTRACE1 ();
177         
178         if (trace1) m_log.trace1 ("findClass", "(" + name + "): nest level " + m_nestLevel);
179         
180         final boolean useClassCache = (m_cache != null);
181         final ClassPathCacheEntry entry = useClassCache ? (ClassPathCacheEntry) m_cache.remove (name) : null;
182         
183         byte [] bytes;
184         int length;
185         URL JavaDoc classURL = null;
186             
187         if (entry != null) // cache hit
188
{
189             ++ m_cacheHits;
190             
191             // used cached class def bytes, no need to repeat disk I/O:
192

193             try
194             {
195                 classURL = new URL JavaDoc (entry.m_srcURL);
196             }
197             catch (MalformedURLException JavaDoc murle) // this should never happen
198
{
199                 if ($assert.ENABLED)
200                 {
201                     murle.printStackTrace (System.out);
202                 }
203             }
204             
205             PoolEntry buf = null;
206             try
207             {
208                 buf = acquirePoolEntry ();
209                 final ByteArrayOStream baos = buf.m_baos; // reset() has been called on this
210

211                 // the original class definition:
212
bytes = entry.m_bytes;
213                 length = bytes.length;
214                 
215                 if ((m_hook != null) && m_hook.processClassDef (name, bytes, length, baos)) // note: this can overwrite 'bytes'
216
{
217                     // the instrumented class definition:
218
bytes = baos.getByteArray ();
219                     length = baos.size ();
220                     
221                     if (trace1) m_log.trace1 ("findClass", "defining [cached] instrumented [" + name + "] {" + length + " bytes }");
222                 }
223                 else
224                 {
225                     if (trace1) m_log.trace1 ("findClass", "defining [cached] [" + name + "] {" + length + " bytes }");
226                 }
227                 
228                 return defineClass (name, bytes, length, classURL);
229             }
230             catch (IOException JavaDoc ioe)
231             {
232                 throw new ClassNotFoundException JavaDoc (name);
233             }
234             finally
235             {
236                 if (buf != null) releasePoolEntry (buf);
237             }
238         }
239         else // cache miss
240
{
241             if (useClassCache) ++ m_cacheMisses;
242             
243             // .class files are not guaranteed to be loadable as resources;
244
// but if Sun's code does it...
245
final String JavaDoc classResource = name.replace ('.', '/') + ".class";
246             
247             // even thought normal delegation is disabled, this will find bootstrap classes:
248
classURL = getResource (classResource); // important to hook into URLClassLoader's overload of this so that Class-Path manifest attributes are processed etc
249

250             if (trace1 && (classURL != null)) m_log.trace1 ("findClass", "[" + name + "] found in " + classURL);
251             
252             if (classURL == null)
253                 throw new ClassNotFoundException JavaDoc (name);
254             else
255             {
256                 InputStream JavaDoc in = null;
257                 PoolEntry buf = null;
258                 try
259                 {
260                     in = classURL.openStream ();
261                     
262                     buf = acquirePoolEntry ();
263                     final ByteArrayOStream baos = buf.m_baos; // reset() has been called on this
264

265                     readFully (in, baos, buf.m_buf);
266                     in.close (); // don't keep the file handle across reentrant calls
267
in = null;
268                     
269                     // the original class definition:
270
bytes = baos.getByteArray ();
271                     length = baos.size ();
272                     
273                     baos.reset (); // reuse this for processClassDef below
274

275                     if ((m_hook != null) && m_hook.processClassDef (name, bytes, length, baos)) // note: this can overwrite 'bytes'
276
{
277                         // the instrumented class definition:
278
bytes = baos.getByteArray ();
279                         length = baos.size ();
280                         
281                         if (trace1) m_log.trace1 ("findClass", "defining instrumented [" + name + "] {" + length + " bytes }");
282                     }
283                     else
284                     {
285                         if (trace1) m_log.trace1 ("findClass", "defining [" + name + "] {" + length + " bytes }");
286                     }
287                     
288                     return defineClass (name, bytes, length, classURL);
289                 }
290                 catch (IOException JavaDoc ioe)
291                 {
292                     throw new ClassNotFoundException JavaDoc (name);
293                 }
294                 finally
295                 {
296                     if (buf != null) releasePoolEntry (buf);
297                     if (in != null) try { in.close (); } catch (Exception JavaDoc ignore) {}
298                 }
299             }
300         }
301     }
302     
303     public void debugDump (final PrintWriter JavaDoc out)
304     {
305         if (out != null)
306         {
307             out.println (this + ": " + m_cacheHits + " class cache hits, " + m_cacheMisses + " misses");
308         }
309     }
310
311     // package: ...............................................................
312

313     // private: ...............................................................
314

315     
316     private static final class PoolEntry
317     {
318         PoolEntry (final int baosCapacity, final int bufSize)
319         {
320             m_baos = new ByteArrayOStream (baosCapacity);
321             m_buf = new byte [bufSize];
322         }
323         
324         void trim (final int baosCapacity, final int baosMaxCapacity)
325         {
326             if (m_baos.capacity () > baosMaxCapacity)
327             {
328                 m_baos = new ByteArrayOStream (baosCapacity);
329             }
330         }
331         
332         ByteArrayOStream m_baos;
333         final byte [] m_buf;
334         
335     } // end of nested class
336

337     
338     /*
339      * 'srcURL' may be null
340      */

341     private Class JavaDoc defineClass (final String JavaDoc className, final byte [] bytes, final int length, final URL JavaDoc srcURL)
342     {
343         // support ProtectionDomains with non-null class source URLs:
344
// [however, disable anything related to sealing or signing]
345

346         final CodeSource JavaDoc csrc = new CodeSource JavaDoc (srcURL, null);
347         
348         // allow getPackage() to return non-null on the class we are about to
349
// define (however, don't bother emulating the original manifest info since
350
// we may be altering manifest content anyway):
351

352         final int lastDot = className.lastIndexOf ('.');
353         if (lastDot >= 0)
354         {
355             final String JavaDoc packageName = className.substring (0, lastDot);
356             
357             final Package JavaDoc pkg = getPackage (packageName);
358             if (pkg == null)
359             {
360                 definePackage (packageName,
361                                IAppConstants.APP_NAME, IAppConstants.APP_VERSION, IAppConstants.APP_COPYRIGHT,
362                                IAppConstants.APP_NAME, IAppConstants.APP_VERSION, IAppConstants.APP_COPYRIGHT,
363                                srcURL);
364             }
365         }
366         
367         return defineClass (className, bytes, 0, length, csrc);
368     }
369     
370     
371     private static URL JavaDoc [] filesToURLs (final File JavaDoc [] classpath)
372         throws MalformedURLException JavaDoc
373     {
374         if ((classpath == null) || (classpath.length == 0))
375             return EMPTY_URL_ARRAY;
376             
377         final URL JavaDoc [] result = new URL JavaDoc [classpath.length];
378         
379         for (int f = 0; f < result.length ; ++ f)
380         {
381             result [f] = classpath [f].toURL (); // note: this does proper dir encoding
382
}
383         
384         return result;
385     }
386     
387     /**
388      * Reads the entire contents of a given stream into a flat byte array.
389      */

390     private static void readFully (final InputStream JavaDoc in, final ByteArrayOStream out, final byte [] buf)
391         throws IOException JavaDoc
392     {
393         for (int read; (read = in.read (buf)) >= 0; )
394         {
395             out.write (buf, 0, read);
396         }
397     }
398     
399     /*
400      * not MT-safe; must be called from loadClass() only
401      */

402     private PoolEntry acquirePoolEntry ()
403     {
404         PoolEntry result;
405         
406         if (m_nestLevel >= BAOS_POOL_SIZE)
407         {
408             result = new PoolEntry (BAOS_INIT_SIZE, BAOS_INIT_SIZE);
409         }
410         else
411         {
412             result = m_bufPool [m_nestLevel];
413             if (result == null)
414             {
415                 result = new PoolEntry (BAOS_INIT_SIZE, BAOS_INIT_SIZE);
416                 m_bufPool [m_nestLevel] = result;
417             }
418             else
419             {
420                 result.m_baos.reset ();
421             }
422         }
423         
424         ++ m_nestLevel;
425             
426         return result;
427     }
428     
429     /*
430      * not MT-safe; must be called from loadClass() only
431      */

432     private void releasePoolEntry (final PoolEntry buf)
433     {
434         if (-- m_nestLevel < BAOS_POOL_SIZE)
435         {
436             buf.trim (BAOS_INIT_SIZE, BAOS_MAX_SIZE);
437         }
438     }
439     
440     
441     private final ClassLoader JavaDoc m_parent;
442     
443     private final IInclExclFilter m_forcedDelegationFilter;
444     private final IInclExclFilter m_throughDelegationFilter;
445     
446     private final Map JavaDoc /* classJavaName:String -> ClassPathCacheEntry */ m_cache; // can be null
447
private final IClassLoadHook m_hook;
448     private final PoolEntry [] m_bufPool;
449     
450     private final Logger m_log; // a loader instance is used concurrently but cached its log config at construction time
451

452     private int m_nestLevel;
453     
454     private int m_cacheHits, m_cacheMisses;
455     
456     private static final int BAOS_INIT_SIZE = 32 * 1024;
457     private static final int BAOS_MAX_SIZE = 1024 * 1024;
458     private static final int BAOS_POOL_SIZE = 8;
459     private static final URL JavaDoc [] EMPTY_URL_ARRAY = new URL JavaDoc [0];
460     
461 } // end of class
462
// ----------------------------------------------------------------------------
Popular Tags