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