org.gradle.model.internal.manage.binding.DefaultStructBindingsStore Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* 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 com.google.common.base.Equivalence.Wrapper;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.UncheckedExecutionException;
import org.gradle.api.Named;
import org.gradle.internal.Cast;
import org.gradle.internal.UncheckedException;
import org.gradle.internal.reflect.PropertyAccessorType;
import org.gradle.internal.reflect.Types.TypeVisitor;
import org.gradle.model.Managed;
import org.gradle.model.Unmanaged;
import org.gradle.model.internal.manage.schema.CollectionSchema;
import org.gradle.model.internal.manage.schema.ManagedImplSchema;
import org.gradle.model.internal.manage.schema.ModelSchema;
import org.gradle.model.internal.manage.schema.ModelSchemaStore;
import org.gradle.model.internal.manage.schema.RuleSourceSchema;
import org.gradle.model.internal.manage.schema.ScalarCollectionSchema;
import org.gradle.model.internal.manage.schema.ScalarValueSchema;
import org.gradle.model.internal.manage.schema.StructSchema;
import org.gradle.model.internal.method.WeaklyTypeReferencingMethod;
import org.gradle.model.internal.type.ModelType;
import org.gradle.model.internal.type.ModelTypes;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import static org.gradle.internal.reflect.Methods.DESCRIPTOR_EQUIVALENCE;
import static org.gradle.internal.reflect.Methods.SIGNATURE_EQUIVALENCE;
import static org.gradle.internal.reflect.PropertyAccessorType.*;
import static org.gradle.internal.reflect.Types.walkTypeHierarchy;
public class DefaultStructBindingsStore implements StructBindingsStore {
private final LoadingCache> bindings = CacheBuilder.newBuilder()
.weakValues()
.build(new CacheLoader>() {
@Override
public StructBindings> load(CacheKey key) throws Exception {
return extract(key.publicType, key.viewTypes, key.delegateType);
}
});
private final ModelSchemaStore schemaStore;
public DefaultStructBindingsStore(ModelSchemaStore schemaStore) {
this.schemaStore = schemaStore;
}
@Override
public StructBindings getBindings(ModelType publicType) {
return getBindings(publicType, Collections.>emptySet(), null);
}
@Override
public StructBindings getBindings(ModelType publicType, Iterable extends ModelType>> internalViewTypes, ModelType> delegateType) {
try {
return Cast.uncheckedCast(bindings.get(new CacheKey(publicType, internalViewTypes, delegateType)));
} catch (ExecutionException e) {
throw UncheckedException.throwAsUncheckedException(e);
} catch (UncheckedExecutionException e) {
throw UncheckedException.throwAsUncheckedException(e.getCause());
}
}
StructBindings extract(ModelType publicType, Iterable extends ModelType>> internalViewTypes, ModelType delegateType) {
if (delegateType != null && Modifier.isAbstract(delegateType.getConcreteClass().getModifiers())) {
throw new InvalidManagedTypeException(String.format("Type '%s' is not a valid managed type: delegate type must be null or a non-abstract type instead of '%s'.",
publicType.getDisplayName(), delegateType.getDisplayName()));
}
Set> implementedViews = collectImplementedViews(publicType, internalViewTypes, delegateType);
StructSchema publicSchema = getStructSchema(publicType);
Iterable> declaredViewSchemas = getStructSchemas(Iterables.concat(Collections.singleton(publicType), internalViewTypes));
Iterable> implementedSchemas = getStructSchemas(implementedViews);
StructSchema delegateSchema = delegateType == null ? null : getStructSchema(delegateType);
StructBindingExtractionContext extractionContext = new StructBindingExtractionContext(publicSchema, implementedSchemas, delegateSchema);
if (!(publicSchema instanceof RuleSourceSchema)) {
validateTypeHierarchy(extractionContext, publicType);
for (ModelType> internalViewType : internalViewTypes) {
validateTypeHierarchy(extractionContext, internalViewType);
}
}
Map> propertyBindings = Maps.newTreeMap();
Set methodBindings = collectMethodBindings(extractionContext, propertyBindings);
ImmutableSortedMap> managedProperties = collectManagedProperties(extractionContext, propertyBindings);
if (extractionContext.problems.hasProblems()) {
throw new InvalidManagedTypeException(extractionContext.problems.format());
}
return new DefaultStructBindings(
publicSchema, declaredViewSchemas, implementedSchemas, delegateSchema,
managedProperties, methodBindings
);
}
private static void validateTypeHierarchy(final StructBindingValidationProblemCollector problems, ModelType type) {
walkTypeHierarchy(type.getConcreteClass(), new TypeVisitor() {
@Override
public void visitType(Class super T> type) {
if (type.isAnnotationPresent(Managed.class)) {
validateManagedType(problems, type);
}
validateType(problems, type);
}
});
}
private static void validateManagedType(StructBindingValidationProblemCollector problems, Class> typeClass) {
if (!typeClass.isInterface() && !Modifier.isAbstract(typeClass.getModifiers())) {
problems.add("Must be defined as an interface or an abstract class.");
}
if (typeClass.getTypeParameters().length > 0) {
problems.add("Cannot be a parameterized type.");
}
}
private static void validateType(StructBindingValidationProblemCollector problems, Class> typeClass) {
Constructor> customConstructor = findCustomConstructor(typeClass);
if (customConstructor != null) {
problems.add(customConstructor, "Custom constructors are not supported.");
}
ensureNoInstanceScopedFields(problems, typeClass);
// sort for determinism
Method[] methods = typeClass.getDeclaredMethods();
Arrays.sort(methods, Ordering.usingToString());
ensureNoProtectedOrPrivateMethods(problems, methods);
ensureNoDefaultMethods(problems, typeClass, methods);
}
private static Constructor> findCustomConstructor(Class> typeClass) {
Constructor>[] constructors = typeClass.getConstructors();
for (Constructor> constructor : constructors) {
if (constructor.getParameterTypes().length > 0) {
return constructor;
}
}
return null;
}
private static void ensureNoInstanceScopedFields(StructBindingValidationProblemCollector problems, Class> typeClass) {
List declaredFields = Arrays.asList(typeClass.getDeclaredFields());
for (Field field : declaredFields) {
int fieldModifiers = field.getModifiers();
if (!field.isSynthetic() && !(Modifier.isStatic(fieldModifiers) && Modifier.isFinal(fieldModifiers))) {
problems.add(field, "Fields must be static final.");
}
}
}
private static void ensureNoProtectedOrPrivateMethods(StructBindingValidationProblemCollector problems, Method[] declaredMethods) {
for (Method declaredMethod : declaredMethods) {
int modifiers = declaredMethod.getModifiers();
if (!declaredMethod.isSynthetic() && !Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) {
problems.add(declaredMethod, "Protected and private methods are not supported.");
}
}
}
private static void ensureNoDefaultMethods(StructBindingValidationProblemCollector problems, Class> typeClass, Method[] declaredMethods) {
if (!typeClass.isInterface()) {
return;
}
for (Method declaredMethod : declaredMethods) {
if (isDefaultInterfaceMethod(declaredMethod) && PropertyAccessorType.of(declaredMethod) == null) {
problems.add(declaredMethod, "Default interface methods are only supported for getters and setters.");
}
}
}
// Copied from Method.isDefault()
private static boolean isDefaultInterfaceMethod(Method method) {
// Default methods are public non-abstract instance methods declared in an interface.
return (method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC;
}
private ImmutableSortedMap> collectManagedProperties(StructBindingExtractionContext extractionContext, Map> propertyBindings) {
ImmutableSortedMap.Builder> managedPropertiesBuilder = ImmutableSortedMap.naturalOrder();
for (Map.Entry> propertyEntry : propertyBindings.entrySet()) {
String propertyName = propertyEntry.getKey();
Multimap accessorBindings = propertyEntry.getValue();
if (isManagedProperty(extractionContext, propertyName, accessorBindings)) {
if (hasSetter(accessorBindings.keySet()) && !hasGetter(accessorBindings.keySet())) {
extractionContext.add(propertyName, "it must both have an abstract getter and a setter");
continue;
}
ModelType> propertyType = determineManagedPropertyType(extractionContext, propertyName, accessorBindings);
ModelSchema> propertySchema = schemaStore.getSchema(propertyType);
managedPropertiesBuilder.put(propertyName, createManagedProperty(extractionContext, propertyName, propertySchema, accessorBindings));
}
}
return managedPropertiesBuilder.build();
}
private static boolean isManagedProperty(StructBindingExtractionContext> extractionContext, String propertyName, Multimap accessorBindings) {
Boolean managed = null;
for (Map.Entry> accessorEntry : accessorBindings.asMap().entrySet()) {
Collection bindings = accessorEntry.getValue();
boolean managedPropertyAccessor = isManagedPropertyAccessor(extractionContext, propertyName, bindings);
if (managed == null) {
managed = managedPropertyAccessor;
} else if (managed != managedPropertyAccessor) {
extractionContext.add(propertyName, "it must have either only abstract accessor methods or only implemented accessor methods");
managed = false;
break;
}
}
assert managed != null;
return managed;
}
private static boolean isManagedPropertyAccessor(StructBindingExtractionContext> extractionContext, String propertyName, Collection bindings) {
Set> implMethods = Sets.newLinkedHashSet();
for (StructMethodBinding binding : bindings) {
if (binding instanceof StructMethodImplementationBinding) {
implMethods.add(((StructMethodImplementationBinding) binding).getImplementorMethod());
}
}
switch (implMethods.size()) {
case 0:
return true;
case 1:
return false;
default:
extractionContext.add(propertyName, String.format("it has multiple implementations for accessor method: %s",
Joiner.on(", ").join(implMethods)));
return false;
}
}
private static ModelType> determineManagedPropertyType(StructBindingExtractionContext> extractionContext, String propertyName, Multimap accessorBindings) {
Collection isGetter = accessorBindings.get(IS_GETTER);
for (StructMethodBinding isGetterBinding : isGetter) {
if (!((ManagedPropertyMethodBinding) isGetterBinding).getDeclaredPropertyType().equals(ModelType.of(Boolean.TYPE))) {
WeaklyTypeReferencingMethod, ?> isGetterMethod = isGetterBinding.getViewMethod();
extractionContext.add(isGetterMethod, String.format("it should either return 'boolean', or its name should be '%s()'",
"get" + isGetterMethod.getName().substring(2)));
}
}
Set> potentialPropertyTypes = Sets.newLinkedHashSet();
for (StructMethodBinding binding : accessorBindings.values()) {
if (binding.getAccessorType() == SETTER) {
continue;
}
ManagedPropertyMethodBinding propertyBinding = (ManagedPropertyMethodBinding) binding;
potentialPropertyTypes.add(propertyBinding.getDeclaredPropertyType());
}
Collection> convergingPropertyTypes = findConvergingTypes(potentialPropertyTypes);
if (convergingPropertyTypes.size() != 1) {
extractionContext.add(propertyName, String.format("it must have a consistent type, but it's defined as %s",
Joiner.on(", ").join(ModelTypes.getDisplayNames(convergingPropertyTypes))));
return convergingPropertyTypes.iterator().next();
}
ModelType> propertyType = Iterables.getOnlyElement(convergingPropertyTypes);
for (StructMethodBinding setterBinding : accessorBindings.get(SETTER)) {
ManagedPropertyMethodBinding propertySetterBinding = (ManagedPropertyMethodBinding) setterBinding;
ModelType> declaredSetterType = propertySetterBinding.getDeclaredPropertyType();
if (!declaredSetterType.equals(propertyType)) {
extractionContext.add(setterBinding.getViewMethod(), String.format("it should take parameter with type '%s'",
propertyType.getDisplayName()));
}
}
return propertyType;
}
private static Set> collectImplementedViews(ModelType publicType, Iterable extends ModelType>> internalViewTypes, ModelType delegateType) {
final Set> viewsToImplement = Sets.newLinkedHashSet();
viewsToImplement.add(publicType);
Iterables.addAll(viewsToImplement, internalViewTypes);
// TODO:LPTR This should be removed once BinaryContainer is a ModelMap
// We need to also implement all the interfaces of the delegate type because otherwise
// BinaryContainer won't recognize managed binaries as BinarySpecInternal
if (delegateType != null) {
walkTypeHierarchy(delegateType.getConcreteClass(), new TypeVisitor() {
@Override
public void visitType(Class super D> type) {
if (type.isInterface()) {
viewsToImplement.add(ModelType.of(type));
}
}
});
}
return ModelTypes.collectHierarchy(viewsToImplement);
}
private static Set collectMethodBindings(StructBindingExtractionContext extractionContext, Map> propertyBindings) {
Collection> implementedMethods = collectImplementedMethods(extractionContext.getImplementedSchemas());
Map, WeaklyTypeReferencingMethod, ?>> publicViewImplMethods = collectPublicViewImplMethods(extractionContext.getPublicSchema());
Map, WeaklyTypeReferencingMethod, ?>> delegateMethods = collectDelegateMethods(extractionContext.getDelegateSchema());
ImmutableSet.Builder methodBindingsBuilder = ImmutableSet.builder();
for (WeaklyTypeReferencingMethod, ?> weakImplementedMethod : implementedMethods) {
Method implementedMethod = weakImplementedMethod.getMethod();
PropertyAccessorType accessorType = PropertyAccessorType.of(implementedMethod);
Wrapper methodKey = SIGNATURE_EQUIVALENCE.wrap(implementedMethod);
WeaklyTypeReferencingMethod, ?> weakDelegateImplMethod = delegateMethods.get(methodKey);
WeaklyTypeReferencingMethod, ?> weakPublicImplMethod = publicViewImplMethods.get(methodKey);
if (weakDelegateImplMethod != null && weakPublicImplMethod != null) {
extractionContext.add(weakImplementedMethod, String.format("it is both implemented by the view '%s' and the delegate type '%s'",
extractionContext.getPublicSchema().getType().getDisplayName(),
extractionContext.getDelegateSchema().getType().getDisplayName()));
}
String propertyName = accessorType == null ? null : accessorType.propertyNameFor(implementedMethod);
StructMethodBinding binding;
if (!Modifier.isAbstract(implementedMethod.getModifiers())) {
binding = new DirectMethodBinding(weakImplementedMethod, accessorType);
} else if (weakPublicImplMethod != null) {
binding = new BridgeMethodBinding(weakImplementedMethod, weakPublicImplMethod, accessorType);
} else if (weakDelegateImplMethod != null) {
binding = new DelegateMethodBinding(weakImplementedMethod, weakDelegateImplMethod, accessorType);
} else if (propertyName != null) {
binding = new ManagedPropertyMethodBinding(weakImplementedMethod, propertyName, accessorType);
} else {
handleNoMethodImplementation(extractionContext, weakImplementedMethod);
continue;
}
methodBindingsBuilder.add(binding);
if (accessorType != null) {
Multimap accessorBindings = propertyBindings.get(propertyName);
if (accessorBindings == null) {
accessorBindings = ArrayListMultimap.create();
propertyBindings.put(propertyName, accessorBindings);
}
accessorBindings.put(accessorType, binding);
}
}
return methodBindingsBuilder.build();
}
private static void handleNoMethodImplementation(StructBindingValidationProblemCollector problems, WeaklyTypeReferencingMethod, ?> method) {
String methodName = method.getName();
PropertyAccessorType accessorType = PropertyAccessorType.fromName(methodName);
if (accessorType != null) {
switch (accessorType) {
case GET_GETTER:
case IS_GETTER:
if (!PropertyAccessorType.takesNoParameter(method.getMethod())) {
problems.add(method, "property accessor", "getter method must not take parameters");
}
break;
case SETTER:
if (!hasVoidReturnType(method.getMethod())) {
problems.add(method, "property accessor", "setter method must have void return type");
}
if (!takesSingleParameter(method.getMethod())) {
problems.add(method, "property accessor", "setter method must take exactly one parameter");
}
break;
default:
throw new AssertionError();
}
} else {
problems.add(method, "managed type", "it must have an implementation");
}
}
private static Map, WeaklyTypeReferencingMethod, ?>> collectDelegateMethods(StructSchema> delegateSchema) {
return delegateSchema == null ? Collections., WeaklyTypeReferencingMethod, ?>>emptyMap() : indexBySignature(delegateSchema.getAllMethods());
}
private static Map, WeaklyTypeReferencingMethod, ?>> collectPublicViewImplMethods(StructSchema publicSchema) {
return indexBySignature(
Sets.filter(
publicSchema.getAllMethods(),
new Predicate>() {
@Override
public boolean apply(WeaklyTypeReferencingMethod, ?> weakMethod) {
return !Modifier.isAbstract(weakMethod.getModifiers());
}
}
)
);
}
private static ImmutableMap, WeaklyTypeReferencingMethod, ?>> indexBySignature(Iterable> methods) {
return Maps.uniqueIndex(methods, new Function, Wrapper>() {
@Override
public Wrapper apply(WeaklyTypeReferencingMethod, ?> weakMethod) {
return SIGNATURE_EQUIVALENCE.wrap(weakMethod.getMethod());
}
});
}
private static Collection> collectImplementedMethods(Iterable> implementedSchemas) {
Map, WeaklyTypeReferencingMethod, ?>> implementedMethodsBuilder = Maps.newLinkedHashMap();
for (StructSchema> implementedSchema : implementedSchemas) {
for (WeaklyTypeReferencingMethod, ?> viewMethod : implementedSchema.getAllMethods()) {
implementedMethodsBuilder.put(DESCRIPTOR_EQUIVALENCE.wrap(viewMethod.getMethod()), viewMethod);
}
}
return implementedMethodsBuilder.values();
}
private static ManagedProperty createManagedProperty(StructBindingExtractionContext> extractionContext, String propertyName, ModelSchema propertySchema, Multimap accessors) {
boolean writable = accessors.containsKey(SETTER);
boolean declaredAsUnmanaged = isDeclaredAsHavingUnmanagedType(accessors.get(GET_GETTER))
|| isDeclaredAsHavingUnmanagedType(accessors.get(IS_GETTER));
boolean internal = !extractionContext.getPublicSchema().hasProperty(propertyName);
validateManagedProperty(extractionContext, propertyName, propertySchema, writable, declaredAsUnmanaged);
return new ManagedProperty(propertyName, propertySchema.getType(), writable, declaredAsUnmanaged, internal);
}
private static void validateManagedProperty(StructBindingExtractionContext> extractionContext, String propertyName, ModelSchema> propertySchema, boolean writable, boolean isDeclaredAsHavingUnmanagedType) {
if (propertyName.equals("name") && Named.class.isAssignableFrom(extractionContext.getPublicSchema().getType().getRawClass())) {
if (writable) {
extractionContext.add(propertyName, String.format("it must not have a setter, because the type implements '%s'", Named.class.getName()));
}
return;
}
// Only managed implementation and value types are allowed as a managed property type unless marked with @Unmanaged
boolean isAllowedPropertyTypeOfManagedType = propertySchema instanceof ManagedImplSchema
|| propertySchema instanceof ScalarValueSchema;
ModelType> propertyType = propertySchema.getType();
if (isAllowedPropertyTypeOfManagedType && isDeclaredAsHavingUnmanagedType) {
extractionContext.add(propertyName, String.format("it is marked as @Unmanaged, but is of @Managed type '%s'; please remove the @Managed annotation",
propertyType.getDisplayName()
));
}
if (!writable && isDeclaredAsHavingUnmanagedType) {
extractionContext.add(propertyName, "it must not be read only, because it is marked as @Unmanaged");
}
if (!(extractionContext.getPublicSchema() instanceof RuleSourceSchema)) {
if (propertySchema instanceof CollectionSchema) {
if (!(propertySchema instanceof ScalarCollectionSchema) && writable) {
extractionContext.add(propertyName, String.format("it cannot have a setter (%s properties must be read only)",
propertyType.getRawClass().getSimpleName()));
}
}
}
}
private static boolean isDeclaredAsHavingUnmanagedType(Collection accessorBindings) {
for (StructMethodBinding accessorBinding : accessorBindings) {
if (accessorBinding.getViewMethod().getMethod().isAnnotationPresent(Unmanaged.class)) {
return true;
}
}
return false;
}
private Iterable> getStructSchemas(Iterable extends ModelType extends T>> types) {
return Iterables.transform(types, new Function, StructSchema extends T>>() {
@Override
public StructSchema extends T> apply(ModelType extends T> type) {
return getStructSchema(type);
}
});
}
private StructSchema getStructSchema(ModelType type) {
ModelSchema schema = schemaStore.getSchema(type);
if (!(schema instanceof StructSchema)) {
throw new IllegalArgumentException(String.format("Type '%s' is not a struct.", type.getDisplayName()));
}
return Cast.uncheckedCast(schema);
}
private static class CacheKey {
private final ModelType> publicType;
private final Set> viewTypes;
private final ModelType> delegateType;
public CacheKey(ModelType> publicType, Iterable extends ModelType>> viewTypes, ModelType> delegateType) {
this.publicType = publicType;
this.viewTypes = ImmutableSet.copyOf(viewTypes);
this.delegateType = delegateType;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
CacheKey cacheKey = (CacheKey) o;
return Objects.equal(publicType, cacheKey.publicType)
&& Objects.equal(viewTypes, cacheKey.viewTypes)
&& Objects.equal(delegateType, cacheKey.delegateType);
}
@Override
public int hashCode() {
return Objects.hashCode(publicType, viewTypes, delegateType);
}
}
/**
* Finds the types in the given collection that cannot be assigned from any other type in the collection.
*/
static Collection> findConvergingTypes(Collection> allTypes) {
if (allTypes.size() == 0) {
throw new IllegalArgumentException("No types given");
}
if (allTypes.size() == 1) {
return allTypes;
}
Set> typesToCheck = Sets.newLinkedHashSet(allTypes);
Set> convergingTypes = Sets.newLinkedHashSet(allTypes);
while (!typesToCheck.isEmpty()) {
Iterator> iTypeToCheck = typesToCheck.iterator();
ModelType> typeToCheck = iTypeToCheck.next();
iTypeToCheck.remove();
Iterator> iRemainingType = convergingTypes.iterator();
while (iRemainingType.hasNext()) {
ModelType> remainingType = iRemainingType.next();
if (!remainingType.equals(typeToCheck) && remainingType.isAssignableFrom(typeToCheck)) {
iRemainingType.remove();
typesToCheck.remove(remainingType);
}
}
}
return convergingTypes;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy