Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.jadira.reflection.equals.EqualsBuilder Maven / Gradle / Ivy
/*
* 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.equals;
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.identity.Tuple;
import org.jadira.reflection.core.platform.FeatureDetection;
/**
* This class is based on the capabilities provided by Commons-Lang's
* EqualsBuilder. 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.
*
* EqualsBuilder 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 equals(),
* the default behaviour is to rely on {@link Object#equals(Object)} which compares 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 EqualsBuilder {
private static final ThreadLocal reflectionBuilder = new ThreadLocal() {
protected EqualsBuilder initialValue() {
return new EqualsBuilder();
};
};
private IdentityHashMap, Tuple> seenReferences;
/**
* If the fields tested are equal. The default value is true
.
*/
private boolean isEquals = true;
private boolean defaultDeepReflect = false;
private ClassAccessFactory classAccessFactory;
public EqualsBuilder() {
seenReferences = new IdentityHashMap, Tuple>();
if (FeatureDetection.hasUnsafe()) {
this.classAccessFactory = UnsafeClassAccessFactory.get();
} else {
this.classAccessFactory = PortableClassAccessFactory.get();
}
}
public EqualsBuilder withDefaultDeepReflect(boolean newDefaultDeepReflect) {
this.defaultDeepReflect = newDefaultDeepReflect;
return this;
}
public EqualsBuilder withClassAccessFactory(
ClassAccessFactory classAccessFactory) {
this.classAccessFactory = classAccessFactory;
return this;
}
public static boolean reflectionEquals(Object lhs, Object rhs) {
if (lhs == rhs) {
return true;
}
if (lhs == null || rhs == null) {
return false;
}
Class> lhsClass = lhs.getClass();
Class> rhsClass = rhs.getClass();
Class> testClass;
if (lhsClass.isInstance(rhs)) {
if (rhsClass.isInstance(lhs)) {
testClass = lhsClass;
} else {
testClass = rhsClass;
}
} else if (rhsClass.isInstance(lhs)) {
if (lhsClass.isInstance(rhs)) {
testClass = rhsClass;
} else {
testClass = lhsClass;
}
} else {
return false;
}
EqualsBuilder equalsBuilder = reflectionBuilder.get();
// In case we recurse into this method
reflectionBuilder.set(null);
equalsBuilder.reset();
ClassAccess> classAccess = equalsBuilder.classAccessFactory
.getClassAccess(testClass);
try {
while ((classAccess.getType() != Object.class)
&& (!classAccess.providesEquals())) {
equalsBuilder.reflectionAppend(lhs, rhs, classAccess);
classAccess = classAccess.getSuperClassAccess();
}
} catch (IllegalArgumentException e) {
return false;
}
if (classAccess.getType() != Object.class) {
final MethodAccess methodAccess;
try {
@SuppressWarnings("unchecked")
final MethodAccess myMethodAccess = (MethodAccess) classAccess.getDeclaredMethodAccess(classAccess.getType().getMethod(
"equals", Object.class));
methodAccess = myMethodAccess;
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Cannot find equals() method");
} catch (SecurityException e) {
throw new IllegalStateException("Cannot find equals() method");
}
equalsBuilder.setEquals(((Boolean) methodAccess.invoke(lhs, rhs))
.booleanValue());
}
final boolean isEqual = equalsBuilder.isEquals();
reflectionBuilder.set(equalsBuilder);
return isEqual;
}
private void reflectionAppend(Object lhs, Object rhs,
ClassAccess> classAccess) {
Tuple t = Tuple.of(lhs, rhs);
if (seenReferences.containsKey(t)) {
return;
}
seenReferences.put(t, t);
@SuppressWarnings("unchecked")
ClassAccess classAccessInHierarchy = (ClassAccess) classAccess;
while (classAccessInHierarchy != null) {
FieldAccess[] fields = (FieldAccess[]) classAccessInHierarchy.getDeclaredFieldAccessors();
for (int i = 0; (i < fields.length) && (isEquals); i++) {
FieldAccess f = fields[i];
if ((f.field().getName().indexOf('$') == -1)
&& (!Modifier.isTransient(f.field().getModifiers()))
&& (!Modifier.isStatic(f.field().getModifiers()))) {
Class> type = f.fieldClass();
if (type.isPrimitive()) {
if (java.lang.Boolean.TYPE == type) {
append(f.getBooleanValue(lhs), f.getBooleanValue(rhs));
} else if (java.lang.Byte.TYPE == type) {
append(f.getByteValue(lhs), f.getByteValue(rhs));
} else if (java.lang.Character.TYPE == type) {
append(f.getCharValue(lhs), f.getCharValue(rhs));
} else if (java.lang.Short.TYPE == type) {
append(f.getShortValue(lhs), f.getShortValue(rhs));
} else if (java.lang.Integer.TYPE == type) {
append(f.getIntValue(lhs), f.getIntValue(rhs));
} else if (java.lang.Long.TYPE == type) {
append(f.getLongValue(lhs), f.getLongValue(rhs));
} else if (java.lang.Float.TYPE == type) {
append(f.getFloatValue(lhs), f.getFloatValue(rhs));
} else if (java.lang.Double.TYPE == type) {
append(f.getDoubleValue(lhs), f.getDoubleValue(rhs));
}
} else {
append(f.getValue(lhs), f.getValue(rhs));
}
}
}
classAccessInHierarchy = classAccessInHierarchy.getSuperClassAccess();
}
}
public EqualsBuilder append(Object lhs, Object rhs) {
preCheckObject(lhs, rhs);
Class> lhsClass = lhs.getClass();
if (!lhsClass.isArray()) {
if (defaultDeepReflect) {
reflectionEquals(lhs, rhs);
} else {
this.setEquals(lhs.equals(rhs));
}
} else if (lhs.getClass() != rhs.getClass()) {
this.setEquals(false);
} else if (lhs instanceof long[]) {
append((long[]) lhs, (long[]) rhs);
} else if (lhs instanceof int[]) {
append((int[]) lhs, (int[]) rhs);
} else if (lhs instanceof short[]) {
append((short[]) lhs, (short[]) rhs);
} else if (lhs instanceof char[]) {
append((char[]) lhs, (char[]) rhs);
} else if (lhs instanceof byte[]) {
append((byte[]) lhs, (byte[]) rhs);
} else if (lhs instanceof double[]) {
append((double[]) lhs, (double[]) rhs);
} else if (lhs instanceof float[]) {
append((float[]) lhs, (float[]) rhs);
} else if (lhs instanceof boolean[]) {
append((boolean[]) lhs, (boolean[]) rhs);
} else {
append((Object[]) lhs, (Object[]) rhs);
}
return this;
}
public EqualsBuilder append(long lhs, long rhs) {
if (isEquals == false) {
return this;
}
isEquals = (lhs == rhs);
return this;
}
public EqualsBuilder append(int lhs, int rhs) {
if (isEquals == false) {
return this;
}
isEquals = (lhs == rhs);
return this;
}
public EqualsBuilder append(short lhs, short rhs) {
if (isEquals == false) {
return this;
}
isEquals = (lhs == rhs);
return this;
}
public EqualsBuilder append(char lhs, char rhs) {
if (isEquals == false) {
return this;
}
isEquals = (lhs == rhs);
return this;
}
public EqualsBuilder append(byte lhs, byte rhs) {
if (isEquals == false) {
return this;
}
isEquals = (lhs == rhs);
return this;
}
public EqualsBuilder append(double lhs, double rhs) {
if (isEquals == false) {
return this;
}
return append(Double.doubleToLongBits(lhs),
Double.doubleToLongBits(rhs));
}
public EqualsBuilder append(float lhs, float rhs) {
if (isEquals == false) {
return this;
}
return append(Float.floatToIntBits(lhs), Float.floatToIntBits(rhs));
}
public EqualsBuilder append(boolean lhs, boolean rhs) {
if (isEquals == false) {
return this;
}
isEquals = (lhs == rhs);
return this;
}
public EqualsBuilder append(Object[] lhs, Object[] rhs) {
preCheckObject(lhs, rhs);
if (!isEquals) {
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; i++) {
append(lhs[i], rhs[i]);
}
return this;
}
public EqualsBuilder append(long[] lhs, long[] rhs) {
preCheckObject(lhs, rhs);
if (!isEquals) {
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; i++) {
append(lhs[i], rhs[i]);
}
return this;
}
public EqualsBuilder append(int[] lhs, int[] rhs) {
preCheckObject(lhs, rhs);
if (!isEquals) {
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; i++) {
append(lhs[i], rhs[i]);
}
return this;
}
public EqualsBuilder append(short[] lhs, short[] rhs) {
preCheckObject(lhs, rhs);
if (!isEquals) {
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; i++) {
append(lhs[i], rhs[i]);
}
return this;
}
public EqualsBuilder append(char[] lhs, char[] rhs) {
preCheckObject(lhs, rhs);
if (!isEquals) {
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; i++) {
append(lhs[i], rhs[i]);
}
return this;
}
public EqualsBuilder append(byte[] lhs, byte[] rhs) {
preCheckObject(lhs, rhs);
if (!isEquals) {
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; i++) {
append(lhs[i], rhs[i]);
}
return this;
}
public EqualsBuilder append(double[] lhs, double[] rhs) {
preCheckObject(lhs, rhs);
if (!isEquals) {
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; i++) {
append(lhs[i], rhs[i]);
}
return this;
}
public EqualsBuilder append(float[] lhs, float[] rhs) {
preCheckObject(lhs, rhs);
if (!isEquals) {
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; i++) {
append(lhs[i], rhs[i]);
}
return this;
}
public EqualsBuilder append(boolean[] lhs, boolean[] rhs) {
preCheckObject(lhs, rhs);
if (!isEquals) {
return this;
}
if (lhs.length != rhs.length) {
this.setEquals(false);
return this;
}
for (int i = 0; i < lhs.length && isEquals; i++) {
append(lhs[i], rhs[i]);
}
return this;
}
private void preCheckObject(Object lhs, Object rhs) {
if (!isEquals) {
return;
}
if (lhs == rhs) {
return;
}
if (lhs == null || rhs == null) {
this.setEquals(false);
return;
}
}
private void setEquals(boolean equals) {
this.isEquals = equals;
}
public boolean isEquals() {
return this.isEquals;
}
public void reset() {
seenReferences = new IdentityHashMap, Tuple>();
this.isEquals = true;
}
}