
org.gradle.model.internal.manage.binding.DefaultStructBindingsStoreTest.groovy Maven / Gradle / Ivy
/*
* 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.model.internal.manage.binding
import org.gradle.api.Named
import org.gradle.model.Managed
import org.gradle.model.ModelMap
import org.gradle.model.ModelSet
import org.gradle.model.NamedThingInterface
import org.gradle.model.Unmanaged
import org.gradle.model.internal.manage.schema.extract.DefaultModelSchemaExtractor
import org.gradle.model.internal.manage.schema.extract.DefaultModelSchemaStore
import org.gradle.model.internal.type.ModelType
import spock.lang.Specification
import spock.lang.Unroll
import static org.gradle.model.ModelTypeTesting.fullyQualifiedNameOf
class DefaultStructBindingsStoreTest extends Specification {
def schemaStore = new DefaultModelSchemaStore(DefaultModelSchemaExtractor.withDefaultStrategies())
def bindingStore = new DefaultStructBindingsStore(schemaStore)
def "extracts empty"() {
def bindings = extract(Object)
expect:
bindings.declaredViewSchemas*.type*.rawClass as List == [Object]
bindings.delegateSchema == null
bindings.managedProperties.isEmpty()
bindings.methodBindings.isEmpty()
}
static abstract class TypeWithAbstractProperty {
abstract int getZ()
abstract void setZ(int value)
}
def "extracts simple type with a managed property"() {
def bindings = extract(TypeWithAbstractProperty)
expect:
bindings.declaredViewSchemas*.type*.rawClass as List == [TypeWithAbstractProperty]
bindings.delegateSchema == null
bindings.managedProperties.values()*.name as List == ["z"]
bindings.methodBindings*.getClass() == [ManagedPropertyMethodBinding, ManagedPropertyMethodBinding]
bindings.methodBindings*.viewMethod*.name == ["getZ", "setZ"]
}
static abstract class TypeWithImplementedProperty {
int getZ() { 0 }
void setZ(int value) {}
}
def "extracts simple type with an implemented property"() {
def bindings = extract(TypeWithImplementedProperty)
expect:
bindings.declaredViewSchemas*.type*.rawClass as List == [TypeWithImplementedProperty]
bindings.delegateSchema == null
bindings.managedProperties.isEmpty()
bindings.methodBindings*.viewMethod*.name == ["getZ", "setZ"]
bindings.methodBindings*.getClass() == [DirectMethodBinding, DirectMethodBinding]
}
static class DelegateTypeWithImplementedProperty {
int z
}
def "extracts simple type with a delegated property"() {
def bindings = extract(TypeWithAbstractProperty, DelegateTypeWithImplementedProperty)
expect:
bindings.declaredViewSchemas*.type*.rawClass as List == [TypeWithAbstractProperty]
bindings.delegateSchema.type.rawClass == DelegateTypeWithImplementedProperty
bindings.managedProperties.isEmpty()
bindings.methodBindings*.viewMethod*.name == ["getZ", "setZ"]
bindings.methodBindings*.getClass() == [DelegateMethodBinding, DelegateMethodBinding]
}
def "fails when delegate type is abstract"() {
when: extract(Object, Serializable)
then: def ex = thrown InvalidManagedTypeException
ex.message == "Type 'Object' is not a valid managed type: delegate type must be null or a non-abstract type instead of 'Serializable'."
}
@Managed
static class EmptyStaticClass {}
def "public type must be abstract"() {
when: extract EmptyStaticClass
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(EmptyStaticClass)} is not a valid managed type:
- Must be defined as an interface or an abstract class."""
}
@Managed
static interface ParameterizedEmptyInterface {}
def "public type cannot be parameterized"() {
when: extract ParameterizedEmptyInterface
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(ParameterizedEmptyInterface)} is not a valid managed type:
- Cannot be a parameterized type."""
}
@Managed
static abstract class WithInstanceScopedField {
private String name
private int age
}
def "instance scoped fields are not allowed"() {
when: extract WithInstanceScopedField
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(WithInstanceScopedField)} is not a valid managed type:
- Field name is not valid: Fields must be static final.
- Field age is not valid: Fields must be static final."""
}
@Managed
static abstract class WithInstanceScopedFieldInSuperclass extends WithInstanceScopedField {
}
def "instance scoped fields are not allowed in super-class"() {
when: extract WithInstanceScopedFieldInSuperclass
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(WithInstanceScopedFieldInSuperclass)} is not a valid managed type:
- Field DefaultStructBindingsStoreTest.WithInstanceScopedField.name is not valid: Fields must be static final.
- Field DefaultStructBindingsStoreTest.WithInstanceScopedField.age is not valid: Fields must be static final."""
}
@Managed
static abstract class ProtectedAbstractMethods {
protected abstract String getName()
protected abstract void setName(String name)
}
@Managed
static abstract class ProtectedAbstractMethodsInSuper extends ProtectedAbstractMethods {
}
def "protected abstract methods are not allowed"() {
when:
extract(ProtectedAbstractMethods)
then:
def e = thrown InvalidManagedTypeException
e.message == """Type ${fullyQualifiedNameOf(ProtectedAbstractMethods)} is not a valid managed type:
- Method getName() is not a valid method: Protected and private methods are not supported.
- Method setName(java.lang.String) is not a valid method: Protected and private methods are not supported."""
when:
extract(ProtectedAbstractMethodsInSuper)
then:
e = thrown InvalidManagedTypeException
e.message == """Type ${fullyQualifiedNameOf(ProtectedAbstractMethodsInSuper)} is not a valid managed type:
- Method DefaultStructBindingsStoreTest.ProtectedAbstractMethods.getName() is not a valid method: Protected and private methods are not supported.
- Method DefaultStructBindingsStoreTest.ProtectedAbstractMethods.setName(java.lang.String) is not a valid method: Protected and private methods are not supported."""
}
@Managed
static abstract class ProtectedAndPrivateNonAbstractMethods {
protected String getName() {
return null;
}
private void setName(String name) {}
}
def "protected and private non-abstract methods are not allowed"() {
when:
extract ProtectedAndPrivateNonAbstractMethods
then:
def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(ProtectedAndPrivateNonAbstractMethods)} is not a valid managed type:
- Method setName(java.lang.String) is not a valid method: Protected and private methods are not supported.
- Method getName() is not a valid method: Protected and private methods are not supported."""
}
@Managed
static abstract class ProtectedAndPrivateNonAbstractMethodsInSuper extends ProtectedAndPrivateNonAbstractMethods {
}
def "protected and private non-abstract methods are not allowed in super-type"() {
when: extract ProtectedAndPrivateNonAbstractMethodsInSuper
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(ProtectedAndPrivateNonAbstractMethodsInSuper)} is not a valid managed type:
- Method DefaultStructBindingsStoreTest.ProtectedAndPrivateNonAbstractMethods.setName(java.lang.String) is not a valid method: Protected and private methods are not supported.
- Method DefaultStructBindingsStoreTest.ProtectedAndPrivateNonAbstractMethods.getName() is not a valid method: Protected and private methods are not supported."""
}
def "fails when implemented property is present in delegate"() {
when:
extract TypeWithImplementedProperty, DelegateTypeWithImplementedProperty
then:
def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(TypeWithImplementedProperty)} is not a valid managed type:
- Method getZ() is not a valid method: it is both implemented by the view '${getName(TypeWithImplementedProperty)}' and the delegate type '${getName(DelegateTypeWithImplementedProperty)}'
- Method setZ(int) is not a valid method: it is both implemented by the view '${getName(TypeWithImplementedProperty)}' and the delegate type '${getName(DelegateTypeWithImplementedProperty)}'"""
}
static abstract class TypeWithAbstractWriteOnlyProperty {
abstract void setZ(int value)
}
def "fails when abstract property has only setter"() {
when:
extract(TypeWithAbstractWriteOnlyProperty)
then:
def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(TypeWithAbstractWriteOnlyProperty)} is not a valid managed type:
- Property 'z' is not valid: it must both have an abstract getter and a setter"""
}
static abstract class TypeWithInconsistentPropertyType {
abstract String getZ()
abstract void setZ(int value)
}
def "fails when property has inconsistent type"() {
when:
extract(TypeWithInconsistentPropertyType)
then:
def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(TypeWithInconsistentPropertyType)} is not a valid managed type:
- Method setZ(int) is not a valid method: it should take parameter with type 'String'"""
}
static interface OverloadingNumber {
Number getValue()
}
static interface OverloadingInteger extends OverloadingNumber {
@Override
Integer getValue()
}
static class OverloadingNumberImpl implements OverloadingNumber {
@Override
Number getValue() { 1.0d }
}
static class OverloadingIntegerImpl extends OverloadingNumberImpl implements OverloadingInteger {
@Override
Integer getValue() { 2 }
}
def "detects overloads"() {
def bindings = extract(OverloadingNumber, OverloadingIntegerImpl)
expect:
bindings.declaredViewSchemas*.type*.rawClass as List == [OverloadingNumber]
bindings.delegateSchema.type.rawClass == OverloadingIntegerImpl
bindings.managedProperties.isEmpty()
bindings.methodBindings*.getClass() == [DelegateMethodBinding, DelegateMethodBinding]
bindings.methodBindings*.viewMethod*.name == ["getValue", "getValue"]
bindings.methodBindings*.viewMethod*.method*.returnType == [Number, Integer]
bindings.methodBindings*.implementorMethod*.name == ["getValue", "getValue"]
bindings.methodBindings*.implementorMethod*.method*.returnType == [Integer, Integer]
}
static enum MyEnum {
A, B, C
}
@Managed
static interface HasUnmanagedOnManaged {
@Unmanaged
MyEnum getMyEnum();
void setMyEnum(MyEnum myEnum)
}
def "cannot annotate managed type property with unmanaged"() {
when: extract HasUnmanagedOnManaged
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(HasUnmanagedOnManaged)} is not a valid managed type:
- Property 'myEnum' is not valid: it is marked as @Unmanaged, but is of @Managed type '${getName(MyEnum)}'; please remove the @Managed annotation"""
}
@Managed
static interface NoSetterForUnmanaged {
@Unmanaged
InputStream getThing();
}
def "must have setter for unmanaged"() {
when: extract NoSetterForUnmanaged
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(NoSetterForUnmanaged)} is not a valid managed type:
- Property 'thing' is not valid: it must not be read only, because it is marked as @Unmanaged"""
}
@Managed
static interface AddsSetterToNoSetterForUnmanaged extends NoSetterForUnmanaged {
void setThing(InputStream inputStream);
}
def "subtype can add unmanaged setter"() {
def bindings = extract(AddsSetterToNoSetterForUnmanaged)
expect:
bindings.getManagedProperty("thing").type == ModelType.of(InputStream)
}
@Managed
static abstract class WritableMapProperty {
abstract void setMap(ModelMap map)
abstract ModelMap getMap()
}
@Managed
static abstract class WritableSetProperty {
abstract void setSet(ModelSet set)
abstract ModelSet getSet()
}
def "map cannot be writable"() {
when: extract WritableMapProperty
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(WritableMapProperty)} is not a valid managed type:
- Property 'map' is not valid: it cannot have a setter (ModelMap properties must be read only)"""
}
def "set cannot be writable"() {
when: extract WritableSetProperty
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(WritableSetProperty)} is not a valid managed type:
- Property 'set' is not valid: it cannot have a setter (ModelSet properties must be read only)"""
}
@Managed
static interface GetterWithParams {
String getName(String name)
void setName(String name)
}
def "malformed getter"() {
when: extract GetterWithParams
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(GetterWithParams)} is not a valid managed type:
- Method getName(java.lang.String) is not a valid property accessor method: getter method must not take parameters
- Property 'name' is not valid: it must both have an abstract getter and a setter"""
}
@Managed
static interface NonVoidSetter {
String getName()
String setName(String name)
}
def "non void setter"() {
def bindings = extract(NonVoidSetter)
expect:
bindings.getManagedProperty("name") != null
}
@Managed
static interface SetterWithExtraParams {
String getName()
void setName(String name, String otherName)
}
def "setter with extra params"() {
when: extract SetterWithExtraParams
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(SetterWithExtraParams)} is not a valid managed type:
- Method setName(java.lang.String, java.lang.String) is not a valid property accessor method: setter method must take exactly one parameter"""
}
@Managed
static interface HasExtraNonPropertyMethods {
String getName()
void setName(String name)
void foo(String bar)
}
@Managed
static interface ChildWithExtraNonPropertyMethods extends HasExtraNonPropertyMethods {
}
def "can only have abstract getters and setters"() {
when: extract HasExtraNonPropertyMethods
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(HasExtraNonPropertyMethods)} is not a valid managed type:
- Method foo(java.lang.String) is not a valid managed type method: it must have an implementation"""
}
def "can only have abstract getters and setters in inherited type"() {
when: extract ChildWithExtraNonPropertyMethods
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(ChildWithExtraNonPropertyMethods)} is not a valid managed type:
- Method DefaultStructBindingsStoreTest.HasExtraNonPropertyMethods.foo(java.lang.String) is not a valid managed type method: it must have an implementation"""
}
@Managed
static interface MisalignedSetterType {
String getThing()
void setThing(Object name)
}
def "misaligned setter type"() {
when: extract MisalignedSetterType
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(MisalignedSetterType)} is not a valid managed type:
- Method setThing(java.lang.Object) is not a valid method: it should take parameter with type 'String'"""
}
@Managed
static abstract class NonAbstractGetterWithSetter {
String getName() {}
abstract void setName(String name)
}
@Managed
static abstract class NonAbstractSetter {
abstract String getName()
void setName(String name) {}
}
def "non-abstract getter with abstract setter is not allowed"() {
when: extract NonAbstractGetterWithSetter
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(NonAbstractGetterWithSetter)} is not a valid managed type:
- Property 'name' is not valid: it must have either only abstract accessor methods or only implemented accessor methods"""
}
def "non-abstract setter without getter is not allowed"() {
when: extract NonAbstractSetter
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(NonAbstractSetter)} is not a valid managed type:
- Property 'name' is not valid: it must have either only abstract accessor methods or only implemented accessor methods"""
}
@Managed
static interface CollectionType {
List getItems()
void setItems(List integers)
}
def "displays a reasonable error message when getter and setter of a property of collection of scalar types do not use the same generic type"() {
given: when: extract CollectionType
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(CollectionType)} is not a valid managed type:
- Method setItems(java.util.List) is not a valid method: it should take parameter with type 'List'"""
}
@Unroll
def "misaligned types #firstType.simpleName and #secondType.simpleName"() {
def interfaceWithPrimitiveProperty = new GroovyClassLoader(getClass().classLoader).parseClass """
import org.gradle.model.Managed
@Managed
interface PrimitiveProperty {
$firstType.name getPrimitiveProperty()
void setPrimitiveProperty($secondType.name value)
}
"""
when: extract interfaceWithPrimitiveProperty
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type PrimitiveProperty is not a valid managed type:
- Method setPrimitiveProperty($secondType.name) is not a valid method: it should take parameter with type '$firstType.simpleName'"""
where:
firstType | secondType
byte | Byte
boolean | Boolean
char | Character
float | Float
long | Long
short | Short
int | Integer
double | Double
Byte | byte
Boolean | boolean
Character | char
Float | float
Long | long
Short | short
Integer | int
Double | double
}
@Managed
abstract static class MutableName implements Named {
abstract void setName(String name)
}
def "Named cannot have setName"() {
when:
extract MutableName
then:
def e = thrown Exception
e.message == """Type ${fullyQualifiedNameOf(MutableName)} is not a valid managed type:
- Property 'name' is not valid: it must not have a setter, because the type implements '$Named.name'"""
}
@Managed
static interface HasIsAndGetPropertyWithDifferentTypes {
boolean isValue()
String getValue()
}
def "handles is/get property with non-matching type"() {
when: extract HasIsAndGetPropertyWithDifferentTypes
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(HasIsAndGetPropertyWithDifferentTypes)} is not a valid managed type:
- Property 'value' is not valid: it must have a consistent type, but it's defined as String, boolean"""
}
@Managed
interface IsNotAllowedForOtherTypeThanBoolean {
String isThing()
void setThing(String thing)
}
@Managed
interface IsAllowedWithBoxedBoolean {
Boolean isThing()
void setThing(Boolean thing)
}
def "should not allow 'is' as a prefix for getter on non boolean in #type"() {
when: extract IsNotAllowedForOtherTypeThanBoolean
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(IsNotAllowedForOtherTypeThanBoolean)} is not a valid managed type:
- Property 'thing' is not valid: it must both have an abstract getter and a setter"""
}
def "allows 'is' as a prefix for getter on non primitive Boolean in #type"() {
def bindings = extract(IsAllowedWithBoxedBoolean)
expect:
bindings.getManagedProperty("thing")
}
@Managed
static abstract class ConstructorWithArguments {
ConstructorWithArguments(String arg) {}
}
@Managed
static abstract class AdditionalConstructorWithArguments {
AdditionalConstructorWithArguments() {}
AdditionalConstructorWithArguments(String arg) {}
}
static class SuperConstructorWithArguments {
SuperConstructorWithArguments(String arg) {}
}
@Managed
static abstract class ConstructorCallingSuperConstructorWithArgs extends SuperConstructorWithArguments {
ConstructorCallingSuperConstructorWithArgs() {
super("foo")
}
}
@Managed
static abstract class CustomConstructorInSuperClass extends ConstructorCallingSuperConstructorWithArgs {
}
def "custom constructors are not allowed"() {
when: extract ConstructorWithArguments
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(ConstructorWithArguments)} is not a valid managed type:
- Constructor DefaultStructBindingsStoreTest.ConstructorWithArguments(java.lang.String) is not valid: Custom constructors are not supported."""
when: extract AdditionalConstructorWithArguments
then: ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(AdditionalConstructorWithArguments)} is not a valid managed type:
- Constructor DefaultStructBindingsStoreTest.AdditionalConstructorWithArguments(java.lang.String) is not valid: Custom constructors are not supported."""
when: extract CustomConstructorInSuperClass
then: ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(CustomConstructorInSuperClass)} is not a valid managed type:
- Constructor DefaultStructBindingsStoreTest.SuperConstructorWithArguments(java.lang.String) is not valid: Custom constructors are not supported."""
}
static abstract class MultipleProblemsSuper {
private String field1
MultipleProblemsSuper(String s) {}
private String getPrivate() { field1 }
}
@Managed
static class MultipleProblems> extends MultipleProblemsSuper {
private String field2
MultipleProblems(String s) { super(s) }
}
def "collects all problems for a type"() {
when: extract MultipleProblems
then: def ex = thrown InvalidManagedTypeException
ex.message == """Type ${fullyQualifiedNameOf(MultipleProblems)} is not a valid managed type:
- Must be defined as an interface or an abstract class.
- Cannot be a parameterized type.
- Constructor DefaultStructBindingsStoreTest.MultipleProblems(java.lang.String) is not valid: Custom constructors are not supported.
- Field field2 is not valid: Fields must be static final.
- Constructor DefaultStructBindingsStoreTest.MultipleProblemsSuper(java.lang.String) is not valid: Custom constructors are not supported.
- Field DefaultStructBindingsStoreTest.MultipleProblemsSuper.field1 is not valid: Fields must be static final.
- Method DefaultStructBindingsStoreTest.MultipleProblemsSuper.getPrivate() is not a valid method: Protected and private methods are not supported."""
}
def extract(Class> type, Class> delegateType = null) {
return extract(type, [], delegateType)
}
def extract(Class> type, List> viewTypes, Class> delegateType = null) {
return bindingStore.getBindings(
ModelType.of(type),
viewTypes.collect { ModelType.of(it) },
delegateType == null ? null : ModelType.of(delegateType)
)
}
@Unroll
def "finds #results.simpleName as the converging types among #types.simpleName"() {
expect:
DefaultStructBindingsStore.findConvergingTypes(types.collect { ModelType.of(it) }) as List == results.collect { ModelType.of(it) }
where:
types | results
[Object] | [Object]
[Object, Serializable] | [Serializable]
[Object, Number, Comparable, Integer] | [Integer]
[Integer, Object, Number, Comparable] | [Integer]
[Integer, Double] | [Integer, Double]
[Integer, Object, Double] | [Integer, Double]
[Integer, Object, Comparable, Double] | [Integer, Double]
}
String getName(ModelType> type) {
type.displayName
}
String getName(Class> type) {
getName(ModelType.of(type))
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy