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