Skip to content

Commit def7663

Browse files
committed
Implement hashCode() for synthesized annotations
Issue: SPR-13066
1 parent ae5c828 commit def7663

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed

spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.lang.reflect.AnnotatedElement;
2121
import java.lang.reflect.InvocationHandler;
2222
import java.lang.reflect.Method;
23+
import java.util.Arrays;
2324
import java.util.Iterator;
2425
import java.util.Map;
2526

@@ -70,6 +71,9 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
7071
if (isEqualsMethod(method)) {
7172
return equals(proxy, args[0]);
7273
}
74+
if (isHashCodeMethod(method)) {
75+
return hashCode(proxy);
76+
}
7377
if (isToStringMethod(method)) {
7478
return toString(proxy);
7579
}
@@ -137,6 +141,12 @@ else if (value instanceof Annotation[]) {
137141
return value;
138142
}
139143

144+
/**
145+
* See {@link Annotation#equals(Object)} for a definition of the required algorithm.
146+
*
147+
* @param proxy the synthesized annotation
148+
* @param other the other object to compare against
149+
*/
140150
private boolean equals(Object proxy, Object other) {
141151
if (this == other) {
142152
return true;
@@ -156,6 +166,72 @@ private boolean equals(Object proxy, Object other) {
156166
return true;
157167
}
158168

169+
/**
170+
* See {@link Annotation#hashCode()} for a definition of the required algorithm.
171+
*
172+
* @param proxy the synthesized annotation
173+
*/
174+
private int hashCode(Object proxy) {
175+
int result = 0;
176+
177+
for (Method attributeMethod : getAttributeMethods(this.annotationType)) {
178+
Object value = invokeMethod(attributeMethod, proxy);
179+
int hashCode;
180+
if (value.getClass().isArray()) {
181+
hashCode = hashCodeForArray(value);
182+
}
183+
else {
184+
hashCode = value.hashCode();
185+
}
186+
result += (127 * attributeMethod.getName().hashCode()) ^ hashCode;
187+
}
188+
189+
return result;
190+
}
191+
192+
/**
193+
* WARNING: we can NOT use any of the {@code nullSafeHashCode()} methods
194+
* in Spring's {@link ObjectUtils} because those hash code generation
195+
* algorithms do not comply with the requirements specified in
196+
* {@link Annotation#hashCode()}.
197+
*
198+
* @param array the array to compute the hash code for
199+
*/
200+
private int hashCodeForArray(Object array) {
201+
if (array instanceof boolean[]) {
202+
return Arrays.hashCode((boolean[]) array);
203+
}
204+
if (array instanceof byte[]) {
205+
return Arrays.hashCode((byte[]) array);
206+
}
207+
if (array instanceof char[]) {
208+
return Arrays.hashCode((char[]) array);
209+
}
210+
if (array instanceof double[]) {
211+
return Arrays.hashCode((double[]) array);
212+
}
213+
if (array instanceof float[]) {
214+
return Arrays.hashCode((float[]) array);
215+
}
216+
if (array instanceof int[]) {
217+
return Arrays.hashCode((int[]) array);
218+
}
219+
if (array instanceof long[]) {
220+
return Arrays.hashCode((long[]) array);
221+
}
222+
if (array instanceof short[]) {
223+
return Arrays.hashCode((short[]) array);
224+
}
225+
226+
// else
227+
return Arrays.hashCode((Object[]) array);
228+
}
229+
230+
/**
231+
* See {@link Annotation#toString()} for guidelines on the recommended format.
232+
*
233+
* @param proxy the synthesized annotation
234+
*/
159235
private String toString(Object proxy) {
160236
StringBuilder sb = new StringBuilder("@").append(annotationType.getName()).append("(");
161237

spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,44 @@ public void equalsForSynthesizedAnnotations() throws Exception {
694694
assertThat(webMappingWithAliases, is(not(synthesizedWebMapping1)));
695695
}
696696

697+
@Test
698+
public void hashCodeForSynthesizedAnnotations() throws Exception {
699+
Method methodWithPath = WebController.class.getMethod("handleMappedWithPathAttribute");
700+
WebMapping webMappingWithAliases = methodWithPath.getAnnotation(WebMapping.class);
701+
assertNotNull(webMappingWithAliases);
702+
703+
Method methodWithPathAndValue = WebController.class.getMethod("handleMappedWithSamePathAndValueAttributes");
704+
WebMapping webMappingWithPathAndValue = methodWithPathAndValue.getAnnotation(WebMapping.class);
705+
assertNotNull(webMappingWithPathAndValue);
706+
707+
WebMapping synthesizedWebMapping1 = synthesizeAnnotation(webMappingWithAliases);
708+
assertNotNull(synthesizedWebMapping1);
709+
WebMapping synthesizedWebMapping2 = synthesizeAnnotation(webMappingWithAliases);
710+
assertNotNull(synthesizedWebMapping2);
711+
712+
// Equality amongst standard annotations
713+
assertThat(webMappingWithAliases.hashCode(), is(webMappingWithAliases.hashCode()));
714+
assertThat(webMappingWithPathAndValue.hashCode(), is(webMappingWithPathAndValue.hashCode()));
715+
716+
// Inequality amongst standard annotations
717+
assertThat(webMappingWithAliases.hashCode(), is(not(webMappingWithPathAndValue.hashCode())));
718+
assertThat(webMappingWithPathAndValue.hashCode(), is(not(webMappingWithAliases.hashCode())));
719+
720+
// Equality amongst synthesized annotations
721+
assertThat(synthesizedWebMapping1.hashCode(), is(synthesizedWebMapping1.hashCode()));
722+
assertThat(synthesizedWebMapping2.hashCode(), is(synthesizedWebMapping2.hashCode()));
723+
assertThat(synthesizedWebMapping1.hashCode(), is(synthesizedWebMapping2.hashCode()));
724+
assertThat(synthesizedWebMapping2.hashCode(), is(synthesizedWebMapping1.hashCode()));
725+
726+
// Equality between standard and synthesized annotations
727+
assertThat(synthesizedWebMapping1.hashCode(), is(webMappingWithPathAndValue.hashCode()));
728+
assertThat(webMappingWithPathAndValue.hashCode(), is(synthesizedWebMapping1.hashCode()));
729+
730+
// Inequality between standard and synthesized annotations
731+
assertThat(synthesizedWebMapping1.hashCode(), is(not(webMappingWithAliases.hashCode())));
732+
assertThat(webMappingWithAliases.hashCode(), is(not(synthesizedWebMapping1.hashCode())));
733+
}
734+
697735
/**
698736
* Fully reflection-based test that verifies support for
699737
* {@linkplain AnnotationUtils#synthesizeAnnotation synthesizing annotations}

0 commit comments

Comments
 (0)