org.apache.hadoop.hdfs.server.namenode.INodeDirectory.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hdfs.server.namenode.INodeDirectory.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.hadoop.hdfs.server.namenode;

import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.hadoop.fs.PathIsNotDirectoryException;
import org.apache.hadoop.fs.permission.FsAction;
import org.apache.hadoop.fs.permission.PermissionStatus;
import org.apache.hadoop.fs.StorageType;
import org.apache.hadoop.fs.XAttr;
import org.apache.hadoop.hdfs.DFSUtil;
import org.apache.hadoop.hdfs.protocol.SnapshotException;
import org.apache.hadoop.hdfs.server.blockmanagement.BlockStoragePolicySuite;
import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithCount;
import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectorySnapshottableFeature;
import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature;
import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature.DirectoryDiffList;
import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
import org.apache.hadoop.hdfs.util.ReadOnlyList;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import org.apache.hadoop.security.AccessControlException;

import static org.apache.hadoop.hdfs.protocol.HdfsConstants.BLOCK_STORAGE_POLICY_ID_UNSPECIFIED;

/**
 * Directory INode class.
 */
public class INodeDirectory extends INodeWithAdditionalFields implements INodeDirectoryAttributes {

    /** Cast INode to INodeDirectory. */
    public static INodeDirectory valueOf(INode inode, Object path)
            throws FileNotFoundException, PathIsNotDirectoryException {
        if (inode == null) {
            throw new FileNotFoundException("Directory does not exist: " + DFSUtil.path2String(path));
        }
        if (!inode.isDirectory()) {
            throw new PathIsNotDirectoryException(DFSUtil.path2String(path));
        }
        return inode.asDirectory();
    }

    // Profiling shows that most of the file lists are between 1 and 4 elements.
    // Thus allocate the corresponding ArrayLists with a small initial capacity.
    public static final int DEFAULT_FILES_PER_DIRECTORY = 2;

    static final byte[] ROOT_NAME = DFSUtil.string2Bytes("");

    private List<INode> children = null;

    /** constructor */
    public INodeDirectory(long id, byte[] name, PermissionStatus permissions, long mtime) {
        super(id, name, permissions, mtime, 0L);
    }

    /**
     * Copy constructor
     * @param other The INodeDirectory to be copied
     * @param adopt Indicate whether or not need to set the parent field of child
     *              INodes to the new node
     * @param featuresToCopy any number of features to copy to the new node.
     *              The method will do a reference copy, not a deep copy.
     */
    public INodeDirectory(INodeDirectory other, boolean adopt, Feature... featuresToCopy) {
        super(other);
        this.children = other.children;
        if (adopt && this.children != null) {
            for (INode child : children) {
                child.setParent(this);
            }
        }
        this.features = featuresToCopy;
        AclFeature aclFeature = getFeature(AclFeature.class);
        if (aclFeature != null) {
            // for the de-duplication of AclFeature
            removeFeature(aclFeature);
            addFeature(AclStorage.addAclFeature(aclFeature));
        }
    }

    /** @return true unconditionally. */
    @Override
    public final boolean isDirectory() {
        return true;
    }

    /** @return this object. */
    @Override
    public final INodeDirectory asDirectory() {
        return this;
    }

    @Override
    public byte getLocalStoragePolicyID() {
        XAttrFeature f = getXAttrFeature();
        XAttr xattr = f == null ? null : f.getXAttr(BlockStoragePolicySuite.getStoragePolicyXAttrPrefixedName());
        if (xattr != null) {
            return (xattr.getValue())[0];
        }
        return BLOCK_STORAGE_POLICY_ID_UNSPECIFIED;
    }

    @Override
    public byte getStoragePolicyID() {
        byte id = getLocalStoragePolicyID();
        if (id != BLOCK_STORAGE_POLICY_ID_UNSPECIFIED) {
            return id;
        }
        // if it is unspecified, check its parent
        return getParent() != null ? getParent().getStoragePolicyID() : BLOCK_STORAGE_POLICY_ID_UNSPECIFIED;
    }

