View Javadoc

1   /*
2    * $Id: GroovyClassLoader.java 4445 2006-12-17 22:35:15Z blackdrag $
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: 4445 $
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                     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 }