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