All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jadira.reflection.hashcode.HashCodeBuilder Maven / Gradle / Ivy

There is a newer version: 7.0.0.CR1
Show newest version
/*
 *  Copyright 2013 Christopher Pheby
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.jadira.reflection.hashcode;

import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;

import org.jadira.reflection.access.api.ClassAccess;
import org.jadira.reflection.access.api.ClassAccessFactory;
import org.jadira.reflection.access.api.FieldAccess;
import org.jadira.reflection.access.api.MethodAccess;
import org.jadira.reflection.access.portable.PortableClassAccessFactory;
import org.jadira.reflection.access.unsafe.UnsafeClassAccessFactory;
import org.jadira.reflection.core.platform.FeatureDetection;

/**
 * This class is based on the capabilities provided by Commons-Lang's
 * HashCodeBuilder. It is not a complete "drop-in" replacement, but is near enough
 * in functionality and in terms of its API that it should be quite
 * straightforward to switch from one class to the other.
 * 
 * HashCodeBuilder makes use of Jadira's reflection capabilities. This means, like
 * Cloning there is a brief warm-up time entailed in preparing introspection
 * when a class is first processed. If you are already performing cloning on a
 * type you will not be impacted by this as any necessary warm-up will have
 * already taken place.
 * 
 * Once the initial warmup has been performed, you will, usually, find enhanced
 * performance compared to standard reflection based approaches. The strategy to
 * be used can be configured.
 *
 * Where objects that are reachable from the object being compared do not
 * override hashCode(), the default behaviour is to rely on
 * Object#hashCode(Object) which uses the object identity for those
 * objects. This is also the behaviour of Commons Lang. You can modify this to
 * reflect into any reachable object that does not override equals by enabling
 * the defaultDeepReflect property.
 * 
 * Transient fields are not compared by the current implementation of this
 * class.
 */
public class HashCodeBuilder {

	private static final ThreadLocal reflectionBuilder = new ThreadLocal() {
		protected HashCodeBuilder initialValue() {
			return new HashCodeBuilder(37, 17);
		};
	};

	private IdentityHashMap seenReferences;

	/**
	 * Constant to use in building the hashCode.
	 */
	private final int constant;

	/**
	 * Running total of the hashCode.
	 */
	private int computedTotal;

	private final int seed;

	private ClassAccessFactory classAccessFactory;

	private boolean defaultDeepReflect = false;

	private  void reflectionAppend(C object) {

		if (seenReferences.containsKey(object)) {
			return;
		}
		seenReferences.put(object, object);

		@SuppressWarnings("unchecked")
		ClassAccess classAccess = (ClassAccess) classAccessFactory
				.getClassAccess(object.getClass());

		while ((classAccess.getType() != Object.class)
				&& (!classAccess.providesHashCode())) {

			ClassAccess classAccessInHierarchy = classAccess;
						
			for (FieldAccess fieldAccess : classAccessInHierarchy.getDeclaredFieldAccessors()) {

				if ((fieldAccess.field().getName().indexOf('$') == -1)
						&& (!Modifier.isTransient(fieldAccess.field().getModifiers()))
						&& (!Modifier.isStatic(fieldAccess.field().getModifiers()))) {
				
					Class type = fieldAccess.fieldClass();
					
					if (type.isPrimitive()) {
						if (java.lang.Boolean.TYPE == type) {
							append(fieldAccess.getBooleanValue(object));
						} else if (java.lang.Byte.TYPE == type) {
							append(fieldAccess.getByteValue(object));
						} else if (java.lang.Character.TYPE == type) {
							append(fieldAccess.getCharValue(object));
						} else if (java.lang.Short.TYPE == type) {
							append(fieldAccess.getShortValue(object));
						} else if (java.lang.Integer.TYPE == type) {
							append(fieldAccess.getIntValue(object));
						} else if (java.lang.Long.TYPE == type) {
							append(fieldAccess.getLongValue(object));
						} else if (java.lang.Float.TYPE == type) {
							append(fieldAccess.getFloatValue(object));
						} else if (java.lang.Double.TYPE == type) {
							append(fieldAccess.getDoubleValue(object));
						}
					} else {
						final Object value = fieldAccess.getValue(object);
						append(value);
					}
				}
			}

			classAccessInHierarchy = classAccessInHierarchy.getSuperClassAccess();
		}
		if (classAccess.getType() != Object.class) {

			final MethodAccess methodAccess;
			try {
				@SuppressWarnings("unchecked")
				final MethodAccess myMethodAccess = (MethodAccess) classAccess
						.getDeclaredMethodAccess(classAccess.getType().getMethod(
								"hashCode"));
				methodAccess = myMethodAccess;
			} catch (NoSuchMethodException e) {
				throw new IllegalStateException("Cannot find hashCode() method");
			} catch (SecurityException e) {
				throw new IllegalStateException("Cannot find hashCode() method");
			}
			append(((Integer) (methodAccess.invoke(object))).intValue());
		}
	}

