Skip to content

Commit

Permalink
GROOVY-5051, GROOVY-10568: add test case
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-milles committed Jul 14, 2023
1 parent 5aed61d commit e16a44c
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 88 deletions.
2 changes: 1 addition & 1 deletion src/main/java/groovy/lang/MetaClassImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -2799,7 +2799,7 @@ public void setProperty(final Class sender, final Object object, final String na
checkInitalised();

//----------------------------------------------------------------------
// Unwrap wrapped values fo now - the new MOP will handle them properly
// Unwrap wrapped values for now - the new MOP will handle them properly
//----------------------------------------------------------------------
if (newValue instanceof Wrapper) newValue = ((Wrapper) newValue).unwrap();

Expand Down
128 changes: 70 additions & 58 deletions src/main/java/org/codehaus/groovy/runtime/CurriedClosure.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@

import groovy.lang.Closure;

import static org.codehaus.groovy.runtime.ArrayGroovyMethods.last;

/**
* A wrapper for Closure to support currying.
* Normally used only internally through the <code>curry()</code>, <code>rcurry()</code> or
* A wrapper for Closure to support currying. Normally used only internally
* through the <code>curry()</code>, <code>rcurry()</code> or
* <code>ncurry()</code> methods on <code>Closure</code>.
* <p>
* Typical usages:
* <pre class="groovyTestCase">
* // normal usage
Expand All @@ -47,118 +50,127 @@
public final class CurriedClosure<V> extends Closure<V> {

private static final long serialVersionUID = 2077643745780234126L;
private final Object[] curriedParams;
private final Object[] curriedArguments;
private final int minParamsExpected;
private int index;
private Class varargType = null;
/** the last parameter type, if it's an array */
private Class<?> varargType;

/**
* Creates the curried closure.
*
* @param index the position where the parameters should be injected (-ve for lazy)
* @param uncurriedClosure the closure to be called after the curried parameters are injected
* @param arguments the supplied parameters
*/
public CurriedClosure(int index, Closure<V> uncurriedClosure, Object... arguments) {
public CurriedClosure(final int index, final Closure<V> uncurriedClosure, final Object... arguments) {
super(uncurriedClosure.clone());
curriedParams = arguments;

this.index = index;
final int origMaxLen = uncurriedClosure.getMaximumNumberOfParameters();
maximumNumberOfParameters = origMaxLen - arguments.length;
Class[] classes = uncurriedClosure.getParameterTypes();
Class lastType = classes.length == 0 ? null : classes[classes.length-1];
if (lastType != null && lastType.isArray()) {
varargType = lastType;
this.curriedArguments = arguments;
int maxLen = uncurriedClosure.getMaximumNumberOfParameters();
this.maximumNumberOfParameters = (maxLen - arguments.length);

Class<?>[] parameterTypes = uncurriedClosure.getParameterTypes();
if (parameterTypes.length > 0 && last(parameterTypes).isArray()){
this.varargType = last(parameterTypes);
}

if (!isVararg()) {
if (isVararg()) {
this.minParamsExpected = 0;
} else {
// perform some early param checking for non-vararg case
if (index < 0) {
// normalise
this.index += origMaxLen;
minParamsExpected = 0;
this.index += maxLen;
this.minParamsExpected = 0;
} else {
minParamsExpected = index + arguments.length;
this.minParamsExpected = index + arguments.length;
}
if (maximumNumberOfParameters < 0) {
throw new IllegalArgumentException("Can't curry " + arguments.length + " arguments for a closure with " + origMaxLen + " parameters.");

if (this.maximumNumberOfParameters < 0) {
throw new IllegalArgumentException("Can't curry " + arguments.length + " arguments for a closure with " + maxLen + " parameters.");
}
if (index < 0) {
if (index < -origMaxLen || index > -arguments.length)
throw new IllegalArgumentException("To curry " + arguments.length + " argument(s) expect index range " +
(-origMaxLen) + ".." + (-arguments.length) + " but found " + index);
} else if (index > maximumNumberOfParameters) {
throw new IllegalArgumentException("To curry " + arguments.length + " argument(s) expect index range 0.." +
maximumNumberOfParameters + " but found " + index);
int lower = -maxLen;
int upper = -arguments.length;
if (index < lower || index > upper)
throw new IllegalArgumentException("To curry " + arguments.length + " argument(s) expect index range " + lower + ".." + upper + " but found " + index);
} else if (index > this.maximumNumberOfParameters) {
throw new IllegalArgumentException("To curry " + arguments.length + " argument(s) expect index range 0.." + this.maximumNumberOfParameters + " but found " + index);
}
} else {
minParamsExpected = 0;
}
}

public CurriedClosure(Closure<V> uncurriedClosure, Object... arguments) {
public CurriedClosure(final Closure<V> uncurriedClosure, final Object... arguments) {
this(0, uncurriedClosure, arguments);
}

public Object[] getUncurriedArguments(Object... arguments) {
//--------------------------------------------------------------------------

public Object[] getUncurriedArguments(final Object... arguments) {
if (isVararg()) {
int normalizedIndex = index < 0 ? index + arguments.length + curriedParams.length : index;
int normalizedIndex = index < 0 ? index + arguments.length + curriedArguments.length : index;
if (normalizedIndex < 0 || normalizedIndex > arguments.length) {
throw new IllegalArgumentException("When currying expected index range between " +
(-arguments.length - curriedParams.length) + ".." + (arguments.length + curriedParams.length) + " but found " + index);
(-arguments.length - curriedArguments.length) + ".." + (arguments.length + curriedArguments.length) + " but found " + index);
}
return createNewCurriedParams(normalizedIndex, arguments);
return getArguments(normalizedIndex, arguments);
}
if (curriedParams.length + arguments.length < minParamsExpected) {
if (curriedArguments.length + arguments.length < minParamsExpected) {
throw new IllegalArgumentException("When currying expected at least " + index + " argument(s) to be supplied before known curried arguments but found " + arguments.length);
}
int newIndex = Math.min(index, curriedParams.length + arguments.length - 1);
int newIndex = Math.min(index, curriedArguments.length + arguments.length - 1);
// rcurried arguments are done lazily to allow normal method selection between overloaded alternatives
newIndex = Math.min(newIndex, arguments.length);
return createNewCurriedParams(newIndex, arguments);
return getArguments(newIndex, arguments);
}

private Object[] createNewCurriedParams(int normalizedIndex, Object[] arguments) {
Object[] newCurriedParams = new Object[curriedParams.length + arguments.length];
System.arraycopy(arguments, 0, newCurriedParams, 0, normalizedIndex);
System.arraycopy(curriedParams, 0, newCurriedParams, normalizedIndex, curriedParams.length);
if (arguments.length - normalizedIndex > 0)
System.arraycopy(arguments, normalizedIndex, newCurriedParams, curriedParams.length + normalizedIndex, arguments.length - normalizedIndex);
return newCurriedParams;
private Object[] getArguments(final int index, final Object[] arguments) {
Object[] newArguments = new Object[curriedArguments.length + arguments.length];
System.arraycopy(arguments, 0, newArguments, 0, index);
System.arraycopy(curriedArguments, 0, newArguments, index, curriedArguments.length);
if (arguments.length - index > 0)
System.arraycopy(arguments, index, newArguments, curriedArguments.length + index, arguments.length - index);
return newArguments;
}

@Override
public void setDelegate(Object delegate) {
((Closure) getOwner()).setDelegate(delegate);
public void setDelegate(final Object delegate) {
getOwner().setDelegate(delegate);
}

@Override
public Object getDelegate() {
return ((Closure) getOwner()).getDelegate();
return getOwner().getDelegate();
}

@Override
@SuppressWarnings("unchecked")
public Closure<V> getOwner() {
return (Closure<V>) super.getOwner();
}

@Override
public void setResolveStrategy(int resolveStrategy) {
((Closure) getOwner()).setResolveStrategy(resolveStrategy);
public void setResolveStrategy(final int resolveStrategy) {
getOwner().setResolveStrategy(resolveStrategy);
}

@Override
public int getResolveStrategy() {
return ((Closure) getOwner()).getResolveStrategy();
return getOwner().getResolveStrategy();
}

@Override
@SuppressWarnings("unchecked")
public Object clone() {
Closure<V> uncurriedClosure = (Closure<V>) ((Closure) getOwner()).clone();
return new CurriedClosure<V>(index, uncurriedClosure, curriedParams);
@SuppressWarnings("unchecked")
Closure<V> uncurriedClosure = (Closure<V>) getOwner().clone();
return new CurriedClosure<V>(index, uncurriedClosure, curriedArguments);
}

@Override
public Class[] getParameterTypes() {
Class[] oldParams = ((Closure) getOwner()).getParameterTypes();
Class[] oldParams = getOwner().getParameterTypes();
int extraParams = 0;
int gobbledParams = curriedParams.length;
int gobbledParams = curriedArguments.length;
if (isVararg()) {
int numNonVarargs = oldParams.length - 1;
if (index < 0) {
Expand All @@ -167,7 +179,7 @@ public Class[] getParameterTypes() {
// so work out minimal type params and vararg on end will allow for other possibilities
if (absIndex > numNonVarargs) gobbledParams = numNonVarargs;
int newNumNonVarargs = numNonVarargs - gobbledParams;
if (absIndex - curriedParams.length > newNumNonVarargs) extraParams = absIndex - curriedParams.length - newNumNonVarargs;
if (absIndex - curriedArguments.length > newNumNonVarargs) extraParams = absIndex - curriedArguments.length - newNumNonVarargs;
int keptParams = Math.max(numNonVarargs - absIndex, 0);
Class[] newParams = new Class[keptParams + newNumNonVarargs + extraParams + 1];
System.arraycopy(oldParams, 0, newParams, 0, keptParams);
Expand All @@ -177,19 +189,19 @@ public Class[] getParameterTypes() {
return newParams;
}
int leadingKept = Math.min(index, numNonVarargs);
int trailingKept = Math.max(numNonVarargs - leadingKept - curriedParams.length, 0);
int trailingKept = Math.max(numNonVarargs - leadingKept - curriedArguments.length, 0);
if (index > leadingKept) extraParams = index - leadingKept;
Class[] newParams = new Class[leadingKept + trailingKept + extraParams + 1];
System.arraycopy(oldParams, 0, newParams, 0, leadingKept);
if (trailingKept > 0) System.arraycopy(oldParams, leadingKept + curriedParams.length, newParams, leadingKept, trailingKept);
if (trailingKept > 0) System.arraycopy(oldParams, leadingKept + curriedArguments.length, newParams, leadingKept, trailingKept);
for (int i = 0; i < extraParams; i++) newParams[leadingKept + trailingKept + i] = varargType.getComponentType();
newParams[newParams.length - 1] = varargType;
return newParams;
}
Class[] newParams = new Class[oldParams.length - gobbledParams + extraParams];
System.arraycopy(oldParams, 0, newParams, 0, index);
if (newParams.length - index > 0)
System.arraycopy(oldParams, curriedParams.length + index, newParams, index, newParams.length - index);
System.arraycopy(oldParams, curriedArguments.length + index, newParams, index, newParams.length - index);
return newParams;
}

Expand Down
5 changes: 3 additions & 2 deletions src/main/java/org/codehaus/groovy/runtime/InvokerHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,10 @@ public static void setPropertySafe2(Object newValue, Object object, String prope
}

/**
* Returns the method pointer for the given object name
* Returns a method closure for the given object and name.
*/
public static Closure getMethodPointer(Object object, String methodName) {
@SuppressWarnings("rawtypes")
public static Closure getMethodPointer(final Object object, final String methodName) {
if (object == null) {
throw new NullPointerException("Cannot access method pointer for '" + methodName + "' on null object");
}
Expand Down
30 changes: 14 additions & 16 deletions src/main/java/org/codehaus/groovy/runtime/MethodClosure.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import groovy.lang.Closure;
import groovy.lang.MetaMethod;
import org.codehaus.groovy.reflection.CachedConstructor;
import org.codehaus.groovy.reflection.ReflectionCache;

import java.util.Arrays;
Expand Down Expand Up @@ -54,20 +53,21 @@ public MethodClosure(final Object owner, final String method) {
this.maximumNumberOfParameters = 0;
this.parameterTypes = MetaClassHelper.EMPTY_TYPE_ARRAY;

Class<?> clazz = owner.getClass() == Class.class ? (Class<?>) owner : owner.getClass();
Class<?> theClass = owner.getClass();
if (theClass == Class.class) theClass = (Class<?>) owner;

if (NEW.equals(method)) {
if (clazz.isArray()) {
Class<?>[] sizeTypes = new Class[ArrayTypeUtils.dimension(clazz)];
if (method.equals(NEW)) {
if (theClass.isArray()) {
Class<?>[] sizeTypes = new Class[ArrayTypeUtils.dimension(theClass)];
Arrays.fill(sizeTypes, int.class);
setParameterTypesAndNumber(sizeTypes);
} else {
for (CachedConstructor c : ReflectionCache.getCachedClass(clazz).getConstructors()) {
for (var c : ReflectionCache.getCachedClass(theClass).getConstructors()) {
setParameterTypesAndNumber(c.getNativeParameterTypes());
}
}
} else {
for (MetaMethod m : InvokerHelper.getMetaClass(clazz).respondsTo(owner, method)) {
for (var m : InvokerHelper.getMetaClass(theClass).respondsTo(owner, method)) {
setParameterTypesAndNumber(makeParameterTypes(owner, m));
if (!m.isStatic()) {
this.anyInstanceMethodExists = true;
Expand All @@ -76,19 +76,17 @@ public MethodClosure(final Object owner, final String method) {
}
}

private void setParameterTypesAndNumber(final Class[] newParameterTypes) {
if (!(newParameterTypes.length > this.maximumNumberOfParameters)) {
return;
private void setParameterTypesAndNumber(final Class[] parameterTypes) {
if (parameterTypes.length > this.maximumNumberOfParameters) {
this.maximumNumberOfParameters = parameterTypes.length;
this.parameterTypes = parameterTypes;
}
this.maximumNumberOfParameters = newParameterTypes.length;
this.parameterTypes = newParameterTypes;
}

/*
* Create a new array of parameter type.
*
* If the owner is a class instance(e.g. String) and the method is instance method,
* we expand the original array of parameter type by inserting the owner at the first place of the expanded array
* Creates an array of parameter types. If the owner is a class instance (ex:
* String) and the method is instance method, we expand the original array of
* parameter type by inserting the owner at the first position of the array.
*/
private Class[] makeParameterTypes(final Object owner, final MetaMethod m) {
Class[] newParameterTypes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -578,13 +578,13 @@ public static void setGroovyObjectPropertySpreadSafe(Object messageArgument, Cla
// --------------------------------------------------------

/**
* Returns the method pointer for the given object name
* Returns a method closure for the given object and name.
*
* @param object the object containing the method
* @param methodName the name of the method of interest
* @param object the object or class providing the method
* @param methodName the method(s) of interest
* @return the resulting Closure
*/
public static Closure getMethodPointer(Object object, String methodName) {
public static Closure getMethodPointer(final Object object, final String methodName) {
return InvokerHelper.getMethodPointer(object, methodName);
}

Expand Down
Loading

0 comments on commit e16a44c

Please sign in to comment.