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