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