001/* 002 * Copyright 2013 Christopher Pheby 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.jadira.reflection.equals; 017 018import java.lang.reflect.Modifier; 019import java.util.IdentityHashMap; 020 021import org.jadira.reflection.access.api.ClassAccess; 022import org.jadira.reflection.access.api.ClassAccessFactory; 023import org.jadira.reflection.access.api.FieldAccess; 024import org.jadira.reflection.access.api.MethodAccess; 025import org.jadira.reflection.access.portable.PortableClassAccessFactory; 026import org.jadira.reflection.access.unsafe.UnsafeClassAccessFactory; 027import org.jadira.reflection.core.identity.Tuple; 028import org.jadira.reflection.core.platform.FeatureDetection; 029 030/** 031 * This class is based on the capabilities provided by Commons-Lang's 032 * EqualsBuilder. It is not a complete "drop-in" replacement, but is near enough 033 * in functionality and in terms of its API that it should be quite 034 * straightforward to switch from one class to the other. 035 * 036 * EqualsBuilder makes use of Jadira's reflection capabilities. This means, like 037 * Cloning there is a brief warm-up time entailed in preparing introspection 038 * when a class is first processed. If you are already performing cloning on a 039 * type you will not be impacted by this as any necessary warm-up will have already taken place. 040 * 041 * Once the initial warmup has been performed, you will, usually, find enhanced performance 042 * compared to standard reflection based approaches. The strategy to be used can be configured. 043 * 044 * Where objects that are reachable from the object being compared do not override equals(), 045 * the default behaviour is to rely on {@link Object#equals(Object)} which compares object 046 * identity for those objects. This is also the behaviour of Commons Lang. You 047 * can modify this to reflect into any reachable object that does not override equals by 048 * enabling the defaultDeepReflect property. 049 * 050 * Transient fields are not compared by the current implementation of this 051 * class. 052 */ 053public class EqualsBuilder { 054 055 private static final ThreadLocal<EqualsBuilder> reflectionBuilder = new ThreadLocal<EqualsBuilder>() { 056 protected EqualsBuilder initialValue() { 057 return new EqualsBuilder(); 058 }; 059 }; 060 061 private IdentityHashMap<Tuple<Object, Object>, Tuple<Object, Object>> seenReferences; 062 063 /** 064 * If the fields tested are equal. The default value is <code>true</code>. 065 */ 066 private boolean isEquals = true; 067 068 private boolean defaultDeepReflect = false; 069 070 private ClassAccessFactory classAccessFactory; 071 072 public EqualsBuilder() { 073 seenReferences = new IdentityHashMap<Tuple<Object, Object>, Tuple<Object, Object>>(); 074 075 if (FeatureDetection.hasUnsafe()) { 076 this.classAccessFactory = UnsafeClassAccessFactory.get(); 077 } else { 078 this.classAccessFactory = PortableClassAccessFactory.get(); 079 } 080 } 081 082 public EqualsBuilder withDefaultDeepReflect(boolean newDefaultDeepReflect) { 083 this.defaultDeepReflect = newDefaultDeepReflect; 084 return this; 085 } 086 087 public EqualsBuilder withClassAccessFactory( 088 ClassAccessFactory classAccessFactory) { 089 this.classAccessFactory = classAccessFactory; 090 return this; 091 } 092 093 public static boolean reflectionEquals(Object lhs, Object rhs) { 094 095 if (lhs == rhs) { 096 return true; 097 } 098 if (lhs == null || rhs == null) { 099 return false; 100 } 101 102 Class<?> lhsClass = lhs.getClass(); 103 Class<?> rhsClass = rhs.getClass(); 104 105 Class<?> testClass; 106 if (lhsClass.isInstance(rhs)) { 107 if (rhsClass.isInstance(lhs)) { 108 testClass = lhsClass; 109 } else { 110 testClass = rhsClass; 111 } 112 } else if (rhsClass.isInstance(lhs)) { 113 if (lhsClass.isInstance(rhs)) { 114 testClass = rhsClass; 115 } else { 116 testClass = lhsClass; 117 } 118 } else { 119 return false; 120 } 121 122 EqualsBuilder equalsBuilder = reflectionBuilder.get(); 123 // In case we recurse into this method 124 reflectionBuilder.set(null); 125 126 equalsBuilder.reset(); 127 128 ClassAccess<?> classAccess = equalsBuilder.classAccessFactory 129 .getClassAccess(testClass); 130 131 try { 132 while ((classAccess.getType() != Object.class) 133 && (!classAccess.providesEquals())) { 134 equalsBuilder.reflectionAppend(lhs, rhs, classAccess); 135 classAccess = classAccess.getSuperClassAccess(); 136 } 137 } catch (IllegalArgumentException e) { 138 return false; 139 } 140 141 if (classAccess.getType() != Object.class) { 142 143 final MethodAccess<Object> methodAccess; 144 try { 145 @SuppressWarnings("unchecked") 146 final MethodAccess<Object> myMethodAccess = (MethodAccess<Object>) classAccess.getDeclaredMethodAccess(classAccess.getType().getMethod( 147 "equals", Object.class)); 148 methodAccess = myMethodAccess; 149 } catch (NoSuchMethodException e) { 150 throw new IllegalStateException("Cannot find equals() method"); 151 } catch (SecurityException e) { 152 throw new IllegalStateException("Cannot find equals() method"); 153 } 154 equalsBuilder.setEquals(((Boolean) methodAccess.invoke(lhs, rhs)) 155 .booleanValue()); 156 } 157 158 final boolean isEqual = equalsBuilder.isEquals(); 159 reflectionBuilder.set(equalsBuilder); 160 161 return isEqual; 162 } 163 164 private void reflectionAppend(Object lhs, Object rhs, 165 ClassAccess<?> classAccess) { 166 167 Tuple<Object, Object> t = Tuple.of(lhs, rhs); 168 if (seenReferences.containsKey(t)) { 169 return; 170 } 171 seenReferences.put(t, t); 172 173 @SuppressWarnings("unchecked") 174 ClassAccess<Object> classAccessInHierarchy = (ClassAccess<Object>) classAccess; 175 176 while (classAccessInHierarchy != null) { 177 178 FieldAccess<Object>[] fields = (FieldAccess<Object>[]) classAccessInHierarchy.getDeclaredFieldAccessors(); 179 180 for (int i = 0; (i < fields.length) && (isEquals); i++) { 181 FieldAccess<Object> f = fields[i]; 182 if ((f.field().getName().indexOf('$') == -1) 183 && (!Modifier.isTransient(f.field().getModifiers())) 184 && (!Modifier.isStatic(f.field().getModifiers()))) { 185 186 Class<?> type = f.fieldClass(); 187 if (type.isPrimitive()) { 188 if (java.lang.Boolean.TYPE == type) { 189 append(f.getBooleanValue(lhs), f.getBooleanValue(rhs)); 190 } else if (java.lang.Byte.TYPE == type) { 191 append(f.getByteValue(lhs), f.getByteValue(rhs)); 192 } else if (java.lang.Character.TYPE == type) { 193 append(f.getCharValue(lhs), f.getCharValue(rhs)); 194 } else if (java.lang.Short.TYPE == type) { 195 append(f.getShortValue(lhs), f.getShortValue(rhs)); 196 } else if (java.lang.Integer.TYPE == type) { 197 append(f.getIntValue(lhs), f.getIntValue(rhs)); 198 } else if (java.lang.Long.TYPE == type) { 199 append(f.getLongValue(lhs), f.getLongValue(rhs)); 200 } else if (java.lang.Float.TYPE == type) { 201 append(f.getFloatValue(lhs), f.getFloatValue(rhs)); 202 } else if (java.lang.Double.TYPE == type) { 203 append(f.getDoubleValue(lhs), f.getDoubleValue(rhs)); 204 } 205 } else { 206 append(f.getValue(lhs), f.getValue(rhs)); 207 } 208 } 209 } 210 211 classAccessInHierarchy = classAccessInHierarchy.getSuperClassAccess(); 212 } 213 } 214 215 public EqualsBuilder append(Object lhs, Object rhs) { 216 217 preCheckObject(lhs, rhs); 218 219 Class<?> lhsClass = lhs.getClass(); 220 if (!lhsClass.isArray()) { 221 if (defaultDeepReflect) { 222 reflectionEquals(lhs, rhs); 223 } else { 224 this.setEquals(lhs.equals(rhs)); 225 } 226 } else if (lhs.getClass() != rhs.getClass()) { 227 this.setEquals(false); 228 } else if (lhs instanceof long[]) { 229 append((long[]) lhs, (long[]) rhs); 230 } else if (lhs instanceof int[]) { 231 append((int[]) lhs, (int[]) rhs); 232 } else if (lhs instanceof short[]) { 233 append((short[]) lhs, (short[]) rhs); 234 } else if (lhs instanceof char[]) { 235 append((char[]) lhs, (char[]) rhs); 236 } else if (lhs instanceof byte[]) { 237 append((byte[]) lhs, (byte[]) rhs); 238 } else if (lhs instanceof double[]) { 239 append((double[]) lhs, (double[]) rhs); 240 } else if (lhs instanceof float[]) { 241 append((float[]) lhs, (float[]) rhs); 242 } else if (lhs instanceof boolean[]) { 243 append((boolean[]) lhs, (boolean[]) rhs); 244 } else { 245 append((Object[]) lhs, (Object[]) rhs); 246 } 247 248 return this; 249 } 250 251 public EqualsBuilder append(long lhs, long rhs) { 252 if (isEquals == false) { 253 return this; 254 } 255 isEquals = (lhs == rhs); 256 return this; 257 } 258 259 public EqualsBuilder append(int lhs, int rhs) { 260 if (isEquals == false) { 261 return this; 262 } 263 isEquals = (lhs == rhs); 264 return this; 265 } 266 267 public EqualsBuilder append(short lhs, short rhs) { 268 if (isEquals == false) { 269 return this; 270 } 271 isEquals = (lhs == rhs); 272 return this; 273 } 274 275 public EqualsBuilder append(char lhs, char rhs) { 276 if (isEquals == false) { 277 return this; 278 } 279 isEquals = (lhs == rhs); 280 return this; 281 } 282 283 public EqualsBuilder append(byte lhs, byte rhs) { 284 if (isEquals == false) { 285 return this; 286 } 287 isEquals = (lhs == rhs); 288 return this; 289 } 290 291 public EqualsBuilder append(double lhs, double rhs) { 292 if (isEquals == false) { 293 return this; 294 } 295 return append(Double.doubleToLongBits(lhs), 296 Double.doubleToLongBits(rhs)); 297 } 298 299 public EqualsBuilder append(float lhs, float rhs) { 300 301 if (isEquals == false) { 302 return this; 303 } 304 return append(Float.floatToIntBits(lhs), Float.floatToIntBits(rhs)); 305 } 306 307 public EqualsBuilder append(boolean lhs, boolean rhs) { 308 if (isEquals == false) { 309 return this; 310 } 311 isEquals = (lhs == rhs); 312 return this; 313 } 314 315 public EqualsBuilder append(Object[] lhs, Object[] rhs) { 316 317 preCheckObject(lhs, rhs); 318 if (!isEquals) { 319 return this; 320 } 321 322 if (lhs.length != rhs.length) { 323 this.setEquals(false); 324 return this; 325 } 326 327 for (int i = 0; i < lhs.length && isEquals; i++) { 328 append(lhs[i], rhs[i]); 329 } 330 return this; 331 } 332 333 public EqualsBuilder append(long[] lhs, long[] rhs) { 334 335 preCheckObject(lhs, rhs); 336 if (!isEquals) { 337 return this; 338 } 339 340 if (lhs.length != rhs.length) { 341 this.setEquals(false); 342 return this; 343 } 344 345 for (int i = 0; i < lhs.length && isEquals; i++) { 346 append(lhs[i], rhs[i]); 347 } 348 return this; 349 } 350 351 public EqualsBuilder append(int[] lhs, int[] rhs) { 352 353 preCheckObject(lhs, rhs); 354 if (!isEquals) { 355 return this; 356 } 357 358 if (lhs.length != rhs.length) { 359 this.setEquals(false); 360 return this; 361 } 362 363 for (int i = 0; i < lhs.length && isEquals; i++) { 364 append(lhs[i], rhs[i]); 365 } 366 return this; 367 } 368 369 public EqualsBuilder append(short[] lhs, short[] rhs) { 370 371 preCheckObject(lhs, rhs); 372 if (!isEquals) { 373 return this; 374 } 375 376 if (lhs.length != rhs.length) { 377 this.setEquals(false); 378 return this; 379 } 380 381 for (int i = 0; i < lhs.length && isEquals; i++) { 382 append(lhs[i], rhs[i]); 383 } 384 return this; 385 } 386 387 public EqualsBuilder append(char[] lhs, char[] rhs) { 388 389 preCheckObject(lhs, rhs); 390 if (!isEquals) { 391 return this; 392 } 393 394 if (lhs.length != rhs.length) { 395 this.setEquals(false); 396 return this; 397 } 398 399 for (int i = 0; i < lhs.length && isEquals; i++) { 400 append(lhs[i], rhs[i]); 401 } 402 return this; 403 } 404 405 public EqualsBuilder append(byte[] lhs, byte[] rhs) { 406 407 preCheckObject(lhs, rhs); 408 if (!isEquals) { 409 return this; 410 } 411 412 if (lhs.length != rhs.length) { 413 this.setEquals(false); 414 return this; 415 } 416 417 for (int i = 0; i < lhs.length && isEquals; i++) { 418 append(lhs[i], rhs[i]); 419 } 420 return this; 421 } 422 423 public EqualsBuilder append(double[] lhs, double[] rhs) { 424 425 preCheckObject(lhs, rhs); 426 if (!isEquals) { 427 return this; 428 } 429 430 if (lhs.length != rhs.length) { 431 this.setEquals(false); 432 return this; 433 } 434 435 for (int i = 0; i < lhs.length && isEquals; i++) { 436 append(lhs[i], rhs[i]); 437 } 438 return this; 439 } 440 441 public EqualsBuilder append(float[] lhs, float[] rhs) { 442 443 preCheckObject(lhs, rhs); 444 if (!isEquals) { 445 return this; 446 } 447 448 if (lhs.length != rhs.length) { 449 this.setEquals(false); 450 return this; 451 } 452 453 for (int i = 0; i < lhs.length && isEquals; i++) { 454 append(lhs[i], rhs[i]); 455 } 456 return this; 457 } 458 459 public EqualsBuilder append(boolean[] lhs, boolean[] rhs) { 460 461 preCheckObject(lhs, rhs); 462 if (!isEquals) { 463 return this; 464 } 465 466 if (lhs.length != rhs.length) { 467 this.setEquals(false); 468 return this; 469 } 470 471 for (int i = 0; i < lhs.length && isEquals; i++) { 472 append(lhs[i], rhs[i]); 473 } 474 return this; 475 } 476 477 private void preCheckObject(Object lhs, Object rhs) { 478 if (!isEquals) { 479 return; 480 } 481 if (lhs == rhs) { 482 return; 483 } 484 if (lhs == null || rhs == null) { 485 this.setEquals(false); 486 return; 487 } 488 } 489 490 private void setEquals(boolean equals) { 491 this.isEquals = equals; 492 } 493 494 public boolean isEquals() { 495 return this.isEquals; 496 } 497 498 public void reset() { 499 seenReferences = new IdentityHashMap<Tuple<Object, Object>, Tuple<Object, Object>>(); 500 this.isEquals = true; 501 } 502}