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