1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 /***
37 * @TODO: multi threaded compiling of the same class but with different roots
38 * for compilation... T1 compiles A, which uses B, T2 compiles B... mark A and B
39 * as parsed and then synchronize compilation. Problems: How to synchronize?
40 * How to get error messages?
41 *
42 */
43 package groovy.lang;
44
45 import java.io.ByteArrayInputStream;
46 import java.io.File;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.lang.reflect.Field;
50 import java.net.MalformedURLException;
51 import java.net.URL;
52 import java.net.URLClassLoader;
53 import java.security.AccessController;
54 import java.security.CodeSource;
55 import java.security.PrivilegedAction;
56 import java.security.ProtectionDomain;
57 import java.util.ArrayList;
58 import java.util.Collection;
59 import java.util.Enumeration;
60 import java.util.HashMap;
61 import java.util.Iterator;
62 import java.util.List;
63 import java.util.Map;
64
65 import org.codehaus.groovy.ast.ClassNode;
66 import org.codehaus.groovy.ast.ModuleNode;
67 import org.codehaus.groovy.classgen.Verifier;
68 import org.codehaus.groovy.control.CompilationFailedException;
69 import org.codehaus.groovy.control.CompilationUnit;
70 import org.codehaus.groovy.control.CompilerConfiguration;
71 import org.codehaus.groovy.control.Phases;
72 import org.codehaus.groovy.control.SourceUnit;
73 import org.objectweb.asm.ClassVisitor;
74 import org.objectweb.asm.ClassWriter;
75
76 /***
77 * A ClassLoader which can load Groovy classes. The loaded classes are cached,
78 * classes from other classlaoders should not be cached. To be able to load a
79 * script that was asked for earlier but was created later it is essential not
80 * to keep anything like a "class not found" information for that class name.
81 * This includes possible parent loaders. Classes that are not chached are always
82 * reloaded.
83 *
84 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
85 * @author Guillaume Laforge
86 * @author Steve Goetze
87 * @author Bing Ran
88 * @author <a href="mailto:scottstirling@rcn.com">Scott Stirling</a>
89 * @author <a href="mailto:blackdrag@gmx.org">Jochen Theodorou</a>
90 * @version $Revision: 1.76 $
91 */
92 public class GroovyClassLoader extends URLClassLoader {
93
94 /***
95 * this cache contains the loaded classes or PARSING, if the class is currently parsed
96 */
97 protected Map classCache = new HashMap();
98 protected Map sourceCache = new HashMap();
99 private CompilerConfiguration config;
100 private Boolean recompile = null;
101
102 private static int scriptNameCounter = 1000000;
103
104 private GroovyResourceLoader resourceLoader = new GroovyResourceLoader() {
105 public URL loadGroovySource(final String filename) throws MalformedURLException {
106 URL file = (URL) AccessController.doPrivileged(new PrivilegedAction() {
107 public Object run() {
108 return getSourceFile(filename);
109 }
110 });
111 return file;
112 }
113 };
114
115 /***
116 * creates a GroovyClassLoader using the current Thread's context
117 * Class loader as parent.
118 */
119 public GroovyClassLoader() {
120 this(Thread.currentThread().getContextClassLoader());
121 }
122
123 /***
124 * creates a GroovyClassLoader using the given ClassLoader as parent
125 */
126 public GroovyClassLoader(ClassLoader loader) {
127 this(loader, null);
128 }
129
130 /***
131 * creates a GroovyClassLoader using the given GroovyClassLoader as parent.
132 * This loader will get the parent's CompilerConfiguration
133 */
134 public GroovyClassLoader(GroovyClassLoader parent) {
135 this(parent, parent.config, false);
136 }
137
138 /***
139 * creates a GroovyClassLaoder.
140 * @param parent the parten class loader
141 * @param config the compiler configuration
142 * @param useConfigurationClasspath determines if the configurations classpath should be added
143 */
144 public GroovyClassLoader(ClassLoader parent, CompilerConfiguration config, boolean useConfigurationClasspath) {
145 super(new URL[0],parent);
146 if (config==null) config = CompilerConfiguration.DEFAULT;
147 this.config = config;
148 if (useConfigurationClasspath) {
149 for (Iterator it=config.getClasspath().iterator(); it.hasNext();) {
150 String path = (String) it.next();
151 this.addClasspath(path);
152 }
153 }
154 }
155
156 /***
157 * creates a GroovyClassLoader using the given ClassLoader as parent.
158 */
159 public GroovyClassLoader(ClassLoader loader, CompilerConfiguration config) {
160 this(loader,config,true);
161 }
162
163 public void setResourceLoader(GroovyResourceLoader resourceLoader) {
164 if (resourceLoader == null) {
165 throw new IllegalArgumentException("Resource loader must not be null!");
166 }
167 this.resourceLoader = resourceLoader;
168 }
169
170 public GroovyResourceLoader getResourceLoader() {
171 return resourceLoader;
172 }
173
174 /***
175 * Loads the given class node returning the implementation Class
176 *
177 * @param classNode
178 * @return a class
179 */
180 public Class defineClass(ClassNode classNode, String file) {
181
182 throw new DeprecationException("the method GroovyClassLoader#defineClass(ClassNode, String) is no longer used and removed");
183 }
184
185 /***
186 * Loads the given class node returning the implementation Class.
187 *
188 * WARNING: this compilation is not synchronized
189 *
190 * @param classNode
191 * @return a class
192 */
193 public Class defineClass(ClassNode classNode, String file, String newCodeBase) {
194 CodeSource codeSource = null;
195 try {
196 codeSource = new CodeSource(new URL("file", "", newCodeBase), (java.security.cert.Certificate[]) null);
197 } catch (MalformedURLException e) {
198
199 }
200
201 CompilationUnit unit = createCompilationUnit(config,codeSource);
202 ClassCollector collector = createCollector(unit,classNode.getModule().getContext());
203 try {
204 unit.addClassNode(classNode);
205 unit.setClassgenCallback(collector);
206 unit.compile(Phases.CLASS_GENERATION);
207
208 return collector.generatedClass;
209 } catch (CompilationFailedException e) {
210 throw new RuntimeException(e);
211 }
212 }
213
214 /***
215 * Parses the given file into a Java class capable of being run
216 *
217 * @param file the file name to parse
218 * @return the main class defined in the given script
219 */
220 public Class parseClass(File file) throws CompilationFailedException, IOException {
221 return parseClass(new GroovyCodeSource(file));
222 }
223
224 /***
225 * Parses the given text into a Java class capable of being run
226 *
227 * @param text the text of the script/class to parse
228 * @param fileName the file name to use as the name of the class
229 * @return the main class defined in the given script
230 */
231 public Class parseClass(String text, String fileName) throws CompilationFailedException {
232 return parseClass(new ByteArrayInputStream(text.getBytes()), fileName);
233 }
234
235 /***
236 * Parses the given text into a Java class capable of being run
237 *
238 * @param text the text of the script/class to parse
239 * @return the main class defined in the given script
240 */
241 public Class parseClass(String text) throws CompilationFailedException {
242 return parseClass(new ByteArrayInputStream(text.getBytes()), "script" + System.currentTimeMillis() + ".groovy");
243 }
244
245 /***
246 * Parses the given character stream into a Java class capable of being run
247 *
248 * @param in an InputStream
249 * @return the main class defined in the given script
250 */
251 public Class parseClass(InputStream in) throws CompilationFailedException {
252 return parseClass(in, generateScriptName());
253 }
254
255 public synchronized String generateScriptName() {
256 scriptNameCounter++;
257 return "script"+scriptNameCounter+".groovy";
258 }
259
260 public Class parseClass(final InputStream in, final String fileName) throws CompilationFailedException {
261
262
263
264
265 GroovyCodeSource gcs = (GroovyCodeSource) AccessController.doPrivileged(new PrivilegedAction() {
266 public Object run() {
267 return new GroovyCodeSource(in, fileName, "/groovy/script");
268 }
269 });
270 return parseClass(gcs);
271 }
272
273
274 public Class parseClass(GroovyCodeSource codeSource) throws CompilationFailedException {
275 return parseClass(codeSource, codeSource.isCachable());
276 }
277
278 /***
279 * Parses the given code source into a Java class. If there is a class file
280 * for the given code source, then no parsing is done, instead the cached class is returned.
281 *
282 * @param shouldCacheSource if true then the generated class will be stored in the source cache
283 *
284 * @return the main class defined in the given script
285 */
286 public Class parseClass(GroovyCodeSource codeSource, boolean shouldCacheSource) throws CompilationFailedException {
287 synchronized (classCache) {
288 Class answer = (Class) sourceCache.get(codeSource.getName());
289 if (answer!=null) return answer;
290
291
292
293 try {
294 CompilationUnit unit = createCompilationUnit(config, codeSource.getCodeSource());
295 SourceUnit su = null;
296 if (codeSource.getFile()==null) {
297 su = unit.addSource(codeSource.getName(), codeSource.getInputStream());
298 } else {
299 su = unit.addSource(codeSource.getFile());
300 }
301
302 ClassCollector collector = createCollector(unit,su);
303 unit.setClassgenCallback(collector);
304 int goalPhase = Phases.CLASS_GENERATION;
305 if (config != null && config.getTargetDirectory()!=null) goalPhase = Phases.OUTPUT;
306 unit.compile(goalPhase);
307
308 answer = collector.generatedClass;
309 for (Iterator iter = collector.getLoadedClasses().iterator(); iter.hasNext();) {
310 Class clazz = (Class) iter.next();
311 setClassCacheEntry(clazz);
312 }
313 if (shouldCacheSource) sourceCache.put(codeSource.getName(), answer);
314 } finally {
315 try {
316 codeSource.getInputStream().close();
317 } catch (IOException e) {
318 throw new GroovyRuntimeException("unable to close stream",e);
319 }
320 }
321 return answer;
322 }
323 }
324
325 /***
326 * gets the currently used classpath.
327 * @return a String[] containing the file information of the urls
328 * @see #getURLs()
329 */
330 protected String[] getClassPath() {
331
332 URL[] urls = getURLs();
333 String[] ret = new String[urls.length];
334 for (int i = 0; i < ret.length; i++) {
335 ret[i] = urls[i].getFile();
336 }
337 return ret;
338 }
339
340 /***
341 * expands the classpath
342 * @param pathList an empty list that will contain the elements of the classpath
343 * @param classpath the classpath specified as a single string
344 * @deprecated
345 */
346 protected void expandClassPath(List pathList, String base, String classpath, boolean isManifestClasspath) {
347 throw new DeprecationException("the method groovy.lang.GroovyClassLoader#expandClassPath(List,String,String,boolean) is no longer used internally and removed");
348 }
349
350 /***
351 * A helper method to allow bytecode to be loaded. spg changed name to
352 * defineClass to make it more consistent with other ClassLoader methods
353 * @deprecated
354 */
355 protected Class defineClass(String name, byte[] bytecode, ProtectionDomain domain) {
356 throw new DeprecationException("the method groovy.lang.GroovyClassLoader#defineClass(String,byte[],ProtectionDomain) is no longer used internally and removed");
357 }
358
359 public static class InnerLoader extends GroovyClassLoader{
360 private GroovyClassLoader delegate;
361 public InnerLoader(GroovyClassLoader delegate) {
362 super(delegate);
363 this.delegate = delegate;
364 }
365 public void addClasspath(String path) {
366 delegate.addClasspath(path);
367 }
368 public void clearCache() {
369 delegate.clearCache();
370 }
371 public URL findResource(String name) {
372 return delegate.findResource(name);
373 }
374 public Enumeration findResources(String name) throws IOException {
375 return delegate.findResources(name);
376 }
377 public Class[] getLoadedClasses() {
378 return delegate.getLoadedClasses();
379 }
380 public URL getResource(String name) {
381 return delegate.getResource(name);
382 }
383 public InputStream getResourceAsStream(String name) {
384 return delegate.getResourceAsStream(name);
385 }
386 public GroovyResourceLoader getResourceLoader() {
387 return delegate.getResourceLoader();
388 }
389 public URL[] getURLs() {
390 return delegate.getURLs();
391 }
392 public Class loadClass(String name, boolean lookupScriptFiles, boolean preferClassOverScript, boolean resolve) throws ClassNotFoundException, CompilationFailedException {
393 Class c = findLoadedClass(name);
394 if (c!=null) return c;
395 return delegate.loadClass(name, lookupScriptFiles, preferClassOverScript, resolve);
396 }
397 public Class parseClass(GroovyCodeSource codeSource, boolean shouldCache) throws CompilationFailedException {
398 return delegate.parseClass(codeSource, shouldCache);
399 }
400 public void setResourceLoader(GroovyResourceLoader resourceLoader) {
401 delegate.setResourceLoader(resourceLoader);
402 }
403 public void addURL(URL url) {
404 delegate.addURL(url);
405 }
406 }
407
408 /***
409 * creates a new CompilationUnit. If you want to add additional
410 * phase operations to the CompilationUnit (for example to inject
411 * additional methods, variables, fields), then you should overwrite
412 * this method.
413 *
414 * @param config the compiler configuration, usually the same as for this class loader
415 * @param source the source containing the initial file to compile, more files may follow during compilation
416 *
417 * @return the CompilationUnit
418 */
419 protected CompilationUnit createCompilationUnit(CompilerConfiguration config, CodeSource source) {
420 return new CompilationUnit(config, source, this);
421 }
422
423 /***
424 * creates a ClassCollector for a new compilation.
425 * @param unit the compilationUnit
426 * @param su the SoruceUnit
427 * @return the ClassCollector
428 */
429 protected ClassCollector createCollector(CompilationUnit unit,SourceUnit su) {
430 InnerLoader loader = (InnerLoader) AccessController.doPrivileged(new PrivilegedAction() {
431 public Object run() {
432 return new InnerLoader(GroovyClassLoader.this);
433 }
434 });
435 return new ClassCollector(loader, unit, su);
436 }
437
438 public static class ClassCollector extends CompilationUnit.ClassgenCallback {
439 private Class generatedClass;
440 private GroovyClassLoader cl;
441 private SourceUnit su;
442 private CompilationUnit unit;
443 private Collection loadedClasses = null;
444
445 protected ClassCollector(InnerLoader cl, CompilationUnit unit, SourceUnit su) {
446 this.cl = cl;
447 this.unit = unit;
448 this.loadedClasses = new ArrayList();
449 this.su = su;
450 }
451
452 protected Class onClassNode(ClassWriter classWriter, ClassNode classNode) {
453 byte[] code = classWriter.toByteArray();
454
455 Class theClass = cl.defineClass(classNode.getName(), code, 0, code.length, unit.getAST().getCodeSource());
456 cl.resolveClass(theClass);
457 this.loadedClasses.add(theClass);
458
459 if (generatedClass == null) {
460 ModuleNode mn = classNode.getModule();
461 SourceUnit msu = null;
462 if (mn!=null) msu = mn.getContext();
463 ClassNode main = null;
464 if (mn!=null) main = (ClassNode) mn.getClasses().get(0);
465 if (msu==su && main==classNode) generatedClass = theClass;
466 }
467
468 return theClass;
469 }
470
471 public void call(ClassVisitor classWriter, ClassNode classNode) {
472 onClassNode((ClassWriter) classWriter, classNode);
473 }
474
475 public Collection getLoadedClasses() {
476 return this.loadedClasses;
477 }
478 }
479
480 /***
481 * open up the super class define that takes raw bytes
482 *
483 */
484 public Class defineClass(String name, byte[] b) {
485 return super.defineClass(name, b, 0, b.length);
486 }
487
488 /***
489 * loads a class from a file or a parent classloader.
490 * This method does call loadClass(String, boolean, boolean, boolean)
491 * with the last parameter set to false.
492 * @throws CompilationFailedException
493 */
494 public Class loadClass(final String name, boolean lookupScriptFiles, boolean preferClassOverScript)
495 throws ClassNotFoundException, CompilationFailedException
496 {
497 return loadClass(name,lookupScriptFiles,preferClassOverScript,false);
498 }
499
500 /***
501 * gets a class from the class cache. This cache contains only classes loaded through
502 * this class loader or an InnerLoader instance. If no class is stored for a
503 * specific name, then the method should return null.
504 *
505 * @param name of the class
506 * @return the class stored for the given name
507 * @see #removeClassCacheEntry(String)
508 * @see #setClassCacheEntry(Class)
509 * @see #clearCache()
510 */
511 protected Class getClassCacheEntry(String name) {
512 if (name==null) return null;
513 synchronized (classCache) {
514 Class cls = (Class) classCache.get(name);
515 return cls;
516 }
517 }
518
519 /***
520 * sets an entry in the class cache.
521 * @param cls the class
522 * @see #removeClassCacheEntry(String)
523 * @see #getClassCacheEntry(String)
524 * @see #clearCache()
525 */
526 protected void setClassCacheEntry(Class cls) {
527 synchronized (classCache) {
528 classCache.put(cls.getName(),cls);
529 }
530 }
531
532 /***
533 * removes a class from the class cache.
534 * @param name of the class
535 * @see #getClassCacheEntry(String)
536 * @see #setClassCacheEntry(Class)
537 * @see #clearCache()
538 */
539 protected void removeClassCacheEntry(String name) {
540 synchronized (classCache) {
541 classCache.remove(name);
542 }
543 }
544
545 /***
546 * adds a URL to the classloader.
547 * @param url the new classpath element
548 */
549 public void addURL(URL url) {
550 super.addURL(url);
551 }
552
553 /***
554 * Indicates if a class is recompilable. Recompileable means, that the classloader
555 * will try to locate a groovy source file for this class and then compile it again,
556 * adding the resulting class as entry to the cache. Giving null as class is like a
557 * recompilation, so the method should always return true here. Only classes that are
558 * implementing GroovyObject are compileable and only if the timestamp in the class
559 * is lower than Long.MAX_VALUE.
560 *
561 * NOTE: First the parent loaders will be asked and only if they don't return a
562 * class the recompilation will happen. Recompilation also only happen if the source
563 * file is newer.
564 *
565 * @see #isSourceNewer(URL, Class)
566 * @param cls the class to be tested. If null the method should return true
567 * @return true if the class should be compiled again
568 */
569 protected boolean isRecompilable(Class cls) {
570 if (cls==null) return true;
571 if (recompile==null && !config.getRecompileGroovySource()) return false;
572 if (recompile!=null && !recompile.booleanValue()) return false;
573 if (!GroovyObject.class.isAssignableFrom(cls)) return false;
574 long timestamp = getTimeStamp(cls);
575 if (timestamp == Long.MAX_VALUE) return false;
576
577 return true;
578 }
579
580 /***
581 * sets if the recompilation should be enable. There are 3 possible
582 * values for this. Any value different than null overrides the
583 * value from the compiler configuration. true means to recompile if needed
584 * false means to never recompile.
585 * @param mode the recompilation mode
586 * @see CompilerConfiguration
587 */
588 public void setShouldRecompile(Boolean mode){
589 recompile = mode;
590 }
591
592
593 /***
594 * gets the currently set recompilation mode. null means, the
595 * compiler configuration is used. False means no recompilation and
596 * true means that recompilation will be done if needed.
597 * @return the recompilation mode
598 */
599 public Boolean isShouldRecompile(){
600 return recompile;
601 }
602
603 /***
604 * loads a class from a file or a parent classloader.
605 *
606 * @param name of the class to be loaded
607 * @param lookupScriptFiles if false no lookup at files is done at all
608 * @param preferClassOverScript if true the file lookup is only done if there is no class
609 * @param resolve @see ClassLoader#loadClass(java.lang.String, boolean)
610 * @return the class found or the class created from a file lookup
611 * @throws ClassNotFoundException
612 */
613 public Class loadClass(final String name, boolean lookupScriptFiles, boolean preferClassOverScript, boolean resolve)
614 throws ClassNotFoundException, CompilationFailedException
615 {
616
617 Class cls=getClassCacheEntry(name);
618
619
620 boolean recompile = isRecompilable(cls);
621 if (!recompile) return cls;
622
623
624 SecurityManager sm = System.getSecurityManager();
625 if (sm != null) {
626 String className = name.replace('/', '.');
627 int i = className.lastIndexOf('.');
628 if (i != -1) {
629 sm.checkPackageAccess(className.substring(0, i));
630 }
631 }
632
633
634 ClassNotFoundException last = null;
635 try {
636 Class parentClassLoaderClass = super.loadClass(name, resolve);
637
638 if (cls!=parentClassLoaderClass) return parentClassLoaderClass;
639 } catch (ClassNotFoundException cnfe) {
640 last = cnfe;
641 } catch (NoClassDefFoundError ncdfe) {
642 if (ncdfe.getMessage().indexOf("wrong name")>0) {
643 last = new ClassNotFoundException(name);
644 } else {
645 throw ncdfe;
646 }
647 }
648
649 if (cls!=null) {
650
651 preferClassOverScript |= !recompile;
652 if (preferClassOverScript) return cls;
653 }
654
655
656
657 if (lookupScriptFiles) {
658
659
660 synchronized (classCache) {
661
662 try {
663
664 if (getClassCacheEntry(name)!=cls) return getClassCacheEntry(name);
665 URL source = resourceLoader.loadGroovySource(name);
666 cls = recompile(source,name,cls);
667 } catch (IOException ioe) {
668 last = new ClassNotFoundException("IOException while openening groovy source: " + name, ioe);
669 } finally {
670 if (cls==null) {
671 removeClassCacheEntry(name);
672 } else {
673 setClassCacheEntry(cls);
674 }
675 }
676 }
677 }
678
679 if (cls==null) {
680
681 if (last==null) throw new AssertionError(true);
682 throw last;
683 }
684 return cls;
685 }
686
687 /***
688 * (Re)Comipiles the given source.
689 * This method starts the compilation of a given source, if
690 * the source has changed since the class was created. For
691 * this isSourceNewer is called.
692 *
693 * @see #isSourceNewer(URL, Class)
694 * @param source the source pointer for the compilation
695 * @param className the name of the class to be generated
696 * @param oldClass a possible former class
697 * @return the old class if the source wasn't new enough, the new class else
698 * @throws CompilationFailedException if the compilation failed
699 * @throws IOException if the source is not readable
700 *
701 */
702 protected Class recompile(URL source, String className, Class oldClass) throws CompilationFailedException, IOException {
703 if (source != null) {
704
705 if ((oldClass!=null && isSourceNewer(source, oldClass)) || (oldClass==null)) {
706 sourceCache.remove(className);
707 return parseClass(source.openStream(),className);
708 }
709 }
710 return oldClass;
711 }
712
713 /***
714 * Implemented here to check package access prior to returning an
715 * already loaded class.
716 * @throws CompilationFailedException if the compilation failed
717 * @throws ClassNotFoundException if the class was not found
718 * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean)
719 */
720 protected Class loadClass(final String name, boolean resolve) throws ClassNotFoundException {
721 return loadClass(name,true,false,resolve);
722 }
723
724 /***
725 * gets the time stamp of a given class. For groovy
726 * generated classes this usually means to return the value
727 * of the static field __timeStamp. If the parameter doesn't
728 * have such a field, then Long.MAX_VALUE is returned
729 *
730 * @param cls the class
731 * @return the time stamp
732 */
733 protected long getTimeStamp(Class cls) {
734 Long o;
735 try {
736 Field field = cls.getField(Verifier.__TIMESTAMP);
737 o = (Long) field.get(null);
738 } catch (Exception e) {
739 return Long.MAX_VALUE;
740 }
741 return o.longValue();
742 }
743
744 private URL getSourceFile(String name) {
745 String filename = name.replace('.', '/') + config.getDefaultScriptExtension();
746 URL ret = getResource(filename);
747 if (ret!=null && ret.getProtocol().equals("file")) {
748 String fileWithoutPackage = filename;
749 if (fileWithoutPackage.indexOf('/')!=-1){
750 int index = fileWithoutPackage.lastIndexOf('/');
751 fileWithoutPackage = fileWithoutPackage.substring(index+1);
752 }
753 File path = new File(ret.getFile()).getParentFile();
754 if (path.exists() && path.isDirectory()) {
755 File file = new File(path, fileWithoutPackage);
756 if (file.exists()) {
757
758
759 File parent = file.getParentFile();
760 String[] files = parent.list();
761 for (int j = 0; j < files.length; j++) {
762 if (files[j].equals(fileWithoutPackage)) return ret;
763 }
764 }
765 }
766
767 return null;
768 }
769 return ret;
770 }
771
772 /***
773 * Decides if the given source is newer than a class.
774 *
775 * @see #getTimeStamp(Class)
776 * @param source the source we may want to compile
777 * @param cls the former class
778 * @return true if the source is newer, false else
779 * @throws IOException if it is not possible to open an
780 * connection for the given source
781 */
782 protected boolean isSourceNewer(URL source, Class cls) throws IOException {
783 long lastMod;
784
785
786
787 if (source.getProtocol().equals("file")) {
788
789 String path = source.getPath().replace('/', File.separatorChar).replace('|', ':');
790 File file = new File(path);
791 lastMod = file.lastModified();
792 }
793 else {
794 lastMod = source.openConnection().getLastModified();
795 }
796 long classTime = getTimeStamp(cls);
797 return classTime+config.getMinimumRecompilationIntervall() < lastMod;
798 }
799
800 /***
801 * adds a classpath to this classloader.
802 * @param path is a jar file or a directory.
803 * @see #addURL(URL)
804 */
805 public void addClasspath(final String path) {
806 AccessController.doPrivileged(new PrivilegedAction() {
807 public Object run() {
808 try {
809 File f = new File(path);
810 URL newURL = f.toURI().toURL();
811 URL[] urls = getURLs();
812 for (int i=0; i<urls.length; i++) {
813 if (urls[i].equals(newURL)) return null;
814 }
815 addURL(newURL);
816 } catch (MalformedURLException e) {
817
818 }
819 return null;
820 }
821 });
822 }
823
824 /***
825 * <p>Returns all Groovy classes loaded by this class loader.
826 *
827 * @return all classes loaded by this class loader
828 */
829 public Class[] getLoadedClasses() {
830 synchronized (classCache) {
831 return (Class[]) classCache.values().toArray(new Class[0]);
832 }
833 }
834
835 /***
836 * removes all classes from the class cache.
837 * @see #getClassCacheEntry(String)
838 * @see #setClassCacheEntry(Class)
839 * @see #removeClassCacheEntry(String)
840 */
841 public void clearCache() {
842 synchronized (classCache) {
843 classCache.clear();
844 }
845 }
846 }