1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.text; 18 19 import com.android.internal.util.ArrayUtils; 20 import com.android.internal.util.GrowingArrayUtils; 21 22 import libcore.util.EmptyArray; 23 24 import java.lang.reflect.Array; 25 26 /* package */ abstract class SpannableStringInternal 27 { 28 /* package */ SpannableStringInternal(CharSequence source, 29 int start, int end) { 30 if (start == 0 && end == source.length()) 31 mText = source.toString(); 32 else 33 mText = source.toString().substring(start, end); 34 35 mSpans = EmptyArray.OBJECT; 36 mSpanData = EmptyArray.INT; 37 38 if (source instanceof Spanned) { 39 Spanned sp = (Spanned) source; 40 Object[] spans = sp.getSpans(start, end, Object.class); 41 42 for (int i = 0; i < spans.length; i++) { 43 int st = sp.getSpanStart(spans[i]); 44 int en = sp.getSpanEnd(spans[i]); 45 int fl = sp.getSpanFlags(spans[i]); 46 47 if (st < start) 48 st = start; 49 if (en > end) 50 en = end; 51 52 setSpan(spans[i], st - start, en - start, fl); 53 } 54 } 55 } 56 57 public final int length() { 58 return mText.length(); 59 } 60 61 public final char charAt(int i) { 62 return mText.charAt(i); 63 } 64 65 public final String toString() { 66 return mText; 67 } 68 69 /* subclasses must do subSequence() to preserve type */ 70 71 public final void getChars(int start, int end, char[] dest, int off) { 72 mText.getChars(start, end, dest, off); 73 } 74 75 /* package */ void setSpan(Object what, int start, int end, int flags) { 76 int nstart = start; 77 int nend = end; 78 79 checkRange("setSpan", start, end); 80 81 if ((flags & Spannable.SPAN_PARAGRAPH) == Spannable.SPAN_PARAGRAPH) { 82 if (start != 0 && start != length()) { 83 char c = charAt(start - 1); 84 85 if (c != '\n') 86 throw new RuntimeException( 87 "PARAGRAPH span must start at paragraph boundary" + 88 " (" + start + " follows " + c + ")"); 89 } 90 91 if (end != 0 && end != length()) { 92 char c = charAt(end - 1); 93 94 if (c != '\n') 95 throw new RuntimeException( 96 "PARAGRAPH span must end at paragraph boundary" + 97 " (" + end + " follows " + c + ")"); 98 } 99 } 100 101 int count = mSpanCount; 102 Object[] spans = mSpans; 103 int[] data = mSpanData; 104 105 for (int i = 0; i < count; i++) { 106 if (spans[i] == what) { 107 int ostart = data[i * COLUMNS + START]; 108 int oend = data[i * COLUMNS + END]; 109 110 data[i * COLUMNS + START] = start; 111 data[i * COLUMNS + END] = end; 112 data[i * COLUMNS + FLAGS] = flags; 113 114 sendSpanChanged(what, ostart, oend, nstart, nend); 115 return; 116 } 117 } 118 119 if (mSpanCount + 1 >= mSpans.length) { 120 Object[] newtags = ArrayUtils.newUnpaddedObjectArray( 121 GrowingArrayUtils.growSize(mSpanCount)); 122 int[] newdata = new int[newtags.length * 3]; 123 124 System.arraycopy(mSpans, 0, newtags, 0, mSpanCount); 125 System.arraycopy(mSpanData, 0, newdata, 0, mSpanCount * 3); 126 127 mSpans = newtags; 128 mSpanData = newdata; 129 } 130 131 mSpans[mSpanCount] = what; 132 mSpanData[mSpanCount * COLUMNS + START] = start; 133 mSpanData[mSpanCount * COLUMNS + END] = end; 134 mSpanData[mSpanCount * COLUMNS + FLAGS] = flags; 135 mSpanCount++; 136 137 if (this instanceof Spannable) 138 sendSpanAdded(what, nstart, nend); 139 } 140 141 /* package */ void removeSpan(Object what) { 142 int count = mSpanCount; 143 Object[] spans = mSpans; 144 int[] data = mSpanData; 145 146 for (int i = count - 1; i >= 0; i--) { 147 if (spans[i] == what) { 148 int ostart = data[i * COLUMNS + START]; 149 int oend = data[i * COLUMNS + END]; 150 151 int c = count - (i + 1); 152 153 System.arraycopy(spans, i + 1, spans, i, c); 154 System.arraycopy(data, (i + 1) * COLUMNS, 155 data, i * COLUMNS, c * COLUMNS); 156 157 mSpanCount--; 158 159 sendSpanRemoved(what, ostart, oend); 160 return; 161 } 162 } 163 } 164 165 public int getSpanStart(Object what) { 166 int count = mSpanCount; 167 Object[] spans = mSpans; 168 int[] data = mSpanData; 169 170 for (int i = count - 1; i >= 0; i--) { 171 if (spans[i] == what) { 172 return data[i * COLUMNS + START]; 173 } 174 } 175 176 return -1; 177 } 178 179 public int getSpanEnd(Object what) { 180 int count = mSpanCount; 181 Object[] spans = mSpans; 182 int[] data = mSpanData; 183 184 for (int i = count - 1; i >= 0; i--) { 185 if (spans[i] == what) { 186 return data[i * COLUMNS + END]; 187 } 188 } 189 190 return -1; 191 } 192 193 public int getSpanFlags(Object what) { 194 int count = mSpanCount; 195 Object[] spans = mSpans; 196 int[] data = mSpanData; 197 198 for (int i = count - 1; i >= 0; i--) { 199 if (spans[i] == what) { 200 return data[i * COLUMNS + FLAGS]; 201 } 202 } 203 204 return 0; 205 } 206 207 public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) { 208 int count = 0; 209 210 int spanCount = mSpanCount; 211 Object[] spans = mSpans; 212 int[] data = mSpanData; 213 Object[] ret = null; 214 Object ret1 = null; 215 216 for (int i = 0; i < spanCount; i++) { 217 if (kind != null && !kind.isInstance(spans[i])) { 218 continue; 219 } 220 221 int spanStart = data[i * COLUMNS + START]; 222 int spanEnd = data[i * COLUMNS + END]; 223 224 if (spanStart > queryEnd) { 225 continue; 226 } 227 if (spanEnd < queryStart) { 228 continue; 229 } 230 231 if (spanStart != spanEnd && queryStart != queryEnd) { 232 if (spanStart == queryEnd) { 233 continue; 234 } 235 if (spanEnd == queryStart) { 236 continue; 237 } 238 } 239 240 if (count == 0) { 241 ret1 = spans[i]; 242 count++; 243 } else { 244 if (count == 1) { 245 ret = (Object[]) Array.newInstance(kind, spanCount - i + 1); 246 ret[0] = ret1; 247 } 248 249 int prio = data[i * COLUMNS + FLAGS] & Spanned.SPAN_PRIORITY; 250 if (prio != 0) { 251 int j; 252 253 for (j = 0; j < count; j++) { 254 int p = getSpanFlags(ret[j]) & Spanned.SPAN_PRIORITY; 255 256 if (prio > p) { 257 break; 258 } 259 } 260 261 System.arraycopy(ret, j, ret, j + 1, count - j); 262 ret[j] = spans[i]; 263 count++; 264 } else { 265 ret[count++] = spans[i]; 266 } 267 } 268 } 269 270 if (count == 0) { 271 return (T[]) ArrayUtils.emptyArray(kind); 272 } 273 if (count == 1) { 274 ret = (Object[]) Array.newInstance(kind, 1); 275 ret[0] = ret1; 276 return (T[]) ret; 277 } 278 if (count == ret.length) { 279 return (T[]) ret; 280 } 281 282 Object[] nret = (Object[]) Array.newInstance(kind, count); 283 System.arraycopy(ret, 0, nret, 0, count); 284 return (T[]) nret; 285 } 286 287 public int nextSpanTransition(int start, int limit, Class kind) { 288 int count = mSpanCount; 289 Object[] spans = mSpans; 290 int[] data = mSpanData; 291 292 if (kind == null) { 293 kind = Object.class; 294 } 295 296 for (int i = 0; i < count; i++) { 297 int st = data[i * COLUMNS + START]; 298 int en = data[i * COLUMNS + END]; 299 300 if (st > start && st < limit && kind.isInstance(spans[i])) 301 limit = st; 302 if (en > start && en < limit && kind.isInstance(spans[i])) 303 limit = en; 304 } 305 306 return limit; 307 } 308 309 private void sendSpanAdded(Object what, int start, int end) { 310 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class); 311 int n = recip.length; 312 313 for (int i = 0; i < n; i++) { 314 recip[i].onSpanAdded((Spannable) this, what, start, end); 315 } 316 } 317 318 private void sendSpanRemoved(Object what, int start, int end) { 319 SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class); 320 int n = recip.length; 321 322 for (int i = 0; i < n; i++) { 323 recip[i].onSpanRemoved((Spannable) this, what, start, end); 324 } 325 } 326 327 private void sendSpanChanged(Object what, int s, int e, int st, int en) { 328 SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en), 329 SpanWatcher.class); 330 int n = recip.length; 331 332 for (int i = 0; i < n; i++) { 333 recip[i].onSpanChanged((Spannable) this, what, s, e, st, en); 334 } 335 } 336 337 private static String region(int start, int end) { 338 return "(" + start + " ... " + end + ")"; 339 } 340 341 private void checkRange(final String operation, int start, int end) { 342 if (end < start) { 343 throw new IndexOutOfBoundsException(operation + " " + 344 region(start, end) + 345 " has end before start"); 346 } 347 348 int len = length(); 349 350 if (start > len || end > len) { 351 throw new IndexOutOfBoundsException(operation + " " + 352 region(start, end) + 353 " ends beyond length " + len); 354 } 355 356 if (start < 0 || end < 0) { 357 throw new IndexOutOfBoundsException(operation + " " + 358 region(start, end) + 359 " starts before 0"); 360 } 361 } 362 363 // Same as SpannableStringBuilder 364 @Override 365 public boolean equals(Object o) { 366 if (o instanceof Spanned && 367 toString().equals(o.toString())) { 368 Spanned other = (Spanned) o; 369 // Check span data 370 Object[] otherSpans = other.getSpans(0, other.length(), Object.class); 371 if (mSpanCount == otherSpans.length) { 372 for (int i = 0; i < mSpanCount; ++i) { 373 Object thisSpan = mSpans[i]; 374 Object otherSpan = otherSpans[i]; 375 if (thisSpan == this) { 376 if (other != otherSpan || 377 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) || 378 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) || 379 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) { 380 return false; 381 } 382 } else if (!thisSpan.equals(otherSpan) || 383 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) || 384 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) || 385 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) { 386 return false; 387 } 388 } 389 return true; 390 } 391 } 392 return false; 393 } 394 395 // Same as SpannableStringBuilder 396 @Override 397 public int hashCode() { 398 int hash = toString().hashCode(); 399 hash = hash * 31 + mSpanCount; 400 for (int i = 0; i < mSpanCount; ++i) { 401 Object span = mSpans[i]; 402 if (span != this) { 403 hash = hash * 31 + span.hashCode(); 404 } 405 hash = hash * 31 + getSpanStart(span); 406 hash = hash * 31 + getSpanEnd(span); 407 hash = hash * 31 + getSpanFlags(span); 408 } 409 return hash; 410 } 411 412 private String mText; 413 private Object[] mSpans; 414 private int[] mSpanData; 415 private int mSpanCount; 416 417 /* package */ static final Object[] EMPTY = new Object[0]; 418 419 private static final int START = 0; 420 private static final int END = 1; 421 private static final int FLAGS = 2; 422 private static final int COLUMNS = 3; 423 } 424