Merges changes/spoon/defBindStringBuilder into trunk.  This gives
StringBuffer (and StringBuilder) browser-specific deferred bindings.

  svn merge -r 2934:2962 https://google-web-toolkit.googlecode.com/svn/changes/spoon/defBindStringBuilder .
  svn merge -r 2963:3190 https://google-web-toolkit.googlecode.com/svn/changes/spoon/defBindStringBuilder .
  svn commit


Patch by: ecc, scottb, spoon
Review by: ecc, scottb, spoon



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@3191 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/core/client/impl/StringBufferImpl.java b/user/src/com/google/gwt/core/client/impl/StringBufferImpl.java
new file mode 100644
index 0000000..5c8df7d
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/impl/StringBufferImpl.java
@@ -0,0 +1,93 @@
+/*
+ * 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
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.client.impl;
+
+/**
+ * <p>
+ * The interface to defer bound implementations of {@link StringBuilder} and
+ * {@link StringBuffer}.
+ * </p>
+ * 
+ * <p>
+ * All of the implementations have been carefully tweaked to get the most
+ * inlining possible, so be sure to check with
+ * {@link com.google.gwt.emultest.java.lang.StringBuilderBenchmark StringBuilderBenchmark}
+ * whenever these classes are modified.
+ * </p>
+ */
+public abstract class StringBufferImpl {
+
+  /**
+   * Append for primitive; the value can be stored and only later converted to a
+   * string.
+   */
+  public abstract void append(Object data, boolean x);
+
+  /**
+   * Append for primitive; the value can be stored and only later converted to a
+   * string.
+   */
+  public abstract void append(Object data, double x);
+
+  /**
+   * Append for primitive; the value can be stored and only later converted to a
+   * string.
+   */
+  public abstract void append(Object data, float x);
+
+  /**
+   * Append for primitive; the value can be stored and only later converted to a
+   * string.
+   */
+  public abstract void append(Object data, int x);
+
+  /**
+   * Append for object. It is important to immediately convert the object to a
+   * string, because the conversion can give different results if it is
+   * deferred.
+   */
+  public abstract void append(Object data, Object x);
+
+  /**
+   * Append for a possibly null string object.
+   */
+  public abstract void append(Object data, String x);
+
+  /**
+   * Append for a string that is definitely not null.
+   */
+  public abstract void appendNonNull(Object data, String x);
+
+  /**
+   * Returns a data holder object for use with subsequent calls.
+   */
+  public abstract Object createData();
+
+  /**
+   * Returns the current length of the string buffer.
+   */
+  public abstract int length(Object data);
+
+  /**
+   * Replaces a segment of the string buffer.
+   */
+  public abstract void replace(Object data, int start, int end, String toInsert);
+
+  /**
+   * Returns the string buffer as a String.
+   */
+  public abstract String toString(Object data);
+}
diff --git a/user/src/com/google/gwt/core/client/impl/StringBufferImplAppend.java b/user/src/com/google/gwt/core/client/impl/StringBufferImplAppend.java
new file mode 100644
index 0000000..6dd2ed5
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/impl/StringBufferImplAppend.java
@@ -0,0 +1,79 @@
+/*
+ * 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
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.client.impl;
+
+/**
+ * A {@link StringBufferImpl} that uses += for appending strings. This appears
+ * to be the fastest implementation everywhere except IE, where it's terrible.
+ */
+public class StringBufferImplAppend extends StringBufferImpl {
+  private String string = "";
+
+  @Override
+  public void append(Object data, boolean x) {
+    string += x;
+  }
+
+  @Override
+  public void append(Object data, double x) {
+    string += x;
+  }
+
+  @Override
+  public void append(Object data, float x) {
+    string += x;
+  }
+
+  @Override
+  public void append(Object data, int x) {
+    string += x;
+  }
+
+  @Override
+  public void append(Object data, Object x) {
+    string += x;
+  }
+
+  @Override
+  public void append(Object data, String x) {
+    string += x;
+  }
+
+  @Override
+  public void appendNonNull(Object data, String x) {
+    string += x;
+  }
+
+  @Override
+  public Object createData() {
+    return null;
+  }
+
+  @Override
+  public int length(Object data) {
+    return string.length();
+  }
+
+  @Override
+  public void replace(Object data, int start, int end, String toInsert) {
+    string = string.substring(0, start) + toInsert + string.substring(end);
+  }
+
+  @Override
+  public String toString(Object data) {
+    return string;
+  }
+}
diff --git a/user/src/com/google/gwt/core/client/impl/StringBufferImplArray.java b/user/src/com/google/gwt/core/client/impl/StringBufferImplArray.java
new file mode 100644
index 0000000..306c390
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/impl/StringBufferImplArray.java
@@ -0,0 +1,27 @@
+/*
+ * 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
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.client.impl;
+
+/**
+ * A {@link StringBufferImpl} that uses an array and an explicit length for
+ * appending strings. Note that the length of the array is stored as a property
+ * of the underlying JavaScriptObject because making it a field on this object
+ * causes difficulty with inlining. This is the best implementation on IE, and
+ * generally second best on all other browsers, making it the best default when
+ * the user agent is unknown.
+ */
+public class StringBufferImplArray extends StringBufferImplArrayBase {
+}
diff --git a/user/src/com/google/gwt/core/client/impl/StringBufferImplArrayBase.java b/user/src/com/google/gwt/core/client/impl/StringBufferImplArrayBase.java
new file mode 100644
index 0000000..227ef93
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/impl/StringBufferImplArrayBase.java
@@ -0,0 +1,90 @@
+/*
+ * 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
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.client.impl;
+
+/**
+ * Superclass for all array-based string builder implementations.
+ */
+public abstract class StringBufferImplArrayBase extends StringBufferImpl {
+
+  @Override
+  public native void append(Object a, boolean x) /*-{
+    a[a.explicitLength++] = x;
+  }-*/;
+
+  @Override
+  public native void append(Object a, double x) /*-{
+    a[a.explicitLength++] = x;
+  }-*/;
+
+  @Override
+  public native void append(Object a, float x) /*-{
+    a[a.explicitLength++] = x;
+  }-*/;
+
+  @Override
+  public native void append(Object a, int x) /*-{
+    a[a.explicitLength++] = x;
+  }-*/;
+
+  @Override
+  public final void append(Object a, Object x) {
+    appendNonNull(a, "" + x);
+  }
+
+  @Override
+  public void append(Object a, String x) {
+    appendNonNull(a, (x == null) ? "null" : x);
+  }
+
+  @Override
+  public native void appendNonNull(Object a, String x) /*-{
+    a[a.explicitLength++] = x;
+  }-*/;
+
+  @Override
+  public final native Object createData() /*-{
+    var array = [];
+    array.explicitLength = 0;
+    return array;
+  }-*/;
+
+  @Override
+  public int length(Object a) {
+    return toString(a).length();
+  }
+
+  @Override
+  public final void replace(Object a, int start, int end, String toInsert) {
+    String s = takeString(a);
+    appendNonNull(a, s.substring(0, start));
+    append(a, toInsert);
+    appendNonNull(a, s.substring(end));
+  }
+
+  @Override
+  public final String toString(Object a) {
+    String s = takeString(a);
+    appendNonNull(a, s);
+    return s;
+  }
+
+  protected native String takeString(Object a) /*-{
+    var s = a.join('');
+    a.length = a.explicitLength = 0;
+    return s;
+  }-*/;
+}
diff --git a/user/src/com/google/gwt/core/client/impl/StringBufferImplConcat.java b/user/src/com/google/gwt/core/client/impl/StringBufferImplConcat.java
new file mode 100644
index 0000000..b0eec79
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/impl/StringBufferImplConcat.java
@@ -0,0 +1,38 @@
+/*
+ * 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
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.client.impl;
+
+/**
+ * A {@link StringBufferImplArrayBase} that does a concat for toString(). The
+ * performance of this implementation is generally slightly worse than
+ * {@link StringBufferImplArray} for everything except IE, where it's terrible.
+ */
+public class StringBufferImplConcat extends StringBufferImplArrayBase {
+  /**
+   * We don't need to do the null check because concat does it automagically.
+   */
+  @Override
+  public native void append(Object a, String x) /*-{
+    a[a.explicitLength++] = x;
+  }-*/;
+
+  @Override
+  protected native String takeString(Object a) /*-{
+    var s = String.prototype.concat.apply('', a);
+    a.length = a.explicitLength = 0;
+    return s;
+  }-*/;
+}
diff --git a/user/src/com/google/gwt/core/client/impl/StringBufferImplPush.java b/user/src/com/google/gwt/core/client/impl/StringBufferImplPush.java
new file mode 100644
index 0000000..c9806f6
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/impl/StringBufferImplPush.java
@@ -0,0 +1,51 @@
+/*
+ * 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
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.client.impl;
+
+/**
+ * A {@link StringBufferImplArrayBase} that uses <code>push</code> for
+ * appending strings. Some external benchmarks suggest this implementation, but
+ * in practice our measurements indication that {@link StringBufferImplArray}
+ * has a slight edge on every browser; the performance is often very close to
+ * {@link StringBufferImplConcat}.
+ */
+public class StringBufferImplPush extends StringBufferImplArrayBase {
+
+  @Override
+  public native void append(Object a, boolean x) /*-{
+    a.push(x);
+  }-*/;
+
+  @Override
+  public native void append(Object a, double x) /*-{
+    a.push(x);
+  }-*/;
+
+  @Override
+  public native void append(Object a, float x) /*-{
+    a.push(x);
+  }-*/;
+
+  @Override
+  public native void append(Object a, int x) /*-{
+    a.push(x);
+  }-*/;
+
+  @Override
+  public native void appendNonNull(Object a, String x) /*-{
+    a.push(x);
+  }-*/;
+}
diff --git a/user/src/com/google/gwt/core/client/impl/StringBuilderImpl.java b/user/src/com/google/gwt/core/client/impl/StringBuilderImpl.java
new file mode 100644
index 0000000..81a55d2
--- /dev/null
+++ b/user/src/com/google/gwt/core/client/impl/StringBuilderImpl.java
@@ -0,0 +1,205 @@
+/*
+ * 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
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.core.client.impl;
+
+/**
+ * <p>
+ * An implementation for a {@link StringBuilder} or {@link StringBuffer}. This
+ * class holds a default implementation based on an array of strings and the
+ * JavaScript join function. Deferred bindings can substitute a subclass
+ * optimized for a particular browser.
+ * </p>
+ * 
+ * <p>
+ * The main implementations are static classes nested within this one. All of
+ * the implementations have been carefully tweaked to get the most inlining
+ * possible, so be sure to check with
+ * {@link com.google.gwt.emultest.java.lang.StringBuilderBenchmark StringBuilderBenchmark}
+ * whenever these classes are modified.
+ * </p>
+ */
+public class StringBuilderImpl {
+
+  /**
+   * A {@link StringBuilderImpl} that uses an array and an explicit length for
+   * appending strings. Note that the length of the array is stored as a
+   * property of the underlying JavaScriptObject. Making it a field of
+   * {@link ImplArray} causes difficulty with inlining.
+   */
+
+  public static class ImplArray extends StringBuilderImpl {
+    private  static native void setArrayLength(String[] array, int length) /*-{
+      array.explicitLength = length;
+    }-*/;
+
+    @SuppressWarnings("unused")
+    private String[] array = new String[0];
+
+    public ImplArray() {
+      setArrayLength(array, 0);
+    }
+
+    @Override
+    public native void append(String s) /*-{
+      var a = this.@com.google.gwt.core.client.impl.StringBuilderImpl.ImplArray::array;
+      a[a.explicitLength++] = s==null ? "null" : s;
+    }-*/;
+
+    @Override
+    public int length() {
+      return toString().length();
+    }
+
+    @Override
+    public void replace(int start, int end, String toInsert) {
+      String s = toString();
+      array = new String[] {s.substring(0, start), toInsert, s.substring(end)};
+    }
+
+    @Override
+    public native String toString() /*-{
+      this.@com.google.gwt.core.client.impl.StringBuilderImpl.ImplArray::array = 
+        [ this.@com.google.gwt.core.client.impl.StringBuilderImpl.ImplArray::array.join('') ];
+      this.@com.google.gwt.core.client.impl.StringBuilderImpl.ImplArray::array.explicitLength = 1;
+      return this.@com.google.gwt.core.client.impl.StringBuilderImpl.ImplArray::array[0];
+    }-*/;
+  }
+
+  /**
+   * A {@link StringBuilderImpl} that uses <code>push</code> for appending
+   * strings.
+   */
+  public static class ImplPush extends StringBuilderImpl {
+    @SuppressWarnings("unused")
+    private String[] array = new String[0];
+
+    @Override
+    public native void append(String s) /*-{
+      this.@com.google.gwt.core.client.impl.StringBuilderImpl.ImplPush::array.push(s == null ? "null" : s);
+    }-*/;
+
+    @Override
+    public int length() {
+      return toString().length();
+    }
+
+    @Override
+    public void replace(int start, int end, String toInsert) {
+      String s = toString();
+      array = new String[] {s.substring(0, start), toInsert, s.substring(end)};
+    }
+
+    @Override
+    public native String toString() /*-{
+      this.@com.google.gwt.core.client.impl.StringBuilderImpl.ImplPush::array = 
+        [ this.@com.google.gwt.core.client.impl.StringBuilderImpl.ImplPush::array.join('') ];
+      return this.@com.google.gwt.core.client.impl.StringBuilderImpl.ImplPush::array[0];
+    }-*/;
+  }
+
+  /**
+   * A {@link StringBuilderImpl} that uses += for appending strings.
+   */
+  public static class ImplStringAppend extends StringBuilderImpl {
+    private String string = "";
+
+    @Override
+    public void append(String s) {
+      string += s;
+    }
+
+    @Override
+    public int length() {
+      return string.length();
+    }
+
+    @Override
+    public void replace(int start, int end, String toInsert) {
+      string = string.substring(0, start) + toInsert + string.substring(end);
+    }
+    
+    @Override
+    public String toString() {
+      return string;
+    }
+  }
+
+  private static native String join(String[] stringArray) /*-{
+    return stringArray.join('');
+  }-*/;
+
+  private static native String setLength(String[] stringArray, int length) /*-{
+    stringArray.length = length;
+  }-*/;
+
+  private int arrayLen = 0;
+  private String[] stringArray = new String[0];
+  private int stringLength = 0;
+
+  public void append(String toAppend) {
+    // Coerce to "null" if null.
+    if (toAppend == null) {
+      toAppend = "null";
+    }
+    int appendLength = toAppend.length();
+    if (appendLength > 0) {
+      stringArray[arrayLen++] = toAppend == null ? "null" : toAppend;
+      stringLength += appendLength;
+      /*
+       * If we hit 1k elements, let's do a join to reduce the array size. This
+       * number was arrived at experimentally through benchmarking.
+       */
+      if (arrayLen > 1024) {
+        toString();
+        // Preallocate the next 1024 (faster on FF).
+        setLength(stringArray, 1024);
+      }
+    }
+  }
+
+  public int length() {
+    return stringLength;
+  }
+
+  public void replace(int start, int end, String toInsert) {
+    // Get the joined string.
+    String s = toString();
+
+    // Build a new buffer in pieces (will throw exceptions).
+    stringArray = new String[] {
+        s.substring(0, start), toInsert, s.substring(end)};
+    arrayLen = 3;
+
+    // Calculate the new string length.
+    stringLength += toInsert.length() - (end - start);
+  }
+
+  @Override
+  public String toString() {
+    /*
+     * Normalize the array to exactly one element (even if it's completely
+     * empty), so we can unconditionally grab the first element.
+     */
+    if (arrayLen != 1) {
+      setLength(stringArray, arrayLen);
+      String s = join(stringArray);
+      // Create a new array to allow everything to get GC'd.
+      stringArray = new String[] {s};
+      arrayLen = 1;
+    }
+    return stringArray[0];
+  }
+}
diff --git a/user/src/com/google/gwt/user/UserAgent.gwt.xml b/user/src/com/google/gwt/user/UserAgent.gwt.xml
index 42433f6..6e4a49f 100644
--- a/user/src/com/google/gwt/user/UserAgent.gwt.xml
+++ b/user/src/com/google/gwt/user/UserAgent.gwt.xml
@@ -49,4 +49,6 @@
       return "unknown";
   ]]></property-provider>
 
