/** Add a snapshot. */
  Snapshot addSnapshot(int id, String name) throws SnapshotException, QuotaExceededException {
    // check snapshot quota
    final int n = getNumSnapshots();
    if (n + 1 > snapshotQuota) {
      throw new SnapshotException(
          "Failed to add snapshot: there are already "
              + n
              + " snapshot(s) and the snapshot quota is "
              + snapshotQuota);
    }
    final Snapshot s = new Snapshot(id, name, this);
    final byte[] nameBytes = s.getRoot().getLocalNameBytes();
    final int i = searchSnapshot(nameBytes);
    if (i >= 0) {
      throw new SnapshotException(
          "Failed to add snapshot: there is already a "
              + "snapshot with the same name \""
              + Snapshot.getSnapshotName(s)
              + "\".");
    }

    final DirectoryDiff d = getDiffs().addDiff(s, this);
    d.snapshotINode = s.getRoot();
    snapshotsByNames.add(-i - 1, s);

    // set modification time
    updateModificationTime(Time.now(), null, null);
    s.getRoot().setModificationTime(getModificationTime(), null, null);
    return s;
  }
  @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();
                }
              };
            }
          });
    }
  }