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