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