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:
parent
6d99d620f3
commit
328e4c08f1
11 changed files with 1441 additions and 3 deletions
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue