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 */ 045public class AnnotationUtils { 046 047 /** 048 * A style that prints annotations as recommended. 049 */ 050 private static final ToStringStyle TO_STRING_STYLE = new ToStringStyle() { 051 /** Serialization version */ 052 private static final long serialVersionUID = 1L; 053 054 { 055 setDefaultFullDetail(true); 056 setArrayContentDetail(true); 057 setUseClassName(true); 058 setUseShortClassName(true); 059 setUseIdentityHashCode(false); 060 setContentStart("("); 061 setContentEnd(")"); 062 setFieldSeparator(", "); 063 setArrayStart("["); 064 setArrayEnd("]"); 065 } 066 067 /** 068 * {@inheritDoc} 069 */ 070 @Override 071 protected String getShortClassName(final java.lang.Class<?> cls) { 072 Class<? extends Annotation> annotationType = null; 073 for (final Class<?> iface : ClassUtils.getAllInterfaces(cls)) { 074 if (Annotation.class.isAssignableFrom(iface)) { 075 @SuppressWarnings("unchecked") // OK because we just checked the assignability 076 final 077 Class<? extends Annotation> found = (Class<? extends Annotation>) iface; 078 annotationType = found; 079 break; 080 } 081 } 082 return new StringBuilder(annotationType == null ? StringUtils.EMPTY : annotationType.getName()) 083 .insert(0, '@').toString(); 084 } 085 086 /** 087 * {@inheritDoc} 088 */ 089 @Override 090 protected void appendDetail(final StringBuffer buffer, final String fieldName, Object value) { 091 if (value instanceof Annotation) { 092 value = AnnotationUtils.toString((Annotation) value); 093 } 094 super.appendDetail(buffer, fieldName, value); 095 } 096 097 }; 098 099 /** 100 * <p>{@code AnnotationUtils} instances should NOT be constructed in 101 * standard programming. Instead, the class should be used statically.</p> 102 * 103 * <p>This constructor is public to permit tools that require a JavaBean 104 * instance to operate.</p> 105 */ 106 public AnnotationUtils() { 107 } 108 109 //----------------------------------------------------------------------- 110 /** 111 * <p>Checks if two annotations are equal using the criteria for equality 112 * presented in the {@link Annotation#equals(Object)} API docs.</p> 113 * 114 * @param a1 the first Annotation to compare, {@code null} returns 115 * {@code false} unless both are {@code null} 116 * @param a2 the second Annotation to compare, {@code null} returns 117 * {@code false} unless both are {@code null} 118 * @return {@code true} if the two annotations are {@code equal} or both 119 * {@code null} 120 */ 121 public static boolean equals(final Annotation a1, final Annotation a2) { 122 if (a1 == a2) { 123 return true; 124 } 125 if (a1 == null || a2 == null) { 126 return false; 127 } 128 final Class<? extends Annotation> type = a1.annotationType(); 129 final Class<? extends Annotation> type2 = a2.annotationType(); 130 Validate.notNull(type, "Annotation %s with null annotationType()", a1); 131 Validate.notNull(type2, "Annotation %s with null annotationType()", a2); 132 if (!type.equals(type2)) { 133 return false; 134 } 135 try { 136 for (final Method m : type.getDeclaredMethods()) { 137 if (m.getParameterTypes().length == 0 138 && isValidAnnotationMemberType(m.getReturnType())) { 139 final Object v1 = m.invoke(a1); 140 final Object v2 = m.invoke(a2); 141 if (!memberEquals(m.getReturnType(), v1, v2)) { 142 return false; 143 } 144 } 145 } 146 } catch (final IllegalAccessException ex) { 147 return false; 148 } catch (final InvocationTargetException ex) { 149 return false; 150 } 151 return true; 152 } 153 154 /** 155 * <p>Generate a hash code for the given annotation using the algorithm 156 * presented in the {@link Annotation#hashCode()} API docs.</p> 157 * 158 * @param a the Annotation for a hash code calculation is desired, not 159 * {@code null} 160 * @return the calculated hash code 161 * @throws RuntimeException if an {@code Exception} is encountered during 162 * annotation member access 163 * @throws IllegalStateException if an annotation method invocation returns 164 * {@code null} 165 */ 166 public static int hashCode(final Annotation a) { 167 int result = 0; 168 final Class<? extends Annotation> type = a.annotationType(); 169 for (final Method m : type.getDeclaredMethods()) { 170 try { 171 final Object value = m.invoke(a); 172 if (value == null) { 173 throw new IllegalStateException( 174 String.format("Annotation method %s returned null", m)); 175 } 176 result += hashMember(m.getName(), value); 177 } catch (final RuntimeException ex) { 178 throw ex; 179 } catch (final Exception ex) { 180 throw new RuntimeException(ex); 181 } 182 } 183 return result; 184 } 185 186 /** 187 * <p>Generate a string representation of an Annotation, as suggested by 188 * {@link Annotation#toString()}.</p> 189 * 190 * @param a the annotation of which a string representation is desired 191 * @return the standard string representation of an annotation, not 192 * {@code null} 193 */ 194 public static String toString(final Annotation a) { 195 final ToStringBuilder builder = new ToStringBuilder(a, TO_STRING_STYLE); 196 for (final Method m : a.annotationType().getDeclaredMethods()) { 197 if (m.getParameterTypes().length > 0) { 198 continue; //wtf? 199 } 200 try { 201 builder.append(m.getName(), m.invoke(a)); 202 } catch (final RuntimeException ex) { 203 throw ex; 204 } catch (final Exception ex) { 205 throw new RuntimeException(ex); 206 } 207 } 208 return builder.build(); 209 } 210 211 /** 212 * <p>Checks if the specified type is permitted as an annotation member.</p> 213 * 214 * <p>The Java language specification only permits certain types to be used 215 * in annotations. These include {@link String}, {@link Class}, primitive 216 * types, {@link Annotation}, {@link Enum}, and single-dimensional arrays of 217 * these types.</p> 218 * 219 * @param type the type to check, {@code null} 220 * @return {@code true} if the type is a valid type to use in an annotation 221 */ 222 public static boolean isValidAnnotationMemberType(Class<?> type) { 223 if (type == null) { 224 return false; 225 } 226 if (type.isArray()) { 227 type = type.getComponentType(); 228 } 229 return type.isPrimitive() || type.isEnum() || type.isAnnotation() 230 || String.class.equals(type) || Class.class.equals(type); 231 } 232 233 //besides modularity, this has the advantage of autoboxing primitives: 234 /** 235 * Helper method for generating a hash code for a member of an annotation. 236 * 237 * @param name the name of the member 238 * @param value the value of the member 239 * @return a hash code for this member 240 */ 241 private static int hashMember(final String name, final Object value) { 242 final int part1 = name.hashCode() * 127; 243 if (value.getClass().isArray()) { 244 return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value); 245 } 246 if (value instanceof Annotation) { 247 return part1 ^ hashCode((Annotation) value); 248 } 249 return part1 ^ value.hashCode(); 250 } 251 252 /** 253 * Helper method for checking whether two objects of the given type are 254 * equal. This method is used to compare the parameters of two annotation 255 * instances. 256 * 257 * @param type the type of the objects to be compared 258 * @param o1 the first object 259 * @param o2 the second object 260 * @return a flag whether these objects are equal 261 */ 262 private static boolean memberEquals(final Class<?> type, final Object o1, final Object o2) { 263 if (o1 == o2) { 264 return true; 265 } 266 if (o1 == null || o2 == null) { 267 return false; 268 } 269 if (type.isArray()) { 270 return arrayMemberEquals(type.getComponentType(), o1, o2); 271 } 272 if (type.isAnnotation()) { 273 return equals((Annotation) o1, (Annotation) o2); 274 } 275 return o1.equals(o2); 276 } 277 278 /** 279 * Helper method for comparing two objects of an array type. 280 * 281 * @param componentType the component type of the array 282 * @param o1 the first object 283 * @param o2 the second object 284 * @return a flag whether these objects are equal 285 */ 286 private static boolean arrayMemberEquals(final Class<?> componentType, final Object o1, final Object o2) { 287 if (componentType.isAnnotation()) { 288 return annotationArrayMemberEquals((Annotation[]) o1, (Annotation[]) o2); 289 } 290 if (componentType.equals(Byte.TYPE)) { 291 return Arrays.equals((byte[]) o1, (byte[]) o2); 292 } 293 if (componentType.equals(Short.TYPE)) { 294 return Arrays.equals((short[]) o1, (short[]) o2); 295 } 296 if (componentType.equals(Integer.TYPE)) { 297 return Arrays.equals((int[]) o1, (int[]) o2); 298 } 299 if (componentType.equals(Character.TYPE)) { 300 return Arrays.equals((char[]) o1, (char[]) o2); 301 } 302 if (componentType.equals(Long.TYPE)) { 303 return Arrays.equals((long[]) o1, (long[]) o2); 304 } 305 if (componentType.equals(Float.TYPE)) { 306 return Arrays.equals((float[]) o1, (float[]) o2); 307 } 308 if (componentType.equals(Double.TYPE)) { 309 return Arrays.equals((double[]) o1, (double[]) o2); 310 } 311 if (componentType.equals(Boolean.TYPE)) { 312 return Arrays.equals((boolean[]) o1, (boolean[]) o2); 313 } 314 return Arrays.equals((Object[]) o1, (Object[]) o2); 315 } 316 317 /** 318 * Helper method for comparing two arrays of annotations. 319 * 320 * @param a1 the first array 321 * @param a2 the second array 322 * @return a flag whether these arrays are equal 323 */ 324 private static boolean annotationArrayMemberEquals(final Annotation[] a1, final Annotation[] a2) { 325 if (a1.length != a2.length) { 326 return false; 327 } 328 for (int i = 0; i < a1.length; i++) { 329 if (!equals(a1[i], a2[i])) { 330 return false; 331 } 332 } 333 return true; 334 } 335 336 /** 337 * Helper method for generating a hash code for an array. 338 * 339 * @param componentType the component type of the array 340 * @param o the array 341 * @return a hash code for the specified array 342 */ 343 private static int arrayMemberHash(final Class<?> componentType, final Object o) { 344 if (componentType.equals(Byte.TYPE)) { 345 return Arrays.hashCode((byte[]) o); 346 } 347 if (componentType.equals(Short.TYPE)) { 348 return Arrays.hashCode((short[]) o); 349 } 350 if (componentType.equals(Integer.TYPE)) { 351 return Arrays.hashCode((int[]) o); 352 } 353 if (componentType.equals(Character.TYPE)) { 354 return Arrays.hashCode((char[]) o); 355 } 356 if (componentType.equals(Long.TYPE)) { 357 return Arrays.hashCode((long[]) o); 358 } 359 if (componentType.equals(Float.TYPE)) { 360 return Arrays.hashCode((float[]) o); 361 } 362 if (componentType.equals(Double.TYPE)) { 363 return Arrays.hashCode((double[]) o); 364 } 365 if (componentType.equals(Boolean.TYPE)) { 366 return Arrays.hashCode((boolean[]) o); 367 } 368 return Arrays.hashCode((Object[]) o); 369 } 370}