001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.lang3;
018
019 import java.lang.annotation.Annotation;
020 import java.lang.reflect.InvocationTargetException;
021 import java.lang.reflect.Method;
022 import java.util.Arrays;
023
024 import org.apache.commons.lang3.builder.ToStringBuilder;
025 import org.apache.commons.lang3.builder.ToStringStyle;
026
027 /**
028 * <p>Helper methods for working with {@link Annotation} instances.</p>
029 *
030 * <p>This class contains various utility methods that make working with
031 * annotations simpler.</p>
032 *
033 * <p>{@link Annotation} instances are always proxy objects; unfortunately
034 * dynamic proxies cannot be depended upon to know how to implement certain
035 * methods in the same manner as would be done by "natural" {@link Annotation}s.
036 * The methods presented in this class can be used to avoid that possibility. It
037 * is of course also possible for dynamic proxies to actually delegate their
038 * e.g. {@link Annotation#equals(Object)}/{@link Annotation#hashCode()}/
039 * {@link Annotation#toString()} implementations to {@link AnnotationUtils}.</p>
040 *
041 * <p>#ThreadSafe#</p>
042 *
043 * @since 3.0
044 * @version $Id: AnnotationUtils.java 1083850 2011-03-21 15:59:10Z mbenson $
045 */
046 public class AnnotationUtils {
047
048 /**
049 * A style that prints annotations as recommended.
050 */
051 private static final ToStringStyle TO_STRING_STYLE = new ToStringStyle() {
052 /** Serialization version */
053 private static final long serialVersionUID = 1L;
054
055 {
056 setDefaultFullDetail(true);
057 setArrayContentDetail(true);
058 setUseClassName(true);
059 setUseShortClassName(true);
060 setUseIdentityHashCode(false);
061 setContentStart("(");
062 setContentEnd(")");
063 setFieldSeparator(", ");
064 setArrayStart("[");
065 setArrayEnd("]");
066 }
067
068 /**
069 * {@inheritDoc}
070 */
071 @Override
072 protected String getShortClassName(java.lang.Class<?> cls) {
073 Class<? extends Annotation> annotationType = null;
074 for (Class<?> iface : ClassUtils.getAllInterfaces(cls)) {
075 if (Annotation.class.isAssignableFrom(iface)) {
076 @SuppressWarnings("unchecked")
077 //because we just checked the assignability
078 Class<? extends Annotation> found = (Class<? extends Annotation>) iface;
079 annotationType = found;
080 break;
081 }
082 }
083 return new StringBuilder(annotationType == null ? "" : annotationType.getName())
084 .insert(0, '@').toString();
085 }
086
087 /**
088 * {@inheritDoc}
089 */
090 @Override
091 protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
092 if (value instanceof Annotation) {
093 value = AnnotationUtils.toString((Annotation) value);
094 }
095 super.appendDetail(buffer, fieldName, value);
096 }
097
098 };
099
100 /**
101 * <p>{@code AnnotationUtils} instances should NOT be constructed in
102 * standard programming. Instead, the class should be used statically.</p>
103 *
104 * <p>This constructor is public to permit tools that require a JavaBean
105 * instance to operate.</p>
106 */
107 public AnnotationUtils() {
108 }
109
110 //-----------------------------------------------------------------------
111 /**
112 * <p>Checks if two annotations are equal using the criteria for equality
113 * presented in the {@link Annotation#equals(Object)} API docs.</p>
114 *
115 * @param a1 the first Annotation to compare, {@code null} returns
116 * {@code false} unless both are {@code null}
117 * @param a2 the second Annotation to compare, {@code null} returns
118 * {@code false} unless both are {@code null}
119 * @return {@code true} if the two annotations are {@code equal} or both
120 * {@code null}
121 */
122 public static boolean equals(Annotation a1, Annotation a2) {
123 if (a1 == a2) {
124 return true;
125 }
126 if (a1 == null || a2 == null) {
127 return false;
128 }
129 Class<? extends Annotation> type = a1.annotationType();
130 Class<? extends Annotation> type2 = a2.annotationType();
131 Validate.notNull(type, "Annotation %s with null annotationType()", a1);
132 Validate.notNull(type2, "Annotation %s with null annotationType()", a2);
133 if (!type.equals(type2)) {
134 return false;
135 }
136 try {
137 for (Method m : type.getDeclaredMethods()) {
138 if (m.getParameterTypes().length == 0
139 && isValidAnnotationMemberType(m.getReturnType())) {
140 Object v1 = m.invoke(a1);
141 Object v2 = m.invoke(a2);
142 if (!memberEquals(m.getReturnType(), v1, v2)) {
143 return false;
144 }
145 }
146 }
147 } catch (IllegalAccessException ex) {
148 return false;
149 } catch (InvocationTargetException ex) {
150 return false;
151 }
152 return true;
153 }
154
155 /**
156 * <p>Generate a hash code for the given annotation using the algorithm
157 * presented in the {@link Annotation#hashCode()} API docs.</p>
158 *
159 * @param a the Annotation for a hash code calculation is desired, not
160 * {@code null}
161 * @return the calculated hash code
162 * @throws RuntimeException if an {@code Exception} is encountered during
163 * annotation member access
164 * @throws IllegalStateException if an annotation method invocation returns
165 * {@code null}
166 */
167 public static int hashCode(Annotation a) {
168 int result = 0;
169 Class<? extends Annotation> type = a.annotationType();
170 for (Method m : type.getDeclaredMethods()) {
171 try {
172 Object value = m.invoke(a);
173 if (value == null) {
174 throw new IllegalStateException(
175 String.format("Annotation method %s returned null", m));
176 }
177 result += hashMember(m.getName(), value);
178 } catch (RuntimeException ex) {
179 throw ex;
180 } catch (Exception ex) {
181 throw new RuntimeException(ex);
182 }
183 }
184 return result;
185 }
186
187 /**
188 * <p>Generate a string representation of an Annotation, as suggested by
189 * {@link Annotation#toString()}.</p>
190 *
191 * @param a the annotation of which a string representation is desired
192 * @return the standard string representation of an annotation, not
193 * {@code null}
194 */
195 public static String toString(final Annotation a) {
196 ToStringBuilder builder = new ToStringBuilder(a, TO_STRING_STYLE);
197 for (Method m : a.annotationType().getDeclaredMethods()) {
198 if (m.getParameterTypes().length > 0) {
199 continue; //wtf?
200 }
201 try {
202 builder.append(m.getName(), m.invoke(a));
203 } catch (RuntimeException ex) {
204 throw ex;
205 } catch (Exception ex) {
206 throw new RuntimeException(ex);
207 }
208 }
209 return builder.build();
210 }
211
212 /**
213 * <p>Checks if the specified type is permitted as an annotation member.</p>
214 *
215 * <p>The Java language specification only permits certain types to be used
216 * in annotations. These include {@link String}, {@link Class}, primitive
217 * types, {@link Annotation}, {@link Enum}, and single-dimensional arrays of
218 * these types.</p>
219 *
220 * @param type the type to check, {@code null}
221 * @return {@code true} if the type is a valid type to use in an annotation
222 */
223 public static boolean isValidAnnotationMemberType(Class<?> type) {
224 if (type == null) {
225 return false;
226 }
227 if (type.isArray()) {
228 type = type.getComponentType();
229 }
230 return type.isPrimitive() || type.isEnum() || type.isAnnotation()
231 || String.class.equals(type) || Class.class.equals(type);
232 }
233
234 //besides modularity, this has the advantage of autoboxing primitives:
235 /**
236 * Helper method for generating a hash code for a member of an annotation.
237 *
238 * @param name the name of the member
239 * @param value the value of the member
240 * @return a hash code for this member
241 */
242 private static int hashMember(String name, Object value) {
243 int part1 = name.hashCode() * 127;
244 if (value.getClass().isArray()) {
245 return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value);
246 }
247 if (value instanceof Annotation) {
248 return part1 ^ hashCode((Annotation) value);
249 }
250 return part1 ^ value.hashCode();
251 }
252
253 /**
254 * Helper method for checking whether two objects of the given type are
255 * equal. This method is used to compare the parameters of two annotation
256 * instances.
257 *
258 * @param type the type of the objects to be compared
259 * @param o1 the first object
260 * @param o2 the second object
261 * @return a flag whether these objects are equal
262 */
263 private static boolean memberEquals(Class<?> type, Object o1, Object o2) {
264 if (o1 == o2) {
265 return true;
266 }
267 if (o1 == null || o2 == null) {
268 return false;
269 }
270 if (type.isArray()) {
271 return arrayMemberEquals(type.getComponentType(), o1, o2);
272 }
273 if (type.isAnnotation()) {
274 return equals((Annotation) o1, (Annotation) o2);
275 }
276 return o1.equals(o2);
277 }
278
279 /**
280 * Helper method for comparing two objects of an array type.
281 *
282 * @param componentType the component type of the array
283 * @param o1 the first object
284 * @param o2 the second object
285 * @return a flag whether these objects are equal
286 */
287 private static boolean arrayMemberEquals(Class<?> componentType, Object o1, Object o2) {
288 if (componentType.isAnnotation()) {
289 return annotationArrayMemberEquals((Annotation[]) o1, (Annotation[]) o2);
290 }
291 if (componentType.equals(Byte.TYPE)) {
292 return Arrays.equals((byte[]) o1, (byte[]) o2);
293 }
294 if (componentType.equals(Short.TYPE)) {
295 return Arrays.equals((short[]) o1, (short[]) o2);
296 }
297 if (componentType.equals(Integer.TYPE)) {
298 return Arrays.equals((int[]) o1, (int[]) o2);
299 }
300 if (componentType.equals(Character.TYPE)) {
301 return Arrays.equals((char[]) o1, (char[]) o2);
302 }
303 if (componentType.equals(Long.TYPE)) {
304 return Arrays.equals((long[]) o1, (long[]) o2);
305 }
306 if (componentType.equals(Float.TYPE)) {
307 return Arrays.equals((float[]) o1, (float[]) o2);
308 }
309 if (componentType.equals(Double.TYPE)) {
310 return Arrays.equals((double[]) o1, (double[]) o2);
311 }
312 if (componentType.equals(Boolean.TYPE)) {
313 return Arrays.equals((boolean[]) o1, (boolean[]) o2);
314 }
315 return Arrays.equals((Object[]) o1, (Object[]) o2);
316 }
317
318 /**
319 * Helper method for comparing two arrays of annotations.
320 *
321 * @param a1 the first array
322 * @param a2 the second array
323 * @return a flag whether these arrays are equal
324 */
325 private static boolean annotationArrayMemberEquals(Annotation[] a1, Annotation[] a2) {
326 if (a1.length != a2.length) {
327 return false;
328 }
329 for (int i = 0; i < a1.length; i++) {
330 if (!equals(a1[i], a2[i])) {
331 return false;
332 }
333 }
334 return true;
335 }
336
337 /**
338 * Helper method for generating a hash code for an array.
339 *
340 * @param componentType the component type of the array
341 * @param o the array
342 * @return a hash code for the specified array
343 */
344 private static int arrayMemberHash(Class<?> componentType, Object o) {
345 if (componentType.equals(Byte.TYPE)) {
346 return Arrays.hashCode((byte[]) o);
347 }
348 if (componentType.equals(Short.TYPE)) {
349 return Arrays.hashCode((short[]) o);
350 }
351 if (componentType.equals(Integer.TYPE)) {
352 return Arrays.hashCode((int[]) o);
353 }
354 if (componentType.equals(Character.TYPE)) {
355 return Arrays.hashCode((char[]) o);
356 }
357 if (componentType.equals(Long.TYPE)) {
358 return Arrays.hashCode((long[]) o);
359 }
360 if (componentType.equals(Float.TYPE)) {
361 return Arrays.hashCode((float[]) o);
362 }
363 if (componentType.equals(Double.TYPE)) {
364 return Arrays.hashCode((double[]) o);
365 }
366 if (componentType.equals(Boolean.TYPE)) {
367 return Arrays.hashCode((boolean[]) o);
368 }
369 return Arrays.hashCode((Object[]) o);
370 }
371 }