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 */ 017package org.apache.commons.lang3; 018 019import java.lang.annotation.Annotation; 020import java.lang.reflect.InvocationTargetException; 021import java.lang.reflect.Method; 022import java.util.Arrays; 023 024import org.apache.commons.lang3.builder.ToStringBuilder; 025import 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 1436770 2013-01-22 07:09:45Z ggregory $ 045 */ 046public 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(final java.lang.Class<?> cls) { 073 Class<? extends Annotation> annotationType = null; 074 for (final Class<?> iface : ClassUtils.getAllInterfaces(cls)) { 075 if (Annotation.class.isAssignableFrom(iface)) { 076 @SuppressWarnings("unchecked") // OK because we just checked the assignability 077 final 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(final StringBuffer buffer, final 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(final Annotation a1, final Annotation a2) { 123 if (a1 == a2) { 124 return true; 125 } 126 if (a1 == null || a2 == null) { 127 return false; 128 } 129 final Class<? extends Annotation> type = a1.annotationType(); 130 final 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 (final Method m : type.getDeclaredMethods()) { 138 if (m.getParameterTypes().length == 0 139 && isValidAnnotationMemberType(m.getReturnType())) { 140 final Object v1 = m.invoke(a1); 141 final Object v2 = m.invoke(a2); 142 if (!memberEquals(m.getReturnType(), v1, v2)) { 143 return false; 144 } 145 } 146 } 147 } catch (final IllegalAccessException ex) { 148 return false; 149 } catch (final 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(final Annotation a) { 168 int result = 0; 169 final Class<? extends Annotation> type = a.annotationType(); 170 for (final Method m : type.getDeclaredMethods()) { 171 try { 172 final 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 (final RuntimeException ex) { 179 throw ex; 180 } catch (final 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 final ToStringBuilder builder = new ToStringBuilder(a, TO_STRING_STYLE); 197 for (final 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 (final RuntimeException ex) { 204 throw ex; 205 } catch (final 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(final String name, final Object value) { 243 final 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(final Class<?> type, final Object o1, final 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(final Class<?> componentType, final Object o1, final 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(final Annotation[] a1, final 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(final Class<?> componentType, final 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}