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