    void setQuota(BlockStoragePolicySuite bsps, long nsQuota, long ssQuota, StorageType type) {
        DirectoryWithQuotaFeature quota = getDirectoryWithQuotaFeature();
        if (quota != null) {
            // already has quota; so set the quota to the new values
            if (type != null) {
                quota.setQuota(ssQuota, type);
            } else {
                quota.setQuota(nsQuota, ssQuota);
            }
            if (!isQuotaSet() && !isRoot()) {
                removeFeature(quota);
            }
        } else {
            final QuotaCounts c = computeQuotaUsage(bsps);
            DirectoryWithQuotaFeature.Builder builder = new DirectoryWithQuotaFeature.Builder()
                    .nameSpaceQuota(nsQuota);
            if (type != null) {
                builder.typeQuota(type, ssQuota);
            } else {
                builder.storageSpaceQuota(ssQuota);
            }
            addDirectoryWithQuotaFeature(builder.build()).setSpaceConsumed(c);
        }
    }

    @Override
    public QuotaCounts getQuotaCounts() {
        final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature();
        return q != null ? q.getQuota() : super.getQuotaCounts();
    }

    @Override
    public void addSpaceConsumed(QuotaCounts counts) {
        super.addSpaceConsumed(counts);

        final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature();
        if (q != null && isQuotaSet()) {
            q.addSpaceConsumed2Cache(counts);
        }
    }

    /**
     * If the directory contains a {@link DirectoryWithQuotaFeature}, return it;
     * otherwise, return null.
     */
    public final DirectoryWithQuotaFeature getDirectoryWithQuotaFeature() {
        return getFeature(DirectoryWithQuotaFeature.class);
    }

    /** Is this directory with quota? */
    final boolean isWithQuota() {
        return getDirectoryWithQuotaFeature() != null;
    }

    DirectoryWithQuotaFeature addDirectoryWithQuotaFeature(DirectoryWithQuotaFeature q) {
        Preconditions.checkState(!isWithQuota(), "Directory is already with quota");
        addFeature(q);
        return q;
    }

    int searchChildren(byte[] name) {
        return children == null ? -1 : Collections.binarySearch(children, name);
    }

    public DirectoryWithSnapshotFeature addSnapshotFeature(DirectoryDiffList diffs) {
        Preconditions.checkState(!isWithSnapshot(), "Directory is already with snapshot");
        DirectoryWithSnapshotFeature sf = new DirectoryWithSnapshotFeature(diffs);
        addFeature(sf);
        return sf;
    }

    /**
     * If feature list contains a {@link DirectoryWithSnapshotFeature}, return it;
     * otherwise, return null.
     */
    public final DirectoryWithSnapshotFeature getDirectoryWithSnapshotFeature() {
        return getFeature(DirectoryWithSnapshotFeature.class);
    }

    /** Is this file has the snapshot feature? */
    public final boolean isWithSnapshot() {
        return getDirectoryWithSnapshotFeature() != null;
    }

    public DirectoryDiffList getDiffs() {
        DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
        return sf != null ? sf.getDiffs() : null;
    }

    @Override
    public INodeDirectoryAttributes getSnapshotINode(int snapshotId) {
        DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
        return sf == null ? this : sf.getDiffs().getSnapshotINode(snapshotId, this);
    }

    @Override
    public String toDetailString() {
        DirectoryWithSnapshotFeature sf = this.getDirectoryWithSnapshotFeature();
        return super.toDetailString() + (sf == null ? "" : ", " + sf.getDiffs());
    }

    public DirectorySnapshottableFeature getDirectorySnapshottableFeature() {
        return getFeature(DirectorySnapshottableFeature.class);
    }

    public boolean isSnapshottable() {
        return getDirectorySnapshottableFeature() != null;
    }

    /**
     * Check if this directory is a descendant directory
     * of a snapshot root directory.
     * @param snapshotRootDir the snapshot root directory
     * @return true if this directory is a descendant of snapshot root
     */
    public boolean isDescendantOfSnapshotRoot(INodeDirectory snapshotRootDir) {
        Preconditions.checkArgument(snapshotRootDir.isSnapshottable());
        INodeDirectory dir = this;
        while (dir != null) {
            if (dir.equals(snapshotRootDir)) {
                return true;
            }
            dir = dir.getParent();
        }
        return false;
    }

