blob: d114d3241b4d5dacaf0641f66777706d70d2fb74 [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++) {
Fabrice Di Meglioa2a035e2011-02-15 14:31:26 -0800217 if (kind != null && !kind.isInstance(spans[i])) {
218 continue;
219 }
220
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800221 int spanStart = data[i * COLUMNS + START];
222 int spanEnd = data[i * COLUMNS + END];
223
224 if (spanStart > queryEnd) {
225 continue;
226 }
227 if (spanEnd < queryStart) {
228 continue;
229 }
230
231 if (spanStart != spanEnd && queryStart != queryEnd) {
232 if (spanStart == queryEnd) {
233 continue;
234 }
235 if (spanEnd == queryStart) {
236 continue;
237 }
238 }
239
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800240 if (count == 0) {
241 ret1 = spans[i];
242 count++;
243 } else {
244 if (count == 1) {
245 ret = (Object[]) Array.newInstance(kind, spanCount - i + 1);
246 ret[0] = ret1;
247 }
248
249 int prio = data[i * COLUMNS + FLAGS] & Spanned.SPAN_PRIORITY;
250 if (prio != 0) {
251 int j;
252
253 for (j = 0; j < count; j++) {
254 int p = getSpanFlags(ret[j]) & Spanned.SPAN_PRIORITY;
255
256 if (prio > p) {
257 break;
258 }
259 }
260
261 System.arraycopy(ret, j, ret, j + 1, count - j);
262 ret[j] = spans[i];
263 count++;
264 } else {
265 ret[count++] = spans[i];
266 }
267 }
268 }
269
270 if (count == 0) {
271 return (T[]) ArrayUtils.emptyArray(kind);
272 }
273 if (count == 1) {
274 ret = (Object[]) Array.newInstance(kind, 1);
275 ret[0] = ret1;
276 return (T[]) ret;
277 }
278 if (count == ret.length) {
279 return (T[]) ret;
280 }
281
282 Object[] nret = (Object[]) Array.newInstance(kind, count);
283 System.arraycopy(ret, 0, nret, 0, count);
284 return (T[]) nret;
285 }
286
287 public int nextSpanTransition(int start, int limit, Class kind) {
288 int count = mSpanCount;
289 Object[] spans = mSpans;
290 int[] data = mSpanData;
291
292 if (kind == null) {
293 kind = Object.class;
294 }
295
296 for (int i = 0; i < count; i++) {
297 int st = data[i * COLUMNS + START];
298 int en = data[i * COLUMNS + END];
299
300 if (st > start && st < limit && kind.isInstance(spans[i]))
301 limit = st;
302 if (en > start && en < limit && kind.isInstance(spans[i]))
303 limit = en;
304 }
305
306 return limit;
307 }
308
309 private void sendSpanAdded(Object what, int start, int end) {
310 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
311 int n = recip.length;
312
313 for (int i = 0; i < n; i++) {
314 recip[i].onSpanAdded((Spannable) this, what, start, end);
315 }
316 }
317
318 private void sendSpanRemoved(Object what, int start, int end) {
319 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
320 int n = recip.length;
321
322 for (int i = 0; i < n; i++) {
323 recip[i].onSpanRemoved((Spannable) this, what, start, end);
324 }
325 }
326
327 private void sendSpanChanged(Object what, int s, int e, int st, int en) {
328 SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en),
329 SpanWatcher.class);
330 int n = recip.length;
331
332 for (int i = 0; i < n; i++) {
333 recip[i].onSpanChanged((Spannable) this, what, s, e, st, en);
334 }
335 }
336
337 private static String region(int start, int end) {
338 return "(" + start + " ... " + end + ")";
339 }
340
341 private void checkRange(final String operation, int start, int end) {
342 if (end < start) {
343 throw new IndexOutOfBoundsException(operation + " " +
344 region(start, end) +
345 " has end before start");
346 }
347
348 int len = length();
349
350 if (start > len || end > len) {
351 throw new IndexOutOfBoundsException(operation + " " +
352 region(start, end) +
353 " ends beyond length " + len);
354 }
355
356 if (start < 0 || end < 0) {
357 throw new IndexOutOfBoundsException(operation + " " +
358 region(start, end) +
359 " starts before 0");
360 }
361 }
362
Chet Haase9b985722013-09-18 15:12:35 -0700363 // Same as SpannableStringBuilder
364 @Override
365 public boolean equals(Object o) {
366 if (o instanceof Spanned &&
367 toString().equals(o.toString())) {
Chet Haase1d3c4b32013-10-03 17:21:14 -0700368 Spanned other = (Spanned) o;
Chet Haase9b985722013-09-18 15:12:35 -0700369 // Check span data
Chet Haase1d3c4b32013-10-03 17:21:14 -0700370 Object[] otherSpans = other.getSpans(0, other.length(), Object.class);
Chet Haase9b985722013-09-18 15:12:35 -0700371 if (mSpanCount == otherSpans.length) {
372 for (int i = 0; i < mSpanCount; ++i) {
373 Object thisSpan = mSpans[i];
374 Object otherSpan = otherSpans[i];
Chet Haase1d3c4b32013-10-03 17:21:14 -0700375 if (thisSpan == this) {
376 if (other != otherSpan ||
377 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
378 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
379 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
380 return false;
381 }
382 } else if (!thisSpan.equals(otherSpan) ||
383 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
384 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
385 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
Chet Haase9b985722013-09-18 15:12:35 -0700386 return false;
387 }
388 }
389 return true;
390 }
Chet Haase9b985722013-09-18 15:12:35 -0700391 }
392 return false;
393 }
394
395 // Same as SpannableStringBuilder
396 @Override
397 public int hashCode() {
398 int hash = toString().hashCode();
399 hash = hash * 31 + mSpanCount;
400 for (int i = 0; i < mSpanCount; ++i) {
401 Object span = mSpans[i];
Chet Haase1d3c4b32013-10-03 17:21:14 -0700402 if (span != this) {
403 hash = hash * 31 + span.hashCode();
404 }
Chet Haase9b985722013-09-18 15:12:35 -0700405 hash = hash * 31 + getSpanStart(span);
406 hash = hash * 31 + getSpanEnd(span);
407 hash = hash * 31 + getSpanFlags(span);
408 }
409 return hash;
410 }
411
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800412 private String mText;
413 private Object[] mSpans;
414 private int[] mSpanData;
415 private int mSpanCount;
416
417 /* package */ static final Object[] EMPTY = new Object[0];
418
419 private static final int START = 0;
420 private static final int END = 1;
421 private static final int FLAGS = 2;
422 private static final int COLUMNS = 3;
423}