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 | InvocationTargetException ex) { 147 return false; 148 } 149 return true; 150 } 151 152 /** 153 * <p>Generate a hash code for the given annotation using the algorithm 154 * presented in the {@link Annotation#hashCode()} API docs.</p> 155 * 156 * @param a the Annotation for a hash code calculation is desired, not 157 * {@code null} 158 * @return the calculated hash code 159 * @throws RuntimeException if an {@code Exception} is encountered during 160 * annotation member access 161 * @throws IllegalStateException if an annotation method invocation returns 162 * {@code null} 163 */ 164 public static int hashCode(final Annotation a) { 165 int result = 0; 166 final Class<? extends Annotation> type = a.annotationType(); 167 for (final Method m : type.getDeclaredMethods()) { 168 try { 169 final Object value = m.invoke(a); 170 if (value == null) { 171 throw new IllegalStateException( 172 String.format("Annotation method %s returned null", m)); 173 } 174 result += hashMember(m.getName(), value); 175 } catch (final RuntimeException ex) { 176 throw ex; 177 } catch (final Exception ex) { 178 throw new RuntimeException(ex); 179 } 180 } 181 return result; 182 } 183 184 /** 185 * <p>Generate a string representation of an Annotation, as suggested by 186 * {@link Annotation#toString()}.</p> 187 * 188 * @param a the annotation of which a string representation is desired 189 * @return the standard string representation of an annotation, not 190 * {@code null} 191 */ 192 public static String toString(final Annotation a) { 193 final ToStringBuilder builder = new ToStringBuilder(a, TO_STRING_STYLE); 194 for (final Method m : a.annotationType().getDeclaredMethods()) { 195 if (m.getParameterTypes().length > 0) { 196 continue; //wtf? 197 } 198 try { 199 builder.append(m.getName(), m.invoke(a)); 200 } catch (final RuntimeException ex) { 201 throw ex; 202 } catch (final Exception ex) { 203 throw new RuntimeException(ex); 204 } 205 } 206 return builder.build(); 207 } 208 209 /** 210 * <p>Checks if the specified type is permitted as an annotation member.</p> 211 * 212 * <p>The Java language specification only permits certain types to be used 213 * in annotations. These include {@link String}, {@link Class}, primitive 214 * types, {@link Annotation}, {@link Enum}, and single-dimensional arrays of 215 * these types.</p> 216 * 217 * @param type the type to check, {@code null} 218 * @return {@code true} if the type is a valid type to use in an annotation 219 */ 220 public static boolean isValidAnnotationMemberType(Class<?> type) { 221 if (type == null) { 222 return false; 223 } 224 if (type.isArray()) { 225 type = type.getComponentType(); 226 } 227 return type.isPrimitive() || type.isEnum() || type.isAnnotation() 228 || String.class.equals(type) || Class.class.equals(type); 229 } 230 231 //besides modularity, this has the advantage of autoboxing primitives: 232 /** 233 * Helper method for generating a hash code for a member of an annotation. 234 * 235 * @param name the name of the member 236 * @param value the value of the member 237 * @return a hash code for this member 238 */ 239 private static int hashMember(final String name, final Object value) { 240 final int part1 = name.hashCode() * 127; 241 if (value.getClass().isArray()) { 242 return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value); 243 } 244 if (value instanceof Annotation) { 245 return part1 ^ hashCode((Annotation) value); 246 } 247 return part1 ^ value.hashCode(); 248 } 249 250 /** 251 * Helper method for checking whether two objects of the given type are 252 * equal. This method is used to compare the parameters of two annotation 253 * instances. 254 * 255 * @param type the type of the objects to be compared 256 * @param o1 the first object 257 * @param o2 the second object 258 * @return a flag whether these objects are equal 259 */ 260 private static boolean memberEquals(final Class<?> type, final Object o1, final Object o2) { 261 if (o1 == o2) { 262 return true; 263 } 264 if (o1 == null || o2 == null) { 265 return false; 266 } 267 if (type.isArray()) { 268 return arrayMemberEquals(type.getComponentType(), o1, o2); 269 } 270 if (type.isAnnotation()) { 271 return equals((Annotation) o1, (Annotation) o2); 272 } 273 return o1.equals(o2); 274 } 275 276 /** 277 * Helper method for comparing two objects of an array type. 278 * 279 * @param componentType the component type of the array 280 * @param o1 the first object 281 * @param o2 the second object 282 * @return a flag whether these objects are equal 283 */ 284 private static boolean arrayMemberEquals(final Class<?> componentType, final Object o1, final Object o2) { 285 if (componentType.isAnnotation()) { 286 return annotationArrayMemberEquals((Annotation[]) o1, (Annotation[]) o2); 287 } 288 if (componentType.equals(Byte.TYPE)) { 289 return Arrays.equals((byte[]) o1, (byte[]) o2); 290 } 291 if (componentType.equals(Short.TYPE)) { 292 return Arrays.equals((short[]) o1, (short[]) o2); 293 } 294 if (componentType.equals(Integer.TYPE)) { 295 return Arrays.equals((int[]) o1, (int[]) o2); 296 } 297 if (componentType.equals(Character.TYPE)) { 298 return Arrays.equals((char[]) o1, (char[]) o2); 299 } 300 if (componentType.equals(Long.TYPE)) { 301 return Arrays.equals((long[]) o1, (long[]) o2); 302 } 303 if (componentType.equals(Float.TYPE)) { 304 return Arrays.equals((float[]) o1, (float[]) o2); 305 } 306 if (componentType.equals(Double.TYPE)) { 307 return Arrays.equals((double[]) o1, (double[]) o2); 308 } 309 if (componentType.equals(Boolean.TYPE)) { 310 return Arrays.equals((boolean[]) o1, (boolean[]) o2); 311 } 312 return Arrays.equals((Object[]) o1, (Object[]) o2); 313 } 314 315 /** 316 * Helper method for comparing two arrays of annotations. 317 * 318 * @param a1 the first array 319 * @param a2 the second array 320 * @return a flag whether these arrays are equal 321 */ 322 private static boolean annotationArrayMemberEquals(final Annotation[] a1, final Annotation[] a2) { 323 if (a1.length != a2.length) { 324 return false; 325 } 326 for (int i = 0; i < a1.length; i++) { 327 if (!equals(a1[i], a2[i])) { 328 return false; 329 } 330 } 331 return true; 332 } 333 334 /** 335 * Helper method for generating a hash code for an array. 336 * 337 * @param componentType the component type of the array 338 * @param o the array 339 * @return a hash code for the specified array 340 */ 341 private static int arrayMemberHash(final Class<?> componentType, final Object o) { 342 if (componentType.equals(Byte.TYPE)) { 343 return Arrays.hashCode((byte[]) o); 344 } 345 if (componentType.equals(Short.TYPE)) { 346 return Arrays.hashCode((short[]) o); 347 } 348 if (componentType.equals(Integer.TYPE)) { 349 return Arrays.hashCode((int[]) o); 350 } 351 if (componentType.equals(Character.TYPE)) { 352 return Arrays.hashCode((char[]) o); 353 } 354 if (componentType.equals(Long.TYPE)) { 355 return Arrays.hashCode((long[]) o); 356 } 357 if (componentType.equals(Float.TYPE)) { 358 return Arrays.hashCode((float[]) o); 359 } 360 if (componentType.equals(Double.TYPE)) { 361 return Arrays.hashCode((double[]) o); 362 } 363 if (componentType.equals(Boolean.TYPE)) { 364 return Arrays.hashCode((boolean[]) o); 365 } 366 return Arrays.hashCode((Object[]) o); 367 } 368}