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