(str);
- }
- return str;
- }
-
- @Override
- public final int length() {
- final int result = myCount + myDeferredShift;
- if (myDebug && isDeferredChangeMode()) {
- int expected = myDebugArray.length();
- if (expected != result) {
- dumpDebugInfo("Incorrect length() processing. Expected: '" + expected + "', actual: '" + result + "'");
- }
- }
- return result;
- }
-
- @Override
- public final char charAt(int i) {
- if (i < 0 || i >= length()) {
- throw new IndexOutOfBoundsException("Wrong offset: " + i + "; count:" + length());
- }
- i += myStart;
- final char result;
- if (!myHasDeferredChanges) {
- if (myOriginalSequence != null) {
- result = myOriginalSequence.charAt(i);
- }
- else {
- result = myArray[i];
- }
- }
- else {
- result = myDeferredChangesStorage.charAt(myArray, i);
- }
-
- if (myDebug && isDeferredChangeMode()) {
- char expected = myDebugArray.charAt(i);
- if (expected != result) {
- dumpDebugInfo("Incorrect charAt() processing for index " + i + ". Expected: '" + expected + "', actual: '" + result + "'");
- }
- }
- return result;
- }
-
- @Override
- @NotNull
- public CharSequence subSequence(final int start, final int end) {
- assertReadAccess();
- assertConsistency();
- if (start == 0 && end == length()) return this;
- if (myOriginalSequence != null) {
- return myOriginalSequence.subSequence(start, end);
- }
- flushDeferredChanged();
- return new CharArrayCharSequence(myArray, start, end);
- }
-
- @Override
- @NotNull
- public char[] getChars() {
- assertReadAccess();
- assertConsistency();
- char[] array = myArray;
- CharSequence originalSequence = myOriginalSequence;
- if (myHasDeferredChanges || originalSequence != null && array == null) {
- // slow track
- synchronized (lock) {
- flushDeferredChanged();
- array = myArray;
- originalSequence = myOriginalSequence;
- if (originalSequence != null && array == null) {
- myArray = array = CharArrayUtil.fromSequence(originalSequence);
- myStringRef = null;
- }
- }
- assertConsistency();
- }
- return array;
- }
-
- @Override
- public void getChars(@NotNull final char[] dst, final int dstOffset) {
- assertReadAccess();
- assertConsistency();
- flushDeferredChanged();
- if (myOriginalSequence == null) {
- System.arraycopy(myArray, myStart, dst, dstOffset, length());
- }
- else {
- CharArrayUtil.getChars(myOriginalSequence, dst, dstOffset);
- }
-
- if (myDebug && isDeferredChangeMode()) {
- char[] expected = new char[dst.length];
- myDebugArray.getChars(expected, dstOffset);
- for (int i = dstOffset, j = myStart; i < dst.length && j < myArray.length; i++, j++) {
- if (expected[i] != myArray[j]) {
- dumpDebugInfo("getChars(char[], int). Given array of length " + dst.length + ", offset " + dstOffset + ". Found char '" + myArray[j] +
- "' at index " + i + ", expected to find '" + expected[i] + "'");
- break;
- }
- }
- }
- }
-
- @NotNull
- public CharSequence substring(final int start, final int end) {
- assertReadAccess();
- final CharSequence result = doSubString(start, end);
-
- assertConsistency();
- return result;
- }
-
- private CharSequence doSubString(int start, int end) {
- if (start == end) return "";
- final CharSequence result;
- if (myOriginalSequence == null) {
- result = myDeferredChangesStorage.substring(myArray, start + myStart, end + myStart);
- }
- else {
- result = myOriginalSequence.subSequence(start, end);
- }
- return result;
- }
-
- @NotNull
- private static char[] resizeArray(@NotNull char[] array, int newSize) {
- if (newSize < array.length) {
- return array;
- }
-
- int newArraySize = array.length;
- if (newArraySize == 0) {
- newArraySize = 16;
- }
- while (newArraySize <= newSize) {
- newArraySize = newArraySize * 12 / 10 + 1;
- }
- char[] newArray = new char[newArraySize];
- System.arraycopy(array, 0, newArray, 0, array.length);
- return newArray;
- }
-
- private void trimToSize() {
- if (myBufferSize != 0 && length() > myBufferSize) {
- flushDeferredChanged();
-
- // make a copy
- int endIndex = myCount - myBufferSize;
- String toDelete = getCharArray().subSequence(0, endIndex).toString();
- remove(0, endIndex, toDelete);
- }
- }
-
- /**
- * @return true if this object is in the defer changes mode, see {@link #setDeferredChangeMode(boolean)};
- */
- public boolean isDeferredChangeMode() {
- return myDeferredChangeMode;
- }
-
- public boolean hasDeferredChanges() {
- return myHasDeferredChanges;
- }
-
- /**
- * There is a possible case that client of this class wants to perform great number of modifications in a short amount of time
- * (e.g. end-user performs formatting of the document backed by the object of the current class). It may result in significant
- * performance degradation is the changes are performed one by one (every time the change is applied tail content is shifted to
- * the left or right). So, we may want to optimize that by avoiding actual array modification until information about
- * all target changes is provided and perform array data moves only after that.
- *
- * This method allows to define that 'defer changes' mode usages, i.e. expected usage pattern is as follows:
- *
- *
- * -
- * Client of this class enters
'defer changes' mode (calls this method with 'true' argument).
- * That means that all subsequent changes will not actually modify backed array data and will be stored separately;
- *
- * -
- * Number of target changes are applied to the current object via standard API
- * ({@link #insert(CharSequence, int) insert},
- * {@link #remove(int, int, CharSequence) remove} and
- * {@link #replace(int, int, java.lang.CharSequence, java.lang.CharSequence, long, boolean)});
- *
- * -
- * Client of this class indicates that
'massive change time' is over by calling this method with 'false'
- * argument. That flushes all deferred changes (if any) to the backed data array and makes every subsequent change to
- * be immediate flushed to the backed array;
- *
- *
- *
- *
- * Note: we can't exclude possibility that 'defer changes' mode is started but inadvertently not ended
- * (due to programming error, unexpected exception etc). Hence, this class is free to automatically end
- * 'defer changes' mode when necessary in order to avoid memory leak with infinite deferred changes storing.
- *
- * @param deferredChangeMode flag that defines if 'defer changes' mode should be used by the current object
- */
- public void setDeferredChangeMode(boolean deferredChangeMode) {
- if (!DISABLE_DEFERRED_PROCESSING) {
- if (deferredChangeMode) {
- if (myDebug) {
- myDebugArray.setText(myDebugTextOnBatchUpdateStart = toString());
- myDebugDeferredChanges.clear();
- }
- prepareForModification();
- }
- else {
- flushDeferredChanged();
- }
- myDeferredChangeMode = deferredChangeMode;
- }
- assertConsistency();
- }
-
- private void flushDeferredChanged() {
- List changes = myDeferredChangesStorage.getChanges();
- if (changes.isEmpty()) {
- return;
- }
-
- synchronized (lock) {
- char[] beforeMerge = null;
- if (myDebug) {
- beforeMerge = new char[myArray.length];
- System.arraycopy(myArray, 0, beforeMerge, 0, myArray.length);
- }
-
- BulkChangesMerger changesMerger = BulkChangesMerger.INSTANCE;
- final boolean inPlace;
- if (myArray.length < length()) {
- myArray = changesMerger.mergeToCharArray(myArray, myCount, changes);
- inPlace = false;
- }
- else {
- changesMerger.mergeInPlace(myArray, myCount, changes);
- inPlace = true;
- }
-
- myCount += myDeferredShift;
- myDeferredShift = 0;
- myDeferredChangesStorage.clear();
- myHasDeferredChanges = false;
- myDeferredChangeMode = false;
- myStringRef = null;
-
- if (myDebug) {
- for (int i = 0, max = length(); i < max; i++) {
- if (myArray[i] != myDebugArray.myArray[i]) {
- dumpDebugInfo("flushDeferredChanged(). Index " + i + ", expected: '" + myDebugArray.myArray[i]+"', actual '" +
- myArray[i]+"'. Text before merge: '" + Arrays.toString(beforeMerge)+"', merge inplace: "+inPlace);
- break;
- }
- }
- }
- }
- assertConsistency();
- }
-
- @Override
- @NonNls
- @NotNull
- public String dumpState() {
- return "deferred changes mode: " + isDeferredChangeMode()+", length: " + length()+" (data array length: " + myCount+
- ", deferred shift: " + myDeferredShift+"); view offsets: [" + myStart+"; "+myCount+"]; deferred changes: "+myDeferredChangesStorage;
- }
-
- private void checkStrings(@NonNls @NotNull String operation, @NotNull String expected, @NotNull CharSequence actual) {
- if (StringUtil.equals(expected, actual)) {
- return;
- }
- for (int i = 0, max = Math.min(expected.length(), actual.length()); i < max; i++) {
- if (actual.charAt(i) != expected.charAt(i)) {
- dumpDebugInfo(
- "Incorrect " +
- operation+" processing. Expected length: " +
- expected.length()+", actual length: " +
- actual.length()+". Unmatched symbol at " +
- i+" - expected: '" +
- expected.charAt(i)+"', " +
- "actual: '" +
- actual.charAt(i)+"', expected document: '" +
- expected+"', actual document: '" +
- actual+"'"
- );
- return;
- }
- }
- dumpDebugInfo("Incorrect " + operation+" processing. Expected length: " + expected.length()+", actual length: " +
- actual.length()+", expected: '" + expected+"', actual: '" + actual+"'");
- }
-
- private void dumpDebugInfo(@NonNls @NotNull String problem) {
- LOG.error(
- "Incorrect CharArray processing detected: " + problem +
- ". Start: " + myStart
- + ", count: " + myCount + ", text on batch update start: " +
- myDebugTextOnBatchUpdateStart + ", deferred changes history: " +
- myDebugDeferredChanges + ", current deferred changes: " + myDeferredChangesStorage
- );
- }
-}
diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/CharArrayTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/CharArrayTest.java
deleted file mode 100644
index 20357ba9d1e5..000000000000
--- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/CharArrayTest.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * Copyright 2000-2011 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.intellij.openapi.editor.impl;
-
-import com.intellij.openapi.editor.event.DocumentEvent;
-import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
-import com.intellij.openapi.util.Pair;
-import com.intellij.util.LocalTimeCounter;
-import com.intellij.util.containers.Stack;
-import com.intellij.util.text.CharSequenceBackedByArray;
-import org.jetbrains.annotations.NonNls;
-import org.jetbrains.annotations.NotNull;
-import org.jmock.Expectations;
-import org.jmock.Mockery;
-import org.jmock.api.Invocation;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.action.CustomAction;
-import org.jmock.lib.legacy.ClassImposteriser;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import static org.junit.Assert.*;
-
-/**
- * @author Denis Zhdanov
- * @since 03/01/2011
- */
-public class CharArrayTest {
- @Rule
- public TestWatcher configReader = new TestWatcher() {
- @Override
- protected void starting(Description description) {
- Config config = description.getAnnotation(Config.class);
- if (config != null) {
- myConfig = config;
- }
- }
- };
-
- private CharArray myArray;
- private Config myConfig;
- private Mockery myMockery;
- private DocumentImpl myDocument;
-
- @Before
- public void setUp() {
- myMockery = new JUnit4Mockery() {{
- setImposteriser(ClassImposteriser.INSTANCE);
- }};
- myDocument = myMockery.mock(DocumentImpl.class);
-
- myMockery.checking(new Expectations() {{
- allowing(myDocument).getTextLength(); will(new CustomAction("getTextLength") {
- @Override
- public Object invoke(Invocation invocation) throws Throwable {
- return myArray.length();
- }
- });
- }});
-
- init(10);
- if (myConfig != null) {
- myArray.insert(myConfig.text(), 0);
- myArray.setDeferredChangeMode(myConfig.deferred());
- }
- }
-
- @After
- public void checkExpectations() {
- myMockery.assertIsSatisfied();
- }
-
- @Config(text = "1234", deferred = true)
- @Test
- public void deferredReplace() {
- replace(1, 3, "abc");
- assertTrue(myArray.hasDeferredChanges());
- checkText("1abc4");
-
- replace(2, 3, "XY");
- checkText("1aXYc4");
-
- replace(3, 6, "ABC");
- checkText("1aXABC");
-
- myArray.setDeferredChangeMode(false);
- checkText("1aXABC");
- }
-
- @Config(text = "01234567", deferred = true)
- @Test
- public void subSequenceWithDeferredChangeBeforeIt() {
- replace(0, 2, "abc");
- CharSequenceBackedByArray subsSequence = (CharSequenceBackedByArray)myArray.subSequence(5, 6);
- assertArrayEquals("4".toCharArray(), subsSequence.getChars());
- }
-
- @Config(text = "01234567", deferred = true)
- @Test
- public void subSequenceWithDeferredChangeIntersectingFromLeft() {
- replace(0, 2, "abc");
- CharSequenceBackedByArray subsSequence = (CharSequenceBackedByArray)myArray.subSequence(2, 4);
- assertArrayEquals("c2".toCharArray(), subsSequence.getChars());
- }
-
- @Config(text = "01234567", deferred = true)
- @Test
- public void subSequenceWithDeferredChangeIntersectingFromRight() {
- replace(4, 6, "abc");
- CharSequenceBackedByArray subsSequence = (CharSequenceBackedByArray)myArray.subSequence(3, 5);
- assertArrayEquals("3a".toCharArray(), subsSequence.getChars());
- }
-
- @Config(text = "01234567", deferred = true)
- @Test
- public void subSequenceWithDeferredChangeAfterIt() {
- replace(6, 8, "abc");
- CharSequenceBackedByArray subsSequence = (CharSequenceBackedByArray)myArray.subSequence(1, 2);
- assertArrayEquals("1".toCharArray(), subsSequence.getChars());
- }
-
- private void init(int size) {
- myArray = new CharArray(size, new char[0], 0) {
- @NotNull
- @Override
- protected DocumentEvent beforeChangedUpdate(int offset, CharSequence oldString, CharSequence newString,
- boolean wholeTextReplaced) {
- return new DocumentEventImpl(myDocument, offset, oldString, newString, LocalTimeCounter.currentTime(), wholeTextReplaced);
- }
-
- @Override
- protected void afterChangedUpdate(@NotNull DocumentEvent event, long newModificationStamp) {
- }
-
- @Override
- protected void assertWriteAccess() {
- }
-
- @Override
- protected void assertReadAccess() {
- }
- };
- }
-
- private void checkText(@NonNls @NotNull String expected) {
- // Test as a whole.
- assertEquals(expected, myArray.toString());
- assertEquals(expected.length(), myArray.length());
-
- // Test 'charAt()'.
- for (int i = 0; i < expected.length(); i++) {
- if (expected.charAt(i) != myArray.charAt(i)) {
- fail(String.format(
- "Detected incorrect 'charAt()' processing for deferred changes. Text: '%1$s'. Expected to get symbol '%2$c' "
- + "(numeric value %2$d) at index %3$d but actual symbol is '%4$c' (numeric value %4$d)",
- expected, (int)expected.charAt(i), i, (int)myArray.charAt(i)));
- }
- assertEquals(expected.charAt(i), myArray.charAt(i));
- }
-
- // Test 'substring()'.
- for (int start = 0; start < myArray.length() - 1; start++) {
- for (int end = start; end < myArray.length(); end++) {
- if (!expected.substring(start, end).equals(myArray.substring(start, end).toString())) {
- fail(String.format(
- "Detected incorrect 'substring()' processing for deferred changes. Text: '%s', expected to get substring '%s' for "
- + "interval [%d; %d) but got '%s'", expected, expected.substring(start, end), start, end, myArray.substring(start, end)
- ));
- }
- }
- }
-
- // Test subSequence().
- checkSubSequence(expected, myArray, new Stack>());
- }
-
- private void checkSubSequence(@NotNull String expected, @NotNull CharSequence actual,
- @NotNull Stack> history) {
- assertEquals(expected.length(), actual.length());
- for (int i = 0; i < expected.length(); i++) {
- char expectedChar = expected.charAt(i);
- char actualChar = actual.charAt(i);
- if (expectedChar != actualChar) {
- fail(String.format(
- "Detected incorrect charAt() processing for result of subSequence() with deferred changes. Original text: '%s', "
- + "actual subSequence text: '%s', index: %d, expected symbol: '%c', actual symbol: '%c', subSequence history: %s",
- myArray.toString(), expected, i, expectedChar, actualChar, history
- ));
- }
- }
- if (!expected.equals(actual.toString())) {
- fail(String.format(
- "Detected incorrect toString() processing for result of subSequence() with deferred changes. Original text: '%s', "
- + "expected subSequence text: '%s', actual subSequence text: '%s', subSequence history: %s",
- myArray.toString(), expected, actual.toString(), history
- ));
- }
- assertEquals(expected, actual.toString());
- for (int start = 0; start < expected.length(); start++) {
- for (int end = start; end < expected.length(); end++) {
- history.push(new Pair(start, end));
- checkSubSequence(expected.substring(start, end), actual.subSequence(start, end), history);
- history.pop();
- }
- }
- }
-
- private void replace(int startOffset, int endOffset, @NonNls String newText) {
- myArray.replace(
- startOffset, endOffset, myArray.substring(startOffset, endOffset), newText, LocalTimeCounter.currentTime(),
- startOffset == 0 && endOffset == myArray.length()
- );
- }
-
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- private @interface Config {
- String text() default "";
- boolean deferred() default false;
- }
-}