@GuardedBy("mutex")
    private void replaceLocalOccupant(@Nullable final Occupant oldOccupant, @Nullable final Occupant newOccupant, @Nullable final NodeID nodeIDToReplaceOccupantFor)
    {
        if (oldOccupant == null && newOccupant == null) {
            throw new IllegalArgumentException("Arguments 'oldOccupant' and 'newOccupant' cannot both be null (but were).");
        }
        if (oldOccupant != null && oldOccupant.isFederated()) {
            throw new IllegalArgumentException("Argument 'oldOccupant' refers to a federated user '" + oldOccupant + "' (use #replaceFederatedOccupant() instead).");
        }
        if (newOccupant != null && newOccupant.isFederated()) {
            throw new IllegalArgumentException("Argument 'newOccupant' refers to a federated user '" + newOccupant + "' (use #replaceFederatedOccupant() instead).");
        }

        Set<NodeID> nodeIDsToReplaceOccupantFor = new HashSet<>();
        if (nodeIDToReplaceOccupantFor == null) {
            nodeIDsToReplaceOccupantFor = localOccupantsByNode.keySet(); // all node ids
        } else {
            nodeIDsToReplaceOccupantFor.add(nodeIDToReplaceOccupantFor); // just the one
        }

        for (NodeID nodeID : nodeIDsToReplaceOccupantFor) {
            // Step 1: remove old occupant, if there is any
            deleteOccupantFromNode(oldOccupant, nodeID);

            // Step 2: add new occupant, if there is any
            if (newOccupant != null) {
                localOccupantsByNode.computeIfAbsent(nodeID, (n) -> new HashMap<>())
                    .computeIfAbsent(newOccupant.getRealJID(), (s) -> new HashSet<>()).add(newOccupant);
                nodeByLocalOccupant.put(newOccupant, nodeID);
            }

            Log.debug("Replaced non-federated occupant {} with {} for node {}", oldOccupant, newOccupant, nodeID);
        }
        Log.debug("Non-federated occupants remaining after replace: {}", nodeByLocalOccupant);
    }
Registers disappearance of an existing occupant, and/or appearance of a new occupant, on a specific node.

This method maintains the two different occupant lookup tables, and keeps them in sync.

Calls to this method must be mode only under guard of a write lock that been obtained from {@link #mutex}.

This method <em>only</em> should be used for non-federated/local users. For federated users, use
{@link #replaceFederatedOccupant(Occupant, Occupant)}.

@param oldOccupant An occupant that is to be removed from the registration of the referred node
@param newOccupant An occupant that is to be added to the registration of the referred node
@param nodeIDToReplaceOccupantFor The id of the node that the old/new occupant need to be (de)registered under. If null then the occupant is (de)registered for each node.
xmppserver/src/main/java/org/jivesoftware/openfire/muc/spi/OccupantManager.java