Fixes issues #490, #986, #1066.

Implements a number of missing String and Character methods, adds and improves
many of the tests for these classes, and adds a general-purpose method to hide
constants from the compiler to make tests more resilient against compiler
optimizations.

Issues: 490, 986, 1066
Patch by: jat
Review by: spoon



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2185 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/junit/client/GWTTestCase.java b/user/src/com/google/gwt/junit/client/GWTTestCase.java
index d5d0feb..6ae89ce 100644
--- a/user/src/com/google/gwt/junit/client/GWTTestCase.java
+++ b/user/src/com/google/gwt/junit/client/GWTTestCase.java
@@ -52,6 +52,7 @@
    *             traces. It can be useful for debugging web mode failures, but
    *             production code should not depend on it.
    */
+  @Deprecated
   public final void addCheckpoint(String msg) {
     // implemented in the translatable version of this class
   }
@@ -78,6 +79,7 @@
    *             traces. It can be useful for debugging web mode failures, but
    *             production code should not depend on it.
    */
+  @Deprecated
   public final void clearCheckpoints() {
     // implemented in the translatable version of this class
   }
@@ -91,6 +93,7 @@
    *             traces. It can be useful for debugging web mode failures, but
    *             production code should not depend on it.
    */
+  @Deprecated
   public final String[] getCheckpoints() {
     // implemented in the translatable version of this class
     return null;
@@ -176,6 +179,23 @@
   }
 
   /**
+   * Wrap a constant in such a way as to prevent the compiler from inlining it.
+   * 
+   * NOTE: if you change this, update the one in gwt-user/core/super as well.
+   * 
+   * @param <T>
+   * @param value to wrap
+   * @return same value, 
+   */
+  protected <T> T hideFromCompiler(T value) {
+    int i = 7;
+    while (i > 0) {
+      i -= 2;
+    }
+    return (i & 1) != 0 ? value : null;
+  }
+
+  /**
    * Runs the test via the {@link JUnitShell} environment. Do not override or
    * call this method.
    */
