1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.text;
18
19import com.android.internal.util.ArrayUtils;
20import com.android.internal.util.GrowingArrayUtils;
21
22import libcore.util.EmptyArray;
23
24import java.lang.reflect.Array;
25
26/* package */ abstract class SpannableStringInternal
27{
28    /* package */ SpannableStringInternal(CharSequence source,
29                                          int start, int end, boolean ignoreNoCopySpan) {
30        if (start == 0 && end == source.length())
31            mText = source.toString();
32        else
33            mText = source.toString().substring(start, end);
34
35        mSpans = EmptyArray.OBJECT;
36        // Invariant: mSpanData.length = mSpans.length * COLUMNS
37        mSpanData = EmptyArray.INT;
38
39        if (source instanceof Spanned) {
40            if (source instanceof SpannableStringInternal) {
41                copySpans((SpannableStringInternal) source, start, end, ignoreNoCopySpan);
42            } else {
43                copySpans((Spanned) source, start, end, ignoreNoCopySpan);
44            }
45        }
46    }
47
48    /**
49     * This unused method is left since this is listed in hidden api list.
50     *
51     * Due to backward compatibility reasons, we copy even NoCopySpan by default
52     */
53    /* package */ SpannableStringInternal(CharSequence source, int start, int end) {
54        this(source, start, end, false /* ignoreNoCopySpan */);
55    }
56
57    /**
58     * Copies another {@link Spanned} object's spans between [start, end] into this object.
59     *
60     * @param src Source object to copy from.
61     * @param start Start index in the source object.
62     * @param end End index in the source object.
63     * @param ignoreNoCopySpan whether to copy NoCopySpans in the {@code source}
64     */
65    private void copySpans(Spanned src, int start, int end, boolean ignoreNoCopySpan) {
66        Object[] spans = src.getSpans(start, end, Object.class);
67
68        for (int i = 0; i < spans.length; i++) {
69            if (ignoreNoCopySpan && spans[i] instanceof NoCopySpan) {
70                continue;
71            }
72            int st = src.getSpanStart(spans[i]);
73            int en = src.getSpanEnd(spans[i]);
74            int fl = src.getSpanFlags(spans[i]);
75
76            if (st < start)
77                st = start;
78            if (en > end)
79                en = end;
80
81            setSpan(spans[i], st - start, en - start, fl, false/*enforceParagraph*/);
82        }
83    }
84
85    /**
86     * Copies a {@link SpannableStringInternal} object's spans between [start, end] into this
87     * object.
88     *
89     * @param src Source object to copy from.
90     * @param start Start index in the source object.
91     * @param end End index in the source object.
92     * @param ignoreNoCopySpan copy NoCopySpan for backward compatible reasons.
93     */
94    private void copySpans(SpannableStringInternal src, int start, int end,
95            boolean ignoreNoCopySpan) {
96        int count = 0;
97        final int[] srcData = src.mSpanData;
98        final Object[] srcSpans = src.mSpans;
99        final int limit = src.mSpanCount;
100        boolean hasNoCopySpan = false;
101
102        for (int i = 0; i < limit; i++) {
103            int spanStart = srcData[i * COLUMNS + START];
104            int spanEnd = srcData[i * COLUMNS + END];
105            if (isOutOfCopyRange(start, end, spanStart, spanEnd)) continue;
106            if (srcSpans[i] instanceof NoCopySpan) {
107                hasNoCopySpan = true;
108                if (ignoreNoCopySpan) {
109                    continue;
110                }
111            }
112            count++;
113        }
114
115        if (count == 0) return;
116
117        if (!hasNoCopySpan && start == 0 && end == src.length()) {
118            mSpans = ArrayUtils.newUnpaddedObjectArray(src.mSpans.length);
119            mSpanData = new int[src.mSpanData.length];
120            mSpanCount = src.mSpanCount;
121            System.arraycopy(src.mSpans, 0, mSpans, 0, src.mSpans.length);
122            System.arraycopy(src.mSpanData, 0, mSpanData, 0, mSpanData.length);
123        } else {
124            mSpanCount = count;
125            mSpans = ArrayUtils.newUnpaddedObjectArray(mSpanCount);
126            mSpanData = new int[mSpans.length * COLUMNS];
127            for (int i = 0, j = 0; i < limit; i++) {
128                int spanStart = srcData[i * COLUMNS + START];
129                int spanEnd = srcData[i * COLUMNS + END];
130                if (isOutOfCopyRange(start, end, spanStart, spanEnd)
131                        || (ignoreNoCopySpan && srcSpans[i] instanceof NoCopySpan)) {
132                    continue;
133                }
134                if (spanStart < start) spanStart = start;
135                if (spanEnd > end) spanEnd = end;
136
137                mSpans[j] = srcSpans[i];
138                mSpanData[j * COLUMNS + START] = spanStart - start;
139                mSpanData[j * COLUMNS + END] = spanEnd - start;
140                mSpanData[j * COLUMNS + FLAGS] = srcData[i * COLUMNS + FLAGS];
141                j++;
142            }
143        }
144    }
145
146    /**
147     * Checks if [spanStart, spanEnd] interval is excluded from [start, end].
148     *
149     * @return True if excluded, false if included.
150     */
151    private final boolean isOutOfCopyRange(int start, int end, int spanStart, int spanEnd) {
152        if (spanStart > end || spanEnd < start) return true;
153        if (spanStart != spanEnd && start != end) {
154            if (spanStart == end || spanEnd == start) return true;
155        }
156        return false;
157    }
158
159    public final int length() {
160        return mText.length();
161    }
162
163    public final char charAt(int i) {
164        return mText.charAt(i);
165    }
166
167    public final String toString() {
168        return mText;
169    }
170
171    /* subclasses must do subSequence() to preserve type */
172
173    public final void getChars(int start, int end, char[] dest, int off) {
174        mText.getChars(start, end, dest, off);
175    }
176
177    /* package */ void setSpan(Object what, int start, int end, int flags) {
178        setSpan(what, start, end, flags, true/*enforceParagraph*/);
179    }
180
181    private boolean isIndexFollowsNextLine(int index) {
182        return index != 0 && index != length() && charAt(index - 1) != '\n';
183    }
184
185    private void setSpan(Object what, int start, int end, int flags, boolean enforceParagraph) {
186        int nstart = start;
187        int nend = end;
188
189        checkRange("setSpan", start, end);
190
191        if ((flags & Spannable.SPAN_PARAGRAPH) == Spannable.SPAN_PARAGRAPH) {
192            if (isIndexFollowsNextLine(start)) {
193                if (!enforceParagraph) {
194                    // do not set the span
195                    return;
196                }
197                throw new RuntimeException("PARAGRAPH span must start at paragraph boundary"
198                        + " (" + start + " follows " + charAt(start - 1) + ")");
199            }
200
201            if (isIndexFollowsNextLine(end)) {
202                if (!enforceParagraph) {
203                    // do not set the span
204                    return;
205                }
206                throw new RuntimeException("PARAGRAPH span must end at paragraph boundary"
207                        + " (" + end + " follows " + charAt(end - 1) + ")");
208            }
209        }
210
211        int count = mSpanCount;
212        Object[] spans = mSpans;
213        int[] data = mSpanData;
214
215        for (int i = 0; i < count; i++) {
216            if (spans[i] == what) {
217                int ostart = data[i * COLUMNS + START];
218                int oend = data[i * COLUMNS + END];
219
220                data[i * COLUMNS + START] = start;
221                data[i * COLUMNS + END] = end;
222                data[i * COLUMNS + FLAGS] = flags;
223
224                sendSpanChanged(what, ostart, oend, nstart, nend);
225                return;
226            }
227        }
228
229        if (mSpanCount + 1 >= mSpans.length) {
230            Object[] newtags = ArrayUtils.newUnpaddedObjectArray(
231                    GrowingArrayUtils.growSize(mSpanCount));
232            int[] newdata = new int[newtags.length * 3];
233
234            System.arraycopy(mSpans, 0, newtags, 0, mSpanCount);
235            System.arraycopy(mSpanData, 0, newdata, 0, mSpanCount * 3);
236
237            mSpans = newtags;
238            mSpanData = newdata;
239        }
240
241        mSpans[mSpanCount] = what;
242        mSpanData[mSpanCount * COLUMNS + START] = start;
243        mSpanData[mSpanCount * COLUMNS + END] = end;
244        mSpanData[mSpanCount * COLUMNS + FLAGS] = flags;
245        mSpanCount++;
246
247        if (this instanceof Spannable)
248            sendSpanAdded(what, nstart, nend);
249    }
250
251    /* package */ void removeSpan(Object what) {
252        removeSpan(what, 0 /* flags */);
253    }
254
255    /**
256     * @hide
257     */
258    public void removeSpan(Object what, int flags) {
259        int count = mSpanCount;
260        Object[] spans = mSpans;
261        int[] data = mSpanData;
262
263        for (int i = count - 1; i >= 0; i--) {
264            if (spans[i] == what) {
265                int ostart = data[i * COLUMNS + START];
266                int oend = data[i * COLUMNS + END];
267
268                int c = count - (i + 1);
269
270                System.arraycopy(spans, i + 1, spans, i, c);
271                System.arraycopy(data, (i + 1) * COLUMNS,
272                        data, i * COLUMNS, c * COLUMNS);
273
274                mSpanCount--;
275
276                if ((flags & Spanned.SPAN_INTERMEDIATE) == 0) {
277                    sendSpanRemoved(what, ostart, oend);
278                }
279                return;
280            }
281        }
282    }
283
284    public int getSpanStart(Object what) {
285        int count = mSpanCount;
286        Object[] spans = mSpans;
287        int[] data = mSpanData;
288
289        for (int i = count - 1; i >= 0; i--) {
290            if (spans[i] == what) {
291                return data[i * COLUMNS + START];
292            }
293        }
294
295        return -1;
296    }
297
298    public int getSpanEnd(Object what) {
299        int count = mSpanCount;
300        Object[] spans = mSpans;
301        int[] data = mSpanData;
302
303        for (int i = count - 1; i >= 0; i--) {
304            if (spans[i] == what) {
305                return data[i * COLUMNS + END];
306            }
307        }
308
309        return -1;
310    }
311
312    public int getSpanFlags(Object what) {
313        int count = mSpanCount;
314        Object[] spans = mSpans;
315        int[] data = mSpanData;
316
317        for (int i = count - 1; i >= 0; i--) {
318            if (spans[i] == what) {
319                return data[i * COLUMNS + FLAGS];
320            }
321        }
322
323        return 0;
324    }
325
326    public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
327        int count = 0;
328
329        int spanCount = mSpanCount;
330        Object[] spans = mSpans;
331        int[] data = mSpanData;
332        Object[] ret = null;
333        Object ret1 = null;
334
335        for (int i = 0; i < spanCount; i++) {
336            int spanStart = data[i * COLUMNS + START];
337            int spanEnd = data[i * COLUMNS + END];
338
339            if (spanStart > queryEnd) {
340                continue;
341            }
342            if (spanEnd < queryStart) {
343                continue;
344            }
345
346            if (spanStart != spanEnd && queryStart != queryEnd) {
347                if (spanStart == queryEnd) {
348                    continue;
349                }
350                if (spanEnd == queryStart) {
351                    continue;
352                }
353            }
354
355            // verify span class as late as possible, since it is expensive
356            if (kind != null && kind != Object.class && !kind.isInstance(spans[i])) {
357                continue;
358            }
359
360            if (count == 0) {
361                ret1 = spans[i];
362                count++;
363            } else {
364                if (count == 1) {
365                    ret = (Object[]) Array.newInstance(kind, spanCount - i + 1);
366                    ret[0] = ret1;
367                }
368
369                int prio = data[i * COLUMNS + FLAGS] & Spanned.SPAN_PRIORITY;
370                if (prio != 0) {
371                    int j;
372
373                    for (j = 0; j < count; j++) {
374                        int p = getSpanFlags(ret[j]) & Spanned.SPAN_PRIORITY;
375
376                        if (prio > p) {
377                            break;
378                        }
379                    }
380
381                    System.arraycopy(ret, j, ret, j + 1, count - j);
382                    ret[j] = spans[i];
383                    count++;
384                } else {
385                    ret[count++] = spans[i];
386                }
387            }
388        }
389
390        if (count == 0) {
391            return (T[]) ArrayUtils.emptyArray(kind);
392        }
393        if (count == 1) {
394            ret = (Object[]) Array.newInstance(kind, 1);
395            ret[0] = ret1;
396            return (T[]) ret;
397        }
398        if (count == ret.length) {
399            return (T[]) ret;
400        }
401
402        Object[] nret = (Object[]) Array.newInstance(kind, count);
403        System.arraycopy(ret, 0, nret, 0, count);
404        return (T[]) nret;
405    }
406
407    public int nextSpanTransition(int start, int limit, Class kind) {
408        int count = mSpanCount;
409        Object[] spans = mSpans;
410        int[] data = mSpanData;
411
412        if (kind == null) {
413            kind = Object.class;
414        }
415
416        for (int i = 0; i < count; i++) {
417            int st = data[i * COLUMNS + START];
418            int en = data[i * COLUMNS + END];
419
420            if (st > start && st < limit && kind.isInstance(spans[i]))
421                limit = st;
422            if (en > start && en < limit && kind.isInstance(spans[i]))
423                limit = en;
424        }
425
426        return limit;
427    }
428
429    private void sendSpanAdded(Object what, int start, int end) {
430        SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
431        int n = recip.length;
432
433        for (int i = 0; i < n; i++) {
434            recip[i].onSpanAdded((Spannable) this, what, start, end);
435        }
436    }
437
438    private void sendSpanRemoved(Object what, int start, int end) {
439        SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
440        int n = recip.length;
441
442        for (int i = 0; i < n; i++) {
443            recip[i].onSpanRemoved((Spannable) this, what, start, end);
444        }
445    }
446
447    private void sendSpanChanged(Object what, int s, int e, int st, int en) {
448        SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en),
449                                       SpanWatcher.class);
450        int n = recip.length;
451
452        for (int i = 0; i < n; i++) {
453            recip[i].onSpanChanged((Spannable) this, what, s, e, st, en);
454        }
455    }
456
457    private static String region(int start, int end) {
458        return "(" + start + " ... " + end + ")";
459    }
460
461    private void checkRange(final String operation, int start, int end) {
462        if (end < start) {
463            throw new IndexOutOfBoundsException(operation + " " +
464                                                region(start, end) +
465                                                " has end before start");
466        }
467
468        int len = length();
469
470        if (start > len || end > len) {
471            throw new IndexOutOfBoundsException(operation + " " +
472                                                region(start, end) +
473                                                " ends beyond length " + len);
474        }
475
476        if (start < 0 || end < 0) {
477            throw new IndexOutOfBoundsException(operation + " " +
478                                                region(start, end) +
479                                                " starts before 0");
480        }
481    }
482
483    // Same as SpannableStringBuilder
484    @Override
485    public boolean equals(Object o) {
486        if (o instanceof Spanned &&
487                toString().equals(o.toString())) {
488            Spanned other = (Spanned) o;
489            // Check span data
490            Object[] otherSpans = other.getSpans(0, other.length(), Object.class);
491            if (mSpanCount == otherSpans.length) {
492                for (int i = 0; i < mSpanCount; ++i) {
493                    Object thisSpan = mSpans[i];
494                    Object otherSpan = otherSpans[i];
495                    if (thisSpan == this) {
496                        if (other != otherSpan ||
497                                getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
498                                getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
499                                getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
500                            return false;
501                        }
502                    } else if (!thisSpan.equals(otherSpan) ||
503                            getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
504                            getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
505                            getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
506                        return false;
507                    }
508                }
509                return true;
510            }
511        }
512        return false;
513    }
514
515    // Same as SpannableStringBuilder
516    @Override
517    public int hashCode() {
518        int hash = toString().hashCode();
519        hash = hash * 31 + mSpanCount;
520        for (int i = 0; i < mSpanCount; ++i) {
521            Object span = mSpans[i];
522            if (span != this) {
523                hash = hash * 31 + span.hashCode();
524            }
525            hash = hash * 31 + getSpanStart(span);
526            hash = hash * 31 + getSpanEnd(span);
527            hash = hash * 31 + getSpanFlags(span);
528        }
529        return hash;
530    }
531
532    /**
533     * Following two unused methods are left since these are listed in hidden api list.
534     *
535     * Due to backward compatibility reasons, we copy even NoCopySpan by default
536     */
537    private void copySpans(Spanned src, int start, int end) {
538        copySpans(src, start, end, false);
539    }
540
541    private void copySpans(SpannableStringInternal src, int start, int end) {
542        copySpans(src, start, end, false);
543    }
544
545
546
547    private String mText;
548    private Object[] mSpans;
549    private int[] mSpanData;
550    private int mSpanCount;
551
552    /* package */ static final Object[] EMPTY = new Object[0];
553
554    private static final int START = 0;
555    private static final int END = 1;
556    private static final int FLAGS = 2;
557    private static final int COLUMNS = 3;
558}
559