From def7663ec465ee293d70efd14dbded1fa3332ee5 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 23 May 2015 23:45:27 +0200 Subject: [PATCH] Implement hashCode() for synthesized annotations Issue: SPR-13066 --- ...ynthesizedAnnotationInvocationHandler.java | 76 +++++++++++++++++++ .../core/annotation/AnnotationUtilsTests.java | 38 ++++++++++ 2 files changed, 114 insertions(+) diff --git a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java index 5f9f27dd20..f8ff81328e 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java @@ -20,6 +20,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.Iterator; import java.util.Map; @@ -70,6 +71,9 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler { if (isEqualsMethod(method)) { return equals(proxy, args[0]); } + if (isHashCodeMethod(method)) { + return hashCode(proxy); + } if (isToStringMethod(method)) { return toString(proxy); } @@ -137,6 +141,12 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler { return value; } + /** + * See {@link Annotation#equals(Object)} for a definition of the required algorithm. + * + * @param proxy the synthesized annotation + * @param other the other object to compare against + */ private boolean equals(Object proxy, Object other) { if (this == other) { return true; @@ -156,6 +166,72 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler { return true; } + /** + * See {@link Annotation#hashCode()} for a definition of the required algorithm. + * + * @param proxy the synthesized annotation + */ + private int hashCode(Object proxy) { + int result = 0; + + for (Method attributeMethod : getAttributeMethods(this.annotationType)) { + Object value = invokeMethod(attributeMethod, proxy); + int hashCode; + if (value.getClass().isArray()) { + hashCode = hashCodeForArray(value); + } + else { + hashCode = value.hashCode(); + } + result += (127 * attributeMethod.getName().hashCode()) ^ hashCode; + } + + return result; + } + + /** + * WARNING: we can NOT use any of the {@code nullSafeHashCode()} methods + * in Spring's {@link ObjectUtils} because those hash code generation + * algorithms do not comply with the requirements specified in + * {@link Annotation#hashCode()}. + * + * @param array the array to compute the hash code for + */ + private int hashCodeForArray(Object array) { + if (array instanceof boolean[]) { + return Arrays.hashCode((boolean[]) array); + } + if (array instanceof byte[]) { + return Arrays.hashCode((byte[]) array); + } + if (array instanceof char[]) { + return Arrays.hashCode((char[]) array); + } + if (array instanceof double[]) { + return Arrays.hashCode((double[]) array); + } + if (array instanceof float[]) { + return Arrays.hashCode((float[]) array); + } + if (array instanceof int[]) { + return Arrays.hashCode((int[]) array); + } + if (array instanceof long[]) { + return Arrays.hashCode((long[]) array); + } + if (array instanceof short[]) { + return Arrays.hashCode((short[]) array); + } + + // else + return Arrays.hashCode((Object[]) array); + } + + /** + * See {@link Annotation#toString()} for guidelines on the recommended format. + * + * @param proxy the synthesized annotation + */ private String toString(Object proxy) { StringBuilder sb = new StringBuilder("@").append(annotationType.getName()).append("("); diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java index 556b3bee03..a549f7c76e 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java @@ -694,6 +694,44 @@ public class AnnotationUtilsTests { assertThat(webMappingWithAliases, is(not(synthesizedWebMapping1))); } + @Test + public void hashCodeForSynthesizedAnnotations() throws Exception { + Method methodWithPath = WebController.class.getMethod("handleMappedWithPathAttribute"); + WebMapping webMappingWithAliases = methodWithPath.getAnnotation(WebMapping.class); + assertNotNull(webMappingWithAliases); + + Method methodWithPathAndValue = WebController.class.getMethod("handleMappedWithSamePathAndValueAttributes"); + WebMapping webMappingWithPathAndValue = methodWithPathAndValue.getAnnotation(WebMapping.class); + assertNotNull(webMappingWithPathAndValue); + + WebMapping synthesizedWebMapping1 = synthesizeAnnotation(webMappingWithAliases); + assertNotNull(synthesizedWebMapping1); + WebMapping synthesizedWebMapping2 = synthesizeAnnotation(webMappingWithAliases); + assertNotNull(synthesizedWebMapping2); + + // Equality amongst standard annotations + assertThat(webMappingWithAliases.hashCode(), is(webMappingWithAliases.hashCode())); + assertThat(webMappingWithPathAndValue.hashCode(), is(webMappingWithPathAndValue.hashCode())); + + // Inequality amongst standard annotations + assertThat(webMappingWithAliases.hashCode(), is(not(webMappingWithPathAndValue.hashCode()))); + assertThat(webMappingWithPathAndValue.hashCode(), is(not(webMappingWithAliases.hashCode()))); + + // Equality amongst synthesized annotations + assertThat(synthesizedWebMapping1.hashCode(), is(synthesizedWebMapping1.hashCode())); + assertThat(synthesizedWebMapping2.hashCode(), is(synthesizedWebMapping2.hashCode())); + assertThat(synthesizedWebMapping1.hashCode(), is(synthesizedWebMapping2.hashCode())); + assertThat(synthesizedWebMapping2.hashCode(), is(synthesizedWebMapping1.hashCode())); + + // Equality between standard and synthesized annotations + assertThat(synthesizedWebMapping1.hashCode(), is(webMappingWithPathAndValue.hashCode())); + assertThat(webMappingWithPathAndValue.hashCode(), is(synthesizedWebMapping1.hashCode())); + + // Inequality between standard and synthesized annotations + assertThat(synthesizedWebMapping1.hashCode(), is(not(webMappingWithAliases.hashCode()))); + assertThat(webMappingWithAliases.hashCode(), is(not(synthesizedWebMapping1.hashCode()))); + } + /** * Fully reflection-based test that verifies support for * {@linkplain AnnotationUtils#synthesizeAnnotation synthesizing annotations}