org.gradle.model.internal.manage.schema.extract.ManagedImplStructStrategy.java Source code

Java tutorial

Introduction

Here is the source code for org.gradle.model.internal.manage.schema.extract.ManagedImplStructStrategy.java

Source

/*
 * Copyright 2015 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.schema.extract;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import org.gradle.api.Action;
import org.gradle.api.Named;
import org.gradle.internal.reflect.MethodDescription;
import org.gradle.model.Managed;
import org.gradle.model.Unmanaged;
import org.gradle.model.internal.manage.schema.*;
import org.gradle.model.internal.type.ModelType;

import java.lang.reflect.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import static org.gradle.model.internal.manage.schema.extract.ModelSchemaUtils.isMethodDeclaredInManagedType;
import static org.gradle.model.internal.manage.schema.extract.ModelSchemaUtils.walkTypeHierarchy;

public class ManagedImplStructStrategy extends StructSchemaExtractionStrategySupport {

    protected ManagedImplStructStrategy(ModelSchemaAspectExtractor aspectExtractor) {
        super(aspectExtractor);
    }

    @Override
    protected boolean isTarget(ModelType<?> type) {
        return type.getRawClass().isAnnotationPresent(Managed.class);
    }

    @Override
    protected <R> void validateTypeHierarchy(final ModelSchemaExtractionContext<R> extractionContext,
            ModelType<R> type) {
        walkTypeHierarchy(type.getConcreteClass(), new ModelSchemaUtils.TypeVisitor<R>() {
            @Override
            public void visitType(Class<? super R> type) {
                if (type.isAnnotationPresent(Managed.class)) {
                    validateManagedType(extractionContext, type);
                }
            }
        });
    }

    @Override
    protected void handleOverloadedMethods(ModelSchemaExtractionContext<?> extractionContext,
            Collection<Method> overloadedMethods) {
        if (isMethodDeclaredInManagedType(overloadedMethods)) {
            throw invalidMethods(extractionContext, "overloaded methods are not supported", overloadedMethods);
        }
    }

    @Override
    protected ModelProperty.StateManagementType determineStateManagementType(
            ModelSchemaExtractionContext<?> extractionContext, PropertyAccessorExtractionContext getterContext) {
        // Named.getName() needs to be handled specially
        if (getterContext.getMostSpecificDeclaration().getName().equals("getName")
                && Named.class.isAssignableFrom(extractionContext.getType().getRawClass())) {
            return ModelProperty.StateManagementType.MANAGED;
        }

        if (getterContext.isDeclaredInManagedType()) {
            if (getterContext.isDeclaredAsAbstract()) {
                return ModelProperty.StateManagementType.MANAGED;
            } else {
                return ModelProperty.StateManagementType.UNMANAGED;
            }
        } else {
            return ModelProperty.StateManagementType.UNMANAGED;
        }
    }

    @Override
    protected <R> ModelManagedImplStructSchema<R> createSchema(ModelSchemaExtractionContext<R> extractionContext,
            Iterable<ModelPropertyExtractionResult<?>> propertyResults, Iterable<ModelSchemaAspect> aspects) {
        ModelType<R> type = extractionContext.getType();
        Iterable<ModelProperty<?>> properties = Iterables.transform(propertyResults,
                new Function<ModelPropertyExtractionResult<?>, ModelProperty<?>>() {
                    @Override
                    public ModelProperty<?> apply(ModelPropertyExtractionResult<?> propertyResult) {
                        return propertyResult.getProperty();
                    }
                });
        return new ModelManagedImplStructSchema<R>(type, properties, aspects);
    }

    @Override
    protected void handleInvalidGetter(ModelSchemaExtractionContext<?> extractionContext, Method getter,
            String message) {
        if (ModelSchemaUtils.isMethodDeclaredInManagedType(getter)) {
            throw invalidMethod(extractionContext, message, getter);
        }
    }

    @Override
    protected void validateSetter(ModelSchemaExtractionContext<?> extractionContext, ModelType<?> propertyType,
            PropertyAccessorExtractionContext getterContext, PropertyAccessorExtractionContext setterContext) {
        // Get most specific setter
        Method mostSpecificSetter = setterContext.getMostSpecificDeclaration();

        if (!getterContext.isDeclaredAsAbstract() && setterContext.isDeclaredAsAbstract()) {
            throw invalidMethod(extractionContext, "setters are not allowed for non-abstract getters",
                    mostSpecificSetter);
        }

        if (mostSpecificSetter.getName().equals("setName")
                && Named.class.isAssignableFrom(extractionContext.getType().getRawClass())) {
            throw new InvalidManagedModelElementTypeException(extractionContext,
                    String.format("@Managed types implementing %s must not declare a setter for the name property",
                            Named.class.getName()));
        } else {
            if (getterContext.isDeclaredInManagedType() && !setterContext.isDeclaredInManagedType()) {
                throw invalidMethods(extractionContext, "unmanaged setter for managed getter",
                        Iterables.concat(getterContext.getDeclaringMethods(), setterContext.getDeclaringMethods()));
            } else if (!getterContext.isDeclaredInManagedType() && setterContext.isDeclaredInManagedType()) {
                throw invalidMethods(extractionContext, "managed setter for unmanaged getter",
                        Iterables.concat(getterContext.getDeclaringMethods(), setterContext.getDeclaringMethods()));
            }
        }

        if (!setterContext.isDeclaredInManagedType()) {
            return;
        }

        if (!Modifier.isAbstract(mostSpecificSetter.getModifiers())) {
            throw invalidMethod(extractionContext, "non-abstract setters are not allowed", mostSpecificSetter);
        }

        if (!mostSpecificSetter.getReturnType().equals(void.class)) {
            throw invalidMethod(extractionContext, "setter method must have void return type", mostSpecificSetter);
        }

        Type[] setterParameterTypes = mostSpecificSetter.getGenericParameterTypes();
        if (setterParameterTypes.length != 1) {
            throw invalidMethod(extractionContext, "setter method must have exactly one parameter",
                    mostSpecificSetter);
        }

        ModelType<?> setterType = ModelType.paramType(mostSpecificSetter, 0);
        if (!setterType.equals(propertyType)) {
            String message = "setter method param must be of exactly the same type as the getter returns (expected: "
                    + propertyType + ", found: " + setterType + ")";
            throw invalidMethod(extractionContext, message, mostSpecificSetter);
        }
    }

    @Override
    protected void validateAllNecessaryMethodsHandled(ModelSchemaExtractionContext<?> extractionContext,
            Collection<Method> allMethods, final Set<Method> handledMethods) {
        Iterable<Method> notHandled = Iterables.filter(allMethods, new Predicate<Method>() {
            @Override
            public boolean apply(Method method) {
                return method.getDeclaringClass().isAnnotationPresent(Managed.class)
                        && !handledMethods.contains(method);
            }
        });

        // TODO - should call out valid getters without setters
        if (!Iterables.isEmpty(notHandled)) {
            throw invalidMethods(extractionContext, "only paired getter/setter methods are supported", notHandled);
        }
    }

    @Override
    protected <P> Action<ModelSchema<P>> createPropertyValidator(
            final ModelSchemaExtractionContext<?> parentContext,
            final ModelPropertyExtractionResult<P> propertyResult) {
        return new Action<ModelSchema<P>>() {
            @Override
            public void execute(ModelSchema<P> propertySchema) {
                ModelProperty<P> property = propertyResult.getProperty();
                // Do not validate unmanaged properties
                if (!property.getStateManagementType().equals(ModelProperty.StateManagementType.MANAGED)) {
                    return;
                }

                // The "name" property is handled differently if type implements Named
                if (property.getName().equals("name")
                        && Named.class.isAssignableFrom(parentContext.getType().getRawClass())) {
                    return;
                }

                // Only managed implementation and value types are allowed as a managed property type unless marked with @Unmanaged
                boolean isAllowedPropertyTypeOfManagedType = propertySchema instanceof ManagedImplModelSchema
                        || propertySchema instanceof ModelValueSchema;

                boolean isDeclaredAsHavingUnmanagedType = propertyResult.getGetter()
                        .isAnnotationPresent(Unmanaged.class);

                if (isAllowedPropertyTypeOfManagedType && isDeclaredAsHavingUnmanagedType) {
                    throw new InvalidManagedModelElementTypeException(parentContext, String.format(
                            "property '%s' is marked as @Unmanaged, but is of @Managed type '%s'. Please remove the @Managed annotation.%n",
                            property.getName(), property.getType()));
                }

                if (!property.isWritable()) {
                    if (isDeclaredAsHavingUnmanagedType) {
                        throw new InvalidManagedModelElementTypeException(parentContext, String.format(
                                "unmanaged property '%s' cannot be read only, unmanaged properties must have setters",
                                property.getName()));
                    }
                }

                if (propertySchema instanceof ModelCollectionSchema) {
                    if (!(propertySchema instanceof ScalarCollectionSchema) && property.isWritable()) {
                        throw new InvalidManagedModelElementTypeException(parentContext,
                                String.format(
                                        "property '%s' cannot have a setter (%s properties must be read only).",
                                        property.getName(), property.getType().toString()));
                    }
                }
            }
        };
    }

    private void validateManagedType(ModelSchemaExtractionContext<?> extractionContext, Class<?> typeClass) {
        if (!typeClass.isInterface() && !Modifier.isAbstract(typeClass.getModifiers())) {
            throw new InvalidManagedModelElementTypeException(extractionContext,
                    "must be defined as an interface or an abstract class.");
        }

        if (typeClass.getTypeParameters().length > 0) {
            throw new InvalidManagedModelElementTypeException(extractionContext, "cannot be a parameterized type.");
        }

        Constructor<?> customConstructor = findCustomConstructor(typeClass);
        if (customConstructor != null) {
            throw invalidMethod(extractionContext, "custom constructors are not allowed", customConstructor);
        }

        ensureNoInstanceScopedFields(extractionContext, typeClass);
        ensureNoProtectedOrPrivateMethods(extractionContext, typeClass);
    }

    private void ensureNoProtectedOrPrivateMethods(ModelSchemaExtractionContext<?> extractionContext,
            Class<?> typeClass) {
        Class<?> superClass = typeClass.getSuperclass();
        if (superClass != null && !superClass.equals(Object.class)) {
            ensureNoProtectedOrPrivateMethods(extractionContext, superClass);
        }

        Iterable<Method> protectedAndPrivateMethods = Iterables
                .filter(Arrays.asList(typeClass.getDeclaredMethods()), new Predicate<Method>() {
                    @Override
                    public boolean apply(Method method) {
                        int modifiers = method.getModifiers();
                        return !method.isSynthetic()
                                && (Modifier.isProtected(modifiers) || Modifier.isPrivate(modifiers));
                    }
                });

        if (!Iterables.isEmpty(protectedAndPrivateMethods)) {
            throw invalidMethods(extractionContext, "protected and private methods are not allowed",
                    protectedAndPrivateMethods);
        }
    }

    private void ensureNoInstanceScopedFields(ModelSchemaExtractionContext<?> extractionContext,
            Class<?> typeClass) {
        Class<?> superClass = typeClass.getSuperclass();
        if (superClass != null && !superClass.equals(Object.class)) {
            ensureNoInstanceScopedFields(extractionContext, superClass);
        }

        List<Field> declaredFields = Arrays.asList(typeClass.getDeclaredFields());
        Iterable<Field> instanceScopedFields = Iterables.filter(declaredFields, new Predicate<Field>() {
            public boolean apply(Field field) {
                return !Modifier.isStatic(field.getModifiers()) && !field.getName().equals("metaClass");
            }
        });
        ImmutableSortedSet<String> sortedDescriptions = ImmutableSortedSet
                .copyOf(Iterables.transform(instanceScopedFields, new Function<Field, String>() {
                    public String apply(Field field) {
                        return field.toString();
                    }
                }));
        if (!sortedDescriptions.isEmpty()) {
            throw new InvalidManagedModelElementTypeException(extractionContext,
                    "instance scoped fields are not allowed (found fields: "
                            + Joiner.on(", ").join(sortedDescriptions) + ").");
        }
    }

    private Constructor<?> findCustomConstructor(Class<?> typeClass) {
        Class<?> superClass = typeClass.getSuperclass();
        if (superClass != null && !superClass.equals(Object.class)) {
            Constructor<?> customSuperConstructor = findCustomConstructor(typeClass.getSuperclass());
            if (customSuperConstructor != null) {
                return customSuperConstructor;
            }
        }
        Constructor<?>[] constructors = typeClass.getConstructors();
        if (constructors.length == 0
                || (constructors.length == 1 && constructors[0].getParameterTypes().length == 0)) {
            return null;
        } else {
            for (Constructor<?> constructor : constructors) {
                if (constructor.getParameterTypes().length > 0) {
                    return constructor;
                }
            }
            //this should never happen
            throw new RuntimeException(String.format(
                    "Expected a constructor taking at least one argument in %s but no such constructors were found",
                    typeClass.getName()));
        }
    }

    private InvalidManagedModelElementTypeException invalidMethod(ModelSchemaExtractionContext<?> extractionContext,
            String message, Method method) {
        return invalidMethod(extractionContext, message, MethodDescription.of(method));
    }

    private InvalidManagedModelElementTypeException invalidMethod(ModelSchemaExtractionContext<?> extractionContext,
            String message, Constructor<?> constructor) {
        return invalidMethod(extractionContext, message, MethodDescription.of(constructor));
    }

    private InvalidManagedModelElementTypeException invalidMethod(ModelSchemaExtractionContext<?> extractionContext,
            String message, MethodDescription methodDescription) {
        return new InvalidManagedModelElementTypeException(extractionContext,
                message + " (invalid method: " + methodDescription.toString() + ").");
    }

    private InvalidManagedModelElementTypeException invalidMethods(
            ModelSchemaExtractionContext<?> extractionContext, String message, Iterable<Method> methods) {
        final ImmutableSortedSet<String> descriptions = ImmutableSortedSet
                .copyOf(Iterables.transform(methods, new Function<Method, String>() {
                    public String apply(Method method) {
                        return MethodDescription.of(method).toString();
                    }
                }));
        return new InvalidManagedModelElementTypeException(extractionContext,
                message + " (invalid methods: " + Joiner.on(", ").join(descriptions) + ").");
    }
}