diff --git a/core/org.eclipse.cdt.core.tests/META-INF/MANIFEST.MF b/core/org.eclipse.cdt.core.tests/META-INF/MANIFEST.MF
index f022affe839..fb68408aabe 100644
--- a/core/org.eclipse.cdt.core.tests/META-INF/MANIFEST.MF
+++ b/core/org.eclipse.cdt.core.tests/META-INF/MANIFEST.MF
@@ -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
diff --git a/core/org.eclipse.cdt.core.tests/misc/org/eclipse/cdt/core/internal/tests/PositionTrackerTests.java b/core/org.eclipse.cdt.core.tests/misc/org/eclipse/cdt/core/internal/tests/PositionTrackerTests.java
new file mode 100644
index 00000000000..da9f87d6f8e
--- /dev/null
+++ b/core/org.eclipse.cdt.core.tests/misc/org/eclipse/cdt/core/internal/tests/PositionTrackerTests.java
@@ -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));
+ }
+}
diff --git a/core/org.eclipse.cdt.core.tests/suite/org/eclipse/cdt/core/suite/AutomatedIntegrationSuite.java b/core/org.eclipse.cdt.core.tests/suite/org/eclipse/cdt/core/suite/AutomatedIntegrationSuite.java
index 1f6982d26db..56346be2a3d 100644
--- a/core/org.eclipse.cdt.core.tests/suite/org/eclipse/cdt/core/suite/AutomatedIntegrationSuite.java
+++ b/core/org.eclipse.cdt.core.tests/suite/org/eclipse/cdt/core/suite/AutomatedIntegrationSuite.java
@@ -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.
diff --git a/core/org.eclipse.cdt.core/.classpath b/core/org.eclipse.cdt.core/.classpath
index e687b150db4..c23d8a7724e 100644
--- a/core/org.eclipse.cdt.core/.classpath
+++ b/core/org.eclipse.cdt.core/.classpath
@@ -5,7 +5,7 @@
+
-
diff --git a/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF b/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF
index 91039c65447..a83285be7bd 100644
--- a/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF
+++ b/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF
@@ -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
diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/CCorePlugin.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/CCorePlugin.java
index a57acb761f0..4cc6799d924 100644
--- a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/CCorePlugin.java
+++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/CCorePlugin.java
@@ -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();
}
diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/IPositionConverter.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/IPositionConverter.java
new file mode 100644
index 00000000000..bb19a657eb3
--- /dev/null
+++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/IPositionConverter.java
@@ -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);
+}
diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/IPositionTrackerManager.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/IPositionTrackerManager.java
new file mode 100644
index 00000000000..93923bd76d2
--- /dev/null
+++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/IPositionTrackerManager.java
@@ -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 null
.
+ */
+ IPositionConverter findPositionAdapter(IFile file, long timestamp);
+}
diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/PositionTracker.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/PositionTracker.java
new file mode 100644
index 00000000000..ec5f40f818a
--- /dev/null
+++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/PositionTracker.java
@@ -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;
+ }
+ }
+}
diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/PositionTrackerChain.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/PositionTrackerChain.java
new file mode 100644
index 00000000000..193384e3bcd
--- /dev/null
+++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/PositionTrackerChain.java
@@ -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, null
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, null
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;
+ }
+}
diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/PositionTrackerManager.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/PositionTrackerManager.java
new file mode 100644
index 00000000000..0b5f6f96f68
--- /dev/null
+++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/core/PositionTrackerManager.java
@@ -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;
+ }
+}