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