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 }