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