private static boolean deepEquals()

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;
  }