Add InternalStyle:rootLength to implement isAbsolute and rootPrefix.

This is the first step at adding a few helper methods for improving
path package performance.

BUG=
R=nweiz@google.com, rnystrom@google.com

Review URL: https://codereview.chromium.org//439223002

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/path@38966 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/lib/src/context.dart b/lib/src/context.dart
index 7a88909..e9237a2 100644
--- a/lib/src/context.dart
+++ b/lib/src/context.dart
@@ -148,10 +148,7 @@
   ///     context.rootPrefix('path/to/foo'); // -> ''
   ///     context.rootPrefix('http://dartlang.org/path/to/foo');
   ///       // -> 'http://dartlang.org'
-  String rootPrefix(String path) {
-    var root = _parse(path).root;
-    return root == null ? '' : root;
-  }
+  String rootPrefix(String path) => path.substring(0, style.rootLength(path));
 
   /// Returns `true` if [path] is an absolute path and `false` if it is a
   /// relative path.
@@ -165,7 +162,7 @@
   /// relative to the root of the current URL. Since root-relative paths are
   /// still absolute in every other sense, [isAbsolute] will return true for
   /// them. They can be detected using [isRootRelative].
-  bool isAbsolute(String path) => _parse(path).isAbsolute;
+  bool isAbsolute(String path) => style.rootLength(path) > 0;
 
   /// Returns `true` if [path] is a relative path and `false` if it is absolute.
   /// On POSIX systems, absolute paths start with a `/` (forward slash). On
@@ -181,7 +178,7 @@
   /// them. They can be detected using [isRootRelative].
   ///
   /// No POSIX and Windows paths are root-relative.
-  bool isRootRelative(String path) => _parse(path).isRootRelative;
+  bool isRootRelative(String path) => style.isRootRelative(path);
 
   /// Joins the given path parts into a single path. Example:
   ///
diff --git a/lib/src/internal_style.dart b/lib/src/internal_style.dart
index 67b5d34..db2d346 100644
--- a/lib/src/internal_style.dart
+++ b/lib/src/internal_style.dart
@@ -33,14 +33,25 @@
   /// "usr", an additional "/" is needed (making "file:///usr").
   bool needsSeparator(String path);
 
+  /// Returns the number of characters of the root part.
+  ///
+  /// Returns 0 if the path is relative.
+  ///
+  /// If the path is root-relative, the root length is 1.
+  int rootLength(String path);
+
   /// Gets the root prefix of [path] if path is absolute. If [path] is relative,
   /// returns `null`.
-  String getRoot(String path);
+  String getRoot(String path) {
+    var length = rootLength(path);
+    if (length > 0) return path.substring(0, length);
+    return isRootRelative(path) ? path[0] : null;
+  }
 
-  /// Gets the root prefix of [path] if it's root-relative.
+  /// Returns whether [path] is root-relative.
   ///
-  /// If [path] is relative or absolute and not root-relative, returns `null`.
-  String getRelativeRoot(String path);
+  /// If [path] is relative or absolute and not root-relative, returns `false`.
+  bool isRootRelative(String path);
 
   /// Returns the path represented by [uri] in this style.
   String pathFromUri(Uri uri);
diff --git a/lib/src/parsed_path.dart b/lib/src/parsed_path.dart
index 57773ee..a7b0afd 100644
--- a/lib/src/parsed_path.dart
+++ b/lib/src/parsed_path.dart
@@ -45,7 +45,7 @@
 
     // Remove the root prefix, if any.
     var root = style.getRoot(path);
-    var isRootRelative = style.getRelativeRoot(path) != null;
+    var isRootRelative = style.isRootRelative(path);
     if (root != null) path = path.substring(root.length);
 
     // Split the parts on path separators.
diff --git a/lib/src/style/posix.dart b/lib/src/style/posix.dart
index b8b82b4..74aeb4c 100644
--- a/lib/src/style/posix.dart
+++ b/lib/src/style/posix.dart
@@ -30,11 +30,13 @@
   bool needsSeparator(String path) =>
       path.isNotEmpty && !isSeparator(path.codeUnitAt(path.length - 1));
 
