1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.jadira.reflection.hashcode;
17
18 import java.lang.reflect.Modifier;
19 import java.util.IdentityHashMap;
20
21 import org.jadira.reflection.access.api.ClassAccess;
22 import org.jadira.reflection.access.api.ClassAccessFactory;
23 import org.jadira.reflection.access.api.FieldAccess;
24 import org.jadira.reflection.access.api.MethodAccess;
25 import org.jadira.reflection.access.portable.PortableClassAccessFactory;
26 import org.jadira.reflection.access.unsafe.UnsafeClassAccessFactory;
27 import org.jadira.reflection.core.platform.FeatureDetection;
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 public class HashCodeBuilder {
56
57 private static final ThreadLocal<HashCodeBuilder> reflectionBuilder = new ThreadLocal<HashCodeBuilder>() {
58 protected HashCodeBuilder initialValue() {
59 return new HashCodeBuilder(37, 17);
60 };
61 };
62
63 private IdentityHashMap<Object, Object> seenReferences;
64
65
66
67
68 private final int constant;
69
70
71
72
73 private int computedTotal;
74
75 private final int seed;
76
77 private ClassAccessFactory classAccessFactory;
78
79 private boolean defaultDeepReflect = false;
80
81 private <C> void reflectionAppend(C object) {
82
83 if (seenReferences.containsKey(object)) {
84 return;
85 }
86 seenReferences.put(object, object);
87
88 @SuppressWarnings("unchecked")
89 ClassAccess<? super C> classAccess = (ClassAccess<C>) classAccessFactory
90 .getClassAccess(object.getClass());
91
92 while ((classAccess.getType() != Object.class)
93 && (!classAccess.providesHashCode())) {
94
95 ClassAccess<? super C> classAccessInHierarchy = classAccess;
96
97 for (FieldAccess<? super C> fieldAccess : classAccessInHierarchy.getDeclaredFieldAccessors()) {
98
99 if ((fieldAccess.field().getName().indexOf('$') == -1)
100 && (!Modifier.isTransient(fieldAccess.field().getModifiers()))
101 && (!Modifier.isStatic(fieldAccess.field().getModifiers()))) {
102
103 Class<?> type = fieldAccess.fieldClass();
104
105 if (type.isPrimitive()) {
106 if (java.lang.Boolean.TYPE == type) {
107 append(fieldAccess.getBooleanValue(object));
108 } else if (java.lang.Byte.TYPE == type) {
109 append(fieldAccess.getByteValue(object));
110 } else if (java.lang.Character.TYPE == type) {
111 append(fieldAccess.getCharValue(object));
112 } else if (java.lang.Short.TYPE == type) {
113 append(fieldAccess.getShortValue(object));
114 } else if (java.lang.Integer.TYPE == type) {
115 append(fieldAccess.getIntValue(object));
116 } else if (java.lang.Long.TYPE == type) {
117 append(fieldAccess.getLongValue(object));
118 } else if (java.lang.Float.TYPE == type) {
119 append(fieldAccess.getFloatValue(object));
120 } else if (java.lang.Double.TYPE == type) {
121 append(fieldAccess.getDoubleValue(object));
122 }
123 } else {
124 final Object value = fieldAccess.getValue(object);
125 append(value);
126 }
127 }
128 }
129
130 classAccessInHierarchy = classAccessInHierarchy.getSuperClassAccess();
131 }
132 if (classAccess.getType() != Object.class) {
133
134 final MethodAccess<Object> methodAccess;
135 try {
136 @SuppressWarnings("unchecked")
137 final MethodAccess<Object> myMethodAccess = (MethodAccess<Object>) classAccess
138 .getDeclaredMethodAccess(classAccess.getType().getMethod(
139 "hashCode"));
140 methodAccess = myMethodAccess;
141 } catch (NoSuchMethodException e) {
142 throw new IllegalStateException("Cannot find hashCode() method");
143 } catch (SecurityException e) {
144 throw new IllegalStateException("Cannot find hashCode() method");
145 }
146 append(((Integer) (methodAccess.invoke(object))).intValue());
147 }
148 }
149
150 public static <T> int reflectionHashCode(T object) {
151 return reflectionHashCode(37, 17, object);
152 }
153
154 public static <T> int reflectionHashCode(int initialNonZeroOddNumber,
155 int multiplierNonZeroOddNumber, T object) {
156
157 if (object == null) {
158 throw new IllegalArgumentException(
159 "Cannot build hashcode for null object");
160 }
161
162 HashCodeBuilder builder = reflectionBuilder.get();
163
164 reflectionBuilder.set(null);
165
166 if (builder.seed == initialNonZeroOddNumber
167 && builder.constant == multiplierNonZeroOddNumber) {
168 builder.reset();
169 } else {
170 builder = new HashCodeBuilder(initialNonZeroOddNumber,
171 multiplierNonZeroOddNumber);
172 }
173
174 builder.reflectionAppend(object);
175
176 final int hashCode = builder.toHashCode();
177 reflectionBuilder.set(builder);
178
179 return hashCode;
180 }
181
182 public HashCodeBuilder() {
183
184 seenReferences = new IdentityHashMap<Object, Object>();
185
186 constant = 37;
187 computedTotal = seed = 17;
188
189 if (FeatureDetection.hasUnsafe()) {
190 this.classAccessFactory = UnsafeClassAccessFactory.get();
191 } else {
192 this.classAccessFactory = PortableClassAccessFactory.get();
193 }
194 }
195
196 public HashCodeBuilder(int initialNonZeroOddNumber,
197 int multiplierNonZeroOddNumber) {
198
199 seenReferences = new IdentityHashMap<Object, Object>();
200
201 if ((initialNonZeroOddNumber % 2 == 0)
202 || (initialNonZeroOddNumber == 0)) {
203 throw new IllegalArgumentException(
204 "Initial Value must be odd and non-zero but was: "
205 + initialNonZeroOddNumber);
206 } else if ((multiplierNonZeroOddNumber % 2 == 0)
207 || (multiplierNonZeroOddNumber == 0)) {
208 throw new IllegalArgumentException(
209 "Multiplier must be odd and non-zero but was: "
210 + multiplierNonZeroOddNumber);
211 }
212
213 constant = multiplierNonZeroOddNumber;
214 computedTotal = seed = initialNonZeroOddNumber;
215
216 if (FeatureDetection.hasUnsafe()) {
217 this.classAccessFactory = UnsafeClassAccessFactory.get();
218 } else {
219 this.classAccessFactory = PortableClassAccessFactory.get();
220 }
221 }
222
223 public HashCodeBuilder withDefaultDeepReflect(boolean newDefaultDeepReflect) {
224 this.defaultDeepReflect = newDefaultDeepReflect;
225 return this;
226 }
227
228 public HashCodeBuilder withClassAccessFactory(
229 ClassAccessFactory classAccessFactory) {
230 this.classAccessFactory = classAccessFactory;
231 return this;
232 }
233
234 public HashCodeBuilder append(boolean value) {
235 computedTotal = computedTotal * constant + (value ? 0 : 1);
236 return this;
237 }
238
239 public HashCodeBuilder append(boolean[] array) {
240 if (seenReferences.containsKey(array)) {
241 return this;
242 }
243 seenReferences.put(array, array);
244
245 if (array == null) {
246 appendNull();
247 } else {
248 for (boolean element : array) {
249 append(element);
250 }
251 }
252 return this;
253 }
254
255 public HashCodeBuilder append(byte value) {
256 computedTotal = (computedTotal * constant) + value;
257 return this;
258 }
259
260 public HashCodeBuilder append(byte[] array) {
261 if (seenReferences.containsKey(array)) {
262 return this;
263 }
264 seenReferences.put(array, array);
265
266 if (array == null) {
267 appendNull();
268 } else {
269 for (byte element : array) {
270 append(element);
271 }
272 }
273 return this;
274 }
275
276 public HashCodeBuilder append(char value) {
277 computedTotal = (computedTotal * constant) + value;
278 return this;
279 }
280
281 public HashCodeBuilder append(char[] array) {
282 if (seenReferences.containsKey(array)) {
283 return this;
284 }
285 seenReferences.put(array, array);
286
287 if (array == null) {
288 appendNull();
289 } else {
290 for (char element : array) {
291 append(element);
292 }
293 }
294 return this;
295 }
296
297 public HashCodeBuilder append(double value) {
298 return append(Double.doubleToLongBits(value));
299 }
300
301 public HashCodeBuilder append(double[] array) {
302 if (seenReferences.containsKey(array)) {
303 return this;
304 }
305 seenReferences.put(array, array);
306
307 if (array == null) {
308 appendNull();
309 } else {
310 for (double element : array) {
311 append(element);
312 }
313 }
314 return this;
315 }
316
317 public HashCodeBuilder append(float value) {
318 computedTotal = (computedTotal * constant)
319 + Float.floatToIntBits(value);
320 return this;
321 }
322
323 public HashCodeBuilder append(float[] array) {
324 if (seenReferences.containsKey(array)) {
325 return this;
326 }
327 seenReferences.put(array, array);
328
329 if (array == null) {
330 appendNull();
331 } else {
332 for (float element : array) {
333 append(element);
334 }
335 }
336 return this;
337 }
338
339 public HashCodeBuilder append(int value) {
340 computedTotal = (computedTotal * constant) + value;
341 return this;
342 }
343
344 public HashCodeBuilder append(int[] array) {
345 if (seenReferences.containsKey(array)) {
346 return this;
347 }
348 seenReferences.put(array, array);
349
350 if (array == null) {
351 appendNull();
352 } else {
353 for (int element : array) {
354 append(element);
355 }
356 }
357 return this;
358 }
359
360 public HashCodeBuilder append(long value) {
361 computedTotal = computedTotal * constant
362 + (int) (value ^ (value >>> 32));
363 return this;
364 }
365
366 public HashCodeBuilder append(long[] array) {
367 if (seenReferences.containsKey(array)) {
368 return this;
369 }
370 seenReferences.put(array, array);
371
372 if (array == null) {
373 appendNull();
374 } else {
375 for (long element : array) {
376 append(element);
377 }
378 }
379 return this;
380 }
381
382 public HashCodeBuilder append(Object object) {
383
384 if (object == null) {
385 appendNull();
386 return this;
387 } else {
388 if (object.getClass() == null) {
389 appendNull();
390 } else if (object.getClass().isArray()) {
391
392 if (object instanceof long[]) {
393 append((long[]) object);
394 } else if (object instanceof int[]) {
395 append((int[]) object);
396 } else if (object instanceof short[]) {
397 append((short[]) object);
398 } else if (object instanceof char[]) {
399 append((char[]) object);
400 } else if (object instanceof byte[]) {
401 append((byte[]) object);
402 } else if (object instanceof double[]) {
403 append((double[]) object);
404 } else if (object instanceof float[]) {
405 append((float[]) object);
406 } else if (object instanceof boolean[]) {
407 append((boolean[]) object);
408 } else {
409 append((Object[]) object);
410 }
411 } else {
412 computedTotal = (computedTotal * constant);
413 if (defaultDeepReflect) {
414 reflectionAppend(object);
415 } else {
416 computedTotal = computedTotal + object.hashCode();
417 }
418 }
419 }
420 return this;
421 }
422
423 public HashCodeBuilder append(Object[] array) {
424 if (seenReferences.containsKey(array)) {
425 return this;
426 }
427 seenReferences.put(array, array);
428
429 if (array == null) {
430 appendNull();
431 } else {
432 for (Object element : array) {
433 append(element);
434 }
435 }
436 return this;
437 }
438
439 public HashCodeBuilder append(short value) {
440 computedTotal = (computedTotal * constant) + value;
441 return this;
442 }
443
444 public HashCodeBuilder append(short[] array) {
445 if (seenReferences.containsKey(array)) {
446 return this;
447 }
448 seenReferences.put(array, array);
449
450 if (array == null) {
451 appendNull();
452 } else {
453 for (short element : array) {
454 append(element);
455 }
456 }
457 return this;
458 }
459
460 public HashCodeBuilder appendSuper(int superHashCode) {
461 computedTotal = (computedTotal * constant) + superHashCode;
462 return this;
463 }
464
465 protected void appendNull() {
466 computedTotal = computedTotal * constant;
467 }
468
469 public int toHashCode() {
470 return computedTotal;
471 }
472
473 public int hashCode() {
474 return toHashCode();
475 }
476
477 public void reset() {
478 seenReferences = new IdentityHashMap<Object, Object>();
479 computedTotal = seed;
480 }
481 }