jdk/test/java/util/Map/InPlaceOpsCollisions.java
changeset 17939 bd750ec19d82
parent 13817 23f16430801a
child 19806 dda89341ee2d
equal deleted inserted replaced
17938:af1b01dfea42 17939:bd750ec19d82
       
     1 /*
       
     2  * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.
       
     8  *
       
     9  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    12  * version 2 for more details (a copy is included in the LICENSE file that
       
    13  * accompanied this code).
       
    14  *
       
    15  * You should have received a copy of the GNU General Public License version
       
    16  * 2 along with this work; if not, write to the Free Software Foundation,
       
    17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18  *
       
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    20  * or visit www.oracle.com if you need additional information or have any
       
    21  * questions.
       
    22  */
       
    23 
       
    24 /*
       
    25  * @test
       
    26  * @bug 8005698
       
    27  * @run main InPlaceOpsCollisions -shortrun
       
    28  * @run main/othervm -Djdk.map.randomseed=true InPlaceOpsCollisions -shortrun
       
    29  * @summary Ensure overrides of in-place operations in Maps behave well with lots of collisions.
       
    30  * @author Brent Christian
       
    31  */
       
    32 import java.util.*;
       
    33 import java.util.function.*;
       
    34 
       
    35 public class InPlaceOpsCollisions {
       
    36 
       
    37     /**
       
    38      * Number of elements per map.
       
    39      */
       
    40     private static final int TEST_SIZE = 5000;
       
    41 
       
    42     final static class HashableInteger implements Comparable<HashableInteger> {
       
    43 
       
    44         final int value;
       
    45         final int hashmask; //yes duplication
       
    46 
       
    47         HashableInteger(int value, int hashmask) {
       
    48             this.value = value;
       
    49             this.hashmask = hashmask;
       
    50         }
       
    51 
       
    52         @Override
       
    53         public boolean equals(Object obj) {
       
    54             if (obj instanceof HashableInteger) {
       
    55                 HashableInteger other = (HashableInteger) obj;
       
    56 
       
    57                 return other.value == value;
       
    58             }
       
    59 
       
    60             return false;
       
    61         }
       
    62 
       
    63         @Override
       
    64         public int hashCode() {
       
    65             return value % hashmask;
       
    66         }
       
    67 
       
    68         @Override
       
    69         public int compareTo(HashableInteger o) {
       
    70             return value - o.value;
       
    71         }
       
    72 
       
    73         @Override
       
    74         public String toString() {
       
    75             return Integer.toString(value);
       
    76         }
       
    77     }
       
    78 
       
    79     static HashableInteger EXTRA_INT_VAL;
       
    80     static String EXTRA_STRING_VAL;
       
    81 
       
    82     private static Object[][] makeTestData(int size) {
       
    83         HashableInteger UNIQUE_OBJECTS[] = new HashableInteger[size];
       
    84         HashableInteger COLLIDING_OBJECTS[] = new HashableInteger[size];
       
    85         String UNIQUE_STRINGS[] = new String[size];
       
    86         String COLLIDING_STRINGS[] = new String[size];
       
    87 
       
    88         for (int i = 0; i < size; i++) {
       
    89             UNIQUE_OBJECTS[i] = new HashableInteger(i, Integer.MAX_VALUE);
       
    90             COLLIDING_OBJECTS[i] = new HashableInteger(i, 10);
       
    91             UNIQUE_STRINGS[i] = unhash(i);
       
    92             COLLIDING_STRINGS[i] = (0 == i % 2)
       
    93                     ? UNIQUE_STRINGS[i / 2]
       
    94                     : "\u0000\u0000\u0000\u0000\u0000" + COLLIDING_STRINGS[i - 1];
       
    95         }
       
    96         EXTRA_INT_VAL = new HashableInteger(size, Integer.MAX_VALUE);
       
    97         EXTRA_STRING_VAL = new String ("Extra Value");
       
    98 
       
    99      return new Object[][] {
       
   100             new Object[]{"Unique Objects", UNIQUE_OBJECTS},
       
   101             new Object[]{"Colliding Objects", COLLIDING_OBJECTS},
       
   102             new Object[]{"Unique Strings", UNIQUE_STRINGS},
       
   103             new Object[]{"Colliding Strings", COLLIDING_STRINGS}
       
   104         };
       
   105     }
       
   106 
       
   107     /**
       
   108      * Returns a string with a hash equal to the argument.
       
   109      *
       
   110      * @return string with a hash equal to the argument.
       
   111      */
       
   112     public static String unhash(int target) {
       
   113         StringBuilder answer = new StringBuilder();
       
   114         if (target < 0) {
       
   115             // String with hash of Integer.MIN_VALUE, 0x80000000
       
   116             answer.append("\\u0915\\u0009\\u001e\\u000c\\u0002");
       
   117 
       
   118             if (target == Integer.MIN_VALUE) {
       
   119                 return answer.toString();
       
   120             }
       
   121             // Find target without sign bit set
       
   122             target = target & Integer.MAX_VALUE;
       
   123         }
       
   124 
       
   125         unhash0(answer, target);
       
   126         return answer.toString();
       
   127     }
       
   128 
       
   129     private static void unhash0(StringBuilder partial, int target) {
       
   130         int div = target / 31;
       
   131         int rem = target % 31;
       
   132 
       
   133         if (div <= Character.MAX_VALUE) {
       
   134             if (div != 0) {
       
   135                 partial.append((char) div);
       
   136             }
       
   137             partial.append((char) rem);
       
   138         } else {
       
   139             unhash0(partial, div);
       
   140             partial.append((char) rem);
       
   141         }
       
   142     }
       
   143 
       
   144     private static void realMain(String[] args) throws Throwable {
       
   145         boolean shortRun = args.length > 0 && args[0].equals("-shortrun");
       
   146 
       
   147         Object[][] mapKeys = makeTestData(shortRun ? (TEST_SIZE / 2) : TEST_SIZE);
       
   148 
       
   149         // loop through data sets
       
   150         for (Object[] keys_desc : mapKeys) {
       
   151             Map<Object, Object>[] maps = (Map<Object, Object>[]) new Map[]{
       
   152                         new HashMap<>(),
       
   153                         new LinkedHashMap<>(),
       
   154                     };
       
   155 
       
   156             // for each map type.
       
   157             for (Map<Object, Object> map : maps) {
       
   158                 String desc = (String) keys_desc[0];
       
   159                 Object[] keys = (Object[]) keys_desc[1];
       
   160                 try {
       
   161                     testInPlaceOps(map, desc, keys);
       
   162                 } catch(Exception all) {
       
   163                     unexpected("Failed for " + map.getClass().getName() + " with " + desc, all);
       
   164                 }
       
   165             }
       
   166         }
       
   167     }
       
   168 
       
   169     private static <T> void testInsertion(Map<T, T> map, String keys_desc, T[] keys) {
       
   170         check("map empty", (map.size() == 0) && map.isEmpty());
       
   171 
       
   172         for (int i = 0; i < keys.length; i++) {
       
   173             check(String.format("insertion: map expected size m%d != i%d", map.size(), i),
       
   174                     map.size() == i);
       
   175             check(String.format("insertion: put(%s[%d])", keys_desc, i), null == map.put(keys[i], keys[i]));
       
   176             check(String.format("insertion: containsKey(%s[%d])", keys_desc, i), map.containsKey(keys[i]));
       
   177             check(String.format("insertion: containsValue(%s[%d])", keys_desc, i), map.containsValue(keys[i]));
       
   178         }
       
   179 
       
   180         check(String.format("map expected size m%d != k%d", map.size(), keys.length),
       
   181                 map.size() == keys.length);
       
   182     }
       
   183 
       
   184 
       
   185     private static <T> void testInPlaceOps(Map<T, T> map, String keys_desc, T[] keys) {
       
   186         System.out.println(map.getClass() + " : " + keys_desc + ", testInPlaceOps");
       
   187         System.out.flush();
       
   188 
       
   189         testInsertion(map, keys_desc, keys);
       
   190         testPutIfAbsent(map, keys_desc, keys);
       
   191 
       
   192         map.clear();
       
   193         testInsertion(map, keys_desc, keys);
       
   194         testRemoveMapping(map, keys_desc, keys);
       
   195 
       
   196         map.clear();
       
   197         testInsertion(map, keys_desc, keys);
       
   198         testReplaceOldValue(map, keys_desc, keys);
       
   199 
       
   200         map.clear();
       
   201         testInsertion(map, keys_desc, keys);
       
   202         testReplaceIfMapped(map, keys_desc, keys);
       
   203 
       
   204         map.clear();
       
   205         testInsertion(map, keys_desc, keys);
       
   206         testComputeIfAbsent(map, keys_desc, keys, (k) -> getExtraVal(keys[0]));
       
   207 
       
   208         map.clear();
       
   209         testInsertion(map, keys_desc, keys);
       
   210         testComputeIfAbsent(map, keys_desc, keys, (k) -> null);
       
   211 
       
   212         map.clear();
       
   213         testInsertion(map, keys_desc, keys);
       
   214         testComputeIfPresent(map, keys_desc, keys, (k, v) -> getExtraVal(keys[0]));
       
   215 
       
   216         map.clear();
       
   217         testInsertion(map, keys_desc, keys);
       
   218         testComputeIfPresent(map, keys_desc, keys, (k, v) -> null);
       
   219 
       
   220         if (!keys_desc.contains("Strings")) { // avoid parseInt() number format error
       
   221             map.clear();
       
   222             testInsertion(map, keys_desc, keys);
       
   223             testComputeNonNull(map, keys_desc, keys);
       
   224         }
       
   225 
       
   226         map.clear();
       
   227         testInsertion(map, keys_desc, keys);
       
   228         testComputeNull(map, keys_desc, keys);
       
   229 
       
   230         if (!keys_desc.contains("Strings")) { // avoid parseInt() number format error
       
   231             map.clear();
       
   232             testInsertion(map, keys_desc, keys);
       
   233             testMergeNonNull(map, keys_desc, keys);
       
   234         }
       
   235 
       
   236         map.clear();
       
   237         testInsertion(map, keys_desc, keys);
       
   238         testMergeNull(map, keys_desc, keys);
       
   239     }
       
   240 
       
   241 
       
   242 
       
   243     private static <T> void testPutIfAbsent(Map<T, T> map, String keys_desc, T[] keys) {
       
   244         T extraVal = getExtraVal(keys[0]);
       
   245         T retVal;
       
   246         removeOddKeys(map, keys);
       
   247         for (int i = 0; i < keys.length; i++) {
       
   248             retVal = map.putIfAbsent(keys[i], extraVal);
       
   249             if (i % 2 == 0) { // even: not absent, not put
       
   250                 check(String.format("putIfAbsent: (%s[%d]) retVal", keys_desc, i), retVal == keys[i]);
       
   251                 check(String.format("putIfAbsent: get(%s[%d])", keys_desc, i), keys[i] == map.get(keys[i]));
       
   252                 check(String.format("putIfAbsent: containsValue(%s[%d])", keys_desc, i), map.containsValue(keys[i]));
       
   253             } else { // odd: absent, was put
       
   254                 check(String.format("putIfAbsent: (%s[%d]) retVal", keys_desc, i), retVal == null);
       
   255                 check(String.format("putIfAbsent: get(%s[%d])", keys_desc, i), extraVal == map.get(keys[i]));
       
   256                 check(String.format("putIfAbsent: !containsValue(%s[%d])", keys_desc, i), !map.containsValue(keys[i]));
       
   257             }
       
   258             check(String.format("insertion: containsKey(%s[%d])", keys_desc, i), map.containsKey(keys[i]));
       
   259         }
       
   260         check(String.format("map expected size m%d != k%d", map.size(), keys.length),
       
   261                 map.size() == keys.length);
       
   262     }
       
   263 
       
   264     private static <T> void testRemoveMapping(Map<T, T> map, String keys_desc, T[] keys) {
       
   265         T extraVal = getExtraVal(keys[0]);
       
   266         boolean removed;
       
   267         int removes = 0;
       
   268         remapOddKeys(map, keys);
       
   269         for (int i = 0; i < keys.length; i++) {
       
   270             removed = map.remove(keys[i], keys[i]);
       
   271             if (i % 2 == 0) { // even: original mapping, should be removed
       
   272                 check(String.format("removeMapping: retVal(%s[%d])", keys_desc, i), removed);
       
   273                 check(String.format("removeMapping: get(%s[%d])", keys_desc, i), null == map.get(keys[i]));
       
   274                 check(String.format("removeMapping: !containsKey(%s[%d])", keys_desc, i), !map.containsKey(keys[i]));
       
   275                 check(String.format("removeMapping: !containsValue(%s[%d])", keys_desc, i), !map.containsValue(keys[i]));
       
   276                 removes++;
       
   277             } else { // odd: new mapping, not removed
       
   278                 check(String.format("removeMapping: retVal(%s[%d])", keys_desc, i), !removed);
       
   279                 check(String.format("removeMapping: get(%s[%d])", keys_desc, i), extraVal == map.get(keys[i]));
       
   280                 check(String.format("removeMapping: containsKey(%s[%d])", keys_desc, i), map.containsKey(keys[i]));
       
   281                 check(String.format("removeMapping: containsValue(%s[%d])", keys_desc, i), map.containsValue(extraVal));
       
   282             }
       
   283         }
       
   284         check(String.format("map expected size m%d != k%d", map.size(), keys.length - removes),
       
   285                 map.size() == keys.length - removes);
       
   286     }
       
   287 
       
   288     private static <T> void testReplaceOldValue(Map<T, T> map, String keys_desc, T[] keys) {
       
   289         // remap odds to extraVal
       
   290         // call replace to replace for extraVal, for all keys
       
   291         // check that all keys map to value from keys array
       
   292         T extraVal = getExtraVal(keys[0]);
       
   293         boolean replaced;
       
   294         remapOddKeys(map, keys);
       
   295 
       
   296         for (int i = 0; i < keys.length; i++) {
       
   297             replaced = map.replace(keys[i], extraVal, keys[i]);
       
   298             if (i % 2 == 0) { // even: original mapping, should not be replaced
       
   299                 check(String.format("replaceOldValue: retVal(%s[%d])", keys_desc, i), !replaced);
       
   300             } else { // odd: new mapping, should be replaced
       
   301                 check(String.format("replaceOldValue: get(%s[%d])", keys_desc, i), replaced);
       
   302             }
       
   303             check(String.format("replaceOldValue: get(%s[%d])", keys_desc, i), keys[i] == map.get(keys[i]));
       
   304             check(String.format("replaceOldValue: containsKey(%s[%d])", keys_desc, i), map.containsKey(keys[i]));
       
   305             check(String.format("replaceOldValue: containsValue(%s[%d])", keys_desc, i), map.containsValue(keys[i]));
       
   306 //            removes++;
       
   307         }
       
   308         check(String.format("replaceOldValue: !containsValue(%s[%s])", keys_desc, extraVal.toString()), !map.containsValue(extraVal));
       
   309         check(String.format("map expected size m%d != k%d", map.size(), keys.length),
       
   310                 map.size() == keys.length);
       
   311     }
       
   312 
       
   313     // TODO: Test case for key mapped to null value
       
   314     private static <T> void testReplaceIfMapped(Map<T, T> map, String keys_desc, T[] keys) {
       
   315         // remove odd keys
       
   316         // call replace for all keys[]
       
   317         // odd keys should remain absent, even keys should be mapped to EXTRA, no value from keys[] should be in map
       
   318         T extraVal = getExtraVal(keys[0]);
       
   319         int expectedSize1 = 0;
       
   320         removeOddKeys(map, keys);
       
   321         int expectedSize2 = map.size();
       
   322 
       
   323         for (int i = 0; i < keys.length; i++) {
       
   324             T retVal = map.replace(keys[i], extraVal);
       
   325             if (i % 2 == 0) { // even: still in map, should be replaced
       
   326                 check(String.format("replaceIfMapped: retVal(%s[%d])", keys_desc, i), retVal == keys[i]);
       
   327                 check(String.format("replaceIfMapped: get(%s[%d])", keys_desc, i), extraVal == map.get(keys[i]));
       
   328                 check(String.format("replaceIfMapped: containsKey(%s[%d])", keys_desc, i), map.containsKey(keys[i]));
       
   329                 expectedSize1++;
       
   330             } else { // odd: was removed, should not be replaced
       
   331                 check(String.format("replaceIfMapped: retVal(%s[%d])", keys_desc, i), retVal == null);
       
   332                 check(String.format("replaceIfMapped: get(%s[%d])", keys_desc, i), null == map.get(keys[i]));
       
   333                 check(String.format("replaceIfMapped: containsKey(%s[%d])", keys_desc, i), !map.containsKey(keys[i]));
       
   334             }
       
   335             check(String.format("replaceIfMapped: !containsValue(%s[%d])", keys_desc, i), !map.containsValue(keys[i]));
       
   336         }
       
   337         check(String.format("replaceIfMapped: containsValue(%s[%s])", keys_desc, extraVal.toString()), map.containsValue(extraVal));
       
   338         check(String.format("map expected size#1 m%d != k%d", map.size(), expectedSize1),
       
   339                 map.size() == expectedSize1);
       
   340         check(String.format("map expected size#2 m%d != k%d", map.size(), expectedSize2),
       
   341                 map.size() == expectedSize2);
       
   342 
       
   343     }
       
   344 
       
   345     private static <T> void testComputeIfAbsent(Map<T, T> map, String keys_desc, T[] keys,
       
   346                                                 Function<T,T> mappingFunction) {
       
   347         // remove a third of the keys
       
   348         // call computeIfAbsent for all keys, func returns EXTRA
       
   349         // check that removed keys now -> EXTRA, other keys -> original val
       
   350         T expectedVal = mappingFunction.apply(keys[0]);
       
   351         T retVal;
       
   352         int expectedSize = 0;
       
   353         removeThirdKeys(map, keys);
       
   354         for (int i = 0; i < keys.length; i++) {
       
   355             retVal = map.computeIfAbsent(keys[i], mappingFunction);
       
   356             if (i % 3 != 2) { // key present, not computed
       
   357                 check(String.format("computeIfAbsent: (%s[%d]) retVal", keys_desc, i), retVal == keys[i]);
       
   358                 check(String.format("computeIfAbsent: get(%s[%d])", keys_desc, i), keys[i] == map.get(keys[i]));
       
   359                 check(String.format("computeIfAbsent: containsValue(%s[%d])", keys_desc, i), map.containsValue(keys[i]));
       
   360                 check(String.format("insertion: containsKey(%s[%d])", keys_desc, i), map.containsKey(keys[i]));
       
   361                 expectedSize++;
       
   362             } else { // key absent, computed unless function return null
       
   363                 check(String.format("computeIfAbsent: (%s[%d]) retVal", keys_desc, i), retVal == expectedVal);
       
   364                 check(String.format("computeIfAbsent: get(%s[%d])", keys_desc, i), expectedVal == map.get(keys[i]));
       
   365                 check(String.format("computeIfAbsent: !containsValue(%s[%d])", keys_desc, i), !map.containsValue(keys[i]));
       
   366                 // mapping should not be added if function returns null
       
   367                 check(String.format("insertion: containsKey(%s[%d])", keys_desc, i), map.containsKey(keys[i]) != (expectedVal == null));
       
   368                 if (expectedVal != null) { expectedSize++; }
       
   369             }
       
   370         }
       
   371         if (expectedVal != null) {
       
   372             check(String.format("computeIfAbsent: containsValue(%s[%s])", keys_desc, expectedVal), map.containsValue(expectedVal));
       
   373         }
       
   374         check(String.format("map expected size m%d != k%d", map.size(), expectedSize),
       
   375                 map.size() == expectedSize);
       
   376     }
       
   377 
       
   378     private static <T> void testComputeIfPresent(Map<T, T> map, String keys_desc, T[] keys,
       
   379                                                 BiFunction<T,T,T> mappingFunction) {
       
   380         // remove a third of the keys
       
   381         // call testComputeIfPresent for all keys[]
       
   382         // removed keys should remain absent, even keys should be mapped to $RESULT
       
   383         // no value from keys[] should be in map
       
   384         T funcResult = mappingFunction.apply(keys[0], keys[0]);
       
   385         int expectedSize1 = 0;
       
   386         removeThirdKeys(map, keys);
       
   387 
       
   388         for (int i = 0; i < keys.length; i++) {
       
   389             T retVal = map.computeIfPresent(keys[i], mappingFunction);
       
   390             if (i % 3 != 2) { // key present
       
   391                 if (funcResult == null) { // was removed
       
   392                     check(String.format("replaceIfMapped: containsKey(%s[%d])", keys_desc, i), !map.containsKey(keys[i]));
       
   393                 } else { // value was replaced
       
   394                     check(String.format("replaceIfMapped: containsKey(%s[%d])", keys_desc, i), map.containsKey(keys[i]));
       
   395                     expectedSize1++;
       
   396                 }
       
   397                 check(String.format("computeIfPresent: retVal(%s[%s])", keys_desc, i), retVal == funcResult);
       
   398                 check(String.format("replaceIfMapped: get(%s[%d])", keys_desc, i), funcResult == map.get(keys[i]));
       
   399 
       
   400             } else { // odd: was removed, should not be replaced
       
   401                 check(String.format("replaceIfMapped: retVal(%s[%d])", keys_desc, i), retVal == null);
       
   402                 check(String.format("replaceIfMapped: get(%s[%d])", keys_desc, i), null == map.get(keys[i]));
       
   403                 check(String.format("replaceIfMapped: containsKey(%s[%d])", keys_desc, i), !map.containsKey(keys[i]));
       
   404             }
       
   405             check(String.format("replaceIfMapped: !containsValue(%s[%d])", keys_desc, i), !map.containsValue(keys[i]));
       
   406         }
       
   407         check(String.format("map expected size#1 m%d != k%d", map.size(), expectedSize1),
       
   408                 map.size() == expectedSize1);
       
   409     }
       
   410 
       
   411     private static <T> void testComputeNonNull(Map<T, T> map, String keys_desc, T[] keys) {
       
   412         // remove a third of the keys
       
   413         // call compute() for all keys[]
       
   414         // all keys should be present: removed keys -> EXTRA, others to k-1
       
   415         BiFunction<T,T,T> mappingFunction = (k, v) -> {
       
   416                 if (v == null) {
       
   417                     return getExtraVal(keys[0]);
       
   418                 } else {
       
   419                     return keys[Integer.parseInt(k.toString()) - 1];
       
   420                 }
       
   421             };
       
   422         T extraVal = getExtraVal(keys[0]);
       
   423         removeThirdKeys(map, keys);
       
   424         for (int i = 1; i < keys.length; i++) {
       
   425             T retVal = map.compute(keys[i], mappingFunction);
       
   426             if (i % 3 != 2) { // key present, should be mapped to k-1
       
   427                 check(String.format("compute: retVal(%s[%d])", keys_desc, i), retVal == keys[i-1]);
       
   428                 check(String.format("compute: get(%s[%d])", keys_desc, i), keys[i-1] == map.get(keys[i]));
       
   429             } else { // odd: was removed, should be replaced with EXTRA
       
   430                 check(String.format("compute: retVal(%s[%d])", keys_desc, i), retVal == extraVal);
       
   431                 check(String.format("compute: get(%s[%d])", keys_desc, i), extraVal == map.get(keys[i]));
       
   432             }
       
   433             check(String.format("compute: containsKey(%s[%d])", keys_desc, i), map.containsKey(keys[i]));
       
   434         }
       
   435         check(String.format("map expected size#1 m%d != k%d", map.size(), keys.length),
       
   436                 map.size() == keys.length);
       
   437         check(String.format("compute: containsValue(%s[%s])", keys_desc, extraVal.toString()), map.containsValue(extraVal));
       
   438         check(String.format("compute: !containsValue(%s,[null])", keys_desc), !map.containsValue(null));
       
   439     }
       
   440 
       
   441     private static <T> void testComputeNull(Map<T, T> map, String keys_desc, T[] keys) {
       
   442         // remove a third of the keys
       
   443         // call compute() for all keys[]
       
   444         // removed keys should -> EXTRA
       
   445         // for other keys: func returns null, should have no mapping
       
   446         BiFunction<T,T,T> mappingFunction = (k, v) -> {
       
   447             // if absent/null -> EXTRA
       
   448             // if present -> null
       
   449             if (v == null) {
       
   450                 return getExtraVal(keys[0]);
       
   451             } else {
       
   452                 return null;
       
   453             }
       
   454         };
       
   455         T extraVal = getExtraVal(keys[0]);
       
   456         int expectedSize = 0;
       
   457         removeThirdKeys(map, keys);
       
   458         for (int i = 0; i < keys.length; i++) {
       
   459             T retVal = map.compute(keys[i], mappingFunction);
       
   460             if (i % 3 != 2) { // key present, func returned null, should be absent from map
       
   461                 check(String.format("compute: retVal(%s[%d])", keys_desc, i), retVal == null);
       
   462                 check(String.format("compute: get(%s[%d])", keys_desc, i), null == map.get(keys[i]));
       
   463                 check(String.format("compute: containsKey(%s[%d])", keys_desc, i), !map.containsKey(keys[i]));
       
   464                 check(String.format("compute: containsValue(%s[%s])", keys_desc, i), !map.containsValue(keys[i]));
       
   465             } else { // odd: was removed, should now be mapped to EXTRA
       
   466                 check(String.format("compute: retVal(%s[%d])", keys_desc, i), retVal == extraVal);
       
   467                 check(String.format("compute: get(%s[%d])", keys_desc, i), extraVal == map.get(keys[i]));
       
   468                 check(String.format("compute: containsKey(%s[%d])", keys_desc, i), map.containsKey(keys[i]));
       
   469                 expectedSize++;
       
   470             }
       
   471         }
       
   472         check(String.format("compute: containsValue(%s[%s])", keys_desc, extraVal.toString()), map.containsValue(extraVal));
       
   473         check(String.format("map expected size#1 m%d != k%d", map.size(), expectedSize),
       
   474                 map.size() == expectedSize);
       
   475     }
       
   476 
       
   477     private static <T> void testMergeNonNull(Map<T, T> map, String keys_desc, T[] keys) {
       
   478         // remove a third of the keys
       
   479         // call merge() for all keys[]
       
   480         // all keys should be present: removed keys now -> EXTRA, other keys -> k-1
       
   481 
       
   482         // Map to preceding key
       
   483         BiFunction<T,T,T> mappingFunction = (k, v) -> keys[Integer.parseInt(k.toString()) - 1];
       
   484         T extraVal = getExtraVal(keys[0]);
       
   485         removeThirdKeys(map, keys);
       
   486         for (int i = 1; i < keys.length; i++) {
       
   487             T retVal = map.merge(keys[i], extraVal, mappingFunction);
       
   488             if (i % 3 != 2) { // key present, should be mapped to k-1
       
   489                 check(String.format("compute: retVal(%s[%d])", keys_desc, i), retVal == keys[i-1]);
       
   490                 check(String.format("compute: get(%s[%d])", keys_desc, i), keys[i-1] == map.get(keys[i]));
       
   491             } else { // odd: was removed, should be replaced with EXTRA
       
   492                 check(String.format("compute: retVal(%s[%d])", keys_desc, i), retVal == extraVal);
       
   493                 check(String.format("compute: get(%s[%d])", keys_desc, i), extraVal == map.get(keys[i]));
       
   494             }
       
   495             check(String.format("compute: containsKey(%s[%d])", keys_desc, i), map.containsKey(keys[i]));
       
   496         }
       
   497 
       
   498         check(String.format("map expected size#1 m%d != k%d", map.size(), keys.length),
       
   499                 map.size() == keys.length);
       
   500         check(String.format("compute: containsValue(%s[%s])", keys_desc, extraVal.toString()), map.containsValue(extraVal));
       
   501         check(String.format("compute: !containsValue(%s,[null])", keys_desc), !map.containsValue(null));
       
   502 
       
   503     }
       
   504 
       
   505     private static <T> void testMergeNull(Map<T, T> map, String keys_desc, T[] keys) {
       
   506         // remove a third of the keys
       
   507         // call merge() for all keys[]
       
   508         // result: removed keys -> EXTRA, other keys absent
       
   509 
       
   510         BiFunction<T,T,T> mappingFunction = (k, v) -> null;
       
   511         T extraVal = getExtraVal(keys[0]);
       
   512         int expectedSize = 0;
       
   513         removeThirdKeys(map, keys);
       
   514         for (int i = 0; i < keys.length; i++) {
       
   515             T retVal = map.merge(keys[i], extraVal, mappingFunction);
       
   516             if (i % 3 != 2) { // key present, func returned null, should be absent from map
       
   517                 check(String.format("compute: retVal(%s[%d])", keys_desc, i), retVal == null);
       
   518                 check(String.format("compute: get(%s[%d])", keys_desc, i), null == map.get(keys[i]));
       
   519                 check(String.format("compute: containsKey(%s[%d])", keys_desc, i), !map.containsKey(keys[i]));
       
   520             } else { // odd: was removed, should now be mapped to EXTRA
       
   521                 check(String.format("compute: retVal(%s[%d])", keys_desc, i), retVal == extraVal);
       
   522                 check(String.format("compute: get(%s[%d])", keys_desc, i), extraVal == map.get(keys[i]));
       
   523                 check(String.format("compute: containsKey(%s[%d])", keys_desc, i), map.containsKey(keys[i]));
       
   524                 expectedSize++;
       
   525             }
       
   526             check(String.format("compute: containsValue(%s[%s])", keys_desc, i), !map.containsValue(keys[i]));
       
   527         }
       
   528         check(String.format("compute: containsValue(%s[%s])", keys_desc, extraVal.toString()), map.containsValue(extraVal));
       
   529         check(String.format("map expected size#1 m%d != k%d", map.size(), expectedSize),
       
   530                 map.size() == expectedSize);
       
   531     }
       
   532 
       
   533     /*
       
   534      * Return the EXTRA val for the key type being used
       
   535      */
       
   536     private static <T> T getExtraVal(T key) {
       
   537         if (key instanceof HashableInteger) {
       
   538             return (T)EXTRA_INT_VAL;
       
   539         } else {
       
   540             return (T)EXTRA_STRING_VAL;
       
   541         }
       
   542     }
       
   543 
       
   544     /*
       
   545      * Remove half of the keys
       
   546      */
       
   547     private static <T> void removeOddKeys(Map<T, T> map, /*String keys_desc, */ T[] keys) {
       
   548         int removes = 0;
       
   549         for (int i = 0; i < keys.length; i++) {
       
   550             if (i % 2 != 0) {
       
   551                 map.remove(keys[i]);
       
   552                 removes++;
       
   553             }
       
   554         }
       
   555         check(String.format("map expected size m%d != k%d", map.size(), keys.length - removes),
       
   556                 map.size() == keys.length - removes);
       
   557     }
       
   558 
       
   559     /*
       
   560      * Remove every third key
       
   561      * This will hopefully leave some removed keys in TreeBins for, e.g., computeIfAbsent
       
   562      * w/ a func that returns null.
       
   563      *
       
   564      * TODO: consider using this in other tests (and maybe adding a remapThirdKeys)
       
   565      */
       
   566     private static <T> void removeThirdKeys(Map<T, T> map, /*String keys_desc, */ T[] keys) {
       
   567         int removes = 0;
       
   568         for (int i = 0; i < keys.length; i++) {
       
   569             if (i % 3 == 2) {
       
   570                 map.remove(keys[i]);
       
   571                 removes++;
       
   572             }
       
   573         }
       
   574         check(String.format("map expected size m%d != k%d", map.size(), keys.length - removes),
       
   575                 map.size() == keys.length - removes);
       
   576     }
       
   577 
       
   578     /*
       
   579      * Re-map the odd-numbered keys to map to the EXTRA value
       
   580      */
       
   581     private static <T> void remapOddKeys(Map<T, T> map, /*String keys_desc, */ T[] keys) {
       
   582         T extraVal = getExtraVal(keys[0]);
       
   583         for (int i = 0; i < keys.length; i++) {
       
   584             if (i % 2 != 0) {
       
   585                 map.put(keys[i], extraVal);
       
   586             }
       
   587         }
       
   588     }
       
   589 
       
   590     //--------------------- Infrastructure ---------------------------
       
   591     static volatile int passed = 0, failed = 0;
       
   592 
       
   593     static void pass() {
       
   594         passed++;
       
   595     }
       
   596 
       
   597     static void fail() {
       
   598         failed++;
       
   599         (new Error("Failure")).printStackTrace(System.err);
       
   600     }
       
   601 
       
   602     static void fail(String msg) {
       
   603         failed++;
       
   604         (new Error("Failure: " + msg)).printStackTrace(System.err);
       
   605     }
       
   606 
       
   607     static void abort() {
       
   608         fail();
       
   609         System.exit(1);
       
   610     }
       
   611 
       
   612     static void abort(String msg) {
       
   613         fail(msg);
       
   614         System.exit(1);
       
   615     }
       
   616 
       
   617     static void unexpected(String msg, Throwable t) {
       
   618         System.err.println("Unexpected: " + msg);
       
   619         unexpected(t);
       
   620     }
       
   621 
       
   622     static void unexpected(Throwable t) {
       
   623         failed++;
       
   624         t.printStackTrace(System.err);
       
   625     }
       
   626 
       
   627     static void check(boolean cond) {
       
   628         if (cond) {
       
   629             pass();
       
   630         } else {
       
   631             fail();
       
   632         }
       
   633     }
       
   634 
       
   635     static void check(String desc, boolean cond) {
       
   636         if (cond) {
       
   637             pass();
       
   638         } else {
       
   639             fail(desc);
       
   640         }
       
   641     }
       
   642 
       
   643     static void equal(Object x, Object y) {
       
   644         if (Objects.equals(x, y)) {
       
   645             pass();
       
   646         } else {
       
   647             fail(x + " not equal to " + y);
       
   648         }
       
   649     }
       
   650 
       
   651     public static void main(String[] args) throws Throwable {
       
   652         Thread.currentThread().setName(Collisions.class.getName());
       
   653 //        Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
       
   654         try {
       
   655             realMain(args);
       
   656         } catch (Throwable t) {
       
   657             unexpected(t);
       
   658         }
       
   659 
       
   660         System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed);
       
   661         if (failed > 0) {
       
   662             throw new Error("Some tests failed");
       
   663         }
       
   664     }
       
   665 }