View Javadoc

1   /*
2    * $Id: GroovyClassLoader.java,v 1.60 2005/11/21 00:40:23 glaforge Exp $
3    *
4    * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5    *
6    * Redistribution and use of this software and associated documentation
7    * ("Software"), with or without modification, are permitted provided that the
8    * following conditions are met:
9    *  1. Redistributions of source code must retain copyright statements and
10   * notices. Redistributions must also contain a copy of this document.
11   *  2. Redistributions in binary form must reproduce the above copyright
12   * notice, this list of conditions and the following disclaimer in the
13   * documentation and/or other materials provided with the distribution.
14   *  3. The name "groovy" must not be used to endorse or promote products
15   * derived from this Software without prior written permission of The Codehaus.
16   * For written permission, please contact info@codehaus.org.
17   *  4. Products derived from this Software may not be called "groovy" nor may
18   * "groovy" appear in their names without prior written permission of The
19   * Codehaus. "groovy" is a registered trademark of The Codehaus.
20   *  5. Due credit should be given to The Codehaus - http://groovy.codehaus.org/
21   *
22   * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
23   * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25   * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
26   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
32   * DAMAGE.
33   *
34   */
35  package groovy.lang;
36  
37  import java.io.BufferedInputStream;
38  import java.io.ByteArrayInputStream;
39  import java.io.ByteArrayOutputStream;
40  import java.io.File;
41  import java.io.IOException;
42  import java.io.InputStream;
43  import java.lang.reflect.Field;
44  import java.net.MalformedURLException;
45  import java.net.URL;
46  import java.security.AccessController;
47  import java.security.CodeSource;
48  import java.security.PrivilegedAction;
49  import java.security.ProtectionDomain;
50  import java.security.SecureClassLoader;
51  import java.util.ArrayList;
52  import java.util.Collection;
53  import java.util.HashMap;
54  import java.util.HashSet;
55  import java.util.Iterator;
56  import java.util.List;
57  import java.util.Map;
58  import java.util.Set;
59  import java.util.jar.Attributes;
60  import java.util.jar.JarEntry;
61  import java.util.jar.JarFile;
62  import java.util.jar.Manifest;
63  
64  import org.codehaus.groovy.ast.ClassNode;
65  import org.codehaus.groovy.ast.ModuleNode;
66  import org.codehaus.groovy.classgen.Verifier;
67  import org.codehaus.groovy.control.CompilationFailedException;
68  import org.codehaus.groovy.control.CompilationUnit;
69  import org.codehaus.groovy.control.CompilerConfiguration;
70  import org.codehaus.groovy.control.Phases;
71  import org.codehaus.groovy.control.SourceUnit;
72  import org.objectweb.asm.ClassVisitor;
73  import org.objectweb.asm.ClassWriter;
74  
75  /***
76   * A ClassLoader which can load Groovy classes
77   *
78   * @author <a href="mailto:james@coredevelopers.net">James Strachan </a>
79   * @author Guillaume Laforge
80   * @author Steve Goetze
81   * @author Bing Ran
82   * @author <a href="mailto:scottstirling@rcn.com">Scott Stirling</a>
83   * @version $Revision: 1.60 $
84   */
85  public class GroovyClassLoader extends SecureClassLoader {
86  
87      private Map cache = new HashMap();
88  
89      private GroovyResourceLoader resourceLoader = new GroovyResourceLoader() {
90          public URL loadGroovySource(String filename) throws MalformedURLException {
91              File file = getSourceFile(filename);
92              return file == null ? null : file.toURL();
93          }
94      };
95  
96      public void removeFromCache(Class aClass) {
97          cache.remove(aClass);
98      }
99  
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 }