Optimize absolute() and normalize().

This short-circuits absolute() if the path is already absolute.
Detecting absolute paths is very fast, whereas join() is relatively
slow.

For normalize(), this runs a single-pass check over a path to see
whether it needs to be normalized at all. This check isn't free, but
it's a lot less costly than parsing and reserializing the path, and it
covers the common case.

See dart-lang/glob#24962

R=rnystrom@google.com

Review URL: https://codereview.chromium.org//1455003002 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 457be80..1dfc09a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.3.7
+
+* Improve the performance of `absolute()` and `normalize()`.
+
 ## 1.3.6
 
 * Ensure that `path.toUri` preserves trailing slashes for relative paths.
diff --git a/benchmark/benchmark.dart b/benchmark/benchmark.dart
index 419eee0..03871d0 100644
--- a/benchmark/benchmark.dart
+++ b/benchmark/benchmark.dart
@@ -33,6 +33,7 @@
       }
     }
 
+    benchmark('absolute', context.absolute);
     benchmark('basename', context.basename);
     benchmark('basenameWithoutExtension', context.basenameWithoutExtension);
     benchmark('dirname', context.dirname);
diff --git a/lib/src/context.dart b/lib/src/context.dart
index db055a1..44bdde9 100644
--- a/lib/src/context.dart
+++ b/lib/src/context.dart
@@ -4,6 +4,7 @@
 
 library path.context;
 
+import 'characters.dart' as chars;
 import 'internal_style.dart';
 import 'style.dart';
 import 'parsed_path.dart';
@@ -73,6 +74,15 @@
   /// If [current] isn't absolute, this won't return an absolute path.
   String absolute(String part1, [String part2, String part3, String part4,
       String part5, String part6, String part7]) {
+    _validateArgList(
+        "absolute", [part1, part2, part3, part4, part5, part6, part7]);
+
+    // If there's a single absolute path, just return it. This is a lot faster
+    // for the common case of `p.absolute(path)`.
+    if (part2 == null && isAbsolute(part1) && !isRootRelative(part1)) {
+      return part1;
+    }
+
     return join(current, part1, part2, part3, part4, part5, part6, part7);
   }
 
@@ -295,11 +305,79 @@
   ///
   ///     context.normalize('path/./to/..//file.text'); // -> 'path/file.txt'
   String normalize(String path) {
+    if (!_needsNormalization(path)) return path;
+
     var parsed = _parse(path);
     parsed.normalize();
     return parsed.toString();
   }
 
+  /// Returns whether [path] needs to be normalized.
+  bool _needsNormalization(String path) {
+    var start = 0;
+    var codeUnits = path.codeUnits;
+    var previousPrevious;
+    var previous;
+
+    // Skip past the root before we start looking for snippets that need
+    // normalization. We want to normalize "//", but not when it's part of
+    // "http://".
+    var root = style.rootLength(path);
+    if (root != 0) {
+      start = root;
+      previous = chars.SLASH;
+
+      // On Windows, the root still needs to be normalized if it contains a
+      // forward slash.
+      if (style == Style.windows) {
+        for (var i = 0; i < root; i++) {
+          if (codeUnits[i] == chars.SLASH) return true;
+        }
+      }
+    }
+
+    for (var i = start; i < codeUnits.length; i++) {
+      var codeUnit = codeUnits[i];
+      if (style.isSeparator(codeUnit)) {
+        // Forward slashes in Windows paths are normalized to backslashes.
+        if (style == Style.windows && codeUnit == chars.SLASH) return true;
+
+        // Multiple separators are normalized to single separators.
+        if (previous != null && style.isSeparator(previous)) return true;
+
+        // Single dots and double dots are normalized to directory traversals.
+        //
+        // This can return false positives for ".../", but that's unlikely
+        // enough that it's probably not going to cause performance issues.
+        if (previous == chars.PERIOD &&
+            (previousPrevious == null ||
+             previousPrevious == chars.PERIOD ||
+             style.isSeparator(previousPrevious))) {
+          return true;
+        }
+      }
+
+      previousPrevious = previous;
+      previous = codeUnit;
+    }
+
+    // Empty paths are normalized to ".".
+    if (previous == null) return true;
+
+    // Trailing separators are removed.
+    if (style.isSeparator(previous)) return true;
+
+    // Single dots and double dots are normalized to directory traversals.
+    if (previous == chars.PERIOD &&
+        (previousPrevious == null ||
+         previousPrevious == chars.SLASH ||
+         previousPrevious == chars.PERIOD)) {
+      return true;
+    }
+
+    return false;
+  }
+
   /// Attempts to convert [path] to an equivalent relative path relative to
   /// [root].
   ///
diff --git a/pubspec.yaml b/pubspec.yaml
index e669844..bc7ab98 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: path
-version: 1.3.7-dev
+version: 1.3.7
 author: Dart Team <misc@dartlang.org>
 description: >
  A string-based path manipulation library. All of the path operations you know