+  <!-- Deferred binding to optimize JRE classes based on user agent. -->
+  <inherits name="com.google.gwt.emul.EmulationWithUserAgent"/>
 </module>
diff --git a/user/super/com/google/gwt/emul/Emulation.gwt.xml b/user/super/com/google/gwt/emul/Emulation.gwt.xml
index e41d58f..d74ae89 100644
--- a/user/super/com/google/gwt/emul/Emulation.gwt.xml
+++ b/user/super/com/google/gwt/emul/Emulation.gwt.xml
@@ -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   -->
 <!-- may obtain a copy of the License at                                    -->
@@ -15,5 +15,9 @@
 <!-- A JavaScript-based emulation of the Java Runtime library.              -->
 <!-- Do not inherit this module directly; inherit com.google.gwt.core.Core. -->
 <module>
-   <super-source/>
+  <replace-with class="com.google.gwt.core.client.impl.StringBufferImplArray">
+    <when-type-is class="com.google.gwt.core.client.impl.StringBufferImpl"/>
+  </replace-with>
+
+  <super-source/>
 </module>
diff --git a/user/super/com/google/gwt/emul/EmulationWithUserAgent.gwt.xml b/user/super/com/google/gwt/emul/EmulationWithUserAgent.gwt.xml
new file mode 100644
index 0000000..4add1cf
--- /dev/null
+++ b/user/super/com/google/gwt/emul/EmulationWithUserAgent.gwt.xml
@@ -0,0 +1,33 @@
+<!--                                                                        -->
+<!-- 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   -->
+<!-- may obtain a copy of the License at                                    -->
+<!--                                                                        -->
+<!-- http://www.apache.org/licenses/LICENSE-2.0                             -->
+<!--                                                                        -->
+<!-- Unless required by applicable law or agreed to in writing, software    -->
+<!-- distributed under the License is distributed on an "AS IS" BASIS,      -->
+<!-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or        -->
+<!-- implied. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<!-- Deferred binding rules for optimized JRE implementations based on user agent. -->
+<module>
+  <inherits name="com.google.gwt.emul.Emulation"/>
+
+  <!-- If the user agent is known, use Append as the default StringBuffer implementation. -->
+  <replace-with class="com.google.gwt.core.client.impl.StringBufferImplAppend">
+    <when-type-is class="com.google.gwt.core.client.impl.StringBufferImpl"/>
+  </replace-with>
+
+  <!--  Append is awful on IE.  Use Array instead. -->
+  <replace-with class="com.google.gwt.core.client.impl.StringBufferImplArray">
+    <when-type-is class="com.google.gwt.core.client.impl.StringBufferImpl"/>
+    <any>
+      <when-property-is name="user.agent" value="ie6"/>
+    </any>
+  </replace-with>
+
+  <super-source/>
+</module>
diff --git a/user/super/com/google/gwt/emul/java/lang/StringBuffer.java b/user/super/com/google/gwt/emul/java/lang/StringBuffer.java
index b253f42..f851bef 100644
--- a/user/super/com/google/gwt/emul/java/lang/StringBuffer.java
+++ b/user/super/com/google/gwt/emul/java/lang/StringBuffer.java
@@ -15,13 +15,24 @@
  */
 package java.lang;
 
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.impl.StringBufferImpl;
+
 /**
  * A fast way to create strings using multiple appends. This is implemented
- * using {@link StringBuilder}, so see that class for implementation notes and
- * performance characteristics.
+ * using a {@link StringBufferImpl} that is chosen with deferred binding.
+ * 
+ * Most methods will give expected performance results. Exceptions are
+ * {@link #setCharAt(int, char)}, which is O(n), and {@link #length()}, which
+ * forces a {@link #toString()} and thus should not be used many times on the
+ * same <code>StringBuffer</code>.
+ * 
+ * This class is an exact clone of {@link StringBuilder} except for the name.
+ * Any change made to one should be mirrored in the other.
  */
 public class StringBuffer implements CharSequence {
-  private final StringBuilder builder = new StringBuilder();
+  private final StringBufferImpl impl = GWT.create(StringBufferImpl.class);
+  private final Object data = impl.createData();
 
   public StringBuffer() {
   }
@@ -43,67 +54,70 @@
   }
 
   public StringBuffer append(boolean x) {
-    builder.append(x);
+    impl.append(data, x);
     return this;
   }
 
   public StringBuffer append(char x) {
-    builder.append(x);
+    impl.appendNonNull(data, String.valueOf(x));
     return this;
   }
 
   public StringBuffer append(char[] x) {
-    builder.append(x);
+    impl.appendNonNull(data, String.valueOf(x));
     return this;
   }
 
   public StringBuffer append(char[] x, int start, int len) {
-    builder.append(x, start, len);
+    impl.appendNonNull(data, String.valueOf(x, start, len));
     return this;
   }
 
   public StringBuffer append(CharSequence x) {
-    builder.append(x);
+    impl.append(data, x);
     return this;
   }
 
   public StringBuffer append(CharSequence x, int start, int end) {
-    builder.append(x, start, end);
+    if (x == null) {
+      x = "null";
+    }
+    impl.append(data, x.subSequence(start, end));
     return this;
   }
 
   public StringBuffer append(double x) {
-    builder.append(x);
+    impl.append(data, x);
     return this;
   }
 
   public StringBuffer append(float x) {
-    builder.append(x);
+    impl.append(data, x);
     return this;
   }
 
   public StringBuffer append(int x) {
-    builder.append(x);
+    impl.append(data, x);
     return this;
   }
 
   public StringBuffer append(long x) {
-    builder.append(x);
+    impl.appendNonNull(data, String.valueOf(x));
     return this;
   }
 
   public StringBuffer append(Object x) {
-    builder.append(x);
+    impl.append(data, x);
     return this;
   }
 
