1
0
Fork 0
mirror of https://github.com/eclipse-cdt/cdt synced 2025-07-23 17:05:26 +02:00

Add position trackers, includes test cases.

This commit is contained in:
Markus Schorn 2006-07-03 13:01:05 +00:00
parent 6d99d620f3
commit 328e4c08f1
11 changed files with 1441 additions and 3 deletions

View file

@ -25,6 +25,7 @@ Require-Bundle: org.eclipse.core.resources,
org.eclipse.core.runtime,
org.eclipse.ui.ide,
org.eclipse.jface,
org.eclipse.ui
org.eclipse.ui,
org.eclipse.jface.text
Eclipse-LazyStart: true
Bundle-Vendor: Eclipse.org

View file

@ -0,0 +1,357 @@
/*******************************************************************************
* Copyright (c) 2006 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.core.internal.tests;
import java.util.Random;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.eclipse.cdt.internal.core.PositionTracker;
import org.eclipse.jface.text.Position;
public class PositionTrackerTests extends TestCase {
public static Test suite() {
return new TestSuite(PositionTrackerTests.class, "PositionTrackerTests");
}
public void testInitialFailures() {
int[][] moves = {
{46, -18, 95, -76, 98, -89, 10, -10, 85, -80, 16, 6, 5, -3,
22, -8, 29, -20, 86, -62, 34, -21, 63, -41, 9, 10, 18, -7},
{0, 2, 1,-4},
{4,-1, 0, 2, 0,-5},
{0, 1, 2, 1, 0,-5},
{0, 1, 2,-3, 1,-4},
{4, 3, 3,-2, 0,-1},
{4,-1, 3, 1, 2,-1},
{0, 1, 2, 8, 1,-8, 0,-10},
{4,-1, 2, 1, 4, 1, 0,-1, 0,-5},
};
int[] buffer = new int[100];
for (int i = 0; i < moves.length; i++) {
testMove(buffer, moves[i]);
}
}
public void testRotations() {
int[][] moves = { { 0, 1, 2, 1, 4, 1, 6, 1, 8, 1, 10, 1, 12, 1, 14, 1, 16, 1, 18, 1, 20, 1, 22, 1, 24, 1 }, {
15, 1, 14, 1, 13, 1, 12, 1, 11, 1, 10, 1, 9, 1, 8, 1, 7, 1, 6, 1, 5, 1, 4, 1, 3, 1 }, {
0, 1, 10, 1, 2, 1, 20, 1, 4, 1, 20, 1, 6, 1, 20, 1, 8, 1, 20, 1, 10, 1, 20, 1, 12, 1 }, };
int[] buffer = new int[30];
for (int i = 0; i < moves.length; i++) {
assertTrue(testMove(buffer, moves[i]).depth() <= 5);
}
}
public void testDepth4() {
fullTest(5, 4);
}
public void testRandomDepth5() {
randomTest(20, 5, 5, 50000);
}
public void testRandomDepth10() {
randomTest(50, 10, 10, 50000);
}
public void testRandomDepth15() {
randomTest(100, 15, 15, 50000);
}
public void testRandomDepth20() {
randomTest(100, 15, 20, 50000);
}
public void testRetireDepth2() {
randomRetireTest(100, 10, 25, 2, 1000);
}
public void testRetireDepth5() {
randomRetireTest(100, 10, 10, 5, 1000);
}
public void testRetireDepth10() {
randomRetireTest(100, 10, 5, 10, 1000);
}
public static void fullTest(int len, int depth) {
// init buffer
int[] buffer = new int[len];
int[] move = new int[2 * depth];
for (int i = 0; i < move.length; i++) {
move[i] = -1;
}
while (nextMove(move, len)) {
testMove(buffer, move);
}
}
public static void randomTest(int buflen, int changelen, int depth, int count) {
// init buffer
Random rand = new Random();
int[] buffer = new int[buflen];
int[] move = new int[2 * depth];
for (int j = 0; j < count; j++) {
for (int i = 0; i < move.length; i += 2) {
move[i] = rand.nextInt(buflen);
move[i + 1] = rand.nextInt(2 * changelen) - changelen;
}
testMove(buffer, move);
}
}
public static void randomRetireTest(int buflen, int changelen, int depth, int trackerDepth, int count) {
// init buffer
Random rand = new Random();
int[] buffer = new int[buflen];
int[] move = new int[2 * depth];
for (int j = 0; j < count; j++) {
for (int i = 0; i < buffer.length; i++) {
buffer[i] = i;
}
PositionTracker t0 = null;
PositionTracker previous = null;
for (int t = 0; t < trackerDepth; t++) {
for (int i = 0; i < move.length; i += 2) {
move[i] = rand.nextInt(buflen);
move[i + 1] = rand.nextInt(2 * changelen) - changelen;
}
PositionTracker tracker = new PositionTracker();
if (previous != null) {
previous.retire(tracker);
}
doMove(buffer, move, tracker);
if (t0 == null) {
t0 = tracker;
}
previous = tracker;
}
check(t0, buffer);
}
}
static PositionTracker testMove(int[] buffer, int[] move) {
try {
return __testMove(buffer, move);
} catch (RuntimeException e) {
System.out.println("Error on move: "); //$NON-NLS-1$
for (int i = 0; i < move.length; i++) {
System.out.print(move[i] + ", "); //$NON-NLS-1$
}
System.out.println();
throw e;
} catch (Error e) {
System.out.println("Error on move: "); //$NON-NLS-1$
for (int i = 0; i < move.length; i++) {
System.out.print(move[i] + ", "); //$NON-NLS-1$
}
System.out.println();
throw e;
}
}
static PositionTracker __testMove(int[] buffer, int[] move) {
PositionTracker tracker = new PositionTracker();
for (int i = 0; i < buffer.length; i++) {
buffer[i] = i;
}
doMove(buffer, move, tracker);
check(tracker, buffer);
return tracker;
}
static void doMove(int[] buffer, int[] move, PositionTracker tracker) {
for (int i = 0; i < move.length; i += 2) {
int m1 = move[i];
int m2 = move[i + 1];
if (m1 == -1) {
break;
}
if (m2 > 0) {
tracker.insert(m1, m2);
for (int j = 0; j < buffer.length; j++) {
if (buffer[j] >= m1) {
buffer[j] += m2;
}
}
} else {
tracker.delete(m1, -m2);
int m3 = m1 - m2;
for (int j = 0; j < buffer.length; j++) {
if (buffer[j] >= m1) {
if (buffer[j] < m3) {
buffer[j] = -1;
} else {
buffer[j] += m2;
}
}
}
}
}
}
private static void check(PositionTracker tracker, int[] buffer) {
int lasti2 = -1;
for (int i = 0; i < buffer.length; i++) {
int i2 = buffer[i];
if (i2 >= 0) {
int i22 = tracker.currentOffset(i);
assertEquals(i22, i2);
assertTrue(lasti2 < i22);
lasti2 = i22;
assertEquals(i, tracker.historicOffset(i2));
}
}
}
private static boolean nextMove(int[] move, int bufLen) {
for (int i = 0; i < move.length; i += 2) {
int m1 = move[i];
if (m1 < 0) {
move[i] = 0;
move[i + 1] = -bufLen;
return true;
}
int m2 = ++move[i + 1];
if (m2 <= bufLen - m1) {
return true;
}
if (m1 < bufLen - 1) {
move[i]++;
move[i + 1] = -bufLen + m1 + 1;
return true;
}
move[i] = 0;
move[i + 1] = -bufLen;
}
return false;
}
public void testInsertion() {
PositionTracker pt= new PositionTracker();
pt.insert(1,1);
checkInsert11(pt);
}
private void checkInsert11(PositionTracker pt) {
// chars
doubleCheck(pt, 0, 0);
backwdCheck(pt, 1, 1);
doubleCheck(pt, 1, 2);
doubleCheck(pt, 2, 3);
// ranges
doubleRangeCheck(pt, new Position(0,2), new Position(0,3));
backwdRangeCheck(pt, new Position(0,1), new Position(0,2));
doubleRangeCheck(pt, new Position(0,1), new Position(0,1));
backwdRangeCheck(pt, new Position(1,0), new Position(1,1));
backwdRangeCheck(pt, new Position(1,0), new Position(1,0));
doubleRangeCheck(pt, new Position(1,1), new Position(2,1));
doubleRangeCheck(pt, new Position(1,0), new Position(2,0));
}
public void testDeletion() {
PositionTracker pt= new PositionTracker();
pt.delete(1,1);
checkDelete11(pt);
}
private void checkDelete11(PositionTracker pt) {
doubleCheck(pt, 0, 0);
fwdCheck (pt, 1, 1);
doubleCheck(pt, 2, 1);
doubleCheck(pt, 3, 2);
// ranges
doubleRangeCheck(pt, new Position(0,3), new Position(0,2));
fwdRangeCheck (pt, new Position(0,2), new Position(0,1));
doubleRangeCheck(pt, new Position(0,1), new Position(0,1));
fwdRangeCheck (pt, new Position(1,1), new Position(1,0));
fwdRangeCheck (pt, new Position(1,0), new Position(1,0));
doubleRangeCheck(pt, new Position(2,1), new Position(1,1));
doubleRangeCheck(pt, new Position(2,0), new Position(1,0));
}
public void testReplace() {
PositionTracker pt= new PositionTracker();
pt.delete(1,1);
pt.insert(1,1);
doubleCheck(pt, 0, 0);
doubleCheck(pt, 1, 1);
doubleCheck(pt, 2, 2);
doubleCheck(pt, 3, 3);
pt.clear();
pt.insert(1,1);
pt.delete(1,1);
doubleCheck(pt, 0, 0);
doubleCheck(pt, 1, 1);
doubleCheck(pt, 2, 2);
doubleCheck(pt, 3, 3);
pt.clear();
pt.delete(0,2);
pt.insert(0,1);
checkDelete11(pt);
pt.clear();
pt.insert(1,1);
pt.delete(1,2);
checkDelete11(pt);
pt.clear();
pt.insert(1,2);
pt.delete(1,1);
checkInsert11(pt);
pt.clear();
pt.delete(1,1);
pt.insert(1,2);
checkInsert11(pt);
}
private void doubleCheck(PositionTracker pt, int orig, int mapped) {
fwdCheck(pt, orig, mapped);
backwdCheck(pt, orig, mapped);
}
private void fwdCheck(PositionTracker pt, int orig, int mapped) {
assertEquals(mapped, pt.currentOffset(orig));
}
private void backwdCheck(PositionTracker pt, int orig, int mapped) {
assertEquals(orig, pt.historicOffset(mapped));
}
private void doubleRangeCheck(PositionTracker pt, Position orig, Position mapped) {
fwdRangeCheck(pt, orig, mapped);
backwdRangeCheck(pt, orig, mapped);
}
private void fwdRangeCheck(PositionTracker pt, Position orig, Position mapped) {
assertEquals(mapped, pt.historicToActual(orig));
}
private void backwdRangeCheck(PositionTracker pt, Position orig, Position mapped) {
assertEquals(orig, pt.actualToHistoric(mapped));
}
}

View file

@ -7,6 +7,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
* Markus Schorn (Wind River Systems)
*******************************************************************************/
/*
* Created on May 16, 2003
@ -20,6 +21,7 @@ import junit.framework.Test;
import junit.framework.TestSuite;
import org.eclipse.cdt.core.cdescriptor.tests.CDescriptorTests;
import org.eclipse.cdt.core.internal.tests.PositionTrackerTests;
import org.eclipse.cdt.core.model.tests.AllCoreTests;
import org.eclipse.cdt.core.model.tests.BinaryTests;
import org.eclipse.cdt.core.model.tests.ElementDeltaTests;
@ -62,6 +64,7 @@ public class AutomatedIntegrationSuite extends TestSuite {
suite.addTest(BinaryTests.suite());
suite.addTest(ElementDeltaTests.suite());
suite.addTest(WorkingCopyTests.suite());
suite.addTest(PositionTrackerTests.suite());
// TODO turning off indexer/search tests until the PDOM
// settles. These'll probably have to be rewritten anyway.

View file

@ -5,7 +5,7 @@
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="utils"/>
<classpathentry kind="src" path="parser"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.4"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View file

@ -73,5 +73,7 @@ Export-Package: org.eclipse.cdt.core,
Require-Bundle: org.eclipse.core.resources,
org.eclipse.core.runtime,
org.eclipse.text,
org.eclipse.core.variables
org.eclipse.core.variables,
org.eclipse.core.filebuffers
Eclipse-LazyStart: true
Bundle-RequiredExecutionEnvironment: J2SE-1.4

View file

@ -7,6 +7,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
* Markus Schorn (Wind River Systems)
*******************************************************************************/
package org.eclipse.cdt.core;
@ -34,6 +35,7 @@ import org.eclipse.cdt.core.resources.ScannerProvider;
import org.eclipse.cdt.internal.core.CDTLogWriter;
import org.eclipse.cdt.internal.core.CDescriptorManager;
import org.eclipse.cdt.internal.core.PathEntryVariableManager;
import org.eclipse.cdt.internal.core.PositionTrackerManager;
import org.eclipse.cdt.internal.core.model.BufferManager;
import org.eclipse.cdt.internal.core.model.CModelManager;
import org.eclipse.cdt.internal.core.model.DeltaProcessor;
@ -213,6 +215,10 @@ public class CCorePlugin extends Plugin {
return fgResourceBundle;
}
public static IPositionTrackerManager getPositionTrackerManager() {
return PositionTrackerManager.getInstance();
}
public static CCorePlugin getDefault() {
return fgCPlugin;
}
@ -241,6 +247,8 @@ public class CCorePlugin extends Plugin {
*/
public void stop(BundleContext context) throws Exception {
try {
PositionTrackerManager.getInstance().uninstall();
if (fDescriptorManager != null) {
fDescriptorManager.shutdown();
}
@ -291,6 +299,7 @@ public class CCorePlugin extends Plugin {
// Set the default for using the structual parse mode to build the CModel
getPluginPreferences().setDefault(PREF_USE_STRUCTURAL_PARSE_MODE, false);
PositionTrackerManager.getInstance().install();
}

View file

@ -0,0 +1,52 @@
/*******************************************************************************
* Copyright (c) 2006 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.core;
import org.eclipse.jface.text.Position;
/**
* Allows for converting character ranges of files previously stored on disk to the
* range where the characters are found in the current version of the file. The
* current version can be the content of a dirty editor, or if there is none, the
* latest verison of the file as stored on disk.
*
* As long as the underlying text of the character range has not been modified the
* converted range will have the same underlying text. Insertions at the beginning
* or the end of the text do not added to the converted range.
*
* An insertion inside the underlying text will increase the length of the converted
* range, a deletion of one of the characters will decrease it.
*
* An deletion followed by an insertion without saving the file inbetween, will cancel
* the deletion as much as possible.
*/
public interface IPositionConverter {
/**
* Converts an actual character range to the range where the underlying text
* was originally found.
* @param actualPosition a range as found in the current text buffer for the file.
* @return a range suitable for the version of the file for which the converter
* was obtained.
*/
Position actualToHistoric(Position actualPosition);
/**
* Converts a historic character range to the range where the underlying text
* currently can be found.
* @param historicPosition a range as found in the version of the file for which
* the converter was obtained.
* @return a range suitable for the current text buffer of the file.
*
*/
Position historicToActual(Position historicPosition);
}

View file

@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright (c) 2006 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.core;
import org.eclipse.core.resources.IFile;
/**
* An interface to manage the position tracking that allows for mapping character
* offsets from a file previously stored on disk to the current offset.
*/
public interface IPositionTrackerManager {
/**
* Returns the position tracker suitable for mapping character offsets of the
* given file/timestamp to the current version of it.
*
* @param file a file for which the position adapter is requested.
* @param timestamp identifies the version of the file stored on disk.
* @return the requested position adapter or <code>null</code>.
*/
IPositionConverter findPositionAdapter(IFile file, long timestamp);
}

View file

@ -0,0 +1,600 @@
/*******************************************************************************
* Copyright (c) 2006 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.Position;
/**
* 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;
}
/**
* Undo 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 acoount 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();
}
public synchronized Position actualToHistoric(Position 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 Position(historic, len);
}
public synchronized Position historicToActual(Position 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 Position(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; // lenght 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;
}
}
}

View file

@ -0,0 +1,206 @@
/*******************************************************************************
* Copyright (c) 2006 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.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
class PositionTrackerChain implements IDocumentListener {
public static final int LINKED_LIST_SIZE = 64;
public static final int LINKED_LIST_ENTRY_SIZE = 32;
public static int MEMORY_SIZE= 32 + LINKED_LIST_SIZE;
private static final int MAX_DEPTH = 100; // 100 saves
private static final long MAX_AGE = 24 * 60 * 60 * 1000; // one day
private LinkedList fTrackers= new LinkedList();
private PositionTracker fActiveTracker;
private IDocument fDocument;
public PositionTrackerChain(long timestamp) {
createCheckpoint(timestamp);
}
public int createCheckpoint(long timestamp) {
// travel in time
while (fActiveTracker != null && fActiveTracker.getTimeStamp() >= timestamp) {
fTrackers.removeLast();
if (fTrackers.isEmpty()) {
fActiveTracker= null;
}
else {
fActiveTracker= (PositionTracker) fTrackers.getLast();
fActiveTracker.revive();
}
}
int retiredMemsize= 0;
PositionTracker newTracker= new PositionTracker();
newTracker.setTimeStamp(timestamp);
fTrackers.add(newTracker);
if (fActiveTracker != null) {
fActiveTracker.retire(newTracker);
retiredMemsize= fActiveTracker.getMemorySize() + LINKED_LIST_ENTRY_SIZE;
}
fActiveTracker= newTracker;
checkTrackerLimits();
return retiredMemsize;
}
private void checkTrackerLimits() {
while (fTrackers.size() >= MAX_DEPTH) {
fTrackers.removeFirst();
}
long minTimeStamp= fActiveTracker.getTimeStamp() - MAX_AGE;
for (Iterator iter = fTrackers.iterator(); iter.hasNext();) {
PositionTracker tracker= (PositionTracker) iter.next();
if (tracker.getRetiredTimeStamp() >= minTimeStamp) {
break;
}
iter.remove();
}
}
/* (non-Javadoc)
* @see org.eclipse.jface.text.IPositionUpdater#update(org.eclipse.jface.text.DocumentEvent)
*/
private void update(DocumentEvent event) {
String text = event.getText();
int insertLen = text != null ? text.length() : 0;
update(event.getOffset(), event.getLength(), insertLen);
}
void update(int offset, int deleteLen, int insertLen) {
if (insertLen > deleteLen) {
fActiveTracker.insert(offset + deleteLen, insertLen - deleteLen);
} else if (insertLen < deleteLen) {
fActiveTracker.delete(offset+insertLen, deleteLen - insertLen);
}
}
/**
* Find the nearest tracker created at or after the given time.
* @param timestamp in milliseconds.
* @return the tracker nearest to the timestamp, <code>null</code> if all were created before.
*/
public PositionTracker findTrackerAtOrAfter(long timestamp) {
PositionTracker candidate= null;
for (ListIterator iter = fTrackers.listIterator(fTrackers.size()); iter.hasPrevious();) {
PositionTracker tracker = (PositionTracker) iter.previous();
long trackerTimestamp= tracker.getTimeStamp();
if (trackerTimestamp >= timestamp) {
candidate= tracker;
}
else {
break;
}
}
return candidate;
}
/**
* Find the tracker created at the given time.
* @param timestamp in milliseconds.
* @return the tracker at the timestamp, <code>null</code> if none created at the given time.
*/
public PositionTracker findTrackerAt(long timestamp) {
for (ListIterator iter = fTrackers.listIterator(fTrackers.size()); iter.hasPrevious();) {
PositionTracker tracker = (PositionTracker) iter.previous();
long trackerTimestamp= tracker.getTimeStamp();
if (trackerTimestamp == timestamp) {
return tracker;
}
if (trackerTimestamp < timestamp) {
return null;
}
}
return null;
}
/**
* Destroy the tracker.
*/
public void dispose() {
stopTracking();
fTrackers= null;
fActiveTracker= null;
}
public void startTracking(IDocument doc) {
stopTracking();
fDocument= doc;
if (fDocument != null) {
fDocument.addDocumentListener(this);
}
}
public void stopTracking() {
if (fDocument != null) {
fDocument.removeDocumentListener(this);
fDocument= null;
}
}
public void documentAboutToBeChanged(DocumentEvent event) {
update(event);
}
public void documentChanged(DocumentEvent event) {
// react before updateing the document
}
public IDocument getCurrentDocument() {
return fDocument;
}
public PositionTracker getActiveTracker() {
return fActiveTracker;
}
public boolean isModified() {
return fTrackers.size() > 1 || fActiveTracker.isModified();
}
public int getMemorySize() {
int size= MEMORY_SIZE;
for (Iterator iter = fTrackers.iterator(); iter.hasNext();) {
PositionTracker tracker = (PositionTracker) iter.next();
size+= LINKED_LIST_ENTRY_SIZE;
size+= tracker.getMemorySize();
}
return size;
}
public int removeOldest() {
int memdiff= 0;
if (fTrackers.size() > 1) {
PositionTracker tracker= (PositionTracker) fTrackers.removeFirst();
memdiff= tracker.getMemorySize() + LINKED_LIST_ENTRY_SIZE;
tracker.clear();
}
return -memdiff;
}
public long getOldestRetirement() {
if (fTrackers.size() > 1) {
PositionTracker tracker= (PositionTracker) fTrackers.getFirst();
return tracker.getRetiredTimeStamp();
}
return Long.MAX_VALUE;
}
}

View file

@ -0,0 +1,178 @@
/*******************************************************************************
* Copyright (c) 2006 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.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import org.eclipse.cdt.core.IPositionConverter;
import org.eclipse.cdt.core.IPositionTrackerManager;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.IFileBuffer;
import org.eclipse.core.filebuffers.IFileBufferListener;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IPath;
public class PositionTrackerManager implements IPositionTrackerManager, IFileBufferListener {
private static final int HASHMAP_ENTRY_SIZE = 56;
private static final int MAX_MEMORY= 1024*512; // 512 kbytes
private static final int MAX_MEMORY_AFTER_CLEANUP= (MAX_MEMORY * 7) / 10; // 70% of MAX_MEMORY
private static PositionTrackerManager sManager= new PositionTrackerManager();
public static PositionTrackerManager getInstance() {
return sManager;
}
private int fMemoryCounter= 0;
private int fInstalled= 0;
private HashMap fPositionTrackerMap;
private PositionTrackerManager() {
fPositionTrackerMap= new HashMap();
}
public synchronized void install() {
if (++fInstalled == 1) {
ITextFileBufferManager mgr= FileBuffers.getTextFileBufferManager();
mgr.addFileBufferListener(this);
}
}
public synchronized void uninstall() {
if (--fInstalled == 0) {
FileBuffers.getTextFileBufferManager().removeFileBufferListener(this);
fPositionTrackerMap.clear();
fMemoryCounter= 0;
}
}
public void bufferCreated(IFileBuffer buffer) {
if (buffer instanceof ITextFileBuffer) {
createCheckpoint((ITextFileBuffer) buffer);
}
}
public void bufferDisposed(IFileBuffer buffer) {
if (buffer instanceof ITextFileBuffer) {
resetToLastCheckpoint((ITextFileBuffer) buffer);
}
}
public void dirtyStateChanged(IFileBuffer buffer, boolean isDirty) {
if (!isDirty && buffer instanceof ITextFileBuffer) {
createCheckpoint((ITextFileBuffer) buffer);
}
}
public void stateValidationChanged(IFileBuffer buffer, boolean isStateValidated) {
if (isStateValidated && !buffer.isDirty()) {
bufferCreated(buffer);
}
}
public void bufferContentAboutToBeReplaced(IFileBuffer buffer) {}
public void bufferContentReplaced(IFileBuffer buffer) {}
public void underlyingFileMoved(IFileBuffer buffer, IPath path) {}
public void underlyingFileDeleted(IFileBuffer buffer) {}
public void stateChangeFailed(IFileBuffer buffer) {}
public void stateChanging(IFileBuffer buffer) {}
private synchronized void createCheckpoint(ITextFileBuffer buffer) {
PositionTrackerChain chain= getChain(buffer);
if (chain == null) {
chain = new PositionTrackerChain(buffer.getModificationStamp());
fPositionTrackerMap.put(buffer.getLocation(), chain);
fMemoryCounter+= PositionTrackerChain.MEMORY_SIZE + HASHMAP_ENTRY_SIZE;
}
else {
chain.stopTracking();
fMemoryCounter+= chain.createCheckpoint(buffer.getModificationStamp());
}
chain.startTracking(buffer.getDocument());
if (fMemoryCounter > MAX_MEMORY) {
runCleanup();
}
}
private synchronized PositionTrackerChain getChain(ITextFileBuffer buffer) {
return (PositionTrackerChain) fPositionTrackerMap.get(buffer.getLocation());
}
private synchronized void resetToLastCheckpoint(ITextFileBuffer buffer) {
PositionTrackerChain chain= getChain(buffer);
if (chain != null) {
chain.stopTracking();
chain.getActiveTracker().clear();
if (!chain.isModified()) {
fPositionTrackerMap.remove(buffer.getLocation());
chain.dispose();
}
}
}
private synchronized void runCleanup() {
fMemoryCounter= 0;
for (Iterator iter = fPositionTrackerMap.values().iterator(); iter.hasNext();) {
PositionTrackerChain chain= (PositionTrackerChain) iter.next();
fMemoryCounter+= HASHMAP_ENTRY_SIZE;
fMemoryCounter+= chain.getMemorySize();
}
if (fMemoryCounter > MAX_MEMORY_AFTER_CLEANUP) {
SortedMap map= new TreeMap();
for (Iterator iter = fPositionTrackerMap.values().iterator(); iter.hasNext();) {
PositionTrackerChain chain = (PositionTrackerChain) iter.next();
addChain(map, chain);
}
while (!map.isEmpty()) {
Long key= (Long) map.firstKey();
List list= (List) map.remove(key);
for (Iterator iter = list.iterator(); iter.hasNext();) {
PositionTrackerChain chain = (PositionTrackerChain) iter.next();
fMemoryCounter+= chain.removeOldest();
addChain(map, chain);
}
if (fMemoryCounter <= MAX_MEMORY_AFTER_CLEANUP) {
break;
}
}
}
}
private synchronized void addChain(SortedMap map, PositionTrackerChain chain) {
long or= chain.getOldestRetirement();
if (or != Long.MAX_VALUE) {
Long lor= new Long(or);
List list= (List) map.get(lor);
if (list == null) {
list= new LinkedList();
map.put(lor, list);
}
list.add(chain);
}
}
public synchronized IPositionConverter findPositionAdapter(IFile file, long timestamp) {
PositionTrackerChain chain= (PositionTrackerChain) fPositionTrackerMap.get(file.getFullPath());
if (chain != null) {
return chain.findTrackerAt(timestamp);
}
return null;
}
}