    public Snapshot getSnapshot(byte[] snapshotName) {
        return getDirectorySnapshottableFeature().getSnapshot(snapshotName);
    }

    public void setSnapshotQuota(int snapshotQuota) {
        getDirectorySnapshottableFeature().setSnapshotQuota(snapshotQuota);
    }

    public Snapshot addSnapshot(int id, String name, final LeaseManager leaseManager,
            final boolean captureOpenFiles, int maxSnapshotLimit) throws SnapshotException {
        return getDirectorySnapshottableFeature().addSnapshot(this, id, name, leaseManager, captureOpenFiles,
                maxSnapshotLimit);
    }

    public Snapshot removeSnapshot(ReclaimContext reclaimContext, String snapshotName) throws SnapshotException {
        return getDirectorySnapshottableFeature().removeSnapshot(reclaimContext, this, snapshotName);
    }

    public void renameSnapshot(String path, String oldName, String newName) throws SnapshotException {
        getDirectorySnapshottableFeature().renameSnapshot(path, oldName, newName);
    }

    /** add DirectorySnapshottableFeature */
    public void addSnapshottableFeature() {
        Preconditions.checkState(!isSnapshottable(), "this is already snapshottable, this=%s", this);
        DirectoryWithSnapshotFeature s = this.getDirectoryWithSnapshotFeature();
        final DirectorySnapshottableFeature snapshottable = new DirectorySnapshottableFeature(s);
        if (s != null) {
            this.removeFeature(s);
        }
        this.addFeature(snapshottable);
    }

    /** remove DirectorySnapshottableFeature */
    public void removeSnapshottableFeature() {
        DirectorySnapshottableFeature s = getDirectorySnapshottableFeature();
        Preconditions.checkState(s != null, "The dir does not have snapshottable feature: this=%s", this);
        this.removeFeature(s);
        if (s.getDiffs().asList().size() > 0) {
            // add a DirectoryWithSnapshotFeature back
            DirectoryWithSnapshotFeature sf = new DirectoryWithSnapshotFeature(s.getDiffs());
            addFeature(sf);
        }
    }

    /** 
     * Replace the given child with a new child. Note that we no longer need to
     * replace an normal INodeDirectory or INodeFile into an
     * INodeDirectoryWithSnapshot or INodeFileUnderConstruction. The only cases
     * for child replacement is for reference nodes.
     */
    public void replaceChild(INode oldChild, final INode newChild, final INodeMap inodeMap) {
        Preconditions.checkNotNull(children);
        final int i = searchChildren(newChild.getLocalNameBytes());
        Preconditions.checkState(i >= 0);
        Preconditions.checkState(oldChild == children.get(i)
                || oldChild == children.get(i).asReference().getReferredINode().asReference().getReferredINode());
        oldChild = children.get(i);

        if (oldChild.isReference() && newChild.isReference()) {
            // both are reference nodes, e.g., DstReference -> WithName
            final INodeReference.WithCount withCount = (WithCount) oldChild.asReference().getReferredINode();
            withCount.removeReference(oldChild.asReference());
        }
        children.set(i, newChild);

        // replace the instance in the created list of the diff list
        DirectoryWithSnapshotFeature sf = this.getDirectoryWithSnapshotFeature();
        if (sf != null) {
            sf.getDiffs().replaceCreatedChild(oldChild, newChild);
        }

        // update the inodeMap
        if (inodeMap != null) {
            inodeMap.put(newChild);
        }
    }

