View Javadoc
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