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) {
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        mSpanData = EmptyArray.INT;
37
38        if (source instanceof Spanned) {
39            if (source instanceof SpannableStringInternal) {
40                copySpans((SpannableStringInternal) source, start, end);
41            } else {
42                copySpans((Spanned) source, start, end);
43            }
44        }
45    }
46
47    /**
48     * Copies another {@link Spanned} object's spans between [start, end] into this object.
49     *
50     * @param src Source object to copy from.
51     * @param start Start index in the source object.
52     * @param end End index in the source object.
53     */
54    private final void copySpans(Spanned src, int start, int end) {
55        Object[] spans = src.getSpans(start, end, Object.class);
56
57        for (int i = 0; i < spans.length; i++) {
58            int st = src.getSpanStart(spans[i]);
59            int en = src.getSpanEnd(spans[i]);
60            int fl = src.getSpanFlags(spans[i]);
61
62            if (st < start)
63                st = start;
64            if (en > end)
65                en = end;
66
67            setSpan(spans[i], st - start, en - start, fl);
68        }
69    }
70
71    /**
72     * Copies a {@link SpannableStringInternal} object's spans between [start, end] into this
73     * object.
74     *
75     * @param src Source object to copy from.
76     * @param start Start index in the source object.
77     * @param end End index in the source object.
78     */
79    private final void copySpans(SpannableStringInternal src, int start, int end) {
80        if (start == 0 && end == src.length()) {
81            mSpans = ArrayUtils.newUnpaddedObjectArray(src.mSpans.length);
82            mSpanData = new int[src.mSpanData.length];
83            mSpanCount = src.mSpanCount;
84            System.arraycopy(src.mSpans, 0, mSpans, 0, src.mSpans.length);
85            System.arraycopy(src.mSpanData, 0, mSpanData, 0, mSpanData.length);
86        } else {
87            int count = 0;
88            int[] srcData = src.mSpanData;
89            int limit = src.mSpanCount;
90            for (int i = 0; i < limit; i++) {
91                int spanStart = srcData[i * COLUMNS + START];
92                int spanEnd = srcData[i * COLUMNS + END];
93                if (isOutOfCopyRange(start, end, spanStart, spanEnd)) continue;
94                count++;
95            }
96
97            if (count == 0) return;
98
99            Object[] srcSpans = src.mSpans;
100            mSpanCount = count;
101            mSpans = ArrayUtils.newUnpaddedObjectArray(mSpanCount);
102            mSpanData = new int[mSpanCount * COLUMNS];
103            for (int i = 0, j = 0; i < limit; i++) {
104                int spanStart = srcData[i * COLUMNS + START];
105                int spanEnd = srcData[i * COLUMNS + END];
106                if (isOutOfCopyRange(start, end, spanStart, spanEnd)) continue;
107                if (spanStart < start) spanStart = start;
108                if (spanEnd > end) spanEnd = end;
109
110                mSpans[j] = srcSpans[i];
111                mSpanData[j * COLUMNS + START] = spanStart - start;
112                mSpanData[j * COLUMNS + END] = spanEnd - start;
113                mSpanData[j * COLUMNS + FLAGS] = srcData[i * COLUMNS + FLAGS];
114                j++;
115            }
116        }
117    }
118
119    /**
120     * Checks if [spanStart, spanEnd] interval is excluded from [start, end].
121     *
122     * @return True if excluded, false if included.
123     */
124    private final boolean isOutOfCopyRange(int start, int end, int spanStart, int spanEnd) {
125        if (spanStart > end || spanEnd < start) return true;
126        if (spanStart != spanEnd && start != end) {
127            if (spanStart == end || spanEnd == start) return true;
128        }
129        return false;
130    }
131
132    public final int length() {
133        return mText.length();
134    }
135
136    public final char charAt(int i) {
137        return mText.charAt(i);
138    }
139
140    public final String toString() {
141        return mText;
142    }
143
144    /* subclasses must do subSequence() to preserve type */
145
146    public final void getChars(int start, int end, char[] dest, int off) {
147        mText.getChars(start, end, dest, off);
148    }
149
150    /* package */ void setSpan(Object what, int start, int end, int flags) {
151        int nstart = start;
152        int nend = end;
153
154        checkRange("setSpan", start, end);
155
156        if ((flags & Spannable.SPAN_PARAGRAPH) == Spannable.SPAN_PARAGRAPH) {
157            if (start != 0 && start != length()) {
158                char c = charAt(start - 1);
159
160                if (c != '\n')
161                    throw new RuntimeException(
162                            "PARAGRAPH span must start at paragraph boundary" +
163                            " (" + start + " follows " + c + ")");
164            }
165
166            if (end != 0 && end != length()) {
167                char c = charAt(end - 1);
168
169                if (c != '\n')
170                    throw new RuntimeException(
171                            "PARAGRAPH span must end at paragraph boundary" +
172                            " (" + end + " follows " + c + ")");
173            }
174        }
175
176        int count = mSpanCount;
177        Object[] spans = mSpans;
178        int[] data = mSpanData;
179
180        for (int i = 0; i < count; i++) {
181            if (spans[i] == what) {
182                int ostart = data[i * COLUMNS + START];
183                int oend = data[i * COLUMNS + END];
184
185                data[i * COLUMNS + START] = start;
186                data[i * COLUMNS + END] = end;
187                data[i * COLUMNS + FLAGS] = flags;
188
189                sendSpanChanged(what, ostart, oend, nstart, nend);
190                return;
191            }
192        }
193
194        if (mSpanCount + 1 >= mSpans.length) {
195            Object[] newtags = ArrayUtils.newUnpaddedObjectArray(
196                    GrowingArrayUtils.growSize(mSpanCount));
197            int[] newdata = new int[newtags.length * 3];
198
199            System.arraycopy(mSpans, 0, newtags, 0, mSpanCount);
200            System.arraycopy(mSpanData, 0, newdata, 0, mSpanCount * 3);
201
202            mSpans = newtags;
203            mSpanData = newdata;
204        }
205
206        mSpans[mSpanCount] = what;
207        mSpanData[mSpanCount * COLUMNS + START] = start;
208        mSpanData[mSpanCount * COLUMNS + END] = end;
209        mSpanData[mSpanCount * COLUMNS + FLAGS] = flags;
210        mSpanCount++;
211
212        if (this instanceof Spannable)
213            sendSpanAdded(what, nstart, nend);
214    }
215
216    /* package */ void removeSpan(Object what) {
217        int count = mSpanCount;
218        Object[] spans = mSpans;
219        int[] data = mSpanData;
220
221        for (int i = count - 1; i >= 0; i--) {
222            if (spans[i] == what) {
223                int ostart = data[i * COLUMNS + START];
224                int oend = data[i * COLUMNS + END];
225
226                int c = count - (i + 1);
227
228                System.arraycopy(spans, i + 1, spans, i, c);
229                System.arraycopy(data, (i + 1) * COLUMNS,
230                                 data, i * COLUMNS, c * COLUMNS);
231
232                mSpanCount--;
233
234                sendSpanRemoved(what, ostart, oend);
235                return;
236            }
237        }
238    }
239
240    public int getSpanStart(Object what) {
241        int count = mSpanCount;
242        Object[] spans = mSpans;
243        int[] data = mSpanData;
244
245        for (int i = count - 1; i >= 0; i--) {
246            if (spans[i] == what) {
247                return data[i * COLUMNS + START];
248            }
249        }
250
251        return -1;
252    }
253
254    public int getSpanEnd(Object what) {
255        int count = mSpanCount;
256        Object[] spans = mSpans;
257        int[] data = mSpanData;
258
259        for (int i = count - 1; i >= 0; i--) {
260            if (spans[i] == what) {
261                return data[i * COLUMNS + END];
262            }
263        }
264
265        return -1;
266    }
267
268    public int getSpanFlags(Object what) {
269        int count = mSpanCount;
270        Object[] spans = mSpans;
271        int[] data = mSpanData;
272
273        for (int i = count - 1; i >= 0; i--) {
274            if (spans[i] == what) {
275                return data[i * COLUMNS + FLAGS];
276            }
277        }
278
279        return 0;
280    }
281
282    public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
283        int count = 0;
284
285        int spanCount = mSpanCount;
286        Object[] spans = mSpans;
287        int[] data = mSpanData;
288        Object[] ret = null;
289        Object ret1 = null;
290
291        for (int i = 0; i < spanCount; i++) {
292            int spanStart = data[i * COLUMNS + START];
293            int spanEnd = data[i * COLUMNS + END];
294
295            if (spanStart > queryEnd) {
296                continue;
297            }
298            if (spanEnd < queryStart) {
299                continue;
300            }
301
302            if (spanStart != spanEnd && queryStart != queryEnd) {
303                if (spanStart == queryEnd) {
304                    continue;
305                }
306                if (spanEnd == queryStart) {
307                    continue;
308                }
309            }
310
311            // verify span class as late as possible, since it is expensive
312            if (kind != null && kind != Object.class && !kind.isInstance(spans[i])) {
313                continue;
314            }
315
316            if (count == 0) {
317                ret1 = spans[i];
318                count++;
319            } else {
320                if (count == 1) {
321                    ret = (Object[]) Array.newInstance(kind, spanCount - i + 1);
322                    ret[0] = ret1;
323                }
324
325                int prio = data[i * COLUMNS + FLAGS] & Spanned.SPAN_PRIORITY;
326                if (prio != 0) {
327                    int j;
328
329                    for (j = 0; j < count; j++) {
330                        int p = getSpanFlags(ret[j]) & Spanned.SPAN_PRIORITY;
331
332                        if (prio > p) {
333                            break;
334                        }
335                    }
336
337                    System.arraycopy(ret, j, ret, j + 1, count - j);
338                    ret[j] = spans[i];
339                    count++;
340                } else {
341                    ret[count++] = spans[i];
342                }
343            }
344        }
345
346        if (count == 0) {
347            return (T[]) ArrayUtils.emptyArray(kind);
348        }
349        if (count == 1) {
350            ret = (Object[]) Array.newInstance(kind, 1);
351            ret[0] = ret1;
352            return (T[]) ret;
353        }
354        if (count == ret.length) {
355            return (T[]) ret;
356        }
357
358        Object[] nret = (Object[]) Array.newInstance(kind, count);
359        System.arraycopy(ret, 0, nret, 0, count);
360        return (T[]) nret;
361    }
362
363    public int nextSpanTransition(int start, int limit, Class kind) {
364        int count = mSpanCount;
365        Object[] spans = mSpans;
366        int[] data = mSpanData;
367
368        if (kind == null) {
369            kind = Object.class;
370        }
371
372        for (int i = 0; i < count; i++) {
373            int st = data[i * COLUMNS + START];
374            int en = data[i * COLUMNS + END];
375
376            if (st > start && st < limit && kind.isInstance(spans[i]))
377                limit = st;
378            if (en > start && en < limit && kind.isInstance(spans[i]))
379                limit = en;
380        }
381
382        return limit;
383    }
384
385    private void sendSpanAdded(Object what, int start, int end) {
386        SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
387        int n = recip.length;
388
389        for (int i = 0; i < n; i++) {
390            recip[i].onSpanAdded((Spannable) this, what, start, end);
391        }
392    }
393
394    private void sendSpanRemoved(Object what, int start, int end) {
395        SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
396        int n = recip.length;
397
398        for (int i = 0; i < n; i++) {
399            recip[i].onSpanRemoved((Spannable) this, what, start, end);
400        }
401    }
402
403    private void sendSpanChanged(Object what, int s, int e, int st, int en) {
404        SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en),
405                                       SpanWatcher.class);
406        int n = recip.length;
407
408        for (int i = 0; i < n; i++) {
409            recip[i].onSpanChanged((Spannable) this, what, s, e, st, en);
410        }
411    }
412
413    private static String region(int start, int end) {
414        return "(" + start + " ... " + end + ")";
415    }
416
417    private void checkRange(final String operation, int start, int end) {
418        if (end < start) {
419            throw new IndexOutOfBoundsException(operation + " " +
420                                                region(start, end) +
421                                                " has end before start");
422        }
423
424        int len = length();
425
426        if (start > len || end > len) {
427            throw new IndexOutOfBoundsException(operation + " " +
428                                                region(start, end) +
429                                                " ends beyond length " + len);
430        }
431
432        if (start < 0 || end < 0) {
433            throw new IndexOutOfBoundsException(operation + " " +
434                                                region(start, end) +
435                                                " starts before 0");
436        }
437    }
438
439    // Same as SpannableStringBuilder
440    @Override
441    public boolean equals(Object o) {
442        if (o instanceof Spanned &&
443                toString().equals(o.toString())) {
444            Spanned other = (Spanned) o;
445            // Check span data
446            Object[] otherSpans = other.getSpans(0, other.length(), Object.class);
447            if (mSpanCount == otherSpans.length) {
448                for (int i = 0; i < mSpanCount; ++i) {
449                    Object thisSpan = mSpans[i];
450                    Object otherSpan = otherSpans[i];
451                    if (thisSpan == this) {
452                        if (other != otherSpan ||
453                                getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
454                                getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
455                                getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
456                            return false;
457                        }
458                    } else if (!thisSpan.equals(otherSpan) ||
459                            getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
460                            getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
461                            getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
462                        return false;
463                    }
464                }
465                return true;
466            }
467        }
468        return false;
469    }
470
471    // Same as SpannableStringBuilder
472    @Override
473    public int hashCode() {
474        int hash = toString().hashCode();
475        hash = hash * 31 + mSpanCount;
476        for (int i = 0; i < mSpanCount; ++i) {
477            Object span = mSpans[i];
478            if (span != this) {
479                hash = hash * 31 + span.hashCode();
480            }
481            hash = hash * 31 + getSpanStart(span);
482            hash = hash * 31 + getSpanEnd(span);
483            hash = hash * 31 + getSpanFlags(span);
484        }
485        return hash;
486    }
487
488    private String mText;
489    private Object[] mSpans;
490    private int[] mSpanData;
491    private int mSpanCount;
492
493    /* package */ static final Object[] EMPTY = new Object[0];
494
495    private static final int START = 0;
496    private static final int END = 1;
497    private static final int FLAGS = 2;
498    private static final int COLUMNS = 3;
499}
500