Repository: groovy
Updated Branches:
  refs/heads/master 04f1e8636 -> bc90dd981


Add `tap`: same as `with`, but returns the incoming object


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/fc88b526
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/fc88b526
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/fc88b526

Branch: refs/heads/master
Commit: fc88b52637996f25c424a90842a59d360e5b081f
Parents: 04f1e86
Author: Christoph Frick <c...@ofnir.net>
Authored: Tue Nov 8 13:04:40 2016 +0100
Committer: paulk <pa...@asert.com.au>
Committed: Wed Nov 16 07:16:49 2016 +1000

----------------------------------------------------------------------
 .../groovy/runtime/DefaultGroovyMethods.java    | 42 +++++++++
 src/spec/doc/style-guide.adoc                   | 23 ++++-
 src/test/groovy/lang/TapMethodTest.groovy       | 92 ++++++++++++++++++++
 3 files changed, 155 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/fc88b526/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java 
b/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index 81bb35e..5fdcc41 100644
--- a/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -218,6 +218,48 @@ public class DefaultGroovyMethods extends 
DefaultGroovyMethodsSupport {
     }
 
     /**
+     * Allows the closure to be called for the object reference self (similar
+     * to <code>with</code> and always returns self.
+     * <p>
+     * Any method invoked inside the closure will first be invoked on the
+     * self reference. For instance, the following method calls to the append()
+     * method are invoked on the StringBuilder instance:
+     * <pre>
+     * def b = new StringBuilder().tap {
+     *   append('foo')
+     *   append('bar')
+     * }
+     * assert b.toString() == 'foobar'
+     * </pre>
+     * This is commonly used to simplify object creation, such as this example:
+     * <pre>
+     * def p = new Person().tap {
+     *   firstName = 'John'
+     *   lastName = 'Doe'
+     * }
+     * </pre>
+     *
+     * @param self    the object to have a closure act upon
+     * @param closure the closure to call on the object
+     * @return self
+     * @since 2.5.0
+     */
+    public static <T,U> U tap(
+            @DelegatesTo.Target("self") U self,
+            @DelegatesTo(value=DelegatesTo.Target.class,
+                    target="self",
+                    strategy=Closure.DELEGATE_FIRST)
+            @ClosureParams(FirstParam.class)
+            Closure<T> closure) {
+        @SuppressWarnings("unchecked")
+        final Closure<T> clonedClosure = (Closure<T>) closure.clone();
+        clonedClosure.setResolveStrategy(Closure.DELEGATE_FIRST);
+        clonedClosure.setDelegate(self);
+        clonedClosure.call(self);
+        return self;
+    }
+
+    /**
      * Allows the subscript operator to be used to lookup dynamic property 
values.
      * <code>bean[somePropertyNameExpression]</code>. The normal property 
notation
      * of groovy is neater and more concise but only works with compile-time 
known

http://git-wip-us.apache.org/repos/asf/groovy/blob/fc88b526/src/spec/doc/style-guide.adoc
----------------------------------------------------------------------
diff --git a/src/spec/doc/style-guide.adoc b/src/spec/doc/style-guide.adoc
index 5458d0d..a9e54f7 100644
--- a/src/spec/doc/style-guide.adoc
+++ b/src/spec/doc/style-guide.adoc
@@ -313,11 +313,11 @@ You can use named parameters with the default constructor 
(first the constructor
 def server = new Server(name: "Obelix", cluster: aCluster)
 ----
 
-== Using with() for repeated operations on the same bean
+== Using `with()` and `tap()` for repeated operations on the same bean
 
 Named-parameters with the default constructor is interesting when creating new 
instances,
 but what if you are updating an instance that was given to you, do you have to 
repeat the 'server' prefix again and again?
-No, thanks to the with() method that Groovy adds on all objects of any kind:
+No, thanks to the `with()` and `tap()` methods that Groovy adds on all objects 
of any kind:
 
 [source,groovy]
 ----
@@ -341,6 +341,25 @@ server.with {
 }
 ----
 
+As with any closure in Groovy, the last statement is considered the return 
value.  In the example above this is the result of `stop()`.  To use this as a 
builder, that just returns the incoming object, there is also `tap()`:
+
+[source,groovy]
+----
+def person = new Person().with {
+    name = "Ada Lovelace"
+    it // Note the explicit return
+}
+----
+
+vs:
+
+[source,groovy]
+----
+def person = new Person().tap {
+    name = "Ada Lovelace"
+}
+----
+
 == Equals and `==`
 
 Java's `==` is actually Groovy's `is()` method, and Groovy's `==` is a clever 
`equals()`!

http://git-wip-us.apache.org/repos/asf/groovy/blob/fc88b526/src/test/groovy/lang/TapMethodTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/groovy/lang/TapMethodTest.groovy 
b/src/test/groovy/lang/TapMethodTest.groovy
new file mode 100644
index 0000000..db29150
--- /dev/null
+++ b/src/test/groovy/lang/TapMethodTest.groovy
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2003-2016 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.lang
+
+/**
+ * Tests the .tap method
+ *
+ */
+class TapMethodTest extends GroovyTestCase {
+
+    void testTapReturnsSelf() {
+        def m1 = [:]
+        def m2 = m1.tap{
+            put("a",1)
+        }
+        assertSame("Outgoing object of tap is not the same as the ingoing", 
m1, m2)
+        assertEquals("Outgoing object of tap is changed", m2, [a: 1])
+    }
+
+    void testDelegateGetsFirstOpportunity() {
+        def sb = new StringBuffer()
+
+        sb.tap {
+            // this should call append() on the
+            // delegate not, the owner
+            append 'some text'
+        }
+
+        assertEquals 'delegate had wrong value', 'some text', sb.toString()
+    }
+
+    void testOwnerGetsOpportunityIfDelegateCannotRespond() {
+        def sb = new StringBuffer()
+
+        def returnValue
+
+        sb.tap {
+            // this should call ownerMethod() on the owner
+            returnValue = ownerMethod()
+        }
+
+        assertEquals 'owner should have responded to method call',
+                     42,
+                     returnValue
+    }
+
+    void testCallingNonExistentMethod() {
+        def sb = new StringBuffer()
+
+        shouldFail(MissingMethodException) {
+            sb.tap {
+                someNoneExistentMethod()
+            }
+        }
+    }
+
+    void testClosureWithResolveStrategyExplicitlySet() {
+        def closure = {
+            append 'some text'
+        }
+        closure.resolveStrategy = Closure.OWNER_ONLY
+
+        def sb = new StringBuffer()
+
+        // .tap should use DELEGATE_FIRST, even though
+        // the closure has another strategy set
+        sb.tap closure
+
+        assertEquals 'delegate had wrong value', 'some text', sb.toString()
+    }
+
+    def ownerMethod() {
+        42
+    }
+
+    void append(String s) {
+        fail 'this should never have been called'
+    }
+}

Reply via email to