/**
   * Build a Map between VO name and a collection of reservation summary information. This
   * collection of reservation summary information is itself a Map between the reservation
   * description and a summary of all reservations with that same description. Those reservations
   * without a description are ignored.
   *
   * @param reservations a Map between reservation ID and a corresponding ReservationInfo object
   *     describing that reservation.
   * @return a Map of VO to reservation-description summaries.
   */
  private Map<String, Map<String, ReservationSummaryInfo>> buildSummaryInfo(
      Map<String, ReservationInfo> reservations) {
    Map<String, Map<String, ReservationSummaryInfo>> summary = new HashMap<>();

    for (Map.Entry<String, ReservationInfo> entry : reservations.entrySet()) {
      String reservationId = entry.getKey();
      ReservationInfo info = entry.getValue();

      /*
       * Ignore those reservations that don't have a state or that
       * state is a final one
       */
      if (!info.hasState() || info.getState().isFinalState()) {
        _log.debug("ignoring reservation " + reservationId + " as state is undefined or final");
        continue;
      }

      /* Skip those reservations that don't have a description */
      if (!info.hasDescription() || info.getDescription().isEmpty()) {
        _log.debug(
            "ignoring reservation " + reservationId + " as description is undefined or empty");
        continue;
      }

      /* Skip all those reservations that don't have a well-defined VO */
      if (!info.hasVo() || info.getVo().isEmpty()) {
        _log.debug("ignoring reservation " + reservationId + " as VO is undefined or empty");
        continue;
      }

      String voName = info.getVo();

      ReservationSummaryInfo thisSummary;

      Map<String, ReservationSummaryInfo> thisVoSummary;

      thisVoSummary = summary.get(voName);

      if (thisVoSummary == null) {
        thisVoSummary = new HashMap<>();
        summary.put(voName, thisVoSummary);
      }

      thisSummary = thisVoSummary.get(info.getDescription());

      if (thisSummary == null) {
        thisSummary = new ReservationSummaryInfo();
        thisVoSummary.put(info.getDescription(), thisSummary);
      }

      // update summary with this reservation.
      thisSummary.addReservationInfo(reservationId, info);
    }

    return summary;
  }
    /**
     * 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));
        }
      }
    }
 /**
  * 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 metrics that update dCache state to reflect the changes. We assume those elements that
   * should be removed have been purged
   *
   * @param update
   * @param currentSummary
   * @param futureSummary
   */
  private void addMetrics(
      StateUpdate update,
      Map<String, Map<String, ReservationSummaryInfo>> currentSummary,
      Map<String, Map<String, ReservationSummaryInfo>> futureSummary) {

    for (Map.Entry<String, Map<String, ReservationSummaryInfo>> voEntry :
        futureSummary.entrySet()) {
      String voName = voEntry.getKey();

      if (_log.isDebugEnabled()) {
        _log.debug("Checking vo " + voName);
      }

      Map<String, ReservationSummaryInfo> futureDescriptions = voEntry.getValue();
      Map<String, ReservationSummaryInfo> currentDescriptions = currentSummary.get(voName);

      StatePath voPath = buildVoPath(voName);

      /* Scan through descriptions after transition is applied */
      for (Map.Entry<String, ReservationSummaryInfo> entry : futureDescriptions.entrySet()) {
        String description = entry.getKey();
        ReservationSummaryInfo futureInfo = entry.getValue();

        /*
         * Try to establish the corresponding current
         * ReservationSummaryInfo for this description
         */
        ReservationSummaryInfo currentInfo =
            currentDescriptions != null ? currentDescriptions.get(description) : null;

        if (!futureInfo.equals(currentInfo)) {
          futureInfo.updateMetrics(
              update, voName, buildDescriptionPath(voPath, description), currentInfo);
        }
      }
    }
  }
 public boolean allocatedNeedsUpdating(ReservationSummaryInfo oldInfo) {
   return oldInfo == null || oldInfo.getAllocated() != getAllocated();
 }
 public boolean freeNeedsUpdating(ReservationSummaryInfo oldInfo) {
   return oldInfo == null || oldInfo.getFree() != getFree();
 }
 public boolean totalNeedsUpdating(ReservationSummaryInfo oldInfo) {
   return oldInfo == null || oldInfo.getTotal() != getTotal();
 }