mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
remove unused CharArray
This commit is contained in:
@@ -1,705 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2014 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.diagnostic.Dumpable;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.impl.ApplicationInfoImpl;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.event.DocumentEvent;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.LocalTimeCounter;
|
||||
import com.intellij.util.text.CharArrayCharSequence;
|
||||
import com.intellij.util.text.CharArrayUtil;
|
||||
import com.intellij.util.text.CharSequenceBackedByArray;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author cdr
|
||||
*/
|
||||
abstract class CharArray implements CharSequenceBackedByArray, Dumpable {
|
||||
private static final boolean CHECK_DOCUMENT_CONSISTENCY = ApplicationManager.getApplication() != null && ApplicationManager.getApplication().isUnitTestMode();
|
||||
private static final Logger LOG = Logger.getInstance("#" + CharArray.class.getName());
|
||||
|
||||
@SuppressWarnings("UseOfArchaicSystemPropertyAccessors")
|
||||
private static final boolean DISABLE_DEFERRED_PROCESSING = Boolean.getBoolean("idea.document.deny.deferred.changes");
|
||||
|
||||
@SuppressWarnings("UseOfArchaicSystemPropertyAccessors")
|
||||
private static final boolean DEBUG_DEFERRED_PROCESSING = LOG.isDebugEnabled() || Boolean.getBoolean("idea.document.debug.bulk.processing");
|
||||
/**
|
||||
* We can't exclude possibility of situation when <code>'defer changes'</code> state is {@link #setDeferredChangeMode(boolean) entered}
|
||||
* but not exited, hence, we want to perform automatic flushing if necessary in order to avoid memory leaks. This constant holds
|
||||
* a value that defines that 'automatic flushing' criteria, i.e. every time number of stored deferred changes exceeds this value,
|
||||
* they are automatically flushed.
|
||||
*/
|
||||
private static final int MAX_DEFERRED_CHANGES_NUMBER = 10000;
|
||||
|
||||
private final TextChangesStorage myDeferredChangesStorage;
|
||||
|
||||
private volatile int myStart; // start offset in myArray (used as an optimization when call substring())
|
||||
private volatile int myCount;
|
||||
|
||||
private volatile CharSequence myOriginalSequence;
|
||||
private volatile char[] myArray;
|
||||
private volatile Reference<String> myStringRef; // buffers String value - for not to generate it every time
|
||||
private volatile int myBufferSize;
|
||||
private volatile int myDeferredShift;
|
||||
private volatile boolean myDeferredChangeMode;
|
||||
private volatile boolean myHasDeferredChanges;
|
||||
// this lock is for mutual exclusion during read action access
|
||||
// (some fields are changed in read action too)
|
||||
private final Object lock = new String("myOriginalSequence");
|
||||
|
||||
// We had a problems with bulk document text processing, hence, debug facilities were introduced. The fields group below work with them.
|
||||
// The main idea is to hold all history of bulk processing iteration in order to be able to retrieve it from client and reproduce the
|
||||
// problem.
|
||||
|
||||
private final boolean myDebug = isDebug();
|
||||
|
||||
boolean isDebug() {
|
||||
return DEBUG_DEFERRED_PROCESSING || CHECK_DOCUMENT_CONSISTENCY && !ApplicationInfoImpl.isInPerformanceTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate instance of the current char array that is used during debug processing as follows - apply every text change
|
||||
* from the bulk changes group to this instance immediately in order to be able to check if the current 'deferred change-aware'
|
||||
* instance functionally behaves at the same way as 'straightforward' one.
|
||||
*/
|
||||
private CharArray myDebugArray;
|
||||
|
||||
/**
|
||||
* Holds deferred changes create during the current bulk processing iteration.
|
||||
*/
|
||||
private List<TextChangeImpl> myDebugDeferredChanges;
|
||||
|
||||
/**
|
||||
* Document text on bulk processing start.
|
||||
*/
|
||||
private String myDebugTextOnBatchUpdateStart;
|
||||
|
||||
// bufferSize == 0 means unbounded
|
||||
CharArray(final int bufferSize, @NotNull char[] data, int length) {
|
||||
myBufferSize = bufferSize;
|
||||
myDeferredChangesStorage = new TextChangesStorage();
|
||||
myArray = Arrays.copyOf(data, length);
|
||||
myCount = length;
|
||||
|
||||
if (myDebug) {
|
||||
myDebugArray = new CharArray(bufferSize, data, length) {
|
||||
@NotNull
|
||||
@Override
|
||||
protected DocumentEvent beforeChangedUpdate(int offset,
|
||||
CharSequence oldString,
|
||||
CharSequence newString,
|
||||
boolean wholeTextReplaced) {
|
||||
return CharArray.this.beforeChangedUpdate(offset, oldString, newString, wholeTextReplaced);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterChangedUpdate(@NotNull DocumentEvent event, long newModificationStamp) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertWriteAccess() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertReadAccess() {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isDebug() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
myDebugDeferredChanges = new ArrayList<TextChangeImpl>();
|
||||
}
|
||||
assertConsistency();
|
||||
}
|
||||
|
||||
public void setBufferSize(int bufferSize) {
|
||||
assert bufferSize >= 0 : bufferSize;
|
||||
myBufferSize = bufferSize;
|
||||
assertConsistency();
|
||||
}
|
||||
|
||||
private DocumentEvent startChange(int offset,
|
||||
@Nullable CharSequence oldString,
|
||||
@Nullable CharSequence newString,
|
||||
boolean wholeTextReplaced) {
|
||||
assert myStart == 0; // can't change substring
|
||||
assertWriteAccess();
|
||||
assertConsistency();
|
||||
|
||||
return beforeChangedUpdate(offset, oldString, newString, wholeTextReplaced);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected abstract DocumentEvent beforeChangedUpdate(int offset,
|
||||
@Nullable CharSequence oldString,
|
||||
@Nullable CharSequence newString,
|
||||
boolean wholeTextReplaced);
|
||||
protected abstract void afterChangedUpdate(@NotNull DocumentEvent event, long newModificationStamp);
|
||||
|
||||
protected abstract void assertWriteAccess();
|
||||
protected abstract void assertReadAccess();
|
||||
|
||||
private void setText(@NotNull CharSequence chars) {
|
||||
assertConsistency();
|
||||
myOriginalSequence = chars.toString();
|
||||
myArray = null;
|
||||
myStringRef = null;
|
||||
myCount = chars.length();
|
||||
assert myStart == 0; // can't change substring
|
||||
myDeferredChangesStorage.clear();
|
||||
myHasDeferredChanges = false;
|
||||
trimToSize();
|
||||
|
||||
if (myDebug) {
|
||||
myDebugArray.setText(chars);
|
||||
myDebugDeferredChanges.clear();
|
||||
}
|
||||
assertConsistency();
|
||||
}
|
||||
|
||||
private void assertConsistency() {
|
||||
if (isDeferredChangeMode()) {
|
||||
assert myOriginalSequence == null;
|
||||
}
|
||||
CharSequence originalSequence = myOriginalSequence;
|
||||
int origLen = originalSequence == null ? -1 : originalSequence.length();
|
||||
String string = com.intellij.reference.SoftReference.dereference(myStringRef);
|
||||
int stringLen = string == null ? -1 : string.length();
|
||||
assert origLen == stringLen || origLen==-1 || stringLen==-1;
|
||||
|
||||
int count = myCount + myDeferredShift;
|
||||
assert count == origLen || origLen==-1;
|
||||
assert count == stringLen || stringLen==-1;
|
||||
|
||||
if (!myDebug) return;
|
||||
final CharSequence seqFromCharArray;
|
||||
|
||||
if (myArray != null) {
|
||||
assert myCount <= myArray.length;
|
||||
seqFromCharArray = new CharArrayCharSequence(myArray, myStart, myCount);
|
||||
}
|
||||
else {
|
||||
seqFromCharArray = null;
|
||||
}
|
||||
|
||||
if (seqFromCharArray != null && originalSequence != null) {
|
||||
assert StringUtil.equals(seqFromCharArray, originalSequence);
|
||||
}
|
||||
if (!isDeferredChangeMode() && seqFromCharArray != null && string != null) {
|
||||
assert StringUtil.equals(seqFromCharArray, string);
|
||||
}
|
||||
if (originalSequence != null && string != null) {
|
||||
assert string.equals(originalSequence.toString());
|
||||
}
|
||||
|
||||
myDebugArray.assertConsistency();
|
||||
|
||||
CharSequence str = com.intellij.reference.SoftReference.dereference(myStringRef);
|
||||
if (str == null) {
|
||||
if (myHasDeferredChanges) {
|
||||
str = doSubString(0, myCount + myDeferredShift).toString();
|
||||
}
|
||||
else if (myOriginalSequence != null) {
|
||||
str = myOriginalSequence.toString();
|
||||
}
|
||||
else {
|
||||
str = seqFromCharArray;
|
||||
}
|
||||
}
|
||||
assert count == str.length();
|
||||
if (isDeferredChangeMode()) {
|
||||
String expected = myDebugArray.toString();
|
||||
checkStrings("toString()", expected, str);
|
||||
}
|
||||
}
|
||||
|
||||
public void replace(int startOffset,
|
||||
int endOffset,
|
||||
@NotNull CharSequence toDelete,
|
||||
@NotNull CharSequence newString,
|
||||
long newModificationStamp,
|
||||
boolean wholeTextReplaced) {
|
||||
final DocumentEvent event = startChange(startOffset, toDelete, newString, wholeTextReplaced);
|
||||
|
||||
startOffset += myStart;
|
||||
endOffset += myStart;
|
||||
doReplace(startOffset, endOffset, newString);
|
||||
afterChangedUpdate(event, newModificationStamp);
|
||||
assertConsistency();
|
||||
}
|
||||
|
||||
private void doReplace(int startOffset, int endOffset, @NotNull CharSequence newString) {
|
||||
prepareForModification();
|
||||
|
||||
if (isDeferredChangeMode()) {
|
||||
storeChange(new TextChangeImpl(newString, startOffset, endOffset));
|
||||
if (myDebug) {
|
||||
myDebugArray.doReplace(startOffset, endOffset, newString);
|
||||
}
|
||||
}
|
||||
else {
|
||||
int newLength = newString.length();
|
||||
int oldLength = endOffset - startOffset;
|
||||
|
||||
CharArrayUtil.getChars(newString, myArray, startOffset, Math.min(newLength, oldLength));
|
||||
myStringRef = null;
|
||||
|
||||
if (newLength > oldLength) {
|
||||
doInsert(newString.subSequence(oldLength, newLength), endOffset);
|
||||
}
|
||||
else if (newLength < oldLength) {
|
||||
doRemove(startOffset + newLength, startOffset + oldLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(int startIndex, int endIndex, @NotNull CharSequence toDelete) {
|
||||
DocumentEvent event = startChange(startIndex, toDelete, null, false);
|
||||
startIndex += myStart;
|
||||
endIndex += myStart;
|
||||
doRemove(startIndex, endIndex);
|
||||
afterChangedUpdate(event, LocalTimeCounter.currentTime());
|
||||
assertConsistency();
|
||||
}
|
||||
|
||||
private void doRemove(int startIndex, int endIndex) {
|
||||
if (startIndex == endIndex) {
|
||||
return;
|
||||
}
|
||||
prepareForModification();
|
||||
|
||||
if (isDeferredChangeMode()) {
|
||||
storeChange(new TextChangeImpl("", startIndex, endIndex));
|
||||
if (myDebug) {
|
||||
myDebugArray.doRemove(startIndex, endIndex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (endIndex < myCount) {
|
||||
System.arraycopy(myArray, endIndex, myArray, startIndex, myCount - endIndex);
|
||||
myStringRef = null;
|
||||
}
|
||||
myCount -= endIndex - startIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public void insert(@NotNull CharSequence s, int startIndex) {
|
||||
DocumentEvent event = startChange(startIndex, null, s, false);
|
||||
startIndex += myStart;
|
||||
doInsert(s, startIndex);
|
||||
|
||||
afterChangedUpdate(event, LocalTimeCounter.currentTime());
|
||||
trimToSize();
|
||||
assertConsistency();
|
||||
}
|
||||
|
||||
private void doInsert(@NotNull CharSequence s, final int startIndex) {
|
||||
prepareForModification();
|
||||
|
||||
if (isDeferredChangeMode()) {
|
||||
storeChange(new TextChangeImpl(s, startIndex));
|
||||
if (myDebug) {
|
||||
myDebugArray.doInsert(s, startIndex);
|
||||
}
|
||||
}
|
||||
else {
|
||||
int insertLength = s.length();
|
||||
myArray = resizeArray(myArray, myCount + insertLength);
|
||||
if (startIndex < myCount) {
|
||||
System.arraycopy(myArray, startIndex, myArray, startIndex + insertLength, myCount - startIndex);
|
||||
}
|
||||
|
||||
CharArrayUtil.getChars(s, myArray, startIndex);
|
||||
myCount += insertLength;
|
||||
myStringRef = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores given change at collection of deferred changes (merging it with others if necessary) and updates current object
|
||||
* state ({@link #length() length} etc).
|
||||
*
|
||||
* @param change new change to store
|
||||
*/
|
||||
private void storeChange(@NotNull TextChangeImpl change) {
|
||||
if (!change.isWithinBounds(length())) {
|
||||
LOG.error(
|
||||
"Invalid change attempt detected - given change bounds are not within the current char array. Change: " +
|
||||
change.getText().length()+":" + change.getStart()+"-" + change.getEnd(), dumpState());
|
||||
return;
|
||||
}
|
||||
if (myDeferredChangesStorage.size() >= MAX_DEFERRED_CHANGES_NUMBER) {
|
||||
flushDeferredChanged();
|
||||
}
|
||||
myDeferredChangesStorage.store(change);
|
||||
myHasDeferredChanges = true;
|
||||
myDeferredShift += change.getDiff();
|
||||
|
||||
if (myDebug) {
|
||||
myDebugDeferredChanges.add(change);
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareForModification() {
|
||||
if (myOriginalSequence != null) {
|
||||
myArray = new char[myOriginalSequence.length()];
|
||||
CharArrayUtil.getChars(myOriginalSequence, myArray, 0);
|
||||
myCount = myArray.length;
|
||||
myOriginalSequence = null;
|
||||
myStart = 0;
|
||||
}
|
||||
myStringRef = null;
|
||||
|
||||
assertConsistency();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public CharSequence getCharArray() {
|
||||
assertConsistency();
|
||||
CharSequence originalSequence = myOriginalSequence;
|
||||
return originalSequence == null ? this : originalSequence;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toString() {
|
||||
assertConsistency();
|
||||
String str = com.intellij.reference.SoftReference.dereference(myStringRef);
|
||||
if (str == null) {
|
||||
if (myHasDeferredChanges) {
|
||||
str = substring(0, length()).toString();
|
||||
}
|
||||
else {
|
||||
str = myOriginalSequence == null ? new String(myArray, myStart, myCount) : myOriginalSequence.toString();
|
||||
}
|
||||
myStringRef = new SoftReference<String>(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 <code>true</code> 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.
|
||||
* <p/>
|
||||
* This method allows to define that <code>'defer changes'</code> mode usages, i.e. expected usage pattern is as follows:
|
||||
* <pre>
|
||||
* <ol>
|
||||
* <li>
|
||||
* Client of this class enters <code>'defer changes'</code> mode (calls this method with <code>'true'</code> argument).
|
||||
* That means that all subsequent changes will not actually modify backed array data and will be stored separately;
|
||||
* </li>
|
||||
* <li>
|
||||
* 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)});
|
||||
* </li>
|
||||
* <li>
|
||||
* Client of this class indicates that <code>'massive change time'</code> is over by calling this method with <code>'false'</code>
|
||||
* 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;
|
||||
* </li>
|
||||
* </ol>
|
||||
* </pre>
|
||||
* <p/>
|
||||
* <b>Note:</b> we can't exclude possibility that <code>'defer changes'</code> mode is started but inadvertently not ended
|
||||
* (due to programming error, unexpected exception etc). Hence, this class is free to automatically end
|
||||
* <code>'defer changes'</code> mode when necessary in order to avoid memory leak with infinite deferred changes storing.
|
||||
*
|
||||
* @param deferredChangeMode flag that defines if <code>'defer changes'</code> 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<TextChangeImpl> 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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<Pair<Integer, Integer>>());
|
||||
}
|
||||
|
||||
private void checkSubSequence(@NotNull String expected, @NotNull CharSequence actual,
|
||||
@NotNull Stack<Pair<Integer, Integer>> 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<Integer, Integer>(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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user