/**
   * Add a standardised amount of information about an interface. This is in the form:
   *
   * <pre>
   *     [interfaces]
   *       |
   *       |
   *       +--[ id ] (branch)
   *       |   |
   *       |   +-- "order"  (integer metric: 1 .. 2 ..)
   *       |   +-- "FQDN" (string metric: the host's FQDN, as presented by the door)
   *       |   +-- "address" (string metric: the host's address; e.g., "127.0.0.1")
   *       |   +-- "address-type"    (string metric: "IPv4", "IPv6" or "unknown")
   *       |   +-- "scope"    (string metric: "IPv4", "IPv6" or "unknown")
   *       |
   * </pre>
   *
   * @param update The StateUpdate to append the new metrics.
   * @param parentPath the path that the id branch will be added.
   * @param lifetime how long the created metrics should last.
   */
  private void addInterfaceInfo(
      StateUpdate update, StatePath parentPath, InetAddress address, long lifetime) {
    StatePath pathToInterfaceBranch = parentPath.newChild(address.getHostAddress());

    String hostName = address.getHostName();
    update.appendUpdate(
        pathToInterfaceBranch.newChild("FQDN"), new StringStateValue(hostName, lifetime));

    String urlName = (isInetAddress(hostName)) ? toUriString(address) : hostName;
    update.appendUpdate(
        pathToInterfaceBranch.newChild("url-name"), new StringStateValue(urlName, lifetime));

    update.appendUpdate(
        pathToInterfaceBranch.newChild("address"),
        new StringStateValue(address.getHostAddress(), lifetime));
    update.appendUpdate(
        pathToInterfaceBranch.newChild("address-type"),
        new StringStateValue(
            (address instanceof Inet4Address)
                ? "IPv4"
                : (address instanceof Inet6Address) ? "IPv6" : "unknown",
            lifetime));
    update.appendUpdate(
        pathToInterfaceBranch.newChild("scope"),
        new StringStateValue(NetworkUtils.InetAddressScope.of(address).toString().toLowerCase()));
  }
  /**
   * Adjust the StateUpdate object so that those descriptions that have disappeared are purged.
   *
   * @param update
   * @param currentDescriptions
   * @param futureDescriptions
   */
  private void purgeMissingSummaries(
      StateUpdate update,
      Map<String, Map<String, ReservationSummaryInfo>> currentVoInfo,
      Map<String, Map<String, ReservationSummaryInfo>> futureVoInfo) {

    for (Map.Entry<String, Map<String, ReservationSummaryInfo>> voEntry :
        currentVoInfo.entrySet()) {
      String voName = voEntry.getKey();
      Set<String> currentDescriptions = voEntry.getValue().keySet();

      StatePath voBasePath = buildVoPath(voName);

      Map<String, ReservationSummaryInfo> futureDescriptions = futureVoInfo.get(voName);

      // If this VO is gone completely, purge everything and move on.
      if (futureDescriptions == null) {
        update.purgeUnder(voBasePath);
        continue;
      }

      // Otherwise, purge those descriptions that have gone.
      for (String thisDescription : currentDescriptions) {
        if (!futureDescriptions.containsKey(thisDescription)) {
          update.purgeUnder(buildDescriptionPath(voBasePath, thisDescription));
        }
      }
    }
  }
 /**
  * Purge all IDs that are not present in this ReservationSummaryInfo but are missing from a new
  * version of ReservationSummaryInfo
  *
  * @param update the StateUpdate to add adjust.
  * @param basePath the base path of the IDs
  * @param newInfo the new version of this ReservationSummaryInfo
  */
 public void purgeMissingIds(
     StateUpdate update, StatePath basePath, ReservationSummaryInfo newInfo) {
   for (String id : _ids) {
     if (!newInfo.hasId(id)) {
       update.purgeUnder(basePath.newChild(id));
     }
   }
 }
  /**
   * Add additional state-update to record information about a door.
   *
   * @param update the StateUpdate we are to add metrics to.
   * @param pathToDoor a StatePath under which we are to add data.
   * @param info the information about the door.
   * @param lifetime the duration, in seconds, for this information
   */
  private void addDoorInfo(
      StateUpdate update, StatePath pathToDoor, LoginBrokerInfo info, long lifetime) {
    StatePath pathToProtocol = pathToDoor.newChild("protocol");

    conditionalAddString(update, pathToProtocol, "engine", info.getProtocolEngine(), lifetime);
    conditionalAddString(update, pathToProtocol, "family", info.getProtocolFamily(), lifetime);
    conditionalAddString(update, pathToProtocol, "version", info.getProtocolVersion(), lifetime);
    conditionalAddString(update, pathToProtocol, "root", info.getRoot(), lifetime);

    update.appendUpdate(
        pathToDoor.newChild("load"), new FloatingPointStateValue(info.getLoad(), lifetime));
    update.appendUpdate(
        pathToDoor.newChild("port"), new IntegerStateValue(info.getPort(), lifetime));
    update.appendUpdate(
        pathToDoor.newChild("cell"), new StringStateValue(info.getCellName(), lifetime));
    update.appendUpdate(
        pathToDoor.newChild("domain"), new StringStateValue(info.getDomainName(), lifetime));
    update.appendUpdate(
        pathToDoor.newChild("update-time"), new IntegerStateValue(info.getUpdateTime(), lifetime));

    info.getAddresses()
        .stream()
        .forEach(i -> addInterfaceInfo(update, pathToDoor.newChild("interfaces"), i, lifetime));

    update.appendUpdateCollection(pathToDoor.newChild("tags"), info.getTags(), lifetime);
  }
    /**
     * Update the provided StateUpdate object so that later processing this StateUpdate will alter
     * dCache state such that it reflects the information held in this object.
     *
     * @param update the StateUpdate to use
     * @param voName the name of the VO
     * @param basePath the StatePath under which metrics will be added
     * @param oldInfo the previous ReservationSummaryInfo or null if this description is new.
     */
    public void updateMetrics(
        StateUpdate update, String voName, StatePath basePath, ReservationSummaryInfo oldInfo) {

      StatePath spacePath = basePath.newChild(PATH_ELEMENT_SPACE_BRANCH);

      if (totalNeedsUpdating(oldInfo)) {
        update.appendUpdate(
            spacePath.newChild(PATH_ELEMENT_TOTAL_METRIC), new IntegerStateValue(getTotal(), true));
      }

      if (freeNeedsUpdating(oldInfo)) {
        update.appendUpdate(
            spacePath.newChild(PATH_ELEMENT_FREE_METRIC), new IntegerStateValue(getFree(), true));
      }

      if (allocatedNeedsUpdating(oldInfo)) {
        update.appendUpdate(
            spacePath.newChild(PATH_ELEMENT_ALLOCATED_METRIC),
            new IntegerStateValue(getAllocated(), true));
      }

      if (usedNeedsUpdating(oldInfo)) {
        update.appendUpdate(
            spacePath.newChild(PATH_ELEMENT_USED_METRIC), new IntegerStateValue(getUsed(), true));
      }

      StatePath resvPath = basePath.newChild(PATH_ELEMENT_RESERVATIONS_BRANCH);

      /* Activity if there was an existing summary information */
      if (oldInfo != null) {
        oldInfo.purgeMissingIds(update, resvPath, this);

        /* Add those entries that are new */
        for (String newId : _ids) {
          if (!oldInfo.hasId(newId)) {
            update.appendUpdate(resvPath.newChild(newId), new StateComposite(true));
          }
        }

      } else {
        /* Add the VO name (we need to this only once) */
        update.appendUpdate(
            basePath.newChild(PATH_ELEMENT_VO_NAME_METRIC), new StringStateValue(voName, true));

        /* Add all IDs */
        for (String id : _ids) {
          update.appendUpdate(resvPath.newChild(id), new StateComposite(true));
        }
      }
    }
 /**
  * Add a string metric at a specific point in the State tree if the value is not NULL.
  *
  * @param update the StateUpdate to append with the metric definition
  * @param parentPath the path to the parent branch for this metric
  * @param name the name of the metric
  * @param value the metric's value, or null if the metric should not be added.
  * @param storeTime how long, in seconds the metric should be preserved.
  */
 private void conditionalAddString(
     StateUpdate update, StatePath parentPath, String name, String value, long storeTime) {
   if (value != null) {
     update.appendUpdate(parentPath.newChild(name), new StringStateValue(value, storeTime));
   }
 }