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