Index: src/main/groovy/lang/MetaClassImpl.java =================================================================== --- src/main/groovy/lang/MetaClassImpl.java (revision 20259) +++ src/main/groovy/lang/MetaClassImpl.java (revision ) @@ -107,7 +107,6 @@ private static final String CLOSURE_CALL_METHOD = "call"; private static final String CLOSURE_DO_CALL_METHOD = "doCall"; - private static final String CLOSURE_CURRY_METHOD = "curry"; protected static final String STATIC_METHOD_MISSING = "$static_methodMissing"; protected static final String STATIC_PROPERTY_MISSING = "$static_propertyMissing"; protected static final String METHOD_MISSING = "methodMissing"; @@ -930,8 +929,6 @@ return ownerMetaClass.invokeMethod(owner, methodName, curriedArguments); } if (method==null) invokeMissingMethod(object,methodName,arguments); - } else if (CLOSURE_CURRY_METHOD.equals(methodName)) { - return closure.curry(arguments); } final Object delegate = closure.getDelegate(); Index: src/main/org/codehaus/groovy/runtime/metaclass/ClosureMetaClass.java =================================================================== --- src/main/org/codehaus/groovy/runtime/metaclass/ClosureMetaClass.java (revision 20604) +++ src/main/org/codehaus/groovy/runtime/metaclass/ClosureMetaClass.java (revision ) @@ -62,7 +62,6 @@ private static final Object[] EMPTY_ARGUMENTS = {}; private static final String CLOSURE_CALL_METHOD = "call"; private static final String CLOSURE_DO_CALL_METHOD = "doCall"; - private static final String CLOSURE_CURRY_METHOD = "curry"; static { CLOSURE_METACLASS = new MetaClassImpl(Closure.class); @@ -263,9 +262,9 @@ } } if (method == null) throw new MissingMethodException(methodName, theClass, arguments, false); - } else if (CLOSURE_CURRY_METHOD.equals(methodName)) { - return closure.curry(arguments); - } else { + } + + if (method == null && closure.getResolveStrategy() != Closure.DELEGATE_ONLY) { method = CLOSURE_METACLASS.pickMethod(methodName, argClasses); } Index: src/main/org/codehaus/groovy/runtime/ComposedClosure.java =================================================================== --- src/main/org/codehaus/groovy/runtime/ComposedClosure.java (revision ) +++ src/main/org/codehaus/groovy/runtime/ComposedClosure.java (revision ) @@ -0,0 +1,101 @@ +/* + * Copyright 2003-2010 the original author or authors. + * + * Licensed 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 org.codehaus.groovy.runtime; + +import groovy.lang.Closure; + +import java.util.List; + +/** + * A wrapper for Closure to support composition. + * Normally used only internally through the rightShift() and + * leftShift() methods on Closure. + *

+ * Typical usages: + *

+ * def twice = { a -> a * 2 }
+ * def inc = { b -> b + 1 }
+ * def f = { x -> twice(inc(x)) } // longhand
+ * def g = inc >> twice
+ * def h = twice << inc
+ * assert f(10) == 22
+ * assert g(10) == 22
+ * assert h(10) == 22
+ *
+ * def s2c = { it.chars[0] }
+ * def p = Integer.&toHexString >> s2c >> Character.&toUpperCase
+ * assert p(15) == 'F'
+ *
+ * def multiply = { a, b -> a * b }
+ * def identity = { a -> [a, a] }
+ * def sq = identity >> multiply
+ * assert (1..5).collect{ sq(it) } == [1, 4, 9, 16, 25]
+ *
+ * def add3 = { a, b, c -> a + b + c }
+ * def add2plus10 = add3.curry(10)
+ * def multBoth = { a, b, c -> [a*c, b*c] }
+ * def twiceBoth = multBoth.rcurry(2)
+ * def twiceBothPlus10 = twiceBoth >> add2plus10
+ * assert twiceBothPlus10(5, 10) == 40
+ * 
+ * + * @author Paul King + */ +public final class ComposedClosure extends Closure { + + private Closure first; + private Closure second; + + public ComposedClosure(Closure first, Closure second) { + super(first); + this.first = (Closure) first.clone(); + this.second = (Closure) second.clone(); + maximumNumberOfParameters = first.getMaximumNumberOfParameters(); + } + + public void setDelegate(Object delegate) { + ((Closure) getOwner()).setDelegate(delegate); + second.setDelegate(delegate); + } + + public Object getDelegate() { + return ((Closure) getOwner()).getDelegate(); + } + + public void setResolveStrategy(int resolveStrategy) { + ((Closure) getOwner()).setResolveStrategy(resolveStrategy); + second.setResolveStrategy(resolveStrategy); + } + + public int getResolveStrategy() { + return ((Closure) getOwner()).getResolveStrategy(); + } + + public Object clone() { + return new ComposedClosure(first, second); + } + + public Class[] getParameterTypes() { + return first.getParameterTypes(); + } + + @Override + public Object call(Object[] args) { + Object temp = first.call(args); + if (temp instanceof List && second.getParameterTypes().length > 1) temp = ((List) temp).toArray(); + return temp instanceof Object[] ? second.call((Object[]) temp) : second.call(temp); + } +} Index: src/test/groovy/xml/MarkupBuilderTest.groovy =================================================================== --- src/test/groovy/xml/MarkupBuilderTest.groovy (revision 19637) +++ src/test/groovy/xml/MarkupBuilderTest.groovy (revision ) @@ -226,6 +226,23 @@ assertExpectedXmlDefault "" } + void testDelegateOnlyToSkipInternalClosureMethods() { + def items = { + pizza(price: 8.5) + curry(price: 8) + burger(price: 7.5) + } + items.resolveStrategy = Closure.DELEGATE_ONLY + xml.menu(items) + assertExpectedXmlDefault ''' + + + + + + ''' + } + void testWithIndentPrinter() { xml = new MarkupBuilder(new IndentPrinter(new PrintWriter(writer), "", false)) xml.element(att1:'attr') { subelement('foo') } Index: src/test/groovy/ClosureComposeTest.groovy =================================================================== --- src/test/groovy/ClosureComposeTest.groovy (revision ) +++ src/test/groovy/ClosureComposeTest.groovy (revision ) @@ -0,0 +1,73 @@ +/* + * Copyright 2003-2010 the original author or authors. + * + * Licensed 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 + +/** + * @author Paul King + */ +class ClosureComposeTest extends GroovyTestCase { + + void testComposeMultiply() { + def twice = { a -> a * 2 } + def thrice = { a -> a * 3 } + def times6 = twice >> thrice + assert times6(3) == 18 + } + + void testReverseComposeAndCallShortcut() { + def twice = { a -> a * 2 } + def thrice = { a -> a * 3 } + assert twice << thrice << 3 == 18 + } + + void testComposeAndLonghand() { + def twice = { a -> a * 2 } + def inc = { b -> b + 1 } + def f = inc >> twice + def g = { x -> twice(inc(x)) } + assert f(10) == 22 + assert g(10) == 22 + } + + void testComposeWithMethodClosure() { + def s2c = { it.chars[0] } + def p = Integer.&toHexString >> s2c >> Character.&toUpperCase + assert p(15) == 'F' + } + + void testComposeWithMultipleArgs() { + def multiply = { a, b -> a * b } + def identity = { a -> [a, a] } + def sq = identity >> multiply + assert (1..5).collect { sq(it) } == [1, 4, 9, 16, 25] + } + + void testReverseCompositionWithMultipleArgs() { + def multiply = { a, b -> a * b } + def identity = { a -> [a, a] } + def sq = multiply << identity + assert (1..5).collect { sq(it) } == [1, 4, 9, 16, 25] + } + + void testComposeWithCurriedClosures() { + def add3 = { a, b, c -> a + b + c } + def add2plus10 = add3.curry(10) + def multBoth = { a, b, c -> [a * c, b * c] } + def twiceBoth = multBoth.rcurry(2) + def twiceBothPlus10 = twiceBoth >> add2plus10 + assert twiceBothPlus10(5, 10) == 40 + } +} Index: src/main/groovy/lang/Closure.java =================================================================== --- src/main/groovy/lang/Closure.java (revision 20481) +++ src/main/groovy/lang/Closure.java (revision ) @@ -17,6 +17,7 @@ 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.typehandling.DefaultTypeTransformation; @@ -53,6 +54,7 @@ * @author John Wilson * @author Jochen Theodorou * @author Graeme Rocher + * @author Paul King * * @version $Revision: 20481 $ */ @@ -423,6 +425,61 @@ return new CurriedClosure(n, this, arguments); } + /** + * Support for Closure forward composition. + *

+ * Typical usage: + *

+     * def twice = { a -> a * 2 }
+     * def thrice = { a -> a * 3 }
+     * def times6 = twice >> thrice
+     * // equivalent: times6 = { a -> thrice(twice(a)) }
+     * assert times6(3) == 18
+     * 
+ * + * @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 twice = { a -> a * 2 }
+     * def thrice = { a -> a * 3 }
+     * def times6 = thrice << twice
+     * // equivalent: times6 = { a -> thrice(twice(a)) }
+     * assert times6(3) == 18
+     * 
+ * + * @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 twice = { a -> a * 2 }
+     * def thrice = { a -> a * 3 }
+     * assert thrice << twice << 3 == 18
+     * 
+ * + * @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); + } + /* (non-Javadoc) * @see java.lang.Object#clone() */ Index: src/main/groovy/lang/ExpandoMetaClass.java =================================================================== --- src/main/groovy/lang/ExpandoMetaClass.java (revision 20232) +++ src/main/groovy/lang/ExpandoMetaClass.java (revision ) @@ -772,9 +772,10 @@ final DefiningClosure definer = new DefiningClosure(); Object delegate = closure.getDelegate(); closure.setDelegate(definer); - closure.setResolveStrategy(Closure.DELEGATE_FIRST); + closure.setResolveStrategy(Closure.DELEGATE_ONLY); closure.call(null); closure.setDelegate(delegate); + closure.setResolveStrategy(Closure.DELEGATE_FIRST); definer.definition = false; return this; }