/* * 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; }-*/; 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 { 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; 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]; } }