1 /*
2  * Copyright (C) 2012 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.compat.annotation.UnsupportedAppUsage;
20 
21 import java.lang.reflect.Array;
22 import java.util.Arrays;
23 
24 /**
25  * A cached set of spans. Caches the result of {@link Spanned#getSpans(int, int, Class)} and then
26  * provides faster access to {@link Spanned#nextSpanTransition(int, int, Class)}.
27  *
28  * Fields are left public for a convenient direct access.
29  *
30  * Note that empty spans are ignored by this class.
31  * @hide
32  */
33 public class SpanSet<E> {
34     private final Class<? extends E> classType;
35 
36     int numberOfSpans;
37     @UnsupportedAppUsage
38     E[] spans;
39     int[] spanStarts;
40     int[] spanEnds;
41     int[] spanFlags;
42 
SpanSet(Class<? extends E> type)43     SpanSet(Class<? extends E> type) {
44         classType = type;
45         numberOfSpans = 0;
46     }
47 
48     @SuppressWarnings("unchecked")
init(Spanned spanned, int start, int limit)49     public void init(Spanned spanned, int start, int limit) {
50         final E[] allSpans = spanned.getSpans(start, limit, classType);
51         final int length = allSpans.length;
52 
53         if (length > 0 && (spans == null || spans.length < length)) {
54             // These arrays may end up being too large because of the discarded empty spans
55             spans = (E[]) Array.newInstance(classType, length);
56             spanStarts = new int[length];
57             spanEnds = new int[length];
58             spanFlags = new int[length];
59         }
60 
61         int prevNumberOfSpans = numberOfSpans;
62         numberOfSpans = 0;
63         for (int i = 0; i < length; i++) {
64             final E span = allSpans[i];
65 
66             final int spanStart = spanned.getSpanStart(span);
67             final int spanEnd = spanned.getSpanEnd(span);
68             if (spanStart == spanEnd) continue;
69 
70             final int spanFlag = spanned.getSpanFlags(span);
71 
72             spans[numberOfSpans] = span;
73             spanStarts[numberOfSpans] = spanStart;
74             spanEnds[numberOfSpans] = spanEnd;
75             spanFlags[numberOfSpans] = spanFlag;
76 
77             numberOfSpans++;
78         }
79 
80         // cleanup extra spans left over from previous init() call
81         if (numberOfSpans < prevNumberOfSpans) {
82             // prevNumberofSpans was > 0, therefore spans != null
83             Arrays.fill(spans, numberOfSpans, prevNumberOfSpans, null);
84         }
85     }
86 
87     /**
88      * Returns true if there are spans intersecting the given interval.
89      * @param end must be strictly greater than start
90      */
hasSpansIntersecting(int start, int end)91     public boolean hasSpansIntersecting(int start, int end) {
92         for (int i = 0; i < numberOfSpans; i++) {
93             // equal test is valid since both intervals are not empty by construction
94             if (spanStarts[i] >= end || spanEnds[i] <= start) continue;
95             return true;
96         }
97         return false;
98     }
99 
100     /**
101      * Similar to {@link Spanned#nextSpanTransition(int, int, Class)}
102      */
getNextTransition(int start, int limit)103     int getNextTransition(int start, int limit) {
104         for (int i = 0; i < numberOfSpans; i++) {
105             final int spanStart = spanStarts[i];
106             final int spanEnd = spanEnds[i];
107             if (spanStart > start && spanStart < limit) limit = spanStart;
108             if (spanEnd > start && spanEnd < limit) limit = spanEnd;
109         }
110         return limit;
111     }
112 
113     /**
114      * Removes all internal references to the spans to avoid memory leaks.
115      */
recycle()116     public void recycle() {
117         if (spans != null) {
118             Arrays.fill(spans, 0, numberOfSpans, null);
119         }
120     }
121 }
122