View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3;
18  
19  import java.lang.annotation.Annotation;
20  import java.lang.reflect.InvocationTargetException;
21  import java.lang.reflect.Method;
22  import java.util.Arrays;
23  
24  import org.apache.commons.lang3.builder.ToStringBuilder;
25  import org.apache.commons.lang3.builder.ToStringStyle;
26  
27  /**
28   * <p>Helper methods for working with {@link Annotation} instances.</p>
29   *
30   * <p>This class contains various utility methods that make working with
31   * annotations simpler.</p>
32   *
33   * <p>{@link Annotation} instances are always proxy objects; unfortunately
34   * dynamic proxies cannot be depended upon to know how to implement certain
35   * methods in the same manner as would be done by "natural" {@link Annotation}s.
36   * The methods presented in this class can be used to avoid that possibility. It
37   * is of course also possible for dynamic proxies to actually delegate their
38   * e.g. {@link Annotation#equals(Object)}/{@link Annotation#hashCode()}/
39   * {@link Annotation#toString()} implementations to {@link AnnotationUtils}.</p>
40   *
41   * <p>#ThreadSafe#</p>
42   *
43   * @since 3.0
44   * @version $Id: AnnotationUtils.java 1436770 2013-01-22 07:09:45Z ggregory $
45   */
46  public class AnnotationUtils {
47  
48      /**
49       * A style that prints annotations as recommended.
50       */
51      private static final ToStringStyle TO_STRING_STYLE = new ToStringStyle() {
52          /** Serialization version */
53          private static final long serialVersionUID = 1L;
54  
55          {
56              setDefaultFullDetail(true);
57              setArrayContentDetail(true);
58              setUseClassName(true);
59              setUseShortClassName(true);
60              setUseIdentityHashCode(false);
61              setContentStart("(");
62              setContentEnd(")");
63              setFieldSeparator(", ");
64              setArrayStart("[");
65              setArrayEnd("]");
66          }
67  
68          /**
69           * {@inheritDoc}
70           */
71          @Override
72          protected String getShortClassName(final java.lang.Class<?> cls) {
73              Class<? extends Annotation> annotationType = null;
74              for (final Class<?> iface : ClassUtils.getAllInterfaces(cls)) {
75                  if (Annotation.class.isAssignableFrom(iface)) {
76                      @SuppressWarnings("unchecked") // OK because we just checked the assignability
77                      final
78                      Class<? extends Annotation> found = (Class<? extends Annotation>) iface;
79                      annotationType = found;
80                      break;
81                  }
82              }
83              return new StringBuilder(annotationType == null ? "" : annotationType.getName())
84                      .insert(0, '@').toString();
85          }
86  
87          /**
88           * {@inheritDoc}
89           */
90          @Override
91          protected void appendDetail(final StringBuffer buffer, final String fieldName, Object value) {
92              if (value instanceof Annotation) {
93                  value = AnnotationUtils.toString((Annotation) value);
94              }
95              super.appendDetail(buffer, fieldName, value);
96          }
97  
98      };
99  
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 }