    INodeReference.WithName replaceChild4ReferenceWithName(INode oldChild, int latestSnapshotId) {
        Preconditions.checkArgument(latestSnapshotId != Snapshot.CURRENT_STATE_ID);
        if (oldChild instanceof INodeReference.WithName) {
            return (INodeReference.WithName) oldChild;
        }

        final INodeReference.WithCount withCount;
        if (oldChild.isReference()) {
            Preconditions.checkState(oldChild instanceof INodeReference.DstReference);
            withCount = (INodeReference.WithCount) oldChild.asReference().getReferredINode();
        } else {
            withCount = new INodeReference.WithCount(null, oldChild);
        }
        final INodeReference.WithName ref = new INodeReference.WithName(this, withCount,
                oldChild.getLocalNameBytes(), latestSnapshotId);
        replaceChild(oldChild, ref, null);
        return ref;
    }

    @Override
    public void recordModification(int latestSnapshotId) {
        if (isInLatestSnapshot(latestSnapshotId) && !shouldRecordInSrcSnapshot(latestSnapshotId)) {
            // add snapshot feature if necessary
            DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
            if (sf == null) {
                sf = addSnapshotFeature(null);
            }
            // record self in the diff list if necessary
            sf.getDiffs().saveSelf2Snapshot(latestSnapshotId, this, null);
        }
    }

    /**
     * Save the child to the latest snapshot.
     * 
     * @return the child inode, which may be replaced.
     */
    public INode saveChild2Snapshot(final INode child, final int latestSnapshotId, final INode snapshotCopy) {
        if (latestSnapshotId == Snapshot.CURRENT_STATE_ID) {
            return child;
        }

        // add snapshot feature if necessary
        DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
        if (sf == null) {
            sf = this.addSnapshotFeature(null);
        }
        return sf.saveChild2Snapshot(this, child, latestSnapshotId, snapshotCopy);
    }

    /**
     * @param name the name of the child
     * @param snapshotId
     *          if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result
     *          from the corresponding snapshot; otherwise, get the result from
     *          the current directory.
     * @return the child inode.
     */
    public INode getChild(byte[] name, int snapshotId) {
        DirectoryWithSnapshotFeature sf;
        if (snapshotId == Snapshot.CURRENT_STATE_ID || (sf = getDirectoryWithSnapshotFeature()) == null) {
            ReadOnlyList<INode> c = getCurrentChildrenList();
            final int i = ReadOnlyList.Util.binarySearch(c, name);
            return i < 0 ? null : c.get(i);
        }

        return sf.getChild(this, name, snapshotId);
    }

    /**
     * Search for the given INode in the children list and the deleted lists of
     * snapshots.
     * @return {@link Snapshot#CURRENT_STATE_ID} if the inode is in the children
     * list; {@link Snapshot#NO_SNAPSHOT_ID} if the inode is neither in the
     * children list nor in any snapshot; otherwise the snapshot id of the
     * corresponding snapshot diff list.
     */
    public int searchChild(INode inode) {
        INode child = getChild(inode.getLocalNameBytes(), Snapshot.CURRENT_STATE_ID);
        if (child != inode) {
            // inode is not in parent's children list, thus inode must be in
            // snapshot. identify the snapshot id and later add it into the path
            DirectoryDiffList diffs = getDiffs();
            if (diffs == null) {
                return Snapshot.NO_SNAPSHOT_ID;
            }
            return diffs.findSnapshotDeleted(inode);
        } else {
            return Snapshot.CURRENT_STATE_ID;
        }
    }

    /**
     * @param snapshotId
     *          if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result
     *          from the corresponding snapshot; otherwise, get the result from
     *          the current directory.
     * @return the current children list if the specified snapshot is null;
     *         otherwise, return the children list corresponding to the snapshot.
     *         Note that the returned list is never null.
     */
    public ReadOnlyList<INode> getChildrenList(final int snapshotId) {
        DirectoryWithSnapshotFeature sf;
        if (snapshotId == Snapshot.CURRENT_STATE_ID || (sf = this.getDirectoryWithSnapshotFeature()) == null) {
            return getCurrentChildrenList();
        }
        return sf.getChildrenList(this, snapshotId);
    }

    private ReadOnlyList<INode> getCurrentChildrenList() {
        return children == null ? ReadOnlyList.Util.<INode>emptyList() : ReadOnlyList.Util.asReadOnlyList(children);
    }

