001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hdfs.server.namenode;
019
020import java.io.FileNotFoundException;
021import java.io.PrintWriter;
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027
028import org.apache.hadoop.fs.PathIsNotDirectoryException;
029import org.apache.hadoop.fs.UnresolvedLinkException;
030import org.apache.hadoop.fs.permission.PermissionStatus;
031import org.apache.hadoop.hdfs.DFSUtil;
032import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
033import org.apache.hadoop.hdfs.protocol.SnapshotAccessControlException;
034import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithCount;
035import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature;
036import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature.DirectoryDiffList;
037import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectorySnapshottable;
038import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
039import org.apache.hadoop.hdfs.util.Diff.ListType;
040import org.apache.hadoop.hdfs.util.ReadOnlyList;
041
042import com.google.common.annotations.VisibleForTesting;
043import com.google.common.base.Preconditions;
044
045/**
046 * Directory INode class.
047 */
048public class INodeDirectory extends INodeWithAdditionalFields
049    implements INodeDirectoryAttributes {
050
051  /** Cast INode to INodeDirectory. */
052  public static INodeDirectory valueOf(INode inode, Object path
053      ) throws FileNotFoundException, PathIsNotDirectoryException {
054    if (inode == null) {
055      throw new FileNotFoundException("Directory does not exist: "
056          + DFSUtil.path2String(path));
057    }
058    if (!inode.isDirectory()) {
059      throw new PathIsNotDirectoryException(DFSUtil.path2String(path));
060    }
061    return inode.asDirectory(); 
062  }
063
064  protected static final int DEFAULT_FILES_PER_DIRECTORY = 5;
065  final static byte[] ROOT_NAME = DFSUtil.string2Bytes("");
066
067  private List<INode> children = null;
068  
069  /** constructor */
070  public INodeDirectory(long id, byte[] name, PermissionStatus permissions,
071      long mtime) {
072    super(id, name, permissions, mtime, 0L);
073  }
074  
075  /**
076   * Copy constructor
077   * @param other The INodeDirectory to be copied
078   * @param adopt Indicate whether or not need to set the parent field of child
079   *              INodes to the new node
080   * @param featuresToCopy any number of features to copy to the new node.
081   *              The method will do a reference copy, not a deep copy.
082   */
083  public INodeDirectory(INodeDirectory other, boolean adopt,
084      Feature... featuresToCopy) {
085    super(other);
086    this.children = other.children;
087    if (adopt && this.children != null) {
088      for (INode child : children) {
089        child.setParent(this);
090      }
091    }
092    this.features = featuresToCopy;
093  }
094
095  /** @return true unconditionally. */
096  @Override
097  public final boolean isDirectory() {
098    return true;
099  }
100
101  /** @return this object. */
102  @Override
103  public final INodeDirectory asDirectory() {
104    return this;
105  }
106
107  /** Is this a snapshottable directory? */
108  public boolean isSnapshottable() {
109    return false;
110  }
111
112  void setQuota(long nsQuota, long dsQuota) {
113    DirectoryWithQuotaFeature quota = getDirectoryWithQuotaFeature();
114    if (quota != null) {
115      // already has quota; so set the quota to the new values
116      quota.setQuota(nsQuota, dsQuota);
117      if (!isQuotaSet() && !isRoot()) {
118        removeFeature(quota);
119      }
120    } else {
121      final Quota.Counts c = computeQuotaUsage();
122      quota = addDirectoryWithQuotaFeature(nsQuota, dsQuota);
123      quota.setSpaceConsumed(c.get(Quota.NAMESPACE), c.get(Quota.DISKSPACE));
124    }
125  }
126
127  @Override
128  public Quota.Counts getQuotaCounts() {
129    final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature();
130    return q != null? q.getQuota(): super.getQuotaCounts();
131  }
132
133  @Override
134  public void addSpaceConsumed(long nsDelta, long dsDelta, boolean verify) 
135      throws QuotaExceededException {
136    final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature();
137    if (q != null) {
138      q.addSpaceConsumed(this, nsDelta, dsDelta, verify);
139    } else {
140      addSpaceConsumed2Parent(nsDelta, dsDelta, verify);
141    }
142  }
143
144  /**
145   * If the directory contains a {@link DirectoryWithQuotaFeature}, return it;
146   * otherwise, return null.
147   */
148  public final DirectoryWithQuotaFeature getDirectoryWithQuotaFeature() {
149    return getFeature(DirectoryWithQuotaFeature.class);
150  }
151
152  /** Is this directory with quota? */
153  final boolean isWithQuota() {
154    return getDirectoryWithQuotaFeature() != null;
155  }
156
157  DirectoryWithQuotaFeature addDirectoryWithQuotaFeature(
158      long nsQuota, long dsQuota) {
159    Preconditions.checkState(!isWithQuota(), "Directory is already with quota");
160    final DirectoryWithQuotaFeature quota = new DirectoryWithQuotaFeature(
161        nsQuota, dsQuota);
162    addFeature(quota);
163    return quota;
164  }
165
166  private int searchChildren(byte[] name) {
167    return children == null? -1: Collections.binarySearch(children, name);
168  }
169  
170  public DirectoryWithSnapshotFeature addSnapshotFeature(
171      DirectoryDiffList diffs) {
172    Preconditions.checkState(!isWithSnapshot(), 
173        "Directory is already with snapshot");
174    DirectoryWithSnapshotFeature sf = new DirectoryWithSnapshotFeature(diffs);
175    addFeature(sf);
176    return sf;
177  }
178  
179  /**
180   * If feature list contains a {@link DirectoryWithSnapshotFeature}, return it;
181   * otherwise, return null.
182   */
183  public final DirectoryWithSnapshotFeature getDirectoryWithSnapshotFeature() {
184    return getFeature(DirectoryWithSnapshotFeature.class);
185  }
186
187  /** Is this file has the snapshot feature? */
188  public final boolean isWithSnapshot() {
189    return getDirectoryWithSnapshotFeature() != null;
190  }
191  
192  public DirectoryDiffList getDiffs() {
193    DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
194    return sf != null ? sf.getDiffs() : null;
195  }
196  
197  @Override
198  public INodeDirectoryAttributes getSnapshotINode(int snapshotId) {
199    DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
200    return sf == null ? this : sf.getDiffs().getSnapshotINode(snapshotId, this);
201  }
202  
203  @Override
204  public String toDetailString() {
205    DirectoryWithSnapshotFeature sf = this.getDirectoryWithSnapshotFeature();
206    return super.toDetailString() + (sf == null ? "" : ", " + sf.getDiffs()); 
207  }
208
209  /** Replace itself with an {@link INodeDirectorySnapshottable}. */
210  public INodeDirectorySnapshottable replaceSelf4INodeDirectorySnapshottable(
211      int latestSnapshotId, final INodeMap inodeMap)
212      throws QuotaExceededException {
213    Preconditions.checkState(!(this instanceof INodeDirectorySnapshottable),
214        "this is already an INodeDirectorySnapshottable, this=%s", this);
215    final INodeDirectorySnapshottable s = new INodeDirectorySnapshottable(this);
216    replaceSelf(s, inodeMap).getDirectoryWithSnapshotFeature().getDiffs()
217        .saveSelf2Snapshot(latestSnapshotId, s, this);
218    return s;
219  }
220
221  /** Replace itself with {@link INodeDirectory}. */
222  public INodeDirectory replaceSelf4INodeDirectory(final INodeMap inodeMap) {
223    Preconditions.checkState(getClass() != INodeDirectory.class,
224        "the class is already INodeDirectory, this=%s", this);
225    return replaceSelf(new INodeDirectory(this, true, this.getFeatures()),
226      inodeMap);
227  }
228
229  /** Replace itself with the given directory. */
230  private final <N extends INodeDirectory> N replaceSelf(final N newDir,
231      final INodeMap inodeMap) {
232    final INodeReference ref = getParentReference();
233    if (ref != null) {
234      ref.setReferredINode(newDir);
235      if (inodeMap != null) {
236        inodeMap.put(newDir);
237      }
238    } else {
239      final INodeDirectory parent = getParent();
240      Preconditions.checkArgument(parent != null, "parent is null, this=%s", this);
241      parent.replaceChild(this, newDir, inodeMap);
242    }
243    clear();
244    return newDir;
245  }
246  
247  /** 
248   * Replace the given child with a new child. Note that we no longer need to
249   * replace an normal INodeDirectory or INodeFile into an
250   * INodeDirectoryWithSnapshot or INodeFileUnderConstruction. The only cases
251   * for child replacement is for {@link INodeDirectorySnapshottable} and 
252   * reference nodes.
253   */
254  public void replaceChild(INode oldChild, final INode newChild,
255      final INodeMap inodeMap) {
256    Preconditions.checkNotNull(children);
257    final int i = searchChildren(newChild.getLocalNameBytes());
258    Preconditions.checkState(i >= 0);
259    Preconditions.checkState(oldChild == children.get(i)
260        || oldChild == children.get(i).asReference().getReferredINode()
261            .asReference().getReferredINode());
262    oldChild = children.get(i);
263    
264    if (oldChild.isReference() && newChild.isReference()) {
265      // both are reference nodes, e.g., DstReference -> WithName
266      final INodeReference.WithCount withCount = 
267          (WithCount) oldChild.asReference().getReferredINode();
268      withCount.removeReference(oldChild.asReference());
269    }
270    children.set(i, newChild);
271    
272    // replace the instance in the created list of the diff list
273    DirectoryWithSnapshotFeature sf = this.getDirectoryWithSnapshotFeature();
274    if (sf != null) {
275      sf.getDiffs().replaceChild(ListType.CREATED, oldChild, newChild);
276    }
277    
278    // update the inodeMap
279    if (inodeMap != null) {
280      inodeMap.put(newChild);
281    }    
282  }
283
284  INodeReference.WithName replaceChild4ReferenceWithName(INode oldChild,
285      int latestSnapshotId) {
286    Preconditions.checkArgument(latestSnapshotId != Snapshot.CURRENT_STATE_ID);
287    if (oldChild instanceof INodeReference.WithName) {
288      return (INodeReference.WithName)oldChild;
289    }
290
291    final INodeReference.WithCount withCount;
292    if (oldChild.isReference()) {
293      Preconditions.checkState(oldChild instanceof INodeReference.DstReference);
294      withCount = (INodeReference.WithCount) oldChild.asReference()
295          .getReferredINode();
296    } else {
297      withCount = new INodeReference.WithCount(null, oldChild);
298    }
299    final INodeReference.WithName ref = new INodeReference.WithName(this,
300        withCount, oldChild.getLocalNameBytes(), latestSnapshotId);
301    replaceChild(oldChild, ref, null);
302    return ref;
303  }
304
305  @Override
306  public INodeDirectory recordModification(int latestSnapshotId) 
307      throws QuotaExceededException {
308    if (isInLatestSnapshot(latestSnapshotId)
309        && !shouldRecordInSrcSnapshot(latestSnapshotId)) {
310      // add snapshot feature if necessary
311      DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
312      if (sf == null) {
313        sf = addSnapshotFeature(null);
314      }
315      // record self in the diff list if necessary
316      sf.getDiffs().saveSelf2Snapshot(latestSnapshotId, this, null);
317    }
318    return this;
319  }
320
321  /**
322   * Save the child to the latest snapshot.
323   * 
324   * @return the child inode, which may be replaced.
325   */
326  public INode saveChild2Snapshot(final INode child, final int latestSnapshotId,
327      final INode snapshotCopy) throws QuotaExceededException {
328    if (latestSnapshotId == Snapshot.CURRENT_STATE_ID) {
329      return child;
330    }
331    
332    // add snapshot feature if necessary
333    DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
334    if (sf == null) {
335      sf = this.addSnapshotFeature(null);
336    }
337    return sf.saveChild2Snapshot(this, child, latestSnapshotId, snapshotCopy);
338  }
339
340  /**
341   * @param name the name of the child
342   * @param snapshotId
343   *          if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result
344   *          from the corresponding snapshot; otherwise, get the result from
345   *          the current directory.
346   * @return the child inode.
347   */
348  public INode getChild(byte[] name, int snapshotId) {
349    DirectoryWithSnapshotFeature sf;
350    if (snapshotId == Snapshot.CURRENT_STATE_ID || 
351        (sf = getDirectoryWithSnapshotFeature()) == null) {
352      ReadOnlyList<INode> c = getCurrentChildrenList();
353      final int i = ReadOnlyList.Util.binarySearch(c, name);
354      return i < 0 ? null : c.get(i);
355    }
356    
357    return sf.getChild(this, name, snapshotId);
358  }
359  
360  /**
361   * @param snapshotId
362   *          if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result
363   *          from the corresponding snapshot; otherwise, get the result from
364   *          the current directory.
365   * @return the current children list if the specified snapshot is null;
366   *         otherwise, return the children list corresponding to the snapshot.
367   *         Note that the returned list is never null.
368   */
369  public ReadOnlyList<INode> getChildrenList(final int snapshotId) {
370    DirectoryWithSnapshotFeature sf;
371    if (snapshotId == Snapshot.CURRENT_STATE_ID
372        || (sf = this.getDirectoryWithSnapshotFeature()) == null) {
373      return getCurrentChildrenList();
374    }
375    return sf.getChildrenList(this, snapshotId);
376  }
377  
378  private ReadOnlyList<INode> getCurrentChildrenList() {
379    return children == null ? ReadOnlyList.Util.<INode> emptyList()
380        : ReadOnlyList.Util.asReadOnlyList(children);
381  }
382
383  /** @return the {@link INodesInPath} containing only the last inode. */
384  INodesInPath getLastINodeInPath(String path, boolean resolveLink
385      ) throws UnresolvedLinkException {
386    return INodesInPath.resolve(this, getPathComponents(path), 1, resolveLink);
387  }
388
389  /** @return the {@link INodesInPath} containing all inodes in the path. */
390  INodesInPath getINodesInPath(String path, boolean resolveLink
391      ) throws UnresolvedLinkException {
392    final byte[][] components = getPathComponents(path);
393    return INodesInPath.resolve(this, components, components.length, resolveLink);
394  }
395
396  /** @return the last inode in the path. */
397  INode getNode(String path, boolean resolveLink) 
398    throws UnresolvedLinkException {
399    return getLastINodeInPath(path, resolveLink).getINode(0);
400  }
401
402  /**
403   * @return the INode of the last component in src, or null if the last
404   * component does not exist.
405   * @throws UnresolvedLinkException if symlink can't be resolved
406   * @throws SnapshotAccessControlException if path is in RO snapshot
407   */
408  INode getINode4Write(String src, boolean resolveLink)
409      throws UnresolvedLinkException, SnapshotAccessControlException {
410    return getINodesInPath4Write(src, resolveLink).getLastINode();
411  }
412
413  /**
414   * @return the INodesInPath of the components in src
415   * @throws UnresolvedLinkException if symlink can't be resolved
416   * @throws SnapshotAccessControlException if path is in RO snapshot
417   */
418  INodesInPath getINodesInPath4Write(String src, boolean resolveLink)
419      throws UnresolvedLinkException, SnapshotAccessControlException {
420    final byte[][] components = INode.getPathComponents(src);
421    INodesInPath inodesInPath = INodesInPath.resolve(this, components,
422        components.length, resolveLink);
423    if (inodesInPath.isSnapshot()) {
424      throw new SnapshotAccessControlException(
425          "Modification on a read-only snapshot is disallowed");
426    }
427    return inodesInPath;
428  }
429
430  /**
431   * Given a child's name, return the index of the next child
432   *
433   * @param name a child's name
434   * @return the index of the next child
435   */
436  static int nextChild(ReadOnlyList<INode> children, byte[] name) {
437    if (name.length == 0) { // empty name
438      return 0;
439    }
440    int nextPos = ReadOnlyList.Util.binarySearch(children, name) + 1;
441    if (nextPos >= 0) {
442      return nextPos;
443    }
444    return -nextPos;
445  }
446  
447  /**
448   * Remove the specified child from this directory.
449   */
450  public boolean removeChild(INode child, int latestSnapshotId)
451      throws QuotaExceededException {
452    if (isInLatestSnapshot(latestSnapshotId)) {
453      // create snapshot feature if necessary
454      DirectoryWithSnapshotFeature sf = this.getDirectoryWithSnapshotFeature();
455      if (sf == null) {
456        sf = this.addSnapshotFeature(null);
457      }
458      return sf.removeChild(this, child, latestSnapshotId);
459    }
460    return removeChild(child);
461  }
462  
463  /** 
464   * Remove the specified child from this directory.
465   * The basic remove method which actually calls children.remove(..).
466   *
467   * @param child the child inode to be removed
468   * 
469   * @return true if the child is removed; false if the child is not found.
470   */
471  public boolean removeChild(final INode child) {
472    final int i = searchChildren(child.getLocalNameBytes());
473    if (i < 0) {
474      return false;
475    }
476
477    final INode removed = children.remove(i);
478    Preconditions.checkState(removed == child);
479    return true;
480  }
481
482  /**
483   * Add a child inode to the directory.
484   * 
485   * @param node INode to insert
486   * @param setModTime set modification time for the parent node
487   *                   not needed when replaying the addition and 
488   *                   the parent already has the proper mod time
489   * @return false if the child with this name already exists; 
490   *         otherwise, return true;
491   */
492  public boolean addChild(INode node, final boolean setModTime,
493      final int latestSnapshotId) throws QuotaExceededException {
494    final int low = searchChildren(node.getLocalNameBytes());
495    if (low >= 0) {
496      return false;
497    }
498
499    if (isInLatestSnapshot(latestSnapshotId)) {
500      // create snapshot feature if necessary
501      DirectoryWithSnapshotFeature sf = this.getDirectoryWithSnapshotFeature();
502      if (sf == null) {
503        sf = this.addSnapshotFeature(null);
504      }
505      return sf.addChild(this, node, setModTime, latestSnapshotId);
506    }
507    addChild(node, low);
508    if (setModTime) {
509      // update modification time of the parent directory
510      updateModificationTime(node.getModificationTime(), latestSnapshotId);
511    }
512    return true;
513  }
514
515  public boolean addChild(INode node) {
516    final int low = searchChildren(node.getLocalNameBytes());
517    if (low >= 0) {
518      return false;
519    }
520    addChild(node, low);
521    return true;
522  }
523
524  /**
525   * Add the node to the children list at the given insertion point.
526   * The basic add method which actually calls children.add(..).
527   */
528  private void addChild(final INode node, final int insertionPoint) {
529    if (children == null) {
530      children = new ArrayList<INode>(DEFAULT_FILES_PER_DIRECTORY);
531    }
532    node.setParent(this);
533    children.add(-insertionPoint - 1, node);
534
535    if (node.getGroupName() == null) {
536      node.setGroup(getGroupName());
537    }
538  }
539
540  @Override
541  public Quota.Counts computeQuotaUsage(Quota.Counts counts, boolean useCache,
542      int lastSnapshotId) {
543    final DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
544    
545    // we are computing the quota usage for a specific snapshot here, i.e., the
546    // computation only includes files/directories that exist at the time of the
547    // given snapshot
548    if (sf != null && lastSnapshotId != Snapshot.CURRENT_STATE_ID
549        && !(useCache && isQuotaSet())) {
550      ReadOnlyList<INode> childrenList = getChildrenList(lastSnapshotId);
551      for (INode child : childrenList) {
552        child.computeQuotaUsage(counts, useCache, lastSnapshotId);
553      }
554      counts.add(Quota.NAMESPACE, 1);
555      return counts;
556    }
557    
558    // compute the quota usage in the scope of the current directory tree
559    final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature();
560    if (useCache && q != null && q.isQuotaSet()) { // use the cached quota
561      return q.addNamespaceDiskspace(counts);
562    } else {
563      useCache = q != null && !q.isQuotaSet() ? false : useCache;
564      return computeDirectoryQuotaUsage(counts, useCache, lastSnapshotId);
565    }
566  }
567
568  private Quota.Counts computeDirectoryQuotaUsage(Quota.Counts counts,
569      boolean useCache, int lastSnapshotId) {
570    if (children != null) {
571      for (INode child : children) {
572        child.computeQuotaUsage(counts, useCache, lastSnapshotId);
573      }
574    }
575    return computeQuotaUsage4CurrentDirectory(counts);
576  }
577  
578  /** Add quota usage for this inode excluding children. */
579  public Quota.Counts computeQuotaUsage4CurrentDirectory(Quota.Counts counts) {
580    counts.add(Quota.NAMESPACE, 1);
581    // include the diff list
582    DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
583    if (sf != null) {
584      sf.computeQuotaUsage4CurrentDirectory(counts);
585    }
586    return counts;
587  }
588
589  @Override
590  public ContentSummaryComputationContext computeContentSummary(
591      ContentSummaryComputationContext summary) {
592    final DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
593    if (sf != null) {
594      sf.computeContentSummary4Snapshot(summary.getCounts());
595    }
596    final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature();
597    if (q != null) {
598      return q.computeContentSummary(this, summary);
599    } else {
600      return computeDirectoryContentSummary(summary);
601    }
602  }
603
604  ContentSummaryComputationContext computeDirectoryContentSummary(
605      ContentSummaryComputationContext summary) {
606    ReadOnlyList<INode> childrenList = getChildrenList(Snapshot.CURRENT_STATE_ID);
607    // Explicit traversing is done to enable repositioning after relinquishing
608    // and reacquiring locks.
609    for (int i = 0;  i < childrenList.size(); i++) {
610      INode child = childrenList.get(i);
611      byte[] childName = child.getLocalNameBytes();
612
613      long lastYieldCount = summary.getYieldCount();
614      child.computeContentSummary(summary);
615
616      // Check whether the computation was paused in the subtree.
617      // The counts may be off, but traversing the rest of children
618      // should be made safe.
619      if (lastYieldCount == summary.getYieldCount()) {
620        continue;
621      }
622      // The locks were released and reacquired. Check parent first.
623      if (getParent() == null) {
624        // Stop further counting and return whatever we have so far.
625        break;
626      }
627      // Obtain the children list again since it may have been modified.
628      childrenList = getChildrenList(Snapshot.CURRENT_STATE_ID);
629      // Reposition in case the children list is changed. Decrement by 1
630      // since it will be incremented when loops.
631      i = nextChild(childrenList, childName) - 1;
632    }
633
634    // Increment the directory count for this directory.
635    summary.getCounts().add(Content.DIRECTORY, 1);
636    // Relinquish and reacquire locks if necessary.
637    summary.yield();
638    return summary;
639  }
640  
641  /**
642   * This method is usually called by the undo section of rename.
643   * 
644   * Before calling this function, in the rename operation, we replace the
645   * original src node (of the rename operation) with a reference node (WithName
646   * instance) in both the children list and a created list, delete the
647   * reference node from the children list, and add it to the corresponding
648   * deleted list.
649   * 
650   * To undo the above operations, we have the following steps in particular:
651   * 
652   * <pre>
653   * 1) remove the WithName node from the deleted list (if it exists) 
654   * 2) replace the WithName node in the created list with srcChild 
655   * 3) add srcChild back as a child of srcParent. Note that we already add 
656   * the node into the created list of a snapshot diff in step 2, we do not need
657   * to add srcChild to the created list of the latest snapshot.
658   * </pre>
659   * 
660   * We do not need to update quota usage because the old child is in the 
661   * deleted list before. 
662   * 
663   * @param oldChild
664   *          The reference node to be removed/replaced
665   * @param newChild
666   *          The node to be added back
667   * @throws QuotaExceededException should not throw this exception
668   */
669  public void undoRename4ScrParent(final INodeReference oldChild,
670      final INode newChild) throws QuotaExceededException {
671    DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
672    Preconditions.checkState(sf != null,
673        "Directory does not have snapshot feature");
674    sf.getDiffs().removeChild(ListType.DELETED, oldChild);
675    sf.getDiffs().replaceChild(ListType.CREATED, oldChild, newChild);
676    addChild(newChild, true, Snapshot.CURRENT_STATE_ID);
677  }
678  
679  /**
680   * Undo the rename operation for the dst tree, i.e., if the rename operation
681   * (with OVERWRITE option) removes a file/dir from the dst tree, add it back
682   * and delete possible record in the deleted list.  
683   */
684  public void undoRename4DstParent(final INode deletedChild,
685      int latestSnapshotId) throws QuotaExceededException {
686    DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
687    Preconditions.checkState(sf != null,
688        "Directory does not have snapshot feature");
689    boolean removeDeletedChild = sf.getDiffs().removeChild(ListType.DELETED,
690        deletedChild);
691    int sid = removeDeletedChild ? Snapshot.CURRENT_STATE_ID : latestSnapshotId;
692    final boolean added = addChild(deletedChild, true, sid);
693    // update quota usage if adding is successfully and the old child has not
694    // been stored in deleted list before
695    if (added && !removeDeletedChild) {
696      final Quota.Counts counts = deletedChild.computeQuotaUsage();
697      addSpaceConsumed(counts.get(Quota.NAMESPACE),
698          counts.get(Quota.DISKSPACE), false);
699    }
700  }
701
702  /** Set the children list to null. */
703  public void clearChildren() {
704    this.children = null;
705  }
706
707  @Override
708  public void clear() {
709    super.clear();
710    clearChildren();
711  }
712
713  /** Call cleanSubtree(..) recursively down the subtree. */
714  public Quota.Counts cleanSubtreeRecursively(final int snapshot,
715      int prior, final BlocksMapUpdateInfo collectedBlocks,
716      final List<INode> removedINodes, final Map<INode, INode> excludedNodes, 
717      final boolean countDiffChange) throws QuotaExceededException {
718    Quota.Counts counts = Quota.Counts.newInstance();
719    // in case of deletion snapshot, since this call happens after we modify
720    // the diff list, the snapshot to be deleted has been combined or renamed
721    // to its latest previous snapshot. (besides, we also need to consider nodes
722    // created after prior but before snapshot. this will be done in 
723    // DirectoryWithSnapshotFeature)
724    int s = snapshot != Snapshot.CURRENT_STATE_ID
725        && prior != Snapshot.NO_SNAPSHOT_ID ? prior : snapshot;
726    for (INode child : getChildrenList(s)) {
727      if (snapshot != Snapshot.CURRENT_STATE_ID && excludedNodes != null
728          && excludedNodes.containsKey(child)) {
729        continue;
730      } else {
731        Quota.Counts childCounts = child.cleanSubtree(snapshot, prior,
732            collectedBlocks, removedINodes, countDiffChange);
733        counts.add(childCounts);
734      }
735    }
736    return counts;
737  }
738
739  @Override
740  public void destroyAndCollectBlocks(final BlocksMapUpdateInfo collectedBlocks,
741      final List<INode> removedINodes) {
742    final DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
743    if (sf != null) {
744      sf.clear(this, collectedBlocks, removedINodes);
745    }
746    for (INode child : getChildrenList(Snapshot.CURRENT_STATE_ID)) {
747      child.destroyAndCollectBlocks(collectedBlocks, removedINodes);
748    }
749    clear();
750    removedINodes.add(this);
751  }
752  
753  @Override
754  public Quota.Counts cleanSubtree(final int snapshotId, int priorSnapshotId,
755      final BlocksMapUpdateInfo collectedBlocks,
756      final List<INode> removedINodes, final boolean countDiffChange)
757      throws QuotaExceededException {
758    DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
759    // there is snapshot data
760    if (sf != null) {
761      return sf.cleanDirectory(this, snapshotId, priorSnapshotId,
762          collectedBlocks, removedINodes, countDiffChange);
763    }
764    // there is no snapshot data
765    if (priorSnapshotId == Snapshot.NO_SNAPSHOT_ID
766        && snapshotId == Snapshot.CURRENT_STATE_ID) {
767      // destroy the whole subtree and collect blocks that should be deleted
768      Quota.Counts counts = Quota.Counts.newInstance();
769      this.computeQuotaUsage(counts, true);
770      destroyAndCollectBlocks(collectedBlocks, removedINodes);
771      return counts; 
772    } else {
773      // process recursively down the subtree
774      Quota.Counts counts = cleanSubtreeRecursively(snapshotId, priorSnapshotId,
775          collectedBlocks, removedINodes, null, countDiffChange);
776      if (isQuotaSet()) {
777        getDirectoryWithQuotaFeature().addSpaceConsumed2Cache(
778            -counts.get(Quota.NAMESPACE), -counts.get(Quota.DISKSPACE));
779      }
780      return counts;
781    }
782  }
783  
784  /**
785   * Compare the metadata with another INodeDirectory
786   */
787  @Override
788  public boolean metadataEquals(INodeDirectoryAttributes other) {
789    return other != null
790        && getQuotaCounts().equals(other.getQuotaCounts())
791        && getPermissionLong() == other.getPermissionLong();
792  }
793  
794  /*
795   * The following code is to dump the tree recursively for testing.
796   * 
797   *      \- foo   (INodeDirectory@33dd2717)
798   *        \- sub1   (INodeDirectory@442172)
799   *          +- file1   (INodeFile@78392d4)
800   *          +- file2   (INodeFile@78392d5)
801   *          +- sub11   (INodeDirectory@8400cff)
802   *            \- file3   (INodeFile@78392d6)
803   *          \- z_file4   (INodeFile@45848712)
804   */
805  static final String DUMPTREE_EXCEPT_LAST_ITEM = "+-"; 
806  static final String DUMPTREE_LAST_ITEM = "\\-";
807  @VisibleForTesting
808  @Override
809  public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix,
810      final int snapshot) {
811    super.dumpTreeRecursively(out, prefix, snapshot);
812    out.print(", childrenSize=" + getChildrenList(snapshot).size());
813    final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature();
814    if (q != null) {
815      out.print(", " + q);
816    }
817    if (this instanceof Snapshot.Root) {
818      out.print(", snapshotId=" + snapshot);
819    }
820    out.println();
821
822    if (prefix.length() >= 2) {
823      prefix.setLength(prefix.length() - 2);
824      prefix.append("  ");
825    }
826    dumpTreeRecursively(out, prefix, new Iterable<SnapshotAndINode>() {
827      final Iterator<INode> i = getChildrenList(snapshot).iterator();
828      
829      @Override
830      public Iterator<SnapshotAndINode> iterator() {
831        return new Iterator<SnapshotAndINode>() {
832          @Override
833          public boolean hasNext() {
834            return i.hasNext();
835          }
836
837          @Override
838          public SnapshotAndINode next() {
839            return new SnapshotAndINode(snapshot, i.next());
840          }
841
842          @Override
843          public void remove() {
844            throw new UnsupportedOperationException();
845          }
846        };
847      }
848    });
849  }
850
851  /**
852   * Dump the given subtrees.
853   * @param prefix The prefix string that each line should print.
854   * @param subs The subtrees.
855   */
856  @VisibleForTesting
857  protected static void dumpTreeRecursively(PrintWriter out,
858      StringBuilder prefix, Iterable<SnapshotAndINode> subs) {
859    if (subs != null) {
860      for(final Iterator<SnapshotAndINode> i = subs.iterator(); i.hasNext();) {
861        final SnapshotAndINode pair = i.next();
862        prefix.append(i.hasNext()? DUMPTREE_EXCEPT_LAST_ITEM: DUMPTREE_LAST_ITEM);
863        pair.inode.dumpTreeRecursively(out, prefix, pair.snapshotId);
864        prefix.setLength(prefix.length() - 2);
865      }
866    }
867  }
868
869  /** A pair of Snapshot and INode objects. */
870  protected static class SnapshotAndINode {
871    public final int snapshotId;
872    public final INode inode;
873
874    public SnapshotAndINode(int snapshot, INode inode) {
875      this.snapshotId = snapshot;
876      this.inode = inode;
877    }
878  }
879
880  public final int getChildrenNum(final int snapshotId) {
881    return getChildrenList(snapshotId).size();
882  }
883}