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:

* *

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