com.impetus.kundera.utils.DeepEquals Maven / Gradle / Ivy
/*******************************************************************************
* *
* * 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 com.impetus.kundera.utils;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.concurrent.ConcurrentHashMap;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import com.impetus.kundera.proxy.ProxyHelper;
import com.impetus.kundera.proxy.collection.ProxyCollection;
/**
* Deeply compare two (2) objects. This method will call any overridden equals()
* methods if they exist. If not, it will then proceed to do a field-by-field
* comparison, and when a non-primitive field is encountered, recursively
* continue the deep comparison. When an array is found, it will also ensure
* that the array contents are deeply equal, not requiring the array instance
* (container) to be identical. This method will successfully compare object
* graphs that have cycles (A->B->C->A). There is no need to ever use the
* Arrays.deepEquals() method as this is a true and more effective super set. *
*
* @author John DeRegnaucourt
*/
public class DeepEquals
{
private static final Map _customEquals = new ConcurrentHashMap();
private static final Map _customHash = new ConcurrentHashMap();
private static final Map> _reflectedFields = new ConcurrentHashMap>();
private static class DualKey
{
private Object _key1;
private Object _key2;
private DualKey()
{
}
private DualKey(Object k1, Object k2)
{
_key1 = k1;
_key2 = k2;
}
public boolean equals(Object other)
{
if (other == null)
{
return false;
}
if (!(other instanceof DualKey))
{
return false;
}
DualKey that = (DualKey) other;
return _key1 == that._key1 && _key2 == that._key2;
}
public int hashCode()
{
int h1 = _key1 != null ? _key1.hashCode() : 0;
int h2 = _key2 != null ? _key2.hashCode() : 0;
return h1 + h2;
}
}
public static boolean deepEquals(Object a, Object b)
{
Set visited = new HashSet();
return deepEquals(a, b, visited);
}
private static boolean deepEquals(Object a, Object b, Set visited)
{
LinkedList stack = new LinkedList();
stack.addFirst(new DualKey(a, b));
while (!stack.isEmpty())
{
DualKey dualKey = stack.removeFirst();
visited.add(dualKey);
if (dualKey._key1 == null || dualKey._key2 == null)
{
if (dualKey._key1 != dualKey._key2)
{
return false;
}
continue;
}
if (!dualKey._key1.getClass().equals(dualKey._key2.getClass()))
{
return false;
}
// Handle all [] types. In order to be equal, the arrays must be the
// same length, be of the same type, be in the same order, and all
// elements within the array must be deeply equivalent.
if (dualKey._key1.getClass().isArray())
{
if (dualKey._key1.getClass().isAssignableFrom(byte[].class))
{
if (!Arrays.equals(((byte[]) dualKey._key1), ((byte[]) dualKey._key2)))
return false;
}
else
{
int len = Array.getLength(dualKey._key1);
if (len != Array.getLength(dualKey._key2))
{
return false;
}
for (int i = 0; i < len; i++)
{
DualKey dk = new DualKey(Array.get(dualKey._key1, i), Array.get(dualKey._key2, i));
if (!visited.contains(dk))
{
stack.addFirst(dk);
}
}
}
continue;
}
// Special handle SortedSets because they are fast to compare
// because their
// elements must be in the same order to be equivalent Sets.
if (dualKey._key1 instanceof SortedSet)
{
if (!compareOrdered(dualKey, stack, visited))
{
return false;
}
continue;
}
// Handled unordered Sets. This is an expensive comparison because
// order cannot
// be assumed, therefore it runs in O(n^2) when the Sets are the
// same length.
if (dualKey._key1 instanceof Set)
{
if (!compareUnordered((Set) dualKey._key1, (Set) dualKey._key2, visited))
{
return false;
}
continue;
}
// Check any Collection that is not a Set. In these cases, element
// order
// matters, therefore this comparison is faster than using unordered
// comparison.
if (dualKey._key1 instanceof Collection)
{
if (!compareOrdered(dualKey, stack, visited))
{
return false;
}
continue;
}
// Compare two SortedMaps. This takes advantage of the fact that
// these
// Maps can be compared in O(N) time due to their ordering.
if (dualKey._key1 instanceof SortedMap)
{
Map map1 = (Map) dualKey._key1;
Map map2 = (Map) dualKey._key2;
if (map1.size() != map2.size())
{
return false;
}
Iterator i1 = map1.entrySet().iterator();
Iterator i2 = map2.entrySet().iterator();
while (i1.hasNext())
{
Map.Entry entry1 = (Map.Entry) i1.next();
Map.Entry entry2 = (Map.Entry) i2.next();
DualKey dk = new DualKey(entry1.getKey(), entry2.getKey());
if (!visited.contains(dk))
{ // Keys must match
stack.addFirst(dk);
}
dk = new DualKey(entry1.getValue(), entry2.getValue());
if (!visited.contains(dk))
{ // Values must match
stack.addFirst(dk);
}
}
continue;
}
// Compare two Unordered Maps. This works in O(N^2) time.
if (dualKey._key1 instanceof Map)
{
Map
© 2015 - 2024 Weber Informatics LLC | Privacy Policy