/**
   * Converts the given DIN and its descendants.
   *
   * <p>Enter/leave with bin field latched, although bin field will change to last inserted slot.
   */
  private void convertDin(final DIN din, final byte[] binKey) {
    din.latch();
    try {
      for (int i = 0; i < din.getNEntries(); i += 1) {

        final IN child = din.fetchIN(i, CacheMode.DEFAULT);

        assert (!child.isBINDelta(false));

        if (child instanceof DBIN) {
          final DBIN dbin = (DBIN) child;
          dbin.latch();
          try {
            for (int j = 0; j < dbin.getNEntries(); j += 1) {
              if (!isLNDeleted(dbin, j)) {
                convertDbinSlot(dbin, j, binKey);
              }
            }
            assert dbin.verifyMemorySize();

            /* Count DBIN obsolete. */
            if (dbin.getLastLoggedLsn() != DbLsn.NULL_LSN) {
              localTracker.countObsoleteNodeInexact(
                  dbin.getLastLoggedLsn(), dbin.getLogType(), 0, dbin.getDatabase());
            }
          } finally {
            dbin.releaseLatch();
          }
        } else {
          convertDin((DIN) child, binKey);
        }

        /* Evict DIN child. */
        din.detachNode(i, false /*updateLsn*/, -1 /*lsn*/);
      }

      assert din.verifyMemorySize();

      /* Count DIN and DupCountLN obsolete. */
      if (din.getLastLoggedLsn() != DbLsn.NULL_LSN) {
        localTracker.countObsoleteNodeInexact(
            din.getLastLoggedLsn(), din.getLogType(), 0, din.getDatabase());
      }
      final ChildReference dupCountRef = din.getDupCountLNRef();
      if (dupCountRef != null && dupCountRef.getLsn() != DbLsn.NULL_LSN) {
        localTracker.countObsoleteNodeInexact(
            dupCountRef.getLsn(), LogEntryType.LOG_DUPCOUNTLN, 0, din.getDatabase());
      }
    } finally {
      din.releaseLatch();
    }
  }
  /**
   * Converts the given DIN and its descendants.
   *
   * <p>Enter/leave with bin field latched, although bin field will change to last inserted slot.
   */
  private void convertDin(final DIN din, final byte[] binKey) {
    din.latch();
    try {
      for (int i = 0; i < din.getNEntries(); i += 1) {
        final IN child = (IN) din.fetchTargetWithExclusiveLatch(i);
        if (child instanceof DBIN) {
          final DBIN dbin = (DBIN) child;
          dbin.latch();
          try {
            for (int j = 0; j < dbin.getNEntries(); j += 1) {
              if (!isLNDeleted(dbin, j)) {
                convertDbinSlot(dbin, j, binKey);
              }
            }
            assert dbin.verifyMemorySize();

            /* Count DBIN obsolete. */
            if (dbin.getLastLoggedVersion() != DbLsn.NULL_LSN) {
              localTracker.countObsoleteNodeInexact(
                  dbin.getLastLoggedVersion(), dbin.getLogType(), 0, dbin.getDatabase());
            }
          } finally {
            dbin.releaseLatch();
          }
        } else {
          convertDin((DIN) child, binKey);
        }

        /* Evict DIN child. */
        din.updateNode(i, null, null);
        envImpl.getInMemoryINs().remove(child);
      }
      assert din.verifyMemorySize();

      /* Count DIN and DupCountLN obsolete. */
      if (din.getLastLoggedVersion() != DbLsn.NULL_LSN) {
        localTracker.countObsoleteNodeInexact(
            din.getLastLoggedVersion(), din.getLogType(), 0, din.getDatabase());
      }
      final ChildReference dupCountRef = din.getDupCountLNRef();
      if (dupCountRef != null && dupCountRef.getLsn() != DbLsn.NULL_LSN) {
        localTracker.countObsoleteNodeInexact(
            dupCountRef.getLsn(), LogEntryType.LOG_DUPCOUNTLN, 0, din.getDatabase());
      }
    } finally {
      din.releaseLatch();
    }
  }
  /**
   * Converts the given DBIN slot, leaving bin/index set to the inserted BIN slot.
   *
   * <p>Enter/leave with bin field latched, although bin field may change.
   *
   * <p>If slot is inserted into current bin, leave bin field unchanged and set index field to
   * inserted slot.
   *
   * <p>If slot is inserted into a different bin, set bin/index fields to inserted slot.
   */
  private void convertDbinSlot(final DBIN dbin, final int dbinIndex, final byte[] binKey) {

    final byte[] newKey = DupKeyData.replaceData(binKey, dbin.getKey(dbinIndex));

    if (DEBUG) {
      System.out.println("DupConvert DBIN LN " + Key.dumpString(newKey, 0));
    }

    /*
     * If the current BIN can hold the new slot, don't bother to do a
     * search to find it.
     */
    if (bin.needsSplitting() || !bin.isKeyInBounds(newKey)) {

      /* Compact keys after finishing with a BIN. */
      bin.compactMemory();

      /* Evict without latches, before moving to a new BIN. */
      bin.releaseLatch();
      envImpl.daemonEviction(false /*backgroundIO*/);

      /* Find a BIN for insertion, split if necessary. */
      bin = dbin.getDatabase().getTree().searchSplitsAllowed(newKey, CacheMode.UNCHANGED);
    }

    final int newIndex =
        bin.insertEntry1(
            null /*ln*/,
            newKey,
            null /*data*/,
            dbin.getLsn(dbinIndex),
            dbin.getState(dbinIndex),
            false);

    if ((newIndex & IN.INSERT_SUCCESS) == 0) {
      throw EnvironmentFailureException.unexpectedState(
          "Key not inserted: " + Key.dumpString(newKey, 0) + " DB: " + dbin.getDatabase().getId());
    }

    index = newIndex & ~IN.INSERT_SUCCESS;

    /*
     * Evict LN from DBIN slot. Although we don't explicitly load DBIN LNs,
     * it may have been loaded by recovery.
     */
    dbin.detachNode(dbinIndex, false /*updateLsn*/, -1 /*lsn*/);

    nConverted += 1;
  }