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    /** @hide */
121    public void removeAt(int index) {
122        System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
123        System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
124        mSize--;
125    }
126
127    /**
128     * Adds a mapping from the specified key to the specified value,
129     * replacing the previous mapping from the specified key if there
130     * was one.
131     */
132    public void put(int key, boolean value) {
133        int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
134
135        if (i >= 0) {
136            mValues[i] = value;
137        } else {
138            i = ~i;
139
140            mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
141            mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
142            mSize++;
143        }
144    }
145
146    /**
147     * Returns the number of key-value mappings that this SparseBooleanArray
148     * currently stores.
149     */
150    public int size() {
151        return mSize;
152    }
153
154    /**
155     * Given an index in the range <code>0...size()-1</code>, returns
156     * the key from the <code>index</code>th key-value mapping that this
157     * SparseBooleanArray stores.
158     *
159     * <p>The keys corresponding to indices in ascending order are guaranteed to
160     * be in ascending order, e.g., <code>keyAt(0)</code> will return the
161     * smallest key and <code>keyAt(size()-1)</code> will return the largest
162     * key.</p>
163     */
164    public int keyAt(int index) {
165        return mKeys[index];
166    }
167
168    /**
169     * Given an index in the range <code>0...size()-1</code>, returns
170     * the value from the <code>index</code>th key-value mapping that this
171     * SparseBooleanArray stores.
172     *
173     * <p>The values corresponding to indices in ascending order are guaranteed
174     * to be associated with keys in ascending order, e.g.,
175     * <code>valueAt(0)</code> will return the value associated with the
176     * smallest key and <code>valueAt(size()-1)</code> will return the value
177     * associated with the largest key.</p>
178     */
179    public boolean valueAt(int index) {
180        return mValues[index];
181    }
182
183    /** @hide */
184    public void setValueAt(int index, boolean value) {
185        mValues[index] = value;
186    }
187
188    /** @hide */
189    public void setKeyAt(int index, int key) {
190        mKeys[index] = key;
191    }
192
193    /**
194     * Returns the index for which {@link #keyAt} would return the
195     * specified key, or a negative number if the specified
196     * key is not mapped.
197     */
198    public int indexOfKey(int key) {
199        return ContainerHelpers.binarySearch(mKeys, mSize, key);
200    }
201
202    /**
203     * Returns an index for which {@link #valueAt} would return the
204     * specified key, or a negative number if no keys map to the
205     * specified value.
206     * Beware that this is a linear search, unlike lookups by key,
207     * and that multiple keys can map to the same value and this will
208     * find only one of them.
209     */
210    public int indexOfValue(boolean value) {
211        for (int i = 0; i < mSize; i++)
212            if (mValues[i] == value)
213                return i;
214
215        return -1;
216    }
217
218    /**
219     * Removes all key-value mappings from this SparseBooleanArray.
220     */
221    public void clear() {
222        mSize = 0;
223    }
224
225    /**
226     * Puts a key/value pair into the array, optimizing for the case where
227     * the key is greater than all existing keys in the array.
228     */
229    public void append(int key, boolean value) {
230        if (mSize != 0 && key <= mKeys[mSize - 1]) {
231            put(key, value);
232            return;
233        }
234
235        mKeys = GrowingArrayUtils.append(mKeys, mSize, key);
236        mValues = GrowingArrayUtils.append(mValues, mSize, value);
237        mSize++;
238    }
239
240    @Override
241    public int hashCode() {
242        int hashCode = mSize;
243        for (int i = 0; i < mSize; i++) {
244            hashCode = 31 * hashCode + mKeys[i] | (mValues[i] ? 1 : 0);
245        }
246        return hashCode;
247    }
248
249    @Override
250    public boolean equals(Object that) {
251      if (this == that) {
252          return true;
253      }
254
255      if (!(that instanceof SparseBooleanArray)) {
256          return false;
257      }
258
259      SparseBooleanArray other = (SparseBooleanArray) that;
260      if (mSize != other.mSize) {
261          return false;
262      }
263
264      for (int i = 0; i < mSize; i++) {
265          if (mKeys[i] != other.mKeys[i]) {
266              return false;
267          }
268          if (mValues[i] != other.mValues[i]) {
269              return false;
270          }
271      }
272      return true;
273    }
274
275    /**
276     * {@inheritDoc}
277     *
278     * <p>This implementation composes a string by iterating over its mappings.
279     */
280    @Override
281    public String toString() {
282        if (size() <= 0) {
283            return "{}";
284        }
285
286        StringBuilder buffer = new StringBuilder(mSize * 28);
287        buffer.append('{');
288        for (int i=0; i<mSize; i++) {
289            if (i > 0) {
290                buffer.append(", ");
291            }
292            int key = keyAt(i);
293            buffer.append(key);
294            buffer.append('=');
295            boolean value = valueAt(i);
296            buffer.append(value);
297        }
298        buffer.append('}');
299        return buffer.toString();
300    }
301
302    private int[] mKeys;
303    private boolean[] mValues;
304    private int mSize;
305}
306