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