use ImmutableText for document contents

This commit is contained in:
peter
2013-11-28 11:38:27 +01:00
parent 3c553d6caa
commit 5b6dadbd16
3 changed files with 943 additions and 108 deletions
@@ -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);
}
}
}