/** Adds an {@code AccessedObject} to the list of accessed objects. */
    void addAccess(AccessedObject accessedObject) {
      boolean added = accessList.add(accessedObject);

      // if this is the first time the object has been accessed,
      // we may need to add its id to the set of descriptions,
      // and if it was a write, add it to the list.
      if (added) {
        String source = accessedObject.getSource();
        Map<Object, Object> idToDescription = sourceToObjIdAndDescription.get(source);
        if (idToDescription == null) {
          idToDescription = new HashMap<Object, Object>();
          sourceToObjIdAndDescription.put(source, idToDescription);
        }
        // if we didn't already have a description set for
        // this object, put its Id in the map so we have a
        // recorded access of it.  We use the keyset of this
        // map when checking for conflicts, so every access
        // needs to be recorded in it
        Object objId = accessedObject.getObjectId();
        if (!idToDescription.containsKey(objId)) {
          idToDescription.put(objId, null);
        }

        // keep track of the write accesses in case we later
        // need to determine whether this detail conflicted
        // with another.
        if (accessedObject.getAccessType().equals(AccessType.WRITE)) {
          writes.add(accessedObject);
        }
      }
    }
    /** Checks if the given detail conflicts with this detail. */
    boolean conflictsWith(AccessedObjectsDetailImpl other) {

      if (other == null) {
        return false;
      }

      // A conflict occurs if two details have write sets that
      // intersect, or if the write set of either set intersects
      // with the other's read set.  We therefore iterate over
      // each detail's write set and check if the second detail
      // had a source that accessed an object with the same key
      // as the write access from the first detail.
      for (AccessedObject o : writes) {

        Map<Object, Object> objIdToDescription =
            other.sourceToObjIdAndDescription.get(o.getSource());

        if (objIdToDescription != null && objIdToDescription.containsKey(o.getObjectId())) {
          return true;
        }
      }

      for (AccessedObject o : other.writes) {

        Map<Object, Object> objIdToDescription = sourceToObjIdAndDescription.get(o.getSource());

        if (objIdToDescription != null && objIdToDescription.containsKey(o.getObjectId())) {
          return true;
        }
      }

      return false;
    }
  /**
   * Returns a formatted list of accesses with one access per line.
   *
   * @param accessedObjects a {@code List} of {@code AccessedObject}
   * @return a formatted representation of the accessed objects
   */
  private String formatAccesses(List<AccessedObject> accessedObjects) {
    StringBuilder formatted = new StringBuilder();
    int count = 0;

    for (AccessedObject object : accessedObjects) {
      if (++count > accessesToShow) break;

      try {
        formatted.append(
            String.format(
                "[source: %s] %-5s %s, " + "description: %s%n",
                object.getSource(),
                object.getAccessType(),
                object.getObjectId(),
                object.getDescription()));
      } catch (Throwable t) {
        // calling toString() on the object id or the description
        // may have failed, though in practice (in the current
        // implementation) only the description will have caused
        // any trouble, so we can include some detail about
        // both the access and the failure
        formatted.append(
            String.format(
                "[source: %s] %-5s %s [%s." + "toString() threw: %s]%n",
                object.getSource(),
                object.getAccessType(),
                object.getObjectId(),
                object.getDescription().getClass(),
                t));
      }
    }

    // if we went over the max count then it means there was still
    // more to show, so add a message about the truncation
    if (--count == accessesToShow)
      formatted.append(
          String.format(
              "[%d further accesses truncated]%n", accessedObjects.size() - accessesToShow));

    return formatted.toString();
  }