    /**
     * Given a child's name, return the index of the next child
     *
     * @param name a child's name
     * @return the index of the next child
     */
    static int nextChild(ReadOnlyList<INode> children, byte[] name) {
        if (name.length == 0) { // empty name
            return 0;
        }
        int nextPos = ReadOnlyList.Util.binarySearch(children, name) + 1;
        if (nextPos >= 0) {
            return nextPos;
        }
        return -nextPos;
    }

    /**
     * Remove the specified child from this directory.
     */
    public boolean removeChild(INode child, int latestSnapshotId) {
        if (isInLatestSnapshot(latestSnapshotId)) {
            // create snapshot feature if necessary
            DirectoryWithSnapshotFeature sf = this.getDirectoryWithSnapshotFeature();
            if (sf == null) {
                sf = this.addSnapshotFeature(null);
            }
            return sf.removeChild(this, child, latestSnapshotId);
        }
        return removeChild(child);
    }

    /** 
     * Remove the specified child from this directory.
     * The basic remove method which actually calls children.remove(..).
     *
     * @param child the child inode to be removed
     * 
     * @return true if the child is removed; false if the child is not found.
     */
    public boolean removeChild(final INode child) {
        final int i = searchChildren(child.getLocalNameBytes());
        if (i < 0) {
            return false;
        }

        final INode removed = children.remove(i);
        Preconditions.checkState(removed == child);
        return true;
    }

    /**
     * Add a child inode to the directory.
     * 
     * @param node INode to insert
     * @param setModTime set modification time for the parent node
     *                   not needed when replaying the addition and 
     *                   the parent already has the proper mod time
     * @return false if the child with this name already exists; 
     *         otherwise, return true;
     */
    public boolean addChild(INode node, final boolean setModTime, final int latestSnapshotId) {
        final int low = searchChildren(node.getLocalNameBytes());
        if (low >= 0) {
            return false;
        }

        if (isInLatestSnapshot(latestSnapshotId)) {
            // create snapshot feature if necessary
            DirectoryWithSnapshotFeature sf = this.getDirectoryWithSnapshotFeature();
            if (sf == null) {
                sf = this.addSnapshotFeature(null);
            }
            return sf.addChild(this, node, setModTime, latestSnapshotId);
        }
        addChild(node, low);
        if (setModTime) {
            // update modification time of the parent directory
            updateModificationTime(node.getModificationTime(), latestSnapshotId);
        }
        return true;
    }

    public boolean addChild(INode node) {
        final int low = searchChildren(node.getLocalNameBytes());
        if (low >= 0) {
            return false;
        }
        addChild(node, low);
        return true;
    }

    /**
     * Add the node to the children list at the given insertion point.
     * The basic add method which actually calls children.add(..).
     */
    private void addChild(final INode node, final int insertionPoint) {
        if (children == null) {
            children = new ArrayList<>(DEFAULT_FILES_PER_DIRECTORY);
        }
        node.setParent(this);
        children.add(-insertionPoint - 1, node);

        if (node.getGroupName() == null) {
            node.setGroup(getGroupName());
        }
    }

    @Override
    public QuotaCounts computeQuotaUsage(BlockStoragePolicySuite bsps, byte blockStoragePolicyId, boolean useCache,
            int lastSnapshotId) {
        final DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();

        QuotaCounts counts = new QuotaCounts.Builder().build();
        // we are computing the quota usage for a specific snapshot here, i.e., the
        // computation only includes files/directories that exist at the time of the
        // given snapshot
        if (sf != null && lastSnapshotId != Snapshot.CURRENT_STATE_ID && !(useCache && isQuotaSet())) {
            ReadOnlyList<INode> childrenList = getChildrenList(lastSnapshotId);
            for (INode child : childrenList) {
                final byte childPolicyId = child.getStoragePolicyIDForQuota(blockStoragePolicyId);
                counts.add(child.computeQuotaUsage(bsps, childPolicyId, useCache, lastSnapshotId));
            }
            counts.addNameSpace(1);
            return counts;
        }

        // compute the quota usage in the scope of the current directory tree
        final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature();
        if (useCache && q != null && q.isQuotaSet()) { // use the cached quota
            return q.AddCurrentSpaceUsage(counts);
        } else {
            useCache = q != null && !q.isQuotaSet() ? false : useCache;
            return computeDirectoryQuotaUsage(bsps, blockStoragePolicyId, counts, useCache, lastSnapshotId);
        }
    }