	public static  int reflectionHashCode(T object) {
		return reflectionHashCode(37, 17, object);
	}

	public static  int reflectionHashCode(int initialNonZeroOddNumber,
			int multiplierNonZeroOddNumber, T object) {

		if (object == null) {
			throw new IllegalArgumentException(
					"Cannot build hashcode for null object");
		}

		HashCodeBuilder builder = reflectionBuilder.get();
		// In case we recurse into this method
		reflectionBuilder.set(null);

		if (builder.seed == initialNonZeroOddNumber
				&& builder.constant == multiplierNonZeroOddNumber) {
			builder.reset();
		} else {
			builder = new HashCodeBuilder(initialNonZeroOddNumber,
					multiplierNonZeroOddNumber);
		}

		builder.reflectionAppend(object);

		final int hashCode = builder.toHashCode();
		reflectionBuilder.set(builder);

		return hashCode;
	}

	public HashCodeBuilder() {

		seenReferences = new IdentityHashMap();

		constant = 37;
		computedTotal = seed = 17;

		if (FeatureDetection.hasUnsafe()) {
			this.classAccessFactory = UnsafeClassAccessFactory.get();
		} else {
			this.classAccessFactory = PortableClassAccessFactory.get();
		}
	}

	public HashCodeBuilder(int initialNonZeroOddNumber,
			int multiplierNonZeroOddNumber) {

		seenReferences = new IdentityHashMap();

		if ((initialNonZeroOddNumber % 2 == 0)
				|| (initialNonZeroOddNumber == 0)) {
			throw new IllegalArgumentException(
					"Initial Value must be odd and non-zero but was: "
							+ initialNonZeroOddNumber);
		} else if ((multiplierNonZeroOddNumber % 2 == 0)
				|| (multiplierNonZeroOddNumber == 0)) {
			throw new IllegalArgumentException(
					"Multiplier must be odd and non-zero but was: "
							+ multiplierNonZeroOddNumber);
		}

		constant = multiplierNonZeroOddNumber;
		computedTotal = seed = initialNonZeroOddNumber;

		if (FeatureDetection.hasUnsafe()) {
			this.classAccessFactory = UnsafeClassAccessFactory.get();
		} else {
			this.classAccessFactory = PortableClassAccessFactory.get();
		}
	}

	public HashCodeBuilder withDefaultDeepReflect(boolean newDefaultDeepReflect) {
		this.defaultDeepReflect = newDefaultDeepReflect;
		return this;
	}

	public HashCodeBuilder withClassAccessFactory(
			ClassAccessFactory classAccessFactory) {
		this.classAccessFactory = classAccessFactory;
		return this;
	}

	public HashCodeBuilder append(boolean value) {
		computedTotal = computedTotal * constant + (value ? 0 : 1);
		return this;
	}

	public HashCodeBuilder append(boolean[] array) {
		if (seenReferences.containsKey(array)) {
			return this;
		}
		seenReferences.put(array, array);

		if (array == null) {
			appendNull();
		} else {
			for (boolean element : array) {
				append(element);
			}
		}
		return this;
	}

	public HashCodeBuilder append(byte value) {
		computedTotal = (computedTotal * constant) + value;
		return this;
	}

	public HashCodeBuilder append(byte[] array) {
		if (seenReferences.containsKey(array)) {
			return this;
		}
		seenReferences.put(array, array);

		if (array == null) {
			appendNull();
		} else {
			for (byte element : array) {
				append(element);
			}
		}
		return this;
	}

