public Node copy() {
   Node c = new Node(nodeId);
   if (labels != null) {
     c.labels = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
     c.labels.addAll(labels);
   } else {
     c.labels = null;
   }
   c.resource = Resources.clone(resource);
   c.running = running;
   return c;
 }
  @SuppressWarnings("unchecked")
  protected void internalUpdateLabelsOnNodes(
      Map<NodeId, Set<String>> nodeToLabels, NodeLabelUpdateOperation op) throws IOException {
    // do update labels from nodes
    Map<NodeId, Set<String>> newNMToLabels = new HashMap<NodeId, Set<String>>();
    Set<String> oldLabels;
    for (Entry<NodeId, Set<String>> entry : nodeToLabels.entrySet()) {
      NodeId nodeId = entry.getKey();
      Set<String> labels = entry.getValue();

      createHostIfNonExisted(nodeId.getHost());
      if (nodeId.getPort() == WILDCARD_PORT) {
        Host host = nodeCollections.get(nodeId.getHost());
        switch (op) {
          case REMOVE:
            removeNodeFromLabels(nodeId, labels);
            host.labels.removeAll(labels);
            for (Node node : host.nms.values()) {
              if (node.labels != null) {
                node.labels.removeAll(labels);
              }
              removeNodeFromLabels(node.nodeId, labels);
            }
            break;
          case ADD:
            addNodeToLabels(nodeId, labels);
            host.labels.addAll(labels);
            for (Node node : host.nms.values()) {
              if (node.labels != null) {
                node.labels.addAll(labels);
              }
              addNodeToLabels(node.nodeId, labels);
            }
            break;
          case REPLACE:
            replaceNodeForLabels(nodeId, host.labels, labels);
            host.labels.clear();
            host.labels.addAll(labels);
            for (Node node : host.nms.values()) {
              replaceNodeForLabels(node.nodeId, node.labels, labels);
              node.labels = null;
            }
            break;
          default:
            break;
        }
        newNMToLabels.put(nodeId, host.labels);
      } else {
        if (EnumSet.of(NodeLabelUpdateOperation.ADD, NodeLabelUpdateOperation.REPLACE)
            .contains(op)) {
          // Add and replace
          createNodeIfNonExisted(nodeId);
          Node nm = getNMInNodeSet(nodeId);
          switch (op) {
            case ADD:
              addNodeToLabels(nodeId, labels);
              if (nm.labels == null) {
                nm.labels = new HashSet<String>();
              }
              nm.labels.addAll(labels);
              break;
            case REPLACE:
              oldLabels = getLabelsByNode(nodeId);
              replaceNodeForLabels(nodeId, oldLabels, labels);
              if (nm.labels == null) {
                nm.labels = new HashSet<String>();
              }
              nm.labels.clear();
              nm.labels.addAll(labels);
              break;
            default:
              break;
          }
          newNMToLabels.put(nodeId, nm.labels);
        } else {
          // remove
          removeNodeFromLabels(nodeId, labels);
          Node nm = getNMInNodeSet(nodeId);
          if (nm.labels != null) {
            nm.labels.removeAll(labels);
            newNMToLabels.put(nodeId, nm.labels);
          }
        }
      }
    }

    if (null != dispatcher && !isDistributedNodeLabelConfiguration) {
      // In case of DistributedNodeLabelConfiguration, no need to save the the
      // NodeLabels Mapping to the back-end store, as on RM restart/failover
      // NodeLabels are collected from NM through Register/Heartbeat again
      dispatcher.getEventHandler().handle(new UpdateNodeToLabelsMappingsEvent(newNMToLabels));
    }

    // shows node->labels we added
    LOG.info(op.name() + " labels on nodes:");
    for (Entry<NodeId, Set<String>> entry : newNMToLabels.entrySet()) {
      LOG.info(
          "  NM="
              + entry.getKey()
              + ", labels=["
              + StringUtils.join(entry.getValue().iterator(), ",")
              + "]");
    }
  }