InjectionUtils.java

/*
 * Copyright (c) 2021 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.inject.weld.internal.injector;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.enterprise.inject.spi.Bean;
import javax.inject.Named;
import javax.inject.Provider;

import org.glassfish.jersey.internal.inject.Injectee;
import org.glassfish.jersey.internal.inject.InjecteeImpl;
import org.glassfish.jersey.internal.inject.InjectionResolver;
import org.glassfish.jersey.internal.util.Pretty;
import org.glassfish.jersey.internal.util.collection.ImmutableCollectors;

/**
 * Utility class for processing of an injection.
 *
 * @author Petr Bouda
 */
public final class InjectionUtils {

    /**
     * Forbids the creation of {@link InjectionUtils} instance.
     */
    private InjectionUtils() {
    }

    /**
     * Just injects the thing, doesn't try to do anything else
     *
     * @param injectMe      the object to inject into.
     * @param bean          information about the injected instance.
     * @param resolvers     all injection resolvers registered in the application.
     * @param proxyResolver object which is able to create a proxy.
     * @param <T>           type of the injected instance.
     */
    static <T> void justInject(T injectMe, Bean<T> bean, Map<Field, InjectionResolver> resolvers,
            JerseyProxyResolver proxyResolver) {
        if (injectMe == null) {
            throw new IllegalArgumentException();
        }

        for (Map.Entry<Field, InjectionResolver> entry : resolvers.entrySet()) {
            Field field = entry.getKey();
            InjectionResolver resolver = entry.getValue();
            Injectee injectee = InjectionUtils.getFieldInjectee(bean, field);

            Object resolvedValue;
            if (injectee.isProvider()) {
                resolvedValue = (Provider<Object>) () -> resolver.resolve(injectee);
            } else if (proxyResolver.isProxiable(injectee)) {
                resolvedValue = proxyResolver.proxy(injectee, resolver);
            } else {
                resolvedValue = resolver.resolve(injectee);
            }

            try {
                ReflectionUtils.setField(field, injectMe, resolvedValue);
            } catch (MultiException me) {
                throw me;
            } catch (Throwable th) {
                throw new MultiException(th);
            }
        }
    }

    /**
     * Returns the injectee for a field.
     *
     * @param bean bean in which the field is placed.
     * @param field      the field to analyze.
     * @return the list (in order) of parameters to the constructor.
     */
    private static Injectee getFieldInjectee(Bean<?> bean, Field field) {
        Set<Annotation> annotations = Arrays.stream(field.getAnnotations())
                .collect(ImmutableCollectors.toImmutableSet());

        Type adjustedType = ReflectionUtils.resolveField(bean.getBeanClass(), field);

        InjecteeImpl injectee = new InjecteeImpl();
        injectee.setParentClassScope(bean.getScope());

        if (isProvider(adjustedType)) {
            ParameterizedType paramType = (ParameterizedType) adjustedType;
            injectee.setRequiredType(paramType.getActualTypeArguments()[0]);
            injectee.setProvider(true);
        } else {
            injectee.setRequiredType(adjustedType);
        }

        injectee.setParent(field);
        injectee.setRequiredQualifiers(getFieldAdjustedQualifierAnnotations(field, annotations));
        return injectee;
    }

    public static boolean isProvider(Type type) {
        if (type instanceof ParameterizedType) {
            ParameterizedType paramType = (ParameterizedType) type;
            return Provider.class.isAssignableFrom((Class<?>) paramType.getRawType());
        }

        return false;
    }

    private static Set<Annotation> getFieldAdjustedQualifierAnnotations(Field field, Set<Annotation> qualifiers) {
        Named n = field.getAnnotation(Named.class);
        if (n == null || !"".equals(n.value())) {
            return qualifiers;
        }

        HashSet<Annotation> retVal = new HashSet<>();
        for (Annotation qualifier : qualifiers) {
            if (qualifier.annotationType().equals(Named.class)) {
                retVal.add(new NamedImpl(field.getName()));
            } else {
                retVal.add(qualifier);
            }
        }

        return retVal;
    }

    /**
     * Gets the fields from the given class and analyzer. Checks service output.
     *
     * @param clazz             the non-null impl class.
     * @param injectAnnotations all annotations which can be used to inject a value.
     * @param errors            for gathering errors.  @return a non-null set (even in error cases, check the collector).
     */
    static Set<Field> getFields(Class<?> clazz, Set<? extends Class<?>> injectAnnotations, Collector errors) {
        Set<Field> retVal;

        try {
            retVal = getFieldsInternal(clazz, injectAnnotations, errors);
        } catch (MultiException me) {
            errors.addMultiException(me);
            return Collections.emptySet();
        } catch (Throwable th) {
            errors.addThrowable(th);
            return Collections.emptySet();
        }

        return retVal;
    }

