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