Avoid repeated file stats in the same directory.

Using readdir(), we can avoid having to stat every single
file/directory/symlink in a directory (and especially not do it 3 times as we
currently do in TreeArtifactValue.explodeDirectory).

RELNOTES: None.
PiperOrigin-RevId: 220305956
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TreeArtifactValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/TreeArtifactValue.java
index 13ff08a..386845a 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TreeArtifactValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TreeArtifactValue.java
@@ -24,11 +24,15 @@
 import com.google.devtools.build.lib.actions.FileArtifactValue;
 import com.google.devtools.build.lib.actions.cache.DigestUtils;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
+import com.google.devtools.build.lib.vfs.Dirent;
+import com.google.devtools.build.lib.vfs.Dirent.Type;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.Symlinks;
 import com.google.devtools.build.skyframe.SkyValue;
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Map;
 import java.util.Set;
 import javax.annotation.Nullable;
@@ -184,12 +188,14 @@
   private static void explodeDirectory(Path treeArtifactPath,
       PathFragment pathToExplode, ImmutableSet.Builder<PathFragment> valuesBuilder)
       throws IOException {
-    for (Path subpath : treeArtifactPath.getRelative(pathToExplode).getDirectoryEntries()) {
-      PathFragment canonicalSubpathFragment = pathToExplode.getChild(subpath.getBaseName());
-      if (subpath.isDirectory()) {
-        explodeDirectory(treeArtifactPath,
-            pathToExplode.getChild(subpath.getBaseName()), valuesBuilder);
-      } else if (subpath.isSymbolicLink()) {
+    Path dir = treeArtifactPath.getRelative(pathToExplode);
+    Collection<Dirent> dirents = dir.readdir(Symlinks.NOFOLLOW);
+    for (Dirent dirent : dirents) {
+      PathFragment canonicalSubpathFragment = pathToExplode.getChild(dirent.getName());
+      if (dirent.getType() == Type.DIRECTORY) {
+        explodeDirectory(treeArtifactPath, canonicalSubpathFragment, valuesBuilder);
+      } else if (dirent.getType() == Type.SYMLINK) {
+        Path subpath = dir.getRelative(dirent.getName());
         PathFragment linkTarget = subpath.readSymbolicLinkUnchecked();
         valuesBuilder.add(canonicalSubpathFragment);
         if (linkTarget.isAbsolute()) {
@@ -214,10 +220,11 @@
             throw new IOException(errorMessage);
           }
         }
-      } else if (subpath.isFile()) {
+      } else if (dirent.getType() == Type.FILE) {
         valuesBuilder.add(canonicalSubpathFragment);
       } else {
         // We shouldn't ever reach here.
+        Path subpath = dir.getRelative(dirent.getName());
         throw new IllegalStateException("Could not determine type of file " + subpath);
       }
     }