    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.lang3;
    018
    019import java.lang.annotation.Annotation;
    020import java.lang.reflect.Method;
    021import java.util.Arrays;
    022
    023import org.apache.commons.lang3.builder.ToStringBuilder;
    024import org.apache.commons.lang3.builder.ToStringStyle;
    025import org.apache.commons.lang3.exception.UncheckedException;
    026
    027/**
    028 * Helper methods for working with {@link Annotation} instances.
    029 *
    030 * <p>This class contains various utility methods that make working with
    031 * annotations simpler.</p>
    032 *
    033 * <p>{@link Annotation} instances are always proxy objects; unfortunately
    034 * dynamic proxies cannot be depended upon to know how to implement certain
    035 * methods in the same manner as would be done by "natural" {@link Annotation}s.
    036 * The methods presented in this class can be used to avoid that possibility. It
    037 * is of course also possible for dynamic proxies to actually delegate their
    038 * e.g. {@link Annotation#equals(Object)}/{@link Annotation#hashCode()}/
    039 * {@link Annotation#toString()} implementations to {@link AnnotationUtils}.</p>
    040 *
    041 * <p>#ThreadSafe#</p>
    042 *
    043 * @since 3.0
    044 */
    045public class AnnotationUtils {
    046
    047    /**
    048     * A style that prints annotations as recommended.
    049     */
    050    private static final ToStringStyle TO_STRING_STYLE = new ToStringStyle() {
    051        /** Serialization version */
    052        private static final long serialVersionUID = 1L;
    053
    054        {
    055            setDefaultFullDetail(true);
    056            setArrayContentDetail(true);
    057            setUseClassName(true);
    058            setUseShortClassName(true);
    059            setUseIdentityHashCode(false);
    060            setContentStart("(");
    061            setContentEnd(")");
    062            setFieldSeparator(", ");
    063            setArrayStart("[");
    064            setArrayEnd("]");
    065        }
    066
    067        /**
    068         * {@inheritDoc}
    069         */
    070        @Override
    071        protected String getShortClassName(final Class<?> cls) {
    072            // formatter:off
    073            return ClassUtils.getAllInterfaces(cls).stream().filter(Annotation.class::isAssignableFrom).findFirst()
    074                .map(iface -> "@" + iface.getName())
    075                .orElse(StringUtils.EMPTY);
    076            // formatter:on
    077        }
    078
    079        /**
    080         * {@inheritDoc}
    081         */
    082        @Override
    083        protected void appendDetail(final StringBuffer buffer, final String fieldName, Object value) {
    084            if (value instanceof Annotation) {
    085                value = AnnotationUtils.toString((Annotation) value);
    086            }
    087            super.appendDetail(buffer, fieldName, value);
    088        }
    089
    090    };
    091
    092    /**
    093     * {@link AnnotationUtils} instances should NOT be constructed in
    094     * standard programming. Instead, the class should be used statically.
    095     *
    096     * <p>This constructor is public to permit tools that require a JavaBean
    097     * instance to operate.</p>
    098     */
    099    public AnnotationUtils() {
    100    }
    101
    102    /**
    103     * Checks if two annotations are equal using the criteria for equality
    104     * presented in the {@link Annotation#equals(Object)} API docs.
    105     *
    106     * @param a1 the first Annotation to compare, {@code null} returns
    107     * {@code false} unless both are {@code null}
    108     * @param a2 the second Annotation to compare, {@code null} returns
    109     * {@code false} unless both are {@code null}
    110     * @return {@code true} if the two annotations are {@code equal} or both
    111     * {@code null}
    112     */
    113    public static boolean equals(final Annotation a1, final Annotation a2) {
    114        if (a1 == a2) {
    115            return true;
    116        }
    117        if (a1 == null || a2 == null) {
    118            return false;
    119        }
    120        final Class<? extends Annotation> type1 = a1.annotationType();
    121        final Class<? extends Annotation> type2 = a2.annotationType();
    122        Validate.notNull(type1, "Annotation %s with null annotationType()", a1);
    123        Validate.notNull(type2, "Annotation %s with null annotationType()", a2);
    124        if (!type1.equals(type2)) {
    125            return false;
    126        }
    127        try {
    128            for (final Method m : type1.getDeclaredMethods()) {
    129                if (m.getParameterTypes().length == 0
    130                        && isValidAnnotationMemberType(m.getReturnType())) {
    131                    final Object v1 = m.invoke(a1);
    132                    final Object v2 = m.invoke(a2);
    133                    if (!memberEquals(m.getReturnType(), v1, v2)) {
    134                        return false;
    135                    }
    136                }
    137            }
    138        } catch (final ReflectiveOperationException ex) {
    139            return false;
    140        }
    141        return true;
    142    }
    143
    144    /**
    145     * Generate a hash code for the given annotation using the algorithm
    146     * presented in the {@link Annotation#hashCode()} API docs.
    147     *
    148     * @param a the Annotation for a hash code calculation is desired, not
    149     * {@code null}
    150     * @return the calculated hash code
    151     * @throws RuntimeException if an {@link Exception} is encountered during
    152     * annotation member access
    153     * @throws IllegalStateException if an annotation method invocation returns
    154     * {@code null}
    155     */
    156    public static int hashCode(final Annotation a) {
    157        int result = 0;
    158        final Class<? extends Annotation> type = a.annotationType();
    159        for (final Method m : type.getDeclaredMethods()) {
    160            try {
    161                final Object value = m.invoke(a);
    162                if (value == null) {
    163                    throw new IllegalStateException(String.format("Annotation method %s returned null", m));
    164                }
    165                result += hashMember(m.getName(), value);
    166            } catch (final ReflectiveOperationException ex) {
    167                throw new UncheckedException(ex);
    168            }
    169        }
    170        return result;
    171    }
    172
    173    /**
    174     * Generate a string representation of an Annotation, as suggested by
    175     * {@link Annotation#toString()}.
    176     *
    177     * @param a the annotation of which a string representation is desired
    178     * @return the standard string representation of an annotation, not
    179     * {@code null}
    180     */
    181    public static String toString(final Annotation a) {
    182        final ToStringBuilder builder = new ToStringBuilder(a, TO_STRING_STYLE);
    183        for (final Method m : a.annotationType().getDeclaredMethods()) {
    184            if (m.getParameterTypes().length > 0) {
    185                continue; // wtf?
    186            }
    187            try {
    188                builder.append(m.getName(), m.invoke(a));
    189            } catch (final ReflectiveOperationException ex) {
    190                throw new UncheckedException(ex);
    191            }
    192        }
    193        return builder.build();
    194    }
    195
    196    /**
    197     * Checks if the specified type is permitted as an annotation member.
    198     *
    199     * <p>The Java language specification only permits certain types to be used
    200     * in annotations. These include {@link String}, {@link Class}, primitive
    201     * types, {@link Annotation}, {@link Enum}, and single-dimensional arrays of
    202     * these types.</p>
    203     *
    204     * @param type the type to check, {@code null}
    205     * @return {@code true} if the type is a valid type to use in an annotation
    206     */
    207    public static boolean isValidAnnotationMemberType(Class<?> type) {
    208        if (type == null) {
    209            return false;
    210        }
    211        if (type.isArray()) {
    212            type = type.getComponentType();
    213        }
    214        return type.isPrimitive() || type.isEnum() || type.isAnnotation()
    215                || String.class.equals(type) || Class.class.equals(type);
    216    }
    217
    218    //besides modularity, this has the advantage of autoboxing primitives:
    219    /**
    220     * Helper method for generating a hash code for a member of an annotation.
    221     *
    222     * @param name the name of the member
    223     * @param value the value of the member
    224     * @return a hash code for this member
    225     */
    226    private static int hashMember(final String name, final Object value) {
    227        final int part1 = name.hashCode() * 127;
    228        if (ObjectUtils.isArray(value)) {
    229            return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value);
    230        }
    231        if (value instanceof Annotation) {
    232            return part1 ^ hashCode((Annotation) value);
    233        }
    234        return part1 ^ value.hashCode();
    235    }
    236
    237    /**
    238     * Helper method for checking whether two objects of the given type are
    239     * equal. This method is used to compare the parameters of two annotation
    240     * instances.
    241     *
    242     * @param type the type of the objects to be compared
    243     * @param o1 the first object
    244     * @param o2 the second object
    245     * @return a flag whether these objects are equal
    246     */
    247    private static boolean memberEquals(final Class<?> type, final Object o1, final Object o2) {
    248        if (o1 == o2) {
    249            return true;
    250        }
    251        if (o1 == null || o2 == null) {
    252            return false;
    253        }
    254        if (type.isArray()) {
    255            return arrayMemberEquals(type.getComponentType(), o1, o2);
    256        }
    257        if (type.isAnnotation()) {
    258            return equals((Annotation) o1, (Annotation) o2);
    259        }
    260        return o1.equals(o2);
    261    }
    262
    263    /**
    264     * Helper method for comparing two objects of an array type.
    265     *
    266     * @param componentType the component type of the array
    267     * @param o1 the first object
    268     * @param o2 the second object
    269     * @return a flag whether these objects are equal
    270     */
    271    private static boolean arrayMemberEquals(final Class<?> componentType, final Object o1, final Object o2) {
    272        if (componentType.isAnnotation()) {
    273            return annotationArrayMemberEquals((Annotation[]) o1, (Annotation[]) o2);
    274        }
    275        if (componentType.equals(Byte.TYPE)) {
    276            return Arrays.equals((byte[]) o1, (byte[]) o2);
    277        }
    278        if (componentType.equals(Short.TYPE)) {
    279            return Arrays.equals((short[]) o1, (short[]) o2);
    280        }
    281        if (componentType.equals(Integer.TYPE)) {
    282            return Arrays.equals((int[]) o1, (int[]) o2);
    283        }
    284        if (componentType.equals(Character.TYPE)) {
    285            return Arrays.equals((char[]) o1, (char[]) o2);
    286        }
    287        if (componentType.equals(Long.TYPE)) {
    288            return Arrays.equals((long[]) o1, (long[]) o2);
    289        }
    290        if (componentType.equals(Float.TYPE)) {
    291            return Arrays.equals((float[]) o1, (float[]) o2);
    292        }
    293        if (componentType.equals(Double.TYPE)) {
    294            return Arrays.equals((double[]) o1, (double[]) o2);
    295        }
    296        if (componentType.equals(Boolean.TYPE)) {
    297            return Arrays.equals((boolean[]) o1, (boolean[]) o2);
    298        }
    299        return Arrays.equals((Object[]) o1, (Object[]) o2);
    300    }
    301
    302    /**
    303     * Helper method for comparing two arrays of annotations.
    304     *
    305     * @param a1 the first array
    306     * @param a2 the second array
    307     * @return a flag whether these arrays are equal
    308     */
    309    private static boolean annotationArrayMemberEquals(final Annotation[] a1, final Annotation[] a2) {
    310        if (a1.length != a2.length) {
    311            return false;
    312        }
    313        for (int i = 0; i < a1.length; i++) {
    314            if (!equals(a1[i], a2[i])) {
    315                return false;
    316            }
    317        }
    318        return true;
    319    }
    320
    321    /**
    322     * Helper method for generating a hash code for an array.
    323     *
    324     * @param componentType the component type of the array
    325     * @param o the array
    326     * @return a hash code for the specified array
    327     */
    328    private static int arrayMemberHash(final Class<?> componentType, final Object o) {
    329        if (componentType.equals(Byte.TYPE)) {
    330            return Arrays.hashCode((byte[]) o);
    331        }
    332        if (componentType.equals(Short.TYPE)) {
    333            return Arrays.hashCode((short[]) o);
    334        }
    335        if (componentType.equals(Integer.TYPE)) {
    336            return Arrays.hashCode((int[]) o);
    337        }
    338        if (componentType.equals(Character.TYPE)) {
    339            return Arrays.hashCode((char[]) o);
    340        }
    341        if (componentType.equals(Long.TYPE)) {
    342            return Arrays.hashCode((long[]) o);
    343        }
    344        if (componentType.equals(Float.TYPE)) {
    345            return Arrays.hashCode((float[]) o);
    346        }
    347        if (componentType.equals(Double.TYPE)) {
    348            return Arrays.hashCode((double[]) o);
    349        }
    350        if (componentType.equals(Boolean.TYPE)) {
    351            return Arrays.hashCode((boolean[]) o);
    352        }
    353        return Arrays.hashCode((Object[]) o);
    354    }
    355}