You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2017/12/20 00:56:36 UTC
[39/49] groovy git commit: Move source files to proper packages
http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/GrabExclude.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/lang/GrabExclude.java b/src/main/groovy/groovy/lang/GrabExclude.java
new file mode 100644
index 0000000..eb5a50d
--- /dev/null
+++ b/src/main/groovy/groovy/lang/GrabExclude.java
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+package groovy.lang;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Used to exclude an indirectly referenced artifact (a transitive dependency) from the classpath.
+ * <p>
+ * Examples:<br>
+ * {@code @GrabExclude(group='mysql', module='mysql-connector-java') // group/module form}<br>
+ * {@code @GrabExclude('mysql:mysql-connector-java') // compact form}<br>
+ * <p>
+ * Further information about customising grape behavior can be found on the Grape documentation page:
+ * <a href="http://groovy-lang.org/grape.html">http://groovy-lang.org/grape.html</a>.
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target({
+ ElementType.CONSTRUCTOR,
+ ElementType.FIELD,
+ ElementType.LOCAL_VARIABLE,
+ ElementType.METHOD,
+ ElementType.PARAMETER,
+ ElementType.TYPE})
+public @interface GrabExclude {
+
+ /**
+ * The organisation or group, e.g.: "org.apache.ant"; required unless the compact form is used.
+ */
+ String group() default "";
+
+ /**
+ * The module or artifact, e.g.: "ant-junit"; required unless the compact form is used.
+ */
+ String module() default "";
+
+ /**
+ * Allows you to specify the group (organisation) and the module (artifact) in one of two compact convenience formats,
+ * e.g.: <code>@GrabExclude('org.apache.ant:ant-junit')</code> or <code>@GrabExclude('org.apache.ant#ant-junit')</code>
+ */
+ String value() default "";
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/GrabResolver.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/lang/GrabResolver.java b/src/main/groovy/groovy/lang/GrabResolver.java
new file mode 100644
index 0000000..cf8d0ac
--- /dev/null
+++ b/src/main/groovy/groovy/lang/GrabResolver.java
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+package groovy.lang;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Used to add a repository for resolving Grape dependencies.
+ * <p>
+ * For example:
+ * <pre>
+ * {@code @GrabResolver}(name='restlet.org', root='http://maven.restlet.org')
+ * {@code @Grab}(group='org.restlet', module='org.restlet', version='1.1.6')
+ * class MyRestlet extends org.restlet.Restlet {
+ * // ...
+ * }
+ * </pre>
+ * By default, the Grapes subsystem uses an Ivy chained resolver. Each resolver
+ * added using {@code @GrabResolver} is appended to the chain. By default, the grape
+ * subsystem is shared globally, so added resolvers will become available for any subsequent
+ * grab calls. Dependency resolution follows Ivy's artifact resolution which tries
+ * to resolve artifacts in the order specified in the chain of resolvers.
+ * <p>
+ * Further information about customising grape behavior can be found on the Grape documentation page:
+ * <a href="http://groovy-lang.org/grape.html">http://groovy-lang.org/grape.html</a>.
+ *
+ * @author Merlyn Albery-Speyer
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target({
+ ElementType.CONSTRUCTOR,
+ ElementType.FIELD,
+ ElementType.LOCAL_VARIABLE,
+ ElementType.METHOD,
+ ElementType.PARAMETER,
+ ElementType.TYPE})
+public @interface GrabResolver {
+ /**
+ * Allows a shorthand form which sets the name and root to this value.
+ * Must not be used if name() or root() is non-empty.
+ */
+ String value() default "";
+
+ /**
+ * A meaningful name for a repo containing the grape/artifact.
+ * A non-empty value is required unless value() is used.
+ */
+ String name() default "";
+
+ /**
+ * The URL for a repo containing the grape/artifact.
+ * A non-empty value is required unless value() is used.
+ */
+ String root() default "";
+
+ /**
+ * Defaults to Maven2 compatibility. Set false for Ivy only compatibility.
+ */
+ boolean m2Compatible() default true;
+
+ /**
+ * By default, when a {@code @GrabResolver} annotation is used, a {@code Grape.addResolver()} call is added
+ * to the static initializers of the class the annotatable node appears in.
+ * If you wish to disable this, add {@code initClass=false} to the annotation.
+ */
+ boolean initClass() default true;
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/Grapes.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/lang/Grapes.java b/src/main/groovy/groovy/lang/Grapes.java
new file mode 100644
index 0000000..8719c74
--- /dev/null
+++ b/src/main/groovy/groovy/lang/Grapes.java
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+package groovy.lang;
+
+/**
+ * Sometimes we will need more than one grab per class, but we can only add
+ * one annotation type per annotatable node. This class allows for multiple
+ * grabs to be added.
+ * <p>
+ * For example:
+ * <p>
+ * <pre>
+ * {@code @Grapes([@Grab(module='m1'), @Grab(module='m2')])}
+ * class AnnotatedClass { ... }
+ * </pre>
+ * <p>
+ * You can override an implicit transitive dependency by providing an explicit one.
+ * E.g. htmlunit 2.6 normally uses xerces 2.9.1 but you can get 2.9.0 as follows:
+ * <pre>
+ * {@code @Grapes}([
+ * {@code @Grab}('net.sourceforge.htmlunit:htmlunit:2.6'),
+ * {@code @Grab}('xerces#xercesImpl;2.9.0')
+ * ])
+ * </pre>
+ * Obviously, only do this if you understand the consequences.
+ * <p>
+ * You can also remove transitive dependencies altogether (provided you
+ * know you don't need them) using {@code @GrabExclude}.
+ * For example, here is how we would not grab the {@code logkit} and
+ * {@code avalon-framework} transitive dependencies for Apache POI:
+ * <pre>
+ * {@code @Grapes}([
+ * {@code @Grab}("org.apache.poi#poi;3.5-beta6"),
+ * {@code @GrabExclude}("logkit:logkit"),
+ * {@code @GrabExclude}("avalon-framework#avalon-framework")
+ * ])
+ * import org.apache.poi.hssf.util.CellReference
+ * assert new CellReference(0, 0, false, false).formatAsString() == 'A1'
+ * assert new CellReference(1, 3).formatAsString() == '$D$2'
+ * </pre>
+ * It is also sometimes also useful to use {@code @GrabConfig} to further adjust how dependencies
+ * are grabbed. See {@code @GrabConfig} for further information.
+ */
+public @interface Grapes {
+ Grab[] value();
+
+ /**
+ * This will be pushed into the child grab annotations if the value is not
+ * set in the child annotation already.
+ * <p>
+ * This results in an effective change in the default value, which each @Grab
+ * can still override
+ */
+ boolean initClass() default true;
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/GroovyCallable.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/lang/GroovyCallable.java b/src/main/groovy/groovy/lang/GroovyCallable.java
new file mode 100644
index 0000000..e425c92
--- /dev/null
+++ b/src/main/groovy/groovy/lang/GroovyCallable.java
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+package groovy.lang;
+
+import java.util.concurrent.Callable;
+
+/**
+ * A special "marker" style interface allowing Groovy classes to implement both
+ * Runnable and Callable yet give preference to Runnable (for backwards compatibility)
+ * for APIs having both Runnable and Callable methods. You should generally NOT use this
+ * method in your own code.
+ *
+ * @see java.util.concurrent.Callable
+ * @since 1.8.0
+ */
+public interface GroovyCallable<V> extends Callable<V> { }
http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/GroovyClassLoader.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/lang/GroovyClassLoader.java b/src/main/groovy/groovy/lang/GroovyClassLoader.java
new file mode 100644
index 0000000..8581879
--- /dev/null
+++ b/src/main/groovy/groovy/lang/GroovyClassLoader.java
@@ -0,0 +1,1099 @@
+/*
+ * 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 groovy.util.CharsetToolkit;
+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.codehaus.groovy.runtime.InvokerHelper;
+import org.codehaus.groovy.runtime.memoize.ConcurrentCommonCache;
+import org.codehaus.groovy.runtime.memoize.EvictableCache;
+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.InputStreamReader;
+import java.io.Reader;
+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.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 <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>
+ */
+public class GroovyClassLoader extends URLClassLoader {
+ private static final URL[] EMPTY_URL_ARRAY = new URL[0];
+ private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
+
+ /**
+ * this cache contains the loaded classes or PARSING, if the class is currently parsed
+ */
+ protected final EvictableCache<String, Class> classCache = new ConcurrentCommonCache<String, Class>();
+
+ /**
+ * This cache contains mappings of file name to class. It is used
+ * to bypass compilation.
+ */
+ protected final ConcurrentCommonCache<String, Class> sourceCache = new ConcurrentCommonCache<String, Class>();
+
+ private final CompilerConfiguration config;
+ private String sourceEncoding;
+ 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(EMPTY_URL_ARRAY, parent);
+ if (config == null) config = CompilerConfiguration.DEFAULT;
+ this.config = config;
+ if (useConfigurationClasspath) {
+ for (String path : config.getClasspath()) {
+ this.addClasspath(path);
+ }
+ }
+
+ initSourceEncoding(config);
+ }
+
+ private void initSourceEncoding(CompilerConfiguration config) {
+ sourceEncoding = config.getSourceEncoding();
+ if (null == sourceEncoding) {
+ // Keep the same default source encoding with the one used by #parseClass(InputStream, String)
+ // TODO should we use org.codehaus.groovy.control.CompilerConfiguration.DEFAULT_SOURCE_ENCODING instead?
+ sourceEncoding = CharsetToolkit.getDefaultSystemCharset().name();
+ }
+ }
+
+ /**
+ * 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.
+ * <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);
+ 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<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");
+ }
+
+ public synchronized String generateScriptName() {
+ scriptNameCounter++;
+ return "script" + scriptNameCounter + ".groovy";
+ }
+
+ public Class parseClass(final Reader reader, final String fileName) throws CompilationFailedException {
+ GroovyCodeSource gcs = AccessController.doPrivileged(new PrivilegedAction<GroovyCodeSource>() {
+ public GroovyCodeSource run() {
+ try {
+ String scriptText = IOGroovyMethods.getText(reader);
+ return new GroovyCodeSource(scriptText, fileName, "/groovy/script");
+ } catch (IOException e) {
+ throw new RuntimeException("Impossible to read the content of the reader for file named: " + fileName, e);
+ }
+ }
+ });
+ return parseClass(gcs);
+ }
+
+ /**
+ * @deprecated Prefer using methods taking a Reader rather than an InputStream to avoid wrong encoding issues.
+ * Use {@link #parseClass(Reader, String) parseClass} instead
+ */
+ @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<GroovyCodeSource>() {
+ 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(final GroovyCodeSource codeSource, boolean shouldCacheSource) throws CompilationFailedException {
+ return sourceCache.getAndPut(
+ codeSource.getName(),
+ new EvictableCache.ValueProvider<String, Class>() {
+ @Override
+ public Class provide(String key) {
+ return doParseClass(codeSource);
+ }
+ },
+ shouldCacheSource
+ );
+ }
+
+ 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<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());
+ }
+ }
+ } 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<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) {
+ 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) {
+ 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) {
+ 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) {
+ 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 {@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)) {
+ String name = source.toExternalForm();
+
+ sourceCache.remove(name);
+
+ if (isFile(source)) {
+ try {
+ return parseClass(new GroovyCodeSource(new File(source.toURI()), sourceEncoding));
+ } catch (URISyntaxException e) {
+ // do nothing and fall back to the other version
+ }
+ }
+ return parseClass(new InputStreamReader(source.openStream(), sourceEncoding), 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.
+ * <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 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<Void>() {
+ 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;
+ }
+ });
+ }
+
+ /**
+ * <p>Returns all Groovy classes loaded by this class loader.
+ *
+ * @return all classes loaded by this class loader
+ */
+ public Class[] getLoadedClasses() {
+ return classCache.values().toArray(EMPTY_CLASS_ARRAY);
+ }
+
+ /**
+ * Removes all classes from the class cache.
+ * <p>
+ * In addition to internal caches this method also clears any
+ * previously set MetaClass information for the given set of
+ * classes being removed.
+ *
+ * @see #getClassCacheEntry(String)
+ * @see #setClassCacheEntry(Class)
+ * @see #removeClassCacheEntry(String)
+ */
+ public void clearCache() {
+ Map<String, Class> clearedClasses = classCache.clear();
+
+ sourceCache.clear();
+
+ for (Map.Entry<String, Class> entry : clearedClasses.entrySet()) {
+ // Another Thread may be using an instance of this class
+ // (for the first time) requiring a ClassInfo lock and
+ // classloading which would require a lock on classCache.
+ // The following locks on ClassInfo and to avoid deadlock
+ // should not be done with a classCache lock.
+ InvokerHelper.removeClass(entry.getValue());
+ }
+ }
+
+ /**
+ * Closes this GroovyClassLoader and clears any caches it maintains.
+ * <p>
+ * No use should be made of this instance after this method is
+ * invoked. Any classes that are already loaded are still accessible.
+ *
+ * @throws IOException
+ * @see URLClassLoader#close()
+ * @see #clearCache()
+ * @since 2.5.0
+ */
+ @Override
+ public void close() throws IOException {
+ super.close();
+ clearCache();
+ }
+
+ 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);
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/GroovyCodeSource.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/lang/GroovyCodeSource.java b/src/main/groovy/groovy/lang/GroovyCodeSource.java
new file mode 100644
index 0000000..a5a2bb1
--- /dev/null
+++ b/src/main/groovy/groovy/lang/GroovyCodeSource.java
@@ -0,0 +1,266 @@
+/*
+ * 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.
+ */
+package groovy.lang;
+
+import groovy.security.GroovyCodeSourcePermission;
+import groovy.util.CharsetToolkit;
+import org.codehaus.groovy.runtime.IOGroovyMethods;
+import org.codehaus.groovy.runtime.ResourceGroovyMethods;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.cert.Certificate;
+import java.util.Objects;
+
+/**
+ * CodeSource wrapper class that allows specific security policies to be associated with a class
+ * compiled from groovy source.
+ *
+ * @author Steve Goetze
+ * @author Guillaume Laforge
+ * @author Merlyn Albery-Speyer
+ */
+public class GroovyCodeSource {
+
+ /**
+ * The codeSource to be given the generated class. This can be used by policy file
+ * grants to administer security.
+ */
+ private CodeSource codeSource;
+
+ /**
+ * The name given to the generated class
+ */
+ private String name;
+
+ /**
+ * The groovy source to be compiled and turned into a class
+ */
+ private String scriptText;
+
+ /**
+ * The certificates used to sign the items from the codesource
+ */
+ Certificate[] certs;
+
+ private boolean cachable;
+
+ private File file;
+
+ private URL url;
+
+ public GroovyCodeSource(String script, String name, String codeBase) {
+ this.name = name;
+ this.scriptText = script;
+ this.codeSource = createCodeSource(codeBase);
+ this.cachable = true;
+ }
+
+ /**
+ * Construct a GroovyCodeSource for an inputStream of groovyCode that has an
+ * unknown provenance -- meaning it didn't come from a File or a URL (e.g. a String).
+ * The supplied codeBase will be used to construct a File URL that should match up
+ * with a java Policy entry that determines the grants to be associated with the
+ * class that will be built from the InputStream.
+ * <p>
+ * The permission groovy.security.GroovyCodeSourcePermission will be used to determine if the given codeBase
+ * may be specified. That is, the current Policy set must have a GroovyCodeSourcePermission that implies
+ * the codeBase, or an exception will be thrown. This is to prevent callers from hijacking
+ * existing codeBase policy entries unless explicitly authorized by the user.
+ */
+ public GroovyCodeSource(Reader reader, String name, String codeBase) {
+ this.name = name;
+ this.codeSource = createCodeSource(codeBase);
+
+ try {
+ this.scriptText = IOGroovyMethods.getText(reader);
+ } catch (IOException e) {
+ throw new RuntimeException("Impossible to read the text content from that reader, for script: " + name + " with codeBase: " + codeBase, e);
+ }
+ }
+
+ public GroovyCodeSource(final File infile, final String encoding) throws IOException {
+ // avoid files which confuse us like ones with .. in path
+ final File file = new File(infile.getCanonicalPath());
+ if (!file.exists()) {
+ throw new FileNotFoundException(file.toString() + " (" + file.getAbsolutePath() + ")");
+ }
+ if (file.isDirectory()) {
+ throw new IllegalArgumentException(file.toString() + " (" + file.getAbsolutePath() + ") is a directory not a Groovy source file.");
+ }
+ try {
+ if (!file.canRead())
+ throw new RuntimeException(file.toString() + " can not be read. Check the read permission of the file \"" + file.toString() + "\" (" + file.getAbsolutePath() + ").");
+ }
+ catch (SecurityException e) {
+ throw e;
+ }
+
+ this.file = file;
+ this.cachable = true;
+ //The calls below require access to user.dir - allow here since getName() and getCodeSource() are
+ //package private and used only by the GroovyClassLoader.
+ try {
+ Object[] info = AccessController.doPrivileged(new PrivilegedExceptionAction<Object[]>() {
+ public Object[] run() throws IOException {
+ // retrieve the content of the file using the provided encoding
+ if (encoding != null) {
+ scriptText = ResourceGroovyMethods.getText(infile, encoding);
+ } else {
+ scriptText = ResourceGroovyMethods.getText(infile);
+ }
+
+ Object[] info = new Object[2];
+ URL url = file.toURI().toURL();
+ info[0] = url.toExternalForm();
+ //toURI().toURL() will encode, but toURL() will not.
+ info[1] = new CodeSource(url, (Certificate[]) null);
+ return info;
+ }
+ });
+
+ this.name = (String) info[0];
+ this.codeSource = (CodeSource) info[1];
+ } catch (PrivilegedActionException pae) {
+ Throwable cause = pae.getCause();
+ if (cause != null && cause instanceof IOException) {
+ throw (IOException) cause;
+ }
+ throw new RuntimeException("Could not construct CodeSource for file: " + file, cause);
+ }
+ }
+
+ /**
+ * @param infile the file to create a GroovyCodeSource for.
+ * @throws IOException if an issue arises opening and reading the file.
+ */
+ public GroovyCodeSource(final File infile) throws IOException {
+ this(infile, CharsetToolkit.getDefaultSystemCharset().name());
+ }
+
+ public GroovyCodeSource(URI uri) throws IOException {
+ this(uri.toURL());
+ }
+
+ public GroovyCodeSource(URL url) {
+ if (url == null) {
+ throw new RuntimeException("Could not construct a GroovyCodeSource from a null URL");
+ }
+ this.url = url;
+ // TODO: GROOVY-6561: GroovyMain got the name this way: script.substring(script.lastIndexOf("/") + 1)
+ this.name = url.toExternalForm();
+ this.codeSource = new CodeSource(url, (java.security.cert.Certificate[]) null);
+ try {
+ String contentEncoding = getContentEncoding(url);
+ if (contentEncoding != null) {
+ this.scriptText = ResourceGroovyMethods.getText(url, contentEncoding);
+ } else {
+ this.scriptText = ResourceGroovyMethods.getText(url); // falls-back on default encoding
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Impossible to read the text content from " + name, e);
+ }
+ }
+
+ /**
+ * TODO(jwagenleitner): remove or fix in future release
+ *
+ * According to the spec getContentEncoding() returns the Content-Encoding
+ * HTTP Header which typically carries values such as 'gzip' or 'deflate'
+ * and is not the character set encoding. For compatibility in 2.4.x,
+ * this behavior is retained but should be removed or fixed (parse
+ * charset from Content-Type header) in future releases.
+ *
+ * see GROOVY-8056 and https://github.com/apache/groovy/pull/500
+ */
+ private static String getContentEncoding(URL url) throws IOException {
+ URLConnection urlConnection = url.openConnection();
+ String encoding = urlConnection.getContentEncoding();
+ try {
+ IOGroovyMethods.closeQuietly(urlConnection.getInputStream());
+ } catch (IOException ignore) {
+ // For compatibility, ignore exceptions from getInputStream() call
+ }
+ return encoding;
+ }
+
+ CodeSource getCodeSource() {
+ return codeSource;
+ }
+
+ public String getScriptText() {
+ return scriptText;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public File getFile() {
+ return file;
+ }
+
+ public URL getURL() {
+ return url;
+ }
+
+ public void setCachable(boolean b) {
+ cachable = b;
+ }
+
+ public boolean isCachable() {
+ return cachable;
+ }
+
+ private static CodeSource createCodeSource(final String codeBase) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(new GroovyCodeSourcePermission(codeBase));
+ }
+ try {
+ return new CodeSource(new URL("file", "", codeBase), (java.security.cert.Certificate[]) null);
+ }
+ catch (MalformedURLException e) {
+ throw new RuntimeException("A CodeSource file URL cannot be constructed from the supplied codeBase: " + codeBase);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ GroovyCodeSource that = (GroovyCodeSource) o;
+ return Objects.equals(codeSource, that.codeSource);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(codeSource);
+ }
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/GroovyInterceptable.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/lang/GroovyInterceptable.java b/src/main/groovy/groovy/lang/GroovyInterceptable.java
new file mode 100644
index 0000000..8f83354
--- /dev/null
+++ b/src/main/groovy/groovy/lang/GroovyInterceptable.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+package groovy.lang;
+
+/**
+ * Marker interface used to notify that all methods should be intercepted through the <code>invokeMethod</code> mechanism
+ * of <code>GroovyObject</code>.
+ *
+ * @author Guillaume Laforge
+ */
+public interface GroovyInterceptable extends GroovyObject {
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/GroovyObject.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/lang/GroovyObject.java b/src/main/groovy/groovy/lang/GroovyObject.java
new file mode 100644
index 0000000..5f271e3
--- /dev/null
+++ b/src/main/groovy/groovy/lang/GroovyObject.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+package groovy.lang;
+
+/**
+ * The interface implemented by all Groovy objects.
+ * <p>
+ * Especially handy for using Groovy objects when in the Java world.
+ *
+ * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
+ */
+public interface GroovyObject {
+
+ /**
+ * Invokes the given method.
+ *
+ * @param name the name of the method to call
+ * @param args the arguments to use for the method call
+ * @return the result of invoking the method
+ */
+ Object invokeMethod(String name, Object args);
+
+ /**
+ * Retrieves a property value.
+ *
+ * @param propertyName the name of the property of interest
+ * @return the given property
+ */
+ Object getProperty(String propertyName);
+
+ /**
+ * Sets the given property to the new value.
+ *
+ * @param propertyName the name of the property of interest
+ * @param newValue the new value for the property
+ */
+ void setProperty(String propertyName, Object newValue);
+
+ /**
+ * Returns the metaclass for a given class.
+ *
+ * @return the metaClass of this instance
+ */
+ MetaClass getMetaClass();
+
+ /**
+ * Allows the MetaClass to be replaced with a derived implementation.
+ *
+ * @param metaClass the new metaclass
+ */
+ void setMetaClass(MetaClass metaClass);
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/GroovyObjectSupport.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/lang/GroovyObjectSupport.java b/src/main/groovy/groovy/lang/GroovyObjectSupport.java
new file mode 100644
index 0000000..cf7a77f
--- /dev/null
+++ b/src/main/groovy/groovy/lang/GroovyObjectSupport.java
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+package groovy.lang;
+
+import org.codehaus.groovy.runtime.InvokerHelper;
+
+/**
+ * A useful base class for Java objects wishing to be Groovy objects
+ *
+ * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
+ */
+public abstract class GroovyObjectSupport implements GroovyObject {
+
+ // never persist the MetaClass
+ private transient MetaClass metaClass;
+
+ public GroovyObjectSupport() {
+ this.metaClass = getDefaultMetaClass();
+ }
+
+ public Object getProperty(String property) {
+ return getMetaClass().getProperty(this, property);
+ }
+
+ public void setProperty(String property, Object newValue) {
+ getMetaClass().setProperty(this, property, newValue);
+ }
+
+ public Object invokeMethod(String name, Object args) {
+ return getMetaClass().invokeMethod(this, name, args);
+ }
+
+ public MetaClass getMetaClass() {
+ return this.metaClass;
+ }
+
+ public void setMetaClass(MetaClass metaClass) {
+ this.metaClass =
+ null == metaClass
+ ? getDefaultMetaClass()
+ : metaClass;
+ }
+
+ private MetaClass getDefaultMetaClass() {
+ return InvokerHelper.getMetaClass(this.getClass());
+ }
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/GroovyResourceLoader.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/lang/GroovyResourceLoader.java b/src/main/groovy/groovy/lang/GroovyResourceLoader.java
new file mode 100644
index 0000000..7abe202
--- /dev/null
+++ b/src/main/groovy/groovy/lang/GroovyResourceLoader.java
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+package groovy.lang;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Allows frameworks that integrate with Groovy to determine how Groovy files are resolved.
+ *
+ * @author Steven Devijver
+ */
+public interface GroovyResourceLoader {
+
+ /**
+ * Loads a Groovy source file given its name.
+ *
+ * @param filename name of the file
+ * @return a URL
+ * @throws java.net.MalformedURLException if the URL is invalid
+ */
+ URL loadGroovySource(String filename) throws MalformedURLException;
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/GroovyRuntimeException.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/lang/GroovyRuntimeException.java b/src/main/groovy/groovy/lang/GroovyRuntimeException.java
new file mode 100644
index 0000000..33eb51c
--- /dev/null
+++ b/src/main/groovy/groovy/lang/GroovyRuntimeException.java
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+package groovy.lang;
+
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.ModuleNode;
+
+/**
+ * An exception thrown by the interpreter
+ *
+ * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
+ */
+public class GroovyRuntimeException extends RuntimeException {
+
+ private ModuleNode module;
+ private ASTNode node;
+
+ public GroovyRuntimeException() {
+ }
+
+ public GroovyRuntimeException(String message) {
+ super(message);
+ }
+
+ public GroovyRuntimeException(String message, ASTNode node) {
+ super(message);
+ this.node = node;
+ }
+
+ public GroovyRuntimeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public GroovyRuntimeException(Throwable t) {
+ super();
+ initCause(t);
+ }
+
+ public void setModule(ModuleNode module) {
+ this.module = module;
+ }
+
+ public ModuleNode getModule() {
+ return module;
+ }
+
+ public String getMessage() {
+ return getMessageWithoutLocationText() + getLocationText();
+ }
+
+ public ASTNode getNode() {
+ return node;
+ }
+
+ public String getMessageWithoutLocationText() {
+ return super.getMessage();
+ }
+
+ protected String getLocationText() {
+ String answer = ". ";
+ if (node != null) {
+ answer += "At [" + node.getLineNumber() + ":" + node.getColumnNumber() + "] ";
+ }
+ if (module != null) {
+ answer += module.getDescription();
+ }
+ if (answer.equals(". ")) {
+ return "";
+ }
+ return answer;
+ }
+}