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