org.eclipse.jface.text.TreeLineTracker Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aspectjtools Show documentation
Show all versions of aspectjtools Show documentation
Tools from the AspectJ project
/*******************************************************************************
* Copyright (c) 2005, 2015 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jface.text;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.text.AbstractLineTracker.DelimiterInfo;
/**
* Abstract implementation of ILineTracker
. It lets the definition of line
* delimiters to subclasses. Assuming that '\n' is the only line delimiter, this abstract
* implementation defines the following line scheme:
*
* - "" -> [0,0]
*
- "a" -> [0,1]
*
- "\n" -> [0,1], [1,0]
*
- "a\n" -> [0,2], [2,0]
*
- "a\nb" -> [0,2], [2,1]
*
- "a\nbc\n" -> [0,2], [2,3], [5,0]
*
*
* This class must be subclassed.
*
*
* Performance: The query operations perform in O(log n) where n
* is the number of lines in the document. The modification operations roughly perform in O(l *
* log n) where n is the number of lines in the document and l is the
* sum of the number of removed, added or modified lines.
*
*
* @since 3.2
*/
abstract class TreeLineTracker implements ILineTracker {
/*
* Differential Balanced Binary Tree
*
* Assumption: lines cannot overlap => there exists a total ordering of the lines by their offset,
* which is the same as the ordering by line number
*
* Base idea: store lines in a binary search tree
* - the key is the line number / line offset
* -> lookup_line is O(log n)
* -> lookup_offset is O(log n)
* - a change in a line somewhere will change any succeeding line numbers / line offsets
* -> replace is O(n)
*
* Differential tree: instead of storing the key (line number, line offset) directly, every node
* stores the difference between its key and its parent's key
* - the sort key is still the line number / line offset, but it remains "virtual"
* - inserting a node (a line) really increases the virtual key of all succeeding nodes (lines), but this
* fact will not be realized in the key information encoded in the nodes.
* -> any change only affects the nodes in the node's parent chain, although more bookkeeping
* has to be done when changing a node or balancing the tree
* -> replace is O(log n)
* -> line offsets and line numbers have to be computed when walking the tree from the root /
* from a node
* -> still O(log n)
*
* The balancing algorithm chosen does not depend on the differential tree property. An AVL tree
* implementation has been chosen for simplicity.
*/
/*
* Turns assertions on/off. Don't make this a a debug option for performance reasons - this way
* the compiler can optimize the asserts away.
*/
private static final boolean ASSERT= false;
/**
* The empty delimiter of the last line. The last line and only the last line must have this
* zero-length delimiter.
*/
private static final String NO_DELIM= ""; //$NON-NLS-1$
/**
* A node represents one line. Its character and line offsets are 0-based and relative to the
* subtree covered by the node. All nodes under the left subtree represent lines before, all
* nodes under the right subtree lines after the current node.
*/
private static final class Node {
Node(int length, String delimiter) {
this.length= length;
this.delimiter= delimiter;
}
/**
* The line index in this node's line tree, or equivalently, the number of lines in the left
* subtree.
*/
int line;
/**
* The line offset in this node's line tree, or equivalently, the number of characters in
* the left subtree.
*/
int offset;
/** The number of characters in this line. */
int length;
/** The line delimiter of this line, needed to answer the delimiter query. */
String delimiter;
/** The parent node, null
if this is the root node. */
Node parent;
/** The left subtree, possibly null
. */
Node left;
/** The right subtree, possibly null
. */
Node right;
/** The balance factor. */
byte balance;
@Override
public final String toString() {
String bal;
switch (balance) {
case 0:
bal= "="; //$NON-NLS-1$
break;
case 1:
bal= "+"; //$NON-NLS-1$
break;
case 2:
bal= "++"; //$NON-NLS-1$
break;
case -1:
bal= "-"; //$NON-NLS-1$
break;
case -2:
bal= "--"; //$NON-NLS-1$
break;
default:
bal= Byte.toString(balance);
}
return "[" + offset + "+" + pureLength() + "+" + delimiter.length() + "|" + line + "|" + bal + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
}
/**
* Returns the pure (without the line delimiter) length of this line.
*
* @return the pure line length
*/
int pureLength() {
return length - delimiter.length();
}
}
/**
* The root node of the tree, never null
.
*/
private Node fRoot= new Node(0, NO_DELIM);
/**
* Creates a new line tracker.
*/
protected TreeLineTracker() {
}
/**
* Package visible constructor for creating a tree tracker from a list tracker.
*
* @param tracker the list line tracker
*/
TreeLineTracker(ListLineTracker tracker) {
final List lines= tracker.getLines();
final int n= lines.size();
if (n == 0)
return;
Line line= lines.get(0);
String delim= line.delimiter;
if (delim == null)
delim= NO_DELIM;
int length= line.length;
fRoot= new Node(length, delim);
Node node= fRoot;
for (int i= 1; i < n; i++) {
line= lines.get(i);
delim= line.delimiter;
if (delim == null)
delim= NO_DELIM;
length= line.length;
node= insertAfter(node, length, delim);
}
if (node.delimiter != NO_DELIM)
insertAfter(node, 0, NO_DELIM);
if (ASSERT) checkTree();
}
/**
* Returns the node (line) including a certain offset. If the offset is between two
* lines, the line starting at offset
is returned.
*
* This means that for offsets smaller than the length, the following holds:
*
*
* line.offset <= offset < line.offset + offset.length
.
*
*
* If offset
is the document length, then this is true:
*
*
* offset= line.offset + line.length
.
*
*
* @param offset a document offset
* @return the line starting at or containing offset
* @throws BadLocationException if the offset is invalid
*/
private Node nodeByOffset(final int offset) throws BadLocationException {
/*
* Works for any binary search tree.
*/
int remaining= offset;
Node node= fRoot;
while (true) {
if (node == null)
fail(offset);
if (remaining < node.offset) {
node= node.left;
} else {
remaining -= node.offset;
if (remaining < node.length
|| remaining == node.length && node.right == null) { // last line
break;
}
remaining -= node.length;
node= node.right;
}
}
return node;
}
/**
* Returns the line number for the given offset. If the offset is between two lines, the line
* starting at offset
is returned. The last line is returned if
* offset
is equal to the document length.
*
* @param offset a document offset
* @return the line number starting at or containing offset
* @throws BadLocationException if the offset is invalid
*/
private int lineByOffset(final int offset) throws BadLocationException {
/*
* Works for any binary search tree.
*/
int remaining= offset;
Node node= fRoot;
int line= 0;
while (true) {
if (node == null)
fail(offset);
if (remaining < node.offset) {
node= node.left;
} else {
remaining -= node.offset;
line+= node.line;
if (remaining < node.length || remaining == node.length && node.right == null) // last line
return line;
remaining -= node.length;
line ++;
node= node.right;
}
}
}
/**
* Returns the node (line) with the given line number. Note that the last line is always
* incomplete, i.e. has the {@link #NO_DELIM} delimiter.
*
* @param line a line number
* @return the line with the given line number
* @throws BadLocationException if the line is invalid
*/
private Node nodeByLine(final int line) throws BadLocationException {
/*
* Works for any binary search tree.
*/
int remaining= line;
Node node= fRoot;
while (true) {
if (node == null)
fail(line);
if (remaining == node.line)
break;
if (remaining < node.line) {
node= node.left;
} else {
remaining -= node.line + 1;
node= node.right;
}
}
return node;
}
/**
* Returns the offset for the given line number. Note that the
* last line is always incomplete, i.e. has the {@link #NO_DELIM} delimiter.
*
* @param line a line number
* @return the line offset with the given line number
* @throws BadLocationException if the line is invalid
*/
private int offsetByLine(final int line) throws BadLocationException {
/*
* Works for any binary search tree.
*/
int remaining= line;
int offset= 0;
Node node= fRoot;
while (true) {
if (node == null)
fail(line);
if (remaining == node.line)
return offset + node.offset;
if (remaining < node.line) {
node= node.left;
} else {
remaining -= node.line + 1;
offset += node.offset + node.length;
node= node.right;
}
}
}
/**
* Left rotation - the given node is rotated down, its right child is rotated up, taking the
* previous structural position of node
.
*
* @param node the node to rotate around
*/
private void rotateLeft(Node node) {
if (ASSERT) Assert.isNotNull(node);
Node child= node.right;
if (ASSERT) Assert.isNotNull(child);
boolean leftChild= node.parent == null || node == node.parent.left;
// restructure
setChild(node.parent, child, leftChild);
setChild(node, child.left, false);
setChild(child, node, true);
// update relative info
// child becomes the new parent, its line and offset counts increase as the former parent
// moves under child's left subtree
child.line += node.line + 1;
child.offset += node.offset + node.length;
}
/**
* Right rotation - the given node is rotated down, its left child is rotated up, taking the
* previous structural position of node
.
*
* @param node the node to rotate around
*/
private void rotateRight(Node node) {
if (ASSERT) Assert.isNotNull(node);
Node child= node.left;
if (ASSERT) Assert.isNotNull(child);
boolean leftChild= node.parent == null || node == node.parent.left;
setChild(node.parent, child, leftChild);
setChild(node, child.right, true);
setChild(child, node, false);
// update relative info
// node loses its left subtree, except for what it keeps in its new subtree
// this is exactly the amount in child
node.line -= child.line + 1;
node.offset -= child.offset + child.length;
}
/**
* Helper method for moving a child, ensuring that parent pointers are set correctly.
*
* @param parent the new parent of child
, null
to replace the
* root node
* @param child the new child of parent
, may be null
* @param isLeftChild true
if child
shall become
* parent
's left child, false
if it shall become
* parent
's right child
*/
private void setChild(Node parent, Node child, boolean isLeftChild) {
if (parent == null) {
if (child == null)
fRoot= new Node(0, NO_DELIM);
else
fRoot= child;
} else {
if (isLeftChild)
parent.left= child;
else
parent.right= child;
}
if (child != null)
child.parent= parent;
}
/**
* A left rotation around parent
, whose structural position is replaced by
* node
.
*
* @param node the node moving up and left
* @param parent the node moving left and down
*/
private void singleLeftRotation(Node node, Node parent) {
rotateLeft(parent);
node.balance= 0;
parent.balance= 0;
}
/**
* A right rotation around parent
, whose structural position is replaced by
* node
.
*
* @param node the node moving up and right
* @param parent the node moving right and down
*/
private void singleRightRotation(Node node, Node parent) {
rotateRight(parent);
node.balance= 0;
parent.balance= 0;
}
/**
* A double left rotation, first rotating right around node
, then left around
* parent
.
*
* @param node the node that will be rotated right
* @param parent the node moving left and down
*/
private void rightLeftRotation(Node node, Node parent) {
Node child= node.left;
rotateRight(node);
rotateLeft(parent);
if (child.balance == 1) {
node.balance= 0;
parent.balance= -1;
child.balance= 0;
} else if (child.balance == 0) {
node.balance= 0;
parent.balance= 0;
} else if (child.balance == -1) {
node.balance= 1;
parent.balance= 0;
child.balance= 0;
}
}
/**
* A double right rotation, first rotating left around node
, then right around
* parent
.
*
* @param node the node that will be rotated left
* @param parent the node moving right and down
*/
private void leftRightRotation(Node node, Node parent) {
Node child= node.right;
rotateLeft(node);
rotateRight(parent);
if (child.balance == -1) {
node.balance= 0;
parent.balance= 1;
child.balance= 0;
} else if (child.balance == 0) {
node.balance= 0;
parent.balance= 0;
} else if (child.balance == 1) {
node.balance= -1;
parent.balance= 0;
child.balance= 0;
}
}
/**
* Inserts a line with the given length and delimiter after node
.
*
* @param node the predecessor of the inserted node
* @param length the line length of the inserted node
* @param delimiter the delimiter of the inserted node
* @return the inserted node
*/
private Node insertAfter(Node node, int length, String delimiter) {
/*
* An insertion really shifts the key of all succeeding nodes. Hence we insert the added node
* between node and the successor of node. The added node becomes either the right child
* of the predecessor node, or the left child of the successor node.
*/
Node added= new Node(length, delimiter);
if (node.right == null)
setChild(node, added, false);
else
setChild(successorDown(node.right), added, true);
// parent chain update
updateParentChain(added, length, 1);
updateParentBalanceAfterInsertion(added);
return added;
}
/**
* Updates the balance information in the parent chain of node until it reaches the root or
* finds a node whose balance violates the AVL constraint, which is the re-balanced.
*
* @param node the child of the first node that needs balance updating
*/
private void updateParentBalanceAfterInsertion(Node node) {
Node parent= node.parent;
while (parent != null) {
if (node == parent.left)
parent.balance--;
else
parent.balance++;
switch (parent.balance) {
case 1:
case -1:
node= parent;
parent= node.parent;
continue;
case -2:
rebalanceAfterInsertionLeft(node);
break;
case 2:
rebalanceAfterInsertionRight(node);
break;
case 0:
break;
default:
if (ASSERT)
Assert.isTrue(false);
}
return;
}
}
/**
* Re-balances a node whose parent has a double positive balance.
*
* @param node the node to re-balance
*/
private void rebalanceAfterInsertionRight(Node node) {
Node parent= node.parent;
if (node.balance == 1) {
singleLeftRotation(node, parent);
} else if (node.balance == -1) {
rightLeftRotation(node, parent);
} else if (ASSERT) {
Assert.isTrue(false);
}
}
/**
* Re-balances a node whose parent has a double negative balance.
*
* @param node the node to re-balance
*/
private void rebalanceAfterInsertionLeft(Node node) {
Node parent= node.parent;
if (node.balance == -1) {
singleRightRotation(node, parent);
} else if (node.balance == 1) {
leftRightRotation(node, parent);
} else if (ASSERT) {
Assert.isTrue(false);
}
}
@Override
public final void replace(int offset, int length, String text) throws BadLocationException {
if (ASSERT) checkTree();
// Inlined nodeByOffset as we need both node and offset
int remaining= offset;
Node first= fRoot;
final int firstNodeOffset;
while (true) {
if (first == null)
fail(offset);
if (remaining < first.offset) {
first= first.left;
} else {
remaining -= first.offset;
if (remaining < first.length
|| remaining == first.length && first.right == null) { // last line
firstNodeOffset= offset - remaining;
break;
}
remaining -= first.length;
first= first.right;
}
}
// Inline nodeByOffset end
if (ASSERT) Assert.isTrue(first != null);
Node last;
if (offset + length < firstNodeOffset + first.length)
last= first;
else
last= nodeByOffset(offset + length);
if (ASSERT) Assert.isTrue(last != null);
int firstLineDelta= firstNodeOffset + first.length - offset;
if (first == last)
replaceInternal(first, text, length, firstLineDelta);
else
replaceFromTo(first, last, text, length, firstLineDelta);
if (ASSERT) checkTree();
}
/**
* Replace happening inside a single line.
*
* @param node the affected node
* @param text the added text
* @param length the replace length, < firstLineDelta
* @param firstLineDelta the number of characters from the replacement offset to the end of
* node
> length
*/
private void replaceInternal(Node node, String text, int length, int firstLineDelta) {
// 1) modification on a single line
DelimiterInfo info= text == null ? null : nextDelimiterInfo(text, 0);
if (info == null || info.delimiter == null) {
// a) trivial case: insert into a single node, no line mangling
int added= text == null ? 0 : text.length();
updateLength(node, added - length);
} else {
// b) more lines to add between two chunks of the first node
// remember what we split off the first line
int remainder= firstLineDelta - length;
String remDelim= node.delimiter;
// join the first line with the first added
int consumed= info.delimiterIndex + info.delimiterLength;
int delta= consumed - firstLineDelta;
updateLength(node, delta);
node.delimiter= info.delimiter;
// Inline addlines start
info= nextDelimiterInfo(text, consumed);
while (info != null) {
int lineLen= info.delimiterIndex - consumed + info.delimiterLength;
node= insertAfter(node, lineLen, info.delimiter);
consumed += lineLen;
info= nextDelimiterInfo(text, consumed);
}
// Inline addlines end
// add remaining chunk merged with last (incomplete) additional line
insertAfter(node, remainder + text.length() - consumed, remDelim);
}
}
/**
* Replace spanning from one node to another.
*
* @param node the first affected node
* @param last the last affected node
* @param text the added text
* @param length the replace length, >= firstLineDelta
* @param firstLineDelta the number of characters removed from the replacement offset to the end
* of node
, <= length
*/
private void replaceFromTo(Node node, Node last, String text, int length, int firstLineDelta) {
// 2) modification covers several lines
// delete intermediate nodes
// TODO could be further optimized: replace intermediate lines with intermediate added lines
// to reduce re-balancing
Node successor= successor(node);
while (successor != last) {
length -= successor.length;
Node toDelete= successor;
successor= successor(successor);
updateLength(toDelete, -toDelete.length);
}
DelimiterInfo info= text == null ? null : nextDelimiterInfo(text, 0);
if (info == null || info.delimiter == null) {
int added= text == null ? 0 : text.length();
// join the two lines if there are no lines added
join(node, last, added - length);
} else {
// join the first line with the first added
int consumed= info.delimiterIndex + info.delimiterLength;
updateLength(node, consumed - firstLineDelta);
node.delimiter= info.delimiter;
length -= firstLineDelta;
// Inline addLines start
info= nextDelimiterInfo(text, consumed);
while (info != null) {
int lineLen= info.delimiterIndex - consumed + info.delimiterLength;
node= insertAfter(node, lineLen, info.delimiter);
consumed += lineLen;
info= nextDelimiterInfo(text, consumed);
}
// Inline addLines end
updateLength(last, text.length() - consumed - length);
}
}
/**
* Joins two consecutive node lines, additionally adjusting the resulting length of the combined
* line by delta
. The first node gets deleted.
*
* @param one the first node to join
* @param two the second node to join
* @param delta the delta to apply to the remaining single node
*/
private void join(Node one, Node two, int delta) {
int oneLength= one.length;
updateLength(one, -oneLength);
updateLength(two, oneLength + delta);
}
/**
* Adjusts the length of a node by delta
, also adjusting the parent chain of
* node
. If the node's length becomes zero and is not the last (incomplete)
* node, it is deleted after the update.
*
* @param node the node to adjust
* @param delta the character delta to add to the node's length
*/
private void updateLength(Node node, int delta) {
if (ASSERT) Assert.isTrue(node.length + delta >= 0);
// update the node itself
node.length += delta;
// check deletion
final int lineDelta;
boolean delete= node.length == 0 && node.delimiter != NO_DELIM;
if (delete)
lineDelta= -1;
else
lineDelta= 0;
// update parent chain
if (delta != 0 || lineDelta != 0)
updateParentChain(node, delta, lineDelta);
if (delete)
delete(node);
}
/**
* Updates the differential indices following the parent chain. All nodes from
* from.parent
to the root are updated.
*
* @param node the child of the first node to update
* @param deltaLength the character delta
* @param deltaLines the line delta
*/
private void updateParentChain(Node node, int deltaLength, int deltaLines) {
updateParentChain(node, null, deltaLength, deltaLines);
}
/**
* Updates the differential indices following the parent chain. All nodes from
* from.parent
to to
(exclusive) are updated.
*
* @param from the child of the first node to update
* @param to the first node not to update
* @param deltaLength the character delta
* @param deltaLines the line delta
*/
private void updateParentChain(Node from, Node to, int deltaLength, int deltaLines) {
Node parent= from.parent;
while (parent != to) {
// only update node if update comes from left subtree
if (from == parent.left) {
parent.offset += deltaLength;
parent.line += deltaLines;
}
from= parent;
parent= from.parent;
}
}
/**
* Deletes a node from the tree, re-balancing it if necessary. The differential indices in the
* node's parent chain have to be updated in advance to calling this method. Generally, don't
* call delete
directly, but call update_length(node, -node.length)
to
* properly remove a node.
*
* @param node the node to delete.
*/
private void delete(Node node) {
if (ASSERT) Assert.isTrue(node != null);
if (ASSERT) Assert.isTrue(node.length == 0);
Node parent= node.parent;
Node toUpdate; // the parent of the node that lost a child
boolean lostLeftChild;
boolean isLeftChild= parent == null || node == parent.left;
if (node.left == null || node.right == null) {
// 1) node has one child at max - replace parent's pointer with the only child
// also handles the trivial case of no children
Node replacement= node.left == null ? node.right : node.left;
setChild(parent, replacement, isLeftChild);
toUpdate= parent;
lostLeftChild= isLeftChild;
// no updates to do - subtrees stay as they are
} else if (node.right.left == null) {
// 2a) node's right child has no left child - replace node with right child, giving node's
// left subtree to the right child
Node replacement= node.right;
setChild(parent, replacement, isLeftChild);
setChild(replacement, node.left, true);
replacement.line= node.line;
replacement.offset= node.offset;
replacement.balance= node.balance;
toUpdate= replacement;
lostLeftChild= false;
// } else if (node.left.right == null) {
// // 2b) symmetric case
// Node replacement= node.left;
// set_child(parent, replacement, isLeftChild);
// set_child(replacement, node.right, false);
// replacement.balance= node.balance;
// toUpdate= replacement;
// lostLeftChild= true;
} else {
// 3) hard case - replace node with its successor
Node successor= successor(node);
// successor exists (otherwise node would not have right child, case 1)
if (ASSERT) Assert.isNotNull(successor);
// successor has no left child (a left child would be the real successor of node)
if (ASSERT) Assert.isTrue(successor.left == null);
if (ASSERT) Assert.isTrue(successor.line == 0);
// successor is the left child of its parent (otherwise parent would be smaller and
// hence the real successor)
if (ASSERT) Assert.isTrue(successor == successor.parent.left);
// successor is not a child of node (would have been covered by 2a)
if (ASSERT) Assert.isTrue(successor.parent != node);
toUpdate= successor.parent;
lostLeftChild= true;
// update relative indices
updateParentChain(successor, node, -successor.length, -1);
// delete successor from its current place - like 1)
setChild(toUpdate, successor.right, true);
// move node's subtrees to its successor
setChild(successor, node.right, false);
setChild(successor, node.left, true);
// replace node by successor in its parent
setChild(parent, successor, isLeftChild);
// update the successor
successor.line= node.line;
successor.offset= node.offset;
successor.balance= node.balance;
}
updateParentBalanceAfterDeletion(toUpdate, lostLeftChild);
}
/**
* Updates the balance information in the parent chain of node.
*
* @param node the first node that needs balance updating
* @param wasLeftChild true
if the deletion happened on node
's
* left subtree, false
if it occurred on node
's right
* subtree
*/
private void updateParentBalanceAfterDeletion(Node node, boolean wasLeftChild) {
while (node != null) {
if (wasLeftChild)
node.balance++;
else
node.balance--;
Node parent= node.parent;
if (parent != null)
wasLeftChild= node == parent.left;
switch (node.balance) {
case 1:
case -1:
return; // done, no tree change
case -2:
if (rebalanceAfterDeletionRight(node.left))
return;
break; // propagate up
case 2:
if (rebalanceAfterDeletionLeft(node.right))
return;
break; // propagate up
case 0:
break; // propagate up
default:
if (ASSERT)
Assert.isTrue(false);
}
node= parent;
}
}
/**
* Re-balances a node whose parent has a double positive balance.
*
* @param node the node to re-balance
* @return true
if the re-balancement leaves the height at
* node.parent
constant, false
if the height changed
*/
private boolean rebalanceAfterDeletionLeft(Node node) {
Node parent= node.parent;
if (node.balance == 1) {
singleLeftRotation(node, parent);
return false;
} else if (node.balance == -1) {
rightLeftRotation(node, parent);
return false;
} else if (node.balance == 0) {
rotateLeft(parent);
node.balance= -1;
parent.balance= 1;
return true;
} else {
if (ASSERT) Assert.isTrue(false);
return true;
}
}
/**
* Re-balances a node whose parent has a double negative balance.
*
* @param node the node to re-balance
* @return true
if the re-balancement leaves the height at
* node.parent
constant, false
if the height changed
*/
private boolean rebalanceAfterDeletionRight(Node node) {
Node parent= node.parent;
if (node.balance == -1) {
singleRightRotation(node, parent);
return false;
} else if (node.balance == 1) {
leftRightRotation(node, parent);
return false;
} else if (node.balance == 0) {
rotateRight(parent);
node.balance= 1;
parent.balance= -1;
return true;
} else {
if (ASSERT) Assert.isTrue(false);
return true;
}
}
/**
* Returns the successor of a node, null
if node is the last node.
*
* @param node a node
* @return the successor of node
, null
if there is none
*/
private Node successor(Node node) {
if (node.right != null)
return successorDown(node.right);
return successorUp(node);
}
/**
* Searches the successor of node
in its parent chain.
*
* @param node a node
* @return the first node in node
's parent chain that is reached from its left
* subtree, null
if there is none
*/
private Node successorUp(final Node node) {
Node child= node;
Node parent= child.parent;
while (parent != null) {
if (child == parent.left)
return parent;
child= parent;
parent= child.parent;
}
if (ASSERT) Assert.isTrue(node.delimiter == NO_DELIM);
return null;
}
/**
* Searches the left-most node in a given subtree.
*
* @param node a node
* @return the left-most node in the given subtree
*/
private Node successorDown(Node node) {
Node child= node.left;
while (child != null) {
node= child;
child= node.left;
}
return node;
}
/* miscellaneous */
/**
* Throws an exception.
*
* @param offset the illegal character or line offset that caused the exception
* @throws BadLocationException always
*/
private void fail(int offset) throws BadLocationException {
throw new BadLocationException();
}
/**
* Returns the information about the first delimiter found in the given
* text starting at the given offset.
*
* @param text the text to be searched
* @param offset the offset in the given text
* @return the information of the first found delimiter or null
*/
protected abstract DelimiterInfo nextDelimiterInfo(String text, int offset);
@Override
public final String getLineDelimiter(int line) throws BadLocationException {
Node node= nodeByLine(line);
return node.delimiter == NO_DELIM ? null : node.delimiter;
}
@Override
public final int computeNumberOfLines(String text) {
int count= 0;
int start= 0;
DelimiterInfo delimiterInfo= nextDelimiterInfo(text, start);
while (delimiterInfo != null && delimiterInfo.delimiterIndex > -1) {
++count;
start= delimiterInfo.delimiterIndex + delimiterInfo.delimiterLength;
delimiterInfo= nextDelimiterInfo(text, start);
}
return count;
}
@Override
public final int getNumberOfLines() {
// TODO track separately?
Node node= fRoot;
int lines= 0;
while (node != null) {
lines += node.line + 1;
node= node.right;
}
return lines;
}
@Override
public final int getNumberOfLines(int offset, int length) throws BadLocationException {
if (length == 0)
return 1;
int startLine= lineByOffset(offset);
int endLine= lineByOffset(offset + length);
return endLine - startLine + 1;
}
@Override
public final int getLineOffset(int line) throws BadLocationException {
return offsetByLine(line);
}
@Override
public final int getLineLength(int line) throws BadLocationException {
Node node= nodeByLine(line);
return node.length;
}
@Override
public final int getLineNumberOfOffset(int offset) throws BadLocationException {
return lineByOffset(offset);
}
@Override
public final IRegion getLineInformationOfOffset(final int offset) throws BadLocationException {
// Inline nodeByOffset start as we need both node and offset
int remaining= offset;
Node node= fRoot;
final int lineOffset;
while (true) {
if (node == null)
fail(offset);
if (remaining < node.offset) {
node= node.left;
} else {
remaining -= node.offset;
if (remaining < node.length
|| remaining == node.length && node.right == null) { // last line
lineOffset= offset - remaining;
break;
}
remaining -= node.length;
node= node.right;
}
}
// Inline nodeByOffset end
return new Region(lineOffset, node.pureLength());
}
@Override
public final IRegion getLineInformation(int line) throws BadLocationException {
try {
// Inline nodeByLine start
int remaining= line;
int offset= 0;
Node node= fRoot;
while (true) {
if (node == null)
fail(line);
if (remaining == node.line) {
offset += node.offset;
break;
}
if (remaining < node.line) {
node= node.left;
} else {
remaining -= node.line + 1;
offset += node.offset + node.length;
node= node.right;
}
}
// Inline nodeByLine end
return new Region(offset, node.pureLength());
} catch (BadLocationException x) {
/*
* FIXME: this really strange behavior is mandated by the previous line tracker
* implementation and included here for compatibility. See
* LineTrackerTest3#testFunnyLastLineCompatibility().
*/
if (line > 0 && line == getNumberOfLines()) {
line= line - 1;
// Inline nodeByLine start
int remaining= line;
int offset= 0;
Node node= fRoot;
while (true) {
if (node == null)
fail(line);
if (remaining == node.line) {
offset+= node.offset;
break;
}
if (remaining < node.line) {
node= node.left;
} else {
remaining -= node.line + 1;
offset += node.offset + node.length;
node= node.right;
}
}
Node last= node;
// Inline nodeByLine end
if (last.length > 0)
return new Region(offset + last.length, 0);
}
throw x;
}
}
@Override
public final void set(String text) {
fRoot= new Node(0, NO_DELIM);
try {
replace(0, 0, text);
} catch (BadLocationException x) {
throw new InternalError();
}
}
@Override
public String toString() {
int depth= computeDepth(fRoot);
int WIDTH= 30;
int leaves= (int) Math.pow(2, depth - 1);
int width= WIDTH * leaves;
String empty= "."; //$NON-NLS-1$
List roots= new LinkedList<>();
roots.add(fRoot);
StringBuilder buf= new StringBuilder((width + 1) * depth); // see Bug 137688
int indents= leaves;
char[] space= new char[leaves * WIDTH / 2];
Arrays.fill(space, ' ');
for(int d= 0; d < depth; d++) {
// compute indent
indents /= 2;
int spaces= Math.max(0, indents * WIDTH - WIDTH / 2);
// print nodes
for (ListIterator it= roots.listIterator(); it.hasNext();) {
// pad before
buf.append(space, 0, spaces);
Node node= it.next();
String box;
// replace the node with its children
if (node == null) {
it.add(null);
box= empty;
} else {
it.set(node.left);
it.add(node.right);
box= node.toString();
}
// draw the node, pad to WIDTH
int pad_left= (WIDTH - box.length() + 1) / 2;
int pad_right= WIDTH - box.length() - pad_left;
buf.append(space, 0, pad_left);
buf.append(box);
buf.append(space, 0, pad_right);
// pad after
buf.append(space, 0, spaces);
}
buf.append('\n');
}
return buf.toString();
}
/**
* Recursively computes the depth of the tree. Only used by {@link #toString()}.
*
* @param root the subtree to compute the depth of, may be null
* @return the depth of the given tree, 0 if it is null
*/
private byte computeDepth(Node root) {
if (root == null)
return 0;
return (byte) (Math.max(computeDepth(root.left), computeDepth(root.right)) + 1);
}
/**
* Debug-only method that checks the tree structure and the differential offsets.
*/
private void checkTree() {
checkTreeStructure(fRoot);
try {
checkTreeOffsets(nodeByOffset(0), new int[] {0, 0}, null);
} catch (BadLocationException x) {
throw new AssertionError();
}
}
/**
* Debug-only method that validates the tree structure below node
. I.e. it
* checks whether all parent/child pointers are consistent and whether the AVL balance
* information is correct.
*
* @param node the node to validate
* @return the depth of the tree under node
*/
private byte checkTreeStructure(Node node) {
if (node == null)
return 0;
byte leftDepth= checkTreeStructure(node.left);
byte rightDepth= checkTreeStructure(node.right);
Assert.isTrue(node.balance == rightDepth - leftDepth);
Assert.isTrue(node.left == null || node.left.parent == node);
Assert.isTrue(node.right == null || node.right.parent == node);
return (byte) (Math.max(rightDepth, leftDepth) + 1);
}
/**
* Debug-only method that checks the differential offsets of the tree, starting at
* node
and continuing until last
.
*
* @param node the first Node
to check, may be null
* @param offLen an array of length 2, with offLen[0]
the expected offset of
* node
and offLen[1]
the expected line of
* node
* @param last the last Node
to check, may be null
* @return an int[]
of length 2, with the first element being the character
* length of node
's subtree, and the second element the number of lines
* in node
's subtree
*/
private int[] checkTreeOffsets(Node node, int[] offLen, Node last) {
if (node == last)
return offLen;
Assert.isTrue(node.offset == offLen[0]);
Assert.isTrue(node.line == offLen[1]);
if (node.right != null) {
int[] result= checkTreeOffsets(successorDown(node.right), new int[2], node);
offLen[0] += result[0];
offLen[1] += result[1];
}
offLen[0] += node.length;
offLen[1]++;
return checkTreeOffsets(node.parent, offLen, last);
}
}