http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/GroovyShell.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/GroovyShell.java b/src/main/groovy/groovy/lang/GroovyShell.java new file mode 100644 index 0000000..4dc51c9 --- /dev/null +++ b/src/main/groovy/groovy/lang/GroovyShell.java @@ -0,0 +1,611 @@ +/* + * 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.ui.GroovyMain; +import org.apache.groovy.plugin.GroovyRunner; +import org.apache.groovy.plugin.GroovyRunnerRegistry; +import org.codehaus.groovy.control.CompilationFailedException; +import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.runtime.InvokerHelper; +import org.codehaus.groovy.runtime.InvokerInvocationException; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.net.URI; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.List; + +import static org.codehaus.groovy.runtime.InvokerHelper.MAIN_METHOD_NAME; + +/** + * Represents a groovy shell capable of running arbitrary groovy scripts + * + * @author <a href="mailto:[email protected]">James Strachan</a> + * @author Guillaume Laforge + * @author Paul King + */ +public class GroovyShell extends GroovyObjectSupport { + + public static final String DEFAULT_CODE_BASE = "/groovy/shell"; + + private final Binding context; + private int counter; + private final CompilerConfiguration config; + private GroovyClassLoader loader; + + public static void main(String[] args) { + GroovyMain.main(args); + } + + public GroovyShell() { + this(null, new Binding()); + } + + public GroovyShell(Binding binding) { + this(null, binding); + } + + public GroovyShell(ClassLoader parent, CompilerConfiguration config) { + this(parent, new Binding(), config); + } + + public GroovyShell(CompilerConfiguration config) { + this(new Binding(), config); + } + + public GroovyShell(Binding binding, CompilerConfiguration config) { + this(null, binding, config); + } + + public GroovyShell(ClassLoader parent, Binding binding) { + this(parent, binding, CompilerConfiguration.DEFAULT); + } + + public GroovyShell(ClassLoader parent) { + this(parent, new Binding(), CompilerConfiguration.DEFAULT); + } + + public GroovyShell(ClassLoader parent, Binding binding, final CompilerConfiguration config) { + if (binding == null) { + throw new IllegalArgumentException("Binding must not be null."); + } + if (config == null) { + throw new IllegalArgumentException("Compiler configuration must not be null."); + } + final ClassLoader parentLoader = (parent!=null)?parent:GroovyShell.class.getClassLoader(); + this.loader = AccessController.doPrivileged(new PrivilegedAction<GroovyClassLoader>() { + public GroovyClassLoader run() { + return new GroovyClassLoader(parentLoader,config); + } + }); + this.context = binding; + this.config = config; + } + + public void resetLoadedClasses() { + loader.clearCache(); + } + + /** + * Creates a child shell using a new ClassLoader which uses the parent shell's + * class loader as its parent + * + * @param shell is the parent shell used for the variable bindings and the parent class loader + */ + public GroovyShell(GroovyShell shell) { + this(shell.loader, shell.context); + } + + public Binding getContext() { + return context; + } + + public GroovyClassLoader getClassLoader() { + return loader; + } + + public Object getProperty(String property) { + Object answer = getVariable(property); + if (answer == null) { + answer = super.getProperty(property); + } + return answer; + } + + public void setProperty(String property, Object newValue) { + setVariable(property, newValue); + try { + super.setProperty(property, newValue); + } catch (GroovyRuntimeException e) { + // ignore, was probably a dynamic property + } + } + + // + // FIXME: Use List<String> here, current version is not safe + // + + /** + * A helper method which runs the given script file with the given command line arguments + * + * @param scriptFile the file of the script to run + * @param list the command line arguments to pass in + */ + public Object run(File scriptFile, List list) throws CompilationFailedException, IOException { + String[] args = new String[list.size()]; + return run(scriptFile, (String[]) list.toArray(args)); + } + + /** + * A helper method which runs the given cl script with the given command line arguments + * + * @param scriptText is the text content of the script + * @param fileName is the logical file name of the script (which is used to create the class name of the script) + * @param list the command line arguments to pass in + */ + public Object run(String scriptText, String fileName, List list) throws CompilationFailedException { + String[] args = new String[list.size()]; + list.toArray(args); + return run(scriptText, fileName, args); + } + + /** + * Runs the given script file name with the given command line arguments + * + * @param scriptFile the file name of the script to run + * @param args the command line arguments to pass in + */ + public Object run(final File scriptFile, String[] args) throws CompilationFailedException, IOException { + String scriptName = scriptFile.getName(); + int p = scriptName.lastIndexOf("."); + if (p++ >= 0) { + if (scriptName.substring(p).equals("java")) { + throw new CompilationFailedException(0, null); + } + } + + // Get the current context classloader and save it on the stack + final Thread thread = Thread.currentThread(); + //ClassLoader currentClassLoader = thread.getContextClassLoader(); + + class DoSetContext implements PrivilegedAction { + ClassLoader classLoader; + + public DoSetContext(ClassLoader loader) { + classLoader = loader; + } + + public Object run() { + thread.setContextClassLoader(classLoader); + return null; + } + } + + AccessController.doPrivileged(new DoSetContext(loader)); + + // Parse the script, generate the class, and invoke the main method. This is a little looser than + // if you are compiling the script because the JVM isn't executing the main method. + Class scriptClass; + try { + scriptClass = AccessController.doPrivileged(new PrivilegedExceptionAction<Class>() { + public Class run() throws CompilationFailedException, IOException { + return loader.parseClass(scriptFile); + } + }); + } catch (PrivilegedActionException pae) { + Exception e = pae.getException(); + if (e instanceof CompilationFailedException) { + throw (CompilationFailedException) e; + } else if (e instanceof IOException) { + throw (IOException) e; + } else { + throw (RuntimeException) pae.getException(); + } + } + + return runScriptOrMainOrTestOrRunnable(scriptClass, args); + + // Set the context classloader back to what it was. + //AccessController.doPrivileged(new DoSetContext(currentClassLoader)); + } + + /** + * if (theClass is a Script) { + * run it like a script + * } else if (theClass has a main method) { + * run the main method + * } else if (theClass instanceof GroovyTestCase) { + * use the test runner to run it + * } else if (theClass implements Runnable) { + * if (theClass has a constructor with String[] params) + * instantiate theClass with this constructor and run + * else if (theClass has a no-args constructor) + * instantiate theClass with the no-args constructor and run + * } + */ + private Object runScriptOrMainOrTestOrRunnable(Class scriptClass, String[] args) { + // Always set the "args" property, regardless of what path we take in the code. + // Bad enough to have side effects but worse if their behavior is wonky. + context.setProperty("args", args); + + if (scriptClass == null) { + return null; + } + + //TODO: This logic mostly duplicates InvokerHelper.createScript. They should probably be unified. + + if (Script.class.isAssignableFrom(scriptClass)) { + // treat it just like a script if it is one + try { + Script script = InvokerHelper.newScript(scriptClass, context); + return script.run(); + } catch (InstantiationException e) { + // ignore instantiation errors,, try to do main + } catch (IllegalAccessException e) { + // ignore instantiation errors, try to do main + } catch (InvocationTargetException e) { + // ignore instantiation errors, try to do main + } + } + try { + // let's find a main method + scriptClass.getMethod(MAIN_METHOD_NAME, String[].class); + // if that main method exist, invoke it + return InvokerHelper.invokeMethod(scriptClass, MAIN_METHOD_NAME, new Object[]{args}); + } catch (NoSuchMethodException e) { + // if it implements Runnable, try to instantiate it + if (Runnable.class.isAssignableFrom(scriptClass)) { + return runRunnable(scriptClass, args); + } + GroovyRunnerRegistry runnerRegistry = GroovyRunnerRegistry.getInstance(); + for (GroovyRunner runner : runnerRegistry) { + if (runner.canRun(scriptClass, this.loader)) { + return runner.run(scriptClass, this.loader); + } + } + StringBuilder message = new StringBuilder("This script or class could not be run.\n" + + "It should either:\n" + + "- have a main method,\n" + + "- be a JUnit test or extend GroovyTestCase,\n" + + "- implement the Runnable interface,\n" + + "- or be compatible with a registered script runner. Known runners:\n"); + if (runnerRegistry.isEmpty()) { + message.append(" * <none>"); + } else { + for (String key : runnerRegistry.keySet()) { + message.append(" * ").append(key).append("\n"); + } + } + throw new GroovyRuntimeException(message.toString()); + } + } + + private static Object runRunnable(Class scriptClass, String[] args) { + Constructor constructor = null; + Runnable runnable = null; + Throwable reason = null; + try { + // first, fetch the constructor taking String[] as parameter + constructor = scriptClass.getConstructor((new String[]{}).getClass()); + try { + // instantiate a runnable and run it + runnable = (Runnable) constructor.newInstance(new Object[]{args}); + } catch (Throwable t) { + reason = t; + } + } catch (NoSuchMethodException e1) { + try { + // otherwise, find the default constructor + constructor = scriptClass.getConstructor(); + try { + // instantiate a runnable and run it + runnable = (Runnable) constructor.newInstance(); + } catch (InvocationTargetException ite) { + throw new InvokerInvocationException(ite.getTargetException()); + } catch (Throwable t) { + reason = t; + } + } catch (NoSuchMethodException nsme) { + reason = nsme; + } + } + if (constructor != null && runnable != null) { + runnable.run(); + } else { + throw new GroovyRuntimeException("This script or class was runnable but could not be run. ", reason); + } + return null; + } + + /** + * Runs the given script text with command line arguments + * + * @param scriptText is the text content of the script + * @param fileName is the logical file name of the script (which is used to create the class name of the script) + * @param args the command line arguments to pass in + */ + public Object run(final String scriptText, final String fileName, String[] args) throws CompilationFailedException { + GroovyCodeSource gcs = AccessController.doPrivileged(new PrivilegedAction<GroovyCodeSource>() { + public GroovyCodeSource run() { + return new GroovyCodeSource(scriptText, fileName, DEFAULT_CODE_BASE); + } + }); + return run(gcs, args); + } + + /** + * Runs the given script source with command line arguments + * + * @param source is the source content of the script + * @param args the command line arguments to pass in + */ + public Object run(GroovyCodeSource source, List args) throws CompilationFailedException { + return run(source, ((String[]) args.toArray(new String[args.size()]))); + } + + /** + * Runs the given script source with command line arguments + * + * @param source is the source content of the script + * @param args the command line arguments to pass in + */ + public Object run(GroovyCodeSource source, String[] args) throws CompilationFailedException { + Class scriptClass = parseClass(source); + return runScriptOrMainOrTestOrRunnable(scriptClass, args); + } + + /** + * Runs the given script source with command line arguments + * + * @param source is the source content of the script + * @param args the command line arguments to pass in + */ + public Object run(URI source, List args) throws CompilationFailedException, IOException { + return run(new GroovyCodeSource(source), ((String[]) args.toArray(new String[args.size()]))); + } + + /** + * Runs the given script source with command line arguments + * + * @param source is the source content of the script + * @param args the command line arguments to pass in + */ + public Object run(URI source, String[] args) throws CompilationFailedException, IOException { + return run(new GroovyCodeSource(source), args); + } + + /** + * Runs the given script with command line arguments + * + * @param in the stream reading the script + * @param fileName is the logical file name of the script (which is used to create the class name of the script) + * @param list the command line arguments to pass in + */ + public Object run(final Reader in, final String fileName, List list) throws CompilationFailedException { + return run(in, fileName, (String[]) list.toArray(new String[list.size()])); + } + + /** + * Runs the given script with command line arguments + * + * @param in the stream reading the script + * @param fileName is the logical file name of the script (which is used to create the class name of the script) + * @param args the command line arguments to pass in + */ + public Object run(final Reader in, final String fileName, String[] args) throws CompilationFailedException { + GroovyCodeSource gcs = AccessController.doPrivileged(new PrivilegedAction<GroovyCodeSource>() { + public GroovyCodeSource run() { + return new GroovyCodeSource(in, fileName, DEFAULT_CODE_BASE); + } + }); + Class scriptClass = parseClass(gcs); + return runScriptOrMainOrTestOrRunnable(scriptClass, args); + } + + public Object getVariable(String name) { + return context.getVariables().get(name); + } + + public void setVariable(String name, Object value) { + context.setVariable(name, value); + } + + /** + * Evaluates some script against the current Binding and returns the result + * + * @param codeSource + * @throws CompilationFailedException + */ + public Object evaluate(GroovyCodeSource codeSource) throws CompilationFailedException { + Script script = parse(codeSource); + return script.run(); + } + + /** + * Evaluates some script against the current Binding and returns the result + * + * @param scriptText the text of the script + */ + public Object evaluate(final String scriptText) throws CompilationFailedException { + return evaluate(scriptText, generateScriptName(), DEFAULT_CODE_BASE); + } + + /** + * Evaluates some script against the current Binding and returns the result + * + * @param scriptText the text of the script + * @param fileName is the logical file name of the script (which is used to create the class name of the script) + */ + public Object evaluate(String scriptText, String fileName) throws CompilationFailedException { + return evaluate(scriptText, fileName, DEFAULT_CODE_BASE); + } + + /** + * Evaluates some script against the current Binding and returns the result. + * The .class file created from the script is given the supplied codeBase + */ + public Object evaluate(final String scriptText, final String fileName, final String codeBase) throws CompilationFailedException { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(new GroovyCodeSourcePermission(codeBase)); + } + + GroovyCodeSource gcs = AccessController.doPrivileged(new PrivilegedAction<GroovyCodeSource>() { + public GroovyCodeSource run() { + return new GroovyCodeSource(scriptText, fileName, codeBase); + } + }); + + return evaluate(gcs); + } + + /** + * Evaluates some script against the current Binding and returns the result + * + * @param file is the file of the script (which is used to create the class name of the script) + */ + public Object evaluate(File file) throws CompilationFailedException, IOException { + return evaluate(new GroovyCodeSource(file, config.getSourceEncoding())); + } + + /** + * Evaluates some script against the current Binding and returns the result + * + * @param uri is the URI of the script (which is used to create the class name of the script) + */ + public Object evaluate(URI uri) throws CompilationFailedException, IOException { + return evaluate(new GroovyCodeSource(uri)); + } + + /** + * Evaluates some script against the current Binding and returns the result + * + * @param in the stream reading the script + */ + public Object evaluate(Reader in) throws CompilationFailedException { + return evaluate(in, generateScriptName()); + } + + /** + * Evaluates some script against the current Binding and returns the result + * + * @param in the stream reading the script + * @param fileName is the logical file name of the script (which is used to create the class name of the script) + */ + public Object evaluate(Reader in, String fileName) throws CompilationFailedException { + Script script = null; + try { + script = parse(in, fileName); + return script.run(); + } finally { + if (script != null) { + InvokerHelper.removeClass(script.getClass()); + } + } + } + + + /** + * Parses the given script and returns it ready to be run + * + * @param reader the stream reading the script + * @param fileName is the logical file name of the script (which is used to create the class name of the script) + * @return the parsed script which is ready to be run via {@link Script#run()} + */ + public Script parse(final Reader reader, final String fileName) throws CompilationFailedException { + return parse(new GroovyCodeSource(reader, fileName, DEFAULT_CODE_BASE)); + } + + /** + * Parses the groovy code contained in codeSource and returns a java class. + */ + private Class parseClass(final GroovyCodeSource codeSource) throws CompilationFailedException { + // Don't cache scripts + return loader.parseClass(codeSource, false); + } + + /** + * Parses the given script and returns it ready to be run. When running in a secure environment + * (-Djava.security.manager) codeSource.getCodeSource() determines what policy grants should be + * given to the script. + * + * @param codeSource + * @return ready to run script + */ + public Script parse(final GroovyCodeSource codeSource) throws CompilationFailedException { + return InvokerHelper.createScript(parseClass(codeSource), context); + } + + /** + * Parses the given script and returns it ready to be run + * + * @param file is the file of the script (which is used to create the class name of the script) + */ + public Script parse(File file) throws CompilationFailedException, IOException { + return parse(new GroovyCodeSource(file, config.getSourceEncoding())); + } + + /** + * Parses the given script and returns it ready to be run + * + * @param uri is the URI of the script (which is used to create the class name of the script) + */ + public Script parse(URI uri) throws CompilationFailedException, IOException { + return parse(new GroovyCodeSource(uri)); + } + + /** + * Parses the given script and returns it ready to be run + * + * @param scriptText the text of the script + */ + public Script parse(String scriptText) throws CompilationFailedException { + return parse(scriptText, generateScriptName()); + } + + public Script parse(final String scriptText, final String fileName) throws CompilationFailedException { + GroovyCodeSource gcs = AccessController.doPrivileged(new PrivilegedAction<GroovyCodeSource>() { + public GroovyCodeSource run() { + return new GroovyCodeSource(scriptText, fileName, DEFAULT_CODE_BASE); + } + }); + return parse(gcs); + } + + /** + * Parses the given script and returns it ready to be run + * + * @param in the stream reading the script + */ + public Script parse(Reader in) throws CompilationFailedException { + return parse(in, generateScriptName()); + } + + protected synchronized String generateScriptName() { + return "Script" + (++counter) + ".groovy"; + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/GroovySystem.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/GroovySystem.java b/src/main/groovy/groovy/lang/GroovySystem.java new file mode 100644 index 0000000..cb5ea98 --- /dev/null +++ b/src/main/groovy/groovy/lang/GroovySystem.java @@ -0,0 +1,103 @@ +/* + * 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.apache.groovy.plugin.GroovyRunner; +import org.apache.groovy.plugin.GroovyRunnerRegistry; +import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl; +import org.codehaus.groovy.util.ReferenceBundle; +import org.codehaus.groovy.util.ReleaseInfo; + +import java.util.Map; + +public final class GroovySystem { + // + // TODO: make this initialization able to set useReflection true + // TODO: have some way of specifying another MetaClass Registry implementation + // + static { + USE_REFLECTION = true; + META_CLASS_REGISTRY = new MetaClassRegistryImpl(); + } + + /** + * If true then the MetaClass will only use reflection for method dispatch, property access, etc. + */ + @Deprecated + private static final boolean USE_REFLECTION; + + /** + * Reference to the MetaClass Registry to be used by the Groovy run-time system to map classes to MetaClasses + */ + private static final MetaClassRegistry META_CLASS_REGISTRY; + + /** + * Reference to the Runtime Registry to be used by the Groovy run-time system to find classes capable of running scripts + * + * @deprecated use {@link GroovyRunnerRegistry} + */ + @Deprecated + public static final Map<String, GroovyRunner> RUNNER_REGISTRY = GroovyRunnerRegistry.getInstance(); + + private static boolean keepJavaMetaClasses=false; + + private GroovySystem() { + // Do not allow this class to be instantiated + } + + @Deprecated + public static boolean isUseReflection() { + return USE_REFLECTION; + } + + public static MetaClassRegistry getMetaClassRegistry() { + return META_CLASS_REGISTRY; + } + + public static void setKeepJavaMetaClasses(boolean keepJavaMetaClasses) { + GroovySystem.keepJavaMetaClasses = keepJavaMetaClasses; + } + + public static boolean isKeepJavaMetaClasses() { + return keepJavaMetaClasses; + } + + /** + * This method can be used to ensure that no threaded created + * by a reference manager will be active. This is useful if the Groovy + * runtime itself is loaded through a class loader which should be disposed + * off. Without calling this method and if a threaded reference manager is + * active the class loader cannot be unloaded! + * + * Per default no threaded manager will be used. + * + * @since 1.6 + */ + public static void stopThreadedReferenceManager() { + ReferenceBundle.getSoftBundle().getManager().stopThread(); + ReferenceBundle.getWeakBundle().getManager().stopThread(); + } + + /** + * Returns the groovy version + */ + public static String getVersion() { + return ReleaseInfo.getVersion(); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/Groovydoc.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/Groovydoc.java b/src/main/groovy/groovy/lang/Groovydoc.java new file mode 100644 index 0000000..7327913 --- /dev/null +++ b/src/main/groovy/groovy/lang/Groovydoc.java @@ -0,0 +1,40 @@ +/* + * 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.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation to hold the groovydoc for the annotated element at runtime, we call it "Runtime Groovydoc". + * Runtime Groovydoc is a bit like Python's Documentation Strings and will be useful for IDE and developers who set a high value on documentations. + * + * The usage is very simple, just place @Groovydoc at the beginning of the content of groovydoc, then the new parser Parrot will attach the annotation Groovydoc automatically + * + * @since 3.0.0 + */ +@Documented +@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Groovydoc { + String value(); +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/IllegalPropertyAccessException.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/IllegalPropertyAccessException.java b/src/main/groovy/groovy/lang/IllegalPropertyAccessException.java new file mode 100644 index 0000000..8227e3f --- /dev/null +++ b/src/main/groovy/groovy/lang/IllegalPropertyAccessException.java @@ -0,0 +1,49 @@ +/* + * 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.reflect.Field; +import java.lang.reflect.Modifier; + +/** + * An exception occurred if a dynamic property dispatch fails with a + * field not accessible. + * + * @author <a href="mailto:[email protected]">Jochen Theodorou</a> + */ +public class IllegalPropertyAccessException extends MissingPropertyException { + + private static String makeMessage(String propertyName, Class clazz, int modifiers, boolean isField) { + String access = "private"; + if (Modifier.isProtected(modifiers)) access = "protected"; + if (Modifier.isPublic(modifiers)) access = "public"; + String propertyType = "property"; + if (isField) propertyType = "field"; + return "Can not access the "+access+" "+propertyType+" "+propertyName+" in class "+clazz.getName(); + } + + public IllegalPropertyAccessException(String propertyName, Class clazz, int modifiers) { + super(makeMessage(propertyName,clazz,modifiers,false),propertyName,clazz); + } + + public IllegalPropertyAccessException(Field field, Class clazz) { + super(makeMessage(field.getName(),clazz,field.getModifiers(),true),field.getName(),clazz); + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/IncorrectClosureArgumentsException.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/IncorrectClosureArgumentsException.java b/src/main/groovy/groovy/lang/IncorrectClosureArgumentsException.java new file mode 100644 index 0000000..f5b23f5 --- /dev/null +++ b/src/main/groovy/groovy/lang/IncorrectClosureArgumentsException.java @@ -0,0 +1,60 @@ +/* + * 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; + +/** + * An exception occurred when invoking a Closure with the wrong number and/or + * types of arguments + * + * @author <a href="mailto:[email protected]">James Strachan</a> + */ +public class IncorrectClosureArgumentsException extends GroovyRuntimeException { + + private final Closure closure; + private final Object arguments; + private final Class[] expected; + + public IncorrectClosureArgumentsException(Closure closure, Object arguments, Class[] expected) { + super( + "Incorrect arguments to closure: " + + closure + + ". Expected: " + + InvokerHelper.toString(expected) + + ", actual: " + + InvokerHelper.toString(arguments)); + this.closure = closure; + this.arguments = arguments; + this.expected = expected; + } + + public Object getArguments() { + return arguments; + } + + public Closure getClosure() { + return closure; + } + + public Class[] getExpected() { + return expected; + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/IntRange.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/IntRange.java b/src/main/groovy/groovy/lang/IntRange.java new file mode 100644 index 0000000..9377098 --- /dev/null +++ b/src/main/groovy/groovy/lang/IntRange.java @@ -0,0 +1,440 @@ +/* + * 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.IteratorClosureAdapter; +import org.codehaus.groovy.runtime.RangeInfo; + +import java.math.BigInteger; +import java.util.AbstractList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +/** + * Represents a list of Integer objects starting at a specified {@code from} value up (or down) + * to and potentially including a given {@code to} value. + * <p> + * Instances of this class may be either inclusive aware or non-inclusive aware. See the + * relevant constructors for creating each type. Inclusive aware IntRange instances are + * suitable for use with Groovy's range indexing - in particular if the from or to values + * might be negative. This normally happens underneath the covers but is worth keeping + * in mind if creating these ranges yourself explicitly. + * <p> + * Note: the design of this class might seem a little strange at first. It contains a Boolean + * field, {@code inclusive}, which can be {@code true}, {@code false} or {@code null}. This + * design is for backwards compatibility reasons. Groovy uses this class under the covers + * to represent range indexing, e.g. {@code someList[x..y]} and {@code someString[x..<y]}. + * In early versions of Groovy the ranges in these expressions were represented under the + * covers by the {@code new IntRange(x, y)} and {@code new IntRange(x, y-1)}. This turns + * out to be a lossy abstraction when x and/or y are negative values. Now the latter case + * is represented by {@code new IntRange(false, x, y)}. + * <p> + * Note: This class is a copy of {@link ObjectRange} optimized for <code>int</code>. If you make any + * changes to this class, you might consider making parallel changes to {@link ObjectRange}. + */ +public class IntRange extends AbstractList<Integer> implements Range<Integer> { + + /** + * Iterates through each number in an <code>IntRange</code>. + */ + private class IntRangeIterator implements Iterator<Integer> { + /** + * Counts from 0 up to size - 1. + */ + private int index; + + /** + * The number of values in the range. + */ + private int size = size(); + + /** + * The next value to return. + */ + private int value = isReverse() ? getTo() : getFrom(); + + @Override + public boolean hasNext() { + return index < size; + } + + @Override + public Integer next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + if (index++ > 0) { + if (isReverse()) { + --value; + } else { + ++value; + } + } + return value; + } + + /** + * Not supported. + * + * @throws java.lang.UnsupportedOperationException always + */ + @Override + public void remove() { + IntRange.this.remove(index); + } + } + + /** + * For non-inclusive aware ranges, the first number in the range; <code>from</code> is always less than or equal to <code>to</code>. + * For inclusive aware ranges, the <code>from</code> argument supplied to the constructor. + */ + private final int from; + + /** + * For non-inclusive aware ranges, the last number in the range; <code>to</code> is always greater than or equal to <code>from</code>. + * For inclusive aware ranges, the <code>from</code> argument supplied to the constructor. + */ + private final int to; + + /** + * If <code>false</code>, counts up from <code>from</code> to <code>to</code>. Otherwise, counts down + * from <code>to</code> to <code>from</code>. Not used for inclusive-aware ranges (inclusive = true|false). + */ + private final boolean reverse; + + /** + * If <code>true</code> or null, <code>to</code> is included in the range. + * If <code>false</code>, the range stops before the <code>to</code> value. + * <p> + * Null for non-inclusive-aware ranges (which are inclusive). + * <p> + * If true or false, the reverse flag is discarded. + */ + private final Boolean inclusive; + + /** + * Creates a new non-inclusive aware <code>IntRange</code>. If <code>from</code> is greater than + * <code>to</code>, a reverse range is created with <code>from</code> and <code>to</code> swapped. + * + * @param from the first number in the range. + * @param to the last number in the range. + * @throws IllegalArgumentException if the range would contain more than {@link Integer#MAX_VALUE} values. + */ + public IntRange(int from, int to) { + this.inclusive = null; + if (from > to) { + this.from = to; + this.to = from; + this.reverse = true; + } else { + this.from = from; + this.to = to; + this.reverse = false; + } + checkSize(); + } + + /** + * Creates a new non-inclusive aware <code>IntRange</code>. + * + * @param from the first value in the range. + * @param to the last value in the range. + * @param reverse <code>true</code> if the range should count from + * <code>to</code> to <code>from</code>. + * @throws IllegalArgumentException if <code>from</code> is greater than <code>to</code>. + */ + protected IntRange(int from, int to, boolean reverse) { + this.inclusive = null; + if (from > to) { + throw new IllegalArgumentException("'from' must be less than or equal to 'to'"); + } + + this.from = from; + this.to = to; + this.reverse = reverse; + checkSize(); + } + + /** + * Creates a new inclusive aware <code>IntRange</code>. + * + * @param from the first value in the range. + * @param to the last value in the range. + * @param inclusive <code>true</code> if the to value is included in the range. + */ + public IntRange(boolean inclusive, int from, int to) { + this.from = from; + this.to = to; + this.inclusive = inclusive; + this.reverse = false; // range may still be reversed, this value is ignored for inclusive-aware ranges + checkSize(); + } + + /** + * Creates a new NumberRange with the same <code>from</code> and <code>to</code> as this + * IntRange but with a step size of <code>stepSize</code>. + * + * @param stepSize the desired step size + * @return a new NumberRange + * @since 2.5.0 + */ + public <T extends Number & Comparable> NumberRange by(T stepSize) { + return new NumberRange(NumberRange.comparableNumber((Number)from), NumberRange.comparableNumber((Number)to), stepSize, inclusive); + } + + private void checkSize() { + // size() in the Collection interface returns an integer, so ranges can have no more than Integer.MAX_VALUE elements + final Long size = (long) to - from + 1; + if (size > Integer.MAX_VALUE) { + throw new IllegalArgumentException("A range must have no more than " + Integer.MAX_VALUE + " elements but attempted " + size + " elements"); + } + } + + /** + * A method for determining from and to information when using this IntRange to index an aggregate object of the specified size. + * Normally only used internally within Groovy but useful if adding range indexing support for your own aggregates. + * + * @param size the size of the aggregate being indexed + * @return the calculated range information (with 1 added to the to value, ready for providing to subList + */ + public RangeInfo subListBorders(int size) { + if (inclusive == null) { + throw new IllegalStateException("Should not call subListBorders on a non-inclusive aware IntRange"); + } + int tempFrom = from; + if (tempFrom < 0) { + tempFrom += size; + } + int tempTo = to; + if (tempTo < 0) { + tempTo += size; + } + if (tempFrom > tempTo) { + return new RangeInfo(inclusive ? tempTo : tempTo + 1, tempFrom + 1, true); + } + return new RangeInfo(tempFrom, inclusive ? tempTo + 1 : tempTo, false); + } + + /** + * Determines if this object is equal to another object. Delegates to + * {@link AbstractList#equals(Object)} if <code>that</code> is anything + * other than an {@link IntRange}. + * <p> + * It is not necessary to override <code>hashCode</code>, as + * {@link AbstractList#hashCode()} provides a suitable hash code.<p> + * <p> + * Note that equals is generally handled by {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#equals(List, List)} + * instead of this method. + * + * @param that the object to compare + * @return <code>true</code> if the objects are equal + */ + public boolean equals(Object that) { + return that instanceof IntRange ? equals((IntRange) that) : super.equals(that); + } + + /** + * Compares an {@link IntRange} to another {@link IntRange}. + * + * @param that the object to compare for equality + * @return <code>true</code> if the ranges are equal + */ + public boolean equals(IntRange that) { + return that != null && ((inclusive == null && reverse == that.reverse && from == that.from && to == that.to) + || (inclusive != null && inclusive == that.inclusive && from == that.from && to == that.to)); + } + + @Override + public Integer getFrom() { + if (inclusive == null || from <= to) { + return from; + } + return inclusive ? to : to + 1; + } + + @Override + public Integer getTo() { + if (inclusive == null) { + return to; + } + if (from <= to) { + return inclusive ? to : to - 1; + } + return from; + } + + /** + * Returns the inclusive flag. Null for non-inclusive aware ranges or non-null for inclusive aware ranges. + */ + public Boolean getInclusive() { + return inclusive; + } + + /** + * Gets the 'from' value as a primitive integer. + * + * @return the 'from' value as a primitive integer. + */ + public int getFromInt() { + return getFrom(); + } + + /** + * Gets the 'to' value as a primitive integer. + * + * @return the 'to' value as a primitive integer. + */ + public int getToInt() { + return getTo(); + } + + @Override + public boolean isReverse() { + return inclusive == null ? reverse : (from > to); + } + + @Override + public boolean containsWithinBounds(Object o) { + return contains(o); + } + + @Override + public Integer get(int index) { + if (index < 0) { + throw new IndexOutOfBoundsException("Index: " + index + " should not be negative"); + } + if (index >= size()) { + throw new IndexOutOfBoundsException("Index: " + index + " too big for range: " + this); + } + return isReverse() ? getTo() - index : index + getFrom(); + } + + @Override + public int size() { + return getTo() - getFrom() + 1; + } + + @Override + public Iterator<Integer> iterator() { + return new IntRangeIterator(); + } + + @Override + public List<Integer> subList(int fromIndex, int toIndex) { + if (fromIndex < 0) { + throw new IndexOutOfBoundsException("fromIndex = " + fromIndex); + } + if (toIndex > size()) { + throw new IndexOutOfBoundsException("toIndex = " + toIndex); + } + if (fromIndex > toIndex) { + throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); + } + + if (fromIndex == toIndex) { + return new EmptyRange<Integer>(getFrom()); + } + + return new IntRange(fromIndex + getFrom(), toIndex + getFrom() - 1, isReverse()); + } + + public String toString() { + return inclusive != null ? ("" + from + ".." + (inclusive ? "" : "<") + to) + : (reverse ? "" + to + ".." + from : "" + from + ".." + to); + } + + @Override + public String inspect() { + return toString(); + } + + @Override + public boolean contains(Object value) { + if (value instanceof Integer) { + return (Integer) value >= getFrom() && (Integer) value <= getTo(); + } + if (value instanceof BigInteger) { + final BigInteger bigint = (BigInteger) value; + return bigint.compareTo(BigInteger.valueOf(getFrom())) >= 0 && + bigint.compareTo(BigInteger.valueOf(getTo())) <= 0; + } + return false; + } + + @Override + public boolean containsAll(Collection other) { + if (other instanceof IntRange) { + final IntRange range = (IntRange) other; + return getFrom() <= range.getFrom() && range.getTo() <= getTo(); + } + return super.containsAll(other); + } + + @Override + public void step(int step, Closure closure) { + if (step == 0) { + if (!getFrom().equals(getTo())) { + throw new GroovyRuntimeException("Infinite loop detected due to step size of 0"); + } + return; // from == to and step == 0, nothing to do, so return + } + + if (isReverse()) { + step = -step; + } + if (step > 0) { + int value = getFrom(); + while (value <= getTo()) { + closure.call(value); + if (((long) value + step) >= Integer.MAX_VALUE) { + break; + } + value = value + step; + } + } else { + int value = getTo(); + while (value >= getFrom()) { + closure.call(value); + if (((long) value + step) <= Integer.MIN_VALUE) { + break; + } + value = value + step; + } + } + } + + @Override + public List<Integer> step(int step) { + final IteratorClosureAdapter<Integer> adapter = new IteratorClosureAdapter<Integer>(this); + step(step, adapter); + return adapter.asList(); + } + + @Override + public int hashCode(){ + int hashCode; + final int from = this.getFrom(); + final int to = this.getTo(); + + hashCode = ((from+to+1)*(from+to))/2+to; + return hashCode; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/Interceptor.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/Interceptor.java b/src/main/groovy/groovy/lang/Interceptor.java new file mode 100644 index 0000000..e496f1d --- /dev/null +++ b/src/main/groovy/groovy/lang/Interceptor.java @@ -0,0 +1,52 @@ +/* + * 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; + +/** + * Implementers of this interface can be registered in the ProxyMetaClass for + * notifications about method calls for objects managed by the ProxyMetaClass. + * See groovy/lang/InterceptorTest.groovy for details. + * @author Dierk Koenig + */ +public interface Interceptor { + /** + * This code is executed before the method is optionally called. + * @param object receiver object for the method call + * @param methodName name of the method to call + * @param arguments arguments to the method call + * @return any arbitrary result that replaces the result of the + * original method call only if doInvoke() returns false and afterInvoke() + * relays this result. + */ + Object beforeInvoke(Object object, String methodName, Object[] arguments); + /** + * This code is executed after the method is optionally called. + * @param object receiver object for the called method + * @param methodName name of the called method + * @param arguments arguments to the called method + * @param result result of the executed method call or result of beforeInvoke if method was not called + * @return any arbitrary result that can replace the result of the + * original method call. Typically, the result parameter is returned. + */ + Object afterInvoke(Object object, String methodName, Object[] arguments, Object result); + /** + * @return whether the target method should be invoked at all. + */ + boolean doInvoke(); +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/Lazy.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/Lazy.java b/src/main/groovy/groovy/lang/Lazy.java new file mode 100644 index 0000000..17f044f --- /dev/null +++ b/src/main/groovy/groovy/lang/Lazy.java @@ -0,0 +1,156 @@ +/* + * 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.transform.GroovyASTTransformationClass; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Field annotation to simplify lazy initialization. + * <p> + * Example usage without any special modifiers just defers initialization until the first call but is not thread-safe: + * <pre> + * {@code @Lazy} T x + * </pre> + * becomes + * <pre> + * private T $x + * + * T getX() { + * if ($x != null) + * return $x + * else { + * $x = new T() + * return $x + * } + * } + * </pre> + * + * If the field is declared volatile then initialization will be synchronized using + * the <a href="http://en.wikipedia.org/wiki/Double-checked_locking">double-checked locking</a> pattern as shown here: + * + * <pre> + * {@code @Lazy} volatile T x + * </pre> + * becomes + * <pre> + * private volatile T $x + * + * T getX() { + * T $x_local = $x + * if ($x_local != null) + * return $x_local + * else { + * synchronized(this) { + * if ($x == null) { + * $x = new T() + * } + * return $x + * } + * } + * } + * </pre> + * + * By default a field will be initialized by calling its default constructor. + * + * If the field has an initial value expression then this expression will be used instead of calling the default constructor. + * In particular, it is possible to use closure <code>{ ... } ()</code> syntax as follows: + * + * <pre> + * {@code @Lazy} T x = { [1, 2, 3] } () + * </pre> + * becomes + * <pre> + * private T $x + * + * T getX() { + * T $x_local = $x + * if ($x_local != null) + * return $x_local + * else { + * synchronized(this) { + * if ($x == null) { + * $x = { [1, 2, 3] } () + * } + * return $x + * } + * } + * } + * </pre> + * <p> + * <code>@Lazy(soft=true)</code> will use a soft reference instead of the field and use the above rules each time re-initialization is required. + * <p> + * If the <code>soft</code> flag for the annotation is not set but the field is static, then + * the <a href="http://en.wikipedia.org/wiki/Initialization_on_demand_holder_idiom">initialization on demand holder idiom</a> is + * used as follows: + * <pre> + * {@code @Lazy} static FieldType field + * {@code @Lazy} static Date date1 + * {@code @Lazy} static Date date2 = { new Date().copyWith(year: 2000) }() + * {@code @Lazy} static Date date3 = new GregorianCalendar(2009, Calendar.JANUARY, 1).time + * </pre> + * becomes these methods and inners classes within the class containing the above definitions: + * <pre> + * private static class FieldTypeHolder_field { + * private static final FieldType INSTANCE = new FieldType() + * } + * + * private static class DateHolder_date1 { + * private static final Date INSTANCE = new Date() + * } + * + * private static class DateHolder_date2 { + * private static final Date INSTANCE = { new Date().copyWith(year: 2000) }() + * } + * + * private static class DateHolder_date3 { + * private static final Date INSTANCE = new GregorianCalendar(2009, Calendar.JANUARY, 1).time + * } + * + * static FieldType getField() { + * return FieldTypeHolder_field.INSTANCE + * } + * + * static Date getDate1() { + * return DateHolder_date1.INSTANCE + * } + * + * static Date getDate2() { + * return DateHolder_date2.INSTANCE + * } + * + * static Date getDate3() { + * return DateHolder_date3.INSTANCE + * } + * </pre> + */ [email protected] +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.FIELD}) +@GroovyASTTransformationClass("org.codehaus.groovy.transform.LazyASTTransformation") +public @interface Lazy { + /** + * @return if field should be soft referenced instead of hard referenced + */ + boolean soft () default false; +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/ListWithDefault.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/ListWithDefault.java b/src/main/groovy/groovy/lang/ListWithDefault.java new file mode 100644 index 0000000..6b87548 --- /dev/null +++ b/src/main/groovy/groovy/lang/ListWithDefault.java @@ -0,0 +1,257 @@ +/* + * 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.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** + * A wrapper for {@link List} which automatically grows the list when either {@link #get(int)} or + * {@link #getAt(int)} is called with an index greater than or equal to {@code size()}. + * + * @author Andre Steingress + * @since 1.8.7 + */ +public final class ListWithDefault<T> implements List<T> { + + private final List<T> delegate; + private final boolean lazyDefaultValues; + + private final Closure initClosure; + + private ListWithDefault(List<T> items, boolean lazyDefaultValues, Closure initClosure) { + this.delegate = items; + this.lazyDefaultValues = lazyDefaultValues; + this.initClosure = initClosure; + } + + public List<T> getDelegate() { + return delegate != null ? new ArrayList<T>(delegate) : null; + } + + public boolean isLazyDefaultValues() { + return lazyDefaultValues; + } + + public Closure getInitClosure() { + return initClosure != null ? (Closure) initClosure.clone() : null; + } + + public static <T> ListWithDefault<T> newInstance(List<T> items, boolean lazyDefaultValues, Closure initClosure) { + if (items == null) + throw new IllegalArgumentException("Parameter \"items\" must not be null"); + if (initClosure == null) + throw new IllegalArgumentException("Parameter \"initClosure\" must not be null"); + + return new ListWithDefault<T>(new ArrayList<T>(items), lazyDefaultValues, (Closure) initClosure.clone()); + } + + public int size() { + return delegate.size(); + } + + public boolean isEmpty() { + return delegate.isEmpty(); + } + + public boolean contains(Object o) { + return delegate.contains(o); + } + + public Iterator<T> iterator() { + return delegate.iterator(); + } + + public Object[] toArray() { + return delegate.toArray(); + } + + public <T> T[] toArray(T[] ts) { + return delegate.toArray(ts); + } + + public boolean add(T t) { + return delegate.add(t); + } + + public boolean remove(Object o) { + return delegate.remove(o); + } + + public boolean containsAll(Collection<?> objects) { + return delegate.containsAll(objects); + } + + public boolean addAll(Collection<? extends T> ts) { + return delegate.addAll(ts); + } + + public boolean addAll(int i, Collection<? extends T> ts) { + return delegate.addAll(i, ts); + } + + public boolean removeAll(Collection<?> objects) { + return delegate.removeAll(objects); + } + + public boolean retainAll(Collection<?> objects) { + return delegate.retainAll(objects); + } + + public void clear() { + delegate.clear(); + } + + /** + * Overwrites subscript operator handling by redirecting to {@link #get(int)}. + * + * @param index an index (might be greater or equal to {@code size()}, or smaller than 0) + * @return the value at the given {@code index} or the default value + */ + public T getAt(int index) { + return get(index); + } + + /** + * Returns the element at the given index but grows the list if needed. If the requested {@code index} is + * greater than or equal to {@code size()}, the list will grow to the new size and a default value calculated + * using the <code>initClosure</code> will be used to populate the missing value and returned. + * <p> + * If <code>lazyDefaultValues</code> is <code>true</code> any gaps when growing the list are filled + * with nulls. Subsequent attempts to retrieve items from the list from those gap index values + * will, upon finding null, call the <code>initClosure</code> to populate the list for the + * given list value. Hence, when in this mode, nulls cannot be stored in this list. + * If <code>lazyDefaultValues</code> is <code>false</code> any gaps when growing the list are filled + * eagerly by calling the <code>initClosure</code> for all gap indexes during list growth. + * No calls to <code>initClosure</code> are made except during list growth and it is ok to + * store null values in the list when in this mode. + * <p> + * This implementation breaks + * the contract of {@link java.util.List#get(int)} as it a) possibly modifies the underlying list and b) does + * NOT throw an {@link IndexOutOfBoundsException} when {@code index < 0 || index >= size()}. + * + * @param index an index (might be greater or equal to {@code size()}, or smaller than 0) + * @return the value at the given {@code index} or the default value + */ + public T get(int index) { + + final int size = size(); + int normalisedIndex = normaliseIndex(index, size); + if (normalisedIndex < 0) { + throw new IndexOutOfBoundsException("Negative index [" + normalisedIndex + "] too large for list size " + size); + } + + // either index >= size or the normalised index is negative + if (normalisedIndex >= size) { + // find out the number of gaps to fill with null/the default value + final int gapCount = normalisedIndex - size; + + // fill all gaps + for (int i = 0; i < gapCount; i++) { + final int idx = size(); + + // if we lazily create default values, use 'null' as placeholder + if (lazyDefaultValues) + delegate.add(idx, null); + else + delegate.add(idx, getDefaultValue(idx)); + } + + // add the first/last element being always the default value + final int idx = normalisedIndex; + delegate.add(idx, getDefaultValue(idx)); + + // normalise index again to get positive index + normalisedIndex = normaliseIndex(index, size()); + } + + T item = delegate.get(normalisedIndex); + if (item == null && lazyDefaultValues) { + item = getDefaultValue(normalisedIndex); + delegate.set(normalisedIndex, item); + } + + return item; + } + + @SuppressWarnings("unchecked") + private T getDefaultValue(int idx) { + return (T) initClosure.call(new Object[]{idx}); + } + + private static int normaliseIndex(int index, int size) { + if (index < 0) { + index += size; + } + return index; + } + + public T set(int i, T t) { + return delegate.set(i, t); + } + + public void add(int i, T t) { + delegate.add(i, t); + } + + public T remove(int i) { + return delegate.remove(i); + } + + public int indexOf(Object o) { + return delegate.indexOf(o); + } + + public int lastIndexOf(Object o) { + return delegate.lastIndexOf(o); + } + + public ListIterator<T> listIterator() { + return delegate.listIterator(); + } + + public ListIterator<T> listIterator(int i) { + return delegate.listIterator(i); + } + + @Override + public boolean equals(Object obj) { + return delegate.equals(obj); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } + + /** + * Returns a view of a portion of this list. This method returns a list with the same + * lazy list settings as the original list. + * + * @param fromIndex low endpoint of the subList (inclusive) + * @param toIndex upper endpoint of the subList (exclusive) + * @return a view of a specified range within this list, keeping all lazy list settings + */ + public ListWithDefault<T> subList(int fromIndex, int toIndex) { + return new ListWithDefault<T>(delegate.subList(fromIndex, toIndex), lazyDefaultValues, (Closure) initClosure.clone()); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/MapWithDefault.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/MapWithDefault.java b/src/main/groovy/groovy/lang/MapWithDefault.java new file mode 100644 index 0000000..166e06b --- /dev/null +++ b/src/main/groovy/groovy/lang/MapWithDefault.java @@ -0,0 +1,105 @@ +/* + * 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.Collection; +import java.util.Map; +import java.util.Set; + +/** + * A wrapper for Map which allows a default value to be specified. + * + * @author Paul King + * @since 1.7.1 + */ +public final class MapWithDefault<K, V> implements Map<K, V> { + + private final Map<K, V> delegate; + private final Closure initClosure; + + private MapWithDefault(Map<K, V> m, Closure initClosure) { + delegate = m; + this.initClosure = initClosure; + } + + public static <K, V> Map<K, V> newInstance(Map<K, V> m, Closure initClosure) { + return new MapWithDefault<K, V>(m, initClosure); + } + + public int size() { + return delegate.size(); + } + + public boolean isEmpty() { + return delegate.isEmpty(); + } + + public boolean containsKey(Object key) { + return delegate.containsKey(key); + } + + public boolean containsValue(Object value) { + return delegate.containsValue(value); + } + + public V get(Object key) { + if (!delegate.containsKey(key)) { + delegate.put((K)key, (V)initClosure.call(new Object[]{key})); + } + return delegate.get(key); + } + + public V put(K key, V value) { + return delegate.put(key, value); + } + + public V remove(Object key) { + return delegate.remove(key); + } + + public void putAll(Map<? extends K, ? extends V> m) { + delegate.putAll(m); + } + + public void clear() { + delegate.clear(); + } + + public Set<K> keySet() { + return delegate.keySet(); + } + + public Collection<V> values() { + return delegate.values(); + } + + public Set<Map.Entry<K, V>> entrySet() { + return delegate.entrySet(); + } + + @Override + public boolean equals(Object obj) { + return delegate.equals(obj); + } + + @Override + public int hashCode() { + return delegate.hashCode(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/MetaArrayLengthProperty.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/MetaArrayLengthProperty.java b/src/main/groovy/groovy/lang/MetaArrayLengthProperty.java new file mode 100644 index 0000000..8310386 --- /dev/null +++ b/src/main/groovy/groovy/lang/MetaArrayLengthProperty.java @@ -0,0 +1,56 @@ +/* + * 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; + + +/** + * Represents the length property of an array + * + * @author <a href="mailto:[email protected]">James Strachan</a> + */ +public class MetaArrayLengthProperty extends MetaProperty { + + /** + * Sole constructor setting name to "length" and type to int + */ + public MetaArrayLengthProperty() { + super("length", int.class); + } + + /** + * Get this property from the given object. + * @param object an array + * @return the length of the array object + * @throws IllegalArgumentException if object is not an array + */ + public Object getProperty(Object object) { + return java.lang.reflect.Array.getLength(object); + } + + /** + * Sets the property on the given object to the new value + * + * @param object on which to set the property + * @param newValue the new value of the property + * @throws RuntimeException if the property could not be set + */ + public void setProperty(Object object, Object newValue) { + throw new ReadOnlyPropertyException("length", object.getClass()); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/MetaBeanProperty.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/MetaBeanProperty.java b/src/main/groovy/groovy/lang/MetaBeanProperty.java new file mode 100644 index 0000000..89328af --- /dev/null +++ b/src/main/groovy/groovy/lang/MetaBeanProperty.java @@ -0,0 +1,156 @@ +/* + * 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.reflection.CachedField; +import org.codehaus.groovy.runtime.MetaClassHelper; +import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; + +import java.lang.reflect.Modifier; + +/** + * Represents a property on a bean which may have a getter and/or a setter + */ +public class MetaBeanProperty extends MetaProperty { + + private MetaMethod getter; + private MetaMethod setter; + private CachedField field; + + /** + * Sole constructor setting name, type (class), getter and setter. + */ + public MetaBeanProperty(String name, Class type, MetaMethod getter, MetaMethod setter) { + super(name, type); + this.getter = getter; + this.setter = setter; + } + + /** + * Get the property of the given object. + * + * @param object which to be got + * @return the property of the given object + * @throws RuntimeException if the property could not be evaluated + */ + public Object getProperty(Object object) { + MetaMethod getter = getGetter(); + if (getter == null) { + if (field != null) return field.getProperty(object); + //TODO: create a WriteOnlyException class? + throw new GroovyRuntimeException("Cannot read write-only property: " + name); + } + return getter.invoke(object, MetaClassHelper.EMPTY_ARRAY); + } + + /** + * Set the property on the given object to the new value. + * + * @param object on which to set the property + * @param newValue the new value of the property + * @throws RuntimeException if the property could not be set + */ + public void setProperty(Object object, Object newValue) { + MetaMethod setter = getSetter(); + if (setter == null) { + if (field != null && !Modifier.isFinal(field.getModifiers())) { + field.setProperty(object, newValue); + return; + } + throw new GroovyRuntimeException("Cannot set read-only property: " + name); + } + newValue = DefaultTypeTransformation.castToType(newValue, getType()); + setter.invoke(object, new Object[]{newValue}); + } + + /** + * Get the getter method. + * + * @return the getter method for this property. + */ + public MetaMethod getGetter() { + return getter; + } + + /** + * Get the setter method. + * + * @return the setter method for this property. + */ + public MetaMethod getSetter() { + return setter; + } + + /** + * This is for MetaClass to patch up the object later when looking for get*() methods. + * + * @param getter The getter for this property + */ + void setGetter(MetaMethod getter) { + this.getter = getter; + } + + /** + * This is for MetaClass to patch up the object later when looking for set*() methods. + * + * @param setter The setter for this property + */ + void setSetter(MetaMethod setter) { + this.setter = setter; + } + + /** + * Gets the visibility modifiers for the property as defined by the getter and setter methods. + * + * @return the visibility modifier of the getter, the setter, or both depending on which exist + */ + public int getModifiers() { + MetaMethod getter = getGetter(); + MetaMethod setter = getSetter(); + if (setter != null && getter == null) return setter.getModifiers(); + if (getter != null && setter == null) return getter.getModifiers(); + int modifiers = getter.getModifiers() | setter.getModifiers(); + int visibility = 0; + if (Modifier.isPublic(modifiers)) visibility = Modifier.PUBLIC; + if (Modifier.isProtected(modifiers)) visibility = Modifier.PROTECTED; + if (Modifier.isPrivate(modifiers)) visibility = Modifier.PRIVATE; + int states = getter.getModifiers() & setter.getModifiers(); + states &= ~(Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE); + states |= visibility; + return states; + } + + /** + * Sets the field of this property + * + * @param field + */ + public void setField(CachedField field) { + this.field = field; + } + + /** + * Gets the field of this property + * + * @return The field of this property + */ + public CachedField getField() { + return field; + } +}
