View Javadoc
1   /*
2    *  Copyright 2013 Christopher Pheby
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
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   * This class is based on the capabilities provided by Commons-Lang's
31   * HashCodeBuilder. It is not a complete "drop-in" replacement, but is near enough
32   * in functionality and in terms of its API that it should be quite
33   * straightforward to switch from one class to the other.
34   * 
35   * HashCodeBuilder makes use of Jadira's reflection capabilities. This means, like
36   * Cloning there is a brief warm-up time entailed in preparing introspection
37   * when a class is first processed. If you are already performing cloning on a
38   * type you will not be impacted by this as any necessary warm-up will have
39   * already taken place.
40   * 
41   * Once the initial warmup has been performed, you will, usually, find enhanced
42   * performance compared to standard reflection based approaches. The strategy to
43   * be used can be configured.
44   *
45   * Where objects that are reachable from the object being compared do not
46   * override hashCode(), the default behaviour is to rely on
47   * Object#hashCode(Object) which uses the object identity for those
48   * objects. This is also the behaviour of Commons Lang. You can modify this to
49   * reflect into any reachable object that does not override equals by enabling
50   * the defaultDeepReflect property.
51   * 
52   * Transient fields are not compared by the current implementation of this
53   * class.
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  	 * Constant to use in building the hashCode.
67  	 */
68  	private final int constant;
69  
70  	/**
71  	 * Running total of the hashCode.
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 		// In case we recurse into this method
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 }