You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2016/11/16 14:56:13 UTC
[6/7] groovy git commit: GROOVY-3976: Improve "with" by ending with
implied "return delegate" (closes #174)
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 <pa...@asert.com.au>
Authored: Wed Nov 16 08:54:27 2016 +1000
Committer: paulk <pa...@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