-  public StringBuffer append(String toAppend) {
-    builder.append(toAppend);
+  public StringBuffer append(String x) {
+    impl.append(data, x);
     return this;
-  };
+  }
 
   public StringBuffer append(StringBuffer x) {
-    builder.append(x);
+    impl.append(data, x);
     return this;
   }
 
@@ -112,146 +126,146 @@
    * {@link Integer#MAX_VALUE}.
    */
   public int capacity() {
-    return builder.capacity();
+    return Integer.MAX_VALUE;
   }
 
   public char charAt(int index) {
-    return builder.charAt(index);
+    return toString().charAt(index);
   }
 
   public StringBuffer delete(int start, int end) {
-    builder.delete(start, end);
-    return this;
+    return replace(start, end, "");
   }
 
   public StringBuffer deleteCharAt(int start) {
-    builder.deleteCharAt(start);
-    return this;
+    return delete(start, start + 1);
   }
 
   /**
    * This implementation does not track capacity; calling this method has no
    * effect.
    */
+  @SuppressWarnings("unused")
   public void ensureCapacity(int ignoredCapacity) {
-    builder.ensureCapacity(ignoredCapacity);
   }
 
   public void getChars(int srcStart, int srcEnd, char[] dst, int dstStart) {
-    builder.getChars(srcStart, srcEnd, dst, dstStart);
+    String.__checkBounds(length(), srcStart, srcEnd);
+    String.__checkBounds(dst.length, dstStart, dstStart + (srcEnd - srcStart));
+    String s = toString();
+    while (srcStart < srcEnd) {
+      dst[dstStart++] = s.charAt(srcStart++);
+    }
   }
 
   public int indexOf(String x) {
-    return builder.indexOf(x);
+    return toString().indexOf(x);
   }
 
   public int indexOf(String x, int start) {
-    return builder.indexOf(x, start);
+    return toString().indexOf(x, start);
   }
 
   public StringBuffer insert(int index, boolean x) {
-    builder.insert(index, x);
-    return this;
+    return insert(index, String.valueOf(x));
   }
 
   public StringBuffer insert(int index, char x) {
-    builder.insert(index, x);
-    return this;
+    return insert(index, String.valueOf(x));
   }
 
   public StringBuffer insert(int index, char[] x) {
-    builder.insert(index, x);
-    return this;
+    return insert(index, String.valueOf(x));
   }
 
   public StringBuffer insert(int index, char[] x, int offset, int len) {
-    builder.insert(index, x, offset, len);
-    return this;
+    return insert(index, String.valueOf(x, offset, len));
   }
 
   public StringBuffer insert(int index, CharSequence chars) {
-    builder.insert(index, chars);
-    return this;
+    return insert(index, chars.toString());
   }
 
   public StringBuffer insert(int index, CharSequence chars, int start, int end) {
-    builder.insert(index, chars, start, end);
-    return this;
+    return insert(index, chars.subSequence(start, end).toString());
   }
 
   public StringBuffer insert(int index, double x) {
-    builder.insert(index, x);
-    return this;
+    return insert(index, String.valueOf(x));
   }
 
   public StringBuffer insert(int index, float x) {
-    builder.insert(index, x);
-    return this;
+    return insert(index, String.valueOf(x));
   }
 
   public StringBuffer insert(int index, int x) {
-    builder.insert(index, x);
-    return this;
+    return insert(index, String.valueOf(x));
   }
 
   public StringBuffer insert(int index, long x) {
-    builder.insert(index, x);
-    return this;
+    return insert(index, String.valueOf(x));
   }
 
   public StringBuffer insert(int index, Object x) {
-    builder.insert(index, x);
-    return this;
+    return insert(index, String.valueOf(x));
   }
 
   public StringBuffer insert(int index, String x) {
-    builder.insert(index, x);
-    return this;
+    return replace(index, index, x);
   }
 
   public int lastIndexOf(String s) {
-    return builder.lastIndexOf(s);
+    return toString().lastIndexOf(s);
   }
 
   public int lastIndexOf(String s, int start) {
-    return builder.lastIndexOf(s, start);
+    return toString().lastIndexOf(s, start);
   }
 
   public int length() {
-    return builder.length();
+    return impl.length(data);
   }
 
   public StringBuffer replace(int start, int end, String toInsert) {
-    builder.replace(start, end, toInsert);
+    impl.replace(data, start, end, toInsert);
     return this;
   }
 
+  /**
+   * Warning! This method is <b>much</b> slower than the JRE implementation. If
+   * you need to do character level manipulation, you are strongly advised to
+   * use a char[] directly.
+   */
   public void setCharAt(int index, char x) {
-    builder.setCharAt(index, x);
+    replace(index, index + 1, String.valueOf(x));
   }
 
   public void setLength(int newLength) {
-    builder.setLength(newLength);
+    int oldLength = length();
+    if (newLength < oldLength) {
+      delete(newLength, oldLength);
+    } else if (newLength > oldLength) {
+      append(new char[newLength - oldLength]);
+    }
   }
 
   public CharSequence subSequence(int start, int end) {
-    return builder.subSequence(start, end);
+    return this.substring(start, end);
   }
 
   public String substring(int begin) {
-    return builder.substring(begin);
+    return toString().substring(begin);
   }
 
   public String substring(int begin, int end) {
-    return builder.substring(begin, end);
+    return toString().substring(begin, end);
   }
 
   @Override
   public String toString() {
-    return builder.toString();
+    return impl.toString(data);
   }
 
   public void trimToSize() {
-    builder.trimToSize();
   }
 }
