KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > quilt > cl > QuiltClassLoader


1 /* QuiltClassLoader.java */
2 package org.quilt.cl;
3
4 import java.io.ByteArrayInputStream JavaDoc;
5 import java.io.ByteArrayOutputStream JavaDoc;
6 import java.io.File JavaDoc;
7 import java.io.InputStream JavaDoc;
8 import java.io.IOException JavaDoc;
9 import java.lang.reflect.Constructor JavaDoc;
10 import java.net.MalformedURLException JavaDoc;
11 import java.net.URL JavaDoc;
12 import java.net.URLClassLoader JavaDoc;
13 import java.util.Hashtable JavaDoc;
14 import java.util.Iterator JavaDoc;
15 import java.util.List JavaDoc;
16 import java.util.Map JavaDoc;
17 import java.util.Vector JavaDoc;
18
19 import org.apache.bcel.classfile.ClassParser;
20 import org.apache.bcel.classfile.JavaClass;
21
22 // DEBUG
23
import org.apache.bcel.classfile.Field;
24 import org.apache.bcel.classfile.Method;
25 // END
26

27 import org.quilt.reg.QuiltRegistry;
28
29 /**
30  * <p>Quilt's transforming class loader. Can be directed to instrument
31  * a set of classes, matching class names against a list of prefixes
32  * and another list excluding classes from instrumentation, the
33  * exclusion list taking priority. Will delegate loading to a parent
34  * class loader where explicitly directed to; otherwise will be the
35  * defining loader. By default the loading of classes whose names
36  * begin with <tt>java., javax., junit., org.apache.bcel.,
37  * org.apache.tools.ant.</tt> and <tt>org.quilt.</tt> is delegated.</p>
38  *
39  * <p>Classes whose names begin with a reserved prefix, currently
40  * <tt>test.data.Test</tt>, are synthesized instead of being
41  * loaded. This must be specifically enabled.</p>
42  *
43  *
44  *
45  * @author <a HREF="jddixon@users.sourceforge.net">Jim Dixon</a>
46  *
47  * @see ClassFactory
48  */

