MapCollections.java revision c39d9c75590eca86a5e7e32a8824ba04a0d42e9b
1/*
2 * Copyright (C) 2013 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.support.v4.util;
18
19import java.lang.reflect.Array;
20import java.util.Collection;
21import java.util.Iterator;
22import java.util.Map;
23import java.util.Set;
24
25/**
26 * Helper for writing standard Java collection interfaces to a data
27 * structure like {@link ArrayMap}.
28 */
29abstract class MapCollections<K, V> {
30    EntrySet mEntrySet;
31    KeySet mKeySet;
32    ValuesCollection mValues;
33
34    final class ArrayIterator<T> implements Iterator<T> {
35        final int mOffset;
36        int mSize;
37        int mIndex;
38        boolean mCanRemove = false;
39
40        ArrayIterator(int offset) {
41            mOffset = offset;
42            mSize = colGetSize();
43        }
44
45        @Override
46        public boolean hasNext() {
47            return mIndex < mSize;
48        }
49
50        @Override
51        public T next() {
52            Object res = colGetEntry(mIndex, mOffset);
53            mIndex++;
54            mCanRemove = true;
55            return (T)res;
56        }
57
58        @Override
59        public void remove() {
60            if (!mCanRemove) {
61                throw new IllegalStateException();
62            }
63            mIndex--;
64            mSize--;
65            mCanRemove = false;
66            colRemoveAt(mIndex);
67        }
68    }
69
70    final class MapIterator implements Iterator<Map.Entry<K, V>>, Map.Entry<K, V> {
71        int mEnd;
72        int mIndex;
73        boolean mEntryValid = false;
74
75        MapIterator() {
76            mEnd = colGetSize() - 1;
77            mIndex = -1;
78        }
79
80        @Override
81        public boolean hasNext() {
82            return mIndex < mEnd;
83        }
84
85        @Override
86        public Map.Entry<K, V> next() {
87            mIndex++;
88            mEntryValid = true;
89            return this;
90        }
91
92        @Override
93        public void remove() {
94            if (!mEntryValid) {
95                throw new IllegalStateException();
96            }
97            colRemoveAt(mIndex);
98            mIndex--;
99            mEnd--;
100            mEntryValid = false;
101        }
102
103        @Override
104        public K getKey() {
105            if (!mEntryValid) {
106                throw new IllegalStateException(
107                        "This container does not support retaining Map.Entry objects");
108            }
109            return (K)colGetEntry(mIndex, 0);
110        }
111
112        @Override
113        public V getValue() {
114            if (!mEntryValid) {
115                throw new IllegalStateException(
116                        "This container does not support retaining Map.Entry objects");
117            }
118            return (V)colGetEntry(mIndex, 1);
119        }
120
121        @Override
122        public V setValue(V object) {
123            if (!mEntryValid) {
124                throw new IllegalStateException(
125                        "This container does not support retaining Map.Entry objects");
126            }
127            return colSetValue(mIndex, object);
128        }
129
130        @Override
131        public final boolean equals(Object o) {
132            if (!mEntryValid) {
133                throw new IllegalStateException(
134                        "This container does not support retaining Map.Entry objects");
135            }
136            if (!(o instanceof Map.Entry)) {
137                return false;
138            }
139            Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
140            return ContainerHelpers.equal(e.getKey(), colGetEntry(mIndex, 0))
141                    && ContainerHelpers.equal(e.getValue(), colGetEntry(mIndex, 1));
142        }
143
144        @Override
145        public final int hashCode() {
146            if (!mEntryValid) {
147                throw new IllegalStateException(
148                        "This container does not support retaining Map.Entry objects");
149            }
150            final Object key = colGetEntry(mIndex, 0);
151            final Object value = colGetEntry(mIndex, 1);
152            return (key == null ? 0 : key.hashCode()) ^
153                    (value == null ? 0 : value.hashCode());
154        }
155
156        @Override
157        public final String toString() {
158            return getKey() + "=" + getValue();
159        }
160    }
161
162    final class EntrySet implements Set<Map.Entry<K, V>> {
163        @Override
164        public boolean add(Map.Entry<K, V> object) {
165            throw new UnsupportedOperationException();
166        }
167
168        @Override
169        public boolean addAll(Collection<? extends Map.Entry<K, V>> collection) {
170            int oldSize = colGetSize();
171            for (Map.Entry<K, V> entry : collection) {
172                colPut(entry.getKey(), entry.getValue());
173            }
174            return oldSize != colGetSize();
175        }
176
177        @Override
178        public void clear() {
179            colClear();
180        }
181
182        @Override
183        public boolean contains(Object o) {
184            if (!(o instanceof Map.Entry))
185                return false;
186            Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
187            int index = colIndexOfKey(e.getKey());
188            if (index < 0) {
189                return false;
190            }
191            Object foundVal = colGetEntry(index, 1);
192            return ContainerHelpers.equal(foundVal, e.getValue());
193        }
194
195        @Override
196        public boolean containsAll(Collection<?> collection) {
197            Iterator<?> it = collection.iterator();
198            while (it.hasNext()) {
199                if (!contains(it.next())) {
200                    return false;
201                }
202            }
203            return true;
204        }
205
206        @Override
207        public boolean isEmpty() {
208            return colGetSize() == 0;
209        }
210
211        @Override
212        public Iterator<Map.Entry<K, V>> iterator() {
213            return new MapIterator();
214        }
215
216        @Override
217        public boolean remove(Object object) {
218            throw new UnsupportedOperationException();
219        }
220
221        @Override
222        public boolean removeAll(Collection<?> collection) {
223            throw new UnsupportedOperationException();
224        }
225
226        @Override
227        public boolean retainAll(Collection<?> collection) {
228            throw new UnsupportedOperationException();
229        }
230
231        @Override
232        public int size() {
233            return colGetSize();
234        }
235
236        @Override
237        public Object[] toArray() {
238            throw new UnsupportedOperationException();
239        }
240
241        @Override
242        public <T> T[] toArray(T[] array) {
243            throw new UnsupportedOperationException();
244        }
245
246        @Override
247        public boolean equals(Object object) {
248            return equalsSetHelper(this, object);
249        }
250
251        @Override
252        public int hashCode() {
253            int result = 0;
254            for (int i=colGetSize()-1; i>=0; i--) {
255                final Object key = colGetEntry(i, 0);
256                final Object value = colGetEntry(i, 1);
257                result += ( (key == null ? 0 : key.hashCode()) ^
258                        (value == null ? 0 : value.hashCode()) );
259            }
260            return result;
261        }
262    };
263
264    final class KeySet implements Set<K> {
265
266        @Override
267        public boolean add(K object) {
268            throw new UnsupportedOperationException();
269        }
270
271        @Override
272        public boolean addAll(Collection<? extends K> collection) {
273            throw new UnsupportedOperationException();
274        }
275
276        @Override
277        public void clear() {
278            colClear();
279        }
280
281        @Override
282        public boolean contains(Object object) {
283            return colIndexOfKey(object) >= 0;
284        }
285
286        @Override
287        public boolean containsAll(Collection<?> collection) {
288            return containsAllHelper(colGetMap(), collection);
289        }
290
291        @Override
292        public boolean isEmpty() {
293            return colGetSize() == 0;
294        }
295
296        @Override
297        public Iterator<K> iterator() {
298            return new ArrayIterator<K>(0);
299        }
300
301        @Override
302        public boolean remove(Object object) {
303            int index = colIndexOfKey(object);
304            if (index >= 0) {
305                colRemoveAt(index);
306                return true;
307            }
308            return false;
309        }
310
311        @Override
312        public boolean removeAll(Collection<?> collection) {
313            return removeAllHelper(colGetMap(), collection);
314        }
315
316        @Override
317        public boolean retainAll(Collection<?> collection) {
318            return retainAllHelper(colGetMap(), collection);
319        }
320
321        @Override
322        public int size() {
323            return colGetSize();
324        }
325
326        @Override
327        public Object[] toArray() {
328            return toArrayHelper(0);
329        }
330
331        @Override
332        public <T> T[] toArray(T[] array) {
333            return toArrayHelper(array, 0);
334        }
335
336        @Override
337        public boolean equals(Object object) {
338            return equalsSetHelper(this, object);
339        }
340
341        @Override
342        public int hashCode() {
343            int result = 0;
344            for (int i=colGetSize()-1; i>=0; i--) {
345                Object obj = colGetEntry(i, 0);
346                result += obj == null ? 0 : obj.hashCode();
347            }
348            return result;
349        }
350    };
351
352    final class ValuesCollection implements Collection<V> {
353
354        @Override
355        public boolean add(V object) {
356            throw new UnsupportedOperationException();
357        }
358
359        @Override
360        public boolean addAll(Collection<? extends V> collection) {
361            throw new UnsupportedOperationException();
362        }
363
364        @Override
365        public void clear() {
366            colClear();
367        }
368
369        @Override
370        public boolean contains(Object object) {
371            return colIndexOfValue(object) >= 0;
372        }
373
374        @Override
375        public boolean containsAll(Collection<?> collection) {
376            Iterator<?> it = collection.iterator();
377            while (it.hasNext()) {
378                if (!contains(it.next())) {
379                    return false;
380                }
381            }
382            return true;
383        }
384
385        @Override
386        public boolean isEmpty() {
387            return colGetSize() == 0;
388        }
389
390        @Override
391        public Iterator<V> iterator() {
392            return new ArrayIterator<V>(1);
393        }
394
395        @Override
396        public boolean remove(Object object) {
397            int index = colIndexOfValue(object);
398            if (index >= 0) {
399                colRemoveAt(index);
400                return true;
401            }
402            return false;
403        }
404
405        @Override
406        public boolean removeAll(Collection<?> collection) {
407            int N = colGetSize();
408            boolean changed = false;
409            for (int i=0; i<N; i++) {
410                Object cur = colGetEntry(i, 1);
411                if (collection.contains(cur)) {
412                    colRemoveAt(i);
413                    i--;
414                    N--;
415                    changed = true;
416                }
417            }
418            return changed;
419        }
420
421        @Override
422        public boolean retainAll(Collection<?> collection) {
423            int N = colGetSize();
424            boolean changed = false;
425            for (int i=0; i<N; i++) {
426                Object cur = colGetEntry(i, 1);
427                if (!collection.contains(cur)) {
428                    colRemoveAt(i);
429                    i--;
430                    N--;
431                    changed = true;
432                }
433            }
434            return changed;
435        }
436
437        @Override
438        public int size() {
439            return colGetSize();
440        }
441
442        @Override
443        public Object[] toArray() {
444            return toArrayHelper(1);
445        }
446
447        @Override
448        public <T> T[] toArray(T[] array) {
449            return toArrayHelper(array, 1);
450        }
451    };
452
453    public static <K, V> boolean containsAllHelper(Map<K, V> map, Collection<?> collection) {
454        Iterator<?> it = collection.iterator();
455        while (it.hasNext()) {
456            if (!map.containsKey(it.next())) {
457                return false;
458            }
459        }
460        return true;
461    }
462
463    public static <K, V> boolean removeAllHelper(Map<K, V> map, Collection<?> collection) {
464        int oldSize = map.size();
465        Iterator<?> it = collection.iterator();
466        while (it.hasNext()) {
467            map.remove(it.next());
468        }
469        return oldSize != map.size();
470    }
471
472    public static <K, V> boolean retainAllHelper(Map<K, V> map, Collection<?> collection) {
473        int oldSize = map.size();
474        Iterator<K> it = map.keySet().iterator();
475        while (it.hasNext()) {
476            if (!collection.contains(it.next())) {
477                it.remove();
478            }
479        }
480        return oldSize != map.size();
481    }
482
483
484    public Object[] toArrayHelper(int offset) {
485        final int N = colGetSize();
486        Object[] result = new Object[N];
487        for (int i=0; i<N; i++) {
488            result[i] = colGetEntry(i, offset);
489        }
490        return result;
491    }
492
493    public <T> T[] toArrayHelper(T[] array, int offset) {
494        final int N  = colGetSize();
495        if (array.length < N) {
496            @SuppressWarnings("unchecked") T[] newArray
497                = (T[]) Array.newInstance(array.getClass().getComponentType(), N);
498            array = newArray;
499        }
500        for (int i=0; i<N; i++) {
501            array[i] = (T)colGetEntry(i, offset);
502        }
503        if (array.length > N) {
504            array[N] = null;
505        }
506        return array;
507    }
508
509    public static <T> boolean equalsSetHelper(Set<T> set, Object object) {
510        if (set == object) {
511            return true;
512        }
513        if (object instanceof Set) {
514            Set<?> s = (Set<?>) object;
515
516            try {
517                return set.size() == s.size() && set.containsAll(s);
518            } catch (NullPointerException ignored) {
519                return false;
520            } catch (ClassCastException ignored) {
521                return false;
522            }
523        }
524        return false;
525    }
526
527    public Set<Map.Entry<K, V>> getEntrySet() {
528        if (mEntrySet == null) {
529            mEntrySet = new EntrySet();
530        }
531        return mEntrySet;
532    }
533
534    public Set<K> getKeySet() {
535        if (mKeySet == null) {
536            mKeySet = new KeySet();
537        }
538        return mKeySet;
539    }
540
541    public Collection<V> getValues() {
542        if (mValues == null) {
543            mValues = new ValuesCollection();
544        }
545        return mValues;
546    }
547
548    protected abstract int colGetSize();
549    protected abstract Object colGetEntry(int index, int offset);
550    protected abstract int colIndexOfKey(Object key);
551    protected abstract int colIndexOfValue(Object key);
552    protected abstract Map<K, V> colGetMap();
553    protected abstract void colPut(K key, V value);
554    protected abstract V colSetValue(int index, V value);
555    protected abstract void colRemoveAt(int index);
556    protected abstract void colClear();
557}
558