diff --git a/user/super/com/google/gwt/emul/java/lang/StringBuilder.java b/user/super/com/google/gwt/emul/java/lang/StringBuilder.java
index 1a7416d..00f9d19 100644
--- a/user/super/com/google/gwt/emul/java/lang/StringBuilder.java
+++ b/user/super/com/google/gwt/emul/java/lang/StringBuilder.java
@@ -15,28 +15,24 @@
  */
 package java.lang;
 
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.impl.StringBufferImpl;
+
 /**
- * A fast way to create strings using multiple appends. This
- * implementation is optimized for fast appends. Most methods will give expected
- * performance results, with the notable exception of
- * {@link #setCharAt(int, char)}, which is extremely slow and should be avoided
- * if possible.
+ * A fast way to create strings using multiple appends. This is implemented
+ * using a {@link StringBufferImpl} that is chosen with deferred binding.
+ * 
+ * Most methods will give expected performance results. Exceptions are
+ * {@link #setCharAt(int, char)}, which is O(n), and {@link #length()}, which
+ * forces a {@link #toString()} and thus should not be used many times on the
+ * same <code>StringBuilder</code>.
+ * 
+ * This class is an exact clone of {@link StringBuffer} except for the name. Any
+ * change made to one should be mirrored in the other.
  */
 public class StringBuilder implements CharSequence {
-
-  private static native String join(String[] stringArray) /*-{
-    return stringArray.join('');
-  }-*/;
-
-  private static native String setLength(String[] stringArray, int length) /*-{
-    stringArray.length = length;
-  }-*/;
-
-  private int arrayLen = 0;
-
-  private String[] stringArray = new String[0];
-
-  private int stringLength = 0;
+  private final StringBufferImpl impl = GWT.create(StringBufferImpl.class);
+  private final Object data = impl.createData();
 
   public StringBuilder() {
   }
@@ -58,73 +54,71 @@
   }
 
   public StringBuilder append(boolean x) {
-    return append(String.valueOf(x));
+    impl.append(data, x);
+    return this;
   }
 
   public StringBuilder append(char x) {
-    return append(String.valueOf(x));
+    impl.appendNonNull(data, String.valueOf(x));
+    return this;
   }
 
   public StringBuilder append(char[] x) {
-    return append(String.valueOf(x));
+    impl.appendNonNull(data, String.valueOf(x));
+    return this;
   }
 
   public StringBuilder append(char[] x, int start, int len) {
-    return append(String.valueOf(x, start, len));
+    impl.appendNonNull(data, String.valueOf(x, start, len));
+    return this;
   }
 
   public StringBuilder append(CharSequence x) {
-    return append(x.toString());
+    impl.append(data, x);
+    return this;
   }
 
   public StringBuilder append(CharSequence x, int start, int end) {
-    return append(x.subSequence(start, end));
+    if (x == null) {
+      x = "null";
+    }
+    impl.append(data, x.subSequence(start, end));
+    return this;
   }
 
   public StringBuilder append(double x) {
-    return append(String.valueOf(x));
+    impl.append(data, x);
+    return this;
   }
 
   public StringBuilder append(float x) {
-    return append(String.valueOf(x));
+    impl.append(data, x);
+    return this;
   }
 
   public StringBuilder append(int x) {
-    return append(String.valueOf(x));
+    impl.append(data, x);
+    return this;
   }
 
   public StringBuilder append(long x) {
-    return append(String.valueOf(x));
+    impl.appendNonNull(data, String.valueOf(x));
+    return this;
   }
 
   public StringBuilder append(Object x) {
-    return append(String.valueOf(x));
+    impl.append(data, x);
+    return this;
   }
 