-  String getRoot(String path) {
-    if (path.isNotEmpty && isSeparator(path.codeUnitAt(0))) return '/';
-    return null;
+  int rootLength(String path) {
+    if (path.isNotEmpty && isSeparator(path.codeUnitAt(0))) return 1;
+    return 0;
   }
 
+  bool isRootRelative(String path) => false;
+
   String getRelativeRoot(String path) => null;
 
   String pathFromUri(Uri uri) {
diff --git a/lib/src/style/url.dart b/lib/src/style/url.dart
index f383923..d5d0fdb 100644
--- a/lib/src/style/url.dart
+++ b/lib/src/style/url.dart
@@ -36,53 +36,30 @@
 
     // A URI that's just "scheme://" needs an extra separator, despite ending
     // with "/".
-    var root = _getRoot(path);
-    return root != null && root.endsWith('://');
+    return path.endsWith("://") && rootLength(path) == path.length;
   }
 
-  String getRoot(String path) {
-    var root = _getRoot(path);
-    return root == null ? getRelativeRoot(path) : root;
+  int rootLength(String path) {
+    if (path.isEmpty) return 0;
+    if (isSeparator(path.codeUnitAt(0))) return 1;
+    var index = path.indexOf("/");
+    if (index > 0 && path.startsWith('://', index - 1)) {
+      // The root part is up until the next '/', or the full path. Skip
+      // '://' and search for '/' after that.
+      index = path.indexOf('/', index + 2);
+      if (index > 0) return index;
+      return path.length;
+    }
+    return 0;
   }
 
-  String getRelativeRoot(String path) {
-    if (path.isEmpty) return null;
-    return isSeparator(path.codeUnitAt(0)) ? "/" : null;
-  }
+  bool isRootRelative(String path) =>
+      path.isNotEmpty && isSeparator(path.codeUnitAt(0));
+
+  String getRelativeRoot(String path) => isRootRelative(path) ? '/' : null;
 
   String pathFromUri(Uri uri) => uri.toString();
 
   Uri relativePathToUri(String path) => Uri.parse(path);
   Uri absolutePathToUri(String path) => Uri.parse(path);
-
-  // A helper method for [getRoot] that doesn't handle relative roots.
-  String _getRoot(String path) {
-    if (path.isEmpty) return null;
-
-    // We aren't using a RegExp for this because they're slow (issue 19090). If
-    // we could, we'd match against r"[a-zA-Z][-+.a-zA-Z\d]*://[^/]*".
-
-    if (!isAlphabetic(path.codeUnitAt(0))) return null;
-    var start = 1;
-    for (; start < path.length; start++) {
-      var char = path.codeUnitAt(start);
-      if (isAlphabetic(char)) continue;
-      if (isNumeric(char)) continue;
-      if (char == chars.MINUS || char == chars.PLUS || char == chars.PERIOD) {
-        continue;
-      }
-
-      break;
-    }
-
-    if (start + 3 > path.length) return null;
-    if (path.substring(start, start + 3) != '://') return null;
-    start += 3;
-
-    // A URL root can end with a non-"/" prefix.
-    while (start < path.length && !isSeparator(path.codeUnitAt(start))) {
-      start++;
-    }
-    return path.substring(0, start);
-  }
 }
diff --git a/lib/src/style/windows.dart b/lib/src/style/windows.dart
index 2965f1e..16e14d5 100644
--- a/lib/src/style/windows.dart
+++ b/lib/src/style/windows.dart
@@ -34,16 +34,38 @@
     return !isSeparator(path.codeUnitAt(path.length - 1));
   }
 
