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 
17 package android.text;
18 
19 import com.android.internal.util.ArrayUtils;
20 import com.android.internal.util.GrowingArrayUtils;
21 
22 import libcore.util.EmptyArray;
23 
24 import java.lang.reflect.Array;
25 
26 /* package */ abstract class SpannableStringInternal
27 {
SpannableStringInternal(CharSequence source, int start, int end)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      */
copySpans(Spanned src, int start, int end)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      */
copySpans(SpannableStringInternal src, int start, int end)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      */
isOutOfCopyRange(int start, int end, int spanStart, int spanEnd)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 
length()132     public final int length() {
133         return mText.length();
134     }
135 
charAt(int i)136     public final char charAt(int i) {
137         return mText.charAt(i);
138     }
139 
toString()140     public final String toString() {
141         return mText;
142     }
143 
144     /* subclasses must do subSequence() to preserve type */
145 
getChars(int start, int end, char[] dest, int off)146     public final void getChars(int start, int end, char[] dest, int off) {
147         mText.getChars(start, end, dest, off);
148     }
149 
setSpan(Object what, int start, int end, int flags)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 
removeSpan(Object what)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 
getSpanStart(Object what)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 
getSpanEnd(Object what)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 
getSpanFlags(Object what)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 
getSpans(int queryStart, int queryEnd, Class<T> kind)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 
nextSpanTransition(int start, int limit, Class kind)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 
sendSpanAdded(Object what, int start, int end)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 
sendSpanRemoved(Object what, int start, int end)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 
sendSpanChanged(Object what, int s, int e, int st, int en)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 
region(int start, int end)413     private static String region(int start, int end) {
414         return "(" + start + " ... " + end + ")";
415     }
416 
checkRange(final String operation, int start, int end)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
equals(Object o)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
hashCode()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