| /* |
| * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| import org.testng.annotations.DataProvider; |
| import org.testng.annotations.Test; |
| |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.function.BiConsumer; |
| import java.util.stream.Collector; |
| import java.util.stream.Collectors; |
| import java.util.stream.IntStream; |
| |
| import static org.testng.Assert.assertEquals; |
| |
| /* |
| * @test |
| * @bug 8023463 |
| * @summary Test the case where a bin is treeified and vice verser |
| * @run testng MapBinToFromTreeTest |
| */ |
| |
| @Test |
| public class MapBinToFromTreeTest { |
| |
| // Initial capacity of map |
| // Should be >= the map capacity for treeifiying, see HashMap/ConcurrentMap.MIN_TREEIFY_CAPACITY |
| static final int INITIAL_CAPACITY = 64; |
| |
| // Maximum size of map |
| // Should be > the treeify threshold, see HashMap/ConcurrentMap.TREEIFY_THRESHOLD |
| // Should be > INITIAL_CAPACITY to ensure resize occurs |
| static final int SIZE = 256; |
| |
| // Load factor of map |
| // A value 1.0 will ensure that a new threshold == capacity |
| static final float LOAD_FACTOR = 1.0f; |
| |
| @DataProvider(name = "maps") |
| static Object[][] mapProvider() { |
| return new Object[][] { |
| // Pass in the class name as a description for test reporting |
| // purposes |
| { HashMap.class.getName(), new HashMap(INITIAL_CAPACITY, LOAD_FACTOR) }, |
| { LinkedHashMap.class.getName(), new LinkedHashMap(INITIAL_CAPACITY, LOAD_FACTOR) }, |
| { ConcurrentHashMap.class.getName(), new ConcurrentHashMap(INITIAL_CAPACITY, LOAD_FACTOR) }, |
| }; |
| } |
| |
| @Test(dataProvider = "maps") |
| public void testPutThenGet(String d, Map<HashCodeInteger, Integer> m) { |
| put(SIZE, m, (i, s) -> { |
| for (int j = 0; j < s; j++) { |
| assertEquals(m.get(new HashCodeInteger(j)).intValue(), j, |
| String.format("Map.get(%d)", j)); |
| } |
| }); |
| } |
| |
| @Test(dataProvider = "maps") |
| public void testPutThenTraverse(String d, Map<HashCodeInteger, Integer> m) { |
| Collector<Integer, ?, ? extends Collection<Integer>> c = getCollector(m); |
| |
| put(SIZE, m, (i, s) -> { |
| // Note that it is OK to collect to a Set (HashSet) as long as |
| // integer values are used since these tests only check for |
| // collisions and other tests will verify more general functionality |
| Collection<Integer> actual = m.keySet().stream().map(e -> e.value).collect(c); |
| Collection<Integer> expected = IntStream.range(0, s).boxed().collect(c); |
| assertEquals(actual, expected, "Map.keySet()"); |
| }); |
| } |
| |
| @Test(dataProvider = "maps") |
| public void testRemoveThenGet(String d, Map<HashCodeInteger, Integer> m) { |
| put(SIZE, m, (i, s) -> { }); |
| |
| remove(m, (i, s) -> { |
| for (int j = i + 1; j < SIZE; j++) { |
| assertEquals(m.get(new HashCodeInteger(j)).intValue(), j, |
| String.format("Map.get(%d)", j)); |
| } |
| }); |
| } |
| |
| @Test(dataProvider = "maps") |
| public void testRemoveThenTraverse(String d, Map<HashCodeInteger, Integer> m) { |
| put(SIZE, m, (i, s) -> { }); |
| |
| Collector<Integer, ?, ? extends Collection<Integer>> c = getCollector(m); |
| |
| remove(m, (i, s) -> { |
| Collection<Integer> actual = m.keySet().stream().map(e -> e.value).collect(c); |
| Collection<Integer> expected = IntStream.range(i + 1, SIZE).boxed().collect(c); |
| assertEquals(actual, expected, "Map.keySet()"); |
| }); |
| } |
| |
| @Test(dataProvider = "maps") |
| public void testUntreeifyOnResizeWithGet(String d, Map<HashCodeInteger, Integer> m) { |
| // Fill the map with 64 entries grouped into 4 buckets |
| put(INITIAL_CAPACITY, m, (i, s) -> { }); |
| |
| for (int i = INITIAL_CAPACITY; i < SIZE; i++) { |
| // Add further entries in the 0'th bucket so as not to disturb |
| // other buckets, entries of which may be distributed and/or |
| // the bucket untreeified on resize |
| m.put(new HashCodeInteger(i, 0), i); |
| |
| for (int j = 0; j < INITIAL_CAPACITY; j++) { |
| assertEquals(m.get(new HashCodeInteger(j)).intValue(), j, |
| String.format("Map.get(%d) < INITIAL_CAPACITY", j)); |
| } |
| for (int j = INITIAL_CAPACITY; j <= i; j++) { |
| assertEquals(m.get(new HashCodeInteger(j, 0)).intValue(), j, |
| String.format("Map.get(%d) >= INITIAL_CAPACITY", j)); |
| } |
| } |
| } |
| |
| @Test(dataProvider = "maps") |
| public void testUntreeifyOnResizeWithTraverse(String d, Map<HashCodeInteger, Integer> m) { |
| // Fill the map with 64 entries grouped into 4 buckets |
| put(INITIAL_CAPACITY, m, (i, s) -> { }); |
| |
| Collector<Integer, ?, ? extends Collection<Integer>> c = getCollector(m); |
| |
| for (int i = INITIAL_CAPACITY; i < SIZE; i++) { |
| // Add further entries in the 0'th bucket so as not to disturb |
| // other buckets, entries of which may be distributed and/or |
| // the bucket untreeified on resize |
| m.put(new HashCodeInteger(i, 0), i); |
| |
| Collection<Integer> actual = m.keySet().stream().map(e -> e.value).collect(c); |
| Collection<Integer> expected = IntStream.rangeClosed(0, i).boxed().collect(c); |
| assertEquals(actual, expected, "Key set"); |
| } |
| } |
| |
| Collector<Integer, ?, ? extends Collection<Integer>> getCollector(Map<?, ?> m) { |
| Collector<Integer, ?, ? extends Collection<Integer>> collector = m instanceof LinkedHashMap |
| ? Collectors.toList() |
| : Collectors.toSet(); |
| return collector; |
| } |
| |
| void put(int size, Map<HashCodeInteger, Integer> m, BiConsumer<Integer, Integer> c) { |
| for (int i = 0; i < size; i++) { |
| m.put(new HashCodeInteger(i), i); |
| |
| c.accept(i, m.size()); |
| } |
| } |
| |
| void remove(Map<HashCodeInteger, Integer> m, BiConsumer<Integer, Integer> c) { |
| int size = m.size(); |
| // Remove all elements thus ensuring at some point trees will be |
| // converting back to bins |
| for (int i = 0; i < size; i++) { |
| m.remove(new HashCodeInteger(i)); |
| |
| c.accept(i, m.size()); |
| } |
| } |
| |
| final static class HashCodeInteger implements Comparable<HashCodeInteger> { |
| final int value; |
| |
| final int hashcode; |
| |
| HashCodeInteger(int value) { |
| this(value, hash(value)); |
| } |
| |
| HashCodeInteger(int value, int hashcode) { |
| this.value = value; |
| this.hashcode = hashcode; |
| } |
| |
| static int hash(int i) { |
| // Assuming 64 entries with keys from 0 to 63 then a map: |
| // - of capacity 64 will have 4 buckets with 16 entries per-bucket |
| // - of capacity 128 will have 8 buckets with 8 entries per-bucket |
| // - of capacity 256 will have 16 buckets with 4 entries per-bucket |
| // |
| // Re-sizing will result in re-distribution, doubling the buckets |
| // and reducing the entries by half. This will result in |
| // untreeifying when the number of entries is less than untreeify |
| // threshold (see HashMap/ConcurrentMap.UNTREEIFY_THRESHOLD) |
| return (i % 4) + (i / 4) * INITIAL_CAPACITY; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj instanceof HashCodeInteger) { |
| HashCodeInteger other = (HashCodeInteger) obj; |
| return other.value == value; |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return hashcode; |
| } |
| |
| @Override |
| public int compareTo(HashCodeInteger o) { |
| return value - o.value; |
| } |
| |
| @Override |
| public String toString() { |
| return Integer.toString(value); |
| } |
| } |
| } |