• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, boolean ignoreNoCopySpan)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      */
SpannableStringInternal(CharSequence source, int start, int end)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      */
copySpans(Spanned src, int start, int end, boolean ignoreNoCopySpan)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      */
copySpans(SpannableStringInternal src, int start, int end, boolean ignoreNoCopySpan)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      */
isOutOfCopyRange(int start, int end, int spanStart, int spanEnd)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 
length()159     public final int length() {
160         return mText.length();
161     }
162 
charAt(int i)163     public final char charAt(int i) {
164         return mText.charAt(i);
165     }
166 
toString()167     public final String toString() {
168         return mText;
169     }
170 
171     /* subclasses must do subSequence() to preserve type */
172 
getChars(int start, int end, char[] dest, int off)173     public final void getChars(int start, int end, char[] dest, int off) {
174         mText.getChars(start, end, dest, off);
175     }
176 
setSpan(Object what, int start, int end, int flags)177     /* package */ void setSpan(Object what, int start, int end, int flags) {
178         setSpan(what, start, end, flags, true/*enforceParagraph*/);
179     }
180 
isIndexFollowsNextLine(int index)181     private boolean isIndexFollowsNextLine(int index) {
182         return index != 0 && index != length() && charAt(index - 1) != '\n';
183     }
184 
setSpan(Object what, int start, int end, int flags, boolean enforceParagraph)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 
removeSpan(Object what)251     /* package */ void removeSpan(Object what) {
252         removeSpan(what, 0 /* flags */);
253     }
254 
255     /**
256      * @hide
257      */
removeSpan(Object what, int flags)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 
getSpanStart(Object what)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 
getSpanEnd(Object what)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 
getSpanFlags(Object what)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 
getSpans(int queryStart, int queryEnd, Class<T> kind)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 
nextSpanTransition(int start, int limit, Class kind)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 
sendSpanAdded(Object what, int start, int end)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 
sendSpanRemoved(Object what, int start, int end)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 
sendSpanChanged(Object what, int s, int e, int st, int en)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 
region(int start, int end)457     private static String region(int start, int end) {
458         return "(" + start + " ... " + end + ")";
459     }
460 
checkRange(final String operation, int start, int end)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
equals(Object o)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
hashCode()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      */
copySpans(Spanned src, int start, int end)537     private void copySpans(Spanned src, int start, int end) {
538         copySpans(src, start, end, false);
539     }
540 
copySpans(SpannableStringInternal src, int start, int end)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