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; + } +}