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 * @version $Id: AnnotationUtils.java 1436770 2013-01-22 07:09:45Z ggregory $
045 */
046public 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(final java.lang.Class<?> cls) {
073            Class<? extends Annotation> annotationType = null;
074            for (final Class<?> iface : ClassUtils.getAllInterfaces(cls)) {
075                if (Annotation.class.isAssignableFrom(iface)) {
076                    @SuppressWarnings("unchecked") // OK because we just checked the assignability
077                    final
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(final StringBuffer buffer, final 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(final Annotation a1, final Annotation a2) {
123        if (a1 == a2) {
124            return true;
125        }
126        if (a1 == null || a2 == null) {
127            return false;
128        }
129        final Class<? extends Annotation> type = a1.annotationType();
130        final 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 (final Method m : type.getDeclaredMethods()) {
138                if (m.getParameterTypes().length == 0
139                        && isValidAnnotationMemberType(m.getReturnType())) {
140                    final Object v1 = m.invoke(a1);
141                    final Object v2 = m.invoke(a2);
142                    if (!memberEquals(m.getReturnType(), v1, v2)) {
143                        return false;
144                    }
145                }
146            }
147        } catch (final IllegalAccessException ex) {
148            return false;
149        } catch (final 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(final Annotation a) {
168        int result = 0;
169        final Class<? extends Annotation> type = a.annotationType();
170        for (final Method m : type.getDeclaredMethods()) {
171            try {
172                final 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 (final RuntimeException ex) {
179                throw ex;
180            } catch (final 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        final ToStringBuilder builder = new ToStringBuilder(a, TO_STRING_STYLE);
197        for (final 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 (final RuntimeException ex) {
204                throw ex;
205            } catch (final 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(final String name, final Object value) {
243        final 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(final Class<?> type, final Object o1, final 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(final Class<?> componentType, final Object o1, final 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(final Annotation[] a1, final 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(final Class<?> componentType, final 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}