Implement hashCode() for synthesized annotations
Issue: SPR-13066
This commit is contained in:
@@ -20,6 +20,7 @@ import java.lang.annotation.Annotation;
|
|||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
import java.lang.reflect.InvocationHandler;
|
import java.lang.reflect.InvocationHandler;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -70,6 +71,9 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
|||||||
if (isEqualsMethod(method)) {
|
if (isEqualsMethod(method)) {
|
||||||
return equals(proxy, args[0]);
|
return equals(proxy, args[0]);
|
||||||
}
|
}
|
||||||
|
if (isHashCodeMethod(method)) {
|
||||||
|
return hashCode(proxy);
|
||||||
|
}
|
||||||
if (isToStringMethod(method)) {
|
if (isToStringMethod(method)) {
|
||||||
return toString(proxy);
|
return toString(proxy);
|
||||||
}
|
}
|
||||||
@@ -137,6 +141,12 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
|||||||
return value;
|
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) {
|
private boolean equals(Object proxy, Object other) {
|
||||||
if (this == other) {
|
if (this == other) {
|
||||||
return true;
|
return true;
|
||||||
@@ -156,6 +166,72 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler {
|
|||||||
return true;
|
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) {
|
private String toString(Object proxy) {
|
||||||
StringBuilder sb = new StringBuilder("@").append(annotationType.getName()).append("(");
|
StringBuilder sb = new StringBuilder("@").append(annotationType.getName()).append("(");
|
||||||
|
|
||||||
|
|||||||
@@ -694,6 +694,44 @@ public class AnnotationUtilsTests {
|
|||||||
assertThat(webMappingWithAliases, is(not(synthesizedWebMapping1)));
|
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
|
* Fully reflection-based test that verifies support for
|
||||||
* {@linkplain AnnotationUtils#synthesizeAnnotation synthesizing annotations}
|
* {@linkplain AnnotationUtils#synthesizeAnnotation synthesizing annotations}
|
||||||
|
|||||||
Reference in New Issue
Block a user