diff --git a/platform/util/src/com/intellij/util/text/ImmutableText.java b/platform/util/src/com/intellij/util/text/ImmutableText.java index c739be1c254c..dbceae801c60 100644 --- a/platform/util/src/com/intellij/util/text/ImmutableText.java +++ b/platform/util/src/com/intellij/util/text/ImmutableText.java @@ -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).
- *This class has the same methods as - * - * Java String with the following benefits:
java.lang.String or CharSequence. 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 null (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 (offset < 0) ||
- * (length < 0) || ((offset + length) > chars.length)
- */
- 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 this.concat(Text.valueOf(obj))
- */
- public ImmutableText plus(Object obj) {
- return this.concat(valueOf(obj));
- }
-
- /**
- * Returns the concatenation of this text and the specified
- * String (optimization).
- *
- * @param str the string whose characters are concatenated.
- * @return this.concat(Text.valueOf(obj))
- */
- 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 this + that
*/
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 (start < 0) ||
* (start > this.length())
*/
- 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 -1 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
- * [fromIndex, length() - csq.length()]
- * or -1 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 -1 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 [0, fromIndex] or
- * -1 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 true if the character sequence represented by the
- * argument is a prefix of the character sequence represented by
- * this text; false otherwise.
- */
- public boolean startsWith(CharSequence prefix) {
- return startsWith(prefix, 0);
- }
-
- /**
- * Indicates if this text ends with the specified suffix.
- *
- * @param suffix the suffix.
- * @return true if the character sequence represented by the
- * argument is a suffix of the character sequence represented by
- * this text; false 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 this.substring(index).startsWith(prefix)
- */
- 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 true if the specified character sequence has the
- * same character content as this text; false 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 true 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 null.
- * @return true if that is a text with the same character
- * sequence as this text; false 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. System.out)
- */
- @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 (index < 0) ||
- * (index >= this.length())
- */
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 fromIndex,
- * or -1 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 fromIndex,
- * or -1 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 (start < 0) || (end < 0) ||
* (start > end) || (end > this.length())
*/
- 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())
*/
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));
+ }
+ }
}
\ No newline at end of file