001 /* 002 * $Id: GroovyClassLoader.java 4445 2006-12-17 22:35:15Z blackdrag $ 003 * 004 * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved. 005 * 006 * Redistribution and use of this software and associated documentation 007 * ("Software"), with or without modification, are permitted provided that the 008 * following conditions are met: 009 * 1. Redistributions of source code must retain copyright statements and 010 * notices. Redistributions must also contain a copy of this document. 011 * 2. Redistributions in binary form must reproduce the above copyright 012 * notice, this list of conditions and the following disclaimer in the 013 * documentation and/or other materials provided with the distribution. 014 * 3. The name "groovy" must not be used to endorse or promote products 015 * derived from this Software without prior written permission of The Codehaus. 016 * For written permission, please contact info@codehaus.org. 017 * 4. Products derived from this Software may not be called "groovy" nor may 018 * "groovy" appear in their names without prior written permission of The 019 * Codehaus. "groovy" is a registered trademark of The Codehaus. 020 * 5. Due credit should be given to The Codehaus - http://groovy.codehaus.org/ 021 * 022 * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY 023 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 024 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 025 * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR 026 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 027 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 028 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 029 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 030 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 031 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 032 * DAMAGE. 033 * 034 */ 035 036 /** 037 * @TODO: multi threaded compiling of the same class but with different roots 038 * for compilation... T1 compiles A, which uses B, T2 compiles B... mark A and B 039 * as parsed and then synchronize compilation. Problems: How to synchronize? 040 * How to get error messages? 041 * 042 */ 043 package groovy.lang; 044 045 import java.io.ByteArrayInputStream; 046 import java.io.File; 047 import java.io.IOException; 048 import java.io.InputStream; 049 import java.lang.reflect.Field; 050 import java.net.MalformedURLException; 051 import java.net.URL; 052 import java.net.URLClassLoader; 053 import java.security.AccessController; 054 import java.security.CodeSource; 055 import java.security.PrivilegedAction; 056 import java.security.ProtectionDomain; 057 import java.util.ArrayList; 058 import java.util.Collection; 059 import java.util.Enumeration; 060 import java.util.HashMap; 061 import java.util.Iterator; 062 import java.util.List; 063 import java.util.Map; 064 065 import org.codehaus.groovy.ast.ClassNode; 066 import org.codehaus.groovy.ast.ModuleNode; 067 import org.codehaus.groovy.classgen.Verifier; 068 import org.codehaus.groovy.control.CompilationFailedException; 069 import org.codehaus.groovy.control.CompilationUnit; 070 import org.codehaus.groovy.control.CompilerConfiguration; 071 import org.codehaus.groovy.control.Phases; 072 import org.codehaus.groovy.control.SourceUnit; 073 import org.objectweb.asm.ClassVisitor; 074 import org.objectweb.asm.ClassWriter; 075 076 /** 077 * A ClassLoader which can load Groovy classes. The loaded classes are cached, 078 * classes from other classlaoders should not be cached. To be able to load a 079 * script that was asked for earlier but was created later it is essential not 080 * to keep anything like a "class not found" information for that class name. 081 * This includes possible parent loaders. Classes that are not chached are always 082 * reloaded. 083 * 084 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a> 085 * @author Guillaume Laforge 086 * @author Steve Goetze 087 * @author Bing Ran 088 * @author <a href="mailto:scottstirling@rcn.com">Scott Stirling</a> 089 * @author <a href="mailto:blackdrag@gmx.org">Jochen Theodorou</a> 090 * @version $Revision: 4445 $ 091 */ 092 public class GroovyClassLoader extends URLClassLoader { 093 094 /** 095 * this cache contains the loaded classes or PARSING, if the class is currently parsed 096 */ 097 protected Map classCache = new HashMap(); 098 protected Map sourceCache = new HashMap(); 099 private CompilerConfiguration config; 100 private Boolean recompile = null; 101 // use 1000000 as offset to avoid conflicts with names form the GroovyShell 102 private static int scriptNameCounter = 1000000; 103 104 private GroovyResourceLoader resourceLoader = new GroovyResourceLoader() { 105 public URL loadGroovySource(final String filename) throws MalformedURLException { 106 URL file = (URL) AccessController.doPrivileged(new PrivilegedAction() { 107 public Object run() { 108 return getSourceFile(filename); 109 } 110 }); 111 return file; 112 } 113 }; 114 115 /** 116 * creates a GroovyClassLoader using the current Thread's context 117 * Class loader as parent. 118 */ 119 public GroovyClassLoader() { 120 this(Thread.currentThread().getContextClassLoader()); 121 } 122 123 /** 124 * creates a GroovyClassLoader using the given ClassLoader as parent 125 */ 126 public GroovyClassLoader(ClassLoader loader) { 127 this(loader, null); 128 } 129 130 /** 131 * creates a GroovyClassLoader using the given GroovyClassLoader as parent. 132 * This loader will get the parent's CompilerConfiguration 133 */ 134 public GroovyClassLoader(GroovyClassLoader parent) { 135 this(parent, parent.config, false); 136 } 137 138 /** 139 * creates a GroovyClassLaoder. 140 * @param parent the parten class loader 141 * @param config the compiler configuration 142 * @param useConfigurationClasspath determines if the configurations classpath should be added 143 */ 144 public GroovyClassLoader(ClassLoader parent, CompilerConfiguration config, boolean useConfigurationClasspath) { 145 super(new URL[0],parent); 146 if (config==null) config = CompilerConfiguration.DEFAULT; 147 this.config = config; 148 if (useConfigurationClasspath) { 149 for (Iterator it=config.getClasspath().iterator(); it.hasNext();) { 150 String path = (String) it.next(); 151 this.addClasspath(path); 152 } 153 } 154 } 155 156 /** 157 * creates a GroovyClassLoader using the given ClassLoader as parent. 158 */ 159 public GroovyClassLoader(ClassLoader loader, CompilerConfiguration config) { 160 this(loader,config,true); 161 } 162 163 public void setResourceLoader(GroovyResourceLoader resourceLoader) { 164 if (resourceLoader == null) { 165 throw new IllegalArgumentException("Resource loader must not be null!"); 166 } 167 this.resourceLoader = resourceLoader; 168 } 169 170 public GroovyResourceLoader getResourceLoader() { 171 return resourceLoader; 172 } 173 174 /** 175 * Loads the given class node returning the implementation Class 176 * 177 * @param classNode 178 * @return a class 179 */ 180 public Class defineClass(ClassNode classNode, String file) { 181 //return defineClass(classNode, file, "/groovy/defineClass"); 182 throw new DeprecationException("the method GroovyClassLoader#defineClass(ClassNode, String) is no longer used and removed"); 183 } 184 185 /** 186 * Loads the given class node returning the implementation Class. 187 * 188 * WARNING: this compilation is not synchronized 189 * 190 * @param classNode 191 * @return a class 192 */ 193 public Class defineClass(ClassNode classNode, String file, String newCodeBase) { 194 CodeSource codeSource = null; 195 try { 196 codeSource = new CodeSource(new URL("file", "", newCodeBase), (java.security.cert.Certificate[]) null); 197 } catch (MalformedURLException e) { 198 //swallow 199 } 200 201 CompilationUnit unit = createCompilationUnit(config,codeSource); 202 ClassCollector collector = createCollector(unit,classNode.getModule().getContext()); 203 try { 204 unit.addClassNode(classNode); 205 unit.setClassgenCallback(collector); 206 unit.compile(Phases.CLASS_GENERATION); 207 208 return collector.generatedClass; 209 } catch (CompilationFailedException e) { 210 throw new RuntimeException(e); 211 } 212 } 213 214 /** 215 * Parses the given file into a Java class capable of being run 216 * 217 * @param file the file name to parse 218 * @return the main class defined in the given script 219 */ 220 public Class parseClass(File file) throws CompilationFailedException, IOException { 221 return parseClass(new GroovyCodeSource(file)); 222 } 223 224 /** 225 * Parses the given text into a Java class capable of being run 226 * 227 * @param text the text of the script/class to parse 228 * @param fileName the file name to use as the name of the class 229 * @return the main class defined in the given script 230 */ 231 public Class parseClass(String text, String fileName) throws CompilationFailedException { 232 return parseClass(new ByteArrayInputStream(text.getBytes()), fileName); 233 } 234 235 /** 236 * Parses the given text into a Java class capable of being run 237 * 238 * @param text the text of the script/class to parse 239 * @return the main class defined in the given script 240 */ 241 public Class parseClass(String text) throws CompilationFailedException { 242 return parseClass(new ByteArrayInputStream(text.getBytes()), "script" + System.currentTimeMillis() + ".groovy"); 243 } 244 245 /** 246 * Parses the given character stream into a Java class capable of being run 247 * 248 * @param in an InputStream 249 * @return the main class defined in the given script 250 */ 251 public Class parseClass(InputStream in) throws CompilationFailedException { 252 return parseClass(in, generateScriptName()); 253 } 254 255 public synchronized String generateScriptName() { 256 scriptNameCounter++; 257 return "script"+scriptNameCounter+".groovy"; 258 } 259 260 public Class parseClass(final InputStream in, final String fileName) throws CompilationFailedException { 261 // For generic input streams, provide a catch-all codebase of 262 // GroovyScript 263 // Security for these classes can be administered via policy grants with 264 // a codebase of file:groovy.script 265 GroovyCodeSource gcs = (GroovyCodeSource) AccessController.doPrivileged(new PrivilegedAction() { 266 public Object run() { 267 return new GroovyCodeSource(in, fileName, "/groovy/script"); 268 } 269 }); 270 return parseClass(gcs); 271 } 272 273 274 public Class parseClass(GroovyCodeSource codeSource) throws CompilationFailedException { 275 return parseClass(codeSource, codeSource.isCachable()); 276 } 277 278 /** 279 * Parses the given code source into a Java class. If there is a class file 280 * for the given code source, then no parsing is done, instead the cached class is returned. 281 * 282 * @param shouldCacheSource if true then the generated class will be stored in the source cache 283 * 284 * @return the main class defined in the given script 285 */ 286 public Class parseClass(GroovyCodeSource codeSource, boolean shouldCacheSource) throws CompilationFailedException { 287 synchronized (classCache) { 288 Class answer = (Class) sourceCache.get(codeSource.getName()); 289 if (answer!=null) return answer; 290 291 // Was neither already loaded nor compiling, so compile and add to 292 // cache. 293 try { 294 CompilationUnit unit = createCompilationUnit(config, codeSource.getCodeSource()); 295 SourceUnit su = null; 296 if (codeSource.getFile()==null) { 297 su = unit.addSource(codeSource.getName(), codeSource.getInputStream()); 298 } else { 299 su = unit.addSource(codeSource.getFile()); 300 } 301 302 ClassCollector collector = createCollector(unit,su); 303 unit.setClassgenCallback(collector); 304 int goalPhase = Phases.CLASS_GENERATION; 305 if (config != null && config.getTargetDirectory()!=null) goalPhase = Phases.OUTPUT; 306 unit.compile(goalPhase); 307 308 answer = collector.generatedClass; 309 for (Iterator iter = collector.getLoadedClasses().iterator(); iter.hasNext();) { 310 Class clazz = (Class) iter.next(); 311 setClassCacheEntry(clazz); 312 } 313 if (shouldCacheSource) sourceCache.put(codeSource.getName(), answer); 314 } finally { 315 try { 316 InputStream is = codeSource.getInputStream(); 317 if (is!=null) is.close(); 318 } catch (IOException e) { 319 throw new GroovyRuntimeException("unable to close stream",e); 320 } 321 } 322 return answer; 323 } 324 } 325 326 /** 327 * gets the currently used classpath. 328 * @return a String[] containing the file information of the urls 329 * @see #getURLs() 330 */ 331 protected String[] getClassPath() { 332 //workaround for Groovy-835 333 URL[] urls = getURLs(); 334 String[] ret = new String[urls.length]; 335 for (int i = 0; i < ret.length; i++) { 336 ret[i] = urls[i].getFile(); 337 } 338 return ret; 339 } 340 341 /** 342 * expands the classpath 343 * @param pathList an empty list that will contain the elements of the classpath 344 * @param classpath the classpath specified as a single string 345 * @deprecated 346 */ 347 protected void expandClassPath(List pathList, String base, String classpath, boolean isManifestClasspath) { 348 throw new DeprecationException("the method groovy.lang.GroovyClassLoader#expandClassPath(List,String,String,boolean) is no longer used internally and removed"); 349 } 350 351 /** 352 * A helper method to allow bytecode to be loaded. spg changed name to 353 * defineClass to make it more consistent with other ClassLoader methods 354 * @deprecated 355 */ 356 protected Class defineClass(String name, byte[] bytecode, ProtectionDomain domain) { 357 throw new DeprecationException("the method groovy.lang.GroovyClassLoader#defineClass(String,byte[],ProtectionDomain) is no longer used internally and removed"); 358 } 359 360 public static class InnerLoader extends GroovyClassLoader{ 361 private GroovyClassLoader delegate; 362 public InnerLoader(GroovyClassLoader delegate) { 363 super(delegate); 364 this.delegate = delegate; 365 } 366 public void addClasspath(String path) { 367 delegate.addClasspath(path); 368 } 369 public void clearCache() { 370 delegate.clearCache(); 371 } 372 public URL findResource(String name) { 373 return delegate.findResource(name); 374 } 375 public Enumeration findResources(String name) throws IOException { 376 return delegate.findResources(name); 377 } 378 public Class[] getLoadedClasses() { 379 return delegate.getLoadedClasses(); 380 } 381 public URL getResource(String name) { 382 return delegate.getResource(name); 383 } 384 public InputStream getResourceAsStream(String name) { 385 return delegate.getResourceAsStream(name); 386 } 387 public GroovyResourceLoader getResourceLoader() { 388 return delegate.getResourceLoader(); 389 } 390 public URL[] getURLs() { 391 return delegate.getURLs(); 392 } 393 public Class loadClass(String name, boolean lookupScriptFiles, boolean preferClassOverScript, boolean resolve) throws ClassNotFoundException, CompilationFailedException { 394 Class c = findLoadedClass(name); 395 if (c!=null) return c; 396 return delegate.loadClass(name, lookupScriptFiles, preferClassOverScript, resolve); 397 } 398 public Class parseClass(GroovyCodeSource codeSource, boolean shouldCache) throws CompilationFailedException { 399 return delegate.parseClass(codeSource, shouldCache); 400 } 401 public void setResourceLoader(GroovyResourceLoader resourceLoader) { 402 delegate.setResourceLoader(resourceLoader); 403 } 404 public void addURL(URL url) { 405 delegate.addURL(url); 406 } 407 } 408 409 /** 410 * creates a new CompilationUnit. If you want to add additional 411 * phase operations to the CompilationUnit (for example to inject 412 * additional methods, variables, fields), then you should overwrite 413 * this method. 414 * 415 * @param config the compiler configuration, usually the same as for this class loader 416 * @param source the source containing the initial file to compile, more files may follow during compilation 417 * 418 * @return the CompilationUnit 419 */ 420 protected CompilationUnit createCompilationUnit(CompilerConfiguration config, CodeSource source) { 421 return new CompilationUnit(config, source, this); 422 } 423 424 /** 425 * creates a ClassCollector for a new compilation. 426 * @param unit the compilationUnit 427 * @param su the SoruceUnit 428 * @return the ClassCollector 429 */ 430 protected ClassCollector createCollector(CompilationUnit unit,SourceUnit su) { 431 InnerLoader loader = (InnerLoader) AccessController.doPrivileged(new PrivilegedAction() { 432 public Object run() { 433 return new InnerLoader(GroovyClassLoader.this); 434 } 435 }); 436 return new ClassCollector(loader, unit, su); 437 } 438 439 public static class ClassCollector extends CompilationUnit.ClassgenCallback { 440 private Class generatedClass; 441 private GroovyClassLoader cl; 442 private SourceUnit su; 443 private CompilationUnit unit; 444 private Collection loadedClasses = null; 445 446 protected ClassCollector(InnerLoader cl, CompilationUnit unit, SourceUnit su) { 447 this.cl = cl; 448 this.unit = unit; 449 this.loadedClasses = new ArrayList(); 450 this.su = su; 451 } 452 protected GroovyClassLoader getDefiningClassLoader(){ 453 return cl; 454 } 455 protected Class createClass(byte[] code, ClassNode classNode) { 456 GroovyClassLoader cl = getDefiningClassLoader(); 457 Class theClass = cl.defineClass(classNode.getName(), code, 0, code.length, unit.getAST().getCodeSource()); 458 cl.resolveClass(theClass); 459 this.loadedClasses.add(theClass); 460 461 if (generatedClass == null) { 462 ModuleNode mn = classNode.getModule(); 463 SourceUnit msu = null; 464 if (mn!=null) msu = mn.getContext(); 465 ClassNode main = null; 466 if (mn!=null) main = (ClassNode) mn.getClasses().get(0); 467 if (msu==su && main==classNode) generatedClass = theClass; 468 } 469 470 return theClass; 471 } 472 473 protected Class onClassNode(ClassWriter classWriter, ClassNode classNode) { 474 byte[] code = classWriter.toByteArray(); 475 return createClass(code,classNode); 476 } 477 478 public void call(ClassVisitor classWriter, ClassNode classNode) { 479 onClassNode((ClassWriter) classWriter, classNode); 480 } 481 482 public Collection getLoadedClasses() { 483 return this.loadedClasses; 484 } 485 } 486 487 /** 488 * open up the super class define that takes raw bytes 489 * 490 */ 491 public Class defineClass(String name, byte[] b) { 492 return super.defineClass(name, b, 0, b.length); 493 } 494 495 /** 496 * loads a class from a file or a parent classloader. 497 * This method does call loadClass(String, boolean, boolean, boolean) 498 * with the last parameter set to false. 499 * @throws CompilationFailedException 500 */ 501 public Class loadClass(final String name, boolean lookupScriptFiles, boolean preferClassOverScript) 502 throws ClassNotFoundException, CompilationFailedException 503 { 504 return loadClass(name,lookupScriptFiles,preferClassOverScript,false); 505 } 506 507 /** 508 * gets a class from the class cache. This cache contains only classes loaded through 509 * this class loader or an InnerLoader instance. If no class is stored for a 510 * specific name, then the method should return null. 511 * 512 * @param name of the class 513 * @return the class stored for the given name 514 * @see #removeClassCacheEntry(String) 515 * @see #setClassCacheEntry(Class) 516 * @see #clearCache() 517 */ 518 protected Class getClassCacheEntry(String name) { 519 if (name==null) return null; 520 synchronized (classCache) { 521 Class cls = (Class) classCache.get(name); 522 return cls; 523 } 524 } 525 526 /** 527 * sets an entry in the class cache. 528 * @param cls the class 529 * @see #removeClassCacheEntry(String) 530 * @see #getClassCacheEntry(String) 531 * @see #clearCache() 532 */ 533 protected void setClassCacheEntry(Class cls) { 534 synchronized (classCache) { 535 classCache.put(cls.getName(),cls); 536 } 537 } 538 539 /** 540 * removes a class from the class cache. 541 * @param name of the class 542 * @see #getClassCacheEntry(String) 543 * @see #setClassCacheEntry(Class) 544 * @see #clearCache() 545 */ 546 protected void removeClassCacheEntry(String name) { 547 synchronized (classCache) { 548 classCache.remove(name); 549 } 550 } 551 552 /** 553 * adds a URL to the classloader. 554 * @param url the new classpath element 555 */ 556 public void addURL(URL url) { 557 super.addURL(url); 558 } 559 560 /** 561 * Indicates if a class is recompilable. Recompileable means, that the classloader 562 * will try to locate a groovy source file for this class and then compile it again, 563 * adding the resulting class as entry to the cache. Giving null as class is like a 564 * recompilation, so the method should always return true here. Only classes that are 565 * implementing GroovyObject are compileable and only if the timestamp in the class 566 * is lower than Long.MAX_VALUE. 567 * 568 * NOTE: First the parent loaders will be asked and only if they don't return a 569 * class the recompilation will happen. Recompilation also only happen if the source 570 * file is newer. 571 * 572 * @see #isSourceNewer(URL, Class) 573 * @param cls the class to be tested. If null the method should return true 574 * @return true if the class should be compiled again 575 */ 576 protected boolean isRecompilable(Class cls) { 577 if (cls==null) return true; 578 if (recompile==null && !config.getRecompileGroovySource()) return false; 579 if (recompile!=null && !recompile.booleanValue()) return false; 580 if (!GroovyObject.class.isAssignableFrom(cls)) return false; 581 long timestamp = getTimeStamp(cls); 582 if (timestamp == Long.MAX_VALUE) return false; 583 584 return true; 585 } 586 587 /** 588 * sets if the recompilation should be enable. There are 3 possible 589 * values for this. Any value different than null overrides the 590 * value from the compiler configuration. true means to recompile if needed 591 * false means to never recompile. 592 * @param mode the recompilation mode 593 * @see CompilerConfiguration 594 */ 595 public void setShouldRecompile(Boolean mode){ 596 recompile = mode; 597 } 598 599 600 /** 601 * gets the currently set recompilation mode. null means, the 602 * compiler configuration is used. False means no recompilation and 603 * true means that recompilation will be done if needed. 604 * @return the recompilation mode 605 */ 606 public Boolean isShouldRecompile(){ 607 return recompile; 608 } 609 610 /** 611 * loads a class from a file or a parent classloader. 612 * 613 * @param name of the class to be loaded 614 * @param lookupScriptFiles if false no lookup at files is done at all 615 * @param preferClassOverScript if true the file lookup is only done if there is no class 616 * @param resolve @see ClassLoader#loadClass(java.lang.String, boolean) 617 * @return the class found or the class created from a file lookup 618 * @throws ClassNotFoundException 619 */ 620 public Class loadClass(final String name, boolean lookupScriptFiles, boolean preferClassOverScript, boolean resolve) 621 throws ClassNotFoundException, CompilationFailedException 622 { 623 // look into cache 624 Class cls=getClassCacheEntry(name); 625 626 // enable recompilation? 627 boolean recompile = isRecompilable(cls); 628 if (!recompile) return cls; 629 630 // check security manager 631 SecurityManager sm = System.getSecurityManager(); 632 if (sm != null) { 633 String className = name.replace('/', '.'); 634 int i = className.lastIndexOf('.'); 635 if (i != -1) { 636 sm.checkPackageAccess(className.substring(0, i)); 637 } 638 } 639 640 // try parent loader 641 ClassNotFoundException last = null; 642 try { 643 Class parentClassLoaderClass = super.loadClass(name, resolve); 644 // always return if the parent loader was successfull 645 if (cls!=parentClassLoaderClass) return parentClassLoaderClass; 646 } catch (ClassNotFoundException cnfe) { 647 last = cnfe; 648 } catch (NoClassDefFoundError ncdfe) { 649 if (ncdfe.getMessage().indexOf("wrong name")>0) { 650 last = new ClassNotFoundException(name); 651 } else { 652 throw ncdfe; 653 } 654 } 655 656 if (cls!=null) { 657 // prefer class if no recompilation 658 preferClassOverScript |= !recompile; 659 if (preferClassOverScript) return cls; 660 } 661 662 // at this point the loading from a parent loader failed 663 // and we want to recompile if needed. 664 if (lookupScriptFiles) { 665 // synchronize on cache, as we want only one compilation 666 // at the same time 667 synchronized (classCache) { 668 // try groovy file 669 try { 670 // check if recompilation already happend. 671 if (getClassCacheEntry(name)!=cls) return getClassCacheEntry(name); 672 URL source = resourceLoader.loadGroovySource(name); 673 cls = recompile(source,name,cls); 674 } catch (IOException ioe) { 675 last = new ClassNotFoundException("IOException while openening groovy source: " + name, ioe); 676 } finally { 677 if (cls==null) { 678 removeClassCacheEntry(name); 679 } else { 680 setClassCacheEntry(cls); 681 } 682 } 683 } 684 } 685 686 if (cls==null) { 687 // no class found, there has to be an exception before then 688 if (last==null) throw new AssertionError(true); 689 throw last; 690 } 691 return cls; 692 } 693 694 /** 695 * (Re)Comipiles the given source. 696 * This method starts the compilation of a given source, if 697 * the source has changed since the class was created. For 698 * this isSourceNewer is called. 699 * 700 * @see #isSourceNewer(URL, Class) 701 * @param source the source pointer for the compilation 702 * @param className the name of the class to be generated 703 * @param oldClass a possible former class 704 * @return the old class if the source wasn't new enough, the new class else 705 * @throws CompilationFailedException if the compilation failed 706 * @throws IOException if the source is not readable 707 * 708 */ 709 protected Class recompile(URL source, String className, Class oldClass) throws CompilationFailedException, IOException { 710 if (source != null) { 711 // found a source, compile it if newer 712 if ((oldClass!=null && isSourceNewer(source, oldClass)) || (oldClass==null)) { 713 sourceCache.remove(className); 714 return parseClass(source.openStream(),className); 715 } 716 } 717 return oldClass; 718 } 719 720 /** 721 * Implemented here to check package access prior to returning an 722 * already loaded class. 723 * @throws CompilationFailedException if the compilation failed 724 * @throws ClassNotFoundException if the class was not found 725 * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean) 726 */ 727 protected Class loadClass(final String name, boolean resolve) throws ClassNotFoundException { 728 return loadClass(name,true,false,resolve); 729 } 730 731 /** 732 * gets the time stamp of a given class. For groovy 733 * generated classes this usually means to return the value 734 * of the static field __timeStamp. If the parameter doesn't 735 * have such a field, then Long.MAX_VALUE is returned 736 * 737 * @param cls the class 738 * @return the time stamp 739 */ 740 protected long getTimeStamp(Class cls) { 741 Long o; 742 try { 743 Field field = cls.getField(Verifier.__TIMESTAMP); 744 o = (Long) field.get(null); 745 } catch (Exception e) { 746 return Long.MAX_VALUE; 747 } 748 return o.longValue(); 749 } 750 751 private URL getSourceFile(String name) { 752 String filename = name.replace('.', '/') + config.getDefaultScriptExtension(); 753 URL ret = getResource(filename); 754 if (ret!=null && ret.getProtocol().equals("file")) { 755 String fileWithoutPackage = filename; 756 if (fileWithoutPackage.indexOf('/')!=-1){ 757 int index = fileWithoutPackage.lastIndexOf('/'); 758 fileWithoutPackage = fileWithoutPackage.substring(index+1); 759 } 760 File path = new File(ret.getFile()).getParentFile(); 761 if (path.exists() && path.isDirectory()) { 762 File file = new File(path, fileWithoutPackage); 763 if (file.exists()) { 764 // file.exists() might be case insensitive. Let's do 765 // case sensitive match for the filename 766 File parent = file.getParentFile(); 767 String[] files = parent.list(); 768 for (int j = 0; j < files.length; j++) { 769 if (files[j].equals(fileWithoutPackage)) return ret; 770 } 771 } 772 } 773 //file does not exist! 774 return null; 775 } 776 return ret; 777 } 778 779 /** 780 * Decides if the given source is newer than a class. 781 * 782 * @see #getTimeStamp(Class) 783 * @param source the source we may want to compile 784 * @param cls the former class 785 * @return true if the source is newer, false else 786 * @throws IOException if it is not possible to open an 787 * connection for the given source 788 */ 789 protected boolean isSourceNewer(URL source, Class cls) throws IOException { 790 long lastMod; 791 792 // Special handling for file:// protocol, as getLastModified() often reports 793 // incorrect results (-1) 794 if (source.getProtocol().equals("file")) { 795 // Coerce the file URL to a File 796 String path = source.getPath().replace('/', File.separatorChar).replace('|', ':'); 797 File file = new File(path); 798 lastMod = file.lastModified(); 799 } 800 else { 801 lastMod = source.openConnection().getLastModified(); 802 } 803 long classTime = getTimeStamp(cls); 804 return classTime+config.getMinimumRecompilationInterval() < lastMod; 805 } 806 807 /** 808 * adds a classpath to this classloader. 809 * @param path is a jar file or a directory. 810 * @see #addURL(URL) 811 */ 812 public void addClasspath(final String path) { 813 AccessController.doPrivileged(new PrivilegedAction() { 814 public Object run() { 815 try { 816 File f = new File(path); 817 URL newURL = f.toURI().toURL(); 818 URL[] urls = getURLs(); 819 for (int i=0; i<urls.length; i++) { 820 if (urls[i].equals(newURL)) return null; 821 } 822 addURL(newURL); 823 } catch (MalformedURLException e) { 824 //TODO: fail through ? 825 } 826 return null; 827 } 828 }); 829 } 830 831 /** 832 * <p>Returns all Groovy classes loaded by this class loader. 833 * 834 * @return all classes loaded by this class loader 835 */ 836 public Class[] getLoadedClasses() { 837 synchronized (classCache) { 838 return (Class[]) classCache.values().toArray(new Class[0]); 839 } 840 } 841 842 /** 843 * removes all classes from the class cache. 844 * @see #getClassCacheEntry(String) 845 * @see #setClassCacheEntry(Class) 846 * @see #removeClassCacheEntry(String) 847 */ 848 public void clearCache() { 849 synchronized (classCache) { 850 classCache.clear(); 851 } 852 } 853 }