diff --git a/user/super/com/google/gwt/emul/java/lang/Character.java b/user/super/com/google/gwt/emul/java/lang/Character.java
index fbe8f5c..21ae92a 100644
--- a/user/super/com/google/gwt/emul/java/lang/Character.java
+++ b/user/super/com/google/gwt/emul/java/lang/Character.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -19,14 +19,80 @@
 
 /**
  * Wraps a native <code>char</code> as an object.
+ *
+ * TODO(jat): many of the classification methods implemented here are not
+ * correct in that they only handle ASCII characters, and many other methods
+ * are not currently implemented.  I think the proper approach is to introduce * a deferred binding parameter which substitutes an implementation using
+ * a fully-correct Unicode character database, at the expense of additional
+ * data being downloaded.  That way developers that need the functionality
+ * can get it without those who don't need it paying for it.
+ *
+ * <pre>
+ * The following methods are still not implemented -- most would require Unicode
+ * character db to be useful:
+ *  - digit / is* / to*(int codePoint)
+ *  - isDefined(char)
+ *  - isIdentifierIgnorable(char)
+ *  - isJavaIdentifierPart(char)
+ *  - isJavaIdentifierStart(char)
+ *  - isJavaLetter(char) -- deprecated, so probably not
+ *  - isJavaLetterOrDigit(char) -- deprecated, so probably not
+ *  - isISOControl(char)
+ *  - isMirrored(char)
+ *  - isSpaceChar(char)
+ *  - isTitleCase(char)
+ *  - isUnicodeIdentifierPart(char)
+ *  - isUnicodeIdentifierStart(char)
+ *  - isWhitespace(char)
+ *  - getDirectionality(*)
+ *  - getNumericValue(*)
+ *  - getType(*)
+ *  - reverseBytes(char) -- any use for this at all in the browser?
+ *  - toTitleCase(*)
+ *  - all the category constants for classification
+ *
+ * The following do not properly handle characters outside of ASCII:
+ *  - digit(char c, int radix)
+ *  - isDigit(char c)
+ *  - isLetter(char c)
+ *  - isLetterOrDigit(char c)
+ *  - isLowerCase(char c)
+ *  - isUpperCase(char c)
+ * </pre>
  */
 public final class Character implements Comparable<Character>, Serializable {
+  /**
+   * Helper class to share code between implementations, by making a char
+   * array look like a CharSequence.
+   */
+  static class CharSequenceAdapter implements CharSequence {
+    private char[] charArray;
+    private int start;
+    private int end;
 
-  public static final int MIN_RADIX = 2;
-  public static final int MAX_RADIX = 36;
+    public CharSequenceAdapter(char[] charArray) {
+      this(charArray, 0, charArray.length);
+    }
 
-  public static final char MIN_VALUE = '\u0000';
-  public static final char MAX_VALUE = '\uFFFF';
+    public CharSequenceAdapter(char[] charArray, int start, int end) {
+      this.charArray = charArray;
+      this.start = start;
+      this.end = end;
+    }
+
+    public char charAt(int index) {
+      return charArray[index + start];
+    }
+
+    public int length() {
+      return end - start;
+    }
+
+    public java.lang.CharSequence subSequence(int start, int end) {
+      return new CharSequenceAdapter(charArray, this.start + start,
+          this.start + end);
+    }
+  }
 
   /**
    * Use nested class to avoid clinit on outer.
@@ -36,6 +102,77 @@
     private static Character[] boxedValues = new Character[128];
   }
 
+  public static final Class<Character> TYPE = Character.class;
+  public static final int MIN_RADIX = 2;
+
+  public static final int MAX_RADIX = 36;
+  public static final char MIN_VALUE = '\u0000';
+
+  public static final char MAX_VALUE = '\uFFFF';
+  public static final char MIN_SURROGATE = '\uD800';
+  public static final char MAX_SURROGATE = '\uDFFF';
+  public static final char MIN_LOW_SURROGATE = '\uDC00';
+  public static final char MAX_LOW_SURROGATE = '\uDFFF';
+  public static final char MIN_HIGH_SURROGATE = '\uD800';
+
+  public static final char MAX_HIGH_SURROGATE = '\uDBFF';
+  public static final int MIN_SUPPLEMENTARY_CODE_POINT = 0x10000;
+  public static final int MIN_CODE_POINT = 0x0000;
+
+  public static final int MAX_CODE_POINT = 0x10FFFF;
+
+  public static final int SIZE = 16;
+
+  public static int charCount(int codePoint) {
+    return codePoint >= MIN_SUPPLEMENTARY_CODE_POINT ? 2 : 1;
+  }
+
+  public static int codePointAt(char[] a, int index) {
+    return codePointAt(new CharSequenceAdapter(a), index, a.length);
+  }
+
+  public static int codePointAt(char[] a, int index, int limit) {
+    return codePointAt(new CharSequenceAdapter(a), index, limit);
+  }
+
+  public static int codePointAt(CharSequence seq, int index) {
+    return codePointAt(seq, index, seq.length());
+  }
+
+  public static int codePointBefore(char[] a, int index) {
+    return codePointBefore(new CharSequenceAdapter(a), index, 0);
+  }
+
+  public static int codePointBefore(char[] a, int index, int start) {
+    return codePointBefore(new CharSequenceAdapter(a), index, start);
+  }
+
+  public static int codePointBefore(CharSequence cs, int index) {
+    return codePointBefore(cs, index, 0);
+  }
+
+  public static int codePointCount(char[] a, int offset, int count) {
+    return codePointCount(new CharSequenceAdapter(a), offset, offset + count);
+  }
+
+  public static int codePointCount(CharSequence seq, int beginIndex,
+      int endIndex) {
+    int count = 0;
+    for (int idx = beginIndex; idx < endIndex; ) {
+      char ch = seq.charAt(idx++);
+      if (isHighSurrogate(ch) && idx < endIndex
+          && (isLowSurrogate(seq.charAt(idx)))) {
+        // skip the second char of surrogate pairs
+        ++idx;
+      }
+      ++count;
+    }
+    return count;
+  }
+
+  /*
+   * TODO: correct Unicode handling.
+   */
   public static int digit(char c, int radix) {
     if (radix < MIN_RADIX || radix > MAX_RADIX) {
       return -1;
@@ -77,28 +214,52 @@
   /**
    * @skip
    *
-   * Here for shared implementation with Arrays.hashCode
+   * public for shared implementation with Arrays.hashCode
    */
   public static int hashCode(char c) {
     return c;
   }
 
+  /*
+   * TODO: correct Unicode handling.
+   */
   public static native boolean isDigit(char c) /*-{
     return (null != String.fromCharCode(c).match(/\d/));
   }-*/;
 
+  public static boolean isHighSurrogate(char ch) {
+    return ch >= MIN_HIGH_SURROGATE && ch <= MAX_HIGH_SURROGATE;
+  }
+
+  /*
+   * TODO: correct Unicode handling.
+   */
   public static native boolean isLetter(char c) /*-{
     return (null != String.fromCharCode(c).match(/[A-Z]/i));
   }-*/;
 
+  /*
+   * TODO: correct Unicode handling.
+   */
   public static native boolean isLetterOrDigit(char c) /*-{
     return (null != String.fromCharCode(c).match(/[A-Z\d]/i));
   }-*/;
 
+  /*
+   * TODO: correct Unicode handling.
+   */
   public static boolean isLowerCase(char c) {
     return toLowerCase(c) == c && isLetter(c);
   }
 
+  public static boolean isLowSurrogate(char ch) {
+    return ch >= MIN_LOW_SURROGATE && ch <= MAX_LOW_SURROGATE;
+  }
+
+  /**
+   * Deprecated - see isWhitespace(char).
+   */
+  @Deprecated
   public static boolean isSpace(char c) {
     switch (c) {
       case ' ':
@@ -116,9 +277,95 @@
     }
   }
 
+  public static boolean isSupplementaryCodePoint(int codePoint) {
+    return codePoint >= MIN_SUPPLEMENTARY_CODE_POINT && codePoint <= MAX_CODE_POINT;
+  }
+
+  public static boolean isSurrogatePair(char highSurrogate, char lowSurrogate) {
+    return isHighSurrogate(highSurrogate) && isLowSurrogate(lowSurrogate);
+  }
+  
+  /*
+   * TODO: correct Unicode handling.
+   */
   public static boolean isUpperCase(char c) {
     return toUpperCase(c) == c && isLetter(c);
   }
+  
+  public static boolean isValidCodePoint(int codePoint) {
+    return codePoint >= MIN_CODE_POINT && codePoint <= MAX_CODE_POINT;
+  }
+  
+  public static int offsetByCodePoints(char[] a, int start, int count, int index,
+      int codePointOffset) {
+    return offsetByCodePoints(new CharSequenceAdapter(a, start, count), index,
+        codePointOffset);
+  }
+  
+  public static int offsetByCodePoints(CharSequence seq, int index,
+      int codePointOffset) {
+    if (codePointOffset < 0) {
+      // move backwards
+      while (codePointOffset < 0) {
+        --index;
+        if (Character.isLowSurrogate(seq.charAt(index))
+            && Character.isHighSurrogate(seq.charAt(index - 1))) {
+          --index;
+        }
+        ++codePointOffset;
+      }
+    } else {
+      // move forwards
+      while (codePointOffset > 0) {
+        if (Character.isHighSurrogate(seq.charAt(index))
+            && Character.isLowSurrogate(seq.charAt(index + 1))) {
+          ++index;
+        }
+        ++index;
+        --codePointOffset;
+      }
+    }
+    return index;
+  }
+
+  public static char[] toChars(int codePoint) {
+    if (codePoint < 0 || codePoint > MAX_CODE_POINT) {
+      throw new IllegalArgumentException();
+    }
+    if (codePoint >= MIN_SUPPLEMENTARY_CODE_POINT) {
+      return new char[] {
+          getHighSurrogate(codePoint),
+          getLowSurrogate(codePoint),
+      };
+    } else {
+      return new char[] {
+          (char) codePoint,
+      };
+    }
+  }
+
+  public static int toChars(int codePoint, char[] dst, int dstIndex) {
+    if (codePoint < 0 || codePoint > MAX_CODE_POINT) {
+      throw new IllegalArgumentException();
+    }
+    if (codePoint >= MIN_SUPPLEMENTARY_CODE_POINT) {
+      dst[dstIndex++] = getHighSurrogate(codePoint);
+      dst[dstIndex] = getLowSurrogate(codePoint);
+      return 2;
+    } else {
+      dst[dstIndex] = (char) codePoint;
+      return 1;
+    }
+  }
+
+  public static int toCodePoint(char highSurrogate, char lowSurrogate) {
+    /*
+     * High and low surrogate chars have the bottom 10 bits to store the value
+     * above MIN_SUPPLEMENTARY_CODE_POINT, so grab those bits and add the
+     * offset.
+     */
+    return MIN_SUPPLEMENTARY_CODE_POINT + ((highSurrogate & 1023) << 10) + (lowSurrogate & 1023);
+  }
 
   public static native char toLowerCase(char c) /*-{
     return String.fromCharCode(c).toLowerCase().charCodeAt(0);
@@ -143,6 +390,51 @@
     return new Character(c);
   }
 
+  static int codePointAt(CharSequence cs, int index, int limit) {
+    char hiSurrogate = cs.charAt(index++);
+    char loSurrogate;
+    if (Character.isHighSurrogate(hiSurrogate) && index < limit
+        && Character.isLowSurrogate(loSurrogate = cs.charAt(index))) {
+      return Character.toCodePoint(hiSurrogate, loSurrogate);
+    }
+    return hiSurrogate;
+  }
+
+  static int codePointBefore(CharSequence cs, int index, int start) {
+    char loSurrogate = cs.charAt(--index);
+    char highSurrogate;
+    if (isLowSurrogate(loSurrogate) && index > start
+        && isHighSurrogate(highSurrogate = cs.charAt(index - 1))) {
+      return toCodePoint(highSurrogate, loSurrogate);
+    }
+    return loSurrogate;
+  }
+
+  /**
+   * Computes the high surrogate character of the UTF16 representation of a
+   * non-BMP code point. See {@link getLowSurrogate}.
+   * 
+   * @param codePoint requested codePoint, required to be >=
+   *          MIN_SUPPLEMENTARY_CODE_POINT
+   * @return high surrogate character
+   */
+  static char getHighSurrogate(int codePoint) {
+    return (char) (MIN_HIGH_SURROGATE
+        + (((codePoint - MIN_SUPPLEMENTARY_CODE_POINT) >> 10) & 1023));
+  }
+
+  /**
+   * Computes the low surrogate character of the UTF16 representation of a
+   * non-BMP code point. See {@link getHighSurrogate}.
+   * 
+   * @param codePoint requested codePoint, required to be >=
+   *          MIN_SUPPLEMENTARY_CODE_POINT
+   * @return low surrogate character
+   */
+  static char getLowSurrogate(int codePoint) {
+    return (char) (MIN_LOW_SURROGATE + ((codePoint - MIN_SUPPLEMENTARY_CODE_POINT) & 1023));
+  }
+
   private final transient char value;
 
   public Character(char value) {
@@ -154,13 +446,8 @@
   }
 
   public int compareTo(Character c) {
-    if (value < c.value) {
-      return -1;
-    } else if (value > c.value) {
-      return 1;
-    } else {
-      return 0;
-    }
+    // JLS specifies that the chars are promoted to int before subtraction.
+    return value - c.value;
   }
 
   @Override
diff --git a/user/super/com/google/gwt/emul/java/lang/String.java b/user/super/com/google/gwt/emul/java/lang/String.java
index cd2464b..197e5cb 100644
--- a/user/super/com/google/gwt/emul/java/lang/String.java
+++ b/user/super/com/google/gwt/emul/java/lang/String.java
@@ -25,13 +25,63 @@
 import com.google.gwt.core.client.JavaScriptObject;
 
 import java.io.Serializable;
+import java.util.Comparator;
 
 /**
  * Intrinsic string class.
+ * 
+ * TODO(jat): consider whether we want to support the following methods;
+ * 
+ * <ul>
+ * <li>deprecated methods dealing with bytes (I assume not since I can't see much use for them)
+ *   <ul>
+ *    <li>String(byte[] ascii, int hibyte)
+ *    <li>String(byte[] ascii, int hibyte, int offset, int count)
+ *    <li>getBytes(int srcBegin, int srcEnd, byte[] dst, int dstBegin)
+ *   </ul>
+ * <li>methods which in JS will essentially do nothing or be the same as other methods
+ *   <ul>
+ *    <li>copyValueOf(char[] data)
+ *    <li>copyValueOf(char[] data, int offset, int count)
+ *   </ul>
+ * <li>methods added in Java 1.6 (the issue is how will it impact users building against Java 1.5)
+ *   <ul>
+ *    <li>isEmpty()
+ *   </ul>
+ * <li>other methods which are not straightforward in JS
+ *   <ul>
+ *    <li>format(String format, Object... args)
+ *   </ul>
+ * </ul>
+ *  
+ * Also, in general, we need to improve our support of non-ASCII characters.  The problem is
+ * that correct support requires large tables, and we don't want to make users who aren't going
+ * to use that pay for it.  There are two ways to do that:
+ * <ol>
+ *    <li>construct the tables in such a way that if the corresponding method is not called the
+ *       table will be elided from the output.
+ *    <li>provide a deferred binding target selecting the level of compatibility required.  Those
+ *       that only need ASCII (or perhaps a different relatively small subset such as Latin1-5)
+ *       will not pay for large tables, even if they do call toLowercase(), for example.
+ * </ol>
+ *       
+ * Also, if we ever add multi-locale support, there are a number of other methods such as
+ * toLowercase(Locale) we will want to consider supporting.  This is probably rare, but there
+ * will be some apps (such as a translation tool) which cannot be written without this support.
+ * 
+ * Another category of incomplete support is that we currently just use the JS regex support,
+ * which is not exactly the same as Java.  We should support Java syntax by mapping it into
+ * equivalent JS patterns, or emulating them.
  */
-public final class String implements Comparable<String>, CharSequence, 
+public final class String implements Comparable<String>, CharSequence,
     Serializable {
 
+  public static final Comparator<String> CASE_INSENSITIVE_ORDER = new Comparator<String>() {
+    public int compare(String a, String b) {
+      return a.compareToIgnoreCase(b);
+    }
+  };
+
   /**
    * Accesses need to be prefixed with ':' to prevent conflict with built-in
    * JavaScript properties.
@@ -63,7 +113,7 @@
   public static String valueOf(double x) {
     return "" + x;
   }
-  
+
   public static String valueOf(float x) {
     return "" + x;
   }
@@ -161,6 +211,32 @@
   /**
    * @skip
    */
+  static String _String(int[] codePoints, int offset, int count) {
+    char[] chars = new char[count * 2];
+    int charIdx = 0;
+    while (count-- > 0) {
+      charIdx += Character.toChars(codePoints[offset++], chars, charIdx);
+    }
+    return valueOf(chars, 0, charIdx);
+  }
+
+  /**
+   * @skip
+   */
+  static String _String(StringBuffer sb) {
+    return valueOf(sb);
+  }
+
+  /**
+   * @skip
+   */
+  static String _String(StringBuilder sb) {
+    return valueOf(sb);
+  }
+
+  /**
+   * @skip
+   */
   static String _String(String other) {
     return other;
   }
@@ -172,6 +248,52 @@
 
   // CHECKSTYLE_ON
 
+  private static native int compareTo(String thisStr, String otherStr) /*-{
+    // Coerce to a primitive string to force string comparison
+    thisStr = String(thisStr);
+    if (thisStr == otherStr) {
+      return 0;
+    }
+    return thisStr < otherStr ? -1 : 1;
+  }-*/;
+
+  private static native String fromCharCode(char ch) /*-{
+    return String.fromCharCode(ch);
+  }-*/;
+  
+  private static String fromCodePoint(int codePoint) {
+    if (codePoint >= Character.MIN_SUPPLEMENTARY_CODE_POINT) {
+      char hiSurrogate = Character.getHighSurrogate(codePoint);
+      char loSurrogate = Character.getLowSurrogate(codePoint);
+      return String.fromCharCode(hiSurrogate)
+          + String.fromCharCode(loSurrogate);
+    } else {
+      return String.fromCharCode((char) codePoint);
+    }
+  }
+
+  private static native boolean regionMatches(String thisStr, boolean ignoreCase, int toffset,
+      String other, int ooffset, int len) /*-{
+    if (toffset < 0 || ooffset < 0 || len <= 0) {
+      return false;
+    }
+
+    if (toffset + len > thisStr.length || ooffset + len > other.length) {
+      return false;
+    }
+
+    var left = thisStr.substr(toffset, len);
+    var right = other.substr(ooffset, len);
+
+    if (ignoreCase) {
+      left = left.toLowerCase();
+      right = right.toLowerCase();
+    }
+
+    return left == right;
+  }-*/;
+
+
   public String() {
     // magic delegation to _String
     _String();
@@ -187,30 +309,48 @@
     _String(value, offset, count);
   }
 
+  public String(int codePoints[], int offset, int count) {
+    // magic delegation to _String
+    _String(codePoints, offset, count);
+  }
+
   public String(String other) {
     // magic delegation to _String
     _String(other);
   }
 
+  public String(StringBuffer sb) {
+    // magic delegation to _String
+    _String(sb);
+  }
+
+  public String(StringBuilder sb) {
+    // magic delegation to _String
+    _String(sb);
+  }
+
   public native char charAt(int index) /*-{ 
     return this.charCodeAt(index);
   }-*/;
 
+  public int codePointAt(int index) {
+    return Character.codePointAt(this, index, length());
+  }
+
+  public int codePointBefore(int index) {
+    return Character.codePointBefore(this, index, 0);
+  }
+
+  public int codePointCount(int beginIndex, int endIndex) {
+    return Character.codePointCount(this, beginIndex, endIndex);
+  }
+
   public int compareTo(String other) {
-    if (__equals(this, other)) {
-      return 0;
-    }
-    int thisLength = this.length();
-    int otherLength = other.length();
-    int length = Math.min(thisLength, otherLength);
-    for (int i = 0; i < length; i++) {
-      char thisChar = this.charAt(i);
-      char otherChar = other.charAt(i);
-      if (thisChar != otherChar) {
-        return thisChar - otherChar;
-      }
-    }
-    return thisLength - otherLength;
+    return compareTo(this, other);
+  }
+
+  public int compareToIgnoreCase(String other) {
+    return compareTo(toLowerCase(), other.toLowerCase());
   }
 
   public native String concat(String str) /*-{
@@ -221,6 +361,14 @@
     return indexOf(s.toString()) != -1;
   }
 
+  public boolean contentEquals(CharSequence cs) {
+    return equals(cs.toString());
+  }
+
+  public boolean contentEquals(StringBuffer sb) {
+    return equals(sb.toString());
+  }
+
   public native boolean endsWith(String suffix) /*-{
     return (this.lastIndexOf(suffix) != -1)
        && (this.lastIndexOf(suffix) == (this.length - suffix.length));
@@ -240,6 +388,12 @@
     return (this == other) || (this.toLowerCase() == other.toLowerCase());
   }-*/;
 
+  public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) {
+    for (int srcIdx = srcBegin; srcIdx < srcEnd; ++srcIdx) {
+      dst[dstBegin++] = charAt(srcIdx);
+    }
+  }
+
   @Override
   public native int hashCode() /*-{
     var hashCache = @java.lang.String::hashCache;
@@ -272,13 +426,13 @@
     return hashCode;
   }-*/;
 
-  public native int indexOf(int ch) /*-{
-    return this.indexOf(String.fromCharCode(ch));
-  }-*/;
+  public int indexOf(int codePoint) {
+    return indexOf(fromCodePoint(codePoint));
+  }
 
-  public native int indexOf(int ch, int startIndex) /*-{
-    return this.indexOf(String.fromCharCode(ch), startIndex);
-  }-*/;
+  public int indexOf(int codePoint, int startIndex) {
+    return this.indexOf(String.fromCodePoint(codePoint), startIndex);
+  }
 
   public native int indexOf(String str) /*-{
     return this.indexOf(str);
@@ -288,13 +442,17 @@
     return this.indexOf(str, startIndex);
   }-*/;
 
-  public native int lastIndexOf(int ch) /*-{
-    return this.lastIndexOf(String.fromCharCode(ch));
+  public native String intern() /*-{
+    return String(this);
   }-*/;
 
-  public native int lastIndexOf(int ch, int startIndex) /*-{
-    return this.lastIndexOf(String.fromCharCode(ch), startIndex);
-  }-*/;
+  public int lastIndexOf(int codePoint) {
+    return lastIndexOf(fromCodePoint(codePoint));
+  }
+
+  public int lastIndexOf(int codePoint, int startIndex) {
+    return lastIndexOf(fromCodePoint(codePoint), startIndex);
+  }
 
   public native int lastIndexOf(String str) /*-{
     return this.lastIndexOf(str);
@@ -313,6 +471,8 @@
    * <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
    * regular expression. For consistency, use only the subset of regular
    * expression syntax common to both Java and JavaScript.
+   * 
+   * TODO(jat): properly handle Java regex syntax
    */
   public native boolean matches(String regex) /*-{
     var matchObj = new RegExp(regex).exec(this);
@@ -321,6 +481,25 @@
     return (matchObj == null) ? false : (this == matchObj[0]);
   }-*/;
 
+  public int offsetByCodePoints(int index, int codePointOffset) {
+    return Character.offsetByCodePoints(this, index, codePointOffset);
+  }
+
+  public boolean regionMatches(boolean ignoreCase, int toffset, String other,
+      int ooffset, int len) {
+    if (other == null) {
+      throw new NullPointerException();
+    }
+    return regionMatches(this, ignoreCase, toffset, other, ooffset, len);
+  }
+
+  public boolean regionMatches(int toffset, String other, int ooffset, int len) {
+    if (other == null) {
+      throw new NullPointerException();
+    }
+    return regionMatches(this, false, toffset, other, ooffset, len);
+  }
+
   public native String replace(char from, char to) /*-{
     // We previously used \\uXXXX, but Safari 2 doesn't match them properly in RegExp
     // See http://bugs.webkit.org/show_bug.cgi?id=8043
@@ -337,11 +516,20 @@
     return this.replace(RegExp(regex, "g"), String.fromCharCode(to));
   }-*/;
 
+  public String replace(CharSequence from, CharSequence to) {
+    // Escape regex special characters from literal replacement string.
+    String regex = from.toString().replaceAll("([/\\\\\\.\\*\\+\\?\\|\\(\\)\\[\\]\\{\\}])",
+        "\\\\$1");
+    return replaceAll(regex, to.toString());
+  }
+  
   /**
    * Regular expressions vary from the standard implementation. The
    * <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
    * regular expression. For consistency, use only the subset of regular
    * expression syntax common to both Java and JavaScript.
+   * 
+   * TODO(jat): properly handle Java regex syntax
    */
   public native String replaceAll(String regex, String replace) /*-{
     replace = @java.lang.String::__translateReplaceString(Ljava/lang/String;)(replace);
@@ -353,6 +541,8 @@
    * <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
    * regular expression. For consistency, use only the subset of regular
    * expression syntax common to both Java and JavaScript.
+   * 
+   * TODO(jat): properly handle Java regex syntax
    */
   public native String replaceFirst(String regex, String replace) /*-{
     replace = @java.lang.String::__translateReplaceString(Ljava/lang/String;)(replace);
@@ -374,6 +564,8 @@
    * <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
    * regular expression. For consistency, use only the subset of regular
    * expression syntax common to both Java and JavaScript.
+   * 
+   * TODO(jat): properly handle Java regex syntax
    */
   public native String[] split(String regex, int maxMatch) /*-{
     // The compiled regular expression created from the string
@@ -449,15 +641,13 @@
   }-*/;
 
   public native String substring(int beginIndex, int endIndex) /*-{
-    return this.substr(beginIndex, endIndex-beginIndex);
+    return this.substr(beginIndex, endIndex - beginIndex);
   }-*/;
 
   public char[] toCharArray() {
     int n = this.length();
     char[] charArr = new char[n];
-    for (int i = 0; i < n; ++i) {
-      charArr[i] = this.charAt(i);
-    }
+    getChars(0, n, charArr, 0);
     return charArr;
   }
 
diff --git a/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/GWTTestCase.java b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/GWTTestCase.java
index d3adc70..973def6 100644
--- a/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/GWTTestCase.java
+++ b/user/super/com/google/gwt/junit/translatable/com/google/gwt/junit/client/GWTTestCase.java
@@ -221,6 +221,23 @@
           "This test case does not support asynchronous mode.");
     }
   }
