All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.gradle.model.internal.manage.binding.DefaultStructBindingsStore Maven / Gradle / Ivy

There is a newer version: 8.11.1
Show newest version
/*
 * 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> 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> 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 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> 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 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> types) {
        return Iterables.transform(types, new Function, StructSchema>() {
            @Override
            public StructSchema apply(ModelType 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> 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