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