blob: 5c5deb45c3912335072d46b4a080668dbc35aaee [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.text;
18
19import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050020import com.android.internal.util.GrowingArrayUtils;
21
22import libcore.util.EmptyArray;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080023
24import java.lang.reflect.Array;
25
26/* package */ abstract class SpannableStringInternal
27{
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
Adam Lesinski776abc22014-03-07 11:30:59 -050035 mSpans = EmptyArray.OBJECT;
36 mSpanData = EmptyArray.INT;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037
38 if (source instanceof Spanned) {
39 Spanned sp = (Spanned) source;
40 Object[] spans = sp.getSpans(start, end, Object.class);
41
42 for (int i = 0; i < spans.length; i++) {
43 int st = sp.getSpanStart(spans[i]);
44 int en = sp.getSpanEnd(spans[i]);
45 int fl = sp.getSpanFlags(spans[i]);
46
47 if (st < start)
48 st = start;
49 if (en > end)
50 en = end;
51
52 setSpan(spans[i], st - start, en - start, fl);
53 }
54 }
55 }
56
57 public final int length() {
58 return mText.length();
59 }
60
61 public final char charAt(int i) {
62 return mText.charAt(i);
63 }
64
65 public final String toString() {
66 return mText;
67 }
68
69 /* subclasses must do subSequence() to preserve type */
70
71 public final void getChars(int start, int end, char[] dest, int off) {
72 mText.getChars(start, end, dest, off);
73 }
74
75 /* package */ void setSpan(Object what, int start, int end, int flags) {
76 int nstart = start;
77 int nend = end;
78
79 checkRange("setSpan", start, end);
80
81 if ((flags & Spannable.SPAN_PARAGRAPH) == Spannable.SPAN_PARAGRAPH) {
82 if (start != 0 && start != length()) {
83 char c = charAt(start - 1);
84
85 if (c != '\n')
86 throw new RuntimeException(
87 "PARAGRAPH span must start at paragraph boundary" +
88 " (" + start + " follows " + c + ")");
89 }
90
91 if (end != 0 && end != length()) {
92 char c = charAt(end - 1);
93
94 if (c != '\n')
95 throw new RuntimeException(
96 "PARAGRAPH span must end at paragraph boundary" +
97 " (" + end + " follows " + c + ")");
98 }
99 }
100
101 int count = mSpanCount;
102 Object[] spans = mSpans;
103 int[] data = mSpanData;
104
105 for (int i = 0; i < count; i++) {
106 if (spans[i] == what) {
107 int ostart = data[i * COLUMNS + START];
108 int oend = data[i * COLUMNS + END];
109
110 data[i * COLUMNS + START] = start;
111 data[i * COLUMNS + END] = end;
112 data[i * COLUMNS + FLAGS] = flags;
113
114 sendSpanChanged(what, ostart, oend, nstart, nend);
115 return;
116 }
117 }
118
119 if (mSpanCount + 1 >= mSpans.length) {
Adam Lesinski776abc22014-03-07 11:30:59 -0500120 Object[] newtags = ArrayUtils.newUnpaddedObjectArray(
121 GrowingArrayUtils.growSize(mSpanCount));
122 int[] newdata = new int[newtags.length * 3];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800123
124 System.arraycopy(mSpans, 0, newtags, 0, mSpanCount);
125 System.arraycopy(mSpanData, 0, newdata, 0, mSpanCount * 3);
126
127 mSpans = newtags;
128 mSpanData = newdata;
129 }
130
131 mSpans[mSpanCount] = what;
132 mSpanData[mSpanCount * COLUMNS + START] = start;
133 mSpanData[mSpanCount * COLUMNS + END] = end;
134 mSpanData[mSpanCount * COLUMNS + FLAGS] = flags;
135 mSpanCount++;
136
137 if (this instanceof Spannable)
138 sendSpanAdded(what, nstart, nend);
139 }
140
141 /* package */ void removeSpan(Object what) {
142 int count = mSpanCount;
143 Object[] spans = mSpans;
144 int[] data = mSpanData;
145
146 for (int i = count - 1; i >= 0; i--) {
147 if (spans[i] == what) {
148 int ostart = data[i * COLUMNS + START];
149 int oend = data[i * COLUMNS + END];
150
151 int c = count - (i + 1);
152
153 System.arraycopy(spans, i + 1, spans, i, c);
154 System.arraycopy(data, (i + 1) * COLUMNS,
155 data, i * COLUMNS, c * COLUMNS);
156
157 mSpanCount--;
158
159 sendSpanRemoved(what, ostart, oend);
160 return;
161 }
162 }
163 }
164
165 public int getSpanStart(Object what) {
166 int count = mSpanCount;
167 Object[] spans = mSpans;
168 int[] data = mSpanData;
169
170 for (int i = count - 1; i >= 0; i--) {
171 if (spans[i] == what) {
172 return data[i * COLUMNS + START];
173 }
174 }
175
176 return -1;
177 }
178
179 public int getSpanEnd(Object what) {
180 int count = mSpanCount;
181 Object[] spans = mSpans;
182 int[] data = mSpanData;
183
184 for (int i = count - 1; i >= 0; i--) {
185 if (spans[i] == what) {
186 return data[i * COLUMNS + END];
187 }
188 }
189
190 return -1;
191 }
192
193 public int getSpanFlags(Object what) {
194 int count = mSpanCount;
195 Object[] spans = mSpans;
196 int[] data = mSpanData;
197
198 for (int i = count - 1; i >= 0; i--) {
199 if (spans[i] == what) {
200 return data[i * COLUMNS + FLAGS];
201 }
202 }
203
204 return 0;
205 }
206
207 public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
208 int count = 0;
209
210 int spanCount = mSpanCount;
211 Object[] spans = mSpans;
212 int[] data = mSpanData;
213 Object[] ret = null;
214 Object ret1 = null;
215
216 for (int i = 0; i < spanCount; i++) {
217 int spanStart = data[i * COLUMNS + START];
218 int spanEnd = data[i * COLUMNS + END];
219
220 if (spanStart > queryEnd) {
221 continue;
222 }
223 if (spanEnd < queryStart) {
224 continue;
225 }
226
227 if (spanStart != spanEnd && queryStart != queryEnd) {
228 if (spanStart == queryEnd) {
229 continue;
230 }
231 if (spanEnd == queryStart) {
232 continue;
233 }
234 }
235
Chris Craikfc121c22015-07-10 13:12:34 -0700236 // verify span class as late as possible, since it is expensive
237 if (kind != null && !kind.isInstance(spans[i])) {
238 continue;
239 }
240
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800241 if (count == 0) {
242 ret1 = spans[i];
243 count++;
244 } else {
245 if (count == 1) {
246 ret = (Object[]) Array.newInstance(kind, spanCount - i + 1);
247 ret[0] = ret1;
248 }
249
250 int prio = data[i * COLUMNS + FLAGS] & Spanned.SPAN_PRIORITY;
251 if (prio != 0) {
252 int j;
253
254 for (j = 0; j < count; j++) {
255 int p = getSpanFlags(ret[j]) & Spanned.SPAN_PRIORITY;
256
257 if (prio > p) {
258 break;
259 }
260 }
261
262 System.arraycopy(ret, j, ret, j + 1, count - j);
263 ret[j] = spans[i];
264 count++;
265 } else {
266 ret[count++] = spans[i];
267 }
268 }
269 }
270
271 if (count == 0) {
272 return (T[]) ArrayUtils.emptyArray(kind);
273 }
274 if (count == 1) {
275 ret = (Object[]) Array.newInstance(kind, 1);
276 ret[0] = ret1;
277 return (T[]) ret;
278 }
279 if (count == ret.length) {
280 return (T[]) ret;
281 }
282
283 Object[] nret = (Object[]) Array.newInstance(kind, count);
284 System.arraycopy(ret, 0, nret, 0, count);
285 return (T[]) nret;
286 }
287
288 public int nextSpanTransition(int start, int limit, Class kind) {
289 int count = mSpanCount;
290 Object[] spans = mSpans;
291 int[] data = mSpanData;
292
293 if (kind == null) {
294 kind = Object.class;
295 }
296
297 for (int i = 0; i < count; i++) {
298 int st = data[i * COLUMNS + START];
299 int en = data[i * COLUMNS + END];
300
301 if (st > start && st < limit && kind.isInstance(spans[i]))
302 limit = st;
303 if (en > start && en < limit && kind.isInstance(spans[i]))
304 limit = en;
305 }
306
307 return limit;
308 }
309
310 private void sendSpanAdded(Object what, int start, int end) {
311 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
312 int n = recip.length;
313
314 for (int i = 0; i < n; i++) {
315 recip[i].onSpanAdded((Spannable) this, what, start, end);
316 }
317 }
318
319 private void sendSpanRemoved(Object what, int start, int end) {
320 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
321 int n = recip.length;
322
323 for (int i = 0; i < n; i++) {
324 recip[i].onSpanRemoved((Spannable) this, what, start, end);
325 }
326 }
327
328 private void sendSpanChanged(Object what, int s, int e, int st, int en) {
329 SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en),
330 SpanWatcher.class);
331 int n = recip.length;
332
333 for (int i = 0; i < n; i++) {
334 recip[i].onSpanChanged((Spannable) this, what, s, e, st, en);
335 }
336 }
337
338 private static String region(int start, int end) {
339 return "(" + start + " ... " + end + ")";
340 }
341
342 private void checkRange(final String operation, int start, int end) {
343 if (end < start) {
344 throw new IndexOutOfBoundsException(operation + " " +
345 region(start, end) +
346 " has end before start");
347 }
348
349 int len = length();
350
351 if (start > len || end > len) {
352 throw new IndexOutOfBoundsException(operation + " " +
353 region(start, end) +
354 " ends beyond length " + len);
355 }
356
357 if (start < 0 || end < 0) {
358 throw new IndexOutOfBoundsException(operation + " " +
359 region(start, end) +
360 " starts before 0");
361 }
362 }
363
Chet Haase9b985722013-09-18 15:12:35 -0700364 // Same as SpannableStringBuilder
365 @Override
366 public boolean equals(Object o) {
367 if (o instanceof Spanned &&
368 toString().equals(o.toString())) {
Chet Haase1d3c4b32013-10-03 17:21:14 -0700369 Spanned other = (Spanned) o;
Chet Haase9b985722013-09-18 15:12:35 -0700370 // Check span data
Chet Haase1d3c4b32013-10-03 17:21:14 -0700371 Object[] otherSpans = other.getSpans(0, other.length(), Object.class);
Chet Haase9b985722013-09-18 15:12:35 -0700372 if (mSpanCount == otherSpans.length) {
373 for (int i = 0; i < mSpanCount; ++i) {
374 Object thisSpan = mSpans[i];
375 Object otherSpan = otherSpans[i];
Chet Haase1d3c4b32013-10-03 17:21:14 -0700376 if (thisSpan == this) {
377 if (other != otherSpan ||
378 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
379 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
380 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
381 return false;
382 }
383 } else if (!thisSpan.equals(otherSpan) ||
384 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
385 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
386 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
Chet Haase9b985722013-09-18 15:12:35 -0700387 return false;
388 }
389 }
390 return true;
391 }
Chet Haase9b985722013-09-18 15:12:35 -0700392 }
393 return false;
394 }
395
396 // Same as SpannableStringBuilder
397 @Override
398 public int hashCode() {
399 int hash = toString().hashCode();
400 hash = hash * 31 + mSpanCount;
401 for (int i = 0; i < mSpanCount; ++i) {
402 Object span = mSpans[i];
Chet Haase1d3c4b32013-10-03 17:21:14 -0700403 if (span != this) {
404 hash = hash * 31 + span.hashCode();
405 }
Chet Haase9b985722013-09-18 15:12:35 -0700406 hash = hash * 31 + getSpanStart(span);
407 hash = hash * 31 + getSpanEnd(span);
408 hash = hash * 31 + getSpanFlags(span);
409 }
410 return hash;
411 }
412
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800413 private String mText;
414 private Object[] mSpans;
415 private int[] mSpanData;
416 private int mSpanCount;
417
418 /* package */ static final Object[] EMPTY = new Object[0];
419
420 private static final int START = 0;
421 private static final int END = 1;
422 private static final int FLAGS = 2;
423 private static final int COLUMNS = 3;
424}