+
+  /**
+   * Wrap a constant in such a way as to prevent the compiler from inlining it.
+   * 
+   * NOTE: if you change this, update the one in gwt-user/core/src as well.
+   * 
+   * @param <T>
+   * @param value to wrap
+   * @return same value, 
+   */
+  protected <T> T hideFromCompiler(T value) {
+    int i = 7;
+    while (i > 0) {
+      i -= 2;
+    }
+    return (i & 1) != 0 ? value : null;
+  }
   
   protected boolean supportsAsync() {
     return true;
diff --git a/user/test/com/google/gwt/emultest/java/lang/CharacterTest.java b/user/test/com/google/gwt/emultest/java/lang/CharacterTest.java
index 367c71f..c8a615c 100644
--- a/user/test/com/google/gwt/emultest/java/lang/CharacterTest.java
+++ b/user/test/com/google/gwt/emultest/java/lang/CharacterTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -18,12 +18,43 @@
 import com.google.gwt.junit.client.GWTTestCase;
 
 /**
- * TODO: document me.
+ * Tests for java.lang.Character.
  */
 public class CharacterTest extends GWTTestCase {
 
+  private static class CharSequenceAdapter implements CharSequence {
+    private char[] charArray;
+    private int start;
+    private int end;
+
+    public CharSequenceAdapter(char[] charArray) {
+      this(charArray, 0, charArray.length);
+    }
+    
+    public CharSequenceAdapter(char[] charArray, int start, int end) {
+      this.charArray = charArray;
+      this.start = start;
+      this.end = end;
+    }
+    
+    public char charAt(int index) {
+      return charArray[index + start];
+    }
+
+    public int length() {
+      return end - start;
+    }
+
+    public java.lang.CharSequence subSequence(int start, int end) {
+      return new CharSequenceAdapter(charArray, this.start + start,
+          this.start + end);
+    }
+  }
+
+
   /**
-   * TODO: document me.
+   * Helper class which applies some arbitrary char mutation function
+   * to a string and returns it.
    */
   public abstract class Changer {
     String original;
@@ -43,7 +74,8 @@
     }
   }
   /**
-   * TODO: document me.
+   * Helper class which collects the set of characters which pass some
+   * arbitrary boolean function. 
    */
   public abstract class Judge {
     String original;
@@ -119,8 +151,9 @@
   };
   Judge lowerCaseJudge = new LowerCaseJudge(allChars);
   Judge spaceJudge = new Judge(allChars) {
+    @SuppressWarnings("deprecation") // Character.isSpace()
     public boolean pass(char c) {
-      return Character.isSpace(c);
+      return Character.isSpace(c); // suppress deprecation
     }
   };
   Changer upperCaseChanger = new Changer(allChars) {
@@ -138,6 +171,77 @@
     assertEquals(new Character((char) 32).charValue(), (char) 32);
   }
 
+  public void testCodePoint() {
+    assertEquals(1, Character.charCount(65));
+    assertEquals(2, Character.charCount(Character.MIN_SUPPLEMENTARY_CODE_POINT));
+    char[] testPlain = new char[] { 'C', 'A', 'T' };
+    char[] testUnicode = new char[] { 'C', '\uD801', '\uDF00', 'T' };
+    CharSequence plainSequence = new CharSequenceAdapter(testPlain);
+    CharSequence unicodeSequence = new CharSequenceAdapter(testUnicode);
+    assertEquals(65, Character.codePointAt(testPlain, 1));
+    assertEquals(65, Character.codePointAt(plainSequence, 1));
+    assertEquals("codePointAt fails on surrogate pair", 67328,
+        Character.codePointAt(testUnicode, 1));
+    assertEquals("codePointAt fails on surrogate pair", 67328,
+        Character.codePointAt(unicodeSequence, 1));
+    assertEquals("codePointAt fails on first char of surrogate pair", 0xD801,
+        Character.codePointAt(testUnicode, 1, 2));
+    assertEquals(65, Character.codePointBefore(testPlain, 2));
+    assertEquals(65, Character.codePointBefore(plainSequence, 2));
+    assertEquals("codePointBefore fails on surrogate pair", 67328,
+        Character.codePointBefore(testUnicode, 3));
+    assertEquals("codePointBefore fails on surrogate pair", 67328,
+        Character.codePointBefore(unicodeSequence, 3));
+    assertEquals("codePointBefore fails on second char of surrogate pair",
+        0xDF00, Character.codePointBefore(testUnicode, 3, 2));
+    assertEquals("codePointCount(plain): ", 3,
+        Character.codePointCount(testPlain, 0, 3));
+    assertEquals("codePointCount(plain): ", 3,
+        Character.codePointCount(plainSequence, 0, 3));
+    assertEquals("codePointCount(unicode): ", 3,
+        Character.codePointCount(testUnicode, 0, 4));
+    assertEquals("codePointCount(unicode): ", 3,
+        Character.codePointCount(unicodeSequence, 0, 4));
+    assertEquals(1, Character.codePointCount(testPlain, 1, 1));
+    assertEquals(1, Character.codePointCount(plainSequence, 1, 2));
+    assertEquals(1, Character.codePointCount(testUnicode, 1, 2));
+    assertEquals(1, Character.codePointCount(unicodeSequence, 1, 3));
+    assertEquals(2, Character.codePointCount(testUnicode, 2, 2));
+    assertEquals(2, Character.codePointCount(unicodeSequence, 2, 4));
+    assertEquals(1, Character.offsetByCodePoints(testUnicode, 0, 4, 0, 1));
+    assertEquals(1, Character.offsetByCodePoints(unicodeSequence, 0, 1));
+    assertEquals("offsetByCodePoints(1,1): ", 3,
+        Character.offsetByCodePoints(testUnicode, 0, 4, 1, 1));
+    assertEquals("offsetByCodePoints(1,1): ", 3,
+        Character.offsetByCodePoints(unicodeSequence, 1, 1));
+    assertEquals("offsetByCodePoints(2,1): ", 3,
+        Character.offsetByCodePoints(testUnicode, 0, 4, 2, 1));
+    assertEquals("offsetByCodePoints(2,1): ", 3,
+        Character.offsetByCodePoints(unicodeSequence, 2, 1));
+    assertEquals(4, Character.offsetByCodePoints(testUnicode, 0, 4, 3, 1));
+    assertEquals(4, Character.offsetByCodePoints(unicodeSequence, 3, 1));
+    assertEquals(1, Character.offsetByCodePoints(testUnicode, 0, 4, 2, -1));
+    assertEquals(1, Character.offsetByCodePoints(unicodeSequence, 2, -1));
+    assertEquals(1, Character.offsetByCodePoints(testUnicode, 0, 4, 3, -1));
+    assertEquals(1, Character.offsetByCodePoints(unicodeSequence, 3, -1));
+    assertEquals("offsetByCodePoints(4.-1): ", 3,
+        Character.offsetByCodePoints(testUnicode, 0, 4, 4, -1));
+    assertEquals("offsetByCodePoints(4.-1): ", 3,
+        Character.offsetByCodePoints(unicodeSequence, 4, -1));
+    assertEquals(0, Character.offsetByCodePoints(testUnicode, 0, 4, 3, -2));
+    assertEquals(0, Character.offsetByCodePoints(unicodeSequence, 3, -2));
+    char[] nonBmpChar = new char[] { '\uD800', '\uDF46' };
+    assertEquals(0x10346, Character.codePointAt(nonBmpChar, 0));
+    assertEquals(1, Character.codePointCount(nonBmpChar, 0, 2));
+  }
+  
+  public void testCompareTo() {
+    assertTrue(Character.valueOf('A').compareTo('B') < 0);
+    assertTrue(Character.valueOf('B').compareTo('A') > 0);
+    assertTrue(Character.valueOf('C').compareTo('C') == 0);
+    assertTrue(Character.valueOf('\uA001').compareTo('\uA000') > 0);
+  }
+
   public void testConstructor() {
     assertEquals(new Character((char) 32), new Character(' '));
   }
@@ -145,6 +249,33 @@
   public void testDigit() {
     assertEquals("wrong number of digits", 10, digitJudge.allPass().length());
   }
+  
+  public void testSurrogates() {
+    assertFalse(Character.isHighSurrogate('\uDF46'));
+    assertTrue(Character.isLowSurrogate('\uDF46'));
+    assertTrue(Character.isHighSurrogate('\uD800'));
+    assertFalse(Character.isLowSurrogate('\uD800'));
+    assertFalse(Character.isHighSurrogate('X'));
+    assertFalse(Character.isLowSurrogate('X'));
+    assertTrue(Character.isSurrogatePair('\uD800', '\uDF46'));
+    assertFalse(Character.isSurrogatePair('\uDF46', '\uD800'));
+    assertFalse(Character.isSurrogatePair('A', '\uDF46'));
+    assertFalse(Character.isSurrogatePair('\uD800', 'A'));
+    char[] chars = Character.toChars(0x10346);
+    assertEquals(0xD800, chars[0]);
+    assertEquals(0xDF46, chars[1]);
+    assertEquals(2, Character.toChars(67328, chars, 0));
+    assertEquals(0xD801, chars[0]);
+    assertEquals(0xDF00, chars[1]);
+    assertEquals(1, Character.toChars(65, chars, 0));
+    assertEquals('A', chars[0]);
+    assertTrue(Character.isSupplementaryCodePoint(0x10346));
+    assertFalse(Character.isSupplementaryCodePoint(65));
+    assertTrue(Character.isValidCodePoint(0x10346));
+    assertTrue(Character.isValidCodePoint(65));
+    assertFalse(Character.isValidCodePoint(0x1FFFFFFF));
+    assertEquals(0x10346, Character.toCodePoint('\uD800', '\uDF46'));
+  }
 
   public void testLetter() {
     assertEquals("wrong number of letters", 52, letterJudge.allPass().length());
@@ -178,7 +309,7 @@
   }
 
   public void testToString() {
-    assertEquals(new Character((char) 32).toString(), " ");
+    assertEquals(" ", new Character((char) 32).toString());
   }
 
   public void testUpperCase() {
@@ -187,4 +318,8 @@
     assertEquals("wrong number of uppercase letters after toUpperCase", 52,
         new UpperCaseJudge(upperCaseChanger.changed()).allPass().length());
   }
+  
+  public void testValueOf() {
+    assertEquals('A', Character.valueOf('A').charValue());
+  }
 }
