blob: da59968b6cf92d0c5bdb1388ecadfb78b1b322f0 [file] [log] [blame]
/*
* 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.apache.groovy.io.StringBuilderWriter;
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.ConcurrentCommonCache;
import org.codehaus.groovy.runtime.memoize.ConcurrentSoftCache;
import org.codehaus.groovy.runtime.memoize.LRUCache;
import org.codehaus.groovy.runtime.memoize.Memoize;
import java.io.IOException;
import java.io.Serializable;
import java.io.Writer;
/**
* Represents any closure object in Groovy.
* <p>
* Groovy allows instances of Closures to be called in a
* short form. For example:
* <pre class="groovyTestCase">
* def a = 1
* def c = { a }
* assert c() == 1
* </pre>
* 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
* <pre class="groovyTestCase">
* def a = 1
* def c = {a}
* assert c.call() == 1
* </pre>
*/
public abstract class Closure<V> extends GroovyObjectSupport implements Cloneable, Runnable, GroovyCallable<V>, Serializable {
/**
* With this resolveStrategy set the closure will attempt to resolve property references and methods to the
* owner first, then the delegate (<b>this is the default strategy</b>).
*
* For example the following code:
* <pre class="groovyTestCase">
* 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()
* </pre>
* Will succeed, because the x and y fields declared in the Test class shadow the variables in the delegate.<p>
* <i>Note that local variables are always looked up first, independently of the resolution strategy.</i>
*/
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:
* <pre class="groovyTestCase">
* 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()
* </pre>
* This will succeed, because the x and y variables declared in the delegate shadow the fields in the owner class.<p>
* <i>Note that local variables are always looked up first, independently of the resolution strategy.</i>
*/
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 :
*
* <pre class="groovyTestCase">
* 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
* }
* }
*
* groovy.test.GroovyAssert.shouldFail(MissingPropertyException) {
* new Test().run()
* }
* </pre>
*
* will throw "No such property: z" error because even if the z variable is declared in the delegate, no
* lookup is made.<p>
* <i>Note that local variables are always looked up first, independently of the resolution strategy.</i>
*/
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 :
*
* <pre class="groovyTestCase">
* 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
* }
* }
*
* groovy.test.GroovyAssert.shouldFail {
* new Test().run()
* }
* </pre>
*
* will throw an error because even if the owner declares a "z" field, the resolution strategy will bypass
* lookup in the owner.<p>
* <i>Note that local variables are always looked up first, independently of the resolution strategy.</i>
*/
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.<p>
* <i>Note that local variables are always looked up first, independently of the resolution strategy.</i>
*/
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<Object>(null) {
private static final long serialVersionUID = 730973623329943963L;
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;
}
@Override
public Object getProperty(final String property) {
if ("delegate".equals(property)) {
return getDelegate();
}
if ("owner".equals(property)) {
return getOwner();
}
if ("maximumNumberOfParameters".equals(property)) {
return getMaximumNumberOfParameters();
}
if ("parameterTypes".equals(property)) {
return getParameterTypes();
}
if ("metaClass".equals(property)) {
return getMetaClass();
}
if ("class".equals(property)) {
return getClass();
}
if ("directive".equals(property)) {
return getDirective();
}
if ("resolveStrategy".equals(property)) {
return getResolveStrategy();
}
if ("thisObject".equals(property)) {
return getThisObject();
}
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 | MissingFieldException 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;
}
}
@Override
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 with no arguments, returning any value if applicable.
*
* @return The value if applicable or null if there is no return statement in the closure.
*/
@Override
public V call() {
return call(EMPTY_OBJECT_ARRAY);
}
/**
* Invokes the closure with given argument(s), 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});
}
/**
* Invokes the closure with given argument(s), returning any value if applicable.
*
* @return The value if applicable or null if there is no return statement in the closure.
*/
@SuppressWarnings("unchecked")
public V call(final Object... arguments) {
try {
return (V) getMetaClass().invokeMethod(this, "doCall", arguments);
} catch (InvokerInvocationException e) {
UncheckedThrow.rethrow(e.getCause());
return null; // unreachable statement
} catch (Exception e) {
return (V) throwRuntimeException(e);
}
}
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()
*/
@Override
public void run() {
call();
}
/**
* Support for Closure currying.
* <p>
* Typical usage:
* <pre class="groovyTestCase">
* def multiply = { a, b {@code ->} a * b }
* def doubler = multiply.curry(2)
* assert doubler(4) == 8
* </pre>
* 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:
* <pre class="groovyTestCase">
* def a = { one, two, Object[] others {@code ->} 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
* </pre>
*
*
* @param arguments the arguments to bind
* @return the new closure with its arguments bound
*/
public Closure<V> curry(final Object... arguments) {
return new CurriedClosure<V>(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<V> 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:
* <pre class="groovyTestCase">
* def divide = { a, b {@code ->} a / b }
* def halver = divide.rcurry(2)
* assert halver(8) == 4
* </pre>
*
* 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<V> rcurry(final Object... arguments) {
return new CurriedClosure<V>(-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<V> 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:
* <pre>
* def caseInsensitive = { a, b {@code ->} a.toLowerCase() {@code <=>} b.toLowerCase() } as Comparator
* def caseSensitive = { a, b {@code ->} a {@code <=>} 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)
* {@code def catSearcher = Collections.&binarySearch.ncurry(1, "cat")}
* [[animals1, animals2], [caseInsensitive, caseSensitive]].combinations().each{ a, c {@code ->}
* def idx = catSearcher(a.sort(c), c)
* print a.sort(c).toString().padRight(22)
* {@code if (idx < 0) println "Not found but would belong in position ${-idx - 1}"}
* else println "Found at index $idx"
* }
* // {@code =>}
* // [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
* </pre>
*
* 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<V> ncurry(int n, final Object... arguments) {
return new CurriedClosure<V>(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<V> ncurry(int n, final Object argument) {
return ncurry(n, new Object[]{argument});
}
/**
* Support for Closure forward composition.
* <p>
* Typical usage:
* <pre class="groovyTestCase">
* def times2 = { a {@code ->} a * 2 }
* def add3 = { a {@code ->} a + 3 }
* def timesThenAdd = times2 {@code >>} add3
* // equivalent: timesThenAdd = { a {@code ->} add3(times2(a)) }
* assert timesThenAdd(3) == 9
* </pre>
*
* @param other the Closure to compose with the current Closure
* @return the new composed Closure
*/
public <W> Closure<W> rightShift(final Closure<W> other) {
return new ComposedClosure<W>(this, other);
}
/**
* Support for Closure reverse composition.
* <p>
* Typical usage:
* <pre class="groovyTestCase">
* def times2 = { a {@code ->} a * 2 }
* def add3 = { a {@code ->} a + 3 }
* def addThenTimes = times2 {@code <<} add3
* // equivalent: addThenTimes = { a {@code ->} times2(add3(a)) }
* assert addThenTimes(3) == 12
* </pre>
*
* @param other the Closure to compose with the current Closure
* @return the new composed Closure
*/
public Closure<V> leftShift(final Closure other) {
return new ComposedClosure<V>(other, this);
}
/**
* Alias for {@link #rightShift(Closure)}
*
* @return the newly composed closure
*/
public <W> Closure<W> andThen(final Closure<W> other) {
return rightShift(other);
}
/**
* Call {@link #andThen(Closure)} on {@code this}.
*
* @return the newly composed closure
*/
public Closure<V> andThenSelf() {
return andThen(this);
}
/**
* Call {@link #andThen(Closure)} on {@code this} exactly {@code times} times.
*
* @param times the number of times to reverse compose the closure with itself
* @return the newly composed closure
*/
public Closure<V> andThenSelf(int times) {
if (times == 0) return this;
if (times == 1) return andThen(this);
return andThen(andThenSelf(times - 1));
}
/**
* Alias for {@link #leftShift(Closure)}
*
* @return the newly composed closure
*/
public Closure<V> compose(final Closure other) {
return leftShift(other);
}
/**
* Call {@link #compose(Closure)} on {@code this}.
*
* @return the newly composed closure
*/
public Closure<V> composeSelf() {
return compose(this);
}
/**
* Call {@link #compose(Closure)} on {@code this} exactly {@code times} times.
*
* @param times the number of times to compose the closure with itself
* @return the newly composed closure
*/
public Closure<V> composeSelf(int times) {
if (times == 0) return this;
if (times == 1) return compose(this);
return compose(composeSelf(times - 1));
}
/**
* Alias for calling a Closure for non-closure arguments.
* <p>
* Typical usage:
* <pre class="groovyTestCase">
* def times2 = { a {@code ->} a * 2 }
* def add3 = { a {@code ->} a + 3 }
* assert add3 {@code <<} times2 {@code <<} 3 == 9
* </pre>
*
* @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<V> memoize() {
return Memoize.buildMemoizeFunction(new ConcurrentCommonCache(), 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<V> 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<V> 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 ConcurrentSoftCache<Object, Object>(), 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<V> 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 ConcurrentSoftCache<Object, Object>(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 returns 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:
* <pre>
* def fact
* fact = { n, total {@code ->}
* n == 0 ? total : fact.trampoline(n - 1, n * total)
* }.trampoline()
* def factorial = { n {@code ->} fact(n, 1G)}
* println factorial(20) // {@code =>} 2432902008176640000
* </pre>
*
* @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<V> trampoline(final Object... args) {
return new TrampolineClosure<V>(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 returns 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<V> trampoline() {
return new TrampolineClosure<V>(this);
}
/* (non-Javadoc)
* @see java.lang.Object#clone()
*/
@Override
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 {
private static final long serialVersionUID = -5749205698681690370L;
public WritableClosure() {
super(Closure.this);
}
/* (non-Javadoc)
* @see groovy.lang.Writable#writeTo(java.io.Writer)
*/
@Override
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)
*/
@Override
public Object invokeMethod(String method, Object arguments) {
if ("clone".equals(method)) {
return clone();
}
if ("curry".equals(method)) {
return curry((Object[]) arguments);
}
if ("asWritable".equals(method)) {
return asWritable();
}
return Closure.this.invokeMethod(method, arguments);
}
/* (non-Javadoc)
* @see groovy.lang.GroovyObject#getProperty(java.lang.String)
*/
@Override
public Object getProperty(String property) {
return Closure.this.getProperty(property);
}
/* (non-Javadoc)
* @see groovy.lang.GroovyObject#setProperty(java.lang.String, java.lang.Object)
*/
@Override
public void setProperty(String property, Object newValue) {
Closure.this.setProperty(property, newValue);
}
/* (non-Javadoc)
* @see groovy.lang.Closure#call()
*/
@Override
public Object call() {
return ((Closure) getOwner()).call();
}
/* (non-Javadoc)
* @see groovy.lang.Closure#call(java.lang.Object)
*/
@Override
public Object call(Object arguments) {
return ((Closure) getOwner()).call(arguments);
}
@Override
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()
*/
@Override
public Object getDelegate() {
return Closure.this.getDelegate();
}
/* (non-Javadoc)
* @see groovy.lang.Closure#setDelegate(java.lang.Object)
*/
@Override
public void setDelegate(Object delegate) {
Closure.this.setDelegate(delegate);
}
/* (non-Javadoc)
* @see groovy.lang.Closure#getParameterTypes()
*/
@Override
public Class[] getParameterTypes() {
return Closure.this.getParameterTypes();
}
/* (non-Javadoc)
* @see groovy.lang.Closure#getParameterTypes()
*/
@Override
public int getMaximumNumberOfParameters() {
return Closure.this.getMaximumNumberOfParameters();
}
/* (non-Javadoc)
* @see groovy.lang.Closure#asWritable()
*/
@Override
public Closure asWritable() {
return this;
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
Closure.this.run();
}
/* (non-Javadoc)
* @see java.lang.Object#clone()
*/
@Override
public Object clone() {
return ((Closure) Closure.this.clone()).asWritable();
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return Closure.this.hashCode();
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object arg0) {
return Closure.this.equals(arg0);
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
final Writer writer = new StringBuilderWriter();
try {
writeTo(writer);
} catch (IOException e) {
return "";
}
return writer.toString();
}
@Override
public Closure curry(final Object... arguments) {
return (new CurriedClosure(this, arguments)).asWritable();
}
@Override
public void setResolveStrategy(int resolveStrategy) {
Closure.this.setResolveStrategy(resolveStrategy);
}
@Override
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<V> dehydrate() {
Closure<V> result = (Closure<V>) 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<V> rehydrate(Object delegate, Object owner, Object thisObject) {
Closure<V> result = (Closure<V>) this.clone();
result.delegate = delegate;
result.owner = owner;
result.thisObject = thisObject;
return result;
}
}