	public HashCodeBuilder append(char value) {
		computedTotal = (computedTotal * constant) + value;
		return this;
	}

	public HashCodeBuilder append(char[] array) {
		if (seenReferences.containsKey(array)) {
			return this;
		}
		seenReferences.put(array, array);

		if (array == null) {
			appendNull();
		} else {
			for (char element : array) {
				append(element);
			}
		}
		return this;
	}

	public HashCodeBuilder append(double value) {
		return append(Double.doubleToLongBits(value));
	}

	public HashCodeBuilder append(double[] array) {
		if (seenReferences.containsKey(array)) {
			return this;
		}
		seenReferences.put(array, array);

		if (array == null) {
			appendNull();
		} else {
			for (double element : array) {
				append(element);
			}
		}
		return this;
	}

	public HashCodeBuilder append(float value) {
		computedTotal = (computedTotal * constant)
				+ Float.floatToIntBits(value);
		return this;
	}

	public HashCodeBuilder append(float[] array) {
		if (seenReferences.containsKey(array)) {
			return this;
		}
		seenReferences.put(array, array);

		if (array == null) {
			appendNull();
		} else {
			for (float element : array) {
				append(element);
			}
		}
		return this;
	}

	public HashCodeBuilder append(int value) {
		computedTotal = (computedTotal * constant) + value;
		return this;
	}

	public HashCodeBuilder append(int[] array) {
		if (seenReferences.containsKey(array)) {
			return this;
		}
		seenReferences.put(array, array);

		if (array == null) {
			appendNull();
		} else {
			for (int element : array) {
				append(element);
			}
		}
		return this;
	}

	public HashCodeBuilder append(long value) {
		computedTotal = computedTotal * constant
				+ (int) (value ^ (value >>> 32));
		return this;
	}

	public HashCodeBuilder append(long[] array) {
		if (seenReferences.containsKey(array)) {
			return this;
		}
		seenReferences.put(array, array);

		if (array == null) {
			appendNull();
		} else {
			for (long element : array) {
				append(element);
			}
		}
		return this;
	}

	public HashCodeBuilder append(Object object) {

		if (object == null) {
			appendNull();
			return this;
		} else {
			if (object.getClass() == null) {
				appendNull();
			} else if (object.getClass().isArray()) {

				if (object instanceof long[]) {
					append((long[]) object);
				} else if (object instanceof int[]) {
					append((int[]) object);
				} else if (object instanceof short[]) {
					append((short[]) object);
				} else if (object instanceof char[]) {
					append((char[]) object);
				} else if (object instanceof byte[]) {
					append((byte[]) object);
				} else if (object instanceof double[]) {
					append((double[]) object);
				} else if (object instanceof float[]) {
					append((float[]) object);
				} else if (object instanceof boolean[]) {
					append((boolean[]) object);
				} else {
					append((Object[]) object);
				}
			} else {
				computedTotal = (computedTotal * constant);
				if (defaultDeepReflect) {
					reflectionAppend(object);
				} else {
					computedTotal = computedTotal + object.hashCode();
				}
			}
		}
		return this;
	}

	public HashCodeBuilder append(Object[] array) {
		if (seenReferences.containsKey(array)) {
			return this;
		}
		seenReferences.put(array, array);

		if (array == null) {
			appendNull();
		} else {
			for (Object element : array) {
				append(element);
			}
		}
		return this;
	}

	public HashCodeBuilder append(short value) {
		computedTotal = (computedTotal * constant) + value;
		return this;
	}

	public HashCodeBuilder append(short[] array) {
		if (seenReferences.containsKey(array)) {
			return this;
		}
		seenReferences.put(array, array);

		if (array == null) {
			appendNull();
		} else {
			for (short element : array) {
				append(element);
			}
		}
		return this;
	}

	public HashCodeBuilder appendSuper(int superHashCode) {
		computedTotal = (computedTotal * constant) + superHashCode;
		return this;
	}

	protected void appendNull() {
		computedTotal = computedTotal * constant;
	}

	public int toHashCode() {
		return computedTotal;
	}

	public int hashCode() {
		return toHashCode();
	}

	public void reset() {
		seenReferences = new IdentityHashMap();
		computedTotal = seed;
	}
}