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