/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.shared_core.string;
import java.util.Iterator;
import org.eclipse.core.runtime.Assert;
/**
* This is a custom string buffer optimized for append(), clear() and deleteLast().
*
* Basically it aims at being created once, being used for something, having clear() called and then reused
* (ultimately providing minimum allocation/garbage collection overhead for that use-case).
*
* append() is optimizing by doing less checks (so, exceptions thrown may be uglier on invalid operations
* and null is not checked for in the common case -- use appendObject if it may be null).
*
* clear() and deleteLast() only change the internal count and have almost zero overhead.
*
* Note that it's also not synchronized.
*
* @author Fabio
*/
public final class FastStringBuffer {
/**
* Holds the actual chars
*/
private char[] value;
/**
* Count for which chars are actually used
*/
private int count;
/**
* Initializes with a default initial size (128 chars)
*/
public FastStringBuffer() {
this(128);
}
/**
* An initial size can be specified (if available and given for no allocations it can be more efficient)
*/
public FastStringBuffer(int initialSize) {
this.value = new char[initialSize];
this.count = 0;
}
public FastStringBuffer(char[] internalBuffer) {
this.value = internalBuffer;
this.count = internalBuffer.length;
}
/**
* initializes from a string and the additional size for the buffer
*
* @param s string with the initial contents
* @param additionalSize the additional size for the buffer
*/
public FastStringBuffer(String s, int additionalSize) {
this.count = s.length();
if (additionalSize < 0) {
additionalSize = 0;
}
value = new char[this.count + additionalSize];
s.getChars(0, this.count, value, 0);
}
/**
* Appends a string to the buffer. Passing a null string will throw an exception.
*/
public FastStringBuffer append(String string) {
int strLen = string.length();
int newCount = count + strLen;
if (newCount > this.value.length) {
//was: resizeForMinimum(newCount);
int newCapacity = (value.length + 1) * 2;
if (newCount > newCapacity) {
newCapacity = newCount;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
string.getChars(0, strLen, value, this.count);
this.count = newCount;
return this;
}
/**
* Appends a string to the buffer. The buffer must have enough pre-allocated space for it to succeed.
*
* Passing a null string will throw an exception.
* Not having a pre-allocated internal array big enough will throw an exception.
*/
public FastStringBuffer appendNoResize(String string) {
int strLen = string.length();
string.getChars(0, strLen, value, this.count);
this.count = count + strLen;
return this;
}
/**
* Resizes the internal buffer to have at least the minimum capacity passed (but may be more)
* This code was inlined on all methods and it's kept here to use as a reference when needed.
*/
@SuppressWarnings("unused")
private void resizeForMinimum(int minimumCapacity) {
int newCapacity = (value.length + 1) * 2;
if (minimumCapacity > newCapacity) {
newCapacity = minimumCapacity;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
/**
* Appends an int to the buffer.
*/
public FastStringBuffer append(int n) {
//Inlined: append(String.valueOf(n));
String string = String.valueOf(n);
int strLen = string.length();
int newCount = count + strLen;
if (newCount > this.value.length) {
//was: resizeForMinimum(newCount);
int newCapacity = (value.length + 1) * 2;
if (newCount > newCapacity) {
newCapacity = newCount;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
string.getChars(0, strLen, value, this.count);
this.count = newCount;
return this;
}
/**
* Appends a char to the buffer.
*/
public FastStringBuffer append(char n) {
if (count + 1 > value.length) {
//was: resizeForMinimum(newCount);
int minimumCapacity = count + 1;
int newCapacity = (value.length + 1) * 2;
if (minimumCapacity > newCapacity) {
newCapacity = minimumCapacity;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
value[count] = n;
count++;
return this;
}
/**
* Appends a char to the buffer. Use when the size allocated is usually already ok (will only resize on exception
* instead of doing a size check all the time).
*/
public void appendResizeOnExc(char n) {
try {
value[count] = n;
} catch (Exception e) {
//System.out.println("Had to resize for minimun: "+(count+1));
//was: resizeForMinimum(newCount);
int minimumCapacity = count + 1;
int newCapacity = (value.length + 1) * 2;
if (minimumCapacity > newCapacity) {
newCapacity = minimumCapacity;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
value[count] = n;
}
count++;
}
/**
* Appends a long to the buffer.
*/
public FastStringBuffer append(long n) {
//Inlined: append(String.valueOf(b));
String string = String.valueOf(n);
int strLen = string.length();
int newCount = count + strLen;
if (newCount > this.value.length) {
//was: resizeForMinimum(newCount);
int newCapacity = (value.length + 1) * 2;
if (newCount > newCapacity) {
newCapacity = newCount;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
string.getChars(0, strLen, value, this.count);
this.count = newCount;
return this;
}
/**
* Appends a boolean to the buffer.
*/
public FastStringBuffer append(boolean b) {
//Inlined: append(String.valueOf(b));
String string = String.valueOf(b);
int strLen = string.length();
int newCount = count + strLen;
if (newCount > this.value.length) {
//was: resizeForMinimum(newCount);
int newCapacity = (value.length + 1) * 2;
if (newCount > newCapacity) {
newCapacity = newCount;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
string.getChars(0, strLen, value, this.count);
this.count = newCount;
return this;
}
/**
* Appends a double to the buffer.
*/
public FastStringBuffer append(double b) {
//Inlined: append(String.valueOf(b));
String string = String.valueOf(b);
int strLen = string.length();
int newCount = count + strLen;
if (newCount > this.value.length) {
int newCapacity = (value.length + 1) * 2;
if (newCount > newCapacity) {
newCapacity = newCount;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
string.getChars(0, strLen, value, this.count);
this.count = newCount;
return this;
}
/**
* Appends an array of chars to the buffer.
*/
public FastStringBuffer append(char[] chars) {
int newCount = count + chars.length;
if (newCount > value.length) {
//was: resizeForMinimum(newCount);
int newCapacity = (value.length + 1) * 2;
if (newCount > newCapacity) {
newCapacity = newCount;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
System.arraycopy(chars, 0, value, count, chars.length);
count = newCount;
return this;
}
/**
* Appends another buffer to this buffer.
*/
public FastStringBuffer append(FastStringBuffer other) {
//Inlined append(other.value, 0, other.count);
int len = other.count;
int newCount = count + len;
if (newCount > value.length) {
//was: resizeForMinimum(newCount);
int newCapacity = (value.length + 1) * 2;
if (newCount > newCapacity) {
newCapacity = newCount;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
System.arraycopy(other.value, 0, value, count, len);
count = newCount;
return this;
}
/**
* Appends an array of chars to this buffer, starting at the offset passed with the length determined.
*/
public FastStringBuffer append(char[] chars, int offset, int len) {
int newCount = count + len;
if (newCount > value.length) {
//was: resizeForMinimum(newCount);
int newCapacity = (value.length + 1) * 2;
if (newCount > newCapacity) {
newCapacity = newCount;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
System.arraycopy(chars, offset, value, count, len);
count = newCount;
return this;
}
/**
* Reverses the contents on this buffer
*/
public FastStringBuffer reverse() {
final int limit = count / 2;
for (int i = 0; i < limit; ++i) {
char c = value[i];
value[i] = value[count - i - 1];
value[count - i - 1] = c;
}
return this;
}
/**
* Clears this buffer.
*/
public FastStringBuffer clear() {
this.count = 0;
return this;
}
/**
* @return the length of this buffer
*/
public int length() {
return this.count;
}
public boolean isEmpty() {
return this.count == 0;
}
/**
* @return a new string with the contents of this buffer.
*/
@Override
public String toString() {
return new String(value, 0, count);
}
/**
* @return a new char array with the contents of this buffer.
*/
public char[] toCharArray() {
char[] v = new char[count];
System.arraycopy(value, 0, v, 0, count);
return v;
}
/**
* Erases the last char in this buffer
*/
public void deleteLast() {
if (this.count > 0) {
this.count--;
}
}
/**
* @param length
*/
public void deleteLastChars(int charsToDelete) {
this.count -= charsToDelete;
if (this.count < 0) {
this.count = 0;
}
}
/**
* @return the char given at a specific position of the buffer (no bounds check)
*/
public char charAt(int i) {
return this.value[i];
}
/**
* Inserts a string at a given position in the buffer.
*/
public FastStringBuffer insert(int offset, String str) {
int len = str.length();
int newCount = count + len;
if (newCount > value.length) {
//was: resizeForMinimum(newCount);
int newCapacity = (value.length + 1) * 2;
if (newCount > newCapacity) {
newCapacity = newCount;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
System.arraycopy(value, offset, value, offset + len, count - offset);
str.getChars(0, len, value, offset);
count = newCount;
return this;
}
/**
* Inserts a string at a given position in the buffer.
*/
public FastStringBuffer insert(int offset, FastStringBuffer str) {
int len = str.length();
int newCount = count + len;
if (newCount > value.length) {
int newCapacity = (value.length + 1) * 2;
if (newCount > newCapacity) {
newCapacity = newCount;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
System.arraycopy(value, offset, value, offset + len, count - offset);
System.arraycopy(str.value, 0, value, offset, str.count);
count = newCount;
return this;
}
/**
* Inserts a char at a given position in the buffer.
*/
public FastStringBuffer insert(int offset, char c) {
int newCount = count + 1;
if (newCount > value.length) {
//was: resizeForMinimum(newCount);
int newCapacity = (value.length + 1) * 2;
if (newCount > newCapacity) {
newCapacity = newCount;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
System.arraycopy(value, offset, value, offset + 1, count - offset);
value[offset] = c;
count = newCount;
return this;
}
/**
* Appends object.toString(). If null, "null" is appended.
*/
public FastStringBuffer appendObject(Object object) {
//Inlined: return append(object != null ? object.toString() : "null");
String string = object != null ? object.toString() : "null";
int strLen = string.length();
int newCount = count + strLen;
if (newCount > this.value.length) {
//was: resizeForMinimum(newCount);
int newCapacity = (value.length + 1) * 2;
if (newCount > newCapacity) {
newCapacity = newCount;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
string.getChars(0, strLen, value, this.count);
this.count = newCount;
return this;
}
/**
* Sets the new size of this buffer (warning: use with care: no validation is done of the len passed)
*/
public void setCount(int newLen) {
this.count = newLen;
}
public FastStringBuffer delete(int start, int end) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
end = count;
if (start > end)
throw new StringIndexOutOfBoundsException();
int len = end - start;
if (len > 0) {
System.arraycopy(value, start + len, value, start, count - end);
count -= len;
}
return this;
}
public FastStringBuffer replace(int start, int end, String str) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (start > count)
throw new StringIndexOutOfBoundsException("start > length()");
if (start > end)
throw new StringIndexOutOfBoundsException("start > end");
if (end > count)
end = count;
if (end > count)
end = count;
int len = str.length();
int newCount = count + len - (end - start);
if (newCount > value.length) {
//was: resizeForMinimum(newCount);
int newCapacity = (value.length + 1) * 2;
if (newCount > newCapacity) {
newCapacity = newCount;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
System.arraycopy(value, end, value, start + len, count - end);
str.getChars(0, len, value, start);
count = newCount;
return this;
}
/**
* Replaces all the occurrences of a string in this buffer for another string and returns the
* altered version.
*/
public FastStringBuffer replaceAll(String replace, String with) {
int replaceLen = replace.length();
int withLen = with.length();
Assert.isTrue(replaceLen > 0);
int matchPos = 0;
for (int i = 0; i < this.count; i++) {
if (this.value[i] == replace.charAt(matchPos)) {
matchPos++;
if (matchPos == replaceLen) {
this.replace(i - (replaceLen - 1), i + 1, with);
matchPos = 0;
i = i - (replaceLen - withLen);
}
continue;
} else {
matchPos = 0;
}
}
return this;
}
public FastStringBuffer deleteCharAt(int index) {
if ((index < 0) || (index >= count)) {
throw new StringIndexOutOfBoundsException(index);
}
System.arraycopy(value, index + 1, value, index, count - index - 1);
count--;
return this;
}
public int indexOf(char c) {
for (int i = 0; i < this.count; i++) {
if (c == this.value[i]) {
return i;
}
}
return -1;
}
public int indexOf(char c, int fromOffset) {
for (int i = fromOffset; i < this.count; i++) {
if (c == this.value[i]) {
return i;
}
}
return -1;
}
public char firstChar() {
return this.value[0];
}
public char lastChar() {
return this.value[this.count - 1];
}
public final static class BackwardCharIterator implements Iterable<Character> {
private int i;
private FastStringBuffer fastStringBuffer;
public BackwardCharIterator(FastStringBuffer fastStringBuffer) {
this.fastStringBuffer = fastStringBuffer;
i = fastStringBuffer.length();
}
public Iterator<Character> iterator() {
return new Iterator<Character>() {
public boolean hasNext() {
return i > 0;
}
public Character next() {
return fastStringBuffer.value[--i];
}
public void remove() {
throw new RuntimeException("Not implemented");
}
};
}
}
public BackwardCharIterator reverseIterator() {
return new BackwardCharIterator(this);
}
public void rightTrim() {
char c;
//while !isEmpty && lastChar == ' ' || \t.
while (this.count > 0 && ((c = this.value[this.count - 1]) == ' ' || c == '\t')) {
this.count--;
}
}
public char deleteFirst() {
char ret = this.value[0];
this.deleteCharAt(0);
return ret;
}
public FastStringBuffer appendN(final String val, int n) {
final int strLen = val.length();
int min = count + (n * strLen);
if (min > value.length) {
//was: resizeForMinimum(newCount);
int newCapacity = (value.length + 1) * 2;
if (min > newCapacity) {
newCapacity = min;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
while (n-- > 0) {
val.getChars(0, strLen, value, this.count);
this.count += strLen;
}
return this;
}
public FastStringBuffer appendN(char val, int n) {
if (count + n > value.length) {
//was: resizeForMinimum(newCount);
int minimumCapacity = count + n;
int newCapacity = (value.length + 1) * 2;
if (minimumCapacity > newCapacity) {
newCapacity = minimumCapacity;
}
char newValue[] = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}
while (n-- > 0) {
value[count] = val;
count++;
}
return this;
}
public boolean endsWith(String string) {
return startsWith(string, count - string.length());
}
public boolean startsWith(String prefix) {
return startsWith(prefix, 0);
}
public boolean startsWith(char c) {
if (this.count < 1) {
return false;
}
return this.value[0] == c;
}
public boolean endsWith(char c) {
if (this.count < 1) {
return false;
}
return this.value[this.count - 1] == c;
}
public boolean startsWith(String prefix, int offset) {
char ta[] = value;
int to = offset;
char pa[] = prefix.toCharArray();
int po = 0;
int pc = pa.length;
// Note: toffset might be near -1>>>1.
if ((offset < 0) || (offset > count - pc)) {
return false;
}
while (--pc >= 0) {
if (ta[to++] != pa[po++]) {
return false;
}
}
return true;
}
public void setCharAt(int i, char c) {
this.value[i] = c;
}
/**
* Careful: it doesn't check anything. Just sets the internal length.
*/
public void setLength(int i) {
this.count = i;
}
public byte[] getBytes() {
return this.toString().getBytes();
}
public int countNewLines() {
int lines = 0;
for (int i = 0; i < count; i++) {
char c = value[i];
switch (c) {
case '\n':
lines += 1;
break;
case '\r':
lines += 1;
if (i < count - 1) {
if (value[i + 1] == '\n') {
i++; //skip the \n after the \r
}
}
break;
}
}
return lines;
}
public FastStringBuffer insertN(int pos, char c, int repetitions) {
FastStringBuffer other = new FastStringBuffer(repetitions);
other.appendN(c, repetitions);
insert(pos, other);
return this;
}
public String getLastWord() {
FastStringBuffer lastWordBuf = new FastStringBuffer(this.count);
int i;
//skip whitespaces in the end
for (i = this.count - 1; i >= 0; i--) {
if (!Character.isWhitespace(this.value[i])) {
break;
}
}
//actual word
for (; i >= 0; i--) {
if (Character.isWhitespace(this.value[i])) {
break;
}
lastWordBuf.append(this.value[i]);
}
lastWordBuf.reverse();
return lastWordBuf.toString();
}
public void removeWhitespaces() {
int length = this.count;
char[] newVal = new char[length];
int j = 0;
for (int i = 0; i < length; i++) {
char ch = this.value[i];
if (!Character.isWhitespace(ch)) {
newVal[j] = ch;
j++;
}
}
this.count = j;
this.value = newVal;
}
public char[] getInternalCharsArray() {
return this.value;
}
}