    private QuotaCounts computeDirectoryQuotaUsage(BlockStoragePolicySuite bsps, byte blockStoragePolicyId,
            QuotaCounts counts, boolean useCache, int lastSnapshotId) {
        if (children != null) {
            for (INode child : children) {
                final byte childPolicyId = child.getStoragePolicyIDForQuota(blockStoragePolicyId);
                counts.add(child.computeQuotaUsage(bsps, childPolicyId, useCache, lastSnapshotId));
            }
        }
        return computeQuotaUsage4CurrentDirectory(bsps, blockStoragePolicyId, counts);
    }

    /** Add quota usage for this inode excluding children. */
    public QuotaCounts computeQuotaUsage4CurrentDirectory(BlockStoragePolicySuite bsps, byte storagePolicyId,
            QuotaCounts counts) {
        counts.addNameSpace(1);
        // include the diff list
        DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
        if (sf != null) {
            counts.add(sf.computeQuotaUsage4CurrentDirectory(bsps, storagePolicyId));
        }
        return counts;
    }

    @Override
    public ContentSummaryComputationContext computeContentSummary(int snapshotId,
            ContentSummaryComputationContext summary) throws AccessControlException {
        final DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
        if (sf != null && snapshotId == Snapshot.CURRENT_STATE_ID) {
            final ContentCounts counts = new ContentCounts.Builder().build();
            // if the getContentSummary call is against a non-snapshot path, the
            // computation should include all the deleted files/directories
            sf.computeContentSummary4Snapshot(summary.getBlockStoragePolicySuite(), counts);
            summary.getCounts().addContents(counts);
            // Also add ContentSummary to snapshotCounts (So we can extract it
            // later from the ContentSummary of all).
            summary.getSnapshotCounts().addContents(counts);
        }
        final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature();
        if (q != null && snapshotId == Snapshot.CURRENT_STATE_ID) {
            return q.computeContentSummary(this, summary);
        } else {
            return computeDirectoryContentSummary(summary, snapshotId);
        }
    }

    protected ContentSummaryComputationContext computeDirectoryContentSummary(
            ContentSummaryComputationContext summary, int snapshotId) throws AccessControlException {
        // throws exception if failing the permission check
        summary.checkPermission(this, snapshotId, FsAction.READ_EXECUTE);
        ReadOnlyList<INode> childrenList = getChildrenList(snapshotId);
        // Explicit traversing is done to enable repositioning after relinquishing
        // and reacquiring locks.
        for (int i = 0; i < childrenList.size(); i++) {
            INode child = childrenList.get(i);
            byte[] childName = child.getLocalNameBytes();

            long lastYieldCount = summary.getYieldCount();
            child.computeContentSummary(snapshotId, summary);

            // Check whether the computation was paused in the subtree.
            // The counts may be off, but traversing the rest of children
            // should be made safe.
            if (lastYieldCount == summary.getYieldCount()) {
                continue;
            }
            // The locks were released and reacquired. Check parent first.
            if (!isRoot() && getParent() == null) {
                // Stop further counting and return whatever we have so far.
                break;
            }
            // Obtain the children list again since it may have been modified.
            childrenList = getChildrenList(snapshotId);
            // Reposition in case the children list is changed. Decrement by 1
            // since it will be incremented when loops.
            i = nextChild(childrenList, childName) - 1;
        }

        // Increment the directory count for this directory.
        summary.getCounts().addContent(Content.DIRECTORY, 1);
        // Relinquish and reacquire locks if necessary.
        summary.yield();
        return summary;
    }

