SpannableStringBuilder.java revision 90985286442340b9ad9433d6bf8d51702c8d0fd9
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
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
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17package android.text;
18
19import android.graphics.Canvas;
20import android.graphics.Paint;
21import android.util.Log;
22
23import com.android.internal.util.ArrayUtils;
24
25import java.lang.reflect.Array;
26
27/**
28 * This is the class for text whose content and markup can both be changed.
29 */
30public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable,
31        Appendable, GraphicsOperations {
32    /**
33     * Create a new SpannableStringBuilder with empty contents
34     */
35    public SpannableStringBuilder() {
36        this("");
37    }
38
39    /**
40     * Create a new SpannableStringBuilder containing a copy of the
41     * specified text, including its spans if any.
42     */
43    public SpannableStringBuilder(CharSequence text) {
44        this(text, 0, text.length());
45    }
46
47    /**
48     * Create a new SpannableStringBuilder containing a copy of the
49     * specified slice of the specified text, including its spans if any.
50     */
51    public SpannableStringBuilder(CharSequence text, int start, int end) {
52        int srclen = end - start;
53
54        if (srclen < 0) throw new StringIndexOutOfBoundsException();
55
56        int len = ArrayUtils.idealCharArraySize(srclen + 1);
57        mText = new char[len];
58        mGapStart = srclen;
59        mGapLength = len - srclen;
60
61        TextUtils.getChars(text, start, end, mText, 0);
62
63        mSpanCount = 0;
64        int alloc = ArrayUtils.idealIntArraySize(0);
65        mSpans = new Object[alloc];
66        mSpanStarts = new int[alloc];
67        mSpanEnds = new int[alloc];
68        mSpanFlags = new int[alloc];
69
70        if (text instanceof Spanned) {
71            Spanned sp = (Spanned) text;
72            Object[] spans = sp.getSpans(start, end, Object.class);
73
74            for (int i = 0; i < spans.length; i++) {
75                if (spans[i] instanceof NoCopySpan) {
76                    continue;
77                }
78
79                int st = sp.getSpanStart(spans[i]) - start;
80                int en = sp.getSpanEnd(spans[i]) - start;
81                int fl = sp.getSpanFlags(spans[i]);
82
83                if (st < 0)
84                    st = 0;
85                if (st > end - start)
86                    st = end - start;
87
88                if (en < 0)
89                    en = 0;
90                if (en > end - start)
91                    en = end - start;
92
93                setSpan(false, spans[i], st, en, fl);
94            }
95        }
96    }
97
98    public static SpannableStringBuilder valueOf(CharSequence source) {
99        if (source instanceof SpannableStringBuilder) {
100            return (SpannableStringBuilder) source;
101        } else {
102            return new SpannableStringBuilder(source);
103        }
104    }
105
106    /**
107     * Return the char at the specified offset within the buffer.
108     */
109    public char charAt(int where) {
110        int len = length();
111        if (where < 0) {
112            throw new IndexOutOfBoundsException("charAt: " + where + " < 0");
113        } else if (where >= len) {
114            throw new IndexOutOfBoundsException("charAt: " + where + " >= length " + len);
115        }
116
117        if (where >= mGapStart)
118            return mText[where + mGapLength];
119        else
120            return mText[where];
121    }
122
123    /**
124     * Return the number of chars in the buffer.
125     */
126    public int length() {
127        return mText.length - mGapLength;
128    }
129
130    private void resizeFor(int size) {
131        final int oldLength = mText.length;
132        final int newLength = ArrayUtils.idealCharArraySize(size + 1);
133        final int delta = newLength - oldLength;
134        if (delta == 0) return;
135
136        char[] newText = new char[newLength];
137        System.arraycopy(mText, 0, newText, 0, mGapStart);
138        final int after = oldLength - (mGapStart + mGapLength);
139        System.arraycopy(mText, oldLength - after, newText, newLength - after, after);
140        mText = newText;
141
142        mGapLength += delta;
143        if (mGapLength < 1)
144            new Exception("mGapLength < 1").printStackTrace();
145
146        for (int i = 0; i < mSpanCount; i++) {
147            if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += delta;
148            if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += delta;
149        }
150    }
151
152    private void moveGapTo(int where) {
153        if (where == mGapStart)
154            return;
155
156        boolean atEnd = (where == length());
157
158        if (where < mGapStart) {
159            int overlap = mGapStart - where;
160            System.arraycopy(mText, where, mText, mGapStart + mGapLength - overlap, overlap);
161        } else /* where > mGapStart */ {
162            int overlap = where - mGapStart;
163            System.arraycopy(mText, where + mGapLength - overlap, mText, mGapStart, overlap);
164        }
165
166        // XXX be more clever
167        for (int i = 0; i < mSpanCount; i++) {
168            int start = mSpanStarts[i];
169            int end = mSpanEnds[i];
170
171            if (start > mGapStart)
172                start -= mGapLength;
173            if (start > where)
174                start += mGapLength;
175            else if (start == where) {
176                int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
177
178                if (flag == POINT || (atEnd && flag == PARAGRAPH))
179                    start += mGapLength;
180            }
181
182            if (end > mGapStart)
183                end -= mGapLength;
184            if (end > where)
185                end += mGapLength;
186            else if (end == where) {
187                int flag = (mSpanFlags[i] & END_MASK);
188
189                if (flag == POINT || (atEnd && flag == PARAGRAPH))
190                    end += mGapLength;
191            }
192
193            mSpanStarts[i] = start;
194            mSpanEnds[i] = end;
195        }
196
197        mGapStart = where;
198    }
199
200    // Documentation from interface
201    public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) {
202        return replace(where, where, tb, start, end);
203    }
204
205    // Documentation from interface
206    public SpannableStringBuilder insert(int where, CharSequence tb) {
207        return replace(where, where, tb, 0, tb.length());
208    }
209
210    // Documentation from interface
211    public SpannableStringBuilder delete(int start, int end) {
212        SpannableStringBuilder ret = replace(start, end, "", 0, 0);
213
214        if (mGapLength > 2 * length())
215            resizeFor(length());
216
217        return ret; // == this
218    }
219
220    // Documentation from interface
221    public void clear() {
222        replace(0, length(), "", 0, 0);
223    }
224
225    // Documentation from interface
226    public void clearSpans() {
227        for (int i = mSpanCount - 1; i >= 0; i--) {
228            Object what = mSpans[i];
229            int ostart = mSpanStarts[i];
230            int oend = mSpanEnds[i];
231
232            if (ostart > mGapStart)
233                ostart -= mGapLength;
234            if (oend > mGapStart)
235                oend -= mGapLength;
236
237            mSpanCount = i;
238            mSpans[i] = null;
239
240            sendSpanRemoved(what, ostart, oend);
241        }
242    }
243
244    // Documentation from interface
245    public SpannableStringBuilder append(CharSequence text) {
246        int length = length();
247        return replace(length, length, text, 0, text.length());
248    }
249
250    // Documentation from interface
251    public SpannableStringBuilder append(CharSequence text, int start, int end) {
252        int length = length();
253        return replace(length, length, text, start, end);
254    }
255
256    // Documentation from interface
257    public SpannableStringBuilder append(char text) {
258        return append(String.valueOf(text));
259    }
260
261    private void change(int start, int end, CharSequence cs, int csStart, int csEnd) {
262        // Can be negative
263        final int nbNewChars = (csEnd - csStart) - (end - start);
264
265        for (int i = mSpanCount - 1; i >= 0; i--) {
266            int spanStart = mSpanStarts[i];
267            if (spanStart > mGapStart)
268                spanStart -= mGapLength;
269
270            int spanEnd = mSpanEnds[i];
271            if (spanEnd > mGapStart)
272                spanEnd -= mGapLength;
273
274            if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
275                int ost = spanStart;
276                int oen = spanEnd;
277                int clen = length();
278
279                if (spanStart > start && spanStart <= end) {
280                    for (spanStart = end; spanStart < clen; spanStart++)
281                        if (spanStart > end && charAt(spanStart - 1) == '\n')
282                            break;
283                }
284
285                if (spanEnd > start && spanEnd <= end) {
286                    for (spanEnd = end; spanEnd < clen; spanEnd++)
287                        if (spanEnd > end && charAt(spanEnd - 1) == '\n')
288                            break;
289                }
290
291                if (spanStart != ost || spanEnd != oen)
292                    setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]);
293            }
294
295            int flags = 0;
296            if (spanStart == start) flags |= SPAN_START_AT_START;
297            else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END;
298            if (spanEnd == start) flags |= SPAN_END_AT_START;
299            else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END;
300            mSpanFlags[i] |= flags;
301        }
302
303        moveGapTo(end);
304
305        if (nbNewChars >= mGapLength) {
306            resizeFor(mText.length + nbNewChars - mGapLength);
307        }
308
309        // The removal pass needs to be done before the gap is updated in order to broadcast the
310        // correct previous positions to the correct intersecting SpanWatchers
311        if (end > start) { // no need for span fixup on pure insertion
312            // A for loop will not work because the array is being modified
313            // Do not iterate in reverse to keep the SpanWatchers notified in ordering
314            // Also, a removed SpanWatcher should not get notified of removed spans located
315            // further in the span array.
316            int i = 0;
317            while (i < mSpanCount) {
318                if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
319                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE &&
320                mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength &&
321                mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength) {
322                    removeSpan(i);
323                } else {
324                    i++;
325                }
326            }
327        }
328
329        mGapStart += nbNewChars;
330        mGapLength -= nbNewChars;
331
332        if (mGapLength < 1)
333            new Exception("mGapLength < 1").printStackTrace();
334
335        TextUtils.getChars(cs, csStart, csEnd, mText, start);
336
337        if (end > start) { // no need for span fixup on pure insertion
338            final boolean atEnd = (mGapStart + mGapLength == mText.length);
339
340            for (int i = 0; i < mSpanCount; i++) {
341                if (mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength) {
342                    int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
343
344                    if (flag == POINT || (flag == PARAGRAPH && atEnd)) {
345                        mSpanStarts[i] = mGapStart + mGapLength;
346                    } else {
347                        mSpanStarts[i] = start;
348                    }
349                }
350
351                if (mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength) {
352                    int flag = (mSpanFlags[i] & END_MASK);
353
354                    if (flag == POINT || (flag == PARAGRAPH && atEnd)) {
355                        mSpanEnds[i] = mGapStart + mGapLength;
356                    } else {
357                        mSpanEnds[i] = start;
358                    }
359                }
360            }
361        }
362
363        mSpanCountBeforeAdd = mSpanCount;
364
365        if (cs instanceof Spanned) {
366            Spanned sp = (Spanned) cs;
367            Object[] spans = sp.getSpans(csStart, csEnd, Object.class);
368
369            for (int i = 0; i < spans.length; i++) {
370                int st = sp.getSpanStart(spans[i]);
371                int en = sp.getSpanEnd(spans[i]);
372
373                if (st < csStart) st = csStart;
374                if (en > csEnd) en = csEnd;
375
376                // Add span only if this object is not yet used as a span in this string
377                if (getSpanStart(spans[i]) < 0) {
378                    setSpan(false, spans[i], st - csStart + start, en - csStart + start,
379                            sp.getSpanFlags(spans[i]));
380                }
381            }
382        }
383    }
384
385    private void removeSpan(int i) {
386        Object object = mSpans[i];
387
388        int start = mSpanStarts[i];
389        int end = mSpanEnds[i];
390
391        if (start > mGapStart) start -= mGapLength;
392        if (end > mGapStart) end -= mGapLength;
393
394        int count = mSpanCount - (i + 1);
395        System.arraycopy(mSpans, i + 1, mSpans, i, count);
396        System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
397        System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
398        System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
399
400        mSpanCount--;
401
402        mSpans[mSpanCount] = null;
403
404        sendSpanRemoved(object, start, end);
405    }
406
407    // Documentation from interface
408    public SpannableStringBuilder replace(int start, int end, CharSequence tb) {
409        return replace(start, end, tb, 0, tb.length());
410    }
411
412    // Documentation from interface
413    public SpannableStringBuilder replace(final int start, final int end,
414            CharSequence tb, int tbstart, int tbend) {
415        checkRange("replace", start, end);
416
417        int filtercount = mFilters.length;
418        for (int i = 0; i < filtercount; i++) {
419            CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);
420
421            if (repl != null) {
422                tb = repl;
423                tbstart = 0;
424                tbend = repl.length();
425            }
426        }
427
428        final int origLen = end - start;
429        final int newLen = tbend - tbstart;
430
431        TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);
432        sendBeforeTextChanged(textWatchers, start, origLen, newLen);
433
434        // Try to keep the cursor / selection at the same relative position during
435        // a text replacement. If replaced or replacement text length is zero, this
436        // is already taken care of.
437        boolean adjustSelection = origLen != 0 && newLen != 0;
438        int selectionStart = 0;
439        int selectionEnd = 0;
440        if (adjustSelection) {
441            selectionStart = Selection.getSelectionStart(this);
442            selectionEnd = Selection.getSelectionEnd(this);
443        }
444
445        change(start, end, tb, tbstart, tbend);
446
447        if (adjustSelection) {
448            if (selectionStart > start && selectionStart < end) {
449                final int offset = (selectionStart - start) * newLen / origLen;
450                selectionStart = start + offset;
451
452                setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,
453                        Spanned.SPAN_POINT_POINT);
454            }
455            if (selectionEnd > start && selectionEnd < end) {
456                final int offset = (selectionEnd - start) * newLen / origLen;
457                selectionEnd = start + offset;
458
459                setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,
460                        Spanned.SPAN_POINT_POINT);
461            }
462        }
463
464        sendTextChanged(textWatchers, start, origLen, newLen);
465        sendAfterTextChanged(textWatchers);
466
467        // Span watchers need to be called after text watchers, which may update the layout
468        sendToSpanWatchers(start, end, newLen - origLen);
469
470        return this;
471    }
472
473    private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) {
474        for (int i = 0; i < mSpanCountBeforeAdd; i++) {
475            int spanStart = mSpanStarts[i];
476            int spanEnd = mSpanEnds[i];
477            if (spanStart > mGapStart) spanStart -= mGapLength;
478            if (spanEnd > mGapStart) spanEnd -= mGapLength;
479            int spanFlags = mSpanFlags[i];
480
481            int newReplaceEnd = replaceEnd + nbNewChars;
482            boolean spanChanged = false;
483
484            int previousSpanStart = spanStart;
485            if (spanStart > newReplaceEnd) {
486                if (nbNewChars != 0) {
487                    previousSpanStart -= nbNewChars;
488                    spanChanged = true;
489                }
490            } else if (spanStart >= replaceStart) {
491                // No change if span start was already at replace interval boundaries before replace
492                if ((spanStart != replaceStart ||
493                        ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) &&
494                        (spanStart != newReplaceEnd ||
495                        ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) {
496                    // TODO A correct previousSpanStart cannot be computed at this point.
497                    // It would require to save all the previous spans' positions before the replace
498                    // Using an invalid -1 value to convey this would break the broacast range
499                    spanChanged = true;
500                }
501            }
502
503            int previousSpanEnd = spanEnd;
504            if (spanEnd > newReplaceEnd) {
505                if (nbNewChars != 0) {
506                    previousSpanEnd -= nbNewChars;
507                    spanChanged = true;
508                }
509            } else if (spanEnd >= replaceStart) {
510                // No change if span start was already at replace interval boundaries before replace
511                if ((spanEnd != replaceStart ||
512                        ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) &&
513                        (spanEnd != newReplaceEnd ||
514                        ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) {
515                    // TODO same as above for previousSpanEnd
516                    spanChanged = true;
517                }
518            }
519
520            if (spanChanged) {
521                sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd);
522            }
523            mSpanFlags[i] &= ~SPAN_START_END_MASK;
524        }
525
526        // The spans starting at mIntermediateSpanCount were added from the replacement text
527        for (int i = mSpanCountBeforeAdd; i < mSpanCount; i++) {
528            int spanStart = mSpanStarts[i];
529            int spanEnd = mSpanEnds[i];
530            if (spanStart > mGapStart) spanStart -= mGapLength;
531            if (spanEnd > mGapStart) spanEnd -= mGapLength;
532            sendSpanAdded(mSpans[i], spanStart, spanEnd);
533        }
534    }
535
536    /**
537     * Mark the specified range of text with the specified object.
538     * The flags determine how the span will behave when text is
539     * inserted at the start or end of the span's range.
540     */
541    public void setSpan(Object what, int start, int end, int flags) {
542        setSpan(true, what, start, end, flags);
543    }
544
545    private void setSpan(boolean send, Object what, int start, int end, int flags) {
546        checkRange("setSpan", start, end);
547
548        int flagsStart = (flags & START_MASK) >> START_SHIFT;
549        if (flagsStart == PARAGRAPH) {
550            if (start != 0 && start != length()) {
551                char c = charAt(start - 1);
552
553                if (c != '\n')
554                    throw new RuntimeException("PARAGRAPH span must start at paragraph boundary");
555            }
556        }
557
558        int flagsEnd = flags & END_MASK;
559        if (flagsEnd == PARAGRAPH) {
560            if (end != 0 && end != length()) {
561                char c = charAt(end - 1);
562
563                if (c != '\n')
564                    throw new RuntimeException("PARAGRAPH span must end at paragraph boundary");
565            }
566        }
567
568        // 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
569        if (flagsStart == POINT && flagsEnd == MARK && start == end) {
570            if (send) Log.e("SpannableStringBuilder",
571                    "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length");
572            // Silently ignore invalid spans when they are created from this class.
573            // This avoids the duplication of the above test code before all the
574            // calls to setSpan that are done in this class
575            return;
576        }
577
578        int nstart = start;
579        int nend = end;
580
581        if (start > mGapStart) {
582            start += mGapLength;
583        } else if (start == mGapStart) {
584            if (flagsStart == POINT || (flagsStart == PARAGRAPH && start == length()))
585                start += mGapLength;
586        }
587
588        if (end > mGapStart) {
589            end += mGapLength;
590        } else if (end == mGapStart) {
591            if (flagsEnd == POINT || (flagsEnd == PARAGRAPH && end == length()))
592                end += mGapLength;
593        }
594
595        int count = mSpanCount;
596        Object[] spans = mSpans;
597
598        for (int i = 0; i < count; i++) {
599            if (spans[i] == what) {
600                int ostart = mSpanStarts[i];
601                int oend = mSpanEnds[i];
602
603                if (ostart > mGapStart)
604                    ostart -= mGapLength;
605                if (oend > mGapStart)
606                    oend -= mGapLength;
607
608                mSpanStarts[i] = start;
609                mSpanEnds[i] = end;
610                mSpanFlags[i] = flags;
611
612                if (send) sendSpanChanged(what, ostart, oend, nstart, nend);
613
614                return;
615            }
616        }
617
618        if (mSpanCount + 1 >= mSpans.length) {
619            int newsize = ArrayUtils.idealIntArraySize(mSpanCount + 1);
620            Object[] newspans = new Object[newsize];
621            int[] newspanstarts = new int[newsize];
622            int[] newspanends = new int[newsize];
623            int[] newspanflags = new int[newsize];
624
625            System.arraycopy(mSpans, 0, newspans, 0, mSpanCount);
626            System.arraycopy(mSpanStarts, 0, newspanstarts, 0, mSpanCount);
627            System.arraycopy(mSpanEnds, 0, newspanends, 0, mSpanCount);
628            System.arraycopy(mSpanFlags, 0, newspanflags, 0, mSpanCount);
629
630            mSpans = newspans;
631            mSpanStarts = newspanstarts;
632            mSpanEnds = newspanends;
633            mSpanFlags = newspanflags;
634        }
635
636        mSpans[mSpanCount] = what;
637        mSpanStarts[mSpanCount] = start;
638        mSpanEnds[mSpanCount] = end;
639        mSpanFlags[mSpanCount] = flags;
640        mSpanCount++;
641
642        if (send) sendSpanAdded(what, nstart, nend);
643    }
644
645    /**
646     * Remove the specified markup object from the buffer.
647     */
648    public void removeSpan(Object what) {
649        for (int i = mSpanCount - 1; i >= 0; i--) {
650            if (mSpans[i] == what) {
651                removeSpan(i);
652                return;
653            }
654        }
655    }
656
657    /**
658     * Return the buffer offset of the beginning of the specified
659     * markup object, or -1 if it is not attached to this buffer.
660     */
661    public int getSpanStart(Object what) {
662        int count = mSpanCount;
663        Object[] spans = mSpans;
664
665        for (int i = count - 1; i >= 0; i--) {
666            if (spans[i] == what) {
667                int where = mSpanStarts[i];
668
669                if (where > mGapStart)
670                    where -= mGapLength;
671
672                return where;
673            }
674        }
675
676        return -1;
677    }
678
679    /**
680     * Return the buffer offset of the end of the specified
681     * markup object, or -1 if it is not attached to this buffer.
682     */
683    public int getSpanEnd(Object what) {
684        int count = mSpanCount;
685        Object[] spans = mSpans;
686
687        for (int i = count - 1; i >= 0; i--) {
688            if (spans[i] == what) {
689                int where = mSpanEnds[i];
690
691                if (where > mGapStart)
692                    where -= mGapLength;
693
694                return where;
695            }
696        }
697
698        return -1;
699    }
700
701    /**
702     * Return the flags of the end of the specified
703     * markup object, or 0 if it is not attached to this buffer.
704     */
705    public int getSpanFlags(Object what) {
706        int count = mSpanCount;
707        Object[] spans = mSpans;
708
709        for (int i = count - 1; i >= 0; i--) {
710            if (spans[i] == what) {
711                return mSpanFlags[i];
712            }
713        }
714
715        return 0;
716    }
717
718    /**
719     * Return an array of the spans of the specified type that overlap
720     * the specified range of the buffer.  The kind may be Object.class to get
721     * a list of all the spans regardless of type.
722     */
723    @SuppressWarnings("unchecked")
724    public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
725        if (kind == null) return ArrayUtils.emptyArray(kind);
726
727        int spanCount = mSpanCount;
728        Object[] spans = mSpans;
729        int[] starts = mSpanStarts;
730        int[] ends = mSpanEnds;
731        int[] flags = mSpanFlags;
732        int gapstart = mGapStart;
733        int gaplen = mGapLength;
734
735        int count = 0;
736        T[] ret = null;
737        T ret1 = null;
738
739        for (int i = 0; i < spanCount; i++) {
740            int spanStart = starts[i];
741            if (spanStart > gapstart) {
742                spanStart -= gaplen;
743            }
744            if (spanStart > queryEnd) {
745                continue;
746            }
747
748            int spanEnd = ends[i];
749            if (spanEnd > gapstart) {
750                spanEnd -= gaplen;
751            }
752            if (spanEnd < queryStart) {
753                continue;
754            }
755
756            if (spanStart != spanEnd && queryStart != queryEnd) {
757                if (spanStart == queryEnd)
758                    continue;
759                if (spanEnd == queryStart)
760                    continue;
761            }
762
763            // Expensive test, should be performed after the previous tests
764            if (!kind.isInstance(spans[i])) continue;
765
766            if (count == 0) {
767                // Safe conversion thanks to the isInstance test above
768                ret1 = (T) spans[i];
769                count++;
770            } else {
771                if (count == 1) {
772                    // Safe conversion, but requires a suppressWarning
773                    ret = (T[]) Array.newInstance(kind, spanCount - i + 1);
774                    ret[0] = ret1;
775                }
776
777                int prio = flags[i] & SPAN_PRIORITY;
778                if (prio != 0) {
779                    int j;
780
781                    for (j = 0; j < count; j++) {
782                        int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
783
784                        if (prio > p) {
785                            break;
786                        }
787                    }
788
789                    System.arraycopy(ret, j, ret, j + 1, count - j);
790                    // Safe conversion thanks to the isInstance test above
791                    ret[j] = (T) spans[i];
792                    count++;
793                } else {
794                    // Safe conversion thanks to the isInstance test above
795                    ret[count++] = (T) spans[i];
796                }
797            }
798        }
799
800        if (count == 0) {
801            return ArrayUtils.emptyArray(kind);
802        }
803        if (count == 1) {
804            // Safe conversion, but requires a suppressWarning
805            ret = (T[]) Array.newInstance(kind, 1);
806            ret[0] = ret1;
807            return ret;
808        }
809        if (count == ret.length) {
810            return ret;
811        }
812
813        // Safe conversion, but requires a suppressWarning
814        T[] nret = (T[]) Array.newInstance(kind, count);
815        System.arraycopy(ret, 0, nret, 0, count);
816        return nret;
817    }
818
819    /**
820     * Return the next offset after <code>start</code> but less than or
821     * equal to <code>limit</code> where a span of the specified type
822     * begins or ends.
823     */
824    public int nextSpanTransition(int start, int limit, Class kind) {
825        int count = mSpanCount;
826        Object[] spans = mSpans;
827        int[] starts = mSpanStarts;
828        int[] ends = mSpanEnds;
829        int gapstart = mGapStart;
830        int gaplen = mGapLength;
831
832        if (kind == null) {
833            kind = Object.class;
834        }
835
836        for (int i = 0; i < count; i++) {
837            int st = starts[i];
838            int en = ends[i];
839
840            if (st > gapstart)
841                st -= gaplen;
842            if (en > gapstart)
843                en -= gaplen;
844
845            if (st > start && st < limit && kind.isInstance(spans[i]))
846                limit = st;
847            if (en > start && en < limit && kind.isInstance(spans[i]))
848                limit = en;
849        }
850
851        return limit;
852    }
853
854    /**
855     * Return a new CharSequence containing a copy of the specified
856     * range of this buffer, including the overlapping spans.
857     */
858    public CharSequence subSequence(int start, int end) {
859        return new SpannableStringBuilder(this, start, end);
860    }
861
862    /**
863     * Copy the specified range of chars from this buffer into the
864     * specified array, beginning at the specified offset.
865     */
866    public void getChars(int start, int end, char[] dest, int destoff) {
867        checkRange("getChars", start, end);
868
869        if (end <= mGapStart) {
870            System.arraycopy(mText, start, dest, destoff, end - start);
871        } else if (start >= mGapStart) {
872            System.arraycopy(mText, start + mGapLength, dest, destoff, end - start);
873        } else {
874            System.arraycopy(mText, start, dest, destoff, mGapStart - start);
875            System.arraycopy(mText, mGapStart + mGapLength,
876                    dest, destoff + (mGapStart - start),
877                    end - mGapStart);
878        }
879    }
880
881    /**
882     * Return a String containing a copy of the chars in this buffer.
883     */
884    @Override
885    public String toString() {
886        int len = length();
887        char[] buf = new char[len];
888
889        getChars(0, len, buf, 0);
890        return new String(buf);
891    }
892
893    /**
894     * Return a String containing a copy of the chars in this buffer, limited to the
895     * [start, end[ range.
896     * @hide
897     */
898    public String substring(int start, int end) {
899        char[] buf = new char[end - start];
900        getChars(start, end, buf, 0);
901        return new String(buf);
902    }
903
904    private void sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after) {
905        int n = watchers.length;
906
907        for (int i = 0; i < n; i++) {
908            watchers[i].beforeTextChanged(this, start, before, after);
909        }
910    }
911
912    private void sendTextChanged(TextWatcher[] watchers, int start, int before, int after) {
913        int n = watchers.length;
914
915        for (int i = 0; i < n; i++) {
916            watchers[i].onTextChanged(this, start, before, after);
917        }
918    }
919
920    private void sendAfterTextChanged(TextWatcher[] watchers) {
921        int n = watchers.length;
922
923        for (int i = 0; i < n; i++) {
924            watchers[i].afterTextChanged(this);
925        }
926    }
927
928    private void sendSpanAdded(Object what, int start, int end) {
929        SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
930        int n = recip.length;
931
932        for (int i = 0; i < n; i++) {
933            recip[i].onSpanAdded(this, what, start, end);
934        }
935    }
936
937    private void sendSpanRemoved(Object what, int start, int end) {
938        SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
939        int n = recip.length;
940
941        for (int i = 0; i < n; i++) {
942            recip[i].onSpanRemoved(this, what, start, end);
943        }
944    }
945
946    private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) {
947        // The bounds of a possible SpanWatcher are guaranteed to be set before this method is
948        // called, so that the order of the span does not affect this broadcast.
949        SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start),
950                Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class);
951        int n = spanWatchers.length;
952        for (int i = 0; i < n; i++) {
953            spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end);
954        }
955    }
956
957    private static String region(int start, int end) {
958        return "(" + start + " ... " + end + ")";
959    }
960
961    private void checkRange(final String operation, int start, int end) {
962        if (end < start) {
963            throw new IndexOutOfBoundsException(operation + " " +
964                    region(start, end) + " has end before start");
965        }
966
967        int len = length();
968
969        if (start > len || end > len) {
970            throw new IndexOutOfBoundsException(operation + " " +
971                    region(start, end) + " ends beyond length " + len);
972        }
973
974        if (start < 0 || end < 0) {
975            throw new IndexOutOfBoundsException(operation + " " +
976                    region(start, end) + " starts before 0");
977        }
978    }
979
980    /*
981    private boolean isprint(char c) { // XXX
982        if (c >= ' ' && c <= '~')
983            return true;
984        else
985            return false;
986    }
987
988    private static final int startFlag(int flag) {
989        return (flag >> 4) & 0x0F;
990    }
991
992    private static final int endFlag(int flag) {
993        return flag & 0x0F;
994    }
995
996    public void dump() { // XXX
997        for (int i = 0; i < mGapStart; i++) {
998            System.out.print('|');
999            System.out.print(' ');
1000            System.out.print(isprint(mText[i]) ? mText[i] : '.');
1001            System.out.print(' ');
1002        }
1003
1004        for (int i = mGapStart; i < mGapStart + mGapLength; i++) {
1005            System.out.print('|');
1006            System.out.print('(');
1007            System.out.print(isprint(mText[i]) ? mText[i] : '.');
1008            System.out.print(')');
1009        }
1010
1011        for (int i = mGapStart + mGapLength; i < mText.length; i++) {
1012            System.out.print('|');
1013            System.out.print(' ');
1014            System.out.print(isprint(mText[i]) ? mText[i] : '.');
1015            System.out.print(' ');
1016        }
1017
1018        System.out.print('\n');
1019
1020        for (int i = 0; i < mText.length + 1; i++) {
1021            int found = 0;
1022            int wfound = 0;
1023
1024            for (int j = 0; j < mSpanCount; j++) {
1025                if (mSpanStarts[j] == i) {
1026                    found = 1;
1027                    wfound = j;
1028                    break;
1029                }
1030
1031                if (mSpanEnds[j] == i) {
1032                    found = 2;
1033                    wfound = j;
1034                    break;
1035                }
1036            }
1037
1038            if (found == 1) {
1039                if (startFlag(mSpanFlags[wfound]) == MARK)
1040                    System.out.print("(   ");
1041                if (startFlag(mSpanFlags[wfound]) == PARAGRAPH)
1042                    System.out.print("<   ");
1043                else
1044                    System.out.print("[   ");
1045            } else if (found == 2) {
1046                if (endFlag(mSpanFlags[wfound]) == POINT)
1047                    System.out.print(")   ");
1048                if (endFlag(mSpanFlags[wfound]) == PARAGRAPH)
1049                    System.out.print(">   ");
1050                else
1051                    System.out.print("]   ");
1052            } else {
1053                System.out.print("    ");
1054            }
1055        }
1056
1057        System.out.print("\n");
1058    }
1059     */
1060
1061    /**
1062     * Don't call this yourself -- exists for Canvas to use internally.
1063     * {@hide}
1064     */
1065    public void drawText(Canvas c, int start, int end, float x, float y, Paint p) {
1066        checkRange("drawText", start, end);
1067
1068        if (end <= mGapStart) {
1069            c.drawText(mText, start, end - start, x, y, p);
1070        } else if (start >= mGapStart) {
1071            c.drawText(mText, start + mGapLength, end - start, x, y, p);
1072        } else {
1073            char[] buf = TextUtils.obtain(end - start);
1074
1075            getChars(start, end, buf, 0);
1076            c.drawText(buf, 0, end - start, x, y, p);
1077            TextUtils.recycle(buf);
1078        }
1079    }
1080
1081
1082    /**
1083     * Don't call this yourself -- exists for Canvas to use internally.
1084     * {@hide}
1085     */
1086    public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
1087            float x, float y, int flags, Paint p) {
1088        checkRange("drawTextRun", start, end);
1089
1090        int contextLen = contextEnd - contextStart;
1091        int len = end - start;
1092        if (contextEnd <= mGapStart) {
1093            c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, flags, p);
1094        } else if (contextStart >= mGapStart) {
1095            c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
1096                    contextLen, x, y, flags, p);
1097        } else {
1098            char[] buf = TextUtils.obtain(contextLen);
1099            getChars(contextStart, contextEnd, buf, 0);
1100            c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, flags, p);
1101            TextUtils.recycle(buf);
1102        }
1103    }
1104
1105    /**
1106     * Don't call this yourself -- exists for Paint to use internally.
1107     * {@hide}
1108     */
1109    public float measureText(int start, int end, Paint p) {
1110        checkRange("measureText", start, end);
1111
1112        float ret;
1113
1114        if (end <= mGapStart) {
1115            ret = p.measureText(mText, start, end - start);
1116        } else if (start >= mGapStart) {
1117            ret = p.measureText(mText, start + mGapLength, end - start);
1118        } else {
1119            char[] buf = TextUtils.obtain(end - start);
1120
1121            getChars(start, end, buf, 0);
1122            ret = p.measureText(buf, 0, end - start);
1123            TextUtils.recycle(buf);
1124        }
1125
1126        return ret;
1127    }
1128
1129    /**
1130     * Don't call this yourself -- exists for Paint to use internally.
1131     * {@hide}
1132     */
1133    public int getTextWidths(int start, int end, float[] widths, Paint p) {
1134        checkRange("getTextWidths", start, end);
1135
1136        int ret;
1137
1138        if (end <= mGapStart) {
1139            ret = p.getTextWidths(mText, start, end - start, widths);
1140        } else if (start >= mGapStart) {
1141            ret = p.getTextWidths(mText, start + mGapLength, end - start, widths);
1142        } else {
1143            char[] buf = TextUtils.obtain(end - start);
1144
1145            getChars(start, end, buf, 0);
1146            ret = p.getTextWidths(buf, 0, end - start, widths);
1147            TextUtils.recycle(buf);
1148        }
1149
1150        return ret;
1151    }
1152
1153    /**
1154     * Don't call this yourself -- exists for Paint to use internally.
1155     * {@hide}
1156     */
1157    public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
1158            float[] advances, int advancesPos, Paint p) {
1159
1160        float ret;
1161
1162        int contextLen = contextEnd - contextStart;
1163        int len = end - start;
1164
1165        if (end <= mGapStart) {
1166            ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
1167                    flags, advances, advancesPos);
1168        } else if (start >= mGapStart) {
1169            ret = p.getTextRunAdvances(mText, start + mGapLength, len,
1170                    contextStart + mGapLength, contextLen, flags, advances, advancesPos);
1171        } else {
1172            char[] buf = TextUtils.obtain(contextLen);
1173            getChars(contextStart, contextEnd, buf, 0);
1174            ret = p.getTextRunAdvances(buf, start - contextStart, len,
1175                    0, contextLen, flags, advances, advancesPos);
1176            TextUtils.recycle(buf);
1177        }
1178
1179        return ret;
1180    }
1181
1182    /**
1183     * Don't call this yourself -- exists for Paint to use internally.
1184     * {@hide}
1185     */
1186    public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
1187            float[] advances, int advancesPos, Paint p, int reserved) {
1188
1189        float ret;
1190
1191        int contextLen = contextEnd - contextStart;
1192        int len = end - start;
1193
1194        if (end <= mGapStart) {
1195            ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
1196                    flags, advances, advancesPos, reserved);
1197        } else if (start >= mGapStart) {
1198            ret = p.getTextRunAdvances(mText, start + mGapLength, len,
1199                    contextStart + mGapLength, contextLen, flags, advances, advancesPos, reserved);
1200        } else {
1201            char[] buf = TextUtils.obtain(contextLen);
1202            getChars(contextStart, contextEnd, buf, 0);
1203            ret = p.getTextRunAdvances(buf, start - contextStart, len,
1204                    0, contextLen, flags, advances, advancesPos, reserved);
1205            TextUtils.recycle(buf);
1206        }
1207
1208        return ret;
1209    }
1210
1211    /**
1212     * Returns the next cursor position in the run.  This avoids placing the cursor between
1213     * surrogates, between characters that form conjuncts, between base characters and combining
1214     * marks, or within a reordering cluster.
1215     *
1216     * <p>The context is the shaping context for cursor movement, generally the bounds of the metric
1217     * span enclosing the cursor in the direction of movement.
1218     * <code>contextStart</code>, <code>contextEnd</code> and <code>offset</code> are relative to
1219     * the start of the string.</p>
1220     *
1221     * <p>If cursorOpt is CURSOR_AT and the offset is not a valid cursor position,
1222     * this returns -1.  Otherwise this will never return a value before contextStart or after
1223     * contextEnd.</p>
1224     *
1225     * @param contextStart the start index of the context
1226     * @param contextEnd the (non-inclusive) end index of the context
1227     * @param flags either DIRECTION_RTL or DIRECTION_LTR
1228     * @param offset the cursor position to move from
1229     * @param cursorOpt how to move the cursor, one of CURSOR_AFTER,
1230     * CURSOR_AT_OR_AFTER, CURSOR_BEFORE,
1231     * CURSOR_AT_OR_BEFORE, or CURSOR_AT
1232     * @param p the Paint object that is requesting this information
1233     * @return the offset of the next position, or -1
1234     * @deprecated This is an internal method, refrain from using it in your code
1235     */
1236    @Deprecated
1237    public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
1238            int cursorOpt, Paint p) {
1239
1240        int ret;
1241
1242        int contextLen = contextEnd - contextStart;
1243        if (contextEnd <= mGapStart) {
1244            ret = p.getTextRunCursor(mText, contextStart, contextLen,
1245                    flags, offset, cursorOpt);
1246        } else if (contextStart >= mGapStart) {
1247            ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
1248                    flags, offset + mGapLength, cursorOpt) - mGapLength;
1249        } else {
1250            char[] buf = TextUtils.obtain(contextLen);
1251            getChars(contextStart, contextEnd, buf, 0);
1252            ret = p.getTextRunCursor(buf, 0, contextLen,
1253                    flags, offset - contextStart, cursorOpt) + contextStart;
1254            TextUtils.recycle(buf);
1255        }
1256
1257        return ret;
1258    }
1259
1260    // Documentation from interface
1261    public void setFilters(InputFilter[] filters) {
1262        if (filters == null) {
1263            throw new IllegalArgumentException();
1264        }
1265
1266        mFilters = filters;
1267    }
1268
1269    // Documentation from interface
1270    public InputFilter[] getFilters() {
1271        return mFilters;
1272    }
1273
1274    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
1275    private InputFilter[] mFilters = NO_FILTERS;
1276
1277    private char[] mText;
1278    private int mGapStart;
1279    private int mGapLength;
1280
1281    private Object[] mSpans;
1282    private int[] mSpanStarts;
1283    private int[] mSpanEnds;
1284    private int[] mSpanFlags;
1285    private int mSpanCount;
1286    private int mSpanCountBeforeAdd;
1287
1288    // TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned}
1289    private static final int MARK = 1;
1290    private static final int POINT = 2;
1291    private static final int PARAGRAPH = 3;
1292
1293    private static final int START_MASK = 0xF0;
1294    private static final int END_MASK = 0x0F;
1295    private static final int START_SHIFT = 4;
1296
1297    // These bits are not (currently) used by SPANNED flags
1298    private static final int SPAN_START_AT_START = 0x1000;
1299    private static final int SPAN_START_AT_END = 0x2000;
1300    private static final int SPAN_END_AT_START = 0x4000;
1301    private static final int SPAN_END_AT_END = 0x8000;
1302    private static final int SPAN_START_END_MASK = 0xF000;
1303}
1304