Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 03117200D6C for ; Wed, 20 Dec 2017 05:29:17 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id 014A9160C1B; Wed, 20 Dec 2017 04:29:17 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 65CB2160C44 for ; Wed, 20 Dec 2017 05:29:13 +0100 (CET) Received: (qmail 32530 invoked by uid 500); 20 Dec 2017 04:29:12 -0000 Mailing-List: contact commits-help@groovy.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@groovy.apache.org Delivered-To: mailing list commits@groovy.apache.org Received: (qmail 31920 invoked by uid 99); 20 Dec 2017 04:29:11 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 20 Dec 2017 04:29:11 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 77E80F217C; Wed, 20 Dec 2017 04:29:10 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: sunlan@apache.org To: commits@groovy.apache.org Date: Wed, 20 Dec 2017 04:29:47 -0000 Message-Id: <001f018f31244f70bca8932342675738@git.apache.org> In-Reply-To: <48be9b2ce20b4b3a90bfd5192834b3a3@git.apache.org> References: <48be9b2ce20b4b3a90bfd5192834b3a3@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [39/47] groovy git commit: Move source files to proper packages archived-at: Wed, 20 Dec 2017 04:29:17 -0000 http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/lang/Closure.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/Closure.java b/src/main/groovy/groovy/lang/Closure.java new file mode 100644 index 0000000..99d2ea9 --- /dev/null +++ b/src/main/groovy/groovy/lang/Closure.java @@ -0,0 +1,1054 @@ +/* + * 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.internal.util.UncheckedThrow; +import org.codehaus.groovy.reflection.ReflectionCache; +import org.codehaus.groovy.reflection.stdclasses.CachedClosureClass; +import org.codehaus.groovy.runtime.ComposedClosure; +import org.codehaus.groovy.runtime.CurriedClosure; +import org.codehaus.groovy.runtime.InvokerHelper; +import org.codehaus.groovy.runtime.InvokerInvocationException; +import org.codehaus.groovy.runtime.callsite.BooleanClosureWrapper; +import org.codehaus.groovy.runtime.memoize.LRUCache; +import org.codehaus.groovy.runtime.memoize.Memoize; +import org.codehaus.groovy.runtime.memoize.UnlimitedConcurrentCache; + +import java.io.IOException; +import java.io.Serializable; +import java.io.StringWriter; +import java.io.Writer; + +/** + * Represents any closure object in Groovy. + *

+ * Groovy allows instances of Closures to be called in a + * short form. For example: + *

+ * def a = 1
+ * def c = { a }
+ * assert c() == 1
+ * 
+ * To be able to use a Closure in this way with your own + * subclass, you need to provide a doCall method with any + * signature you want to. This ensures that + * {@link #getMaximumNumberOfParameters()} and + * {@link #getParameterTypes()} will work too without any + * additional code. If no doCall method is provided a + * closure must be used in its long form like + *
+ * def a = 1
+ * def c = {a}
+ * assert c.call() == 1
+ * 
+ * + * @author James Strachan + * @author John Wilson + * @author Jochen Theodorou + * @author Graeme Rocher + * @author Paul King + */ +public abstract class Closure extends GroovyObjectSupport implements Cloneable, Runnable, GroovyCallable, Serializable { + + /** + * With this resolveStrategy set the closure will attempt to resolve property references and methods to the + * owner first, then the delegate (this is the default strategy). + * + * For example the following code: + *
+     * class Test {
+     *     def x = 30
+     *     def y = 40
+     *
+     *     def run() {
+     *         def data = [ x: 10, y: 20 ]
+     *         def cl = { y = x + y }
+     *         cl.delegate = data
+     *         cl()
+     *         assert x == 30
+     *         assert y == 70
+     *         assert data == [x:10, y:20]
+     *     }
+     * }
+     *
+     * new Test().run()
+     * 
+ * Will succeed, because the x and y fields declared in the Test class shadow the variables in the delegate.

+ * Note that local variables are always looked up first, independently of the resolution strategy. + */ + public static final int OWNER_FIRST = 0; + + /** + * With this resolveStrategy set the closure will attempt to resolve property references and methods to the + * delegate first then the owner. + * + * For example the following code: + *

+     * class Test {
+     *     def x = 30
+     *     def y = 40
+     *
+     *     def run() {
+     *         def data = [ x: 10, y: 20 ]
+     *         def cl = { y = x + y }
+     *         cl.delegate = data
+     *         cl.resolveStrategy = Closure.DELEGATE_FIRST
+     *         cl()
+     *         assert x == 30
+     *         assert y == 40
+     *         assert data == [x:10, y:30]
+     *     }
+     * }
+     *
+     * new Test().run()
+     * 
+ * This will succeed, because the x and y variables declared in the delegate shadow the fields in the owner class.

+ * Note that local variables are always looked up first, independently of the resolution strategy. + */ + public static final int DELEGATE_FIRST = 1; + + /** + * With this resolveStrategy set the closure will resolve property references and methods to the owner only + * and not call the delegate at all. For example the following code : + * + *

+     * class Test {
+     *     def x = 30
+     *     def y = 40
+     *
+     *     def run() {
+     *         def data = [ x: 10, y: 20, z: 30 ]
+     *         def cl = { y = x + y + z }
+     *         cl.delegate = data
+     *         cl.resolveStrategy = Closure.OWNER_ONLY
+     *         cl()
+     *         println x
+     *         println y
+     *         println data
+     *     }
+     * }
+     *
+     * new Test().run()
+     * 
+ * + * will throw "No such property: z" error because even if the z variable is declared in the delegate, no + * lookup is made.

+ * Note that local variables are always looked up first, independently of the resolution strategy. + */ + public static final int OWNER_ONLY = 2; + + /** + * With this resolveStrategy set the closure will resolve property references and methods to the delegate + * only and entirely bypass the owner. For example the following code : + * + *

+     * class Test {
+     *     def x = 30
+     *     def y = 40
+     *     def z = 50
+     *
+     *     def run() {
+     *         def data = [ x: 10, y: 20 ]
+     *         def cl = { y = x + y + z }
+     *         cl.delegate = data
+     *         cl.resolveStrategy = Closure.DELEGATE_ONLY
+     *         cl()
+     *         println x
+     *         println y
+     *         println data
+     *     }
+     * }
+     *
+     * new Test().run()
+     * 
+ * + * will throw an error because even if the owner declares a "z" field, the resolution strategy will bypass + * lookup in the owner.

+ * Note that local variables are always looked up first, independently of the resolution strategy. + */ + public static final int DELEGATE_ONLY = 3; + + /** + * With this resolveStrategy set the closure will resolve property references to itself and go + * through the usual MetaClass look-up process. This means that properties and methods are neither resolved + * from the owner nor the delegate, but only on the closure object itself. This allows the developer to + * override getProperty using ExpandoMetaClass of the closure itself.

+ * Note that local variables are always looked up first, independently of the resolution strategy. + */ + public static final int TO_SELF = 4; + + public static final int DONE = 1, SKIP = 2; + private static final Object[] EMPTY_OBJECT_ARRAY = {}; + public static final Closure IDENTITY = new Closure(null) { + public Object doCall(Object args) { + return args; + } + }; + + private Object delegate; + private Object owner; + private Object thisObject; + private int resolveStrategy = OWNER_FIRST; + private int directive; + protected Class[] parameterTypes; + protected int maximumNumberOfParameters; + private static final long serialVersionUID = 4368710879820278874L; + private BooleanClosureWrapper bcw; + + public Closure(Object owner, Object thisObject) { + this.owner = owner; + this.delegate = owner; + this.thisObject = thisObject; + + final CachedClosureClass cachedClass = (CachedClosureClass) ReflectionCache.getCachedClass(getClass()); + parameterTypes = cachedClass.getParameterTypes(); + maximumNumberOfParameters = cachedClass.getMaximumNumberOfParameters(); + } + + /** + * Constructor used when the "this" object for the Closure is null. + * This is rarely the case in normal Groovy usage. + * + * @param owner the Closure owner + */ + public Closure(Object owner) { + this(owner, null); + } + + /** + * Sets the strategy which the closure uses to resolve property references and methods. + * The default is Closure.OWNER_FIRST + * + * @param resolveStrategy The resolve strategy to set + * + * @see groovy.lang.Closure#DELEGATE_FIRST + * @see groovy.lang.Closure#DELEGATE_ONLY + * @see groovy.lang.Closure#OWNER_FIRST + * @see groovy.lang.Closure#OWNER_ONLY + * @see groovy.lang.Closure#TO_SELF + */ + public void setResolveStrategy(int resolveStrategy) { + this.resolveStrategy = resolveStrategy; + } + + /** + * Gets the strategy which the closure uses to resolve methods and properties + * + * @return The resolve strategy + * + * @see groovy.lang.Closure#DELEGATE_FIRST + * @see groovy.lang.Closure#DELEGATE_ONLY + * @see groovy.lang.Closure#OWNER_FIRST + * @see groovy.lang.Closure#OWNER_ONLY + * @see groovy.lang.Closure#TO_SELF + */ + public int getResolveStrategy() { + return resolveStrategy; + } + + public Object getThisObject(){ + return thisObject; + } + + public Object getProperty(final String property) { + if ("delegate".equals(property)) { + return getDelegate(); + } else if ("owner".equals(property)) { + return getOwner(); + } else if ("maximumNumberOfParameters".equals(property)) { + return getMaximumNumberOfParameters(); + } else if ("parameterTypes".equals(property)) { + return getParameterTypes(); + } else if ("metaClass".equals(property)) { + return getMetaClass(); + } else if ("class".equals(property)) { + return getClass(); + } else if ("directive".equals(property)) { + return getDirective(); + } else if ("resolveStrategy".equals(property)) { + return getResolveStrategy(); + } else if ("thisObject".equals(property)) { + return getThisObject(); + } else { + switch(resolveStrategy) { + case DELEGATE_FIRST: + return getPropertyDelegateFirst(property); + case DELEGATE_ONLY: + return InvokerHelper.getProperty(this.delegate, property); + case OWNER_ONLY: + return InvokerHelper.getProperty(this.owner, property); + case TO_SELF: + return super.getProperty(property); + default: + return getPropertyOwnerFirst(property); + } + } + } + + private Object getPropertyDelegateFirst(String property) { + if (delegate == null) return getPropertyOwnerFirst(property); + return getPropertyTryThese(property, this.delegate, this.owner); + } + + private Object getPropertyOwnerFirst(String property) { + return getPropertyTryThese(property, this.owner, this.delegate); + } + + private Object getPropertyTryThese(String property, Object firstTry, Object secondTry) { + try { + // let's try getting the property on the first object + return InvokerHelper.getProperty(firstTry, property); + + } catch (MissingPropertyException e1) { + if (secondTry != null && firstTry != this && firstTry != secondTry) { + try { + // let's try getting the property on the second object + return InvokerHelper.getProperty(secondTry, property); + } catch (GroovyRuntimeException e2) { + // ignore, we'll throw e1 + } + } + throw e1; + + } catch (MissingFieldException e2) { // see GROOVY-5875 + if (secondTry != null && firstTry != this && firstTry != secondTry) { + try { + // let's try getting the property on the second object + return InvokerHelper.getProperty(secondTry, property); + } catch (GroovyRuntimeException e3) { + // ignore, we'll throw e2 + } + } + throw e2; + } + } + + public void setProperty(String property, Object newValue) { + if ("delegate".equals(property)) { + setDelegate(newValue); + } else if ("metaClass".equals(property)) { + setMetaClass((MetaClass) newValue); + } else if ("resolveStrategy".equals(property)) { + setResolveStrategy(((Number) newValue).intValue()); + } else if ("directive".equals(property)) { + setDirective(((Number) newValue).intValue()); + } else { + switch(resolveStrategy) { + case DELEGATE_FIRST: + setPropertyDelegateFirst(property, newValue); + break; + case DELEGATE_ONLY: + InvokerHelper.setProperty(this.delegate, property, newValue); + break; + case OWNER_ONLY: + InvokerHelper.setProperty(this.owner, property, newValue); + break; + case TO_SELF: + super.setProperty(property, newValue); + break; + default: + setPropertyOwnerFirst(property, newValue); + } + } + } + + private void setPropertyDelegateFirst(String property, Object newValue) { + if (delegate == null) setPropertyOwnerFirst(property, newValue); + else setPropertyTryThese(property, newValue, this.delegate, this.owner); + } + + private void setPropertyOwnerFirst(String property, Object newValue) { + setPropertyTryThese(property, newValue, this.owner, this.delegate); + } + + private void setPropertyTryThese(String property, Object newValue, Object firstTry, Object secondTry) { + try { + // let's try setting the property on the first object + InvokerHelper.setProperty(firstTry, property, newValue); + } catch (GroovyRuntimeException e1) { + if (firstTry != null && firstTry != this && firstTry != secondTry) { + try { + // let's try setting the property on the second object + InvokerHelper.setProperty(secondTry, property, newValue); + return; + } catch (GroovyRuntimeException e2) { + // ignore, we'll throw e1 + } + } + throw e1; + } + } + + public boolean isCase(Object candidate){ + if (bcw==null) { + bcw = new BooleanClosureWrapper(this); + } + return bcw.call(candidate); + } + + /** + * Invokes the closure without any parameters, returning any value if applicable. + * + * @return the value if applicable or null if there is no return statement in the closure + */ + public V call() { + final Object[] NOARGS = EMPTY_OBJECT_ARRAY; + return call(NOARGS); + } + + @SuppressWarnings("unchecked") + public V call(Object... args) { + try { + return (V) getMetaClass().invokeMethod(this,"doCall",args); + } catch (InvokerInvocationException e) { + UncheckedThrow.rethrow(e.getCause()); + return null; // unreachable statement + } catch (Exception e) { + return (V) throwRuntimeException(e); + } + } + + /** + * Invokes the closure, returning any value if applicable. + * + * @param arguments could be a single value or a List of values + * @return the value if applicable or null if there is no return statement in the closure + */ + public V call(final Object arguments) { + return call(new Object[]{arguments}); + } + + protected static Object throwRuntimeException(Throwable throwable) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException) throwable; + } else { + throw new GroovyRuntimeException(throwable.getMessage(), throwable); + } + } + + /** + * @return the owner Object to which method calls will go which is + * typically the outer class when the closure is constructed + */ + public Object getOwner() { + return this.owner; + } + + /** + * @return the delegate Object to which method calls will go which is + * typically the outer class when the closure is constructed + */ + public Object getDelegate() { + return this.delegate; + } + + /** + * Allows the delegate to be changed such as when performing markup building + * + * @param delegate the new delegate + */ + public void setDelegate(Object delegate) { + this.delegate = delegate; + } + + /** + * @return the parameter types of the longest doCall method + * of this closure + */ + public Class[] getParameterTypes() { + return parameterTypes; + } + + /** + * @return the maximum number of parameters a doCall method + * of this closure can take + */ + public int getMaximumNumberOfParameters() { + return maximumNumberOfParameters; + } + + /** + * @return a version of this closure which implements Writable. Note that + * the returned Writable also overrides {@link #toString()} in order + * to allow rendering the result directly to a String. + */ + public Closure asWritable() { + return new WritableClosure(); + } + + /* (non-Javadoc) + * @see java.lang.Runnable#run() + */ + public void run() { + call(); + } + + /** + * Support for Closure currying. + *

+ * Typical usage: + *

+     * def multiply = { a, b -> a * b }
+     * def doubler = multiply.curry(2)
+     * assert doubler(4) == 8
+     * 
+ * Note: special treatment is given to Closure vararg-style capability. + * If you curry a vararg parameter, you don't consume the entire vararg array + * but instead the first parameter of the vararg array as the following example shows: + *
+     * def a = { one, two, Object[] others -> one + two + others.sum() }
+     * assert a.parameterTypes.name == ['java.lang.Object', 'java.lang.Object', '[Ljava.lang.Object;']
+     * assert a(1,2,3,4) == 10
+     * def b = a.curry(1)
+     * assert b.parameterTypes.name == ['java.lang.Object', '[Ljava.lang.Object;']
+     * assert b(2,3,4) == 10
+     * def c = b.curry(2)
+     * assert c.parameterTypes.name == ['[Ljava.lang.Object;']
+     * assert c(3,4) == 10
+     * def d = c.curry(3)
+     * assert d.parameterTypes.name == ['[Ljava.lang.Object;']
+     * assert d(4) == 10
+     * def e = d.curry(4)
+     * assert e.parameterTypes.name == ['[Ljava.lang.Object;']
+     * assert e() == 10
+     * assert e(5) == 15
+     * 
+ * + * + * @param arguments the arguments to bind + * @return the new closure with its arguments bound + */ + public Closure curry(final Object... arguments) { + return new CurriedClosure(this, arguments); + } + + /** + * Support for Closure currying. + * + * @param argument the argument to bind + * @return the new closure with the argument bound + * @see #curry(Object...) + */ + public Closure curry(final Object argument) { + return curry(new Object[]{argument}); + } + + /** + * Support for Closure "right" currying. + * Parameters are supplied on the right rather than left as per the normal curry() method. + * Typical usage: + *
+     * def divide = { a, b -> a / b }
+     * def halver = divide.rcurry(2)
+     * assert halver(8) == 4
+     * 
+ * + * The position of the curried parameters will be calculated lazily, for example, + * if two overloaded doCall methods are available, the supplied arguments plus the + * curried arguments will be concatenated and the result used for method selection. + * + * @param arguments the arguments to bind + * @return the new closure with its arguments bound + * @see #curry(Object...) + */ + public Closure rcurry(final Object... arguments) { + return new CurriedClosure(-arguments.length, this, arguments); + } + + /** + * Support for Closure "right" currying. + * + * @param argument the argument to bind + * @return the new closure with the argument bound + * @see #rcurry(Object...) + */ + public Closure rcurry(final Object argument) { + return rcurry(new Object[]{argument}); + } + + /** + * Support for Closure currying at a given index. + * Parameters are supplied from index position "n". + * Typical usage: + *
+     * def caseInsensitive = { a, b -> a.toLowerCase() <=> b.toLowerCase() } as Comparator
+     * def caseSensitive = { a, b -> a <=> b } as Comparator
+     * def animals1 = ['ant', 'dog', 'BEE']
+     * def animals2 = animals1 + ['Cat']
+     * // curry middle param of this utility method:
+     * // Collections#binarySearch(List list, Object key, Comparator c)
+     * def catSearcher = Collections.&binarySearch.ncurry(1, "cat")
+     * [[animals1, animals2], [caseInsensitive, caseSensitive]].combinations().each{ a, c ->
+     *   def idx = catSearcher(a.sort(c), c)
+     *   print a.sort(c).toString().padRight(22)
+     *   if (idx < 0) println "Not found but would belong in position ${-idx - 1}"
+     *   else println "Found at index $idx"
+     * }
+     * // =>
+     * // [ant, BEE, dog]       Not found but would belong in position 2
+     * // [ant, BEE, Cat, dog]  Found at index 2
+     * // [BEE, ant, dog]       Not found but would belong in position 2
+     * // [BEE, Cat, ant, dog]  Not found but would belong in position 3
+     * 
+ * + * The position of the curried parameters will be calculated eagerly + * and implies all arguments prior to the specified n index are supplied. + * Default parameter values prior to the n index will not be available. + * + * @param n the index from which to bind parameters (may be -ve in which case it will be normalized) + * @param arguments the arguments to bind + * @return the new closure with its arguments bound + * @see #curry(Object...) + */ + public Closure ncurry(int n, final Object... arguments) { + return new CurriedClosure(n, this, arguments); + } + + /** + * Support for Closure currying at a given index. + * + * @param argument the argument to bind + * @return the new closure with the argument bound + * @see #ncurry(int, Object...) + */ + public Closure ncurry(int n, final Object argument) { + return ncurry(n, new Object[]{argument}); + } + + /** + * Support for Closure forward composition. + *

+ * Typical usage: + *

+     * def times2 = { a -> a * 2 }
+     * def add3 = { a -> a + 3 }
+     * def timesThenAdd = times2 >> add3
+     * // equivalent: timesThenAdd = { a -> add3(times2(a)) }
+     * assert timesThenAdd(3) == 9
+     * 
+ * + * @param other the Closure to compose with the current Closure + * @return the new composed Closure + */ + public Closure rightShift(final Closure other) { + return new ComposedClosure(this, other); + } + + /** + * Support for Closure reverse composition. + *

+ * Typical usage: + *

+     * def times2 = { a -> a * 2 }
+     * def add3 = { a -> a + 3 }
+     * def addThenTimes = times2 << add3
+     * // equivalent: addThenTimes = { a -> times2(add3(a)) }
+     * assert addThenTimes(3) == 12
+     * 
+ * + * @param other the Closure to compose with the current Closure + * @return the new composed Closure + */ + public Closure leftShift(final Closure other) { + return new ComposedClosure(other, this); + } + + /* * + * Alias for calling a Closure for non-closure arguments. + *

+ * Typical usage: + *

+     * def times2 = { a -> a * 2 }
+     * def add3 = { a -> a * 3 }
+     * assert add3 << times2 << 3 == 9
+     * 
+ * + * @param arg the argument to call the closure with + * @return the result of calling the Closure + */ + public V leftShift(final Object arg) { + return call(arg); + } + + /** + * Creates a caching variant of the closure. + * Whenever the closure is called, the mapping between the parameters and the return value is preserved in cache + * making subsequent calls with the same arguments fast. + * This variant will keep all cached values forever, i.e. till the closure gets garbage-collected. + * The returned function can be safely used concurrently from multiple threads, however, the implementation + * values high average-scenario performance and so concurrent calls on the memoized function with identical argument values + * may not necessarily be able to benefit from each other's cached return value. With this having been mentioned, + * the performance trade-off still makes concurrent use of memoized functions safe and highly recommended. + * + * The cache gets garbage-collected together with the memoized closure. + * + * @return A new closure forwarding to the original one while caching the results + */ + public Closure memoize() { + return Memoize.buildMemoizeFunction(new UnlimitedConcurrentCache(), this); + } + + /** + * Creates a caching variant of the closure with upper limit on the cache size. + * Whenever the closure is called, the mapping between the parameters and the return value is preserved in cache + * making subsequent calls with the same arguments fast. + * This variant will keep all values until the upper size limit is reached. Then the values in the cache start rotating + * using the LRU (Last Recently Used) strategy. + * The returned function can be safely used concurrently from multiple threads, however, the implementation + * values high average-scenario performance and so concurrent calls on the memoized function with identical argument values + * may not necessarily be able to benefit from each other's cached return value. With this having been mentioned, + * the performance trade-off still makes concurrent use of memoized functions safe and highly recommended. + * + * The cache gets garbage-collected together with the memoized closure. + * + * @param maxCacheSize The maximum size the cache can grow to + * @return A new function forwarding to the original one while caching the results + */ + public Closure memoizeAtMost(final int maxCacheSize) { + if (maxCacheSize < 0) throw new IllegalArgumentException("A non-negative number is required as the maxCacheSize parameter for memoizeAtMost."); + + return Memoize.buildMemoizeFunction(new LRUCache(maxCacheSize), this); + } + + /** + * Creates a caching variant of the closure with automatic cache size adjustment and lower limit + * on the cache size. + * Whenever the closure is called, the mapping between the parameters and the return value is preserved in cache + * making subsequent calls with the same arguments fast. + * This variant allows the garbage collector to release entries from the cache and at the same time allows + * the user to specify how many entries should be protected from the eventual gc-initiated eviction. + * Cached entries exceeding the specified preservation threshold are made available for eviction based on + * the LRU (Last Recently Used) strategy. + * Given the non-deterministic nature of garbage collector, the actual cache size may grow well beyond the limits + * set by the user if memory is plentiful. + * The returned function can be safely used concurrently from multiple threads, however, the implementation + * values high average-scenario performance and so concurrent calls on the memoized function with identical argument values + * may not necessarily be able to benefit from each other's cached return value. Also the protectedCacheSize parameter + * might not be respected accurately in such scenarios for some periods of time. With this having been mentioned, + * the performance trade-off still makes concurrent use of memoized functions safe and highly recommended. + * + * The cache gets garbage-collected together with the memoized closure. + * @param protectedCacheSize Number of cached return values to protect from garbage collection + * @return A new function forwarding to the original one while caching the results + */ + public Closure memoizeAtLeast(final int protectedCacheSize) { + if (protectedCacheSize < 0) throw new IllegalArgumentException("A non-negative number is required as the protectedCacheSize parameter for memoizeAtLeast."); + + return Memoize.buildSoftReferenceMemoizeFunction(protectedCacheSize, new UnlimitedConcurrentCache(), this); + } + + /** + * Creates a caching variant of the closure with automatic cache size adjustment and lower and upper limits + * on the cache size. + * Whenever the closure is called, the mapping between the parameters and the return value is preserved in cache + * making subsequent calls with the same arguments fast. + * This variant allows the garbage collector to release entries from the cache and at the same time allows + * the user to specify how many entries should be protected from the eventual gc-initiated eviction. + * Cached entries exceeding the specified preservation threshold are made available for eviction based on + * the LRU (Last Recently Used) strategy. + * Given the non-deterministic nature of garbage collector, the actual cache size may grow well beyond the protected + * size limits set by the user, if memory is plentiful. + * Also, this variant will never exceed in size the upper size limit. Once the upper size limit has been reached, + * the values in the cache start rotating using the LRU (Last Recently Used) strategy. + * The returned function can be safely used concurrently from multiple threads, however, the implementation + * values high average-scenario performance and so concurrent calls on the memoized function with identical argument values + * may not necessarily be able to benefit from each other's cached return value. Also the protectedCacheSize parameter + * might not be respected accurately in such scenarios for some periods of time. With this having been mentioned, + * the performance trade-off still makes concurrent use of memoized functions safe and highly recommended. + * + * The cache gets garbage-collected together with the memoized closure. + * @param protectedCacheSize Number of cached return values to protect from garbage collection + * @param maxCacheSize The maximum size the cache can grow to + * @return A new function forwarding to the original one while caching the results + */ + public Closure memoizeBetween(final int protectedCacheSize, final int maxCacheSize) { + if (protectedCacheSize < 0) throw new IllegalArgumentException("A non-negative number is required as the protectedCacheSize parameter for memoizeBetween."); + if (maxCacheSize < 0) throw new IllegalArgumentException("A non-negative number is required as the maxCacheSize parameter for memoizeBetween."); + if (protectedCacheSize > maxCacheSize) throw new IllegalArgumentException("The maxCacheSize parameter to memoizeBetween is required to be greater or equal to the protectedCacheSize parameter."); + + return Memoize.buildSoftReferenceMemoizeFunction(protectedCacheSize, new LRUCache(maxCacheSize), this); + } + + /** + * Builds a trampolined variant of the current closure. + * To prevent stack overflow due to deep recursion, functions can instead leverage the trampoline mechanism + * and avoid recursive calls altogether. Under trampoline, the function is supposed to perform one step of + * the calculation and, instead of a recursive call to itself or another function, it return back a new closure, + * which will be executed by the trampoline as the next step. + * Once a non-closure value is returned, the trampoline stops and returns the value as the final result. + * Here is an example: + *
+     * def fact
+     * fact = { n, total ->
+     *     n == 0 ? total : fact.trampoline(n - 1, n * total)
+     * }.trampoline()
+     * def factorial = { n -> fact(n, 1G)}
+     * println factorial(20) // => 2432902008176640000
+     * 
+ * + * @param args Parameters to the closure, so as the trampoline mechanism can call it + * @return A closure, which will execute the original closure on a trampoline. + */ + public Closure trampoline(final Object... args) { + return new TrampolineClosure(this.curry(args)); + } + + /** + * Builds a trampolined variant of the current closure. + * To prevent stack overflow due to deep recursion, functions can instead leverage the trampoline mechanism + * and avoid recursive calls altogether. Under trampoline, the function is supposed to perform one step of + * the calculation and, instead of a recursive call to itself or another function, it return back a new closure, + * which will be executed by the trampoline as the next step. + * Once a non-closure value is returned, the trampoline stops and returns the value as the final result. + * @return A closure, which will execute the original closure on a trampoline. + * @see #trampoline(Object...) + */ + public Closure trampoline() { + return new TrampolineClosure(this); + } + + /* (non-Javadoc) + * @see java.lang.Object#clone() + */ + public Object clone() { + try { + return super.clone(); + } catch (final CloneNotSupportedException e) { + return null; + } + } + + /* + * Implementation note: + * This has to be an inner class! + * + * Reason: + * Closure.this.call will call the outer call method, but + * with the inner class as executing object. This means any + * invokeMethod or getProperty call will be called on this + * inner class instead of the outer! + */ + private class WritableClosure extends Closure implements Writable { + public WritableClosure() { + super(Closure.this); + } + + /* (non-Javadoc) + * @see groovy.lang.Writable#writeTo(java.io.Writer) + */ + public Writer writeTo(Writer out) throws IOException { + Closure.this.call(new Object[]{out}); + + return out; + } + + /* (non-Javadoc) + * @see groovy.lang.GroovyObject#invokeMethod(java.lang.String, java.lang.Object) + */ + public Object invokeMethod(String method, Object arguments) { + if ("clone".equals(method)) { + return clone(); + } else if ("curry".equals(method)) { + return curry((Object[]) arguments); + } else if ("asWritable".equals(method)) { + return asWritable(); + } else { + return Closure.this.invokeMethod(method, arguments); + } + } + + /* (non-Javadoc) + * @see groovy.lang.GroovyObject#getProperty(java.lang.String) + */ + public Object getProperty(String property) { + return Closure.this.getProperty(property); + } + + /* (non-Javadoc) + * @see groovy.lang.GroovyObject#setProperty(java.lang.String, java.lang.Object) + */ + public void setProperty(String property, Object newValue) { + Closure.this.setProperty(property, newValue); + } + + /* (non-Javadoc) + * @see groovy.lang.Closure#call() + */ + public Object call() { + return ((Closure) getOwner()).call(); + } + + /* (non-Javadoc) + * @see groovy.lang.Closure#call(java.lang.Object) + */ + public Object call(Object arguments) { + return ((Closure) getOwner()).call(arguments); + } + + public Object call(Object... args) { + return ((Closure) getOwner()).call(args); + } + + public Object doCall(Object... args) { + return call(args); + } + + /* (non-Javadoc) + * @see groovy.lang.Closure#getDelegate() + */ + public Object getDelegate() { + return Closure.this.getDelegate(); + } + + /* (non-Javadoc) + * @see groovy.lang.Closure#setDelegate(java.lang.Object) + */ + public void setDelegate(Object delegate) { + Closure.this.setDelegate(delegate); + } + + /* (non-Javadoc) + * @see groovy.lang.Closure#getParameterTypes() + */ + public Class[] getParameterTypes() { + return Closure.this.getParameterTypes(); + } + + /* (non-Javadoc) + * @see groovy.lang.Closure#getParameterTypes() + */ + public int getMaximumNumberOfParameters() { + return Closure.this.getMaximumNumberOfParameters(); + } + + /* (non-Javadoc) + * @see groovy.lang.Closure#asWritable() + */ + public Closure asWritable() { + return this; + } + + /* (non-Javadoc) + * @see java.lang.Runnable#run() + */ + public void run() { + Closure.this.run(); + } + + /* (non-Javadoc) + * @see java.lang.Object#clone() + */ + public Object clone() { + return ((Closure) Closure.this.clone()).asWritable(); + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return Closure.this.hashCode(); + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object arg0) { + return Closure.this.equals(arg0); + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + public String toString() { + final StringWriter writer = new StringWriter(); + + try { + writeTo(writer); + } catch (IOException e) { + return null; + } + + return writer.toString(); + } + + public Closure curry(final Object... arguments) { + return (new CurriedClosure(this, arguments)).asWritable(); + } + + public void setResolveStrategy(int resolveStrategy) { + Closure.this.setResolveStrategy(resolveStrategy); + } + + public int getResolveStrategy() { + return Closure.this.getResolveStrategy(); + } + } + + /** + * @return Returns the directive. + */ + public int getDirective() { + return directive; + } + + /** + * @param directive The directive to set. + */ + public void setDirective(int directive) { + this.directive = directive; + } + + /** + * Returns a copy of this closure where the "owner", "delegate" and "thisObject" + * fields are null, allowing proper serialization when one of them is not serializable. + * + * @return a serializable closure. + * + * @since 1.8.5 + */ + @SuppressWarnings("unchecked") + public Closure dehydrate() { + Closure result = (Closure) this.clone(); + result.delegate = null; + result.owner = null; + result.thisObject = null; + return result; + } + + /** + * Returns a copy of this closure for which the delegate, owner and thisObject are + * replaced with the supplied parameters. Use this when you want to rehydrate a + * closure which has been made serializable thanks to the {@link #dehydrate()} + * method. + * @param delegate the closure delegate + * @param owner the closure owner + * @param thisObject the closure "this" object + * @return a copy of this closure where owner, delegate and thisObject are replaced + * + * @since 1.8.5 + */ + @SuppressWarnings("unchecked") + public Closure rehydrate(Object delegate, Object owner, Object thisObject) { + Closure result = (Closure) this.clone(); + result.delegate = delegate; + result.owner = owner; + result.thisObject = thisObject; + return result; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/lang/ClosureException.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/ClosureException.java b/src/main/groovy/groovy/lang/ClosureException.java new file mode 100644 index 0000000..12986ad --- /dev/null +++ b/src/main/groovy/groovy/lang/ClosureException.java @@ -0,0 +1,38 @@ +/* + * 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; + +/** + * An exception thrown by a closure invocation + * + * @author James Strachan + */ +public class ClosureException extends RuntimeException { + + private final Closure closure; + + public ClosureException(Closure closure, Throwable cause) { + super("Exception thrown by call to closure: " + closure + " reason: " + cause, cause); + this.closure = closure; + } + + public Closure getClosure() { + return closure; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/lang/ClosureInvokingMethod.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/ClosureInvokingMethod.java b/src/main/groovy/groovy/lang/ClosureInvokingMethod.java new file mode 100644 index 0000000..3e54278 --- /dev/null +++ b/src/main/groovy/groovy/lang/ClosureInvokingMethod.java @@ -0,0 +1,48 @@ +/* + * 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; + +/** + * An interface for MetaMethods that invoke closures to implements. Used by ExpandoMetaClass + * + * @see groovy.lang.ExpandoMetaClass + * + * @author Graeme Rocher + * @since 1.5 + */ +public interface ClosureInvokingMethod { + + /** + * Returns the original closure that this method invokes + * @return The closure + */ + Closure getClosure(); + + /** + * Is it a static method? + * @return True if it is + */ + boolean isStatic(); + + /** + * The method name + * @return The method name + */ + String getName(); +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/lang/Delegate.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/Delegate.java b/src/main/groovy/groovy/lang/Delegate.java new file mode 100644 index 0000000..180dc84 --- /dev/null +++ b/src/main/groovy/groovy/lang/Delegate.java @@ -0,0 +1,218 @@ +/* + * 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.transform.Undefined; +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; + +/** + * Annotation to automatically delegate part of the functionality of an owner class to the + * annotated delegation target. The target can be a field (or property) or a method's return value. + *

+ * The delegate type is either the type of the annotated field (or property) or the return type of + * the annotated method. The method can be thought of as a getter or factory method for the delegate. + * All public instance methods present in the delegate type and not present in the owner class + * will be added to owner class at compile time. The implementation of such automatically added + * methods is code which calls through to the delegate as per the normal delegate pattern. + *

+ * As an example, consider this code: + *

+ * class Event {
+ *     {@code @Delegate} Date when
+ *     String title, url
+ * }
+ *
+ * def gr8conf = new Event(title: "GR8 Conference",
+ *                           url: "http://www.gr8conf.org",
+ *                          when: Date.parse("yyyy/MM/dd", "2009/05/18"))
+ *
+ * def javaOne = new Event(title: "JavaOne",
+ *                           url: "http://java.sun.com/javaone/",
+ *                          when: Date.parse("yyyy/MM/dd", "2009/06/02"))
+ *
+ * assert gr8conf.before(javaOne.when)
+ * 
+ * + * In this example, the {@code Event} class will have a method called + * {@code before(Date otherDate)} as well as other public methods of the + * {@code Date} class. + * The implementation of the {@code before()} method will look like this: + *
+ *     public boolean before(Date otherDate) {
+ *         return when.before(otherDate);
+ *     }
+ * 
+ * + * By default, the owner class will also be modified to implement any interfaces + * implemented by the delegate type. So, in the example above, because {@code Date} + * implements {@code Cloneable} the following will be true: + * + *
+ * assert gr8conf instanceof Cloneable
+ * 
+ * + * This behavior can be disabled by setting the + * annotation's {@code interfaces} element to false, + * i.e. {@code @Delegate(interfaces = false)}, e.g. in the above + * example, the delegate definition would become: + *
+ *     {@code @Delegate}(interfaces = false) Date when
+ * 
+ * and the following would be true: + *
+ * assert !(gr8conf instanceof Cloneable)
+ * 
+ * + * If multiple delegation targets are used and the same method signature occurs + * in more than one of the respective delegate types, then the delegate will be + * made to the first defined target having that signature. If this does occur, + * it might be regarded as a smell (or at least poor style) and it might be + * clearer to do the delegation by long hand. + *

+ * By default, methods of the delegate type marked as {@code @Deprecated} are + * not automatically added to the owner class (but see the technical note + * about interfaces below). You can force these methods to + * be added by setting the annotation's {@code deprecated} element to true, + * i.e. {@code @Delegate(deprecated = true)}. + *

+ * For example, in the example above if we change the delegate definition to: + *

+ *     {@code @Delegate}(deprecated = true) Date when
+ * 
+ * then the following additional lines will execute successfully (during 2009): + *
+ * assert gr8conf.year + 1900 == 2009
+ * assert gr8conf.toGMTString().contains(" 2009 ")
+ * 
+ * Otherwise these lines produce a groovy.lang.MissingPropertyException + * or groovy.lang.MissingMethodException respectively as those two methods are + * {@code @Deprecated} in {@code Date}. + *

+ * Technical notes: + *

    + *
  • Static methods, synthetic methods or methods from the GroovyObject interface + * are not candidates for delegation
  • + *
  • Non-abstract non-static methods defined in the owner class or its superclasses take + * precedence over methods with identical signatures from a {@code @Delegate} target
  • + *
  • All methods defined in the owner class (including static, abstract or private etc.) + * take precedence over methods with identical signatures from a {@code @Delegate} target
  • + *
  • Recursive delegation to your own class is not allowed
  • + *
  • Mixing of {@code @Delegate} with default method arguments is known not to work in some cases. + * We recommend not using these features together.
  • + *
  • When the delegate type is an interface, the {@code deprecated} attribute will be + * ignored if the owner class implements that interface (i.e. you must set {@code interfaces=false} + * if you want the {@code deprecated} attribute to be used). Otherwise, the resulting class would + * not compile anyway without manually adding in any deprecated methods in the interface.
  • + *
  • {@code @Delegate} can work in combination with {@code @Lazy} when annotating a field (or property)
  • + *
+ */ +@java.lang.annotation.Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD}) +@GroovyASTTransformationClass("org.codehaus.groovy.transform.DelegateASTTransformation") +public @interface Delegate { + /** + * @return true if owner class should implement interfaces implemented by delegate type + */ + boolean interfaces() default true; + + /** + * Whether to apply the delegate pattern to deprecated methods; to avoid compilation + * errors, this is ignored if the type of the delegate target is an interface and + * {@code interfaces=true}. + * + * @return true if owner class should delegate to methods annotated with @Deprecated + */ + boolean deprecated() default false; + + /** + * Whether to carry over annotations from the methods of the delegate + * to your delegating method. Currently Closure annotation members are + * not supported. + * + * @return true if generated delegate methods should keep method annotations + */ + boolean methodAnnotations() default false; + + /** + * Whether to carry over annotations from the parameters of delegate + * methods to your delegating method. Currently Closure annotation members are + * not supported. + * + * @return true if generated delegate methods should keep parameter annotations + */ + boolean parameterAnnotations() default false; + + /** + * List of method and/or property names to exclude when delegating. + * Only one of 'includes', 'includeTypes', 'excludes' or 'excludeTypes' should be used. + * For convenience, a String with comma separated names + * can be used in addition to an array (using Groovy's literal list notation) of String values. + * If interfaces is true (the default), you will need to manually supply any methods excluded + * from delegation that are required for the interface. + * @since 2.2.0 + */ + String[] excludes() default {}; + + + /** + * List of interfaces containing method signatures to exclude when delegating. + * Only one of 'includes', 'includeTypes', 'excludes', 'excludeTypes' should be used. + * If interfaces is true (the default), you will need to manually supply any methods excluded + * from delegation that are required for the interface. + * @since 2.3.0 + */ + Class[] excludeTypes() default {}; + + /** + * List of method and/or property names to include when delegating. + * Only one of 'includes', 'includeTypes', 'excludes' or 'excludeTypes' should be used. + * For convenience, a String with comma separated names + * can be used in addition to an array (using Groovy's literal list notation) of String values. + * The default value is a special marker value indicating that no includes are defined; all fields + * are included if 'includes' remains undefined and 'excludes' is explicitly or implicitly an empty list. + * If interfaces is true (the default), you will need to manually supply any methods not included + * via delegation that are required for the interface. + * @since 2.2.0 + */ + String[] includes() default {Undefined.STRING}; + + /** + * List of interfaces containing method signatures to include when delegating. + * Only one of 'includes', 'includeTypes', 'excludes' or 'excludeTypes' should be used. + * The default value is a special marker value indicating that no includeTypes are defined. + * If interfaces is true (the default), you will need to manually supply any methods excluded + * from delegation that are required for the interface. + * @since 2.3.0 + */ + Class[] includeTypes() default {Undefined.CLASS.class}; + + /** + * Whether to apply the delegate pattern to all methods, including those with names that are considered internal. + * + * @return true if owner class should delegate to methods which have internal names + * @since 2.5.0 + */ + boolean allNames() default false; +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/lang/DelegatesTo.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/DelegatesTo.java b/src/main/groovy/groovy/lang/DelegatesTo.java new file mode 100644 index 0000000..54a5e36 --- /dev/null +++ b/src/main/groovy/groovy/lang/DelegatesTo.java @@ -0,0 +1,96 @@ +/* + * 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; + +/** + * This annotation can be used by API or DSL writers to document parameters which accept a closure. + * In that case, using this annotation, you can specify what the delegate type of the closure will + * be. This is important for IDE support. + *

+ * This annotation can also be used to help the type checker ({@link groovy.transform.TypeChecked}) + * which would not report errors then if the delegate is of the documented type. Of course, it is + * also compatible with {@link groovy.transform.CompileStatic}. + *

+ * Example: + *

+ * // Document the fact that the delegate of the closure will be an ExecSpec
+ * ExecResult exec(@DelegatesTo(ExecSpec) Closure closure) { ... }
+ * 
+ * + * @author Cedric Champeau + * @author Peter Niderwieser + * @since 2.1.0 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.PARAMETER}) +public @interface DelegatesTo { + Class value() default Target.class; + + /** + * The {@link Closure#resolveStrategy} used by the closure. + */ + int strategy() default Closure.OWNER_FIRST; + + /** + * The index of the generic type that will be the type of the closure's delegate. + * The generic types are considered with respect to the {@code @DelegatesTo.Target} annotated + * parameter for this usage, with the index starting at 0. + */ + int genericTypeIndex() default -1; + + /** + * In cases when there are multiple {@code @DelegatesTo.Target} annotated parameters, this + * member should be set to the {@link DelegatesTo.Target#value()} of the correct target. + */ + String target() default ""; + + /** + * The type member should be used when the type of the delegate cannot + * be represented with {@link #value()}, {@link #genericTypeIndex()} or + * {@link #target()}. In this case, it is possible to use a String to represent + * the type, at the cost of potential uncaught errors at compile time if the + * type is invalid and increased compile time. + * + * @return a String representation of a type + * @since 2.4.0 + */ + String type() default ""; + + /** + * Parameter annotation used to specify the delegate for a {@code @DelegatesTo} annotated + * parameter of the same method. + */ + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target({ElementType.PARAMETER}) + public static @interface Target { + + /** + * An identifier that should be used to disambiguate targets when there are + * multiple {@code @DelegatesTo.Target} annotated parameters. + */ + String value() default ""; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/lang/DelegatingMetaClass.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/DelegatingMetaClass.java b/src/main/groovy/groovy/lang/DelegatingMetaClass.java new file mode 100644 index 0000000..ee2ade9 --- /dev/null +++ b/src/main/groovy/groovy/lang/DelegatingMetaClass.java @@ -0,0 +1,308 @@ +/* + * 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.ClassNode; +import org.codehaus.groovy.runtime.InvokerHelper; + +import java.lang.reflect.Method; +import java.util.List; + +/** + * @author John Wilson + */ + +public class DelegatingMetaClass implements MetaClass, MutableMetaClass, GroovyObject { + protected MetaClass delegate; + + public DelegatingMetaClass(final MetaClass delegate) { + this.delegate = delegate; + } + + public DelegatingMetaClass(final Class theClass) { + this(GroovySystem.getMetaClassRegistry().getMetaClass(theClass)); + } + + public boolean isModified() { + return this.delegate instanceof MutableMetaClass && ((MutableMetaClass) this.delegate).isModified(); + } + + /* (non-Javadoc) + * @see groovy.lang.MetaClass#addNewInstanceMethod(java.lang.reflect.Method) + */ + public void addNewInstanceMethod(Method method) { + if (delegate instanceof MutableMetaClass) + ((MutableMetaClass) delegate).addNewInstanceMethod(method); + } + + /* (non-Javadoc) + * @see groovy.lang.MetaClass#addNewStaticMethod(java.lang.reflect.Method) + */ + public void addNewStaticMethod(Method method) { + if (delegate instanceof MutableMetaClass) + ((MutableMetaClass) delegate).addNewStaticMethod(method); + } + + public void addMetaMethod(MetaMethod metaMethod) { + if (delegate instanceof MutableMetaClass) + ((MutableMetaClass) delegate).addMetaMethod(metaMethod); + } + + public void addMetaBeanProperty(MetaBeanProperty metaBeanProperty) { + if (delegate instanceof MutableMetaClass) + ((MutableMetaClass) delegate).addMetaBeanProperty(metaBeanProperty); + } + + /* (non-Javadoc) + * @see groovy.lang.MetaClass#initialize() + */ + public void initialize() { + delegate.initialize(); + } + + /* (non-Javadoc) + * @see groovy.lang.MetaClass#getAttribute(java.lang.Object, java.lang.String) + */ + public Object getAttribute(Object object, String attribute) { + return delegate.getAttribute(object, attribute); + } + + /* (non-Javadoc) + * @see groovy.lang.MetaClass#getClassNode() + */ + public ClassNode getClassNode() { + return delegate.getClassNode(); + } + + /* (non-Javadoc) + * @see groovy.lang.MetaClass#getMetaMethods() + */ + public List getMetaMethods() { + return delegate.getMetaMethods(); + } + + /* (non-Javadoc) + * @see groovy.lang.MetaClass#getMethods() + */ + public List getMethods() { + return delegate.getMethods(); + } + + public List respondsTo(Object obj, String name, Object[] argTypes) { + return delegate.respondsTo(obj, name, argTypes); + } + + public List respondsTo(Object obj, String name) { + return delegate.respondsTo(obj, name); + } + + public MetaProperty hasProperty(Object obj, String name) { + return delegate.hasProperty(obj, name); + } + + /* (non-Javadoc) + * @see groovy.lang.MetaClass#getProperties() + */ + public List getProperties() { + return delegate.getProperties(); + } + + /* (non-Javadoc) + * @see groovy.lang.MetaClass#getProperty(java.lang.Object, java.lang.String) + */ + public Object getProperty(Object object, String property) { + return delegate.getProperty(object, property); + } + + /* (non-Javadoc) + * @see groovy.lang.MetaClass#invokeConstructor(java.lang.Object[]) + */ + public Object invokeConstructor(Object[] arguments) { + return delegate.invokeConstructor(arguments); + } + + /* (non-Javadoc) + * @see groovy.lang.MetaClass#invokeMethod(java.lang.Object, java.lang.String, java.lang.Object) + */ + public Object invokeMethod(Object object, String methodName, Object arguments) { + return delegate.invokeMethod(object, methodName, arguments); + } + + /* (non-Javadoc) + * @see groovy.lang.MetaClass#invokeMethod(java.lang.Object, java.lang.String, java.lang.Object[]) + */ + public Object invokeMethod(Object object, String methodName, Object[] arguments) { + return delegate.invokeMethod(object, methodName, arguments); + } + + /* (non-Javadoc) + * @see groovy.lang.MetaClass#invokeStaticMethod(java.lang.Object, java.lang.String, java.lang.Object[]) + */ + public Object invokeStaticMethod(Object object, String methodName, Object[] arguments) { + return delegate.invokeStaticMethod(object, methodName, arguments); + } + + /* (non-Javadoc) + * @see groovy.lang.MetaClass#setAttribute(java.lang.Object, java.lang.String, java.lang.Object) + */ + public void setAttribute(Object object, String attribute, Object newValue) { + delegate.setAttribute(object, attribute, newValue); + } + + /* (non-Javadoc) + * @see groovy.lang.MetaClass#setProperty(java.lang.Object, java.lang.String, java.lang.Object) + */ + public void setProperty(Object object, String property, Object newValue) { + delegate.setProperty(object, property, newValue); + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + return delegate.equals(obj); + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + return delegate.hashCode(); + } + + public String toString() { + return super.toString() + "[" + delegate.toString() + "]"; + } + + /** + * @deprecated + */ + @Deprecated + public MetaMethod pickMethod(String methodName, Class[] arguments) { + return delegate.pickMethod(methodName, arguments); + } + + public Object getAttribute(Class sender, Object receiver, String messageName, boolean useSuper) { + return this.delegate.getAttribute(sender, receiver, messageName, useSuper); + } + + public Object getProperty(Class sender, Object receiver, String messageName, boolean useSuper, boolean fromInsideClass) { + return this.delegate.getProperty(sender, receiver, messageName, useSuper, fromInsideClass); + } + + public MetaProperty getMetaProperty(String name) { + return this.delegate.getMetaProperty(name); + } + + public MetaMethod getStaticMetaMethod(String name, Object[] args) { + return this.delegate.getStaticMetaMethod(name, args); + } + + public MetaMethod getStaticMetaMethod(String name, Class[] argTypes) { + return this.delegate.getStaticMetaMethod(name, argTypes); + } + + public MetaMethod getMetaMethod(String name, Object[] args) { + return this.delegate.getMetaMethod(name, args); + } + + public Class getTheClass() { + return this.delegate.getTheClass(); + } + + public Object invokeMethod(Class sender, Object receiver, String methodName, Object[] arguments, boolean isCallToSuper, boolean fromInsideClass) { + return this.delegate.invokeMethod(sender, receiver, methodName, arguments, isCallToSuper, fromInsideClass); + } + + public Object invokeMissingMethod(Object instance, String methodName, Object[] arguments) { + return this.delegate.invokeMissingMethod(instance, methodName, arguments); + } + + public Object invokeMissingProperty(Object instance, String propertyName, Object optionalValue, boolean isGetter) { + return this.delegate.invokeMissingProperty(instance, propertyName, optionalValue, isGetter); + } + + public boolean isGroovyObject() { + return GroovyObject.class.isAssignableFrom(this.delegate.getTheClass()); + } + + public void setAttribute(Class sender, Object receiver, String messageName, Object messageValue, boolean useSuper, boolean fromInsideClass) { + this.delegate.setAttribute(sender, receiver, messageName, messageValue, useSuper, fromInsideClass); + } + + public void setProperty(Class sender, Object receiver, String messageName, Object messageValue, boolean useSuper, boolean fromInsideClass) { + this.delegate.setProperty(sender, receiver, messageName, messageValue, useSuper, fromInsideClass); + } + + public int selectConstructorAndTransformArguments(int numberOfConstructors, Object[] arguments) { + return this.delegate.selectConstructorAndTransformArguments(numberOfConstructors, arguments); + } + + public void setAdaptee(MetaClass adaptee) { + this.delegate = adaptee; + } + + public MetaClass getAdaptee() { + return this.delegate; + } + + public Object invokeMethod(String name, Object args) { + try { + return getMetaClass().invokeMethod(this, name, args); + } + catch (MissingMethodException e) { + if (delegate instanceof GroovyObject) + return ((GroovyObject) delegate).invokeMethod(name, args); + else + throw e; + } + } + + public Object getProperty(String property) { + try { + return getMetaClass().getProperty(this, property); + } + catch (MissingPropertyException e) { + if (delegate instanceof GroovyObject) + return ((GroovyObject) delegate).getProperty(property); + else + throw e; + } + } + + public void setProperty(String property, Object newValue) { + try { + getMetaClass().setProperty(this, property, newValue); + } + catch (MissingPropertyException e) { + if (delegate instanceof GroovyObject) + ((GroovyObject) delegate).setProperty(property, newValue); + else + throw e; + } + } + + public MetaClass getMetaClass() { + return InvokerHelper.getMetaClass(getClass()); + } + + public void setMetaClass(MetaClass metaClass) { + throw new UnsupportedOperationException(); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/lang/DeprecationException.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/DeprecationException.java b/src/main/groovy/groovy/lang/DeprecationException.java new file mode 100644 index 0000000..36ad893 --- /dev/null +++ b/src/main/groovy/groovy/lang/DeprecationException.java @@ -0,0 +1,43 @@ +/* + * 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; + +/** + * Use this exception to mark a method implementation as being deprecated. + * + * Use the message to indicate the recommended way of calling the desired functionality. + * Make throwing this exception the only line in the method implementation, i.e. unlike + * the JavaDoc deprecated feature there is no relay to the new implementation but an early + * and deliberate halt of execution ("fail early"). + * + * This exception is supposed to be used in the SNAPSHOT releases only. Before release, all + * references to this exception should be resolved and the according methods removed. + * + * @author Dierk Koenig + */ +public class DeprecationException extends RuntimeException { + + public DeprecationException(String message) { + super(message); + } + + public DeprecationException(String message, Throwable cause) { + super(message, cause); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/lang/EmptyRange.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/EmptyRange.java b/src/main/groovy/groovy/lang/EmptyRange.java new file mode 100644 index 0000000..b67b32e --- /dev/null +++ b/src/main/groovy/groovy/lang/EmptyRange.java @@ -0,0 +1,214 @@ +/* + * 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; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Constructing Ranges like 0..<0 + */ +public class EmptyRange extends AbstractList implements Range { + + /** + * The value at which the range originates (may be null). + */ + protected T at; + + /** + * Creates a new {@link EmptyRange}. + * + * @param at the value at which the range starts (may be null). + */ + public EmptyRange(T at) { + this.at = at; + } + + /** + * {@inheritDoc} + */ + @Override + public T getFrom() { + return at; + } + + /** + * {@inheritDoc} + */ + @Override + public T getTo() { + return at; + } + + /** + * Never true for an empty range. + * + * @return false + */ + @Override + public boolean isReverse() { + return false; + } + + /** + * Never true for an empty range. + * + * @return false + */ + @Override + public boolean containsWithinBounds(Object o) { + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public String inspect() { + return InvokerHelper.inspect(at) + "..<" + InvokerHelper.inspect(at); + } + + /** + * {@inheritDoc} + */ + public String toString() { + return (null == at) + ? "null..IndexOutOfBoundsException for an empty range. + * + * @throws IndexOutOfBoundsException always + */ + @Override + public T get(int index) { + throw new IndexOutOfBoundsException("can't get values from Empty Ranges"); + } + + /** + * Always throws UnsupportedOperationException for an empty range. + * + * @throws UnsupportedOperationException always + */ + @Override + public boolean add(T o) { + throw new UnsupportedOperationException("cannot add to Empty Ranges"); + } + + /** + * Always throws UnsupportedOperationException for an empty range. + * + * @throws UnsupportedOperationException + */ + @Override + public boolean addAll(int index, Collection c) { + throw new UnsupportedOperationException("cannot add to Empty Ranges"); + } + + /** + * Always throws UnsupportedOperationException for an empty range. + * + * @throws UnsupportedOperationException + */ + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException("cannot add to Empty Ranges"); + } + + /** + * Always throws UnsupportedOperationException for an empty range. + * + * @throws UnsupportedOperationException + */ + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException("cannot remove from Empty Ranges"); + } + + /** + * Always throws UnsupportedOperationException for an empty range. + * + * @throws UnsupportedOperationException + */ + @Override + public T remove(int index) { + throw new UnsupportedOperationException("cannot remove from Empty Ranges"); + } + + /** + * Always throws UnsupportedOperationException for an empty range. + * + * @throws UnsupportedOperationException + */ + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException("cannot remove from Empty Ranges"); + } + + /** + * Always throws UnsupportedOperationException for an empty range. + * + * @throws UnsupportedOperationException + */ + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException("cannot retainAll in Empty Ranges"); + } + + /** + * Always throws UnsupportedOperationException for an empty range. + * + * @throws UnsupportedOperationException + */ + @Override + public T set(int index, T element) { + throw new UnsupportedOperationException("cannot set in Empty Ranges"); + } + + /** + * Always does nothing for an empty range. + */ + @Override + public void step(int step, Closure closure) { + } + + /** + * Always returns an empty list for an empty range. + */ + @Override + public List step(int step) { + return new ArrayList(); + } +}