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