View Javadoc

1   /*
2    * $Id: GroovyClassLoader.java,v 1.76 2006/06/23 15:25:19 blackdrag Exp $
3    *
4    * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5    *
6    * Redistribution and use of this software and associated documentation
7    * ("Software"), with or without modification, are permitted provided that the
8    * following conditions are met:
9    *  1. Redistributions of source code must retain copyright statements and
10   * notices. Redistributions must also contain a copy of this document.
11   *  2. Redistributions in binary form must reproduce the above copyright
12   * notice, this list of conditions and the following disclaimer in the
13   * documentation and/or other materials provided with the distribution.
14   *  3. The name "groovy" must not be used to endorse or promote products
15   * derived from this Software without prior written permission of The Codehaus.
16   * For written permission, please contact info@codehaus.org.
17   *  4. Products derived from this Software may not be called "groovy" nor may
18   * "groovy" appear in their names without prior written permission of The
19   * Codehaus. "groovy" is a registered trademark of The Codehaus.
20   *  5. Due credit should be given to The Codehaus - http://groovy.codehaus.org/
21   *
22   * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
23   * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25   * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
26   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
32   * DAMAGE.
33   *
34   */
35  
36  /***
37   * @TODO: multi threaded compiling of the same class but with different roots
38   * for compilation... T1 compiles A, which uses B, T2 compiles B... mark A and B
39   * as parsed and then synchronize compilation. Problems: How to synchronize? 
40   * How to get error messages?   
41   * 
42   */
43  package groovy.lang;
44  
45  import java.io.ByteArrayInputStream;
46  import java.io.File;
47  import java.io.IOException;
48  import java.io.InputStream;
49  import java.lang.reflect.Field;
50  import java.net.MalformedURLException;
51  import java.net.URL;
52  import java.net.URLClassLoader;
53  import java.security.AccessController;
54  import java.security.CodeSource;
55  import java.security.PrivilegedAction;
56  import java.security.ProtectionDomain;
57  import java.util.ArrayList;
58  import java.util.Collection;
59  import java.util.Enumeration;
60  import java.util.HashMap;
61  import java.util.Iterator;
62  import java.util.List;
63  import java.util.Map;
64  
65  import org.codehaus.groovy.ast.ClassNode;
66  import org.codehaus.groovy.ast.ModuleNode;
67  import org.codehaus.groovy.classgen.Verifier;
68  import org.codehaus.groovy.control.CompilationFailedException;
69  import org.codehaus.groovy.control.CompilationUnit;
70  import org.codehaus.groovy.control.CompilerConfiguration;
71  import org.codehaus.groovy.control.Phases;
72  import org.codehaus.groovy.control.SourceUnit;
73  import org.objectweb.asm.ClassVisitor;
74  import org.objectweb.asm.ClassWriter;
75  
76  /***
77   * A ClassLoader which can load Groovy classes. The loaded classes are cached, 
78   * classes from other classlaoders should not be cached. To be able to load a 
79   * script that was asked for earlier but was created later it is essential not
80   * to keep anything like a "class not found" information for that class name. 
81   * This includes possible parent loaders. Classes that are not chached are always 
82   * reloaded.
83   *
84   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
85   * @author Guillaume Laforge
86   * @author Steve Goetze
87   * @author Bing Ran
88   * @author <a href="mailto:scottstirling@rcn.com">Scott Stirling</a>
89   * @author <a href="mailto:blackdrag@gmx.org">Jochen Theodorou</a>
90   * @version $Revision: 1.76 $
91   */
92  public class GroovyClassLoader extends URLClassLoader {
93  
94      /***
95       * this cache contains the loaded classes or PARSING, if the class is currently parsed 
96       */
97      protected Map classCache = new HashMap();
98      protected Map sourceCache = new HashMap();
99      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                     codeSource.getInputStream().close();
317                 } catch (IOException e) {
318                     throw new GroovyRuntimeException("unable to close stream",e);
319                 }
320             }
321             return answer;
322         }
323     }
324     
325     /***
326      * gets the currently used classpath. 
327      * @return a String[] containing the file information of the urls 
328      * @see #getURLs()
329      */
330     protected String[] getClassPath() {
331         //workaround for Groovy-835
332         URL[] urls = getURLs();
333         String[] ret = new String[urls.length];
334         for (int i = 0; i < ret.length; i++) {
335             ret[i] =  urls[i].getFile();
336         }
337         return ret;
338     }
339 
340     /***
341      * expands the classpath
342      * @param pathList an empty list that will contain the elements of the classpath
343      * @param classpath the classpath specified as a single string
344      * @deprecated
345      */
346     protected void expandClassPath(List pathList, String base, String classpath, boolean isManifestClasspath) {
347         throw new DeprecationException("the method groovy.lang.GroovyClassLoader#expandClassPath(List,String,String,boolean) is no longer used internally and removed");
348     }
349 
350     /***
351      * A helper method to allow bytecode to be loaded. spg changed name to
352      * defineClass to make it more consistent with other ClassLoader methods
353      * @deprecated
354      */
355     protected Class defineClass(String name, byte[] bytecode, ProtectionDomain domain) {
356         throw new DeprecationException("the method groovy.lang.GroovyClassLoader#defineClass(String,byte[],ProtectionDomain) is no longer used internally and removed");
357     }
358     
359     public static class InnerLoader extends GroovyClassLoader{
360         private GroovyClassLoader delegate;
361     	public InnerLoader(GroovyClassLoader delegate) {
362     		super(delegate);
363             this.delegate = delegate;
364     	}
365         public void addClasspath(String path) {
366             delegate.addClasspath(path);
367         }
368         public void clearCache() {
369             delegate.clearCache();
370         }
371         public URL findResource(String name) {
372             return delegate.findResource(name);
373         }
374         public Enumeration findResources(String name) throws IOException {
375             return delegate.findResources(name);
376         }
377         public Class[] getLoadedClasses() {
378             return delegate.getLoadedClasses();
379         }
380         public URL getResource(String name) {
381             return delegate.getResource(name);
382         }
383         public InputStream getResourceAsStream(String name) {
384             return delegate.getResourceAsStream(name);
385         }
386         public GroovyResourceLoader getResourceLoader() {
387             return delegate.getResourceLoader();
388         }
389         public URL[] getURLs() {
390             return delegate.getURLs();
391         }
392         public Class loadClass(String name, boolean lookupScriptFiles, boolean preferClassOverScript, boolean resolve) throws ClassNotFoundException, CompilationFailedException {
393             Class c = findLoadedClass(name);
394             if (c!=null) return c;
395             return delegate.loadClass(name, lookupScriptFiles, preferClassOverScript, resolve);
396         }
397         public Class parseClass(GroovyCodeSource codeSource, boolean shouldCache) throws CompilationFailedException {
398             return delegate.parseClass(codeSource, shouldCache);
399         }
400         public void setResourceLoader(GroovyResourceLoader resourceLoader) {
401             delegate.setResourceLoader(resourceLoader);
402         }
403         public void addURL(URL url) {
404             delegate.addURL(url);
405         }        
406     }
407     
408     /***
409      * creates a new CompilationUnit. If you want to add additional
410      * phase operations to the CompilationUnit (for example to inject
411      * additional methods, variables, fields), then you should overwrite
412      * this method.
413      * 
414      * @param config the compiler configuration, usually the same as for this class loader
415      * @param source the source containing the initial file to compile, more files may follow during compilation
416      * 
417      * @return the CompilationUnit
418      */
419     protected CompilationUnit createCompilationUnit(CompilerConfiguration config, CodeSource source) {
420         return new CompilationUnit(config, source, this);
421     }
422 
423     /***
424      * creates a ClassCollector for a new compilation.
425      * @param unit the compilationUnit
426      * @param su  the SoruceUnit
427      * @return the ClassCollector
428      */
429     protected ClassCollector createCollector(CompilationUnit unit,SourceUnit su) {
430     	InnerLoader loader = (InnerLoader) AccessController.doPrivileged(new PrivilegedAction() {
431             public Object run() {
432                 return new InnerLoader(GroovyClassLoader.this);
433             }
434         }); 
435         return new ClassCollector(loader, unit, su);
436     }
437 
438     public static class ClassCollector extends CompilationUnit.ClassgenCallback {
439         private Class generatedClass;
440         private GroovyClassLoader cl;
441         private SourceUnit su;
442         private CompilationUnit unit;
443         private Collection loadedClasses = null;
444 
445         protected ClassCollector(InnerLoader cl, CompilationUnit unit, SourceUnit su) {
446             this.cl = cl;
447             this.unit = unit;
448             this.loadedClasses = new ArrayList();
449             this.su = su;
450         }
451 
452         protected Class onClassNode(ClassWriter classWriter, ClassNode classNode) {
453             byte[] code = classWriter.toByteArray();
454 
455             Class theClass = cl.defineClass(classNode.getName(), code, 0, code.length, unit.getAST().getCodeSource());
456             cl.resolveClass(theClass);
457             this.loadedClasses.add(theClass);
458 
459             if (generatedClass == null) {
460                 ModuleNode mn = classNode.getModule();
461                 SourceUnit msu = null;
462                 if (mn!=null) msu = mn.getContext();
463                 ClassNode main = null;
464                 if (mn!=null) main = (ClassNode) mn.getClasses().get(0);
465                 if (msu==su && main==classNode) generatedClass = theClass;
466             }
467 
468             return theClass;
469         }
470 
471         public void call(ClassVisitor classWriter, ClassNode classNode) {
472             onClassNode((ClassWriter) classWriter, classNode);
473         }
474 
475         public Collection getLoadedClasses() {
476             return this.loadedClasses;
477         }
478     }
479 
480     /***
481      * open up the super class define that takes raw bytes
482      *
483      */
484     public Class defineClass(String name, byte[] b) {
485         return super.defineClass(name, b, 0, b.length);
486     }
487     
488     /***
489      * loads a class from a file or a parent classloader.
490      * This method does call loadClass(String, boolean, boolean, boolean)
491      * with the last parameter set to false.
492      * @throws CompilationFailedException 
493      */
494     public Class loadClass(final String name, boolean lookupScriptFiles, boolean preferClassOverScript)
495         throws ClassNotFoundException, CompilationFailedException
496     {
497         return loadClass(name,lookupScriptFiles,preferClassOverScript,false);
498     }
499 
500     /***
501      * gets a class from the class cache. This cache contains only classes loaded through
502      * this class loader or an InnerLoader instance. If no class is stored for a
503      * specific name, then the method should return null. 
504      *  
505      * @param name of the class
506      * @return the class stored for the given name 
507      * @see #removeClassCacheEntry(String)
508      * @see #setClassCacheEntry(Class)
509      * @see #clearCache()
510      */    
511     protected Class getClassCacheEntry(String name) {
512         if (name==null) return null;
513         synchronized (classCache) {
514             Class cls = (Class) classCache.get(name);
515             return cls;
516         }
517     }
518     
519     /***
520      * sets an entry in the class cache. 
521      * @param cls the class
522      * @see #removeClassCacheEntry(String)
523      * @see #getClassCacheEntry(String)
524      * @see #clearCache()
525      */
526     protected void setClassCacheEntry(Class cls) {
527         synchronized (classCache) {
528             classCache.put(cls.getName(),cls);
529         }
530     }    
531     
532     /***
533      * removes a class from the class cache.
534      * @param name of the class
535      * @see #getClassCacheEntry(String)
536      * @see #setClassCacheEntry(Class)
537      * @see #clearCache()
538      */
539     protected void removeClassCacheEntry(String name) {
540         synchronized (classCache) {
541             classCache.remove(name);
542         }
543     }    
544     
545     /***
546      * adds a URL to the classloader.
547      * @param url the new classpath element 
548      */
549     public void addURL(URL url) {
550         super.addURL(url);
551     }
552     
553     /***
554      * Indicates if a class is recompilable. Recompileable means, that the classloader
555      * will try to locate a groovy source file for this class and then compile it again,
556      * adding the resulting class as entry to the cache. Giving null as class is like a
557      * recompilation, so the method should always return true here. Only classes that are
558      * implementing GroovyObject are compileable and only if the timestamp in the class
559      * is lower than Long.MAX_VALUE.  
560      * 
561      *  NOTE: First the parent loaders will be asked and only if they don't return a
562      *  class the recompilation will happen. Recompilation also only happen if the source
563      *  file is newer.
564      * 
565      * @see #isSourceNewer(URL, Class)
566      * @param cls the class to be tested. If null the method should return true
567      * @return true if the class should be compiled again
568      */
569     protected boolean isRecompilable(Class cls) {
570         if (cls==null) return true;
571         if (recompile==null && !config.getRecompileGroovySource()) return false;
572         if (recompile!=null && !recompile.booleanValue()) return false;
573         if (!GroovyObject.class.isAssignableFrom(cls)) return false;
574         long timestamp = getTimeStamp(cls); 
575         if (timestamp == Long.MAX_VALUE) return false;
576         
577         return true;
578     }
579     
580     /***
581      * sets if the recompilation should be enable. There are 3 possible
582      * values for this. Any value different than null overrides the
583      * value from the compiler configuration. true means to recompile if needed
584      * false means to never recompile.  
585      * @param mode the recompilation mode
586      * @see CompilerConfiguration
587      */
588     public void setShouldRecompile(Boolean mode){
589         recompile = mode;
590     }
591     
592     
593     /***
594      * gets the currently set recompilation mode. null means, the 
595      * compiler configuration is used. False means no recompilation and 
596      * true means that recompilation will be done if needed. 
597      * @return the recompilation mode
598      */
599     public Boolean isShouldRecompile(){
600         return recompile;
601     }
602 
603     /***
604      * loads a class from a file or a parent classloader.
605      *
606      * @param name                      of the class to be loaded
607      * @param lookupScriptFiles         if false no lookup at files is done at all
608      * @param preferClassOverScript     if true the file lookup is only done if there is no class
609      * @param resolve                   @see ClassLoader#loadClass(java.lang.String, boolean)
610      * @return                          the class found or the class created from a file lookup
611      * @throws ClassNotFoundException
612      */
613     public Class loadClass(final String name, boolean lookupScriptFiles, boolean preferClassOverScript, boolean resolve)
614         throws ClassNotFoundException, CompilationFailedException
615     {
616         // look into cache
617         Class cls=getClassCacheEntry(name);
618         
619         // enable recompilation?
620         boolean recompile = isRecompilable(cls);
621         if (!recompile) return cls;
622 
623         // check security manager
624         SecurityManager sm = System.getSecurityManager();
625         if (sm != null) {
626             String className = name.replace('/', '.');
627             int i = className.lastIndexOf('.');
628             if (i != -1) {
629                 sm.checkPackageAccess(className.substring(0, i));
630             }
631         }
632 
633         // try parent loader
634         ClassNotFoundException last = null;
635         try {
636             Class parentClassLoaderClass = super.loadClass(name, resolve);
637             // always return if the parent loader was successfull 
638             if (cls!=parentClassLoaderClass) return parentClassLoaderClass;
639         } catch (ClassNotFoundException cnfe) {
640             last = cnfe;
641         } catch (NoClassDefFoundError ncdfe) {
642             if (ncdfe.getMessage().indexOf("wrong name")>0) {
643                 last = new ClassNotFoundException(name);
644             } else {
645                 throw ncdfe;
646             }
647         }
648 
649         if (cls!=null) {
650             // prefer class if no recompilation
651             preferClassOverScript |= !recompile;
652             if (preferClassOverScript) return cls;
653         }
654 
655         // at this point the loading from a parent loader failed
656         // and we want to recompile if needed.
657         if (lookupScriptFiles) {
658             // synchronize on cache, as we want only one compilation
659             // at the same time
660             synchronized (classCache) {
661                 // try groovy file
662                 try {
663                     // check if recompilation already happend.
664                     if (getClassCacheEntry(name)!=cls) return getClassCacheEntry(name);
665                     URL source = resourceLoader.loadGroovySource(name);
666                     cls = recompile(source,name,cls);
667                 } catch (IOException ioe) {
668                     last = new ClassNotFoundException("IOException while openening groovy source: " + name, ioe);
669                 } finally {
670                     if (cls==null) {
671                         removeClassCacheEntry(name);
672                     } else {
673                         setClassCacheEntry(cls);
674                     }
675                 }
676             }
677         }
678 
679         if (cls==null) {
680             // no class found, there has to be an exception before then
681             if (last==null) throw new AssertionError(true);
682             throw last;
683         }
684         return cls;
685     }
686 
687     /***
688      * (Re)Comipiles the given source. 
689      * This method starts the compilation of a given source, if
690      * the source has changed since the class was created. For
691      * this isSourceNewer is called.
692      * 
693      * @see #isSourceNewer(URL, Class)
694      * @param source the source pointer for the compilation
695      * @param className the name of the class to be generated
696      * @param oldClass a possible former class
697      * @return the old class if the source wasn't new enough, the new class else
698      * @throws CompilationFailedException if the compilation failed
699      * @throws IOException if the source is not readable
700      * 
701      */
702     protected Class recompile(URL source, String className, Class oldClass) throws CompilationFailedException, IOException {
703         if (source != null) {
704             // found a source, compile it if newer
705             if ((oldClass!=null && isSourceNewer(source, oldClass)) || (oldClass==null)) {
706                 sourceCache.remove(className);
707                 return parseClass(source.openStream(),className);
708             }
709         }
710         return oldClass;
711     }
712 
713     /***
714      * Implemented here to check package access prior to returning an
715      * already loaded class.
716      * @throws CompilationFailedException if the compilation failed
717      * @throws ClassNotFoundException if the class was not found
718      * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean)
719      */
720     protected Class loadClass(final String name, boolean resolve) throws ClassNotFoundException {
721         return loadClass(name,true,false,resolve);
722     }
723 
724     /***
725      * gets the time stamp of a given class. For groovy
726      * generated classes this usually means to return the value
727      * of the static field __timeStamp. If the parameter doesn't
728      * have such a field, then Long.MAX_VALUE is returned
729      * 
730      * @param cls the class 
731      * @return the time stamp
732      */
733     protected long getTimeStamp(Class cls) {
734         Long o;
735         try {
736             Field field = cls.getField(Verifier.__TIMESTAMP);
737             o = (Long) field.get(null);
738         } catch (Exception e) {
739             return Long.MAX_VALUE;
740         }
741         return o.longValue();
742     }
743 
744     private URL getSourceFile(String name) {
745         String filename = name.replace('.', '/') + config.getDefaultScriptExtension();
746         URL ret = getResource(filename);
747         if (ret!=null && ret.getProtocol().equals("file")) {
748             String fileWithoutPackage = filename;
749             if (fileWithoutPackage.indexOf('/')!=-1){
750                 int index = fileWithoutPackage.lastIndexOf('/');
751                 fileWithoutPackage = fileWithoutPackage.substring(index+1);
752             }
753             File path = new File(ret.getFile()).getParentFile();
754             if (path.exists() && path.isDirectory()) {
755                 File file = new File(path, fileWithoutPackage);
756                 if (file.exists()) {
757                     // file.exists() might be case insensitive. Let's do
758                     // case sensitive match for the filename
759                     File parent = file.getParentFile();
760                     String[] files = parent.list();
761                     for (int j = 0; j < files.length; j++) {
762                         if (files[j].equals(fileWithoutPackage)) return ret;
763                     }
764                 }
765             }
766             //file does not exist!
767             return null;
768         }
769         return ret;
770     }
771 
772     /***
773      * Decides if the given source is newer than a class.
774      * 
775      * @see #getTimeStamp(Class)
776      * @param source the source we may want to compile
777      * @param cls the former class
778      * @return true if the source is newer, false else
779      * @throws IOException if it is not possible to open an
780      * connection for the given source
781      */
782     protected boolean isSourceNewer(URL source, Class cls) throws IOException {
783         long lastMod;
784 
785         // Special handling for file:// protocol, as getLastModified() often reports
786         // incorrect results (-1)
787         if (source.getProtocol().equals("file")) {
788             // Coerce the file URL to a File
789             String path = source.getPath().replace('/', File.separatorChar).replace('|', ':');
790             File file = new File(path);
791             lastMod = file.lastModified();
792         }
793         else {
794             lastMod = source.openConnection().getLastModified();
795         }
796         long classTime = getTimeStamp(cls);
797         return classTime+config.getMinimumRecompilationIntervall() < lastMod;
798     }
799 
800     /***
801      * adds a classpath to this classloader.  
802      * @param path is a jar file or a directory.
803      * @see #addURL(URL)
804      */
805     public void addClasspath(final String path) {
806         AccessController.doPrivileged(new PrivilegedAction() {
807             public Object run() {
808                 try {
809                     File f = new File(path);
810                     URL newURL = f.toURI().toURL();
811                     URL[] urls = getURLs();
812                     for (int i=0; i<urls.length; i++) {
813                         if (urls[i].equals(newURL)) return null;
814                     }
815                     addURL(newURL);
816                 } catch (MalformedURLException e) {
817                     //TODO: fail through ?
818                 }
819                 return null;
820             }
821         });
822     }
823 
824     /***
825      * <p>Returns all Groovy classes loaded by this class loader.
826      *
827      * @return all classes loaded by this class loader
828      */
829     public Class[] getLoadedClasses() {
830         synchronized (classCache) {
831             return (Class[]) classCache.values().toArray(new Class[0]);
832         }
833     }
834     
835     /***
836      * removes all classes from the class cache.
837      * @see #getClassCacheEntry(String)
838      * @see #setClassCacheEntry(Class)
839      * @see #removeClassCacheEntry(String)
840      */    
841     public void clearCache() {
842         synchronized (classCache) {
843             classCache.clear();
844         }
845     }
846 }