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}