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