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.annotation.Nullable; 20 import android.graphics.BaseCanvas; 21 import android.graphics.Paint; 22 import android.util.Log; 23 24 import com.android.internal.annotations.GuardedBy; 25 import com.android.internal.util.ArrayUtils; 26 import com.android.internal.util.GrowingArrayUtils; 27 28 import libcore.util.EmptyArray; 29 30 import java.lang.reflect.Array; 31 import java.util.IdentityHashMap; 32 33 /** 34 * This is the class for text whose content and markup can both be changed. 35 */ 36 public class SpannableStringBuilder implements CharSequence, GetChars, Spannable, Editable, 37 Appendable, GraphicsOperations { 38 private final static String TAG = "SpannableStringBuilder"; 39 /** 40 * Create a new SpannableStringBuilder with empty contents 41 */ SpannableStringBuilder()42 public SpannableStringBuilder() { 43 this(""); 44 } 45 46 /** 47 * Create a new SpannableStringBuilder containing a copy of the 48 * specified text, including its spans if any. 49 */ SpannableStringBuilder(CharSequence text)50 public SpannableStringBuilder(CharSequence text) { 51 this(text, 0, text.length()); 52 } 53 54 /** 55 * Create a new SpannableStringBuilder containing a copy of the 56 * specified slice of the specified text, including its spans if any. 57 */ SpannableStringBuilder(CharSequence text, int start, int end)58 public SpannableStringBuilder(CharSequence text, int start, int end) { 59 int srclen = end - start; 60 61 if (srclen < 0) throw new StringIndexOutOfBoundsException(); 62 63 mText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(srclen)); 64 mGapStart = srclen; 65 mGapLength = mText.length - srclen; 66 67 TextUtils.getChars(text, start, end, mText, 0); 68 69 mSpanCount = 0; 70 mSpanInsertCount = 0; 71 mSpans = EmptyArray.OBJECT; 72 mSpanStarts = EmptyArray.INT; 73 mSpanEnds = EmptyArray.INT; 74 mSpanFlags = EmptyArray.INT; 75 mSpanMax = EmptyArray.INT; 76 mSpanOrder = EmptyArray.INT; 77 78 if (text instanceof Spanned) { 79 Spanned sp = (Spanned) text; 80 Object[] spans = sp.getSpans(start, end, Object.class); 81 82 for (int i = 0; i < spans.length; i++) { 83 if (spans[i] instanceof NoCopySpan) { 84 continue; 85 } 86 87 int st = sp.getSpanStart(spans[i]) - start; 88 int en = sp.getSpanEnd(spans[i]) - start; 89 int fl = sp.getSpanFlags(spans[i]); 90 91 if (st < 0) 92 st = 0; 93 if (st > end - start) 94 st = end - start; 95 96 if (en < 0) 97 en = 0; 98 if (en > end - start) 99 en = end - start; 100 101 setSpan(false, spans[i], st, en, fl, false/*enforceParagraph*/); 102 } 103 restoreInvariants(); 104 } 105 } 106 valueOf(CharSequence source)107 public static SpannableStringBuilder valueOf(CharSequence source) { 108 if (source instanceof SpannableStringBuilder) { 109 return (SpannableStringBuilder) source; 110 } else { 111 return new SpannableStringBuilder(source); 112 } 113 } 114 115 /** 116 * Return the char at the specified offset within the buffer. 117 */ charAt(int where)118 public char charAt(int where) { 119 int len = length(); 120 if (where < 0) { 121 throw new IndexOutOfBoundsException("charAt: " + where + " < 0"); 122 } else if (where >= len) { 123 throw new IndexOutOfBoundsException("charAt: " + where + " >= length " + len); 124 } 125 126 if (where >= mGapStart) 127 return mText[where + mGapLength]; 128 else 129 return mText[where]; 130 } 131 132 /** 133 * Return the number of chars in the buffer. 134 */ length()135 public int length() { 136 return mText.length - mGapLength; 137 } 138 resizeFor(int size)139 private void resizeFor(int size) { 140 final int oldLength = mText.length; 141 if (size + 1 <= oldLength) { 142 return; 143 } 144 145 char[] newText = ArrayUtils.newUnpaddedCharArray(GrowingArrayUtils.growSize(size)); 146 System.arraycopy(mText, 0, newText, 0, mGapStart); 147 final int newLength = newText.length; 148 final int delta = newLength - oldLength; 149 final int after = oldLength - (mGapStart + mGapLength); 150 System.arraycopy(mText, oldLength - after, newText, newLength - after, after); 151 mText = newText; 152 153 mGapLength += delta; 154 if (mGapLength < 1) 155 new Exception("mGapLength < 1").printStackTrace(); 156 157 if (mSpanCount != 0) { 158 for (int i = 0; i < mSpanCount; i++) { 159 if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += delta; 160 if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += delta; 161 } 162 calcMax(treeRoot()); 163 } 164 } 165 moveGapTo(int where)166 private void moveGapTo(int where) { 167 if (where == mGapStart) 168 return; 169 170 boolean atEnd = (where == length()); 171 172 if (where < mGapStart) { 173 int overlap = mGapStart - where; 174 System.arraycopy(mText, where, mText, mGapStart + mGapLength - overlap, overlap); 175 } else /* where > mGapStart */ { 176 int overlap = where - mGapStart; 177 System.arraycopy(mText, where + mGapLength - overlap, mText, mGapStart, overlap); 178 } 179 180 // TODO: be more clever (although the win really isn't that big) 181 if (mSpanCount != 0) { 182 for (int i = 0; i < mSpanCount; i++) { 183 int start = mSpanStarts[i]; 184 int end = mSpanEnds[i]; 185 186 if (start > mGapStart) 187 start -= mGapLength; 188 if (start > where) 189 start += mGapLength; 190 else if (start == where) { 191 int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT; 192 193 if (flag == POINT || (atEnd && flag == PARAGRAPH)) 194 start += mGapLength; 195 } 196 197 if (end > mGapStart) 198 end -= mGapLength; 199 if (end > where) 200 end += mGapLength; 201 else if (end == where) { 202 int flag = (mSpanFlags[i] & END_MASK); 203 204 if (flag == POINT || (atEnd && flag == PARAGRAPH)) 205 end += mGapLength; 206 } 207 208 mSpanStarts[i] = start; 209 mSpanEnds[i] = end; 210 } 211 calcMax(treeRoot()); 212 } 213 214 mGapStart = where; 215 } 216 217 // Documentation from interface insert(int where, CharSequence tb, int start, int end)218 public SpannableStringBuilder insert(int where, CharSequence tb, int start, int end) { 219 return replace(where, where, tb, start, end); 220 } 221 222 // Documentation from interface insert(int where, CharSequence tb)223 public SpannableStringBuilder insert(int where, CharSequence tb) { 224 return replace(where, where, tb, 0, tb.length()); 225 } 226 227 // Documentation from interface delete(int start, int end)228 public SpannableStringBuilder delete(int start, int end) { 229 SpannableStringBuilder ret = replace(start, end, "", 0, 0); 230 231 if (mGapLength > 2 * length()) 232 resizeFor(length()); 233 234 return ret; // == this 235 } 236 237 // Documentation from interface clear()238 public void clear() { 239 replace(0, length(), "", 0, 0); 240 mSpanInsertCount = 0; 241 } 242 243 // Documentation from interface clearSpans()244 public void clearSpans() { 245 for (int i = mSpanCount - 1; i >= 0; i--) { 246 Object what = mSpans[i]; 247 int ostart = mSpanStarts[i]; 248 int oend = mSpanEnds[i]; 249 250 if (ostart > mGapStart) 251 ostart -= mGapLength; 252 if (oend > mGapStart) 253 oend -= mGapLength; 254 255 mSpanCount = i; 256 mSpans[i] = null; 257 258 sendSpanRemoved(what, ostart, oend); 259 } 260 if (mIndexOfSpan != null) { 261 mIndexOfSpan.clear(); 262 } 263 mSpanInsertCount = 0; 264 } 265 266 // Documentation from interface append(CharSequence text)267 public SpannableStringBuilder append(CharSequence text) { 268 int length = length(); 269 return replace(length, length, text, 0, text.length()); 270 } 271 272 /** 273 * Appends the character sequence {@code text} and spans {@code what} over the appended part. 274 * See {@link Spanned} for an explanation of what the flags mean. 275 * @param text the character sequence to append. 276 * @param what the object to be spanned over the appended text. 277 * @param flags see {@link Spanned}. 278 * @return this {@code SpannableStringBuilder}. 279 */ append(CharSequence text, Object what, int flags)280 public SpannableStringBuilder append(CharSequence text, Object what, int flags) { 281 int start = length(); 282 append(text); 283 setSpan(what, start, length(), flags); 284 return this; 285 } 286 287 // Documentation from interface append(CharSequence text, int start, int end)288 public SpannableStringBuilder append(CharSequence text, int start, int end) { 289 int length = length(); 290 return replace(length, length, text, start, end); 291 } 292 293 // Documentation from interface append(char text)294 public SpannableStringBuilder append(char text) { 295 return append(String.valueOf(text)); 296 } 297 298 // Returns true if a node was removed (so we can restart search from root) removeSpansForChange(int start, int end, boolean textIsRemoved, int i)299 private boolean removeSpansForChange(int start, int end, boolean textIsRemoved, int i) { 300 if ((i & 1) != 0) { 301 // internal tree node 302 if (resolveGap(mSpanMax[i]) >= start && 303 removeSpansForChange(start, end, textIsRemoved, leftChild(i))) { 304 return true; 305 } 306 } 307 if (i < mSpanCount) { 308 if ((mSpanFlags[i] & Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) == 309 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE && 310 mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength && 311 mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength && 312 // The following condition indicates that the span would become empty 313 (textIsRemoved || mSpanStarts[i] > start || mSpanEnds[i] < mGapStart)) { 314 mIndexOfSpan.remove(mSpans[i]); 315 removeSpan(i); 316 return true; 317 } 318 return resolveGap(mSpanStarts[i]) <= end && (i & 1) != 0 && 319 removeSpansForChange(start, end, textIsRemoved, rightChild(i)); 320 } 321 return false; 322 } 323 change(int start, int end, CharSequence cs, int csStart, int csEnd)324 private void change(int start, int end, CharSequence cs, int csStart, int csEnd) { 325 // Can be negative 326 final int replacedLength = end - start; 327 final int replacementLength = csEnd - csStart; 328 final int nbNewChars = replacementLength - replacedLength; 329 330 boolean changed = false; 331 for (int i = mSpanCount - 1; i >= 0; i--) { 332 int spanStart = mSpanStarts[i]; 333 if (spanStart > mGapStart) 334 spanStart -= mGapLength; 335 336 int spanEnd = mSpanEnds[i]; 337 if (spanEnd > mGapStart) 338 spanEnd -= mGapLength; 339 340 if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) { 341 int ost = spanStart; 342 int oen = spanEnd; 343 int clen = length(); 344 345 if (spanStart > start && spanStart <= end) { 346 for (spanStart = end; spanStart < clen; spanStart++) 347 if (spanStart > end && charAt(spanStart - 1) == '\n') 348 break; 349 } 350 351 if (spanEnd > start && spanEnd <= end) { 352 for (spanEnd = end; spanEnd < clen; spanEnd++) 353 if (spanEnd > end && charAt(spanEnd - 1) == '\n') 354 break; 355 } 356 357 if (spanStart != ost || spanEnd != oen) { 358 setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i], 359 true/*enforceParagraph*/); 360 changed = true; 361 } 362 } 363 364 int flags = 0; 365 if (spanStart == start) flags |= SPAN_START_AT_START; 366 else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END; 367 if (spanEnd == start) flags |= SPAN_END_AT_START; 368 else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END; 369 mSpanFlags[i] |= flags; 370 } 371 if (changed) { 372 restoreInvariants(); 373 } 374 375 moveGapTo(end); 376 377 if (nbNewChars >= mGapLength) { 378 resizeFor(mText.length + nbNewChars - mGapLength); 379 } 380 381 final boolean textIsRemoved = replacementLength == 0; 382 // The removal pass needs to be done before the gap is updated in order to broadcast the 383 // correct previous positions to the correct intersecting SpanWatchers 384 if (replacedLength > 0) { // no need for span fixup on pure insertion 385 while (mSpanCount > 0 && 386 removeSpansForChange(start, end, textIsRemoved, treeRoot())) { 387 // keep deleting spans as needed, and restart from root after every deletion 388 // because deletion can invalidate an index. 389 } 390 } 391 392 mGapStart += nbNewChars; 393 mGapLength -= nbNewChars; 394 395 if (mGapLength < 1) 396 new Exception("mGapLength < 1").printStackTrace(); 397 398 TextUtils.getChars(cs, csStart, csEnd, mText, start); 399 400 if (replacedLength > 0) { // no need for span fixup on pure insertion 401 // TODO potential optimization: only update bounds on intersecting spans 402 final boolean atEnd = (mGapStart + mGapLength == mText.length); 403 404 for (int i = 0; i < mSpanCount; i++) { 405 final int startFlag = (mSpanFlags[i] & START_MASK) >> START_SHIFT; 406 mSpanStarts[i] = updatedIntervalBound(mSpanStarts[i], start, nbNewChars, startFlag, 407 atEnd, textIsRemoved); 408 409 final int endFlag = (mSpanFlags[i] & END_MASK); 410 mSpanEnds[i] = updatedIntervalBound(mSpanEnds[i], start, nbNewChars, endFlag, 411 atEnd, textIsRemoved); 412 } 413 // TODO potential optimization: only fix up invariants when bounds actually changed 414 restoreInvariants(); 415 } 416 417 if (cs instanceof Spanned) { 418 Spanned sp = (Spanned) cs; 419 Object[] spans = sp.getSpans(csStart, csEnd, Object.class); 420 421 for (int i = 0; i < spans.length; i++) { 422 int st = sp.getSpanStart(spans[i]); 423 int en = sp.getSpanEnd(spans[i]); 424 425 if (st < csStart) st = csStart; 426 if (en > csEnd) en = csEnd; 427 428 // Add span only if this object is not yet used as a span in this string 429 if (getSpanStart(spans[i]) < 0) { 430 int copySpanStart = st - csStart + start; 431 int copySpanEnd = en - csStart + start; 432 int copySpanFlags = sp.getSpanFlags(spans[i]) | SPAN_ADDED; 433 434 setSpan(false, spans[i], copySpanStart, copySpanEnd, copySpanFlags, 435 false/*enforceParagraph*/); 436 } 437 } 438 restoreInvariants(); 439 } 440 } 441 updatedIntervalBound(int offset, int start, int nbNewChars, int flag, boolean atEnd, boolean textIsRemoved)442 private int updatedIntervalBound(int offset, int start, int nbNewChars, int flag, boolean atEnd, 443 boolean textIsRemoved) { 444 if (offset >= start && offset < mGapStart + mGapLength) { 445 if (flag == POINT) { 446 // A POINT located inside the replaced range should be moved to the end of the 447 // replaced text. 448 // The exception is when the point is at the start of the range and we are doing a 449 // text replacement (as opposed to a deletion): the point stays there. 450 if (textIsRemoved || offset > start) { 451 return mGapStart + mGapLength; 452 } 453 } else { 454 if (flag == PARAGRAPH) { 455 if (atEnd) { 456 return mGapStart + mGapLength; 457 } 458 } else { // MARK 459 // MARKs should be moved to the start, with the exception of a mark located at 460 // the end of the range (which will be < mGapStart + mGapLength since mGapLength 461 // is > 0, which should stay 'unchanged' at the end of the replaced text. 462 if (textIsRemoved || offset < mGapStart - nbNewChars) { 463 return start; 464 } else { 465 // Move to the end of replaced text (needed if nbNewChars != 0) 466 return mGapStart; 467 } 468 } 469 } 470 } 471 return offset; 472 } 473 474 // Note: caller is responsible for removing the mIndexOfSpan entry. removeSpan(int i)475 private void removeSpan(int i) { 476 Object object = mSpans[i]; 477 478 int start = mSpanStarts[i]; 479 int end = mSpanEnds[i]; 480 481 if (start > mGapStart) start -= mGapLength; 482 if (end > mGapStart) end -= mGapLength; 483 484 int count = mSpanCount - (i + 1); 485 System.arraycopy(mSpans, i + 1, mSpans, i, count); 486 System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count); 487 System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count); 488 System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count); 489 System.arraycopy(mSpanOrder, i + 1, mSpanOrder, i, count); 490 491 mSpanCount--; 492 493 invalidateIndex(i); 494 mSpans[mSpanCount] = null; 495 496 // Invariants must be restored before sending span removed notifications. 497 restoreInvariants(); 498 499 sendSpanRemoved(object, start, end); 500 } 501 502 // Documentation from interface replace(int start, int end, CharSequence tb)503 public SpannableStringBuilder replace(int start, int end, CharSequence tb) { 504 return replace(start, end, tb, 0, tb.length()); 505 } 506 507 // Documentation from interface replace(final int start, final int end, CharSequence tb, int tbstart, int tbend)508 public SpannableStringBuilder replace(final int start, final int end, 509 CharSequence tb, int tbstart, int tbend) { 510 checkRange("replace", start, end); 511 512 int filtercount = mFilters.length; 513 for (int i = 0; i < filtercount; i++) { 514 CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end); 515 516 if (repl != null) { 517 tb = repl; 518 tbstart = 0; 519 tbend = repl.length(); 520 } 521 } 522 523 final int origLen = end - start; 524 final int newLen = tbend - tbstart; 525 526 if (origLen == 0 && newLen == 0 && !hasNonExclusiveExclusiveSpanAt(tb, tbstart)) { 527 // This is a no-op iif there are no spans in tb that would be added (with a 0-length) 528 // Early exit so that the text watchers do not get notified 529 return this; 530 } 531 532 TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class); 533 sendBeforeTextChanged(textWatchers, start, origLen, newLen); 534 535 // Try to keep the cursor / selection at the same relative position during 536 // a text replacement. If replaced or replacement text length is zero, this 537 // is already taken care of. 538 boolean adjustSelection = origLen != 0 && newLen != 0; 539 int selectionStart = 0; 540 int selectionEnd = 0; 541 if (adjustSelection) { 542 selectionStart = Selection.getSelectionStart(this); 543 selectionEnd = Selection.getSelectionEnd(this); 544 } 545 546 change(start, end, tb, tbstart, tbend); 547 548 if (adjustSelection) { 549 boolean changed = false; 550 if (selectionStart > start && selectionStart < end) { 551 final long diff = selectionStart - start; 552 final int offset = Math.toIntExact(diff * newLen / origLen); 553 selectionStart = start + offset; 554 555 changed = true; 556 setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart, 557 Spanned.SPAN_POINT_POINT, true/*enforceParagraph*/); 558 } 559 if (selectionEnd > start && selectionEnd < end) { 560 final long diff = selectionEnd - start; 561 final int offset = Math.toIntExact(diff * newLen / origLen); 562 selectionEnd = start + offset; 563 564 changed = true; 565 setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd, 566 Spanned.SPAN_POINT_POINT, true/*enforceParagraph*/); 567 } 568 if (changed) { 569 restoreInvariants(); 570 } 571 } 572 573 sendTextChanged(textWatchers, start, origLen, newLen); 574 sendAfterTextChanged(textWatchers); 575 576 // Span watchers need to be called after text watchers, which may update the layout 577 sendToSpanWatchers(start, end, newLen - origLen); 578 579 return this; 580 } 581 hasNonExclusiveExclusiveSpanAt(CharSequence text, int offset)582 private static boolean hasNonExclusiveExclusiveSpanAt(CharSequence text, int offset) { 583 if (text instanceof Spanned) { 584 Spanned spanned = (Spanned) text; 585 Object[] spans = spanned.getSpans(offset, offset, Object.class); 586 final int length = spans.length; 587 for (int i = 0; i < length; i++) { 588 Object span = spans[i]; 589 int flags = spanned.getSpanFlags(span); 590 if (flags != Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) return true; 591 } 592 } 593 return false; 594 } 595 sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars)596 private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) { 597 for (int i = 0; i < mSpanCount; i++) { 598 int spanFlags = mSpanFlags[i]; 599 600 // This loop handles only modified (not added) spans. 601 if ((spanFlags & SPAN_ADDED) != 0) continue; 602 int spanStart = mSpanStarts[i]; 603 int spanEnd = mSpanEnds[i]; 604 if (spanStart > mGapStart) spanStart -= mGapLength; 605 if (spanEnd > mGapStart) spanEnd -= mGapLength; 606 607 int newReplaceEnd = replaceEnd + nbNewChars; 608 boolean spanChanged = false; 609 610 int previousSpanStart = spanStart; 611 if (spanStart > newReplaceEnd) { 612 if (nbNewChars != 0) { 613 previousSpanStart -= nbNewChars; 614 spanChanged = true; 615 } 616 } else if (spanStart >= replaceStart) { 617 // No change if span start was already at replace interval boundaries before replace 618 if ((spanStart != replaceStart || 619 ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) && 620 (spanStart != newReplaceEnd || 621 ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) { 622 // TODO A correct previousSpanStart cannot be computed at this point. 623 // It would require to save all the previous spans' positions before the replace 624 // Using an invalid -1 value to convey this would break the broacast range 625 spanChanged = true; 626 } 627 } 628 629 int previousSpanEnd = spanEnd; 630 if (spanEnd > newReplaceEnd) { 631 if (nbNewChars != 0) { 632 previousSpanEnd -= nbNewChars; 633 spanChanged = true; 634 } 635 } else if (spanEnd >= replaceStart) { 636 // No change if span start was already at replace interval boundaries before replace 637 if ((spanEnd != replaceStart || 638 ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) && 639 (spanEnd != newReplaceEnd || 640 ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) { 641 // TODO same as above for previousSpanEnd 642 spanChanged = true; 643 } 644 } 645 646 if (spanChanged) { 647 sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd); 648 } 649 mSpanFlags[i] &= ~SPAN_START_END_MASK; 650 } 651 652 // Handle added spans 653 for (int i = 0; i < mSpanCount; i++) { 654 int spanFlags = mSpanFlags[i]; 655 if ((spanFlags & SPAN_ADDED) != 0) { 656 mSpanFlags[i] &= ~SPAN_ADDED; 657 int spanStart = mSpanStarts[i]; 658 int spanEnd = mSpanEnds[i]; 659 if (spanStart > mGapStart) spanStart -= mGapLength; 660 if (spanEnd > mGapStart) spanEnd -= mGapLength; 661 sendSpanAdded(mSpans[i], spanStart, spanEnd); 662 } 663 } 664 } 665 666 /** 667 * Mark the specified range of text with the specified object. 668 * The flags determine how the span will behave when text is 669 * inserted at the start or end of the span's range. 670 */ setSpan(Object what, int start, int end, int flags)671 public void setSpan(Object what, int start, int end, int flags) { 672 setSpan(true, what, start, end, flags, true/*enforceParagraph*/); 673 } 674 675 // Note: if send is false, then it is the caller's responsibility to restore 676 // invariants. If send is false and the span already exists, then this method 677 // will not change the index of any spans. setSpan(boolean send, Object what, int start, int end, int flags, boolean enforceParagraph)678 private void setSpan(boolean send, Object what, int start, int end, int flags, 679 boolean enforceParagraph) { 680 checkRange("setSpan", start, end); 681 682 int flagsStart = (flags & START_MASK) >> START_SHIFT; 683 if (isInvalidParagraph(start, flagsStart)) { 684 if (!enforceParagraph) { 685 // do not set the span 686 return; 687 } 688 throw new RuntimeException("PARAGRAPH span must start at paragraph boundary" 689 + " (" + start + " follows " + charAt(start - 1) + ")"); 690 } 691 692 int flagsEnd = flags & END_MASK; 693 if (isInvalidParagraph(end, flagsEnd)) { 694 if (!enforceParagraph) { 695 // do not set the span 696 return; 697 } 698 throw new RuntimeException("PARAGRAPH span must end at paragraph boundary" 699 + " (" + end + " follows " + charAt(end - 1) + ")"); 700 } 701 702 // 0-length Spanned.SPAN_EXCLUSIVE_EXCLUSIVE 703 if (flagsStart == POINT && flagsEnd == MARK && start == end) { 704 if (send) { 705 Log.e(TAG, "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length"); 706 } 707 // Silently ignore invalid spans when they are created from this class. 708 // This avoids the duplication of the above test code before all the 709 // calls to setSpan that are done in this class 710 return; 711 } 712 713 int nstart = start; 714 int nend = end; 715 716 if (start > mGapStart) { 717 start += mGapLength; 718 } else if (start == mGapStart) { 719 if (flagsStart == POINT || (flagsStart == PARAGRAPH && start == length())) 720 start += mGapLength; 721 } 722 723 if (end > mGapStart) { 724 end += mGapLength; 725 } else if (end == mGapStart) { 726 if (flagsEnd == POINT || (flagsEnd == PARAGRAPH && end == length())) 727 end += mGapLength; 728 } 729 730 if (mIndexOfSpan != null) { 731 Integer index = mIndexOfSpan.get(what); 732 if (index != null) { 733 int i = index; 734 int ostart = mSpanStarts[i]; 735 int oend = mSpanEnds[i]; 736 737 if (ostart > mGapStart) 738 ostart -= mGapLength; 739 if (oend > mGapStart) 740 oend -= mGapLength; 741 742 mSpanStarts[i] = start; 743 mSpanEnds[i] = end; 744 mSpanFlags[i] = flags; 745 746 if (send) { 747 restoreInvariants(); 748 sendSpanChanged(what, ostart, oend, nstart, nend); 749 } 750 751 return; 752 } 753 } 754 755 mSpans = GrowingArrayUtils.append(mSpans, mSpanCount, what); 756 mSpanStarts = GrowingArrayUtils.append(mSpanStarts, mSpanCount, start); 757 mSpanEnds = GrowingArrayUtils.append(mSpanEnds, mSpanCount, end); 758 mSpanFlags = GrowingArrayUtils.append(mSpanFlags, mSpanCount, flags); 759 mSpanOrder = GrowingArrayUtils.append(mSpanOrder, mSpanCount, mSpanInsertCount); 760 invalidateIndex(mSpanCount); 761 mSpanCount++; 762 mSpanInsertCount++; 763 // Make sure there is enough room for empty interior nodes. 764 // This magic formula computes the size of the smallest perfect binary 765 // tree no smaller than mSpanCount. 766 int sizeOfMax = 2 * treeRoot() + 1; 767 if (mSpanMax.length < sizeOfMax) { 768 mSpanMax = new int[sizeOfMax]; 769 } 770 771 if (send) { 772 restoreInvariants(); 773 sendSpanAdded(what, nstart, nend); 774 } 775 } 776 isInvalidParagraph(int index, int flag)777 private boolean isInvalidParagraph(int index, int flag) { 778 return flag == PARAGRAPH && index != 0 && index != length() && charAt(index - 1) != '\n'; 779 } 780 781 /** 782 * Remove the specified markup object from the buffer. 783 */ removeSpan(Object what)784 public void removeSpan(Object what) { 785 if (mIndexOfSpan == null) return; 786 Integer i = mIndexOfSpan.remove(what); 787 if (i != null) { 788 removeSpan(i.intValue()); 789 } 790 } 791 792 /** 793 * Return externally visible offset given offset into gapped buffer. 794 */ resolveGap(int i)795 private int resolveGap(int i) { 796 return i > mGapStart ? i - mGapLength : i; 797 } 798 799 /** 800 * Return the buffer offset of the beginning of the specified 801 * markup object, or -1 if it is not attached to this buffer. 802 */ getSpanStart(Object what)803 public int getSpanStart(Object what) { 804 if (mIndexOfSpan == null) return -1; 805 Integer i = mIndexOfSpan.get(what); 806 return i == null ? -1 : resolveGap(mSpanStarts[i]); 807 } 808 809 /** 810 * Return the buffer offset of the end of the specified 811 * markup object, or -1 if it is not attached to this buffer. 812 */ getSpanEnd(Object what)813 public int getSpanEnd(Object what) { 814 if (mIndexOfSpan == null) return -1; 815 Integer i = mIndexOfSpan.get(what); 816 return i == null ? -1 : resolveGap(mSpanEnds[i]); 817 } 818 819 /** 820 * Return the flags of the end of the specified 821 * markup object, or 0 if it is not attached to this buffer. 822 */ getSpanFlags(Object what)823 public int getSpanFlags(Object what) { 824 if (mIndexOfSpan == null) return 0; 825 Integer i = mIndexOfSpan.get(what); 826 return i == null ? 0 : mSpanFlags[i]; 827 } 828 829 /** 830 * Return an array of the spans of the specified type that overlap 831 * the specified range of the buffer. The kind may be Object.class to get 832 * a list of all the spans regardless of type. 833 */ 834 @SuppressWarnings("unchecked") getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind)835 public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind) { 836 return getSpans(queryStart, queryEnd, kind, true); 837 } 838 839 /** 840 * Return an array of the spans of the specified type that overlap 841 * the specified range of the buffer. The kind may be Object.class to get 842 * a list of all the spans regardless of type. 843 * 844 * @param queryStart Start index. 845 * @param queryEnd End index. 846 * @param kind Class type to search for. 847 * @param sortByInsertionOrder If true the results are sorted by the insertion order. 848 * @param <T> 849 * @return Array of the spans. Empty array if no results are found. 850 * 851 * @hide 852 */ getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind, boolean sortByInsertionOrder)853 public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind, 854 boolean sortByInsertionOrder) { 855 if (kind == null) return (T[]) ArrayUtils.emptyArray(Object.class); 856 if (mSpanCount == 0) return ArrayUtils.emptyArray(kind); 857 int count = countSpans(queryStart, queryEnd, kind, treeRoot()); 858 if (count == 0) { 859 return ArrayUtils.emptyArray(kind); 860 } 861 862 // Safe conversion, but requires a suppressWarning 863 T[] ret = (T[]) Array.newInstance(kind, count); 864 final int[] prioSortBuffer = sortByInsertionOrder ? obtain(count) : EmptyArray.INT; 865 final int[] orderSortBuffer = sortByInsertionOrder ? obtain(count) : EmptyArray.INT; 866 getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, prioSortBuffer, 867 orderSortBuffer, 0, sortByInsertionOrder); 868 if (sortByInsertionOrder) { 869 sort(ret, prioSortBuffer, orderSortBuffer); 870 recycle(prioSortBuffer); 871 recycle(orderSortBuffer); 872 } 873 return ret; 874 } 875 countSpans(int queryStart, int queryEnd, Class kind, int i)876 private int countSpans(int queryStart, int queryEnd, Class kind, int i) { 877 int count = 0; 878 if ((i & 1) != 0) { 879 // internal tree node 880 int left = leftChild(i); 881 int spanMax = mSpanMax[left]; 882 if (spanMax > mGapStart) { 883 spanMax -= mGapLength; 884 } 885 if (spanMax >= queryStart) { 886 count = countSpans(queryStart, queryEnd, kind, left); 887 } 888 } 889 if (i < mSpanCount) { 890 int spanStart = mSpanStarts[i]; 891 if (spanStart > mGapStart) { 892 spanStart -= mGapLength; 893 } 894 if (spanStart <= queryEnd) { 895 int spanEnd = mSpanEnds[i]; 896 if (spanEnd > mGapStart) { 897 spanEnd -= mGapLength; 898 } 899 if (spanEnd >= queryStart && 900 (spanStart == spanEnd || queryStart == queryEnd || 901 (spanStart != queryEnd && spanEnd != queryStart)) && 902 (Object.class == kind || kind.isInstance(mSpans[i]))) { 903 count++; 904 } 905 if ((i & 1) != 0) { 906 count += countSpans(queryStart, queryEnd, kind, rightChild(i)); 907 } 908 } 909 } 910 return count; 911 } 912 913 /** 914 * Fills the result array with the spans found under the current interval tree node. 915 * 916 * @param queryStart Start index for the interval query. 917 * @param queryEnd End index for the interval query. 918 * @param kind Class type to search for. 919 * @param i Index of the current tree node. 920 * @param ret Array to be filled with results. 921 * @param priority Buffer to keep record of the priorities of spans found. 922 * @param insertionOrder Buffer to keep record of the insertion orders of spans found. 923 * @param count The number of found spans. 924 * @param sort Flag to fill the priority and insertion order buffers. If false then 925 * the spans with priority flag will be sorted in the result array. 926 * @param <T> 927 * @return The total number of spans found. 928 */ 929 @SuppressWarnings("unchecked") getSpansRec(int queryStart, int queryEnd, Class<T> kind, int i, T[] ret, int[] priority, int[] insertionOrder, int count, boolean sort)930 private <T> int getSpansRec(int queryStart, int queryEnd, Class<T> kind, 931 int i, T[] ret, int[] priority, int[] insertionOrder, int count, boolean sort) { 932 if ((i & 1) != 0) { 933 // internal tree node 934 int left = leftChild(i); 935 int spanMax = mSpanMax[left]; 936 if (spanMax > mGapStart) { 937 spanMax -= mGapLength; 938 } 939 if (spanMax >= queryStart) { 940 count = getSpansRec(queryStart, queryEnd, kind, left, ret, priority, 941 insertionOrder, count, sort); 942 } 943 } 944 if (i >= mSpanCount) return count; 945 int spanStart = mSpanStarts[i]; 946 if (spanStart > mGapStart) { 947 spanStart -= mGapLength; 948 } 949 if (spanStart <= queryEnd) { 950 int spanEnd = mSpanEnds[i]; 951 if (spanEnd > mGapStart) { 952 spanEnd -= mGapLength; 953 } 954 if (spanEnd >= queryStart && 955 (spanStart == spanEnd || queryStart == queryEnd || 956 (spanStart != queryEnd && spanEnd != queryStart)) && 957 (Object.class == kind || kind.isInstance(mSpans[i]))) { 958 int spanPriority = mSpanFlags[i] & SPAN_PRIORITY; 959 int target = count; 960 if (sort) { 961 priority[target] = spanPriority; 962 insertionOrder[target] = mSpanOrder[i]; 963 } else if (spanPriority != 0) { 964 //insertion sort for elements with priority 965 int j = 0; 966 for (; j < count; j++) { 967 int p = getSpanFlags(ret[j]) & SPAN_PRIORITY; 968 if (spanPriority > p) break; 969 } 970 System.arraycopy(ret, j, ret, j + 1, count - j); 971 target = j; 972 } 973 ret[target] = (T) mSpans[i]; 974 count++; 975 } 976 if (count < ret.length && (i & 1) != 0) { 977 count = getSpansRec(queryStart, queryEnd, kind, rightChild(i), ret, priority, 978 insertionOrder, count, sort); 979 } 980 } 981 return count; 982 } 983 984 /** 985 * Obtain a temporary sort buffer. 986 * 987 * @param elementCount the size of the int[] to be returned 988 * @return an int[] with elementCount length 989 */ obtain(final int elementCount)990 private static int[] obtain(final int elementCount) { 991 int[] result = null; 992 synchronized (sCachedIntBuffer) { 993 // try finding a tmp buffer with length of at least elementCount 994 // if not get the first available one 995 int candidateIndex = -1; 996 for (int i = sCachedIntBuffer.length - 1; i >= 0; i--) { 997 if (sCachedIntBuffer[i] != null) { 998 if (sCachedIntBuffer[i].length >= elementCount) { 999 candidateIndex = i; 1000 break; 1001 } else if (candidateIndex == -1) { 1002 candidateIndex = i; 1003 } 1004 } 1005 } 1006 1007 if (candidateIndex != -1) { 1008 result = sCachedIntBuffer[candidateIndex]; 1009 sCachedIntBuffer[candidateIndex] = null; 1010 } 1011 } 1012 result = checkSortBuffer(result, elementCount); 1013 return result; 1014 } 1015 1016 /** 1017 * Recycle sort buffer. 1018 * 1019 * @param buffer buffer to be recycled 1020 */ recycle(int[] buffer)1021 private static void recycle(int[] buffer) { 1022 synchronized (sCachedIntBuffer) { 1023 for (int i = 0; i < sCachedIntBuffer.length; i++) { 1024 if (sCachedIntBuffer[i] == null || buffer.length > sCachedIntBuffer[i].length) { 1025 sCachedIntBuffer[i] = buffer; 1026 break; 1027 } 1028 } 1029 } 1030 } 1031 1032 /** 1033 * Check the size of the buffer and grow if required. 1034 * 1035 * @param buffer buffer to be checked. 1036 * @param size required size. 1037 * @return Same buffer instance if the current size is greater than required size. Otherwise a 1038 * new instance is created and returned. 1039 */ checkSortBuffer(int[] buffer, int size)1040 private static int[] checkSortBuffer(int[] buffer, int size) { 1041 if (buffer == null || size > buffer.length) { 1042 return ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(size)); 1043 } 1044 return buffer; 1045 } 1046 1047 /** 1048 * An iterative heap sort implementation. It will sort the spans using first their priority 1049 * then insertion order. A span with higher priority will be before a span with lower 1050 * priority. If priorities are the same, the spans will be sorted with insertion order. A 1051 * span with a lower insertion order will be before a span with a higher insertion order. 1052 * 1053 * @param array Span array to be sorted. 1054 * @param priority Priorities of the spans 1055 * @param insertionOrder Insertion orders of the spans 1056 * @param <T> Span object type. 1057 * @param <T> 1058 */ sort(T[] array, int[] priority, int[] insertionOrder)1059 private final <T> void sort(T[] array, int[] priority, int[] insertionOrder) { 1060 int size = array.length; 1061 for (int i = size / 2 - 1; i >= 0; i--) { 1062 siftDown(i, array, size, priority, insertionOrder); 1063 } 1064 1065 for (int i = size - 1; i > 0; i--) { 1066 final T tmpSpan = array[0]; 1067 array[0] = array[i]; 1068 array[i] = tmpSpan; 1069 1070 final int tmpPriority = priority[0]; 1071 priority[0] = priority[i]; 1072 priority[i] = tmpPriority; 1073 1074 final int tmpOrder = insertionOrder[0]; 1075 insertionOrder[0] = insertionOrder[i]; 1076 insertionOrder[i] = tmpOrder; 1077 1078 siftDown(0, array, i, priority, insertionOrder); 1079 } 1080 } 1081 1082 /** 1083 * Helper function for heap sort. 1084 * 1085 * @param index Index of the element to sift down. 1086 * @param array Span array to be sorted. 1087 * @param size Current heap size. 1088 * @param priority Priorities of the spans 1089 * @param insertionOrder Insertion orders of the spans 1090 * @param <T> Span object type. 1091 */ siftDown(int index, T[] array, int size, int[] priority, int[] insertionOrder)1092 private final <T> void siftDown(int index, T[] array, int size, int[] priority, 1093 int[] insertionOrder) { 1094 int left = 2 * index + 1; 1095 while (left < size) { 1096 if (left < size - 1 && compareSpans(left, left + 1, priority, insertionOrder) < 0) { 1097 left++; 1098 } 1099 if (compareSpans(index, left, priority, insertionOrder) >= 0) { 1100 break; 1101 } 1102 1103 final T tmpSpan = array[index]; 1104 array[index] = array[left]; 1105 array[left] = tmpSpan; 1106 1107 final int tmpPriority = priority[index]; 1108 priority[index] = priority[left]; 1109 priority[left] = tmpPriority; 1110 1111 final int tmpOrder = insertionOrder[index]; 1112 insertionOrder[index] = insertionOrder[left]; 1113 insertionOrder[left] = tmpOrder; 1114 1115 index = left; 1116 left = 2 * index + 1; 1117 } 1118 } 1119 1120 /** 1121 * Compare two span elements in an array. Comparison is based first on the priority flag of 1122 * the span, and then the insertion order of the span. 1123 * 1124 * @param left Index of the element to compare. 1125 * @param right Index of the other element to compare. 1126 * @param priority Priorities of the spans 1127 * @param insertionOrder Insertion orders of the spans 1128 * @return 1129 */ compareSpans(int left, int right, int[] priority, int[] insertionOrder)1130 private final int compareSpans(int left, int right, int[] priority, 1131 int[] insertionOrder) { 1132 int priority1 = priority[left]; 1133 int priority2 = priority[right]; 1134 if (priority1 == priority2) { 1135 return Integer.compare(insertionOrder[left], insertionOrder[right]); 1136 } 1137 // since high priority has to be before a lower priority, the arguments to compare are 1138 // opposite of the insertion order check. 1139 return Integer.compare(priority2, priority1); 1140 } 1141 1142 /** 1143 * Return the next offset after <code>start</code> but less than or 1144 * equal to <code>limit</code> where a span of the specified type 1145 * begins or ends. 1146 */ nextSpanTransition(int start, int limit, Class kind)1147 public int nextSpanTransition(int start, int limit, Class kind) { 1148 if (mSpanCount == 0) return limit; 1149 if (kind == null) { 1150 kind = Object.class; 1151 } 1152 return nextSpanTransitionRec(start, limit, kind, treeRoot()); 1153 } 1154 nextSpanTransitionRec(int start, int limit, Class kind, int i)1155 private int nextSpanTransitionRec(int start, int limit, Class kind, int i) { 1156 if ((i & 1) != 0) { 1157 // internal tree node 1158 int left = leftChild(i); 1159 if (resolveGap(mSpanMax[left]) > start) { 1160 limit = nextSpanTransitionRec(start, limit, kind, left); 1161 } 1162 } 1163 if (i < mSpanCount) { 1164 int st = resolveGap(mSpanStarts[i]); 1165 int en = resolveGap(mSpanEnds[i]); 1166 if (st > start && st < limit && kind.isInstance(mSpans[i])) 1167 limit = st; 1168 if (en > start && en < limit && kind.isInstance(mSpans[i])) 1169 limit = en; 1170 if (st < limit && (i & 1) != 0) { 1171 limit = nextSpanTransitionRec(start, limit, kind, rightChild(i)); 1172 } 1173 } 1174 1175 return limit; 1176 } 1177 1178 /** 1179 * Return a new CharSequence containing a copy of the specified 1180 * range of this buffer, including the overlapping spans. 1181 */ subSequence(int start, int end)1182 public CharSequence subSequence(int start, int end) { 1183 return new SpannableStringBuilder(this, start, end); 1184 } 1185 1186 /** 1187 * Copy the specified range of chars from this buffer into the 1188 * specified array, beginning at the specified offset. 1189 */ getChars(int start, int end, char[] dest, int destoff)1190 public void getChars(int start, int end, char[] dest, int destoff) { 1191 checkRange("getChars", start, end); 1192 1193 if (end <= mGapStart) { 1194 System.arraycopy(mText, start, dest, destoff, end - start); 1195 } else if (start >= mGapStart) { 1196 System.arraycopy(mText, start + mGapLength, dest, destoff, end - start); 1197 } else { 1198 System.arraycopy(mText, start, dest, destoff, mGapStart - start); 1199 System.arraycopy(mText, mGapStart + mGapLength, 1200 dest, destoff + (mGapStart - start), 1201 end - mGapStart); 1202 } 1203 } 1204 1205 /** 1206 * Return a String containing a copy of the chars in this buffer. 1207 */ 1208 @Override toString()1209 public String toString() { 1210 int len = length(); 1211 char[] buf = new char[len]; 1212 1213 getChars(0, len, buf, 0); 1214 return new String(buf); 1215 } 1216 1217 /** 1218 * Return a String containing a copy of the chars in this buffer, limited to the 1219 * [start, end[ range. 1220 * @hide 1221 */ substring(int start, int end)1222 public String substring(int start, int end) { 1223 char[] buf = new char[end - start]; 1224 getChars(start, end, buf, 0); 1225 return new String(buf); 1226 } 1227 1228 /** 1229 * Returns the depth of TextWatcher callbacks. Returns 0 when the object is not handling 1230 * TextWatchers. A return value greater than 1 implies that a TextWatcher caused a change that 1231 * recursively triggered a TextWatcher. 1232 */ getTextWatcherDepth()1233 public int getTextWatcherDepth() { 1234 return mTextWatcherDepth; 1235 } 1236 sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after)1237 private void sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after) { 1238 int n = watchers.length; 1239 1240 mTextWatcherDepth++; 1241 for (int i = 0; i < n; i++) { 1242 watchers[i].beforeTextChanged(this, start, before, after); 1243 } 1244 mTextWatcherDepth--; 1245 } 1246 sendTextChanged(TextWatcher[] watchers, int start, int before, int after)1247 private void sendTextChanged(TextWatcher[] watchers, int start, int before, int after) { 1248 int n = watchers.length; 1249 1250 mTextWatcherDepth++; 1251 for (int i = 0; i < n; i++) { 1252 watchers[i].onTextChanged(this, start, before, after); 1253 } 1254 mTextWatcherDepth--; 1255 } 1256 sendAfterTextChanged(TextWatcher[] watchers)1257 private void sendAfterTextChanged(TextWatcher[] watchers) { 1258 int n = watchers.length; 1259 1260 mTextWatcherDepth++; 1261 for (int i = 0; i < n; i++) { 1262 watchers[i].afterTextChanged(this); 1263 } 1264 mTextWatcherDepth--; 1265 } 1266 sendSpanAdded(Object what, int start, int end)1267 private void sendSpanAdded(Object what, int start, int end) { 1268 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class); 1269 int n = recip.length; 1270 1271 for (int i = 0; i < n; i++) { 1272 recip[i].onSpanAdded(this, what, start, end); 1273 } 1274 } 1275 sendSpanRemoved(Object what, int start, int end)1276 private void sendSpanRemoved(Object what, int start, int end) { 1277 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class); 1278 int n = recip.length; 1279 1280 for (int i = 0; i < n; i++) { 1281 recip[i].onSpanRemoved(this, what, start, end); 1282 } 1283 } 1284 sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end)1285 private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) { 1286 // The bounds of a possible SpanWatcher are guaranteed to be set before this method is 1287 // called, so that the order of the span does not affect this broadcast. 1288 SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start), 1289 Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class); 1290 int n = spanWatchers.length; 1291 for (int i = 0; i < n; i++) { 1292 spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end); 1293 } 1294 } 1295 region(int start, int end)1296 private static String region(int start, int end) { 1297 return "(" + start + " ... " + end + ")"; 1298 } 1299 checkRange(final String operation, int start, int end)1300 private void checkRange(final String operation, int start, int end) { 1301 if (end < start) { 1302 throw new IndexOutOfBoundsException(operation + " " + 1303 region(start, end) + " has end before start"); 1304 } 1305 1306 int len = length(); 1307 1308 if (start > len || end > len) { 1309 throw new IndexOutOfBoundsException(operation + " " + 1310 region(start, end) + " ends beyond length " + len); 1311 } 1312 1313 if (start < 0 || end < 0) { 1314 throw new IndexOutOfBoundsException(operation + " " + 1315 region(start, end) + " starts before 0"); 1316 } 1317 } 1318 1319 /* 1320 private boolean isprint(char c) { // XXX 1321 if (c >= ' ' && c <= '~') 1322 return true; 1323 else 1324 return false; 1325 } 1326 1327 private static final int startFlag(int flag) { 1328 return (flag >> 4) & 0x0F; 1329 } 1330 1331 private static final int endFlag(int flag) { 1332 return flag & 0x0F; 1333 } 1334 1335 public void dump() { // XXX 1336 for (int i = 0; i < mGapStart; i++) { 1337 System.out.print('|'); 1338 System.out.print(' '); 1339 System.out.print(isprint(mText[i]) ? mText[i] : '.'); 1340 System.out.print(' '); 1341 } 1342 1343 for (int i = mGapStart; i < mGapStart + mGapLength; i++) { 1344 System.out.print('|'); 1345 System.out.print('('); 1346 System.out.print(isprint(mText[i]) ? mText[i] : '.'); 1347 System.out.print(')'); 1348 } 1349 1350 for (int i = mGapStart + mGapLength; i < mText.length; i++) { 1351 System.out.print('|'); 1352 System.out.print(' '); 1353 System.out.print(isprint(mText[i]) ? mText[i] : '.'); 1354 System.out.print(' '); 1355 } 1356 1357 System.out.print('\n'); 1358 1359 for (int i = 0; i < mText.length + 1; i++) { 1360 int found = 0; 1361 int wfound = 0; 1362 1363 for (int j = 0; j < mSpanCount; j++) { 1364 if (mSpanStarts[j] == i) { 1365 found = 1; 1366 wfound = j; 1367 break; 1368 } 1369 1370 if (mSpanEnds[j] == i) { 1371 found = 2; 1372 wfound = j; 1373 break; 1374 } 1375 } 1376 1377 if (found == 1) { 1378 if (startFlag(mSpanFlags[wfound]) == MARK) 1379 System.out.print("( "); 1380 if (startFlag(mSpanFlags[wfound]) == PARAGRAPH) 1381 System.out.print("< "); 1382 else 1383 System.out.print("[ "); 1384 } else if (found == 2) { 1385 if (endFlag(mSpanFlags[wfound]) == POINT) 1386 System.out.print(") "); 1387 if (endFlag(mSpanFlags[wfound]) == PARAGRAPH) 1388 System.out.print("> "); 1389 else 1390 System.out.print("] "); 1391 } else { 1392 System.out.print(" "); 1393 } 1394 } 1395 1396 System.out.print("\n"); 1397 } 1398 */ 1399 1400 /** 1401 * Don't call this yourself -- exists for Canvas to use internally. 1402 * {@hide} 1403 */ 1404 @Override drawText(BaseCanvas c, int start, int end, float x, float y, Paint p)1405 public void drawText(BaseCanvas c, int start, int end, float x, float y, Paint p) { 1406 checkRange("drawText", start, end); 1407 1408 if (end <= mGapStart) { 1409 c.drawText(mText, start, end - start, x, y, p); 1410 } else if (start >= mGapStart) { 1411 c.drawText(mText, start + mGapLength, end - start, x, y, p); 1412 } else { 1413 char[] buf = TextUtils.obtain(end - start); 1414 1415 getChars(start, end, buf, 0); 1416 c.drawText(buf, 0, end - start, x, y, p); 1417 TextUtils.recycle(buf); 1418 } 1419 } 1420 1421 1422 /** 1423 * Don't call this yourself -- exists for Canvas to use internally. 1424 * {@hide} 1425 */ 1426 @Override drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)1427 public void drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, 1428 float x, float y, boolean isRtl, Paint p) { 1429 checkRange("drawTextRun", start, end); 1430 1431 int contextLen = contextEnd - contextStart; 1432 int len = end - start; 1433 if (contextEnd <= mGapStart) { 1434 c.drawTextRun(mText, start, len, contextStart, contextLen, x, y, isRtl, p); 1435 } else if (contextStart >= mGapStart) { 1436 c.drawTextRun(mText, start + mGapLength, len, contextStart + mGapLength, 1437 contextLen, x, y, isRtl, p); 1438 } else { 1439 char[] buf = TextUtils.obtain(contextLen); 1440 getChars(contextStart, contextEnd, buf, 0); 1441 c.drawTextRun(buf, start - contextStart, len, 0, contextLen, x, y, isRtl, p); 1442 TextUtils.recycle(buf); 1443 } 1444 } 1445 1446 /** 1447 * Don't call this yourself -- exists for Paint to use internally. 1448 * {@hide} 1449 */ measureText(int start, int end, Paint p)1450 public float measureText(int start, int end, Paint p) { 1451 checkRange("measureText", start, end); 1452 1453 float ret; 1454 1455 if (end <= mGapStart) { 1456 ret = p.measureText(mText, start, end - start); 1457 } else if (start >= mGapStart) { 1458 ret = p.measureText(mText, start + mGapLength, end - start); 1459 } else { 1460 char[] buf = TextUtils.obtain(end - start); 1461 1462 getChars(start, end, buf, 0); 1463 ret = p.measureText(buf, 0, end - start); 1464 TextUtils.recycle(buf); 1465 } 1466 1467 return ret; 1468 } 1469 1470 /** 1471 * Don't call this yourself -- exists for Paint to use internally. 1472 * {@hide} 1473 */ getTextWidths(int start, int end, float[] widths, Paint p)1474 public int getTextWidths(int start, int end, float[] widths, Paint p) { 1475 checkRange("getTextWidths", start, end); 1476 1477 int ret; 1478 1479 if (end <= mGapStart) { 1480 ret = p.getTextWidths(mText, start, end - start, widths); 1481 } else if (start >= mGapStart) { 1482 ret = p.getTextWidths(mText, start + mGapLength, end - start, widths); 1483 } else { 1484 char[] buf = TextUtils.obtain(end - start); 1485 1486 getChars(start, end, buf, 0); 1487 ret = p.getTextWidths(buf, 0, end - start, widths); 1488 TextUtils.recycle(buf); 1489 } 1490 1491 return ret; 1492 } 1493 1494 /** 1495 * Don't call this yourself -- exists for Paint to use internally. 1496 * {@hide} 1497 */ getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesPos, Paint p)1498 public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, 1499 float[] advances, int advancesPos, Paint p) { 1500 1501 float ret; 1502 1503 int contextLen = contextEnd - contextStart; 1504 int len = end - start; 1505 1506 if (end <= mGapStart) { 1507 ret = p.getTextRunAdvances(mText, start, len, contextStart, contextLen, 1508 isRtl, advances, advancesPos); 1509 } else if (start >= mGapStart) { 1510 ret = p.getTextRunAdvances(mText, start + mGapLength, len, 1511 contextStart + mGapLength, contextLen, isRtl, advances, advancesPos); 1512 } else { 1513 char[] buf = TextUtils.obtain(contextLen); 1514 getChars(contextStart, contextEnd, buf, 0); 1515 ret = p.getTextRunAdvances(buf, start - contextStart, len, 1516 0, contextLen, isRtl, advances, advancesPos); 1517 TextUtils.recycle(buf); 1518 } 1519 1520 return ret; 1521 } 1522 1523 /** 1524 * Returns the next cursor position in the run. This avoids placing the cursor between 1525 * surrogates, between characters that form conjuncts, between base characters and combining 1526 * marks, or within a reordering cluster. 1527 * 1528 * <p>The context is the shaping context for cursor movement, generally the bounds of the metric 1529 * span enclosing the cursor in the direction of movement. 1530 * <code>contextStart</code>, <code>contextEnd</code> and <code>offset</code> are relative to 1531 * the start of the string.</p> 1532 * 1533 * <p>If cursorOpt is CURSOR_AT and the offset is not a valid cursor position, 1534 * this returns -1. Otherwise this will never return a value before contextStart or after 1535 * contextEnd.</p> 1536 * 1537 * @param contextStart the start index of the context 1538 * @param contextEnd the (non-inclusive) end index of the context 1539 * @param dir either DIRECTION_RTL or DIRECTION_LTR 1540 * @param offset the cursor position to move from 1541 * @param cursorOpt how to move the cursor, one of CURSOR_AFTER, 1542 * CURSOR_AT_OR_AFTER, CURSOR_BEFORE, 1543 * CURSOR_AT_OR_BEFORE, or CURSOR_AT 1544 * @param p the Paint object that is requesting this information 1545 * @return the offset of the next position, or -1 1546 * @deprecated This is an internal method, refrain from using it in your code 1547 */ 1548 @Deprecated getTextRunCursor(int contextStart, int contextEnd, int dir, int offset, int cursorOpt, Paint p)1549 public int getTextRunCursor(int contextStart, int contextEnd, int dir, int offset, 1550 int cursorOpt, Paint p) { 1551 1552 int ret; 1553 1554 int contextLen = contextEnd - contextStart; 1555 if (contextEnd <= mGapStart) { 1556 ret = p.getTextRunCursor(mText, contextStart, contextLen, 1557 dir, offset, cursorOpt); 1558 } else if (contextStart >= mGapStart) { 1559 ret = p.getTextRunCursor(mText, contextStart + mGapLength, contextLen, 1560 dir, offset + mGapLength, cursorOpt) - mGapLength; 1561 } else { 1562 char[] buf = TextUtils.obtain(contextLen); 1563 getChars(contextStart, contextEnd, buf, 0); 1564 ret = p.getTextRunCursor(buf, 0, contextLen, 1565 dir, offset - contextStart, cursorOpt) + contextStart; 1566 TextUtils.recycle(buf); 1567 } 1568 1569 return ret; 1570 } 1571 1572 // Documentation from interface setFilters(InputFilter[] filters)1573 public void setFilters(InputFilter[] filters) { 1574 if (filters == null) { 1575 throw new IllegalArgumentException(); 1576 } 1577 1578 mFilters = filters; 1579 } 1580 1581 // Documentation from interface getFilters()1582 public InputFilter[] getFilters() { 1583 return mFilters; 1584 } 1585 1586 // Same as SpannableStringInternal 1587 @Override equals(Object o)1588 public boolean equals(Object o) { 1589 if (o instanceof Spanned && 1590 toString().equals(o.toString())) { 1591 Spanned other = (Spanned) o; 1592 // Check span data 1593 Object[] otherSpans = other.getSpans(0, other.length(), Object.class); 1594 if (mSpanCount == otherSpans.length) { 1595 for (int i = 0; i < mSpanCount; ++i) { 1596 Object thisSpan = mSpans[i]; 1597 Object otherSpan = otherSpans[i]; 1598 if (thisSpan == this) { 1599 if (other != otherSpan || 1600 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) || 1601 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) || 1602 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) { 1603 return false; 1604 } 1605 } else if (!thisSpan.equals(otherSpan) || 1606 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) || 1607 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) || 1608 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) { 1609 return false; 1610 } 1611 } 1612 return true; 1613 } 1614 } 1615 return false; 1616 } 1617 1618 // Same as SpannableStringInternal 1619 @Override hashCode()1620 public int hashCode() { 1621 int hash = toString().hashCode(); 1622 hash = hash * 31 + mSpanCount; 1623 for (int i = 0; i < mSpanCount; ++i) { 1624 Object span = mSpans[i]; 1625 if (span != this) { 1626 hash = hash * 31 + span.hashCode(); 1627 } 1628 hash = hash * 31 + getSpanStart(span); 1629 hash = hash * 31 + getSpanEnd(span); 1630 hash = hash * 31 + getSpanFlags(span); 1631 } 1632 return hash; 1633 } 1634 1635 // Primitives for treating span list as binary tree 1636 1637 // The spans (along with start and end offsets and flags) are stored in linear arrays sorted 1638 // by start offset. For fast searching, there is a binary search structure imposed over these 1639 // arrays. This structure is inorder traversal of a perfect binary tree, a slightly unusual 1640 // but advantageous approach. 1641 1642 // The value-containing nodes are indexed 0 <= i < n (where n = mSpanCount), thus preserving 1643 // logic that accesses the values as a contiguous array. Other balanced binary tree approaches 1644 // (such as a complete binary tree) would require some shuffling of node indices. 1645 1646 // Basic properties of this structure: For a perfect binary tree of height m: 1647 // The tree has 2^(m+1) - 1 total nodes. 1648 // The root of the tree has index 2^m - 1. 1649 // All leaf nodes have even index, all interior nodes odd. 1650 // The height of a node of index i is the number of trailing ones in i's binary representation. 1651 // The left child of a node i of height h is i - 2^(h - 1). 1652 // The right child of a node i of height h is i + 2^(h - 1). 1653 1654 // Note that for arbitrary n, interior nodes of this tree may be >= n. Thus, the general 1655 // structure of a recursive traversal of node i is: 1656 // * traverse left child if i is an interior node 1657 // * process i if i < n 1658 // * traverse right child if i is an interior node and i < n 1659 treeRoot()1660 private int treeRoot() { 1661 return Integer.highestOneBit(mSpanCount) - 1; 1662 } 1663 1664 // (i+1) & ~i is equal to 2^(the number of trailing ones in i) leftChild(int i)1665 private static int leftChild(int i) { 1666 return i - (((i + 1) & ~i) >> 1); 1667 } 1668 rightChild(int i)1669 private static int rightChild(int i) { 1670 return i + (((i + 1) & ~i) >> 1); 1671 } 1672 1673 // The span arrays are also augmented by an mSpanMax[] array that represents an interval tree 1674 // over the binary tree structure described above. For each node, the mSpanMax[] array contains 1675 // the maximum value of mSpanEnds of that node and its descendants. Thus, traversals can 1676 // easily reject subtrees that contain no spans overlapping the area of interest. 1677 1678 // Note that mSpanMax[] also has a valid valuefor interior nodes of index >= n, but which have 1679 // descendants of index < n. In these cases, it simply represents the maximum span end of its 1680 // descendants. This is a consequence of the perfect binary tree structure. calcMax(int i)1681 private int calcMax(int i) { 1682 int max = 0; 1683 if ((i & 1) != 0) { 1684 // internal tree node 1685 max = calcMax(leftChild(i)); 1686 } 1687 if (i < mSpanCount) { 1688 max = Math.max(max, mSpanEnds[i]); 1689 if ((i & 1) != 0) { 1690 max = Math.max(max, calcMax(rightChild(i))); 1691 } 1692 } 1693 mSpanMax[i] = max; 1694 return max; 1695 } 1696 1697 // restores binary interval tree invariants after any mutation of span structure restoreInvariants()1698 private void restoreInvariants() { 1699 if (mSpanCount == 0) return; 1700 1701 // invariant 1: span starts are nondecreasing 1702 1703 // This is a simple insertion sort because we expect it to be mostly sorted. 1704 for (int i = 1; i < mSpanCount; i++) { 1705 if (mSpanStarts[i] < mSpanStarts[i - 1]) { 1706 Object span = mSpans[i]; 1707 int start = mSpanStarts[i]; 1708 int end = mSpanEnds[i]; 1709 int flags = mSpanFlags[i]; 1710 int insertionOrder = mSpanOrder[i]; 1711 int j = i; 1712 do { 1713 mSpans[j] = mSpans[j - 1]; 1714 mSpanStarts[j] = mSpanStarts[j - 1]; 1715 mSpanEnds[j] = mSpanEnds[j - 1]; 1716 mSpanFlags[j] = mSpanFlags[j - 1]; 1717 mSpanOrder[j] = mSpanOrder[j - 1]; 1718 j--; 1719 } while (j > 0 && start < mSpanStarts[j - 1]); 1720 mSpans[j] = span; 1721 mSpanStarts[j] = start; 1722 mSpanEnds[j] = end; 1723 mSpanFlags[j] = flags; 1724 mSpanOrder[j] = insertionOrder; 1725 invalidateIndex(j); 1726 } 1727 } 1728 1729 // invariant 2: max is max span end for each node and its descendants 1730 calcMax(treeRoot()); 1731 1732 // invariant 3: mIndexOfSpan maps spans back to indices 1733 if (mIndexOfSpan == null) { 1734 mIndexOfSpan = new IdentityHashMap<Object, Integer>(); 1735 } 1736 for (int i = mLowWaterMark; i < mSpanCount; i++) { 1737 Integer existing = mIndexOfSpan.get(mSpans[i]); 1738 if (existing == null || existing != i) { 1739 mIndexOfSpan.put(mSpans[i], i); 1740 } 1741 } 1742 mLowWaterMark = Integer.MAX_VALUE; 1743 } 1744 1745 // Call this on any update to mSpans[], so that mIndexOfSpan can be updated invalidateIndex(int i)1746 private void invalidateIndex(int i) { 1747 mLowWaterMark = Math.min(i, mLowWaterMark); 1748 } 1749 1750 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 1751 1752 @GuardedBy("sCachedIntBuffer") 1753 private static final int[][] sCachedIntBuffer = new int[6][0]; 1754 1755 private InputFilter[] mFilters = NO_FILTERS; 1756 1757 private char[] mText; 1758 private int mGapStart; 1759 private int mGapLength; 1760 1761 private Object[] mSpans; 1762 private int[] mSpanStarts; 1763 private int[] mSpanEnds; 1764 private int[] mSpanMax; // see calcMax() for an explanation of what this array stores 1765 private int[] mSpanFlags; 1766 private int[] mSpanOrder; // store the order of span insertion 1767 private int mSpanInsertCount; // counter for the span insertion 1768 1769 private int mSpanCount; 1770 private IdentityHashMap<Object, Integer> mIndexOfSpan; 1771 private int mLowWaterMark; // indices below this have not been touched 1772 1773 // TextWatcher callbacks may trigger changes that trigger more callbacks. This keeps track of 1774 // how deep the callbacks go. 1775 private int mTextWatcherDepth; 1776 1777 // TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned} 1778 private static final int MARK = 1; 1779 private static final int POINT = 2; 1780 private static final int PARAGRAPH = 3; 1781 1782 private static final int START_MASK = 0xF0; 1783 private static final int END_MASK = 0x0F; 1784 private static final int START_SHIFT = 4; 1785 1786 // These bits are not (currently) used by SPANNED flags 1787 private static final int SPAN_ADDED = 0x800; 1788 private static final int SPAN_START_AT_START = 0x1000; 1789 private static final int SPAN_START_AT_END = 0x2000; 1790 private static final int SPAN_END_AT_START = 0x4000; 1791 private static final int SPAN_END_AT_END = 0x8000; 1792 private static final int SPAN_START_END_MASK = 0xF000; 1793 } 1794