All Downloads are FREE. Search and download functionalities are using the official Maven repository.

groovy.lang.GroovyClassLoader Maven / Gradle / Ivy

There is a newer version: 1.0.1437
Show newest version
/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.InnerClassNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.classgen.Verifier;
import org.codehaus.groovy.control.BytecodeProcessor;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.Phases;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.runtime.IOGroovyMethods;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * 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 James Strachan
 * @author Guillaume Laforge
 * @author Steve Goetze
 * @author Bing Ran
 * @author Scott Stirling
 * @author Jochen Theodorou
 */
public class GroovyClassLoader extends URLClassLoader {

    private static final URL[] EMPTY_URL_ARRAY = new URL[0];

    /**
     * this cache contains the loaded classes or PARSING, if the class is currently parsed
     */
    protected final Map classCache = new HashMap();

    /**
     * This cache contains mappings of file name to class. It is used
     * to bypass compilation.
     */
    protected final Map sourceCache = new HashMap();
    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() {
                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(EMPTY_URL_ARRAY, 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.
     * 

* 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); definePackageInternal(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() { 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"); } 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. */ @Deprecated 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() { public GroovyCodeSource run() { try { String scriptText = config.getSourceEncoding() != null ? IOGroovyMethods.getText(in, config.getSourceEncoding()) : IOGroovyMethods.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 { synchronized (sourceCache) { Class answer = sourceCache.get(codeSource.getName()); if (answer != null) return answer; answer = doParseClass(codeSource); if (shouldCacheSource) sourceCache.put(codeSource.getName(), answer); 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()); if (recompile!=null && recompile || recompile==null && config.getRecompileGroovySource()) { unit.addFirstPhaseOperation(TimestampAdder.INSTANCE, CompilePhase.CLASS_GENERATION.getPhaseNumber()); } SourceUnit su = null; File file = codeSource.getFile(); if (file != null) { su = unit.addSource(file); } else { URL url = codeSource.getURL(); if (url != null) { su = unit.addSource(url); } else { su = unit.addSource(codeSource.getName(), codeSource.getScriptText()); } } 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(); definePackageInternal(clazzName); setClassCacheEntry(clazz); if (clazzName.equals(mainClass)) answer = clazz; } return answer; } private static void validate(GroovyCodeSource codeSource) { if (codeSource.getFile() == null) { if (codeSource.getScriptText() == null) { throw new IllegalArgumentException("Script text to compile cannot be null!"); } } } private void definePackageInternal(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; } protected PermissionCollection getPermissions(CodeSource codeSource) { PermissionCollection perms; try { 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() { public ProtectionDomain run() { return getClass().getProtectionDomain(); } }); PermissionCollection myPerms = myDomain.getPermissions(); if (myPerms != null) { for (Enumeration elements = myPerms.elements(); elements.hasMoreElements();) { perms.add(elements.nextElement()); } } } catch (Throwable e) { // We lied about our CodeSource and that makes URLClassLoader unhappy. perms = new Permissions(); } 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() { 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 loadedClasses; protected ClassCollector(InnerLoader cl, CompilationUnit unit, SourceUnit su) { this.cl = cl; this.unit = unit; this.loadedClasses = new ArrayList(); this.su = su; } public GroovyClassLoader getDefiningClassLoader() { return cl; } protected Class createClass(byte[] code, ClassNode classNode) { BytecodeProcessor bytecodePostprocessor = unit.getConfiguration().getBytecodePostprocessor(); byte[] fcode = code; if (bytecodePostprocessor!=null) { fcode = bytecodePostprocessor.processBytecode(classNode.getName(), fcode); } GroovyClassLoader cl = getDefiningClassLoader(); Class theClass = cl.defineClass(classNode.getName(), fcode, 0, fcode.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. *

* 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 {@link java.lang.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) { String name = source.toExternalForm(); sourceCache.remove(name); if (isFile(source)) { try { return parseClass(new GroovyCodeSource(new File(source.toURI()), config.getSourceEncoding())); } catch (URISyntaxException e) { // do nothing and fall back to the other version } } return parseClass(source.openStream(), name); } } } return oldClass; } @Override public Class loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } /** * 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. *

* 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 static 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 static boolean isFile(URL ret) { return ret != null && ret.getProtocol().equals("file"); } private static 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 static File fileReallyExists(URL ret, String fileWithoutPackage) { File path; try { /* fix for GROOVY-5809 */ path = new File(ret.toURI()); } catch(URISyntaxException e) { path = new File(decodeFileName(ret.getFile())); } path = path.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; } /** * 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 // See ClassNodeResolver.isSourceNewer for another method that replaces '|' with ':'. // WTF: Why is this done and where is it documented? 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() { public Void run() { URI newURI; try { newURI = new URI(path); // check if we can create a URL from that URI newURI.toURL(); } catch (URISyntaxException e) { // the URI has a false format, so lets try it with files ... newURI=new File(path).toURI(); } catch (MalformedURLException e) { // the URL has a false format, so lets try it with files ... newURI=new File(path).toURI(); } catch (IllegalArgumentException e) { // the URL is not absolute, so lets try it with files ... newURI=new File(path).toURI(); } URL[] urls = getURLs(); for (URL url : urls) { // Do not use URL.equals. It uses the network to resolve names and compares ip addresses! // That is a violation of RFC and just plain evil. // http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html // http://docs.oracle.com/javase/7/docs/api/java/net/URL.html#equals(java.lang.Object) // "Since hosts comparison requires name resolution, this operation is a blocking operation. // Note: The defined behavior for equals is known to be inconsistent with virtual hosting in HTTP." try { if (newURI.equals(url.toURI())) return null; } catch (URISyntaxException e) { // fail fast! if we got a malformed URI the Classloader has to tell it throw new RuntimeException( e ); } } try { addURL(newURI.toURL()); } catch (MalformedURLException e) { // fail fast! if we got a malformed URL the Classloader has to tell it throw new RuntimeException( e ); } return null; } }); } /** *

Returns all Groovy classes loaded by this class loader. * * @return all classes loaded by this class loader */ public Class[] getLoadedClasses() { synchronized (classCache) { final Collection 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(); } } private static class TimestampAdder extends CompilationUnit.PrimaryClassNodeOperation implements Opcodes { private static final TimestampAdder INSTANCE = new TimestampAdder(); private TimestampAdder() {} protected void addTimeStamp(ClassNode node) { if (node.getDeclaredField(Verifier.__TIMESTAMP) == null) { // in case if verifier visited the call already FieldNode timeTagField = new FieldNode( Verifier.__TIMESTAMP, ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC, ClassHelper.long_TYPE, //"", node, new ConstantExpression(System.currentTimeMillis())); // alternatively, FieldNode timeTagField = SourceUnit.createFieldNode("public static final long __timeStamp = " + System.currentTimeMillis() + "L"); timeTagField.setSynthetic(true); node.addField(timeTagField); timeTagField = new FieldNode( Verifier.__TIMESTAMP__ + String.valueOf(System.currentTimeMillis()), ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC, ClassHelper.long_TYPE, //"", node, new ConstantExpression((long) 0)); // alternatively, FieldNode timeTagField = SourceUnit.createFieldNode("public static final long __timeStamp = " + System.currentTimeMillis() + "L"); timeTagField.setSynthetic(true); node.addField(timeTagField); } } @Override public void call(final SourceUnit source, final GeneratorContext context, final ClassNode classNode) throws CompilationFailedException { if ((classNode.getModifiers() & Opcodes.ACC_INTERFACE) > 0) { // does not apply on interfaces return; } if (!(classNode instanceof InnerClassNode)) { addTimeStamp(classNode); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy