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 * http://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 /** Serialization version */ 52 private static final long serialVersionUID = 1L; 53 54 { 55 setDefaultFullDetail(true); 56 setArrayContentDetail(true); 57 setUseClassName(true); 58 setUseShortClassName(true); 59 setUseIdentityHashCode(false); 60 setContentStart("("); 61 setContentEnd(")"); 62 setFieldSeparator(", "); 63 setArrayStart("["); 64 setArrayEnd("]"); 65 } 66 67 /** 68 * {@inheritDoc} 69 */ 70 @Override 71 protected String getShortClassName(final Class<?> cls) { 72 // formatter:off 73 return ClassUtils.getAllInterfaces(cls).stream().filter(Annotation.class::isAssignableFrom).findFirst() 74 .map(iface -> "@" + iface.getName()) 75 .orElse(StringUtils.EMPTY); 76 // formatter:on 77 } 78 79 /** 80 * {@inheritDoc} 81 */ 82 @Override 83 protected void appendDetail(final StringBuffer buffer, final String fieldName, Object value) { 84 if (value instanceof Annotation) { 85 value = AnnotationUtils.toString((Annotation) value); 86 } 87 super.appendDetail(buffer, fieldName, value); 88 } 89 90 }; 91 92 /** 93 * {@link AnnotationUtils} instances should NOT be constructed in 94 * standard programming. Instead, the class should be used statically. 95 * 96 * <p>This constructor is public to permit tools that require a JavaBean 97 * instance to operate.</p> 98 */ AnnotationUtils()99 public AnnotationUtils() { 100 } 101 102 /** 103 * Checks if two annotations are equal using the criteria for equality 104 * presented in the {@link Annotation#equals(Object)} API docs. 105 * 106 * @param a1 the first Annotation to compare, {@code null} returns 107 * {@code false} unless both are {@code null} 108 * @param a2 the second Annotation to compare, {@code null} returns 109 * {@code false} unless both are {@code null} 110 * @return {@code true} if the two annotations are {@code equal} or both 111 * {@code null} 112 */ equals(final Annotation a1, final Annotation a2)113 public static boolean equals(final Annotation a1, final Annotation a2) { 114 if (a1 == a2) { 115 return true; 116 } 117 if (a1 == null || a2 == null) { 118 return false; 119 } 120 final Class<? extends Annotation> type1 = a1.annotationType(); 121 final Class<? extends Annotation> type2 = a2.annotationType(); 122 Validate.notNull(type1, "Annotation %s with null annotationType()", a1); 123 Validate.notNull(type2, "Annotation %s with null annotationType()", a2); 124 if (!type1.equals(type2)) { 125 return false; 126 } 127 try { 128 for (final Method m : type1.getDeclaredMethods()) { 129 if (m.getParameterTypes().length == 0 130 && isValidAnnotationMemberType(m.getReturnType())) { 131 final Object v1 = m.invoke(a1); 132 final Object v2 = m.invoke(a2); 133 if (!memberEquals(m.getReturnType(), v1, v2)) { 134 return false; 135 } 136 } 137 } 138 } catch (final ReflectiveOperationException ex) { 139 return false; 140 } 141 return true; 142 } 143 144 /** 145 * Generate a hash code for the given annotation using the algorithm 146 * presented in the {@link Annotation#hashCode()} API docs. 147 * 148 * @param a the Annotation for a hash code calculation is desired, not 149 * {@code null} 150 * @return the calculated hash code 151 * @throws RuntimeException if an {@link Exception} is encountered during 152 * annotation member access 153 * @throws IllegalStateException if an annotation method invocation returns 154 * {@code null} 155 */ hashCode(final Annotation a)156 public static int hashCode(final Annotation a) { 157 int result = 0; 158 final Class<? extends Annotation> type = a.annotationType(); 159 for (final Method m : type.getDeclaredMethods()) { 160 try { 161 final Object value = m.invoke(a); 162 if (value == null) { 163 throw new IllegalStateException(String.format("Annotation method %s returned null", m)); 164 } 165 result += hashMember(m.getName(), value); 166 } catch (final ReflectiveOperationException ex) { 167 throw new UncheckedException(ex); 168 } 169 } 170 return result; 171 } 172 173 /** 174 * Generate a string representation of an Annotation, as suggested by 175 * {@link Annotation#toString()}. 176 * 177 * @param a the annotation of which a string representation is desired 178 * @return the standard string representation of an annotation, not 179 * {@code null} 180 */ toString(final Annotation a)181 public static String toString(final Annotation a) { 182 final ToStringBuilder builder = new ToStringBuilder(a, TO_STRING_STYLE); 183 for (final Method m : a.annotationType().getDeclaredMethods()) { 184 if (m.getParameterTypes().length > 0) { 185 continue; // wtf? 186 } 187 try { 188 builder.append(m.getName(), m.invoke(a)); 189 } catch (final ReflectiveOperationException ex) { 190 throw new UncheckedException(ex); 191 } 192 } 193 return builder.build(); 194 } 195 196 /** 197 * Checks if the specified type is permitted as an annotation member. 198 * 199 * <p>The Java language specification only permits certain types to be used 200 * in annotations. These include {@link String}, {@link Class}, primitive 201 * types, {@link Annotation}, {@link Enum}, and single-dimensional arrays of 202 * these types.</p> 203 * 204 * @param type the type to check, {@code null} 205 * @return {@code true} if the type is a valid type to use in an annotation 206 */ isValidAnnotationMemberType(Class<?> type)207 public static boolean isValidAnnotationMemberType(Class<?> type) { 208 if (type == null) { 209 return false; 210 } 211 if (type.isArray()) { 212 type = type.getComponentType(); 213 } 214 return type.isPrimitive() || type.isEnum() || type.isAnnotation() 215 || String.class.equals(type) || Class.class.equals(type); 216 } 217 218 //besides modularity, this has the advantage of autoboxing primitives: 219 /** 220 * Helper method for generating a hash code for a member of an annotation. 221 * 222 * @param name the name of the member 223 * @param value the value of the member 224 * @return a hash code for this member 225 */ hashMember(final String name, final Object value)226 private static int hashMember(final String name, final Object value) { 227 final int part1 = name.hashCode() * 127; 228 if (ObjectUtils.isArray(value)) { 229 return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value); 230 } 231 if (value instanceof Annotation) { 232 return part1 ^ hashCode((Annotation) value); 233 } 234 return part1 ^ value.hashCode(); 235 } 236 237 /** 238 * Helper method for checking whether two objects of the given type are 239 * equal. This method is used to compare the parameters of two annotation 240 * instances. 241 * 242 * @param type the type of the objects to be compared 243 * @param o1 the first object 244 * @param o2 the second object 245 * @return a flag whether these objects are equal 246 */ memberEquals(final Class<?> type, final Object o1, final Object o2)247 private static boolean memberEquals(final Class<?> type, final Object o1, final Object o2) { 248 if (o1 == o2) { 249 return true; 250 } 251 if (o1 == null || o2 == null) { 252 return false; 253 } 254 if (type.isArray()) { 255 return arrayMemberEquals(type.getComponentType(), o1, o2); 256 } 257 if (type.isAnnotation()) { 258 return equals((Annotation) o1, (Annotation) o2); 259 } 260 return o1.equals(o2); 261 } 262 263 /** 264 * Helper method for comparing two objects of an array type. 265 * 266 * @param componentType the component type of the array 267 * @param o1 the first object 268 * @param o2 the second object 269 * @return a flag whether these objects are equal 270 */ arrayMemberEquals(final Class<?> componentType, final Object o1, final Object o2)271 private static boolean arrayMemberEquals(final Class<?> componentType, final Object o1, final Object o2) { 272 if (componentType.isAnnotation()) { 273 return annotationArrayMemberEquals((Annotation[]) o1, (Annotation[]) o2); 274 } 275 if (componentType.equals(Byte.TYPE)) { 276 return Arrays.equals((byte[]) o1, (byte[]) o2); 277 } 278 if (componentType.equals(Short.TYPE)) { 279 return Arrays.equals((short[]) o1, (short[]) o2); 280 } 281 if (componentType.equals(Integer.TYPE)) { 282 return Arrays.equals((int[]) o1, (int[]) o2); 283 } 284 if (componentType.equals(Character.TYPE)) { 285 return Arrays.equals((char[]) o1, (char[]) o2); 286 } 287 if (componentType.equals(Long.TYPE)) { 288 return Arrays.equals((long[]) o1, (long[]) o2); 289 } 290 if (componentType.equals(Float.TYPE)) { 291 return Arrays.equals((float[]) o1, (float[]) o2); 292 } 293 if (componentType.equals(Double.TYPE)) { 294 return Arrays.equals((double[]) o1, (double[]) o2); 295 } 296 if (componentType.equals(Boolean.TYPE)) { 297 return Arrays.equals((boolean[]) o1, (boolean[]) o2); 298 } 299 return Arrays.equals((Object[]) o1, (Object[]) o2); 300 } 301 302 /** 303 * Helper method for comparing two arrays of annotations. 304 * 305 * @param a1 the first array 306 * @param a2 the second array 307 * @return a flag whether these arrays are equal 308 */ annotationArrayMemberEquals(final Annotation[] a1, final Annotation[] a2)309 private static boolean annotationArrayMemberEquals(final Annotation[] a1, final Annotation[] a2) { 310 if (a1.length != a2.length) { 311 return false; 312 } 313 for (int i = 0; i < a1.length; i++) { 314 if (!equals(a1[i], a2[i])) { 315 return false; 316 } 317 } 318 return true; 319 } 320 321 /** 322 * Helper method for generating a hash code for an array. 323 * 324 * @param componentType the component type of the array 325 * @param o the array 326 * @return a hash code for the specified array 327 */ arrayMemberHash(final Class<?> componentType, final Object o)328 private static int arrayMemberHash(final Class<?> componentType, final Object o) { 329 if (componentType.equals(Byte.TYPE)) { 330 return Arrays.hashCode((byte[]) o); 331 } 332 if (componentType.equals(Short.TYPE)) { 333 return Arrays.hashCode((short[]) o); 334 } 335 if (componentType.equals(Integer.TYPE)) { 336 return Arrays.hashCode((int[]) o); 337 } 338 if (componentType.equals(Character.TYPE)) { 339 return Arrays.hashCode((char[]) o); 340 } 341 if (componentType.equals(Long.TYPE)) { 342 return Arrays.hashCode((long[]) o); 343 } 344 if (componentType.equals(Float.TYPE)) { 345 return Arrays.hashCode((float[]) o); 346 } 347 if (componentType.equals(Double.TYPE)) { 348 return Arrays.hashCode((double[]) o); 349 } 350 if (componentType.equals(Boolean.TYPE)) { 351 return Arrays.hashCode((boolean[]) o); 352 } 353 return Arrays.hashCode((Object[]) o); 354 } 355 } 356