mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
optimize ImmutableText memory usage
* nodes shouldn't contain null fields * chunk into small fragments only when document modifications are made
This commit is contained in:
@@ -29,10 +29,6 @@ package com.intellij.util.text;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* A pruned and optimized version of javolution.text.Text
|
||||
*
|
||||
@@ -40,15 +36,6 @@ import java.io.Writer;
|
||||
* fast {@link #concat concatenation}, {@link #insert insertion} and
|
||||
* {@link #delete deletion} capabilities (O[Log(n)]) instead of
|
||||
* O[n] for StringBuffer/StringBuilder).</p>
|
||||
* <p> This class has the same methods as
|
||||
* <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/lang/String.html">
|
||||
* Java String</a> with the following benefits:<ul>
|
||||
* <li> No need for an intermediate
|
||||
* {@link StringBuffer}/{@link StringBuilder} in order to manipulate
|
||||
* textual documents (insertion, deletion or concatenation).</li>
|
||||
* <li> More flexible as they allows for search and comparison with any
|
||||
* <code>java.lang.String</code> or <code>CharSequence</code>.</li>
|
||||
* </ul></p>
|
||||
*
|
||||
* <p><i> Implementation Note: To avoid expensive copy operations ,
|
||||
* {@link ImmutableText} instances are broken down into smaller immutable
|
||||
@@ -76,41 +63,10 @@ public final class ImmutableText extends ImmutableCharSequence implements CharAr
|
||||
*/
|
||||
private static final int BLOCK_MASK = ~(BLOCK_SIZE - 1);
|
||||
|
||||
/**
|
||||
* Holds the primitive text factory.
|
||||
*/
|
||||
private final Node myNode;
|
||||
|
||||
/**
|
||||
* Holds the raw data (primitive) or <code>null</code> (composite).
|
||||
*/
|
||||
private final char[] _data;
|
||||
|
||||
/**
|
||||
* Holds the total number of characters.
|
||||
*/
|
||||
private final int _count;
|
||||
|
||||
/**
|
||||
* Holds the head block of character (composite).
|
||||
*/
|
||||
private final ImmutableText _head;
|
||||
|
||||
/**
|
||||
* Holds the tail block of character (composite).
|
||||
*/
|
||||
private final ImmutableText _tail;
|
||||
|
||||
private ImmutableText(char[] data) {
|
||||
_data = data;
|
||||
_count = data.length;
|
||||
_head = _tail = null;
|
||||
}
|
||||
|
||||
private ImmutableText(ImmutableText head, ImmutableText tail) {
|
||||
_count = head._count + tail._count;
|
||||
_data = null;
|
||||
_head = head;
|
||||
_tail = tail;
|
||||
private ImmutableText(Node node) {
|
||||
myNode = node;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,18 +82,7 @@ public final class ImmutableText extends ImmutableCharSequence implements CharAr
|
||||
}
|
||||
|
||||
private static ImmutableText valueOf(@NotNull CharSequence str) {
|
||||
return valueOf(str, 0, str.length());
|
||||
}
|
||||
|
||||
private static ImmutableText valueOf(@NotNull CharSequence str, int start, int end) {
|
||||
int length = end - start;
|
||||
if (length <= BLOCK_SIZE) {
|
||||
return new ImmutableText(CharArrayUtil.fromSequence(str, start, end));
|
||||
}
|
||||
|
||||
// Splits on a block boundary.
|
||||
int half = ((length + BLOCK_SIZE) >> 1) & BLOCK_MASK;
|
||||
return new ImmutableText(valueOf(str, start, start + half), valueOf(str, start + half, end));
|
||||
return new ImmutableText(new LeafNode(CharArrayUtil.fromSequence(str, 0, str.length())));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,33 +93,27 @@ public final class ImmutableText extends ImmutableCharSequence implements CharAr
|
||||
* @return the corresponding instance.
|
||||
*/
|
||||
public static ImmutableText valueOf(@NotNull char[] chars) {
|
||||
return valueOf(chars, 0, chars.length);
|
||||
return new ImmutableText(new LeafNode(chars));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text that contains the characters from the specified
|
||||
* subarray of characters.
|
||||
*
|
||||
* @param chars the source of the characters.
|
||||
* @param offset the index of the first character in the data source.
|
||||
* @param length the length of the text returned.
|
||||
* @return the corresponding instance.
|
||||
* @throws IndexOutOfBoundsException if <code>(offset < 0) ||
|
||||
* (length < 0) || ((offset + length) > chars.length)</code>
|
||||
*/
|
||||
public static ImmutableText valueOf(@NotNull char[] chars, int offset, int length) {
|
||||
if ((offset < 0) || (length < 0) || ((offset + length) > chars.length))
|
||||
throw new IndexOutOfBoundsException();
|
||||
private ImmutableText ensureChunked() {
|
||||
if (length() >= BLOCK_SIZE && myNode instanceof LeafNode) {
|
||||
return new ImmutableText(nodeOf(((LeafNode)myNode)._data, 0, length()));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private static Node nodeOf(@NotNull char[] chars, int offset, int length) {
|
||||
if (length <= BLOCK_SIZE) {
|
||||
if (offset == 0 && length == chars.length) {
|
||||
return new ImmutableText(chars);
|
||||
return new LeafNode(chars);
|
||||
}
|
||||
char[] subArray = new char[length];
|
||||
System.arraycopy(chars, offset, subArray, 0, length);
|
||||
return new ImmutableText(subArray);
|
||||
return new LeafNode(subArray);
|
||||
} else { // Splits on a block boundary.
|
||||
int half = ((length + BLOCK_SIZE) >> 1) & BLOCK_MASK;
|
||||
return new ImmutableText(valueOf(chars, offset, half), valueOf(chars, offset + half, length - half));
|
||||
return new CompositeNode(nodeOf(chars, offset, half), nodeOf(chars, offset + half, length - half));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,45 +142,7 @@ public final class ImmutableText extends ImmutableCharSequence implements CharAr
|
||||
* @return the number of characters (16-bits Unicode) composing this text.
|
||||
*/
|
||||
public int length() {
|
||||
return _count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the concatenation of this text and the textual
|
||||
* representation of the specified object.
|
||||
*
|
||||
* @param obj the object whose textual representation is concatenated.
|
||||
* @return <code>this.concat(Text.valueOf(obj))</code>
|
||||
*/
|
||||
public ImmutableText plus(Object obj) {
|
||||
return this.concat(valueOf(obj));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the concatenation of this text and the specified
|
||||
* <code>String</code> (optimization).
|
||||
*
|
||||
* @param str the string whose characters are concatenated.
|
||||
* @return <code>this.concat(Text.valueOf(obj))</code>
|
||||
*/
|
||||
public ImmutableText plus(String str) {
|
||||
|
||||
ImmutableText merge = this.append(str);
|
||||
return merge != null ? merge : concat(valueOf(str));
|
||||
}
|
||||
|
||||
private ImmutableText append(String str) { // Try to append, returns null if cannot.
|
||||
int length = str.length();
|
||||
if (_data == null) {
|
||||
ImmutableText merge = _tail.append(str);
|
||||
return merge != null ? new ImmutableText(_head, merge) : null;
|
||||
} else { // Primitive.
|
||||
if (_count + length > BLOCK_SIZE) return null; // Cannot merge.
|
||||
char[] chars = new char[_count + length];
|
||||
System.arraycopy(_data, 0, chars, 0, _count);
|
||||
str.getChars(0, length, chars, _count);
|
||||
return new ImmutableText(chars);
|
||||
}
|
||||
return myNode.nodeLength();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,66 +155,7 @@ public final class ImmutableText extends ImmutableCharSequence implements CharAr
|
||||
* @return <code>this + that</code>
|
||||
*/
|
||||
public ImmutableText concat(ImmutableText that) {
|
||||
if (that.length() == 0) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// All Text instances are maintained balanced:
|
||||
// (head < tail * 2) & (tail < head * 2)
|
||||
|
||||
final int length = this._count + that._count;
|
||||
if (length <= BLOCK_SIZE) { // Merges to primitive.
|
||||
char[] chars = new char[length];
|
||||
this.getChars(0, this._count, chars, 0);
|
||||
that.getChars(0, that._count, chars, this._count);
|
||||
return new ImmutableText(chars);
|
||||
} else { // Returns a composite.
|
||||
ImmutableText head = this;
|
||||
ImmutableText tail = that;
|
||||
|
||||
if (((head._count << 1) < tail._count) && (tail._data == null)) { // tail is composite
|
||||
// head too small, returns (head + tail/2) + (tail/2)
|
||||
if (tail._head._count > tail._tail._count) {
|
||||
// Rotates to concatenate with smaller part.
|
||||
tail = tail.rightRotation();
|
||||
}
|
||||
head = head.concat(tail._head);
|
||||
tail = tail._tail;
|
||||
|
||||
} else if (((tail._count << 1) < head._count)
|
||||
&& (head._data == null)) { // head is composite.
|
||||
// tail too small, returns (head/2) + (head/2 concat tail)
|
||||
if (head._tail._count > head._head._count) {
|
||||
// Rotates to concatenate with smaller part.
|
||||
head = head.leftRotation();
|
||||
}
|
||||
tail = head._tail.concat(tail);
|
||||
head = head._head;
|
||||
}
|
||||
return new ImmutableText(head, tail);
|
||||
}
|
||||
}
|
||||
|
||||
private ImmutableText rightRotation() {
|
||||
// See: http://en.wikipedia.org/wiki/Tree_rotation
|
||||
ImmutableText P = this._head;
|
||||
if (P._data != null)
|
||||
return this; // Head not a composite, cannot rotate.
|
||||
ImmutableText A = P._head;
|
||||
ImmutableText B = P._tail;
|
||||
ImmutableText C = this._tail;
|
||||
return new ImmutableText(A, new ImmutableText(B, C));
|
||||
}
|
||||
|
||||
private ImmutableText leftRotation() {
|
||||
// See: http://en.wikipedia.org/wiki/Tree_rotation
|
||||
ImmutableText Q = this._tail;
|
||||
if (Q._data != null)
|
||||
return this; // Tail not a composite, cannot rotate.
|
||||
ImmutableText B = Q._head;
|
||||
ImmutableText C = Q._tail;
|
||||
ImmutableText A = this._head;
|
||||
return new ImmutableText(new ImmutableText(A, B), C);
|
||||
return that.length() == 0 ? this : new ImmutableText(ensureChunked().myNode.concatNodes(that.ensureChunked().myNode));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -324,7 +166,7 @@ public final class ImmutableText extends ImmutableCharSequence implements CharAr
|
||||
* @throws IndexOutOfBoundsException if <code>(start < 0) ||
|
||||
* (start > this.length())</code>
|
||||
*/
|
||||
public ImmutableText subtext(int start) {
|
||||
private ImmutableText subtext(int start) {
|
||||
return subtext(start, length());
|
||||
}
|
||||
|
||||
@@ -359,23 +201,7 @@ public final class ImmutableText extends ImmutableCharSequence implements CharAr
|
||||
if (start == end) return this;
|
||||
if (start > end)
|
||||
throw new IndexOutOfBoundsException();
|
||||
return subtext(0, start).concat(subtext(end));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces each character sequence of this text that matches the specified
|
||||
* target sequence with the specified replacement sequence.
|
||||
*
|
||||
* @param target the character sequence to be replaced.
|
||||
* @param replacement the replacement sequence.
|
||||
* @return the resulting text.
|
||||
*/
|
||||
public ImmutableText replace(CharSequence target, CharSequence replacement) {
|
||||
int i = indexOf(target);
|
||||
return (i < 0) ? this : // No target sequence found.
|
||||
subtext(0, i).concat(valueOf(replacement)).concat(
|
||||
subtext(i + target.length()).replace(target,
|
||||
replacement));
|
||||
return ensureChunked().subtext(0, start).concat(subtext(end));
|
||||
}
|
||||
|
||||
public CharSequence subSequence(final int start, final int end) {
|
||||
@@ -383,211 +209,16 @@ public final class ImmutableText extends ImmutableCharSequence implements CharAr
|
||||
return new CharSequenceSubSequence(this, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index within this text of the first occurrence
|
||||
* of the specified character sequence searching forward.
|
||||
*
|
||||
* @param csq a character sequence.
|
||||
* @return the index of the first character of the character sequence found;
|
||||
* or <code>-1</code> if the character sequence is not found.
|
||||
*/
|
||||
public int indexOf(CharSequence csq) {
|
||||
return indexOf(csq, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index within this text of the first occurrence
|
||||
* of the specified characters sequence searching forward from
|
||||
* the specified index.
|
||||
*
|
||||
* @param csq a character sequence.
|
||||
* @param fromIndex the index to start the search from.
|
||||
* @return the index in the range
|
||||
* <code>[fromIndex, length() - csq.length()]</code>
|
||||
* or <code>-1</code> if the character sequence is not found.
|
||||
*/
|
||||
public int indexOf(CharSequence csq, int fromIndex) {
|
||||
|
||||
// Limit cases.
|
||||
final int csqLength = csq.length();
|
||||
final int min = Math.max(0, fromIndex);
|
||||
final int max = _count - csqLength;
|
||||
if (csqLength == 0) {
|
||||
return (min > max) ? -1 : min;
|
||||
}
|
||||
|
||||
// Searches for csq.
|
||||
final char c = csq.charAt(0);
|
||||
for (int i = indexOf(c, min); (i >= 0) && (i <= max); i = indexOf(c,
|
||||
++i)) {
|
||||
boolean match = true;
|
||||
for (int j = 1; j < csqLength; j++) {
|
||||
if (this.charAt(i + j) != csq.charAt(j)) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index within this text of the last occurrence of
|
||||
* the specified characters sequence searching backward.
|
||||
*
|
||||
* @param csq a character sequence.
|
||||
* @return the index of the first character of the character sequence found;
|
||||
* or <code>-1</code> if the character sequence is not found.
|
||||
*/
|
||||
public int lastIndexOf(CharSequence csq) {
|
||||
return lastIndexOf(csq, _count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index within this text of the last occurrence of
|
||||
* the specified character sequence searching backward from the specified
|
||||
* index.
|
||||
*
|
||||
* @param csq a character sequence.
|
||||
* @param fromIndex the index to start the backward search from.
|
||||
* @return the index in the range <code>[0, fromIndex]</code> or
|
||||
* <code>-1</code> if the character sequence is not found.
|
||||
*/
|
||||
public int lastIndexOf(CharSequence csq, int fromIndex) {
|
||||
|
||||
// Limit cases.
|
||||
final int csqLength = csq.length();
|
||||
final int min = 0;
|
||||
final int max = Math.min(fromIndex, _count - csqLength);
|
||||
if (csqLength == 0) {
|
||||
return (min > max) ? -1 : max;
|
||||
}
|
||||
|
||||
// Searches for csq.
|
||||
final char c = csq.charAt(0);
|
||||
for (int i = lastIndexOf(c, max); (i >= 0); i = lastIndexOf(c, --i)) {
|
||||
boolean match = true;
|
||||
for (int j = 1; j < csqLength; j++) {
|
||||
if (this.charAt(i + j) != csq.charAt(j)) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this text starts with the specified prefix.
|
||||
*
|
||||
* @param prefix the prefix.
|
||||
* @return <code>true</code> if the character sequence represented by the
|
||||
* argument is a prefix of the character sequence represented by
|
||||
* this text; <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean startsWith(CharSequence prefix) {
|
||||
return startsWith(prefix, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this text ends with the specified suffix.
|
||||
*
|
||||
* @param suffix the suffix.
|
||||
* @return <code>true</code> if the character sequence represented by the
|
||||
* argument is a suffix of the character sequence represented by
|
||||
* this text; <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean endsWith(CharSequence suffix) {
|
||||
return startsWith(suffix, length() - suffix.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this text starts with the specified prefix
|
||||
* at the specified index.
|
||||
*
|
||||
* @param prefix the prefix.
|
||||
* @param index the index of the prefix location in this string.
|
||||
* @return <code>this.substring(index).startsWith(prefix)</code>
|
||||
*/
|
||||
public boolean startsWith(CharSequence prefix, int index) {
|
||||
final int prefixLength = prefix.length();
|
||||
if ((index >= 0) && (index <= (this.length() - prefixLength))) {
|
||||
for (int i = 0, j = index; i < prefixLength;) {
|
||||
if (prefix.charAt(i++) != this.charAt(j++)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this text, with leading and trailing
|
||||
* whitespace omitted.
|
||||
*
|
||||
* @return a copy of this text with leading and trailing white
|
||||
* space removed, or this text if it has no leading or
|
||||
* trailing white space.
|
||||
*/
|
||||
public ImmutableText trim() {
|
||||
int first = 0; // First character index.
|
||||
int last = length() - 1; // Last character index.
|
||||
while ((first <= last) && (charAt(first) <= ' ')) {
|
||||
first++;
|
||||
}
|
||||
while ((last >= first) && (charAt(last) <= ' ')) {
|
||||
last--;
|
||||
}
|
||||
return subtext(first, last + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this text has the same character content as the specified
|
||||
* character sequence.
|
||||
*
|
||||
* @param csq the character sequence to compare with.
|
||||
* @return <code>true</code> if the specified character sequence has the
|
||||
* same character content as this text; <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean contentEquals(CharSequence csq) {
|
||||
if (csq.length() != _count)
|
||||
return false;
|
||||
for (int i = 0; i < _count;) {
|
||||
if (this.charAt(i) != csq.charAt(i++))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this text against the specified object for equality.
|
||||
* Returns <code>true</code> if the specified object is a text having
|
||||
* the same character sequence as this text.
|
||||
* For generic comparison with any character sequence the
|
||||
* {@link #contentEquals(CharSequence)} should be used.
|
||||
*
|
||||
* @param obj the object to compare with or <code>null</code>.
|
||||
* @return <code>true</code> if that is a text with the same character
|
||||
* sequence as this text; <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (!(obj instanceof ImmutableText))
|
||||
return false;
|
||||
final ImmutableText that = (ImmutableText) obj;
|
||||
if (this._count != that._count)
|
||||
int len = this.length();
|
||||
if (len != that.length())
|
||||
return false;
|
||||
for (int i = 0; i < _count;) {
|
||||
for (int i = 0; i < len;) {
|
||||
if (this.charAt(i) != that.charAt(i++))
|
||||
return false;
|
||||
}
|
||||
@@ -608,165 +239,49 @@ public final class ImmutableText extends ImmutableCharSequence implements CharAr
|
||||
return h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the current statistics on this text tree structure.
|
||||
*
|
||||
* @param out the stream to use for output (e.g. <code>System.out</code>)
|
||||
*/
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public void printStatistics(PrintStream out) {
|
||||
int length = this.length();
|
||||
int leaves = getNbrOfLeaves();
|
||||
out.print("LENGTH: " + length());
|
||||
out.print(", MAX DEPTH: " + getDepth());
|
||||
out.print(", NBR OF BRANCHES: " + getNbrOfBranches());
|
||||
out.print(", NBR OF LEAVES: " + leaves);
|
||||
out.print(", AVG LEAVE LENGTH: " + (length + (leaves >> 1)) / leaves);
|
||||
out.println();
|
||||
}
|
||||
private int getDepth() {
|
||||
if (_data != null) // Primitive.
|
||||
return 0;
|
||||
return Math.max(_head.getDepth(), _tail.getDepth()) + 1;
|
||||
}
|
||||
private int getNbrOfBranches() {
|
||||
return (_data == null) ?
|
||||
_head.getNbrOfBranches() + _tail.getNbrOfBranches() + 1 : 0;
|
||||
}
|
||||
private int getNbrOfLeaves() {
|
||||
return (_data == null) ?
|
||||
_head.getNbrOfLeaves() + _tail.getNbrOfLeaves() : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints out this text to the specified writer.
|
||||
*
|
||||
* @param writer the destination writer.
|
||||
*/
|
||||
public void print(Writer writer) throws IOException {
|
||||
if (_data != null) { // Primitive
|
||||
writer.write(_data, 0, _count);
|
||||
} else { // Composite.
|
||||
_head.print(writer);
|
||||
_tail.print(writer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints out this text to the specified writer and then terminates
|
||||
* the line.
|
||||
*
|
||||
* @param writer the destination writer.
|
||||
*/
|
||||
public void println(Writer writer) throws IOException {
|
||||
print(writer);
|
||||
writer.write('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the character at the specified index.
|
||||
*
|
||||
* @param index the index of the character.
|
||||
* @return the character at the specified index.
|
||||
* @throws IndexOutOfBoundsException if <code>(index < 0) ||
|
||||
* (index >= this.length())</code>
|
||||
*/
|
||||
public char charAt(int index) {
|
||||
if (myNode instanceof LeafNode) {
|
||||
return ((LeafNode)myNode)._data[index];
|
||||
}
|
||||
|
||||
InnerLeaf leaf = myLastLeaf;
|
||||
if (leaf == null || index < leaf.offset || index >= leaf.offset + leaf.leafText._count) {
|
||||
if (leaf == null || index < leaf.offset || index >= leaf.offset + leaf.leafNode.nodeLength()) {
|
||||
myLastLeaf = leaf = findLeaf(index, 0);
|
||||
}
|
||||
return leaf.leafText._data[index - leaf.offset];
|
||||
return leaf.leafNode._data[index - leaf.offset];
|
||||
}
|
||||
private volatile InnerLeaf myLastLeaf;
|
||||
|
||||
private InnerLeaf findLeaf(int index, int offset) {
|
||||
ImmutableText node = this;
|
||||
Node node = myNode;
|
||||
while (true) {
|
||||
if (index >= node._count) {
|
||||
if (index >= node.nodeLength()) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
if (node._data != null) {
|
||||
return new InnerLeaf(node, offset);
|
||||
if (node instanceof LeafNode) {
|
||||
return new InnerLeaf((LeafNode)node, offset);
|
||||
}
|
||||
if (index < node._head._count) {
|
||||
node = node._head;
|
||||
CompositeNode composite = (CompositeNode)node;
|
||||
if (index < composite._head.nodeLength()) {
|
||||
node = composite._head;
|
||||
} else {
|
||||
offset += node._head._count;
|
||||
index -= node._head._count;
|
||||
node = node._tail;
|
||||
offset += composite._head.nodeLength();
|
||||
index -= composite._head.nodeLength();
|
||||
node = composite._tail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class InnerLeaf {
|
||||
final ImmutableText leafText;
|
||||
final LeafNode leafNode;
|
||||
final int offset;
|
||||
|
||||
private InnerLeaf(ImmutableText leafText, int offset) {
|
||||
this.leafText = leafText;
|
||||
private InnerLeaf(LeafNode leafNode, int offset) {
|
||||
this.leafNode = leafNode;
|
||||
this.offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index within this text of the first occurrence of the
|
||||
* specified character, starting the search at the specified index.
|
||||
*
|
||||
* @param c the character to search for.
|
||||
* @param fromIndex the index to start the search from.
|
||||
* @return the index of the first occurrence of the character in this text
|
||||
* that is greater than or equal to <code>fromIndex</code>,
|
||||
* or <code>-1</code> if the character does not occur.
|
||||
*/
|
||||
public int indexOf(char c, int fromIndex) {
|
||||
if (_data != null) { // Primitive.
|
||||
for (int i = Math.max(fromIndex, 0); i < _count; i++) {
|
||||
if (_data[i] == c)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
} else { // Composite.
|
||||
final int cesure = _head._count;
|
||||
if (fromIndex < cesure) {
|
||||
final int headIndex = _head.indexOf(c, fromIndex);
|
||||
if (headIndex >= 0)
|
||||
return headIndex; // Found in head.
|
||||
}
|
||||
final int tailIndex = _tail.indexOf(c, fromIndex - cesure);
|
||||
return (tailIndex >= 0) ? tailIndex + cesure : -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index within this text of the first occurrence of the
|
||||
* specified character, searching backward and starting at the specified
|
||||
* index.
|
||||
*
|
||||
* @param c the character to search for.
|
||||
* @param fromIndex the index to start the search backward from.
|
||||
* @return the index of the first occurrence of the character in this text
|
||||
* that is less than or equal to <code>fromIndex</code>,
|
||||
* or <code>-1</code> if the character does not occur.
|
||||
*/
|
||||
public int lastIndexOf(char c, int fromIndex) {
|
||||
if (_data != null) { // Primitive.
|
||||
for (int i = Math.min(fromIndex, _count - 1); i >= 0; i--) {
|
||||
if (_data[i] == c)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
} else { // Composite.
|
||||
final int cesure = _head._count;
|
||||
if (fromIndex >= cesure) {
|
||||
final int tailIndex = _tail.lastIndexOf(c, fromIndex - cesure);
|
||||
if (tailIndex >= 0)
|
||||
return tailIndex + cesure; // Found in tail.
|
||||
}
|
||||
return _head.lastIndexOf(c, fromIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a portion of this text.
|
||||
*
|
||||
@@ -777,30 +292,15 @@ public final class ImmutableText extends ImmutableCharSequence implements CharAr
|
||||
* @throws IndexOutOfBoundsException if <code>(start < 0) || (end < 0) ||
|
||||
* (start > end) || (end > this.length())</code>
|
||||
*/
|
||||
public ImmutableText subtext(int start, int end) {
|
||||
if (_data != null) { // Primitive.
|
||||
if ((start < 0) || (start > end) || (end > _count))
|
||||
throw new IndexOutOfBoundsException();
|
||||
if ((start == 0) && (end == _count))
|
||||
return this;
|
||||
if (start == end)
|
||||
return EMPTY;
|
||||
int length = end - start;
|
||||
char[] chars = new char[length];
|
||||
System.arraycopy(_data, start, chars, 0, length);
|
||||
return new ImmutableText(chars);
|
||||
} else { // Composite.
|
||||
final int cesure = _head._count;
|
||||
if (end <= cesure)
|
||||
return _head.subtext(start, end);
|
||||
if (start >= cesure)
|
||||
return _tail.subtext(start - cesure, end - cesure);
|
||||
if ((start == 0) && (end == _count))
|
||||
return this;
|
||||
// Overlaps head and tail.
|
||||
return _head.subtext(start, cesure).concat(
|
||||
_tail.subtext(0, end - cesure));
|
||||
}
|
||||
private ImmutableText subtext(int start, int end) {
|
||||
if ((start < 0) || (start > end) || (end > length()))
|
||||
throw new IndexOutOfBoundsException();
|
||||
if ((start == 0) && (end == length()))
|
||||
return this;
|
||||
if (start == end)
|
||||
return EMPTY;
|
||||
|
||||
return new ImmutableText(myNode.subNode(start, end));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -815,21 +315,7 @@ public final class ImmutableText extends ImmutableCharSequence implements CharAr
|
||||
* (start > end) || (end > this.length())</code>
|
||||
*/
|
||||
public void getChars(int start, int end, @NotNull char[] dest, int destPos) {
|
||||
if (_data != null) { // Primitive.
|
||||
if ((start < 0) || (end > _count) || (start > end))
|
||||
throw new IndexOutOfBoundsException();
|
||||
System.arraycopy(_data, start, dest, destPos, end - start);
|
||||
} else { // Composite.
|
||||
final int cesure = _head._count;
|
||||
if (end <= cesure) {
|
||||
_head.getChars(start, end, dest, destPos);
|
||||
} else if (start >= cesure) {
|
||||
_tail.getChars(start - cesure, end - cesure, dest, destPos);
|
||||
} else { // Overlaps head and tail.
|
||||
_head.getChars(start, cesure, dest, destPos);
|
||||
_tail.getChars(0, end - cesure, dest, destPos + cesure - start);
|
||||
}
|
||||
}
|
||||
myNode.getChars(start, end, dest, destPos);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -839,13 +325,150 @@ public final class ImmutableText extends ImmutableCharSequence implements CharAr
|
||||
*/
|
||||
@NotNull
|
||||
public String toString() {
|
||||
if (_data != null) { // Primitive.
|
||||
return new String(_data, 0, _count);
|
||||
if (myNode instanceof LeafNode) { // Primitive.
|
||||
return new String(((LeafNode)myNode)._data, 0, length());
|
||||
} else { // Composite.
|
||||
char[] data = new char[_count];
|
||||
this.getChars(0, _count, data, 0);
|
||||
return new String(data, 0, _count);
|
||||
int len = length();
|
||||
char[] data = new char[len];
|
||||
this.getChars(0, len, data, 0);
|
||||
return new String(data, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class Node {
|
||||
|
||||
abstract int nodeLength();
|
||||
|
||||
Node concatNodes(Node that) {
|
||||
// All Text instances are maintained balanced:
|
||||
// (head < tail * 2) & (tail < head * 2)
|
||||
|
||||
final int length = this.nodeLength() + that.nodeLength();
|
||||
if (length <= BLOCK_SIZE) { // Merges to primitive.
|
||||
char[] chars = new char[length];
|
||||
this.getChars(0, this.nodeLength(), chars, 0);
|
||||
that.getChars(0, that.nodeLength(), chars, this.nodeLength());
|
||||
return new LeafNode(chars);
|
||||
} else { // Returns a composite.
|
||||
Node head = this;
|
||||
Node tail = that;
|
||||
|
||||
if (((head.nodeLength() << 1) < tail.nodeLength()) && tail instanceof CompositeNode) {
|
||||
// head too small, returns (head + tail/2) + (tail/2)
|
||||
if (((CompositeNode)tail)._head.nodeLength() > ((CompositeNode)tail)._tail.nodeLength()) {
|
||||
// Rotates to concatenate with smaller part.
|
||||
tail = ((CompositeNode)tail).rightRotation();
|
||||
}
|
||||
head = head.concatNodes(((CompositeNode)tail)._head);
|
||||
tail = ((CompositeNode)tail)._tail;
|
||||
|
||||
} else if (((tail.nodeLength() << 1) < head.nodeLength()) && head instanceof CompositeNode) {
|
||||
// tail too small, returns (head/2) + (head/2 concat tail)
|
||||
if (((CompositeNode)head)._tail.nodeLength() > ((CompositeNode)head)._head.nodeLength()) {
|
||||
// Rotates to concatenate with smaller part.
|
||||
head = ((CompositeNode)head).leftRotation();
|
||||
}
|
||||
tail = ((CompositeNode)head)._tail.concatNodes(tail);
|
||||
head = ((CompositeNode)head)._head;
|
||||
}
|
||||
return new CompositeNode(head, tail);
|
||||
}
|
||||
}
|
||||
|
||||
abstract void getChars(int start, int end, @NotNull char[] dest, int destPos);
|
||||
|
||||
abstract Node subNode(int start, int end);
|
||||
|
||||
}
|
||||
|
||||
private static class LeafNode extends Node {
|
||||
final char[] _data;
|
||||
|
||||
LeafNode(char[] _data) {
|
||||
this._data = _data;
|
||||
}
|
||||
|
||||
@Override
|
||||
int nodeLength() {
|
||||
return _data.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
void getChars(int start, int end, @NotNull char[] dest, int destPos) {
|
||||
if ((start < 0) || (end > nodeLength()) || (start > end))
|
||||
throw new IndexOutOfBoundsException();
|
||||
System.arraycopy(_data, start, dest, destPos, end - start);
|
||||
}
|
||||
|
||||
@Override
|
||||
Node subNode(int start, int end) {
|
||||
int length = end - start;
|
||||
char[] chars = new char[length];
|
||||
System.arraycopy(_data, start, chars, 0, length);
|
||||
return new LeafNode(chars);
|
||||
}
|
||||
}
|
||||
|
||||
private static class CompositeNode extends Node {
|
||||
final int _count;
|
||||
final Node _head;
|
||||
final Node _tail;
|
||||
|
||||
CompositeNode(Node _head, Node _tail) {
|
||||
_count = _head.nodeLength() + _tail.nodeLength();
|
||||
this._head = _head;
|
||||
this._tail = _tail;
|
||||
}
|
||||
|
||||
@Override
|
||||
int nodeLength() {
|
||||
return _count;
|
||||
}
|
||||
|
||||
Node rightRotation() {
|
||||
// See: http://en.wikipedia.org/wiki/Tree_rotation
|
||||
Node P = this._head;
|
||||
if (!(P instanceof CompositeNode))
|
||||
return this; // Head not a composite, cannot rotate.
|
||||
Node A = ((CompositeNode)P)._head;
|
||||
Node B = ((CompositeNode)P)._tail;
|
||||
Node C = this._tail;
|
||||
return new CompositeNode(A, new CompositeNode(B, C));
|
||||
}
|
||||
|
||||
Node leftRotation() {
|
||||
// See: http://en.wikipedia.org/wiki/Tree_rotation
|
||||
Node Q = this._tail;
|
||||
if (!(Q instanceof CompositeNode))
|
||||
return this; // Tail not a composite, cannot rotate.
|
||||
Node B = ((CompositeNode)Q)._head;
|
||||
Node C = ((CompositeNode)Q)._tail;
|
||||
Node A = this._head;
|
||||
return new CompositeNode(new CompositeNode(A, B), C);
|
||||
}
|
||||
|
||||
@Override
|
||||
void getChars(int start, int end, @NotNull char[] dest, int destPos) {
|
||||
final int cesure = _head.nodeLength();
|
||||
if (end <= cesure) {
|
||||
_head.getChars(start, end, dest, destPos);
|
||||
} else if (start >= cesure) {
|
||||
_tail.getChars(start - cesure, end - cesure, dest, destPos);
|
||||
} else { // Overlaps head and tail.
|
||||
_head.getChars(start, cesure, dest, destPos);
|
||||
_tail.getChars(0, end - cesure, dest, destPos + cesure - start);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Node subNode(int start, int end) {
|
||||
final int cesure = _head.nodeLength();
|
||||
if (end <= cesure)
|
||||
return _head.subNode(start, end);
|
||||
if (start >= cesure)
|
||||
return _tail.subNode(start - cesure, end - cesure);
|
||||
// Overlaps head and tail.
|
||||
return _head.subNode(start, cesure).concatNodes(_tail.subNode(0, end - cesure));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user