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