    /**
     * Will find all the initialize fields in the class.
     *
     * @param clazz             the class to search for fields
     * @param injectAnnotations all annotations which can be used to inject a value.
     * @param errors            the error collector
     * @return A non-null but possibly empty set of initializer fields
     */
    private static Set<Field> getFieldsInternal(Class<?> clazz, Set<? extends Class<?>> injectAnnotations, Collector errors) {
        Set<Field> retVal = new LinkedHashSet<>();

        for (Field field : ReflectionUtils.getAllFields(clazz)) {
            if (!hasInjectAnnotation(field, injectAnnotations)) {
                // Not an initializer field
                continue;
            }

            if (!isProperField(field)) {
                errors.addThrowable(new IllegalArgumentException("The field " + Pretty.field(field)
                        + " may not be static, final or have an Annotation type"));
                continue;
            }

            retVal.add(field);
        }

        return retVal;
    }

    /**
     * Checks whether an annotated element has any annotation that was used for the injection.
     *
     * @param annotated         the annotated element.
     * @param injectAnnotations all annotations which can be used to inject a value.
     * @return True if element contains at least one inject annotation.
     */
    private static boolean hasInjectAnnotation(AnnotatedElement annotated, Set<? extends Class<?>> injectAnnotations) {
        for (Annotation annotation : annotated.getAnnotations()) {
            if (injectAnnotations.contains(annotation.annotationType())) {
                return true;
            }
        }

        return false;
    }

    private static boolean isProperField(Field field) {
        if (isStatic(field) || isFinal(field)) {
            return false;
        }

        Class<?> type = field.getType();
        return !type.isAnnotation();
    }

    /**
     * Returns true if the underlying member is static.
     *
     * @param member The non-null member to test.
     * @return true if the member is static.
     */
    private static boolean isStatic(Member member) {
        int modifiers = member.getModifiers();
        return ((modifiers & Modifier.STATIC) != 0);
    }

    /**
     * Returns true if the underlying member is abstract.
     *
     * @param member The non-null member to test.
     * @return true if the member is abstract.
     */
    private static boolean isFinal(Member member) {
        int modifiers = member.getModifiers();
        return ((modifiers & Modifier.FINAL) != 0);
    }

    /**
     * Returns all annotations that can be managed using registered and provided {@link InjectionResolver injection resolvers}.
     *
     * @param resolvers all registered resolvers.
     * @return all possible injection annotations.
     */
    @SuppressWarnings("unchecked")
    public static Collection<Class<? extends Annotation>> getInjectAnnotations(Collection<InjectionResolver> resolvers) {
        List<Class<? extends Annotation>> annotations = new ArrayList<>();
        for (InjectionResolver resolver : resolvers) {
            annotations.add(resolver.getAnnotation());
        }
        return annotations;
    }

    /**
     * Assigns {@link InjectionResolver} to every {@link AnnotatedElement} provided as a method parameter. Injection resolver
     * will be used for fetching the proper value during the injection processing.
     *
     * @param annotatedElements all annotated elements from the class which this injector belongs to.
     * @param resolvers         all registered injection resolvers.
     * @param <A>               type of the annotated elements.
     * @return immutable map of all fields along with injection resolvers using that can be injected.
     */
    static <A extends AnnotatedElement> Map<A, InjectionResolver> mapElementToResolver(Set<A> annotatedElements,
            Map<? extends Class<?>, InjectionResolver> resolvers) {

        Map<A, InjectionResolver> mappedElements = new HashMap<>();
        for (A element : annotatedElements) {
            mappedElements.put(element, findResolver(resolvers, element));
        }
        return mappedElements;
    }

    static InjectionResolver findResolver(Map<? extends Class<?>, InjectionResolver> resolvers, AnnotatedElement element) {
        for (Annotation annotation : element.getAnnotations()) {
            InjectionResolver injectionResolver = resolvers.get(annotation.annotationType());
            if (injectionResolver != null) {
                return injectionResolver;
            }
        }

        return null;
    }

    /**
     * Creates a map from resolvers where the annotation that is handled by resolver is a key and resolver itself is value.
     *
     * @param resolvers collection of resolvers.
     * @return map resolver annotation to resolver.
     */
    static Map<? extends Class<?>, InjectionResolver> mapAnnotationToResolver(Collection<InjectionResolver> resolvers) {
        return resolvers.stream().collect(Collectors.toMap(InjectionResolver::getAnnotation, Function.identity()));
    }
}