• 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)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);
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         int nstart = start;
153         int nend = end;
154 
155         checkRange("setSpan", start, end);
156 
157         if ((flags & Spannable.SPAN_PARAGRAPH) == Spannable.SPAN_PARAGRAPH) {
158             if (start != 0 && start != length()) {
159                 char c = charAt(start - 1);
160 
161                 if (c != '\n')
162                     throw new RuntimeException(
163                             "PARAGRAPH span must start at paragraph boundary" +
164                             " (" + start + " follows " + c + ")");
165             }
166 
167             if (end != 0 && end != length()) {
168                 char c = charAt(end - 1);
169 
170                 if (c != '\n')
171                     throw new RuntimeException(
172                             "PARAGRAPH span must end at paragraph boundary" +
173                             " (" + end + " follows " + c + ")");
174             }
175         }
176 
177         int count = mSpanCount;
178         Object[] spans = mSpans;
179         int[] data = mSpanData;
180 
181         for (int i = 0; i < count; i++) {
182             if (spans[i] == what) {
183                 int ostart = data[i * COLUMNS + START];
184                 int oend = data[i * COLUMNS + END];
185 
186                 data[i * COLUMNS + START] = start;
187                 data[i * COLUMNS + END] = end;
188                 data[i * COLUMNS + FLAGS] = flags;
189 
190                 sendSpanChanged(what, ostart, oend, nstart, nend);
191                 return;
192             }
193         }
194 
195         if (mSpanCount + 1 >= mSpans.length) {
196             Object[] newtags = ArrayUtils.newUnpaddedObjectArray(
197                     GrowingArrayUtils.growSize(mSpanCount));
198             int[] newdata = new int[newtags.length * 3];
199 
200             System.arraycopy(mSpans, 0, newtags, 0, mSpanCount);
201             System.arraycopy(mSpanData, 0, newdata, 0, mSpanCount * 3);
202 
203             mSpans = newtags;
204             mSpanData = newdata;
205         }
206 
207         mSpans[mSpanCount] = what;
208         mSpanData[mSpanCount * COLUMNS + START] = start;
209         mSpanData[mSpanCount * COLUMNS + END] = end;
210         mSpanData[mSpanCount * COLUMNS + FLAGS] = flags;
211         mSpanCount++;
212 
213         if (this instanceof Spannable)
214             sendSpanAdded(what, nstart, nend);
215     }
216 
removeSpan(Object what)217     /* package */ void removeSpan(Object what) {
218         int count = mSpanCount;
219         Object[] spans = mSpans;
220         int[] data = mSpanData;
221 
222         for (int i = count - 1; i >= 0; i--) {
223             if (spans[i] == what) {
224                 int ostart = data[i * COLUMNS + START];
225                 int oend = data[i * COLUMNS + END];
226 
227                 int c = count - (i + 1);
228 
229                 System.arraycopy(spans, i + 1, spans, i, c);
230                 System.arraycopy(data, (i + 1) * COLUMNS,
231                                  data, i * COLUMNS, c * COLUMNS);
232 
233                 mSpanCount--;
234 
235                 sendSpanRemoved(what, ostart, oend);
236                 return;
237             }
238         }
239     }
240 
getSpanStart(Object what)241     public int getSpanStart(Object what) {
242         int count = mSpanCount;
243         Object[] spans = mSpans;
244         int[] data = mSpanData;
245 
246         for (int i = count - 1; i >= 0; i--) {
247             if (spans[i] == what) {
248                 return data[i * COLUMNS + START];
249             }
250         }
251 
252         return -1;
253     }
254 
getSpanEnd(Object what)255     public int getSpanEnd(Object what) {
256         int count = mSpanCount;
257         Object[] spans = mSpans;
258         int[] data = mSpanData;
259 
260         for (int i = count - 1; i >= 0; i--) {
261             if (spans[i] == what) {
262                 return data[i * COLUMNS + END];
263             }
264         }
265 
266         return -1;
267     }
268 
getSpanFlags(Object what)269     public int getSpanFlags(Object what) {
270         int count = mSpanCount;
271         Object[] spans = mSpans;
272         int[] data = mSpanData;
273 
274         for (int i = count - 1; i >= 0; i--) {
275             if (spans[i] == what) {
276                 return data[i * COLUMNS + FLAGS];
277             }
278         }
279 
280         return 0;
281     }
282 
getSpans(int queryStart, int queryEnd, Class<T> kind)283     public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
284         int count = 0;
285 
286         int spanCount = mSpanCount;
287         Object[] spans = mSpans;
288         int[] data = mSpanData;
289         Object[] ret = null;
290         Object ret1 = null;
291 
292         for (int i = 0; i < spanCount; i++) {
293             int spanStart = data[i * COLUMNS + START];
294             int spanEnd = data[i * COLUMNS + END];
295 
296             if (spanStart > queryEnd) {
297                 continue;
298             }
299             if (spanEnd < queryStart) {
300                 continue;
301             }
302 
303             if (spanStart != spanEnd && queryStart != queryEnd) {
304                 if (spanStart == queryEnd) {
305                     continue;
306                 }
307                 if (spanEnd == queryStart) {
308                     continue;
309                 }
310             }
311 
312             // verify span class as late as possible, since it is expensive
313             if (kind != null && kind != Object.class && !kind.isInstance(spans[i])) {
314                 continue;
315             }
316 
317             if (count == 0) {
318                 ret1 = spans[i];
319                 count++;
320             } else {
321                 if (count == 1) {
322                     ret = (Object[]) Array.newInstance(kind, spanCount - i + 1);
323                     ret[0] = ret1;
324                 }
325 
326                 int prio = data[i * COLUMNS + FLAGS] & Spanned.SPAN_PRIORITY;
327                 if (prio != 0) {
328                     int j;
329 
330                     for (j = 0; j < count; j++) {
331                         int p = getSpanFlags(ret[j]) & Spanned.SPAN_PRIORITY;
332 
333                         if (prio > p) {
334                             break;
335                         }
336                     }
337 
338                     System.arraycopy(ret, j, ret, j + 1, count - j);
339                     ret[j] = spans[i];
340                     count++;
341                 } else {
342                     ret[count++] = spans[i];
343                 }
344             }
345         }
346 
347         if (count == 0) {
348             return (T[]) ArrayUtils.emptyArray(kind);
349         }
350         if (count == 1) {
351             ret = (Object[]) Array.newInstance(kind, 1);
352             ret[0] = ret1;
353             return (T[]) ret;
354         }
355         if (count == ret.length) {
356             return (T[]) ret;
357         }
358 
359         Object[] nret = (Object[]) Array.newInstance(kind, count);
360         System.arraycopy(ret, 0, nret, 0, count);
361         return (T[]) nret;
362     }
363 
nextSpanTransition(int start, int limit, Class kind)364     public int nextSpanTransition(int start, int limit, Class kind) {
365         int count = mSpanCount;
366         Object[] spans = mSpans;
367         int[] data = mSpanData;
368 
369         if (kind == null) {
370             kind = Object.class;
371         }
372 
373         for (int i = 0; i < count; i++) {
374             int st = data[i * COLUMNS + START];
375             int en = data[i * COLUMNS + END];
376 
377             if (st > start && st < limit && kind.isInstance(spans[i]))
378                 limit = st;
379             if (en > start && en < limit && kind.isInstance(spans[i]))
380                 limit = en;
381         }
382 
383         return limit;
384     }
385 
sendSpanAdded(Object what, int start, int end)386     private void sendSpanAdded(Object what, int start, int end) {
387         SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
388         int n = recip.length;
389 
390         for (int i = 0; i < n; i++) {
391             recip[i].onSpanAdded((Spannable) this, what, start, end);
392         }
393     }
394 
sendSpanRemoved(Object what, int start, int end)395     private void sendSpanRemoved(Object what, int start, int end) {
396         SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
397         int n = recip.length;
398 
399         for (int i = 0; i < n; i++) {
400             recip[i].onSpanRemoved((Spannable) this, what, start, end);
401         }
402     }
403 
sendSpanChanged(Object what, int s, int e, int st, int en)404     private void sendSpanChanged(Object what, int s, int e, int st, int en) {
405         SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en),
406                                        SpanWatcher.class);
407         int n = recip.length;
408 
409         for (int i = 0; i < n; i++) {
410             recip[i].onSpanChanged((Spannable) this, what, s, e, st, en);
411         }
412     }
413 
region(int start, int end)414     private static String region(int start, int end) {
415         return "(" + start + " ... " + end + ")";
416     }
417 
checkRange(final String operation, int start, int end)418     private void checkRange(final String operation, int start, int end) {
419         if (end < start) {
420             throw new IndexOutOfBoundsException(operation + " " +
421                                                 region(start, end) +
422                                                 " has end before start");
423         }
424 
425         int len = length();
426 
427         if (start > len || end > len) {
428             throw new IndexOutOfBoundsException(operation + " " +
429                                                 region(start, end) +
430                                                 " ends beyond length " + len);
431         }
432 
433         if (start < 0 || end < 0) {
434             throw new IndexOutOfBoundsException(operation + " " +
435                                                 region(start, end) +
436                                                 " starts before 0");
437         }
438     }
439 
440     // Same as SpannableStringBuilder
441     @Override
equals(Object o)442     public boolean equals(Object o) {
443         if (o instanceof Spanned &&
444                 toString().equals(o.toString())) {
445             Spanned other = (Spanned) o;
446             // Check span data
447             Object[] otherSpans = other.getSpans(0, other.length(), Object.class);
448             if (mSpanCount == otherSpans.length) {
449                 for (int i = 0; i < mSpanCount; ++i) {
450                     Object thisSpan = mSpans[i];
451                     Object otherSpan = otherSpans[i];
452                     if (thisSpan == this) {
453                         if (other != otherSpan ||
454                                 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
455                                 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
456                                 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
457                             return false;
458                         }
459                     } else if (!thisSpan.equals(otherSpan) ||
460                             getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
461                             getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
462                             getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
463                         return false;
464                     }
465                 }
466                 return true;
467             }
468         }
469         return false;
470     }
471 
472     // Same as SpannableStringBuilder
473     @Override
hashCode()474     public int hashCode() {
475         int hash = toString().hashCode();
476         hash = hash * 31 + mSpanCount;
477         for (int i = 0; i < mSpanCount; ++i) {
478             Object span = mSpans[i];
479             if (span != this) {
480                 hash = hash * 31 + span.hashCode();
481             }
482             hash = hash * 31 + getSpanStart(span);
483             hash = hash * 31 + getSpanEnd(span);
484             hash = hash * 31 + getSpanFlags(span);
485         }
486         return hash;
487     }
488 
489     private String mText;
490     private Object[] mSpans;
491     private int[] mSpanData;
492     private int mSpanCount;
493 
494     /* package */ static final Object[] EMPTY = new Object[0];
495 
496     private static final int START = 0;
497     private static final int END = 1;
498     private static final int FLAGS = 2;
499     private static final int COLUMNS = 3;
500 }
501