001 /* 002 Copyright (C) 2001-2003 Renaud Pawlak, Lionel Seinturier, Fabrice 003 Legond-Aubry. 004 005 This program is free software; you can redistribute it and/or modify 006 it under the terms of the GNU Lesser General Public License as 007 published by the Free Software Foundation; either version 2 of the 008 License, or (at your option) any later version. 009 010 This program is distributed in the hope that it will be useful, 011 but WITHOUT ANY WARRANTY; without even the implied warranty of 012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 GNU Lesser General Public License for more details. 014 015 You should have received a copy of the GNU Lesser General Public 016 License along with this program; if not, write to the Free Software 017 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 018 USA */ 019 020 package org.objectweb.jac.core; 021 022 import java.io.File; 023 import java.io.FileInputStream; 024 import java.io.FileOutputStream; 025 import java.io.IOException; 026 import java.io.InputStream; 027 import java.io.InvalidClassException; 028 import java.io.ObjectInputStream; 029 import java.io.ObjectOutputStream; 030 import java.lang.reflect.Field; 031 import java.lang.reflect.Method; 032 import java.net.URL; 033 import java.net.URLClassLoader; 034 import java.security.MessageDigest; 035 import java.security.NoSuchAlgorithmException; 036 import java.util.Arrays; 037 import java.util.HashSet; 038 import java.util.Hashtable; 039 import java.util.Iterator; 040 import java.util.Properties; 041 import org.apache.log4j.Logger; 042 import org.objectweb.jac.core.rtti.ClassInfo; 043 import org.objectweb.jac.core.rtti.LoadtimeRTTI; 044 import org.objectweb.jac.util.ExtArrays; 045 import org.objectweb.jac.util.Streams; 046 import org.objectweb.jac.util.Strings; 047 048 /** 049 * The JAC specific class loader for JAC objects. 050 * 051 * <p>This loader does the following things :</p> 052 * <ul> 053 * <li>load a set of Properties from all the found "jac.prop" files 054 * <li>get from them the choosen wrappeeTranslator 055 * <li>instanciate the choosen wrappeTranslator 056 * <li>replace the system class loader. 057 * <li>then for each loaded class it can delegates the translation of 058 * JAC objects to the choosen <code>WrappeeTranslator</code>. 059 * </ul> 060 * @see org.objectweb.jac.core.WrappeeTranslator */ 061 public class JacLoader extends ClassLoader { 062 static Logger logger = Logger.getLogger("loader"); 063 static Logger loggerRes = Logger.getLogger("loader.resource"); 064 065 /** 066 * Stores the bytecode of the loaded and translated classes. 067 * 068 * @see isLoaded(String) 069 * @see getLoadedBytecode(String) */ 070 private Hashtable classes = new Hashtable(); 071 072 /** If true, caches the tranlated classes on disk. */ 073 private boolean write = false; 074 075 /** If true, clears the disk cache. */ 076 private boolean clean = false; 077 078 private ClassLoader parentLoader = ClassLoader.getSystemClassLoader(); 079 080 private WrappeeTranslator wt = null; 081 private String bytecodeModifier; 082 083 /* Packages whose loading we delegate */ 084 private String[] ignoredPackages = { 085 "java.", "javax.", "sun.", "org.xml.sax.", "org.w3c.dom.", "org.apache.log4j" 086 }; 087 088 /* Classes whose loading we delegate. Those classes are thus shared 089 by all Jac loaders, and untranslated */ 090 private HashSet ignoredClasses = new HashSet(); 091 092 093 private static ClassLoader otherClassLoader; 094 095 LoadtimeRTTI rtti; 096 097 /** 098 * Create a JacLoader. 099 * 100 * @param write if true, caches the tranlated classes on disk 101 * @param clean if true, clears the disk cache 102 * @param otherClassLoader if not null, indicates another 103 * ClassLoader where to search for classes not in original 104 * ClassPath 105 */ 106 public JacLoader(boolean write, 107 boolean clean, 108 ClassLoader otherClassLoader) 109 throws Exception 110 { 111 logger.info("building JacLoader..."); 112 //super (ClassLoader.getSystemClassLoader()); 113 this.write = write; 114 this.clean = clean; 115 if (otherClassLoader != null) 116 JacLoader.otherClassLoader = otherClassLoader; 117 118 ignoredClasses.add("org.objectweb.jac.util.Log"); 119 ignoredClasses.add("org.objectweb.jac.core.Jac"); 120 ignoredClasses.add("org.objectweb.jac.core.rtti.LoadtimeRTTI"); 121 ignoredClasses.add("org.objectweb.jac.core.rtti.ClassInfo"); 122 ignoredClasses.add("org.objectweb.jac.core.rtti.MethodInfo"); 123 ignoredClasses.add("org.objectweb.jac.core.rtti.InvokeInfo"); 124 // load the properties 125 JacPropLoader.loadProps(); 126 logger.info("JacPropLoader = "+ 127 Strings.hex(JacPropLoader.class)); 128 129 // get the bytecodeModifier string 130 bytecodeModifier = org.objectweb.jac.core.JacPropLoader.bytecodeModifier; 131 logger.info("jacloader: Selected bytecode modifier = '" + 132 bytecodeModifier +"'"); 133 134 // instanciate the bytecode translator 135 Class c = Class.forName("org.objectweb.jac.core.translators.WrappeeTranslator_"+ 136 bytecodeModifier); 137 Class repositoryClass = loadClass("org.objectweb.jac.core.rtti.ClassRepository",true); 138 rtti = (LoadtimeRTTI) 139 repositoryClass.getMethod("get",ExtArrays.emptyClassArray).invoke(null,ExtArrays.emptyObjectArray); 140 wt = (WrappeeTranslator)c.getConstructor( 141 new Class[] {LoadtimeRTTI.class}).newInstance( 142 new Object[] {rtti}); 143 144 logger.info("Instanciated bytecode modifier is "+wt); 145 } 146 147 148 /** 149 * Create a JacLoader. Same as 150 * <code>JacLoader(boolean,boolean,null)</code>. 151 * 152 * @param write if true, caches the tranlated classes on disk 153 * @param clean if true, clears the disk cache 154 */ 155 public JacLoader(boolean write, 156 boolean clean) 157 throws Exception 158 { 159 this(write, clean, null); 160 } 161 162 163 public void setWrappeeTranslator(WrappeeTranslator wt) { 164 this.wt = wt; 165 } 166 167 private void addIgnoredPkgs(String[] ignoredPackages) { 168 String[] new_p = new String[ignoredPackages.length + 169 this.ignoredPackages.length]; 170 171 System.arraycopy(this.ignoredPackages, 0, new_p, 0, 172 this.ignoredPackages.length); 173 System.arraycopy(ignoredPackages, 0, new_p, this.ignoredPackages.length, 174 ignoredPackages.length); 175 this.ignoredPackages = new_p; 176 } 177 178 /** 179 * Tells wether to defer loading of a class to the parent ClassLoader 180 * @param classname name of the class 181 * @return true if we should defer the loading of that class 182 */ 183 public boolean deferClass(String classname) { 184 if (ignoredClasses.contains(classname)) 185 return true; 186 for(int i=0; i<ignoredPackages.length; i++) { 187 if (classname.startsWith(ignoredPackages[i])) { 188 return true; 189 } 190 } 191 return false; 192 } 193 194 /** 195 * Tells wether a defered class's bytecode should be analyzed 196 * @param classname the name of the class 197 */ 198 protected boolean analyzeClass(String classname) { 199 return false; 200 } 201 202 protected Class loadClass(String class_name, boolean resolve) 203 throws ClassNotFoundException 204 { 205 logger.debug("loadClass("+class_name+","+resolve+")"); 206 Class cl = null; 207 208 cl = findLoadedClass(class_name); 209 if (cl!=null) { 210 logger.debug("already loaded "+class_name); 211 return cl; 212 } 213 cl = (Class)classes.get(class_name); 214 if (cl==null) { 215 if (deferClass(class_name) && !JacPropLoader.adaptClass(class_name)) { 216 logger.debug("defer "+class_name); 217 byte[] bytes = null; 218 if (analyzeClass(class_name)) { 219 try { 220 logger.debug("fill RTTI for "+class_name); 221 bytes = wt.fillClassRTTI(class_name); 222 } catch (Exception e) { 223 logger.error("Failed to fill RTTI for "+class_name,e); 224 // TODO: we should throw another exception 225 throw new ClassNotFoundException( 226 "jacloader: cannot find class "+ 227 class_name+" on disk: "+e); 228 } 229 } 230 cl = parentLoader.loadClass(class_name); 231 } 232 233 if (cl == null) { 234 byte[] bytes; 235 String resourcePath = "/" + class_name.replace('.', '/') + ".class"; 236 logger.debug("resourcePath = "+resourcePath); 237 bytes = loadResource(resourcePath); 238 239 if (wt!=null && classIsToBeAdapted(class_name)) { 240 logger.info("adapting "+class_name); 241 String baseName = 242 Jac.getJacRoot()+"classes_"+bytecodeModifier+"/"+ 243 class_name.replace('.','/'); 244 File cacheFile = new File(baseName+".class"); 245 MessageDigest digest; 246 boolean useCache = false; 247 File md5File = new File(baseName+".md5"); 248 File rttiFile = new File(baseName+".rtti"); 249 byte[] orig_md5 = null; 250 try { 251 digest = MessageDigest.getInstance("MD5"); 252 digest.update(bytes); 253 orig_md5 = digest.digest(); 254 if (md5File.exists()) { 255 byte[] stored_md5 = loadFile(md5File); 256 useCache = Arrays.equals(orig_md5,stored_md5); 257 } 258 } catch (NoSuchAlgorithmException e) { 259 logger.warn("Could not get an MD5 digest: "+e); 260 } 261 boolean loaded = false; 262 if (useCache && cacheFile.exists() && rttiFile.exists()) { 263 logger.info("loading class from cache "+cacheFile); 264 bytes = loadFile(cacheFile); 265 try { 266 FileInputStream rttiStream = new FileInputStream(rttiFile); 267 ObjectInputStream objStream = new ObjectInputStream(rttiStream); 268 ClassInfo classInfo = (ClassInfo)objStream.readObject(); 269 rttiStream.close(); 270 objStream.close(); 271 rtti.setClassInfo(class_name,classInfo); 272 loaded = true; 273 } catch (InvalidClassException e) { 274 // ignored 275 logger.info("Rtti format must have changed: "+rttiFile); 276 } catch (Exception e) { 277 logger.error("Failed to read rtti cache file "+rttiFile+": "+e); 278 } 279 } 280 if (!loaded) { 281 try { 282 //bytes = wt.translateClass(defineClass(class_name, bytes, 0, bytes.length)); 283 bytes = wt.translateClass(class_name); 284 } catch (Exception e) { 285 logger.error("Failed to translate class "+class_name,e); 286 throw new ClassNotFoundException( 287 "jacloader: cannot find class "+ 288 class_name+" on disk: "+e); 289 } 290 if (bytes!=null && write) { 291 logger.info("writing "+cacheFile); 292 try { 293 cacheFile.getParentFile().mkdirs(); 294 FileOutputStream out = new FileOutputStream(cacheFile); 295 try { 296 out.write(bytes); 297 } finally { 298 out.close(); 299 } 300 } catch (Exception e) { 301 logger.warn("Failed to write class file "+cacheFile+": "+e); 302 } 303 if (orig_md5!=null) { 304 logger.info("writing "+md5File); 305 try { 306 md5File.getParentFile().mkdirs(); 307 FileOutputStream out = new FileOutputStream(md5File); 308 try { 309 out.write(orig_md5); 310 } finally { 311 out.close(); 312 } 313 } catch (Exception e) { 314 logger.warn("Failed to write class file "+md5File+": "+e); 315 } 316 } 317 try { 318 FileOutputStream rttiStream = new FileOutputStream(rttiFile); 319 ObjectOutputStream objStream = new ObjectOutputStream(rttiStream); 320 objStream.writeObject(rtti.getClassInfo(class_name)); 321 objStream.flush(); 322 rttiStream.close(); 323 objStream.close(); 324 } catch (Exception e) { 325 logger.error("Failed to write rtti cache file "+rttiFile+": "+e); 326 } 327 } 328 } 329 } 330 logger.debug("defineClass "+class_name); 331 if (bytes==null) { 332 logger.debug("defer "+class_name); 333 try { 334 if (otherClassLoader!=null) 335 cl = otherClassLoader.loadClass(class_name); 336 else 337 cl = parentLoader.loadClass(class_name); 338 } catch(Exception e) { 339 logger.error("Failed to load class "+class_name,e); 340 } 341 } else { 342 try { 343 cl = defineClass(class_name, bytes, 0, bytes.length); 344 } catch(Exception e1) { 345 logger.error("Failed to define class "+class_name,e1); 346 // we are not authorized to load this class, we defer 347 logger.debug("defer "+class_name); 348 bytes = null; 349 if (analyzeClass(class_name)) 350 try { 351 logger.debug("fill RTTI for "+class_name); 352 bytes = wt.fillClassRTTI(class_name); 353 } catch (Exception e) { 354 logger.error("Failed to fill RTTI for "+class_name,e); 355 throw new ClassNotFoundException( 356 "jacloader: cannot find class "+ 357 class_name+" on disk: "+e); 358 } 359 cl = parentLoader.loadClass(class_name); 360 } 361 //cl = defineClass(class_name, bytes, 0, bytes.length); 362 } 363 } 364 365 if (resolve) 366 resolveClass(cl); 367 } 368 369 if (cl.getClassLoader()!=null) 370 logger.info(class_name+"@"+Integer.toHexString(cl.hashCode())+ 371 " ["+cl.getClassLoader()+"]"); 372 373 logger.debug("----------"); 374 classes.put(class_name, cl); 375 376 return cl; 377 } 378 379 public InputStream getResourceAsStream(String name) 380 { 381 loggerRes.debug("getResourceAsStream: "+name); 382 383 if ((name == null) || (name.length() == 0)) 384 return null; 385 386 InputStream result = null; 387 388 result = super.getResourceAsStream(name); 389 if ((result == null) && (otherClassLoader != null)) { 390 loggerRes.debug(" resource not found with system classloader, trying with: "+otherClassLoader); 391 result = otherClassLoader.getResourceAsStream(name); 392 } 393 return result; 394 } 395 396 public URL getResource(String name) 397 { 398 loggerRes.debug("getResource: "+name+" (parent="+getParent()+")"); 399 400 URL result = null; 401 402 //result = super.getResource(name); 403 if (getParent() != null) { 404 result = getParent().getResource(name); 405 } 406 if (result == null) { 407 result = findResource(name); 408 } 409 410 // if not in usual CLASSPATH, search in additionnal CLASSPATH 411 if ((result == null) && (otherClassLoader != null)) { 412 loggerRes.debug(" resource not found with "+getParent()+", trying with: "+otherClassLoader); 413 if (otherClassLoader instanceof URLClassLoader) 414 loggerRes.debug(" classpath="+Arrays.asList(((URLClassLoader)otherClassLoader).getURLs())); 415 result = otherClassLoader.getResource(name); 416 } 417 418 return result; 419 } 420 421 byte[] loadResource(String resourcePath) throws ClassNotFoundException { 422 InputStream in = null; 423 424 logger.debug( "loadResource: " + resourcePath); 425 426 in = super.getClass().getResourceAsStream(resourcePath); 427 428 // if not in usual CLASSPATH, search in additionnal CLASSPATH 429 if ((in == null) && (otherClassLoader != null)) 430 { 431 logger.debug( 432 "loadResource: searching in otherClassLoader ..."); 433 in = otherClassLoader.getResourceAsStream(resourcePath.substring(1)); 434 } 435 436 if (in == null) 437 { 438 throw new ClassNotFoundException("jacloader: Can not find resource "+resourcePath); 439 } 440 try { 441 return Streams.readStream(in); 442 } catch (IOException e) { 443 throw new ClassNotFoundException( 444 "jacloader: failed to load resource "+resourcePath+" : "+e); 445 } 446 447 } 448 449 byte[] loadFile(File file) throws ClassNotFoundException { 450 try { 451 InputStream in = new FileInputStream(file); 452 if (in == null) 453 throw new ClassNotFoundException("jacloader: Can not find resource "+file); 454 return Streams.readStream(in); 455 } catch (IOException e) { 456 throw new ClassNotFoundException( 457 "jacloader: failed to load resource "+file+" : "+e); 458 } 459 460 } 461 462 /** 463 * Tell wether a class is to be adapted or not. All data 464 * are extracted from the jac.prop files located a various 465 * place. 466 * 467 * 468 * @see org.objectweb.jac.core.JacPropLoader 469 * @see org.objectweb.jac.core.JacPropTools 470 * @param name a <code>String</code> value 471 * @return a <code>boolean</code> value (true if to be adpated). 472 * 473 */ 474 public static boolean classIsToBeAdapted(String name) { 475 // System.out.println("classIsToBeAdapted "+name+"? "+Strings.hex(JacPropLoader.class)); 476 int index = name.lastIndexOf("."); 477 String packagename = ""; 478 if (index!=-1) 479 packagename = name.substring(0,index); 480 Iterator it; 481 if (JacPropLoader.adaptClass(name)) 482 return true; 483 484 // do not translate classes that follow 485 if ( name.endsWith(".Run") || 486 name.endsWith(".Main") || 487 name.endsWith("AC") || 488 name.endsWith("Wrapper") || 489 (name.indexOf('$') != -1) || 490 name.endsWith("Exception") || 491 name.startsWith("org.objectweb.jac.core") || 492 name.startsWith("org.objectweb.jac.util") || 493 name.startsWith("org.objectweb.jac.aspects")) 494 return false; 495 496 if (JacPropLoader.doNotAdaptClass(name)) 497 return false; 498 499 return true; 500 } 501 502 /** 503 * display some information about a class<br> 504 * Only use for debug purpose to verify that the class 505 * has been patched correctly. 506 * 507 * @see java.lang.ClassLoader 508 * @param name the class to display 509 * 510 */ 511 public static void displayClassInfo(String name) 512 { 513 System.out.println ("jacloader: Displaying data on class " + name); 514 Class cl = null; 515 try { 516 cl = Class.forName (name); 517 } 518 catch (Exception e) 519 { 520 System.out.println ("\tClass is not available from the loader."); 521 return; 522 } 523 Field[] fl = cl.getDeclaredFields(); 524 for (int i=0; i<fl.length; i++) 525 { 526 System.out.println ("\tField "+i+" : name("+ 527 fl[i].getName()+"), type("+ 528 fl[i].getType().getName() 529 +"), attr("+fl[i].getModifiers()+")"); 530 } 531 Method[] fm = cl.getDeclaredMethods(); 532 for (int i=0; i<fm.length; i++) 533 { 534 System.out.println ("\tMethod "+i+" ("+ 535 fm[i].getName()+"), return type is "+ 536 fm[i].getReturnType().getName()); 537 Class[] clexcep = fm[i].getExceptionTypes(); 538 for (int j=0; j<clexcep.length;j++) 539 System.out.println ("\t\tThrow Exception : "+clexcep[j].getName()); 540 Class[] clpar = fm[i].getParameterTypes(); 541 for (int j=0; j<clpar.length;j++) 542 System.out.println ("\t\tParameter number "+j+" is a "+clpar[j].getName()); 543 } 544 } 545 546 547 /** 548 * Returns true if the class is already loaded. 549 * 550 * @param classname the class name to test 551 * @return true if already loaded 552 */ 553 public boolean isLoaded (String classname) { 554 return (classes.containsKey(classname)); 555 } 556 557 /** 558 * Loads a class and calls <code>main()</code> in that class. 559 * 560 * <p>This method was extracted "as is" from javassist 1.0 from 561 * Shigeru Chiba. 562 * 563 * <p><a href="www.csg.is.titech.ac.jp/~chiba/javassist/">Javassist 564 * Homepage</a> 565 * 566 * @param classname the loaded class. 567 * @param args parameters passed to <code>main()</code>. */ 568 569 public void run(String classname, String[] args) throws Throwable { 570 Class c = this.loadClass(classname); 571 ClassLoader saved = Thread.currentThread().getContextClassLoader(); 572 try { 573 Thread.currentThread().setContextClassLoader(this); 574 c.getDeclaredMethod("main", new Class[] { String[].class }) 575 .invoke(null, new Object[] { args }); 576 } 577 catch (java.lang.reflect.InvocationTargetException e) { 578 throw e.getTargetException(); 579 } 580 finally { 581 Thread.currentThread().setContextClassLoader(saved); 582 } 583 } 584 585 /** 586 * Use JacPropLoader to read some JAC properties 587 * @param props the properties to read 588 */ 589 public void readProperties(Properties props) { 590 JacPropLoader.addProps(props); 591 } 592 593 /** 594 * Usage: java org.objectweb.jac.core.JacLoader [class] 595 */ 596 public static void main(String[] args) throws Exception { 597 JacLoader loader = new JacLoader(true,true, otherClassLoader); 598 for (int i=0; i<args.length; i++) { 599 JacPropLoader.packagesToAdapt.add(args[i]); 600 loader.loadClass(args[i]); 601 } 602 } 603 }