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

org.jboss.weld.spock.impl.ClassScanning Maven / Gradle / Ivy

The newest version!
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2022, Red Hat, Inc., and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * 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.jboss.weld.spock.impl;

import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields;
import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedMethods;
import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated;
import static org.junit.platform.commons.support.HierarchyTraversalMode.BOTTOM_UP;
import static org.junit.platform.commons.support.ReflectionSupport.findMethods;
import static org.spockframework.util.Identifiers.CLEANUP_METHOD;
import static org.spockframework.util.Identifiers.CLEANUP_SPEC_METHOD;
import static org.spockframework.util.Identifiers.SETUP_METHOD;
import static org.spockframework.util.Identifiers.SETUP_SPEC_METHOD;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import jakarta.decorator.Decorator;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.context.NormalScope;
import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.inject.Produces;
import jakarta.enterprise.inject.Stereotype;
import jakarta.enterprise.inject.spi.Extension;
import jakarta.inject.Inject;
import jakarta.inject.Qualifier;
import jakarta.interceptor.Interceptor;

import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.spock.auto.AddBeanClasses;
import org.jboss.weld.spock.auto.AddEnabledDecorators;
import org.jboss.weld.spock.auto.AddEnabledInterceptors;
import org.jboss.weld.spock.auto.AddExtensions;
import org.jboss.weld.spock.auto.AddPackages;
import org.jboss.weld.spock.auto.EnableAlternativeStereotypes;
import org.jboss.weld.spock.auto.EnableAlternatives;
import org.jboss.weld.spock.auto.ExcludeBean;
import org.jboss.weld.spock.auto.ExcludeBeanClasses;
import org.junit.platform.commons.support.AnnotationSupport;
import org.junit.platform.commons.util.CollectionUtils;
import org.junit.platform.commons.util.Preconditions;
import org.spockframework.runtime.model.FeatureMetadata;

/**
 * Provides automagic bean class discovery for a test class instance.
 *
 * @author Björn Kautler
 */
