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 replacedLength = end - start;
264        final int replacementLength = csEnd - csStart;
265        final int nbNewChars = replacementLength - replacedLength;
266
267        for (int i = mSpanCount - 1; i >= 0; i--) {
268            int spanStart = mSpanStarts[i];
269            if (spanStart > mGapStart)
270                spanStart -= mGapLength;
271
272            int spanEnd = mSpanEnds[i];
273            if (spanEnd > mGapStart)
274                spanEnd -= mGapLength;
275
276            if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
277                int ost = spanStart;
278                int oen = spanEnd;
279                int clen = length();
280
281                if (spanStart > start && spanStart <= end) {
282                    for (spanStart = end; spanStart < clen; spanStart++)
283                        if (spanStart > end && charAt(spanStart - 1) == '\n')
284                            break;
285                }
286
287                if (spanEnd > start && spanEnd <= end) {
288                    for (spanEnd = end; spanEnd < clen; spanEnd++)
289                        if (spanEnd > end && charAt(spanEnd - 1) == '\n')
290                            break;
291                }
292
293                if (spanStart != ost || spanEnd != oen)
294                    setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]);
295            }
296
297            int flags = 0;
298            if (spanStart == start) flags |= SPAN_START_AT_START;
299            else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END;
300            if (spanEnd == start) flags |= SPAN_END_AT_START;
301            else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END;
302            mSpanFlags[i] |= flags;
303        }
304
305        moveGapTo(end);
306
307        if (nbNewChars >= mGapLength) {
308            resizeFor(mText.length + nbNewChars - mGapLength);
309        }
310
311        final boolean textIsRemoved = replacementLength == 0;
312        // The removal pass needs to be done before the gap is updated in order to broadcast the
313        // correct previous positions to the correct intersecting SpanWatchers
314        if (replacedLength > 0) { // no need for span fixup on pure insertion
315            // A for loop will not work because the array is being modified
316            // Do not iterate in reverse to keep the SpanWatchers notified in ordering
317            // Also, a removed SpanWatcher should not get notified of removed spans located
318            // further in the span array.
319            int i = 0;
320            while (i < mSpanCount) {
321                if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) ==
322                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE &&
323                        mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength &&
324                        mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength &&
325                        // This condition indicates that the span would become empty
326                        (textIsRemoved || mSpanStarts[i] > start || mSpanEnds[i] < mGapStart)) {
327                    removeSpan(i);
328                    continue; // do not increment i, spans will be shifted left in the array
329                }
330
331                i++;
332            }
333        }
334
335        mGapStart += nbNewChars;
336        mGapLength -= nbNewChars;
337
338        if (mGapLength < 1)
339            new Exception("mGapLength < 1").printStackTrace();
340
341        TextUtils.getChars(cs, csStart, csEnd, mText, start);
342
343        if (replacedLength > 0) { // no need for span fixup on pure insertion
344            final boolean atEnd = (mGapStart + mGapLength == mText.length);
345
346            for (int i = 0; i < mSpanCount; i++) {
347                final int startFlag = (mSpanFlags[i] & START_MASK) >> START_SHIFT;
348                mSpanStarts[i] = updatedIntervalBound(mSpanStarts[i], start, nbNewChars, startFlag,
349                        atEnd, textIsRemoved);
350
351                final int endFlag = (mSpanFlags[i] & END_MASK);
352                mSpanEnds[i] = updatedIntervalBound(mSpanEnds[i], start, nbNewChars, endFlag,
353                        atEnd, textIsRemoved);
354            }
355        }
356
357        mSpanCountBeforeAdd = mSpanCount;
358
359        if (cs instanceof Spanned) {
360            Spanned sp = (Spanned) cs;
361            Object[] spans = sp.getSpans(csStart, csEnd, Object.class);
362
363            for (int i = 0; i < spans.length; i++) {
364                int st = sp.getSpanStart(spans[i]);
365                int en = sp.getSpanEnd(spans[i]);
366
367                if (st < csStart) st = csStart;
368                if (en > csEnd) en = csEnd;
369
370                // Add span only if this object is not yet used as a span in this string
371                if (getSpanStart(spans[i]) < 0) {
372                    setSpan(false, spans[i], st - csStart + start, en - csStart + start,
373                            sp.getSpanFlags(spans[i]));
374                }
375            }
376        }
377    }
378
379    private int updatedIntervalBound(int offset, int start, int nbNewChars, int flag, boolean atEnd,
380            boolean textIsRemoved) {
381        if (offset >= start && offset < mGapStart + mGapLength) {
382            if (flag == POINT) {
383                // A POINT located inside the replaced range should be moved to the end of the
384                // replaced text.
385                // The exception is when the point is at the start of the range and we are doing a
386                // text replacement (as opposed to a deletion): the point stays there.
387                if (textIsRemoved || offset > start) {
388                    return mGapStart + mGapLength;
389                }
390            } else {
391                if (flag == PARAGRAPH) {
392                    if (atEnd) {
393                        return mGapStart + mGapLength;
394                    }
395                } else { // MARK
396                    // MARKs should be moved to the start, with the exception of a mark located at
397                    // the end of the range (which will be < mGapStart + mGapLength since mGapLength
398                    // is > 0, which should stay 'unchanged' at the end of the replaced text.
399                    if (textIsRemoved || offset < mGapStart - nbNewChars) {
400                        return start;
401                    } else {
402                        // Move to the end of replaced text (needed if nbNewChars != 0)
403                        return mGapStart;
404                    }
405                }
406            }
407        }
408        return offset;
409    }
410
411    private void removeSpan(int i) {
412        Object object = mSpans[i];
413
414        int start = mSpanStarts[i];
415        int end = mSpanEnds[i];
416
417        if (start > mGapStart) start -= mGapLength;
418        if (end > mGapStart) end -= mGapLength;
419
420        int count = mSpanCount - (i + 1);
421        System.arraycopy(mSpans, i + 1, mSpans, i, count);
422        System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count);
423        System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count);
424        System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count);
425
426        mSpanCount--;
427
428        mSpans[mSpanCount] = null;
429
430        sendSpanRemoved(object, start, end);
431    }
432
433    // Documentation from interface
434    public SpannableStringBuilder replace(int start, int end, CharSequence tb) {
435        return replace(start, end, tb, 0, tb.length());
436    }
437
438    // Documentation from interface
439    public SpannableStringBuilder replace(final int start, final int end,
440            CharSequence tb, int tbstart, int tbend) {
441        checkRange("replace", start, end);
442
443        int filtercount = mFilters.length;
444        for (int i = 0; i < filtercount; i++) {
445            CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);
446
447            if (repl != null) {
448                tb = repl;
449                tbstart = 0;
450                tbend = repl.length();
451            }
452        }
453
454        final int origLen = end - start;
455        final int newLen = tbend - tbstart;
456
457        if (origLen == 0 && newLen == 0 && !hasNonExclusiveExclusiveSpanAt(tb, tbstart)) {
458            // This is a no-op iif there are no spans in tb that would be added (with a 0-length)
459            // Early exit so that the text watchers do not get notified
460            return this;
461        }
462
463        TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);
464        sendBeforeTextChanged(textWatchers, start, origLen, newLen);
465
466        // Try to keep the cursor / selection at the same relative position during
467        // a text replacement. If replaced or replacement text length is zero, this
468        // is already taken care of.
469        boolean adjustSelection = origLen != 0 && newLen != 0;
470        int selectionStart = 0;
471        int selectionEnd = 0;
472        if (adjustSelection) {
473            selectionStart = Selection.getSelectionStart(this);
474            selectionEnd = Selection.getSelectionEnd(this);
475        }
476
477        change(start, end, tb, tbstart, tbend);
478
479        if (adjustSelection) {
480            if (selectionStart > start && selectionStart < end) {
481                final int offset = (selectionStart - start) * newLen / origLen;
482                selectionStart = start + offset;
483
484                setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,
485                        Spanned.SPAN_POINT_POINT);
486            }
487            if (selectionEnd > start && selectionEnd < end) {
488                final int offset = (selectionEnd - start) * newLen / origLen;
489                selectionEnd = start + offset;
490
491                setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,
492                        Spanned.SPAN_POINT_POINT);
493            }
494        }
495
496        sendTextChanged(textWatchers, start, origLen, newLen);
497        sendAfterTextChanged(textWatchers);
498
499        // Span watchers need to be called after text watchers, which may update the layout
500        sendToSpanWatchers(start, end, newLen - origLen);
501
502        return this;
503    }
504
505    private static boolean hasNonExclusiveExclusiveSpanAt(CharSequence text, int offset) {
506        if (text instanceof Spanned) {
507            Spanned spanned = (Spanned) text;
508            Object[] spans = spanned.getSpans(offset, offset, Object.class);
509            final int length = spans.length;
510            for (int i = 0; i < length; i++) {
511                Object span = spans[i];
512                int flags = spanned.getSpanFlags(span);
513                if (flags != Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) return true;
514            }
515        }
516        return false;
517    }
518
519    private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) {
520        for (int i = 0; i < mSpanCountBeforeAdd; i++) {
521            int spanStart = mSpanStarts[i];
522            int spanEnd = mSpanEnds[i];
523            if (spanStart > mGapStart) spanStart -= mGapLength;
524            if (spanEnd > mGapStart) spanEnd -= mGapLength;
525            int spanFlags = mSpanFlags[i];
526
527            int newReplaceEnd = replaceEnd + nbNewChars;
528            boolean spanChanged = false;
529
530            int previousSpanStart = spanStart;
531            if (spanStart > newReplaceEnd) {
532                if (nbNewChars != 0) {
533                    previousSpanStart -= nbNewChars;
534                    spanChanged = true;
535                }
536            } else if (spanStart >= replaceStart) {
537                // No change if span start was already at replace interval boundaries before replace
538                if ((spanStart != replaceStart ||
539                        ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) &&
540                        (spanStart != newReplaceEnd ||
541                        ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) {
542                    // TODO A correct previousSpanStart cannot be computed at this point.
543                    // It would require to save all the previous spans' positions before the replace
544                    // Using an invalid -1 value to convey this would break the broacast range
545                    spanChanged = true;
546                }
547            }
548
549            int previousSpanEnd = spanEnd;
550            if (spanEnd > newReplaceEnd) {
551                if (nbNewChars != 0) {
552                    previousSpanEnd -= nbNewChars;
553                    spanChanged = true;
554                }
555            } else if (spanEnd >= replaceStart) {
556                // No change if span start was already at replace interval boundaries before replace
557                if ((spanEnd != replaceStart ||
558                        ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) &&
559                        (spanEnd != newReplaceEnd ||
560                        ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) {
561                    // TODO same as above for previousSpanEnd
562                    spanChanged = true;
563                }
564            }
565
566            if (spanChanged) {
567                sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd);
568            }
569            mSpanFlags[i] &= ~SPAN_START_END_MASK;
570        }
571
572        // The spans starting at mIntermediateSpanCount were added from the replacement text
573        for (int i = mSpanCountBeforeAdd; i < mSpanCount; i++) {
574            int spanStart = mSpanStarts[i];
575            int spanEnd = mSpanEnds[i];
576            if (spanStart > mGapStart) spanStart -= mGapLength;
577            if (spanEnd > mGapStart) spanEnd -= mGapLength;
578            sendSpanAdded(mSpans[i], spanStart, spanEnd);
579        }
580    }
581
582    /**
583     * Mark the specified range of text with the specified object.
584     * The flags determine how the span will behave when text is
585     * inserted at the start or end of the span's range.
586     */
587    public void setSpan(Object what, int start, int end, int flags) {
588        setSpan(true, what, start, end, flags);
589    }
590
591    private void setSpan(boolean send, Object what, int start, int end, int flags) {
592        checkRange("setSpan", start, end);
593
594        int flagsStart = (flags & START_MASK) >> START_SHIFT;
595        if (flagsStart == PARAGRAPH) {
596            if (start != 0 && start != length()) {
597                char c = charAt(start - 1);
598
599                if (c != '\n')
600                    throw new RuntimeException("PARAGRAPH span must start at paragraph boundary");
601            }
602        }
603
604        int flagsEnd = flags & END_MASK;
605        if (flagsEnd == PARAGRAPH) {
606            if (end != 0 && end != length()) {
607                char c = charAt(end - 1);
608
609                if (c != '\n')
610                    throw new RuntimeException("PARAGRAPH span must end at paragraph boundary");
611            }
612        }
613
614        // 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
615        if (flagsStart == POINT && flagsEnd == MARK && start == end) {
616            if (send) Log.e("SpannableStringBuilder",
617                    "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length");
618            // Silently ignore invalid spans when they are created from this class.
619            // This avoids the duplication of the above test code before all the
620            // calls to setSpan that are done in this class
621            return;
622        }
623
624        int nstart = start;
625        int nend = end;
626
627        if (start > mGapStart) {
628            start += mGapLength;
629        } else if (start == mGapStart) {
630            if (flagsStart == POINT || (flagsStart == PARAGRAPH && start == length()))
631                start += mGapLength;
632        }
633
634        if (end > mGapStart) {
635            end += mGapLength;
636        } else if (end == mGapStart) {
637            if (flagsEnd == POINT || (flagsEnd == PARAGRAPH && end == length()))
638                end += mGapLength;
639        }
640
641        int count = mSpanCount;
642        Object[] spans = mSpans;
643
644        for (int i = 0; i < count; i++) {
645            if (spans[i] == what) {
646                int ostart = mSpanStarts[i];
647                int oend = mSpanEnds[i];
648
649                if (ostart > mGapStart)
650                    ostart -= mGapLength;
651                if (oend > mGapStart)
652                    oend -= mGapLength;
653
654                mSpanStarts[i] = start;
655                mSpanEnds[i] = end;
656                mSpanFlags[i] = flags;
657
658                if (send) sendSpanChanged(what, ostart, oend, nstart, nend);
659
660                return;
661            }
662        }
663
664        if (mSpanCount + 1 >= mSpans.length) {
665            int newsize = ArrayUtils.idealIntArraySize(mSpanCount + 1);
666            Object[] newspans = new Object[newsize];
667            int[] newspanstarts = new int[newsize];
668            int[] newspanends = new int[newsize];
669            int[] newspanflags = new int[newsize];
670
671            System.arraycopy(mSpans, 0, newspans, 0, mSpanCount);
672            System.arraycopy(mSpanStarts, 0, newspanstarts, 0, mSpanCount);
673            System.arraycopy(mSpanEnds, 0, newspanends, 0, mSpanCount);
674            System.arraycopy(mSpanFlags, 0, newspanflags, 0, mSpanCount);
675
676            mSpans = newspans;
677            mSpanStarts = newspanstarts;
678            mSpanEnds = newspanends;
679            mSpanFlags = newspanflags;
680        }
681
682        mSpans[mSpanCount] = what;
683        mSpanStarts[mSpanCount] = start;
684        mSpanEnds[mSpanCount] = end;
685        mSpanFlags[mSpanCount] = flags;
686        mSpanCount++;
687
688        if (send) sendSpanAdded(what, nstart, nend);
689    }
690
691    /**
692     * Remove the specified markup object from the buffer.
693     */
694    public void removeSpan(Object what) {
695        for (int i = mSpanCount - 1; i >= 0; i--) {
696            if (mSpans[i] == what) {
697                removeSpan(i);
698                return;
699            }
700        }
701    }
702
703    /**
704     * Return the buffer offset of the beginning of the specified
705     * markup object, or -1 if it is not attached to this buffer.
706     */
707    public int getSpanStart(Object what) {
708        int count = mSpanCount;
709        Object[] spans = mSpans;
710
711        for (int i = count - 1; i >= 0; i--) {
712            if (spans[i] == what) {
713                int where = mSpanStarts[i];
714
715                if (where > mGapStart)
716                    where -= mGapLength;
717
718                return where;
719            }
720        }
721
722        return -1;
723    }
724
725    /**
726     * Return the buffer offset of the end of the specified
727     * markup object, or -1 if it is not attached to this buffer.
728     */
729    public int getSpanEnd(Object what) {
730        int count = mSpanCount;
731        Object[] spans = mSpans;
732
733        for (int i = count - 1; i >= 0; i--) {
734            if (spans[i] == what) {
735                int where = mSpanEnds[i];
736
737                if (where > mGapStart)
738                    where -= mGapLength;
739
740                return where;
741            }
742        }
743
744        return -1;
745    }
746
747    /**
748     * Return the flags of the end of the specified
749     * markup object, or 0 if it is not attached to this buffer.
750     */
751    public int getSpanFlags(Object what) {
752        int count = mSpanCount;
753        Object[] spans = mSpans;
754
755        for (int i = count - 1; i >= 0; i--) {
756            if (spans[i] == what) {
757                return mSpanFlags[i];
758            }
759        }
760
761        return 0;
762    }
763
764    /**
765     * Return an array of the spans of the specified type that overlap
766     * the specified range of the buffer.  The kind may be Object.class to get
767     * a list of all the spans regardless of type.
768     */
769    @SuppressWarnings("unchecked")
770    public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
771        if (kind == null) return ArrayUtils.emptyArray(kind);
772
773        int spanCount = mSpanCount;
774        Object[] spans = mSpans;
775        int[] starts = mSpanStarts;
776        int[] ends = mSpanEnds;
777        int[] flags = mSpanFlags;
778        int gapstart = mGapStart;
779        int gaplen = mGapLength;
780
781        int count = 0;
782        T[] ret = null;
783        T ret1 = null;
784
785        for (int i = 0; i < spanCount; i++) {
786            int spanStart = starts[i];
787            if (spanStart > gapstart) {
788                spanStart -= gaplen;
789            }
790            if (spanStart > queryEnd) {
791                continue;
792            }
793
794            int spanEnd = ends[i];
795            if (spanEnd > gapstart) {
796                spanEnd -= gaplen;
797            }
798            if (spanEnd < queryStart) {
799                continue;
800            }
801
802            if (spanStart != spanEnd && queryStart != queryEnd) {
803                if (spanStart == queryEnd)
804                    continue;
805                if (spanEnd == queryStart)
806                    continue;
807            }
808
809            // Expensive test, should be performed after the previous tests
810            if (!kind.isInstance(spans[i])) continue;
811
812            if (count == 0) {
813                // Safe conversion thanks to the isInstance test above
814                ret1 = (T) spans[i];
815                count++;
816            } else {
817                if (count == 1) {
818                    // Safe conversion, but requires a suppressWarning
819                    ret = (T[]) Array.newInstance(kind, spanCount - i + 1);
820                    ret[0] = ret1;
821                }
822
823                int prio = flags[i] & SPAN_PRIORITY;
824                if (prio != 0) {
825                    int j;
826
827                    for (j = 0; j < count; j++) {
828                        int p = getSpanFlags(ret[j]) & SPAN_PRIORITY;
829
830                        if (prio > p) {
831                            break;
832                        }
833                    }
834
835                    System.arraycopy(ret, j, ret, j + 1, count - j);
836                    // Safe conversion thanks to the isInstance test above
837                    ret[j] = (T) spans[i];
838                    count++;
839                } else {
840                    // Safe conversion thanks to the isInstance test above
841                    ret[count++] = (T) spans[i];
842                }
843            }
844        }
845
846        if (count == 0) {
847            return ArrayUtils.emptyArray(kind);
848        }
849        if (count == 1) {
850            // Safe conversion, but requires a suppressWarning
851            ret = (T[]) Array.newInstance(kind, 1);
852            ret[0] = ret1;
853            return ret;
854        }
855        if (count == ret.length) {
856            return ret;
857        }
858
859        // Safe conversion, but requires a suppressWarning
860        T[] nret = (T[]) Array.newInstance(kind, count);
861        System.arraycopy(ret, 0, nret, 0, count);
862        return nret;
863    }
864
865    /**
866     * Return the next offset after <code>start</code> but less than or
867     * equal to <code>limit</code> where a span of the specified type
868     * begins or ends.
869     */
870    public int nextSpanTransition(int start, int limit, Class kind) {
871        int count = mSpanCount;
872        Object[] spans = mSpans;
873        int[] starts = mSpanStarts;
874        int[] ends = mSpanEnds;
875        int gapstart = mGapStart;
876        int gaplen = mGapLength;
877
878        if (kind == null) {
879            kind = Object.class;
880        }
881
882        for (int i = 0; i < count; i++) {
883            int st = starts[i];
884            int en = ends[i];
885
886            if (st > gapstart)
887                st -= gaplen;
888            if (en > gapstart)
889                en -= gaplen;
890
891            if (st > start && st < limit && kind.isInstance(spans[i]))
892                limit = st;
893            if (en > start && en < limit && kind.isInstance(spans[i]))
894                limit = en;
895        }
896
897        return limit;
898    }
899
900    /**
901     * Return a new CharSequence containing a copy of the specified
902     * range of this buffer, including the overlapping spans.
903     */
904    public CharSequence subSequence(int start, int end) {
905        return new SpannableStringBuilder(this, start, end);
906    }
907
908    /**
909     * Copy the specified range of chars from this buffer into the
910     * specified array, beginning at the specified offset.
911     */
912    public void getChars(int start, int end, char[] dest, int destoff) {
913        checkRange("getChars", start, end);
914
915        if (end <= mGapStart) {
916            System.arraycopy(mText, start, dest, destoff, end - start);
917        } else if (start >= mGapStart) {
918            System.arraycopy(mText, start + mGapLength, dest, destoff, end - start);
919        } else {
920            System.arraycopy(mText, start, dest, destoff, mGapStart - start);
921            System.arraycopy(mText, mGapStart + mGapLength,
922                    dest, destoff + (mGapStart - start),
923                    end - mGapStart);
924        }
925    }
926
927    /**
928     * Return a String containing a copy of the chars in this buffer.
929     */
930    @Override
931    public String toString() {
932        int len = length();
933        char[] buf = new char[len];
934
935        getChars(0, len, buf, 0);
936        return new String(buf);
937    }
938
939    /**
940     * Return a String containing a copy of the chars in this buffer, limited to the
941     * [start, end[ range.
942     * @hide
943     */
944    public String substring(int start, int end) {
945        char[] buf = new char[end - start];
946        getChars(start, end, buf, 0);
947        return new String(buf);
948    }
949
950    private void sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after) {
951        int n = watchers.length;
952
953        for (int i = 0; i < n; i++) {
954            watchers[i].beforeTextChanged(this, start, before, after);
955        }
956    }
957
958    private void sendTextChanged(TextWatcher[] watchers, int start, int before, int after) {
959        int n = watchers.length;
960
961        for (int i = 0; i < n; i++) {
962            watchers[i].onTextChanged(this, start, before, after);
963        }
964    }
965
966    private void sendAfterTextChanged(TextWatcher[] watchers) {
967        int n = watchers.length;
968
969        for (int i = 0; i < n; i++) {
970            watchers[i].afterTextChanged(this);
971        }
972    }
973
974    private void sendSpanAdded(Object what, int start, int end) {
975        SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
976        int n = recip.length;
977
978        for (int i = 0; i < n; i++) {
979            recip[i].onSpanAdded(this, what, start, end);
980        }
981    }
982
983    private void sendSpanRemoved(Object what, int start, int end) {
984        SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
985        int n = recip.length;
986
987        for (int i = 0; i < n; i++) {
988            recip[i].onSpanRemoved(this, what, start, end);
989        }
990    }
991
992    private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) {
993        // The bounds of a possible SpanWatcher are guaranteed to be set before this method is
994        // called, so that the order of the span does not affect this broadcast.
995        SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start),
996                Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class);
997        int n = spanWatchers.length;
998        for (int i = 0; i < n; i++) {
999            spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end);
1000        }
1001    }
1002
1003    private static String region(int start, int end) {
1004        return "(" + start + " ... " + end + ")";
1005    }
1006
1007    private void checkRange(final String operation, int start, int end) {
1008        if (end < start) {
1009            throw new IndexOutOfBoundsException(operation + " " +
1010                    region(start, end) + " has end before start");
1011        }
1012
1013        int len = length();
1014
1015        if (start > len || end > len) {
1016            throw new IndexOutOfBoundsException(operation + " " +
1017                    region(start, end) + " ends beyond length " + len);
1018        }
1019
1020        if (start < 0 || end < 0) {
1021            throw new IndexOutOfBoundsException(operation + " " +
1022                    region(start, end) + " starts before 0");
1023        }
1024    }
1025
1026    /*
1027    private boolean isprint(char c) { // XXX
1028        if (c >= ' ' && c <= '~')
1029            return true;
1030        else
1031            return false;
1032    }
1033
1034    private static final int startFlag(int flag) {
1035        return (flag >> 4) & 0x0F;
1036    }
1037
1038    private static final int endFlag(int flag) {
1039        return flag & 0x0F;
1040    }
1041
1042    public void dump() { // XXX
1043        for (int i = 0; i < mGapStart; i++) {
1044            System.out.print('|');
1045            System.out.print(' ');
1046            System.out.print(isprint(mText[i]) ? mText[i] : '.');
1047            System.out.print(' ');
1048        }
1049
1050        for (int i = mGapStart; i < mGapStart + mGapLength; i++) {
1051            System.out.print('|');
1052            System.out.print('(');
1053            System.out.print(isprint(mText[i]) ? mText[i] : '.');
1054            System.out.print(')');
1055        }
1056
1057        for (int i = mGapStart + mGapLength; i < mText.length; i++) {
1058            System.out.print('|');
1059            System.out.print(' ');
1060            System.out.print(isprint(mText[i]) ? mText[i] : '.');
1061            System.out.print(' ');
1062        }
1063
1064        System.out.print('\n');
1065
1066        for (int i = 0; i < mText.length + 1; i++) {
1067            int found = 0;
1068            int wfound = 0;
1069
1070            for (int j = 0; j < mSpanCount; j++) {
1071                if (mSpanStarts[j] == i) {
1072                    found = 1;
1073                    wfound = j;
1074                    break;
1075                }
1076
1077                if (mSpanEnds[j] == i) {
1078                    found = 2;
1079                    wfound = j;
1080                    break;
1081                }
1082            }
1083
1084            if (found == 1) {
1085                if (startFlag(mSpanFlags[wfound]) == MARK)
1086                    System.out.print("(   ");
1087                if (startFlag(mSpanFlags[wfound]) == PARAGRAPH)
1088                    System.out.print("<   ");
1089                else
1090                    System.out.print("[   ");
1091            } else if (found == 2) {
1092                if (endFlag(mSpanFlags[wfound]) == POINT)
1093                    System.out.print(")   ");
1094                if (endFlag(mSpanFlags[wfound]) == PARAGRAPH)
1095                    System.out.print(">   ");
1096                else
1097                    System.out.print("]   ");
1098            } else {
1099                System.out.print("    ");
1100            }
1101        }
1102
1103        System.out.print("\n");
1104    }
1105    */
1106
1107    /**
1108     * Don't call this yourself -- exists for Canvas to use internally.
1109     * {@hide}
1110     */
1111    public void drawText(Canvas c, int start, int end, float x, float y, Paint p) {
1112        checkRange("drawText", start, end);
1113
1114        if (end <= mGapStart) {
1115            c.drawText(mText, start, end - start, x, y, p);
1116        } else if (start >= mGapStart) {
1117            c.drawText(mText, start + mGapLength, end - start, x, y, p);
1118        } else {
1119            char[] buf = TextUtils.obtain(end - start);
1120
1121            getChars(start, end, buf, 0);
1122            c.drawText(buf, 0, end - start, x, y, p);
1123            TextUtils.recycle(buf);
1124        }
1125    }
1126
1127
1128    /**
1129     * Don't call this yourself -- exists for Canvas to use internally.
1130     * {@hide}
1131     */
1132    public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd,
1133            float x, float y, int flags, Paint p) {
1134        checkRange("drawTextRun", start, end);
1135
1136        int contextLen = contextEnd - contextStart;
1137        int len = end - start;
1138        if (contextEnd <= mGapStart) {
1139            c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, flags, p);
1140        } else if (contextStart >= mGapStart) {
1141            c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength,
1142                    contextLen, x, y, flags, p);
1143        } else {
1144            char[] buf = TextUtils.obtain(contextLen);
1145            getChars(contextStart, contextEnd, buf, 0);
1146            c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, flags, p);
1147            TextUtils.recycle(buf);
1148        }
1149    }
1150
1151    /**
1152     * Don't call this yourself -- exists for Paint to use internally.
1153     * {@hide}
1154     */
1155    public float measureText(int start, int end, Paint p) {
1156        checkRange("measureText", start, end);
1157
1158        float ret;
1159
1160        if (end <= mGapStart) {
1161            ret = p.measureText(mText, start, end - start);
1162        } else if (start >= mGapStart) {
1163            ret = p.measureText(mText, start + mGapLength, end - start);
1164        } else {
1165            char[] buf = TextUtils.obtain(end - start);
1166
1167            getChars(start, end, buf, 0);
1168            ret = p.measureText(buf, 0, end - start);
1169            TextUtils.recycle(buf);
1170        }
1171
1172        return ret;
1173    }
1174
1175    /**
1176     * Don't call this yourself -- exists for Paint to use internally.
1177     * {@hide}
1178     */
1179    public int getTextWidths(int start, int end, float[] widths, Paint p) {
1180        checkRange("getTextWidths", start, end);
1181
1182        int ret;
1183
1184        if (end <= mGapStart) {
1185            ret = p.getTextWidths(mText, start, end - start, widths);
1186        } else if (start >= mGapStart) {
1187            ret = p.getTextWidths(mText, start + mGapLength, end - start, widths);
1188        } else {
1189            char[] buf = TextUtils.obtain(end - start);
1190
1191            getChars(start, end, buf, 0);
1192            ret = p.getTextWidths(buf, 0, end - start, widths);
1193            TextUtils.recycle(buf);
1194        }
1195
1196        return ret;
1197    }
1198
1199    /**
1200     * Don't call this yourself -- exists for Paint to use internally.
1201     * {@hide}
1202     */
1203    public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
1204            float[] advances, int advancesPos, Paint p) {
1205
1206        float ret;
1207
1208        int contextLen = contextEnd - contextStart;
1209        int len = end - start;
1210
1211        if (end <= mGapStart) {
1212            ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
1213                    flags, advances, advancesPos);
1214        } else if (start >= mGapStart) {
1215            ret = p.getTextRunAdvances(mText, start + mGapLength, len,
1216                    contextStart + mGapLength, contextLen, flags, advances, advancesPos);
1217        } else {
1218            char[] buf = TextUtils.obtain(contextLen);
1219            getChars(contextStart, contextEnd, buf, 0);
1220            ret = p.getTextRunAdvances(buf, start - contextStart, len,
1221                    0, contextLen, flags, advances, advancesPos);
1222            TextUtils.recycle(buf);
1223        }
1224
1225        return ret;
1226    }
1227
1228    /**
1229     * Don't call this yourself -- exists for Paint to use internally.
1230     * {@hide}
1231     */
1232    public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, int flags,
1233            float[] advances, int advancesPos, Paint p, int reserved) {
1234
1235        float ret;
1236
1237        int contextLen = contextEnd - contextStart;
1238        int len = end - start;
1239
1240        if (end <= mGapStart) {
1241            ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen,
1242                    flags, advances, advancesPos, reserved);
1243        } else if (start >= mGapStart) {
1244            ret = p.getTextRunAdvances(mText, start + mGapLength, len,
1245                    contextStart + mGapLength, contextLen, flags, advances, advancesPos, reserved);
1246        } else {
1247            char[] buf = TextUtils.obtain(contextLen);
1248            getChars(contextStart, contextEnd, buf, 0);
1249            ret = p.getTextRunAdvances(buf, start - contextStart, len,
1250                    0, contextLen, flags, advances, advancesPos, reserved);
1251            TextUtils.recycle(buf);
1252        }
1253
1254        return ret;
1255    }
1256
1257    /**
1258     * Returns the next cursor position in the run.  This avoids placing the cursor between
1259     * surrogates, between characters that form conjuncts, between base characters and combining
1260     * marks, or within a reordering cluster.
1261     *
1262     * <p>The context is the shaping context for cursor movement, generally the bounds of the metric
1263     * span enclosing the cursor in the direction of movement.
1264     * <code>contextStart</code>, <code>contextEnd</code> and <code>offset</code> are relative to
1265     * the start of the string.</p>
1266     *
1267     * <p>If cursorOpt is CURSOR_AT and the offset is not a valid cursor position,
1268     * this returns -1.  Otherwise this will never return a value before contextStart or after
1269     * contextEnd.</p>
1270     *
1271     * @param contextStart the start index of the context
1272     * @param contextEnd the (non-inclusive) end index of the context
1273     * @param flags either DIRECTION_RTL or DIRECTION_LTR
1274     * @param offset the cursor position to move from
1275     * @param cursorOpt how to move the cursor, one of CURSOR_AFTER,
1276     * CURSOR_AT_OR_AFTER, CURSOR_BEFORE,
1277     * CURSOR_AT_OR_BEFORE, or CURSOR_AT
1278     * @param p the Paint object that is requesting this information
1279     * @return the offset of the next position, or -1
1280     * @deprecated This is an internal method, refrain from using it in your code
1281     */
1282    @Deprecated
1283    public int getTextRunCursor(int contextStart, int contextEnd, int flags, int offset,
1284            int cursorOpt, Paint p) {
1285
1286        int ret;
1287
1288        int contextLen = contextEnd - contextStart;
1289        if (contextEnd <= mGapStart) {
1290            ret = p.getTextRunCursor(mText, contextStart, contextLen,
1291                    flags, offset, cursorOpt);
1292        } else if (contextStart >= mGapStart) {
1293            ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen,
1294                    flags, offset + mGapLength, cursorOpt) - mGapLength;
1295        } else {
1296            char[] buf = TextUtils.obtain(contextLen);
1297            getChars(contextStart, contextEnd, buf, 0);
1298            ret = p.getTextRunCursor(buf, 0, contextLen,
1299                    flags, offset - contextStart, cursorOpt) + contextStart;
1300            TextUtils.recycle(buf);
1301        }
1302
1303        return ret;
1304    }
1305
1306    // Documentation from interface
1307    public void setFilters(InputFilter[] filters) {
1308        if (filters == null) {
1309            throw new IllegalArgumentException();
1310        }
1311
1312        mFilters = filters;
1313    }
1314
1315    // Documentation from interface
1316    public InputFilter[] getFilters() {
1317        return mFilters;
1318    }
1319
1320    private static final InputFilter[] NO_FILTERS = new InputFilter[0];
1321    private InputFilter[] mFilters = NO_FILTERS;
1322
1323    private char[] mText;
1324    private int mGapStart;
1325    private int mGapLength;
1326
1327    private Object[] mSpans;
1328    private int[] mSpanStarts;
1329    private int[] mSpanEnds;
1330    private int[] mSpanFlags;
1331    private int mSpanCount;
1332    private int mSpanCountBeforeAdd;
1333
1334    // TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned}
1335    private static final int MARK = 1;
1336    private static final int POINT = 2;
1337    private static final int PARAGRAPH = 3;
1338
1339    private static final int START_MASK = 0xF0;
1340    private static final int END_MASK = 0x0F;
1341    private static final int START_SHIFT = 4;
1342
1343    // These bits are not (currently) used by SPANNED flags
1344    private static final int SPAN_START_AT_START = 0x1000;
1345    private static final int SPAN_START_AT_END = 0x2000;
1346    private static final int SPAN_END_AT_START = 0x4000;
1347    private static final int SPAN_END_AT_END = 0x8000;
1348    private static final int SPAN_START_END_MASK = 0xF000;
1349}
1350