mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
use ImmutableText for document contents
This commit is contained in:
@@ -33,11 +33,11 @@ import com.intellij.openapi.util.*;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.LocalTimeCounter;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.text.CharArrayUtil;
|
||||
import com.intellij.util.text.ImmutableText;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -59,7 +59,7 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
private ReadonlyFragmentModificationHandler myReadonlyFragmentModificationHandler;
|
||||
|
||||
private final LineSet myLineSet = new LineSet();
|
||||
private final CharArray myText;
|
||||
private volatile ImmutableText myText;
|
||||
|
||||
private boolean myIsReadOnly = false;
|
||||
private boolean isStripTrailingSpacesEnabled = true;
|
||||
@@ -76,6 +76,7 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
private volatile boolean myDoingBulkUpdate = false;
|
||||
private volatile boolean myAcceptSlashR = false;
|
||||
private boolean myChangeInProgress;
|
||||
private volatile int myBufferSize;
|
||||
|
||||
public DocumentImpl(@NotNull String text) {
|
||||
this(text, false);
|
||||
@@ -87,7 +88,7 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
|
||||
public DocumentImpl(@NotNull CharSequence chars, boolean forUseInNonAWTThread) {
|
||||
assertValidSeparators(chars);
|
||||
myText = new MyCharArray(CharArrayUtil.fromSequence(chars), chars.length());
|
||||
myText = ImmutableText.valueOf(CharArrayUtil.fromSequence(chars));
|
||||
myLineSet.documentCreated(this);
|
||||
setCyclicBufferSize(0);
|
||||
setModificationStamp(LocalTimeCounter.currentTime());
|
||||
@@ -103,14 +104,10 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
}
|
||||
}
|
||||
|
||||
public char[] getRawChars() {
|
||||
return myText.getChars();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public char[] getChars() {
|
||||
return ArrayUtil.realloc(CharArrayUtil.fromSequence(getCharsSequence()), myText.length());
|
||||
return CharArrayUtil.fromSequence(myText);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -136,7 +133,7 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
}
|
||||
|
||||
boolean markAsNeedsStrippingLater = false;
|
||||
CharSequence text = myText.getCharArray();
|
||||
CharSequence text = myText;
|
||||
RangeMarker caretMarker = caretOffset < 0 || caretOffset > getTextLength() ? null : createRangeMarker(caretOffset, caretOffset);
|
||||
try {
|
||||
for (int line = 0; line < myLineSet.getLineCount(); line++) {
|
||||
@@ -174,7 +171,7 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
});
|
||||
}
|
||||
});
|
||||
text = myText.getCharArray();
|
||||
text = myText;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -356,7 +353,16 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
throwGuardedFragment(marker, offset, null, s.toString());
|
||||
}
|
||||
|
||||
myText.insert(s, offset);
|
||||
final DocumentEvent event = beforeChangedUpdate(offset, null, s, false);
|
||||
myText = myText.insert(offset, ImmutableText.valueOf(s));
|
||||
changedUpdate(event, LocalTimeCounter.currentTime());
|
||||
trimToSize();
|
||||
}
|
||||
|
||||
private void trimToSize() {
|
||||
if (myBufferSize != 0 && getTextLength() > myBufferSize) {
|
||||
deleteString(0, getTextLength() - myBufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -368,14 +374,16 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
if (startOffset == endOffset) return;
|
||||
assertNotNestedModification();
|
||||
|
||||
CharSequence sToDelete = myText.substring(startOffset, endOffset);
|
||||
CharSequence sToDelete = myText.subSequence(startOffset, endOffset);
|
||||
|
||||
RangeMarker marker = getRangeGuard(startOffset, endOffset);
|
||||
if (marker != null) {
|
||||
throwGuardedFragment(marker, startOffset, sToDelete.toString(), null);
|
||||
}
|
||||
|
||||
myText.remove(startOffset, endOffset, sToDelete);
|
||||
final DocumentEvent event = beforeChangedUpdate(startOffset, sToDelete, null, false);
|
||||
myText = myText.delete(startOffset, endOffset);
|
||||
changedUpdate(event, LocalTimeCounter.currentTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -441,13 +449,16 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
}
|
||||
|
||||
s = s.subSequence(newStartInString, newEndInString);
|
||||
CharSequence sToDelete = myText.substring(startOffset, endOffset);
|
||||
CharSequence sToDelete = myText.subSequence(startOffset, endOffset);
|
||||
RangeMarker guard = getRangeGuard(startOffset, endOffset);
|
||||
if (guard != null) {
|
||||
throwGuardedFragment(guard, startOffset, sToDelete.toString(), s.toString());
|
||||
}
|
||||
|
||||
myText.replace(startOffset, endOffset, sToDelete, s, newModificationStamp, wholeTextReplaced);
|
||||
final DocumentEvent event = beforeChangedUpdate(startOffset, sToDelete, s, wholeTextReplaced);
|
||||
myText = myText.delete(startOffset, endOffset).insert(startOffset, ImmutableText.valueOf(s));
|
||||
changedUpdate(event, newModificationStamp);
|
||||
trimToSize();
|
||||
}
|
||||
|
||||
private void assertBounds(final int startOffset, final int endOffset) {
|
||||
@@ -545,6 +556,7 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
|
||||
@NotNull
|
||||
private DocumentEvent beforeChangedUpdate(int offset, CharSequence oldString, CharSequence newString, boolean wholeTextReplaced) {
|
||||
assertWriteAccess();
|
||||
myChangeInProgress = true;
|
||||
try {
|
||||
return doBeforeChangedUpdate(offset, oldString, newString, wholeTextReplaced);
|
||||
@@ -625,7 +637,7 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
|
||||
@Override
|
||||
public String compute() {
|
||||
return myText.substring(range.getStartOffset(), range.getEndOffset()).toString();
|
||||
return myText.subSequence(range.getStartOffset(), range.getEndOffset()).toString();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -635,20 +647,31 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
return myText.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be used very carefully - only to read the array, and to be sure, that nobody changes
|
||||
* text, while this array is processed.
|
||||
* Really it is used only to optimize paint in Editor.
|
||||
* [Valentin] 25.04.2001: More really, it is used in 61 places in 29 files across the project :-)))
|
||||
*/
|
||||
CharSequence getCharsNoThreadCheck() {
|
||||
return getCharsSequence();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public CharSequence getCharsSequence() {
|
||||
return myText.getCharArray();
|
||||
return new CharSequence() {
|
||||
@Override
|
||||
public int length() {
|
||||
return myText.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char charAt(int index) {
|
||||
return myText.charAt(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence subSequence(int start, int end) {
|
||||
return myText.subSequence(start, end);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return myText.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -786,7 +809,8 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
|
||||
@Override
|
||||
public void setCyclicBufferSize(int bufferSize) {
|
||||
myText.setBufferSize(bufferSize);
|
||||
assert bufferSize >= 0 : bufferSize;
|
||||
myBufferSize = bufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -826,7 +850,6 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
return;
|
||||
}
|
||||
myDoingBulkUpdate = value;
|
||||
myText.setDeferredChangeMode(value);
|
||||
if (value) {
|
||||
getPublisher().updateStarted(this);
|
||||
}
|
||||
@@ -857,7 +880,6 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
@NotNull
|
||||
public String dumpState() {
|
||||
@NonNls StringBuilder result = new StringBuilder();
|
||||
result.append("deferred mode: ").append(myText.isDeferredChangeMode() ? "on" : "off");
|
||||
result.append(", intervals:\n");
|
||||
for (int line = 0; line < getLineCount(); line++) {
|
||||
result.append(line).append(": ").append(getLineStartOffset(line)).append("-")
|
||||
@@ -869,39 +891,5 @@ public class DocumentImpl extends UserDataHolderBase implements DocumentEx {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private class MyCharArray extends CharArray {
|
||||
private MyCharArray(@NotNull char[] chars, int length) {
|
||||
super(0, chars, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
protected DocumentEvent beforeChangedUpdate(int offset,
|
||||
CharSequence oldString,
|
||||
CharSequence newString,
|
||||
boolean wholeTextReplaced) {
|
||||
return DocumentImpl.this.beforeChangedUpdate(offset, oldString, newString, wholeTextReplaced);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterChangedUpdate(@NotNull DocumentEvent event, long newModificationStamp) {
|
||||
((DocumentImpl)event.getDocument()).changedUpdate(event, newModificationStamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertWriteAccess() {
|
||||
DocumentImpl.this.assertWriteAccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertReadAccess() {
|
||||
if (myAssertThreading) {
|
||||
final Application application = ApplicationManager.getApplication();
|
||||
if (application != null) {
|
||||
application.assertReadAccessAllowed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.ContainerUtilRt;
|
||||
import com.intellij.util.containers.Convertor;
|
||||
import com.intellij.util.messages.MessageBusConnection;
|
||||
import com.intellij.util.text.CharArrayCharSequence;
|
||||
import com.intellij.util.text.CharArrayUtil;
|
||||
import com.intellij.util.ui.ButtonlessScrollBarUI;
|
||||
import com.intellij.util.ui.GraphicsUtil;
|
||||
@@ -2172,9 +2173,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
int visibleLine = yPositionToVisibleLine(clip.y);
|
||||
|
||||
Point position = new Point(0, visibleLine * lineHeight);
|
||||
char[] prefixText = myPrefixText;
|
||||
CharSequence prefixText = myPrefixText == null ? null : new CharArrayCharSequence(myPrefixText);
|
||||
if (clipStartVisualPos.line == 0 && prefixText != null) {
|
||||
position.x = drawBackground(g, myPrefixAttributes.getBackgroundColor(), prefixText, 0, prefixText.length, position,
|
||||
position.x = drawBackground(g, myPrefixAttributes.getBackgroundColor(), prefixText, 0, prefixText.length(), position,
|
||||
myPrefixAttributes.getFontType(),
|
||||
defaultBackground, clip);
|
||||
}
|
||||
@@ -2237,7 +2238,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
// background is already drawn.
|
||||
boolean[] caretRowPainted = new boolean[1];
|
||||
|
||||
char[] text = myDocument.getChars();
|
||||
CharSequence text = myDocument.getCharsSequence();
|
||||
|
||||
while (!iterationState.atEnd() && !lIterator.atEnd()) {
|
||||
int hEnd = iterationState.getEndOffset();
|
||||
@@ -2276,8 +2277,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
defaultBackground, clip, softWrapsToSkip, caretRowPainted
|
||||
);
|
||||
}
|
||||
char[] chars = collapsedFolderAt.getPlaceholderText().toCharArray();
|
||||
position.x = drawBackground(g, backColor, chars, 0, chars.length, position, fontType, defaultBackground, clip);
|
||||
CharSequence chars = collapsedFolderAt.getPlaceholderText();
|
||||
position.x = drawBackground(g, backColor, chars, 0, chars.length(), position, fontType, defaultBackground, clip);
|
||||
}
|
||||
|
||||
lIterator.advance();
|
||||
@@ -2292,8 +2293,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
defaultBackground, clip, softWrapsToSkip, caretRowPainted
|
||||
);
|
||||
}
|
||||
char[] chars = collapsedFolderAt.getPlaceholderText().toCharArray();
|
||||
position.x = drawBackground(g, backColor, chars, 0, chars.length, position, fontType, defaultBackground, clip);
|
||||
CharSequence chars = collapsedFolderAt.getPlaceholderText();
|
||||
position.x = drawBackground(g, backColor, chars, 0, chars.length(), position, fontType, defaultBackground, clip);
|
||||
}
|
||||
else if (hEnd > lEnd - lIterator.getSeparatorLength()) {
|
||||
position.x = drawSoftWrapAwareBackground(
|
||||
@@ -2389,7 +2390,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
|
||||
private int drawSoftWrapAwareBackground(@NotNull Graphics g,
|
||||
Color backColor,
|
||||
@NotNull char[] text,
|
||||
@NotNull CharSequence text,
|
||||
int start,
|
||||
int end,
|
||||
@NotNull Point position,
|
||||
@@ -2458,7 +2459,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
paintSelectionOnFirstSoftWrapLineIfNecessary(g, position, clip, defaultBackground, fontType);
|
||||
|
||||
int i = CharArrayUtil.lastIndexOf(softWrapText, "\n", softWrapText.length()) + 1;
|
||||
int width = getTextSegmentWidth(CharArrayUtil.fromSequence(softWrapText), i, softWrapText.length(), 0, fontType, clip)
|
||||
int width = getTextSegmentWidth(softWrapText, i, softWrapText.length(), 0, fontType, clip)
|
||||
+ getSoftWrapModel().getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP);
|
||||
position.x = 0;
|
||||
position.y += getLineHeight();
|
||||
@@ -2592,7 +2593,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
|
||||
private int drawBackground(@NotNull Graphics g,
|
||||
Color backColor,
|
||||
@NotNull char[] text,
|
||||
@NotNull CharSequence text,
|
||||
int start,
|
||||
int end,
|
||||
@NotNull Point position,
|
||||
@@ -2674,7 +2675,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
|
||||
Point position = new Point(0, visibleLine * lineHeight);
|
||||
if (startLine == 0 && myPrefixText != null) {
|
||||
position.x = drawStringWithSoftWraps(g, myPrefixText, 0, myPrefixText.length, position, clip,
|
||||
position.x = drawStringWithSoftWraps(g, new CharArrayCharSequence(myPrefixText), 0, myPrefixText.length, position, clip,
|
||||
myPrefixAttributes.getEffectColor(), myPrefixAttributes.getEffectType(),
|
||||
myPrefixAttributes.getFontType(), myPrefixAttributes.getForegroundColor(), logicalPosition);
|
||||
}
|
||||
@@ -2701,7 +2702,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
int fontType = attributes.getFontType();
|
||||
g.setColor(currentColor);
|
||||
|
||||
char[] chars = myDocument instanceof DocumentImpl ? ((DocumentImpl)myDocument).getRawChars() : myDocument.getChars();
|
||||
CharSequence chars = myDocument.getCharsSequence();
|
||||
|
||||
while (!iterationState.atEnd() && !lIterator.atEnd()) {
|
||||
int hEnd = iterationState.getEndOffset();
|
||||
@@ -2800,7 +2801,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
}
|
||||
else {
|
||||
myLastPaintedPlaceholderWidth = drawString(
|
||||
g, CharArrayUtil.fromSequence(hintText), 0, hintText.length(), new Point(0, 0), clip, null, null, Font.PLAIN,
|
||||
g, hintText, 0, hintText.length(), new Point(0, 0), clip, null, null, Font.PLAIN,
|
||||
myFoldingModel.getPlaceholderAttributes().getForegroundColor()
|
||||
);
|
||||
flushCachedChars(g);
|
||||
@@ -2830,7 +2831,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
}
|
||||
|
||||
private class CachedFontContent {
|
||||
final char[][] data = new char[CACHED_CHARS_BUFFER_SIZE][];
|
||||
final CharSequence[] data = new CharSequence[CACHED_CHARS_BUFFER_SIZE];
|
||||
final int[] starts = new int[CACHED_CHARS_BUFFER_SIZE];
|
||||
final int[] ends = new int[CACHED_CHARS_BUFFER_SIZE];
|
||||
final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
|
||||
@@ -2842,7 +2843,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
final boolean myHasBreakSymbols;
|
||||
final int spaceWidth;
|
||||
|
||||
@Nullable private char[] myLastData;
|
||||
@Nullable private CharSequence myLastData;
|
||||
|
||||
private CachedFontContent(@NotNull FontInfo fontInfo) {
|
||||
myFontType = fontInfo;
|
||||
@@ -2873,14 +2874,14 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
}
|
||||
}
|
||||
|
||||
private void addContent(@NotNull Graphics g, char[] _data, int _start, int _end, int _x, int _y, @Nullable Color _color) {
|
||||
private void addContent(@NotNull Graphics g, CharSequence _data, int _start, int _end, int _x, int _y, @Nullable Color _color) {
|
||||
final int count = myCount;
|
||||
if (count > 0) {
|
||||
final int lastCount = count - 1;
|
||||
final Color lastColor = color[lastCount];
|
||||
if (_data == myLastData && _start == ends[lastCount] && (_color == null || lastColor == null || _color.equals(lastColor))
|
||||
&& _y == y[lastCount] /* there is a possible case that vertical position is adjusted because of soft wrap */
|
||||
&& (!myHasBreakSymbols || !myFontType.getSymbolsToBreakDrawingIteration().contains(_data[ends[lastCount] - 1]))) {
|
||||
&& (!myHasBreakSymbols || !myFontType.getSymbolsToBreakDrawingIteration().contains(_data.charAt(ends[lastCount] - 1)))) {
|
||||
ends[lastCount] = _end;
|
||||
if (lastColor == null) color[lastCount] = _color;
|
||||
return;
|
||||
@@ -2983,12 +2984,12 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
@JdkConstants.FontStyle int fontType,
|
||||
Color fontColor,
|
||||
@NotNull Ref<LogicalPosition> startDrawingLogicalPosition) {
|
||||
return drawStringWithSoftWraps(g, text.toCharArray(), 0, text.length(), position, clip, effectColor, effectType,
|
||||
return drawStringWithSoftWraps(g, text, 0, text.length(), position, clip, effectColor, effectType,
|
||||
fontType, fontColor, startDrawingLogicalPosition);
|
||||
}
|
||||
|
||||
private int drawStringWithSoftWraps(@NotNull Graphics g,
|
||||
final char[] text,
|
||||
final CharSequence text,
|
||||
final int start,
|
||||
final int end,
|
||||
@NotNull Point position,
|
||||
@@ -3036,6 +3037,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
outer:
|
||||
for (SoftWrap softWrap : getSoftWrapModel().getSoftWrapsForRange(startToUse, softWrapRetrievalEndOffset)) {
|
||||
char[] softWrapChars = softWrap.getChars();
|
||||
CharArrayCharSequence softWrapSeq = new CharArrayCharSequence(softWrapChars);
|
||||
|
||||
if (softWrap.equals(lastSkippedSoftWrap)) {
|
||||
// If we are here that means that we are located on soft wrap-introduced visual line just after soft wrap. Hence, we need
|
||||
@@ -3044,7 +3046,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
if (i < softWrapChars.length - 1) {
|
||||
position.x = 0; // Soft wrap starts new visual line
|
||||
position.x = drawString(
|
||||
g, softWrapChars, i + 1, softWrapChars.length, position, clip, null, null, fontType, fontColor
|
||||
g, softWrapSeq, i + 1, softWrapChars.length, position, clip, null, null, fontType, fontColor
|
||||
);
|
||||
}
|
||||
position.x += mySoftWrapModel.paint(g, SoftWrapDrawingType.AFTER_SOFT_WRAP, position.x, position.y, getLineHeight());
|
||||
@@ -3073,7 +3075,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
// Draw soft wrap symbols on current visual line if any.
|
||||
if (i - softWrapSegmentStartIndex > 0) {
|
||||
drawString(
|
||||
g, softWrapChars, softWrapSegmentStartIndex, i, position, clip, null, null, fontType, fontColor
|
||||
g, softWrapSeq, softWrapSegmentStartIndex, i, position, clip, null, null, fontType, fontColor
|
||||
);
|
||||
}
|
||||
mySoftWrapModel.paint(g, SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED, position.x, position.y, getLineHeight());
|
||||
@@ -3093,7 +3095,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
// Draw remaining soft wrap symbols from its last line if any.
|
||||
if (softWrapSegmentStartIndex < softWrapChars.length) {
|
||||
position.x += drawString(
|
||||
g, softWrapChars, softWrapSegmentStartIndex, softWrapChars.length, position, clip, null, null, fontType, fontColor
|
||||
g, softWrapSeq, softWrapSegmentStartIndex, softWrapChars.length, position, clip, null, null, fontType, fontColor
|
||||
);
|
||||
}
|
||||
position.x += mySoftWrapModel.paint(g, SoftWrapDrawingType.AFTER_SOFT_WRAP, position.x, position.y, getLineHeight());
|
||||
@@ -3103,7 +3105,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
}
|
||||
|
||||
private int drawString(@NotNull Graphics g,
|
||||
final char[] text,
|
||||
final CharSequence text,
|
||||
int start,
|
||||
int end,
|
||||
@NotNull Point position,
|
||||
@@ -3142,11 +3144,11 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
int y = getAscent() + position.y;
|
||||
int x = position.x;
|
||||
|
||||
return drawTabbedString(g, text.toCharArray(), 0, text.length(), x, y, effectColor, effectType, fontType, fontColor, clip);
|
||||
return drawTabbedString(g, text, 0, text.length(), x, y, effectColor, effectType, fontType, fontColor, clip);
|
||||
}
|
||||
|
||||
private int drawTabbedString(@NotNull Graphics g,
|
||||
char[] text,
|
||||
CharSequence text,
|
||||
int start,
|
||||
int end,
|
||||
int x,
|
||||
@@ -3159,7 +3161,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
int xStart = x;
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
if (text[i] != '\t') continue;
|
||||
if (text.charAt(i) != '\t') continue;
|
||||
|
||||
x = drawTablessString(text, start, i, g, x, y, fontType, fontColor, clip);
|
||||
|
||||
@@ -3219,7 +3221,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
return x;
|
||||
}
|
||||
|
||||
private int drawTablessString(final char[] text,
|
||||
private int drawTablessString(final CharSequence text,
|
||||
int start,
|
||||
final int end,
|
||||
@NotNull final Graphics g,
|
||||
@@ -3230,9 +3232,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
@NotNull final Rectangle clip) {
|
||||
int endX = x;
|
||||
if (start < end) {
|
||||
FontInfo font = EditorUtil.fontForChar(text[start], fontType, this);
|
||||
FontInfo font = EditorUtil.fontForChar(text.charAt(start), fontType, this);
|
||||
for (int j = start; j < end; j++) {
|
||||
final char c = text[j];
|
||||
final char c = text.charAt(j);
|
||||
FontInfo newFont = EditorUtil.fontForChar(c, fontType, this);
|
||||
if (font != newFont || endX > clip.x + clip.width) {
|
||||
if (!(x < clip.x && endX < clip.x || x > clip.x + clip.width && endX > clip.x + clip.width)) {
|
||||
@@ -3289,7 +3291,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
}
|
||||
|
||||
private void drawCharsCached(@NotNull Graphics g,
|
||||
char[] data,
|
||||
CharSequence data,
|
||||
int start,
|
||||
int end,
|
||||
int x,
|
||||
@@ -3301,13 +3303,13 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
}
|
||||
else {
|
||||
myForceRefreshFont = false;
|
||||
FontInfo fnt = EditorUtil.fontForChar(data[start], fontType, this);
|
||||
FontInfo fnt = EditorUtil.fontForChar(data.charAt(start), fontType, this);
|
||||
drawCharsCached(g, data, start, end, x, y, fnt, color);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawCharsCached(@NotNull Graphics g,
|
||||
@NotNull char[] data,
|
||||
@NotNull CharSequence data,
|
||||
int start,
|
||||
int end,
|
||||
int x,
|
||||
@@ -3330,17 +3332,17 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
cache.addContent(g, data, start, end, x, y, color);
|
||||
}
|
||||
|
||||
private static boolean spacesOnly(char[] chars, int start, int end) {
|
||||
private static boolean spacesOnly(CharSequence chars, int start, int end) {
|
||||
for (int i = start; i < end; i++) {
|
||||
if (chars[i] != ' ') return false;
|
||||
if (chars.charAt(i) != ' ') return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static final char IDEOGRAPHIC_SPACE = '\u3000'; // http://www.marathon-studios.com/unicode/U3000/Ideographic_Space
|
||||
|
||||
private void drawChars(@NotNull Graphics g, char[] data, int start, int end, int x, int y) {
|
||||
g.drawChars(data, start, end - start, x, y);
|
||||
private void drawChars(@NotNull Graphics g, CharSequence data, int start, int end, int x, int y) {
|
||||
g.drawString(data.subSequence(start, end).toString(), x, y);
|
||||
|
||||
if (mySettings.isWhitespacesShown()) {
|
||||
Color oldColor = g.getColor();
|
||||
@@ -3348,7 +3350,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
final FontMetrics metrics = g.getFontMetrics();
|
||||
|
||||
for (int i = start; i < end; i++) {
|
||||
final char c = data[i];
|
||||
final char c = data.charAt(i);
|
||||
final int charWidth = isOracleRetina ? GraphicsUtil.charWidth(c, g.getFont()) : metrics.charWidth(c);
|
||||
|
||||
if (c == ' ') {
|
||||
@@ -3383,7 +3385,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
UIUtil.drawLine(g, x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
|
||||
}
|
||||
|
||||
private int getTextSegmentWidth(@NotNull char[] text,
|
||||
private int getTextSegmentWidth(@NotNull CharSequence text,
|
||||
int start,
|
||||
int end,
|
||||
int xStart,
|
||||
@@ -3392,7 +3394,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
int x = xStart;
|
||||
|
||||
for (int i = start; i < end && xStart < clip.x + clip.width; i++) {
|
||||
char c = text[i];
|
||||
char c = text.charAt(i);
|
||||
if (c == '\t') {
|
||||
x = EditorUtil.nextTabStop(x, this);
|
||||
}
|
||||
@@ -4464,12 +4466,12 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
g.fillRect(x, y, myWidth, lineHeight - 1);
|
||||
final LogicalPosition startPosition = getCaretModel().getLogicalPosition();
|
||||
final int offset = logicalPositionToOffset(startPosition);
|
||||
char[] chars = myDocument instanceof DocumentImpl ? ((DocumentImpl)myDocument).getRawChars() : myDocument.getChars();
|
||||
if (chars.length > offset && myDocument.getTextLength() > offset) {
|
||||
CharSequence chars = myDocument.getCharsSequence();
|
||||
if (chars.length() > offset && myDocument.getTextLength() > offset) {
|
||||
FoldRegion folding = myFoldingModel.getCollapsedRegionAtOffset(offset);
|
||||
final char ch;
|
||||
if (folding == null || folding.isExpanded()) {
|
||||
ch = chars[offset];
|
||||
ch = chars.charAt(offset);
|
||||
}
|
||||
else {
|
||||
VisualPosition visual = getCaretModel().getVisualPosition();
|
||||
@@ -4478,7 +4480,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
ch = folding.getPlaceholderText().charAt(visual.column - foldingPosition.column);
|
||||
}
|
||||
else {
|
||||
ch = chars[offset];
|
||||
ch = chars.charAt(offset);
|
||||
}
|
||||
}
|
||||
IterationState state = null;
|
||||
@@ -6652,7 +6654,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
Color color,
|
||||
@NotNull FontInfo fontInfo)
|
||||
{
|
||||
drawCharsCached(g, data, start, end, x, y, fontInfo, color);
|
||||
drawCharsCached(g, new CharArrayCharSequence(data), start, end, x, y, fontInfo, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,845 @@
|
||||
/*
|
||||
* Javolution - Java(TM) Solution for Real-Time and Embedded Systems
|
||||
* Copyright (C) 2005 - Javolution (http://javolution.org/)
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software is
|
||||
* freely granted, provided that this notice is preserved.
|
||||
*/
|
||||
package com.intellij.util.text;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.io.Writer;
|
||||
import java.lang.CharSequence;
|
||||
|
||||
/**
|
||||
* A pruned and optimized version of javolution.text.Text
|
||||
*
|
||||
* <p> This class represents an immutable character sequence with
|
||||
* fast {@link #concat concatenation}, {@link #insert insertion} and
|
||||
* {@link #delete deletion} capabilities (O[Log(n)]) instead of
|
||||
* O[n] for StringBuffer/StringBuilder).</p>
|
||||
* <p> This class has the same methods as
|
||||
* <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/lang/String.html">
|
||||
* Java String</a> with the following benefits:<ul>
|
||||
* <li> No need for an intermediate
|
||||
* {@link StringBuffer}/{@link StringBuilder} in order to manipulate
|
||||
* textual documents (insertion, deletion or concatenation).</li>
|
||||
* <li> More flexible as they allows for search and comparison with any
|
||||
* <code>java.lang.String</code> or <code>CharSequence</code>.</li>
|
||||
* </ul></p>
|
||||
*
|
||||
* <p><i> Implementation Note: To avoid expensive copy operations ,
|
||||
* {@link ImmutableText} instances are broken down into smaller immutable
|
||||
* sequences, they form a minimal-depth binary tree.
|
||||
* The tree is maintained balanced automatically through <a
|
||||
* href="http://en.wikipedia.org/wiki/Tree_rotation">tree rotations</a>.
|
||||
* Insertion/deletions are performed in <code>O[Log(n)]</code>
|
||||
* instead of <code>O[n]</code> for
|
||||
* <code>StringBuffer/StringBuilder</code>.</i></p>
|
||||
*
|
||||
* @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
|
||||
* @author Wilfried Middleton
|
||||
* @version 5.3, January 10, 2007
|
||||
*/
|
||||
@SuppressWarnings("AssignmentToForLoopParameter")
|
||||
public final class ImmutableText implements CharSequence {
|
||||
|
||||
/**
|
||||
* Holds the default size for primitive blocks of characters.
|
||||
*/
|
||||
private static final int BLOCK_SIZE = 1 << 6;
|
||||
|
||||
/**
|
||||
* Holds the mask used to ensure a block boundary cesures.
|
||||
*/
|
||||
private static final int BLOCK_MASK = ~(BLOCK_SIZE - 1);
|
||||
|
||||
/**
|
||||
* Holds the primitive text factory.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Holds the raw data (primitive) or <code>null</code> (composite).
|
||||
*/
|
||||
private final char[] _data;
|
||||
|
||||
/**
|
||||
* Holds the total number of characters.
|
||||
*/
|
||||
private final int _count;
|
||||
|
||||
/**
|
||||
* Holds the head block of character (composite).
|
||||
*/
|
||||
private final ImmutableText _head;
|
||||
|
||||
/**
|
||||
* Holds the tail block of character (composite).
|
||||
*/
|
||||
private final ImmutableText _tail;
|
||||
|
||||
private ImmutableText(char[] data) {
|
||||
_data = data;
|
||||
_count = data.length;
|
||||
_head = _tail = null;
|
||||
}
|
||||
|
||||
private ImmutableText(ImmutableText head, ImmutableText tail) {
|
||||
_count = head._count + tail._count;
|
||||
_data = null;
|
||||
_head = head;
|
||||
_tail = tail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text representing the specified object.
|
||||
*
|
||||
* @param obj the object to represent as text.
|
||||
* @return the textual representation of the specified object.
|
||||
*/
|
||||
public static ImmutableText valueOf(Object obj) {
|
||||
if (obj instanceof Number) // Use faster primitive formatting.
|
||||
return valueOfNumber(obj);
|
||||
return valueOf(String.valueOf(obj));
|
||||
}
|
||||
|
||||
// For Integer, Long, Float and Double use direct formatting.
|
||||
private static ImmutableText valueOfNumber(Object num) {
|
||||
if (num instanceof Integer)
|
||||
return valueOf(((Integer)num).intValue());
|
||||
if (num instanceof Long)
|
||||
return valueOf(((Long)num).longValue());
|
||||
if (num instanceof Float)
|
||||
return valueOf(((Float)num).floatValue());
|
||||
if (num instanceof Double)
|
||||
return valueOf(((Double)num).doubleValue());
|
||||
return valueOf(String.valueOf(num));
|
||||
}
|
||||
|
||||
private static ImmutableText valueOf(String str) {
|
||||
return valueOf(str, 0, str.length());
|
||||
}
|
||||
|
||||
private static ImmutableText valueOf(String str, int start, int end) {
|
||||
int length = end - start;
|
||||
if (length <= BLOCK_SIZE) {
|
||||
char[] chars = new char[length];
|
||||
str.getChars(start, end, chars, 0);
|
||||
return new ImmutableText(chars);
|
||||
} else { // Splits on a block boundary.
|
||||
int half = ((length + BLOCK_SIZE) >> 1) & BLOCK_MASK;
|
||||
return new ImmutableText(valueOf(str, start, start + half), valueOf(str, start + half, end));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text that contains the characters from the specified
|
||||
* array.
|
||||
*
|
||||
* @param chars the array source of the characters.
|
||||
* @return the corresponding instance.
|
||||
*/
|
||||
public static ImmutableText valueOf(char[] chars) {
|
||||
return valueOf(chars, 0, chars.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text that contains the characters from the specified
|
||||
* subarray of characters.
|
||||
*
|
||||
* @param chars the source of the characters.
|
||||
* @param offset the index of the first character in the data source.
|
||||
* @param length the length of the text returned.
|
||||
* @return the corresponding instance.
|
||||
* @throws IndexOutOfBoundsException if <code>(offset < 0) ||
|
||||
* (length < 0) || ((offset + length) > chars.length)</code>
|
||||
*/
|
||||
public static ImmutableText valueOf(char[] chars, int offset, int length) {
|
||||
if ((offset < 0) || (length < 0) || ((offset + length) > chars.length))
|
||||
throw new IndexOutOfBoundsException();
|
||||
if (length <= BLOCK_SIZE) {
|
||||
if (offset == 0 && length == chars.length) {
|
||||
return new ImmutableText(chars);
|
||||
}
|
||||
char[] subArray = new char[length];
|
||||
System.arraycopy(chars, offset, subArray, 0, length);
|
||||
return new ImmutableText(subArray);
|
||||
} else { // Splits on a block boundary.
|
||||
int half = ((length + BLOCK_SIZE) >> 1) & BLOCK_MASK;
|
||||
return new ImmutableText(valueOf(chars, offset, half), valueOf(chars, offset + half, length - half));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the text representation of the <code>boolean</code> argument.
|
||||
*
|
||||
* @param b a <code>boolean</code>.
|
||||
* @return if the argument is <code>true</code>, the text
|
||||
* <code>"true"</code> is returned; otherwise, the text
|
||||
* <code>"false"</code> is returned.
|
||||
*/
|
||||
public static ImmutableText valueOf(boolean b) {
|
||||
return b ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
private static final ImmutableText TRUE = valueOf("true");
|
||||
|
||||
private static final ImmutableText FALSE = valueOf("false");
|
||||
|
||||
private static final ImmutableText EMPTY = valueOf("");
|
||||
|
||||
/**
|
||||
* Returns the length of this text.
|
||||
*
|
||||
* @return the number of characters (16-bits Unicode) composing this text.
|
||||
*/
|
||||
public int length() {
|
||||
return _count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the concatenation of this text and the textual
|
||||
* representation of the specified object.
|
||||
*
|
||||
* @param obj the object whose textual representation is concatenated.
|
||||
* @return <code>this.concat(Text.valueOf(obj))</code>
|
||||
*/
|
||||
public ImmutableText plus(Object obj) {
|
||||
return this.concat(valueOf(obj));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the concatenation of this text and the specified
|
||||
* <code>String</code> (optimization).
|
||||
*
|
||||
* @param str the string whose characters are concatenated.
|
||||
* @return <code>this.concat(Text.valueOf(obj))</code>
|
||||
*/
|
||||
public ImmutableText plus(String str) {
|
||||
|
||||
ImmutableText merge = this.append(str);
|
||||
return merge != null ? merge : concat(valueOf(str));
|
||||
}
|
||||
|
||||
private ImmutableText append(String str) { // Try to append, returns null if cannot.
|
||||
int length = str.length();
|
||||
if (_data == null) {
|
||||
ImmutableText merge = _tail.append(str);
|
||||
return merge != null ? new ImmutableText(_head, merge) : null;
|
||||
} else { // Primitive.
|
||||
if (_count + length > BLOCK_SIZE) return null; // Cannot merge.
|
||||
char[] chars = new char[_count + length];
|
||||
System.arraycopy(_data, 0, chars, 0, _count);
|
||||
str.getChars(0, length, chars, _count);
|
||||
return new ImmutableText(chars);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenates the specified text to the end of this text.
|
||||
* This method is very fast (faster even than
|
||||
* <code>StringBuffer.append(String)</code>) and still returns
|
||||
* a text instance with an internal binary tree of minimal depth!
|
||||
*
|
||||
* @param that the text that is concatenated.
|
||||
* @return <code>this + that</code>
|
||||
*/
|
||||
public ImmutableText concat(ImmutableText that) {
|
||||
// All Text instances are maintained balanced:
|
||||
// (head < tail * 2) & (tail < head * 2)
|
||||
|
||||
final int length = this._count + that._count;
|
||||
if (length <= BLOCK_SIZE) { // Merges to primitive.
|
||||
char[] chars = new char[length];
|
||||
this.getChars(0, this._count, chars, 0);
|
||||
that.getChars(0, that._count, chars, this._count);
|
||||
return new ImmutableText(chars);
|
||||
} else { // Returns a composite.
|
||||
ImmutableText head = this;
|
||||
ImmutableText tail = that;
|
||||
|
||||
if (((head._count << 1) < tail._count) && (tail._data == null)) { // tail is composite
|
||||
// head too small, returns (head + tail/2) + (tail/2)
|
||||
if (tail._head._count > tail._tail._count) {
|
||||
// Rotates to concatenate with smaller part.
|
||||
tail = tail.rightRotation();
|
||||
}
|
||||
head = head.concat(tail._head);
|
||||
tail = tail._tail;
|
||||
|
||||
} else if (((tail._count << 1) < head._count)
|
||||
&& (head._data == null)) { // head is composite.
|
||||
// tail too small, returns (head/2) + (head/2 concat tail)
|
||||
if (head._tail._count > head._head._count) {
|
||||
// Rotates to concatenate with smaller part.
|
||||
head = head.leftRotation();
|
||||
}
|
||||
tail = head._tail.concat(tail);
|
||||
head = head._head;
|
||||
}
|
||||
return new ImmutableText(head, tail);
|
||||
}
|
||||
}
|
||||
|
||||
private ImmutableText rightRotation() {
|
||||
// See: http://en.wikipedia.org/wiki/Tree_rotation
|
||||
ImmutableText P = this._head;
|
||||
if (P._data != null)
|
||||
return this; // Head not a composite, cannot rotate.
|
||||
ImmutableText A = P._head;
|
||||
ImmutableText B = P._tail;
|
||||
ImmutableText C = this._tail;
|
||||
return new ImmutableText(A, new ImmutableText(B, C));
|
||||
}
|
||||
|
||||
private ImmutableText leftRotation() {
|
||||
// See: http://en.wikipedia.org/wiki/Tree_rotation
|
||||
ImmutableText Q = this._tail;
|
||||
if (Q._data != null)
|
||||
return this; // Tail not a composite, cannot rotate.
|
||||
ImmutableText B = Q._head;
|
||||
ImmutableText C = Q._tail;
|
||||
ImmutableText A = this._head;
|
||||
return new ImmutableText(new ImmutableText(A, B), C);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a portion of this text.
|
||||
*
|
||||
* @param start the index of the first character inclusive.
|
||||
* @return the sub-text starting at the specified position.
|
||||
* @throws IndexOutOfBoundsException if <code>(start < 0) ||
|
||||
* (start > this.length())</code>
|
||||
*/
|
||||
public ImmutableText subtext(int start) {
|
||||
return subtext(start, length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text having the specified text inserted at
|
||||
* the specified location.
|
||||
*
|
||||
* @param index the insertion position.
|
||||
* @param txt the text being inserted.
|
||||
* @return <code>subtext(0, index).concat(txt).concat(subtext(index))</code>
|
||||
* @throws IndexOutOfBoundsException if <code>(index < 0) ||
|
||||
* (index > this.length())</code>
|
||||
*/
|
||||
public ImmutableText insert(int index, ImmutableText txt) {
|
||||
return subtext(0, index).concat(txt).concat(subtext(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text without the characters between the specified indexes.
|
||||
*
|
||||
* @param start the beginning index, inclusive.
|
||||
* @param end the ending index, exclusive.
|
||||
* @return <code>subtext(0, start).concat(subtext(end))</code>
|
||||
* @throws IndexOutOfBoundsException if <code>(start < 0) || (end < 0) ||
|
||||
* (start > end) || (end > this.length()</code>
|
||||
*/
|
||||
public ImmutableText delete(int start, int end) {
|
||||
if (start > end)
|
||||
throw new IndexOutOfBoundsException();
|
||||
return subtext(0, start).concat(subtext(end));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces each character sequence of this text that matches the specified
|
||||
* target sequence with the specified replacement sequence.
|
||||
*
|
||||
* @param target the character sequence to be replaced.
|
||||
* @param replacement the replacement sequence.
|
||||
* @return the resulting text.
|
||||
*/
|
||||
public ImmutableText replace(CharSequence target, CharSequence replacement) {
|
||||
int i = indexOf(target);
|
||||
return (i < 0) ? this : // No target sequence found.
|
||||
subtext(0, i).concat(valueOf(replacement)).concat(
|
||||
subtext(i + target.length()).replace(target,
|
||||
replacement));
|
||||
}
|
||||
|
||||
public CharSequence subSequence(final int start, final int end) {
|
||||
return new CharSequenceSubSequence(this, start, end) {
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
char[] chars = new char[end - start];
|
||||
getChars(start, end, chars, 0);
|
||||
return StringFactory.createShared(chars);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index within this text of the first occurrence
|
||||
* of the specified character sequence searching forward.
|
||||
*
|
||||
* @param csq a character sequence.
|
||||
* @return the index of the first character of the character sequence found;
|
||||
* or <code>-1</code> if the character sequence is not found.
|
||||
*/
|
||||
public int indexOf(CharSequence csq) {
|
||||
return indexOf(csq, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index within this text of the first occurrence
|
||||
* of the specified characters sequence searching forward from
|
||||
* the specified index.
|
||||
*
|
||||
* @param csq a character sequence.
|
||||
* @param fromIndex the index to start the search from.
|
||||
* @return the index in the range
|
||||
* <code>[fromIndex, length() - csq.length()]</code>
|
||||
* or <code>-1</code> if the character sequence is not found.
|
||||
*/
|
||||
public int indexOf(CharSequence csq, int fromIndex) {
|
||||
|
||||
// Limit cases.
|
||||
final int csqLength = csq.length();
|
||||
final int min = Math.max(0, fromIndex);
|
||||
final int max = _count - csqLength;
|
||||
if (csqLength == 0) {
|
||||
return (min > max) ? -1 : min;
|
||||
}
|
||||
|
||||
// Searches for csq.
|
||||
final char c = csq.charAt(0);
|
||||
for (int i = indexOf(c, min); (i >= 0) && (i <= max); i = indexOf(c,
|
||||
++i)) {
|
||||
boolean match = true;
|
||||
for (int j = 1; j < csqLength; j++) {
|
||||
if (this.charAt(i + j) != csq.charAt(j)) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index within this text of the last occurrence of
|
||||
* the specified characters sequence searching backward.
|
||||
*
|
||||
* @param csq a character sequence.
|
||||
* @return the index of the first character of the character sequence found;
|
||||
* or <code>-1</code> if the character sequence is not found.
|
||||
*/
|
||||
public int lastIndexOf(CharSequence csq) {
|
||||
return lastIndexOf(csq, _count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index within this text of the last occurrence of
|
||||
* the specified character sequence searching backward from the specified
|
||||
* index.
|
||||
*
|
||||
* @param csq a character sequence.
|
||||
* @param fromIndex the index to start the backward search from.
|
||||
* @return the index in the range <code>[0, fromIndex]</code> or
|
||||
* <code>-1</code> if the character sequence is not found.
|
||||
*/
|
||||
public int lastIndexOf(CharSequence csq, int fromIndex) {
|
||||
|
||||
// Limit cases.
|
||||
final int csqLength = csq.length();
|
||||
final int min = 0;
|
||||
final int max = Math.min(fromIndex, _count - csqLength);
|
||||
if (csqLength == 0) {
|
||||
return (min > max) ? -1 : max;
|
||||
}
|
||||
|
||||
// Searches for csq.
|
||||
final char c = csq.charAt(0);
|
||||
for (int i = lastIndexOf(c, max); (i >= 0); i = lastIndexOf(c, --i)) {
|
||||
boolean match = true;
|
||||
for (int j = 1; j < csqLength; j++) {
|
||||
if (this.charAt(i + j) != csq.charAt(j)) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this text starts with the specified prefix.
|
||||
*
|
||||
* @param prefix the prefix.
|
||||
* @return <code>true</code> if the character sequence represented by the
|
||||
* argument is a prefix of the character sequence represented by
|
||||
* this text; <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean startsWith(CharSequence prefix) {
|
||||
return startsWith(prefix, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this text ends with the specified suffix.
|
||||
*
|
||||
* @param suffix the suffix.
|
||||
* @return <code>true</code> if the character sequence represented by the
|
||||
* argument is a suffix of the character sequence represented by
|
||||
* this text; <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean endsWith(CharSequence suffix) {
|
||||
return startsWith(suffix, length() - suffix.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this text starts with the specified prefix
|
||||
* at the specified index.
|
||||
*
|
||||
* @param prefix the prefix.
|
||||
* @param index the index of the prefix location in this string.
|
||||
* @return <code>this.substring(index).startsWith(prefix)</code>
|
||||
*/
|
||||
public boolean startsWith(CharSequence prefix, int index) {
|
||||
final int prefixLength = prefix.length();
|
||||
if ((index >= 0) && (index <= (this.length() - prefixLength))) {
|
||||
for (int i = 0, j = index; i < prefixLength;) {
|
||||
if (prefix.charAt(i++) != this.charAt(j++)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this text, with leading and trailing
|
||||
* whitespace omitted.
|
||||
*
|
||||
* @return a copy of this text with leading and trailing white
|
||||
* space removed, or this text if it has no leading or
|
||||
* trailing white space.
|
||||
*/
|
||||
public ImmutableText trim() {
|
||||
int first = 0; // First character index.
|
||||
int last = length() - 1; // Last character index.
|
||||
while ((first <= last) && (charAt(first) <= ' ')) {
|
||||
first++;
|
||||
}
|
||||
while ((last >= first) && (charAt(last) <= ' ')) {
|
||||
last--;
|
||||
}
|
||||
return subtext(first, last + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this text has the same character content as the specified
|
||||
* character sequence.
|
||||
*
|
||||
* @param csq the character sequence to compare with.
|
||||
* @return <code>true</code> if the specified character sequence has the
|
||||
* same character content as this text; <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean contentEquals(CharSequence csq) {
|
||||
if (csq.length() != _count)
|
||||
return false;
|
||||
for (int i = 0; i < _count;) {
|
||||
if (this.charAt(i) != csq.charAt(i++))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this text against the specified object for equality.
|
||||
* Returns <code>true</code> if the specified object is a text having
|
||||
* the same character sequence as this text.
|
||||
* For generic comparison with any character sequence the
|
||||
* {@link #contentEquals(CharSequence)} should be used.
|
||||
*
|
||||
* @param obj the object to compare with or <code>null</code>.
|
||||
* @return <code>true</code> if that is a text with the same character
|
||||
* sequence as this text; <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (!(obj instanceof ImmutableText))
|
||||
return false;
|
||||
final ImmutableText that = (ImmutableText) obj;
|
||||
if (this._count != that._count)
|
||||
return false;
|
||||
for (int i = 0; i < _count;) {
|
||||
if (this.charAt(i) != that.charAt(i++))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash code for this text.
|
||||
*
|
||||
* @return the hash code value.
|
||||
*/
|
||||
public int hashCode() {
|
||||
int h = 0;
|
||||
final int length = this.length();
|
||||
for (int i = 0; i < length;) {
|
||||
h = 31 * h + charAt(i++);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the current statistics on this text tree structure.
|
||||
*
|
||||
* @param out the stream to use for output (e.g. <code>System.out</code>)
|
||||
*/
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public void printStatistics(PrintStream out) {
|
||||
int length = this.length();
|
||||
int leaves = getNbrOfLeaves();
|
||||
out.print("LENGTH: " + length());
|
||||
out.print(", MAX DEPTH: " + getDepth());
|
||||
out.print(", NBR OF BRANCHES: " + getNbrOfBranches());
|
||||
out.print(", NBR OF LEAVES: " + leaves);
|
||||
out.print(", AVG LEAVE LENGTH: " + (length + (leaves >> 1)) / leaves);
|
||||
out.println();
|
||||
}
|
||||
private int getDepth() {
|
||||
if (_data != null) // Primitive.
|
||||
return 0;
|
||||
return Math.max(_head.getDepth(), _tail.getDepth()) + 1;
|
||||
}
|
||||
private int getNbrOfBranches() {
|
||||
return (_data == null) ?
|
||||
_head.getNbrOfBranches() + _tail.getNbrOfBranches() + 1 : 0;
|
||||
}
|
||||
private int getNbrOfLeaves() {
|
||||
return (_data == null) ?
|
||||
_head.getNbrOfLeaves() + _tail.getNbrOfLeaves() : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints out this text to the specified writer.
|
||||
*
|
||||
* @param writer the destination writer.
|
||||
*/
|
||||
public void print(Writer writer) throws IOException {
|
||||
if (_data != null) { // Primitive
|
||||
writer.write(_data, 0, _count);
|
||||
} else { // Composite.
|
||||
_head.print(writer);
|
||||
_tail.print(writer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints out this text to the specified writer and then terminates
|
||||
* the line.
|
||||
*
|
||||
* @param writer the destination writer.
|
||||
*/
|
||||
public void println(Writer writer) throws IOException {
|
||||
print(writer);
|
||||
writer.write('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the character at the specified index.
|
||||
*
|
||||
* @param index the index of the character.
|
||||
* @return the character at the specified index.
|
||||
* @throws IndexOutOfBoundsException if <code>(index < 0) ||
|
||||
* (index >= this.length())</code>
|
||||
*/
|
||||
public char charAt(int index) {
|
||||
InnerLeaf leaf = myLastLeaf;
|
||||
if (leaf == null || index < leaf.offset || index >= leaf.offset + leaf.leafText._count) {
|
||||
leaf = myLastLeaf = findLeaf(index, 0);
|
||||
}
|
||||
return leaf.leafText._data[index - leaf.offset];
|
||||
}
|
||||
private volatile InnerLeaf myLastLeaf;
|
||||
|
||||
private InnerLeaf findLeaf(int index, int offset) {
|
||||
ImmutableText node = this;
|
||||
while (true) {
|
||||
if (index >= node._count) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
if (node._data != null) {
|
||||
return new InnerLeaf(node, offset);
|
||||
}
|
||||
if (index < node._head._count) {
|
||||
node = node._head;
|
||||
} else {
|
||||
offset += node._head._count;
|
||||
index -= node._head._count;
|
||||
node = node._tail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class InnerLeaf {
|
||||
final ImmutableText leafText;
|
||||
final int offset;
|
||||
|
||||
private InnerLeaf(ImmutableText leafText, int offset) {
|
||||
this.leafText = leafText;
|
||||
this.offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index within this text of the first occurrence of the
|
||||
* specified character, starting the search at the specified index.
|
||||
*
|
||||
* @param c the character to search for.
|
||||
* @param fromIndex the index to start the search from.
|
||||
* @return the index of the first occurrence of the character in this text
|
||||
* that is greater than or equal to <code>fromIndex</code>,
|
||||
* or <code>-1</code> if the character does not occur.
|
||||
*/
|
||||
public int indexOf(char c, int fromIndex) {
|
||||
if (_data != null) { // Primitive.
|
||||
for (int i = Math.max(fromIndex, 0); i < _count; i++) {
|
||||
if (_data[i] == c)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
} else { // Composite.
|
||||
final int cesure = _head._count;
|
||||
if (fromIndex < cesure) {
|
||||
final int headIndex = _head.indexOf(c, fromIndex);
|
||||
if (headIndex >= 0)
|
||||
return headIndex; // Found in head.
|
||||
}
|
||||
final int tailIndex = _tail.indexOf(c, fromIndex - cesure);
|
||||
return (tailIndex >= 0) ? tailIndex + cesure : -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index within this text of the first occurrence of the
|
||||
* specified character, searching backward and starting at the specified
|
||||
* index.
|
||||
*
|
||||
* @param c the character to search for.
|
||||
* @param fromIndex the index to start the search backward from.
|
||||
* @return the index of the first occurrence of the character in this text
|
||||
* that is less than or equal to <code>fromIndex</code>,
|
||||
* or <code>-1</code> if the character does not occur.
|
||||
*/
|
||||
public int lastIndexOf(char c, int fromIndex) {
|
||||
if (_data != null) { // Primitive.
|
||||
for (int i = Math.min(fromIndex, _count - 1); i >= 0; i--) {
|
||||
if (_data[i] == c)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
} else { // Composite.
|
||||
final int cesure = _head._count;
|
||||
if (fromIndex >= cesure) {
|
||||
final int tailIndex = _tail.lastIndexOf(c, fromIndex - cesure);
|
||||
if (tailIndex >= 0)
|
||||
return tailIndex + cesure; // Found in tail.
|
||||
}
|
||||
return _head.lastIndexOf(c, fromIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a portion of this text.
|
||||
*
|
||||
* @param start the index of the first character inclusive.
|
||||
* @param end the index of the last character exclusive.
|
||||
* @return the sub-text starting at the specified start position and
|
||||
* ending just before the specified end position.
|
||||
* @throws IndexOutOfBoundsException if <code>(start < 0) || (end < 0) ||
|
||||
* (start > end) || (end > this.length())</code>
|
||||
*/
|
||||
public ImmutableText subtext(int start, int end) {
|
||||
if (_data != null) { // Primitive.
|
||||
if ((start < 0) || (start > end) || (end > _count))
|
||||
throw new IndexOutOfBoundsException();
|
||||
if ((start == 0) && (end == _count))
|
||||
return this;
|
||||
if (start == end)
|
||||
return EMPTY;
|
||||
int length = end - start;
|
||||
char[] chars = new char[length];
|
||||
System.arraycopy(_data, start, chars, 0, length);
|
||||
return new ImmutableText(chars);
|
||||
} else { // Composite.
|
||||
final int cesure = _head._count;
|
||||
if (end <= cesure)
|
||||
return _head.subtext(start, end);
|
||||
if (start >= cesure)
|
||||
return _tail.subtext(start - cesure, end - cesure);
|
||||
if ((start == 0) && (end == _count))
|
||||
return this;
|
||||
// Overlaps head and tail.
|
||||
return _head.subtext(start, cesure).concat(
|
||||
_tail.subtext(0, end - cesure));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the characters from this text into the destination
|
||||
* character array.
|
||||
*
|
||||
* @param start the index of the first character to copy.
|
||||
* @param end the index after the last character to copy.
|
||||
* @param dest the destination array.
|
||||
* @param destPos the start offset in the destination array.
|
||||
* @throws IndexOutOfBoundsException if <code>(start < 0) || (end < 0) ||
|
||||
* (start > end) || (end > this.length())</code>
|
||||
*/
|
||||
public void getChars(int start, int end, char[] dest, int destPos) {
|
||||
if (_data != null) { // Primitive.
|
||||
if ((start < 0) || (end > _count) || (start > end))
|
||||
throw new IndexOutOfBoundsException();
|
||||
System.arraycopy(_data, start, dest, destPos, end - start);
|
||||
} else { // Composite.
|
||||
final int cesure = _head._count;
|
||||
if (end <= cesure) {
|
||||
_head.getChars(start, end, dest, destPos);
|
||||
} else if (start >= cesure) {
|
||||
_tail.getChars(start - cesure, end - cesure, dest, destPos);
|
||||
} else { // Overlaps head and tail.
|
||||
_head.getChars(start, cesure, dest, destPos);
|
||||
_tail.getChars(0, end - cesure, dest, destPos + cesure - start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <code>String</code> representation of this text.
|
||||
*
|
||||
* @return the <code>java.lang.String</code> for this text.
|
||||
*/
|
||||
@NotNull
|
||||
public String toString() {
|
||||
if (_data != null) { // Primitive.
|
||||
return new String(_data, 0, _count);
|
||||
} else { // Composite.
|
||||
char[] data = new char[_count];
|
||||
this.getChars(0, _count, data, 0);
|
||||
return new String(data, 0, _count);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user