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