From 5b6dadbd1604d67a77c815d7ef4ad2e7afa7995e Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 28 Nov 2013 11:37:28 +0100 Subject: [PATCH] use ImmutableText for document contents --- .../openapi/editor/impl/DocumentImpl.java | 116 ++- .../openapi/editor/impl/EditorImpl.java | 90 +- .../com/intellij/util/text/ImmutableText.java | 845 ++++++++++++++++++ 3 files changed, 943 insertions(+), 108 deletions(-) create mode 100644 platform/util/src/com/intellij/util/text/ImmutableText.java diff --git a/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java b/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java index cf8877f34fc6..4b4357be8803 100644 --- a/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java +++ b/platform/core-impl/src/com/intellij/openapi/editor/impl/DocumentImpl.java @@ -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() { @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(); - } - } - } - } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index ed42071c5b69..ce640085e6d4 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -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 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); } } } diff --git a/platform/util/src/com/intellij/util/text/ImmutableText.java b/platform/util/src/com/intellij/util/text/ImmutableText.java new file mode 100644 index 000000000000..8c59088f9378 --- /dev/null +++ b/platform/util/src/com/intellij/util/text/ImmutableText.java @@ -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 + * + *

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).

+ *

This class has the same methods as + * + * Java String with the following benefits:

    + *
  • No need for an intermediate + * {@link StringBuffer}/{@link StringBuilder} in order to manipulate + * textual documents (insertion, deletion or concatenation).
  • + *
  • More flexible as they allows for search and comparison with any + * java.lang.String or CharSequence.
  • + *

+ * + *

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 tree rotations. + * Insertion/deletions are performed in O[Log(n)] + * instead of O[n] for + * StringBuffer/StringBuilder.

+ * + * @author Jean-Marie Dautelle + * @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 null (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 (offset < 0) || + * (length < 0) || ((offset + length) > chars.length) + */ + 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 boolean argument. + * + * @param b a boolean. + * @return if the argument is true, the text + * "true" is returned; otherwise, the text + * "false" 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 this.concat(Text.valueOf(obj)) + */ + public ImmutableText plus(Object obj) { + return this.concat(valueOf(obj)); + } + + /** + * Returns the concatenation of this text and the specified + * String (optimization). + * + * @param str the string whose characters are concatenated. + * @return this.concat(Text.valueOf(obj)) + */ + 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 + * StringBuffer.append(String)) and still returns + * a text instance with an internal binary tree of minimal depth! + * + * @param that the text that is concatenated. + * @return this + that + */ + 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 (start < 0) || + * (start > this.length()) + */ + 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 subtext(0, index).concat(txt).concat(subtext(index)) + * @throws IndexOutOfBoundsException if (index < 0) || + * (index > this.length()) + */ + 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 subtext(0, start).concat(subtext(end)) + * @throws IndexOutOfBoundsException if (start < 0) || (end < 0) || + * (start > end) || (end > this.length() + */ + 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 -1 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 + * [fromIndex, length() - csq.length()] + * or -1 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 -1 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 [0, fromIndex] or + * -1 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 true if the character sequence represented by the + * argument is a prefix of the character sequence represented by + * this text; false otherwise. + */ + public boolean startsWith(CharSequence prefix) { + return startsWith(prefix, 0); + } + + /** + * Indicates if this text ends with the specified suffix. + * + * @param suffix the suffix. + * @return true if the character sequence represented by the + * argument is a suffix of the character sequence represented by + * this text; false 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 this.substring(index).startsWith(prefix) + */ + 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 true if the specified character sequence has the + * same character content as this text; false 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 true 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 null. + * @return true if that is a text with the same character + * sequence as this text; false 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. System.out) + */ + @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 (index < 0) || + * (index >= this.length()) + */ + 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 fromIndex, + * or -1 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 fromIndex, + * or -1 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 (start < 0) || (end < 0) || + * (start > end) || (end > this.length()) + */ + 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 (start < 0) || (end < 0) || + * (start > end) || (end > this.length()) + */ + 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 String representation of this text. + * + * @return the java.lang.String 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); + } + } + +} \ No newline at end of file