/*
Copyright 1996-2008 Ariba, 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.
$Id: //ariba/platform/util/core/ariba/util/core/FastStringBuffer.java#11 $
*/
package ariba.util.core;
/**
Object subclass resembling the java.lang.StringBuffer class (an object that
manages a mutable string). Unlike java.lang.StringBuffer, none of
FastStringBuffer's methods are synchronized, which results in a significant
performance increase. FastStringBuffer also has additional API that allows
it to be more easily modified than the standard StringBuffer.
@aribaapi documented
*/
public class FastStringBuffer
{
String string;
char buffer[];
int length;
boolean doublesCapacity;
// the maximum number of bytes we'll add to our capacity
int maxGrowIncrement = 4 * 1024 * 1024;
/* constructors */
/**
Constructs an empty String buffer with the specified initial length.
@param length the initial length, must be greater than or
equal to zero
@aribaapi documented
*/
public FastStringBuffer (int length)
{
this.string = null;
this.buffer = new char[length];
this.length = 0;
this.doublesCapacity = true;
}
/**
Creates a FastStringBuffer containing the empty string.
@aribaapi documented
*/
public FastStringBuffer ()
{
this("");
}
/**
Creates a FastStringBuffer containing the characters in <b>aString</b>.
@param aString the initial string to hold
@aribaapi documented
*/
public FastStringBuffer (String aString)
{
super();
if (aString == null || aString.equals("")) {
buffer = new char[8];
}
else {
buffer = new char[aString.length() + 1];
setStringValue(aString);
}
doublesCapacity=true;
}
/**
Creates a FastStringBuffer containing the characters in <b>aString</b>
from the index <b>start</b> to <b>end</b>.
@param aString the string to copy a range of characters from
@param start the index of the string to start copying from
@param end the index of the string to end copying by, the
character at position <b>end</b> is excluded.
@aribaapi documented
*/
public FastStringBuffer (String aString, int start, int end)
{
buffer = new char[end - start];
length = end-start;
string = null;
doublesCapacity = true;
aString.getChars(start, end, buffer, 0);
}
/**
Creates a FastStringBuffer containing the character <b>aChar</b>.
@param aChar the initial character to contain
@aribaapi documented
*/
public FastStringBuffer (char aChar)
{
super();
buffer = new char[8];
buffer[0] = aChar;
length = 1;
doublesCapacity=true;
}
/**
Return the buffer holding the state of this FormatBuffer. The
effect on the returned array of operations performed on this
FormatBuffer is not defined.
@return returns the character array that is used for internal
storage. There may be garbage characters after the length.
@aribaapi documented
*/
public char[] getBuffer ()
{
return buffer;
}
void setCapacity (int newCapacity)
{
char oldBuffer[] = buffer;
buffer = new char[newCapacity];
System.arraycopy(oldBuffer, 0, buffer, 0, length);
}
void makeRoomFor (int want)
{
int avail = buffer.length - length;
if (want <= avail) {
return;
}
int proposedIncrease = doublesCapacity ? buffer.length : 20;
proposedIncrease = Math.min(maxGrowIncrement, proposedIncrease);
if (want > avail + proposedIncrease) {
setCapacity(buffer.length + want);
}
else {
setCapacity(buffer.length + proposedIncrease);
}
}
/**
Set whether the FastStringBuffer should double its size
when some data is inserted and the internal buffer is
too small.
@param aFlag if true (the default value if the method is not
called) the array will double capacity when growing. If false
it will only pad by up to 20 characters.
@aribaapi documented
*/
public void setDoublesCapacityWhenGrowing (boolean aFlag)
{
doublesCapacity = aFlag;
}
/**
Returns whether FastStringBuffer doubles its size when
some data is inserted and the internal buffer is
too small.
@return true if the buffer will double when growing, false
otherwise.
@aribaapi documented
*/
public boolean getDoublesCapacityWhenGrowing ()
{
return doublesCapacity;
}
/**
Sets the FastStringBuffer's contents to the characters in
<b>aString</b>.
@param aString the string to set the contents of the
fastStringBuffer to. Any previous contents will be cleared.
@aribaapi documented
*/
public void setStringValue (String aString)
{
length = 0;
append(aString);
string = aString;
}
/**
Returns the String for the FastStringBuffer's contents.
@param startIndex the index of the FastStringBuffer to start
copying from.
@param endIndex the index of the FastStringBuffer to end
copying with. The character at endIndex is excluded.
@return a new String containing the characters between the
specified start and end index into the FastStringBuffer
@exception StringIndexOutOfBoundsException if the
<code>startIndex</code> or <code>endIndex</code> arguments
index characters outside the bounds of the FastStringBuffer.
@aribaapi documented
*/
public String substring (int startIndex, int endIndex)
{
if (startIndex < 0 || startIndex > length) {
throw new StringIndexOutOfBoundsException(startIndex);
}
if (endIndex > length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
return new String(buffer, startIndex, endIndex - startIndex);
}
/**
Returns the String for the FastStringBuffer's contents.
@return a string representing the contents of this buffer.
@aribaapi documented
*/
public String toString ()
{
if (string == null) {
if (length == 0) {
string = "";
}
else {
string = new String(buffer, 0, length);
}
}
return string;
}
/**
Returns the character at <b>index</b>.
@param index the index of the character to return.
@return the character at the location specified.
@exception StringIndexOutOfBoundsException If the index is invalid
@aribaapi documented
*/
public char charAt (int index)
{
if (index < 0 || index >= length) {
throw new StringIndexOutOfBoundsException(index);
}
return buffer[index];
}
/**
Check if the FastStringBuffer's characters start with the
contents of a specified String.
@param value the String to match against
@return <b>true</b> if the specified <b>value</b> matches the
first characters in the FastStringBuffer, <b>false</b>
otherwise. Returns true if <b>value</b> is empty. If value is
not empty, but the FastStringBuffer is empty, it will return
false.
@aribaapi documented
*/
public boolean startsWith (String value)
{
return this.startsWith(value, 0);
}
/**
Check if the FastStringBuffer's characters start with the
contents of a specified String.
@param value the String to match against.
@param offset the offset of FastStringBuffer's contents to
start the comparison at.
@return <b>true</b> if the specified <b>value</b> matches the
offset characters in the FastStringBuffer, <b>false</b>
otherwise. Returns true if <b>value</b> is empty as long as
the offset is valid.
@aribaapi documented
*/
public boolean startsWith (String value, int offset)
{
int valueLength = value.length();
if (valueLength == 0) {
return true;
}
if (length == 0) {
return false;
}
if (offset < 0 || offset + valueLength > length) {
return false;
}
for (int idx = 0; idx < valueLength; idx++) {
if (this.buffer[offset+idx]!=value.charAt(idx)) {
return false;
}
}
return true;
}
/**
Returns the index of the first occurrence of <b>value</b> in the
FastStringBuffer. If the <b>value</b> is not found, -1 is returned.
@param value the String to search for in the FastStringBuffer
@return the index of the first occurrence of the String in the
character sequence represented by this object, or
<code>-1</code> if the String does not occur.
@aribaapi documented
*/
public int indexOf (String value)
{
return this.indexOf(value, 0);
}
/**
Returns the index of the first occurrence of <b>value</b> in the
FastStringBuffer, starting at character <b>offset</b>. If the
<b>value</b> is not found, -1 is returned.
@param value the String to search for in the FastStringBuffer
@param offset the offset into the FastStringBuffer's contents
to start searching at.
@return the index of the first occurrence of the String in the
character sequence represented by this object starting at the
specified offset, or <code>-1</code> if the String does not
occur or if the index is out of bounds.
@aribaapi documented
*/
public int indexOf (String value, int offset)
{
if (offset < 0 || offset >= this.length) {
return -1;
}
if (offset + value.length() > this.length) {
return -1;
}
/* String.java: There is an empty string at index 0 in an
empty string. */
if (this.length == 0 && value.length() == 0 && offset == 0) {
return 0;
}
if (this.length == 0) {
return -1;
}
char sentinal = value.charAt(0);
int idx = offset;
int startMatch;
int lastSearchIndex = this.length - value.length();
/*
The while loop is broken into two sections:
1) First we quickly scan for the start of a potential match
2) If the first character matches then compare on a
char-by-char basis for match
If 1 fails there will be no match
If 2 fails continue scanning from just beyond the last scan hit
*/
while (true) {
boolean found = false;
// 1) Quick scan
while (!found && idx <= lastSearchIndex) {
if (this.buffer[idx] == sentinal) {
found = true;
}
idx++;
}
if (!found) {
return -1;
}
// 2) Exhaustive match
startMatch = idx - 1;
int valueLength = value.length();
for (int jdx = 1; jdx < valueLength; jdx++) {
if (this.buffer[idx] != value.charAt(jdx)) {
found = false;
break;
}
idx++;
}
if (found) {
return startMatch;
}
// don't forget to rewind back to the last place we
// stopped our scan for sentinal
idx = startMatch + 1;
}
}
/**
Returns the index of the first occurrence of <b>aChar</b> in the
FastStringBuffer, starting at character <b>offset</b>.
@param aChar the character to locate in the FastStringBuffer
@param offset the offset in the FastStringBuffer for the first
character to search
@return the index of the first occurrence of the char in the
character sequence represented by this object starting at the
specified offset, or <code>-1</code> if the char does not
occur.
@aribaapi documented
*/
public int indexOf (char aChar, int offset)
{
int i;
if (offset < 0 || offset >= length) {
return -1;
}
for (i = offset; i < length; i++) {
if (buffer[i] == aChar) {
return i;
}
}
return -1;
}
/**
Returns the index of the first occurrence of <b>aChar</b> in the
FastStringBuffer. Equivalent to the code:
<pre>
indexOf(aChar, 0);
</pre>
@return the index of the first occurrence of the char in the
character sequence represented by this object, or
<code>-1</code> if the char does not occur.
@see #indexOf
@param aChar the character to locate in the FastStringBuffer
@aribaapi documented
*/
public int indexOf (char aChar)
{
return indexOf(aChar, 0);
}
/**
Check if a tab or a space occurs at the specified position in
the FastStringBuffer.
@param index the index of the FastStringBuffer's contents to check.
@return <b>true</b> if the FastStringBuffer contains a space
or tab character at position <b>index</b>, <b>false</b>
otherwise
@exception StringIndexOutOfBoundsException If the index is invalid.
@aribaapi documented
*/
public boolean tabOrSpaceAt (int index)
{
if (index < 0 || index >= length) {
throw new StringIndexOutOfBoundsException(index);
}
return (buffer[index] == ' ' || buffer[index] == '\t');
}
/**
Sets the character at the location <b>index</b> to the
character <b>ch</b>.
@param index the index of the character in the
FastStringBuffer to replace. It must be >= 0 and <= the length.
@param ch the character to replace the value with.
@exception StringIndexOutOfBoundsException If the index is invalid.
@aribaapi documented
*/
public void setCharAt (int index, char ch)
{
if ((index < 0) || (index >= length)) {
throw new StringIndexOutOfBoundsException(index);
}
buffer[index] = ch;
string = null;
}
/**
Appends <b>aChar</b> to the FastStringBuffer.
@param aChar the character to append to the FastStringBuffer
@aribaapi documented
*/
public void append (char aChar)
{
if (length == buffer.length) {
makeRoomFor(1);
}
buffer[length++] = aChar;
string = null;
}
private static final String JavaNullString = String.valueOf((String)null);
/**
Appends <b>aString</b> to the FastStringBuffer.
The behavior of append(null) is not defined.
@param aString the String to append to the FastStringBuffer
@aribaapi documented
*/
public void append (String aString)
{
boolean wasEmpty = (this.length == 0);
// I want to assert, but that breaks catalog. StringBuffer
// appends String.valueOf((String)null) but that would
// also break catalog.
if (aString == null) {
return;
}
appendStringRange(aString, 0, aString.length());
if (wasEmpty) {
string = aString;
}
}
/**
Appends a string representation of <b>aObject</b> to the
FastStringBuffer. Equivalent to the code:
<pre>
append(String.valueOf(aObject));
</pre>
@param aObject the Object to append the String value of.
@aribaapi documented
*/
public void append (Object aObject)
{
append(String.valueOf(aObject));
}
/**
Appends the char array to the FastStringBuffer.
@param value the character array to append to the string
@aribaapi documented
*/
public void append (char[] value)
{
replace(length, value);
}
/**
Appends a FastStringBuffer to this FastStringBuffer.
@param aBuffer the FastStringBuffer to append to this
FastStringBuffer.
@aribaapi documented
*/
public void append (FastStringBuffer aBuffer)
{
appendCharRange(aBuffer.buffer, 0, aBuffer.length);
}
/**
Copies characters from the string into the FastStringBuffer.
<p>
The first character to be copied is at index <code>startOffset</code>;
the last character to be copied is at index <code>endOffset-1</code>
(thus the total number of characters to be copied is
<code>endOffset-startOffset</code>).
@param aString the source String
@param startOffset index of the first character in the string
to copy.
@param endOffset index after the last character in the string
to copy.
@exception StringIndexOutOfBoundsException If srcBegin or
srcEnd is out of range, or if srcBegin is greater than the
srcEnd.
@aribaapi documented
*/
public void appendStringRange (String aString,
int startOffset,
int endOffset)
{
// can't find docs on boundry conditions...play it safe
if (startOffset == endOffset) {
return;
}
// give room for the new data
makeRoomFor(endOffset - startOffset);
aString.getChars(startOffset, endOffset, buffer, length);
length += endOffset - startOffset;
string = null;
}
/**
Copies characters from the character array into the
FastStringBuffer.
<p>
The first character to be copied is at index <code>startOffset</code>;
the last character to be copied is at index <code>endOffset-1</code>
(thus the total number of characters to be copied is
<code>endOffset-startOffset</code>).
@param aChar the source character array
@param startOffset index of the first character in the array
to copy.
@param endOffset index after the last character in the array
to copy.
@exception StringIndexOutOfBoundsException If startOffset or
endOffset is out of range, or if startOffset is greater than the
endOffset.
@aribaapi documented
*/
public void appendCharRange (char []aChar,
int startOffset,
int endOffset)
{
// can't find docs on boundry conditions...play it safe
if (startOffset == endOffset) {
return;
}
// give room for the new data
makeRoomFor(endOffset - startOffset);
System.arraycopy(aChar, startOffset, this.buffer, this.length,
endOffset - startOffset);
length += endOffset - startOffset;
string = null;
}
/**
Replaces <b>value</b> at <b>index</b>. If <b>index</b> +
value.length() is greater than or equal to the number of
characters within the buffer, it is expanded to accommodate.
@param index the index in the FastStringBuffer to start
replacing characters at.
@param value a character array used for the replacement
contents.
@exception StringIndexOutOfBoundsException if the index is invalid.
@aribaapi documented
*/
public void replace (int index, char [] value)
{
if ((index < 0) || (index > length)) {
throw new StringIndexOutOfBoundsException(index);
}
int extraSpaceNeeded = index + value.length - this.length;
if (extraSpaceNeeded > 0) {
this.makeRoomFor(extraSpaceNeeded);
this.length += extraSpaceNeeded;
}
System.arraycopy(value, 0, this.buffer, index, value.length);
this.string = null;
}
/**
Replaces <b>value</b> at <b>index</b> for span chars. If
<b>index</b> + value.length() is greater than or equal to the
number of characters within the buffer is expanded to
accommodate.
@param index index to start replacing at
@param value character to replace with
@param span number of characters to replace
@exception StringIndexOutOfBoundsException if the index is
invalid
@aribaapi documented
*/
public void replace (int index, char value, int span)
{
if ((index < 0) || (index >= length)) {
throw new StringIndexOutOfBoundsException(index);
}
int extraSpaceNeeded = index + span - this.length;
if (extraSpaceNeeded > 0) {
this.makeRoomFor(extraSpaceNeeded);
this.length += extraSpaceNeeded;
}
for (int idx = 0; idx < span; idx++) {
this.buffer[idx+index] = value;
}
this.string = null;
}
/**
For each instance of <code>oldString</code> found in the
buffer, we'll replace it with <code>newString</code>.
@param oldString string to replace
@param newString replacement string
@aribaapi documented
*/
public void replace (String oldString, String newString)
{
Assert.that(newString != null && oldString != null,
"You may not use a null string with StringBuffers");
if (oldString.length()==0) {
return;
}
int i = indexOf(oldString);
int oldLength = oldString.length();
int newLength = newString.length();
while (i > -1) {
int from = i+oldLength;
if (from < length()) {
moveChars(from, i);
}
else {
truncateToLength(i);
}
insert(newString, i);
i += newLength;
if (i >= length()) {
break;
}
i = indexOf(oldString, i);
}
}
/**
Inserts <b>aChar</b> at <b>index</b>. If <b>index</b> is
greater than or equal to the number of characters within the buffer,
appends <b>aChar</b>.
@param aChar character to insert
@param index location to insert character at
@exception StringIndexOutOfBoundsException if the index is invalid.
@aribaapi documented
*/
public void insert (char aChar, int index)
{
char oldBuffer[];
if (index < 0) {
throw new StringIndexOutOfBoundsException(index);
}
else if (index >= length) {
append(aChar);
return;
}
if (length < buffer.length) {
System.arraycopy(buffer, index, buffer, index + 1,
length - index);
buffer[index] = aChar;
length++;
string = null;
return;
}
oldBuffer = buffer;
buffer = new char[buffer.length + 20];
if (index > 0) {
System.arraycopy(oldBuffer, 0, buffer, 0, index);
}
if (index < length) {
System.arraycopy(oldBuffer, index, buffer, index + 1,
length - index);
}
buffer[index] = aChar;
length++;
string = null;
}
/**
Inserts <b>aString</b> at <b>index</b>. If <b>index</b> is
greater than or equal to the number of characters within the buffer,
appends <b>aString</b>.
@param aString string to insert into the FastStringBuffer
@param index location to insert string.
@exception StringIndexOutOfBoundsException If the index is invalid.
@aribaapi documented
*/
public void insert (String aString, int index)
{
Assert.that(aString != null,
"May not use null strings with FastStringBuffer");
char oldBuffer[];
int stringLength;
if (index < 0) {
throw new StringIndexOutOfBoundsException(index);
}
else if (index > length) {
append(aString);
return;
}
else if (aString.equals("")) {
return;
}
stringLength = aString.length();
if (length + stringLength < buffer.length) {
System.arraycopy(buffer, index, buffer, index + stringLength,
length - index);
aString.getChars(0, stringLength, buffer, index);
length += stringLength;
string = null;
return;
}
oldBuffer = buffer;
buffer = new char[length + stringLength + 20];
if (index > 0) {
System.arraycopy(oldBuffer, 0, buffer, 0, index);
}
System.arraycopy(oldBuffer, index, buffer, index + stringLength,
length - index);
aString.getChars(0, stringLength, buffer, index);
length += stringLength;
string = null;
}
/**
Removes the character at <b>index</b>.
@param index location to remove the character from
@exception StringIndexOutOfBoundsException if the index is invalid.
@aribaapi documented
*/
public void removeCharAt (int index)
{
if (index < 0 || index >= length) {
throw new StringIndexOutOfBoundsException(index);
}
if (index + 1 == length) {
length--;
string = null;
return;
}
System.arraycopy(buffer, index + 1, buffer, index, length - (index+1));
length--;
string = null;
}
/**
Truncates the FastStringBuffer to <b>aLength</b> characters. If
<b>aLength</b> is invalid, does nothing.
@param aLength the length to truncate the FastStringBuffer to
@aribaapi documented
*/
public void truncateToLength (int aLength)
{
if (aLength < 0 || aLength > length) {
return;
}
length = aLength;
string = null;
}
/**
Returns the number of characters in the FastStringBuffer.
@return the current number of characters stored.
@aribaapi documented
*/
public int length ()
{
return length;
}
/**
This removes the characters between <b>fromIndex</b> and
<b>toIndex</b> (non inclusive.)
if fromIndex <= toIndex the method does nothing.
@param fromIndex index to start removing characters from
@param toIndex index to end removing characters
@exception StringIndexOutOfBoundsException if the fromIndex
and toIndex are outside the range of the string.
@aribaapi documented
*/
public void moveChars (int fromIndex, int toIndex)
{
if (fromIndex <= toIndex) {
return;
}
else if (fromIndex < 0 || fromIndex >= length) {
throw new StringIndexOutOfBoundsException(fromIndex);
}
else if (toIndex < 0 || toIndex >= length) {
throw new StringIndexOutOfBoundsException(toIndex);
}
System.arraycopy(buffer, fromIndex, buffer, toIndex,
length - fromIndex);
length -= fromIndex - toIndex;
string = null;
}
/**
Returns the FastStringBuffer's char array, for situation where it is
needed. For example, you can draw the FastStringBuffer's contents
by passing the array to the Graphic's <b>drawString()</b> method that
takes a char array, rather than first convert the StringBuffer to a
String. You should never modify this array yourself.
@return returns the character array that is used for internal
storage. There may be garbage characters after the length.
@aribaapi documented
*/
public char[] charArray ()
{
return buffer;
}
}