1 /* QuiltClassLoader.java */
2 package org.quilt.cl;
3
4 import java.io.ByteArrayInputStream;
5 import java.io.ByteArrayOutputStream;
6 import java.io.File;
7 import java.io.InputStream;
8 import java.io.IOException;
9 import java.lang.reflect.Constructor;
10 import java.net.MalformedURLException;
11 import java.net.URL;
12 import java.net.URLClassLoader;
13 import java.util.Hashtable;
14 import java.util.Iterator;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Vector;
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 {
50
51 /*** Operating system specific */
52 public static final char FILE_PATH_DELIM_CHAR = File.separatorChar;
53 public static final String FILE_PATH_DELIM_STR = File.separator;
54 public static final char CLASSPATH_DELIM_CHAR = File.pathSeparatorChar;
55 public static final String 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[] 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[] dels = DELEGATED;
68
69 private List delegated = new Vector();
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 excluded = new Vector();
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 included = new Vector();
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 classPath = new Vector();
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 parent = null;
95
96 /*** Prefix indicating that the class should be synthesized. */
97 public static final String SYNTH_PREFIX = "test.data.Test";
98 private String 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 cxf = new Vector();
105 /*** Configurable method transformers. */
106 List mxf = new Vector();
107 /*** Configurable graph transformers. */
108 List gxf = new Vector();
109 /*** QuiltRegistry list. */
110 List regList = new Vector();
111
112 /*** Constructor with abbreviated argument list. */
113 public QuiltClassLoader (URL[] cp, String [] 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[] cp, ClassLoader parent,
126 String [] del, String [] inc, String [] exc) {
127 super(cp == null ? new URL[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 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)delegated.get(i)) ) {
165 return true;
166 }
167 }
168 return false;
169 }
170 /*** Should class be instrumented? */
171 private boolean instrumentTheClass (final String 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)excluded.get(i) ) ) {
177 return false;
178 }
179 }
180 for (int i = 0; i < included.size(); i++) {
181 if ( name.startsWith ( (String)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 classFileName (final String 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 loadClass (String name)
201 throws ClassNotFoundException {
202 if (name == null) {
203 throw new IllegalArgumentException("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 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 findClass (String name)
230 throws ClassNotFoundException {
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(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 e) {
286 e.printStackTrace(); // DEBUG
287 throw new ClassNotFoundException(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 urlsToString(){
296 StringBuffer sb = new StringBuffer() .append("classpath:\n");
297 URL[] 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 className)
306 throws IOException {
307 URL 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("null fileURL for " + className);
319 }
320 InputStream ins = fileURL.openStream();
321 ByteArrayOutputStream outs = new ByteArrayOutputStream (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 url) {
336 classPath.add(url);
337 }
338 /*** @return The classpath used by this QuiltClassLoader. */
339 public URL[] getClassPath() {
340 URL[] myURLs = new URL[ classPath.size() ];
341 return (URL[]) (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 THIS_DIR = "." + FILE_PATH_DELIM_STR;
349 public static final String 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 domainToFileName (String 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 sb = new StringBuffer();
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[] cpToURLs (String cp ) {
392 URL[] urls;
393 if (cp == null) {
394 urls = new URL[0];
395 } else {
396 String [] elements = cp.split(":");
397 List urlList = new Vector();
398 int urlCount = 0;
399 for (int i = 0; i < elements.length; i++) {
400 String noDots = domainToFileName(elements[i]);
401 boolean foundJar = noDots.endsWith(".jar");
402 File file = new File (noDots);
403 String urlForm = "file://" + file.getAbsolutePath();
404 if (!foundJar && !urlForm.endsWith(FILE_PATH_DELIM_STR)) {
405 urlForm += FILE_PATH_DELIM_STR;
406 }
407 try {
408 URL candidate = new URL(urlForm);
409 urlCount++; // didn't throw exception
410 urlList.add(candidate);
411 } catch (MalformedURLException e) {
412 System.err.println ("WARNING: ignoring malformed URL "
413 + urlForm);
414 }
415 }
416 urls = new URL[urlCount];
417 for (int k = 0; k < urls.length; k++) {
418 urls[k] = (URL) 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 cp) {
430 classPath.clear();
431 URL[] 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 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[] getDelegated() {
457 String[] myDels = new String[ delegated.size() ];
458 return (String[]) (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 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[] getExcluded() {
475 String[] myExc = new String [ excluded.size() ];
476 return (String[]) (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 s) {
484 excluded.clear();
485 if (s != null) {
486 String [] 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 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[] getIncluded() {
506 String[] myInc = new String [ included.size() ];
507 return (String[]) (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 s) {
515 included.clear();
516 if (s != null) {
517 String [] 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 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 regMap = new Hashtable();
553
554 /*** Get a reference to a Quilt registry. */
555 public QuiltRegistry getRegistry (String 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 regName) {
569 QuiltRegistry qr = null;
570 if (regMap.containsKey(regName)) {
571 qr = (QuiltRegistry) regMap.get(regName);
572 } else try {
573 Class o = Class.forName(regName, false, parent);
574 Constructor con = o.getConstructor( new Class[] {
575 QuiltClassLoader.class });
576 qr = (QuiltRegistry)con.newInstance(new Object[] {this});
577 regList.add(qr);
578 regMap.put (regName, qr);
579 } catch (Exception 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 getReport() {
593 StringBuffer sb = new StringBuffer();
594 if (!regList.isEmpty()) {
595 Iterator 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 }
This page was automatically generated by Maven