2 * Copyright (C) 2006 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 import android.annotation.Nullable;
20 import android.graphics.Canvas;
21 import android.graphics.Paint;
22 import android.text.style.ParagraphStyle;
23 import android.util.Log;
25 import com.android.internal.util.ArrayUtils;
26 import com.android.internal.util.GrowingArrayUtils;
28 import libcore.util.EmptyArray;
30 import java.lang.reflect.Array;
31 import java.util.IdentityHashMap;
34 * This is the class for text whose content and markup can both be changed.
36 public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable,
37 Appendable, GraphicsOperations {
38 private final static String TAG = "SpannableStringBuilder";
40 * Create a new SpannableStringBuilder with empty contents
42 public SpannableStringBuilder() {
47 * Create a new SpannableStringBuilder containing a copy of the
48 * specified text, including its spans if any.
50 public SpannableStringBuilder(CharSequence text) {
51 this(text, 0, text.length());
55 * Create a new SpannableStringBuilder containing a copy of the
56 * specified slice of the specified text, including its spans if any.
58 public SpannableStringBuilder(CharSequence text, int start, int end) {
59 int srclen = end - start;
61 if (srclen < 0) throw new StringIndexOutOfBoundsException();
63 mText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(srclen));
65 mGapLength = mText.length - srclen;
67 TextUtils.getChars(text, start, end, mText, 0);
71 mSpans = EmptyArray.OBJECT;
72 mSpanStarts = EmptyArray.INT;
73 mSpanEnds = EmptyArray.INT;
74 mSpanFlags = EmptyArray.INT;
75 mSpanMax = EmptyArray.INT;
76 mSpanOrder = EmptyArray.INT;
77 mPrioSortBuffer = EmptyArray.INT;
78 mOrderSortBuffer = EmptyArray.INT;
80 if (text instanceof Spanned) {
81 Spanned sp = (Spanned) text;
82 Object[] spans = sp.getSpans(start, end, Object.class);
84 for (int i = 0; i < spans.length; i++) {
85 if (spans[i] instanceof NoCopySpan) {
89 int st = sp.getSpanStart(spans[i]) - start;
90 int en = sp.getSpanEnd(spans[i]) - start;
91 int fl = sp.getSpanFlags(spans[i]);
100 if (en > end - start)
103 setSpan(false, spans[i], st, en, fl);
109 public static SpannableStringBuilder valueOf(CharSequence source) {
110 if (source instanceof SpannableStringBuilder) {
111 return (SpannableStringBuilder) source;
113 return new SpannableStringBuilder(source);
118 * Return the char at the specified offset within the buffer.
120 public char charAt(int where) {
123 throw new IndexOutOfBoundsException("charAt: " + where + " < 0");
124 } else if (where >= len) {
125 throw new IndexOutOfBoundsException("charAt: " + where + " >= length " + len);
128 if (where >= mGapStart)
129 return mText[where + mGapLength];
135 * Return the number of chars in the buffer.
137 public int length() {
138 return mText.length - mGapLength;
141 private void resizeFor(int size) {
142 final int oldLength = mText.length;
143 if (size + 1 <= oldLength) {
147 char[] newText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(size));
148 System.arraycopy(mText, 0, newText, 0, mGapStart);
149 final int newLength = newText.length;
150 final int delta = newLength - oldLength;
151 final int after = oldLength - (mGapStart + mGapLength);
152 System.arraycopy(mText, oldLength - after, newText, newLength - after, after);
157 new Exception("mGapLength < 1").printStackTrace();
159 if (mSpanCount != 0) {
160 for (int i = 0; i < mSpanCount; i++) {
161 if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += delta;
162 if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += delta;
168 private void moveGapTo(int where) {
169 if (where == mGapStart)
172 boolean atEnd = (where == length());
174 if (where < mGapStart) {
175 int overlap = mGapStart - where;
176 System.arraycopy(mText, where, mText, mGapStart + mGapLength - overlap, overlap);
177 } else /* where > mGapStart */ {
178 int overlap = where - mGapStart;
179 System.arraycopy(mText, where + mGapLength - overlap, mText, mGapStart, overlap);
182 // TODO: be more clever (although the win really isn't that big)
183 if (mSpanCount != 0) {
184 for (int i = 0; i < mSpanCount; i++) {
185 int start = mSpanStarts[i];
186 int end = mSpanEnds[i];
188 if (start > mGapStart)
192 else if (start == where) {
193 int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
195 if (flag == POINT || (atEnd && flag == PARAGRAPH))
203 else if (end == where) {
204 int flag = (mSpanFlags[i] & END_MASK);
206 if (flag == POINT || (atEnd && flag == PARAGRAPH))
210 mSpanStarts[i] = start;
219 // Documentation from interface
220 public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) {
221 return replace(where, where, tb, start, end);
224 // Documentation from interface
225 public SpannableStringBuilder insert(int where, CharSequence tb) {
226 return replace(where, where, tb, 0, tb.length());
229 // Documentation from interface
230 public SpannableStringBuilder delete(int start, int end) {
231 SpannableStringBuilder ret = replace(start, end, "", 0, 0);
233 if (mGapLength > 2 * length())
236 return ret; // == this
239 // Documentation from interface
240 public void clear() {
241 replace(0, length(), "", 0, 0);
242 mSpanInsertCount = 0;
245 // Documentation from interface
246 public void clearSpans() {
247 for (int i = mSpanCount - 1; i >= 0; i--) {
248 Object what = mSpans[i];
249 int ostart = mSpanStarts[i];
250 int oend = mSpanEnds[i];
252 if (ostart > mGapStart)
253 ostart -= mGapLength;
254 if (oend > mGapStart)
260 sendSpanRemoved(what, ostart, oend);
262 if (mIndexOfSpan != null) {
263 mIndexOfSpan.clear();
265 mSpanInsertCount = 0;
268 // Documentation from interface
269 public SpannableStringBuilder append(CharSequence text) {
270 int length = length();
271 return replace(length, length, text, 0, text.length());
275 * Appends the character sequence {@code text} and spans {@code what} over the appended part.
276 * See {@link Spanned} for an explanation of what the flags mean.
277 * @param text the character sequence to append.
278 * @param what the object to be spanned over the appended text.
279 * @param flags see {@link Spanned}.
280 * @return this {@code SpannableStringBuilder}.
282 public SpannableStringBuilder append(CharSequence text, Object what, int flags) {
283 int start = length();
285 setSpan(what, start, length(), flags);
289 // Documentation from interface
290 public SpannableStringBuilder append(CharSequence text, int start, int end) {
291 int length = length();
292 return replace(length, length, text, start, end);
295 // Documentation from interface
296 public SpannableStringBuilder append(char text) {
297 return append(String.valueOf(text));
300 // Returns true if a node was removed (so we can restart search from root)
301 private boolean removeSpansForChange(int start, int end, boolean textIsRemoved, int i) {
303 // internal tree node
304 if (resolveGap(mSpanMax[i]) >= start &&
305 removeSpansForChange(start, end, textIsRemoved, leftChild(i))) {
309 if (i < mSpanCount) {
310 if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
311 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE &&
312 mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength &&
313 mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength &&
314 // The following condition indicates that the span would become empty
315 (textIsRemoved || mSpanStarts[i] > start || mSpanEnds[i] < mGapStart)) {
316 mIndexOfSpan.remove(mSpans[i]);
320 return resolveGap(mSpanStarts[i]) <= end && (i & 1) != 0 &&
321 removeSpansForChange(start, end, textIsRemoved, rightChild(i));
326 private void change(int start, int end, CharSequence cs, int csStart, int csEnd) {
328 final int replacedLength = end - start;
329 final int replacementLength = csEnd - csStart;
330 final int nbNewChars = replacementLength - replacedLength;
332 boolean changed = false;
333 for (int i = mSpanCount - 1; i >= 0; i--) {
334 int spanStart = mSpanStarts[i];
335 if (spanStart > mGapStart)
336 spanStart -= mGapLength;
338 int spanEnd = mSpanEnds[i];
339 if (spanEnd > mGapStart)
340 spanEnd -= mGapLength;
342 if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
347 if (spanStart > start && spanStart <= end) {
348 for (spanStart = end; spanStart < clen; spanStart++)
349 if (spanStart > end && charAt(spanStart - 1) == '\n')
353 if (spanEnd > start && spanEnd <= end) {
354 for (spanEnd = end; spanEnd < clen; spanEnd++)
355 if (spanEnd > end && charAt(spanEnd - 1) == '\n')
359 if (spanStart != ost || spanEnd != oen) {
360 setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]);
366 if (spanStart == start) flags |= SPAN_START_AT_START;
367 else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END;
368 if (spanEnd == start) flags |= SPAN_END_AT_START;
369 else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END;
370 mSpanFlags[i] |= flags;
378 if (nbNewChars >= mGapLength) {
379 resizeFor(mText.length + nbNewChars - mGapLength);
382 final boolean textIsRemoved = replacementLength == 0;
383 // The removal pass needs to be done before the gap is updated in order to broadcast the
384 // correct previous positions to the correct intersecting SpanWatchers
385 if (replacedLength > 0) { // no need for span fixup on pure insertion
386 while (mSpanCount > 0 &&
387 removeSpansForChange(start, end, textIsRemoved, treeRoot())) {
388 // keep deleting spans as needed, and restart from root after every deletion
389 // because deletion can invalidate an index.
393 mGapStart += nbNewChars;
394 mGapLength -= nbNewChars;
397 new Exception("mGapLength < 1").printStackTrace();
399 TextUtils.getChars(cs, csStart, csEnd, mText, start);
401 if (replacedLength > 0) { // no need for span fixup on pure insertion
402 // TODO potential optimization: only update bounds on intersecting spans
403 final boolean atEnd = (mGapStart + mGapLength == mText.length);
405 for (int i = 0; i < mSpanCount; i++) {
406 final int startFlag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
407 mSpanStarts[i] = updatedIntervalBound(mSpanStarts[i], start, nbNewChars, startFlag,
408 atEnd, textIsRemoved);
410 final int endFlag = (mSpanFlags[i] & END_MASK);
411 mSpanEnds[i] = updatedIntervalBound(mSpanEnds[i], start, nbNewChars, endFlag,
412 atEnd, textIsRemoved);
414 // TODO potential optimization: only fix up invariants when bounds actually changed
418 if (cs instanceof Spanned) {
419 Spanned sp = (Spanned) cs;
420 Object[] spans = sp.getSpans(csStart, csEnd, Object.class);
422 for (int i = 0; i < spans.length; i++) {
423 int st = sp.getSpanStart(spans[i]);
424 int en = sp.getSpanEnd(spans[i]);
426 if (st < csStart) st = csStart;
427 if (en > csEnd) en = csEnd;
429 // Add span only if this object is not yet used as a span in this string
430 if (getSpanStart(spans[i]) < 0) {
431 int copySpanStart = st - csStart + start;
432 int copySpanEnd = en - csStart + start;
433 int copySpanFlags = sp.getSpanFlags(spans[i]) | SPAN_ADDED;
435 int flagsStart = (copySpanFlags & START_MASK) >> START_SHIFT;
436 int flagsEnd = copySpanFlags & END_MASK;
438 if(!isInvalidParagraphStart(copySpanStart, flagsStart) &&
439 !isInvalidParagraphEnd(copySpanEnd, flagsEnd)) {
440 setSpan(false, spans[i], copySpanStart, copySpanEnd, copySpanFlags);
448 private int updatedIntervalBound(int offset, int start, int nbNewChars, int flag, boolean atEnd,
449 boolean textIsRemoved) {
450 if (offset >= start && offset < mGapStart + mGapLength) {
452 // A POINT located inside the replaced range should be moved to the end of the
454 // The exception is when the point is at the start of the range and we are doing a
455 // text replacement (as opposed to a deletion): the point stays there.
456 if (textIsRemoved || offset > start) {
457 return mGapStart + mGapLength;
460 if (flag == PARAGRAPH) {
462 return mGapStart + mGapLength;
465 // MARKs should be moved to the start, with the exception of a mark located at
466 // the end of the range (which will be < mGapStart + mGapLength since mGapLength
467 // is > 0, which should stay 'unchanged' at the end of the replaced text.
468 if (textIsRemoved || offset < mGapStart - nbNewChars) {
471 // Move to the end of replaced text (needed if nbNewChars != 0)
480 // Note: caller is responsible for removing the mIndexOfSpan entry.
481 private void removeSpan(int i) {
482 Object object = mSpans[i];
484 int start = mSpanStarts[i];
485 int end = mSpanEnds[i];
487 if (start > mGapStart) start -= mGapLength;
488 if (end > mGapStart) end -= mGapLength;
490 int count = mSpanCount - (i + 1);
491 System.arraycopy(mSpans, i + 1, mSpans, i, count);
492 System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
493 System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
494 System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
495 System.arraycopy(mSpanOrder, i + 1, mSpanOrder, i, count);
500 mSpans[mSpanCount] = null;
502 // Invariants must be restored before sending span removed notifications.
505 sendSpanRemoved(object, start, end);
508 // Documentation from interface
509 public SpannableStringBuilder replace(int start, int end, CharSequence tb) {
510 return replace(start, end, tb, 0, tb.length());
513 // Documentation from interface
514 public SpannableStringBuilder replace(final int start, final int end,
515 CharSequence tb, int tbstart, int tbend) {
516 checkRange("replace", start, end);
518 int filtercount = mFilters.length;
519 for (int i = 0; i < filtercount; i++) {
520 CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);
525 tbend = repl.length();
529 final int origLen = end - start;
530 final int newLen = tbend - tbstart;
532 if (origLen == 0 && newLen == 0 && !hasNonExclusiveExclusiveSpanAt(tb, tbstart)) {
533 // This is a no-op iif there are no spans in tb that would be added (with a 0-length)
534 // Early exit so that the text watchers do not get notified
538 TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);
539 sendBeforeTextChanged(textWatchers, start, origLen, newLen);
541 // Try to keep the cursor / selection at the same relative position during
542 // a text replacement. If replaced or replacement text length is zero, this
543 // is already taken care of.
544 boolean adjustSelection = origLen != 0 && newLen != 0;
545 int selectionStart = 0;
546 int selectionEnd = 0;
547 if (adjustSelection) {
548 selectionStart = Selection.getSelectionStart(this);
549 selectionEnd = Selection.getSelectionEnd(this);
552 change(start, end, tb, tbstart, tbend);
554 if (adjustSelection) {
555 boolean changed = false;
556 if (selectionStart > start && selectionStart < end) {
557 final int offset = (selectionStart - start) * newLen / origLen;
558 selectionStart = start + offset;
561 setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,
562 Spanned.SPAN_POINT_POINT);
564 if (selectionEnd > start && selectionEnd < end) {
565 final int offset = (selectionEnd - start) * newLen / origLen;
566 selectionEnd = start + offset;
569 setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,
570 Spanned.SPAN_POINT_POINT);
577 sendTextChanged(textWatchers, start, origLen, newLen);
578 sendAfterTextChanged(textWatchers);
580 // Span watchers need to be called after text watchers, which may update the layout
581 sendToSpanWatchers(start, end, newLen - origLen);
586 private static boolean hasNonExclusiveExclusiveSpanAt(CharSequence text, int offset) {
587 if (text instanceof Spanned) {
588 Spanned spanned = (Spanned) text;
589 Object[] spans = spanned.getSpans(offset, offset, Object.class);
590 final int length = spans.length;
591 for (int i = 0; i < length; i++) {
592 Object span = spans[i];
593 int flags = spanned.getSpanFlags(span);
594 if (flags != Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) return true;
600 private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) {
601 for (int i = 0; i < mSpanCount; i++) {
602 int spanFlags = mSpanFlags[i];
604 // This loop handles only modified (not added) spans.
605 if ((spanFlags & SPAN_ADDED) != 0) continue;
606 int spanStart = mSpanStarts[i];
607 int spanEnd = mSpanEnds[i];
608 if (spanStart > mGapStart) spanStart -= mGapLength;
609 if (spanEnd > mGapStart) spanEnd -= mGapLength;
611 int newReplaceEnd = replaceEnd + nbNewChars;
612 boolean spanChanged = false;
614 int previousSpanStart = spanStart;
615 if (spanStart > newReplaceEnd) {
616 if (nbNewChars != 0) {
617 previousSpanStart -= nbNewChars;
620 } else if (spanStart >= replaceStart) {
621 // No change if span start was already at replace interval boundaries before replace
622 if ((spanStart != replaceStart ||
623 ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) &&
624 (spanStart != newReplaceEnd ||
625 ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) {
626 // TODO A correct previousSpanStart cannot be computed at this point.
627 // It would require to save all the previous spans' positions before the replace
628 // Using an invalid -1 value to convey this would break the broacast range
633 int previousSpanEnd = spanEnd;
634 if (spanEnd > newReplaceEnd) {
635 if (nbNewChars != 0) {
636 previousSpanEnd -= nbNewChars;
639 } else if (spanEnd >= replaceStart) {
640 // No change if span start was already at replace interval boundaries before replace
641 if ((spanEnd != replaceStart ||
642 ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) &&
643 (spanEnd != newReplaceEnd ||
644 ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) {
645 // TODO same as above for previousSpanEnd
651 sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd);
653 mSpanFlags[i] &= ~SPAN_START_END_MASK;
656 // Handle added spans
657 for (int i = 0; i < mSpanCount; i++) {
658 int spanFlags = mSpanFlags[i];
659 if ((spanFlags & SPAN_ADDED) != 0) {
660 mSpanFlags[i] &= ~SPAN_ADDED;
661 int spanStart = mSpanStarts[i];
662 int spanEnd = mSpanEnds[i];
663 if (spanStart > mGapStart) spanStart -= mGapLength;
664 if (spanEnd > mGapStart) spanEnd -= mGapLength;
665 sendSpanAdded(mSpans[i], spanStart, spanEnd);
671 * Mark the specified range of text with the specified object.
672 * The flags determine how the span will behave when text is
673 * inserted at the start or end of the span's range.
675 public void setSpan(Object what, int start, int end, int flags) {
676 setSpan(true, what, start, end, flags);
679 // Note: if send is false, then it is the caller's responsibility to restore
680 // invariants. If send is false and the span already exists, then this method
681 // will not change the index of any spans.
682 private void setSpan(boolean send, Object what, int start, int end, int flags) {
683 checkRange("setSpan", start, end);
685 int flagsStart = (flags & START_MASK) >> START_SHIFT;
686 if(isInvalidParagraphStart(start, flagsStart)) {
687 throw new RuntimeException("PARAGRAPH span must start at paragraph boundary");
690 int flagsEnd = flags & END_MASK;
691 if(isInvalidParagraphEnd(end, flagsEnd)) {
692 throw new RuntimeException("PARAGRAPH span must end at paragraph boundary");
695 // 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
696 if (flagsStart == POINT && flagsEnd == MARK && start == end) {
698 Log.e(TAG, "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length");
700 // Silently ignore invalid spans when they are created from this class.
701 // This avoids the duplication of the above test code before all the
702 // calls to setSpan that are done in this class
709 if (start > mGapStart) {
711 } else if (start == mGapStart) {
712 if (flagsStart == POINT || (flagsStart == PARAGRAPH && start == length()))
716 if (end > mGapStart) {
718 } else if (end == mGapStart) {
719 if (flagsEnd == POINT || (flagsEnd == PARAGRAPH && end == length()))
723 if (mIndexOfSpan != null) {
724 Integer index = mIndexOfSpan.get(what);
727 int ostart = mSpanStarts[i];
728 int oend = mSpanEnds[i];
730 if (ostart > mGapStart)
731 ostart -= mGapLength;
732 if (oend > mGapStart)
735 mSpanStarts[i] = start;
737 mSpanFlags[i] = flags;
741 sendSpanChanged(what, ostart, oend, nstart, nend);
748 mSpans = GrowingArrayUtils.append(mSpans, mSpanCount, what);
749 mSpanStarts = GrowingArrayUtils.append(mSpanStarts, mSpanCount, start);
750 mSpanEnds = GrowingArrayUtils.append(mSpanEnds, mSpanCount, end);
751 mSpanFlags = GrowingArrayUtils.append(mSpanFlags, mSpanCount, flags);
752 mSpanOrder = GrowingArrayUtils.append(mSpanOrder, mSpanCount, mSpanInsertCount);
753 invalidateIndex(mSpanCount);
756 // Make sure there is enough room for empty interior nodes.
757 // This magic formula computes the size of the smallest perfect binary
758 // tree no smaller than mSpanCount.
759 int sizeOfMax = 2 * treeRoot() + 1;
760 if (mSpanMax.length < sizeOfMax) {
761 mSpanMax = new int[sizeOfMax];
766 sendSpanAdded(what, nstart, nend);
770 private final boolean isInvalidParagraphStart(int start, int flagsStart) {
771 if (flagsStart == PARAGRAPH) {
772 if (start != 0 && start != length()) {
773 char c = charAt(start - 1);
775 if (c != '\n') return true;
781 private final boolean isInvalidParagraphEnd(int end, int flagsEnd) {
782 if (flagsEnd == PARAGRAPH) {
783 if (end != 0 && end != length()) {
784 char c = charAt(end - 1);
786 if (c != '\n') return true;
793 * Remove the specified markup object from the buffer.
795 public void removeSpan(Object what) {
796 if (mIndexOfSpan == null) return;
797 Integer i = mIndexOfSpan.remove(what);
799 removeSpan(i.intValue());
804 * Return externally visible offset given offset into gapped buffer.
806 private int resolveGap(int i) {
807 return i > mGapStart ? i - mGapLength : i;
811 * Return the buffer offset of the beginning of the specified
812 * markup object, or -1 if it is not attached to this buffer.
814 public int getSpanStart(Object what) {
815 if (mIndexOfSpan == null) return -1;
816 Integer i = mIndexOfSpan.get(what);
817 return i == null ? -1 : resolveGap(mSpanStarts[i]);
821 * Return the buffer offset of the end of the specified
822 * markup object, or -1 if it is not attached to this buffer.
824 public int getSpanEnd(Object what) {
825 if (mIndexOfSpan == null) return -1;
826 Integer i = mIndexOfSpan.get(what);
827 return i == null ? -1 : resolveGap(mSpanEnds[i]);
831 * Return the flags of the end of the specified
832 * markup object, or 0 if it is not attached to this buffer.
834 public int getSpanFlags(Object what) {
835 if (mIndexOfSpan == null) return 0;
836 Integer i = mIndexOfSpan.get(what);
837 return i == null ? 0 : mSpanFlags[i];
841 * Return an array of the spans of the specified type that overlap
842 * the specified range of the buffer. The kind may be Object.class to get
843 * a list of all the spans regardless of type.
845 @SuppressWarnings("unchecked")
846 public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind) {
847 return getSpans(queryStart, queryEnd, kind, true);
851 * Return an array of the spans of the specified type that overlap
852 * the specified range of the buffer. The kind may be Object.class to get
853 * a list of all the spans regardless of type.
855 * @param queryStart Start index.
856 * @param queryEnd End index.
857 * @param kind Class type to search for.
858 * @param sort If true the results are sorted by the insertion order.
860 * @return Array of the spans. Empty array if no results are found.
864 public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind,
866 if (kind == null) return (T[]) ArrayUtils.emptyArray(Object.class);
867 if (mSpanCount == 0) return ArrayUtils.emptyArray(kind);
868 int count = countSpans(queryStart, queryEnd, kind, treeRoot());
870 return ArrayUtils.emptyArray(kind);
873 // Safe conversion, but requires a suppressWarning
874 T[] ret = (T[]) Array.newInstance(kind, count);
876 mPrioSortBuffer = checkSortBuffer(mPrioSortBuffer, count);
877 mOrderSortBuffer = checkSortBuffer(mOrderSortBuffer, count);
879 getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, mPrioSortBuffer,
880 mOrderSortBuffer, 0, sort);
881 if (sort) sort(ret, mPrioSortBuffer, mOrderSortBuffer);
885 private int countSpans(int queryStart, int queryEnd, Class kind, int i) {
888 // internal tree node
889 int left = leftChild(i);
890 int spanMax = mSpanMax[left];
891 if (spanMax > mGapStart) {
892 spanMax -= mGapLength;
894 if (spanMax >= queryStart) {
895 count = countSpans(queryStart, queryEnd, kind, left);
898 if (i < mSpanCount) {
899 int spanStart = mSpanStarts[i];
900 if (spanStart > mGapStart) {
901 spanStart -= mGapLength;
903 if (spanStart <= queryEnd) {
904 int spanEnd = mSpanEnds[i];
905 if (spanEnd > mGapStart) {
906 spanEnd -= mGapLength;
908 if (spanEnd >= queryStart &&
909 (spanStart == spanEnd || queryStart == queryEnd ||
910 (spanStart != queryEnd && spanEnd != queryStart)) &&
911 (Object.class == kind || kind.isInstance(mSpans[i]))) {
915 count += countSpans(queryStart, queryEnd, kind, rightChild(i));
923 * Fills the result array with the spans found under the current interval tree node.
925 * @param queryStart Start index for the interval query.
926 * @param queryEnd End index for the interval query.
927 * @param kind Class type to search for.
928 * @param i Index of the current tree node.
929 * @param ret Array to be filled with results.
930 * @param priority Buffer to keep record of the priorities of spans found.
931 * @param insertionOrder Buffer to keep record of the insertion orders of spans found.
932 * @param count The number of found spans.
933 * @param sort Flag to fill the priority and insertion order buffers. If false then
934 * the spans with priority flag will be sorted in the result array.
936 * @return The total number of spans found.
938 @SuppressWarnings("unchecked")
939 private <T> int getSpansRec(int queryStart, int queryEnd, Class<T> kind,
940 int i, T[] ret, int[] priority, int[] insertionOrder, int count, boolean sort) {
942 // internal tree node
943 int left = leftChild(i);
944 int spanMax = mSpanMax[left];
945 if (spanMax > mGapStart) {
946 spanMax -= mGapLength;
948 if (spanMax >= queryStart) {
949 count = getSpansRec(queryStart, queryEnd, kind, left, ret, priority,
950 insertionOrder, count, sort);
953 if (i >= mSpanCount) return count;
954 int spanStart = mSpanStarts[i];
955 if (spanStart > mGapStart) {
956 spanStart -= mGapLength;
958 if (spanStart <= queryEnd) {
959 int spanEnd = mSpanEnds[i];
960 if (spanEnd > mGapStart) {
961 spanEnd -= mGapLength;
963 if (spanEnd >= queryStart &&
964 (spanStart == spanEnd || queryStart == queryEnd ||
965 (spanStart != queryEnd && spanEnd != queryStart)) &&
966 (Object.class == kind || kind.isInstance(mSpans[i]))) {
967 int spanPriority = mSpanFlags[i] & SPAN_PRIORITY;
969 ret[count] = (T) mSpans[i];
970 priority[count] = spanPriority;
971 insertionOrder[count] = mSpanOrder[i];
972 } else if (spanPriority != 0) {
973 //insertion sort for elements with priority
975 for (; j < count; j++) {
976 int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
977 if (spanPriority > p) break;
979 System.arraycopy(ret, j, ret, j + 1, count - j);
980 ret[j] = (T) mSpans[i];
984 if (count < ret.length && (i & 1) != 0) {
985 count = getSpansRec(queryStart, queryEnd, kind, rightChild(i), ret, priority,
986 insertionOrder, count, sort);
993 * Check the size of the buffer and grow if required.
995 * @param buffer Buffer to be checked.
996 * @param size Required size.
997 * @return Same buffer instance if the current size is greater than required size. Otherwise a
998 * new instance is created and returned.
1000 private final int[] checkSortBuffer(int[] buffer, int size) {
1001 if(size > buffer.length) {
1002 return ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(size));
1008 * An iterative heap sort implementation. It will sort the spans using first their priority
1009 * then insertion order. A span with higher priority will be before a span with lower
1010 * priority. If priorities are the same, the spans will be sorted with insertion order. A
1011 * span with a lower insertion order will be before a span with a higher insertion order.
1013 * @param array Span array to be sorted.
1014 * @param priority Priorities of the spans
1015 * @param insertionOrder Insertion orders of the spans
1016 * @param <T> Span object type.
1019 private final <T> void sort(T[] array, int[] priority, int[] insertionOrder) {
1020 int size = array.length;
1021 for (int i = size / 2 - 1; i >= 0; i--) {
1022 siftDown(i, array, size, priority, insertionOrder);
1025 for (int i = size - 1; i > 0; i--) {
1027 int prio = priority[0];
1028 int insertOrder = insertionOrder[0];
1029 array[0] = array[i];
1030 priority[0] = priority[i];
1031 insertionOrder[0] = insertionOrder[i];
1032 siftDown(0, array, i, priority, insertionOrder);
1035 insertionOrder[i] = insertOrder;
1040 * Helper function for heap sort.
1042 * @param index Index of the element to sift down.
1043 * @param array Span array to be sorted.
1044 * @param size Current heap size.
1045 * @param priority Priorities of the spans
1046 * @param insertionOrder Insertion orders of the spans
1047 * @param <T> Span object type.
1049 private final <T> void siftDown(int index, T[] array, int size, int[] priority,
1050 int[] insertionOrder) {
1052 int prio = priority[index];
1053 int insertOrder = insertionOrder[index];
1055 int left = 2 * index + 1;
1056 while (left < size) {
1057 if (left < size - 1 && compareSpans(left, left + 1, priority, insertionOrder) < 0) {
1060 if (compareSpans(index, left, priority, insertionOrder) >= 0) {
1063 array[index] = array[left];
1064 priority[index] = priority[left];
1065 insertionOrder[index] = insertionOrder[left];
1067 left = 2 * index + 1;
1070 priority[index] = prio;
1071 insertionOrder[index] = insertOrder;
1075 * Compare two span elements in an array. Comparison is based first on the priority flag of
1076 * the span, and then the insertion order of the span.
1078 * @param left Index of the element to compare.
1079 * @param right Index of the other element to compare.
1080 * @param priority Priorities of the spans
1081 * @param insertionOrder Insertion orders of the spans
1084 private final int compareSpans(int left, int right, int[] priority,
1085 int[] insertionOrder) {
1086 int priority1 = priority[left];
1087 int priority2 = priority[right];
1088 if (priority1 == priority2) {
1089 return Integer.compare(insertionOrder[left], insertionOrder[right]);
1091 // since high priority has to be before a lower priority, the arguments to compare are
1092 // opposite of the insertion order check.
1093 return Integer.compare(priority2, priority1);
1097 * Return the next offset after <code>start</code> but less than or
1098 * equal to <code>limit</code> where a span of the specified type
1101 public int nextSpanTransition(int start, int limit, Class kind) {
1102 if (mSpanCount == 0) return limit;
1104 kind = Object.class;
1106 return nextSpanTransitionRec(start, limit, kind, treeRoot());
1109 private int nextSpanTransitionRec(int start, int limit, Class kind, int i) {
1111 // internal tree node
1112 int left = leftChild(i);
1113 if (resolveGap(mSpanMax[left]) > start) {
1114 limit = nextSpanTransitionRec(start, limit, kind, left);
1117 if (i < mSpanCount) {
1118 int st = resolveGap(mSpanStarts[i]);
1119 int en = resolveGap(mSpanEnds[i]);
1120 if (st > start && st < limit && kind.isInstance(mSpans[i]))
1122 if (en > start && en < limit && kind.isInstance(mSpans[i]))
1124 if (st < limit && (i & 1) != 0) {
1125 limit = nextSpanTransitionRec(start, limit, kind, rightChild(i));
1133 * Return a new CharSequence containing a copy of the specified
1134 * range of this buffer, including the overlapping spans.
1136 public CharSequence subSequence(int start, int end) {
1137 return new SpannableStringBuilder(this, start, end);
1141 * Copy the specified range of chars from this buffer into the
1142 * specified array, beginning at the specified offset.
1144 public void getChars(int start, int end, char[] dest, int destoff) {
1145 checkRange("getChars", start, end);
1147 if (end <= mGapStart) {
1148 System.arraycopy(mText, start, dest, destoff, end - start);
1149 } else if (start >= mGapStart) {
1150 System.arraycopy(mText, start + mGapLength, dest, destoff, end - start);
1152 System.arraycopy(mText, start, dest, destoff, mGapStart - start);
1153 System.arraycopy(mText, mGapStart + mGapLength,
1154 dest, destoff + (mGapStart - start),
1160 * Return a String containing a copy of the chars in this buffer.
1163 public String toString() {
1165 char[] buf = new char[len];
1167 getChars(0, len, buf, 0);
1168 return new String(buf);
1172 * Return a String containing a copy of the chars in this buffer, limited to the
1173 * [start, end[ range.
1176 public String substring(int start, int end) {
1177 char[] buf = new char[end - start];
1178 getChars(start, end, buf, 0);
1179 return new String(buf);
1183 * Returns the depth of TextWatcher callbacks. Returns 0 when the object is not handling
1184 * TextWatchers. A return value greater than 1 implies that a TextWatcher caused a change that
1185 * recursively triggered a TextWatcher.
1187 public int getTextWatcherDepth() {
1188 return mTextWatcherDepth;
1191 private void sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after) {
1192 int n = watchers.length;
1194 mTextWatcherDepth++;
1195 for (int i = 0; i < n; i++) {
1196 watchers[i].beforeTextChanged(this, start, before, after);
1198 mTextWatcherDepth--;
1201 private void sendTextChanged(TextWatcher[] watchers, int start, int before, int after) {
1202 int n = watchers.length;
1204 mTextWatcherDepth++;
1205 for (int i = 0; i < n; i++) {
1206 watchers[i].onTextChanged(this, start, before, after);
1208 mTextWatcherDepth--;
1211 private void sendAfterTextChanged(TextWatcher[] watchers) {
1212 int n = watchers.length;
1214 mTextWatcherDepth++;
1215 for (int i = 0; i < n; i++) {
1216 watchers[i].afterTextChanged(this);
1218 mTextWatcherDepth--;
1221 private void sendSpanAdded(Object what, int start, int end) {
1222 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
1223 int n = recip.length;
1225 for (int i = 0; i < n; i++) {
1226 recip[i].onSpanAdded(this, what, start, end);
1230 private void sendSpanRemoved(Object what, int start, int end) {
1231 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
1232 int n = recip.length;
1234 for (int i = 0; i < n; i++) {
1235 recip[i].onSpanRemoved(this, what, start, end);
1239 private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) {
1240 // The bounds of a possible SpanWatcher are guaranteed to be set before this method is
1241 // called, so that the order of the span does not affect this broadcast.
1242 SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start),
1243 Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class);
1244 int n = spanWatchers.length;
1245 for (int i = 0; i < n; i++) {
1246 spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end);
1250 private static String region(int start, int end) {
1251 return "(" + start + " ... " + end + ")";
1254 private void checkRange(final String operation, int start, int end) {
1256 throw new IndexOutOfBoundsException(operation + " " +
1257 region(start, end) + " has end before start");
1262 if (start > len || end > len) {
1263 throw new IndexOutOfBoundsException(operation + " " +
1264 region(start, end) + " ends beyond length " + len);
1267 if (start < 0 || end < 0) {
1268 throw new IndexOutOfBoundsException(operation + " " +
1269 region(start, end) + " starts before 0");
1274 private boolean isprint(char c) { // XXX
1275 if (c >= ' ' && c <= '~')
1281 private static final int startFlag(int flag) {
1282 return (flag >> 4) & 0x0F;
1285 private static final int endFlag(int flag) {
1289 public void dump() { // XXX
1290 for (int i = 0; i < mGapStart; i++) {
1291 System.out.print('|');
1292 System.out.print(' ');
1293 System.out.print(isprint(mText[i]) ? mText[i] : '.');
1294 System.out.print(' ');
1297 for (int i = mGapStart; i < mGapStart + mGapLength; i++) {
1298 System.out.print('|');
1299 System.out.print('(');
1300 System.out.print(isprint(mText[i]) ? mText[i] : '.');
1301 System.out.print(')');
1304 for (int i = mGapStart + mGapLength; i < mText.length; i++) {
1305 System.out.print('|');
1306 System.out.print(' ');
1307 System.out.print(isprint(mText[i]) ? mText[i] : '.');
1308 System.out.print(' ');
1311 System.out.print('\n');
1313 for (int i = 0; i < mText.length + 1; i++) {
1317 for (int j = 0; j < mSpanCount; j++) {
1318 if (mSpanStarts[j] == i) {
1324 if (mSpanEnds[j] == i) {
1332 if (startFlag(mSpanFlags[wfound]) == MARK)
1333 System.out.print("( ");
1334 if (startFlag(mSpanFlags[wfound]) == PARAGRAPH)
1335 System.out.print("< ");
1337 System.out.print("[ ");
1338 } else if (found == 2) {
1339 if (endFlag(mSpanFlags[wfound]) == POINT)
1340 System.out.print(") ");
1341 if (endFlag(mSpanFlags[wfound]) == PARAGRAPH)
1342 System.out.print("> ");
1344 System.out.print("] ");
1346 System.out.print(" ");
1350 System.out.print("\n");
1355 * Don't call this yourself -- exists for Canvas to use internally.
1358 public void drawText(Canvas c, int start, int end, float x, float y, Paint p) {
1359 checkRange("drawText", start, end);
1361 if (end <= mGapStart) {
1362 c.drawText(mText, start, end - start, x, y, p);
1363 } else if (start >= mGapStart) {
1364 c.drawText(mText, start + mGapLength, end - start, x, y, p);
1366 char[] buf = TextUtils.obtain(end - start);
1368 getChars(start, end, buf, 0);
1369 c.drawText(buf, 0, end - start, x, y, p);
1370 TextUtils.recycle(buf);
1376 * Don't call this yourself -- exists for Canvas to use internally.
1379 public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
1380 float x, float y, boolean isRtl, Paint p) {
1381 checkRange("drawTextRun", start, end);
1383 int contextLen = contextEnd - contextStart;
1384 int len = end - start;
1385 if (contextEnd <= mGapStart) {
1386 c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, isRtl, p);
1387 } else if (contextStart >= mGapStart) {
1388 c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
1389 contextLen, x, y, isRtl, p);
1391 char[] buf = TextUtils.obtain(contextLen);
1392 getChars(contextStart, contextEnd, buf, 0);
1393 c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, isRtl, p);
1394 TextUtils.recycle(buf);
1399 * Don't call this yourself -- exists for Paint to use internally.
1402 public float measureText(int start, int end, Paint p) {
1403 checkRange("measureText", start, end);
1407 if (end <= mGapStart) {
1408 ret = p.measureText(mText, start, end - start);
1409 } else if (start >= mGapStart) {
1410 ret = p.measureText(mText, start + mGapLength, end - start);
1412 char[] buf = TextUtils.obtain(end - start);
1414 getChars(start, end, buf, 0);
1415 ret = p.measureText(buf, 0, end - start);
1416 TextUtils.recycle(buf);
1423 * Don't call this yourself -- exists for Paint to use internally.
1426 public int getTextWidths(int start, int end, float[] widths, Paint p) {
1427 checkRange("getTextWidths", start, end);
1431 if (end <= mGapStart) {
1432 ret = p.getTextWidths(mText, start, end - start, widths);
1433 } else if (start >= mGapStart) {
1434 ret = p.getTextWidths(mText, start + mGapLength, end - start, widths);
1436 char[] buf = TextUtils.obtain(end - start);
1438 getChars(start, end, buf, 0);
1439 ret = p.getTextWidths(buf, 0, end - start, widths);
1440 TextUtils.recycle(buf);
1447 * Don't call this yourself -- exists for Paint to use internally.
1450 public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl,
1451 float[] advances, int advancesPos, Paint p) {
1455 int contextLen = contextEnd - contextStart;
1456 int len = end - start;
1458 if (end <= mGapStart) {
1459 ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
1460 isRtl, advances, advancesPos);
1461 } else if (start >= mGapStart) {
1462 ret = p.getTextRunAdvances(mText, start + mGapLength, len,
1463 contextStart + mGapLength, contextLen, isRtl, advances, advancesPos);
1465 char[] buf = TextUtils.obtain(contextLen);
1466 getChars(contextStart, contextEnd, buf, 0);
1467 ret = p.getTextRunAdvances(buf, start - contextStart, len,
1468 0, contextLen, isRtl, advances, advancesPos);
1469 TextUtils.recycle(buf);
1476 * Returns the next cursor position in the run. This avoids placing the cursor between
1477 * surrogates, between characters that form conjuncts, between base characters and combining
1478 * marks, or within a reordering cluster.
1480 * <p>The context is the shaping context for cursor movement, generally the bounds of the metric
1481 * span enclosing the cursor in the direction of movement.
1482 * <code>contextStart</code>, <code>contextEnd</code> and <code>offset</code> are relative to
1483 * the start of the string.</p>
1485 * <p>If cursorOpt is CURSOR_AT and the offset is not a valid cursor position,
1486 * this returns -1. Otherwise this will never return a value before contextStart or after
1489 * @param contextStart the start index of the context
1490 * @param contextEnd the (non-inclusive) end index of the context
1491 * @param dir either DIRECTION_RTL or DIRECTION_LTR
1492 * @param offset the cursor position to move from
1493 * @param cursorOpt how to move the cursor, one of CURSOR_AFTER,
1494 * CURSOR_AT_OR_AFTER, CURSOR_BEFORE,
1495 * CURSOR_AT_OR_BEFORE, or CURSOR_AT
1496 * @param p the Paint object that is requesting this information
1497 * @return the offset of the next position, or -1
1498 * @deprecated This is an internal method, refrain from using it in your code
1501 public int getTextRunCursor(int contextStart, int contextEnd, int dir, int offset,
1502 int cursorOpt, Paint p) {
1506 int contextLen = contextEnd - contextStart;
1507 if (contextEnd <= mGapStart) {
1508 ret = p.getTextRunCursor(mText, contextStart, contextLen,
1509 dir, offset, cursorOpt);
1510 } else if (contextStart >= mGapStart) {
1511 ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
1512 dir, offset + mGapLength, cursorOpt) - mGapLength;
1514 char[] buf = TextUtils.obtain(contextLen);
1515 getChars(contextStart, contextEnd, buf, 0);
1516 ret = p.getTextRunCursor(buf, 0, contextLen,
1517 dir, offset - contextStart, cursorOpt) + contextStart;
1518 TextUtils.recycle(buf);
1524 // Documentation from interface
1525 public void setFilters(InputFilter[] filters) {
1526 if (filters == null) {
1527 throw new IllegalArgumentException();
1533 // Documentation from interface
1534 public InputFilter[] getFilters() {
1538 // Same as SpannableStringInternal
1540 public boolean equals(Object o) {
1541 if (o instanceof Spanned &&
1542 toString().equals(o.toString())) {
1543 Spanned other = (Spanned) o;
1545 Object[] otherSpans = other.getSpans(0, other.length(), Object.class);
1546 if (mSpanCount == otherSpans.length) {
1547 for (int i = 0; i < mSpanCount; ++i) {
1548 Object thisSpan = mSpans[i];
1549 Object otherSpan = otherSpans[i];
1550 if (thisSpan == this) {
1551 if (other != otherSpan ||
1552 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
1553 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
1554 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
1557 } else if (!thisSpan.equals(otherSpan) ||
1558 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
1559 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
1560 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
1570 // Same as SpannableStringInternal
1572 public int hashCode() {
1573 int hash = toString().hashCode();
1574 hash = hash * 31 + mSpanCount;
1575 for (int i = 0; i < mSpanCount; ++i) {
1576 Object span = mSpans[i];
1578 hash = hash * 31 + span.hashCode();
1580 hash = hash * 31 + getSpanStart(span);
1581 hash = hash * 31 + getSpanEnd(span);
1582 hash = hash * 31 + getSpanFlags(span);
1587 // Primitives for treating span list as binary tree
1589 // The spans (along with start and end offsets and flags) are stored in linear arrays sorted
1590 // by start offset. For fast searching, there is a binary search structure imposed over these
1591 // arrays. This structure is inorder traversal of a perfect binary tree, a slightly unusual
1592 // but advantageous approach.
1594 // The value-containing nodes are indexed 0 <= i < n (where n = mSpanCount), thus preserving
1595 // logic that accesses the values as a contiguous array. Other balanced binary tree approaches
1596 // (such as a complete binary tree) would require some shuffling of node indices.
1598 // Basic properties of this structure: For a perfect binary tree of height m:
1599 // The tree has 2^(m+1) - 1 total nodes.
1600 // The root of the tree has index 2^m - 1.
1601 // All leaf nodes have even index, all interior nodes odd.
1602 // The height of a node of index i is the number of trailing ones in i's binary representation.
1603 // The left child of a node i of height h is i - 2^(h - 1).
1604 // The right child of a node i of height h is i + 2^(h - 1).
1606 // Note that for arbitrary n, interior nodes of this tree may be >= n. Thus, the general
1607 // structure of a recursive traversal of node i is:
1608 // * traverse left child if i is an interior node
1609 // * process i if i < n
1610 // * traverse right child if i is an interior node and i < n
1612 private int treeRoot() {
1613 return Integer.highestOneBit(mSpanCount) - 1;
1616 // (i+1) & ~i is equal to 2^(the number of trailing ones in i)
1617 private static int leftChild(int i) {
1618 return i - (((i + 1) & ~i) >> 1);
1621 private static int rightChild(int i) {
1622 return i + (((i + 1) & ~i) >> 1);
1625 // The span arrays are also augmented by an mSpanMax[] array that represents an interval tree
1626 // over the binary tree structure described above. For each node, the mSpanMax[] array contains
1627 // the maximum value of mSpanEnds of that node and its descendants. Thus, traversals can
1628 // easily reject subtrees that contain no spans overlapping the area of interest.
1630 // Note that mSpanMax[] also has a valid valuefor interior nodes of index >= n, but which have
1631 // descendants of index < n. In these cases, it simply represents the maximum span end of its
1632 // descendants. This is a consequence of the perfect binary tree structure.
1633 private int calcMax(int i) {
1636 // internal tree node
1637 max = calcMax(leftChild(i));
1639 if (i < mSpanCount) {
1640 max = Math.max(max, mSpanEnds[i]);
1642 max = Math.max(max, calcMax(rightChild(i)));
1649 // restores binary interval tree invariants after any mutation of span structure
1650 private void restoreInvariants() {
1651 if (mSpanCount == 0) return;
1653 // invariant 1: span starts are nondecreasing
1655 // This is a simple insertion sort because we expect it to be mostly sorted.
1656 for (int i = 1; i < mSpanCount; i++) {
1657 if (mSpanStarts[i] < mSpanStarts[i - 1]) {
1658 Object span = mSpans[i];
1659 int start = mSpanStarts[i];
1660 int end = mSpanEnds[i];
1661 int flags = mSpanFlags[i];
1662 int insertionOrder = mSpanOrder[i];
1665 mSpans[j] = mSpans[j - 1];
1666 mSpanStarts[j] = mSpanStarts[j - 1];
1667 mSpanEnds[j] = mSpanEnds[j - 1];
1668 mSpanFlags[j] = mSpanFlags[j - 1];
1669 mSpanOrder[j] = mSpanOrder[j - 1];
1671 } while (j > 0 && start < mSpanStarts[j - 1]);
1673 mSpanStarts[j] = start;
1675 mSpanFlags[j] = flags;
1676 mSpanOrder[j] = insertionOrder;
1681 // invariant 2: max is max span end for each node and its descendants
1682 calcMax(treeRoot());
1684 // invariant 3: mIndexOfSpan maps spans back to indices
1685 if (mIndexOfSpan == null) {
1686 mIndexOfSpan = new IdentityHashMap<Object, Integer>();
1688 for (int i = mLowWaterMark; i < mSpanCount; i++) {
1689 Integer existing = mIndexOfSpan.get(mSpans[i]);
1690 if (existing == null || existing != i) {
1691 mIndexOfSpan.put(mSpans[i], i);
1694 mLowWaterMark = Integer.MAX_VALUE;
1697 // Call this on any update to mSpans[], so that mIndexOfSpan can be updated
1698 private void invalidateIndex(int i) {
1699 mLowWaterMark = Math.min(i, mLowWaterMark);
1702 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
1703 private InputFilter[] mFilters = NO_FILTERS;
1705 private char[] mText;
1706 private int mGapStart;
1707 private int mGapLength;
1709 private Object[] mSpans;
1710 private int[] mSpanStarts;
1711 private int[] mSpanEnds;
1712 private int[] mSpanMax; // see calcMax() for an explanation of what this array stores
1713 private int[] mSpanFlags;
1714 private int[] mSpanOrder; // store the order of span insertion
1715 private int mSpanInsertCount; // counter for the span insertion
1716 private int[] mPrioSortBuffer; // buffer used to sort getSpans result
1717 private int[] mOrderSortBuffer; // buffer used to sort getSpans result
1719 private int mSpanCount;
1720 private IdentityHashMap<Object, Integer> mIndexOfSpan;
1721 private int mLowWaterMark; // indices below this have not been touched
1723 // TextWatcher callbacks may trigger changes that trigger more callbacks. This keeps track of
1724 // how deep the callbacks go.
1725 private int mTextWatcherDepth;
1727 // TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned}
1728 private static final int MARK = 1;
1729 private static final int POINT = 2;
1730 private static final int PARAGRAPH = 3;
1732 private static final int START_MASK = 0xF0;
1733 private static final int END_MASK = 0x0F;
1734 private static final int START_SHIFT = 4;
1736 // These bits are not (currently) used by SPANNED flags
1737 private static final int SPAN_ADDED = 0x800;
1738 private static final int SPAN_START_AT_START = 0x1000;
1739 private static final int SPAN_START_AT_END = 0x2000;
1740 private static final int SPAN_END_AT_START = 0x4000;
1741 private static final int SPAN_END_AT_END = 0x8000;
1742 private static final int SPAN_START_END_MASK = 0xF000;