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