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   */
45  public class AnnotationUtils {
46  
47      /**
48       * A style that prints annotations as recommended.
49       */
50      private static final ToStringStyle TO_STRING_STYLE = new ToStringStyle() {
51          /** Serialization version */
52          private static final long serialVersionUID = 1L;
53  
54          {
55              setDefaultFullDetail(true);
56              setArrayContentDetail(true);
57              setUseClassName(true);
58              setUseShortClassName(true);
59              setUseIdentityHashCode(false);
60              setContentStart("(");
61              setContentEnd(")");
62              setFieldSeparator(", ");
63              setArrayStart("[");
64              setArrayEnd("]");
65          }
66  
67          /**
68           * {@inheritDoc}
69           */
70          @Override
71          protected String getShortClassName(final java.lang.Class<?> cls) {
72              Class<? extends Annotation> annotationType = null;
73              for (final Class<?> iface : ClassUtils.getAllInterfaces(cls)) {
74                  if (Annotation.class.isAssignableFrom(iface)) {
75                      @SuppressWarnings("unchecked") // OK because we just checked the assignability
76                      final
77                      Class<? extends Annotation> found = (Class<? extends Annotation>) iface;
78                      annotationType = found;
79                      break;
80                  }
81              }
82              return new StringBuilder(annotationType == null ? "" : annotationType.getName())
83                      .insert(0, '@').toString();
84          }
85  
86          /**
87           * {@inheritDoc}
88           */
89          @Override
90          protected void appendDetail(final StringBuffer buffer, final String fieldName, Object value) {
91              if (value instanceof Annotation) {
92                  value = AnnotationUtils.toString((Annotation) value);
93              }
94              super.appendDetail(buffer, fieldName, value);
95          }
96  
97      };
98  
99      /**
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 }