-  String getRoot(String path) {
-    var root = _getRoot(path);
-    return root == null ? getRelativeRoot(path) : root;
+  int rootLength(String path) {
+    if (path.isEmpty) return 0;
+    if (path.codeUnitAt(0) == chars.SLASH) return 1;
+    if (path.codeUnitAt(0) == chars.BACKSLASH) {
+    if (path.length < 2 || path.codeUnitAt(1) != chars.BACKSLASH) return 1;
+      // The path is a network share. Search for up to two '\'s, as they are
+      // the server and share - and part of the root part.
+      var index = path.indexOf('\\', 2);
+      if (index > 0) {
+        index = path.indexOf('\\', index + 1);
+        if (index > 0) return index;
+      }
+      return path.length;
+    }
+    // If the path is of the form 'C:/' or 'C:\', with C being any letter, it's
+    // a root part.
+    if (path.length < 3) return 0;
+    // Check for the letter.
+    if (!isAlphabetic(path.codeUnitAt(0))) return 0;
+    // Check for the ':'.
+    if (path.codeUnitAt(1) != chars.COLON) return 0;
+    // Check for either '/' or '\'.
+    if (!isSeparator(path.codeUnitAt(2))) return 0;
+    return 3;
   }
 
+  bool isRootRelative(String path) => rootLength(path) == 1;
+
   String getRelativeRoot(String path) {
-    if (path.isEmpty) return null;
-    if (!isSeparator(path.codeUnitAt(0))) return null;
-    if (path.length > 1 && isSeparator(path.codeUnitAt(1))) return null;
-    return path[0];
+    var length = rootLength(path);
+    if (length == 1) return path[0];
+    return null;
   }
 
   String pathFromUri(Uri uri) {
@@ -100,39 +122,4 @@
       return new Uri(scheme: 'file', pathSegments: parsed.parts);
     }
   }
-
-  // A helper method for [getRoot] that doesn't handle relative roots.
-  String _getRoot(String path) {
-    if (path.length < 3) return null;
-
-    // We aren't using a RegExp for this because they're slow (issue 19090). If
-    // we could, we'd match against r'^(\\\\[^\\]+\\[^\\/]+|[a-zA-Z]:[/\\])'.
-
-    // Try roots like "C:\".
-    if (isAlphabetic(path.codeUnitAt(0))) {
-      if (path.codeUnitAt(1) != chars.COLON) return null;
-      if (!isSeparator(path.codeUnitAt(2))) return null;
-      return path.substring(0, 3);
-    }
-
-    // Try roots like "\\server\share".
-    if (!path.startsWith('\\\\')) return null;
-
-    var start = 2;
-    // The server is one or more non-"\" characters.
-    while (start < path.length && path.codeUnitAt(start) != chars.BACKSLASH) {
-      start++;
-    }
-    if (start == 2 || start == path.length) return null;
-
-    // The share is one or more non-"\" characters.
-    start += 1;
-    if (path.codeUnitAt(start) == chars.BACKSLASH) return null;
-    start += 1;
-    while (start < path.length && path.codeUnitAt(start) != chars.BACKSLASH) {
-      start++;
-    }
-
-    return path.substring(0, start);
-  }
-}
\ No newline at end of file
+}
diff --git a/test/url_test.dart b/test/url_test.dart
index 27691d8..d75377e 100644
--- a/test/url_test.dart
+++ b/test/url_test.dart
@@ -37,6 +37,7 @@
     expect(context.rootPrefix('http://dartlang.org'), 'http://dartlang.org');
     expect(context.rootPrefix('file://'), 'file://');
     expect(context.rootPrefix('/'), '/');
+    expect(context.rootPrefix('foo/bar://'), '');
   });
 
   test('dirname', () {
diff --git a/test/windows_test.dart b/test/windows_test.dart
index 7c16e31..72eefc3 100644
--- a/test/windows_test.dart
+++ b/test/windows_test.dart
@@ -41,6 +41,9 @@
     expect(context.rootPrefix('C:\\'), r'C:\');
     expect(context.rootPrefix('C:/'), 'C:/');
     expect(context.rootPrefix(r'\\server\share\a\b'), r'\\server\share');
+    expect(context.rootPrefix(r'\\server\share'), r'\\server\share');
+    expect(context.rootPrefix(r'\\server\'), r'\\server\');
+    expect(context.rootPrefix(r'\\server'), r'\\server');
     expect(context.rootPrefix(r'\a\b'), r'\');
     expect(context.rootPrefix(r'/a/b'), r'/');
     expect(context.rootPrefix(r'\'), r'\');