Skip to content

Commit

Permalink
Merge branch 'release'
Browse files Browse the repository at this point in the history
  • Loading branch information
adammurdoch committed May 30, 2016
2 parents 086a8ba + e2644a3 commit c71da58
Show file tree
Hide file tree
Showing 22 changed files with 391 additions and 89 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright 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 org.gradle.api.dsl

import org.gradle.integtests.fixtures.AbstractIntegrationSpec
import spock.lang.Issue

class DynamicMethodLookupIntegrationTest extends AbstractIntegrationSpec {
@Issue("GRADLE-3460")
def "extension configuration method is preferred over property with closure value"() {
given:
buildFile << """
class ContactExtension {
String prop
}
class ContactPlugin implements Plugin<Project> {
public void apply(Project project) {
ContactExtension extension = project.extensions.create('contacts', ContactExtension)
project.ext.contacts = { String... args ->
return args
}
}
}
apply plugin: ContactPlugin
contacts {
assert delegate instanceof ContactExtension
prop = "value"
}
assert extensions.contacts.prop == "value"
assert contacts() == []
assert contacts("a") == [ "a" ]
assert contacts("a", "b", "c") == [ "a", "b", "c" ]
"""
expect:
succeeds()
}

def "extension configuration method is preferred over property with untyped closure value"() {
given:
buildFile << """
class ContactExtension {
String prop
}
extensions.create('contacts', ContactExtension)
ext.contacts = { args ->
return args
}
contacts {
assert delegate instanceof ContactExtension
prop = "value"
}
assert extensions.contacts.prop == "value"
assert contacts() == null
assert contacts("a") == "a"
"""

expect:
succeeds()
}

// Documents actual behaviour for backwards compatibility, not necessarily desired behaviour
def "inherited convention method is preferred over property with closure value"() {
given:
settingsFile << "include 'child'"
buildFile << """
class ContactConvention {
def contacts(String arg) { arg }
}
convention.plugins.contacts = new ContactConvention()
subprojects {
ext.contacts = { throw new RuntimeException() }
assert contacts("a") == "a"
}
"""

expect:
succeeds()
}

def "property with closure value is preferred over inherited property with closure value"() {
given:
settingsFile << "include 'child'"
buildFile << """
ext.contacts = { throw new RuntimeException() }
subprojects {
ext.contacts = { it }
assert contacts("a") == "a"
}
"""

expect:
succeeds()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -428,20 +428,59 @@ assert 'overridden value' == global
succeeds("test")
}

def canInjectMethodsFromParentProject() {
def canAddMethodsUsingAPropertyWhoseValueIsAClosure() {
file("settings.gradle").writelns("include 'child1', 'child2'");
file("build.gradle") << """
class Thing {
def prop1 = { it }
}
convention.plugins.thing = new Thing()
ext.prop2 = { it / 2 }
file("settings.gradle").writelns("include 'child'");
file("build.gradle").writelns(
"subprojects {",
" ext.injectedMethod = { project.name }",
"}"
);
file("child/build.gradle").writelns(
"task testTask << {",
" assert injectedMethod() == 'child'",
"}"
);
assert prop1(12) == 12
assert prop2(12) == 6
"""
file("child1/build.gradle") << """
ext.prop3 = { it * 2 }
assert prop1(12) == 12
assert prop2(12) == 6
assert prop3(12) == 24
"""

expect:
succeeds()
}

def appliesTypeConversionForClosureParameters() {
file('build.gradle') << '''
enum Letter { A, B, C }
ext.letter = null
ext.m = { Letter l -> letter = l }
m("A")
assert letter == Letter.A
m(Letter.B)
assert letter == Letter.B
'''

expect:
succeeds()
}

def canInjectMethodsFromParentProject() {

file("settings.gradle").writelns("include 'child1', 'child2'");
file("build.gradle") << """
subprojects {
ext.useSomeProperty = { project.name }
ext.useSomeMethod = { file(it) }
}
"""
file("child1/build.gradle") << """
task testTask << {
assert useSomeProperty() == 'child1'
assert useSomeMethod('f') == file('f')
}
"""

expect:
succeeds("testTask")
Expand Down Expand Up @@ -900,7 +939,7 @@ task print(type: MyTask) {
succeeds()
}

def dynamicPropertiesTakePrecedenceOverDecorations() {
def dynamicPropertiesOfDecoratedObjectTakePrecedenceOverDecorations() {
buildFile << """
class DynamicTask extends DefaultTask {
def props = [:]
Expand Down
3 changes: 3 additions & 0 deletions subprojects/core/src/main/java/org/gradle/api/Project.java
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@
*
* <li>The methods of the parent project, recursively up to the root project.</li>
*
* <li>A property of the project whose value is a closure. The closure is treated as a method and called with the provided parameters.
* The property is located as described above.</li>
*
* </ul>
*/
@HasInternalProtocol
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ protected ConfigureDelegate createConfigureDelegate(Closure configureClosure) {
return new PolymorphicDomainObjectContainerConfigureDelegate(configureClosure, this);
}

private class ContainerDynamicObject extends CompositeDynamicObject {
private class ContainerDynamicObject extends MixInClosurePropertiesAsMethodsDynamicObject {
private ContainerDynamicObject(ContainerElementsDynamicObject elementsDynamicObject) {
setObjects(new BeanDynamicObject(AbstractPolymorphicDomainObjectContainer.this), elementsDynamicObject, getConvention().getExtensionsAsDynamicObject());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,38 @@
package org.gradle.api.internal;

import groovy.lang.Closure;
import org.gradle.api.*;
import org.gradle.api.InvalidUserDataException;
import org.gradle.api.NamedDomainObjectCollection;
import org.gradle.api.Namer;
import org.gradle.api.Rule;
import org.gradle.api.UnknownDomainObjectException;
import org.gradle.api.internal.collections.CollectionEventRegister;
import org.gradle.api.internal.collections.CollectionFilter;
import org.gradle.api.internal.plugins.DefaultConvention;
import org.gradle.api.plugins.Convention;
import org.gradle.api.plugins.ExtensionContainer;
import org.gradle.api.specs.Spec;
import org.gradle.api.specs.Specs;
import org.gradle.internal.metaobject.*;
import org.gradle.internal.metaobject.AbstractDynamicObject;
import org.gradle.internal.metaobject.BeanDynamicObject;
import org.gradle.internal.metaobject.DynamicObject;
import org.gradle.internal.metaobject.GetPropertyResult;
import org.gradle.internal.metaobject.InvokeMethodResult;
import org.gradle.internal.metaobject.MixInClosurePropertiesAsMethodsDynamicObject;
import org.gradle.internal.reflect.Instantiator;
import org.gradle.util.ConfigureUtil;

import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;

public class DefaultNamedDomainObjectCollection<T> extends DefaultDomainObjectCollection<T> implements NamedDomainObjectCollection<T>, DynamicObjectAware {

Expand Down Expand Up @@ -295,7 +313,7 @@ protected String getTypeDisplayName() {
return getType().getSimpleName();
}

private class ContainerDynamicObject extends CompositeDynamicObject {
private class ContainerDynamicObject extends MixInClosurePropertiesAsMethodsDynamicObject {
private ContainerDynamicObject(ContainerElementsDynamicObject elementsDynamicObject) {
setObjects(new BeanDynamicObject(DefaultNamedDomainObjectCollection.this), elementsDynamicObject, convention.getExtensionsAsDynamicObject());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@
import org.gradle.api.internal.plugins.ExtraPropertiesDynamicObjectAdapter;
import org.gradle.api.plugins.Convention;
import org.gradle.api.plugins.ExtraPropertiesExtension;
import org.gradle.internal.metaobject.*;
import org.gradle.internal.metaobject.AbstractDynamicObject;
import org.gradle.internal.metaobject.BeanDynamicObject;
import org.gradle.internal.metaobject.CompositeDynamicObject;
import org.gradle.internal.metaobject.DynamicObject;
import org.gradle.internal.metaobject.GetPropertyResult;
import org.gradle.internal.metaobject.InvokeMethodResult;
import org.gradle.internal.metaobject.MixInClosurePropertiesAsMethodsDynamicObject;
import org.gradle.internal.metaobject.SetPropertyResult;
import org.gradle.internal.reflect.Instantiator;

import java.util.ArrayList;
Expand All @@ -37,7 +43,7 @@
*
* @see org.gradle.api.internal.AsmBackedClassGenerator.MixInExtensibleDynamicObject
*/
public class ExtensibleDynamicObject extends CompositeDynamicObject implements HasConvention {
public class ExtensibleDynamicObject extends MixInClosurePropertiesAsMethodsDynamicObject implements HasConvention {

public enum Location {
BeforeConvention, AfterConvention
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
*/
package org.gradle.internal.metaobject;

import groovy.lang.Closure;

import java.util.HashMap;
import java.util.Map;

Expand Down Expand Up @@ -99,19 +97,6 @@ public void invokeMethod(String name, InvokeMethodResult result, Object... argum
if (result.isFound()) {
return;
}
GetPropertyResult propertyLookup = new GetPropertyResult();
object.getProperty(name, propertyLookup);
if (propertyLookup.isFound()) {
Object property = propertyLookup.getValue();
if (property instanceof Closure) {
Closure closure = (Closure) property;
closure.setResolveStrategy(Closure.DELEGATE_FIRST);
Object value = closure.call(arguments);
result.result(value);
return;
}
}
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2014 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.gradle.internal.metaobject;

import groovy.lang.Closure;

/**
* Exposes methods for those properties whose value is a closure.
*
* TODO: use composition instead of inheritance
*/
public abstract class MixInClosurePropertiesAsMethodsDynamicObject extends CompositeDynamicObject {
@Override
public void invokeMethod(String name, InvokeMethodResult result, Object... arguments) {
super.invokeMethod(name, result, arguments);
if (result.isFound()) {
return;
}

GetPropertyResult propertyLookup = new GetPropertyResult();
getProperty(name, propertyLookup);
if (propertyLookup.isFound()) {
Object property = propertyLookup.getValue();
if (property instanceof Closure) {
Closure closure = (Closure) property;
closure.setResolveStrategy(Closure.DELEGATE_FIRST);
new BeanDynamicObject(closure).invokeMethod("doCall", result, arguments);
}
}
}
}
Loading

0 comments on commit c71da58

Please sign in to comment.