in firebase-database/src/testUtil/java/com/google/firebase/database/DeepEquals.java [102:254]
private static boolean deepEquals(Object a, Object b, Set visited) {
Deque<DualKey> stack = new ArrayDeque<>();
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())
&& !weakTypeMatch(dualKey.key1, dualKey.key2)) {
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()) {
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<Object, Object> map1 = (Map) dualKey.key1;
Map<Object, Object> map2 = (Map) dualKey.key2;
if (map1.size() != map2.size()) {
return false;
}
for (Map.Entry entry1 : map1.entrySet()) {
Map.Entry saveEntry2 = null;
for (Map.Entry entry2 :
map2.entrySet()) { // recurse here (yes, that makes this a Stack-based implementation
// with partial recursion in the case of Map keys).
if (deepEquals(entry1.getKey(), entry2.getKey(), visited)) {
saveEntry2 = entry2;
break;
}
}
if (saveEntry2 == null) {
return false;
}
// Defer value comparisons to future iterations.
DualKey dk = new DualKey(entry1.getValue(), saveEntry2.getValue());
if (!visited.contains(dk)) {
stack.addFirst(dk);
}
}
continue;
}
if (hasCustomEquals(dualKey.key1.getClass())) {
if (!dualKey.key1.equals(dualKey.key2)) {
return false;
}
continue;
}
Collection<Field> fields = getDeepDeclaredFields(dualKey.key1.getClass());
for (Field field : fields) {
try {
DualKey dk = new DualKey(field.get(dualKey.key1), field.get(dualKey.key2));
if (!visited.contains(dk)) {
stack.addFirst(dk);
}
} catch (Exception e) {
continue;
}
}
}
return true;
}