Make FileSystem operate on LocalPath instead of Path.

PiperOrigin-RevId: 179082062
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/LocalPath.java b/src/main/java/com/google/devtools/build/lib/vfs/LocalPath.java
index a32a4e3..b05c89f 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/LocalPath.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/LocalPath.java
@@ -15,12 +15,14 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
 import com.google.devtools.build.lib.util.OS;
 import com.google.devtools.build.lib.windows.WindowsShortPath;
 import com.google.devtools.build.lib.windows.jni.WindowsFileOperations;
 import java.io.IOException;
-import java.util.Arrays;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.List;
+import java.util.regex.Pattern;
 import javax.annotation.Nullable;
 
 /**
@@ -50,6 +52,8 @@
 
   public static final LocalPath EMPTY = create("");
 
+  private static final Splitter PATH_SPLITTER = Splitter.on('/').omitEmptyStrings();
+
   private final String path;
   private final int driveStrLength; // 0 for relative paths, 1 on Unix, 3 on Windows
   private final OsPathPolicy os;
@@ -228,19 +232,33 @@
    * Splits a path into its constituent parts. The root is not included. This is an inefficient
    * operation and should be avoided.
    */
-  public String[] split() {
-    String[] segments = path.split("/");
-    if (driveStrLength > 0) {
-      // String#split("/") for some reason returns a zero-length array
-      // String#split("/hello") returns a 2-length array, so this makes little sense
-      if (segments.length == 0) {
-        return segments;
-      }
-      return Arrays.copyOfRange(segments, 1, segments.length);
+  public List<String> split() {
+    List<String> segments = PATH_SPLITTER.splitToList(path);
+    if (driveStrLength > 1) {
+      return segments.subList(1, segments.size());
     }
     return segments;
   }
 
+  /** Returns the drive of this local path, eg. "/" on Unix or "C:/" on Windows. */
+  public LocalPath getDrive() {
+    if (driveStrLength == 0) {
+      throw new IllegalArgumentException("Cannot get mount of non-absolute path.");
+    }
+    return new LocalPath(path.substring(0, driveStrLength), driveStrLength, os);
+  }
+
+  /**
+   * Returns whether this is the root of the entire file system.
+   *
+   * <p>Please avoid this method. On Unix, this corresponds to the '/' mount point. Windows drives
+   * (C:/) do not have a parent and are not the root of the entire file system, so do not return
+   * true.
+   */
+  public boolean isRoot() {
+    return os.isRoot(this);
+  }
+
   /**
    * Returns whether this path is an ancestor of another path.
    *
@@ -339,10 +357,14 @@
     char getSeparator();
 
     boolean isCaseSensitive();
+
+    boolean isRoot(LocalPath localPath);
   }
 
   @VisibleForTesting
   static class UnixOsPathPolicy implements OsPathPolicy {
+    private static Splitter UNIX_PATH_SPLITTER =
+        Splitter.on(Pattern.compile("/+")).omitEmptyStrings();
 
     @Override
     public int needsToNormalize(String path) {
@@ -362,7 +384,7 @@
         dotCount = c == '.' ? dotCount + 1 : 0;
         prevChar = c;
       }
-      if (prevChar == '/' || dotCount == 1 || dotCount == 2) {
+      if ((n > 1 && prevChar == '/') || dotCount == 1 || dotCount == 2) {
         return NEEDS_NORMALIZE;
       }
       return NORMALIZED;
@@ -377,8 +399,8 @@
         return path;
       }
       boolean isAbsolute = path.charAt(0) == '/';
-      String[] segments = path.split("/+");
-      int segmentCount = removeRelativePaths(segments, isAbsolute ? 1 : 0);
+      String[] segments = Iterables.toArray(UNIX_PATH_SPLITTER.split(path), String.class);
+      int segmentCount = removeRelativePaths(segments, 0);
       StringBuilder sb = new StringBuilder(path.length());
       if (isAbsolute) {
         sb.append('/');
@@ -425,6 +447,11 @@
     public boolean isCaseSensitive() {
       return true;
     }
+
+    @Override
+    public boolean isRoot(LocalPath localPath) {
+      return localPath.path.equals("/");
+    }
   }
 
   /** Mac is a unix file system that is case insensitive. */
@@ -451,8 +478,9 @@
 
     private static final int NEEDS_SHORT_PATH_NORMALIZATION = NEEDS_NORMALIZE + 1;
 
-    // msys root, used to resolve paths from msys starting with "/"
-    private static final AtomicReference<String> UNIX_ROOT = new AtomicReference<>(null);
+    private static Splitter WINDOWS_PATH_SPLITTER =
+        Splitter.on(Pattern.compile("[\\\\/]+")).omitEmptyStrings();
+
     private final ShortPathResolver shortPathResolver;
 
     interface ShortPathResolver {
@@ -482,10 +510,6 @@
     public int needsToNormalize(String path) {
       int n = path.length();
       int normalizationLevel = 0;
-      // Check for unix path
-      if (n > 0 && path.charAt(0) == '/') {
-        normalizationLevel = Math.max(normalizationLevel, NEEDS_NORMALIZE);
-      }
       int dotCount = 0;
       char prevChar = 0;
       int segmentBeginIndex = 0; // The start index of the current path index
@@ -517,7 +541,7 @@
         dotCount = c == '.' ? dotCount + 1 : 0;
         prevChar = c;
       }
-      if (prevChar == '/' || dotCount == 1 || dotCount == 2) {
+      if ((n > 1 && prevChar == '/') || dotCount == 1 || dotCount == 2) {
         normalizationLevel = Math.max(normalizationLevel, NEEDS_NORMALIZE);
       }
       return normalizationLevel;
@@ -534,26 +558,19 @@
           path = resolvedPath;
         }
       }
-      String[] segments = path.split("[\\\\/]+");
+      String[] segments = Iterables.toArray(WINDOWS_PATH_SPLITTER.splitToList(path), String.class);
       int driveStrLength = getDriveStrLength(path);
       boolean isAbsolute = driveStrLength > 0;
-      int segmentSkipCount = isAbsolute ? 1 : 0;
+      int segmentSkipCount = isAbsolute && driveStrLength > 1 ? 1 : 0;
 
       StringBuilder sb = new StringBuilder(path.length());
       if (isAbsolute) {
-        char driveLetter = path.charAt(0);
-        sb.append(Character.toUpperCase(driveLetter));
-        sb.append(":/");
-      }
-      // unix path support
-      if (!path.isEmpty() && path.charAt(0) == '/') {
-        if (path.length() == 2 || (path.length() > 2 && path.charAt(2) == '/')) {
-          sb.append(Character.toUpperCase(path.charAt(1)));
-          sb.append(":/");
-          segmentSkipCount = 2;
+        char c = path.charAt(0);
+        if (c == '/') {
+          sb.append('/');
         } else {
-          String unixRoot = getUnixRoot();
-          sb.append(unixRoot);
+          sb.append(Character.toUpperCase(c));
+          sb.append(":/");
         }
       }
       int segmentCount = removeRelativePaths(segments, segmentSkipCount);
@@ -570,6 +587,12 @@
     @Override
     public int getDriveStrLength(String path) {
       int n = path.length();
+      if (n == 0) {
+        return 0;
+      }
+      if (path.charAt(0) == '/') {
+        return 1;
+      }
       if (n < 3) {
         return 0;
       }
@@ -622,44 +645,10 @@
       return false;
     }
 
-    private String getUnixRoot() {
-      String value = UNIX_ROOT.get();
-      if (value == null) {
-        String jvmFlag = "bazel.windows_unix_root";
-        value = determineUnixRoot(jvmFlag);
-        if (value == null) {
-          throw new IllegalStateException(
-              String.format(
-                  "\"%1$s\" JVM flag is not set. Use the --host_jvm_args flag or export the "
-                      + "BAZEL_SH environment variable. For example "
-                      + "\"--host_jvm_args=-D%1$s=c:/tools/msys64\" or "
-                      + "\"set BAZEL_SH=c:/tools/msys64/usr/bin/bash.exe\".",
-                  jvmFlag));
-        }
-        if (getDriveStrLength(value) != 3) {
-          throw new IllegalStateException(
-              String.format("\"%s\" must be an absolute path, got: \"%s\"", jvmFlag, value));
-        }
-        value = value.replace('\\', '/');
-        if (value.length() > 3 && value.endsWith("/")) {
-          value = value.substring(0, value.length() - 1);
-        }
-        UNIX_ROOT.set(value);
-      }
-      return value;
-    }
-
-    private String determineUnixRoot(String jvmArgName) {
-      // Get the path from a JVM flag, if specified.
-      String path = System.getProperty(jvmArgName);
-      if (path == null) {
-        return null;
-      }
-      path = path.trim();
-      if (path.isEmpty()) {
-        return null;
-      }
-      return path;
+    @Override
+    public boolean isRoot(LocalPath localPath) {
+      // Return true for Unix paths for testing
+      return localPath.path.equals("/");
     }
   }
 
@@ -685,7 +674,8 @@
   private static int removeRelativePaths(String[] segments, int starti) {
     int segmentCount = 0;
     int shift = starti;
-    for (int i = starti; i < segments.length; ++i) {
+    int n = segments.length;
+    for (int i = starti; i < n; ++i) {
       String segment = segments[i];
       switch (segment) {
         case ".":