-  public StringBuilder append(String toAppend) {
-    // Coerce to "null" if null.
-    if (toAppend == null) {
-      toAppend = "null";
-    }
-    int appendLength = toAppend.length();
-    if (appendLength > 0) {
-      stringArray[arrayLen++] = toAppend;
-      stringLength += appendLength;
-      /*
-       * If we hit 1k elements, let's do a join to reduce the array size. This
-       * number was arrived at experimentally through benchmarking.
-       */
-      if (arrayLen > 1024) {
-        toString();
-        // Preallocate the next 1024 (faster on FF).
-        setLength(stringArray, 1024);
-      }
-    }
+  public StringBuilder append(String x) {
+    impl.append(data, x);
     return this;
-  };
+  }
 
-  public StringBuilder append(StringBuffer x) {
-    return append(String.valueOf(x));
+  public StringBuilder append(StringBuilder x) {
+    impl.append(data, x);
+    return this;
   }
 
   /**
@@ -156,7 +150,7 @@
   }
 
   public void getChars(int srcStart, int srcEnd, char[] dst, int dstStart) {
-    String.__checkBounds(stringLength, srcStart, srcEnd);
+    String.__checkBounds(length(), srcStart, srcEnd);
     String.__checkBounds(dst.length, dstStart, dstStart + (srcEnd - srcStart));
     String s = toString();
     while (srcStart < srcEnd) {
@@ -229,21 +223,11 @@
   }
 
   public int length() {
-    return stringLength;
+    return impl.length(data);
   }
 
   public StringBuilder replace(int start, int end, String toInsert) {
-    // Get the joined string.
-    String s = toString();
-
-    // Build a new buffer in pieces (will throw exceptions).
-    stringArray = new String[] {
-        s.substring(0, start), toInsert, s.substring(end)};
-    arrayLen = 3;
-
-    // Calculate the new string length.
-    stringLength += toInsert.length() - (end - start);
-
+    impl.replace(data, start, end, toInsert);
     return this;
   }
 
@@ -257,7 +241,7 @@
   }
 
   public void setLength(int newLength) {
-    int oldLength = stringLength;
+    int oldLength = length();
     if (newLength < oldLength) {
       delete(newLength, oldLength);
     } else if (newLength > oldLength) {
@@ -279,18 +263,7 @@
 
   @Override
   public String toString() {
-    /*
-     * Normalize the array to exactly one element (even if it's completely
-     * empty), so we can unconditionally grab the first element.
-     */
-    if (arrayLen != 1) {
-      setLength(stringArray, arrayLen);
-      String s = join(stringArray);
-      // Create a new array to allow everything to get GC'd.
-      stringArray = new String[] {s};
-      arrayLen = 1;
-    }
-    return stringArray[0];
+    return impl.toString(data);
   }
 
   public void trimToSize() {
diff --git a/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java b/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java
index 74a017b..c2646fa 100644
--- a/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java
@@ -515,6 +515,35 @@
     assertTrue(b);
     assertEquals("null true", test);
   }