class ClassScanning {
    static void scanForRequiredBeanClasses(Class testClass, Weld weld, boolean explicitInjection) {
        List> classesToProcess = new ArrayList<>();
        classesToProcess.add(testClass);

        Set> foundClasses = new HashSet<>();
        Set excludedBeanTypes = new HashSet<>();
        Set> excludedBeanClasses = new HashSet<>();

        while (!classesToProcess.isEmpty()) {
            Class currClass = classesToProcess.remove(0);

            if (foundClasses.contains(currClass) ||
                    excludedBeanTypes.contains(currClass) || excludedBeanClasses.contains(currClass) ||
                    currClass.isPrimitive() || currClass.isSynthetic() ||
                    currClass.getName().startsWith("java") || currClass.getName().startsWith("sun")) {
                continue;
            }

            foundClasses.add(currClass);

            findAnnotatedFields(currClass, ExcludeBean.class).stream()
                    .map(Field::getType)
                    .forEach(excludedBeanTypes::add);

            findAnnotatedMethods(currClass, ExcludeBean.class, BOTTOM_UP).stream()
                    .map(Method::getReturnType)
                    .forEach(excludedBeanTypes::add);

            findAnnotatedFields(currClass, Inject.class).stream()
                    .map(ClassScanning::unwrapInstanceTypeParameter)
                    .forEach(cls -> addClassesToProcess(classesToProcess, cls));

            findAnnotatedMethods(currClass, Inject.class, BOTTOM_UP).stream()
                    .map(Method::getReturnType)
                    .forEach(cls -> addClassesToProcess(classesToProcess, cls));

            findFirstAnnotatedConstructor(currClass, Inject.class)
                    .map(Stream::of).orElseGet(Stream::empty)
                    .flatMap(cons -> getExecutableParameterTypes(cons, explicitInjection).stream())
                    .forEach(cls -> addClassesToProcess(classesToProcess, cls));

            findAnnotatedDeclaredFields(currClass, Produces.class).stream()
                    .map(Field::getType)
                    .forEach(cls -> addClassesToProcess(classesToProcess, cls));

            findAnnotatedDeclaredMethods(currClass, Produces.class).stream()
                    .flatMap(method -> Stream.concat(
                            getExecutableParameterTypes(method, explicitInjection).stream(),
                            Stream.of(method.getReturnType())))
                    .forEach(cls -> addClassesToProcess(classesToProcess, cls));

            findAnnotatedMethods(currClass, FeatureMetadata.class, BOTTOM_UP).stream()
                    .flatMap(method -> getExecutableParameterTypes(method, explicitInjection).stream())
                    .forEach(cls -> addClassesToProcess(classesToProcess, cls));

            findMethods(currClass, method -> SETUP_SPEC_METHOD.equals(method.getName()), BOTTOM_UP).stream()
                    .flatMap(method -> getExecutableParameterTypes(method, explicitInjection).stream())
                    .forEach(cls -> addClassesToProcess(classesToProcess, cls));

            findMethods(currClass, method -> SETUP_METHOD.equals(method.getName()), BOTTOM_UP).stream()
                    .flatMap(method -> getExecutableParameterTypes(method, explicitInjection).stream())
                    .forEach(cls -> addClassesToProcess(classesToProcess, cls));

            findMethods(currClass, method -> CLEANUP_METHOD.equals(method.getName()), BOTTOM_UP).stream()
                    .flatMap(method -> getExecutableParameterTypes(method, explicitInjection).stream())
                    .forEach(cls -> addClassesToProcess(classesToProcess, cls));

            findMethods(currClass, method -> CLEANUP_SPEC_METHOD.equals(method.getName()), BOTTOM_UP).stream()
                    .flatMap(method -> getExecutableParameterTypes(method, explicitInjection).stream())
                    .forEach(cls -> addClassesToProcess(classesToProcess, cls));

            AnnotationSupport.findRepeatableAnnotations(currClass, AddPackages.class)
                    .forEach(ann -> stream(ann.value())
                            .distinct()
                            .forEach(cls -> weld.addPackage(ann.recursively(), cls)));

            AnnotationSupport.findRepeatableAnnotations(currClass, AddBeanClasses.class).stream()
                    .flatMap(ann -> stream(ann.value()))
                    .distinct()
                    .forEach(it -> {
                        classesToProcess.add(it);
                        weld.addBeanClass(it);
                    });

            AnnotationSupport.findRepeatableAnnotations(currClass, AddExtensions.class).stream()
                    .flatMap(ann -> stream(ann.value()))
                    .distinct()
                    .map(ClassScanning::createExtension)
                    .forEach(weld::addExtension);

            AnnotationSupport.findRepeatableAnnotations(currClass, AddEnabledInterceptors.class).stream()
                    .flatMap(ann -> stream(ann.value()))
                    .distinct()
                    .forEach(interceptor -> {
                        classesToProcess.add(interceptor);
                        weld.addInterceptor(interceptor);
                        weld.addBeanClass(interceptor);
                    });

            AnnotationSupport.findRepeatableAnnotations(currClass, AddEnabledDecorators.class).stream()
                    .flatMap(ann -> stream(ann.value()))
                    .distinct()
                    .forEach(decorator -> {
                        classesToProcess.add(decorator);
                        weld.addDecorator(decorator);
                        weld.addBeanClass(decorator);
                    });

            AnnotationSupport.findRepeatableAnnotations(currClass, EnableAlternatives.class).stream()
                    .flatMap(ann -> stream(ann.value()))
                    .distinct()
                    .forEach(weld::addAlternative);

            AnnotationSupport.findRepeatableAnnotations(currClass, EnableAlternativeStereotypes.class).stream()
                    .flatMap(ann -> stream(ann.value()))
                    .distinct()
                    .forEach(weld::addAlternativeStereotype);

            AnnotationSupport.findRepeatableAnnotations(currClass, ExcludeBeanClasses.class).stream()
                    .flatMap(ann -> stream(ann.value()))
                    .distinct()
                    .forEach(excludedBeanClasses::add);
        }

        for (Class foundClass : foundClasses) {
            if (hasBeanDefiningAnnotation(foundClass)) {
                weld.addBeanClass(foundClass);
            }
        }

        weld.addExtension(new ExcludedBeansExtension(excludedBeanTypes, excludedBeanClasses));
    }

