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