+  
+  public void testInliningOrder() {
+    final StringBuffer buf = new StringBuffer();
+    class Misc {
+      // TODO(spoon) rename all this
+      String effect1() {
+        if (FALSE) {
+          return effect1();
+        }
+        buf.append("effect1");
+        return "effect2";
+      }
+      void effect2() {
+        if (FALSE) {
+          effect2();
+        }
+        buf.append("effect2");
+      }
+      void useBoth(Object x, Object y) {
+        StringBuffer b = buf;
+        b.append(x);
+        b.append(y);
+      }
+    }
+   
+    Misc misc = new Misc();
+    misc.useBoth("blah", buf.toString());
+    assertEquals("blah", buf.toString());
+  }
 
   public void testJavaScriptReservedWords() {
     boolean delete = TRUE;
diff --git a/user/test/com/google/gwt/emultest/EmulSuite.java b/user/test/com/google/gwt/emultest/EmulSuite.java
index 6d37d9e..34266e4 100644
--- a/user/test/com/google/gwt/emultest/EmulSuite.java
+++ b/user/test/com/google/gwt/emultest/EmulSuite.java
@@ -25,6 +25,7 @@
 import com.google.gwt.emultest.java.lang.LongTest;
 import com.google.gwt.emultest.java.lang.ObjectTest;
 import com.google.gwt.emultest.java.lang.ShortTest;
+import com.google.gwt.emultest.java.lang.StringBufferDefaultImplTest;
 import com.google.gwt.emultest.java.lang.StringBufferTest;
 import com.google.gwt.emultest.java.lang.StringTest;
 import com.google.gwt.emultest.java.lang.SystemTest;
@@ -71,6 +72,7 @@
     suite.addTestSuite(ObjectTest.class);
     suite.addTestSuite(ShortTest.class);
     suite.addTestSuite(StringBufferTest.class);
+    suite.addTestSuite(StringBufferDefaultImplTest.class);
     suite.addTestSuite(StringTest.class);
     suite.addTestSuite(SystemTest.class);
 
diff --git a/user/test/com/google/gwt/emultest/EmulSuiteUnknownAgent.gwt.xml b/user/test/com/google/gwt/emultest/EmulSuiteUnknownAgent.gwt.xml
new file mode 100644
index 0000000..718c824
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/EmulSuiteUnknownAgent.gwt.xml
@@ -0,0 +1,22 @@
+<!--                                                                        -->
+<!-- Copyright 2007 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   -->
+<!-- may obtain a copy of the License at                                    -->
+<!--                                                                        -->
+<!-- http://www.apache.org/licenses/LICENSE-2.0                             -->
+<!--                                                                        -->
+<!-- Unless required by applicable law or agreed to in writing, software    -->
+<!-- distributed under the License is distributed on an "AS IS" BASIS,      -->
+<!-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or        -->
+<!-- implied. License for the specific language governing permissions and   -->
+<!-- limitations under the License.                                         -->
+
+<module>
+  <inherits name='com.google.gwt.emultest.EmulSuite'/>
+
+  <!--  Remove JRE deferred bindings, so that the default implementations can be tested -->
+  <replace-with class="com.google.gwt.core.client.impl.StringBufferImplArray">
+    <when-type-is class="com.google.gwt.core.client.impl.StringBufferImpl"/>
+  </replace-with>
+</module>
diff --git a/user/test/com/google/gwt/emultest/java/lang/StringBufferBenchmark.java b/user/test/com/google/gwt/emultest/java/lang/StringBufferBenchmark.java
new file mode 100644
index 0000000..b1855a2
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/lang/StringBufferBenchmark.java
@@ -0,0 +1,400 @@
+/*
+ * 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
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.emultest.java.lang;
+
+import com.google.gwt.benchmarks.client.Benchmark;
+import com.google.gwt.benchmarks.client.IntRange;
+import com.google.gwt.benchmarks.client.Operator;
+import com.google.gwt.benchmarks.client.RangeField;
+import com.google.gwt.core.client.JavaScriptObject;
+
+/**
+ * A {@link Benchmark} for {@link StringBuilder StringBuilders}. This includes
+ * a benchmark from Ray Cromwell that builds display commands in one of two
+ * ways. One version uses a StringBuilder, and the other uses raw pushes with
+ * JavaScript. Note that there is no actual DisplayList interface, because
+ * otherwise the benchmark might have some dynamic dispatch involved. By default
+ * this benchmarks only the standard <code>StringBuilder</code> and
+ * <code>StringBuffer</code>. To run the full suite, comment in the alternate
+ * version of {@link #appendKindsRange}.
+ */
+public class StringBufferBenchmark extends Benchmark {
+  /**
+   * The type of StringBuilder to use for a test.
+   */
+  protected enum SBType {
+    JS("raw JavaScrpt"), STRBLD("StringBuilder"), STRBUF("StringBuffer");
+
+    public String description;
+
+    private SBType(String description) {
+      this.description = description;
+    }
+  }
+
+  /**
+   * A DisplayList represented using a native JavaScript array, and updated via
+   * the JavaScript push() method.
+   */
+  @SuppressWarnings("unused")
+  private static class JSArrayDisplayList {
+    private JavaScriptObject jso = JavaScriptObject.createArray();
+
+    public void begin() {
+      jso = JavaScriptObject.createArray();
+    }
+
+    public native void cmd(String cmd) /*-{
+      this.@com.google.gwt.emultest.java.lang.StringBufferBenchmark$JSArrayDisplayList::jso.push(cmd, 0);
+    }-*/;
+
+    public native void cmd(String cmd, int a) /*-{
+      this.@com.google.gwt.emultest.java.lang.StringBufferBenchmark$JSArrayDisplayList::jso.push(cmd, 1, a);
+    }-*/;
+
+    public native void cmd(String cmd, int a, int b) /*-{
+      this.@com.google.gwt.emultest.java.lang.StringBufferBenchmark$JSArrayDisplayList::jso.push(cmd, 2, a, b);
+    }-*/;
+
+    public native void cmd(String cmd, int a, int b, int c) /*-{
+      this.@com.google.gwt.emultest.java.lang.StringBufferBenchmark$JSArrayDisplayList::jso.push(cmd, 3, a, b, c);
+    }-*/;
+
+    public native String end() /*-{
+      return this.@com.google.gwt.emultest.java.lang.StringBufferBenchmark$JSArrayDisplayList::jso.join('');
+    }-*/;
+
+    public void fill() {
+      cmd("F");
+    }
+
+    public void lineTo(int x, int y) {
+      cmd("L", 0, 0);
+    }
+
+    public void moveTo(int x, int y) {
+      cmd("M", 0, 0);
+    }
+
+    public void rotate(int angle) {
+      cmd("R", angle);
+    }
+
+    public void stroke() {
+      cmd("S");
+    }
+
+    public void translate(int x, int y) {
+      cmd("T", x, y);
+    }
+  }
+
+  /**
+   * A DisplayList represented as a {@link StringBuffer} of commands and
+   * arguments. To contrast, see {@link JSArrayDisplayList}.
+   */
+  @SuppressWarnings("unused")
+  private static class StringBufferDisplayList {
+    private StringBuffer strbld = new StringBuffer();
+
+    public void begin() {
+      strbld = new StringBuffer();
+    }
+
+    public void cmd(String cmd) {
+      strbld.append(cmd);
+      strbld.append(0);
+    }
+
+    public void cmd(String cmd, int a) {
+      strbld.append(cmd);
+      strbld.append(1);
+      strbld.append(a);
+    }
+
+    public void cmd(String cmd, int a, int b) {
+      strbld.append(cmd);
+      strbld.append(2);
+      strbld.append(a);
+      strbld.append(b);
+    }
+
+    public void cmd(String cmd, int a, int b, int c) {
+      strbld.append(cmd);
+      strbld.append(3);
+      strbld.append(a);
+      strbld.append(b);
+      strbld.append(c);
+    }
+
+    public String end() {
+      return strbld.toString();
+    }
+
+    public void fill() {
+      cmd("F");
+    }
+
+    public void lineTo(int x, int y) {
+      cmd("L", 0, 0);
+    }
+
+    public void moveTo(int x, int y) {
+      cmd("M", 0, 0);
+    }
+
+    public void rotate(int angle) {
+      cmd("R", angle);
+    }
+
+    public void stroke() {
+      cmd("S");
+    }
+
+    public void translate(int x, int y) {
+      cmd("T", x, y);
+    }
+  }
+
+  /**
+   * A DisplayList represented as a {@link StringBuilder} of commands and
+   * arguments. To contrast, see {@link JSArrayDisplayList}.
+   */
+  @SuppressWarnings("unused")
+  private static class StringBuilderDisplayList {
+    private StringBuilder strbld = new StringBuilder();
+
+    public void begin() {
+      strbld = new StringBuilder();
+    }
+
+    public void cmd(String cmd) {
+      strbld.append(cmd);
+      strbld.append(0);
+    }
+
+    public void cmd(String cmd, int a) {
+      strbld.append(cmd);
+      strbld.append(1);
+      strbld.append(a);
+    }
+
+    public void cmd(String cmd, int a, int b) {
+      strbld.append(cmd);
+      strbld.append(2);
+      strbld.append(a);
+      strbld.append(b);
+    }
+
+    public void cmd(String cmd, int a, int b, int c) {
+      strbld.append(cmd);
+      strbld.append(3);
+      strbld.append(a);
+      strbld.append(b);
+      strbld.append(c);
+    }
+
+    public String end() {
+      return strbld.toString();
+    }
+
+    public void fill() {
+      cmd("F");
+    }
+
+    public void lineTo(int x, int y) {
+      cmd("L", 0, 0);
+    }
+
+    public void moveTo(int x, int y) {
+      cmd("M", 0, 0);
+    }
+
+    public void rotate(int angle) {
+      cmd("R", angle);
+    }
+
+    public void stroke() {
+      cmd("S");
+    }
+
+    public void translate(int x, int y) {
+      cmd("T", x, y);
+    }
+  }
+
+  private static final String P_CLOSE = "</p>";
+  private static final String P_OPEN = "<p>";
+
+  public final SBType[] appendKindsRange = new SBType[] {
+      SBType.STRBUF, SBType.STRBLD};
+  public final IntRange appendTimesRange = new IntRange(32, 4096,
+      Operator.MULTIPLY, 2);
+  public final SBType[] displayListKindsRange = new SBType[] {
+      SBType.STRBUF, SBType.STRBLD, SBType.JS};
+  public final IntRange displayListTimesRange = new IntRange(32, 4096,
+      Operator.MULTIPLY, 2);
+
+  private volatile String abcde = "abcde";
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.emultest.EmulSuite";
+  }
+
+  /**
+   * Needed for JUnit.
+   */
+  public void testAppend() {
+  }
+
+  public void testAppend(@RangeField("appendTimesRange")
+  Integer times, @RangeField("appendKindsRange")
+  SBType sbtype) {
+    int outerTimes = 1;
+    switch (sbtype) {
+      case STRBLD:
+        for (int i = 0; i < outerTimes; i++) {
+          appendWithStringBuilder(times);
+        }
+        break;
+
+      case STRBUF:
+        for (int i = 0; i < outerTimes; i++) {
+          appendWithStringBuffer(times);
+        }
+        break;
+    }
+  }
+
+  /**
+   * Needed for JUnit.
+   */
+  public void testDisplayList() {
+  }
+
+  /**
+   * Test creating a display list command sequence.
+   */
+  public void testDisplayList(@RangeField("displayListTimesRange")
+  Integer times, @RangeField("displayListKindsRange")
+  SBType sbtype) {
+    switch (sbtype) {
+      case JS:
+        drawWithJSArrayDisplayList(times);
+        break;
+
+      case STRBUF:
+        drawWithStringBufferDisplayList(times);
+        break;
+
+      case STRBLD:
+        drawWithStringBuilderDisplayList(times);
+        break;
+    }
+  }
+
+  private void appendWithStringBuffer(int times) {
+    StringBuffer sb = new StringBuffer();
+    for (int i = 0; i < times; i++) {
+      sb.append(P_OPEN);
+      sb.append(abcde);
+      sb.append(P_CLOSE);
+    }
+    pretendToUse(sb.toString().length());
+  }
+
+  private void appendWithStringBuilder(int times) {
+    StringBuilder sb = new StringBuilder();
+    for (int i = 0; i < times; i++) {
+      sb.append(P_OPEN);
+      sb.append(abcde);
+      sb.append(P_CLOSE);
+    }
+    pretendToUse(sb.toString().length());
+  }
+
+  /**
+   * Test drawing commands using JSArrayDisplayList.
+   */
+  private void drawWithJSArrayDisplayList(int times) {
+    JSArrayDisplayList dl = new JSArrayDisplayList();
+    dl.begin();
+    for (int i = 0; i < times; i++) {
+      // draw a box
+      dl.translate(50, 50);
+      dl.rotate(45);
+      dl.moveTo(0, 0);
+      dl.lineTo(100, 0);
+      dl.lineTo(100, 100);
+      dl.lineTo(0, 100);
+      dl.lineTo(0, 0);
+      dl.stroke();
+      dl.fill();
+    }
+    pretendToUse(dl.end().length());
+  }
+
+  /**
+   * Test drawing commands using {@link StringBufferDisplayList}.
+   */
+  private void drawWithStringBufferDisplayList(int times) {
+    final StringBufferDisplayList dl = new StringBufferDisplayList();
+    dl.begin();
+    for (int i = 0; i < times; i++) {
+      // draw a box
+      dl.translate(50, 50);
+      dl.rotate(45);
+      dl.moveTo(0, 0);
+      dl.lineTo(100, 0);
+      dl.lineTo(100, 100);
+      dl.lineTo(0, 100);
+      dl.lineTo(0, 0);
+      dl.stroke();
+      dl.fill();
+    }
+    pretendToUse(dl.end().length());
+  }
+
+  /**
+   * Test drawing commands using {@link StringBufferDisplayList}.
+   */
+  private void drawWithStringBuilderDisplayList(int times) {
+    final StringBuilderDisplayList dl = new StringBuilderDisplayList();
+    dl.begin();
+    for (int i = 0; i < times; i++) {
+      // draw a box
+      dl.translate(50, 50);
+      dl.rotate(45);
+      dl.moveTo(0, 0);
+      dl.lineTo(100, 0);
+      dl.lineTo(100, 100);
+      dl.lineTo(0, 100);
+      dl.lineTo(0, 0);
+      dl.stroke();
+      dl.fill();
+    }
+    pretendToUse(dl.end().length());
+  }
+
+  /**
+   * Make a value appear to be live, so that dead code elimination will not
+   * strip it out.
+   */
+  private native void pretendToUse(int x) /*-{
+    $wnd.completelyUselessField = x
+  }-*/;
+}
diff --git a/user/test/com/google/gwt/emultest/java/lang/StringBufferDefaultImplTest.java b/user/test/com/google/gwt/emultest/java/lang/StringBufferDefaultImplTest.java
new file mode 100644
index 0000000..75f9b02
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/lang/StringBufferDefaultImplTest.java
@@ -0,0 +1,27 @@
+/*
+ * 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
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.emultest.java.lang;
+
+/**
+ * The same as {@link StringBufferTest} except that it uses the default string
+ * buffer implementation instead of the browser-specific deferred binding.
+ */
+public class StringBufferDefaultImplTest extends StringBufferTest {
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.emultest.EmulSuiteUnknownAgent";
+  }
+}
diff --git a/user/test/com/google/gwt/emultest/java/lang/StringBufferImplBenchmark.java b/user/test/com/google/gwt/emultest/java/lang/StringBufferImplBenchmark.java
new file mode 100644
index 0000000..6c06e0e
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/lang/StringBufferImplBenchmark.java
@@ -0,0 +1,177 @@
+/*
+ * 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
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.emultest.java.lang;
+
+import com.google.gwt.benchmarks.client.Benchmark;
+import com.google.gwt.benchmarks.client.IntRange;
+import com.google.gwt.benchmarks.client.IterationTimeLimit;
+import com.google.gwt.benchmarks.client.Operator;
+import com.google.gwt.benchmarks.client.RangeField;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.impl.StringBufferImpl;
+import com.google.gwt.core.client.impl.StringBufferImplAppend;
+import com.google.gwt.core.client.impl.StringBufferImplArray;
+import com.google.gwt.core.client.impl.StringBufferImplConcat;
+import com.google.gwt.core.client.impl.StringBufferImplPush;
+
+/**
+ * Tests StringBuilder impl directly against each other. Useful when profiling
+ * browser behavior.
+ */
+public class StringBufferImplBenchmark extends Benchmark {
+
+  /**
+   * The type of StringBuilder to use for a test.
+   */
+  protected enum SBType {
+    APPEND("Append"), ARRAY("Array"), CONCAT("Concat"), PUSH("Push");
+
+    public String description;
+
+    private SBType(String description) {
+      this.description = description;
+    }
+
+    @Override
+    public String toString() {
+      return description;
+    }
+  }
+
+  @SuppressWarnings("unused")
+  private static volatile String result;
+
+  private static volatile Object[] stashSomeGarbage;
+
+  static {
+    if (GWT.isClient()) {
+      stashSomeGarbage = new Object[10000];
+      for (int i = 0; i < stashSomeGarbage.length; ++i) {
+        stashSomeGarbage[i] = new Object();
+      }
+    }
+  }
+
+  final SBType[] appendKindsRange = new SBType[] {
+      SBType.APPEND, SBType.ARRAY, SBType.CONCAT, SBType.PUSH};
+
+  final IntRange manyTimesRange = new IntRange(32, 8192, Operator.MULTIPLY, 2);
+
+  final IntRange singleTimesRange = new IntRange(32, 8192, Operator.MULTIPLY, 2);
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.emultest.EmulSuite";
+  }
+
+  public void testManyAppends() {
+  }
+
+  @IterationTimeLimit(0)
+  public void testManyAppends(@RangeField("manyTimesRange")
+  Integer times, @RangeField("appendKindsRange")
+  SBType sbtype) {
+    int number = (int) Math.sqrt(times.intValue());
+    switch (sbtype) {
+      case APPEND:
+        for (int i = 0; i < number; ++i) {
+          result = doAppend(number);
+          result = null;
+        }
+        break;
+      case ARRAY:
+        for (int i = 0; i < number; ++i) {
+          result = doArray(number);
+          result = null;
+        }
+        break;
+      case CONCAT:
+        for (int i = 0; i < number; ++i) {
+          result = doConcat(number);
+          result = null;
+        }
+        break;
+      case PUSH:
+        for (int i = 0; i < number; ++i) {
+          result = doPush(number);
+          result = null;
+        }
+        break;
+    }
+  }
+
+  public void testSingleAppend() {
+  }
+
+  @IterationTimeLimit(0)
+  public void testSingleAppend(@RangeField("singleTimesRange")
+  Integer times, @RangeField("appendKindsRange")
+  SBType sbtype) {
+    int number = times;
+    switch (sbtype) {
+      case APPEND:
+        result = doAppend(number);
+        break;
+      case ARRAY:
+        result = doArray(number);
+        break;
+      case CONCAT:
+        result = doConcat(number);
+        break;
+      case PUSH:
+        result = doPush(number);
+        break;
+    }
+    result = null;
+  }
+
+  private String doAppend(int limit) {
+    StringBufferImpl impl = new StringBufferImplAppend();
+    Object data = impl.createData();
+    for (int i = 0; i < limit; i++) {
+      impl.appendNonNull(data, "hello");
+    }
+    return impl.toString(data);
+  }
+
+  private String doArray(int limit) {
+    StringBufferImpl impl = new StringBufferImplArray();
+    Object data = impl.createData();
+    for (int i = 0; i < limit; i++) {
+      impl.appendNonNull(data, "hello");
+    }
+    return impl.toString(data);
+  }
+
+  private String doConcat(int limit) {
+    StringBufferImpl impl = new StringBufferImplConcat();
+    Object data = impl.createData();
+    for (int i = 0; i < limit; i++) {
+      impl.appendNonNull(data, "hello");
+    }
+    return impl.toString(data);
+  }
+
+  private String doPush(int limit) {
+    StringBufferImpl impl = new StringBufferImplPush();
+    Object data = impl.createData();
+    for (int i = 0; i < limit; i++) {
+      impl.appendNonNull(data, "hello");
+    }
+    return impl.toString(data);
+  }
+}
diff --git a/user/test/com/google/gwt/emultest/java/lang/StringBufferTest.java b/user/test/com/google/gwt/emultest/java/lang/StringBufferTest.java
index 27b568a..b5e2241 100644
--- a/user/test/com/google/gwt/emultest/java/lang/StringBufferTest.java
+++ b/user/test/com/google/gwt/emultest/java/lang/StringBufferTest.java
@@ -69,13 +69,12 @@
     x.append((CharSequence) "abc");
     assertEquals("abc", x.toString());
     x = new StringBuffer();
