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
17package android.util;
18
19import com.android.internal.util.ArrayUtils;
20import com.android.internal.util.GrowingArrayUtils;
21
22import libcore.util.EmptyArray;
23
24/**
25 * SparseBooleanArrays map integers to booleans.
26 * Unlike a normal array of booleans
27 * there can be gaps in the indices.  It is intended to be more memory efficient
28 * than using a HashMap to map Integers to Booleans, both because it avoids
29 * auto-boxing keys and values and its data structure doesn't rely on an extra entry object
30 * for each mapping.
31 *
32 * <p>Note that this container keeps its mappings in an array data structure,
33 * using a binary search to find keys.  The implementation is not intended to be appropriate for
34 * data structures
35 * that may contain large numbers of items.  It is generally slower than a traditional
36 * HashMap, since lookups require a binary search and adds and removes require inserting
37 * and deleting entries in the array.  For containers holding up to hundreds of items,
38 * the performance difference is not significant, less than 50%.</p>
39 *
40 * <p>It is possible to iterate over the items in this container using
41 * {@link #keyAt(int)} and {@link #valueAt(int)}. Iterating over the keys using
42 * <code>keyAt(int)</code> with ascending values of the index will return the
43 * keys in ascending order, or the values corresponding to the keys in ascending
44 * order in the case of <code>valueAt(int)</code>.</p>
45 */
46public class SparseBooleanArray implements Cloneable {
47    /**
48     * Creates a new SparseBooleanArray containing no mappings.
49     */
50    public SparseBooleanArray() {
51        this(10);
52    }
53
54    /**
55     * Creates a new SparseBooleanArray containing no mappings that will not
56     * require any additional memory allocation to store the specified
57     * number of mappings.  If you supply an initial capacity of 0, the
58     * sparse array will be initialized with a light-weight representation
59     * not requiring any additional array allocations.
60     */
61    public SparseBooleanArray(int initialCapacity) {
62        if (initialCapacity == 0) {
63            mKeys = EmptyArray.INT;
64            mValues = EmptyArray.BOOLEAN;
65        } else {
66            mKeys = ArrayUtils.newUnpaddedIntArray(initialCapacity);
67            mValues = new boolean[mKeys.length];
68        }
69        mSize = 0;
70    }
71
72    @Override
73    public SparseBooleanArray clone() {
74        SparseBooleanArray clone = null;
75        try {
76            clone = (SparseBooleanArray) super.clone();
77            clone.mKeys = mKeys.clone();
78            clone.mValues = mValues.clone();
79        } catch (CloneNotSupportedException cnse) {
80            /* ignore */
81        }
82        return clone;
83    }
84
85    /**
86     * Gets the boolean mapped from the specified key, or <code>false</code>
87     * if no such mapping has been made.
88     */
89    public boolean get(int key) {
90        return get(key, false);
91    }
92
93    /**
94     * Gets the boolean mapped from the specified key, or the specified value
95     * if no such mapping has been made.
96     */
97    public boolean get(int key, boolean valueIfKeyNotFound) {
98        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
99
100        if (i < 0) {
101            return valueIfKeyNotFound;
102        } else {
103            return mValues[i];
104        }
105    }
106
107    /**
108     * Removes the mapping from the specified key, if there was any.
109     */
110    public void delete(int key) {
111        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
112
113        if (i >= 0) {
114            System.arraycopy(mKeys, i + 1, mKeys, i, mSize - (i + 1));
115            System.arraycopy(mValues, i + 1, mValues, i, mSize - (i + 1));
116            mSize--;
117        }
118    }
119
120    /**
121     * Removes the mapping at the specified index.
122     * <p>
123     * For indices outside of the range {@code 0...size()-1}, the behavior is undefined.
124     */
125    public void removeAt(int index) {
126        System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
127        System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
128        mSize--;
129    }
130
131    /**
132     * Adds a mapping from the specified key to the specified value,
133     * replacing the previous mapping from the specified key if there
134     * was one.
135     */
136    public void put(int key, boolean value) {
137        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
138
139        if (i >= 0) {
140            mValues[i] = value;
141        } else {
142            i = ~i;
143
144            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
145            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
146            mSize++;
147        }
148    }
149
150    /**
151     * Returns the number of key-value mappings that this SparseBooleanArray
152     * currently stores.
153     */
154    public int size() {
155        return mSize;
156    }
157
158    /**
159     * Given an index in the range <code>0...size()-1</code>, returns
160     * the key from the <code>index</code>th key-value mapping that this
161     * SparseBooleanArray stores.
162     *
163     * <p>The keys corresponding to indices in ascending order are guaranteed to
164     * be in ascending order, e.g., <code>keyAt(0)</code> will return the
165     * smallest key and <code>keyAt(size()-1)</code> will return the largest
166     * key.</p>
167     */
168    public int keyAt(int index) {
169        return mKeys[index];
170    }
171
172    /**
173     * Given an index in the range <code>0...size()-1</code>, returns
174     * the value from the <code>index</code>th key-value mapping that this
175     * SparseBooleanArray stores.
176     *
177     * <p>The values corresponding to indices in ascending order are guaranteed
178     * to be associated with keys in ascending order, e.g.,
179     * <code>valueAt(0)</code> will return the value associated with the
180     * smallest key and <code>valueAt(size()-1)</code> will return the value
181     * associated with the largest key.</p>
182     */
183    public boolean valueAt(int index) {
184        return mValues[index];
185    }
186
187    /** @hide */
188    public void setValueAt(int index, boolean value) {
189        mValues[index] = value;
190    }
191
192    /** @hide */
193    public void setKeyAt(int index, int key) {
194        mKeys[index] = key;
195    }
196
197    /**
198     * Returns the index for which {@link #keyAt} would return the
199     * specified key, or a negative number if the specified
200     * key is not mapped.
201     */
202    public int indexOfKey(int key) {
203        return ContainerHelpers.binarySearch(mKeys, mSize, key);
204    }
205
206    /**
207     * Returns an index for which {@link #valueAt} would return the
208     * specified key, or a negative number if no keys map to the
209     * specified value.
210     * Beware that this is a linear search, unlike lookups by key,
211     * and that multiple keys can map to the same value and this will
212     * find only one of them.
213     */
214    public int indexOfValue(boolean value) {
215        for (int i = 0; i < mSize; i++)
216            if (mValues[i] == value)
217                return i;
218
219        return -1;
220    }
221
222    /**
223     * Removes all key-value mappings from this SparseBooleanArray.
224     */
225    public void clear() {
226        mSize = 0;
227    }
228
229    /**
230     * Puts a key/value pair into the array, optimizing for the case where
231     * the key is greater than all existing keys in the array.
232     */
233    public void append(int key, boolean value) {
234        if (mSize != 0 && key <= mKeys[mSize - 1]) {
235            put(key, value);
236            return;
237        }
238
239        mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
240        mValues = GrowingArrayUtils.append(mValues, mSize, value);
241        mSize++;
242    }
243
244    @Override
245    public int hashCode() {
246        int hashCode = mSize;
247        for (int i = 0; i < mSize; i++) {
248            hashCode = 31 * hashCode + mKeys[i] | (mValues[i] ? 1 : 0);
249        }
250        return hashCode;
251    }
252
253    @Override
254    public boolean equals(Object that) {
255      if (this == that) {
256          return true;
257      }
258
259      if (!(that instanceof SparseBooleanArray)) {
260          return false;
261      }
262
263      SparseBooleanArray other = (SparseBooleanArray) that;
264      if (mSize != other.mSize) {
265          return false;
266      }
267
268      for (int i = 0; i < mSize; i++) {
269          if (mKeys[i] != other.mKeys[i]) {
270              return false;
271          }
272          if (mValues[i] != other.mValues[i]) {
273              return false;
274          }
275      }
276      return true;
277    }
278
279    /**
280     * {@inheritDoc}
281     *
282     * <p>This implementation composes a string by iterating over its mappings.
283     */
284    @Override
285    public String toString() {
286        if (size() <= 0) {
287            return "{}";
288        }
289
290        StringBuilder buffer = new StringBuilder(mSize * 28);
291        buffer.append('{');
292        for (int i=0; i<mSize; i++) {
293            if (i > 0) {
294                buffer.append(", ");
295            }
296            int key = keyAt(i);
297            buffer.append(key);
298            buffer.append('=');
299            boolean value = valueAt(i);
300            buffer.append(value);
301        }
302        buffer.append('}');
303        return buffer.toString();
304    }
305
306    private int[] mKeys;
307    private boolean[] mValues;
308    private int mSize;
309}
310