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