    /**
     * This method is usually called by the undo section of rename.
     * 
     * Before calling this function, in the rename operation, we replace the
     * original src node (of the rename operation) with a reference node (WithName
     * instance) in both the children list and a created list, delete the
     * reference node from the children list, and add it to the corresponding
     * deleted list.
     * 
     * To undo the above operations, we have the following steps in particular:
     * 
     * <pre>
     * 1) remove the WithName node from the deleted list (if it exists) 
     * 2) replace the WithName node in the created list with srcChild 
     * 3) add srcChild back as a child of srcParent. Note that we already add 
     * the node into the created list of a snapshot diff in step 2, we do not need
     * to add srcChild to the created list of the latest snapshot.
     * </pre>
     * 
     * We do not need to update quota usage because the old child is in the 
     * deleted list before. 
     * 
     * @param oldChild
     *          The reference node to be removed/replaced
     * @param newChild
     *          The node to be added back
     */
    public void undoRename4ScrParent(final INodeReference oldChild, final INode newChild) {
        DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
        assert sf != null : "Directory does not have snapshot feature";
        sf.getDiffs().removeDeletedChild(oldChild);
        sf.getDiffs().replaceCreatedChild(oldChild, newChild);
        addChild(newChild, true, Snapshot.CURRENT_STATE_ID);
    }

    /**
     * Undo the rename operation for the dst tree, i.e., if the rename operation
     * (with OVERWRITE option) removes a file/dir from the dst tree, add it back
     * and delete possible record in the deleted list.  
     */
    public void undoRename4DstParent(final BlockStoragePolicySuite bsps, final INode deletedChild,
            int latestSnapshotId) {
        DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
        assert sf != null : "Directory does not have snapshot feature";
        boolean removeDeletedChild = sf.getDiffs().removeDeletedChild(deletedChild);
        int sid = removeDeletedChild ? Snapshot.CURRENT_STATE_ID : latestSnapshotId;
        final boolean added = addChild(deletedChild, true, sid);
        // update quota usage if adding is successfully and the old child has not
        // been stored in deleted list before
        if (added && !removeDeletedChild) {
            final QuotaCounts counts = deletedChild.computeQuotaUsage(bsps);
            addSpaceConsumed(counts);
        }
    }

    /** Set the children list to null. */
    public void clearChildren() {
        this.children = null;
    }

    @Override
    public void clear() {
        super.clear();
        clearChildren();
    }

    /** Call cleanSubtree(..) recursively down the subtree. */
    public void cleanSubtreeRecursively(ReclaimContext reclaimContext, final int snapshot, int prior,
            final Map<INode, INode> excludedNodes) {
        // in case of deletion snapshot, since this call happens after we modify
        // the diff list, the snapshot to be deleted has been combined or renamed
        // to its latest previous snapshot. (besides, we also need to consider nodes
        // created after prior but before snapshot. this will be done in 
        // DirectoryWithSnapshotFeature)
        int s = snapshot != Snapshot.CURRENT_STATE_ID && prior != Snapshot.NO_SNAPSHOT_ID ? prior : snapshot;
        for (INode child : getChildrenList(s)) {
            if (snapshot == Snapshot.CURRENT_STATE_ID || excludedNodes == null
                    || !excludedNodes.containsKey(child)) {
                child.cleanSubtree(reclaimContext, snapshot, prior);
            }
        }
    }

    @Override
    public void destroyAndCollectBlocks(ReclaimContext reclaimContext) {
        reclaimContext.quotaDelta().add(new QuotaCounts.Builder().nameSpace(1).build());
        final DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
        if (sf != null) {
            sf.clear(reclaimContext, this);
        }
        for (INode child : getChildrenList(Snapshot.CURRENT_STATE_ID)) {
            child.destroyAndCollectBlocks(reclaimContext);
        }
        if (getAclFeature() != null) {
            AclStorage.removeAclFeature(getAclFeature());
        }
        clear();
        reclaimContext.removedINodes.add(this);
    }

