blob: 2391cda70ddc55f7a6ca0cd4536c1d203c578eb7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2014 Wind River Systems, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Markus Schorn - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.internal.core;
import java.io.PrintStream;
import org.eclipse.cdt.core.IPositionConverter;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
/**
* Tracks changes made to a text buffer, to afterwards recalculate positions.
* @author markus.schorn@windriver.com
*/
public class PositionTracker implements IPositionConverter {
private static final int MEMORY_SIZE = 48;
private static final int NODE_MEMORY_SIZE= 32;
private Node fAboveRoot = new Node(0, 0, 0);
private PositionTracker fFollowedBy = null;
private long fTimeStamp;
/**
* Resets the tracker to a state reflecting no changes.
*/
public synchronized void clear() {
fAboveRoot = new Node(0, 0, 0);
fFollowedBy = null;
}
/**
* Undoes the retirement to make this the head of a tracker chain again.
*/
synchronized void revive() {
fFollowedBy = null;
}
/**
* Notifies the tracker of the insertion of characters.
* It is assumed that character get inserted before the offset.
*
* @param offset offset of the character in front of which insertion occurs.
* @param count amount of characters inserted.
*/
public synchronized void insert(int offset, int count) {
assert fFollowedBy == null;
assert offset >= 0;
if (count == 0 || offset < 0) {
return;
}
fAboveRoot.addChars(offset, count, 0);
}
/**
* Notifies the tracker of the removal of characters.
* delete(0,1) removes the first character,
* for convenience delete(1,-1) does the same.
*
* @param offset offset of the first character deleted.
* @param count amount of characters deleted.
*/
public synchronized void delete(int offset, int count) {
assert fFollowedBy == null;
assert offset >= 0;
if (count < 0) {
delete(offset + count, -count);
} else {
if (count == 0 || offset < 0) {
return;
}
fAboveRoot.removeChars(offset, count, 0, true);
}
}
/**
* Calculates the position in the original unmodified text.
*
* @param currentOffset position in the modified text.
* @return position in the unmodified text.
*/
public synchronized int historicOffset(int currentOffset) {
return historicOffset(currentOffset, true);
}
private synchronized int historicOffset(int currentOffset, boolean nextOnDelete) {
int orig = currentOffset;
if (fFollowedBy != null) {
orig = fFollowedBy.historicOffset(orig, nextOnDelete);
}
orig = fAboveRoot.calculateOriginalOffset(orig, 0, nextOnDelete);
return orig;
}
/**
* Calculates the position in the modified text.
*
* @param historicOffset position in the unmodified text.
* @return position in the modified text.
*/
public synchronized int currentOffset(int historicOffset) {
return currentOffset(historicOffset, true);
}
private synchronized int currentOffset(int historicOffset, boolean nextOnDelete) {
int current = fAboveRoot.calculateCurrentOffset(historicOffset, 0, nextOnDelete);
if (fFollowedBy != null) {
current = fFollowedBy.currentOffset(current, nextOnDelete);
}
return current;
}
/**
* Makes this tracker final. Future changes are tracked by the tracker
* supplied and will be taken into account when converting positions.
*
* @param inFavourOf tracker that tracks changes from now on.
*/
public synchronized void retire(PositionTracker inFavourOf) {
assert fFollowedBy == null;
fFollowedBy = inFavourOf;
}
/**
* For the purpose of testing.
*/
public synchronized void print(PrintStream out) {
fAboveRoot.print(0, out, 0);
}
/**
* For the purpose of testing.
*/
public synchronized int depth() {
return fAboveRoot.depth() - 1;
}
public synchronized boolean isModified() {
return fAboveRoot.fLeft != null || fAboveRoot.fRight!=null;
}
public synchronized long getTimeStamp() {
return fTimeStamp;
}
public synchronized void setTimeStamp(long timeStamp) {
fTimeStamp = timeStamp;
}
public synchronized long getRetiredTimeStamp() {
if (fFollowedBy == null) {
return Long.MAX_VALUE;
}
return fFollowedBy.getTimeStamp();
}
public synchronized int getMemorySize() {
return MEMORY_SIZE + NODE_MEMORY_SIZE * countNodes();
}
private synchronized int countNodes() {
return fAboveRoot.countNodes();
}
@Override
public synchronized IRegion actualToHistoric(IRegion actualPosition) {
int actual= actualPosition.getOffset();
int len= actualPosition.getLength();
int historic= historicOffset(actual, true);
if (len > 0) {
len= historicOffset(actual + len - 1, false) - historic + 1;
}
assert len >= 0;
return new Region(historic, len);
}
@Override
public synchronized IRegion historicToActual(IRegion historicPosition) {
int historic= historicPosition.getOffset();
int len= historicPosition.getLength();
int actual= currentOffset(historic, true);
if (len > 0) {
len= currentOffset(historic + len - 1, false) - actual + 1;
}
assert len >= 0;
return new Region(actual, len);
}
/**
* Nodes implementing a red black binary tree.
*
* @author markus.schorn@windriver.com
*/
private static class Node {
private static final boolean RED = true;
private static final boolean BLACK = false;
private int fDeltaPos2; // Sum of this and pos2 of parent yields pos2.
private int fPos1;
private int fChange; // Length of text change (+ add, - remove)
private boolean fColor;
private Node fLeft;
private Node fRight;
private Node fParent;
Node(int pos1, int deltaPos2, int change) {
fDeltaPos2 = deltaPos2;
fPos1 = pos1;
fChange = change;
fLeft = fRight = fParent = null;
fColor = RED;
}
int depth() {
if (fLeft == null) {
if (fRight == null) {
return 1;
}
return fRight.depth() + 1;
}
if (fRight == null) {
return fLeft.depth() + 1;
}
return StrictMath.max(fLeft.depth(), fRight.depth()) + 1;
}
// Forward calculation.
int calculateCurrentOffset(int value1, int parentPos2, boolean nextOnDelete) {
int fPos2 = parentPos2 + fDeltaPos2;
int rel1 = value1 - fPos1;
// Is value ahead of this change?
if (rel1 < 0) {
if (fLeft != null) {
return fLeft.calculateCurrentOffset(value1, fPos2, nextOnDelete);
}
// Value is directly ahead of this change.
return rel1 + fPos2;
}
// Is value deleted by this?
if (rel1 < -fChange) {
return nextOnDelete ? fPos2 : fPos2 - 1;
}
// Value is after this change.
if (fRight != null) {
return fRight.calculateCurrentOffset(value1, fPos2, nextOnDelete);
}
// Value is directly after this change.
return rel1 + fPos2 + fChange;
}
// Backward calculation.
int calculateOriginalOffset(int value2, int parentPos2, boolean nextOnDelete) {
int fPos2 = parentPos2 + fDeltaPos2;
int rel2 = value2 - fPos2;
// Is value ahead of this change?
if (rel2 < 0) {
if (fLeft != null) {
return fLeft.calculateOriginalOffset(value2, fPos2, nextOnDelete);
}
// Value is directly ahead of this change.
return rel2 + fPos1;
}
// Is value added by this?
if (rel2 < fChange) {
return nextOnDelete ? fPos1 : fPos1 - 1;
}
// Offset is behind this change.
if (fRight != null) {
return fRight.calculateOriginalOffset(value2, fPos2, nextOnDelete);
}
// Offset is directly behind this change.
return rel2 + fPos1 - fChange;
}
void addChars(int value2, int add, int fPos2) {
int rel2 = value2 - fPos2;
if (fParent != null) {
fParent.balance(); // This may change both the parent and fDeltaPos2;
}
// Added ahead of this change?
if (rel2 < 0) {
fDeltaPos2 += add; // Advance
if (fLeft != null) {
int childPos2 = fPos2 + fLeft.fDeltaPos2;
fLeft.fDeltaPos2 -= add; // Unadvance
fLeft.addChars(value2, add, childPos2); // Use modified parent pos
return;
}
addLeft(rel2 + fPos1, rel2 - add, add); // Modify delta pos
return;
}
// Added inside range of another change?
int range2 = fChange > 0 ? fChange : 0;
if (rel2 <= range2 && !isHolder()) {
fChange += add;
// Insert in a deletion at the end
if (fChange <= 0) {
fPos1 += add;
fDeltaPos2 += add;
if (fLeft != null) {
fLeft.fDeltaPos2 -= add;
}
} else if (fRight != null) {
fRight.fDeltaPos2 += add; // advance right branch
}
return;
}
// Added behind this change.
if (fRight != null) {
fRight.addChars(value2, add, fPos2 + fRight.fDeltaPos2);
return;
}
// Added directly behind this change.
addRight(rel2 + fPos1 - fChange, rel2, add);
}
boolean removeChars(int firstChar2, int remove, int fPos2, boolean mustRemove) {
int relFirstChar2 = firstChar2 - fPos2;
int relAfterLastChar2 = relFirstChar2 + remove;
// No insertion - no balancing
if (mustRemove && fParent != null) {
fParent.balance();
}
// Ahead and no merge possible.
if (relAfterLastChar2 < 0) {
fDeltaPos2 -= remove; // Advance
if (fLeft != null) {
fLeft.fDeltaPos2 += remove; // Unadvance
return fLeft.removeChars(firstChar2, remove, fPos2 - remove + fLeft.fDeltaPos2, mustRemove);
}
if (mustRemove) {
addLeft(relFirstChar2 + fPos1, relFirstChar2 + remove, -remove);
return true;
}
return false;
}
// Behind and no merge possible.
int range2 = (fChange > 0) ? fChange : 0;
if (relFirstChar2 > range2 || isHolder()) {
if (fRight != null) {
fRight.removeChars(firstChar2, remove, fPos2 + fRight.fDeltaPos2, mustRemove);
return true;
}
if (mustRemove) {
addRight(relFirstChar2 + fPos1 - fChange, relFirstChar2, -remove);
return true;
}
return false;
}
int delAbove = 0;
if (relFirstChar2 < 0) {
delAbove = -relFirstChar2;
}
int delBelow = relAfterLastChar2 - range2;
if (delBelow < 0) {
delBelow = 0;
}
int delInside = remove - delAbove - delBelow;
// Delegate above to left children.
if (delAbove > 0 && fLeft != null) {
if (fLeft.removeChars(firstChar2, delAbove, fPos2 + fLeft.fDeltaPos2, false)) {
fDeltaPos2 -= delAbove;
fLeft.fDeltaPos2 += delAbove;
fPos2 -= delAbove;
delAbove = 0;
}
}
// Delegate below to right children.
if (delBelow > 0 && fRight != null) {
if (fRight.removeChars(fPos2 + range2, delBelow, fPos2 + fRight.fDeltaPos2, false)) {
delBelow = 0;
}
}
// Do the adjustments in this node.
fChange -= delAbove + delInside + delBelow;
fDeltaPos2 -= delAbove;
fPos1 -= delAbove;
assert fPos1 >= 0;
if (fLeft != null) {
fLeft.fDeltaPos2 += delAbove; // lhs is unaffected, undo
}
if (fRight != null) {
fRight.fDeltaPos2 -= delInside; // rhs is additionally affected.
}
return true;
}
private void balance() {
if (fParent == null) {
if (fRight != null) {
fRight.fColor = BLACK;
}
return;
}
Node grandParent = fParent.fParent;
if (fLeft == null || fRight == null) {
return;
}
if (fLeft.isRed() && fRight.isRed()) {
fLeft.fColor = fRight.fColor = BLACK;
if (grandParent != null) {
fColor = RED;
if (fParent.isRed()) {
rotateAround(grandParent);
}
}
}
}
private void rotateAround(Node grandParent) {
if (grandParent.fLeft == fParent) {
rotateRightAround(grandParent);
} else {
rotateLeftAround(grandParent);
}
}
private void rotateRightAround(Node grandParent) {
if (fParent.fLeft == this) {
grandParent.rotateRight();
fParent.fColor = BLACK;
fParent.fRight.fColor = RED;
} else {
fParent.rotateLeft();
grandParent.rotateRight();
fColor = BLACK;
grandParent.fColor = RED;
}
}
private void rotateLeftAround(Node grandParent) {
if (fParent.fRight == this) {
grandParent.rotateLeft();
fParent.fColor = BLACK;
fParent.fLeft.fColor = RED;
} else {
fParent.rotateRight();
grandParent.rotateLeft();
fColor = BLACK;
grandParent.fColor = RED;
}
}
private void rotateRight() {
assert fLeft != null;
Node root = this;
Node left = fLeft;
Node leftRight = left.fRight;
int rootAbove = root.fDeltaPos2;
int aboveLeft = -root.fDeltaPos2 - left.fDeltaPos2;
int leftRoot = left.fDeltaPos2;
// Put under old parent.
if (fParent.fLeft == this) {
fParent.putLeft(left);
} else {
fParent.putRight(left);
}
left.fDeltaPos2 += rootAbove;
// Change the right node.
left.putRight(root);
root.fDeltaPos2 += aboveLeft;
// change left of right node.
root.putLeft(leftRight);
if (leftRight != null) {
leftRight.fDeltaPos2 += leftRoot;
}
}
private void rotateLeft() {
assert fRight != null;
Node root = this;
Node right = fRight;
Node rightLeft = right.fLeft;
int rootAbove = root.fDeltaPos2;
int parentRight = -root.fDeltaPos2 - right.fDeltaPos2;
int rightRoot = right.fDeltaPos2;
// Put under old parent.
if (fParent.fRight == this) {
fParent.putRight(right);
} else {
fParent.putLeft(right);
}
right.fDeltaPos2 += rootAbove;
// Change the left node.
right.putLeft(root);
root.fDeltaPos2 += parentRight;
// Change right of left node.
root.putRight(rightLeft);
if (rightLeft != null) {
rightLeft.fDeltaPos2 += rightRoot;
}
}
private boolean isRed() {
return fColor == RED;
}
private void putLeft(Node add) {
fLeft = add;
if (fLeft != null) {
fLeft.fParent = this;
}
}
private void putRight(Node add) {
fRight = add;
if (fRight != null) {
fRight.fParent = this;
}
}
private void addLeft(int pos1, int pos2, int change) {
fLeft = new Node(pos1, pos2, change);
fLeft.fParent = this;
if (isHolder()) {
assert false;
} else if (isRed()) {
fLeft.rotateAround(fParent);
}
}
private boolean isHolder() {
return fParent == null;
}
private void addRight(int pos1, int pos2, int change) {
fRight = new Node(pos1, pos2, change);
fRight.fParent = this;
if (isHolder()) {
fRight.fColor = BLACK;
} else if (isRed()) {
fRight.rotateAround(fParent);
}
}
public void print(int i, PrintStream out, int parentOffset) {
parentOffset += fDeltaPos2;
if (fRight != null) {
fRight.print(i + 1, out, parentOffset);
}
for (int j = 0; j < i; j++)
out.print(" "); //$NON-NLS-1$
out.println(fPos1 + "<->" + parentOffset + " : " + fChange + (fColor ? " // red" : "")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
if (fLeft != null) {
fLeft.print(i + 1, out, parentOffset);
}
}
public int countNodes() {
int count= 1;
if (fLeft != null) {
count += fLeft.countNodes();
}
if (fRight != null) {
count += fRight.countNodes();
}
return count;
}
}
}