This adds a feature to CPStringBuilder so that it knows when its array has been shared with a String object. Once this has happened, any future write operations will allocate a new array and reset the flag. This fixes the issue in PR36147.
ChangeLog: 2008-05-11 Andrew John Hughes <[EMAIL PROTECTED]> PR classpath/36147 * gnu/java/lang/CPStringBuilder.java: (allocated): New flag to mark whether or not the array has been allocated to a String object. (ensureCapacity(int)): Removed. (ensureCapacity_unsynchronized(int)): Renamed to ensureCapacity, and creates an array when allocated is true. (allocateArray(int)): Added. (trimToSize()): Use allocateArray method. (toString()): Set allocated to true; (substring(int,int)): Likewise. -- Andrew :) Support Free Java! Contribute to GNU Classpath and the OpenJDK http://www.gnu.org/software/classpath http://openjdk.java.net PGP Key: 94EFD9D8 (http://subkeys.pgp.net) Fingerprint = F8EF F1EA 401E 2E60 15FA 7927 142C 2591 94EF D9D8
Index: gnu/java/lang/CPStringBuilder.java =================================================================== RCS file: /sources/classpath/classpath/gnu/java/lang/CPStringBuilder.java,v retrieving revision 1.7 diff -u -r1.7 CPStringBuilder.java --- gnu/java/lang/CPStringBuilder.java 11 May 2008 15:44:42 -0000 1.7 +++ gnu/java/lang/CPStringBuilder.java 11 May 2008 18:40:56 -0000 @@ -71,6 +71,19 @@ private char[] value; /** + * A flag to denote whether the string being created has been + * allocated to a [EMAIL PROTECTED] String} object. On construction, + * the character array, [EMAIL PROTECTED] #value} is referenced only + * by this class. Once [EMAIL PROTECTED] #toString()}, + * [EMAIL PROTECTED] #substring(int)} or [EMAIL PROTECTED] #substring(int,int)} + * are called, the array is also referenced by a [EMAIL PROTECTED] String} + * object and this flag is set. Subsequent modifications to + * this buffer cause a new array to be allocated and the flag + * to be reset. + */ + private boolean allocated = false; + + /** * The default capacity of a buffer. * This can be configured using gnu.classpath.cpstringbuilder.capacity */ @@ -172,21 +185,6 @@ } /** - * Increase the capacity of this <code>CPStringBuilder</code>. This will - * ensure that an expensive growing operation will not occur until - * <code>minimumCapacity</code> is reached. The buffer is grown to the - * larger of <code>minimumCapacity</code> and - * <code>capacity() * 2 + 2</code>, if it is not already large enough. - * - * @param minimumCapacity the new capacity - * @see #capacity() - */ - public void ensureCapacity(int minimumCapacity) - { - ensureCapacity_unsynchronized(minimumCapacity); - } - - /** * Set the length of this StringBuffer. If the new length is greater than * the current length, all the new characters are set to '\0'. If the new * length is less than the current length, the first <code>newLength</code> @@ -205,9 +203,9 @@ int valueLength = value.length; - /* Always call ensureCapacity_unsynchronized in order to preserve + /* Always call ensureCapacity in order to preserve copy-on-write semantics. */ - ensureCapacity_unsynchronized(newLength); + ensureCapacity(newLength); if (newLength < valueLength) { @@ -309,7 +307,7 @@ if (index < 0 || index >= count) throw new StringIndexOutOfBoundsException(index); // Call ensureCapacity to enforce copy-on-write. - ensureCapacity_unsynchronized(count); + ensureCapacity(count); value[index] = ch; } @@ -340,7 +338,7 @@ if (str == null) str = "null"; int len = str.length(); - ensureCapacity_unsynchronized(count + len); + ensureCapacity(count + len); str.getChars(0, len, value, count); count += len; return this; @@ -402,7 +400,7 @@ { if (offset < 0 || count < 0 || offset > data.length - count) throw new StringIndexOutOfBoundsException(); - ensureCapacity_unsynchronized(this.count + count); + ensureCapacity(this.count + count); System.arraycopy(data, offset, value, this.count, count); this.count += count; return this; @@ -430,7 +428,7 @@ */ public CPStringBuilder append(char ch) { - ensureCapacity_unsynchronized(count + 1); + ensureCapacity(count + 1); value[count++] = ch; return this; } @@ -465,7 +463,7 @@ return append("null"); if (end - start > 0) { - ensureCapacity_unsynchronized(count + end - start); + ensureCapacity(count + end - start); for (; start < end; ++start) value[count++] = seq.charAt(start); } @@ -542,7 +540,7 @@ public CPStringBuilder appendCodePoint(int code) { int len = Character.charCount(code); - ensureCapacity_unsynchronized(count + len); + ensureCapacity(count + len); Character.toChars(code, value, count); count += len; return this; @@ -565,7 +563,7 @@ throw new StringIndexOutOfBoundsException(start); if (end > count) end = count; - ensureCapacity_unsynchronized(count); + ensureCapacity(count); if (count - end != 0) System.arraycopy(value, end, value, start, count - end); count -= end - start; @@ -607,7 +605,7 @@ int len = str.length(); // Calculate the difference in 'count' after the replace. int delta = len - (end > count ? count : end) + start; - ensureCapacity_unsynchronized(count + delta); + ensureCapacity(count + delta); if (delta != 0 && end < count) System.arraycopy(value, end, value, end + delta, count - end); @@ -635,7 +633,7 @@ if (offset < 0 || offset > count || len < 0 || str_offset < 0 || str_offset > str.length - len) throw new StringIndexOutOfBoundsException(); - ensureCapacity_unsynchronized(count + len); + ensureCapacity(count + len); System.arraycopy(value, offset, value, offset + len, count - offset); System.arraycopy(str, str_offset, value, offset, len); count += len; @@ -675,7 +673,7 @@ if (str == null) str = "null"; int len = str.length(); - ensureCapacity_unsynchronized(count + len); + ensureCapacity(count + len); System.arraycopy(value, offset, value, offset + len, count - offset); str.getChars(0, len, value, offset); count += len; @@ -721,7 +719,7 @@ if (start < 0 || end < 0 || start > end || end > sequence.length()) throw new IndexOutOfBoundsException(); int len = end - start; - ensureCapacity_unsynchronized(count + len); + ensureCapacity(count + len); System.arraycopy(value, offset, value, offset + len, count - offset); for (int i = start; i < end; ++i) value[offset++] = sequence.charAt(i); @@ -773,7 +771,7 @@ { if (offset < 0 || offset > count) throw new StringIndexOutOfBoundsException(offset); - ensureCapacity_unsynchronized(count + 1); + ensureCapacity(count + 1); System.arraycopy(value, offset, value, offset + 1, count - offset); value[offset] = ch; count++; @@ -928,7 +926,7 @@ public CPStringBuilder reverse() { // Call ensureCapacity to enforce copy-on-write. - ensureCapacity_unsynchronized(count); + ensureCapacity(count); for (int i = count >> 1, j = count - i; --i >= 0; ++j) { char c = value[i]; @@ -955,11 +953,7 @@ // If we save more than 200 characters, shrink. // If we save more than 1/4 of the buffer, shrink. if (wouldSave > 200 || wouldSave * 4 > value.length) - { - char[] newValue = new char[count]; - System.arraycopy(value, 0, newValue, 0, count); - value = newValue; - } + allocateArray(count); } /** @@ -1040,27 +1034,41 @@ /** * Increase the capacity of this <code>StringBuilder</code>. This will - * ensure that an expensive growing operation will not occur until - * <code>minimumCapacity</code> is reached. The buffer is grown to the - * larger of <code>minimumCapacity</code> and + * ensure that an expensive growing operation will not occur until either + * <code>minimumCapacity</code> is reached or the array has been allocated. + * The buffer is grown to the larger of <code>minimumCapacity</code> and * <code>capacity() * 2 + 2</code>, if it is not already large enough. * * @param minimumCapacity the new capacity * @see #capacity() */ - protected void ensureCapacity_unsynchronized(int minimumCapacity) + public void ensureCapacity(int minimumCapacity) { - if (minimumCapacity > value.length) + if (allocated || minimumCapacity > value.length) { int max = value.length * 2 + 2; minimumCapacity = (minimumCapacity < max ? max : minimumCapacity); - char[] nb = new char[minimumCapacity]; - System.arraycopy(value, 0, nb, 0, count); - value = nb; + allocateArray(minimumCapacity); } } /** + * Allocates a new character array. This method is triggered when + * a write is attempted after the array has been passed to a + * [EMAIL PROTECTED] String} object, so that the builder does not modify + * the immutable [EMAIL PROTECTED] String}. + * + * @param capacity the size of the new array. + */ + private void allocateArray(int capacity) + { + char[] nb = new char[capacity]; + System.arraycopy(value, 0, nb, 0, count); + value = nb; + allocated = false; + } + + /** * Get the length of the <code>String</code> this <code>StringBuilder</code> * would create. Not to be confused with the <em>capacity</em> of the * <code>StringBuilder</code>. @@ -1123,6 +1131,7 @@ int len = endIndex - beginIndex; if (len == 0) return ""; + allocated = true; return VMCPStringBuilder.toString(value, beginIndex, len); } @@ -1136,6 +1145,7 @@ */ public String toString() { + allocated = true; return VMCPStringBuilder.toString(value, 0, count); }