    private static void addClassesToProcess(Collection> classesToProcess, Type type) {
        if (type instanceof Class) {
            classesToProcess.add((Class) type);
        } else if (type instanceof ParameterizedType) {
            ParameterizedType ptype = (ParameterizedType) type;

            classesToProcess.add((Class) ptype.getRawType());

            for (Type arg : ptype.getActualTypeArguments()) {
                addClassesToProcess(classesToProcess, arg);
            }
        }
    }

    private static List> getExecutableParameterTypes(Executable executable, boolean explicitInjection) {
        List> types = new ArrayList<>();

        if (explicitInjection) {
            Annotation[][] paramAnns = executable.getParameterAnnotations();
            Class[] paramTypes = executable.getParameterTypes();
            for (int c = 0; c < paramTypes.length; ++c) {
                if (stream(paramAnns[c]).anyMatch(ClassScanning::isBeanParameterAnnotation)) {
                    types.add(paramTypes[c]);
                }
            }
        } else {
            types.addAll(asList(executable.getParameterTypes()));
        }

        return types;
    }

    private static Extension createExtension(Class clazz) {
        try {
            return clazz.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static boolean isBeanParameterAnnotation(Annotation ann) {
        return isAnnotated(ann.annotationType(), Qualifier.class);
    }

    private static boolean hasBeanDefiningAnnotation(Class clazz) {
        return isAnnotated(clazz, NormalScope.class) || isAnnotated(clazz, Dependent.class) ||
                isAnnotated(clazz, Interceptor.class) || isAnnotated(clazz, Decorator.class) ||
                isAnnotated(clazz, Stereotype.class);
    }

    private static List findAllFieldsInHierarchy(Class clazz) {
        Preconditions.notNull(clazz, "Class must not be null");
        List localFields = getDeclaredFields(clazz).stream().filter((field) -> !field.isSynthetic())
                .collect(Collectors.toList());
        List superclassFields = getSuperclassFields(clazz).stream()
                .filter((field) -> !isMethodShadowedByLocalFields(field, localFields))
                .collect(Collectors.toList());
        List methods = new ArrayList<>();
        methods.addAll(superclassFields);
        methods.addAll(localFields);

        return methods;
    }

    private static List getSuperclassFields(Class clazz) {
        Class superclass = clazz.getSuperclass();
        return superclass != null && superclass != Object.class ? findAllFieldsInHierarchy(superclass)
                : Collections.emptyList();
    }

    private static List getDeclaredFields(Class clazz) {
        return asList(clazz.getDeclaredFields());
    }

    private static List getDeclaredMethods(Class clazz) {
        return asList(clazz.getDeclaredMethods());
    }

    private static boolean isMethodShadowedByLocalFields(Field field, List localFields) {
        return localFields.stream()
                .anyMatch((local) -> isFieldShadowedBy(field, local));
    }

    private static boolean isFieldShadowedBy(Field upper, Field lower) {
        return upper.getType().equals(lower.getType());
    }

    private static List findAnnotatedDeclaredFields(Class clazz, Class annotationType) {
        return getDeclaredFields(clazz).stream()
                .filter((field) -> isAnnotated(field, annotationType))
                .collect(CollectionUtils.toUnmodifiableList());
    }

    private static List findAnnotatedDeclaredMethods(Class clazz, Class annotationType) {
        return getDeclaredMethods(clazz).stream()
                .filter((field) -> isAnnotated(field, annotationType))
                .collect(CollectionUtils.toUnmodifiableList());
    }

    private static List> getDeclaredConstructors(Class clazz) {
        return asList(clazz.getDeclaredConstructors());
    }

    private static Optional> findFirstAnnotatedConstructor(Class clazz,
            Class annotationType) {
        Optional> found = getDeclaredConstructors(clazz).stream()
                .filter((cons) -> isAnnotated(cons, annotationType))
                .findFirst();

        if (found.isPresent() || clazz.getSuperclass() == null) {
            return found;
        }

        return findFirstAnnotatedConstructor(clazz.getSuperclass(), annotationType);
    }

    private static Class unwrapInstanceTypeParameter(Field field) {
        Class type = field.getType();
        if (type.equals(Instance.class)) {
            ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
            Type typeParameter = parameterizedType.getActualTypeArguments()[0];
            if (typeParameter instanceof ParameterizedType) {
                type = (Class) ((ParameterizedType) typeParameter).getRawType();
            } else {
                type = (Class) typeParameter;
            }
        }
        return type;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy