Loading core/java/android/text/Layout.java +5 −1 Original line number Diff line number Diff line Loading @@ -1826,8 +1826,12 @@ public abstract class Layout { return ArrayUtils.emptyArray(type); } if(text instanceof SpannableStringBuilder) { return ((SpannableStringBuilder) text).getSpans(start, end, type, false); } else { return text.getSpans(start, end, type); } } private char getEllipsisChar(TextUtils.TruncateAt method) { return (method == TextUtils.TruncateAt.END_SMALL) ? Loading core/java/android/text/SpannableStringBuilder.java +181 −23 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.text; import android.annotation.Nullable; import android.graphics.Canvas; import android.graphics.Paint; import android.text.style.ParagraphStyle; import android.util.Log; import com.android.internal.util.ArrayUtils; Loading Loading @@ -66,11 +67,15 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable TextUtils.getChars(text, start, end, mText, 0); mSpanCount = 0; mSpanInsertCount = 0; mSpans = EmptyArray.OBJECT; mSpanStarts = EmptyArray.INT; mSpanEnds = EmptyArray.INT; mSpanFlags = EmptyArray.INT; mSpanMax = EmptyArray.INT; mSpanOrder = EmptyArray.INT; mPrioSortBuffer = EmptyArray.INT; mOrderSortBuffer = EmptyArray.INT; if (text instanceof Spanned) { Spanned sp = (Spanned) text; Loading Loading @@ -234,6 +239,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable // Documentation from interface public void clear() { replace(0, length(), "", 0, 0); mSpanInsertCount = 0; } // Documentation from interface Loading @@ -256,6 +262,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (mIndexOfSpan != null) { mIndexOfSpan.clear(); } mSpanInsertCount = 0; } // Documentation from interface Loading Loading @@ -485,6 +492,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count); System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count); System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count); System.arraycopy(mSpanOrder, i + 1, mSpanOrder, i, count); mSpanCount--; Loading Loading @@ -712,9 +720,6 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable end += mGapLength; } int count = mSpanCount; Object[] spans = mSpans; if (mIndexOfSpan != null) { Integer index = mIndexOfSpan.get(what); if (index != null) { Loading Loading @@ -744,8 +749,10 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable mSpanStarts = GrowingArrayUtils.append(mSpanStarts, mSpanCount, start); mSpanEnds = GrowingArrayUtils.append(mSpanEnds, mSpanCount, end); mSpanFlags = GrowingArrayUtils.append(mSpanFlags, mSpanCount, flags); mSpanOrder = GrowingArrayUtils.append(mSpanOrder, mSpanCount, mSpanInsertCount); invalidateIndex(mSpanCount); mSpanCount++; mSpanInsertCount++; // Make sure there is enough room for empty interior nodes. // This magic formula computes the size of the smallest perfect binary // tree no smaller than mSpanCount. Loading Loading @@ -837,6 +844,25 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable */ @SuppressWarnings("unchecked") public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind) { return getSpans(queryStart, queryEnd, kind, true); } /** * Return an array of the spans of the specified type that overlap * the specified range of the buffer. The kind may be Object.class to get * a list of all the spans regardless of type. * * @param queryStart Start index. * @param queryEnd End index. * @param kind Class type to search for. * @param sort If true the results are sorted by the insertion order. * @param <T> * @return Array of the spans. Empty array if no results are found. * * @hide */ public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind, boolean sort) { if (kind == null) return (T[]) ArrayUtils.emptyArray(Object.class); if (mSpanCount == 0) return ArrayUtils.emptyArray(kind); int count = countSpans(queryStart, queryEnd, kind, treeRoot()); Loading @@ -846,7 +872,13 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable // Safe conversion, but requires a suppressWarning T[] ret = (T[]) Array.newInstance(kind, count); getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, 0); if (sort) { mPrioSortBuffer = checkSortBuffer(mPrioSortBuffer, count); mOrderSortBuffer = checkSortBuffer(mOrderSortBuffer, count); } getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, mPrioSortBuffer, mOrderSortBuffer, 0, sort); if (sort) sort(ret, mPrioSortBuffer, mOrderSortBuffer); return ret; } Loading Loading @@ -876,7 +908,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (spanEnd >= queryStart && (spanStart == spanEnd || queryStart == queryEnd || (spanStart != queryEnd && spanEnd != queryStart)) && kind.isInstance(mSpans[i])) { (Object.class == kind || kind.isInstance(mSpans[i]))) { count++; } if ((i & 1) != 0) { Loading @@ -887,9 +919,25 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable return count; } /** * Fills the result array with the spans found under the current interval tree node. * * @param queryStart Start index for the interval query. * @param queryEnd End index for the interval query. * @param kind Class type to search for. * @param i Index of the current tree node. * @param ret Array to be filled with results. * @param priority Buffer to keep record of the priorities of spans found. * @param insertionOrder Buffer to keep record of the insertion orders of spans found. * @param count The number of found spans. * @param sort Flag to fill the priority and insertion order buffers. If false then * the spans with priority flag will be sorted in the result array. * @param <T> * @return The total number of spans found. */ @SuppressWarnings("unchecked") private <T> int getSpansRec(int queryStart, int queryEnd, Class<T> kind, int i, T[] ret, int count) { int i, T[] ret, int[] priority, int[] insertionOrder, int count, boolean sort) { if ((i & 1) != 0) { // internal tree node int left = leftChild(i); Loading @@ -898,7 +946,8 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable spanMax -= mGapLength; } if (spanMax >= queryStart) { count = getSpansRec(queryStart, queryEnd, kind, left, ret, count); count = getSpansRec(queryStart, queryEnd, kind, left, ret, priority, insertionOrder, count, sort); } } if (i >= mSpanCount) return count; Loading @@ -914,35 +963,136 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (spanEnd >= queryStart && (spanStart == spanEnd || queryStart == queryEnd || (spanStart != queryEnd && spanEnd != queryStart)) && kind.isInstance(mSpans[i])) { int prio = mSpanFlags[i] & SPAN_PRIORITY; if (prio != 0) { int j; for (j = 0; j < count; j++) { (Object.class == kind || kind.isInstance(mSpans[i]))) { int spanPriority = mSpanFlags[i] & SPAN_PRIORITY; if(sort) { ret[count] = (T) mSpans[i]; priority[count] = spanPriority; insertionOrder[count] = mSpanOrder[i]; } else if (spanPriority != 0) { //insertion sort for elements with priority int j = 0; for (; j < count; j++) { int p = getSpanFlags(ret[j]) & SPAN_PRIORITY; if (prio > p) { break; } if (spanPriority > p) break; } System.arraycopy(ret, j, ret, j + 1, count - j); // Safe conversion thanks to the isInstance test above ret[j] = (T) mSpans[i]; } else { // Safe conversion thanks to the isInstance test above ret[count] = (T) mSpans[i]; } count++; } if (count < ret.length && (i & 1) != 0) { count = getSpansRec(queryStart, queryEnd, kind, rightChild(i), ret, count); count = getSpansRec(queryStart, queryEnd, kind, rightChild(i), ret, priority, insertionOrder, count, sort); } } return count; } /** * Check the size of the buffer and grow if required. * * @param buffer Buffer to be checked. * @param size Required size. * @return Same buffer instance if the current size is greater than required size. Otherwise a * new instance is created and returned. */ private final int[] checkSortBuffer(int[] buffer, int size) { if(size > buffer.length) { return ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(size)); } return buffer; } /** * An iterative heap sort implementation. It will sort the spans using first their priority * then insertion order. A span with higher priority will be before a span with lower * priority. If priorities are the same, the spans will be sorted with insertion order. A * span with a lower insertion order will be before a span with a higher insertion order. * * @param array Span array to be sorted. * @param priority Priorities of the spans * @param insertionOrder Insertion orders of the spans * @param <T> Span object type. * @param <T> */ private final <T> void sort(T[] array, int[] priority, int[] insertionOrder) { int size = array.length; for (int i = size / 2 - 1; i >= 0; i--) { siftDown(i, array, size, priority, insertionOrder); } for (int i = size - 1; i > 0; i--) { T v = array[0]; int prio = priority[0]; int insertOrder = insertionOrder[0]; array[0] = array[i]; priority[0] = priority[i]; insertionOrder[0] = insertionOrder[i]; siftDown(0, array, i, priority, insertionOrder); array[i] = v; priority[i] = prio; insertionOrder[i] = insertOrder; } } /** * Helper function for heap sort. * * @param index Index of the element to sift down. * @param array Span array to be sorted. * @param size Current heap size. * @param priority Priorities of the spans * @param insertionOrder Insertion orders of the spans * @param <T> Span object type. */ private final <T> void siftDown(int index, T[] array, int size, int[] priority, int[] insertionOrder) { T v = array[index]; int prio = priority[index]; int insertOrder = insertionOrder[index]; int left = 2 * index + 1; while (left < size) { if (left < size - 1 && compareSpans(left, left + 1, priority, insertionOrder) < 0) { left++; } if (compareSpans(index, left, priority, insertionOrder) >= 0) { break; } array[index] = array[left]; priority[index] = priority[left]; insertionOrder[index] = insertionOrder[left]; index = left; left = 2 * index + 1; } array[index] = v; priority[index] = prio; insertionOrder[index] = insertOrder; } /** * Compare two span elements in an array. Comparison is based first on the priority flag of * the span, and then the insertion order of the span. * * @param left Index of the element to compare. * @param right Index of the other element to compare. * @param priority Priorities of the spans * @param insertionOrder Insertion orders of the spans * @return */ private final int compareSpans(int left, int right, int[] priority, int[] insertionOrder) { int priority1 = priority[left]; int priority2 = priority[right]; if (priority1 == priority2) { return Integer.compare(insertionOrder[left], insertionOrder[right]); } // since high priority has to be before a lower priority, the arguments to compare are // opposite of the insertion order check. return Integer.compare(priority2, priority1); } /** * Return the next offset after <code>start</code> but less than or * equal to <code>limit</code> where a span of the specified type Loading Loading @@ -1509,18 +1659,21 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable int start = mSpanStarts[i]; int end = mSpanEnds[i]; int flags = mSpanFlags[i]; int insertionOrder = mSpanOrder[i]; int j = i; do { mSpans[j] = mSpans[j - 1]; mSpanStarts[j] = mSpanStarts[j - 1]; mSpanEnds[j] = mSpanEnds[j - 1]; mSpanFlags[j] = mSpanFlags[j - 1]; mSpanOrder[j] = mSpanOrder[j - 1]; j--; } while (j > 0 && start < mSpanStarts[j - 1]); mSpans[j] = span; mSpanStarts[j] = start; mSpanEnds[j] = end; mSpanFlags[j] = flags; mSpanOrder[j] = insertionOrder; invalidateIndex(j); } } Loading Loading @@ -1558,6 +1711,11 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable private int[] mSpanEnds; private int[] mSpanMax; // see calcMax() for an explanation of what this array stores private int[] mSpanFlags; private int[] mSpanOrder; // store the order of span insertion private int mSpanInsertCount; // counter for the span insertion private int[] mPrioSortBuffer; // buffer used to sort getSpans result private int[] mOrderSortBuffer; // buffer used to sort getSpans result private int mSpanCount; private IdentityHashMap<Object, Integer> mIndexOfSpan; private int mLowWaterMark; // indices below this have not been touched Loading core/java/android/text/SpannableStringInternal.java +87 −12 Original line number Diff line number Diff line Loading @@ -36,13 +36,28 @@ import java.lang.reflect.Array; mSpanData = EmptyArray.INT; if (source instanceof Spanned) { Spanned sp = (Spanned) source; Object[] spans = sp.getSpans(start, end, Object.class); if (source instanceof SpannableStringInternal) { copySpans((SpannableStringInternal) source, start, end); } else { copySpans((Spanned) source, start, end); } } } /** * Copies another {@link Spanned} object's spans between [start, end] into this object. * * @param src Source object to copy from. * @param start Start index in the source object. * @param end End index in the source object. */ private final void copySpans(Spanned src, int start, int end) { Object[] spans = src.getSpans(start, end, Object.class); for (int i = 0; i < spans.length; i++) { int st = sp.getSpanStart(spans[i]); int en = sp.getSpanEnd(spans[i]); int fl = sp.getSpanFlags(spans[i]); int st = src.getSpanStart(spans[i]); int en = src.getSpanEnd(spans[i]); int fl = src.getSpanFlags(spans[i]); if (st < start) st = start; Loading @@ -52,6 +67,66 @@ import java.lang.reflect.Array; setSpan(spans[i], st - start, en - start, fl); } } /** * Copies a {@link SpannableStringInternal} object's spans between [start, end] into this * object. * * @param src Source object to copy from. * @param start Start index in the source object. * @param end End index in the source object. */ private final void copySpans(SpannableStringInternal src, int start, int end) { if (start == 0 && end == src.length()) { mSpans = ArrayUtils.newUnpaddedObjectArray(src.mSpans.length); mSpanData = new int[src.mSpanData.length]; mSpanCount = src.mSpanCount; System.arraycopy(src.mSpans, 0, mSpans, 0, src.mSpans.length); System.arraycopy(src.mSpanData, 0, mSpanData, 0, mSpanData.length); } else { int count = 0; int[] srcData = src.mSpanData; int limit = src.mSpanCount; for (int i = 0; i < limit; i++) { int spanStart = srcData[i * COLUMNS + START]; int spanEnd = srcData[i * COLUMNS + END]; if (isOutOfCopyRange(start, end, spanStart, spanEnd)) continue; count++; } if (count == 0) return; Object[] srcSpans = src.mSpans; mSpanCount = count; mSpans = ArrayUtils.newUnpaddedObjectArray(mSpanCount); mSpanData = new int[mSpanCount * COLUMNS]; for (int i = 0, j = 0; i < limit; i++) { int spanStart = srcData[i * COLUMNS + START]; int spanEnd = srcData[i * COLUMNS + END]; if (isOutOfCopyRange(start, end, spanStart, spanEnd)) continue; if (spanStart < start) spanStart = start; if (spanEnd > end) spanEnd = end; mSpans[j] = srcSpans[i]; mSpanData[j * COLUMNS + START] = spanStart - start; mSpanData[j * COLUMNS + END] = spanEnd - start; mSpanData[j * COLUMNS + FLAGS] = srcData[i * COLUMNS + FLAGS]; j++; } } } /** * Checks if [spanStart, spanEnd] interval is excluded from [start, end]. * * @return True if excluded, false if included. */ private final boolean isOutOfCopyRange(int start, int end, int spanStart, int spanEnd) { if (spanStart > end || spanEnd < start) return true; if (spanStart != spanEnd && start != end) { if (spanStart == end || spanEnd == start) return true; } return false; } public final int length() { Loading Loading @@ -234,7 +309,7 @@ import java.lang.reflect.Array; } // verify span class as late as possible, since it is expensive if (kind != null && !kind.isInstance(spans[i])) { if (kind != null && kind != Object.class && !kind.isInstance(spans[i])) { continue; } Loading core/tests/benchmarks/src/android/text/SpannableStringBuilderBenchmark.java 0 → 100644 +138 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.text; import com.google.caliper.AfterExperiment; import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; import com.google.caliper.Param; public class SpannableStringBuilderBenchmark { @Param({"android.text.style.ImageSpan", "android.text.style.ParagraphStyle", "android.text.style.CharacterStyle", "java.lang.Object"}) private String paramType; @Param({"1", "4", "16"}) private String paramStringMult; private Class clazz; private SpannableStringBuilder builder; @BeforeExperiment protected void setUp() throws Exception { clazz = Class.forName(paramType); int strSize = Integer.valueOf(paramStringMult); StringBuilder strBuilder = new StringBuilder(); for (int i = 0; i < strSize; i++) { strBuilder.append(TEST_STRING); } builder = new SpannableStringBuilder(Html.fromHtml(strBuilder.toString())); } @AfterExperiment protected void tearDown() { builder.clear(); builder = null; } @Benchmark public void timeGetSpans(int reps) throws Exception { for (int i = 0; i < reps; i++) { builder.getSpans(0, builder.length(), clazz); } } //contains 0 ImageSpans, 2 ParagraphSpans, 53 CharacterStyleSpans public static String TEST_STRING = "<p><span><a href=\"http://android.com\">some link</a></span></p>\n" + "<h1 style=\"margin: 0.0px 0.0px 10.0px 0.0px; line-height: 64.0px; font: 62.0px " + "'Helvetica Neue Light'; color: #000000; \"><span>some title</span></h1>\n" + "<p><span>by <a href=\"http://android.com\"><span>some name</span></a>\n" + " <a href=\"https://android.com\"><span>some text</span></a></span></p>\n" + "<p><span>some date</span></p>\n" + "<table cellspacing=\"0\" cellpadding=\"0\">\n" + " <tbody><tr><td valign=\"bottom\">\n" + " <p><span><blockquote>a paragraph</blockquote></span><br></p>\n" + " </tbody></tr></td>\n" + "</table>\n" + "<h2 style=\"margin: 0.0px 0.0px 0.0px 0.0px; line-height: 38.0px; font: 26.0px " + "'Helvetica Neue Light'; color: #262626; -webkit-text-stroke: #262626\">" + "<span>some header two</span></h2>\n" + "<p><span>Lorem ipsum dolor concludaturque. </span></p>\n" + "<p><span></span><br></p>\n" + "<p><span>Vix te doctus</span></p>\n" + "<p><span><b>Error mel</b></span><span>, est ei. <a href=\"http://andorid.com\">" + "<span>asda</span></a> ullamcorper eam.</span></p>\n" + "<p><span>adversarium <a href=\"http://android.com\"><span>efficiantur</span></a>, " + "mea te.</span></p>\n" + "<p><span></span><br></p>\n" + "<h1>Testing display of HTML elements</h1>\n" + "<h2>2nd level heading</h2>\n" + "<p>test paragraph.</p>\n" + "<h3>3rd level heading</h3>\n" + "<p>test paragraph.</p>\n" + "<h4>4th level heading</h4>\n" + "<p>test paragraph.</p>\n" + "<h5>5th level heading</h5>\n" + "<p>test paragraph.</p>\n" + "<h6>6th level heading</h6>\n" + "<p>test paragraph.</p>\n" + "<h2>level elements</h2>\n" + "<p>a normap paragraph(<code>p</code> element).\n" + " with some <strong>strong</strong>.</p>\n" + "<div>This is a <code>div</code> element. </div>\n" + "<blockquote><p>This is a block quotation with some <em>style</em></p></blockquote>\n" + "<address>an address element</address>\n" + "<h2>Text-level markup</h2>\n" + "<ul>\n" + " <li> <abbr title=\"Cascading Style Sheets\">CSS</abbr> (an abbreviation;\n" + " <code>abbr</code> markup used)\n" + " <li> <acronym title=\"radio detecting and ranging\">radar</acronym>\n" + " <li> <b>bolded</b>\n" + " <li> <big>big thing</big>\n" + " <li> <font size=6>large size</font>\n" + " <li> <font face=Courier>Courier font</font>\n" + " <li> <font color=red>red text</font>\n" + " <li> <cite>Origin of Species</cite>\n" + " <li> <code>a[i] = b[i] + c[i);</code>\n" + " <li> some <del>deleted</del> text\n" + " <li> an <dfn>octet</dfn> is an\n" + " <li> this is <em>very</em> simple\n" + " <li> <i lang=\"la\">Homo sapiens</i>\n" + " <li> some <ins>inserted</ins> text\n" + " <li> type <kbd>yes</kbd> when\n" + " <li> <q>Hello!</q>\n" + " <li> <q>She said <q>Hello!</q></q>\n" + " <li> <samp>ccc</samp>\n" + " <li> <small>important</small>\n" + " <li> <strike>overstruck</strike>\n" + " <li> <strong>this is highlighted text</strong>\n" + " <li> <code>sub</code> and\n" + " <code>sup</code> x<sub>1</sub> and H<sub>2</sub>O\n" + " M<sup>lle</sup>, 1<sup>st</sup>, e<sup>x</sup>, sin<sup>2</sup> <i>x</i>,\n" + " e<sup>x<sup>2</sup></sup> and f(x)<sup>g(x)<sup>a+b+c</sup></sup>\n" + " (where 2 and a+b+c should appear as exponents of exponents).\n" + " <li> <tt>text in monospace font</tt>\n" + " <li> <u>underlined</u> text\n" + " <li> <code>cat</code> <var>filename</var> displays the\n" + " the <var>filename</var>.\n" + "</ul>\n"; } core/tests/benchmarks/src/android/text/SpannableStringInternalCopyBenchmark.java 0 → 100644 +61 −0 File added.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/text/Layout.java +5 −1 Original line number Diff line number Diff line Loading @@ -1826,8 +1826,12 @@ public abstract class Layout { return ArrayUtils.emptyArray(type); } if(text instanceof SpannableStringBuilder) { return ((SpannableStringBuilder) text).getSpans(start, end, type, false); } else { return text.getSpans(start, end, type); } } private char getEllipsisChar(TextUtils.TruncateAt method) { return (method == TextUtils.TruncateAt.END_SMALL) ? Loading
core/java/android/text/SpannableStringBuilder.java +181 −23 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.text; import android.annotation.Nullable; import android.graphics.Canvas; import android.graphics.Paint; import android.text.style.ParagraphStyle; import android.util.Log; import com.android.internal.util.ArrayUtils; Loading Loading @@ -66,11 +67,15 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable TextUtils.getChars(text, start, end, mText, 0); mSpanCount = 0; mSpanInsertCount = 0; mSpans = EmptyArray.OBJECT; mSpanStarts = EmptyArray.INT; mSpanEnds = EmptyArray.INT; mSpanFlags = EmptyArray.INT; mSpanMax = EmptyArray.INT; mSpanOrder = EmptyArray.INT; mPrioSortBuffer = EmptyArray.INT; mOrderSortBuffer = EmptyArray.INT; if (text instanceof Spanned) { Spanned sp = (Spanned) text; Loading Loading @@ -234,6 +239,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable // Documentation from interface public void clear() { replace(0, length(), "", 0, 0); mSpanInsertCount = 0; } // Documentation from interface Loading @@ -256,6 +262,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (mIndexOfSpan != null) { mIndexOfSpan.clear(); } mSpanInsertCount = 0; } // Documentation from interface Loading Loading @@ -485,6 +492,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable System.arraycopy(mSpanStarts, i + 1, mSpanStarts, i, count); System.arraycopy(mSpanEnds, i + 1, mSpanEnds, i, count); System.arraycopy(mSpanFlags, i + 1, mSpanFlags, i, count); System.arraycopy(mSpanOrder, i + 1, mSpanOrder, i, count); mSpanCount--; Loading Loading @@ -712,9 +720,6 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable end += mGapLength; } int count = mSpanCount; Object[] spans = mSpans; if (mIndexOfSpan != null) { Integer index = mIndexOfSpan.get(what); if (index != null) { Loading Loading @@ -744,8 +749,10 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable mSpanStarts = GrowingArrayUtils.append(mSpanStarts, mSpanCount, start); mSpanEnds = GrowingArrayUtils.append(mSpanEnds, mSpanCount, end); mSpanFlags = GrowingArrayUtils.append(mSpanFlags, mSpanCount, flags); mSpanOrder = GrowingArrayUtils.append(mSpanOrder, mSpanCount, mSpanInsertCount); invalidateIndex(mSpanCount); mSpanCount++; mSpanInsertCount++; // Make sure there is enough room for empty interior nodes. // This magic formula computes the size of the smallest perfect binary // tree no smaller than mSpanCount. Loading Loading @@ -837,6 +844,25 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable */ @SuppressWarnings("unchecked") public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind) { return getSpans(queryStart, queryEnd, kind, true); } /** * Return an array of the spans of the specified type that overlap * the specified range of the buffer. The kind may be Object.class to get * a list of all the spans regardless of type. * * @param queryStart Start index. * @param queryEnd End index. * @param kind Class type to search for. * @param sort If true the results are sorted by the insertion order. * @param <T> * @return Array of the spans. Empty array if no results are found. * * @hide */ public <T> T[] getSpans(int queryStart, int queryEnd, @Nullable Class<T> kind, boolean sort) { if (kind == null) return (T[]) ArrayUtils.emptyArray(Object.class); if (mSpanCount == 0) return ArrayUtils.emptyArray(kind); int count = countSpans(queryStart, queryEnd, kind, treeRoot()); Loading @@ -846,7 +872,13 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable // Safe conversion, but requires a suppressWarning T[] ret = (T[]) Array.newInstance(kind, count); getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, 0); if (sort) { mPrioSortBuffer = checkSortBuffer(mPrioSortBuffer, count); mOrderSortBuffer = checkSortBuffer(mOrderSortBuffer, count); } getSpansRec(queryStart, queryEnd, kind, treeRoot(), ret, mPrioSortBuffer, mOrderSortBuffer, 0, sort); if (sort) sort(ret, mPrioSortBuffer, mOrderSortBuffer); return ret; } Loading Loading @@ -876,7 +908,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (spanEnd >= queryStart && (spanStart == spanEnd || queryStart == queryEnd || (spanStart != queryEnd && spanEnd != queryStart)) && kind.isInstance(mSpans[i])) { (Object.class == kind || kind.isInstance(mSpans[i]))) { count++; } if ((i & 1) != 0) { Loading @@ -887,9 +919,25 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable return count; } /** * Fills the result array with the spans found under the current interval tree node. * * @param queryStart Start index for the interval query. * @param queryEnd End index for the interval query. * @param kind Class type to search for. * @param i Index of the current tree node. * @param ret Array to be filled with results. * @param priority Buffer to keep record of the priorities of spans found. * @param insertionOrder Buffer to keep record of the insertion orders of spans found. * @param count The number of found spans. * @param sort Flag to fill the priority and insertion order buffers. If false then * the spans with priority flag will be sorted in the result array. * @param <T> * @return The total number of spans found. */ @SuppressWarnings("unchecked") private <T> int getSpansRec(int queryStart, int queryEnd, Class<T> kind, int i, T[] ret, int count) { int i, T[] ret, int[] priority, int[] insertionOrder, int count, boolean sort) { if ((i & 1) != 0) { // internal tree node int left = leftChild(i); Loading @@ -898,7 +946,8 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable spanMax -= mGapLength; } if (spanMax >= queryStart) { count = getSpansRec(queryStart, queryEnd, kind, left, ret, count); count = getSpansRec(queryStart, queryEnd, kind, left, ret, priority, insertionOrder, count, sort); } } if (i >= mSpanCount) return count; Loading @@ -914,35 +963,136 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (spanEnd >= queryStart && (spanStart == spanEnd || queryStart == queryEnd || (spanStart != queryEnd && spanEnd != queryStart)) && kind.isInstance(mSpans[i])) { int prio = mSpanFlags[i] & SPAN_PRIORITY; if (prio != 0) { int j; for (j = 0; j < count; j++) { (Object.class == kind || kind.isInstance(mSpans[i]))) { int spanPriority = mSpanFlags[i] & SPAN_PRIORITY; if(sort) { ret[count] = (T) mSpans[i]; priority[count] = spanPriority; insertionOrder[count] = mSpanOrder[i]; } else if (spanPriority != 0) { //insertion sort for elements with priority int j = 0; for (; j < count; j++) { int p = getSpanFlags(ret[j]) & SPAN_PRIORITY; if (prio > p) { break; } if (spanPriority > p) break; } System.arraycopy(ret, j, ret, j + 1, count - j); // Safe conversion thanks to the isInstance test above ret[j] = (T) mSpans[i]; } else { // Safe conversion thanks to the isInstance test above ret[count] = (T) mSpans[i]; } count++; } if (count < ret.length && (i & 1) != 0) { count = getSpansRec(queryStart, queryEnd, kind, rightChild(i), ret, count); count = getSpansRec(queryStart, queryEnd, kind, rightChild(i), ret, priority, insertionOrder, count, sort); } } return count; } /** * Check the size of the buffer and grow if required. * * @param buffer Buffer to be checked. * @param size Required size. * @return Same buffer instance if the current size is greater than required size. Otherwise a * new instance is created and returned. */ private final int[] checkSortBuffer(int[] buffer, int size) { if(size > buffer.length) { return ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(size)); } return buffer; } /** * An iterative heap sort implementation. It will sort the spans using first their priority * then insertion order. A span with higher priority will be before a span with lower * priority. If priorities are the same, the spans will be sorted with insertion order. A * span with a lower insertion order will be before a span with a higher insertion order. * * @param array Span array to be sorted. * @param priority Priorities of the spans * @param insertionOrder Insertion orders of the spans * @param <T> Span object type. * @param <T> */ private final <T> void sort(T[] array, int[] priority, int[] insertionOrder) { int size = array.length; for (int i = size / 2 - 1; i >= 0; i--) { siftDown(i, array, size, priority, insertionOrder); } for (int i = size - 1; i > 0; i--) { T v = array[0]; int prio = priority[0]; int insertOrder = insertionOrder[0]; array[0] = array[i]; priority[0] = priority[i]; insertionOrder[0] = insertionOrder[i]; siftDown(0, array, i, priority, insertionOrder); array[i] = v; priority[i] = prio; insertionOrder[i] = insertOrder; } } /** * Helper function for heap sort. * * @param index Index of the element to sift down. * @param array Span array to be sorted. * @param size Current heap size. * @param priority Priorities of the spans * @param insertionOrder Insertion orders of the spans * @param <T> Span object type. */ private final <T> void siftDown(int index, T[] array, int size, int[] priority, int[] insertionOrder) { T v = array[index]; int prio = priority[index]; int insertOrder = insertionOrder[index]; int left = 2 * index + 1; while (left < size) { if (left < size - 1 && compareSpans(left, left + 1, priority, insertionOrder) < 0) { left++; } if (compareSpans(index, left, priority, insertionOrder) >= 0) { break; } array[index] = array[left]; priority[index] = priority[left]; insertionOrder[index] = insertionOrder[left]; index = left; left = 2 * index + 1; } array[index] = v; priority[index] = prio; insertionOrder[index] = insertOrder; } /** * Compare two span elements in an array. Comparison is based first on the priority flag of * the span, and then the insertion order of the span. * * @param left Index of the element to compare. * @param right Index of the other element to compare. * @param priority Priorities of the spans * @param insertionOrder Insertion orders of the spans * @return */ private final int compareSpans(int left, int right, int[] priority, int[] insertionOrder) { int priority1 = priority[left]; int priority2 = priority[right]; if (priority1 == priority2) { return Integer.compare(insertionOrder[left], insertionOrder[right]); } // since high priority has to be before a lower priority, the arguments to compare are // opposite of the insertion order check. return Integer.compare(priority2, priority1); } /** * Return the next offset after <code>start</code> but less than or * equal to <code>limit</code> where a span of the specified type Loading Loading @@ -1509,18 +1659,21 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable int start = mSpanStarts[i]; int end = mSpanEnds[i]; int flags = mSpanFlags[i]; int insertionOrder = mSpanOrder[i]; int j = i; do { mSpans[j] = mSpans[j - 1]; mSpanStarts[j] = mSpanStarts[j - 1]; mSpanEnds[j] = mSpanEnds[j - 1]; mSpanFlags[j] = mSpanFlags[j - 1]; mSpanOrder[j] = mSpanOrder[j - 1]; j--; } while (j > 0 && start < mSpanStarts[j - 1]); mSpans[j] = span; mSpanStarts[j] = start; mSpanEnds[j] = end; mSpanFlags[j] = flags; mSpanOrder[j] = insertionOrder; invalidateIndex(j); } } Loading Loading @@ -1558,6 +1711,11 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable private int[] mSpanEnds; private int[] mSpanMax; // see calcMax() for an explanation of what this array stores private int[] mSpanFlags; private int[] mSpanOrder; // store the order of span insertion private int mSpanInsertCount; // counter for the span insertion private int[] mPrioSortBuffer; // buffer used to sort getSpans result private int[] mOrderSortBuffer; // buffer used to sort getSpans result private int mSpanCount; private IdentityHashMap<Object, Integer> mIndexOfSpan; private int mLowWaterMark; // indices below this have not been touched Loading
core/java/android/text/SpannableStringInternal.java +87 −12 Original line number Diff line number Diff line Loading @@ -36,13 +36,28 @@ import java.lang.reflect.Array; mSpanData = EmptyArray.INT; if (source instanceof Spanned) { Spanned sp = (Spanned) source; Object[] spans = sp.getSpans(start, end, Object.class); if (source instanceof SpannableStringInternal) { copySpans((SpannableStringInternal) source, start, end); } else { copySpans((Spanned) source, start, end); } } } /** * Copies another {@link Spanned} object's spans between [start, end] into this object. * * @param src Source object to copy from. * @param start Start index in the source object. * @param end End index in the source object. */ private final void copySpans(Spanned src, int start, int end) { Object[] spans = src.getSpans(start, end, Object.class); for (int i = 0; i < spans.length; i++) { int st = sp.getSpanStart(spans[i]); int en = sp.getSpanEnd(spans[i]); int fl = sp.getSpanFlags(spans[i]); int st = src.getSpanStart(spans[i]); int en = src.getSpanEnd(spans[i]); int fl = src.getSpanFlags(spans[i]); if (st < start) st = start; Loading @@ -52,6 +67,66 @@ import java.lang.reflect.Array; setSpan(spans[i], st - start, en - start, fl); } } /** * Copies a {@link SpannableStringInternal} object's spans between [start, end] into this * object. * * @param src Source object to copy from. * @param start Start index in the source object. * @param end End index in the source object. */ private final void copySpans(SpannableStringInternal src, int start, int end) { if (start == 0 && end == src.length()) { mSpans = ArrayUtils.newUnpaddedObjectArray(src.mSpans.length); mSpanData = new int[src.mSpanData.length]; mSpanCount = src.mSpanCount; System.arraycopy(src.mSpans, 0, mSpans, 0, src.mSpans.length); System.arraycopy(src.mSpanData, 0, mSpanData, 0, mSpanData.length); } else { int count = 0; int[] srcData = src.mSpanData; int limit = src.mSpanCount; for (int i = 0; i < limit; i++) { int spanStart = srcData[i * COLUMNS + START]; int spanEnd = srcData[i * COLUMNS + END]; if (isOutOfCopyRange(start, end, spanStart, spanEnd)) continue; count++; } if (count == 0) return; Object[] srcSpans = src.mSpans; mSpanCount = count; mSpans = ArrayUtils.newUnpaddedObjectArray(mSpanCount); mSpanData = new int[mSpanCount * COLUMNS]; for (int i = 0, j = 0; i < limit; i++) { int spanStart = srcData[i * COLUMNS + START]; int spanEnd = srcData[i * COLUMNS + END]; if (isOutOfCopyRange(start, end, spanStart, spanEnd)) continue; if (spanStart < start) spanStart = start; if (spanEnd > end) spanEnd = end; mSpans[j] = srcSpans[i]; mSpanData[j * COLUMNS + START] = spanStart - start; mSpanData[j * COLUMNS + END] = spanEnd - start; mSpanData[j * COLUMNS + FLAGS] = srcData[i * COLUMNS + FLAGS]; j++; } } } /** * Checks if [spanStart, spanEnd] interval is excluded from [start, end]. * * @return True if excluded, false if included. */ private final boolean isOutOfCopyRange(int start, int end, int spanStart, int spanEnd) { if (spanStart > end || spanEnd < start) return true; if (spanStart != spanEnd && start != end) { if (spanStart == end || spanEnd == start) return true; } return false; } public final int length() { Loading Loading @@ -234,7 +309,7 @@ import java.lang.reflect.Array; } // verify span class as late as possible, since it is expensive if (kind != null && !kind.isInstance(spans[i])) { if (kind != null && kind != Object.class && !kind.isInstance(spans[i])) { continue; } Loading
core/tests/benchmarks/src/android/text/SpannableStringBuilderBenchmark.java 0 → 100644 +138 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.text; import com.google.caliper.AfterExperiment; import com.google.caliper.BeforeExperiment; import com.google.caliper.Benchmark; import com.google.caliper.Param; public class SpannableStringBuilderBenchmark { @Param({"android.text.style.ImageSpan", "android.text.style.ParagraphStyle", "android.text.style.CharacterStyle", "java.lang.Object"}) private String paramType; @Param({"1", "4", "16"}) private String paramStringMult; private Class clazz; private SpannableStringBuilder builder; @BeforeExperiment protected void setUp() throws Exception { clazz = Class.forName(paramType); int strSize = Integer.valueOf(paramStringMult); StringBuilder strBuilder = new StringBuilder(); for (int i = 0; i < strSize; i++) { strBuilder.append(TEST_STRING); } builder = new SpannableStringBuilder(Html.fromHtml(strBuilder.toString())); } @AfterExperiment protected void tearDown() { builder.clear(); builder = null; } @Benchmark public void timeGetSpans(int reps) throws Exception { for (int i = 0; i < reps; i++) { builder.getSpans(0, builder.length(), clazz); } } //contains 0 ImageSpans, 2 ParagraphSpans, 53 CharacterStyleSpans public static String TEST_STRING = "<p><span><a href=\"http://android.com\">some link</a></span></p>\n" + "<h1 style=\"margin: 0.0px 0.0px 10.0px 0.0px; line-height: 64.0px; font: 62.0px " + "'Helvetica Neue Light'; color: #000000; \"><span>some title</span></h1>\n" + "<p><span>by <a href=\"http://android.com\"><span>some name</span></a>\n" + " <a href=\"https://android.com\"><span>some text</span></a></span></p>\n" + "<p><span>some date</span></p>\n" + "<table cellspacing=\"0\" cellpadding=\"0\">\n" + " <tbody><tr><td valign=\"bottom\">\n" + " <p><span><blockquote>a paragraph</blockquote></span><br></p>\n" + " </tbody></tr></td>\n" + "</table>\n" + "<h2 style=\"margin: 0.0px 0.0px 0.0px 0.0px; line-height: 38.0px; font: 26.0px " + "'Helvetica Neue Light'; color: #262626; -webkit-text-stroke: #262626\">" + "<span>some header two</span></h2>\n" + "<p><span>Lorem ipsum dolor concludaturque. </span></p>\n" + "<p><span></span><br></p>\n" + "<p><span>Vix te doctus</span></p>\n" + "<p><span><b>Error mel</b></span><span>, est ei. <a href=\"http://andorid.com\">" + "<span>asda</span></a> ullamcorper eam.</span></p>\n" + "<p><span>adversarium <a href=\"http://android.com\"><span>efficiantur</span></a>, " + "mea te.</span></p>\n" + "<p><span></span><br></p>\n" + "<h1>Testing display of HTML elements</h1>\n" + "<h2>2nd level heading</h2>\n" + "<p>test paragraph.</p>\n" + "<h3>3rd level heading</h3>\n" + "<p>test paragraph.</p>\n" + "<h4>4th level heading</h4>\n" + "<p>test paragraph.</p>\n" + "<h5>5th level heading</h5>\n" + "<p>test paragraph.</p>\n" + "<h6>6th level heading</h6>\n" + "<p>test paragraph.</p>\n" + "<h2>level elements</h2>\n" + "<p>a normap paragraph(<code>p</code> element).\n" + " with some <strong>strong</strong>.</p>\n" + "<div>This is a <code>div</code> element. </div>\n" + "<blockquote><p>This is a block quotation with some <em>style</em></p></blockquote>\n" + "<address>an address element</address>\n" + "<h2>Text-level markup</h2>\n" + "<ul>\n" + " <li> <abbr title=\"Cascading Style Sheets\">CSS</abbr> (an abbreviation;\n" + " <code>abbr</code> markup used)\n" + " <li> <acronym title=\"radio detecting and ranging\">radar</acronym>\n" + " <li> <b>bolded</b>\n" + " <li> <big>big thing</big>\n" + " <li> <font size=6>large size</font>\n" + " <li> <font face=Courier>Courier font</font>\n" + " <li> <font color=red>red text</font>\n" + " <li> <cite>Origin of Species</cite>\n" + " <li> <code>a[i] = b[i] + c[i);</code>\n" + " <li> some <del>deleted</del> text\n" + " <li> an <dfn>octet</dfn> is an\n" + " <li> this is <em>very</em> simple\n" + " <li> <i lang=\"la\">Homo sapiens</i>\n" + " <li> some <ins>inserted</ins> text\n" + " <li> type <kbd>yes</kbd> when\n" + " <li> <q>Hello!</q>\n" + " <li> <q>She said <q>Hello!</q></q>\n" + " <li> <samp>ccc</samp>\n" + " <li> <small>important</small>\n" + " <li> <strike>overstruck</strike>\n" + " <li> <strong>this is highlighted text</strong>\n" + " <li> <code>sub</code> and\n" + " <code>sup</code> x<sub>1</sub> and H<sub>2</sub>O\n" + " M<sup>lle</sup>, 1<sup>st</sup>, e<sup>x</sup>, sin<sup>2</sup> <i>x</i>,\n" + " e<sup>x<sup>2</sup></sup> and f(x)<sup>g(x)<sup>a+b+c</sup></sup>\n" + " (where 2 and a+b+c should appear as exponents of exponents).\n" + " <li> <tt>text in monospace font</tt>\n" + " <li> <u>underlined</u> text\n" + " <li> <code>cat</code> <var>filename</var> displays the\n" + " the <var>filename</var>.\n" + "</ul>\n"; }
core/tests/benchmarks/src/android/text/SpannableStringInternalCopyBenchmark.java 0 → 100644 +61 −0 File added.Preview size limit exceeded, changes collapsed. Show changes