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.Method;
21  import java.util.Arrays;
22  
23  import org.apache.commons.lang3.builder.ToStringBuilder;
24  import org.apache.commons.lang3.builder.ToStringStyle;
25  import org.apache.commons.lang3.exception.UncheckedException;
26  
27  /**
28   * Helper methods for working with {@link Annotation} instances.
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 void appendDetail(final StringBuffer buffer, final String fieldName, Object value) {
72              if (value instanceof Annotation) {
73                  value = AnnotationUtils.toString((Annotation) value);
74              }
75              super.appendDetail(buffer, fieldName, value);
76          }
77  
78          /**
79           * {@inheritDoc}
80           */
81          @Override
82          protected String getShortClassName(final Class<?> cls) {
83              // formatter:off
84              return ClassUtils.getAllInterfaces(cls).stream().filter(Annotation.class::isAssignableFrom).findFirst()
85                  .map(iface -> "@" + iface.getName())
86                  .orElse(StringUtils.EMPTY);
87              // formatter:on
88          }
89  
90      };
91  
92      /**
93       * Helper method for comparing two arrays of annotations.
94       *
95       * @param a1 the first array
96       * @param a2 the second array
97       * @return a flag whether these arrays are equal
98       */
99      private static boolean annotationArrayMemberEquals(final Annotation[] a1, final Annotation[] a2) {
100         if (a1.length != a2.length) {
101             return false;
102         }
103         for (int i = 0; i < a1.length; i++) {
104             if (!equals(a1[i], a2[i])) {
105                 return false;
106             }
107         }
108         return true;
109     }
110 
111     /**
112      * Helper method for comparing two objects of an array type.
113      *
114      * @param componentType the component type of the array
115      * @param o1 the first object
116      * @param o2 the second object
117      * @return a flag whether these objects are equal
118      */
119     private static boolean arrayMemberEquals(final Class<?> componentType, final Object o1, final Object o2) {
120         if (componentType.isAnnotation()) {
121             return annotationArrayMemberEquals((Annotation[]) o1, (Annotation[]) o2);
122         }
123         if (componentType.equals(Byte.TYPE)) {
124             return Arrays.equals((byte[]) o1, (byte[]) o2);
125         }
126         if (componentType.equals(Short.TYPE)) {
127             return Arrays.equals((short[]) o1, (short[]) o2);
128         }
129         if (componentType.equals(Integer.TYPE)) {
130             return Arrays.equals((int[]) o1, (int[]) o2);
131         }
132         if (componentType.equals(Character.TYPE)) {
133             return Arrays.equals((char[]) o1, (char[]) o2);
134         }
135         if (componentType.equals(Long.TYPE)) {
136             return Arrays.equals((long[]) o1, (long[]) o2);
137         }
138         if (componentType.equals(Float.TYPE)) {
139             return Arrays.equals((float[]) o1, (float[]) o2);
140         }
141         if (componentType.equals(Double.TYPE)) {
142             return Arrays.equals((double[]) o1, (double[]) o2);
143         }
144         if (componentType.equals(Boolean.TYPE)) {
145             return Arrays.equals((boolean[]) o1, (boolean[]) o2);
146         }
147         return Arrays.equals((Object[]) o1, (Object[]) o2);
148     }
149 
150     /**
151      * Helper method for generating a hash code for an array.
152      *
153      * @param componentType the component type of the array
154      * @param o the array
155      * @return a hash code for the specified array
156      */
157     private static int arrayMemberHash(final Class<?> componentType, final Object o) {
158         if (componentType.equals(Byte.TYPE)) {
159             return Arrays.hashCode((byte[]) o);
160         }
161         if (componentType.equals(Short.TYPE)) {
162             return Arrays.hashCode((short[]) o);
163         }
164         if (componentType.equals(Integer.TYPE)) {
165             return Arrays.hashCode((int[]) o);
166         }
167         if (componentType.equals(Character.TYPE)) {
168             return Arrays.hashCode((char[]) o);
169         }
170         if (componentType.equals(Long.TYPE)) {
171             return Arrays.hashCode((long[]) o);
172         }
173         if (componentType.equals(Float.TYPE)) {
174             return Arrays.hashCode((float[]) o);
175         }
176         if (componentType.equals(Double.TYPE)) {
177             return Arrays.hashCode((double[]) o);
178         }
179         if (componentType.equals(Boolean.TYPE)) {
180             return Arrays.hashCode((boolean[]) o);
181         }
182         return Arrays.hashCode((Object[]) o);
183     }
184 
185     /**
186      * Checks if two annotations are equal using the criteria for equality
187      * presented in the {@link Annotation#equals(Object)} API docs.
188      *
189      * @param a1 the first Annotation to compare, {@code null} returns
190      * {@code false} unless both are {@code null}
191      * @param a2 the second Annotation to compare, {@code null} returns
192      * {@code false} unless both are {@code null}
193      * @return {@code true} if the two annotations are {@code equal} or both
194      * {@code null}
195      */
196     public static boolean equals(final Annotation a1, final Annotation a2) {
197         if (a1 == a2) {
198             return true;
199         }
200         if (a1 == null || a2 == null) {
201             return false;
202         }
203         final Class<? extends Annotation> type1 = a1.annotationType();
204         final Class<? extends Annotation> type2 = a2.annotationType();
205         Validate.notNull(type1, "Annotation %s with null annotationType()", a1);
206         Validate.notNull(type2, "Annotation %s with null annotationType()", a2);
207         if (!type1.equals(type2)) {
208             return false;
209         }
210         try {
211             for (final Method m : type1.getDeclaredMethods()) {
212                 if (m.getParameterTypes().length == 0
213                         && isValidAnnotationMemberType(m.getReturnType())) {
214                     final Object v1 = m.invoke(a1);
215                     final Object v2 = m.invoke(a2);
216                     if (!memberEquals(m.getReturnType(), v1, v2)) {
217                         return false;
218                     }
219                 }
220             }
221         } catch (final ReflectiveOperationException ex) {
222             return false;
223         }
224         return true;
225     }
226 
227     /**
228      * Generate a hash code for the given annotation using the algorithm
229      * presented in the {@link Annotation#hashCode()} API docs.
230      *
231      * @param a the Annotation for a hash code calculation is desired, not
232      * {@code null}
233      * @return the calculated hash code
234      * @throws RuntimeException if an {@link Exception} is encountered during
235      * annotation member access
236      * @throws IllegalStateException if an annotation method invocation returns
237      * {@code null}
238      */
239     public static int hashCode(final Annotation a) {
240         int result = 0;
241         final Class<? extends Annotation> type = a.annotationType();
242         for (final Method m : type.getDeclaredMethods()) {
243             try {
244                 final Object value = m.invoke(a);
245                 if (value == null) {
246                     throw new IllegalStateException(String.format("Annotation method %s returned null", m));
247                 }
248                 result += hashMember(m.getName(), value);
249             } catch (final ReflectiveOperationException ex) {
250                 throw new UncheckedException(ex);
251             }
252         }
253         return result;
254     }
255 
256     //besides modularity, this has the advantage of autoboxing primitives:
257     /**
258      * Helper method for generating a hash code for a member of an annotation.
259      *
260      * @param name the name of the member
261      * @param value the value of the member
262      * @return a hash code for this member
263      */
264     private static int hashMember(final String name, final Object value) {
265         final int part1 = name.hashCode() * 127;
266         if (ObjectUtils.isArray(value)) {
267             return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value);
268         }
269         if (value instanceof Annotation) {
270             return part1 ^ hashCode((Annotation) value);
271         }
272         return part1 ^ value.hashCode();
273     }
274 
275     /**
276      * Checks if the specified type is permitted as an annotation member.
277      *
278      * <p>The Java language specification only permits certain types to be used
279      * in annotations. These include {@link String}, {@link Class}, primitive
280      * types, {@link Annotation}, {@link Enum}, and single-dimensional arrays of
281      * these types.</p>
282      *
283      * @param type the type to check, {@code null}
284      * @return {@code true} if the type is a valid type to use in an annotation
285      */
286     public static boolean isValidAnnotationMemberType(Class<?> type) {
287         if (type == null) {
288             return false;
289         }
290         if (type.isArray()) {
291             type = type.getComponentType();
292         }
293         return type.isPrimitive() || type.isEnum() || type.isAnnotation()
294                 || String.class.equals(type) || Class.class.equals(type);
295     }
296 
297     /**
298      * Helper method for checking whether two objects of the given type are
299      * equal. This method is used to compare the parameters of two annotation
300      * instances.
301      *
302      * @param type the type of the objects to be compared
303      * @param o1 the first object
304      * @param o2 the second object
305      * @return a flag whether these objects are equal
306      */
307     private static boolean memberEquals(final Class<?> type, final Object o1, final Object o2) {
308         if (o1 == o2) {
309             return true;
310         }
311         if (o1 == null || o2 == null) {
312             return false;
313         }
314         if (type.isArray()) {
315             return arrayMemberEquals(type.getComponentType(), o1, o2);
316         }
317         if (type.isAnnotation()) {
318             return equals((Annotation) o1, (Annotation) o2);
319         }
320         return o1.equals(o2);
321     }
322 
323     /**
324      * Generate a string representation of an Annotation, as suggested by
325      * {@link Annotation#toString()}.
326      *
327      * @param a the annotation of which a string representation is desired
328      * @return the standard string representation of an annotation, not
329      * {@code null}
330      */
331     public static String toString(final Annotation a) {
332         final ToStringBuilder builder = new ToStringBuilder(a, TO_STRING_STYLE);
333         for (final Method m : a.annotationType().getDeclaredMethods()) {
334             if (m.getParameterTypes().length > 0) {
335                 continue; // wtf?
336             }
337             try {
338                 builder.append(m.getName(), m.invoke(a));
339             } catch (final ReflectiveOperationException ex) {
340                 throw new UncheckedException(ex);
341             }
342         }
343         return builder.build();
344     }
345 
346     /**
347      * {@link AnnotationUtils} instances should NOT be constructed in
348      * standard programming. Instead, the class should be used statically.
349      *
350      * <p>This constructor is public to permit tools that require a JavaBean
351      * instance to operate.</p>
352      */
353     public AnnotationUtils() {
354     }
355 }