001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hdfs.server.namenode; 019 020import java.io.FileNotFoundException; 021import java.io.PrintWriter; 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.Iterator; 025import java.util.List; 026import java.util.Map; 027 028import org.apache.hadoop.fs.PathIsNotDirectoryException; 029import org.apache.hadoop.fs.UnresolvedLinkException; 030import org.apache.hadoop.fs.permission.PermissionStatus; 031import org.apache.hadoop.hdfs.DFSUtil; 032import org.apache.hadoop.hdfs.protocol.QuotaExceededException; 033import org.apache.hadoop.hdfs.protocol.SnapshotAccessControlException; 034import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithCount; 035import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectorySnapshottable; 036import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectoryWithSnapshot; 037import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeFileUnderConstructionWithSnapshot; 038import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeFileWithSnapshot; 039import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot; 040import org.apache.hadoop.hdfs.util.ReadOnlyList; 041 042import com.google.common.annotations.VisibleForTesting; 043import com.google.common.base.Preconditions; 044 045/** 046 * Directory INode class. 047 */ 048public class INodeDirectory extends INodeWithAdditionalFields 049 implements INodeDirectoryAttributes { 050 /** Cast INode to INodeDirectory. */ 051 public static INodeDirectory valueOf(INode inode, Object path 052 ) throws FileNotFoundException, PathIsNotDirectoryException { 053 if (inode == null) { 054 throw new FileNotFoundException("Directory does not exist: " 055 + DFSUtil.path2String(path)); 056 } 057 if (!inode.isDirectory()) { 058 throw new PathIsNotDirectoryException(DFSUtil.path2String(path)); 059 } 060 return inode.asDirectory(); 061 } 062 063 protected static final int DEFAULT_FILES_PER_DIRECTORY = 5; 064 final static byte[] ROOT_NAME = DFSUtil.string2Bytes(""); 065 066 private List<INode> children = null; 067 068 /** constructor */ 069 public INodeDirectory(long id, byte[] name, PermissionStatus permissions, 070 long mtime) { 071 super(id, name, permissions, mtime, 0L); 072 } 073 074 /** 075 * Copy constructor 076 * @param other The INodeDirectory to be copied 077 * @param adopt Indicate whether or not need to set the parent field of child 078 * INodes to the new node 079 */ 080 public INodeDirectory(INodeDirectory other, boolean adopt) { 081 super(other); 082 this.children = other.children; 083 if (adopt && this.children != null) { 084 for (INode child : children) { 085 child.setParent(this); 086 } 087 } 088 } 089 090 /** @return true unconditionally. */ 091 @Override 092 public final boolean isDirectory() { 093 return true; 094 } 095 096 /** @return this object. */ 097 @Override 098 public final INodeDirectory asDirectory() { 099 return this; 100 } 101 102 /** Is this a snapshottable directory? */ 103 public boolean isSnapshottable() { 104 return false; 105 } 106 107 private int searchChildren(byte[] name) { 108 return children == null? -1: Collections.binarySearch(children, name); 109 } 110 111 /** 112 * Remove the specified child from this directory. 113 * 114 * @param child the child inode to be removed 115 * @param latest See {@link INode#recordModification(Snapshot, INodeMap)}. 116 */ 117 public boolean removeChild(INode child, Snapshot latest, 118 final INodeMap inodeMap) throws QuotaExceededException { 119 if (isInLatestSnapshot(latest)) { 120 return replaceSelf4INodeDirectoryWithSnapshot(inodeMap) 121 .removeChild(child, latest, inodeMap); 122 } 123 124 return removeChild(child); 125 } 126 127 /** 128 * Remove the specified child from this directory. 129 * The basic remove method which actually calls children.remove(..). 130 * 131 * @param child the child inode to be removed 132 * 133 * @return true if the child is removed; false if the child is not found. 134 */ 135 protected final boolean removeChild(final INode child) { 136 final int i = searchChildren(child.getLocalNameBytes()); 137 if (i < 0) { 138 return false; 139 } 140 141 final INode removed = children.remove(i); 142 Preconditions.checkState(removed == child); 143 return true; 144 } 145 146 /** 147 * Replace itself with {@link INodeDirectoryWithQuota} or 148 * {@link INodeDirectoryWithSnapshot} depending on the latest snapshot. 149 */ 150 INodeDirectoryWithQuota replaceSelf4Quota(final Snapshot latest, 151 final long nsQuota, final long dsQuota, final INodeMap inodeMap) 152 throws QuotaExceededException { 153 Preconditions.checkState(!(this instanceof INodeDirectoryWithQuota), 154 "this is already an INodeDirectoryWithQuota, this=%s", this); 155 156 if (!this.isInLatestSnapshot(latest)) { 157 final INodeDirectoryWithQuota q = new INodeDirectoryWithQuota( 158 this, true, nsQuota, dsQuota); 159 replaceSelf(q, inodeMap); 160 return q; 161 } else { 162 final INodeDirectoryWithSnapshot s = new INodeDirectoryWithSnapshot(this); 163 s.setQuota(nsQuota, dsQuota); 164 return replaceSelf(s, inodeMap).saveSelf2Snapshot(latest, this); 165 } 166 } 167 /** Replace itself with an {@link INodeDirectorySnapshottable}. */ 168 public INodeDirectorySnapshottable replaceSelf4INodeDirectorySnapshottable( 169 Snapshot latest, final INodeMap inodeMap) throws QuotaExceededException { 170 Preconditions.checkState(!(this instanceof INodeDirectorySnapshottable), 171 "this is already an INodeDirectorySnapshottable, this=%s", this); 172 final INodeDirectorySnapshottable s = new INodeDirectorySnapshottable(this); 173 replaceSelf(s, inodeMap).saveSelf2Snapshot(latest, this); 174 return s; 175 } 176 177 /** Replace itself with an {@link INodeDirectoryWithSnapshot}. */ 178 public INodeDirectoryWithSnapshot replaceSelf4INodeDirectoryWithSnapshot( 179 final INodeMap inodeMap) { 180 return replaceSelf(new INodeDirectoryWithSnapshot(this), inodeMap); 181 } 182 183 /** Replace itself with {@link INodeDirectory}. */ 184 public INodeDirectory replaceSelf4INodeDirectory(final INodeMap inodeMap) { 185 Preconditions.checkState(getClass() != INodeDirectory.class, 186 "the class is already INodeDirectory, this=%s", this); 187 return replaceSelf(new INodeDirectory(this, true), inodeMap); 188 } 189 190 /** Replace itself with the given directory. */ 191 private final <N extends INodeDirectory> N replaceSelf(final N newDir, 192 final INodeMap inodeMap) { 193 final INodeReference ref = getParentReference(); 194 if (ref != null) { 195 ref.setReferredINode(newDir); 196 if (inodeMap != null) { 197 inodeMap.put(newDir); 198 } 199 } else { 200 final INodeDirectory parent = getParent(); 201 Preconditions.checkArgument(parent != null, "parent is null, this=%s", this); 202 parent.replaceChild(this, newDir, inodeMap); 203 } 204 clear(); 205 return newDir; 206 } 207 208 /** 209 * Used when load fileUC from fsimage. The file to be replaced is actually 210 * only in snapshot, thus may not be contained in the children list. 211 * See HDFS-5428 for details. 212 */ 213 public void replaceChildFileInSnapshot(INodeFile oldChild, 214 final INodeFile newChild) { 215 if (children != null) { 216 final int i = searchChildren(newChild.getLocalNameBytes()); 217 if (i >= 0 && children.get(i).getId() == oldChild.getId()) { 218 // no need to consider reference node here, since we already do the 219 // replacement in FSImageFormat.Loader#loadFilesUnderConstruction 220 children.set(i, newChild); 221 } 222 } 223 } 224 225 /** Replace the given child with a new child. */ 226 public void replaceChild(INode oldChild, final INode newChild, 227 final INodeMap inodeMap) { 228 Preconditions.checkNotNull(children); 229 final int i = searchChildren(newChild.getLocalNameBytes()); 230 Preconditions.checkState(i >= 0); 231 Preconditions.checkState(oldChild == children.get(i) 232 || oldChild == children.get(i).asReference().getReferredINode() 233 .asReference().getReferredINode()); 234 oldChild = children.get(i); 235 236 if (oldChild.isReference() && !newChild.isReference()) { 237 // replace the referred inode, e.g., 238 // INodeFileWithSnapshot -> INodeFileUnderConstructionWithSnapshot 239 final INode withCount = oldChild.asReference().getReferredINode(); 240 withCount.asReference().setReferredINode(newChild); 241 } else { 242 if (oldChild.isReference()) { 243 // both are reference nodes, e.g., DstReference -> WithName 244 final INodeReference.WithCount withCount = 245 (WithCount) oldChild.asReference().getReferredINode(); 246 withCount.removeReference(oldChild.asReference()); 247 } 248 children.set(i, newChild); 249 } 250 // update the inodeMap 251 if (inodeMap != null) { 252 inodeMap.put(newChild); 253 } 254 } 255 256 INodeReference.WithName replaceChild4ReferenceWithName(INode oldChild, 257 Snapshot latest) { 258 Preconditions.checkArgument(latest != null); 259 if (oldChild instanceof INodeReference.WithName) { 260 return (INodeReference.WithName)oldChild; 261 } 262 263 final INodeReference.WithCount withCount; 264 if (oldChild.isReference()) { 265 Preconditions.checkState(oldChild instanceof INodeReference.DstReference); 266 withCount = (INodeReference.WithCount) oldChild.asReference() 267 .getReferredINode(); 268 } else { 269 withCount = new INodeReference.WithCount(null, oldChild); 270 } 271 final INodeReference.WithName ref = new INodeReference.WithName(this, 272 withCount, oldChild.getLocalNameBytes(), latest.getId()); 273 replaceChild(oldChild, ref, null); 274 return ref; 275 } 276 277 private void replaceChildFile(final INodeFile oldChild, 278 final INodeFile newChild, final INodeMap inodeMap) { 279 replaceChild(oldChild, newChild, inodeMap); 280 oldChild.clear(); 281 newChild.updateBlockCollection(); 282 } 283 284 /** Replace a child {@link INodeFile} with an {@link INodeFileWithSnapshot}. */ 285 INodeFileWithSnapshot replaceChild4INodeFileWithSnapshot( 286 final INodeFile child, final INodeMap inodeMap) { 287 Preconditions.checkArgument(!(child instanceof INodeFileWithSnapshot), 288 "Child file is already an INodeFileWithSnapshot, child=" + child); 289 final INodeFileWithSnapshot newChild = new INodeFileWithSnapshot(child); 290 replaceChildFile(child, newChild, inodeMap); 291 return newChild; 292 } 293 294 /** Replace a child {@link INodeFile} with an {@link INodeFileUnderConstructionWithSnapshot}. */ 295 INodeFileUnderConstructionWithSnapshot replaceChild4INodeFileUcWithSnapshot( 296 final INodeFileUnderConstruction child, final INodeMap inodeMap) { 297 Preconditions.checkArgument(!(child instanceof INodeFileUnderConstructionWithSnapshot), 298 "Child file is already an INodeFileUnderConstructionWithSnapshot, child=" + child); 299 final INodeFileUnderConstructionWithSnapshot newChild 300 = new INodeFileUnderConstructionWithSnapshot(child, null); 301 replaceChildFile(child, newChild, inodeMap); 302 return newChild; 303 } 304 305 @Override 306 public INodeDirectory recordModification(Snapshot latest, 307 final INodeMap inodeMap) throws QuotaExceededException { 308 if (isInLatestSnapshot(latest)) { 309 return replaceSelf4INodeDirectoryWithSnapshot(inodeMap) 310 .recordModification(latest, inodeMap); 311 } else { 312 return this; 313 } 314 } 315 316 /** 317 * Save the child to the latest snapshot. 318 * 319 * @return the child inode, which may be replaced. 320 */ 321 public INode saveChild2Snapshot(final INode child, final Snapshot latest, 322 final INode snapshotCopy, final INodeMap inodeMap) 323 throws QuotaExceededException { 324 if (latest == null) { 325 return child; 326 } 327 return replaceSelf4INodeDirectoryWithSnapshot(inodeMap) 328 .saveChild2Snapshot(child, latest, snapshotCopy, inodeMap); 329 } 330 331 /** 332 * @param name the name of the child 333 * @param snapshot 334 * if it is not null, get the result from the given snapshot; 335 * otherwise, get the result from the current directory. 336 * @return the child inode. 337 */ 338 public INode getChild(byte[] name, Snapshot snapshot) { 339 final ReadOnlyList<INode> c = getChildrenList(snapshot); 340 final int i = ReadOnlyList.Util.binarySearch(c, name); 341 return i < 0? null: c.get(i); 342 } 343 344 /** @return the {@link INodesInPath} containing only the last inode. */ 345 INodesInPath getLastINodeInPath(String path, boolean resolveLink 346 ) throws UnresolvedLinkException { 347 return INodesInPath.resolve(this, getPathComponents(path), 1, resolveLink); 348 } 349 350 /** @return the {@link INodesInPath} containing all inodes in the path. */ 351 INodesInPath getINodesInPath(String path, boolean resolveLink 352 ) throws UnresolvedLinkException { 353 final byte[][] components = getPathComponents(path); 354 return INodesInPath.resolve(this, components, components.length, resolveLink); 355 } 356 357 /** @return the last inode in the path. */ 358 INode getNode(String path, boolean resolveLink) 359 throws UnresolvedLinkException { 360 return getLastINodeInPath(path, resolveLink).getINode(0); 361 } 362 363 /** 364 * @return the INode of the last component in src, or null if the last 365 * component does not exist. 366 * @throws UnresolvedLinkException if symlink can't be resolved 367 * @throws SnapshotAccessControlException if path is in RO snapshot 368 */ 369 INode getINode4Write(String src, boolean resolveLink) 370 throws UnresolvedLinkException, SnapshotAccessControlException { 371 return getINodesInPath4Write(src, resolveLink).getLastINode(); 372 } 373 374 /** 375 * @return the INodesInPath of the components in src 376 * @throws UnresolvedLinkException if symlink can't be resolved 377 * @throws SnapshotAccessControlException if path is in RO snapshot 378 */ 379 INodesInPath getINodesInPath4Write(String src, boolean resolveLink) 380 throws UnresolvedLinkException, SnapshotAccessControlException { 381 final byte[][] components = INode.getPathComponents(src); 382 INodesInPath inodesInPath = INodesInPath.resolve(this, components, 383 components.length, resolveLink); 384 if (inodesInPath.isSnapshot()) { 385 throw new SnapshotAccessControlException( 386 "Modification on a read-only snapshot is disallowed"); 387 } 388 return inodesInPath; 389 } 390 391 /** 392 * Given a child's name, return the index of the next child 393 * 394 * @param name a child's name 395 * @return the index of the next child 396 */ 397 static int nextChild(ReadOnlyList<INode> children, byte[] name) { 398 if (name.length == 0) { // empty name 399 return 0; 400 } 401 int nextPos = ReadOnlyList.Util.binarySearch(children, name) + 1; 402 if (nextPos >= 0) { 403 return nextPos; 404 } 405 return -nextPos; 406 } 407 408 /** 409 * Add a child inode to the directory. 410 * 411 * @param node INode to insert 412 * @param setModTime set modification time for the parent node 413 * not needed when replaying the addition and 414 * the parent already has the proper mod time 415 * @param inodeMap update the inodeMap if the directory node gets replaced 416 * @return false if the child with this name already exists; 417 * otherwise, return true; 418 */ 419 public boolean addChild(INode node, final boolean setModTime, 420 final Snapshot latest, final INodeMap inodeMap) 421 throws QuotaExceededException { 422 final int low = searchChildren(node.getLocalNameBytes()); 423 if (low >= 0) { 424 return false; 425 } 426 427 if (isInLatestSnapshot(latest)) { 428 INodeDirectoryWithSnapshot sdir = 429 replaceSelf4INodeDirectoryWithSnapshot(inodeMap); 430 boolean added = sdir.addChild(node, setModTime, latest, inodeMap); 431 return added; 432 } 433 addChild(node, low); 434 if (setModTime) { 435 // update modification time of the parent directory 436 updateModificationTime(node.getModificationTime(), latest, inodeMap); 437 } 438 return true; 439 } 440 441 442 /** The same as addChild(node, false, null, false) */ 443 public boolean addChild(INode node) { 444 final int low = searchChildren(node.getLocalNameBytes()); 445 if (low >= 0) { 446 return false; 447 } 448 addChild(node, low); 449 return true; 450 } 451 452 /** 453 * Add the node to the children list at the given insertion point. 454 * The basic add method which actually calls children.add(..). 455 */ 456 private void addChild(final INode node, final int insertionPoint) { 457 if (children == null) { 458 children = new ArrayList<INode>(DEFAULT_FILES_PER_DIRECTORY); 459 } 460 node.setParent(this); 461 children.add(-insertionPoint - 1, node); 462 463 if (node.getGroupName() == null) { 464 node.setGroup(getGroupName()); 465 } 466 } 467 468 @Override 469 public Quota.Counts computeQuotaUsage(Quota.Counts counts, boolean useCache, 470 int lastSnapshotId) { 471 if (children != null) { 472 for (INode child : children) { 473 child.computeQuotaUsage(counts, useCache, lastSnapshotId); 474 } 475 } 476 return computeQuotaUsage4CurrentDirectory(counts); 477 } 478 479 /** Add quota usage for this inode excluding children. */ 480 public Quota.Counts computeQuotaUsage4CurrentDirectory(Quota.Counts counts) { 481 counts.add(Quota.NAMESPACE, 1); 482 return counts; 483 } 484 485 @Override 486 public ContentSummaryComputationContext computeContentSummary( 487 ContentSummaryComputationContext summary) { 488 ReadOnlyList<INode> childrenList = getChildrenList(null); 489 // Explicit traversing is done to enable repositioning after relinquishing 490 // and reacquiring locks. 491 for (int i = 0; i < childrenList.size(); i++) { 492 INode child = childrenList.get(i); 493 byte[] childName = child.getLocalNameBytes(); 494 495 long lastYieldCount = summary.getYieldCount(); 496 child.computeContentSummary(summary); 497 498 // Check whether the computation was paused in the subtree. 499 // The counts may be off, but traversing the rest of children 500 // should be made safe. 501 if (lastYieldCount == summary.getYieldCount()) { 502 continue; 503 } 504 505 // The locks were released and reacquired. Check parent first. 506 if (getParent() == null) { 507 // Stop further counting and return whatever we have so far. 508 break; 509 } 510 511 // Obtain the children list again since it may have been modified. 512 childrenList = getChildrenList(null); 513 // Reposition in case the children list is changed. Decrement by 1 514 // since it will be incremented when loops. 515 i = nextChild(childrenList, childName) - 1; 516 } 517 518 // Increment the directory count for this directory. 519 summary.getCounts().add(Content.DIRECTORY, 1); 520 521 // Relinquish and reacquire locks if necessary. 522 summary.yield(); 523 524 return summary; 525 } 526 527 /** 528 * @param snapshot 529 * if it is not null, get the result from the given snapshot; 530 * otherwise, get the result from the current directory. 531 * @return the current children list if the specified snapshot is null; 532 * otherwise, return the children list corresponding to the snapshot. 533 * Note that the returned list is never null. 534 */ 535 public ReadOnlyList<INode> getChildrenList(final Snapshot snapshot) { 536 return children == null ? ReadOnlyList.Util.<INode>emptyList() 537 : ReadOnlyList.Util.asReadOnlyList(children); 538 } 539 540 /** Set the children list to null. */ 541 public void clearChildren() { 542 this.children = null; 543 } 544 545 @Override 546 public void clear() { 547 super.clear(); 548 clearChildren(); 549 } 550 551 /** Call cleanSubtree(..) recursively down the subtree. */ 552 public Quota.Counts cleanSubtreeRecursively(final Snapshot snapshot, 553 Snapshot prior, final BlocksMapUpdateInfo collectedBlocks, 554 final List<INode> removedINodes, final Map<INode, INode> excludedNodes, 555 final boolean countDiffChange) throws QuotaExceededException { 556 Quota.Counts counts = Quota.Counts.newInstance(); 557 // in case of deletion snapshot, since this call happens after we modify 558 // the diff list, the snapshot to be deleted has been combined or renamed 559 // to its latest previous snapshot. (besides, we also need to consider nodes 560 // created after prior but before snapshot. this will be done in 561 // INodeDirectoryWithSnapshot#cleanSubtree) 562 Snapshot s = snapshot != null && prior != null ? prior : snapshot; 563 for (INode child : getChildrenList(s)) { 564 if (snapshot != null && excludedNodes != null 565 && excludedNodes.containsKey(child)) { 566 continue; 567 } else { 568 Quota.Counts childCounts = child.cleanSubtree(snapshot, prior, 569 collectedBlocks, removedINodes, countDiffChange); 570 counts.add(childCounts); 571 } 572 } 573 return counts; 574 } 575 576 @Override 577 public void destroyAndCollectBlocks(final BlocksMapUpdateInfo collectedBlocks, 578 final List<INode> removedINodes) { 579 for (INode child : getChildrenList(null)) { 580 child.destroyAndCollectBlocks(collectedBlocks, removedINodes); 581 } 582 clear(); 583 removedINodes.add(this); 584 } 585 586 @Override 587 public Quota.Counts cleanSubtree(final Snapshot snapshot, Snapshot prior, 588 final BlocksMapUpdateInfo collectedBlocks, 589 final List<INode> removedINodes, final boolean countDiffChange) 590 throws QuotaExceededException { 591 if (prior == null && snapshot == null) { 592 // destroy the whole subtree and collect blocks that should be deleted 593 Quota.Counts counts = Quota.Counts.newInstance(); 594 this.computeQuotaUsage(counts, true); 595 destroyAndCollectBlocks(collectedBlocks, removedINodes); 596 return counts; 597 } else { 598 // process recursively down the subtree 599 Quota.Counts counts = cleanSubtreeRecursively(snapshot, prior, 600 collectedBlocks, removedINodes, null, countDiffChange); 601 if (isQuotaSet()) { 602 ((INodeDirectoryWithQuota) this).addSpaceConsumed2Cache( 603 -counts.get(Quota.NAMESPACE), -counts.get(Quota.DISKSPACE)); 604 } 605 return counts; 606 } 607 } 608 609 /** 610 * Compare the metadata with another INodeDirectory 611 */ 612 @Override 613 public boolean metadataEquals(INodeDirectoryAttributes other) { 614 return other != null 615 && getNsQuota() == other.getNsQuota() 616 && getDsQuota() == other.getDsQuota() 617 && getPermissionLong() == other.getPermissionLong(); 618 } 619 620 /* 621 * The following code is to dump the tree recursively for testing. 622 * 623 * \- foo (INodeDirectory@33dd2717) 624 * \- sub1 (INodeDirectory@442172) 625 * +- file1 (INodeFile@78392d4) 626 * +- file2 (INodeFile@78392d5) 627 * +- sub11 (INodeDirectory@8400cff) 628 * \- file3 (INodeFile@78392d6) 629 * \- z_file4 (INodeFile@45848712) 630 */ 631 static final String DUMPTREE_EXCEPT_LAST_ITEM = "+-"; 632 static final String DUMPTREE_LAST_ITEM = "\\-"; 633 @VisibleForTesting 634 @Override 635 public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix, 636 final Snapshot snapshot) { 637 super.dumpTreeRecursively(out, prefix, snapshot); 638 out.print(", childrenSize=" + getChildrenList(snapshot).size()); 639 if (this instanceof INodeDirectoryWithQuota) { 640 out.print(((INodeDirectoryWithQuota)this).quotaString()); 641 } 642 if (this instanceof Snapshot.Root) { 643 out.print(", snapshotId=" + snapshot.getId()); 644 } 645 out.println(); 646 647 if (prefix.length() >= 2) { 648 prefix.setLength(prefix.length() - 2); 649 prefix.append(" "); 650 } 651 dumpTreeRecursively(out, prefix, new Iterable<SnapshotAndINode>() { 652 final Iterator<INode> i = getChildrenList(snapshot).iterator(); 653 654 @Override 655 public Iterator<SnapshotAndINode> iterator() { 656 return new Iterator<SnapshotAndINode>() { 657 @Override 658 public boolean hasNext() { 659 return i.hasNext(); 660 } 661 662 @Override 663 public SnapshotAndINode next() { 664 return new SnapshotAndINode(snapshot, i.next()); 665 } 666 667 @Override 668 public void remove() { 669 throw new UnsupportedOperationException(); 670 } 671 }; 672 } 673 }); 674 } 675 676 /** 677 * Dump the given subtrees. 678 * @param prefix The prefix string that each line should print. 679 * @param subs The subtrees. 680 */ 681 @VisibleForTesting 682 protected static void dumpTreeRecursively(PrintWriter out, 683 StringBuilder prefix, Iterable<SnapshotAndINode> subs) { 684 if (subs != null) { 685 for(final Iterator<SnapshotAndINode> i = subs.iterator(); i.hasNext();) { 686 final SnapshotAndINode pair = i.next(); 687 prefix.append(i.hasNext()? DUMPTREE_EXCEPT_LAST_ITEM: DUMPTREE_LAST_ITEM); 688 pair.inode.dumpTreeRecursively(out, prefix, pair.snapshot); 689 prefix.setLength(prefix.length() - 2); 690 } 691 } 692 } 693 694 /** A pair of Snapshot and INode objects. */ 695 protected static class SnapshotAndINode { 696 public final Snapshot snapshot; 697 public final INode inode; 698 699 public SnapshotAndINode(Snapshot snapshot, INode inode) { 700 this.snapshot = snapshot; 701 this.inode = inode; 702 } 703 704 public SnapshotAndINode(Snapshot snapshot) { 705 this(snapshot, snapshot.getRoot()); 706 } 707 } 708 709 public final int getChildrenNum(final Snapshot snapshot) { 710 return getChildrenList(snapshot).size(); 711 } 712}