View Javadoc

    1   /*
    2    * $Id: GroovyClassLoader.java 4445 2006-12-17 22:35:15Z blackdrag $
    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: 4445 $
    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                     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 }