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 }