    @Override
    public void cleanSubtree(ReclaimContext reclaimContext, final int snapshotId, int priorSnapshotId) {
        DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
        // there is snapshot data
        if (sf != null) {
            sf.cleanDirectory(reclaimContext, this, snapshotId, priorSnapshotId);
        } else {
            // there is no snapshot data
            if (priorSnapshotId == Snapshot.NO_SNAPSHOT_ID && snapshotId == Snapshot.CURRENT_STATE_ID) {
                // destroy the whole subtree and collect blocks that should be deleted
                destroyAndCollectBlocks(reclaimContext);
            } else {
                // make a copy the quota delta
                QuotaCounts old = reclaimContext.quotaDelta().getCountsCopy();
                // process recursively down the subtree
                cleanSubtreeRecursively(reclaimContext, snapshotId, priorSnapshotId, null);
                QuotaCounts current = reclaimContext.quotaDelta().getCountsCopy();
                current.subtract(old);
                if (isQuotaSet()) {
                    reclaimContext.quotaDelta().addQuotaDirUpdate(this, current);
                }
            }
        }
    }

    /**
     * Compare the metadata with another INodeDirectory
     */
    @Override
    public boolean metadataEquals(INodeDirectoryAttributes other) {
        return other != null && getQuotaCounts().equals(other.getQuotaCounts())
                && getPermissionLong() == other.getPermissionLong() && getAclFeature() == other.getAclFeature()
                && getXAttrFeature() == other.getXAttrFeature();
    }

    /*
     * The following code is to dump the tree recursively for testing.
     * 
     *      \- foo   (INodeDirectory@33dd2717)
     *        \- sub1   (INodeDirectory@442172)
     *          +- file1   (INodeFile@78392d4)
     *          +- file2   (INodeFile@78392d5)
     *          +- sub11   (INodeDirectory@8400cff)
     *            \- file3   (INodeFile@78392d6)
     *          \- z_file4   (INodeFile@45848712)
     */
    static final String DUMPTREE_EXCEPT_LAST_ITEM = "+-";
    static final String DUMPTREE_LAST_ITEM = "\\-";

    @VisibleForTesting
    @Override
    public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix, final int snapshot) {
        super.dumpTreeRecursively(out, prefix, snapshot);
        out.print(", childrenSize=" + getChildrenList(snapshot).size());
        final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature();
        if (q != null) {
            out.print(", " + q);
        }
        if (this instanceof Snapshot.Root) {
            out.print(", snapshotId=" + snapshot);
        }
        out.println();

        if (prefix.length() >= 2) {
            prefix.setLength(prefix.length() - 2);
            prefix.append("  ");
        }
        dumpTreeRecursively(out, prefix, new Iterable<SnapshotAndINode>() {
            final Iterator<INode> i = getChildrenList(snapshot).iterator();

            @Override
            public Iterator<SnapshotAndINode> iterator() {
                return new Iterator<SnapshotAndINode>() {
                    @Override
                    public boolean hasNext() {
                        return i.hasNext();
                    }

                    @Override
                    public SnapshotAndINode next() {
                        return new SnapshotAndINode(snapshot, i.next());
                    }

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

        final DirectorySnapshottableFeature s = getDirectorySnapshottableFeature();
        if (s != null) {
            s.dumpTreeRecursively(this, out, prefix, snapshot);
        }
    }

    /**
     * Dump the given subtrees.
     * @param prefix The prefix string that each line should print.
     * @param subs The subtrees.
     */
    @VisibleForTesting
    public static void dumpTreeRecursively(PrintWriter out, StringBuilder prefix, Iterable<SnapshotAndINode> subs) {
        if (subs != null) {
            for (final Iterator<SnapshotAndINode> i = subs.iterator(); i.hasNext();) {
                final SnapshotAndINode pair = i.next();
                prefix.append(i.hasNext() ? DUMPTREE_EXCEPT_LAST_ITEM : DUMPTREE_LAST_ITEM);
                pair.inode.dumpTreeRecursively(out, prefix, pair.snapshotId);
                prefix.setLength(prefix.length() - 2);
            }
        }
    }

    /** A pair of Snapshot and INode objects. */
    public static class SnapshotAndINode {
        public final int snapshotId;
        public final INode inode;

        public SnapshotAndINode(int snapshot, INode inode) {
            this.snapshotId = snapshot;
            this.inode = inode;
        }
    }

    public final int getChildrenNum(final int snapshotId) {
        return getChildrenList(snapshotId).size();
    }
}