49 public class QuiltClassLoader extends URLClassLoader JavaDoc {
50
51     /** Operating system specific */
52     public static final char FILE_PATH_DELIM_CHAR = File.separatorChar;
53     public static final String JavaDoc FILE_PATH_DELIM_STR = File.separator;
54     public static final char CLASSPATH_DELIM_CHAR = File.pathSeparatorChar;
55     public static final String JavaDoc CLASSPATH_DELIM_STR = File.pathSeparator;
56
57     /**
58      * Names of classes which must be loaded by the parent. There is one
59      * exception to this list: org.quilt.QIC, which is not delegated and
60      * not instrumented.
61      */

62     public static final String JavaDoc[] DELEGATED =
63         {"java.", "javax.", "junit.", "org.apache.bcel.",
64          "org.apache.tools.ant.", "org.quilt.", "sun."};
65
66     /** XXX This is misleading! What's wanted is a copy. */
67     private String JavaDoc[] dels = DELEGATED;
68
69     private List JavaDoc delegated = new Vector JavaDoc();
70     /**
71      * Names of classes NOT to be instrumented. Names are matched
72      * as above. The excluded list is consulted first.
73      */

74     private List JavaDoc excluded = new Vector JavaDoc();
75
76     /**
77      * Names of classes to be instrumented. At this time no
78      * wildcards are permitted. Any class whose name begins
79      * with a string in the array will be instrumented,
80      * unless it is on the excluded list.
81      */

82     private List JavaDoc included = new Vector JavaDoc();
83
84     /**
85      * URLs in the order in which they are to be searched. Those
86      * ending in '/' are directories. Any others are jars.
87      */

88     private List JavaDoc classPath = new Vector JavaDoc();
89
90     /** Delegation class loader. Unless a class is to be instrumented
91      * (is on the inclusion list and not on the exclusion list),
92      * loading will be delegated to this class loader.
93      */

94     private ClassLoader JavaDoc parent = null;
95
96     /** Prefix indicating that the class should be synthesized. */
97     public static final String JavaDoc SYNTH_PREFIX = "test.data.Test";
98     private String JavaDoc synthPrefix = SYNTH_PREFIX;
99     private boolean synthEnabled = false;
100
101     /** Responsible for instrumenting classes. */
102     public ClassTransformer xformer = null;
103     /** Configurable class transformers. */
104     List JavaDoc cxf = new Vector JavaDoc();
105     /** Configurable method transformers. */
106     List JavaDoc mxf = new Vector JavaDoc();
107     /** Configurable graph transformers. */
108     List JavaDoc gxf = new Vector JavaDoc();
109     /** QuiltRegistry list. */
110     List JavaDoc regList = new Vector JavaDoc();
111
112     /** Constructor with abbreviated argument list. */
113     public QuiltClassLoader (URL JavaDoc[] cp, String JavaDoc [] inc) {
114         this(cp, null, null, inc, null);
115     }
116     /**
117      * Constructor with full argument list.
118      *
119      * @param cp Class path, an array of paths
120      * @param parent Class loader which we delegate to.
121      * @param del String array, names of classes to be delegated
122      * @param inc String array, names of classes to be instrumented
123      * @param exc String array, names of classes not to be instrumented.
124      */

125     public QuiltClassLoader (URL JavaDoc[] cp, ClassLoader JavaDoc parent,
126                             String JavaDoc [] del, String JavaDoc [] inc, String JavaDoc [] exc) {
127         super(cp == null ? new URL JavaDoc[0] : cp, parent);
128
129         if (cp != null) {
130             for (int i = 0; i < cp.length; i++) {
131                 classPath.add(cp[i]);
132             }
133         }
134         if (parent == null) {
135             this.parent = getSystemClassLoader();
136         } else {
137             this.parent = parent;
138         }
139         for (int i = 0; i < dels.length; i++) {
140             delegated.add( dels[i] );
141         }
142         if (del != null) {
143             for (int i = 0; i < del.length; i++ ) {
144                 delegated.add(del[i]);
145             }
146         }
147         if (inc != null) {
148             for (int i = 0; i < inc.length; i++) {
149                 included.add(inc[i]);
150             }
151         }
152         if (exc != null) {
153             for (int i = 0; i < exc.length; i++) {
154                 excluded.add(exc[i]);
155             }
156         }
157     }
158     /** Do we delegate loading this to the parent? */
159     private boolean delegateTheClass (final String JavaDoc name) {
160         if (name.equals("org.quilt.QIC")) {
161             return false;
162         }
163         for (int i = 0; i < delegated.size(); i++) {
164             if (name.startsWith( (String JavaDoc)delegated.get(i)) ) {
165                 return true;
166             }
167         }
168         return false;
169     }
170     /** Should class be instrumented? */
171     private boolean instrumentTheClass (final String JavaDoc name) {
172         if (name.equals("org.quilt.QIC")) {
173             return false;
174         }
175         for (int i = 0; i < excluded.size(); i++) {
176             if ( name.startsWith ( (String JavaDoc)excluded.get(i) ) ) {
177                 return false;
178             }
179         }
180         for (int i = 0; i < included.size(); i++) {
181             if ( name.startsWith ( (String JavaDoc)included.get(i) ) ) {
182                 return true;
183             }
184         }
185         return false;
186     }
187     /**
188      * Convert a class name into a file name by replacing dots with
189      * forward slashes and appending ".class".
190      */

191     public static String JavaDoc classFileName (final String JavaDoc className) {
192          return className.replace('.', FILE_PATH_DELIM_CHAR) + ".class";
193     }
194     /**
195      * Class loader. Delegates the loading if specifically instructed
196      * to do so. Returns the class if it has already been loaded.
197      * Otherwise creates a class transformer if necessary and then
198      * passes the name to <code>findClass.</code>
199      */

200     public synchronized Class JavaDoc loadClass (String JavaDoc name)
201                                         throws ClassNotFoundException JavaDoc {
202         if (name == null) {
203             throw new IllegalArgumentException JavaDoc("null class name");
204         }
205         if (delegateTheClass(name)) {
206             // DEBUG
207
// System.out.println("QCL.loadClass: delegating " + name);
208
// END
209
return parent.loadClass(name);
210         }
211         Class JavaDoc c = findLoadedClass (name);
212         if (c != null) {
213             return c;
214         }
215         if (xformer == null) {
216             xformer = new ClassTransformer( cxf, mxf, gxf );
217         }
218         return findClass (name);
219     }
220     /**
221      * Locate the class whose name is passed and define it. If the
222      * class name has the appropriate prefix and synthesizing it is
223      * enabled, it synthesizes it. Otherwise it searches for it
224      * along the class path. If indicated, it transforms (instruments)
225      * the class. Finally, it defines and returns the result.
226      *
227      * @param name Class name in embedded dot (.) form.
228      */

229     protected Class JavaDoc findClass (String JavaDoc name)
230                                         throws ClassNotFoundException JavaDoc {
231         // we only instrument the class if we have transformers at
232
// class, method, or graph level
233
boolean instIt = instrumentTheClass(name)
234                   && (cxf.size() > 0 || mxf.size() > 0 || gxf.size() > 0);
235         byte [] b = null;
236         if ( name.startsWith ( synthPrefix )) {
237             JavaClass jc = ClassFactory.getInstance()
238                                 .makeClass(name, classFileName(name))
239                                 .getJavaClass();
240             if (instIt) {
241                 jc = xformer.xform(jc);
242             }
243             b = jc.getBytes(); // convert it into a byte array
244
} else {
245             // DEBUG
246
//System.out.println("QCL.findClass: locating " + name);
247
// END
248
try {
249                 b = getClassData (name);
250                 if (instIt) {
251                     // DEBUG
252
// System.out.println("QCL.findClass: instrumenting " + name);
253
// END
254
// convert to bcel JavaClass -
255
// throws IOException, ClassFormatException
256
JavaClass jc = new ClassParser (
257                         new ByteArrayInputStream JavaDoc(b), classFileName(name) )
258                             .parse();
259                     JavaClass temp = xformer.xform (jc);
260 // // DEBUG
261
// Field [] myFields = temp.getFields();
262
// StringBuffer fieldData = new StringBuffer();
263
// for (int k = 0; k < myFields.length; k++)
264
// fieldData.append(" ")
265
// .append(myFields[k]).append("\n");
266

267 // Method[] myMethods = temp.getMethods();
268
// StringBuffer methodData = new StringBuffer();
269
// for (int k = 0; k < myMethods.length; k++)
270
// methodData.append(" ")
271
// .append(myMethods[k].getName()).append("\n");
272

273 // System.out.println(
274
// "QCL.findClass after instrumenting JavaClass for "
275
// + name
276
// + "\nFIELDS (" + myFields.length + ") :\n"
277
// + fieldData.toString()
278
// + "METHODS (" + myMethods.length + ") :\n"
279
// + methodData.toString() );
280
// // END
281

282                     //b = xformer.xform (jc).getBytes();
283
b = temp.getBytes();
284                 }
285             } catch (IOException JavaDoc e) {
286                 e.printStackTrace(); // DEBUG
287
throw new ClassNotFoundException JavaDoc(name, e);
288             }
289         }
290
291         // this can throw a ClassFormatError or IndexOutOfBoundsException
292
return defineClass (name, b, 0, b.length);
293     }
294     /** @return Classpath as a newline-terminated String. */
295     public String JavaDoc urlsToString(){
296         StringBuffer JavaDoc sb = new StringBuffer JavaDoc() .append("classpath:\n");
297         URL JavaDoc[] urls = getURLs();
298         for (int k = 0; k < urls.length; k++) {
299             sb.append(" ").append(k).append(" ")
300                                        .append(urls[k]).append("\n");
301         }
302         return sb.toString();
303     }
304     /** Find a class along the class path and load it as a byte array. */
305     protected byte[] getClassData (String JavaDoc className)
306                                             throws IOException JavaDoc {
307         URL JavaDoc fileURL = findResource ( classFileName(className) );
308         // DEBUG XXX
309
if (fileURL == null) {
310             System.err.println("QCL.getClassData mapping " + className
311                 + " to " + classFileName(className) );
312             System.err.println(" findResource returned null\n"
313                 + urlsToString());
314         }
315         // END
316
if (fileURL == null) {
317             // ClassNotFoundException();
318
throw new IOException JavaDoc("null fileURL for " + className);
319         }
320         InputStream JavaDoc ins = fileURL.openStream();
321         ByteArrayOutputStream JavaDoc outs = new ByteArrayOutputStream JavaDoc (65536);
322         byte [] buffer = new byte [4096];
323         int count;
324         while ( (count = ins.read(buffer)) != -1 ) {
325             outs.write(buffer, 0, count);
326         }
327         return outs.toByteArray();
328     }
329
330     // ADD/GET/SET METHODS //////////////////////////////////////////
331
/**
332      * Add a path to the class loader's classpath.
333      * @param url Path to be added.
334      */

335     public void addPath (URL JavaDoc url) {
336         classPath.add(url);
337     }
338     /** @return The classpath used by this QuiltClassLoader. */
339     public URL JavaDoc[] getClassPath() {
340         URL JavaDoc[] myURLs = new URL JavaDoc[ classPath.size() ];
341         return (URL JavaDoc[]) (classPath.toArray(myURLs));
342     }
343     /**
344      * Convert domain name in classpath to file name, allowing for
345      * initial dots. Need to cope with ../../target/big.jar and
346      * similar constructions.
347      */

348     public static final String JavaDoc THIS_DIR = "." + FILE_PATH_DELIM_STR;
349     public static final String JavaDoc UP_DIR = ".." + FILE_PATH_DELIM_STR;
350     public static final int THIS_DIR_LEN = THIS_DIR.length();
351     public static final int UP_DIR_LEN = UP_DIR.length();
352
353     /**
354      * Convert a dotted domain name to its path form, allowing for
355      * leading ./ and ../ and terminating .jar
356      */

357     public static String JavaDoc domainToFileName (String JavaDoc name) {
358         // ignore any leading dots
359
int startNdx;
360         for (startNdx = 0; startNdx < name.length(); ) {
361             if ( name.substring(startNdx).startsWith(THIS_DIR)) {
362                 startNdx += THIS_DIR_LEN;
363             } else if (name.substring(startNdx).startsWith(UP_DIR)) {
364                 startNdx += UP_DIR_LEN;
365             } else {
366                 break;
367             }
368         }
369         // leave .jar intact
370
int endNdx;
371         if ( name.endsWith(".jar") ){
372             endNdx = name.length() - 4;
373         } else {
374             endNdx = name.length();
375         }
376         
377         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
378         if (startNdx > 0) {
379             sb.append(name.substring(0, startNdx));
380         }
381         sb.append(name.substring(startNdx, endNdx)
382                                     .replace('.', FILE_PATH_DELIM_CHAR));
383         if (endNdx != name.length()) {
384             sb.append(".jar");
385         }
386         return sb.toString();
387     }
388     /**
389      * Convert classpath in normal form to URL[]
390      */

391     public static URL JavaDoc[] cpToURLs (String JavaDoc cp ) {
392         URL JavaDoc[] urls;
393         if (cp == null) {
394             urls = new URL JavaDoc[0];
395         } else {
396             String JavaDoc [] elements = cp.split(":");
397             List JavaDoc urlList = new Vector JavaDoc();
398             int urlCount = 0;
399             for (int i = 0; i < elements.length; i++) {
400                 String JavaDoc noDots = domainToFileName(elements[i]);
401                 boolean foundJar = noDots.endsWith(".jar");
402                 File JavaDoc file = new File JavaDoc (noDots);
403                 String JavaDoc urlForm = "file://" + file.getAbsolutePath();
404                 if (!foundJar && !urlForm.endsWith(FILE_PATH_DELIM_STR)) {
405                     urlForm += FILE_PATH_DELIM_STR;
406                 }
407                 try {
408                     URL JavaDoc candidate = new URL JavaDoc(urlForm);
409                     urlCount++; // didn't throw exception
410
urlList.add(candidate);
411                 } catch (MalformedURLException JavaDoc e) {
412                     System.err.println ("WARNING: ignoring malformed URL "
413                         + urlForm);
414                 }
415             }
416             urls = new URL JavaDoc[urlCount];
417             for (int k = 0; k < urls.length; k++) {
418                 urls[k] = (URL JavaDoc) urlList.get(k);
419             }
420         }
421         return urls;
422     }
423     /**
424      * Convert classpath in normal form to URL[] and sets loader
425      * classpath to the corresponding value.
426      *
427      * @param cp Class path in colon- or semicolon-delimited form.
428      */

429     public void setClassPath (String JavaDoc cp) {
430         classPath.clear();
431         URL JavaDoc[] urls = cpToURLs(cp);
432         for (int i = 0; i < urls.length; i++) {
433             classPath.add(urls[i]);
434             addURL( urls[i] );
435         }
436 // // DEBUG
437
// System.out.println("after setting classpath, new classpath is:");
438
// URL[] currURLs = getURLs();
439
// for (int k = 0; k < currURLs.length; k++) {
440
// System.out.println(" " + k + " " + currURLs[k].getPath() );
441
// }
442
// // END
443
}
444     /**
445      * Add a class name prefix to the list of those to be delegated
446      * to the parent.
447      * @param prefix Prefix to be added.
448      */

449     public void addDelegated (final String JavaDoc prefix) {
450         delegated.add(prefix);
451     }
452     /**
453      * @return As a String array the list of class name prefixes
454      * whose loading is to be delegated to the parent.
455      */

456     public String JavaDoc[] getDelegated() {
457         String JavaDoc[] myDels = new String JavaDoc[ delegated.size() ];
458         return (String JavaDoc[]) (delegated.toArray(myDels));
459     }
460
461     /**
462      * Add a class name prefix to the list of those to be excluded
463      * from instrumentation.
464      *
465      * @param prefix Prefix to be added.
466      */

467     public void addExcluded (final String JavaDoc prefix) {
468         excluded.add(prefix);
469     }
470     /**
471      * @return As a String array the list of class name prefixes
472      * which are NOT to be instrumented.
473      */

474     public String JavaDoc[] getExcluded() {
475         String JavaDoc[] myExc = new String JavaDoc [ excluded.size() ];
476         return (String JavaDoc[]) (excluded.toArray(myExc));
477     }
478     /**
479      * Sets the list of classes to be excluded from instrumentation.
480      *
481      * @param s List of classes in comma-separated String form.
482      */

483     public void setExcluded(String JavaDoc s) {
484         excluded.clear();
485         if (s != null) {
486             String JavaDoc [] newExc = s.split(",");
487             for (int i = 0; i < newExc.length; i++) {
488                 excluded.add ( newExc[i] );
489             }
490         }
491     }
492     /**
493      * Add a class name prefix to the list of those to be
494      * instrumented.
495      *
496      * @param prefix Prefix to be added.
497      */

498     public void addIncluded (final String JavaDoc prefix) {
499         included.add(prefix);
500     }
501     /**
502      * @return As a String array the list of class name prefixes
503      * which ARE to be instrumented.
504      */

505     public String JavaDoc[] getIncluded() {
506         String JavaDoc[] myInc = new String JavaDoc [ included.size() ];
507         return (String JavaDoc[]) (included.toArray(myInc));
508     }
509     /**
510      * Sets the list of classes to be instrumented.
511      *
512      * @param s List of classes in comma-separated String form.
513      */

514     public void setIncluded(String JavaDoc s) {
515         included.clear();
516         if (s != null) {
517             String JavaDoc [] newInc = s.split(",");
518             for (int i = 0; i < newInc.length; i++) {
519                 included.add ( newInc[i] );
520             }
521         }
522     }
523     /** Get synthesizing-enabled flag. */
524     public boolean getSynthEnabled() {
525         return synthEnabled;
526     }
527     /** Enable class synthesizing. */
528     public void setSynthEnabled (boolean b) {
529         synthEnabled = b;
530     }
531
532     /**
533      * @return The prefix signifying that a class is to be synthesized.
534      */

535     public String JavaDoc getSynthPrefix() {
536         return synthPrefix;
537     }
538     /** Add a class transformer. */
539     public void addClassXformer( ClassXformer xf) {
540         cxf.add (xf);
541     }
542     /** Add a method transformer. */
543     public void addMethodXformer (MethodXformer xf) {
544         mxf.add (xf);
545     }
546     /** Add a graph transformer. */
547     public void addGraphXformer (GraphXformer xf) {
548         gxf.add (xf);
549     }
550
551     /** Map of registries by String name. */
552     public Map JavaDoc regMap = new Hashtable JavaDoc();
553
554     /** Get a reference to a Quilt registry. */
555     public QuiltRegistry getRegistry (String JavaDoc regName) {
556         QuiltRegistry qr = null;
557         if (regMap.containsKey(regName)) {
558             qr = (QuiltRegistry) regMap.get (regName);
559         }
560         return qr;
561     }
562     /**
563      * Add a new QuiltRegistry to the list. An example of the
564      * argument is "org.quilt.cover.stmt.StmtRegistry".
565      *
566      * @param regName The domain name of the registry in dotted form.
567      */

568     public QuiltRegistry addQuiltRegistry (String JavaDoc regName) {
569         QuiltRegistry qr = null;
570         if (regMap.containsKey(regName)) {
571             qr = (QuiltRegistry) regMap.get(regName);
572         } else try {
573             Class JavaDoc o = Class.forName(regName, false, parent);
574             Constructor JavaDoc con = o.getConstructor( new Class JavaDoc[] {
575                                                     QuiltClassLoader.class });
576             qr = (QuiltRegistry)con.newInstance(new Object JavaDoc[] {this});
577             regList.add(qr);
578             regMap.put (regName, qr);
579         } catch (Exception JavaDoc e) {
580             System.out.println (
581                 "\nQuiltClassLoader.addQuiltRegistry:"
582                 + "\n EXCEPTION while trying to add " + regName
583                 + "\n Is it on the parent's CLASSPATH?"
584                 + "\n Exception: " + e);
585         }
586         return qr;
587     }
588     /**
589      * Get reports from any or all registries. XXX This should not
590      * be returning a String -- it might be huge.
591      */

592     public String JavaDoc getReport() {
593         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
594         if (!regList.isEmpty()) {
595             Iterator JavaDoc i = regList.iterator();
596             while (i.hasNext()) {
597                 QuiltRegistry reg = (QuiltRegistry)i.next();
598                 sb.append(reg.getReport());
599             }
600         }
601         return sb.toString();
602     }
603 }
604
Popular Tags