/**
  * Recursively compute the difference between snapshots under a given directory/file.
  *
  * @param node The directory/file under which the diff is computed.
  * @param parentPath Relative path (corresponding to the snapshot root) of the node's parent.
  * @param diffReport data structure used to store the diff.
  */
 private void computeDiffRecursively(
     INode node, List<byte[]> parentPath, SnapshotDiffInfo diffReport) {
   ChildrenDiff diff = new ChildrenDiff();
   byte[][] relativePath = parentPath.toArray(new byte[parentPath.size()][]);
   if (node.isDirectory()) {
     INodeDirectory dir = node.asDirectory();
     if (dir instanceof INodeDirectoryWithSnapshot) {
       INodeDirectoryWithSnapshot sdir = (INodeDirectoryWithSnapshot) dir;
       boolean change = sdir.computeDiffBetweenSnapshots(diffReport.from, diffReport.to, diff);
       if (change) {
         diffReport.addDirDiff(sdir, relativePath, diff);
       }
     }
     ReadOnlyList<INode> children =
         dir.getChildrenList(diffReport.isFromEarlier() ? diffReport.to : diffReport.from);
     for (INode child : children) {
       final byte[] name = child.getLocalNameBytes();
       if (diff.searchIndex(ListType.CREATED, name) < 0
           && diff.searchIndex(ListType.DELETED, name) < 0) {
         parentPath.add(name);
         computeDiffRecursively(child, parentPath, diffReport);
         parentPath.remove(parentPath.size() - 1);
       }
     }
   } else if (node.isFile() && node.asFile() instanceof FileWithSnapshot) {
     FileWithSnapshot file = (FileWithSnapshot) node.asFile();
     Snapshot earlierSnapshot = diffReport.isFromEarlier() ? diffReport.from : diffReport.to;
     Snapshot laterSnapshot = diffReport.isFromEarlier() ? diffReport.to : diffReport.from;
     boolean change = file.getDiffs().changedBetweenSnapshots(earlierSnapshot, laterSnapshot);
     if (change) {
       diffReport.addFileDiff(file.asINodeFile(), relativePath);
     }
   }
 }
 @Override
 public ContentSummaryComputationContext computeContentSummary(
     final ContentSummaryComputationContext summary) {
   super.computeContentSummary(summary);
   summary.getCounts().add(Content.SNAPSHOT, snapshotsByNames.size());
   summary.getCounts().add(Content.SNAPSHOTTABLE_DIRECTORY, 1);
   return summary;
 }
  @Override
  public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix, Snapshot snapshot) {
    super.dumpTreeRecursively(out, prefix, snapshot);

    if (snapshot == null) {
      out.println();
      out.print(prefix);

      out.print("Snapshot of ");
      final String name = getLocalName();
      out.print(name.isEmpty() ? "/" : name);
      out.print(": quota=");
      out.print(getSnapshotQuota());

      int n = 0;
      for (DirectoryDiff diff : getDiffs()) {
        if (diff.isSnapshotRoot()) {
          n++;
        }
      }
      Preconditions.checkState(n == snapshotsByNames.size());
      out.print(", #snapshot=");
      out.println(n);

      dumpTreeRecursively(
          out,
          prefix,
          new Iterable<SnapshotAndINode>() {
            @Override
            public Iterator<SnapshotAndINode> iterator() {
              return new Iterator<SnapshotAndINode>() {
                final Iterator<DirectoryDiff> i = getDiffs().iterator();
                private DirectoryDiff next = findNext();

                private DirectoryDiff findNext() {
                  for (; i.hasNext(); ) {
                    final DirectoryDiff diff = i.next();
                    if (diff.isSnapshotRoot()) {
                      return diff;
                    }
                  }
                  return null;
                }

                @Override
                public boolean hasNext() {
                  return next != null;
                }

                @Override
                public SnapshotAndINode next() {
                  final Snapshot s = next.snapshot;
                  final SnapshotAndINode pair = new SnapshotAndINode(s);
                  next = findNext();
                  return pair;
                }

                @Override
                public void remove() {
                  throw new UnsupportedOperationException();
                }
              };
            }
          });
    }
  }