From 4c0f8dba503c3a1adcc1ef9f17f65d2aad38fab1 Mon Sep 17 00:00:00 2001 From: Matthew Jason Benson Date: Tue, 21 Sep 2010 00:27:40 +0000 Subject: [PATCH] add hashCode and toString methods to AnnotationUtils, plus tests git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@999169 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/commons/lang3/AnnotationUtils.java | 118 +++++++++++++++++- .../commons/lang3/AnnotationUtilsTest.java | 19 +++ 2 files changed, 131 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/apache/commons/lang3/AnnotationUtils.java b/src/main/java/org/apache/commons/lang3/AnnotationUtils.java index 12e95354f..45ac61d8c 100644 --- a/src/main/java/org/apache/commons/lang3/AnnotationUtils.java +++ b/src/main/java/org/apache/commons/lang3/AnnotationUtils.java @@ -17,8 +17,10 @@ package org.apache.commons.lang3; import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.Iterator; /** * Helper methods for working with {@link Annotation}s. @@ -38,12 +40,10 @@ public AnnotationUtils() { } /** - * Learn whether two annotations are equivalent as defined by - * {@link Annotation#equals(Object)}. This method is useful because - * dynamically created {@link Annotation} instances are always proxy - * objects, which, though dependent upon implementation, very often cannot - * be depended upon to behave "normally" in terms of {@link #equals(Object)} - * implementation. + * Learn whether two annotations are equivalent; dynamically created + * {@link Annotation} instances are always proxy objects which cannot be + * depended upon to know how to implement {@link Annotation#equals(Object)} + * per spec. * @param a1 the first Annotation to compare * @param a2 the second Annotation to compare */ @@ -78,6 +78,83 @@ && isValidAnnotationMemberType(m.getReturnType())) { return true; } + /** + * Generate a hashcode for the given annotation; dynamically created + * {@link Annotation} instances are always proxy objects which cannot be + * depended upon to know how to implement {@link Annotation#hashCode()} per + * spec. + * + * @param a the Annotation for a hashcode calculation is desired + * @return the calculated hashcode + * @throws InvocationTargetException + * @throws IllegalAccessException + * @throws IllegalArgumentException + */ + public static int hashCode(Annotation a) throws IllegalArgumentException, + IllegalAccessException, InvocationTargetException { + int result = 0; + Class type = a.annotationType(); + for (Method m : type.getDeclaredMethods()) { + result += hashMember(m.getName(), m.invoke(a)); + } + return result; + } + + /** + * Generate a string representation of an Annotation, as suggested by + * {@link Annotation#toString()}. + * @param a the annotation of which a string representation is desired + * @return String + */ + public static String toString(final Annotation a) { + return new StringBuilder(a.annotationType().getName()).insert(0, '@').append('(') + .append(StringUtils.join(new Iterable() { + + public Iterator iterator() { + final Method[] methods = a.annotationType().getDeclaredMethods(); + return new Iterator() { + int pos = 0; + + public boolean hasNext() { + return pos < methods.length; + } + + public String next() { + Method m = methods[pos++]; + try { + return new StringBuilder(m.getName()).append('=') + .append(m.invoke(a)).toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + }; + } + + }, ", ")).append(')').toString(); + } + + //besides modularity, this has the advantage of autoboxing primitives: + private static int hashMember(String name, Object value) throws IllegalArgumentException, + IllegalAccessException, InvocationTargetException { + int part1 = name.hashCode() * 127; + if (value == null) { + return part1; + } + if (value.getClass().isArray()) { + return part1 ^ arrayMemberHash(value.getClass().getComponentType(), value); + } + if (value instanceof Annotation) { + return part1 ^ hashCode((Annotation) value); + } + return part1 ^ value.hashCode(); + } + /** * Learn whether the specified type is permitted as an annotation member. * These include {@link String}, {@link Class}, primitive types, @@ -154,4 +231,33 @@ private static boolean annotationArrayMemberEquals(Annotation[] a1, Annotation[] } return true; } + + private static int arrayMemberHash(Class componentType, Object o) { + if (componentType.equals(Byte.TYPE)) { + return Arrays.hashCode((byte[]) o); + } + if (componentType.equals(Short.TYPE)) { + return Arrays.hashCode((short[]) o); + } + if (componentType.equals(Integer.TYPE)) { + return Arrays.hashCode((int[]) o); + } + if (componentType.equals(Character.TYPE)) { + return Arrays.hashCode((char[]) o); + } + if (componentType.equals(Long.TYPE)) { + return Arrays.hashCode((long[]) o); + } + if (componentType.equals(Float.TYPE)) { + return Arrays.hashCode((float[]) o); + } + if (componentType.equals(Double.TYPE)) { + return Arrays.hashCode((double[]) o); + } + if (componentType.equals(Boolean.TYPE)) { + return Arrays.hashCode((boolean[]) o); + } + return Arrays.hashCode((Object[]) o); + } + } diff --git a/src/test/java/org/apache/commons/lang3/AnnotationUtilsTest.java b/src/test/java/org/apache/commons/lang3/AnnotationUtilsTest.java index 64968f86f..c261968b1 100644 --- a/src/test/java/org/apache/commons/lang3/AnnotationUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/AnnotationUtilsTest.java @@ -470,4 +470,23 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl assertTrue(AnnotationUtils.equals(generated, generated2)); assertTrue(AnnotationUtils.equals(generated2, generated)); } + + @Test(timeout = 666) + public void testHashCode() throws Exception { + final Test testAnno = getClass().getDeclaredMethod("testHashCode") + .getAnnotation(Test.class); + assertEquals(testAnno.hashCode(), AnnotationUtils.hashCode(testAnno)); + } + + @Test(timeout = 666) + public void testToString() throws Exception { + final Test testAnno = getClass().getDeclaredMethod("testToString") + .getAnnotation(Test.class); + String toString = AnnotationUtils.toString(testAnno); + assertTrue(toString.startsWith("@org.junit.Test(")); + assertTrue(toString.endsWith(")")); + assertTrue(toString.contains("expected=class org.junit.Test$None")); + assertTrue(toString.contains("timeout=666")); + assertTrue(toString.contains(", ")); + } }