001 /* 002 * $Id: GroovyClassLoader.java,v 1.60 2005/11/21 00:40:23 glaforge Exp $ 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 package groovy.lang; 036 037 import java.io.BufferedInputStream; 038 import java.io.ByteArrayInputStream; 039 import java.io.ByteArrayOutputStream; 040 import java.io.File; 041 import java.io.IOException; 042 import java.io.InputStream; 043 import java.lang.reflect.Field; 044 import java.net.MalformedURLException; 045 import java.net.URL; 046 import java.security.AccessController; 047 import java.security.CodeSource; 048 import java.security.PrivilegedAction; 049 import java.security.ProtectionDomain; 050 import java.security.SecureClassLoader; 051 import java.util.ArrayList; 052 import java.util.Collection; 053 import java.util.HashMap; 054 import java.util.HashSet; 055 import java.util.Iterator; 056 import java.util.List; 057 import java.util.Map; 058 import java.util.Set; 059 import java.util.jar.Attributes; 060 import java.util.jar.JarEntry; 061 import java.util.jar.JarFile; 062 import java.util.jar.Manifest; 063 064 import org.codehaus.groovy.ast.ClassNode; 065 import org.codehaus.groovy.ast.ModuleNode; 066 import org.codehaus.groovy.classgen.Verifier; 067 import org.codehaus.groovy.control.CompilationFailedException; 068 import org.codehaus.groovy.control.CompilationUnit; 069 import org.codehaus.groovy.control.CompilerConfiguration; 070 import org.codehaus.groovy.control.Phases; 071 import org.codehaus.groovy.control.SourceUnit; 072 import org.objectweb.asm.ClassVisitor; 073 import org.objectweb.asm.ClassWriter; 074 075 /** 076 * A ClassLoader which can load Groovy classes 077 * 078 * @author <a href="mailto:james@coredevelopers.net">James Strachan </a> 079 * @author Guillaume Laforge 080 * @author Steve Goetze 081 * @author Bing Ran 082 * @author <a href="mailto:scottstirling@rcn.com">Scott Stirling</a> 083 * @version $Revision: 1.60 $ 084 */ 085 public class GroovyClassLoader extends SecureClassLoader { 086 087 private Map cache = new HashMap(); 088 089 private GroovyResourceLoader resourceLoader = new GroovyResourceLoader() { 090 public URL loadGroovySource(String filename) throws MalformedURLException { 091 File file = getSourceFile(filename); 092 return file == null ? null : file.toURL(); 093 } 094 }; 095 096 public void removeFromCache(Class aClass) { 097 cache.remove(aClass); 098 } 099 100 public static class PARSING { 101 } 102 103 private class NOT_RESOLVED { 104 } 105 106 private CompilerConfiguration config; 107 private String[] searchPaths; 108 private Set additionalPaths = new HashSet(); 109 110 /** 111 * creates a GroovyClassLoader using the current Thread's context 112 * Class loader as parent. 113 */ 114 public GroovyClassLoader() { 115 this(Thread.currentThread().getContextClassLoader()); 116 } 117 118 /** 119 * creates a GroovyClassLoader using the given ClassLoader as parent 120 */ 121 public GroovyClassLoader(ClassLoader loader) { 122 this(loader, null); 123 } 124 125 /** 126 * creates a GroovyClassLoader using the given GroovyClassLoader as parent. 127 * This loader will get the parent's CompilerConfiguration 128 */ 129 public GroovyClassLoader(GroovyClassLoader parent) { 130 this(parent, parent.config); 131 } 132 133 /** 134 * creates a GroovyClassLoader using the given ClassLoader as parent. 135 */ 136 public GroovyClassLoader(ClassLoader loader, CompilerConfiguration config) { 137 super(loader); 138 if (config==null) config = CompilerConfiguration.DEFAULT; 139 this.config = config; 140 } 141 142 public void setResourceLoader(GroovyResourceLoader resourceLoader) { 143 if (resourceLoader == null) { 144 throw new IllegalArgumentException("Resource loader must not be null!"); 145 } 146 this.resourceLoader = resourceLoader; 147 } 148 149 public GroovyResourceLoader getResourceLoader() { 150 return resourceLoader; 151 } 152 153 /** 154 * Loads the given class node returning the implementation Class 155 * 156 * @param classNode 157 * @return a class 158 */ 159 public Class defineClass(ClassNode classNode, String file) { 160 return defineClass(classNode, file, "/groovy/defineClass"); 161 } 162 163 /** 164 * Loads the given class node returning the implementation Class 165 * 166 * @param classNode 167 * @return a class 168 */ 169 public Class defineClass(ClassNode classNode, String file, String newCodeBase) { 170 CodeSource codeSource = null; 171 try { 172 codeSource = new CodeSource(new URL("file", "", newCodeBase), (java.security.cert.Certificate[]) null); 173 } catch (MalformedURLException e) { 174 //swallow 175 } 176 177 CompilationUnit unit = new CompilationUnit(config, codeSource, this); 178 try { 179 ClassCollector collector = createCollector(unit,classNode.getModule().getContext()); 180 181 unit.addClassNode(classNode); 182 unit.setClassgenCallback(collector); 183 unit.compile(Phases.CLASS_GENERATION); 184 185 return collector.generatedClass; 186 } catch (CompilationFailedException e) { 187 throw new RuntimeException(e); 188 } 189 } 190 191 /** 192 * Parses the given file into a Java class capable of being run 193 * 194 * @param file the file name to parse 195 * @return the main class defined in the given script 196 */ 197 public Class parseClass(File file) throws CompilationFailedException, IOException { 198 return parseClass(new GroovyCodeSource(file)); 199 } 200 201 /** 202 * Parses the given text into a Java class capable of being run 203 * 204 * @param text the text of the script/class to parse 205 * @param fileName the file name to use as the name of the class 206 * @return the main class defined in the given script 207 */ 208 public Class parseClass(String text, String fileName) throws CompilationFailedException { 209 return parseClass(new ByteArrayInputStream(text.getBytes()), fileName); 210 } 211 212 /** 213 * Parses the given text into a Java class capable of being run 214 * 215 * @param text the text of the script/class to parse 216 * @return the main class defined in the given script 217 */ 218 public Class parseClass(String text) throws CompilationFailedException { 219 return parseClass(new ByteArrayInputStream(text.getBytes()), "script" + System.currentTimeMillis() + ".groovy"); 220 } 221 222 /** 223 * Parses the given character stream into a Java class capable of being run 224 * 225 * @param in an InputStream 226 * @return the main class defined in the given script 227 */ 228 public Class parseClass(InputStream in) throws CompilationFailedException { 229 return parseClass(in, "script" + System.currentTimeMillis() + ".groovy"); 230 } 231 232 public Class parseClass(final InputStream in, final String fileName) throws CompilationFailedException { 233 //For generic input streams, provide a catch-all codebase of 234 // GroovyScript 235 //Security for these classes can be administered via policy grants with 236 // a codebase 237 //of file:groovy.script 238 GroovyCodeSource gcs = (GroovyCodeSource) AccessController.doPrivileged(new PrivilegedAction() { 239 public Object run() { 240 return new GroovyCodeSource(in, fileName, "/groovy/script"); 241 } 242 }); 243 return parseClass(gcs); 244 } 245 246 247 public Class parseClass(GroovyCodeSource codeSource) throws CompilationFailedException { 248 return parseClass(codeSource, true); 249 } 250 251 /** 252 * Parses the given code source into a Java class capable of being run 253 * 254 * @return the main class defined in the given script 255 */ 256 public Class parseClass(GroovyCodeSource codeSource, boolean shouldCache) throws CompilationFailedException { 257 String name = codeSource.getName(); 258 Class answer = null; 259 //ASTBuilder.resolveName can call this recursively -- for example when 260 // resolving a Constructor 261 //invocation for a class that is currently being compiled. 262 synchronized (cache) { 263 answer = (Class) cache.get(name); 264 if (answer != null) { 265 return (answer == PARSING.class ? null : answer); 266 } else { 267 cache.put(name, PARSING.class); 268 } 269 } 270 //Was neither already loaded nor compiling, so compile and add to 271 // cache. 272 try { 273 CompilationUnit unit = new CompilationUnit(config, codeSource.getCodeSource(), this); 274 // try { 275 SourceUnit su = null; 276 if (codeSource.getFile()==null) { 277 su = unit.addSource(name, codeSource.getInputStream()); 278 } else { 279 su = unit.addSource(codeSource.getFile()); 280 } 281 282 ClassCollector collector = createCollector(unit,su); 283 unit.setClassgenCallback(collector); 284 int goalPhase = Phases.CLASS_GENERATION; 285 if (config != null && config.getTargetDirectory()!=null) goalPhase = Phases.OUTPUT; 286 unit.compile(goalPhase); 287 288 answer = collector.generatedClass; 289 if (shouldCache) { 290 for (Iterator iter = collector.getLoadedClasses().iterator(); iter.hasNext();) { 291 Class clazz = (Class) iter.next(); 292 cache.put(clazz.getName(),clazz); 293 } 294 } 295 } finally { 296 synchronized (cache) { 297 cache.remove(name); 298 if (shouldCache) { 299 cache.put(name, answer); 300 } 301 } 302 try { 303 codeSource.getInputStream().close(); 304 } catch (IOException e) { 305 throw new GroovyRuntimeException("unable to close stream",e); 306 } 307 } 308 return answer; 309 } 310 311 /** 312 * Using this classloader you can load groovy classes from the system 313 * classpath as though they were already compiled. Note that .groovy classes 314 * found with this mechanism need to conform to the standard java naming 315 * convention - i.e. the public class inside the file must match the 316 * filename and the file must be located in a directory structure that 317 * matches the package structure. 318 */ 319 /*protected Class findClass(final String name) throws ClassNotFoundException { 320 SecurityManager sm = System.getSecurityManager(); 321 if (sm != null) { 322 String className = name.replace('/', '.'); 323 int i = className.lastIndexOf('.'); 324 if (i != -1) { 325 sm.checkPackageDefinition(className.substring(0, i)); 326 } 327 } 328 try { 329 return (Class) AccessController.doPrivileged(new PrivilegedExceptionAction() { 330 public Object run() throws ClassNotFoundException { 331 return findGroovyClass(name); 332 } 333 }); 334 } catch (PrivilegedActionException pae) { 335 throw (ClassNotFoundException) pae.getException(); 336 } 337 }*/ 338 339 /* protected Class findGroovyClass(String name) throws ClassNotFoundException { 340 //Use a forward slash here for the path separator. It will work as a 341 // separator 342 //for the File class on all platforms, AND it is required as a jar file 343 // entry separator. 344 String filename = name.replace('.', '/') + ".groovy"; 345 String[] paths = getClassPath(); 346 // put the absolute classname in a File object so we can easily 347 // pluck off the class name and the package path 348 File classnameAsFile = new File(filename); 349 // pluck off the classname without the package 350 String classname = classnameAsFile.getName(); 351 String pkg = classnameAsFile.getParent(); 352 String pkgdir; 353 for (int i = 0; i < paths.length; i++) { 354 String pathName = paths[i]; 355 File path = new File(pathName); 356 if (path.exists()) { 357 if (path.isDirectory()) { 358 // patch to fix case preserving but case insensitive file 359 // systems (like macosx) 360 // JIRA issue 414 361 // 362 // first see if the file even exists, no matter what the 363 // case is 364 File nocasefile = new File(path, filename); 365 if (!nocasefile.exists()) 366 continue; 367 368 // now we know the file is there is some form or another, so 369 // let's look up all the files to see if the one we're 370 // really 371 // looking for is there 372 if (pkg == null) 373 pkgdir = pathName; 374 else 375 pkgdir = pathName + "/" + pkg; 376 File pkgdirF = new File(pkgdir); 377 // make sure the resulting path is there and is a dir 378 if (pkgdirF.exists() && pkgdirF.isDirectory()) { 379 File files[] = pkgdirF.listFiles(); 380 for (int j = 0; j < files.length; j++) { 381 // do the case sensitive comparison 382 if (files[j].getName().equals(classname)) { 383 try { 384 return parseClass(files[j]); 385 } catch (CompilationFailedException e) { 386 throw new ClassNotFoundException("Syntax error in groovy file: " + files[j].getAbsolutePath(), e); 387 } catch (IOException e) { 388 throw new ClassNotFoundException("Error reading groovy file: " + files[j].getAbsolutePath(), e); 389 } 390 } 391 } 392 } 393 } else { 394 try { 395 JarFile jarFile = new JarFile(path); 396 JarEntry entry = jarFile.getJarEntry(filename); 397 if (entry != null) { 398 byte[] bytes = extractBytes(jarFile, entry); 399 Certificate[] certs = entry.getCertificates(); 400 try { 401 return parseClass(new GroovyCodeSource(new ByteArrayInputStream(bytes), filename, path, certs)); 402 } catch (CompilationFailedException e1) { 403 throw new ClassNotFoundException("Syntax error in groovy file: " + filename, e1); 404 } 405 } 406 407 } catch (IOException e) { 408 // Bad jar in classpath, ignore 409 } 410 } 411 } 412 } 413 throw new ClassNotFoundException(name); 414 }*/ 415 416 //Read the bytes from a non-null JarEntry. This is done here because the 417 // entry must be read completely 418 //in order to get verified certificates, which can only be obtained after a 419 // full read. 420 private byte[] extractBytes(JarFile jarFile, JarEntry entry) { 421 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 422 int b; 423 try { 424 BufferedInputStream bis = new BufferedInputStream(jarFile.getInputStream(entry)); 425 while ((b = bis.read()) != -1) { 426 baos.write(b); 427 } 428 } catch (IOException ioe) { 429 throw new GroovyRuntimeException("Could not read the jar bytes for " + entry.getName()); 430 } 431 return baos.toByteArray(); 432 } 433 434 /** 435 * Workaround for Groovy-835 436 * 437 * @return the classpath as an array of strings, uses the classpath in the CompilerConfiguration object if possible, 438 * otherwise defaults to the value of the <tt>java.class.path</tt> system property 439 */ 440 protected String[] getClassPath() { 441 if (null == searchPaths) { 442 String classpath; 443 if(null != config && null != config.getClasspath()) { 444 //there's probably a better way to do this knowing the internals of 445 //Groovy, but it works for now 446 StringBuffer sb = new StringBuffer(); 447 for(Iterator iter = config.getClasspath().iterator(); iter.hasNext(); ) { 448 sb.append(iter.next().toString()); 449 sb.append(File.pathSeparatorChar); 450 } 451 //remove extra path separator 452 sb.deleteCharAt(sb.length()-1); 453 classpath = sb.toString(); 454 } else { 455 classpath = System.getProperty("java.class.path", "."); 456 } 457 List pathList = new ArrayList(additionalPaths); 458 expandClassPath(pathList, null, classpath, false); 459 searchPaths = new String[pathList.size()]; 460 searchPaths = (String[]) pathList.toArray(searchPaths); 461 } 462 return searchPaths; 463 } 464 465 /** 466 * @param pathList an empty list that will contain the elements of the classpath 467 * @param classpath the classpath specified as a single string 468 */ 469 protected void expandClassPath(List pathList, String base, String classpath, boolean isManifestClasspath) { 470 471 // checking against null prevents an NPE when recursevely expanding the 472 // classpath 473 // in case the classpath is malformed 474 if (classpath != null) { 475 476 // Sun's convention for the class-path attribute is to seperate each 477 // entry with spaces 478 // but some libraries don't respect that convention and add commas, 479 // colons, semi-colons 480 String[] paths; 481 if (isManifestClasspath) { 482 paths = classpath.split("[\\ ,:;]"); 483 } else { 484 paths = classpath.split(File.pathSeparator); 485 } 486 487 for (int i = 0; i < paths.length; i++) { 488 if (paths.length > 0) { 489 File path = null; 490 491 if ("".equals(base)) { 492 path = new File(paths[i]); 493 } else { 494 path = new File(base, paths[i]); 495 } 496 497 if (path.exists()) { 498 if (!path.isDirectory()) { 499 try { 500 JarFile jar = new JarFile(path); 501 pathList.add(paths[i]); 502 503 Manifest manifest = jar.getManifest(); 504 if (manifest != null) { 505 Attributes classPathAttributes = manifest.getMainAttributes(); 506 String manifestClassPath = classPathAttributes.getValue("Class-Path"); 507 508 if (manifestClassPath != null) 509 expandClassPath(pathList, paths[i], manifestClassPath, true); 510 } 511 } catch (IOException e) { 512 // Bad jar, ignore 513 continue; 514 } 515 } else { 516 pathList.add(paths[i]); 517 } 518 } 519 } 520 } 521 } 522 } 523 524 /** 525 * A helper method to allow bytecode to be loaded. spg changed name to 526 * defineClass to make it more consistent with other ClassLoader methods 527 */ 528 protected Class defineClass(String name, byte[] bytecode, ProtectionDomain domain) { 529 return defineClass(name, bytecode, 0, bytecode.length, domain); 530 } 531 532 protected ClassCollector createCollector(CompilationUnit unit,SourceUnit su) { 533 return new ClassCollector(this, unit, su); 534 } 535 536 public static class ClassCollector extends CompilationUnit.ClassgenCallback { 537 private Class generatedClass; 538 private GroovyClassLoader cl; 539 private SourceUnit su; 540 private CompilationUnit unit; 541 private Collection loadedClasses = null; 542 543 protected ClassCollector(GroovyClassLoader cl, CompilationUnit unit, SourceUnit su) { 544 this.cl = cl; 545 this.unit = unit; 546 this.loadedClasses = new ArrayList(); 547 this.su = su; 548 } 549 550 protected Class onClassNode(ClassWriter classWriter, ClassNode classNode) { 551 byte[] code = classWriter.toByteArray(); 552 553 Class theClass = cl.defineClass(classNode.getName(), code, 0, code.length, unit.getAST().getCodeSource()); 554 this.loadedClasses.add(theClass); 555 556 if (generatedClass == null) { 557 ModuleNode mn = classNode.getModule(); 558 SourceUnit msu = null; 559 if (mn!=null) msu = mn.getContext(); 560 ClassNode main = null; 561 if (mn!=null) main = (ClassNode) mn.getClasses().get(0); 562 if (msu==su && main==classNode) generatedClass = theClass; 563 } 564 565 return theClass; 566 } 567 568 public void call(ClassVisitor classWriter, ClassNode classNode) { 569 onClassNode((ClassWriter) classWriter, classNode); 570 } 571 572 public Collection getLoadedClasses() { 573 return this.loadedClasses; 574 } 575 } 576 577 /** 578 * open up the super class define that takes raw bytes 579 * 580 */ 581 public Class defineClass(String name, byte[] b) { 582 Class c = super.defineClass(name, b, 0, b.length); 583 synchronized (cache) { 584 cache.put(name, c); 585 } 586 return c; 587 } 588 589 /** 590 * loads a class from a file or a parent classloader. 591 * This method does call @see #loadClass(String, boolean, boolean, boolean) 592 * with the last parameter set to false. 593 */ 594 public Class loadClass(final String name, boolean lookupScriptFiles, boolean preferClassOverScript) 595 throws ClassNotFoundException 596 { 597 return loadClass(name,lookupScriptFiles,preferClassOverScript,false); 598 } 599 600 /** 601 * loads a class from a file or a parent classloader. 602 * 603 * @param name of the class to be loaded 604 * @param lookupScriptFiles if false no lookup at files is done at all 605 * @param preferClassOverScript if true the file lookup is only done if there is no class 606 * @param resolve @see ClassLoader#loadClass(java.lang.String, boolean) 607 * @return the class found or the class created from a file lookup 608 * @throws ClassNotFoundException 609 */ 610 public Class loadClass(final String name, boolean lookupScriptFiles, boolean preferClassOverScript, boolean resolve) 611 throws ClassNotFoundException 612 { 613 // look into cache 614 synchronized (cache) { 615 Class cls = (Class) cache.get(name); 616 if (cls == NOT_RESOLVED.class) throw new ClassNotFoundException(name); 617 if (cls!=null) return cls; 618 } 619 620 // check security manager 621 SecurityManager sm = System.getSecurityManager(); 622 if (sm != null) { 623 String className = name.replace('/', '.'); 624 int i = className.lastIndexOf('.'); 625 if (i != -1) { 626 sm.checkPackageAccess(className.substring(0, i)); 627 } 628 } 629 630 // try parent loader 631 Class cls = null; 632 ClassNotFoundException last = null; 633 try { 634 cls = super.loadClass(name, resolve); 635 } catch (ClassNotFoundException cnfe) { 636 last = cnfe; 637 } 638 639 if (cls!=null) { 640 boolean recompile = false; 641 if (getTimeStamp(cls) < Long.MAX_VALUE) { 642 Class[] inters = cls.getInterfaces(); 643 for (int i = 0; i < inters.length; i++) { 644 if (inters[i].getName().equals(GroovyObject.class.getName())) { 645 recompile=true; 646 break; 647 } 648 } 649 } 650 651 preferClassOverScript |= cls.getClassLoader()==this; 652 preferClassOverScript |= !recompile; 653 if(preferClassOverScript) return cls; 654 } 655 656 if (lookupScriptFiles) { 657 // try groovy file 658 try { 659 URL source = (URL) AccessController.doPrivileged(new PrivilegedAction() { 660 public Object run() { 661 try { 662 return resourceLoader.loadGroovySource(name); 663 } catch (MalformedURLException e) { 664 return null; // ugly to return null 665 } 666 } 667 }); 668 if (source != null) { 669 // found a source, compile it then 670 if ((cls!=null && isSourceNewer(source, cls)) || (cls==null)) { 671 synchronized (cache) { 672 cache.put(name,PARSING.class); 673 } 674 cls = parseClass(source.openStream()); 675 } 676 } 677 } catch (Exception e) { 678 cls = null; 679 last = new ClassNotFoundException("Failed to parse groovy file: " + name, e); 680 } 681 } 682 683 if (cls==null) { 684 // no class found, there has to be an exception before then 685 if (last==null) throw new AssertionError(true); 686 synchronized (cache) { 687 cache.put(name, NOT_RESOLVED.class); 688 } 689 throw last; 690 } 691 692 //class found, store it in cache 693 synchronized (cache) { 694 cache.put(name, cls); 695 } 696 return cls; 697 } 698 699 /** 700 * Implemented here to check package access prior to returning an 701 * already loaded class. 702 * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean) 703 */ 704 protected synchronized Class loadClass(final String name, boolean resolve) throws ClassNotFoundException { 705 return loadClass(name,true,false,resolve); 706 } 707 708 private long getTimeStamp(Class cls) { 709 Field field; 710 Long o; 711 try { 712 field = cls.getField(Verifier.__TIMESTAMP); 713 o = (Long) field.get(null); 714 } catch (Exception e) { 715 return Long.MAX_VALUE; 716 } 717 return o.longValue(); 718 } 719 720 private File getSourceFile(String name) { 721 File source = null; 722 String filename = name.replace('.', '/') + ".groovy"; 723 String[] paths = getClassPath(); 724 for (int i = 0; i < paths.length; i++) { 725 String pathName = paths[i]; 726 File path = new File(pathName); 727 if (path.exists()) { // case sensitivity depending on OS! 728 if (path.isDirectory()) { 729 File file = new File(path, filename); 730 if (file.exists()) { 731 // file.exists() might be case insensitive. Let's do 732 // case sensitive match for the filename 733 boolean fileExists = false; 734 int sepp = filename.lastIndexOf('/'); 735 String fn = filename; 736 if (sepp >= 0) { 737 fn = filename.substring(++sepp); 738 } 739 File parent = file.getParentFile(); 740 String[] files = parent.list(); 741 for (int j = 0; j < files.length; j++) { 742 if (files[j].equals(fn)) { 743 fileExists = true; 744 break; 745 } 746 } 747 748 if (fileExists) { 749 source = file; 750 break; 751 } 752 } 753 } 754 } 755 } 756 return source; 757 } 758 759 private boolean isSourceNewer(URL source, Class cls) throws IOException { 760 long lastMod; 761 762 // Special handling for file:// protocol, as getLastModified() often reports 763 // incorrect results (-1) 764 if (source.getProtocol().equals("file")) { 765 // Coerce the file URL to a File 766 String path = source.getPath().replace('/', File.separatorChar).replace('|', ':'); 767 File file = new File(path); 768 lastMod = file.lastModified(); 769 } 770 else { 771 lastMod = source.openConnection().getLastModified(); 772 } 773 return lastMod > getTimeStamp(cls); 774 } 775 776 public void addClasspath(String path) { 777 additionalPaths.add(path); 778 searchPaths = null; 779 } 780 781 /** 782 * <p>Returns all Groovy classes loaded by this class loader. 783 * 784 * @return all classes loaded by this class loader 785 */ 786 public Class[] getLoadedClasses() { 787 Class[] loadedClasses = null; 788 HashSet set = new HashSet(cache.size()); 789 synchronized (cache) { 790 for (Iterator iter = cache.values().iterator(); iter.hasNext();) { 791 Class element = (Class) iter.next(); 792 if (element==NOT_RESOLVED.class) continue; 793 set.add(element); 794 } 795 loadedClasses = (Class[])set.toArray(new Class[0]); 796 } 797 return loadedClasses; 798 } 799 }