Skip to content

Commit a125513

Browse files
committed
HV-662 Implementing equals() and hashCode() for AnnotationProxy
1 parent 59b272f commit a125513

File tree

2 files changed

+587
-10
lines changed

2 files changed

+587
-10
lines changed

engine/src/main/java/org/hibernate/validator/internal/util/annotationfactory/AnnotationProxy.java

Lines changed: 122 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@
1616
*/
1717
package org.hibernate.validator.internal.util.annotationfactory;
1818

19+
import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap;
20+
1921
import java.io.Serializable;
2022
import java.lang.annotation.Annotation;
2123
import java.lang.reflect.InvocationHandler;
24+
import java.lang.reflect.InvocationTargetException;
2225
import java.lang.reflect.Method;
23-
import java.util.HashMap;
26+
import java.util.Arrays;
27+
import java.util.Collections;
2428
import java.util.Map;
29+
import java.util.Map.Entry;
2530
import java.util.Set;
2631
import java.util.SortedSet;
2732
import java.util.TreeSet;
@@ -42,16 +47,11 @@
4247
* default value for that element from the annotation interface, if it exists.
4348
* If no default exists, it will throw an exception.
4449
* <p/>
45-
* Warning: this class does not implement <code>hashCode()</code> and
46-
* <code>equals()</code> - it just uses the ones it inherits from <code>Object</code>.
47-
* This means that an <code>AnnotationProxy</code> does <i>not</i> follow the
48-
* recommendations of the <code>Annotation</code> javadoc about these two
49-
* methods. That's why you should never mix <code>AnnotationProxies</code>
50-
* with "real" annotations. For example, don't put them into the same
51-
* <code>Collection</code>.
5250
*
5351
* @author Paolo Perrotta
5452
* @author Davide Marchignoli
53+
* @author Gunnar Morling
54+
*
5555
* @see java.lang.annotation.Annotation
5656
*/
5757
public class AnnotationProxy implements Annotation, InvocationHandler, Serializable {
@@ -61,14 +61,16 @@ public class AnnotationProxy implements Annotation, InvocationHandler, Serializa
6161

6262
private final Class<? extends Annotation> annotationType;
6363
private final Map<String, Object> values;
64+
private final int hashCode;
6465

6566
public AnnotationProxy(AnnotationDescriptor<?> descriptor) {
6667
this.annotationType = descriptor.type();
67-
values = getAnnotationValues( descriptor );
68+
values = Collections.unmodifiableMap( getAnnotationValues( descriptor ) );
69+
this.hashCode = calculateHashCode();
6870
}
6971

7072
private Map<String, Object> getAnnotationValues(AnnotationDescriptor<?> descriptor) {
71-
Map<String, Object> result = new HashMap<String, Object>();
73+
Map<String, Object> result = newHashMap();
7274
int processedValuesFromDescriptor = 0;
7375
final Method[] declaredMethods = ReflectionHelper.getDeclaredMethods( annotationType );
7476
for ( Method m : declaredMethods ) {
@@ -99,17 +101,113 @@ else if ( m.getDefaultValue() != null ) {
99101
return result;
100102
}
101103

104+
private Object getAnnotationMemberValue(Annotation annotation, String name) {
105+
try {
106+
return ReflectionHelper.getDeclaredMethod( annotation.annotationType(), name )
107+
.invoke( annotation );
108+
}
109+
catch ( IllegalAccessException e ) {
110+
throw log.getUnableToRetrieveAnnotationParameterValueException( e );
111+
}
112+
catch ( IllegalArgumentException e ) {
113+
throw log.getUnableToRetrieveAnnotationParameterValueException( e );
114+
}
115+
catch ( InvocationTargetException e ) {
116+
throw log.getUnableToRetrieveAnnotationParameterValueException( e );
117+
}
118+
}
119+
120+
@Override
102121
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
103122
if ( values.containsKey( method.getName() ) ) {
104123
return values.get( method.getName() );
105124
}
106125
return method.invoke( this, args );
107126
}
108127

128+
@Override
109129
public Class<? extends Annotation> annotationType() {
110130
return annotationType;
111131
}
112132

133+
/**
134+
* Performs an equality check as described in {@link Annotation#equals(Object)}.
135+
*
136+
* @param obj The object to compare
137+
*
138+
* @return Whether the given object is equal to this annotation proxy or not
139+
*
140+
* @see Annotation#equals(Object)
141+
*/
142+
@Override
143+
public boolean equals(Object obj) {
144+
if ( this == obj ) {
145+
return true;
146+
}
147+
if ( obj == null ) {
148+
return false;
149+
}
150+
if ( !annotationType.isInstance( obj ) ) {
151+
return false;
152+
}
153+
154+
Annotation other = annotationType.cast( obj );
155+
156+
//compare annotation member values
157+
for ( Entry<String, Object> member : values.entrySet() ) {
158+
159+
Object value = member.getValue();
160+
Object otherValue = getAnnotationMemberValue( other, member.getKey() );
161+
162+
if ( !areEqual( value, otherValue ) ) {
163+
return false;
164+
}
165+
}
166+
167+
return true;
168+
}
169+
170+
/**
171+
* Calculates the hash code of this annotation proxy as described in
172+
* {@link Annotation#hashCode()}.
173+
*
174+
* @return The hash code of this proxy.
175+
*
176+
* @see Annotation#hashCode()
177+
*/
178+
@Override
179+
public int hashCode() {
180+
return hashCode;
181+
}
182+
183+
private int calculateHashCode() {
184+
185+
int hashCode = 0;
186+
187+
for ( Entry<String, Object> member : values.entrySet() ) {
188+
Object value = member.getValue();
189+
190+
int nameHashCode = member.getKey().hashCode();
191+
192+
int valueHashCode =
193+
!value.getClass().isArray() ? value.hashCode() :
194+
value.getClass() == boolean[].class ? Arrays.hashCode( (boolean[]) value ) :
195+
value.getClass() == byte[].class ? Arrays.hashCode( (byte[]) value ) :
196+
value.getClass() == char[].class ? Arrays.hashCode( (char[]) value ) :
197+
value.getClass() == double[].class ? Arrays.hashCode( (double[]) value ) :
198+
value.getClass() == float[].class ? Arrays.hashCode( (float[]) value ) :
199+
value.getClass() == int[].class ? Arrays.hashCode( (int[]) value ) :
200+
value.getClass() == long[].class ? Arrays.hashCode( (long[]) value ) :
201+
value.getClass() == short[].class ? Arrays.hashCode( (short[]) value ) :
202+
Arrays.hashCode( (Object[]) value );
203+
204+
hashCode += 127 * nameHashCode ^ valueHashCode;
205+
}
206+
207+
return hashCode;
208+
}
209+
210+
@Override
113211
public String toString() {
114212
StringBuilder result = new StringBuilder();
115213
result.append( '@' ).append( annotationType.getName() ).append( '(' );
@@ -133,4 +231,18 @@ private SortedSet<String> getRegisteredMethodsInAlphabeticalOrder() {
133231
result.addAll( values.keySet() );
134232
return result;
135233
}
234+
235+
private boolean areEqual(Object o1, Object o2) {
236+
return
237+
!o1.getClass().isArray() ? o1.equals( o2 ) :
238+
o1.getClass() == boolean[].class ? Arrays.equals( (boolean[]) o1, (boolean[]) o2 ) :
239+
o1.getClass() == byte[].class ? Arrays.equals( (byte[]) o1, (byte[]) o2 ) :
240+
o1.getClass() == char[].class ? Arrays.equals( (char[]) o1, (char[]) o2 ) :
241+
o1.getClass() == double[].class ? Arrays.equals( (double[]) o1, (double[]) o2 ) :
242+
o1.getClass() == float[].class ? Arrays.equals( (float[]) o1, (float[]) o2 ) :
243+
o1.getClass() == int[].class ? Arrays.equals( (int[]) o1, (int[]) o2 ) :
244+
o1.getClass() == long[].class ? Arrays.equals( (long[]) o1, (long[]) o2 ) :
245+
o1.getClass() == short[].class ? Arrays.equals( (short[]) o1, (short[]) o2 ) :
246+
Arrays.equals( (Object[]) o1, (Object[]) o2 );
247+
}
136248
}

0 commit comments

Comments
 (0)