    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 void appendDetail(final StringBuffer buffer, final String fieldName, Object value) {
    072            if (value instanceof Annotation) {
    073                value = AnnotationUtils.toString((Annotation) value);
    074            }
    075            super.appendDetail(buffer, fieldName, value);
    076        }
    077
    078        /**
    079         * {@inheritDoc}
    080         */
    081        @Override
    082        protected String getShortClassName(final Class<?> cls) {
    083            // formatter:off
    084            return ClassUtils.getAllInterfaces(cls).stream().filter(Annotation.class::isAssignableFrom).findFirst()
    085                .map(iface -> "@" + iface.getName())
    086                .orElse(StringUtils.EMPTY);
    087            // formatter:on
    088        }
    089
    090    };
    091
    092    /**
    093     * Helper method for comparing two arrays of annotations.
    094     *
    095     * @param a1 the first array
    096     * @param a2 the second array
    097     * @return a flag whether these arrays are equal
    098     */
    099    private static boolean annotationArrayMemberEquals(final Annotation[] a1, final Annotation[] a2) {
    100        if (a1.length != a2.length) {
    101            return false;
    102        }
    103        for (int i = 0; i < a1.length; i++) {
    104            if (!equals(a1[i], a2[i])) {
    105                return false;
    106            }
    107        }
    108        return true;
    109    }
    110
    111    /**
    112     * Helper method for comparing two objects of an array type.
    113     *
    114     * @param componentType the component type of the array
    115     * @param o1 the first object
    116     * @param o2 the second object
    117     * @return a flag whether these objects are equal
    118     */
    119    private static boolean arrayMemberEquals(final Class<?> componentType, final Object o1, final Object o2) {
    120        if (componentType.isAnnotation()) {
    121            return annotationArrayMemberEquals((Annotation[]) o1, (Annotation[]) o2);
    122        }
    123        if (componentType.equals(Byte.TYPE)) {
    124            return Arrays.equals((byte[]) o1, (byte[]) o2);
    125        }
    126        if (componentType.equals(Short.TYPE)) {
    127            return Arrays.equals((short[]) o1, (short[]) o2);
    128        }
    129        if (componentType.equals(Integer.TYPE)) {
    130            return Arrays.equals((int[]) o1, (int[]) o2);
    131        }
    132        if (componentType.equals(Character.TYPE)) {
    133            return Arrays.equals((char[]) o1, (char[]) o2);
    134        }
    135        if (componentType.equals(Long.TYPE)) {
    136            return Arrays.equals((long[]) o1, (long[]) o2);
    137        }
    138        if (componentType.equals(Float.TYPE)) {
    139            return Arrays.equals((float[]) o1, (float[]) o2);
    140        }
    141        if (componentType.equals(Double.TYPE)) {
    142            return Arrays.equals((double[]) o1, (double[]) o2);
    143        }
    144        if (componentType.equals(Boolean.TYPE)) {
    145            return Arrays.equals((boolean[]) o1, (boolean[]) o2);
    146        }
    147        return Arrays.equals((Object[]) o1, (Object[]) o2);
    148    }
    149
    150    /**
    151     * Helper method for generating a hash code for an array.
    152     *
    153     * @param componentType the component type of the array
    154     * @param o the array
    155     * @return a hash code for the specified array
    156     */
    157    private static int arrayMemberHash(final Class<?> componentType, final Object o) {
    158        if (componentType.equals(Byte.TYPE)) {
    159            return Arrays.hashCode((byte[]) o);
    160        }
    161        if (componentType.equals(Short.TYPE)) {
    162            return Arrays.hashCode((short[]) o);
    163        }
    164        if (componentType.equals(Integer.TYPE)) {
    165            return Arrays.hashCode((int[]) o);
    166        }
    167        if (componentType.equals(Character.TYPE)) {
    168            return Arrays.hashCode((char[]) o);
    169        }
    170        if (componentType.equals(Long.TYPE)) {
    171            return Arrays.hashCode((long[]) o);
    172        }
    173        if (componentType.equals(Float.TYPE)) {
    174            return Arrays.hashCode((float[]) o);
    175        }
    176        if (componentType.equals(Double.TYPE)) {
    177            return Arrays.hashCode((double[]) o);
    178        }
    179        if (componentType.equals(Boolean.TYPE)) {
    180            return Arrays.hashCode((boolean[]) o);
    181        }
    182        return Arrays.hashCode((Object[]) o);
    183    }
    184
    185    /**
    186     * Checks if two annotations are equal using the criteria for equality
    187     * presented in the {@link Annotation#equals(Object)} API docs.
    188     *
    189     * @param a1 the first Annotation to compare, {@code null} returns
    190     * {@code false} unless both are {@code null}
    191     * @param a2 the second Annotation to compare, {@code null} returns
    192     * {@code false} unless both are {@code null}
    193     * @return {@code true} if the two annotations are {@code equal} or both
    194     * {@code null}
    195     */
    196    public static boolean equals(final Annotation a1, final Annotation a2) {
    197        if (a1 == a2) {
    198            return true;
    199        }
    200        if (a1 == null || a2 == null) {
    201            return false;
    202        }
    203        final Class<? extends Annotation> type1 = a1.annotationType();
    204        final Class<? extends Annotation> type2 = a2.annotationType();
    205        Validate.notNull(type1, "Annotation %s with null annotationType()", a1);
    206        Validate.notNull(type2, "Annotation %s with null annotationType()", a2);
    207        if (!type1.equals(type2)) {
    208            return false;
    209        }
    210        try {
    211            for (final Method m : type1.getDeclaredMethods()) {
    212                if (m.getParameterTypes().length == 0
    213                        && isValidAnnotationMemberType(m.getReturnType())) {
    214                    final Object v1 = m.invoke(a1);
    215                    final Object v2 = m.invoke(a2);
    216                    if (!memberEquals(m.getReturnType(), v1, v2)) {
    217                        return false;
    218                    }
    219                }
    220            }
    221        } catch (final ReflectiveOperationException ex) {
    222            return false;
    223        }
    224        return true;
    225    }
    226
    227    /**
    228     * Generate a hash code for the given annotation using the algorithm
    229     * presented in the {@link Annotation#hashCode()} API docs.
    230     *
    231     * @param a the Annotation for a hash code calculation is desired, not
    232     * {@code null}
    233     * @return the calculated hash code
    234     * @throws RuntimeException if an {@link Exception} is encountered during
    235     * annotation member access
    236     * @throws IllegalStateException if an annotation method invocation returns
    237     * {@code null}
    238     */
    239    public static int hashCode(final Annotation a) {
    240        int result = 0;
    241        final Class<? extends Annotation> type = a.annotationType();
    242        for (final Method m : type.getDeclaredMethods()) {
    243            try {
    244                final Object value = m.invoke(a);
    245                if (value == null) {
    246                    throw new IllegalStateException(String.format("Annotation method %s returned null", m));
    247                }
    248                result += hashMember(m.getName(), value);
    249            } catch (final ReflectiveOperationException ex) {
    250                throw new UncheckedException(ex);
    251            }
    252        }
    253        return result;
    254    }
    255
    256    //besides modularity, this has the advantage of autoboxing primitives:
    257    /**
    258     * Helper method for generating a hash code for a member of an annotation.
    259     *
    260     * @param name the name of the member
    261     * @param value the value of the member
    262     * @return a hash code for this member
    263     */
    264    private static int hashMember(final String name, final Object value) {
    265        final int part1 = name.hashCode() * 127;
    266        if (ObjectUtils.isArray(value)) {
    267            return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value);
    268        }
    269        if (value instanceof Annotation) {
    270            return part1 ^ hashCode((Annotation) value);
    271        }
    272        return part1 ^ value.hashCode();
    273    }
    274
    275    /**
    276     * Checks if the specified type is permitted as an annotation member.
    277     *
    278     * <p>The Java language specification only permits certain types to be used
    279     * in annotations. These include {@link String}, {@link Class}, primitive
    280     * types, {@link Annotation}, {@link Enum}, and single-dimensional arrays of
    281     * these types.</p>
    282     *
    283     * @param type the type to check, {@code null}
    284     * @return {@code true} if the type is a valid type to use in an annotation
    285     */
    286    public static boolean isValidAnnotationMemberType(Class<?> type) {
    287        if (type == null) {
    288            return false;
    289        }
    290        if (type.isArray()) {
    291            type = type.getComponentType();
    292        }
    293        return type.isPrimitive() || type.isEnum() || type.isAnnotation()
    294                || String.class.equals(type) || Class.class.equals(type);
    295    }
    296
    297    /**
    298     * Helper method for checking whether two objects of the given type are
    299     * equal. This method is used to compare the parameters of two annotation
    300     * instances.
    301     *
    302     * @param type the type of the objects to be compared
    303     * @param o1 the first object
    304     * @param o2 the second object
    305     * @return a flag whether these objects are equal
    306     */
    307    private static boolean memberEquals(final Class<?> type, final Object o1, final Object o2) {
    308        if (o1 == o2) {
    309            return true;
    310        }
    311        if (o1 == null || o2 == null) {
    312            return false;
    313        }
    314        if (type.isArray()) {
    315            return arrayMemberEquals(type.getComponentType(), o1, o2);
    316        }
    317        if (type.isAnnotation()) {
    318            return equals((Annotation) o1, (Annotation) o2);
    319        }
    320        return o1.equals(o2);
    321    }
    322
    323    /**
    324     * Generate a string representation of an Annotation, as suggested by
    325     * {@link Annotation#toString()}.
    326     *
    327     * @param a the annotation of which a string representation is desired
    328     * @return the standard string representation of an annotation, not
    329     * {@code null}
    330     */
    331    public static String toString(final Annotation a) {
    332        final ToStringBuilder builder = new ToStringBuilder(a, TO_STRING_STYLE);
    333        for (final Method m : a.annotationType().getDeclaredMethods()) {
    334            if (m.getParameterTypes().length > 0) {
    335                continue; // wtf?
    336            }
    337            try {
    338                builder.append(m.getName(), m.invoke(a));
    339            } catch (final ReflectiveOperationException ex) {
    340                throw new UncheckedException(ex);
    341            }
    342        }
    343        return builder.build();
    344    }
    345
    346    /**
    347     * {@link AnnotationUtils} instances should NOT be constructed in
    348     * standard programming. Instead, the class should be used statically.
    349     *
    350     * <p>This constructor is public to permit tools that require a JavaBean
    351     * instance to operate.</p>
    352     */
    353    public AnnotationUtils() {
    354    }
    355}