-    x.append((CharSequence) "abcde", 2, 3);
+    x.append("abcde", 2, 3);
     assertEquals("c", x.toString());
   }
 
   /**
-   * Check that capacity methods are present, even though
-   * they do nothing.
+   * Check that capacity methods are present, even though they do nothing.
    */
   public void testCapacity() {
     StringBuffer buf = new StringBuffer();
@@ -170,7 +169,7 @@
     x.insert(2, (CharSequence) "abcde");
     assertEquals("01abcde234", x.toString());
     x = new StringBuffer("01234");
-    x.insert(2, (CharSequence) "abcde", 2, 4);
+    x.insert(2, "abcde", 2, 4);
     assertEquals("01cd234", x.toString());
     x = new StringBuffer("!");
     x.insert(1, C.FALSE_VALUE);
@@ -330,16 +329,16 @@
     assertEquals("abcde", bld.toString());
 
     bld = new StringBuilder();
-    bld.append((CharSequence) "abcde", 2, 4);
+    bld.append("abcde", 2, 4);
     assertEquals("cd", bld.toString());
 
     bld = new StringBuilder();
-    bld.append(1.0);
-    assertEquals("1.0", bld.toString());
+    bld.append(1.5);
+    assertEquals("1.5", bld.toString());
 
     bld = new StringBuilder();
-    bld.append(1.0F);
-    assertEquals("1.0", bld.toString());
+    bld.append(1.5F);
+    assertEquals("1.5", bld.toString());
 
     bld = new StringBuilder();
     bld.append(5);
@@ -420,7 +419,7 @@
     assertEquals("01abcde234", bld.toString());
 
     bld = new StringBuilder("01234");
-    bld.insert(2, (CharSequence) "abcde", 2, 4);
+    bld.insert(2, "abcde", 2, 4);
     assertEquals("01cd234", bld.toString());
 
     bld = new StringBuilder("01234");