groovy-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sun...@apache.org
Subject [6/7] groovy git commit: GROOVY-3976: Improve "with" by ending with implied "return delegate" (closes #174)
Date Wed, 16 Nov 2016 14:56:13 GMT
GROOVY-3976: Improve "with" by ending with implied "return delegate" (closes #174)


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

Branch: refs/heads/parrot
Commit: bc90dd981ff0f6b4853420f0604345375992333a
Parents: fc88b52
Author: paulk <paulk@asert.com.au>
Authored: Wed Nov 16 08:54:27 2016 +1000
Committer: paulk <paulk@asert.com.au>
Committed: Wed Nov 16 08:54:27 2016 +1000

----------------------------------------------------------------------
 .../groovy/runtime/DefaultGroovyMethods.java    | 71 +++++++++++++++++---
 src/spec/doc/style-guide.adoc                   |  4 +-
 src/test/groovy/lang/TapMethodTest.groovy       | 26 +++----
 src/test/groovy/lang/WithMethodTest.groovy      | 51 +++++++++-----
 4 files changed, 115 insertions(+), 37 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/bc90dd98/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 5fdcc41..d23c1b5 100644
--- a/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -197,12 +197,19 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport
{
      *   return it
      * }
      * </pre>
+     * The other typical usage, uses the self object while creating some value:
+     * <pre>
+     * def fullName = person.with{ "$firstName $lastName" }
+     * </pre>
      *
      * @param self    the object to have a closure act upon
      * @param closure the closure to call on the object
      * @return result of calling the closure
+     * @see #with(Object, boolean, Closure)
+     * @see #tap(Object, Closure)
      * @since 1.5.0
      */
+    @SuppressWarnings("unchecked")
     public static <T,U> T with(
             @DelegatesTo.Target("self") U self,
             @DelegatesTo(value=DelegatesTo.Target.class,
@@ -210,11 +217,61 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport
{
                     strategy=Closure.DELEGATE_FIRST)
             @ClosureParams(FirstParam.class)
             Closure<T> closure) {
+        return (T) with(self, false, (Closure<Object>)closure);
+    }
+
+    /**
+     * Allows the closure to be called for the object reference self.
+     * <p/>
+     * Any method invoked inside the closure will first be invoked on the
+     * self reference. For exampe, the following method calls to the append()
+     * method are invoked on the StringBuilder instance and then, because
+     * 'returning' is true, the self instance is returned:
+     * <pre class="groovyTestCase">
+     * def b = new StringBuilder().with(true) {
+     *   append('foo')
+     *   append('bar')
+     * }
+     * assert b.toString() == 'foobar'
+     * </pre>
+     * The returning parameter is commonly set to true when using with to simplify object
+     * creation, such as this example:
+     * <pre>
+     * def p = new Person().with(true) {
+     *   firstName = 'John'
+     *   lastName = 'Doe'
+     * }
+     * </pre>
+     * Alternatively, 'tap' is an alias for 'with(true)', so that method can be used instead.
+     *
+     * The other main use case for with is when returning a value calculated using self as
shown here:
+     * <pre>
+     * def fullName = person.with(false){ "$firstName $lastName" }
+     * </pre>
+     * Alternatively, 'with' is an alias for 'with(false)', so the boolean parameter can
be ommitted instead.
+     *
+     * @param self      the object to have a closure act upon
+     * @param returning if true, return the self object; otherwise, the result of calling
the closure
+     * @param closure   the closure to call on the object
+     * @return the self object or the result of calling the closure depending on 'returning'
+     * @see #with(Object, Closure)
+     * @see #tap(Object, Closure)
+     * @since 2.5.0
+     */
+    public static <T,U extends T, V extends T> T with(
+            @DelegatesTo.Target("self") U self,
+            boolean returning,
+            @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();
+        final Closure<V> clonedClosure = (Closure<V>) closure.clone();
         clonedClosure.setResolveStrategy(Closure.DELEGATE_FIRST);
         clonedClosure.setDelegate(self);
-        return clonedClosure.call(self);
+        V result = clonedClosure.call(self);
+        return returning ? self : result;
     }
 
     /**
@@ -242,8 +299,11 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport
{
      * @param self    the object to have a closure act upon
      * @param closure the closure to call on the object
      * @return self
+     * @see #with(Object, boolean, Closure)
+     * @see #with(Object, Closure)
      * @since 2.5.0
      */
+    @SuppressWarnings("unchecked")
     public static <T,U> U tap(
             @DelegatesTo.Target("self") U self,
             @DelegatesTo(value=DelegatesTo.Target.class,
@@ -251,12 +311,7 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport
{
                     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;
+        return (U) with(self, true, (Closure<Object>)closure);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/groovy/blob/bc90dd98/src/spec/doc/style-guide.adoc
----------------------------------------------------------------------
diff --git a/src/spec/doc/style-guide.adoc b/src/spec/doc/style-guide.adoc
index a9e54f7..344632a 100644
--- a/src/spec/doc/style-guide.adoc
+++ b/src/spec/doc/style-guide.adoc
@@ -347,7 +347,7 @@ As with any closure in Groovy, the last statement is considered the return
value
 ----
 def person = new Person().with {
     name = "Ada Lovelace"
-    it // Note the explicit return
+    it // Note the explicit mention of it as the return value
 }
 ----
 
@@ -360,6 +360,8 @@ def person = new Person().tap {
 }
 ----
 
+Note: you can also use `with(true)` instead of `tap()` and `with(false)` instead of `with()`.
+
 == 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/bc90dd98/src/test/groovy/lang/TapMethodTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/groovy/lang/TapMethodTest.groovy b/src/test/groovy/lang/TapMethodTest.groovy
index db29150..95e4774 100644
--- a/src/test/groovy/lang/TapMethodTest.groovy
+++ b/src/test/groovy/lang/TapMethodTest.groovy
@@ -1,23 +1,25 @@
 /*
- * Copyright 2003-2016 the original author or authors.
+ *  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
  *
- * 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
  *
- *      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.
+ *  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 {
 

http://git-wip-us.apache.org/repos/asf/groovy/blob/bc90dd98/src/test/groovy/lang/WithMethodTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/groovy/lang/WithMethodTest.groovy b/src/test/groovy/lang/WithMethodTest.groovy
index 41f0460..d93ea48 100644
--- a/src/test/groovy/lang/WithMethodTest.groovy
+++ b/src/test/groovy/lang/WithMethodTest.groovy
@@ -20,10 +20,9 @@ package groovy.lang
 
 /**
  * Tests the .with method
- *
  */
- class WithMethodTest extends GroovyTestCase {
-     
+class WithMethodTest extends GroovyTestCase {
+
      void testDelegateGetsFirstOpportunity() {
          def sb = new StringBuffer()
 
@@ -32,55 +31,75 @@ package groovy.lang
              // 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.with {
              // 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.with {
                  someNoneExistentMethod()
              }
          }
      }
-     
+
      void testClosureWithResolveStrategyExplicitlySet() {
          def closure = {
              append 'some text'
          }
          closure.resolveStrategy = Closure.OWNER_ONLY
-         
+
          def sb = new StringBuffer()
-         
+
          // .with should use DELEGATE_FIRST, even though
          // the closure has another strategy set
          sb.with closure
-         
+
          assertEquals 'delegate had wrong value', 'some text', sb.toString()
      }
-     
+
+     void testBooleanVariant() {
+         def p = new PersonWith(firstName: 'Johnny', lastName: 'Depp')
+         def result1 = p.with(false) {
+             "$firstName $lastName"
+         }
+         assert result1 instanceof GString
+         assert result1.toString() == 'Johnny Depp'
+         def result2 = p.with(true) {
+             lastName = 'Cash'
+         }
+         assert result2 instanceof PersonWith
+         assert result2.toString() == 'WithMethodTest$PersonWith(Johnny, Cash)'
+     }
+
      def ownerMethod() {
          42
      }
-     
+
      void append(String s) {
          fail 'this should never have been called'
      }
+
+     @groovy.transform.ToString(includePackage = false)
+     class PersonWith {
+         String firstName
+         String lastName
+     }
  }
\ No newline at end of file


Mime
View raw message