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 ex) {
147            return false;
148        } catch (final InvocationTargetException ex) {
149            return false;
150        }
151        return true;
152    }
153
154    /**
155     * <p>Generate a hash code for the given annotation using the algorithm
156     * presented in the {@link Annotation#hashCode()} API docs.</p>
157     *
158     * @param a the Annotation for a hash code calculation is desired, not
159     * {@code null}
160     * @return the calculated hash code
161     * @throws RuntimeException if an {@code Exception} is encountered during
162     * annotation member access
163     * @throws IllegalStateException if an annotation method invocation returns
164     * {@code null}
165     */
166    public static int hashCode(final Annotation a) {
167        int result = 0;
168        final Class<? extends Annotation> type = a.annotationType();
169        for (final Method m : type.getDeclaredMethods()) {
170            try {
171                final Object value = m.invoke(a);
172                if (value == null) {
173                    throw new IllegalStateException(
174                            String.format("Annotation method %s returned null", m));
175                }
176                result += hashMember(m.getName(), value);
177            } catch (final RuntimeException ex) {
178                throw ex;
179            } catch (final Exception ex) {
180                throw new RuntimeException(ex);
181            }
182        }
183        return result;
184    }
185
186    /**
187     * <p>Generate a string representation of an Annotation, as suggested by
188     * {@link Annotation#toString()}.</p>
189     *
190     * @param a the annotation of which a string representation is desired
191     * @return the standard string representation of an annotation, not
192     * {@code null}
193     */
194    public static String toString(final Annotation a) {
195        final ToStringBuilder builder = new ToStringBuilder(a, TO_STRING_STYLE);
196        for (final Method m : a.annotationType().getDeclaredMethods()) {
197            if (m.getParameterTypes().length > 0) {
198                continue; //wtf?
199            }
200            try {
201                builder.append(m.getName(), m.invoke(a));
202            } catch (final RuntimeException ex) {
203                throw ex;
204            } catch (final Exception ex) {
205                throw new RuntimeException(ex);
206            }
207        }
208        return builder.build();
209    }
210
211    /**
212     * <p>Checks if the specified type is permitted as an annotation member.</p>
213     *
214     * <p>The Java language specification only permits certain types to be used
215     * in annotations. These include {@link String}, {@link Class}, primitive
216     * types, {@link Annotation}, {@link Enum}, and single-dimensional arrays of
217     * these types.</p>
218     *
219     * @param type the type to check, {@code null}
220     * @return {@code true} if the type is a valid type to use in an annotation
221     */
222    public static boolean isValidAnnotationMemberType(Class<?> type) {
223        if (type == null) {
224            return false;
225        }
226        if (type.isArray()) {
227            type = type.getComponentType();
228        }
229        return type.isPrimitive() || type.isEnum() || type.isAnnotation()
230                || String.class.equals(type) || Class.class.equals(type);
231    }
232
233    //besides modularity, this has the advantage of autoboxing primitives:
234    /**
235     * Helper method for generating a hash code for a member of an annotation.
236     *
237     * @param name the name of the member
238     * @param value the value of the member
239     * @return a hash code for this member
240     */
241    private static int hashMember(final String name, final Object value) {
242        final int part1 = name.hashCode() * 127;
243        if (value.getClass().isArray()) {
244            return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value);
245        }
246        if (value instanceof Annotation) {
247            return part1 ^ hashCode((Annotation) value);
248        }
249        return part1 ^ value.hashCode();
250    }
251
252    /**
253     * Helper method for checking whether two objects of the given type are
254     * equal. This method is used to compare the parameters of two annotation
255     * instances.
256     *
257     * @param type the type of the objects to be compared
258     * @param o1 the first object
259     * @param o2 the second object
260     * @return a flag whether these objects are equal
261     */
262    private static boolean memberEquals(final Class<?> type, final Object o1, final Object o2) {
263        if (o1 == o2) {
264            return true;
265        }
266        if (o1 == null || o2 == null) {
267            return false;
268        }
269        if (type.isArray()) {
270            return arrayMemberEquals(type.getComponentType(), o1, o2);
271        }
272        if (type.isAnnotation()) {
273            return equals((Annotation) o1, (Annotation) o2);
274        }
275        return o1.equals(o2);
276    }
277
278    /**
279     * Helper method for comparing two objects of an array type.
280     *
281     * @param componentType the component type of the array
282     * @param o1 the first object
283     * @param o2 the second object
284     * @return a flag whether these objects are equal
285     */
286    private static boolean arrayMemberEquals(final Class<?> componentType, final Object o1, final Object o2) {
287        if (componentType.isAnnotation()) {
288            return annotationArrayMemberEquals((Annotation[]) o1, (Annotation[]) o2);
289        }
290        if (componentType.equals(Byte.TYPE)) {
291            return Arrays.equals((byte[]) o1, (byte[]) o2);
292        }
293        if (componentType.equals(Short.TYPE)) {
294            return Arrays.equals((short[]) o1, (short[]) o2);
295        }
296        if (componentType.equals(Integer.TYPE)) {
297            return Arrays.equals((int[]) o1, (int[]) o2);
298        }
299        if (componentType.equals(Character.TYPE)) {
300            return Arrays.equals((char[]) o1, (char[]) o2);
301        }
302        if (componentType.equals(Long.TYPE)) {
303            return Arrays.equals((long[]) o1, (long[]) o2);
304        }
305        if (componentType.equals(Float.TYPE)) {
306            return Arrays.equals((float[]) o1, (float[]) o2);
307        }
308        if (componentType.equals(Double.TYPE)) {
309            return Arrays.equals((double[]) o1, (double[]) o2);
310        }
311        if (componentType.equals(Boolean.TYPE)) {
312            return Arrays.equals((boolean[]) o1, (boolean[]) o2);
313        }
314        return Arrays.equals((Object[]) o1, (Object[]) o2);
315    }
316
317    /**
318     * Helper method for comparing two arrays of annotations.
319     *
320     * @param a1 the first array
321     * @param a2 the second array
322     * @return a flag whether these arrays are equal
323     */
324    private static boolean annotationArrayMemberEquals(final Annotation[] a1, final Annotation[] a2) {
325        if (a1.length != a2.length) {
326            return false;
327        }
328        for (int i = 0; i < a1.length; i++) {
329            if (!equals(a1[i], a2[i])) {
330                return false;
331            }
332        }
333        return true;
334    }
335
336    /**
337     * Helper method for generating a hash code for an array.
338     *
339     * @param componentType the component type of the array
340     * @param o the array
341     * @return a hash code for the specified array
342     */
343    private static int arrayMemberHash(final Class<?> componentType, final Object o) {
344        if (componentType.equals(Byte.TYPE)) {
345            return Arrays.hashCode((byte[]) o);
346        }
347        if (componentType.equals(Short.TYPE)) {
348            return Arrays.hashCode((short[]) o);
349        }
350        if (componentType.equals(Integer.TYPE)) {
351            return Arrays.hashCode((int[]) o);
352        }
353        if (componentType.equals(Character.TYPE)) {
354            return Arrays.hashCode((char[]) o);
355        }
356        if (componentType.equals(Long.TYPE)) {
357            return Arrays.hashCode((long[]) o);
358        }
359        if (componentType.equals(Float.TYPE)) {
360            return Arrays.hashCode((float[]) o);
361        }
362        if (componentType.equals(Double.TYPE)) {
363            return Arrays.hashCode((double[]) o);
364        }
365        if (componentType.equals(Boolean.TYPE)) {
366            return Arrays.hashCode((boolean[]) o);
367        }
368        return Arrays.hashCode((Object[]) o);
369    }
370}