/* * Copyright 2003-2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * @todo multi threaded compiling of the same class but with different roots * for compilation... T1 compiles A, which uses B, T2 compiles B... mark A and B * as parsed and then synchronize compilation. Problems: How to synchronize? * How to get error messages? * */ package groovy.lang; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.classgen.Verifier; import org.codehaus.groovy.control.*; import org.codehaus.groovy.runtime.DefaultGroovyMethods; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import java.io.*; import java.net.*; import java.security.*; import java.util.*; /** * A ClassLoader which can load Groovy classes. The loaded classes are cached, * classes from other classloaders should not be cached. To be able to load a * script that was asked for earlier but was created later it is essential not * to keep anything like a "class not found" information for that class name. * This includes possible parent loaders. Classes that are not cached are always * reloaded. * * @author <a href="mailto:james@coredevelopers.net">James Strachan</a> * @author Guillaume Laforge * @author Steve Goetze * @author Bing Ran * @author <a href="mailto:scottstirling@rcn.com">Scott Stirling</a> * @author <a href="mailto:blackdrag@gmx.org">Jochen Theodorou</a> * @version $Revision$ */ public class GroovyClassLoader extends URLClassLoader { /** * this cache contains the loaded classes or PARSING, if the class is currently parsed */ protected final Map<String, Class> classCache = new HashMap<String, Class>(); /** * This cache contains mappings of file name to class. It is used * to bypass compilation. */ protected final Map<String, Class> sourceCache = new HashMap<String, Class>(); private final CompilerConfiguration config; private Boolean recompile; // use 1000000 as offset to avoid conflicts with names form the GroovyShell private static int scriptNameCounter = 1000000; private GroovyResourceLoader resourceLoader = new GroovyResourceLoader() { public URL loadGroovySource(final String filename) throws MalformedURLException { return AccessController.doPrivileged(new PrivilegedAction<URL>() { public URL run() { for (String extension : config.getScriptExtensions()) { try { URL ret = getSourceFile(filename, extension); if (ret != null) return ret; } catch (Throwable t) { // } } return null; } }); } }; /** * creates a GroovyClassLoader using the current Thread's context * Class loader as parent. */ public GroovyClassLoader() { this(Thread.currentThread().getContextClassLoader()); } /** * creates a GroovyClassLoader using the given ClassLoader as parent */ public GroovyClassLoader(ClassLoader loader) { this(loader, null); } /** * creates a GroovyClassLoader using the given GroovyClassLoader as parent. * This loader will get the parent's CompilerConfiguration */ public GroovyClassLoader(GroovyClassLoader parent) { this(parent, parent.config, false); } /** * creates a GroovyClassLoader. * * @param parent the parent class loader * @param config the compiler configuration * @param useConfigurationClasspath determines if the configurations classpath should be added */ public GroovyClassLoader(ClassLoader parent, CompilerConfiguration config, boolean useConfigurationClasspath) { super(new URL[0], parent); if (config == null) config = CompilerConfiguration.DEFAULT; this.config = config; if (useConfigurationClasspath) { for (String path : config.getClasspath()) { this.addClasspath(path); } } } /** * creates a GroovyClassLoader using the given ClassLoader as parent. */ public GroovyClassLoader(ClassLoader loader, CompilerConfiguration config) { this(loader, config, true); } public void setResourceLoader(GroovyResourceLoader resourceLoader) { if (resourceLoader == null) { throw new IllegalArgumentException("Resource loader must not be null!"); } this.resourceLoader = resourceLoader; } public GroovyResourceLoader getResourceLoader() { return resourceLoader; } /** * Loads the given class node returning the implementation Class * * @param classNode * @return a class * @deprecated */ public Class defineClass(ClassNode classNode, String file) { //return defineClass(classNode, file, "/groovy/defineClass"); throw new DeprecationException("the method GroovyClassLoader#defineClass(ClassNode, String) is no longer used and removed"); } /** * Loads the given class node returning the implementation Class. * <p/> * WARNING: this compilation is not synchronized * * @param classNode * @return a class */ public Class defineClass(ClassNode classNode, String file, String newCodeBase) { CodeSource codeSource = null; try { codeSource = new CodeSource(new URL("file", "", newCodeBase), (java.security.cert.Certificate[]) null); } catch (MalformedURLException e) { //swallow } CompilationUnit unit = createCompilationUnit(config, codeSource); ClassCollector collector = createCollector(unit, classNode.getModule().getContext()); try { unit.addClassNode(classNode); unit.setClassgenCallback(collector); unit.compile(Phases.CLASS_GENERATION); definePackage(collector.generatedClass.getName()); return collector.generatedClass; } catch (CompilationFailedException e) { throw new RuntimeException(e); } } /** * Parses the given file into a Java class capable of being run * * @param file the file name to parse * @return the main class defined in the given script */ public Class parseClass(File file) throws CompilationFailedException, IOException { return parseClass(new GroovyCodeSource(file, config.getSourceEncoding())); } /** * Parses the given text into a Java class capable of being run * * @param text the text of the script/class to parse * @param fileName the file name to use as the name of the class * @return the main class defined in the given script */ public Class parseClass(final String text, final String fileName) throws CompilationFailedException { GroovyCodeSource gcs = AccessController.doPrivileged(new PrivilegedAction<GroovyCodeSource>() { public GroovyCodeSource run() { return new GroovyCodeSource(text, fileName, "/groovy/script"); } }); gcs.setCachable(false); return parseClass(gcs); } /** * Parses the given text into a Java class capable of being run * * @param text the text of the script/class to parse * @return the main class defined in the given script */ public Class parseClass(String text) throws CompilationFailedException { return parseClass(text, "script" + System.currentTimeMillis() + Math.abs(text.hashCode()) + ".groovy"); } /** * Parses the given character stream into a Java class capable of being run * * @param in an InputStream * @return the main class defined in the given script * @deprecated Prefer using methods taking a Reader rather than an InputStream to avoid wrong encoding issues. */ public Class parseClass(InputStream in) throws CompilationFailedException { return parseClass(in, generateScriptName()); } public synchronized String generateScriptName() { scriptNameCounter++; return "script" + scriptNameCounter + ".groovy"; } /** * @deprecated Prefer using methods taking a Reader rather than an InputStream to avoid wrong encoding issues. */ public Class parseClass(final InputStream in, final String fileName) throws CompilationFailedException { // For generic input streams, provide a catch-all codebase of GroovyScript // Security for these classes can be administered via policy grants with // a codebase of file:groovy.script GroovyCodeSource gcs = AccessController.doPrivileged(new PrivilegedAction<GroovyCodeSource>() { public GroovyCodeSource run() { try { String scriptText = config.getSourceEncoding() != null ? DefaultGroovyMethods.getText(in, config.getSourceEncoding()) : DefaultGroovyMethods.getText(in); return new GroovyCodeSource(scriptText, fileName, "/groovy/script"); } catch (IOException e) { throw new RuntimeException("Impossible to read the content of the input stream for file named: " + fileName, e); } } }); return parseClass(gcs); } public Class parseClass(GroovyCodeSource codeSource) throws CompilationFailedException { return parseClass(codeSource, codeSource.isCachable()); } /** * Parses the given code source into a Java class. If there is a class file * for the given code source, then no parsing is done, instead the cached class is returned. * * @param shouldCacheSource if true then the generated class will be stored in the source cache * @return the main class defined in the given script */ public Class parseClass(GroovyCodeSource codeSource, boolean shouldCacheSource) throws CompilationFailedException { Class answer; synchronized (sourceCache) { answer = sourceCache.get(codeSource.getName()); if (answer != null) return answer; if (shouldCacheSource) { answer = doParseClass(codeSource); sourceCache.put(codeSource.getName(), answer); } } if (!shouldCacheSource) answer = doParseClass(codeSource); return answer; } private Class doParseClass(GroovyCodeSource codeSource) { validate(codeSource); Class answer; // Was neither already loaded nor compiling, so compile and add to cache. CompilationUnit unit = createCompilationUnit(config, codeSource.getCodeSource()); SourceUnit su = null; if (codeSource.getFile() == null) { su = unit.addSource(codeSource.getName(), codeSource.getScriptText()); } else { su = unit.addSource(codeSource.getFile()); } ClassCollector collector = createCollector(unit, su); unit.setClassgenCallback(collector); int goalPhase = Phases.CLASS_GENERATION; if (config != null && config.getTargetDirectory() != null) goalPhase = Phases.OUTPUT; unit.compile(goalPhase); answer = collector.generatedClass; String mainClass = su.getAST().getMainClassName(); for (Object o : collector.getLoadedClasses()) { Class clazz = (Class) o; String clazzName = clazz.getName(); definePackage(clazzName); setClassCacheEntry(clazz); if (clazzName.equals(mainClass)) answer = clazz; } return answer; } private void validate(GroovyCodeSource codeSource) { if (codeSource.getFile() == null) { if (codeSource.getScriptText() == null) { throw new IllegalArgumentException("Script text to compile cannot be null!"); } } } private void definePackage(String className) { int i = className.lastIndexOf('.'); if (i != -1) { String pkgName = className.substring(0, i); java.lang.Package pkg = getPackage(pkgName); if (pkg == null) { definePackage(pkgName, null, null, null, null, null, null, null); } } } /** * gets the currently used classpath. * * @return a String[] containing the file information of the urls * @see #getURLs() */ protected String[] getClassPath() { //workaround for Groovy-835 URL[] urls = getURLs(); String[] ret = new String[urls.length]; for (int i = 0; i < ret.length; i++) { ret[i] = urls[i].getFile(); } return ret; } /** * expands the classpath * * @param pathList an empty list that will contain the elements of the classpath * @param classpath the classpath specified as a single string * @deprecated */ protected void expandClassPath(List pathList, String base, String classpath, boolean isManifestClasspath) { throw new DeprecationException("the method groovy.lang.GroovyClassLoader#expandClassPath(List,String,String,boolean) is no longer used internally and removed"); } /** * A helper method to allow bytecode to be loaded. spg changed name to * defineClass to make it more consistent with other ClassLoader methods * * @deprecated */ protected Class defineClass(String name, byte[] bytecode, ProtectionDomain domain) { throw new DeprecationException("the method groovy.lang.GroovyClassLoader#defineClass(String,byte[],ProtectionDomain) is no longer used internally and removed"); } protected PermissionCollection getPermissions(CodeSource codeSource) { PermissionCollection perms; try { perms = super.getPermissions(codeSource); } catch (SecurityException e) { // We lied about our CodeSource and that makes URLClassLoader unhappy. perms = new Permissions(); } ProtectionDomain myDomain = AccessController.doPrivileged(new PrivilegedAction<ProtectionDomain>() { public ProtectionDomain run() { return getClass().getProtectionDomain(); } }); PermissionCollection myPerms = myDomain.getPermissions(); if (myPerms != null) { for (Enumeration<Permission> elements = myPerms.elements(); elements.hasMoreElements();) { perms.add(elements.nextElement()); } } perms.setReadOnly(); return perms; } public static class InnerLoader extends GroovyClassLoader { private final GroovyClassLoader delegate; private final long timeStamp; public InnerLoader(GroovyClassLoader delegate) { super(delegate); this.delegate = delegate; timeStamp = System.currentTimeMillis(); } public void addClasspath(String path) { delegate.addClasspath(path); } public void clearCache() { delegate.clearCache(); } public URL findResource(String name) { return delegate.findResource(name); } public Enumeration findResources(String name) throws IOException { return delegate.findResources(name); } public Class[] getLoadedClasses() { return delegate.getLoadedClasses(); } public URL getResource(String name) { return delegate.getResource(name); } public InputStream getResourceAsStream(String name) { return delegate.getResourceAsStream(name); } public GroovyResourceLoader getResourceLoader() { return delegate.getResourceLoader(); } public URL[] getURLs() { return delegate.getURLs(); } public Class loadClass(String name, boolean lookupScriptFiles, boolean preferClassOverScript, boolean resolve) throws ClassNotFoundException, CompilationFailedException { Class c = findLoadedClass(name); if (c != null) return c; return delegate.loadClass(name, lookupScriptFiles, preferClassOverScript, resolve); } public Class parseClass(GroovyCodeSource codeSource, boolean shouldCache) throws CompilationFailedException { return delegate.parseClass(codeSource, shouldCache); } public void setResourceLoader(GroovyResourceLoader resourceLoader) { delegate.setResourceLoader(resourceLoader); } public void addURL(URL url) { delegate.addURL(url); } public long getTimeStamp() { return timeStamp; } } /** * creates a new CompilationUnit. If you want to add additional * phase operations to the CompilationUnit (for example to inject * additional methods, variables, fields), then you should overwrite * this method. * * @param config the compiler configuration, usually the same as for this class loader * @param source the source containing the initial file to compile, more files may follow during compilation * @return the CompilationUnit */ protected CompilationUnit createCompilationUnit(CompilerConfiguration config, CodeSource source) { return new CompilationUnit(config, source, this); } /** * creates a ClassCollector for a new compilation. * * @param unit the compilationUnit * @param su the SourceUnit * @return the ClassCollector */ protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) { InnerLoader loader = AccessController.doPrivileged(new PrivilegedAction<InnerLoader>() { public InnerLoader run() { return new InnerLoader(GroovyClassLoader.this); } }); return new ClassCollector(loader, unit, su); } public static class ClassCollector extends CompilationUnit.ClassgenCallback { private Class generatedClass; private final GroovyClassLoader cl; private final SourceUnit su; private final CompilationUnit unit; private final Collection<Class> loadedClasses; protected ClassCollector(InnerLoader cl, CompilationUnit unit, SourceUnit su) { this.cl = cl; this.unit = unit; this.loadedClasses = new ArrayList<Class>(); this.su = su; } public GroovyClassLoader getDefiningClassLoader() { return cl; } protected Class createClass(byte[] code, ClassNode classNode) { GroovyClassLoader cl = getDefiningClassLoader(); Class theClass = cl.defineClass(classNode.getName(), code, 0, code.length, unit.getAST().getCodeSource()); this.loadedClasses.add(theClass); if (generatedClass == null) { ModuleNode mn = classNode.getModule(); SourceUnit msu = null; if (mn != null) msu = mn.getContext(); ClassNode main = null; if (mn != null) main = (ClassNode) mn.getClasses().get(0); if (msu == su && main == classNode) generatedClass = theClass; } return theClass; } protected Class onClassNode(ClassWriter classWriter, ClassNode classNode) { byte[] code = classWriter.toByteArray(); return createClass(code, classNode); } public void call(ClassVisitor classWriter, ClassNode classNode) { onClassNode((ClassWriter) classWriter, classNode); } public Collection getLoadedClasses() { return this.loadedClasses; } } /** * open up the super class define that takes raw bytes */ public Class defineClass(String name, byte[] b) { return super.defineClass(name, b, 0, b.length); } /** * loads a class from a file or a parent classloader. * This method does call loadClass(String, boolean, boolean, boolean) * with the last parameter set to false. * * @throws CompilationFailedException if compilation was not successful */ public Class loadClass(final String name, boolean lookupScriptFiles, boolean preferClassOverScript) throws ClassNotFoundException, CompilationFailedException { return loadClass(name, lookupScriptFiles, preferClassOverScript, false); } /** * gets a class from the class cache. This cache contains only classes loaded through * this class loader or an InnerLoader instance. If no class is stored for a * specific name, then the method should return null. * * @param name of the class * @return the class stored for the given name * @see #removeClassCacheEntry(String) * @see #setClassCacheEntry(Class) * @see #clearCache() */ protected Class getClassCacheEntry(String name) { if (name == null) return null; synchronized (classCache) { return classCache.get(name); } } /** * sets an entry in the class cache. * * @param cls the class * @see #removeClassCacheEntry(String) * @see #getClassCacheEntry(String) * @see #clearCache() */ protected void setClassCacheEntry(Class cls) { synchronized (classCache) { classCache.put(cls.getName(), cls); } } /** * removes a class from the class cache. * * @param name of the class * @see #getClassCacheEntry(String) * @see #setClassCacheEntry(Class) * @see #clearCache() */ protected void removeClassCacheEntry(String name) { synchronized (classCache) { classCache.remove(name); } } /** * adds a URL to the classloader. * * @param url the new classpath element */ public void addURL(URL url) { super.addURL(url); } /** * Indicates if a class is recompilable. Recompilable means, that the classloader * will try to locate a groovy source file for this class and then compile it again, * adding the resulting class as entry to the cache. Giving null as class is like a * recompilation, so the method should always return true here. Only classes that are * implementing GroovyObject are compilable and only if the timestamp in the class * is lower than Long.MAX_VALUE. * <p/> * NOTE: First the parent loaders will be asked and only if they don't return a * class the recompilation will happen. Recompilation also only happen if the source * file is newer. * * @param cls the class to be tested. If null the method should return true * @return true if the class should be compiled again * @see #isSourceNewer(URL, Class) */ protected boolean isRecompilable(Class cls) { if (cls == null) return true; if (cls.getClassLoader() == this) return false; if (recompile == null && !config.getRecompileGroovySource()) return false; if (recompile != null && !recompile) return false; if (!GroovyObject.class.isAssignableFrom(cls)) return false; long timestamp = getTimeStamp(cls); if (timestamp == Long.MAX_VALUE) return false; return true; } /** * sets if the recompilation should be enable. There are 3 possible * values for this. Any value different than null overrides the * value from the compiler configuration. true means to recompile if needed * false means to never recompile. * * @param mode the recompilation mode * @see CompilerConfiguration */ public void setShouldRecompile(Boolean mode) { recompile = mode; } /** * gets the currently set recompilation mode. null means, the * compiler configuration is used. False means no recompilation and * true means that recompilation will be done if needed. * * @return the recompilation mode */ public Boolean isShouldRecompile() { return recompile; } /** * loads a class from a file or a parent classloader. * * @param name of the class to be loaded * @param lookupScriptFiles if false no lookup at files is done at all * @param preferClassOverScript if true the file lookup is only done if there is no class * @param resolve @see ClassLoader#loadClass(java.lang.String, boolean) * @return the class found or the class created from a file lookup * @throws ClassNotFoundException if the class could not be found * @throws CompilationFailedException if the source file could not be compiled */ public Class loadClass(final String name, boolean lookupScriptFiles, boolean preferClassOverScript, boolean resolve) throws ClassNotFoundException, CompilationFailedException { // look into cache Class cls = getClassCacheEntry(name); // enable recompilation? boolean recompile = isRecompilable(cls); if (!recompile) return cls; // try parent loader ClassNotFoundException last = null; try { Class parentClassLoaderClass = super.loadClass(name, resolve); // always return if the parent loader was successful if (cls != parentClassLoaderClass) return parentClassLoaderClass; } catch (ClassNotFoundException cnfe) { last = cnfe; } catch (NoClassDefFoundError ncdfe) { if (ncdfe.getMessage().indexOf("wrong name") > 0) { last = new ClassNotFoundException(name); } else { throw ncdfe; } } // check security manager SecurityManager sm = System.getSecurityManager(); if (sm != null) { String className = name.replace('/', '.'); int i = className.lastIndexOf('.'); // no checks on the sun.reflect classes for reflection speed-up // in particular ConstructorAccessorImpl, MethodAccessorImpl, FieldAccessorImpl and SerializationConstructorAccessorImpl // which are generated at runtime by the JDK if (i != -1 && !className.startsWith("sun.reflect.")) { sm.checkPackageAccess(className.substring(0, i)); } } // prefer class if no recompilation if (cls != null && preferClassOverScript) return cls; // at this point the loading from a parent loader failed // and we want to recompile if needed. if (lookupScriptFiles) { // try groovy file try { // check if recompilation already happened. final Class classCacheEntry = getClassCacheEntry(name); if (classCacheEntry != cls) return classCacheEntry; URL source = resourceLoader.loadGroovySource(name); // if recompilation fails, we want cls==null Class oldClass = cls; cls = null; cls = recompile(source, name, oldClass); } catch (IOException ioe) { last = new ClassNotFoundException("IOException while opening groovy source: " + name, ioe); } finally { if (cls == null) { removeClassCacheEntry(name); } else { setClassCacheEntry(cls); } } } if (cls == null) { // no class found, there should have been an exception before now if (last == null) throw new AssertionError(true); throw last; } return cls; } /** * (Re)Compiles the given source. * This method starts the compilation of a given source, if * the source has changed since the class was created. For * this isSourceNewer is called. * * @param source the source pointer for the compilation * @param className the name of the class to be generated * @param oldClass a possible former class * @return the old class if the source wasn't new enough, the new class else * @throws CompilationFailedException if the compilation failed * @throws IOException if the source is not readable * @see #isSourceNewer(URL, Class) */ protected Class recompile(URL source, String className, Class oldClass) throws CompilationFailedException, IOException { if (source != null) { // found a source, compile it if newer if ((oldClass != null && isSourceNewer(source, oldClass)) || (oldClass == null)) { synchronized (sourceCache) { sourceCache.remove(className); return parseClass(source.openStream(), className); } } } return oldClass; } /** * Implemented here to check package access prior to returning an * already loaded class. * * @throws CompilationFailedException if the compilation failed * @throws ClassNotFoundException if the class was not found * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean) */ protected Class loadClass(final String name, boolean resolve) throws ClassNotFoundException { return loadClass(name, true, true, resolve); } /** * gets the time stamp of a given class. For groovy * generated classes this usually means to return the value * of the static field __timeStamp. If the parameter doesn't * have such a field, then Long.MAX_VALUE is returned * * @param cls the class * @return the time stamp */ protected long getTimeStamp(Class cls) { return Verifier.getTimestamp(cls); } /** * This method will take a file name and try to "decode" any URL encoded characters. For example * if the file name contains any spaces this method call will take the resulting %20 encoded values * and convert them to spaces. * <p/> * This method was added specifically to fix defect: Groovy-1787. The defect involved a situation * where two scripts were sitting in a directory with spaces in its name. The code would fail * when the class loader tried to resolve the file name and would choke on the URLEncoded space values. */ private String decodeFileName(String fileName) { String decodedFile = fileName; try { decodedFile = URLDecoder.decode(fileName, "UTF-8"); } catch (UnsupportedEncodingException e) { System.err.println("Encountered an invalid encoding scheme when trying to use URLDecoder.decode() inside of the GroovyClassLoader.decodeFileName() method. Returning the unencoded URL."); System.err.println("Please note that if you encounter this error and you have spaces in your directory you will run into issues. Refer to GROOVY-1787 for description of this bug."); } return decodedFile; } private boolean isFile(URL ret) { return ret != null && ret.getProtocol().equals("file"); } private File getFileForUrl(URL ret, String filename) { String fileWithoutPackage = filename; if (fileWithoutPackage.indexOf('/') != -1) { int index = fileWithoutPackage.lastIndexOf('/'); fileWithoutPackage = fileWithoutPackage.substring(index + 1); } return fileReallyExists(ret, fileWithoutPackage); } private File fileReallyExists(URL ret, String fileWithoutPackage) { File path = new File(decodeFileName(ret.getFile())).getParentFile(); if (path.exists() && path.isDirectory()) { File file = new File(path, fileWithoutPackage); if (file.exists()) { // file.exists() might be case insensitive. Let's do // case sensitive match for the filename File parent = file.getParentFile(); for (String child : parent.list()) { if (child.equals(fileWithoutPackage)) return file; } } } //file does not exist! return null; } private URL getSourceFile(String name, String extension) { String filename = name.replace('.', '/') + "." + extension; URL ret = getResource(filename); if (isFile(ret) && getFileForUrl(ret, filename) == null) return null; return ret; } private URL getSourceFile(String name) { return getSourceFile(name, config.getDefaultScriptExtension()); } /** * Decides if the given source is newer than a class. * * @param source the source we may want to compile * @param cls the former class * @return true if the source is newer, false else * @throws IOException if it is not possible to open an * connection for the given source * @see #getTimeStamp(Class) */ protected boolean isSourceNewer(URL source, Class cls) throws IOException { long lastMod; // Special handling for file:// protocol, as getLastModified() often reports // incorrect results (-1) if (isFile(source)) { // Coerce the file URL to a File String path = source.getPath().replace('/', File.separatorChar).replace('|', ':'); File file = new File(path); lastMod = file.lastModified(); } else { URLConnection conn = source.openConnection(); lastMod = conn.getLastModified(); conn.getInputStream().close(); } long classTime = getTimeStamp(cls); return classTime + config.getMinimumRecompilationInterval() < lastMod; } /** * adds a classpath to this classloader. * * @param path is a jar file or a directory. * @see #addURL(URL) */ public void addClasspath(final String path) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { try { File f = new File(path); URL newURL = f.toURI().toURL(); URL[] urls = getURLs(); for (URL url : urls) { if (url.equals(newURL)) return null; } addURL(newURL); } catch (MalformedURLException e) { //TODO: fail through ? } return null; } }); } /** * <p>Returns all Groovy classes loaded by this class loader. * * @return all classes loaded by this class loader */ public Class[] getLoadedClasses() { synchronized (classCache) { final Collection<Class> values = classCache.values(); return values.toArray(new Class[values.size()]); } } /** * Removes all classes from the class cache. * * @see #getClassCacheEntry(String) * @see #setClassCacheEntry(Class) * @see #removeClassCacheEntry(String) */ public void clearCache() { synchronized (classCache) { classCache.clear(); } synchronized (sourceCache) { sourceCache.clear(); } } }