jdk/src/share/classes/sun/reflect/annotation/AnnotationSupport.java
author alundblad
Tue, 22 Oct 2013 12:35:27 +0200
changeset 21316 ca0a7cd228c9
parent 16037 1cf21ce98a25
child 21358 d41ff832d4f6
permissions -rw-r--r--
8004912: Repeating annotations - getAnnotationsByType(Class<T>) is not working as expected for few inheritance scenarios 8019420: Repeatable non-inheritable annotation types are mishandled by Core Reflection Reviewed-by: jfranck

/*
 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.reflect.annotation;

import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;

public final class AnnotationSupport {

    /**
     * Finds and returns all annotations in {@code annotations} matching
     * the given {@code annoClass}.
     *
     * Apart from annotations directly present in {@code annotations} this
     * method searches for annotations inside containers i.e. indirectly
     * present annotations.
     *
     * The order of the elements in the array returned depends on the iteration
     * order of the provided map. Specifically, the directly present annotations
     * come before the indirectly present annotations if and only if the
     * directly present annotations come before the indirectly present
     * annotations in the map.
     *
     * @param annotations the {@code Map} in which to search for annotations
     * @param annoClass the type of annotation to search for
     * @param includeNonInheritedContainees if false, the annoClass must be
     *        inheritable for the containers to be searched
     *
     * @return an array of instances of {@code annoClass} or an empty
     *         array if none were found
     */
    private static <A extends Annotation> A[] getDirectlyAndIndirectlyPresent(
            Map<Class<? extends Annotation>, Annotation> annotations,
            Class<A> annoClass,
            boolean includeNonInheritedContainees) {

        List<A> result = new ArrayList<A>();

        @SuppressWarnings("unchecked")
        A direct = (A) annotations.get(annoClass);
        if (direct != null)
            result.add(direct);

        if (includeNonInheritedContainees ||
                AnnotationType.getInstance(annoClass).isInherited()) {
            A[] indirect = getIndirectlyPresent(annotations, annoClass);

            if (indirect != null) {

                boolean indirectFirst = direct == null ||
                                        containerBeforeContainee(annotations, annoClass);

                result.addAll((indirectFirst ? 0 : 1), Arrays.asList(indirect));
            }
        }

        @SuppressWarnings("unchecked")
        A[] arr = (A[]) Array.newInstance(annoClass, result.size());
        return result.toArray(arr);
    }


    /**
     * Equivalent to calling {@code getDirectlyAndIndirectlyPresentAnnotations(
     * annotations, annoClass, true)}.
     */
    public static <A extends Annotation> A[] getDirectlyAndIndirectlyPresent(
            Map<Class<? extends Annotation>, Annotation> annotations,
            Class<A> annoClass) {

        return getDirectlyAndIndirectlyPresent(annotations, annoClass, true);
    }


    /**
     * Finds and returns all annotations matching the given {@code annoClass}
     * indirectly present in {@code annotations}.
     *
     * @param annotations annotations to search indexed by their types
     * @param annoClass the type of annotation to search for
     *
     * @return an array of instances of {@code annoClass} or an empty array if no
     *         indirectly present annotations were found
     */
    private static <A extends Annotation> A[] getIndirectlyPresent(
            Map<Class<? extends Annotation>, Annotation> annotations,
            Class<A> annoClass) {

        Repeatable repeatable = annoClass.getDeclaredAnnotation(Repeatable.class);
        if (repeatable == null)
            return null;  // Not repeatable -> no indirectly present annotations

        Class<? extends Annotation> containerClass = repeatable.value();

        Annotation container = annotations.get(containerClass);
        if (container == null)
            return null;

        // Unpack container
        A[] valueArray = getValueArray(container);
        checkTypes(valueArray, container, annoClass);

        return valueArray;
    }


    /**
     * Figures out if conatiner class comes before containee class among the
     * keys of the given map.
     *
     * @return true if container class is found before containee class when
     *         iterating over annotations.keySet().
     */
    private static <A extends Annotation> boolean containerBeforeContainee(
            Map<Class<? extends Annotation>, Annotation> annotations,
            Class<A> annoClass) {

        Class<? extends Annotation> containerClass =
                annoClass.getDeclaredAnnotation(Repeatable.class).value();

        for (Class<? extends Annotation> c : annotations.keySet()) {
            if (c == containerClass) return true;
            if (c == annoClass) return false;
        }

        // Neither containee nor container present
        return false;
    }


    /**
     * Finds and returns all associated annotations matching the given class.
     *
     * The order of the elements in the array returned depends on the iteration
     * order of the provided maps. Specifically, the directly present annotations
     * come before the indirectly present annotations if and only if the
     * directly present annotations come before the indirectly present
     * annotations in the relevant map.
     *
     * @param declaredAnnotations the declared annotations indexed by their types
     * @param allAnnotations declared and inherited annotations indexed by their types
     * @param annoClass the type of annotation to search for
     *
     * @return an array of instances of {@code annoClass} or an empty array if none were found.
     */
    public static <A extends Annotation> A[] getAssociatedAnnotations(
            Map<Class<? extends Annotation>, Annotation> declaredAnnotations,
            Map<Class<? extends Annotation>, Annotation> allAnnotations,
            Class<A> annoClass) {

        // Search declared
        A[] result = getDirectlyAndIndirectlyPresent(declaredAnnotations, annoClass);

        // Search inherited
        if (result.length == 0)
            result = getDirectlyAndIndirectlyPresent(allAnnotations, annoClass, false);

        return result;
    }


    /* Reflectively invoke the values-method of the given annotation
     * (container), cast it to an array of annotations and return the result.
     */
    private static <A extends Annotation> A[] getValueArray(Annotation container) {
        try {
            // According to JLS the container must have an array-valued value
            // method. Get the AnnotationType, get the "value" method and invoke
            // it to get the content.

            Class<? extends Annotation> containerClass = container.annotationType();
            AnnotationType annoType = AnnotationType.getInstance(containerClass);
            if (annoType == null)
                throw invalidContainerException(container, null);

            Method m = annoType.members().get("value");
            if (m == null)
                throw invalidContainerException(container, null);

            m.setAccessible(true);

            // This will erase to (Annotation[]) but we do a runtime cast on the
            // return-value in the method that call this method.
            @SuppressWarnings("unchecked")
            A[] values = (A[]) m.invoke(container);

            return values;

        } catch (IllegalAccessException    | // couldn't loosen security
                 IllegalArgumentException  | // parameters doesn't match
                 InvocationTargetException | // the value method threw an exception
                 ClassCastException e) {

            throw invalidContainerException(container, e);

        }
    }


    private static AnnotationFormatError invalidContainerException(Annotation anno,
                                                                   Throwable cause) {
        return new AnnotationFormatError(
                anno + " is an invalid container for repeating annotations",
                cause);
    }


    /* Sanity check type of all the annotation instances of type {@code annoClass}
     * from {@code container}.
     */
    private static <A extends Annotation> void checkTypes(A[] annotations,
                                                          Annotation container,
                                                          Class<A> annoClass) {
        for (A a : annotations) {
            if (!annoClass.isInstance(a)) {
                throw new AnnotationFormatError(
                        String.format("%s is an invalid container for " +
                                      "repeating annotations of type: %s",
                                      container, annoClass));
            }
        }
    }
}