View Javadoc

    1   /*
    2    * $Id: GroovyClassLoader.java,v 1.76 2006/06/23 15:25:19 blackdrag Exp $
    3    *
    4    * Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
    5    *
    6    * Redistribution and use of this software and associated documentation
    7    * ("Software"), with or without modification, are permitted provided that the
    8    * following conditions are met:
    9    *  1. Redistributions of source code must retain copyright statements and
    10   * notices. Redistributions must also contain a copy of this document.
    11   *  2. Redistributions in binary form must reproduce the above copyright
    12   * notice, this list of conditions and the following disclaimer in the
    13   * documentation and/or other materials provided with the distribution.
    14   *  3. The name "groovy" must not be used to endorse or promote products
    15   * derived from this Software without prior written permission of The Codehaus.
    16   * For written permission, please contact info@codehaus.org.
    17   *  4. Products derived from this Software may not be called "groovy" nor may
    18   * "groovy" appear in their names without prior written permission of The
    19   * Codehaus. "groovy" is a registered trademark of The Codehaus.
    20   *  5. Due credit should be given to The Codehaus - http://groovy.codehaus.org/
    21   *
    22   * THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
    23   * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    24   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    25   * DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
    26   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    27   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    28   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    29   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    30   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
    31   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
    32   * DAMAGE.
    33   *
    34   */
    35
    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     // 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                     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         //workaround for Groovy-835
    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         // look into cache
    617         Class cls=getClassCacheEntry(name);
    618
    619         // enable recompilation?
    620         boolean recompile = isRecompilable(cls);
    621         if (!recompile) return cls;
    622
    623         // check security manager
    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         // try parent loader
    634         ClassNotFoundException last = null;
    635         try {
    636             Class parentClassLoaderClass = super.loadClass(name, resolve);
    637             // always return if the parent loader was successfull
    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             // prefer class if no recompilation
    651             preferClassOverScript |= !recompile;
    652             if (preferClassOverScript) return cls;
    653         }
    654
    655         // at this point the loading from a parent loader failed
    656         // and we want to recompile if needed.
    657         if (lookupScriptFiles) {
    658             // synchronize on cache, as we want only one compilation
    659             // at the same time
    660             synchronized (classCache) {
    661                 // try groovy file
    662                 try {
    663                     // check if recompilation already happend.
    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             // no class found, there has to be an exception before then
    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             // found a source, compile it if newer
    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                     // file.exists() might be case insensitive. Let's do
    758                     // case sensitive match for the filename
    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             //file does not exist!
    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         // Special handling for file:// protocol, as getLastModified() often reports
    786         // incorrect results (-1)
    787         if (source.getProtocol().equals("file")) {
    788             // Coerce the file URL to a File
    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                     //TODO: fail through ?
    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 }