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