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 com.google.android.test.activity;
18
19import android.util.ArrayMap;
20import android.util.ArraySet;
21import android.util.Log;
22
23import java.util.Collection;
24import java.util.HashMap;
25import java.util.HashSet;
26import java.util.Iterator;
27import java.util.Map;
28import java.util.Set;
29
30public class ArrayMapTests {
31    static final int OP_ADD = 1;
32    static final int OP_REM = 2;
33
34    static int[] OPS = new int[] {
35            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
36            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
37            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
38            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
39
40            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
41            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
42
43            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
44            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
45
46            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
47            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
48
49            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
50            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
51            OP_ADD, OP_ADD, OP_ADD,
52            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
53            OP_REM, OP_REM, OP_REM,
54            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
55    };
56
57    static int[] KEYS = new int[] {
58            // General adding and removing.
59             -1,    1900,    600,    200,   1200,   1500,   1800,    100,   1900,
60            2100,    300,    800,    600,   1100,   1300,   2000,   1000,   1400,
61             600,    -1,    1900,    600,    300,   2100,    200,    800,    800,
62            1800,   1500,   1300,   1100,   2000,   1400,   1000,   1200,   1900,
63
64            // Shrink when removing item from end.
65             100,    200,    300,    400,    500,    600,    700,    800,    900,
66             900,    800,    700,    600,    500,    400,    300,    200,    100,
67
68            // Shrink when removing item from middle.
69             100,    200,    300,    400,    500,    600,    700,    800,    900,
70             900,    800,    700,    600,    500,    400,    200,    300,    100,
71
72            // Shrink when removing item from front.
73             100,    200,    300,    400,    500,    600,    700,    800,    900,
74             900,    800,    700,    600,    500,    400,    100,    200,    300,
75
76            // Test hash collisions.
77             105,    106,    108,    104,    102,    102,    107,      5,    205,
78               4,    202,    203,      3,      5,    101,    109,    200,    201,
79               0,     -1,    100,
80             106,    108,    104,    102,    103,    105,    107,    101,    109,
81              -1,    100,      0,
82               4,      5,      3,      5,    200,    203,    202,    201,    205,
83    };
84
85    static class ControlledHash {
86        final int mValue;
87
88        ControlledHash(int value) {
89            mValue = value;
90        }
91
92        @Override
93        public final boolean equals(Object o) {
94            if (o == null) {
95                return false;
96            }
97            return mValue == ((ControlledHash)o).mValue;
98        }
99
100        @Override
101        public final int hashCode() {
102            return mValue/100;
103        }
104
105        @Override
106        public final String toString() {
107            return Integer.toString(mValue);
108        }
109    }
110
111    private static boolean compare(Object v1, Object v2) {
112        if (v1 == null) {
113            return v2 == null;
114        }
115        if (v2 == null) {
116            return false;
117        }
118        return v1.equals(v2);
119    }
120
121    private static boolean compareMaps(HashMap map, ArrayMap array) {
122        if (map.size() != array.size()) {
123            Log.e("test", "Bad size: expected " + map.size() + ", got " + array.size());
124            return false;
125        }
126
127        Set<Map.Entry> mapSet = map.entrySet();
128        for (Map.Entry entry : mapSet) {
129            Object expValue = entry.getValue();
130            Object gotValue = array.get(entry.getKey());
131            if (!compare(expValue, gotValue)) {
132                Log.e("test", "Bad value: expected " + expValue + ", got " + gotValue
133                        + " at key " + entry.getKey());
134                return false;
135            }
136        }
137
138        for (int i=0; i<array.size(); i++) {
139            Object gotValue = array.valueAt(i);
140            Object key = array.keyAt(i);
141            Object expValue = map.get(key);
142            if (!compare(expValue, gotValue)) {
143                Log.e("test", "Bad value: expected " + expValue + ", got " + gotValue
144                        + " at key " + key);
145                return false;
146            }
147        }
148
149        if (map.entrySet().hashCode() != array.entrySet().hashCode()) {
150            Log.e("test", "Entry set hash codes differ: map=0x"
151                    + Integer.toHexString(map.entrySet().hashCode()) + " array=0x"
152                    + Integer.toHexString(array.entrySet().hashCode()));
153            return false;
154        }
155
156        if (!map.entrySet().equals(array.entrySet())) {
157            Log.e("test", "Failed calling equals on map entry set against array set");
158            return false;
159        }
160
161        if (!array.entrySet().equals(map.entrySet())) {
162            Log.e("test", "Failed calling equals on array entry set against map set");
163            return false;
164        }
165
166        if (map.keySet().hashCode() != array.keySet().hashCode()) {
167            Log.e("test", "Key set hash codes differ: map=0x"
168                    + Integer.toHexString(map.keySet().hashCode()) + " array=0x"
169                    + Integer.toHexString(array.keySet().hashCode()));
170            return false;
171        }
172
173        if (!map.keySet().equals(array.keySet())) {
174            Log.e("test", "Failed calling equals on map key set against array set");
175            return false;
176        }
177
178        if (!array.keySet().equals(map.keySet())) {
179            Log.e("test", "Failed calling equals on array key set against map set");
180            return false;
181        }
182
183        if (!map.keySet().containsAll(array.keySet())) {
184            Log.e("test", "Failed map key set contains all of array key set");
185            return false;
186        }
187
188        if (!array.keySet().containsAll(map.keySet())) {
189            Log.e("test", "Failed array key set contains all of map key set");
190            return false;
191        }
192
193        if (!array.containsAll(map.keySet())) {
194            Log.e("test", "Failed array contains all of map key set");
195            return false;
196        }
197
198        if (!map.entrySet().containsAll(array.entrySet())) {
199            Log.e("test", "Failed map entry set contains all of array entry set");
200            return false;
201        }
202
203        if (!array.entrySet().containsAll(map.entrySet())) {
204            Log.e("test", "Failed array entry set contains all of map entry set");
205            return false;
206        }
207
208        return true;
209    }
210
211    private static boolean compareSets(HashSet set, ArraySet array) {
212        if (set.size() != array.size()) {
213            Log.e("test", "Bad size: expected " + set.size() + ", got " + array.size());
214            return false;
215        }
216
217        for (Object entry : set) {
218            if (!array.contains(entry)) {
219                Log.e("test", "Bad value: expected " + entry + " not found in ArraySet");
220                return false;
221            }
222        }
223
224        for (int i=0; i<array.size(); i++) {
225            Object entry = array.valueAt(i);
226            if (!set.contains(entry)) {
227                Log.e("test", "Bad value: unexpected " + entry + " in ArraySet");
228                return false;
229            }
230        }
231
232        int index = 0;
233        for (Object entry : array) {
234            Object realEntry = array.valueAt(index);
235            if (!compare(entry, realEntry)) {
236                Log.e("test", "Bad iterator: expected value " + realEntry + ", got " + entry
237                        + " at index " + index);
238                return false;
239            }
240            index++;
241        }
242
243        return true;
244    }
245
246    private static boolean validateArrayMap(ArrayMap array) {
247        Set<Map.Entry> entrySet = array.entrySet();
248        int index=0;
249        Iterator<Map.Entry> entryIt = entrySet.iterator();
250        while (entryIt.hasNext()) {
251            Map.Entry entry = entryIt.next();
252            Object value = entry.getKey();
253            Object realValue = array.keyAt(index);
254            if (!compare(realValue, value)) {
255                Log.e("test", "Bad array map entry set: expected key " + realValue
256                        + ", got " + value + " at index " + index);
257                return false;
258            }
259            value = entry.getValue();
260            realValue = array.valueAt(index);
261            if (!compare(realValue, value)) {
262                Log.e("test", "Bad array map entry set: expected value " + realValue
263                        + ", got " + value + " at index " + index);
264                return false;
265            }
266            index++;
267        }
268
269        index = 0;
270        Set keySet = array.keySet();
271        Iterator keyIt = keySet.iterator();
272        while (keyIt.hasNext()) {
273            Object value = keyIt.next();
274            Object realValue = array.keyAt(index);
275            if (!compare(realValue, value)) {
276                Log.e("test", "Bad array map key set: expected key " + realValue
277                        + ", got " + value + " at index " + index);
278                return false;
279            }
280            index++;
281        }
282
283        index = 0;
284        Collection valueCol = array.values();
285        Iterator valueIt = valueCol.iterator();
286        while (valueIt.hasNext()) {
287            Object value = valueIt.next();
288            Object realValue = array.valueAt(index);
289            if (!compare(realValue, value)) {
290                Log.e("test", "Bad array map value col: expected value " + realValue
291                        + ", got " + value + " at index " + index);
292                return false;
293            }
294            index++;
295        }
296
297        return true;
298    }
299
300    private static void dump(Map map, ArrayMap array) {
301        Log.e("test", "HashMap of " + map.size() + " entries:");
302        Set<Map.Entry> mapSet = map.entrySet();
303        for (Map.Entry entry : mapSet) {
304            Log.e("test", "    " + entry.getKey() + " -> " + entry.getValue());
305        }
306        Log.e("test", "ArrayMap of " + array.size() + " entries:");
307        for (int i=0; i<array.size(); i++) {
308            Log.e("test", "    " + array.keyAt(i) + " -> " + array.valueAt(i));
309        }
310    }
311
312    private static void dump(Set set, ArraySet array) {
313        Log.e("test", "HashSet of " + set.size() + " entries:");
314        for (Object entry : set) {
315            Log.e("test", "    " + entry);
316        }
317        Log.e("test", "ArraySet of " + array.size() + " entries:");
318        for (int i=0; i<array.size(); i++) {
319            Log.e("test", "    " + array.valueAt(i));
320        }
321    }
322
323    private static void dump(ArrayMap map1, ArrayMap map2) {
324        Log.e("test", "ArrayMap of " + map1.size() + " entries:");
325        Set<Map.Entry> mapSet = map1.entrySet();
326        for (int i=0; i<map2.size(); i++) {
327            Log.e("test", "    " + map1.keyAt(i) + " -> " + map1.valueAt(i));
328        }
329        Log.e("test", "ArrayMap of " + map2.size() + " entries:");
330        for (int i=0; i<map2.size(); i++) {
331            Log.e("test", "    " + map2.keyAt(i) + " -> " + map2.valueAt(i));
332        }
333    }
334
335    public static void run() {
336        HashMap<ControlledHash, Integer> hashMap = new HashMap<ControlledHash, Integer>();
337        ArrayMap<ControlledHash, Integer> arrayMap = new ArrayMap<ControlledHash, Integer>();
338        HashSet<ControlledHash> hashSet = new HashSet<ControlledHash>();
339        ArraySet<ControlledHash> arraySet = new ArraySet<ControlledHash>();
340
341        for (int i=0; i<OPS.length; i++) {
342            Integer oldHash;
343            Integer oldArray;
344            boolean hashChanged;
345            boolean arrayChanged;
346            ControlledHash key = KEYS[i] < 0 ? null : new ControlledHash(KEYS[i]);
347            switch (OPS[i]) {
348                case OP_ADD:
349                    Log.i("test", "Adding key: " + KEYS[i]);
350                    oldHash = hashMap.put(key, i);
351                    oldArray = arrayMap.put(key, i);
352                    hashChanged = hashSet.add(key);
353                    arrayChanged = arraySet.add(key);
354                    break;
355                case OP_REM:
356                    Log.i("test", "Removing key: " + KEYS[i]);
357                    oldHash = hashMap.remove(key);
358                    oldArray = arrayMap.remove(key);
359                    hashChanged = hashSet.remove(key);
360                    arrayChanged = arraySet.remove(key);
361                    break;
362                default:
363                    Log.e("test", "Bad operation " + OPS[i] + " @ " + i);
364                    return;
365            }
366            if (!compare(oldHash, oldArray)) {
367                Log.e("test", "Bad result: expected " + oldHash + ", got " + oldArray);
368                dump(hashMap, arrayMap);
369                return;
370            }
371            if (hashChanged != arrayChanged) {
372                Log.e("test", "Bad change: expected " + hashChanged + ", got " + arrayChanged);
373                dump(hashSet, arraySet);
374                return;
375            }
376            if (!validateArrayMap(arrayMap)) {
377                dump(hashMap, arrayMap);
378                return;
379            }
380            if (!compareMaps(hashMap, arrayMap)) {
381                dump(hashMap, arrayMap);
382                return;
383            }
384            if (!compareSets(hashSet, arraySet)) {
385                dump(hashSet, arraySet);
386                return;
387            }
388        }
389
390        arrayMap.put(new ControlledHash(50000), 100);
391        ControlledHash lookup = new ControlledHash(50000);
392        Iterator<ControlledHash> it = arrayMap.keySet().iterator();
393        while (it.hasNext()) {
394            if (it.next().equals(lookup)) {
395                it.remove();
396            }
397        }
398        if (arrayMap.containsKey(lookup)) {
399            Log.e("test", "Bad map iterator: didn't remove test key");
400            dump(hashMap, arrayMap);
401        }
402
403        arraySet.add(new ControlledHash(50000));
404        it = arraySet.iterator();
405        while (it.hasNext()) {
406            if (it.next().equals(lookup)) {
407                it.remove();
408            }
409        }
410        if (arraySet.contains(lookup)) {
411            Log.e("test", "Bad set iterator: didn't remove test key");
412            dump(hashSet, arraySet);
413        }
414
415        if (!equalsMapTest()) {
416            return;
417        }
418
419        if (!equalsSetTest()) {
420            return;
421        }
422
423        // map copy constructor test
424        ArrayMap newMap = new ArrayMap<Integer, String>();
425        for (int i = 0; i < 10; ++i) {
426            newMap.put(i, String.valueOf(i));
427        }
428        ArrayMap mapCopy = new ArrayMap(newMap);
429        if (!compare(mapCopy, newMap)) {
430            Log.e("test", "ArrayMap copy constructor failure: expected " +
431                    newMap + ", got " + mapCopy);
432            dump(newMap, mapCopy);
433            return;
434        }
435
436        // set copy constructor test
437        ArraySet newSet = new ArraySet<Integer>();
438        for (int i = 0; i < 10; ++i) {
439            newSet.add(i);
440        }
441        ArraySet setCopy = new ArraySet(newSet);
442        if (!compare(setCopy, newSet)) {
443            Log.e("test", "ArraySet copy constructor failure: expected " +
444                    newSet + ", got " + setCopy);
445            dump(newSet, setCopy);
446            return;
447        }
448
449        Log.e("test", "Test successful; printing final map.");
450        dump(hashMap, arrayMap);
451
452        Log.e("test", "Test successful; printing final set.");
453        dump(hashSet, arraySet);
454    }
455
456    private static boolean equalsMapTest() {
457        ArrayMap<Integer, String> map1 = new ArrayMap<Integer, String>();
458        ArrayMap<Integer, String> map2 = new ArrayMap<Integer, String>();
459        HashMap<Integer, String> map3 = new HashMap<Integer, String>();
460        if (!compare(map1, map2) || !compare(map1, map3) || !compare(map3, map2)) {
461            Log.e("test", "ArrayMap equals failure for empty maps " + map1 + ", " +
462                    map2 + ", " + map3);
463            return false;
464        }
465
466        for (int i = 0; i < 10; ++i) {
467            String value = String.valueOf(i);
468            map1.put(i, value);
469            map2.put(i, value);
470            map3.put(i, value);
471        }
472        if (!compare(map1, map2) || !compare(map1, map3) || !compare(map3, map2)) {
473            Log.e("test", "ArrayMap equals failure for populated maps " + map1 + ", " +
474                    map2 + ", " + map3);
475            return false;
476        }
477
478        map1.remove(0);
479        if (compare(map1, map2) || compare(map1, map3) || compare(map3, map1)) {
480            Log.e("test", "ArrayMap equals failure for map size " + map1 + ", " +
481                    map2 + ", " + map3);
482            return false;
483        }
484
485        map1.put(0, "-1");
486        if (compare(map1, map2) || compare(map1, map3) || compare(map3, map1)) {
487            Log.e("test", "ArrayMap equals failure for map contents " + map1 + ", " +
488                    map2 + ", " + map3);
489            return false;
490        }
491
492        return true;
493    }
494
495    private static boolean equalsSetTest() {
496        ArraySet<Integer> set1 = new ArraySet<Integer>();
497        ArraySet<Integer> set2 = new ArraySet<Integer>();
498        HashSet<Integer> set3 = new HashSet<Integer>();
499        if (!compare(set1, set2) || !compare(set1, set3) || !compare(set3, set2)) {
500            Log.e("test", "ArraySet equals failure for empty sets " + set1 + ", " +
501                    set2 + ", " + set3);
502            return false;
503        }
504
505        for (int i = 0; i < 10; ++i) {
506            set1.add(i);
507            set2.add(i);
508            set3.add(i);
509        }
510        if (!compare(set1, set2) || !compare(set1, set3) || !compare(set3, set2)) {
511            Log.e("test", "ArraySet equals failure for populated sets " + set1 + ", " +
512                    set2 + ", " + set3);
513            return false;
514        }
515
516        set1.remove(0);
517        if (compare(set1, set2) || compare(set1, set3) || compare(set3, set1)) {
518            Log.e("test", "ArraSet equals failure for set size " + set1 + ", " +
519                    set2 + ", " + set3);
520            return false;
521        }
522
523        set1.add(-1);
524        if (compare(set1, set2) || compare(set1, set3) || compare(set3, set1)) {
525            Log.e("test", "ArraySet equals failure for set contents " + set1 + ", " +
526                    set2 + ", " + set3);
527            return false;
528        }
529
530        return true;
531    }
532}
533