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