diff --git a/user/test/com/google/gwt/emultest/java/lang/StringTest.java b/user/test/com/google/gwt/emultest/java/lang/StringTest.java
index 46430af..53fc23a 100644
--- a/user/test/com/google/gwt/emultest/java/lang/StringTest.java
+++ b/user/test/com/google/gwt/emultest/java/lang/StringTest.java
@@ -20,43 +20,114 @@
 /**
  * TODO: COMPILER OPTIMIZATIONS HAVE MADE THIS TEST NOT ACTUALLY TEST ANYTHING!
  * NEED A VERSION THAT DOESN'T USE STATICALLY DETERMINABLE STRINGS!
+ * 
+ * See individual method TODOs for ones that still need work -- the ones without
+ * comments are already protected against optimization.
  */
 public class StringTest extends GWTTestCase {
 
+  @Override
   public String getModuleName() {
     return "com.google.gwt.emultest.EmulSuite";
   }
 
+  /*
+   * TODO: needs rewriting to avoid compiler optimizations.
+   */
   public void testCharAt() {
     assertEquals("abc".charAt(1), 'b');
   }
 
+  public void testCodePoint() {
+    String testPlain = hideFromCompiler("CAT");
+    String testUnicode = hideFromCompiler("C\uD801\uDF00T");
+    assertEquals("CAT", new String(new int[] { 'C', 'A', 'T' }, 0, 3));
+    assertEquals("C\uD801\uDF00T", new String(new int[] { 'C', 67328, 'T' }, 0, 3));
+    assertEquals("\uD801\uDF00", new String(new int[] { 'C', 67328, 'T' }, 1, 1));
+    assertEquals(65, testPlain.codePointAt(1));
+    assertEquals("codePointAt fails on surrogate pair", 67328, testUnicode.codePointAt(1));
+    assertEquals(65, testPlain.codePointBefore(2));
+    assertEquals("codePointBefore fails on surrogate pair", 67328, testUnicode.codePointBefore(3));
+    assertEquals("codePointCount(plain): ", 3, testPlain.codePointCount(0, 3));
+    assertEquals("codePointCount(unicode): ", 3, testUnicode.codePointCount(0, 4));
+    assertEquals(1, testPlain.codePointCount(1, 2));
+    assertEquals(1, testUnicode.codePointCount(1, 2));
+    assertEquals(2, testUnicode.codePointCount(2, 4));
+    assertEquals(1, testUnicode.offsetByCodePoints(0, 1));
+    assertEquals("offsetByCodePoints(1,1): ", 3, testUnicode.offsetByCodePoints(1, 1));
+    assertEquals("offsetByCodePoints(2,1): ", 3, testUnicode.offsetByCodePoints(2, 1));
+    assertEquals(4, testUnicode.offsetByCodePoints(3, 1));
+    assertEquals(1, testUnicode.offsetByCodePoints(2, -1));
+    assertEquals(1, testUnicode.offsetByCodePoints(3, -1));
+    assertEquals("offsetByCodePoints(4.-1): ", 3, testUnicode.offsetByCodePoints(4, -1));
+    assertEquals(0, testUnicode.offsetByCodePoints(3, -2));
+    /*
+     * The next line contains a Unicode character outside the base multilingual
+     * plane -- it may not show properly depending on your fonts, etc.  The
+     * character is the Gothic letter Faihu, or U+10346.  We use it to verify
+     * that multi-char UTF16 characters are handled properly.
+     * 
+     * In Windows 2000, registry changes are required to support non-BMP characters
+     * (or surrogates in general) -- surrogates are not supported before Win2k and
+     * they are enabled by default in WinXP and later.
+     * 
+     * [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\LanguagePack]
+     *     SURROGATE=(REG_DWORD)0x00000002
+     *     
+     * [HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\International\Scripts\42]
+     *     IEFixedFontName=[Surrogate Font Face Name]
+     *     IEPropFontName=[Surrogate Font Face Name] 
+     */
+    String nonBmpChar = hideFromCompiler("𐍆");
+    assertEquals("\uD800\uDF46", nonBmpChar);
+    assertEquals(0x10346, nonBmpChar.codePointAt(0));
+    assertEquals(2, nonBmpChar.length());
+    assertEquals(1, nonBmpChar.codePointCount(0, 2));
+  }
+
   public void testConcat() {
-    assertEquals("abcdef", "abc" + "def");
-    assertEquals("abcdef", "abc".concat("def"));
-    assertEquals("".concat(""), "");
-    char c = 'd';
-    String s = "abc";
-    assertEquals("abcd", "abc" + 'd');
-    assertEquals("abcd", "abc" + c);
+    String abc = String.valueOf(new char[] {'a', 'b', 'c'});
+    String def = String.valueOf(new char[] {'d', 'e', 'f'});
+    String empty = String.valueOf(new char[] {});
+    assertEquals("abcdef", abc + def);
+    assertEquals("abcdef", abc.concat(def));
+    assertEquals("", empty.concat(empty));
+    char c = def.charAt(0);
+    String s = abc;
+    assertEquals("abcd", abc + 'd');
+    assertEquals("abcd", abc + c);
     assertEquals("abcd", s + 'd');
     assertEquals("abcd", s + c);
     s += c;
     assertEquals("abcd", s);
   }
-
+  
   public void testConstructor() {
     char[] chars = {'a', 'b', 'c', 'd', 'e', 'f'};
-    String constant = "abcdef";
-    String shortString = "cde";
-    assertEquals(new String(constant), constant);
-    assertEquals(new String(chars), constant);
-    assertEquals(new String(chars, 2, 3), shortString);
-    assertEquals(new String(""), "");
-    assertEquals(new String(new String(new String(new String("")))), "");
-    assertEquals(new String(new char[] {}), "");
+    String constant = String.valueOf(new char[] {'a', 'b', 'c', 'd', 'e', 'f'});
+    String shortString = String.valueOf(new char[] {'c', 'd', 'e'});
+    assertEquals(constant, new String(hideFromCompiler(constant)));
+    assertEquals(constant, new String(chars), constant);
+    assertEquals(shortString, new String(chars, 2, 3), shortString);
+    assertEquals("", new String(hideFromCompiler("")));
+    assertEquals("", new String(new String(new String(new String(hideFromCompiler(""))))));
+    assertEquals("", new String(new char[] {}));
+    StringBuffer buf = new StringBuffer();
+    buf.append('c');
+    buf.append('a');
+    buf.append('t');
+    assertEquals("cat", new String(buf));
+    StringBuilder sb = new StringBuilder();
+    sb.append('c');
+    sb.append('a');
+    sb.append('t');
+    assertEquals("cat", new String(sb));
   }
 
+  /*
+   * TODO: needs rewriting to avoid compiler optimizations.
+   *     (StringBuffer tests are ok)
+   */
   public void testContains() {
     // at the beginning
     assertTrue("abcdef".contains("ab"));
@@ -82,6 +153,9 @@
     assertFalse("c", haystack.endsWith(haystack + "j"));
   }
 
+  /*
+   * TODO: needs rewriting to avoid compiler optimizations.
+   */
   public void testEquals() {
     assertFalse("ABC".equals("abc"));
     assertFalse("abc".equals("ABC"));
@@ -93,6 +167,9 @@
     assertFalse("".equals(null));
   }
 
+  /*
+   * TODO: needs rewriting to avoid compiler optimizations.
+   */
   public void testEqualsIgnoreCase() {
     assertTrue("ABC".equalsIgnoreCase("abc"));
     assertTrue("abc".equalsIgnoreCase("ABC"));
@@ -149,28 +226,44 @@
     assertEquals(haystack.indexOf(""), 0);
   }
 
+  public void testIntern() {
+    String s1 = String.valueOf(new char[] {'a', 'b', 'c', 'd', 'e', 'f'});
+    String s2 = String.valueOf(new char[] {'a', 'b', 'c', 'd', 'e', 'f'});
+    assertTrue("strings not equal", s1.equals(s2));
+    assertSame("interns are not the same reference", s1.intern(), s2.intern());
+  }
+
   public void testLastIndexOf() {
     String x = "abcdeabcdef";
     assertEquals(9, x.lastIndexOf("e"));
     assertEquals(10, x.lastIndexOf("f"));
     assertEquals(-1, x.lastIndexOf("f", 1));
   }
-
+  
   public void testLength() {
-    assertEquals(3, "abc".length());
+    String abc = String.valueOf(new char[] {'a', 'b', 'c'});
+    assertEquals(3, abc.length());
     String str = "x";
     for (int i = 0; i < 16; i++) {
       str = str + str;
     }
     assertEquals(1 << 16, str.length());
+    String cat = String.valueOf(new char[] {'C', '\uD801', '\uDF00', 'T'});
+    assertEquals(4, cat.length());
   }
 
+  /*
+   * TODO: needs rewriting to avoid compiler optimizations.
+   */
   public void testLowerCase() {
     assertEquals("abc", "AbC".toLowerCase());
     assertEquals("abc", "abc".toLowerCase());
     assertEquals("", "".toLowerCase());
   }
 
+  /*
+   * TODO: needs rewriting to avoid compiler optimizations.
+   */
   public void testMatch() {
     assertFalse("1f", "abbbbcd".matches("b*"));
     assertFalse("2f", "abbbbcd".matches("b+"));
@@ -182,15 +275,41 @@
     assertFalse("8f", "abbbbcd".matches("a.*e"));
   }
 
+  /*
+   * TODO: needs rewriting to avoid compiler optimizations.
+   */
   public void testNull() {
     assertNull(returnNull());
     String a = returnNull() + returnNull();
     assertEquals("nullnull", a);
   }
 
+  public void testRegionMatches() {
+    String test = String.valueOf(new char[] {'a', 'b', 'c', 'd', 'e', 'f'});
+    assertTrue(test.regionMatches(1, "bcd", 0, 3));
+    assertTrue(test.regionMatches(1, "bcdx", 0, 3));
+    assertFalse(test.regionMatches(1, "bcdx", 0, 4));
+    assertFalse(test.regionMatches(1, "bcdx", 1, 3));
+    assertTrue(test.regionMatches(true, 0, "XAbCd", 1, 4));
+    assertTrue(test.regionMatches(true, 1, "BcD", 0, 3));
+    assertTrue(test.regionMatches(true, 1, "bCdx", 0, 3));
+    assertFalse(test.regionMatches(true, 1, "bCdx", 0, 4));
+    assertFalse(test.regionMatches(true, 1, "bCdx", 1, 3));
+    assertTrue(test.regionMatches(true, 0, "xaBcd", 1, 4));
+    test = test.toUpperCase();
+    assertTrue(test.regionMatches(true, 0, "XAbCd", 1, 4));
+    assertTrue(test.regionMatches(true, 1, "BcD", 0, 3));
+    assertTrue(test.regionMatches(true, 1, "bCdx", 0, 3));
+    assertFalse(test.regionMatches(true, 1, "bCdx", 0, 4));
+    assertFalse(test.regionMatches(true, 1, "bCdx", 1, 3));
+    assertTrue(test.regionMatches(true, 0, "xaBcd", 1, 4));
+  }
+
   public void testReplace() {
-    assertEquals("axax".replace('x', 'a'), "aaaa");
-    assertEquals("aaaa".replace('x', 'a'), "aaaa");
+    String axax = String.valueOf(new char[] {'a', 'x', 'a', 'x'});
+    String aaaa = String.valueOf(new char[] {'a', 'a', 'a', 'a'});
+    assertEquals("aaaa", axax.replace('x', 'a'));
+    assertEquals("aaaa", aaaa.replace('x', 'a'));
     for (char from = 32; from < 250; ++from) {
       char to = (char) (from + 5);
       assertEquals(toS(to), toS(from).replace(from, to));
@@ -200,19 +319,37 @@
       assertEquals(toS(to), toS(from).replace(from, to));
     }
     // issue 1480
-    assertEquals("example xd", "example xd".replace('\r', ' ').replace('\n', ' '));
-    assertEquals("dog food", "dog\u0120food".replace('\u0120', ' '));
-    assertEquals("ABABAB", "\u1111B\u1111B\u1111B".replace('\u1111', 'A'));
+    String exampleXd
+        = String.valueOf(new char[] {'e', 'x', 'a', 'm', 'p', 'l', 'e', ' ', 'x', 'd'});
+    assertEquals("example xd", exampleXd.replace('\r', ' ').replace('\n', ' '));
+    String dogFood = String.valueOf(new char[] {'d', 'o', 'g', '\u0120', 'f', 'o', 'o', 'd'});
+    assertEquals("dog food", dogFood.replace('\u0120', ' '));
+    String testStr = String.valueOf(new char[] {'\u1111', 'B', '\u1111', 'B', '\u1111', 'B'});
+    assertEquals("ABABAB", testStr.replace('\u1111', 'A'));
+    assertEquals("foobar", hideFromCompiler("bazbar").replace("baz", "foo"));
+    assertEquals("$0bar", hideFromCompiler("foobar").replace("foo", "$0"));
+    assertEquals("+1", hideFromCompiler("*[)1").replace("*[)", "+"));
   }
 
   public void testReplaceAll() {
-    assertEquals("abcdef", "xxxxabcxxdexf".replaceAll("x*", ""));
-    assertEquals("1\\1abc123\\123de1234\\1234f", "1abc123de1234f".replaceAll(
+    String regex = hideFromCompiler("*[").replaceAll("([/\\\\\\.\\*\\+\\?\\|\\(\\)\\[\\]\\{\\}])",
+        "\\\\$1");
+    assertEquals("\\*\\[", regex);
+    assertEquals("+1", hideFromCompiler("*[1").replaceAll(regex, "+"));
+    String x1 = String.valueOf(new char[] {'x', 'x', 'x', 'a', 'b', 'c', 'x', 'x', 'd', 'e', 'x',
+    'f'});
+    assertEquals("abcdef", x1.replaceAll("x*", ""));
+    String x2 = String.valueOf(new char[] {'1', 'a', 'b', 'c', '1', '2', '3', 'd', 'e', '1', '2',
+        '3', '4', 'f'});
+    assertEquals("1\\1abc123\\123de1234\\1234f", x2.replaceAll(
         "([1234]+)", "$1\\\\$1"));
-    assertEquals("\n  \n", "x  x".replaceAll("x", "\n"));
-    assertEquals("x  x", "\n  \n".replaceAll("\\\n", "x"));
-    assertEquals("x\"\\", "x".replaceAll("x", "\\x\\\"\\\\"));
-    assertEquals("$$x$", "x".replaceAll("(x)", "\\$\\$$1\\$"));
+    String x3 = String.valueOf(new char[] {'x', ' ', ' ', 'x'});
+    assertEquals("\n  \n", x3.replaceAll("x", "\n"));
+    String x4 = String.valueOf(new char[] {'\n', ' ', ' ', '\n'});
+    assertEquals("x  x", x4.replaceAll("\\\n", "x"));
+    String x5 = String.valueOf(new char[] {'x'});
+    assertEquals("x\"\\", x5.replaceAll("x", "\\x\\\"\\\\"));
+    assertEquals("$$x$", x5.replaceAll("(x)", "\\$\\$$1\\$"));
   }
 
   public void testSplit() {
@@ -245,6 +382,9 @@
     assertFalse(haystack.startsWith(haystack + "j"));
   }
 
+  /*
+   * TODO: needs rewriting to avoid compiler optimizations.
+   */
   public void testSubstring() {
     String haystack = "abcdefghi";
     assertEquals("cd", haystack.substring(2, 4));
@@ -260,6 +400,9 @@
     }
   }
 
+  /*
+   * TODO: needs rewriting to avoid compiler optimizations.
+   */
   public void testTrim() {
     trimRightAssertEquals("abc", "   \t abc \n  ");
     trimRightAssertEquals("abc", "abc".trim());
@@ -272,12 +415,18 @@
     trimRightAssertEquals("", "   \t ".trim());
   }
 
+  /*
+   * TODO: needs rewriting to avoid compiler optimizations.
+   */
   public void testUpperCase() {
     assertEquals("abc", "AbC".toLowerCase());
     assertEquals("abc", "abc".toLowerCase());
     assertEquals("", "".toLowerCase());
   }
 
+  /*
+   * TODO: needs rewriting to avoid compiler optimizations.
+   */
   public void testValueOf() {
     assertTrue(String.valueOf(C.FLOAT_VALUE).startsWith(C.FLOAT_STRING));
     assertEquals(C.INT_STRING, String.valueOf(C.INT_VALUE));
@@ -293,6 +442,8 @@
 
   /**
    * Helper method for testTrim to avoid compiler optimizations.
+   * 
+   * TODO: insufficient, compiler now inlines.
    */
   public void trimRightAssertEquals(String left, String right) {
     assertEquals(left, right.trim());
@@ -300,6 +451,8 @@
 
   /**
    * Helper method for testTrim to avoid compiler optimizations.
+   * 
+   * TODO: insufficient, compiler now inlines.
    */
   public void trimRightAssertSame(String left, String right) {
     assertSame(left, right.trim());