/**
  * reset the state (before each round)
  *
  * @param clusterSnapshot the current cluster snapshot
  */
 public final synchronized void resetState(ClusterSnapshot clusterSnapshot) {
   this.clusterSnapshot = clusterSnapshot;
   accessesByPrimaryOwner = new Accesses[clusterSnapshot.size()];
   for (int i = 0; i < accessesByPrimaryOwner.length; ++i) {
     accessesByPrimaryOwner[i] = new Accesses();
   }
   hasAccessesCalculated = false;
 }
  /** calculates the object request list to request to each member */
  public final synchronized void calculateAccesses() {
    if (hasAccessesCalculated) {
      return;
    }
    hasAccessesCalculated = true;

    if (log.isTraceEnabled()) {
      log.trace("Calculating accessed keys for data placement optimization");
    }

    RemoteTopKeyRequest request = new RemoteTopKeyRequest(streamLibContainer.getCapacity() * 2);

    request.merge(streamLibContainer.getTopKFrom(REMOTE_PUT, maxNumberOfKeysToRequest), 2);
    request.merge(streamLibContainer.getTopKFrom(REMOTE_GET, maxNumberOfKeysToRequest), 1);

    sortObjectsByPrimaryOwner(request.toRequestMap(maxNumberOfKeysToRequest), true);

    request.clear();

    LocalTopKeyRequest localTopKeyRequest = new LocalTopKeyRequest();

    localTopKeyRequest.merge(streamLibContainer.getTopKFrom(LOCAL_PUT), 2);
    localTopKeyRequest.merge(streamLibContainer.getTopKFrom(LOCAL_GET), 1);

    sortObjectsByPrimaryOwner(localTopKeyRequest.toRequestMap(), false);

    request.clear();

    if (log.isTraceEnabled()) {
      StringBuilder stringBuilder = new StringBuilder("Accesses:\n");
      for (int i = 0; i < accessesByPrimaryOwner.length; ++i) {
        stringBuilder
            .append(clusterSnapshot.get(i))
            .append(" ==> ")
            .append(accessesByPrimaryOwner[i])
            .append("\n");
      }
      log.debug(stringBuilder);
    }

    streamLibContainer.resetStat(REMOTE_GET);
    streamLibContainer.resetStat(LOCAL_GET);
    streamLibContainer.resetStat(REMOTE_PUT);
    streamLibContainer.resetStat(LOCAL_PUT);
  }
  /**
   * returns the request object list for the {@code member}
   *
   * @param member the destination member
   * @return the request object list. It can be empty if no requests are necessary
   */
  public final synchronized ObjectRequest getObjectRequestForAddress(Address member) {
    int addressIndex = clusterSnapshot.indexOf(member);

    if (addressIndex == -1) {
      log.warnf(
          "Trying to get Object Requests to send to %s but it does not exists in cluster snapshot %s",
          member, clusterSnapshot);
      return new ObjectRequest(null, null);
    }

    ObjectRequest request = accessesByPrimaryOwner[addressIndex].toObjectRequest();

    if (log.isInfoEnabled()) {
      log.debugf(
          "Getting request list for %s. Request is %s",
          member, request.toString(log.isDebugEnabled()));
    }

    return request;
  }
  /**
   * sort the keys and number of access by primary owner
   *
   * @param accesses the remote accesses
   * @param remote true if the accesses to process are from remote access, false otherwise
   */
  @SuppressWarnings("unchecked")
  private void sortObjectsByPrimaryOwner(Map<Object, Long> accesses, boolean remote) {
    Map<Object, List<Address>> primaryOwners =
        getDefaultConsistentHash().locateAll(accesses.keySet(), 1);

    for (Entry<Object, Long> entry : accesses.entrySet()) {
      Object key = entry.getKey();
      Address primaryOwner = primaryOwners.remove(key).get(0);
      int addressIndex = clusterSnapshot.indexOf(primaryOwner);

      if (addressIndex == -1) {
        log.warnf(
            "Primary owner [%s] does not exists in cluster snapshot %s",
            primaryOwner, clusterSnapshot);
        continue;
      }

      accessesByPrimaryOwner[addressIndex].add(entry.getKey(), entry.getValue(), remote);
    }
  }