diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java index c84bdb22d..6ab43ca93 100644 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java @@ -18,311 +18,10 @@ package net.openhft.chronicle.map; -import net.openhft.collections.ConcurrentMapLamdadSupport; import net.openhft.collections.SharedHashMap; -import java.util.Map; -import java.util.Objects; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Function; - /** * @author Rob Austin. */ -public interface ChronicleMap extends SharedHashMap, ConcurrentMapLamdadSupport { - - - - /** - * {@inheritDoc} - * - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException {@inheritDoc} - * @implNote This implementation assumes that the ConcurrentMap cannot contain null values and {@code - * get()} returning null unambiguously means the key is absent. Implementations which support null values - * must override this default implementation. - * @since 1.8 - */ - default V getOrDefault(Object key, V defaultValue) { - V v; - return ((v = get(key)) != null) ? v : defaultValue; - } - - /** - * {@inheritDoc} - * - * @throws NullPointerException {@inheritDoc} - * @implSpec The default implementation is equivalent to, for this {@code map}: - *
 {@code
-     * for ((Map.Entry entry : map.entrySet())
-     *     action.accept(entry.getKey(), entry.getValue());
-     * }
- * @implNote The default implementation assumes that {@code IllegalStateException} thrown by {@code - * getKey()} or {@code getValue()} indicates that the entry has been removed and cannot be processed. - * Operation continues for subsequent entries. - * @since 1.8 - */ - default void forEach(BiConsumer action) { - Objects.requireNonNull(action); - for (Map.Entry entry : entrySet()) { - K k; - V v; - try { - k = entry.getKey(); - v = entry.getValue(); - } catch (IllegalStateException ise) { - // this usually means the entry is no longer in the map. - continue; - } - action.accept(k, v); - } - } - - - /** - * {@inheritDoc} - * - * @throws UnsupportedOperationException {@inheritDoc} - * @throws NullPointerException {@inheritDoc} - * @throws ClassCastException {@inheritDoc} - * @throws IllegalArgumentException {@inheritDoc} - * @implSpec

The default implementation is equivalent to, for this {@code map}: - *

 {@code
-     * for ((Map.Entry entry : map.entrySet())
-     *     do {
-     *        K k = entry.getKey();
-     *        V v = entry.getValue();
-     *     } while(!replace(k, v, function.apply(k, v)));
-     * }
- * - * The default implementation may retry these steps when multiple threads attempt updates including - * potentially calling the function repeatedly for a given key. - * - *

This implementation assumes that the ConcurrentMap cannot contain null values and {@code get()} - * returning null unambiguously means the key is absent. Implementations which support null values - * must override this default implementation. - * @since 1.8 - */ - default void replaceAll(BiFunction function) { - Objects.requireNonNull(function); - final BiConsumer action = (k, v) -> { - while (!replace(k, v, function.apply(k, v))) { - // v changed or k is gone - if ((v = get(k)) == null) { - // k is no longer in the map. - break; - } - } - }; - - forEach(action); - } - - /** - * {@inheritDoc} - * - * @throws UnsupportedOperationException {@inheritDoc} - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException {@inheritDoc} - * @implSpec The default implementation is equivalent to the following steps for this {@code map}, then - * returning the current value or {@code null} if now absent: - * - *

 {@code
-     * if (map.get(key) == null) {
-     *     V newValue = mappingFunction.apply(key);
-     *     if (newValue != null)
-     *         return map.putIfAbsent(key, newValue);
-     * }
-     * }
- * - * The default implementation may retry these steps when multiple threads attempt updates including - * potentially calling the mapping function multiple times. - * - *

This implementation assumes that the ConcurrentMap cannot contain null values and {@code get()} - * returning null unambiguously means the key is absent. Implementations which support null values - * must override this default implementation. - * @since 1.8 - */ - - default V computeIfAbsent(K key, - Function mappingFunction) { - Objects.requireNonNull(mappingFunction); - V v, newValue; - return ((v = get(key)) == null && - (newValue = mappingFunction.apply(key)) != null && - (v = putIfAbsent(key, newValue)) == null) ? newValue : v; - } - - /** - * {@inheritDoc} - * - * @throws UnsupportedOperationException {@inheritDoc} - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException {@inheritDoc} - * @implSpec The default implementation is equivalent to performing the following steps for this {@code - * map}, then returning the current value or {@code null} if now absent. : - * - *

 {@code
-     * if (map.get(key) != null) {
-     *     V oldValue = map.get(key);
-     *     V newValue = remappingFunction.apply(key, oldValue);
-     *     if (newValue != null)
-     *         map.replace(key, oldValue, newValue);
-     *     else
-     *         map.remove(key, oldValue);
-     * }
-     * }
- * - * The default implementation may retry these steps when multiple threads attempt updates including - * potentially calling the remapping function multiple times. - * - *

This implementation assumes that the ConcurrentMap cannot contain null values and {@code get()} - * returning null unambiguously means the key is absent. Implementations which support null values - * must override this default implementation. - * @since 1.8 - */ - default V computeIfPresent(K key, - BiFunction remappingFunction) { - Objects.requireNonNull(remappingFunction); - V oldValue; - while ((oldValue = get(key)) != null) { - V newValue = remappingFunction.apply(key, oldValue); - if (newValue != null) { - if (replace(key, oldValue, newValue)) - return newValue; - } else if (remove(key, oldValue)) - return null; - } - return oldValue; - } - - /** - * {@inheritDoc} - * - * @throws UnsupportedOperationException {@inheritDoc} - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException {@inheritDoc} - * @implSpec The default implementation is equivalent to performing the following steps for this {@code - * map}, then returning the current value or {@code null} if absent: - * - *

 {@code
-     * V oldValue = map.get(key);
-     * V newValue = remappingFunction.apply(key, oldValue);
-     * if (oldValue != null ) {
-     *    if (newValue != null)
-     *       map.replace(key, oldValue, newValue);
-     *    else
-     *       map.remove(key, oldValue);
-     * } else {
-     *    if (newValue != null)
-     *       map.putIfAbsent(key, newValue);
-     *    else
-     *       return null;
-     * }
-     * }
- * - * The default implementation may retry these steps when multiple threads attempt updates including - * potentially calling the remapping function multiple times. - * - *

This implementation assumes that the ConcurrentMap cannot contain null values and {@code get()} - * returning null unambiguously means the key is absent. Implementations which support null values - * must override this default implementation. - * @since 1.8 - */ - default V compute(K key, - BiFunction remappingFunction) { - Objects.requireNonNull(remappingFunction); - V oldValue = get(key); - for (; ; ) { - V newValue = remappingFunction.apply(key, oldValue); - if (newValue == null) { - // delete mapping - if (oldValue != null || containsKey(key)) { - // something to remove - if (remove(key, oldValue)) { - // removed the old value as expected - return null; - } - - // some other value replaced old value. try again. - oldValue = get(key); - } else { - // nothing to do. Leave things as they were. - return null; - } - } else { - // add or replace old mapping - if (oldValue != null) { - // replace - if (replace(key, oldValue, newValue)) { - // replaced as expected. - return newValue; - } - - // some other value replaced old value. try again. - oldValue = get(key); - } else { - // add (replace if oldValue was null) - if ((oldValue = putIfAbsent(key, newValue)) == null) { - // replaced - return newValue; - } - - // some other value replaced old value. try again. - } - } - } - } - - - /** - * {@inheritDoc} - * - * @throws UnsupportedOperationException {@inheritDoc} - * @throws ClassCastException {@inheritDoc} - * @throws NullPointerException {@inheritDoc} - * @implSpec The default implementation is equivalent to performing the following steps for this {@code - * map}, then returning the current value or {@code null} if absent: - * - *

 {@code
-     * V oldValue = map.get(key);
-     * V newValue = (oldValue == null) ? value :
-     *              remappingFunction.apply(oldValue, value);
-     * if (newValue == null)
-     *     map.remove(key);
-     * else
-     *     map.put(key, newValue);
-     * }
- * - *

The default implementation may retry these steps when multiple threads attempt updates including - * potentially calling the remapping function multiple times. - * - *

This implementation assumes that the ConcurrentMap cannot contain null values and {@code get()} - * returning null unambiguously means the key is absent. Implementations which support null values - * must override this default implementation. - * @since 1.8 - */ - default V merge(K key, V value, - BiFunction remappingFunction) { - Objects.requireNonNull(remappingFunction); - Objects.requireNonNull(value); - V oldValue = get(key); - for (; ; ) { - if (oldValue != null) { - V newValue = remappingFunction.apply(oldValue, value); - if (newValue != null) { - if (replace(key, oldValue, newValue)) - return newValue; - } else if (remove(key, oldValue)) { - return null; - } - oldValue = get(key); - } else { - if ((oldValue = putIfAbsent(key, value)) == null) { - return value; - } - } - } - } +public interface ChronicleMap extends SharedHashMap { } diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java index a70b77439..fb8b5b262 100644 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java @@ -18,13 +18,237 @@ package net.openhft.chronicle.map; -import net.openhft.collections.ChronicleMapBuilder0; +import net.openhft.collections.*; +import net.openhft.lang.io.serialization.BytesMarshallerFactory; +import net.openhft.lang.io.serialization.ObjectSerializer; +import net.openhft.lang.model.constraints.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; /** * @author Rob Austin. */ -public class ChronicleMapBuilder extends ChronicleMapBuilder0 { - public ChronicleMapBuilder(Class aClass, Class aClass2) { - super(aClass, aClass2); +public class ChronicleMapBuilder extends SharedHashMapBuilder implements Cloneable { + + private static final Logger LOG = LoggerFactory.getLogger(ChronicleMapBuilder.class.getName()); + + public ChronicleMapBuilder(Class kClass, Class vClass) { + super(kClass, vClass); } + + public ChronicleMapBuilder() { + super(); + } + + + public static ChronicleMapBuilder of(Class kClass, Class vClass) { + return new ChronicleMapBuilder(kClass, vClass); + } + + @Override + public ChronicleMapBuilder clone() { + + return (ChronicleMapBuilder) super.clone(); + } + + + /** + * Set minimum number of segments. See concurrencyLevel in {@link java.util.concurrent.ConcurrentHashMap}. + * + * @return this builder object back + */ + public ChronicleMapBuilder minSegments(int minSegments) { + super.minSegments(minSegments); + return this; + } + + + /** + *

Note that the actual entrySize will be aligned to 4 (default entry alignment). I. e. if you set + * entry size to 30, the actual entry size will be 32 (30 aligned to 4 bytes). If you don't want entry + * size to be aligned, set {@code entryAndValueAlignment(Alignment.NO_ALIGNMENT)}. + * + * @param entrySize the size in bytes + * @return this {@code SharedHashMapBuilder} back + * @see #entryAndValueAlignment(net.openhft.collections.Alignment) + * @see #entryAndValueAlignment() + */ + public ChronicleMapBuilder entrySize(int entrySize) { + super.entrySize(entrySize); + return this; + } + + + /** + * Specifies alignment of address in memory of entries and independently of address in memory of values + * within entries.

Useful when values of the map are updated intensively, particularly fields with + * volatile access, because it doesn't work well if the value crosses cache lines. Also, on some (nowadays + * rare) architectures any misaligned memory access is more expensive than aligned.

Note that + * specified {@link #entrySize()} will be aligned according to this alignment. I. e. if you set {@code + * entrySize(20)} and {@link net.openhft.collections.Alignment#OF_8_BYTES}, actual entry size will be 24 + * (20 aligned to 8 bytes). + * + * @return this {@code SharedHashMapBuilder} back + * @see #entryAndValueAlignment() + */ + public ChronicleMapBuilder entryAndValueAlignment(Alignment alignment) { + super.entryAndValueAlignment(alignment); + return this; + } + + + public ChronicleMapBuilder entries(long entries) { + super.entries(entries); + return this; + } + + public ChronicleMapBuilder replicas(int replicas) { + super.replicas(replicas); + return this; + } + + + public ChronicleMapBuilder actualEntriesPerSegment(int actualEntriesPerSegment) { + super.actualEntriesPerSegment(actualEntriesPerSegment); + return this; + } + + + public ChronicleMapBuilder actualSegments(int actualSegments) { + super.actualSegments(actualSegments); + return this; + } + + + /** + * Not supported yet. + * + * @param transactional if the built map should be transactional + * @return this {@code SharedHashMapBuilder} back + */ + public ChronicleMapBuilder transactional(boolean transactional) { + super.transactional(transactional); + return this; + } + + + public ChronicleMapBuilder kClass(Class kClass) { + super.kClass(kClass); + return this; + } + + + public SharedHashMapBuilder file(File file) { + super.file(file); + return this; + } + + + public ChronicleMapBuilder lockTimeOutMS(long lockTimeOutMS) { + super.lockTimeOutMS(lockTimeOutMS); + return this; + } + + + public ChronicleMapBuilder errorListener(SharedMapErrorListener errorListener) { + super.errorListener(errorListener); + return this; + } + + + /** + * Map.put() returns the previous value, functionality which is rarely used but fairly cheap for HashMap. + * In the case, for an off heap collection, it has to create a new object (or return a recycled one) + * Either way it's expensive for something you probably don't use. + * + * @param putReturnsNull false if you want SharedHashMap.put() to not return the object that was replaced + * but instead return null + * @return an instance of the map builder + */ + public ChronicleMapBuilder putReturnsNull(boolean putReturnsNull) { + super.putReturnsNull(putReturnsNull); + return this; + } + + + /** + * Map.remove() returns the previous value, functionality which is rarely used but fairly cheap for + * HashMap. In the case, for an off heap collection, it has to create a new object (or return a recycled + * one) Either way it's expensive for something you probably don't use. + * + * @param removeReturnsNull false if you want SharedHashMap.remove() to not return the object that was + * removed but instead return null + * @return an instance of the map builder + */ + public ChronicleMapBuilder removeReturnsNull(boolean removeReturnsNull) { + super.removeReturnsNull(removeReturnsNull); + return this; + } + + + public ChronicleMapBuilder generatedValueType(boolean generatedValueType) { + super.generatedValueType(generatedValueType); + return this; + } + + + public ChronicleMapBuilder largeSegments(boolean largeSegments) { + super.largeSegments(largeSegments); + return this; + } + + + public ChronicleMapBuilder metaDataBytes(int metaDataBytes) { + super.metaDataBytes(metaDataBytes); + return this; + } + + + public ChronicleMapBuilder canReplicate(boolean canReplicate) { + super.canReplicate(canReplicate); + return this; + } + + + public ChronicleMapBuilder timeProvider(TimeProvider timeProvider) { + super.timeProvider(timeProvider); + return this; + } + + public ChronicleMapBuilder identifier(byte identifier) { + super.identifier(identifier); + return this; + } + + public ChronicleMapBuilder tcpReplicatorBuilder(TcpReplicatorBuilder tcpReplicatorBuilder) { + super.tcpReplicatorBuilder(tcpReplicatorBuilder); + return this; + } + + + public ChronicleMapBuilder udpReplicatorBuilder(UdpReplicatorBuilder udpReplicatorBuilder) { + super.udpReplicatorBuilder(udpReplicatorBuilder); + return this; + } + + + public ChronicleMapBuilder bytesMarshallerFactory(BytesMarshallerFactory bytesMarshallerFactory) { + super.bytesMarshallerFactory(bytesMarshallerFactory); + return this; + } + + + public ChronicleMapBuilder objectSerializer(ObjectSerializer objectSerializer) { + super.objectSerializer(objectSerializer); + return this; + } + + public ChronicleMapBuilder externalReplicatorBuilder(@NotNull ExternalReplicatorBuilder externalReplicatorBuilder) { + super.externalReplicatorBuilder(externalReplicatorBuilder); + return this; + } + + } diff --git a/src/main/java/net/openhft/collections/ChronicleMapBuilder0.java b/src/main/java/net/openhft/collections/ChronicleMapBuilder0.java deleted file mode 100644 index fde6bc55e..000000000 --- a/src/main/java/net/openhft/collections/ChronicleMapBuilder0.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2014 Higher Frequency Trading - *

- * http://www.higherfrequencytrading.com - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.openhft.collections; - -import net.openhft.chronicle.map.ChronicleMap; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; - -/** - * @author Rob Austin. - */ -public class ChronicleMapBuilder0 extends SharedHashMapBuilder implements Cloneable { - - private static final Logger LOG = LoggerFactory.getLogger(ChronicleMapBuilder0.class.getName()); - - public ChronicleMapBuilder0(Class kClass, Class vClass) { - super(kClass, vClass); - } - - public static ChronicleMapBuilder0 of(Class kClass, Class vClass) { - return new ChronicleMapBuilder0(kClass, vClass); - } - - - /** - * Not supported yet. - * - * @param transactional if the built map should be transactional - * @return this {@code SharedHashMapBuilder} back - */ - public ChronicleMapBuilder0 transactional(boolean transactional) { - this.transactional = transactional; - return this; - } - - public boolean transactional() { - return transactional; - } - - - public ChronicleMapBuilder0 kClass(Class kClass) { - this.kClass = kClass; - return this; - } - - public ChronicleMapBuilder0 vClass(Class vClass) { - this.vClass = vClass; - return this; - } - - public ChronicleMapBuilder0 file(File file) { - this.file = file; - return this; - } - - - public ChronicleMap create() throws IOException { - super.create(); - - if (kClass == null) - throw new IllegalArgumentException("missing mandatory parameter kClass"); - - if (vClass == null) - throw new IllegalArgumentException("missing mandatory parameter vClass"); - - if (file == null) - throw new IllegalArgumentException("missing mandatory parameter file"); - - - ChronicleMapBuilder0 builder = toBuilder(); - - if (!canReplicate()) - return new ChronicleMap0(builder, file, kClass, vClass); - - if (identifier <= 0) - throw new IllegalArgumentException("Identifier must be positive, " + identifier + " given"); - - final ChronicleReplicatedMap result = - new ChronicleReplicatedMap(builder, kClass, vClass); - - if (externalReplicatorBuilder != null) - externalReplicator = applyExternalReplicator(result, externalReplicatorBuilder, kClass, vClass); - - if (tcpReplicatorBuilder != null) - applyTcpReplication(result, tcpReplicatorBuilder); - - if (udpReplicatorBuilder != null) { - if (tcpReplicatorBuilder == null) - LOG.warn("MISSING TCP REPLICATION : The UdpReplicator only attempts to read data (" + - "it does not enforce or guarantee delivery), you should use the UdpReplicator if " + - "you have a large number of nodes, and you wish to receive the data before it " + - "becomes available on TCP/IP. Since data delivery is not guaranteed, it is " + - "recommended that you only use the UDP" + - " " + - "Replicator " + - "in conjunction with a TCP Replicator"); - applyUdpReplication(result, udpReplicatorBuilder); - } - return result; - } - - ChronicleMapBuilder0 toBuilder() throws IOException { - ChronicleMapBuilder0 builder = clone(); - - for (int i = 0; i < 10; i++) { - if (file.exists() && file.length() > 0) { - readFile(file, builder); - break; - } - if (file.createNewFile() || file.length() == 0) { - newFile(file); - break; - } - try { - Thread.sleep(100); - } catch (InterruptedException e) { - throw new IOException(e); - } - } - if (builder == null || !file.exists()) - throw new FileNotFoundException("Unable to create " + file); - return builder; - } - - @Override - public ChronicleMapBuilder0 clone() { - return (ChronicleMapBuilder0) super.clone(); - } - -} diff --git a/src/main/java/net/openhft/collections/CollectionView.java b/src/main/java/net/openhft/collections/CollectionView.java deleted file mode 100644 index 2e2f10c32..000000000 --- a/src/main/java/net/openhft/collections/CollectionView.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2014 Higher Frequency Trading - *

- * http://www.higherfrequencytrading.com - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.openhft.collections; - -import java.util.Collection; -import java.util.Iterator; - -/** - * @author Rob Austin. - */ -public interface CollectionView { - - AbstractVanillaSharedMap getMap(); - - /** - * Removes all of the elements from this view, by removing all the mappings from the map backing this - * view. - */ - void clear(); - - int size(); - - - boolean isEmpty(); - - // implementations below rely on concrete classes supplying these - // abstract methods - - /** - * Returns an iterator over the elements in this collection. - * - *

The returned iterator is weakly consistent. - * - * @return an iterator over the elements in this collection - */ - Iterator iterator(); - - boolean contains(Object o); - - boolean remove(Object o); - - static final String oomeMsg = "Required array size too large"; - - Object[] toArray(); - - @SuppressWarnings("unchecked") - T[] toArray(T[] a); - - /** - * Returns a string representation of this collection. The string representation consists of the string - * representations of the collection's elements in the order they are returned by its iterator, enclosed - * in square brackets ({@code "[]"}). Adjacent elements are separated by the characters {@code ", "} - * (comma and space). Elements are converted to strings as by {@link String#valueOf(Object)}. - * - * @return a string representation of this collection - */ - String toString(); - - boolean containsAll(Collection c); - - boolean removeAll(Collection c); - - boolean retainAll(Collection c); -} diff --git a/src/main/java/net/openhft/collections/ConcurrentHashMap.java b/src/main/java/net/openhft/collections/ConcurrentHashMap.java deleted file mode 100644 index f64a7d0f1..000000000 --- a/src/main/java/net/openhft/collections/ConcurrentHashMap.java +++ /dev/null @@ -1,4163 +0,0 @@ -/* - * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. - - * Written by Doug Lea with assistance from members of JCP JSR-166 - * Expert Group and released to the public domain, as explained at - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -package net.openhft.collections; - -import net.openhft.lang.io.NativeBytes; - -import java.io.Serializable; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.*; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CountedCompleter; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.LockSupport; -import java.util.function.*; -import java.util.stream.Stream; - -/** - * A hash table supporting full concurrency of retrievals and high expected concurrency for updates. This - * class obeys the same functional specification as {@link java.util.Hashtable}, and includes versions of - * methods corresponding to each method of {@code Hashtable}. However, even though all operations are - * thread-safe, retrieval operations do not entail locking, and there is not any support for - * locking the entire table in a way that prevents all access. This class is fully interoperable with {@code - * Hashtable} in programs that rely on its thread safety but not on its synchronization details. - * - *

Retrieval operations (including {@code get}) generally do not block, so may overlap with update - * operations (including {@code put} and {@code remove}). Retrievals reflect the results of the most recently - * completed update operations holding upon their onset. (More formally, an update operation for a - * given key bears a happens-before relation with any (non-null) retrieval for that key reporting the - * updated value.) For aggregate operations such as {@code putAll} and {@code clear}, concurrent retrievals - * may reflect insertion or removal of only some entries. Similarly, Iterators, Spliterators and Enumerations - * return elements reflecting the state of the hash table at some point at or since the creation of the - * iterator/enumeration. They do not throw {@link java.util.ConcurrentModificationException - * ConcurrentModificationException}. However, iterators are designed to be used by only one thread at a time. - * Bear in mind that the results of aggregate status methods including {@code size}, {@code isEmpty}, and - * {@code containsValue} are typically useful only when a map is not undergoing concurrent updates in other - * threads. Otherwise the results of these methods reflect transient states that may be adequate for - * monitoring or estimation purposes, but not for program control. - * - *

The table is dynamically expanded when there are too many collisions (i.e., keys that have distinct hash - * codes but fall into the same slot modulo the table size), with the expected average effect of maintaining - * roughly two bins per mapping (corresponding to a 0.75 load factor threshold for resizing). There may be - * much variance around this average as mappings are added and removed, but overall, this maintains a commonly - * accepted time/space tradeoff for hash tables. However, resizing this or any other kind of hash table may - * be a relatively slow operation. When possible, it is a good idea to provide a size estimate as an optional - * {@code initialCapacity} constructor argument. An additional optional {@code loadFactor} constructor - * argument provides a further means of customizing initial table capacity by specifying the table density to - * be used in calculating the amount of space to allocate for the given number of elements. Also, for - * compatibility with previous versions of this class, constructors may optionally specify an expected {@code - * concurrencyLevel} as an additional hint for internal sizing. Note that using many keys with exactly the - * same {@code hashCode()} is a sure way to slow down performance of any hash table. To ameliorate impact, - * when keys are {@link Comparable}, this class may use comparison order among keys to help break ties. - * - *

A {@link Set} projection of a ConcurrentHashMap may be created (using {@link )} or {@link # (int)}), or - * viewed (using {@link # (Object)} when only keys are of interest, and the mapped values are (perhaps - * transiently) not used or all take the same mapping value. - * - *

A ConcurrentHashMap can be used as scalable frequency map (a form of histogram or multiset) by using - * {@link java.util.concurrent.atomic.LongAdder} values and initializing via {@link #computeIfAbsent - * computeIfAbsent}. For example, to add a count to a {@code ConcurrentHashMap freqs}, you - * can use {@code freqs.computeIfAbsent(k -> new LongAdder()).increment();} - * - *

This class and its views and iterators implement all of the optional methods of the {@link Map} - * and {@link Iterator} interfaces. - * - *

Like {@link Hashtable} but unlike {@link HashMap}, this class does not allow {@code null} to be - * used as a key or value. - * - *

ConcurrentHashMaps support a set of sequential and parallel bulk operations that, unlike most {@link - * Stream} methods, are designed to be safely, and often sensibly, applied even with maps that are being - * concurrently updated by other threads; for example, when computing a snapshot summary of the values in a - * shared registry. There are three kinds of operation, each with four forms, accepting functions with Keys, - * Values, Entries, and (Key, Value) arguments and/or return values. Because the elements of a - * ConcurrentHashMap are not ordered in any particular way, and may be processed in different orders in - * different parallel executions, the correctness of supplied functions should not depend on any ordering, or - * on any other objects or values that may transiently change while computation is in progress; and except for - * forEach actions, should ideally be side-effect-free. Bulk operations on {@link java.util.Map.Entry} objects - * do not support method {@code setValue}. - * - *

  • forEach: Perform a given action on each element. A variant form applies a given transformation on - * each element before performing the action.
  • - * - *
  • search: Return the first available non-null result of applying a given function on each element; - * skipping further search when a result is found.
  • - * - *
  • reduce: Accumulate each element. The supplied reduction function cannot rely on ordering (more - * formally, it should be both associative and commutative). There are five variants: - * - *
      - * - *
    • Plain reductions. (There is not a form of this method for (key, value) function arguments since there - * is no corresponding return type.)
    • - * - *
    • Mapped reductions that accumulate the results of a given function applied to each element.
    • - * - *
    • Reductions to scalar doubles, longs, and ints, using a given basis value.
    • - * - *
- * - *

These bulk operations accept a {@code parallelismThreshold} argument. Methods proceed sequentially if - * the current map size is estimated to be less than the given threshold. Using a value of {@code - * Long.MAX_VALUE} suppresses all parallelism. Using a value of {@code 1} results in maximal parallelism by - * partitioning into enough subtasks to fully utilize the {@link ForkJoinPool#commonPool()} that is used for - * all parallel computations. Normally, you would initially choose one of these extreme values, and then - * measure performance of using in-between values that trade off overhead versus throughput. - * - *

The concurrency properties of bulk operations follow from those of ConcurrentHashMap: Any non-null - * result returned from {@code get(key)} and related access methods bears a happens-before relation with the - * associated insertion or update. The result of any bulk operation reflects the composition of these - * per-element relations (but is not necessarily atomic with respect to the map as a whole unless it is - * somehow known to be quiescent). Conversely, because keys and values in the map are never null, null serves - * as a reliable atomic indicator of the current lack of any result. To maintain this property, null serves - * as an implicit basis for all non-scalar reduction operations. For the double, long, and int versions, the - * basis should be one that, when combined with any other value, returns that other value (more formally, it - * should be the identity element for the reduction). Most common reductions have these properties; for - * example, computing a sum with basis 0 or a minimum with basis MAX_VALUE. - * - *

Search and transformation functions provided as arguments should similarly return null to indicate the - * lack of any result (in which case it is not used). In the case of mapped reductions, this also enables - * transformations to serve as filters, returning null (or, in the case of primitive specializations, the - * identity basis) if the element should not be combined. You can create compound transformations and - * filterings by composing them yourself under this "null means there is nothing there now" rule before using - * them in search or reduce operations. - * - *

Methods accepting and/or returning Entry arguments maintain key-value associations. They may be useful - * for example when finding the key for the greatest value. Note that "plain" Entry arguments can be supplied - * using {@code new AbstractMap.SimpleEntry(k,v)}. - * - *

Bulk operations may complete abruptly, throwing an exception encountered in the application of a - * supplied function. Bear in mind when handling such exceptions that other concurrently executing functions - * could also have thrown exceptions, or would have done so if the first exception had not occurred. - * - *

Speedups for parallel compared to sequential forms are common but not guaranteed. Parallel operations - * involving brief functions on small maps may execute more slowly than sequential forms if the underlying - * work to parallelize the computation is more expensive than the computation itself. Similarly, - * parallelization may not lead to much actual parallelism if all processors are busy performing unrelated - * tasks. - * - *

All arguments to all task methods must be non-null. - * - *

This class is a member of the Java - * Collections Framework. - * - * @param the type of keys maintained by this map - * @param the type of mapped values - * @author Doug Lea - * @since 1.5 - */ -public abstract class ConcurrentHashMap extends AbstractMap - implements ConcurrentMap, Serializable { - private static final long serialVersionUID = 7249069246763182397L; - - /* - * Overview: - * - * The primary design goal of this hash table is to maintain - * concurrent readability (typically method get(), but also - * iterators and related methods) while minimizing update - * contention. Secondary goals are to keep space consumption about - * the same or better than java.util.HashMap, and to support high - * initial insertion rates on an empty table by many threads. - * - * This map usually acts as a binned (bucketed) hash table. Each - * key-value mapping is held in a Node. Most nodes are instances - * of the basic Node class with hash, key, value, and next - * fields. However, various subclasses exist: TreeNodes are - * arranged in balanced trees, not lists. TreeBins hold the roots - * of sets of TreeNodes. ForwardingNodes are placed at the heads - * of bins during resizing. ReservationNodes are used as - * placeholders while establishing values in computeIfAbsent and - * related methods. The types TreeBin, ForwardingNode, and - * ReservationNode do not hold normal user keys, values, or - * hashes, and are readily distinguishable during search etc - * because they have negative hash fields and null key and value - * fields. (These special nodes are either uncommon or transient, - * so the impact of carrying around some unused fields is - * insignificant.) - * - * The table is lazily initialized to a power-of-two size upon the - * first insertion. Each bin in the table normally contains a - * list of Nodes (most often, the list has only zero or one Node). - * Table accesses require volatile/atomic reads, writes, and - * CASes. Because there is no other way to arrange this without - * adding further indirections, we use intrinsics - * (sun.misc.Unsafe) operations. - * - * We use the top (sign) bit of Node hash fields for control - * purposes -- it is available anyway because of addressing - * constraints. Nodes with negative hash fields are specially - * handled or ignored in map methods. - * - * Insertion (via put or its variants) of the first node in an - * empty bin is performed by just CASing it to the bin. This is - * by far the most common case for put operations under most - * key/hash distributions. Other update operations (insert, - * delete, and replace) require locks. We do not want to waste - * the space required to associate a distinct lock object with - * each bin, so instead use the first node of a bin list itself as - * a lock. Locking support for these locks relies on builtin - * "synchronized" monitors. - * - * Using the first node of a list as a lock does not by itself - * suffice though: When a node is locked, any update must first - * validate that it is still the first node after locking it, and - * retry if not. Because new nodes are always appended to lists, - * once a node is first in a bin, it remains first until deleted - * or the bin becomes invalidated (upon resizing). - * - * The main disadvantage of per-bin locks is that other update - * operations on other nodes in a bin list protected by the same - * lock can stall, for example when user equals() or mapping - * functions take a long time. However, statistically, under - * random hash codes, this is not a common problem. Ideally, the - * frequency of nodes in bins follows a Poisson distribution - * (http://en.wikipedia.org/wiki/Poisson_distribution) with a - * parameter of about 0.5 on average, given the resizing threshold - * of 0.75, although with a large variance because of resizing - * granularity. Ignoring variance, the expected occurrences of - * list size k are (exp(-0.5) * pow(0.5, k) / factorial(k)). The - * first values are: - * - * 0: 0.60653066 - * 1: 0.30326533 - * 2: 0.07581633 - * 3: 0.01263606 - * 4: 0.00157952 - * 5: 0.00015795 - * 6: 0.00001316 - * 7: 0.00000094 - * 8: 0.00000006 - * more: less than 1 in ten million - * - * Lock contention probability for two threads accessing distinct - * elements is roughly 1 / (8 * #elements) under random hashes. - * - * Actual hash code distributions encountered in practice - * sometimes deviate significantly from uniform randomness. This - * includes the case when N > (1<<30), so some keys MUST collide. - * Similarly for dumb or hostile usages in which multiple keys are - * designed to have identical hash codes or ones that differs only - * in masked-out high bits. So we use a secondary strategy that - * applies when the number of nodes in a bin exceeds a - * threshold. These TreeBins use a balanced tree to hold nodes (a - * specialized form of red-black trees), bounding search time to - * O(log N). Each search step in a TreeBin is at least twice as - * slow as in a regular list, but given that N cannot exceed - * (1<<64) (before running out of addresses) this bounds search - * steps, lock hold times, etc, to reasonable constants (roughly - * 100 nodes inspected per operation worst case) so long as keys - * are Comparable (which is very common -- String, Long, etc). - * TreeBin nodes (TreeNodes) also maintain the same "next" - * traversal pointers as regular nodes, so can be traversed in - * iterators in the same way. - * - * The table is resized when occupancy exceeds a percentage - * threshold (nominally, 0.75, but see below). Any thread - * noticing an overfull bin may assist in resizing after the - * initiating thread allocates and sets up the replacement array. - * However, rather than stalling, these other threads may proceed - * with insertions etc. The use of TreeBins shields us from the - * worst case effects of overfilling while resizes are in - * progress. Resizing proceeds by transferring bins, one by one, - * from the table to the next table. However, threads claim small - * blocks of indices to transfer (via field transferIndex) before - * doing so, reducing contention. A generation stamp in field - * sizeCtl ensures that resizings do not overlap. Because we are - * using power-of-two expansion, the elements from each bin must - * either stay at same index, or move with a power of two - * offset. We eliminate unnecessary node creation by catching - * cases where old nodes can be reused because their next fields - * won't change. On average, only about one-sixth of them need - * cloning when a table doubles. The nodes they replace will be - * garbage collectable as soon as they are no longer referenced by - * any reader thread that may be in the midst of concurrently - * traversing table. Upon transfer, the old table bin contains - * only a special forwarding node (with hash field "MOVED") that - * contains the next table as its key. On encountering a - * forwarding node, access and update operations restart, using - * the new table. - * - * Each bin transfer requires its bin lock, which can stall - * waiting for locks while resizing. However, because other - * threads can join in and help resize rather than contend for - * locks, average aggregate waits become shorter as resizing - * progresses. The transfer operation must also ensure that all - * accessible bins in both the old and new table are usable by any - * traversal. This is arranged in part by proceeding from the - * last bin (table.length - 1) up towards the first. Upon seeing - * a forwarding node, traversals (see class Traverser) arrange to - * move to the new table without revisiting nodes. To ensure that - * no intervening nodes are skipped even when moved out of order, - * a stack (see class TableStack) is created on first encounter of - * a forwarding node during a traversal, to maintain its place if - * later processing the current table. The need for these - * save/restore mechanics is relatively rare, but when one - * forwarding node is encountered, typically many more will be. - * So Traversers use a simple caching scheme to avoid creating so - * many new TableStack nodes. (Thanks to Peter Levart for - * suggesting use of a stack here.) - * - * The traversal scheme also applies to partial traversals of - * ranges of bins (via an alternate Traverser constructor) - * to support partitioned aggregate operations. Also, read-only - * operations give up if ever forwarded to a null table, which - * provides support for shutdown-style clearing, which is also not - * currently implemented. - * - * Lazy table initialization minimizes footprint until first use, - * and also avoids resizings when the first operation is from a - * putAll, constructor with map argument, or deserialization. - * These cases attempt to override the initial capacity settings, - * but harmlessly fail to take effect in cases of races. - * - * The element count is maintained using a specialization of - * LongAdder. We need to incorporate a specialization rather than - * just use a LongAdder in order to access implicit - * contention-sensing that leads to creation of multiple - * CounterCells. The counter mechanics avoid contention on - * updates but can encounter cache thrashing if read too - * frequently during concurrent access. To avoid reading so often, - * resizing under contention is attempted only upon adding to a - * bin already holding two or more nodes. Under uniform hash - * distributions, the probability of this occurring at threshold - * is around 13%, meaning that only about 1 in 8 puts check - * threshold (and after resizing, many fewer do so). - * - * TreeBins use a special form of comparison for search and - * related operations (which is the main reason we cannot use - * existing collections such as TreeMaps). TreeBins contain - * Comparable elements, but may contain others, as well as - * elements that are Comparable but not necessarily Comparable for - * the same T, so we cannot invoke compareTo among them. To handle - * this, the tree is ordered primarily by hash value, then by - * Comparable.compareTo order if applicable. On lookup at a node, - * if elements are not comparable or compare as 0 then both left - * and right children may need to be searched in the case of tied - * hash values. (This corresponds to the full list search that - * would be necessary if all elements were non-Comparable and had - * tied hashes.) On insertion, to keep a total ordering (or as - * close as is required here) across rebalancings, we compare - * classes and identityHashCodes as tie-breakers. The red-black - * balancing code is updated from pre-jdk-collections - * (http://gee.cs.oswego.edu/dl/classes/collections/RBCell.java) - * based in turn on Cormen, Leiserson, and Rivest "Introduction to - * Algorithms" (CLR). - * - * TreeBins also require an additional locking mechanism. While - * list traversal is always possible by readers even during - * updates, tree traversal is not, mainly because of tree-rotations - * that may change the root node and/or its linkages. TreeBins - * include a simple read-write lock mechanism parasitic on the - * main bin-synchronization strategy: Structural adjustments - * associated with an insertion or removal are already bin-locked - * (and so cannot conflict with other writers) but must wait for - * ongoing readers to finish. Since there can be only one such - * waiter, we use a simple scheme using a single "waiter" field to - * block writers. However, readers need never block. If the root - * lock is held, they proceed along the slow traversal path (via - * next-pointers) until the lock becomes available or the list is - * exhausted, whichever comes first. These cases are not fast, but - * maximize aggregate expected throughput. - * - * Maintaining API and serialization compatibility with previous - * versions of this class introduces several oddities. Mainly: We - * leave untouched but unused constructor arguments refering to - * concurrencyLevel. We accept a loadFactor constructor argument, - * but apply it only to initial table capacity (which is the only - * time that we can guarantee to honor it.) We also declare an - * unused "Segment" class that is instantiated in minimal form - * only when serializing. - * - * Also, solely for compatibility with previous versions of this - * class, it extends AbstractMap, even though all of its methods - * are overridden, so it is just useless baggage. - * - * This file is organized to make things a little easier to follow - * while reading than they might otherwise: First the main static - * declarations and utilities, then fields, then main public - * methods (with a few factorings of multiple public methods into - * internal ones), then sizing methods, trees, traversers, and - * bulk operations. - */ - - /* ---------------- Constants -------------- */ - - /** - * The largest possible table capacity. This value must be exactly 1<<30 to stay within Java array - * allocation and indexing bounds for power of two table sizes, and is further required because the top - * two bits of 32bit hash fields are used for control purposes. - */ - private static final int MAXIMUM_CAPACITY = 1 << 30; - - /** - * The default initial table capacity. Must be a power of 2 (i.e., at least 1) and at most - * MAXIMUM_CAPACITY. - */ - private static final int DEFAULT_CAPACITY = 16; - - /** - * The largest possible (non-power of two) array size. Needed by toArray and related methods. - */ - static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; - - /** - * The default concurrency level for this table. Unused but defined for compatibility with previous - * versions of this class. - */ - private static final int DEFAULT_CONCURRENCY_LEVEL = 16; - - /** - * The load factor for this table. Overrides of this value in constructors affect only the initial table - * capacity. The actual floating point value isn't normally used -- it is simpler to use expressions such - * as {@code n - (n >>> 2)} for the associated resizing threshold. - */ - private static final float LOAD_FACTOR = 0.75f; - - /** - * The bin count threshold for using a tree rather than list for a bin. Bins are converted to trees when - * adding an element to a bin with at least this many nodes. The value must be greater than 2, and should - * be at least 8 to mesh with assumptions in tree removal about conversion back to plain bins upon - * shrinkage. - */ - static final int TREEIFY_THRESHOLD = 8; - - /** - * The bin count threshold for untreeifying a (split) bin during a resize operation. Should be less than - * TREEIFY_THRESHOLD, and at most 6 to mesh with shrinkage detection under removal. - */ - static final int UNTREEIFY_THRESHOLD = 6; - - /** - * The smallest table capacity for which bins may be treeified. (Otherwise the table is resized if too - * many nodes in a bin.) The value should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts between - * resizing and treeification thresholds. - */ - static final int MIN_TREEIFY_CAPACITY = 64; - - /** - * Minimum number of rebinnings per transfer step. Ranges are subdivided to allow multiple resizer - * threads. This value serves as a lower bound to avoid resizers encountering excessive memory - * contention. The value should be at least DEFAULT_CAPACITY. - */ - private static final int MIN_TRANSFER_STRIDE = 16; - - /** - * The number of bits used for generation stamp in sizeCtl. Must be at least 6 for 32bit arrays. - */ - private static int RESIZE_STAMP_BITS = 16; - - /** - * The maximum number of threads that can help resize. Must fit in 32 - RESIZE_STAMP_BITS bits. - */ - private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1; - - /** - * The bit shift for recording size stamp in sizeCtl. - */ - private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS; - - /* - * Encodings for Node hash fields. See above for explanation. - */ - static final int MOVED = -1; // hash for forwarding nodes - static final int TREEBIN = -2; // hash for roots of trees - static final int RESERVED = -3; // hash for transient reservations - static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash - - /** - * Number of CPUS, to place bounds on some sizings - */ - static final int NCPU = Runtime.getRuntime().availableProcessors(); - - - - /* ---------------- Nodes -------------- */ - - /** - * Key-value entry. This class is never exported out as a user-mutable Map.Entry (i.e., one supporting - * setValue; see MapEntry below), but can be used for read-only traversals used in bulk tasks. Subclasses - * of Node with a negative hash field are special, and contain null keys and values (but are never - * exported). Otherwise, keys and vals are never null. - */ - static class Node implements Map.Entry { - final int hash; - final K key; - volatile V val; - volatile Node next; - - Node(int hash, K key, V val, Node next) { - this.hash = hash; - this.key = key; - this.val = val; - this.next = next; - } - - public final K getKey() { - return key; - } - - public final V getValue() { - return val; - } - - public final int hashCode() { - return key.hashCode() ^ val.hashCode(); - } - - public final String toString() { - return key + "=" + val; - } - - public final V setValue(V value) { - throw new UnsupportedOperationException(); - } - - public final boolean equals(Object o) { - Object k, v, u; - Map.Entry e; - return ((o instanceof Map.Entry) && - (k = (e = (Map.Entry) o).getKey()) != null && - (v = e.getValue()) != null && - (k == key || k.equals(key)) && - (v == (u = val) || v.equals(u))); - } - - /** - * Virtualized support for map.get(); overridden in subclasses. - */ - Node find(int h, Object k) { - Node e = this; - if (k != null) { - do { - K ek; - if (e.hash == h && - ((ek = e.key) == k || (ek != null && k.equals(ek)))) - return e; - } while ((e = e.next) != null); - } - return null; - } - } - - /* ---------------- Static utilities -------------- */ - - /** - * Spreads (XORs) higher bits of hash to lower and also forces top bit to 0. Because the table uses - * power-of-two masking, sets of hashes that vary only in bits above the current mask will always collide. - * (Among known examples are sets of Float keys holding consecutive whole numbers in small tables.) So we - * apply a transform that spreads the impact of higher bits downward. There is a tradeoff between speed, - * utility, and quality of bit-spreading. Because many common sets of hashes are already reasonably - * distributed (so don't benefit from spreading), and because we use trees to handle large sets of - * collisions in bins, we just XOR some shifted bits in the cheapest possible way to reduce systematic - * lossage, as well as to incorporate impact of the highest bits that would otherwise never be used in - * index calculations because of table bounds. - */ - static final int spread(int h) { - return (h ^ (h >>> 16)) & HASH_BITS; - } - - /** - * Returns a power of two table size for the given desired capacity. See Hackers Delight, sec 3.2 - */ - private static final int tableSizeFor(int c) { - int n = c - 1; - n |= n >>> 1; - n |= n >>> 2; - n |= n >>> 4; - n |= n >>> 8; - n |= n >>> 16; - return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; - } - - /** - * Returns x's Class if it is of the form "class C implements Comparable", else null. - */ - static Class comparableClassFor(Object x) { - if (x instanceof Comparable) { - Class c; - Type[] ts, as; - Type t; - ParameterizedType p; - if ((c = x.getClass()) == String.class) // bypass checks - return c; - if ((ts = c.getGenericInterfaces()) != null) { - for (int i = 0; i < ts.length; ++i) { - if (((t = ts[i]) instanceof ParameterizedType) && - ((p = (ParameterizedType) t).getRawType() == - Comparable.class) && - (as = p.getActualTypeArguments()) != null && - as.length == 1 && as[0] == c) // type arg is c - return c; - } - } - } - return null; - } - - /** - * Returns k.compareTo(x) if x matches kc (k's screened comparable class), else 0. - */ - @SuppressWarnings({"rawtypes", "unchecked"}) // for cast to Comparable - static int compareComparables(Class kc, Object k, Object x) { - return (x == null || x.getClass() != kc ? 0 : - ((Comparable) k).compareTo(x)); - } - - /* ---------------- Table element access -------------- */ - - /* - * Volatile access methods are used for table elements as well as - * elements of in-progress next table while resizing. All uses of - * the tab arguments must be null checked by callers. All callers - * also paranoically precheck that tab's length is not zero (or an - * equivalent check), thus ensuring that any index argument taking - * the form of a hash value anded with (length - 1) is a valid - * index. Note that, to be correct wrt arbitrary concurrency - * errors by users, these checks must operate on local variables, - * which accounts for some odd-looking inline assignments below. - * Note that calls to setTabAt always occur within locked regions, - * and so in principle require only release ordering, not - * full volatile semantics, but are currently coded as volatile - * writes to be conservative. - */ - - @SuppressWarnings("unchecked") - static final Node tabAt(Node[] tab, int i) { - return (Node) U.getObjectVolatile(tab, ((long) i << ASHIFT) + ABASE); - } - - static final boolean casTabAt(Node[] tab, int i, - Node c, Node v) { - return U.compareAndSwapObject(tab, ((long) i << ASHIFT) + ABASE, c, v); - } - - static final void setTabAt(Node[] tab, int i, Node v) { - U.putObjectVolatile(tab, ((long) i << ASHIFT) + ABASE, v); - } - - /* ---------------- Fields -------------- */ - - /** - * The array of bins. Lazily initialized upon first insertion. Size is always a power of two. Accessed - * directly by iterators. - */ - transient volatile Node[] table; - - /** - * The next table to use; non-null only while resizing. - */ - private transient volatile Node[] nextTable; - - /** - * Base counter value, used mainly when there is no contention, but also as a fallback during table - * initialization races. Updated via CAS. - */ - private transient volatile long baseCount; - - /** - * Table initialization and resizing control. When negative, the table is being initialized or resized: - * -1 for initialization, else -(1 + the number of active resizing threads). Otherwise, when table is - * null, holds the initial table size to use upon creation, or 0 for default. After initialization, holds - * the next element count value upon which to resize the table. - */ - private transient volatile int sizeCtl; - - /** - * The next table index (plus one) to split while resizing. - */ - private transient volatile int transferIndex; - - /** - * Spinlock (locked via CAS) used when resizing and/or creating CounterCells. - */ - private transient volatile int cellsBusy; - - /** - * Table of counter cells. When non-null, size is a power of 2. - */ - private transient volatile CounterCell[] counterCells; - - - - /* ---------------- Public operations -------------- */ - - /** - * Creates a new, empty map with the default initial table size (16). - */ - public ConcurrentHashMap() { - } - - /** - * Creates a new, empty map with an initial table size based on the given number of elements ({@code - * initialCapacity}), table density ({@code loadFactor}), and number of concurrently updating threads - * ({@code concurrencyLevel}). - * - * @param initialCapacity the initial capacity. The implementation performs internal sizing to - * accommodate this many elements, given the specified load factor. - * @param loadFactor the load factor (table density) for establishing the initial table size - * @param concurrencyLevel the estimated number of concurrently updating threads. The implementation may - * use this value as a sizing hint. - * @throws IllegalArgumentException if the initial capacity is negative or the load factor or - * concurrencyLevel are nonpositive - */ - public ConcurrentHashMap(int initialCapacity, - float loadFactor, int concurrencyLevel) { - if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0) - throw new IllegalArgumentException(); - if (initialCapacity < concurrencyLevel) // Use at least as many bins - initialCapacity = concurrencyLevel; // as estimated threads - long size = (long) (1.0 + (long) initialCapacity / loadFactor); - int cap = (size >= (long) MAXIMUM_CAPACITY) ? - MAXIMUM_CAPACITY : tableSizeFor((int) size); - this.sizeCtl = cap; - } - - - - /** - * Implementation for put and putIfAbsent - */ - V putVal(K key, V value, boolean onlyIfAbsent) { - if (key == null || value == null) throw new NullPointerException(); - int hash = spread(key.hashCode()); - int binCount = 0; - for (Node[] tab = table; ; ) { - Node f; - int n, i, fh; - if (tab == null || (n = tab.length) == 0) - tab = initTable(); - else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { - if (casTabAt(tab, i, null, - new Node(hash, key, value, null))) - break; // no lock when adding to empty bin - } else if ((fh = f.hash) == MOVED) - tab = helpTransfer(tab, f); - else { - V oldVal = null; - synchronized (f) { - if (tabAt(tab, i) == f) { - if (fh >= 0) { - binCount = 1; - for (Node e = f; ; ++binCount) { - K ek; - if (e.hash == hash && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { - oldVal = e.val; - if (!onlyIfAbsent) - e.val = value; - break; - } - Node pred = e; - if ((e = e.next) == null) { - pred.next = new Node(hash, key, - value, null); - break; - } - } - } else if (f instanceof TreeBin) { - Node p; - binCount = 2; - if ((p = ((TreeBin) f).putTreeVal(hash, key, - value)) != null) { - oldVal = p.val; - if (!onlyIfAbsent) - p.val = value; - } - } - } - } - if (binCount != 0) { - if (binCount >= TREEIFY_THRESHOLD) - treeifyBin(tab, i); - if (oldVal != null) - return oldVal; - break; - } - } - } - addCount(1L, binCount); - return null; - } - - - - - - /** - * Implementation for the four public remove/replace methods: Replaces node value with v, conditional upon - * match of cv if non-null. If resulting value is null, delete. - */ - final V replaceNode(Object key, V value, Object cv) { - int hash = spread(key.hashCode()); - for (Node[] tab = table; ; ) { - Node f; - int n, i, fh; - if (tab == null || (n = tab.length) == 0 || - (f = tabAt(tab, i = (n - 1) & hash)) == null) - break; - else if ((fh = f.hash) == MOVED) - tab = helpTransfer(tab, f); - else { - V oldVal = null; - boolean validated = false; - synchronized (f) { - if (tabAt(tab, i) == f) { - if (fh >= 0) { - validated = true; - for (Node e = f, pred = null; ; ) { - K ek; - if (e.hash == hash && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { - V ev = e.val; - if (cv == null || cv == ev || - (ev != null && cv.equals(ev))) { - oldVal = ev; - if (value != null) - e.val = value; - else if (pred != null) - pred.next = e.next; - else - setTabAt(tab, i, e.next); - } - break; - } - pred = e; - if ((e = e.next) == null) - break; - } - } else if (f instanceof TreeBin) { - validated = true; - TreeBin t = (TreeBin) f; - TreeNode r, p; - if ((r = t.root) != null && - (p = r.findTreeNode(hash, key, null)) != null) { - V pv = p.val; - if (cv == null || cv == pv || - (pv != null && cv.equals(pv))) { - oldVal = pv; - if (value != null) - p.val = value; - else if (t.removeTreeNode(p)) - setTabAt(tab, i, untreeify(t.first)); - } - } - } - } - } - if (validated) { - if (oldVal != null) { - if (value == null) - addCount(-1L, -1); - return oldVal; - } - break; - } - } - } - return null; - } - - - - - - - // Overrides of JDK8+ Map extension method defaults - - /** - * Returns the value to which the specified key is mapped, or the given default value if this map contains - * no mapping for the key. - * - * @param key the key whose associated value is to be returned - * @param defaultValue the value to return if this map contains no mapping for the given key - * @return the mapping for the key, if present; else the default value - * @throws NullPointerException if the specified key is null - */ - public V getOrDefault(Object key, V defaultValue) { - V v; - return (v = get(key)) == null ? defaultValue : v; - } - - - /** - * If the value for the specified key is present, attempts to compute a new mapping given the key and its - * current mapped value. The entire method invocation is performed atomically. Some attempted update - * operations on this map by other threads may be blocked while computation is in progress, so the - * computation should be short and simple, and must not attempt to update any other mappings of this map. - * - * @param key key with which a value may be associated - * @param remappingFunction the function to compute a value - * @return the new value associated with the specified key, or null if none - * @throws NullPointerException if the specified key or remappingFunction is null - * @throws IllegalStateException if the computation detectably attempts a recursive update to this map - * that would otherwise never complete - * @throws RuntimeException or Error if the remappingFunction does so, in which case the mapping is - * unchanged - */ - public V computeIfPresent(K key, BiFunction remappingFunction) { - if (key == null || remappingFunction == null) - throw new NullPointerException(); - int h = spread(key.hashCode()); - V val = null; - int delta = 0; - int binCount = 0; - for (Node[] tab = table; ; ) { - Node f; - int n, i, fh; - if (tab == null || (n = tab.length) == 0) - tab = initTable(); - else if ((f = tabAt(tab, i = (n - 1) & h)) == null) - break; - else if ((fh = f.hash) == MOVED) - tab = helpTransfer(tab, f); - else { - synchronized (f) { - if (tabAt(tab, i) == f) { - if (fh >= 0) { - binCount = 1; - for (Node e = f, pred = null; ; ++binCount) { - K ek; - if (e.hash == h && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { - val = remappingFunction.apply(key, e.val); - if (val != null) - e.val = val; - else { - delta = -1; - Node en = e.next; - if (pred != null) - pred.next = en; - else - setTabAt(tab, i, en); - } - break; - } - pred = e; - if ((e = e.next) == null) - break; - } - } else if (f instanceof TreeBin) { - binCount = 2; - TreeBin t = (TreeBin) f; - TreeNode r, p; - if ((r = t.root) != null && - (p = r.findTreeNode(h, key, null)) != null) { - val = remappingFunction.apply(key, p.val); - if (val != null) - p.val = val; - else { - delta = -1; - if (t.removeTreeNode(p)) - setTabAt(tab, i, untreeify(t.first)); - } - } - } - } - } - if (binCount != 0) - break; - } - } - if (delta != 0) - addCount((long) delta, binCount); - return val; - } - - - - - /** - * Returns the number of mappings. This method should be used instead of {@link #size} because a - * ConcurrentHashMap may contain more mappings than can be represented as an int. The value returned is an - * estimate; the actual count may differ if there are concurrent insertions or removals. - * - * @return the number of mappings - * @since 1.8 - */ - public long mappingCount() { - long n = sumCount(); - return (n < 0L) ? 0L : n; // ignore transient negative values - } - - - - /* ---------------- Special Nodes -------------- */ - - /** - * A node inserted at head of bins during transfer operations. - */ - static final class ForwardingNode extends Node { - final Node[] nextTable; - - ForwardingNode(Node[] tab) { - super(MOVED, null, null, null); - this.nextTable = tab; - } - - Node find(int h, Object k) { - // loop to avoid arbitrarily deep recursion on forwarding nodes - outer: - for (Node[] tab = nextTable; ; ) { - Node e; - int n; - if (k == null || tab == null || (n = tab.length) == 0 || - (e = tabAt(tab, (n - 1) & h)) == null) - return null; - for (; ; ) { - int eh; - K ek; - if ((eh = e.hash) == h && - ((ek = e.key) == k || (ek != null && k.equals(ek)))) - return e; - if (eh < 0) { - if (e instanceof ForwardingNode) { - tab = ((ForwardingNode) e).nextTable; - continue outer; - } else - return e.find(h, k); - } - if ((e = e.next) == null) - return null; - } - } - } - } - - /** - * A place-holder node used in computeIfAbsent and compute - */ - static final class ReservationNode extends Node { - ReservationNode() { - super(RESERVED, null, null, null); - } - - Node find(int h, Object k) { - return null; - } - } - - /* ---------------- Table Initialization and Resizing -------------- */ - - /** - * Returns the stamp bits for resizing a table of size n. Must be negative when shifted left by - * RESIZE_STAMP_SHIFT. - */ - static final int resizeStamp(int n) { - return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1)); - } - - /** - * Initializes table, using the size recorded in sizeCtl. - */ - private final Node[] initTable() { - Node[] tab; - int sc; - while ((tab = table) == null || tab.length == 0) { - if ((sc = sizeCtl) < 0) - Thread.yield(); // lost initialization race; just spin - else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { - try { - if ((tab = table) == null || tab.length == 0) { - int n = (sc > 0) ? sc : DEFAULT_CAPACITY; - @SuppressWarnings("unchecked") - Node[] nt = (Node[]) new Node[n]; - table = tab = nt; - sc = n - (n >>> 2); - } - } finally { - sizeCtl = sc; - } - break; - } - } - return tab; - } - - /** - * Adds to count, and if table is too small and not already resizing, initiates transfer. If already - * resizing, helps perform transfer if work is available. Rechecks occupancy after a transfer to see if - * another resize is already needed because resizings are lagging additions. - * - * @param x the count to add - * @param check if <0, don't check resize, if <= 1 only check if uncontended - */ - private final void addCount(long x, int check) { - CounterCell[] as; - long b, s; - if ((as = counterCells) != null || - !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { - CounterCell a; - long v; - int m; - boolean uncontended = true; - if (as == null || (m = as.length - 1) < 0 || - (a = as[probe() & m]) == null || - !(uncontended = - U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { - fullAddCount(x, uncontended); - return; - } - if (check <= 1) - return; - s = sumCount(); - } - if (check >= 0) { - Node[] tab, nt; - int n, sc; - while (s >= (long) (sc = sizeCtl) && (tab = table) != null && - (n = tab.length) < MAXIMUM_CAPACITY) { - int rs = resizeStamp(n); - if (sc < 0) { - if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || - sc == rs + MAX_RESIZERS || (nt = nextTable) == null || - transferIndex <= 0) - break; - if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) - transfer(tab, nt); - } else if (U.compareAndSwapInt(this, SIZECTL, sc, - (rs << RESIZE_STAMP_SHIFT) + 2)) - transfer(tab, null); - s = sumCount(); - } - } - } - - private int probe() { - - try { - final Class aClass = Class.forName("java.util.concurrent.ThreadLocalRandom"); - final Method stringHash32 = aClass.getMethod("getProbe"); - stringHash32.setAccessible(true); - return (int) stringHash32.invoke(aClass); - - } catch (Exception e) { - e.printStackTrace(); - } - return 0; - } - - - private int localInit() { - try { - final Class aClass = Class.forName("java.util.concurrent.ThreadLocalRandom"); - final Method stringHash32 = aClass.getMethod("localInit"); - stringHash32.setAccessible(true); - return (int) stringHash32.invoke(aClass); - - } catch (Exception e) { - e.printStackTrace(); - } - return 0; - - } - - private int advanceProbe(int h) { - try { - final Class aClass = Class.forName("java.util.concurrent.ThreadLocalRandom"); - final Method stringHash32 = aClass.getMethod("advanceProbe"); - stringHash32.setAccessible(true); - return (int) stringHash32.invoke(aClass, new Object[]{h}); - - } catch (Exception e) { - e.printStackTrace(); - } - return 0; - - } - - /** - * Helps transfer if a resize is in progress. - */ - final Node[] helpTransfer(Node[] tab, Node f) { - Node[] nextTab; - int sc; - if (tab != null && (f instanceof ForwardingNode) && - (nextTab = ((ForwardingNode) f).nextTable) != null) { - int rs = resizeStamp(tab.length); - while (nextTab == nextTable && table == tab && - (sc = sizeCtl) < 0) { - if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || - sc == rs + MAX_RESIZERS || transferIndex <= 0) - break; - if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) { - transfer(tab, nextTab); - break; - } - } - return nextTab; - } - return table; - } - - /** - * Tries to presize table to accommodate the given number of elements. - * - * @param size number of elements (doesn't need to be perfectly accurate) - */ - private final void tryPresize(int size) { - int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : - tableSizeFor(size + (size >>> 1) + 1); - int sc; - while ((sc = sizeCtl) >= 0) { - Node[] tab = table; - int n; - if (tab == null || (n = tab.length) == 0) { - n = (sc > c) ? sc : c; - if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { - try { - if (table == tab) { - @SuppressWarnings("unchecked") - Node[] nt = (Node[]) new Node[n]; - table = nt; - sc = n - (n >>> 2); - } - } finally { - sizeCtl = sc; - } - } - } else if (c <= sc || n >= MAXIMUM_CAPACITY) - break; - else if (tab == table) { - int rs = resizeStamp(n); - if (sc < 0) { - Node[] nt; - if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || - sc == rs + MAX_RESIZERS || (nt = nextTable) == null || - transferIndex <= 0) - break; - if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) - transfer(tab, nt); - } else if (U.compareAndSwapInt(this, SIZECTL, sc, - (rs << RESIZE_STAMP_SHIFT) + 2)) - transfer(tab, null); - } - } - } - - /** - * Moves and/or copies the nodes in each bin to new table. See above for explanation. - */ - private final void transfer(Node[] tab, Node[] nextTab) { - int n = tab.length, stride; - if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) - stride = MIN_TRANSFER_STRIDE; // subdivide range - if (nextTab == null) { // initiating - try { - @SuppressWarnings("unchecked") - Node[] nt = (Node[]) new Node[n << 1]; - nextTab = nt; - } catch (Throwable ex) { // try to cope with OOME - sizeCtl = Integer.MAX_VALUE; - return; - } - nextTable = nextTab; - transferIndex = n; - } - int nextn = nextTab.length; - ForwardingNode fwd = new ForwardingNode(nextTab); - boolean advance = true; - boolean finishing = false; // to ensure sweep before committing nextTab - for (int i = 0, bound = 0; ; ) { - Node f; - int fh; - while (advance) { - int nextIndex, nextBound; - if (--i >= bound || finishing) - advance = false; - else if ((nextIndex = transferIndex) <= 0) { - i = -1; - advance = false; - } else if (U.compareAndSwapInt - (this, TRANSFERINDEX, nextIndex, - nextBound = (nextIndex > stride ? - nextIndex - stride : 0))) { - bound = nextBound; - i = nextIndex - 1; - advance = false; - } - } - if (i < 0 || i >= n || i + n >= nextn) { - int sc; - if (finishing) { - nextTable = null; - table = nextTab; - sizeCtl = (n << 1) - (n >>> 1); - return; - } - if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { - if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) - return; - finishing = advance = true; - i = n; // recheck before commit - } - } else if ((f = tabAt(tab, i)) == null) - advance = casTabAt(tab, i, null, fwd); - else if ((fh = f.hash) == MOVED) - advance = true; // already processed - else { - synchronized (f) { - if (tabAt(tab, i) == f) { - Node ln, hn; - if (fh >= 0) { - int runBit = fh & n; - Node lastRun = f; - for (Node p = f.next; p != null; p = p.next) { - int b = p.hash & n; - if (b != runBit) { - runBit = b; - lastRun = p; - } - } - if (runBit == 0) { - ln = lastRun; - hn = null; - } else { - hn = lastRun; - ln = null; - } - for (Node p = f; p != lastRun; p = p.next) { - int ph = p.hash; - K pk = p.key; - V pv = p.val; - if ((ph & n) == 0) - ln = new Node(ph, pk, pv, ln); - else - hn = new Node(ph, pk, pv, hn); - } - setTabAt(nextTab, i, ln); - setTabAt(nextTab, i + n, hn); - setTabAt(tab, i, fwd); - advance = true; - } else if (f instanceof TreeBin) { - TreeBin t = (TreeBin) f; - TreeNode lo = null, loTail = null; - TreeNode hi = null, hiTail = null; - int lc = 0, hc = 0; - for (Node e = t.first; e != null; e = e.next) { - int h = e.hash; - TreeNode p = new TreeNode - (h, e.key, e.val, null, null); - if ((h & n) == 0) { - if ((p.prev = loTail) == null) - lo = p; - else - loTail.next = p; - loTail = p; - ++lc; - } else { - if ((p.prev = hiTail) == null) - hi = p; - else - hiTail.next = p; - hiTail = p; - ++hc; - } - } - ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) : - (hc != 0) ? new TreeBin(lo) : t; - hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) : - (lc != 0) ? new TreeBin(hi) : t; - setTabAt(nextTab, i, ln); - setTabAt(nextTab, i + n, hn); - setTabAt(tab, i, fwd); - advance = true; - } - } - } - } - } - } - - /* ---------------- Counter support -------------- */ - - /** - * A padded cell for distributing counts. Adapted from LongAdder and Striped64. See their internal docs - * for explanation. - */ - @sun.misc.Contended - static final class CounterCell { - volatile long value; - - CounterCell(long x) { - value = x; - } - } - - final long sumCount() { - CounterCell[] as = counterCells; - CounterCell a; - long sum = baseCount; - if (as != null) { - for (int i = 0; i < as.length; ++i) { - if ((a = as[i]) != null) - sum += a.value; - } - } - return sum; - } - - // See LongAdder version for explanation - private final void fullAddCount(long x, boolean wasUncontended) { - int h; - if ((h = probe()) == 0) { - localInit(); // force initialization - h = probe(); - wasUncontended = true; - } - boolean collide = false; // True if last slot nonempty - for (; ; ) { - CounterCell[] as; - CounterCell a; - int n; - long v; - if ((as = counterCells) != null && (n = as.length) > 0) { - if ((a = as[(n - 1) & h]) == null) { - if (cellsBusy == 0) { // Try to attach new Cell - CounterCell r = new CounterCell(x); // Optimistic create - if (cellsBusy == 0 && - U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { - boolean created = false; - try { // Recheck under lock - CounterCell[] rs; - int m, j; - if ((rs = counterCells) != null && - (m = rs.length) > 0 && - rs[j = (m - 1) & h] == null) { - rs[j] = r; - created = true; - } - } finally { - cellsBusy = 0; - } - if (created) - break; - continue; // Slot is now non-empty - } - } - collide = false; - } else if (!wasUncontended) // CAS already known to fail - wasUncontended = true; // Continue after rehash - else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)) - break; - else if (counterCells != as || n >= NCPU) - collide = false; // At max size or stale - else if (!collide) - collide = true; - else if (cellsBusy == 0 && - U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { - try { - if (counterCells == as) {// Expand table unless stale - CounterCell[] rs = new CounterCell[n << 1]; - for (int i = 0; i < n; ++i) - rs[i] = as[i]; - counterCells = rs; - } - } finally { - cellsBusy = 0; - } - collide = false; - continue; // Retry with expanded table - } - h = advanceProbe(h); - } else if (cellsBusy == 0 && counterCells == as && - U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { - boolean init = false; - try { // Initialize table - if (counterCells == as) { - CounterCell[] rs = new CounterCell[2]; - rs[h & 1] = new CounterCell(x); - counterCells = rs; - init = true; - } - } finally { - cellsBusy = 0; - } - if (init) - break; - } else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x)) - break; // Fall back on using base - } - } - - - - - /* ---------------- Conversion from/to TreeBins -------------- */ - - /** - * Replaces all linked nodes in bin at given index unless table is too small, in which case resizes - * instead. - */ - private final void treeifyBin(Node[] tab, int index) { - Node b; - int n, sc; - if (tab != null) { - if ((n = tab.length) < MIN_TREEIFY_CAPACITY) - tryPresize(n << 1); - else if ((b = tabAt(tab, index)) != null && b.hash >= 0) { - synchronized (b) { - if (tabAt(tab, index) == b) { - TreeNode hd = null, tl = null; - for (Node e = b; e != null; e = e.next) { - TreeNode p = - new TreeNode(e.hash, e.key, e.val, - null, null); - if ((p.prev = tl) == null) - hd = p; - else - tl.next = p; - tl = p; - } - setTabAt(tab, index, new TreeBin(hd)); - } - } - } - } - } - - /** - * Returns a list on non-TreeNodes replacing those in given list. - */ - static Node untreeify(Node b) { - Node hd = null, tl = null; - for (Node q = b; q != null; q = q.next) { - Node p = new Node(q.hash, q.key, q.val, null); - if (tl == null) - hd = p; - else - tl.next = p; - tl = p; - } - return hd; - } - - /* ---------------- TreeNodes -------------- */ - - /** - * Nodes for use in TreeBins - */ - static final class TreeNode extends Node { - TreeNode parent; // red-black tree links - TreeNode left; - TreeNode right; - TreeNode prev; // needed to unlink next upon deletion - boolean red; - - TreeNode(int hash, K key, V val, Node next, - TreeNode parent) { - super(hash, key, val, next); - this.parent = parent; - } - - Node find(int h, Object k) { - return findTreeNode(h, k, null); - } - - /** - * Returns the TreeNode (or null if not found) for the given key starting at given root. - */ - final TreeNode findTreeNode(int h, Object k, Class kc) { - if (k != null) { - TreeNode p = this; - do { - int ph, dir; - K pk; - TreeNode q; - TreeNode pl = p.left, pr = p.right; - if ((ph = p.hash) > h) - p = pl; - else if (ph < h) - p = pr; - else if ((pk = p.key) == k || (pk != null && k.equals(pk))) - return p; - else if (pl == null) - p = pr; - else if (pr == null) - p = pl; - else if ((kc != null || - (kc = comparableClassFor(k)) != null) && - (dir = compareComparables(kc, k, pk)) != 0) - p = (dir < 0) ? pl : pr; - else if ((q = pr.findTreeNode(h, k, kc)) != null) - return q; - else - p = pl; - } while (p != null); - } - return null; - } - } - - /* ---------------- TreeBins -------------- */ - - /** - * TreeNodes used at the heads of bins. TreeBins do not hold user keys or values, but instead point to - * list of TreeNodes and their root. They also maintain a parasitic read-write lock forcing writers (who - * hold bin lock) to wait for readers (who do not) to complete before tree restructuring operations. - */ - static final class TreeBin extends Node { - TreeNode root; - volatile TreeNode first; - volatile Thread waiter; - volatile int lockState; - // values for lockState - static final int WRITER = 1; // set while holding write lock - static final int WAITER = 2; // set when waiting for write lock - static final int READER = 4; // increment value for setting read lock - - /** - * Tie-breaking utility for ordering insertions when equal hashCodes and non-comparable. We don't - * require a total order, just a consistent insertion rule to maintain equivalence across - * rebalancings. Tie-breaking further than necessary simplifies testing a bit. - */ - static int tieBreakOrder(Object a, Object b) { - int d; - if (a == null || b == null || - (d = a.getClass().getName(). - compareTo(b.getClass().getName())) == 0) - d = (System.identityHashCode(a) <= System.identityHashCode(b) ? - -1 : 1); - return d; - } - - /** - * Creates bin with initial set of nodes headed by b. - */ - TreeBin(TreeNode b) { - super(TREEBIN, null, null, null); - this.first = b; - TreeNode r = null; - for (TreeNode x = b, next; x != null; x = next) { - next = (TreeNode) x.next; - x.left = x.right = null; - if (r == null) { - x.parent = null; - x.red = false; - r = x; - } else { - K k = x.key; - int h = x.hash; - Class kc = null; - for (TreeNode p = r; ; ) { - int dir, ph; - K pk = p.key; - if ((ph = p.hash) > h) - dir = -1; - else if (ph < h) - dir = 1; - else if ((kc == null && - (kc = comparableClassFor(k)) == null) || - (dir = compareComparables(kc, k, pk)) == 0) - dir = tieBreakOrder(k, pk); - TreeNode xp = p; - if ((p = (dir <= 0) ? p.left : p.right) == null) { - x.parent = xp; - if (dir <= 0) - xp.left = x; - else - xp.right = x; - r = balanceInsertion(r, x); - break; - } - } - } - } - this.root = r; - assert checkInvariants(root); - } - - /** - * Acquires write lock for tree restructuring. - */ - private final void lockRoot() { - if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER)) - contendedLock(); // offload to separate method - } - - /** - * Releases write lock for tree restructuring. - */ - private final void unlockRoot() { - lockState = 0; - } - - /** - * Possibly blocks awaiting root lock. - */ - private final void contendedLock() { - boolean waiting = false; - for (int s; ; ) { - if (((s = lockState) & ~WAITER) == 0) { - if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) { - if (waiting) - waiter = null; - return; - } - } else if ((s & WAITER) == 0) { - if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) { - waiting = true; - waiter = Thread.currentThread(); - } - } else if (waiting) - LockSupport.park(this); - } - } - - /** - * Returns matching node or null if none. Tries to search using tree comparisons from root, but - * continues linear search when lock not available. - */ - final Node find(int h, Object k) { - if (k != null) { - for (Node e = first; e != null; ) { - int s; - K ek; - if (((s = lockState) & (WAITER | WRITER)) != 0) { - if (e.hash == h && - ((ek = e.key) == k || (ek != null && k.equals(ek)))) - return e; - e = e.next; - } else if (U.compareAndSwapInt(this, LOCKSTATE, s, - s + READER)) { - TreeNode r, p; - try { - p = ((r = root) == null ? null : - r.findTreeNode(h, k, null)); - } finally { - Thread w; - if (U.getAndAddInt(this, LOCKSTATE, -READER) == - (READER | WAITER) && (w = waiter) != null) - LockSupport.unpark(w); - } - return p; - } - } - } - return null; - } - - /** - * Finds or adds a node. - * - * @return null if added - */ - final TreeNode putTreeVal(int h, K k, V v) { - Class kc = null; - boolean searched = false; - for (TreeNode p = root; ; ) { - int dir, ph; - K pk; - if (p == null) { - first = root = new TreeNode(h, k, v, null, null); - break; - } else if ((ph = p.hash) > h) - dir = -1; - else if (ph < h) - dir = 1; - else if ((pk = p.key) == k || (pk != null && k.equals(pk))) - return p; - else if ((kc == null && - (kc = comparableClassFor(k)) == null) || - (dir = compareComparables(kc, k, pk)) == 0) { - if (!searched) { - TreeNode q, ch; - searched = true; - if (((ch = p.left) != null && - (q = ch.findTreeNode(h, k, kc)) != null) || - ((ch = p.right) != null && - (q = ch.findTreeNode(h, k, kc)) != null)) - return q; - } - dir = tieBreakOrder(k, pk); - } - - TreeNode xp = p; - if ((p = (dir <= 0) ? p.left : p.right) == null) { - TreeNode x, f = first; - first = x = new TreeNode(h, k, v, f, xp); - if (f != null) - f.prev = x; - if (dir <= 0) - xp.left = x; - else - xp.right = x; - if (!xp.red) - x.red = true; - else { - lockRoot(); - try { - root = balanceInsertion(root, x); - } finally { - unlockRoot(); - } - } - break; - } - } - assert checkInvariants(root); - return null; - } - - /** - * Removes the given node, that must be present before this call. This is messier than typical - * red-black deletion code because we cannot swap the contents of an interior node with a leaf - * successor that is pinned by "next" pointers that are accessible independently of lock. So instead - * we swap the tree linkages. - * - * @return true if now too small, so should be untreeified - */ - final boolean removeTreeNode(TreeNode p) { - TreeNode next = (TreeNode) p.next; - TreeNode pred = p.prev; // unlink traversal pointers - TreeNode r, rl; - if (pred == null) - first = next; - else - pred.next = next; - if (next != null) - next.prev = pred; - if (first == null) { - root = null; - return true; - } - if ((r = root) == null || r.right == null || // too small - (rl = r.left) == null || rl.left == null) - return true; - lockRoot(); - try { - TreeNode replacement; - TreeNode pl = p.left; - TreeNode pr = p.right; - if (pl != null && pr != null) { - TreeNode s = pr, sl; - while ((sl = s.left) != null) // find successor - s = sl; - boolean c = s.red; - s.red = p.red; - p.red = c; // swap colors - TreeNode sr = s.right; - TreeNode pp = p.parent; - if (s == pr) { // p was s's direct parent - p.parent = s; - s.right = p; - } else { - TreeNode sp = s.parent; - if ((p.parent = sp) != null) { - if (s == sp.left) - sp.left = p; - else - sp.right = p; - } - if ((s.right = pr) != null) - pr.parent = s; - } - p.left = null; - if ((p.right = sr) != null) - sr.parent = p; - if ((s.left = pl) != null) - pl.parent = s; - if ((s.parent = pp) == null) - r = s; - else if (p == pp.left) - pp.left = s; - else - pp.right = s; - if (sr != null) - replacement = sr; - else - replacement = p; - } else if (pl != null) - replacement = pl; - else if (pr != null) - replacement = pr; - else - replacement = p; - if (replacement != p) { - TreeNode pp = replacement.parent = p.parent; - if (pp == null) - r = replacement; - else if (p == pp.left) - pp.left = replacement; - else - pp.right = replacement; - p.left = p.right = p.parent = null; - } - - root = (p.red) ? r : balanceDeletion(r, replacement); - - if (p == replacement) { // detach pointers - TreeNode pp; - if ((pp = p.parent) != null) { - if (p == pp.left) - pp.left = null; - else if (p == pp.right) - pp.right = null; - p.parent = null; - } - } - } finally { - unlockRoot(); - } - assert checkInvariants(root); - return false; - } - - /* ------------------------------------------------------------ */ - // Red-black tree methods, all adapted from CLR - - static TreeNode rotateLeft(TreeNode root, - TreeNode p) { - TreeNode r, pp, rl; - if (p != null && (r = p.right) != null) { - if ((rl = p.right = r.left) != null) - rl.parent = p; - if ((pp = r.parent = p.parent) == null) - (root = r).red = false; - else if (pp.left == p) - pp.left = r; - else - pp.right = r; - r.left = p; - p.parent = r; - } - return root; - } - - static TreeNode rotateRight(TreeNode root, - TreeNode p) { - TreeNode l, pp, lr; - if (p != null && (l = p.left) != null) { - if ((lr = p.left = l.right) != null) - lr.parent = p; - if ((pp = l.parent = p.parent) == null) - (root = l).red = false; - else if (pp.right == p) - pp.right = l; - else - pp.left = l; - l.right = p; - p.parent = l; - } - return root; - } - - static TreeNode balanceInsertion(TreeNode root, - TreeNode x) { - x.red = true; - for (TreeNode xp, xpp, xppl, xppr; ; ) { - if ((xp = x.parent) == null) { - x.red = false; - return x; - } else if (!xp.red || (xpp = xp.parent) == null) - return root; - if (xp == (xppl = xpp.left)) { - if ((xppr = xpp.right) != null && xppr.red) { - xppr.red = false; - xp.red = false; - xpp.red = true; - x = xpp; - } else { - if (x == xp.right) { - root = rotateLeft(root, x = xp); - xpp = (xp = x.parent) == null ? null : xp.parent; - } - if (xp != null) { - xp.red = false; - if (xpp != null) { - xpp.red = true; - root = rotateRight(root, xpp); - } - } - } - } else { - if (xppl != null && xppl.red) { - xppl.red = false; - xp.red = false; - xpp.red = true; - x = xpp; - } else { - if (x == xp.left) { - root = rotateRight(root, x = xp); - xpp = (xp = x.parent) == null ? null : xp.parent; - } - if (xp != null) { - xp.red = false; - if (xpp != null) { - xpp.red = true; - root = rotateLeft(root, xpp); - } - } - } - } - } - } - - static TreeNode balanceDeletion(TreeNode root, - TreeNode x) { - for (TreeNode xp, xpl, xpr; ; ) { - if (x == null || x == root) - return root; - else if ((xp = x.parent) == null) { - x.red = false; - return x; - } else if (x.red) { - x.red = false; - return root; - } else if ((xpl = xp.left) == x) { - if ((xpr = xp.right) != null && xpr.red) { - xpr.red = false; - xp.red = true; - root = rotateLeft(root, xp); - xpr = (xp = x.parent) == null ? null : xp.right; - } - if (xpr == null) - x = xp; - else { - TreeNode sl = xpr.left, sr = xpr.right; - if ((sr == null || !sr.red) && - (sl == null || !sl.red)) { - xpr.red = true; - x = xp; - } else { - if (sr == null || !sr.red) { - if (sl != null) - sl.red = false; - xpr.red = true; - root = rotateRight(root, xpr); - xpr = (xp = x.parent) == null ? - null : xp.right; - } - if (xpr != null) { - xpr.red = (xp == null) ? false : xp.red; - if ((sr = xpr.right) != null) - sr.red = false; - } - if (xp != null) { - xp.red = false; - root = rotateLeft(root, xp); - } - x = root; - } - } - } else { // symmetric - if (xpl != null && xpl.red) { - xpl.red = false; - xp.red = true; - root = rotateRight(root, xp); - xpl = (xp = x.parent) == null ? null : xp.left; - } - if (xpl == null) - x = xp; - else { - TreeNode sl = xpl.left, sr = xpl.right; - if ((sl == null || !sl.red) && - (sr == null || !sr.red)) { - xpl.red = true; - x = xp; - } else { - if (sl == null || !sl.red) { - if (sr != null) - sr.red = false; - xpl.red = true; - root = rotateLeft(root, xpl); - xpl = (xp = x.parent) == null ? - null : xp.left; - } - if (xpl != null) { - xpl.red = (xp == null) ? false : xp.red; - if ((sl = xpl.left) != null) - sl.red = false; - } - if (xp != null) { - xp.red = false; - root = rotateRight(root, xp); - } - x = root; - } - } - } - } - } - - /** - * Recursive invariant check - */ - static boolean checkInvariants(TreeNode t) { - TreeNode tp = t.parent, tl = t.left, tr = t.right, - tb = t.prev, tn = (TreeNode) t.next; - if (tb != null && tb.next != t) - return false; - if (tn != null && tn.prev != t) - return false; - if (tp != null && t != tp.left && t != tp.right) - return false; - if (tl != null && (tl.parent != t || tl.hash > t.hash)) - return false; - if (tr != null && (tr.parent != t || tr.hash < t.hash)) - return false; - if (t.red && tl != null && tl.red && tr != null && tr.red) - return false; - if (tl != null && !checkInvariants(tl)) - return false; - if (tr != null && !checkInvariants(tr)) - return false; - return true; - } - - private static final sun.misc.Unsafe U; - private static final long LOCKSTATE; - - static { - try { - U = sun.misc.Unsafe.getUnsafe(); - Class k = TreeBin.class; - LOCKSTATE = U.objectFieldOffset - (k.getDeclaredField("lockState")); - } catch (Exception e) { - throw new Error(e); - } - } - } - - /* ----------------Table Traversal -------------- */ - - /** - * Records the table, its length, and current traversal index for a traverser that must process a region - * of a forwarded table before proceeding with current table. - */ - static final class TableStack { - int length; - int index; - Node[] tab; - TableStack next; - } - - /** - * Encapsulates traversal for methods such as containsValue; also serves as a base class for other - * iterators and spliterators. - * - * Method advance visits once each still-valid node that was reachable upon iterator construction. It - * might miss some that were added to a bin after the bin was visited, which is OK wrt consistency - * guarantees. Maintaining this property in the face of possible ongoing resizes requires a fair amount of - * bookkeeping state that is difficult to optimize away amidst volatile accesses. Even so, traversal - * maintains reasonable throughput. - * - * Normally, iteration proceeds bin-by-bin traversing lists. However, if the table has been resized, then - * all future steps must traverse both the bin at the current index as well as at (index + baseSize); and - * so on for further resizings. To paranoically cope with potential sharing by users of iterators across - * threads, iteration terminates if a bounds checks fails for a table read. - */ - static class Traverser { - Node[] tab; // current table; updated if resized - Node next; // the next entry to use - TableStack stack, spare; // to save/restore on ForwardingNodes - int index; // index of bin to use next - int baseIndex; // current index of initial table - int baseLimit; // index bound for initial table - final int baseSize; // initial table size - - Traverser(Node[] tab, int size, int index, int limit) { - this.tab = tab; - this.baseSize = size; - this.baseIndex = this.index = index; - this.baseLimit = limit; - this.next = null; - } - - /** - * Advances if possible, returning next valid node, or null if none. - */ - final Node advance() { - Node e; - if ((e = next) != null) - e = e.next; - for (; ; ) { - Node[] t; - int i, n; // must use locals in checks - if (e != null) - return next = e; - if (baseIndex >= baseLimit || (t = tab) == null || - (n = t.length) <= (i = index) || i < 0) - return next = null; - if ((e = tabAt(t, i)) != null && e.hash < 0) { - if (e instanceof ForwardingNode) { - tab = ((ForwardingNode) e).nextTable; - e = null; - pushState(t, i, n); - continue; - } else if (e instanceof TreeBin) - e = ((TreeBin) e).first; - else - e = null; - } - if (stack != null) - recoverState(n); - else if ((index = i + baseSize) >= n) - index = ++baseIndex; // visit upper slots if present - } - } - - /** - * Saves traversal state upon encountering a forwarding node. - */ - private void pushState(Node[] t, int i, int n) { - TableStack s = spare; // reuse if possible - if (s != null) - spare = s.next; - else - s = new TableStack(); - s.tab = t; - s.length = n; - s.index = i; - s.next = stack; - stack = s; - } - - /** - * Possibly pops traversal state. - * - * @param n length of current table - */ - private void recoverState(int n) { - TableStack s; - int len; - while ((s = stack) != null && (index += (len = s.length)) >= n) { - n = len; - index = s.index; - tab = s.tab; - s.tab = null; - TableStack next = s.next; - s.next = spare; // save for reuse - stack = next; - spare = s; - } - if (s == null && (index += baseSize) >= n) - index = ++baseIndex; - } - } - - /** - * Base of key, value, and entry Iterators. Adds fields to Traverser to support iterator.remove. - */ - static class BaseIterator extends Traverser { - final ConcurrentHashMap map; - Node lastReturned; - - BaseIterator(Node[] tab, int size, int index, int limit, - ConcurrentHashMap map) { - super(tab, size, index, limit); - this.map = map; - advance(); - } - - public final boolean hasNext() { - return next != null; - } - - public final boolean hasMoreElements() { - return next != null; - } - - public final void remove() { - Node p; - if ((p = lastReturned) == null) - throw new IllegalStateException(); - lastReturned = null; - map.replaceNode(p.key, null, null); - } - } - - static final class KeyIterator extends BaseIterator - implements Iterator, Enumeration { - KeyIterator(Node[] tab, int index, int size, int limit, - ConcurrentHashMap map) { - super(tab, index, size, limit, map); - } - - public final K next() { - Node p; - if ((p = next) == null) - throw new NoSuchElementException(); - K k = p.key; - lastReturned = p; - advance(); - return k; - } - - public final K nextElement() { - return next(); - } - } - - static final class ValueIterator extends BaseIterator - implements Iterator, Enumeration { - ValueIterator(Node[] tab, int index, int size, int limit, - ConcurrentHashMap map) { - super(tab, index, size, limit, map); - } - - public final V next() { - Node p; - if ((p = next) == null) - throw new NoSuchElementException(); - V v = p.val; - lastReturned = p; - advance(); - return v; - } - - public final V nextElement() { - return next(); - } - } - - static final class EntryIterator extends BaseIterator - implements Iterator> { - EntryIterator(Node[] tab, int index, int size, int limit, - ConcurrentHashMap map) { - super(tab, index, size, limit, map); - } - - public final Map.Entry next() { - Node p; - if ((p = next) == null) - throw new NoSuchElementException(); - K k = p.key; - V v = p.val; - lastReturned = p; - advance(); - return new MapEntry(k, v, map); - } - } - - /** - * Exported Entry for EntryIterator - */ - static final class MapEntry implements Map.Entry { - final K key; // non-null - V val; // non-null - final ConcurrentHashMap map; - - MapEntry(K key, V val, ConcurrentHashMap map) { - this.key = key; - this.val = val; - this.map = map; - } - - public K getKey() { - return key; - } - - public V getValue() { - return val; - } - - public int hashCode() { - return key.hashCode() ^ val.hashCode(); - } - - public String toString() { - return key + "=" + val; - } - - public boolean equals(Object o) { - Object k, v; - Map.Entry e; - return ((o instanceof Map.Entry) && - (k = (e = (Map.Entry) o).getKey()) != null && - (v = e.getValue()) != null && - (k == key || k.equals(key)) && - (v == val || v.equals(val))); - } - - /** - * Sets our entry's value and writes through to the map. The value to return is somewhat arbitrary - * here. Since we do not necessarily track asynchronous changes, the most recent "previous" value - * could be different from what we return (or could even have been removed, in which case the put will - * re-establish). We do not and cannot guarantee more. - */ - public V setValue(V value) { - if (value == null) throw new NullPointerException(); - V v = val; - val = value; - map.put(key, value); - return v; - } - } - - static final class KeySpliterator extends Traverser - implements Spliterator { - long est; // size estimate - - KeySpliterator(Node[] tab, int size, int index, int limit, - long est) { - super(tab, size, index, limit); - this.est = est; - } - - public Spliterator trySplit() { - int i, f, h; - return (h = ((i = baseIndex) + (f = baseLimit)) >>> 1) <= i ? null : - new KeySpliterator(tab, baseSize, baseLimit = h, - f, est >>>= 1); - } - - public void forEachRemaining(Consumer action) { - if (action == null) throw new NullPointerException(); - for (Node p; (p = advance()) != null; ) - action.accept(p.key); - } - - public boolean tryAdvance(Consumer action) { - if (action == null) throw new NullPointerException(); - Node p; - if ((p = advance()) == null) - return false; - action.accept(p.key); - return true; - } - - public long estimateSize() { - return est; - } - - public int characteristics() { - return Spliterator.DISTINCT | Spliterator.CONCURRENT | - Spliterator.NONNULL; - } - } - - static final class ValueSpliterator extends Traverser - implements Spliterator { - long est; // size estimate - - ValueSpliterator(Node[] tab, int size, int index, int limit, - long est) { - super(tab, size, index, limit); - this.est = est; - } - - public Spliterator trySplit() { - int i, f, h; - return (h = ((i = baseIndex) + (f = baseLimit)) >>> 1) <= i ? null : - new ValueSpliterator(tab, baseSize, baseLimit = h, - f, est >>>= 1); - } - - public void forEachRemaining(Consumer action) { - if (action == null) throw new NullPointerException(); - for (Node p; (p = advance()) != null; ) - action.accept(p.val); - } - - public boolean tryAdvance(Consumer action) { - if (action == null) throw new NullPointerException(); - Node p; - if ((p = advance()) == null) - return false; - action.accept(p.val); - return true; - } - - public long estimateSize() { - return est; - } - - public int characteristics() { - return Spliterator.CONCURRENT | Spliterator.NONNULL; - } - } - - static final class EntrySpliterator extends Traverser - implements Spliterator> { - final ConcurrentHashMap map; // To export MapEntry - long est; // size estimate - - EntrySpliterator(Node[] tab, int size, int index, int limit, - long est, ConcurrentHashMap map) { - super(tab, size, index, limit); - this.map = map; - this.est = est; - } - - public Spliterator> trySplit() { - int i, f, h; - return (h = ((i = baseIndex) + (f = baseLimit)) >>> 1) <= i ? null : - new EntrySpliterator(tab, baseSize, baseLimit = h, - f, est >>>= 1, map); - } - - public void forEachRemaining(Consumer> action) { - if (action == null) throw new NullPointerException(); - for (Node p; (p = advance()) != null; ) - action.accept(new MapEntry(p.key, p.val, map)); - } - - public boolean tryAdvance(Consumer> action) { - if (action == null) throw new NullPointerException(); - Node p; - if ((p = advance()) == null) - return false; - action.accept(new MapEntry(p.key, p.val, map)); - return true; - } - - public long estimateSize() { - return est; - } - - public int characteristics() { - return Spliterator.DISTINCT | Spliterator.CONCURRENT | - Spliterator.NONNULL; - } - } - - // Parallel bulk operations - - - // ------------------------------------------------------- - - /** - * Base class for bulk tasks. Repeats some fields and code from class Traverser, because we need to - * subclass CountedCompleter. - */ - @SuppressWarnings("serial") - abstract static class BulkTask extends CountedCompleter { - Node[] tab; // same as Traverser - Node next; - TableStack stack, spare; - int index; - int baseIndex; - int baseLimit; - final int baseSize; - int batch; // split control - - BulkTask(BulkTask par, int b, int i, int f, Node[] t) { - super(par); - this.batch = b; - this.index = this.baseIndex = i; - if ((this.tab = t) == null) - this.baseSize = this.baseLimit = 0; - else if (par == null) - this.baseSize = this.baseLimit = t.length; - else { - this.baseLimit = f; - this.baseSize = par.baseSize; - } - } - - /** - * Same as Traverser version - */ - final Node advance() { - Node e; - if ((e = next) != null) - e = e.next; - for (; ; ) { - Node[] t; - int i, n; - if (e != null) - return next = e; - if (baseIndex >= baseLimit || (t = tab) == null || - (n = t.length) <= (i = index) || i < 0) - return next = null; - if ((e = tabAt(t, i)) != null && e.hash < 0) { - if (e instanceof ForwardingNode) { - tab = ((ForwardingNode) e).nextTable; - e = null; - pushState(t, i, n); - continue; - } else if (e instanceof TreeBin) - e = ((TreeBin) e).first; - else - e = null; - } - if (stack != null) - recoverState(n); - else if ((index = i + baseSize) >= n) - index = ++baseIndex; - } - } - - private void pushState(Node[] t, int i, int n) { - TableStack s = spare; - if (s != null) - spare = s.next; - else - s = new TableStack(); - s.tab = t; - s.length = n; - s.index = i; - s.next = stack; - stack = s; - } - - private void recoverState(int n) { - TableStack s; - int len; - while ((s = stack) != null && (index += (len = s.length)) >= n) { - n = len; - index = s.index; - tab = s.tab; - s.tab = null; - TableStack next = s.next; - s.next = spare; // save for reuse - stack = next; - spare = s; - } - if (s == null && (index += baseSize) >= n) - index = ++baseIndex; - } - } - - /* - * Task classes. Coded in a regular but ugly format/style to - * simplify checks that each variant differs in the right way from - * others. The null screenings exist because compilers cannot tell - * that we've already null-checked task arguments, so we force - * simplest hoisted bypass to help avoid convoluted traps. - */ - @SuppressWarnings("serial") - static final class ForEachKeyTask - extends BulkTask { - final Consumer action; - - ForEachKeyTask - (BulkTask p, int b, int i, int f, Node[] t, - Consumer action) { - super(p, b, i, f, t); - this.action = action; - } - - public final void compute() { - final Consumer action; - if ((action = this.action) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - new ForEachKeyTask - (this, batch >>>= 1, baseLimit = h, f, tab, - action).fork(); - } - for (Node p; (p = advance()) != null; ) - action.accept(p.key); - propagateCompletion(); - } - } - } - - @SuppressWarnings("serial") - static final class ForEachValueTask - extends BulkTask { - final Consumer action; - - ForEachValueTask - (BulkTask p, int b, int i, int f, Node[] t, - Consumer action) { - super(p, b, i, f, t); - this.action = action; - } - - public final void compute() { - final Consumer action; - if ((action = this.action) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - new ForEachValueTask - (this, batch >>>= 1, baseLimit = h, f, tab, - action).fork(); - } - for (Node p; (p = advance()) != null; ) - action.accept(p.val); - propagateCompletion(); - } - } - } - - @SuppressWarnings("serial") - static final class ForEachEntryTask - extends BulkTask { - final Consumer> action; - - ForEachEntryTask - (BulkTask p, int b, int i, int f, Node[] t, - Consumer> action) { - super(p, b, i, f, t); - this.action = action; - } - - public final void compute() { - final Consumer> action; - if ((action = this.action) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - new ForEachEntryTask - (this, batch >>>= 1, baseLimit = h, f, tab, - action).fork(); - } - for (Node p; (p = advance()) != null; ) - action.accept(p); - propagateCompletion(); - } - } - } - - @SuppressWarnings("serial") - static final class ForEachMappingTask - extends BulkTask { - final BiConsumer action; - - ForEachMappingTask - (BulkTask p, int b, int i, int f, Node[] t, - BiConsumer action) { - super(p, b, i, f, t); - this.action = action; - } - - public final void compute() { - final BiConsumer action; - if ((action = this.action) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - new ForEachMappingTask - (this, batch >>>= 1, baseLimit = h, f, tab, - action).fork(); - } - for (Node p; (p = advance()) != null; ) - action.accept(p.key, p.val); - propagateCompletion(); - } - } - } - - @SuppressWarnings("serial") - static final class ForEachTransformedKeyTask - extends BulkTask { - final Function transformer; - final Consumer action; - - ForEachTransformedKeyTask - (BulkTask p, int b, int i, int f, Node[] t, - Function transformer, Consumer action) { - super(p, b, i, f, t); - this.transformer = transformer; - this.action = action; - } - - public final void compute() { - final Function transformer; - final Consumer action; - if ((transformer = this.transformer) != null && - (action = this.action) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - new ForEachTransformedKeyTask - (this, batch >>>= 1, baseLimit = h, f, tab, - transformer, action).fork(); - } - for (Node p; (p = advance()) != null; ) { - U u; - if ((u = transformer.apply(p.key)) != null) - action.accept(u); - } - propagateCompletion(); - } - } - } - - @SuppressWarnings("serial") - static final class ForEachTransformedValueTask - extends BulkTask { - final Function transformer; - final Consumer action; - - ForEachTransformedValueTask - (BulkTask p, int b, int i, int f, Node[] t, - Function transformer, Consumer action) { - super(p, b, i, f, t); - this.transformer = transformer; - this.action = action; - } - - public final void compute() { - final Function transformer; - final Consumer action; - if ((transformer = this.transformer) != null && - (action = this.action) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - new ForEachTransformedValueTask - (this, batch >>>= 1, baseLimit = h, f, tab, - transformer, action).fork(); - } - for (Node p; (p = advance()) != null; ) { - U u; - if ((u = transformer.apply(p.val)) != null) - action.accept(u); - } - propagateCompletion(); - } - } - } - - @SuppressWarnings("serial") - static final class ForEachTransformedEntryTask - extends BulkTask { - final Function, ? extends U> transformer; - final Consumer action; - - ForEachTransformedEntryTask - (BulkTask p, int b, int i, int f, Node[] t, - Function, ? extends U> transformer, Consumer action) { - super(p, b, i, f, t); - this.transformer = transformer; - this.action = action; - } - - public final void compute() { - final Function, ? extends U> transformer; - final Consumer action; - if ((transformer = this.transformer) != null && - (action = this.action) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - new ForEachTransformedEntryTask - (this, batch >>>= 1, baseLimit = h, f, tab, - transformer, action).fork(); - } - for (Node p; (p = advance()) != null; ) { - U u; - if ((u = transformer.apply(p)) != null) - action.accept(u); - } - propagateCompletion(); - } - } - } - - @SuppressWarnings("serial") - static final class ForEachTransformedMappingTask - extends BulkTask { - final BiFunction transformer; - final Consumer action; - - ForEachTransformedMappingTask - (BulkTask p, int b, int i, int f, Node[] t, - BiFunction transformer, - Consumer action) { - super(p, b, i, f, t); - this.transformer = transformer; - this.action = action; - } - - public final void compute() { - final BiFunction transformer; - final Consumer action; - if ((transformer = this.transformer) != null && - (action = this.action) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - new ForEachTransformedMappingTask - (this, batch >>>= 1, baseLimit = h, f, tab, - transformer, action).fork(); - } - for (Node p; (p = advance()) != null; ) { - U u; - if ((u = transformer.apply(p.key, p.val)) != null) - action.accept(u); - } - propagateCompletion(); - } - } - } - - @SuppressWarnings("serial") - static final class SearchKeysTask - extends BulkTask { - final Function searchFunction; - final AtomicReference result; - - SearchKeysTask - (BulkTask p, int b, int i, int f, Node[] t, - Function searchFunction, - AtomicReference result) { - super(p, b, i, f, t); - this.searchFunction = searchFunction; - this.result = result; - } - - public final U getRawResult() { - return result.get(); - } - - public final void compute() { - final Function searchFunction; - final AtomicReference result; - if ((searchFunction = this.searchFunction) != null && - (result = this.result) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - if (result.get() != null) - return; - addToPendingCount(1); - new SearchKeysTask - (this, batch >>>= 1, baseLimit = h, f, tab, - searchFunction, result).fork(); - } - while (result.get() == null) { - U u; - Node p; - if ((p = advance()) == null) { - propagateCompletion(); - break; - } - if ((u = searchFunction.apply(p.key)) != null) { - if (result.compareAndSet(null, u)) - quietlyCompleteRoot(); - break; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class SearchValuesTask - extends BulkTask { - final Function searchFunction; - final AtomicReference result; - - SearchValuesTask - (BulkTask p, int b, int i, int f, Node[] t, - Function searchFunction, - AtomicReference result) { - super(p, b, i, f, t); - this.searchFunction = searchFunction; - this.result = result; - } - - public final U getRawResult() { - return result.get(); - } - - public final void compute() { - final Function searchFunction; - final AtomicReference result; - if ((searchFunction = this.searchFunction) != null && - (result = this.result) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - if (result.get() != null) - return; - addToPendingCount(1); - new SearchValuesTask - (this, batch >>>= 1, baseLimit = h, f, tab, - searchFunction, result).fork(); - } - while (result.get() == null) { - U u; - Node p; - if ((p = advance()) == null) { - propagateCompletion(); - break; - } - if ((u = searchFunction.apply(p.val)) != null) { - if (result.compareAndSet(null, u)) - quietlyCompleteRoot(); - break; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class SearchEntriesTask - extends BulkTask { - final Function, ? extends U> searchFunction; - final AtomicReference result; - - SearchEntriesTask - (BulkTask p, int b, int i, int f, Node[] t, - Function, ? extends U> searchFunction, - AtomicReference result) { - super(p, b, i, f, t); - this.searchFunction = searchFunction; - this.result = result; - } - - public final U getRawResult() { - return result.get(); - } - - public final void compute() { - final Function, ? extends U> searchFunction; - final AtomicReference result; - if ((searchFunction = this.searchFunction) != null && - (result = this.result) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - if (result.get() != null) - return; - addToPendingCount(1); - new SearchEntriesTask - (this, batch >>>= 1, baseLimit = h, f, tab, - searchFunction, result).fork(); - } - while (result.get() == null) { - U u; - Node p; - if ((p = advance()) == null) { - propagateCompletion(); - break; - } - if ((u = searchFunction.apply(p)) != null) { - if (result.compareAndSet(null, u)) - quietlyCompleteRoot(); - return; - } - } - } - } - } - - - @SuppressWarnings("serial") - static final class ReduceKeysTask - extends BulkTask { - final BiFunction reducer; - K result; - ReduceKeysTask rights, nextRight; - - ReduceKeysTask - (BulkTask p, int b, int i, int f, Node[] t, - ReduceKeysTask nextRight, - BiFunction reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.reducer = reducer; - } - - public final K getRawResult() { - return result; - } - - public final void compute() { - final BiFunction reducer; - if ((reducer = this.reducer) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new ReduceKeysTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, reducer)).fork(); - } - K r = null; - for (Node p; (p = advance()) != null; ) { - K u = p.key; - r = (r == null) ? u : u == null ? r : reducer.apply(r, u); - } - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - ReduceKeysTask - t = (ReduceKeysTask) c, - s = t.rights; - while (s != null) { - K tr, sr; - if ((sr = s.result) != null) - t.result = (((tr = t.result) == null) ? sr : - reducer.apply(tr, sr)); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class ReduceValuesTask - extends BulkTask { - final BiFunction reducer; - V result; - ReduceValuesTask rights, nextRight; - - ReduceValuesTask - (BulkTask p, int b, int i, int f, Node[] t, - ReduceValuesTask nextRight, - BiFunction reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.reducer = reducer; - } - - public final V getRawResult() { - return result; - } - - public final void compute() { - final BiFunction reducer; - if ((reducer = this.reducer) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new ReduceValuesTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, reducer)).fork(); - } - V r = null; - for (Node p; (p = advance()) != null; ) { - V v = p.val; - r = (r == null) ? v : reducer.apply(r, v); - } - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - ReduceValuesTask - t = (ReduceValuesTask) c, - s = t.rights; - while (s != null) { - V tr, sr; - if ((sr = s.result) != null) - t.result = (((tr = t.result) == null) ? sr : - reducer.apply(tr, sr)); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class ReduceEntriesTask - extends BulkTask> { - final BiFunction, Map.Entry, ? extends Map.Entry> reducer; - Map.Entry result; - ReduceEntriesTask rights, nextRight; - - ReduceEntriesTask - (BulkTask p, int b, int i, int f, Node[] t, - ReduceEntriesTask nextRight, - BiFunction, Map.Entry, ? extends Map.Entry> reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.reducer = reducer; - } - - public final Map.Entry getRawResult() { - return result; - } - - public final void compute() { - final BiFunction, Map.Entry, ? extends Map.Entry> reducer; - if ((reducer = this.reducer) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new ReduceEntriesTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, reducer)).fork(); - } - Map.Entry r = null; - for (Node p; (p = advance()) != null; ) - r = (r == null) ? p : reducer.apply(r, p); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - ReduceEntriesTask - t = (ReduceEntriesTask) c, - s = t.rights; - while (s != null) { - Map.Entry tr, sr; - if ((sr = s.result) != null) - t.result = (((tr = t.result) == null) ? sr : - reducer.apply(tr, sr)); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceKeysTask - extends BulkTask { - final Function transformer; - final BiFunction reducer; - U result; - MapReduceKeysTask rights, nextRight; - - MapReduceKeysTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceKeysTask nextRight, - Function transformer, - BiFunction reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.transformer = transformer; - this.reducer = reducer; - } - - public final U getRawResult() { - return result; - } - - public final void compute() { - final Function transformer; - final BiFunction reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new MapReduceKeysTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, reducer)).fork(); - } - U r = null; - for (Node p; (p = advance()) != null; ) { - U u; - if ((u = transformer.apply(p.key)) != null) - r = (r == null) ? u : reducer.apply(r, u); - } - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - MapReduceKeysTask - t = (MapReduceKeysTask) c, - s = t.rights; - while (s != null) { - U tr, sr; - if ((sr = s.result) != null) - t.result = (((tr = t.result) == null) ? sr : - reducer.apply(tr, sr)); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceValuesTask - extends BulkTask { - final Function transformer; - final BiFunction reducer; - U result; - MapReduceValuesTask rights, nextRight; - - MapReduceValuesTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceValuesTask nextRight, - Function transformer, - BiFunction reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.transformer = transformer; - this.reducer = reducer; - } - - public final U getRawResult() { - return result; - } - - public final void compute() { - final Function transformer; - final BiFunction reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new MapReduceValuesTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, reducer)).fork(); - } - U r = null; - for (Node p; (p = advance()) != null; ) { - U u; - if ((u = transformer.apply(p.val)) != null) - r = (r == null) ? u : reducer.apply(r, u); - } - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - MapReduceValuesTask - t = (MapReduceValuesTask) c, - s = t.rights; - while (s != null) { - U tr, sr; - if ((sr = s.result) != null) - t.result = (((tr = t.result) == null) ? sr : - reducer.apply(tr, sr)); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceEntriesTask - extends BulkTask { - final Function, ? extends U> transformer; - final BiFunction reducer; - U result; - MapReduceEntriesTask rights, nextRight; - - MapReduceEntriesTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceEntriesTask nextRight, - Function, ? extends U> transformer, - BiFunction reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.transformer = transformer; - this.reducer = reducer; - } - - public final U getRawResult() { - return result; - } - - public final void compute() { - final Function, ? extends U> transformer; - final BiFunction reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new MapReduceEntriesTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, reducer)).fork(); - } - U r = null; - for (Node p; (p = advance()) != null; ) { - U u; - if ((u = transformer.apply(p)) != null) - r = (r == null) ? u : reducer.apply(r, u); - } - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - MapReduceEntriesTask - t = (MapReduceEntriesTask) c, - s = t.rights; - while (s != null) { - U tr, sr; - if ((sr = s.result) != null) - t.result = (((tr = t.result) == null) ? sr : - reducer.apply(tr, sr)); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceMappingsTask - extends BulkTask { - final BiFunction transformer; - final BiFunction reducer; - U result; - MapReduceMappingsTask rights, nextRight; - - MapReduceMappingsTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceMappingsTask nextRight, - BiFunction transformer, - BiFunction reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.transformer = transformer; - this.reducer = reducer; - } - - public final U getRawResult() { - return result; - } - - public final void compute() { - final BiFunction transformer; - final BiFunction reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new MapReduceMappingsTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, reducer)).fork(); - } - U r = null; - for (Node p; (p = advance()) != null; ) { - U u; - if ((u = transformer.apply(p.key, p.val)) != null) - r = (r == null) ? u : reducer.apply(r, u); - } - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - MapReduceMappingsTask - t = (MapReduceMappingsTask) c, - s = t.rights; - while (s != null) { - U tr, sr; - if ((sr = s.result) != null) - t.result = (((tr = t.result) == null) ? sr : - reducer.apply(tr, sr)); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceKeysToDoubleTask - extends BulkTask { - final ToDoubleFunction transformer; - final DoubleBinaryOperator reducer; - final double basis; - double result; - MapReduceKeysToDoubleTask rights, nextRight; - - MapReduceKeysToDoubleTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceKeysToDoubleTask nextRight, - ToDoubleFunction transformer, - double basis, - DoubleBinaryOperator reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; - this.reducer = reducer; - } - - public final Double getRawResult() { - return result; - } - - public final void compute() { - final ToDoubleFunction transformer; - final DoubleBinaryOperator reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - double r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new MapReduceKeysToDoubleTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - MapReduceKeysToDoubleTask - t = (MapReduceKeysToDoubleTask) c, - s = t.rights; - while (s != null) { - t.result = reducer.applyAsDouble(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceValuesToDoubleTask - extends BulkTask { - final ToDoubleFunction transformer; - final DoubleBinaryOperator reducer; - final double basis; - double result; - MapReduceValuesToDoubleTask rights, nextRight; - - MapReduceValuesToDoubleTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceValuesToDoubleTask nextRight, - ToDoubleFunction transformer, - double basis, - DoubleBinaryOperator reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; - this.reducer = reducer; - } - - public final Double getRawResult() { - return result; - } - - public final void compute() { - final ToDoubleFunction transformer; - final DoubleBinaryOperator reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - double r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new MapReduceValuesToDoubleTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.val)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - MapReduceValuesToDoubleTask - t = (MapReduceValuesToDoubleTask) c, - s = t.rights; - while (s != null) { - t.result = reducer.applyAsDouble(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceEntriesToDoubleTask - extends BulkTask { - final ToDoubleFunction> transformer; - final DoubleBinaryOperator reducer; - final double basis; - double result; - MapReduceEntriesToDoubleTask rights, nextRight; - - MapReduceEntriesToDoubleTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceEntriesToDoubleTask nextRight, - ToDoubleFunction> transformer, - double basis, - DoubleBinaryOperator reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; - this.reducer = reducer; - } - - public final Double getRawResult() { - return result; - } - - public final void compute() { - final ToDoubleFunction> transformer; - final DoubleBinaryOperator reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - double r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new MapReduceEntriesToDoubleTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.applyAsDouble(r, transformer.applyAsDouble(p)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - MapReduceEntriesToDoubleTask - t = (MapReduceEntriesToDoubleTask) c, - s = t.rights; - while (s != null) { - t.result = reducer.applyAsDouble(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceMappingsToDoubleTask - extends BulkTask { - final ToDoubleBiFunction transformer; - final DoubleBinaryOperator reducer; - final double basis; - double result; - MapReduceMappingsToDoubleTask rights, nextRight; - - MapReduceMappingsToDoubleTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceMappingsToDoubleTask nextRight, - ToDoubleBiFunction transformer, - double basis, - DoubleBinaryOperator reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; - this.reducer = reducer; - } - - public final Double getRawResult() { - return result; - } - - public final void compute() { - final ToDoubleBiFunction transformer; - final DoubleBinaryOperator reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - double r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new MapReduceMappingsToDoubleTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.applyAsDouble(r, transformer.applyAsDouble(p.key, p.val)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - MapReduceMappingsToDoubleTask - t = (MapReduceMappingsToDoubleTask) c, - s = t.rights; - while (s != null) { - t.result = reducer.applyAsDouble(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceKeysToLongTask - extends BulkTask { - final ToLongFunction transformer; - final LongBinaryOperator reducer; - final long basis; - long result; - MapReduceKeysToLongTask rights, nextRight; - - MapReduceKeysToLongTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceKeysToLongTask nextRight, - ToLongFunction transformer, - long basis, - LongBinaryOperator reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; - this.reducer = reducer; - } - - public final Long getRawResult() { - return result; - } - - public final void compute() { - final ToLongFunction transformer; - final LongBinaryOperator reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - long r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new MapReduceKeysToLongTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.applyAsLong(r, transformer.applyAsLong(p.key)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - MapReduceKeysToLongTask - t = (MapReduceKeysToLongTask) c, - s = t.rights; - while (s != null) { - t.result = reducer.applyAsLong(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceValuesToLongTask - extends BulkTask { - final ToLongFunction transformer; - final LongBinaryOperator reducer; - final long basis; - long result; - MapReduceValuesToLongTask rights, nextRight; - - MapReduceValuesToLongTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceValuesToLongTask nextRight, - ToLongFunction transformer, - long basis, - LongBinaryOperator reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; - this.reducer = reducer; - } - - public final Long getRawResult() { - return result; - } - - public final void compute() { - final ToLongFunction transformer; - final LongBinaryOperator reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - long r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new MapReduceValuesToLongTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.applyAsLong(r, transformer.applyAsLong(p.val)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - MapReduceValuesToLongTask - t = (MapReduceValuesToLongTask) c, - s = t.rights; - while (s != null) { - t.result = reducer.applyAsLong(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceEntriesToLongTask - extends BulkTask { - final ToLongFunction> transformer; - final LongBinaryOperator reducer; - final long basis; - long result; - MapReduceEntriesToLongTask rights, nextRight; - - MapReduceEntriesToLongTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceEntriesToLongTask nextRight, - ToLongFunction> transformer, - long basis, - LongBinaryOperator reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; - this.reducer = reducer; - } - - public final Long getRawResult() { - return result; - } - - public final void compute() { - final ToLongFunction> transformer; - final LongBinaryOperator reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - long r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new MapReduceEntriesToLongTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.applyAsLong(r, transformer.applyAsLong(p)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - MapReduceEntriesToLongTask - t = (MapReduceEntriesToLongTask) c, - s = t.rights; - while (s != null) { - t.result = reducer.applyAsLong(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceMappingsToLongTask - extends BulkTask { - final ToLongBiFunction transformer; - final LongBinaryOperator reducer; - final long basis; - long result; - MapReduceMappingsToLongTask rights, nextRight; - - MapReduceMappingsToLongTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceMappingsToLongTask nextRight, - ToLongBiFunction transformer, - long basis, - LongBinaryOperator reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; - this.reducer = reducer; - } - - public final Long getRawResult() { - return result; - } - - public final void compute() { - final ToLongBiFunction transformer; - final LongBinaryOperator reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - long r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new MapReduceMappingsToLongTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.applyAsLong(r, transformer.applyAsLong(p.key, p.val)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - MapReduceMappingsToLongTask - t = (MapReduceMappingsToLongTask) c, - s = t.rights; - while (s != null) { - t.result = reducer.applyAsLong(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceKeysToIntTask - extends BulkTask { - final ToIntFunction transformer; - final IntBinaryOperator reducer; - final int basis; - int result; - MapReduceKeysToIntTask rights, nextRight; - - MapReduceKeysToIntTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceKeysToIntTask nextRight, - ToIntFunction transformer, - int basis, - IntBinaryOperator reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; - this.reducer = reducer; - } - - public final Integer getRawResult() { - return result; - } - - public final void compute() { - final ToIntFunction transformer; - final IntBinaryOperator reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - int r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new MapReduceKeysToIntTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.applyAsInt(r, transformer.applyAsInt(p.key)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - MapReduceKeysToIntTask - t = (MapReduceKeysToIntTask) c, - s = t.rights; - while (s != null) { - t.result = reducer.applyAsInt(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceValuesToIntTask - extends BulkTask { - final ToIntFunction transformer; - final IntBinaryOperator reducer; - final int basis; - int result; - MapReduceValuesToIntTask rights, nextRight; - - MapReduceValuesToIntTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceValuesToIntTask nextRight, - ToIntFunction transformer, - int basis, - IntBinaryOperator reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; - this.reducer = reducer; - } - - public final Integer getRawResult() { - return result; - } - - public final void compute() { - final ToIntFunction transformer; - final IntBinaryOperator reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - int r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new MapReduceValuesToIntTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.applyAsInt(r, transformer.applyAsInt(p.val)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - MapReduceValuesToIntTask - t = (MapReduceValuesToIntTask) c, - s = t.rights; - while (s != null) { - t.result = reducer.applyAsInt(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceEntriesToIntTask - extends BulkTask { - final ToIntFunction> transformer; - final IntBinaryOperator reducer; - final int basis; - int result; - MapReduceEntriesToIntTask rights, nextRight; - - MapReduceEntriesToIntTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceEntriesToIntTask nextRight, - ToIntFunction> transformer, - int basis, - IntBinaryOperator reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; - this.reducer = reducer; - } - - public final Integer getRawResult() { - return result; - } - - public final void compute() { - final ToIntFunction> transformer; - final IntBinaryOperator reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - int r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new MapReduceEntriesToIntTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.applyAsInt(r, transformer.applyAsInt(p)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - MapReduceEntriesToIntTask - t = (MapReduceEntriesToIntTask) c, - s = t.rights; - while (s != null) { - t.result = reducer.applyAsInt(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - @SuppressWarnings("serial") - static final class MapReduceMappingsToIntTask - extends BulkTask { - final ToIntBiFunction transformer; - final IntBinaryOperator reducer; - final int basis; - int result; - MapReduceMappingsToIntTask rights, nextRight; - - MapReduceMappingsToIntTask - (BulkTask p, int b, int i, int f, Node[] t, - MapReduceMappingsToIntTask nextRight, - ToIntBiFunction transformer, - int basis, - IntBinaryOperator reducer) { - super(p, b, i, f, t); - this.nextRight = nextRight; - this.transformer = transformer; - this.basis = basis; - this.reducer = reducer; - } - - public final Integer getRawResult() { - return result; - } - - public final void compute() { - final ToIntBiFunction transformer; - final IntBinaryOperator reducer; - if ((transformer = this.transformer) != null && - (reducer = this.reducer) != null) { - int r = this.basis; - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - addToPendingCount(1); - (rights = new MapReduceMappingsToIntTask - (this, batch >>>= 1, baseLimit = h, f, tab, - rights, transformer, r, reducer)).fork(); - } - for (Node p; (p = advance()) != null; ) - r = reducer.applyAsInt(r, transformer.applyAsInt(p.key, p.val)); - result = r; - CountedCompleter c; - for (c = firstComplete(); c != null; c = c.nextComplete()) { - @SuppressWarnings("unchecked") - MapReduceMappingsToIntTask - t = (MapReduceMappingsToIntTask) c, - s = t.rights; - while (s != null) { - t.result = reducer.applyAsInt(t.result, s.result); - s = t.rights = s.nextRight; - } - } - } - } - } - - // Unsafe mechanics - private static final sun.misc.Unsafe U; - private static final long SIZECTL; - private static final long TRANSFERINDEX; - private static final long BASECOUNT; - private static final long CELLSBUSY; - private static final long CELLVALUE; - private static final long ABASE; - private static final int ASHIFT; - - static { - try { - U = NativeBytes.UNSAFE; - Class k = ConcurrentHashMap.class; - SIZECTL = U.objectFieldOffset - (k.getDeclaredField("sizeCtl")); - TRANSFERINDEX = U.objectFieldOffset - (k.getDeclaredField("transferIndex")); - BASECOUNT = U.objectFieldOffset - (k.getDeclaredField("baseCount")); - CELLSBUSY = U.objectFieldOffset - (k.getDeclaredField("cellsBusy")); - Class ck = CounterCell.class; - CELLVALUE = U.objectFieldOffset - (ck.getDeclaredField("value")); - Class ak = Node[].class; - ABASE = U.arrayBaseOffset(ak); - int scale = U.arrayIndexScale(ak); - if ((scale & (scale - 1)) != 0) - throw new Error("data type scale not a power of two"); - ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); - } catch (Exception e) { - throw new Error(e); - } - } -} diff --git a/src/main/java/net/openhft/collections/ConcurrentMapLamdadSupport.java b/src/main/java/net/openhft/collections/ConcurrentMapLamdadSupport.java deleted file mode 100644 index ea31db616..000000000 --- a/src/main/java/net/openhft/collections/ConcurrentMapLamdadSupport.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright 2014 Higher Frequency Trading - *

- * http://www.higherfrequencytrading.com - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.openhft.collections; - -import java.util.Map; -import java.util.function.*; - -/** - * @author Rob Austin. - */ -public interface ConcurrentMapLamdadSupport { - - - /** - * Returns a {@link java.util.Set} view of the keys contained in this map. The set is backed by the map, - * so changes to the map are reflected in the set, and vice-versa. The set supports element removal, which - * removes the corresponding mapping from this map, via the {@code Iterator.remove}, {@code Set.remove}, - * {@code removeAll}, {@code retainAll}, and {@code clear} operations. It does not support the {@code - * add} or {@code addAll} operations. - * - *

The view's iterators and spliterators are weakly - * consistent. - * - *

The view's {@code spliterator} reports {@link java.util.Spliterator#CONCURRENT}, {@link - * java.util.Spliterator#DISTINCT}, and {@link java.util.Spliterator#NONNULL}. - * - * @return the set view - */ - net.openhft.collections.KeySetView keySet(); - - - long mappingCount(); - - net.openhft.collections.KeySetView keySet(V mappedValue); - - void forEach(long parallelismThreshold, - BiConsumer action); - - void forEach(long parallelismThreshold, - BiFunction transformer, - Consumer action); - - U search(long parallelismThreshold, - BiFunction searchFunction); - - U reduce(long parallelismThreshold, - BiFunction transformer, - BiFunction reducer); - - double reduceToDouble(long parallelismThreshold, - ToDoubleBiFunction transformer, - double basis, - DoubleBinaryOperator reducer); - - long reduceToLong(long parallelismThreshold, - ToLongBiFunction transformer, - long basis, - LongBinaryOperator reducer); - - int reduceToInt(long parallelismThreshold, - ToIntBiFunction transformer, - int basis, - IntBinaryOperator reducer); - - - void forEachKey(long parallelismThreshold, - Consumer action); - - void forEachKey(long parallelismThreshold, - Function transformer, - Consumer action); - - U searchKeys(long parallelismThreshold, - Function searchFunction); - - K reduceKeys(long parallelismThreshold, - BiFunction reducer); - - U reduceKeys(long parallelismThreshold, - Function transformer, - BiFunction reducer); - - double reduceKeysToDouble(long parallelismThreshold, - ToDoubleFunction transformer, - double basis, - DoubleBinaryOperator reducer); - - long reduceKeysToLong(long parallelismThreshold, - ToLongFunction transformer, - long basis, - LongBinaryOperator reducer); - - int reduceKeysToInt(long parallelismThreshold, - ToIntFunction transformer, - int basis, - IntBinaryOperator reducer); - - void forEachValue(long parallelismThreshold, - Consumer action); - - void forEachValue(long parallelismThreshold, - Function transformer, - Consumer action); - - U searchValues(long parallelismThreshold, - Function searchFunction); - - V reduceValues(long parallelismThreshold, - BiFunction reducer); - - U reduceValues(long parallelismThreshold, - Function transformer, - BiFunction reducer); - - double reduceValuesToDouble(long parallelismThreshold, - ToDoubleFunction transformer, - double basis, - DoubleBinaryOperator reducer); - - long reduceValuesToLong(long parallelismThreshold, - ToLongFunction transformer, - long basis, - LongBinaryOperator reducer); - - int reduceValuesToInt(long parallelismThreshold, - ToIntFunction transformer, - int basis, - IntBinaryOperator reducer); - - void forEachEntry(long parallelismThreshold, - Consumer> action); - - void forEachEntry(long parallelismThreshold, - Function, ? extends U> transformer, - Consumer action); - - U searchEntries(long parallelismThreshold, - Function, ? extends U> searchFunction); - - Map.Entry reduceEntries(long parallelismThreshold, - BiFunction, Map.Entry, ? extends Map.Entry> reducer); - - U reduceEntries(long parallelismThreshold, - Function, ? extends U> transformer, - BiFunction reducer); - - double reduceEntriesToDouble(long parallelismThreshold, - ToDoubleFunction> transformer, - double basis, - DoubleBinaryOperator reducer); - - long reduceEntriesToLong(long parallelismThreshold, - ToLongFunction> transformer, - long basis, - LongBinaryOperator reducer); - - int reduceEntriesToInt(long parallelismThreshold, - ToIntFunction> transformer, - int basis, - IntBinaryOperator reducer); -} diff --git a/src/main/java/net/openhft/collections/KeySetView.java b/src/main/java/net/openhft/collections/KeySetView.java deleted file mode 100644 index 589e8dbb4..000000000 --- a/src/main/java/net/openhft/collections/KeySetView.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2014 Higher Frequency Trading - *

- * http://www.higherfrequencytrading.com - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.openhft.collections; - -import java.util.Collection; -import java.util.Iterator; -import java.util.Set; -import java.util.Spliterator; -import java.util.function.Consumer; - -/** - * @author Rob Austin. - */ -public interface KeySetView extends Collection, java.io.Serializable, Set, CollectionView { - /** - * Returns the default mapped value for additions, or {@code null} if additions are not supported. - * - * @return the default mapped value for additions, or {@code null} if not supported - */ - V getMappedValue(); - - /** - * {@inheritDoc} - * - * @throws NullPointerException if the specified key is null - */ - boolean contains(Object o); - - /** - * Removes the key from this map view, by removing the key (and its corresponding value) from the backing - * map. This method does nothing if the key is not in the map. - * - * @param o the key to be removed from the backing map - * @return {@code true} if the backing map contained the specified key - * @throws NullPointerException if the specified key is null - */ - boolean remove(Object o); - - /** - * @return an iterator over the keys of the backing map - */ - Iterator iterator(); - - /** - * Adds the specified key to this set view by mapping the key to the default mapped value in the backing - * map, if defined. - * - * @param e key to be added - * @return {@code true} if this set changed as a result of the call - * @throws NullPointerException if the specified key is null - * @throws UnsupportedOperationException if no default mapped value for additions was provided - */ - boolean add(K e); - - /** - * Adds all of the elements in the specified collection to this set, as if by calling {@link #add} on each - * one. - * - * @param c the elements to be inserted into this set - * @return {@code true} if this set changed as a result of the call - * @throws NullPointerException if the collection or any of its elements are {@code null} - * @throws UnsupportedOperationException if no default mapped value for additions was provided - */ - boolean addAll(Collection c); - - int hashCode(); - - boolean equals(Object o); - - Spliterator spliterator(); - - void forEach(Consumer action); -} diff --git a/src/main/java/net/openhft/collections/SharedHashMapBuilder.java b/src/main/java/net/openhft/collections/SharedHashMapBuilder.java index f73a72ea8..1d2a89f71 100644 --- a/src/main/java/net/openhft/collections/SharedHashMapBuilder.java +++ b/src/main/java/net/openhft/collections/SharedHashMapBuilder.java @@ -292,7 +292,7 @@ public class SharedHashMapBuilder implements Cloneable { SharedHashMapBuilder builder = toBuilder(); if (!canReplicate()) - return new VanillaSharedMap(builder, file, kClass, vClass); + return new VanillaSharedHashMap(builder, file, kClass, vClass); if (identifier <= 0) throw new IllegalArgumentException("Identifier must be positive, " + identifier + " given"); diff --git a/src/main/java/net/openhft/collections/VSHM.java b/src/main/java/net/openhft/collections/VSHM.java deleted file mode 100644 index a378cb1b4..000000000 --- a/src/main/java/net/openhft/collections/VSHM.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2014 Higher Frequency Trading - *

- * http://www.higherfrequencytrading.com - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.openhft.collections; - -import net.openhft.chronicle.map.ChronicleMap; - -import java.io.File; -import java.io.IOException; - -/** - * @author Rob Austin. - */ -class ChronicleMap0 extends VanillaSharedMap implements ChronicleMap { - public ChronicleMap0(ChronicleMapBuilder0 builder, File file, Class kClass, Class vClass) - throws IOException { - super(builder, file, kClass, vClass); - } - - -} diff --git a/src/main/java/net/openhft/collections/VanillaSharedHashMap.java b/src/main/java/net/openhft/collections/VanillaSharedHashMap.java new file mode 100644 index 000000000..e842882be --- /dev/null +++ b/src/main/java/net/openhft/collections/VanillaSharedHashMap.java @@ -0,0 +1,1472 @@ +/* + * Copyright 2014 Higher Frequency Trading + *

+ * http://www.higherfrequencytrading.com + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.openhft.collections; + +import net.openhft.chronicle.map.ChronicleMap; +import net.openhft.lang.Maths; +import net.openhft.lang.collection.DirectBitSet; +import net.openhft.lang.collection.SingleThreadedDirectBitSet; +import net.openhft.lang.io.*; +import net.openhft.lang.io.serialization.BytesMarshallable; +import net.openhft.lang.io.serialization.ObjectSerializer; +import net.openhft.lang.io.serialization.impl.VanillaBytesMarshallerFactory; +import net.openhft.lang.model.Byteable; +import net.openhft.lang.model.DataValueClasses; +import net.openhft.lang.model.constraints.NotNull; +import net.openhft.lang.model.constraints.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Array; +import java.nio.channels.FileChannel; +import java.util.*; + +import static java.lang.Thread.currentThread; + + +class VanillaSharedHashMap extends AbstractVanillaSharedHashMap implements ChronicleMap { + + public VanillaSharedHashMap(SharedHashMapBuilder builder, File file, + Class kClass, Class vClass) throws IOException { + super(builder, kClass, vClass); + ObjectSerializer objectSerializer = builder.objectSerializer(); + BytesStore bytesStore = file == null + ? DirectStore.allocateLazy(sizeInBytes(), objectSerializer) + : new MappedStore(file, FileChannel.MapMode.READ_WRITE, sizeInBytes(), objectSerializer); + createMappedStoreAndSegments(bytesStore); + } +} + +abstract class AbstractVanillaSharedHashMap extends AbstractMap + implements SharedHashMap { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractVanillaSharedHashMap.class); + + /** + * Because DirectBitSet implementations couldn't find more than 64 continuous clear or set bits. + */ + private static final int MAX_ENTRY_OVERSIZE_FACTOR = 64; + + private final ObjectSerializer objectSerializer; + private SharedHashMapBuilder builder; + + private static int figureBufferAllocationFactor(SharedHashMapBuilder builder) { + // if expected map size is about 1000, seems rather wasteful to allocate + // key and value serialization buffers each x64 of expected entry size.. + return (int) Math.min(Math.max(2L, builder.entries() >> 10), + MAX_ENTRY_OVERSIZE_FACTOR); + } + + private final int bufferAllocationFactor; + + private final ThreadLocal localBufferForKeys = + new ThreadLocal(); + private final ThreadLocal localBufferForValues = + new ThreadLocal(); + + final Class kClass; + final Class vClass; + private final long lockTimeOutNS; + final int metaDataBytes; + Segment[] segments; // non-final for close() + // non-final for close() and because it is initialized out of constructor + BytesStore ms; + final Hasher hasher; + + // private final int replicas; + final int entrySize; + final Alignment alignment; + final int entriesPerSegment; + + private final SharedMapErrorListener errorListener; + + /** + * Non-final because could be changed in VanillaSharedReplicatedHashMap. + */ + volatile SharedMapEventListener> eventListener; + + private final boolean generatedKeyType; + final boolean generatedValueType; + + // if set the ReturnsNull fields will cause some functions to return NULL + // rather than as returning the Object can be expensive for something you probably don't use. + final boolean putReturnsNull; + final boolean removeReturnsNull; + + transient Set> entrySet; + + + public AbstractVanillaSharedHashMap(SharedHashMapBuilder builder, + Class kClass, Class vClass) throws IOException { + this.builder = builder.clone(); + bufferAllocationFactor = figureBufferAllocationFactor(builder); + this.kClass = kClass; + this.vClass = vClass; + + lockTimeOutNS = builder.lockTimeOutMS() * 1000000; + + // this.replicas = builder.replicas(); + this.entrySize = builder.alignedEntrySize(); + this.alignment = builder.entryAndValueAlignment(); + + this.errorListener = builder.errorListener(); + this.generatedKeyType = builder.generatedKeyType(); + this.generatedValueType = builder.generatedValueType(); + this.putReturnsNull = builder.putReturnsNull(); + this.removeReturnsNull = builder.removeReturnsNull(); + this.objectSerializer = builder.objectSerializer(); + + int segments = builder.actualSegments(); + int entriesPerSegment = builder.actualEntriesPerSegment(); + this.entriesPerSegment = entriesPerSegment; + this.metaDataBytes = builder.metaDataBytes(); + this.eventListener = builder.eventListener(); + + int hashMask = useSmallMultiMaps() ? 0xFFFF : ~0; + this.hasher = new Hasher(segments, hashMask); + + @SuppressWarnings("unchecked") + Segment[] ss = (Segment[]) Array.newInstance(segmentType(), segments); + this.segments = ss; + } + + Class segmentType() { + return Segment.class; + } + + long createMappedStoreAndSegments(BytesStore bytesStore) throws IOException { + this.ms = bytesStore; + + onHeaderCreated(); + + long offset = getHeaderSize(); + long segmentSize = segmentSize(); + for (int i = 0; i < this.segments.length; i++) { + this.segments[i] = createSegment((NativeBytes) ms.bytes(offset, segmentSize), i); + offset += segmentSize; + } + return offset; + } + + /** + * called when the header is created + */ + void onHeaderCreated() { + + } + + int getHeaderSize() { + return SharedHashMapBuilder.HEADER_SIZE; + } + + Segment createSegment(NativeBytes bytes, int index) { + return new Segment(bytes, index); + } + + @Override + public File file() { + return ms.file(); + } + + /** + * @param size positive number + * @return number of bytes taken by {@link net.openhft.lang.io.AbstractBytes#writeStopBit(long)} applied + * to {@code size} + */ + static int expectedStopBits(long size) { + if (size <= 127) + return 1; + // numberOfLeadingZeros is cheap intrinsic on modern CPUs + // integral division is not... but there is no choice + return ((70 - Long.numberOfLeadingZeros(size)) / 7); + } + + + long sizeInBytes() { + return getHeaderSize() + + segments.length * segmentSize(); + } + + + long sizeOfMultiMap() { + int np2 = Maths.nextPower2(entriesPerSegment, 8); + return align64(np2 * (entriesPerSegment > (1 << 16) ? 8L : 4L)); + } + + boolean useSmallMultiMaps() { + return entriesPerSegment <= (1 << 16); + } + + long sizeOfBitSets() { + return align64(entriesPerSegment / 8); + } + + int numberOfBitSets() { + return 1; // for free list + // + (replicas > 0 ? 1 : 0) // deleted set + // + replicas; // to notify each replica of a change. + } + + long segmentSize() { + long ss = SharedHashMapBuilder.SEGMENT_HEADER + + sizeOfMultiMap() * multiMapsPerSegment() + + numberOfBitSets() * sizeOfBitSets() // the free list and 0+ dirty lists. + + sizeOfEntriesInSegment(); + if ((ss & 63) != 0) + throw new AssertionError(); + + // Say, there is 32 KB L1 cache with 2(4, 8) way set associativity, 64-byte lines. + // It means there are 32 * 1024 / 64 / 2(4, 8) = 256(128, 64) sets, + // i. e. each way (bank) contains 256(128, 64) lines. (L2 and L3 caches has more sets.) + // If segment size in lines multiplied by 2^n is divisible by set size, + // every 2^n-th segment header fall into the same set. + // To break this up we make segment size odd in lines, in this case only each + // 256(128, 64)-th segment header fall into the same set. + + // If there are 64 sets in L1, it should be 8- or much less likely 4-way, and segments + // collision by pairs is not so terrible. + + // if the size is a multiple of 4096 or slightly more. Make sure it is at least 64 more than a multiple. + if ((ss & 4093) < 64) + ss = (ss & ~63) + 64; + + return ss; + } + + int multiMapsPerSegment() { + return 1; + } + + private long sizeOfEntriesInSegment() { + return align64((long) entriesPerSegment * entrySize); + } + + /** + * Cache line alignment, assuming 64-byte cache lines. + */ + static long align64(long l) { + return (l + 63) & ~63; + } + + /** + * {@inheritDoc} + */ + @Override + public void close() { + if (ms == null) + return; + ms.free(); + segments = null; + ms = null; + } + + private DirectBytes acquireBufferForKey() { + DirectBytes buffer = localBufferForKeys.get(); + if (buffer == null) { + buffer = new DirectStore(ms.objectSerializer(), + entrySize * bufferAllocationFactor, false).bytes(); + localBufferForKeys.set(buffer); + } else { + buffer.clear(); + } + return buffer; + } + + private DirectBytes acquireBufferForValue() { + DirectBytes buffer = localBufferForValues.get(); + if (buffer == null) { + buffer = new DirectStore(ms.objectSerializer(), + entrySize * bufferAllocationFactor, false).bytes(); + localBufferForValues.set(buffer); + } else { + buffer.clear(); + } + return buffer; + } + + void checkKey(Object key) { + if (!kClass.isInstance(key)) { + // key.getClass will cause NPE exactly as needed + throw new ClassCastException("Key must be a " + kClass.getName() + + " but was a " + key.getClass()); + } + } + + void checkValue(Object value) { + if (!vClass.isInstance(value)) { + throw new ClassCastException("Value must be a " + vClass.getName() + + " but was a " + value.getClass()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public V put(K key, V value) { + return put0(key, value, true); + } + + /** + * {@inheritDoc} + */ + @Override + public V putIfAbsent(K key, V value) { + return put0(key, value, false); + } + + private V put0(K key, V value, boolean replaceIfPresent) { + checkKey(key); + checkValue(value); + Bytes keyBytes = getKeyAsBytes(key); + long hash = Hasher.hash(keyBytes); + int segmentNum = hasher.getSegment(hash); + int segmentHash = hasher.segmentHash(hash); + return segments[segmentNum].put(keyBytes, key, value, segmentHash, replaceIfPresent); + } + + DirectBytes getKeyAsBytes(K key) { + DirectBytes buffer = acquireBufferForKey(); + if (generatedKeyType) + ((BytesMarshallable) key).writeMarshallable(buffer); + else + buffer.writeInstance(kClass, key); + buffer.flip(); + return buffer; + } + + DirectBytes getValueAsBytes(V value) { + DirectBytes buffer = acquireBufferForValue(); + buffer.clear(); + if (generatedValueType) + ((BytesMarshallable) value).writeMarshallable(buffer); + else + buffer.writeInstance(vClass, value); + buffer.flip(); + return buffer; + } + + /** + * {@inheritDoc} + */ + @Override + public V get(Object key) { + return lookupUsing((K) key, null, false); + } + + @Override + public V getUsing(K key, V value) { + return lookupUsing(key, value, false); + } + + @Override + public V acquireUsing(K key, V value) { + return lookupUsing(key, value, true); + } + + V lookupUsing(K key, V value, boolean create) { + checkKey(key); + Bytes keyBytes = getKeyAsBytes(key); + long hash = Hasher.hash(keyBytes); + int segmentNum = hasher.getSegment(hash); + int segmentHash = hasher.segmentHash(hash); + return segments[segmentNum].acquire(keyBytes, key, value, segmentHash, create); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean containsKey(final Object key) { + checkKey(key); + Bytes keyBytes = getKeyAsBytes((K) key); + long hash = Hasher.hash(keyBytes); + int segmentNum = hasher.getSegment(hash); + int segmentHash = hasher.segmentHash(hash); + + return segments[segmentNum].containsKey(keyBytes, segmentHash); + } + + @Override + public void clear() { + for (Segment segment : segments) + segment.clear(); + } + + /** + * {@inheritDoc} + */ + @NotNull + @Override + public Set> entrySet() { + return (entrySet != null) ? entrySet : (entrySet = new EntrySet()); + } + + + /** + * {@inheritDoc} + * + * @throws NullPointerException if the specified key is null + */ + @Override + public V remove(final Object key) { + return removeIfValueIs(key, null); + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if the specified key is null + */ + @Override + public boolean remove(final Object key, final Object value) { + if (value == null) + return false; // CHM compatibility; I would throw NPE + return removeIfValueIs(key, (V) value) != null; + } + + + /** + * removes ( if there exists ) an entry from the map, if the {@param key} and {@param expectedValue} match + * that of a maps.entry. If the {@param expectedValue} equals null then ( if there exists ) an entry whose + * key equals {@param key} this is removed. + * + * @param key the key of the entry to remove + * @param expectedValue null if not required + * @return true if and entry was removed + */ + V removeIfValueIs(final Object key, final V expectedValue) { + checkKey(key); + Bytes keyBytes = getKeyAsBytes((K) key); + long hash = Hasher.hash(keyBytes); + int segmentNum = hasher.getSegment(hash); + int segmentHash = hasher.segmentHash(hash); + return segments[segmentNum].remove(keyBytes, (K) key, expectedValue, segmentHash); + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if any of the arguments are null + */ + @Override + public boolean replace(final K key, final V oldValue, final V newValue) { + checkValue(oldValue); + return oldValue.equals(replaceIfValueIs(key, oldValue, newValue)); + } + + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, or null if there was no mapping + * for the key + * @throws NullPointerException if the specified key or value is null + */ + @Override + public V replace(final K key, final V value) { + return replaceIfValueIs(key, null, value); + } + + @Override + public long longSize() { + long result = 0; + + for (final Segment segment : this.segments) { + result += segment.getSize(); + } + + return result; + } + + @Override + public int size() { + long size = longSize(); + return size > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) size; + } + + /** + * replace the value in a map, only if the existing entry equals {@param existingValue} + * + * @param key the key into the map + * @param existingValue the expected existing value in the map ( could be null when we don't wish to do + * this check ) + * @param newValue the new value you wish to store in the map + * @return the value that was replaced + */ + V replaceIfValueIs(@net.openhft.lang.model.constraints.NotNull final K key, final V existingValue, final V newValue) { + checkKey(key); + checkValue(newValue); + Bytes keyBytes = getKeyAsBytes(key); + long hash = Hasher.hash(keyBytes); + int segmentNum = hasher.getSegment(hash); + int segmentHash = hasher.segmentHash(hash); + return segments[segmentNum].replace(keyBytes, key, existingValue, newValue, segmentHash); + } + + /** + * For testing + */ + void checkConsistency() { + for (Segment segment : segments) { + segment.checkConsistency(); + } + } + + + static final class Hasher { + + static long hash(Bytes bytes) { + long h = 0; + long i = bytes.position(); + long limit = bytes.limit(); // clustering. + for (; i < limit - 7; i += 8) + h = 1011001110001111L * h + bytes.readLong(i); + for (; i < limit - 1; i += 2) + h = 101111 * h + bytes.readShort(i); + if (i < limit) + h = 2111 * h + bytes.readByte(i); + h *= 11018881818881011L; + h ^= (h >>> 41) ^ (h >>> 21); + //System.out.println(bytes + " => " + Long.toHexString(h)); + return h; + } + + private final int segments; + private final int bits; + private final int mask; + + Hasher(int segments, int mask) { + this.segments = segments; + this.bits = Maths.intLog2(segments); + this.mask = mask; + } + + int segmentHash(long hash) { + return (int) (hash >>> bits) & mask; + } + + int getSegment(long hash) { + return (int) (hash & (segments - 1)); + } + } + + // these methods should be package local, not public or private. + class Segment implements SharedSegment { + /* + The entry format is + - stop-bit encoded length for key + - bytes for the key + - stop-bit encoded length of the value + - bytes for the value. + */ + static final int LOCK_OFFSET = 0; // 64-bit + static final int SIZE_OFFSET = LOCK_OFFSET + 8; // 32-bit + static final int PAD1_OFFSET = SIZE_OFFSET + 4; // 32-bit + static final int REPLICA_OFFSET = PAD1_OFFSET + 4; // 64-bit + + private final NativeBytes bytes; + private final int index; + final MultiStoreBytes tmpBytes = new MultiStoreBytes(); + private IntIntMultiMap hashLookup; + private final SingleThreadedDirectBitSet freeList; + private int nextPosToSearchFrom = 0; + final long entriesOffset; + + + /** + * @param bytes + * @param index the index of this segment held by the map + */ + Segment(NativeBytes bytes, int index) { + this.bytes = bytes; + this.index = index; + + long start = bytes.startAddr() + SharedHashMapBuilder.SEGMENT_HEADER; + createHashLookups(start); + start += sizeOfMultiMap() * multiMapsPerSegment(); + final NativeBytes bsBytes = new NativeBytes( + tmpBytes.objectSerializer(), start, start + sizeOfBitSets(), null); + freeList = new SingleThreadedDirectBitSet(bsBytes); + start += numberOfBitSets() * sizeOfBitSets(); + entriesOffset = start - bytes.startAddr(); + assert bytes.capacity() >= entriesOffset + entriesPerSegment * entrySize; + } + + void createHashLookups(long start) { + hashLookup = createMultiMap(start); + } + + IntIntMultiMap createMultiMap(long start) { + final NativeBytes multiMapBytes = + new NativeBytes(new VanillaBytesMarshallerFactory(), start, start + sizeOfMultiMap(), null); + multiMapBytes.load(); + return useSmallMultiMaps() ? + new VanillaShortShortMultiMap(multiMapBytes) : + new VanillaIntIntMultiMap(multiMapBytes); + } + + public int getIndex() { + return index; + } + + + /* Methods with private access modifier considered private to Segment + * class, although Java allows to access them from outer class anyway. + */ + + /** + * increments the size by one + */ + void incrementSize() { + this.bytes.addInt(SIZE_OFFSET, 1); + } + + void resetSize() { + this.bytes.writeInt(SIZE_OFFSET, 0); + } + + /** + * decrements the size by one + */ + void decrementSize() { + this.bytes.addInt(SIZE_OFFSET, -1); + } + + /** + * reads the the number of entries in this segment + */ + int getSize() { + // any negative value is in error state. + return Math.max(0, this.bytes.readVolatileInt(SIZE_OFFSET)); + } + + + public void lock() throws IllegalStateException { + while (true) { + final boolean success = bytes.tryLockNanosLong(LOCK_OFFSET, lockTimeOutNS); + if (success) return; + if (currentThread().isInterrupted()) { + throw new IllegalStateException(new InterruptedException("Unable to obtain lock, interrupted")); + } else { + errorListener.onLockTimeout(bytes.threadIdForLockLong(LOCK_OFFSET)); + bytes.resetLockLong(LOCK_OFFSET); + } + } + } + + public void unlock() { + try { + bytes.unlockLong(LOCK_OFFSET); + } catch (IllegalMonitorStateException e) { + errorListener.errorOnUnlock(e); + } + } + + public long offsetFromPos(long pos) { + return entriesOffset + pos * entrySize; + } + + long posFromOffset(long offset) { + return (offset - entriesOffset) / entrySize; + } + + + public MultiStoreBytes entry(long offset) { + return reuse(tmpBytes, offset); + } + + private MultiStoreBytes reuse(MultiStoreBytes entry, long offset) { + offset += metaDataBytes; + entry.storePositionAndSize(bytes, offset, + // "Infinity". Limit not used when treating entries as + // possibly oversized + bytes.limit() - offset); + return entry; + } + + long entryStartAddr(long offset) { + // entry.address() points to "needed" start addr + metaDataBytes + return bytes.startAddr() + offset; + } + + private long entrySize(long keyLen, long valueLen) { + return alignment.alignAddr(metaDataBytes + + expectedStopBits(keyLen) + keyLen + + expectedStopBits(valueLen)) + valueLen; + } + + int inBlocks(long sizeInBytes) { + if (sizeInBytes <= entrySize) + return 1; + // int division is MUCH faster than long on Intel CPUs + sizeInBytes -= 1; + if (sizeInBytes <= Integer.MAX_VALUE) + return (((int) sizeInBytes) / entrySize) + 1; + return (int) (sizeInBytes / entrySize) + 1; + } + + /** + * Used to acquire an object of type V from the Segment.

{@code usingValue} is reused to read the + * value if key is present in this Segment, if key is absent in this Segment:

  1. If {@code + * create == false}, just {@code null} is returned (except when event listener provides a value "on + * get missing" - then it is put into this Segment for the key).
  2. If {@code create == + * true}, {@code usingValue} or a newly created instance of value class, if {@code usingValue == + * null}, is put into this Segment for the key.
+ * + * @param keyBytes serialized {@code key} + * @param hash2 a hash code related to the {@code keyBytes} + * @return the value which is finally associated with the given key in this Segment after execution of + * this method, or {@code null}. + */ + V acquire(Bytes keyBytes, K key, V usingValue, int hash2, boolean create) { + lock(); + try { + MultiStoreBytes entry = tmpBytes; + long offset = searchKey(keyBytes, hash2, entry, hashLookup); + if (offset >= 0) { + return onKeyPresentOnAcquire(key, usingValue, offset, entry); + } else { + usingValue = tryObtainUsingValueOnAcquire(keyBytes, key, usingValue, create); + if (usingValue != null) { + // If `create` is false, this method was called from get() or getUsing() + // and non-null `usingValue` was returned by notifyMissed() method. + // This "missed" default value is considered as genuine value + // rather than "using" container to fill up, even if it implements Byteable. + offset = putEntry(keyBytes, usingValue, create); + incrementSize(); + notifyPut(offset, true, key, usingValue, posFromOffset(offset)); + return usingValue; + } else { + return null; + } + } + } finally { + unlock(); + } + } + + long searchKey(Bytes keyBytes, int hash2, + MultiStoreBytes entry, IntIntMultiMap hashLookup) { + long keyLen = keyBytes.remaining(); + hashLookup.startSearch(hash2); + for (int pos; (pos = hashLookup.nextPos()) >= 0; ) { + long offset = offsetFromPos(pos); + reuse(entry, offset); + if (!keyEquals(keyBytes, keyLen, entry)) + continue; + // key is found + entry.skip(keyLen); + return offset; + } + // key is not found + return -1L; + } + + V onKeyPresentOnAcquire(K key, V usingValue, long offset, NativeBytes entry) { + V v = readValue(entry, usingValue); + notifyGet(offset, key, v); + return v; + } + + V tryObtainUsingValueOnAcquire(Bytes keyBytes, K key, V usingValue, boolean create) { + if (create) { + if (usingValue != null) { + return usingValue; + } else { + if (generatedValueType) + return DataValueClasses.newDirectReference(vClass); + else { + try { + return vClass.newInstance(); + } catch (Exception e) { + throw new AssertionError(e); + } + } + } + } else { + if (usingValue instanceof Byteable) + ((Byteable) usingValue).bytes(null, 0); + return notifyMissed(keyBytes, key, usingValue); + } + } + + V put(Bytes keyBytes, K key, V value, int hash2, boolean replaceIfPresent) { + lock(); + try { + long keyLen = keyBytes.remaining(); + hashLookup.startSearch(hash2); + for (int pos; (pos = hashLookup.nextPos()) >= 0; ) { + long offset = offsetFromPos(pos); + NativeBytes entry = entry(offset); + if (!keyEquals(keyBytes, keyLen, entry)) + continue; + // key is found + entry.skip(keyLen); + if (replaceIfPresent) { + return replaceValueOnPut(key, value, entry, pos, offset, !putReturnsNull, hashLookup); + } else { + return putReturnsNull ? null : readValue(entry, null); + } + } + // key is not found + long offset = putEntry(keyBytes, value, false); + incrementSize(); + notifyPut(offset, true, key, value, posFromOffset(offset)); + return null; + } finally { + unlock(); + } + } + + V replaceValueOnPut(K key, V value, NativeBytes entry, int pos, long offset, + boolean readPrevValue, IntIntMultiMap searchedHashLookup) { + long valueLenPos = entry.position(); + long valueLen = readValueLen(entry); + long entryEndAddr = entry.positionAddr() + valueLen; + V prevValue = null; + if (readPrevValue) + prevValue = readValue(entry, null, valueLen); + + // putValue may relocate entry and change offset + offset = putValue(pos, offset, entry, valueLenPos, entryEndAddr, value, searchedHashLookup); + notifyPut(offset, false, key, value, posFromOffset(offset)); + return prevValue; + } + + /** + * Puts entry. If {@code value} implements {@link net.openhft.lang.model.Byteable} interface and + * {@code usingValue} is {@code true}, the value is backed with the bytes of this entry. + * + * @param keyBytes serialized key + * @param value the value to put + * @param usingValue {@code true} if the value should be backed with the bytes of the entry, if it + * implements {@link net.openhft.lang.model.Byteable} interface, {@code false} if it + * should put itself + * @return offset of the written entry in the Segment bytes + */ + private long putEntry(Bytes keyBytes, V value, boolean usingValue) { + long keyLen = keyBytes.remaining(); + + // "if-else polymorphism" is not very beautiful, but allows to + // reuse the rest code of this method and doesn't hurt performance. + boolean byteableValue = usingValue && value instanceof Byteable; + long valueLen; + Bytes valueBytes = null; + Byteable valueAsByteable = null; + if (!byteableValue) { + valueBytes = getValueAsBytes(value); + valueLen = valueBytes.remaining(); + } else { + valueAsByteable = (Byteable) value; + valueLen = valueAsByteable.maxSize(); + } + + long entrySize = entrySize(keyLen, valueLen); + int pos = alloc(inBlocks(entrySize)); + long offset = offsetFromPos(pos); + clearMetaData(offset); + NativeBytes entry = entry(offset); + + entry.writeStopBit(keyLen); + entry.write(keyBytes); + + writeValueOnPutEntry(valueLen, valueBytes, valueAsByteable, entry); + hashLookup.putAfterFailedSearch(pos); + return offset; + } + + void writeValueOnPutEntry(long valueLen, @Nullable Bytes valueBytes, + @Nullable Byteable valueAsByteable, NativeBytes entry) { + entry.writeStopBit(valueLen); + alignment.alignPositionAddr(entry); + + if (valueBytes != null) { + entry.write(valueBytes); + } else { + assert valueAsByteable != null; + long valueOffset = entry.positionAddr() - bytes.address(); + bytes.zeroOut(valueOffset, valueOffset + valueLen); + valueAsByteable.bytes(bytes, valueOffset); + } + } + + void clearMetaData(long offset) { + if (metaDataBytes > 0) + bytes.zeroOut(offset, offset + metaDataBytes); + } + + int alloc(int blocks) { + int ret = (int) freeList.setNextNContinuousClearBits(nextPosToSearchFrom, + blocks); + if (ret == DirectBitSet.NOT_FOUND) { + ret = (int) freeList.setNextNContinuousClearBits(0, blocks); + if (ret == DirectBitSet.NOT_FOUND) { + if (blocks == 1) { + throw new IllegalArgumentException( + "Segment is full, no free entries found"); + } else { + throw new IllegalArgumentException( + "Segment is full or has no ranges of " + blocks + + " continuous free blocks" + ); + } + } + } + // if bit at nextPosToSearchFrom is clear, it was skipped because + // more than 1 block was requested. Don't move nextPosToSearchFrom + // in this case. blocks == 1 clause is just a fast path. + if (blocks == 1 || freeList.isSet(nextPosToSearchFrom)) + nextPosToSearchFrom = ret + blocks; + return ret; + } + + private boolean realloc(int fromPos, int oldBlocks, int newBlocks) { + if (freeList.allClear(fromPos + oldBlocks, fromPos + newBlocks)) { + freeList.set(fromPos + oldBlocks, fromPos + newBlocks); + return true; + } else { + return false; + } + } + + void free(int fromPos, int blocks) { + freeList.clear(fromPos, fromPos + blocks); + if (fromPos < nextPosToSearchFrom) + nextPosToSearchFrom = fromPos; + } + + V readValue(NativeBytes entry, V value) { + return readValue(entry, value, readValueLen(entry)); + } + + long readValueLen(Bytes entry) { + long valueLen = entry.readStopBit(); + alignment.alignPositionAddr(entry); + return valueLen; + } + + /** + * TODO use the value length to limit reading + * + * @param value the object to reuse (if possible), if {@code null} a new object is created + */ + V readValue(NativeBytes entry, V value, long valueLen) { + if (generatedValueType) + if (value == null) + value = DataValueClasses.newDirectReference(vClass); + else + assert value instanceof Byteable; + if (value instanceof Byteable) { + long valueOffset = entry.positionAddr() - bytes.address(); + ((Byteable) value).bytes(bytes, valueOffset); + return value; + } + return entry.readInstance(vClass, value); + } + + boolean keyEquals(Bytes keyBytes, long keyLen, Bytes entry) { + return keyLen == entry.readStopBit() && entry.startsWith(keyBytes); + } + + /** + * Removes a key (or key-value pair) from the Segment.

The entry will only be removed if {@code + * expectedValue} equals to {@code null} or the value previously corresponding to the specified key. + * + * @param keyBytes bytes of the key to remove + * @param hash2 a hash code related to the {@code keyBytes} + * @return the value of the entry that was removed if the entry corresponding to the {@code keyBytes} + * exists and {@link #removeReturnsNull} is {@code false}, {@code null} otherwise + */ + V remove(Bytes keyBytes, K key, V expectedValue, int hash2) { + lock(); + try { + long keyLen = keyBytes.remaining(); + hashLookup.startSearch(hash2); + for (int pos; (pos = hashLookup.nextPos()) >= 0; ) { + long offset = offsetFromPos(pos); + NativeBytes entry = entry(offset); + if (!keyEquals(keyBytes, keyLen, entry)) + continue; + // key is found + entry.skip(keyLen); + long valueLen = readValueLen(entry); + long entryEndAddr = entry.positionAddr() + valueLen; + V valueRemoved = expectedValue != null || !removeReturnsNull + ? readValue(entry, null, valueLen) : null; + if (expectedValue != null && !expectedValue.equals(valueRemoved)) + return null; + hashLookup.removePrevPos(); + decrementSize(); + free(pos, inBlocks(entryEndAddr - entryStartAddr(offset))); + notifyRemoved(offset, key, valueRemoved, pos); + return valueRemoved; + } + // key is not found + return null; + } finally { + unlock(); + } + } + + boolean containsKey(Bytes keyBytes, int hash2) { + lock(); + try { + long keyLen = keyBytes.remaining(); + IntIntMultiMap hashLookup = containsKeyHashLookup(); + hashLookup.startSearch(hash2); + for (int pos; (pos = hashLookup.nextPos()) >= 0; ) { + Bytes entry = entry(offsetFromPos(pos)); + if (keyEquals(keyBytes, keyLen, entry)) + return true; + } + return false; + } finally { + unlock(); + } + } + + IntIntMultiMap containsKeyHashLookup() { + return hashLookup; + } + + /** + * Replaces the specified value for the key with the given value.

{@code newValue} is set only if + * the existing value corresponding to the specified key is equal to {@code expectedValue} or {@code + * expectedValue == null}. + * + * @param hash2 a hash code related to the {@code keyBytes} + * @return the replaced value or {@code null} if the value was not replaced + */ + V replace(Bytes keyBytes, K key, V expectedValue, V newValue, int hash2) { + lock(); + try { + long keyLen = keyBytes.remaining(); + hashLookup.startSearch(hash2); + for (int pos; (pos = hashLookup.nextPos()) >= 0; ) { + long offset = offsetFromPos(pos); + NativeBytes entry = entry(offset); + if (!keyEquals(keyBytes, keyLen, entry)) + continue; + // key is found + entry.skip(keyLen); + return onKeyPresentOnReplace(key, expectedValue, newValue, pos, offset, entry, + hashLookup); + } + // key is not found + return null; + } finally { + unlock(); + } + } + + V onKeyPresentOnReplace(K key, V expectedValue, V newValue, int pos, long offset, + NativeBytes entry, IntIntMultiMap searchedHashLookup) { + long valueLenPos = entry.position(); + long valueLen = readValueLen(entry); + long entryEndAddr = entry.positionAddr() + valueLen; + V valueRead = readValue(entry, null, valueLen); + if (valueRead == null) + return null; + if (expectedValue == null || expectedValue.equals(valueRead)) { + // putValue may relocate entry and change offset + offset = putValue(pos, offset, entry, valueLenPos, entryEndAddr, newValue, + searchedHashLookup); + notifyPut(offset, false, key, newValue, + posFromOffset(offset)); + return valueRead; + } + return null; + } + + + void notifyPut(long offset, boolean added, K key, V value, final long pos) { + if (eventListener != SharedMapEventListeners.NOP) { + tmpBytes.storePositionAndSize(bytes, offset, entrySize); + eventListener.onPut(AbstractVanillaSharedHashMap.this, tmpBytes, metaDataBytes, + added, key, value, pos, this); + } + } + + void notifyGet(long offset, K key, V value) { + if (eventListener != SharedMapEventListeners.NOP) { + tmpBytes.storePositionAndSize(bytes, offset, entrySize); + eventListener.onGetFound(AbstractVanillaSharedHashMap.this, tmpBytes, metaDataBytes, + key, value); + } + } + + V notifyMissed(Bytes keyBytes, K key, V usingValue) { + if (eventListener != SharedMapEventListeners.NOP) { + return eventListener.onGetMissing(AbstractVanillaSharedHashMap.this, keyBytes, + key, usingValue); + } + return null; + } + + void notifyRemoved(long offset, K key, V value, final int pos) { + if (eventListener != SharedMapEventListeners.NOP) { + tmpBytes.storePositionAndSize(bytes, offset, entrySize); + eventListener.onRemove(AbstractVanillaSharedHashMap.this, tmpBytes, metaDataBytes, + key, value, pos, this); + } + } + + long putValue(int pos, long offset, NativeBytes entry, long valueLenPos, + long entryEndAddr, V value, IntIntMultiMap searchedHashLookup) { + if (value instanceof Byteable) { + return putValue(pos, offset, entry, valueLenPos, entryEndAddr, + null, (Byteable) value, false, searchedHashLookup); + } else { + return putValue(pos, offset, entry, valueLenPos, entryEndAddr, + getValueAsBytes(value), null, true, searchedHashLookup); + } + } + + /** + * Replaces value in existing entry. May cause entry relocation, because there may be not enough space + * for new value in location already allocated for this entry. + * + * @param pos index of the first block occupied by the entry + * @param offset relative offset of the entry in Segment bytes (before, i. e. including + * metaData) + * @param entry relative pointer in Segment bytes + * @param valueLenPos relative position of value "stop bit" in entry + * @param entryEndAddr absolute address of the entry end + * @param valueBytes serialized value, or {@code null} if valueAsByteable is given + * @param valueAsByteable the value to put as {@code Byteable}, or {@code null} if valueBytes is + * given + * @param allowOversize {@code true} if the entry is allowed become oversized if it was not yet + * @return relative offset of the entry in Segment bytes after putting value (that may cause entry + * relocation) + */ + long putValue(int pos, long offset, NativeBytes entry, long valueLenPos, + long entryEndAddr, @Nullable Bytes valueBytes, + @Nullable Byteable valueAsByteable, boolean allowOversize, + IntIntMultiMap searchedHashLookup) { + long valueLenAddr = entry.address() + valueLenPos; + long newValueLen; + if (valueBytes != null) { + newValueLen = valueBytes.remaining(); + } else { + assert valueAsByteable != null; + newValueLen = valueAsByteable.maxSize(); + } + long newValueAddr = alignment.alignAddr( + valueLenAddr + expectedStopBits(newValueLen)); + long newEntryEndAddr = newValueAddr + newValueLen; + // Fast check before counting "sizes in blocks" that include + // integral division + newValueDoesNotFit: + if (newEntryEndAddr != entryEndAddr) { + long entryStartAddr = entryStartAddr(offset); + long oldEntrySize = entryEndAddr - entryStartAddr; + int oldSizeInBlocks = inBlocks(oldEntrySize); + int newSizeInBlocks = inBlocks(newEntryEndAddr - entryStartAddr); + if (newSizeInBlocks > oldSizeInBlocks) { + if (!allowOversize && oldSizeInBlocks == 1) { + // If the value is Byteable, illegal oversize could be detected because of + // too high (inaccurate maxSize() estimate. Marshall the value to get + // the precise size. + if (valueAsByteable != null) { + // noinspection unchecked + return putValue(pos, offset, entry, valueLenPos, entryEndAddr, + getValueAsBytes((V) valueAsByteable), null, false, searchedHashLookup); + } + throw new IllegalArgumentException("Byteable value is not allowed " + + "to make the entry oversized while it was not initially."); + } + if (newSizeInBlocks > MAX_ENTRY_OVERSIZE_FACTOR) { + if (valueAsByteable != null) { + // noinspection unchecked + return putValue(pos, offset, entry, valueLenPos, entryEndAddr, + getValueAsBytes((V) valueAsByteable), null, false, searchedHashLookup); + } + throw new IllegalArgumentException("Value too large: " + + "entry takes " + newSizeInBlocks + " blocks, " + + MAX_ENTRY_OVERSIZE_FACTOR + " is maximum."); + } + if (realloc(pos, oldSizeInBlocks, newSizeInBlocks)) + break newValueDoesNotFit; + // RELOCATION + free(pos, oldSizeInBlocks); + eventListener.onRelocation(pos, this); + int prevPos = pos; + pos = alloc(newSizeInBlocks); + // putValue() is called from put() and replace() + // after successful search by key + replacePosInHashLookupOnRelocation(searchedHashLookup, prevPos, pos); + offset = offsetFromPos(pos); + // Moving metadata, key stop bit and key. + // Don't want to fiddle with pseudo-buffers for this, + // since we already have all absolute addresses. + long newEntryStartAddr = entryStartAddr(offset); + NativeBytes.UNSAFE.copyMemory(entryStartAddr, + newEntryStartAddr, valueLenAddr - entryStartAddr); + entry = entry(offset); + // END OF RELOCATION + } else if (newSizeInBlocks < oldSizeInBlocks) { + // Freeing extra blocks + freeList.clear(pos + newSizeInBlocks, pos + oldSizeInBlocks); + // Do NOT reset nextPosToSearchFrom, because if value + // once was larger it could easily became oversized again, + // But if these blocks will be taken by that time, + // this entry will need to be relocated. + } + } + // Common code for all cases + entry.position(valueLenPos); + entry.writeStopBit(newValueLen); + alignment.alignPositionAddr(entry); + if (valueBytes != null) { + entry.write(valueBytes); + } else { + if (valueAsByteable instanceof BytesMarshallable) { + long posAddr = entry.positionAddr(); + ((BytesMarshallable) valueAsByteable).writeMarshallable(entry); + long actualValueLen = entry.positionAddr() - posAddr; + if (actualValueLen > newValueLen) { + throw new AssertionError( + "Byteable value returned maxSize less than the actual size"); + } + } else { + entry.write(valueAsByteable.bytes(), valueAsByteable.offset(), newValueLen); + } + } + return offset; + } + + void replacePosInHashLookupOnRelocation(IntIntMultiMap searchedHashLookup, int prevPos, int pos) { + searchedHashLookup.replacePrevPos(pos); + } + + void clear() { + lock(); + try { + hashLookup.clear(); + freeList.clear(); + resetSize(); + } finally { + unlock(); + } + } + + void visit(IntIntMultiMap.EntryConsumer entryConsumer) { + hashLookup.forEach(entryConsumer); + } + + public Entry getEntry(long pos) { + long offset = offsetFromPos(pos); + NativeBytes entry = entry(offset); + entry.readStopBit(); + K key = entry.readInstance(kClass, null); //todo: readUsing? + + V value = readValue(entry, null); //todo: reusable container + + //notifyGet(offset - metaDataBytes, key, value); //todo: should we call this? + + return new WriteThroughEntry(key, value); + } + + /** + * Check there is no garbage in freeList. + */ + void checkConsistency() { + lock(); + try { + IntIntMultiMap hashLookup = checkConsistencyHashLookup(); + for (int pos = 0; (pos = (int) freeList.nextSetBit(pos)) >= 0; ) { + PosPresentOnce check = new PosPresentOnce(pos); + hashLookup.forEach(check); + if (check.count != 1) + throw new AssertionError(); + long offset = offsetFromPos(pos); + Bytes entry = entry(offset); + long keyLen = entry.readStopBit(); + entry.skip(keyLen); + afterKeyHookOnCheckConsistency(entry); + long valueLen = entry.readStopBit(); + long sizeInBytes = entrySize(keyLen, valueLen); + int entrySizeInBlocks = inBlocks(sizeInBytes); + if (!freeList.allSet(pos, pos + entrySizeInBlocks)) + throw new AssertionError(); + pos += entrySizeInBlocks; + } + } finally { + unlock(); + } + } + + void afterKeyHookOnCheckConsistency(Bytes entry) { + // no-op + } + + IntIntMultiMap checkConsistencyHashLookup() { + return hashLookup; + } + + private class PosPresentOnce implements IntIntMultiMap.EntryConsumer { + int pos, count = 0; + + PosPresentOnce(int pos) { + this.pos = pos; + } + + @Override + public void accept(int hash, int pos) { + if (this.pos == pos) count++; + } + } + } + + final class EntryIterator implements Iterator>, IntIntMultiMap.EntryConsumer { + + int segmentIndex = segments.length; + + Entry nextEntry, lastReturned; + + Deque segmentPositions = new ArrayDeque(); //todo: replace with a more efficient, auto resizing int[] + + EntryIterator() { + nextEntry = nextSegmentEntry(); + } + + public boolean hasNext() { + return nextEntry != null; + } + + public void remove() { + if (lastReturned == null) throw new IllegalStateException(); + AbstractVanillaSharedHashMap.this.remove(lastReturned.getKey()); + lastReturned = null; + } + + public Map.Entry next() { + Entry e = nextEntry; + if (e == null) + throw new NoSuchElementException(); + lastReturned = e; // cannot assign until after null check + nextEntry = nextSegmentEntry(); + return e; + } + + Entry nextSegmentEntry() { + while (segmentIndex >= 0) { + if (segmentPositions.isEmpty()) { + switchToNextSegment(); + } else { + final Segment segment = segments[segmentIndex]; + segment.lock(); + try { + while (!segmentPositions.isEmpty()) { + Entry entry = segment.getEntry(segmentPositions.removeFirst()); + if (entry != null) { + return entry; + } + } + } finally { + segment.unlock(); + } + } + } + return null; + } + + private void switchToNextSegment() { + segmentPositions.clear(); + segmentIndex--; + if (segmentIndex >= 0) { + final Segment segment = segments[segmentIndex]; + segment.lock(); + try { + segments[segmentIndex].visit(this); + } finally { + segment.unlock(); + } + } + } + + @Override + public void accept(int key, int value) { + segmentPositions.add(value); + } + } + + final class EntrySet extends AbstractSet> { + public Iterator> iterator() { + return new EntryIterator(); + } + + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry) o; + try { + V v = AbstractVanillaSharedHashMap.this.get(e.getKey()); + return v != null && v.equals(e.getValue()); + } catch (ClassCastException ex) { + return false; + } catch (NullPointerException ex) { + return false; + } + } + + public boolean remove(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry) o; + try { + Object key = e.getKey(); + Object value = e.getValue(); + return AbstractVanillaSharedHashMap.this.remove(key, value); + } catch (ClassCastException ex) { + return false; + } catch (NullPointerException ex) { + return false; + } + } + + public int size() { + return AbstractVanillaSharedHashMap.this.size(); + } + + public boolean isEmpty() { + return AbstractVanillaSharedHashMap.this.isEmpty(); + } + + public void clear() { + AbstractVanillaSharedHashMap.this.clear(); + } + } + + final class WriteThroughEntry extends SimpleEntry { + + WriteThroughEntry(K key, V value) { + super(key, value); + } + + @Override + public V setValue(V value) { + put(getKey(), value); + return super.setValue(value); + } + } +} diff --git a/src/main/java/net/openhft/collections/VanillaSharedMap.java b/src/main/java/net/openhft/collections/VanillaSharedMap.java deleted file mode 100644 index e81ee4be7..000000000 --- a/src/main/java/net/openhft/collections/VanillaSharedMap.java +++ /dev/null @@ -1,2842 +0,0 @@ -/* - * Copyright 2014 Higher Frequency Trading - *

- * http://www.higherfrequencytrading.com - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.openhft.collections; - -import net.openhft.chronicle.map.ChronicleMap; -import net.openhft.lang.Maths; -import net.openhft.lang.collection.DirectBitSet; -import net.openhft.lang.collection.SingleThreadedDirectBitSet; -import net.openhft.lang.io.*; -import net.openhft.lang.io.serialization.BytesMarshallable; -import net.openhft.lang.io.serialization.ObjectSerializer; -import net.openhft.lang.io.serialization.impl.VanillaBytesMarshallerFactory; -import net.openhft.lang.model.Byteable; -import net.openhft.lang.model.DataValueClasses; -import net.openhft.lang.model.constraints.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Array; -import java.nio.channels.FileChannel; -import java.util.*; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.*; - -import static java.lang.Thread.currentThread; - - -class VanillaSharedMap extends AbstractVanillaSharedMap { - - public VanillaSharedMap(SharedHashMapBuilder builder, File file, - Class kClass, Class vClass) throws IOException { - super(builder, kClass, vClass); - ObjectSerializer objectSerializer = builder.objectSerializer(); - BytesStore bytesStore = file == null - ? DirectStore.allocateLazy(sizeInBytes(), objectSerializer) - : new MappedStore(file, FileChannel.MapMode.READ_WRITE, sizeInBytes(), objectSerializer); - createMappedStoreAndSegments(bytesStore); - } -} - -abstract class AbstractVanillaSharedMap extends net.openhft.collections.ConcurrentHashMap - implements ChronicleMap { - - private static final Logger LOG = LoggerFactory.getLogger(AbstractVanillaSharedMap.class); - - /** - * Because DirectBitSet implementations couldn't find more than 64 continuous clear or set bits. - */ - private static final int MAX_ENTRY_OVERSIZE_FACTOR = 64; - - private final ObjectSerializer objectSerializer; - private SharedHashMapBuilder builder; - - - // views - - transient ValuesView values; - transient EntrySetView entrySet; - transient KeySetView keySet; - - - /* ----------------Views -------------- */ - - /** - * Base class for views. - */ - abstract static class CollectionView - implements Collection, net.openhft.collections.CollectionView, java.io.Serializable { - private static final long serialVersionUID = 7249069246763182397L; - final AbstractVanillaSharedMap map; - - CollectionView(AbstractVanillaSharedMap map) { - this.map = map; - } - - /** - * Returns the map backing this view. - * - * @return the map backing this view - */ - public AbstractVanillaSharedMap getMap() { - return map; - } - - /** - * Removes all of the elements from this view, by removing all the mappings from the map backing this - * view. - */ - public final void clear() { - map.clear(); - } - - public final int size() { - return map.size(); - } - - public final boolean isEmpty() { - return map.isEmpty(); - } - - // implementations below rely on concrete classes supplying these - // abstract methods - - /** - * Returns an iterator over the elements in this collection. - * - *

The returned iterator is weakly consistent. - * - * @return an iterator over the elements in this collection - */ - public abstract Iterator iterator(); - - public abstract boolean contains(Object o); - - public abstract boolean remove(Object o); - - private static final String oomeMsg = "Required array size too large"; - - public final Object[] toArray() { - long sz = map.mappingCount(); - if (sz > MAX_ARRAY_SIZE) - throw new OutOfMemoryError(oomeMsg); - int n = (int) sz; - Object[] r = new Object[n]; - int i = 0; - for (E e : this) { - if (i == n) { - if (n >= MAX_ARRAY_SIZE) - throw new OutOfMemoryError(oomeMsg); - if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) - n = MAX_ARRAY_SIZE; - else - n += (n >>> 1) + 1; - r = Arrays.copyOf(r, n); - } - r[i++] = e; - } - return (i == n) ? r : Arrays.copyOf(r, i); - } - - @SuppressWarnings("unchecked") - public final T[] toArray(T[] a) { - long sz = map.mappingCount(); - if (sz > MAX_ARRAY_SIZE) - throw new OutOfMemoryError(oomeMsg); - int m = (int) sz; - T[] r = (a.length >= m) ? a : - (T[]) java.lang.reflect.Array - .newInstance(a.getClass().getComponentType(), m); - int n = r.length; - int i = 0; - for (E e : this) { - if (i == n) { - if (n >= MAX_ARRAY_SIZE) - throw new OutOfMemoryError(oomeMsg); - if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) - n = MAX_ARRAY_SIZE; - else - n += (n >>> 1) + 1; - r = Arrays.copyOf(r, n); - } - r[i++] = (T) e; - } - if (a == r && i < n) { - r[i] = null; // null-terminate - return r; - } - return (i == n) ? r : Arrays.copyOf(r, i); - } - - /** - * Returns a string representation of this collection. The string representation consists of the - * string representations of the collection's elements in the order they are returned by its iterator, - * enclosed in square brackets ({@code "[]"}). Adjacent elements are separated by the characters - * {@code ", "} (comma and space). Elements are converted to strings as by {@link - * String#valueOf(Object)}. - * - * @return a string representation of this collection - */ - public final String toString() { - StringBuilder sb = new StringBuilder(); - sb.append('['); - Iterator it = iterator(); - if (it.hasNext()) { - for (; ; ) { - Object e = it.next(); - sb.append(e == this ? "(this Collection)" : e); - if (!it.hasNext()) - break; - sb.append(',').append(' '); - } - } - return sb.append(']').toString(); - } - - public final boolean containsAll(Collection c) { - if (c != this) { - for (Object e : c) { - if (e == null || !contains(e)) - return false; - } - } - return true; - } - - public final boolean removeAll(Collection c) { - if (c == null) throw new NullPointerException(); - boolean modified = false; - for (Iterator it = iterator(); it.hasNext(); ) { - if (c.contains(it.next())) { - it.remove(); - modified = true; - } - } - return modified; - } - - public final boolean retainAll(Collection c) { - if (c == null) throw new NullPointerException(); - boolean modified = false; - for (Iterator it = iterator(); it.hasNext(); ) { - if (!c.contains(it.next())) { - it.remove(); - modified = true; - } - } - return modified; - } - - } - - - /** - * A view of a ConcurrentHashMap as a {@link Collection} of values, in which additions are disabled. This - * class cannot be directly instantiated. See {@link #values()}. - */ - static final class ValuesView extends CollectionView - implements Collection, java.io.Serializable { - private static final long serialVersionUID = 2249069246763182397L; - - ValuesView(AbstractVanillaSharedMap map) { - super(map); - } - - public final boolean contains(Object o) { - return map.containsValue(o); - } - - public final boolean remove(Object o) { - if (o != null) { - for (Iterator it = iterator(); it.hasNext(); ) { - if (o.equals(it.next())) { - it.remove(); - return true; - } - } - } - return false; - } - - public final Iterator iterator() { - ConcurrentHashMap m = map; - Node[] t; - int f = (t = m.table) == null ? 0 : t.length; - return new ValueIterator(t, f, 0, f, m); - } - - public final boolean add(V e) { - throw new UnsupportedOperationException(); - } - - public final boolean addAll(Collection c) { - throw new UnsupportedOperationException(); - } - - public Spliterator spliterator() { - Node[] t; - ConcurrentHashMap m = map; - long n = m.sumCount(); - int f = (t = m.table) == null ? 0 : t.length; - return new ValueSpliterator(t, f, 0, f, n < 0L ? 0L : n); - } - - public void forEach(Consumer action) { - if (action == null) throw new NullPointerException(); - Node[] t; - if ((t = map.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) - action.accept(p.val); - } - } - } - - /** - * A view of a ConcurrentHashMap as a {@link Set} of (key, value) entries. This class cannot be directly - * instantiated. See {@link #entrySet()}. - */ - final class EntrySetView extends CollectionView> - implements Set>, java.io.Serializable { - private static final long serialVersionUID = 2249069246763182397L; - - EntrySetView(AbstractVanillaSharedMap map) { - super(map); - } - - public boolean contains(Object o) { - Object k, v, r; - Map.Entry e; - return ((o instanceof Map.Entry) && - (k = (e = (Map.Entry) o).getKey()) != null && - (r = map.get(k)) != null && - (v = e.getValue()) != null && - (v == r || v.equals(r))); - } - - public boolean remove(Object o) { - Object k, v; - Map.Entry e; - return ((o instanceof Map.Entry) && - (k = (e = (Map.Entry) o).getKey()) != null && - (v = e.getValue()) != null && - map.remove(k, v)); - } - - /** - * @return an iterator over the entries of the backing map - */ - public Iterator> iterator() { - return new EntryIterator(); - } - - public boolean add(Entry e) { - return map.putVal(e.getKey(), e.getValue(), false) == null; - } - - public boolean addAll(Collection> c) { - boolean added = false; - for (Entry e : c) { - if (add(e)) - added = true; - } - return added; - } - - public final int hashCode() { - int h = 0; - Node[] t; - if ((t = map.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) { - h += p.hashCode(); - } - } - return h; - } - - public final boolean equals(Object o) { - Set c; - return ((o instanceof Set) && - ((c = (Set) o) == this || - (containsAll(c) && c.containsAll(this)))); - } - - public Spliterator> spliterator() { - Node[] t; - ConcurrentHashMap m = map; - long n = m.sumCount(); - int f = (t = m.table) == null ? 0 : t.length; - return new EntrySpliterator(t, f, 0, f, n < 0L ? 0L : n, m); - } - - public void forEach(Consumer> action) { - if (action == null) throw new NullPointerException(); - Node[] t; - if ((t = map.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) - action.accept(new MapEntry(p.key, p.val, map)); - } - } - - } - - - /** - * Creates a new {@link Set} backed by a ConcurrentHashMap from the given type to {@code Boolean.TRUE}. - * - * @param the element type of the returned set - * @return the new set - * @since 1.8 - */ - public static KeySetView newKeySet() { - final SharedHashMapBuilder builder = SharedHashMapBuilder.of(Integer.class, - String.class); - - try { - final AbstractVanillaSharedMap chronicleMap = (AbstractVanillaSharedMap) builder.clone().vClass(Boolean.class).file(null) - .create(); - return new KeySetView - (chronicleMap, Boolean.TRUE); - } catch (IOException e) { - throw new IllegalStateException(e); - } - - } - - /** - * Creates a new {@link Set} backed by a ConcurrentHashMap from the given type to {@code Boolean.TRUE}. - * - * @param initialCapacity The implementation performs internal sizing to accommodate this many elements. - * @param the element type of the returned set - * @return the new set - * @throws IllegalArgumentException if the initial capacity of elements is negative - * @since 1.8 - */ - public static KeySetView newKeySet(int initialCapacity) { - return newKeySet(); - } - - /** - * Returns a {@link Set} view of the keys in this map, using the given common mapped value for any - * additions (i.e., {@link Collection#add} and {@link Collection#addAll(Collection)}). This is of course - * only appropriate if it is acceptable to use the same value for all additions from this view. - * - * @param mappedValue the mapped value to use for any additions - * @return the set view - * @throws NullPointerException if the mappedValue is null - */ - public KeySetView keySet(V mappedValue) { - if (mappedValue == null) - throw new NullPointerException(); - return new KeySetView(this, mappedValue); - } - - - /** - * A view of a ConcurrentHashMap as a {@link Set} of keys, in which additions may optionally be enabled by - * mapping to a common value. This class cannot be directly instantiated. See {@link #keySet() keySet()}, - * {@link #keySet(Object) keySet(V)}, {@link #newKeySet() newKeySet()}, {@link #newKeySet(int) - * newKeySet(int)}. - * - * @since 1.8 - */ - public static class KeySetView extends CollectionView - implements Set, java.io.Serializable, net.openhft.collections.KeySetView { - - private static final long serialVersionUID = 7249069246763182397L; - private final V value; - - KeySetView(AbstractVanillaSharedMap map, V value) { // non-public - super(map); - this.value = value; - } - - /** - * Returns the default mapped value for additions, or {@code null} if additions are not supported. - * - * @return the default mapped value for additions, or {@code null} if not supported - */ - public V getMappedValue() { - return value; - } - - /** - * {@inheritDoc} - * - * @throws NullPointerException if the specified key is null - */ - public boolean contains(Object o) { - return map.containsKey(o); - } - - /** - * Removes the key from this map view, by removing the key (and its corresponding value) from the - * backing map. This method does nothing if the key is not in the map. - * - * @param o the key to be removed from the backing map - * @return {@code true} if the backing map contained the specified key - * @throws NullPointerException if the specified key is null - */ - public boolean remove(Object o) { - return map.remove(o) != null; - } - - /** - * @return an iterator over the keys of the backing map - */ - public Iterator iterator() { - Node[] t; - ConcurrentHashMap m = map; - int f = (t = m.table) == null ? 0 : t.length; - return new KeyIterator(t, f, 0, f, m); - } - - /** - * Adds the specified key to this set view by mapping the key to the default mapped value in the - * backing map, if defined. - * - * @param e key to be added - * @return {@code true} if this set changed as a result of the call - * @throws NullPointerException if the specified key is null - * @throws UnsupportedOperationException if no default mapped value for additions was provided - */ - public boolean add(K e) { - V v; - if ((v = value) == null) - throw new UnsupportedOperationException(); - - return map.putVal(e, v, true) == null; - } - - /** - * Adds all of the elements in the specified collection to this set, as if by calling {@link #add} on - * each one. - * - * @param c the elements to be inserted into this set - * @return {@code true} if this set changed as a result of the call - * @throws NullPointerException if the collection or any of its elements are {@code null} - * @throws UnsupportedOperationException if no default mapped value for additions was provided - */ - public boolean addAll(Collection c) { - boolean added = false; - V v; - if ((v = value) == null) - throw new UnsupportedOperationException(); - for (K e : c) { - if (map.putVal(e, v, true) == null) - added = true; - } - return added; - } - - public int hashCode() { - int h = 0; - for (K e : this) - h += e.hashCode(); - return h; - } - - public boolean equals(Object o) { - Set c; - return ((o instanceof Set) && - ((c = (Set) o) == this || - (containsAll(c) && c.containsAll(this)))); - } - - public Spliterator spliterator() { - Node[] t; - ConcurrentHashMap m = map; - long n = m.sumCount(); - int f = (t = m.table) == null ? 0 : t.length; - return new KeySpliterator(t, f, 0, f, n < 0L ? 0L : n); - } - - public void forEach(Consumer action) { - if (action == null) throw new NullPointerException(); - Node[] t; - if ((t = map.table) != null) { - Traverser it = new Traverser(t, t.length, 0, t.length); - for (Node p; (p = it.advance()) != null; ) - action.accept(p.key); - } - } - } - - - /** - * Returns a {@link Set} view of the keys contained in this map. The set is backed by the map, so changes - * to the map are reflected in the set, and vice-versa. The set supports element removal, which removes - * the corresponding mapping from this map, via the {@code Iterator.remove}, {@code Set.remove}, {@code - * removeAll}, {@code retainAll}, and {@code clear} operations. It does not support the {@code add} or - * {@code addAll} operations. - * - *

The view's iterators and spliterators are weakly - * consistent. - * - *

The view's {@code spliterator} reports {@link Spliterator#CONCURRENT}, {@link Spliterator#DISTINCT}, - * and {@link Spliterator#NONNULL}. - * - * @return the set view - */ - public AbstractVanillaSharedMap.KeySetView keySet() { - KeySetView ks; - return (ks = keySet) != null ? ks : (keySet = new KeySetView(this, null)); - } - - // todo consider a better way to do this so that is is not creating an entry object each time - public void forEach(BiConsumer action) { - if (action == null) throw new NullPointerException(); - - final EntryIterator entryIterator = new EntryIterator(); - - for (Entry e = entryIterator.next(); entryIterator.hasNext(); e = entryIterator.next()) { - action.accept(e.getKey(), e.getValue()); - } - - } - - - /** - * If the specified key is not already associated with a value, attempts to compute its value using the - * given mapping function and enters it into this map unless {@code null}. The entire method invocation - * is performed atomically, so the function is applied at most once per key. Some attempted update - * operations on this map by other threads may be blocked while computation is in progress, so the - * computation should be short and simple, and must not attempt to update any other mappings of this map. - * - * @param key key with which the specified value is to be associated - * @param mappingFunction the function to compute a value - * @return the current (existing or computed) value associated with the specified key, or null if the - * computed value is null - * @throws NullPointerException if the specified key or mappingFunction is null - * @throws IllegalStateException if the computation detectably attempts a recursive update to this map - * that would otherwise never complete - * @throws RuntimeException or Error if the mappingFunction does so, in which case the mapping is - * left unestablished - */ - public V computeIfAbsent(K key, Function mappingFunction) { - checkKey(key); - - Bytes keyBytes = getKeyAsBytes(key); - long hash = Hasher.hash(keyBytes); - int segmentNum = hasher.getSegment(hash); - int segmentHash = hasher.segmentHash(hash); - return segments[segmentNum].put(keyBytes, key, null, segmentHash, false, mappingFunction); - - } - - - /** - * Returns the value to which the specified key is mapped, or the given default value if this map contains - * no mapping for the key. - * - * @param key the key whose associated value is to be returned - * @param defaultValue the value to return if this map contains no mapping for the given key - * @return the mapping for the key, if present; else the default value - * @throws NullPointerException if the specified key is null - */ - public V getOrDefault(Object key, V defaultValue) { - V v; - return (v = get(key)) == null ? defaultValue : v; - } - - /** - * Computes initial batch value for bulk tasks. The returned value is approximately exp2 of the number of - * times (minus one) to split task by two before executing leaf action. This value is faster to compute - * and more convenient to use as a guide to splitting than is the depth, since it is used while dividing - * by two anyway. - */ - final int batchFor(long b) { - long n; - if (b == Long.MAX_VALUE || (n = sumCount()) <= 1L || n < b) - return 0; - int sp = ForkJoinPool.getCommonPoolParallelism() << 2; // slack of 4 - return (b <= 0L || (n /= b) >= sp) ? sp : (int) n; - } - - - /** - * Performs the given action for each non-null transformation of each (key, value). - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element, or null if there is - * no transformation (in which case the action is not applied) - * @param action the action - * @param the return type of the transformer - * @since 1.8 - */ - public void forEach(long parallelismThreshold, - BiFunction transformer, - Consumer action) { - if (transformer == null || action == null) - throw new NullPointerException(); - new ForEachTransformedMappingTask - (null, batchFor(parallelismThreshold), 0, 0, table, - transformer, action).invoke(); - } - - /** - * Returns a non-null result from applying the given search function on each (key, value), or null if - * none. Upon success, further element processing is suppressed and the results of any other parallel - * invocations of the search function are ignored. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param searchFunction a function returning a non-null result on success, else null - * @param the return type of the search function - * @return a non-null result from applying the given search function on each (key, value), or null if none - * @since 1.8 - */ - public U search(long parallelismThreshold, - BiFunction searchFunction) { - if (searchFunction == null) throw new NullPointerException(); - return new SearchMappingsTask - (null, batchFor(parallelismThreshold), 0, 0, table, - searchFunction, new AtomicReference()).invoke(); - } - - /** - * Returns the result of accumulating the given transformation of all (key, value) pairs using the given - * reducer to combine values, or null if none. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element, or null if there is - * no transformation (in which case it is not combined) - * @param reducer a commutative associative combining function - * @param the return type of the transformer - * @return the result of accumulating the given transformation of all (key, value) pairs - * @since 1.8 - */ - public U reduce(long parallelismThreshold, - BiFunction transformer, - BiFunction reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceMappingsTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation of all (key, value) pairs using the given - * reducer to combine values, and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation of all (key, value) pairs - * @since 1.8 - */ - public double reduceToDouble(long parallelismThreshold, - ToDoubleBiFunction transformer, - double basis, - DoubleBinaryOperator reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceMappingsToDoubleTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation of all (key, value) pairs using the given - * reducer to combine values, and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation of all (key, value) pairs - * @since 1.8 - */ - public long reduceToLong(long parallelismThreshold, - ToLongBiFunction transformer, - long basis, - LongBinaryOperator reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceMappingsToLongTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation of all (key, value) pairs using the given - * reducer to combine values, and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation of all (key, value) pairs - * @since 1.8 - */ - public int reduceToInt(long parallelismThreshold, - ToIntBiFunction transformer, - int basis, - IntBinaryOperator reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceMappingsToIntTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Performs the given action for each key. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param action the action - * @since 1.8 - */ - public void forEachKey(long parallelismThreshold, - Consumer action) { - if (action == null) throw new NullPointerException(); - new ForEachKeyTask - (null, batchFor(parallelismThreshold), 0, 0, table, - action).invoke(); - } - - /** - * Performs the given action for each non-null transformation of each key. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element, or null if there is - * no transformation (in which case the action is not applied) - * @param action the action - * @param the return type of the transformer - * @since 1.8 - */ - public void forEachKey(long parallelismThreshold, - Function transformer, - Consumer action) { - if (transformer == null || action == null) - throw new NullPointerException(); - new ForEachTransformedKeyTask - (null, batchFor(parallelismThreshold), 0, 0, table, - transformer, action).invoke(); - } - - /** - * Returns a non-null result from applying the given search function on each key, or null if none. Upon - * success, further element processing is suppressed and the results of any other parallel invocations of - * the search function are ignored. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param searchFunction a function returning a non-null result on success, else null - * @param the return type of the search function - * @return a non-null result from applying the given search function on each key, or null if none - * @since 1.8 - */ - public U searchKeys(long parallelismThreshold, - Function searchFunction) { - if (searchFunction == null) throw new NullPointerException(); - return new SearchKeysTask - (null, batchFor(parallelismThreshold), 0, 0, table, - searchFunction, new AtomicReference()).invoke(); - } - - /** - * Returns the result of accumulating all keys using the given reducer to combine values, or null if - * none. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param reducer a commutative associative combining function - * @return the result of accumulating all keys using the given reducer to combine values, or null if none - * @since 1.8 - */ - public K reduceKeys(long parallelismThreshold, - BiFunction reducer) { - if (reducer == null) throw new NullPointerException(); - return new ReduceKeysTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation of all keys using the given reducer to - * combine values, or null if none. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element, or null if there is - * no transformation (in which case it is not combined) - * @param reducer a commutative associative combining function - * @param the return type of the transformer - * @return the result of accumulating the given transformation of all keys - * @since 1.8 - */ - public U reduceKeys(long parallelismThreshold, - Function transformer, - BiFunction reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceKeysTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation of all keys using the given reducer to - * combine values, and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation of all keys - * @since 1.8 - */ - public double reduceKeysToDouble(long parallelismThreshold, - ToDoubleFunction transformer, - double basis, - DoubleBinaryOperator reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceKeysToDoubleTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation of all keys using the given reducer to - * combine values, and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation of all keys - * @since 1.8 - */ - public long reduceKeysToLong(long parallelismThreshold, - ToLongFunction transformer, - long basis, - LongBinaryOperator reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceKeysToLongTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation of all keys using the given reducer to - * combine values, and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation of all keys - * @since 1.8 - */ - public int reduceKeysToInt(long parallelismThreshold, - ToIntFunction transformer, - int basis, - IntBinaryOperator reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceKeysToIntTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Performs the given action for each value. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param action the action - * @since 1.8 - */ - public void forEachValue(long parallelismThreshold, - Consumer action) { - if (action == null) - throw new NullPointerException(); - new ForEachValueTask - (null, batchFor(parallelismThreshold), 0, 0, table, - action).invoke(); - } - - /** - * Performs the given action for each non-null transformation of each value. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element, or null if there is - * no transformation (in which case the action is not applied) - * @param action the action - * @param the return type of the transformer - * @since 1.8 - */ - public void forEachValue(long parallelismThreshold, - Function transformer, - Consumer action) { - if (transformer == null || action == null) - throw new NullPointerException(); - new ForEachTransformedValueTask - (null, batchFor(parallelismThreshold), 0, 0, table, - transformer, action).invoke(); - } - - /** - * Returns a non-null result from applying the given search function on each value, or null if none. Upon - * success, further element processing is suppressed and the results of any other parallel invocations of - * the search function are ignored. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param searchFunction a function returning a non-null result on success, else null - * @param the return type of the search function - * @return a non-null result from applying the given search function on each value, or null if none - * @since 1.8 - */ - public U searchValues(long parallelismThreshold, - Function searchFunction) { - if (searchFunction == null) throw new NullPointerException(); - return new SearchValuesTask - (null, batchFor(parallelismThreshold), 0, 0, table, - searchFunction, new AtomicReference()).invoke(); - } - - /** - * Returns the result of accumulating all values using the given reducer to combine values, or null if - * none. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param reducer a commutative associative combining function - * @return the result of accumulating all values - * @since 1.8 - */ - public V reduceValues(long parallelismThreshold, - BiFunction reducer) { - if (reducer == null) throw new NullPointerException(); - return new ReduceValuesTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation of all values using the given reducer to - * combine values, or null if none. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element, or null if there is - * no transformation (in which case it is not combined) - * @param reducer a commutative associative combining function - * @param the return type of the transformer - * @return the result of accumulating the given transformation of all values - * @since 1.8 - */ - public U reduceValues(long parallelismThreshold, - Function transformer, - BiFunction reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceValuesTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation of all values using the given reducer to - * combine values, and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation of all values - * @since 1.8 - */ - public double reduceValuesToDouble(long parallelismThreshold, - ToDoubleFunction transformer, - double basis, - DoubleBinaryOperator reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceValuesToDoubleTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation of all values using the given reducer to - * combine values, and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation of all values - * @since 1.8 - */ - public long reduceValuesToLong(long parallelismThreshold, - ToLongFunction transformer, - long basis, - LongBinaryOperator reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceValuesToLongTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation of all values using the given reducer to - * combine values, and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation of all values - * @since 1.8 - */ - public int reduceValuesToInt(long parallelismThreshold, - ToIntFunction transformer, - int basis, - IntBinaryOperator reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceValuesToIntTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Performs the given action for each entry. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param action the action - * @since 1.8 - */ - public void forEachEntry(long parallelismThreshold, - Consumer> action) { - if (action == null) throw new NullPointerException(); - new ForEachEntryTask(null, batchFor(parallelismThreshold), 0, 0, table, - action).invoke(); - } - - /** - * Performs the given action for each non-null transformation of each entry. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element, or null if there is - * no transformation (in which case the action is not applied) - * @param action the action - * @param the return type of the transformer - * @since 1.8 - */ - public void forEachEntry(long parallelismThreshold, - Function, ? extends U> transformer, - Consumer action) { - if (transformer == null || action == null) - throw new NullPointerException(); - new ForEachTransformedEntryTask - (null, batchFor(parallelismThreshold), 0, 0, table, - transformer, action).invoke(); - } - - /** - * Returns a non-null result from applying the given search function on each entry, or null if none. Upon - * success, further element processing is suppressed and the results of any other parallel invocations of - * the search function are ignored. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param searchFunction a function returning a non-null result on success, else null - * @param the return type of the search function - * @return a non-null result from applying the given search function on each entry, or null if none - * @since 1.8 - */ - public U searchEntries(long parallelismThreshold, - Function, ? extends U> searchFunction) { - if (searchFunction == null) throw new NullPointerException(); - return new SearchEntriesTask - (null, batchFor(parallelismThreshold), 0, 0, table, - searchFunction, new AtomicReference()).invoke(); - } - - /** - * Returns the result of accumulating all entries using the given reducer to combine values, or null if - * none. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param reducer a commutative associative combining function - * @return the result of accumulating all entries - * @since 1.8 - */ - public Map.Entry reduceEntries(long parallelismThreshold, - BiFunction, Map.Entry, ? extends Map.Entry> reducer) { - if (reducer == null) throw new NullPointerException(); - return new ReduceEntriesTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation of all entries using the given reducer to - * combine values, or null if none. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element, or null if there is - * no transformation (in which case it is not combined) - * @param reducer a commutative associative combining function - * @param the return type of the transformer - * @return the result of accumulating the given transformation of all entries - * @since 1.8 - */ - public U reduceEntries(long parallelismThreshold, - Function, ? extends U> transformer, - BiFunction reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceEntriesTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation of all entries using the given reducer to - * combine values, and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation of all entries - * @since 1.8 - */ - public double reduceEntriesToDouble(long parallelismThreshold, - ToDoubleFunction> transformer, - double basis, - DoubleBinaryOperator reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceEntriesToDoubleTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation of all entries using the given reducer to - * combine values, and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation of all entries - * @since 1.8 - */ - public long reduceEntriesToLong(long parallelismThreshold, - ToLongFunction> transformer, - long basis, - LongBinaryOperator reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceEntriesToLongTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Returns the result of accumulating the given transformation of all entries using the given reducer to - * combine values, and the given basis as an identity value. - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param transformer a function returning the transformation for an element - * @param basis the identity (initial default value) for the reduction - * @param reducer a commutative associative combining function - * @return the result of accumulating the given transformation of all entries - * @since 1.8 - */ - public int reduceEntriesToInt(long parallelismThreshold, - ToIntFunction> transformer, - int basis, - IntBinaryOperator reducer) { - if (transformer == null || reducer == null) - throw new NullPointerException(); - return new MapReduceEntriesToIntTask - (null, batchFor(parallelismThreshold), 0, 0, table, - null, transformer, basis, reducer).invoke(); - } - - /** - * Performs the given action for each (key, value). - * - * @param parallelismThreshold the (estimated) number of elements needed for this operation to be executed - * in parallel - * @param action the action - * @since 1.8 - */ - public void forEach(long parallelismThreshold, - BiConsumer action) { - if (action == null) throw new NullPointerException(); - new ForEachMappingTask - (null, batchFor(parallelismThreshold), 0, 0, table, - action).invoke(); - } - - - @SuppressWarnings("serial") - static final class SearchMappingsTask - extends BulkTask { - final BiFunction searchFunction; - final AtomicReference result; - - SearchMappingsTask - (BulkTask p, int b, int i, int f, Node[] t, - BiFunction searchFunction, - AtomicReference result) { - super(p, b, i, f, t); - this.searchFunction = searchFunction; - this.result = result; - } - - public final U getRawResult() { - return result.get(); - } - - public final void compute() { - final BiFunction searchFunction; - final AtomicReference result; - if ((searchFunction = this.searchFunction) != null && - (result = this.result) != null) { - for (int i = baseIndex, f, h; batch > 0 && - (h = ((f = baseLimit) + i) >>> 1) > i; ) { - if (result.get() != null) - return; - addToPendingCount(1); - new SearchMappingsTask - (this, batch >>>= 1, baseLimit = h, f, tab, - searchFunction, result).fork(); - } - while (result.get() == null) { - U u; - Node p; - if ((p = advance()) == null) { - propagateCompletion(); - break; - } - if ((u = searchFunction.apply(p.key, p.val)) != null) { - if (result.compareAndSet(null, u)) - quietlyCompleteRoot(); - break; - } - } - } - } - } - - private static int figureBufferAllocationFactor(SharedHashMapBuilder builder) { - // if expected map size is about 1000, seems rather wasteful to allocate - // key and value serialization buffers each x64 of expected entry size.. - return (int) Math.min(Math.max(2L, builder.entries() >> 10), - MAX_ENTRY_OVERSIZE_FACTOR); - } - - private final int bufferAllocationFactor; - - private final ThreadLocal localBufferForKeys = - new ThreadLocal(); - private final ThreadLocal localBufferForValues = - new ThreadLocal(); - - final Class kClass; - final Class vClass; - private final long lockTimeOutNS; - final int metaDataBytes; - Segment[] segments; // non-final for close() - // non-final for close() and because it is initialized out of constructor - BytesStore ms; - final Hasher hasher; - - // private final int replicas; - final int entrySize; - final Alignment alignment; - final int entriesPerSegment; - - private final SharedMapErrorListener errorListener; - - /** - * Non-final because could be changed in VanillaSharedReplicatedHashMap. - */ - volatile SharedMapEventListener> eventListener; - - private final boolean generatedKeyType; - final boolean generatedValueType; - - // if set the ReturnsNull fields will cause some functions to return NULL - // rather than as returning the Object can be expensive for something you probably don't use. - final boolean putReturnsNull; - final boolean removeReturnsNull; - - - public AbstractVanillaSharedMap(SharedHashMapBuilder builder, - Class kClass, Class vClass) throws IOException { - this.builder = builder.clone(); - bufferAllocationFactor = figureBufferAllocationFactor(builder); - this.kClass = kClass; - this.vClass = vClass; - - lockTimeOutNS = builder.lockTimeOutMS() * 1000000; - - // this.replicas = builder.replicas(); - this.entrySize = builder.alignedEntrySize(); - this.alignment = builder.entryAndValueAlignment(); - - this.errorListener = builder.errorListener(); - this.generatedKeyType = builder.generatedKeyType(); - this.generatedValueType = builder.generatedValueType(); - this.putReturnsNull = builder.putReturnsNull(); - this.removeReturnsNull = builder.removeReturnsNull(); - this.objectSerializer = builder.objectSerializer(); - - int segments = builder.actualSegments(); - int entriesPerSegment = builder.actualEntriesPerSegment(); - this.entriesPerSegment = entriesPerSegment; - this.metaDataBytes = builder.metaDataBytes(); - this.eventListener = builder.eventListener(); - - int hashMask = useSmallMultiMaps() ? 0xFFFF : ~0; - this.hasher = new Hasher(segments, hashMask); - - @SuppressWarnings("unchecked") - Segment[] ss = (Segment[]) Array.newInstance(segmentType(), segments); - this.segments = ss; - } - - Class segmentType() { - return Segment.class; - } - - long createMappedStoreAndSegments(BytesStore bytesStore) throws IOException { - this.ms = bytesStore; - - onHeaderCreated(); - - long offset = getHeaderSize(); - long segmentSize = segmentSize(); - for (int i = 0; i < this.segments.length; i++) { - this.segments[i] = createSegment((NativeBytes) ms.bytes(offset, segmentSize), i); - offset += segmentSize; - } - return offset; - } - - /** - * called when the header is created - */ - void onHeaderCreated() { - - } - - int getHeaderSize() { - return SharedHashMapBuilder.HEADER_SIZE; - } - - Segment createSegment(NativeBytes bytes, int index) { - return new Segment(bytes, index); - } - - @Override - public File file() { - return ms.file(); - } - - /** - * @param size positive number - * @return number of bytes taken by {@link net.openhft.lang.io.AbstractBytes#writeStopBit(long)} applied - * to {@code size} - */ - static int expectedStopBits(long size) { - if (size <= 127) - return 1; - // numberOfLeadingZeros is cheap intrinsic on modern CPUs - // integral division is not... but there is no choice - return ((70 - Long.numberOfLeadingZeros(size)) / 7); - } - - - long sizeInBytes() { - return getHeaderSize() + - segments.length * segmentSize(); - } - - - long sizeOfMultiMap() { - int np2 = Maths.nextPower2(entriesPerSegment, 8); - return align64(np2 * (entriesPerSegment > (1 << 16) ? 8L : 4L)); - } - - boolean useSmallMultiMaps() { - return entriesPerSegment <= (1 << 16); - } - - long sizeOfBitSets() { - return align64(entriesPerSegment / 8); - } - - int numberOfBitSets() { - return 1; // for free list - // + (replicas > 0 ? 1 : 0) // deleted set - // + replicas; // to notify each replica of a change. - } - - long segmentSize() { - long ss = SharedHashMapBuilder.SEGMENT_HEADER - + sizeOfMultiMap() * multiMapsPerSegment() - + numberOfBitSets() * sizeOfBitSets() // the free list and 0+ dirty lists. - + sizeOfEntriesInSegment(); - if ((ss & 63) != 0) - throw new AssertionError(); - - // Say, there is 32 KB L1 cache with 2(4, 8) way set associativity, 64-byte lines. - // It means there are 32 * 1024 / 64 / 2(4, 8) = 256(128, 64) sets, - // i. e. each way (bank) contains 256(128, 64) lines. (L2 and L3 caches has more sets.) - // If segment size in lines multiplied by 2^n is divisible by set size, - // every 2^n-th segment header fall into the same set. - // To break this up we make segment size odd in lines, in this case only each - // 256(128, 64)-th segment header fall into the same set. - - // If there are 64 sets in L1, it should be 8- or much less likely 4-way, and segments - // collision by pairs is not so terrible. - - // if the size is a multiple of 4096 or slightly more. Make sure it is at least 64 more than a multiple. - if ((ss & 4093) < 64) - ss = (ss & ~63) + 64; - - return ss; - } - - int multiMapsPerSegment() { - return 1; - } - - private long sizeOfEntriesInSegment() { - return align64((long) entriesPerSegment * entrySize); - } - - /** - * Cache line alignment, assuming 64-byte cache lines. - */ - static long align64(long l) { - return (l + 63) & ~63; - } - - /** - * {@inheritDoc} - */ - @Override - public void close() { - if (ms == null) - return; - ms.free(); - segments = null; - ms = null; - } - - private DirectBytes acquireBufferForKey() { - DirectBytes buffer = localBufferForKeys.get(); - if (buffer == null) { - buffer = new DirectStore(ms.objectSerializer(), - entrySize * bufferAllocationFactor, false).bytes(); - localBufferForKeys.set(buffer); - } else { - buffer.clear(); - } - return buffer; - } - - private DirectBytes acquireBufferForValue() { - DirectBytes buffer = localBufferForValues.get(); - if (buffer == null) { - buffer = new DirectStore(ms.objectSerializer(), - entrySize * bufferAllocationFactor, false).bytes(); - localBufferForValues.set(buffer); - } else { - buffer.clear(); - } - return buffer; - } - - void checkKey(Object key) { - if (!kClass.isInstance(key)) { - // key.getClass will cause NPE exactly as needed - throw new ClassCastException("Key must be a " + kClass.getName() + - " but was a " + key.getClass()); - } - } - - void checkValue(Object value) { - if (!vClass.isInstance(value)) { - throw new ClassCastException("Value must be a " + vClass.getName() + - " but was a " + value.getClass()); - } - } - - /** - * {@inheritDoc} - */ - @Override - public V put(K key, V value) { - return putVal(key, value, true); - } - - - /** - * {@inheritDoc} - */ - @Override - public V putIfAbsent(K key, V value) { - return putVal(key, value, false); - } - - V putVal(K key, V value, boolean onlyIfAbsent) { - return put0(key, value, !onlyIfAbsent); - } - - V put0(K key, V value, boolean replaceIfPresent) { - super.putVal(key, value, replaceIfPresent); - checkKey(key); - checkValue(value); - Bytes keyBytes = getKeyAsBytes(key); - long hash = Hasher.hash(keyBytes); - int segmentNum = hasher.getSegment(hash); - int segmentHash = hasher.segmentHash(hash); - return segments[segmentNum].put(keyBytes, key, value, segmentHash, replaceIfPresent, null); - } - - - DirectBytes getKeyAsBytes(K key) { - DirectBytes buffer = acquireBufferForKey(); - if (generatedKeyType) - ((BytesMarshallable) key).writeMarshallable(buffer); - else - buffer.writeInstance(kClass, key); - buffer.flip(); - return buffer; - } - - DirectBytes getValueAsBytes(V value) { - DirectBytes buffer = acquireBufferForValue(); - buffer.clear(); - if (generatedValueType) - ((BytesMarshallable) value).writeMarshallable(buffer); - else - buffer.writeInstance(vClass, value); - buffer.flip(); - return buffer; - } - - /** - * {@inheritDoc} - */ - @Override - public V get(Object key) { - return lookupUsing((K) key, null, false); - } - - @Override - public V getUsing(K key, V value) { - return lookupUsing(key, value, false); - } - - @Override - public V acquireUsing(K key, V value) { - return lookupUsing(key, value, true); - } - - V lookupUsing(K key, V value, boolean create) { - checkKey(key); - Bytes keyBytes = getKeyAsBytes(key); - long hash = Hasher.hash(keyBytes); - int segmentNum = hasher.getSegment(hash); - int segmentHash = hasher.segmentHash(hash); - return segments[segmentNum].acquire(keyBytes, key, value, segmentHash, create); - } - - - /** - * {@inheritDoc} - */ - @Override - public boolean containsKey(final Object key) { - checkKey(key); - Bytes keyBytes = getKeyAsBytes((K) key); - long hash = Hasher.hash(keyBytes); - int segmentNum = hasher.getSegment(hash); - int segmentHash = hasher.segmentHash(hash); - - return segments[segmentNum].containsKey(keyBytes, segmentHash); - } - - @Override - public void clear() { - for (Segment segment : segments) - segment.clear(); - } - - - /** - * Returns a {@link Collection} view of the values contained in this map. The collection is backed by the - * map, so changes to the map are reflected in the collection, and vice-versa. The collection supports - * element removal, which removes the corresponding mapping from this map, via the {@code - * Iterator.remove}, {@code Collection.remove}, {@code removeAll}, {@code retainAll}, and {@code clear} - * operations. It does not support the {@code add} or {@code addAll} operations. - * - *

The view's iterators and spliterators are weakly - * consistent. - * - *

The view's {@code spliterator} reports {@link Spliterator#CONCURRENT} and {@link - * Spliterator#NONNULL}. - * - * @return the collection view - */ - public Collection values() { - ValuesView vs; - return (vs = values) != null ? vs : (values = new ValuesView(this)); - } - - /** - * Returns a {@link Set} view of the mappings contained in this map. The set is backed by the map, so - * changes to the map are reflected in the set, and vice-versa. The set supports element removal, which - * removes the corresponding mapping from the map, via the {@code Iterator.remove}, {@code Set.remove}, - * {@code removeAll}, {@code retainAll}, and {@code clear} operations. - * - *

The view's iterators and spliterators are weakly - * consistent. - * - *

The view's {@code spliterator} reports {@link Spliterator#CONCURRENT}, {@link Spliterator#DISTINCT}, - * and {@link Spliterator#NONNULL}. - * - * @return the set view - */ - public Set> entrySet() { - EntrySetView es; - return (es = entrySet) != null ? es : (entrySet = new EntrySetView(this)); - } - - /** - * {@inheritDoc} - * - * @throws NullPointerException if the specified key is null - */ - @Override - public V remove(final Object key) { - return removeIfValueIs(key, null); - } - - /** - * {@inheritDoc} - * - * @throws NullPointerException if the specified key is null - */ - @Override - public boolean remove(final Object key, final Object value) { - if (value == null) - return false; // CHM compatibility; I would throw NPE - return removeIfValueIs(key, (V) value) != null; - } - - - /** - * removes ( if there exists ) an entry from the map, if the {@param key} and {@param expectedValue} match - * that of a maps.entry. If the {@param expectedValue} equals null then ( if there exists ) an entry whose - * key equals {@param key} this is removed. - * - * @param key the key of the entry to remove - * @param expectedValue null if not required - * @return true if and entry was removed - */ - V removeIfValueIs(final Object key, final V expectedValue) { - checkKey(key); - Bytes keyBytes = getKeyAsBytes((K) key); - long hash = Hasher.hash(keyBytes); - int segmentNum = hasher.getSegment(hash); - int segmentHash = hasher.segmentHash(hash); - return segments[segmentNum].remove(keyBytes, (K) key, expectedValue, segmentHash); - } - - /** - * {@inheritDoc} - * - * @throws NullPointerException if any of the arguments are null - */ - @Override - public boolean replace(final K key, final V oldValue, final V newValue) { - checkValue(oldValue); - return oldValue.equals(replaceIfValueIs(key, oldValue, newValue)); - } - - - /** - * {@inheritDoc} - * - * @return the previous value associated with the specified key, or null if there was no mapping - * for the key - * @throws NullPointerException if the specified key or value is null - */ - @Override - public V replace(final K key, final V value) { - return replaceIfValueIs(key, null, value); - } - - @Override - public long longSize() { - long result = 0; - - for (final Segment segment : this.segments) { - result += segment.getSize(); - } - - return result; - } - - @Override - public int size() { - long size = longSize(); - return size > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) size; - } - - /** - * replace the value in a map, only if the existing entry equals {@param existingValue} - * - * @param key the key into the map - * @param existingValue the expected existing value in the map ( could be null when we don't wish to do - * this check ) - * @param newValue the new value you wish to store in the map - * @return the value that was replaced - */ - V replaceIfValueIs(@net.openhft.lang.model.constraints.NotNull final K key, final V existingValue, final V newValue) { - checkKey(key); - checkValue(newValue); - Bytes keyBytes = getKeyAsBytes(key); - long hash = Hasher.hash(keyBytes); - int segmentNum = hasher.getSegment(hash); - int segmentHash = hasher.segmentHash(hash); - return segments[segmentNum].replace(keyBytes, key, existingValue, newValue, segmentHash); - } - - /** - * For testing - */ - void checkConsistency() { - for (Segment segment : segments) { - segment.checkConsistency(); - } - } - - - static final class Hasher { - - static long hash(Bytes bytes) { - long h = 0; - long i = bytes.position(); - long limit = bytes.limit(); // clustering. - for (; i < limit - 7; i += 8) - h = 1011001110001111L * h + bytes.readLong(i); - for (; i < limit - 1; i += 2) - h = 101111 * h + bytes.readShort(i); - if (i < limit) - h = 2111 * h + bytes.readByte(i); - h *= 11018881818881011L; - h ^= (h >>> 41) ^ (h >>> 21); - //System.out.println(bytes + " => " + Long.toHexString(h)); - return h; - } - - private final int segments; - private final int bits; - private final int mask; - - Hasher(int segments, int mask) { - this.segments = segments; - this.bits = Maths.intLog2(segments); - this.mask = mask; - } - - int segmentHash(long hash) { - return (int) (hash >>> bits) & mask; - } - - int getSegment(long hash) { - return (int) (hash & (segments - 1)); - } - } - - // these methods should be package local, not public or private. - class Segment implements SharedSegment { - /* - The entry format is - - stop-bit encoded length for key - - bytes for the key - - stop-bit encoded length of the value - - bytes for the value. - */ - static final int LOCK_OFFSET = 0; // 64-bit - static final int SIZE_OFFSET = LOCK_OFFSET + 8; // 32-bit - static final int PAD1_OFFSET = SIZE_OFFSET + 4; // 32-bit - static final int REPLICA_OFFSET = PAD1_OFFSET + 4; // 64-bit - - private final NativeBytes bytes; - private final int index; - final MultiStoreBytes tmpBytes = new MultiStoreBytes(); - private IntIntMultiMap hashLookup; - private final SingleThreadedDirectBitSet freeList; - private int nextPosToSearchFrom = 0; - final long entriesOffset; - - - /** - * @param bytes - * @param index the index of this segment held by the map - */ - Segment(NativeBytes bytes, int index) { - this.bytes = bytes; - this.index = index; - - long start = bytes.startAddr() + SharedHashMapBuilder.SEGMENT_HEADER; - createHashLookups(start); - start += sizeOfMultiMap() * multiMapsPerSegment(); - final NativeBytes bsBytes = new NativeBytes( - tmpBytes.objectSerializer(), start, start + sizeOfBitSets(), null); - freeList = new SingleThreadedDirectBitSet(bsBytes); - start += numberOfBitSets() * sizeOfBitSets(); - entriesOffset = start - bytes.startAddr(); - assert bytes.capacity() >= entriesOffset + entriesPerSegment * entrySize; - } - - void createHashLookups(long start) { - hashLookup = createMultiMap(start); - } - - IntIntMultiMap createMultiMap(long start) { - final NativeBytes multiMapBytes = - new NativeBytes(new VanillaBytesMarshallerFactory(), start, start + sizeOfMultiMap(), null); - multiMapBytes.load(); - return useSmallMultiMaps() ? - new VanillaShortShortMultiMap(multiMapBytes) : - new VanillaIntIntMultiMap(multiMapBytes); - } - - public int getIndex() { - return index; - } - - - /* Methods with private access modifier considered private to Segment - * class, although Java allows to access them from outer class anyway. - */ - - /** - * increments the size by one - */ - void incrementSize() { - this.bytes.addInt(SIZE_OFFSET, 1); - } - - void resetSize() { - this.bytes.writeInt(SIZE_OFFSET, 0); - } - - /** - * decrements the size by one - */ - void decrementSize() { - this.bytes.addInt(SIZE_OFFSET, -1); - } - - /** - * reads the the number of entries in this segment - */ - int getSize() { - // any negative value is in error state. - return Math.max(0, this.bytes.readVolatileInt(SIZE_OFFSET)); - } - - - public void lock() throws IllegalStateException { - while (true) { - final boolean success = bytes.tryLockNanosLong(LOCK_OFFSET, lockTimeOutNS); - if (success) return; - if (currentThread().isInterrupted()) { - throw new IllegalStateException(new InterruptedException("Unable to obtain lock, interrupted")); - } else { - errorListener.onLockTimeout(bytes.threadIdForLockLong(LOCK_OFFSET)); - bytes.resetLockLong(LOCK_OFFSET); - } - } - } - - public void unlock() { - try { - bytes.unlockLong(LOCK_OFFSET); - } catch (IllegalMonitorStateException e) { - errorListener.errorOnUnlock(e); - } - } - - public long offsetFromPos(long pos) { - return entriesOffset + pos * entrySize; - } - - long posFromOffset(long offset) { - return (offset - entriesOffset) / entrySize; - } - - - public MultiStoreBytes entry(long offset) { - return reuse(tmpBytes, offset); - } - - private MultiStoreBytes reuse(MultiStoreBytes entry, long offset) { - offset += metaDataBytes; - entry.storePositionAndSize(bytes, offset, - // "Infinity". Limit not used when treating entries as - // possibly oversized - bytes.limit() - offset); - return entry; - } - - long entryStartAddr(long offset) { - // entry.address() points to "needed" start addr + metaDataBytes - return bytes.startAddr() + offset; - } - - private long entrySize(long keyLen, long valueLen) { - return alignment.alignAddr(metaDataBytes + - expectedStopBits(keyLen) + keyLen + - expectedStopBits(valueLen)) + valueLen; - } - - int inBlocks(long sizeInBytes) { - if (sizeInBytes <= entrySize) - return 1; - // int division is MUCH faster than long on Intel CPUs - sizeInBytes -= 1; - if (sizeInBytes <= Integer.MAX_VALUE) - return (((int) sizeInBytes) / entrySize) + 1; - return (int) (sizeInBytes / entrySize) + 1; - } - - /** - * Used to acquire an object of type V from the Segment.

{@code usingValue} is reused to read the - * value if key is present in this Segment, if key is absent in this Segment:

  1. If {@code - * create == false}, just {@code null} is returned (except when event listener provides a value "on - * get missing" - then it is put into this Segment for the key).
  2. If {@code create == - * true}, {@code usingValue} or a newly created instance of value class, if {@code usingValue == - * null}, is put into this Segment for the key.
- * - * @param keyBytes serialized {@code key} - * @param hash2 a hash code related to the {@code keyBytes} - * @return the value which is finally associated with the given key in this Segment after execution of - * this method, or {@code null}. - */ - V acquire(Bytes keyBytes, K key, V usingValue, int hash2, boolean create) { - lock(); - try { - MultiStoreBytes entry = tmpBytes; - long offset = searchKey(keyBytes, hash2, entry, hashLookup); - if (offset >= 0) { - return onKeyPresentOnAcquire(key, usingValue, offset, entry); - } else { - usingValue = tryObtainUsingValueOnAcquire(keyBytes, key, usingValue, create); - if (usingValue != null) { - // If `create` is false, this method was called from get() or getUsing() - // and non-null `usingValue` was returned by notifyMissed() method. - // This "missed" default value is considered as genuine value - // rather than "using" container to fill up, even if it implements Byteable. - offset = putEntry(keyBytes, usingValue, create); - incrementSize(); - notifyPut(offset, true, key, usingValue, posFromOffset(offset)); - return usingValue; - } else { - return null; - } - } - } finally { - unlock(); - } - } - - long searchKey(Bytes keyBytes, int hash2, - MultiStoreBytes entry, IntIntMultiMap hashLookup) { - long keyLen = keyBytes.remaining(); - hashLookup.startSearch(hash2); - for (int pos; (pos = hashLookup.nextPos()) >= 0; ) { - long offset = offsetFromPos(pos); - reuse(entry, offset); - if (!keyEquals(keyBytes, keyLen, entry)) - continue; - // key is found - entry.skip(keyLen); - return offset; - } - // key is not found - return -1L; - } - - V onKeyPresentOnAcquire(K key, V usingValue, long offset, NativeBytes entry) { - V v = readValue(entry, usingValue); - notifyGet(offset, key, v); - return v; - } - - V tryObtainUsingValueOnAcquire(Bytes keyBytes, K key, V usingValue, boolean create) { - if (create) { - if (usingValue != null) { - return usingValue; - } else { - if (generatedValueType) - return DataValueClasses.newDirectReference(vClass); - else { - try { - return vClass.newInstance(); - } catch (Exception e) { - throw new AssertionError(e); - } - } - } - } else { - if (usingValue instanceof Byteable) - ((Byteable) usingValue).bytes(null, 0); - return notifyMissed(keyBytes, key, usingValue); - } - } - - - /** - * @param keyBytes the bytes of the key - * @param key the key to the entry you wish to add or update - * @param value the value in the entry you wish to add or update - * @param hash2 the hash of the key in this segment - * @param replaceIfPresent true if the item is to be replaced - * @param mappingFunction if non null, this takes presidents over the value, the value calculated via - * the mappingFunction is used instead - * @return - */ - V put(Bytes keyBytes, K key, V value, int hash2, boolean replaceIfPresent, Function mappingFunction) { - lock(); - try { - long keyLen = keyBytes.remaining(); - hashLookup.startSearch(hash2); - for (int pos; (pos = hashLookup.nextPos()) >= 0; ) { - long offset = offsetFromPos(pos); - NativeBytes entry = entry(offset); - if (!keyEquals(keyBytes, keyLen, entry)) - continue; - // key is found - entry.skip(keyLen); - if (replaceIfPresent) { - { - if (mappingFunction != null) - value = mappingFunction.apply(key); - if (value == null) - return putReturnsNull ? null : readValue(entry, null); - return replaceValueOnPut(key, value, entry, pos, offset, !putReturnsNull, hashLookup); - } - } else { - return putReturnsNull ? null : readValue(entry, null); - } - } - - if (mappingFunction != null) - value = mappingFunction.apply(key); - if (value == null) - return null; - // key is not found - long offset = putEntry(keyBytes, value, false); - incrementSize(); - notifyPut(offset, true, key, value, posFromOffset(offset)); - return null; - } finally { - unlock(); - } - } - - V replaceValueOnPut(K key, V value, NativeBytes entry, int pos, long offset, - boolean readPrevValue, IntIntMultiMap searchedHashLookup) { - long valueLenPos = entry.position(); - long valueLen = readValueLen(entry); - long entryEndAddr = entry.positionAddr() + valueLen; - V prevValue = null; - if (readPrevValue) - prevValue = readValue(entry, null, valueLen); - - // putValue may relocate entry and change offset - offset = putValue(pos, offset, entry, valueLenPos, entryEndAddr, value, searchedHashLookup); - notifyPut(offset, false, key, value, posFromOffset(offset)); - return prevValue; - } - - /** - * Puts entry. If {@code value} implements {@link net.openhft.lang.model.Byteable} interface and - * {@code usingValue} is {@code true}, the value is backed with the bytes of this entry. - * - * @param keyBytes serialized key - * @param value the value to put - * @param usingValue {@code true} if the value should be backed with the bytes of the entry, if it - * implements {@link net.openhft.lang.model.Byteable} interface, {@code false} if it - * should put itself - * @return offset of the written entry in the Segment bytes - */ - private long putEntry(Bytes keyBytes, V value, boolean usingValue) { - long keyLen = keyBytes.remaining(); - - // "if-else polymorphism" is not very beautiful, but allows to - // reuse the rest code of this method and doesn't hurt performance. - boolean byteableValue = usingValue && value instanceof Byteable; - long valueLen; - Bytes valueBytes = null; - Byteable valueAsByteable = null; - if (!byteableValue) { - valueBytes = getValueAsBytes(value); - valueLen = valueBytes.remaining(); - } else { - valueAsByteable = (Byteable) value; - valueLen = valueAsByteable.maxSize(); - } - - long entrySize = entrySize(keyLen, valueLen); - int pos = alloc(inBlocks(entrySize)); - long offset = offsetFromPos(pos); - clearMetaData(offset); - NativeBytes entry = entry(offset); - - entry.writeStopBit(keyLen); - entry.write(keyBytes); - - writeValueOnPutEntry(valueLen, valueBytes, valueAsByteable, entry); - hashLookup.putAfterFailedSearch(pos); - return offset; - } - - void writeValueOnPutEntry(long valueLen, @Nullable Bytes valueBytes, - @Nullable Byteable valueAsByteable, NativeBytes entry) { - entry.writeStopBit(valueLen); - alignment.alignPositionAddr(entry); - - if (valueBytes != null) { - entry.write(valueBytes); - } else { - assert valueAsByteable != null; - long valueOffset = entry.positionAddr() - bytes.address(); - bytes.zeroOut(valueOffset, valueOffset + valueLen); - valueAsByteable.bytes(bytes, valueOffset); - } - } - - void clearMetaData(long offset) { - if (metaDataBytes > 0) - bytes.zeroOut(offset, offset + metaDataBytes); - } - - int alloc(int blocks) { - int ret = (int) freeList.setNextNContinuousClearBits(nextPosToSearchFrom, - blocks); - if (ret == DirectBitSet.NOT_FOUND) { - ret = (int) freeList.setNextNContinuousClearBits(0, blocks); - if (ret == DirectBitSet.NOT_FOUND) { - if (blocks == 1) { - throw new IllegalArgumentException( - "Segment is full, no free entries found"); - } else { - throw new IllegalArgumentException( - "Segment is full or has no ranges of " + blocks - + " continuous free blocks" - ); - } - } - } - // if bit at nextPosToSearchFrom is clear, it was skipped because - // more than 1 block was requested. Don't move nextPosToSearchFrom - // in this case. blocks == 1 clause is just a fast path. - if (blocks == 1 || freeList.isSet(nextPosToSearchFrom)) - nextPosToSearchFrom = ret + blocks; - return ret; - } - - private boolean realloc(int fromPos, int oldBlocks, int newBlocks) { - if (freeList.allClear(fromPos + oldBlocks, fromPos + newBlocks)) { - freeList.set(fromPos + oldBlocks, fromPos + newBlocks); - return true; - } else { - return false; - } - } - - void free(int fromPos, int blocks) { - freeList.clear(fromPos, fromPos + blocks); - if (fromPos < nextPosToSearchFrom) - nextPosToSearchFrom = fromPos; - } - - V readValue(NativeBytes entry, V value) { - return readValue(entry, value, readValueLen(entry)); - } - - long readValueLen(Bytes entry) { - long valueLen = entry.readStopBit(); - alignment.alignPositionAddr(entry); - return valueLen; - } - - /** - * TODO use the value length to limit reading - * - * @param value the object to reuse (if possible), if {@code null} a new object is created - */ - V readValue(NativeBytes entry, V value, long valueLen) { - if (generatedValueType) - if (value == null) - value = DataValueClasses.newDirectReference(vClass); - else - assert value instanceof Byteable; - if (value instanceof Byteable) { - long valueOffset = entry.positionAddr() - bytes.address(); - ((Byteable) value).bytes(bytes, valueOffset); - return value; - } - return entry.readInstance(vClass, value); - } - - boolean keyEquals(Bytes keyBytes, long keyLen, Bytes entry) { - return keyLen == entry.readStopBit() && entry.startsWith(keyBytes); - } - - /** - * Removes a key (or key-value pair) from the Segment.

The entry will only be removed if {@code - * expectedValue} equals to {@code null} or the value previously corresponding to the specified key. - * - * @param keyBytes bytes of the key to remove - * @param hash2 a hash code related to the {@code keyBytes} - * @return the value of the entry that was removed if the entry corresponding to the {@code keyBytes} - * exists and {@link #removeReturnsNull} is {@code false}, {@code null} otherwise - */ - V remove(Bytes keyBytes, K key, V expectedValue, int hash2) { - lock(); - try { - long keyLen = keyBytes.remaining(); - hashLookup.startSearch(hash2); - for (int pos; (pos = hashLookup.nextPos()) >= 0; ) { - long offset = offsetFromPos(pos); - NativeBytes entry = entry(offset); - if (!keyEquals(keyBytes, keyLen, entry)) - continue; - // key is found - entry.skip(keyLen); - long valueLen = readValueLen(entry); - long entryEndAddr = entry.positionAddr() + valueLen; - V valueRemoved = expectedValue != null || !removeReturnsNull - ? readValue(entry, null, valueLen) : null; - if (expectedValue != null && !expectedValue.equals(valueRemoved)) - return null; - hashLookup.removePrevPos(); - decrementSize(); - free(pos, inBlocks(entryEndAddr - entryStartAddr(offset))); - notifyRemoved(offset, key, valueRemoved, pos); - return valueRemoved; - } - // key is not found - return null; - } finally { - unlock(); - } - } - - boolean containsKey(Bytes keyBytes, int hash2) { - lock(); - try { - long keyLen = keyBytes.remaining(); - IntIntMultiMap hashLookup = containsKeyHashLookup(); - hashLookup.startSearch(hash2); - for (int pos; (pos = hashLookup.nextPos()) >= 0; ) { - Bytes entry = entry(offsetFromPos(pos)); - if (keyEquals(keyBytes, keyLen, entry)) - return true; - } - return false; - } finally { - unlock(); - } - } - - IntIntMultiMap containsKeyHashLookup() { - return hashLookup; - } - - /** - * Replaces the specified value for the key with the given value.

{@code newValue} is set only if - * the existing value corresponding to the specified key is equal to {@code expectedValue} or {@code - * expectedValue == null}. - * - * @param hash2 a hash code related to the {@code keyBytes} - * @return the replaced value or {@code null} if the value was not replaced - */ - V replace(Bytes keyBytes, K key, V expectedValue, V newValue, int hash2) { - lock(); - try { - long keyLen = keyBytes.remaining(); - hashLookup.startSearch(hash2); - for (int pos; (pos = hashLookup.nextPos()) >= 0; ) { - long offset = offsetFromPos(pos); - NativeBytes entry = entry(offset); - if (!keyEquals(keyBytes, keyLen, entry)) - continue; - // key is found - entry.skip(keyLen); - return onKeyPresentOnReplace(key, expectedValue, newValue, pos, offset, entry, - hashLookup); - } - // key is not found - return null; - } finally { - unlock(); - } - } - - V onKeyPresentOnReplace(K key, V expectedValue, V newValue, int pos, long offset, - NativeBytes entry, IntIntMultiMap searchedHashLookup) { - long valueLenPos = entry.position(); - long valueLen = readValueLen(entry); - long entryEndAddr = entry.positionAddr() + valueLen; - V valueRead = readValue(entry, null, valueLen); - if (valueRead == null) - return null; - if (expectedValue == null || expectedValue.equals(valueRead)) { - // putValue may relocate entry and change offset - offset = putValue(pos, offset, entry, valueLenPos, entryEndAddr, newValue, - searchedHashLookup); - notifyPut(offset, false, key, newValue, - posFromOffset(offset)); - return valueRead; - } - return null; - } - - - void notifyPut(long offset, boolean added, K key, V value, final long pos) { - if (eventListener != SharedMapEventListeners.NOP) { - tmpBytes.storePositionAndSize(bytes, offset, entrySize); - eventListener.onPut(AbstractVanillaSharedMap.this, tmpBytes, metaDataBytes, - added, key, value, pos, this); - } - } - - void notifyGet(long offset, K key, V value) { - if (eventListener != SharedMapEventListeners.NOP) { - tmpBytes.storePositionAndSize(bytes, offset, entrySize); - eventListener.onGetFound(AbstractVanillaSharedMap.this, tmpBytes, metaDataBytes, - key, value); - } - } - - V notifyMissed(Bytes keyBytes, K key, V usingValue) { - if (eventListener != SharedMapEventListeners.NOP) { - return eventListener.onGetMissing(AbstractVanillaSharedMap.this, keyBytes, - key, usingValue); - } - return null; - } - - void notifyRemoved(long offset, K key, V value, final int pos) { - if (eventListener != SharedMapEventListeners.NOP) { - tmpBytes.storePositionAndSize(bytes, offset, entrySize); - eventListener.onRemove(AbstractVanillaSharedMap.this, tmpBytes, metaDataBytes, - key, value, pos, this); - } - } - - long putValue(int pos, long offset, NativeBytes entry, long valueLenPos, - long entryEndAddr, V value, IntIntMultiMap searchedHashLookup) { - if (value instanceof Byteable) { - return putValue(pos, offset, entry, valueLenPos, entryEndAddr, - null, (Byteable) value, false, searchedHashLookup); - } else { - return putValue(pos, offset, entry, valueLenPos, entryEndAddr, - getValueAsBytes(value), null, true, searchedHashLookup); - } - } - - /** - * Replaces value in existing entry. May cause entry relocation, because there may be not enough space - * for new value in location already allocated for this entry. - * - * @param pos index of the first block occupied by the entry - * @param offset relative offset of the entry in Segment bytes (before, i. e. including - * metaData) - * @param entry relative pointer in Segment bytes - * @param valueLenPos relative position of value "stop bit" in entry - * @param entryEndAddr absolute address of the entry end - * @param valueBytes serialized value, or {@code null} if valueAsByteable is given - * @param valueAsByteable the value to put as {@code Byteable}, or {@code null} if valueBytes is - * given - * @param allowOversize {@code true} if the entry is allowed become oversized if it was not yet - * @return relative offset of the entry in Segment bytes after putting value (that may cause entry - * relocation) - */ - long putValue(int pos, long offset, NativeBytes entry, long valueLenPos, - long entryEndAddr, @Nullable Bytes valueBytes, - @Nullable Byteable valueAsByteable, boolean allowOversize, - IntIntMultiMap searchedHashLookup) { - long valueLenAddr = entry.address() + valueLenPos; - long newValueLen; - if (valueBytes != null) { - newValueLen = valueBytes.remaining(); - } else { - assert valueAsByteable != null; - newValueLen = valueAsByteable.maxSize(); - } - long newValueAddr = alignment.alignAddr( - valueLenAddr + expectedStopBits(newValueLen)); - long newEntryEndAddr = newValueAddr + newValueLen; - // Fast check before counting "sizes in blocks" that include - // integral division - newValueDoesNotFit: - if (newEntryEndAddr != entryEndAddr) { - long entryStartAddr = entryStartAddr(offset); - long oldEntrySize = entryEndAddr - entryStartAddr; - int oldSizeInBlocks = inBlocks(oldEntrySize); - int newSizeInBlocks = inBlocks(newEntryEndAddr - entryStartAddr); - if (newSizeInBlocks > oldSizeInBlocks) { - if (!allowOversize && oldSizeInBlocks == 1) { - // If the value is Byteable, illegal oversize could be detected because of - // too high (inaccurate maxSize() estimate. Marshall the value to get - // the precise size. - if (valueAsByteable != null) { - // noinspection unchecked - return putValue(pos, offset, entry, valueLenPos, entryEndAddr, - getValueAsBytes((V) valueAsByteable), null, false, searchedHashLookup); - } - throw new IllegalArgumentException("Byteable value is not allowed " + - "to make the entry oversized while it was not initially."); - } - if (newSizeInBlocks > MAX_ENTRY_OVERSIZE_FACTOR) { - if (valueAsByteable != null) { - // noinspection unchecked - return putValue(pos, offset, entry, valueLenPos, entryEndAddr, - getValueAsBytes((V) valueAsByteable), null, false, searchedHashLookup); - } - throw new IllegalArgumentException("Value too large: " + - "entry takes " + newSizeInBlocks + " blocks, " + - MAX_ENTRY_OVERSIZE_FACTOR + " is maximum."); - } - if (realloc(pos, oldSizeInBlocks, newSizeInBlocks)) - break newValueDoesNotFit; - // RELOCATION - free(pos, oldSizeInBlocks); - eventListener.onRelocation(pos, this); - int prevPos = pos; - pos = alloc(newSizeInBlocks); - // putValue() is called from put() and replace() - // after successful search by key - replacePosInHashLookupOnRelocation(searchedHashLookup, prevPos, pos); - offset = offsetFromPos(pos); - // Moving metadata, key stop bit and key. - // Don't want to fiddle with pseudo-buffers for this, - // since we already have all absolute addresses. - long newEntryStartAddr = entryStartAddr(offset); - NativeBytes.UNSAFE.copyMemory(entryStartAddr, - newEntryStartAddr, valueLenAddr - entryStartAddr); - entry = entry(offset); - // END OF RELOCATION - } else if (newSizeInBlocks < oldSizeInBlocks) { - // Freeing extra blocks - freeList.clear(pos + newSizeInBlocks, pos + oldSizeInBlocks); - // Do NOT reset nextPosToSearchFrom, because if value - // once was larger it could easily became oversized again, - // But if these blocks will be taken by that time, - // this entry will need to be relocated. - } - } - // Common code for all cases - entry.position(valueLenPos); - entry.writeStopBit(newValueLen); - alignment.alignPositionAddr(entry); - if (valueBytes != null) { - entry.write(valueBytes); - } else { - if (valueAsByteable instanceof BytesMarshallable) { - long posAddr = entry.positionAddr(); - ((BytesMarshallable) valueAsByteable).writeMarshallable(entry); - long actualValueLen = entry.positionAddr() - posAddr; - if (actualValueLen > newValueLen) { - throw new AssertionError( - "Byteable value returned maxSize less than the actual size"); - } - } else { - entry.write(valueAsByteable.bytes(), valueAsByteable.offset(), newValueLen); - } - } - return offset; - } - - void replacePosInHashLookupOnRelocation(IntIntMultiMap searchedHashLookup, int prevPos, int pos) { - searchedHashLookup.replacePrevPos(pos); - } - - void clear() { - lock(); - try { - hashLookup.clear(); - freeList.clear(); - resetSize(); - } finally { - unlock(); - } - } - - void visit(IntIntMultiMap.EntryConsumer entryConsumer) { - hashLookup.forEach(entryConsumer); - } - - public Entry getEntry(long pos) { - long offset = offsetFromPos(pos); - NativeBytes entry = entry(offset); - entry.readStopBit(); - K key = entry.readInstance(kClass, null); //todo: readUsing? - - V value = readValue(entry, null); //todo: reusable container - - //notifyGet(offset - metaDataBytes, key, value); //todo: should we call this? - - return new WriteThroughEntry(key, value); - } - - /** - * Check there is no garbage in freeList. - */ - void checkConsistency() { - lock(); - try { - IntIntMultiMap hashLookup = checkConsistencyHashLookup(); - for (int pos = 0; (pos = (int) freeList.nextSetBit(pos)) >= 0; ) { - PosPresentOnce check = new PosPresentOnce(pos); - hashLookup.forEach(check); - if (check.count != 1) - throw new AssertionError(); - long offset = offsetFromPos(pos); - Bytes entry = entry(offset); - long keyLen = entry.readStopBit(); - entry.skip(keyLen); - afterKeyHookOnCheckConsistency(entry); - long valueLen = entry.readStopBit(); - long sizeInBytes = entrySize(keyLen, valueLen); - int entrySizeInBlocks = inBlocks(sizeInBytes); - if (!freeList.allSet(pos, pos + entrySizeInBlocks)) - throw new AssertionError(); - pos += entrySizeInBlocks; - } - } finally { - unlock(); - } - } - - void afterKeyHookOnCheckConsistency(Bytes entry) { - // no-op - } - - IntIntMultiMap checkConsistencyHashLookup() { - return hashLookup; - } - - - private class PosPresentOnce implements IntIntMultiMap.EntryConsumer { - int pos, count = 0; - - PosPresentOnce(int pos) { - this.pos = pos; - } - - @Override - public void accept(int hash, int pos) { - if (this.pos == pos) count++; - } - } - } - - final class EntryIterator implements Iterator>, IntIntMultiMap.EntryConsumer { - - int segmentIndex = segments.length; - - Entry nextEntry, lastReturned; - - Deque segmentPositions = new ArrayDeque(); //todo: replace with a more efficient, auto resizing int[] - - EntryIterator() { - nextEntry = nextSegmentEntry(); - } - - public boolean hasNext() { - return nextEntry != null; - } - - public void remove() { - if (lastReturned == null) throw new IllegalStateException(); - AbstractVanillaSharedMap.this.remove(lastReturned.getKey()); - lastReturned = null; - } - - public Map.Entry next() { - Entry e = nextEntry; - if (e == null) - throw new NoSuchElementException(); - lastReturned = e; // cannot assign until after null check - nextEntry = nextSegmentEntry(); - return e; - } - - - Entry nextSegmentEntry() { - while (segmentIndex >= 0) { - if (segmentPositions.isEmpty()) { - switchToNextSegment(); - } else { - - final Segment segment = segments[segmentIndex]; - segment.lock(); - try { - while (!segmentPositions.isEmpty()) { - final Entry entry = (Entry) segment.getEntry(segmentPositions.removeFirst()); - if (entry != null) { - return entry; - } - } - } finally { - segment.unlock(); - } - } - } - return null; - } - - private void switchToNextSegment() { - segmentPositions.clear(); - segmentIndex--; - if (segmentIndex >= 0) { - - final Segment segment = segments[segmentIndex]; - segment.lock(); - try { - segment.visit(this); - } finally { - segment.unlock(); - } - } - } - - @Override - public void accept(int key, int value) { - segmentPositions.add(value); - } - } - - final class EntrySet extends AbstractSet> { - public Iterator> iterator() { - return new EntryIterator(); - } - - public boolean contains(Object o) { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry e = (Map.Entry) o; - try { - V v = AbstractVanillaSharedMap.this.get(e.getKey()); - return v != null && v.equals(e.getValue()); - } catch (ClassCastException ex) { - return false; - } catch (NullPointerException ex) { - return false; - } - } - - public boolean remove(Object o) { - if (!(o instanceof Map.Entry)) - return false; - Map.Entry e = (Map.Entry) o; - try { - Object key = e.getKey(); - Object value = e.getValue(); - return AbstractVanillaSharedMap.this.remove(key, value); - } catch (ClassCastException ex) { - return false; - } catch (NullPointerException ex) { - return false; - } - } - - public int size() { - return AbstractVanillaSharedMap.this.size(); - } - - public boolean isEmpty() { - return AbstractVanillaSharedMap.this.isEmpty(); - } - - public void clear() { - AbstractVanillaSharedMap.this.clear(); - } - } - - final class WriteThroughEntry extends SimpleEntry { - - WriteThroughEntry(K key, V value) { - super(key, value); - } - - @Override - public V setValue(V value) { - put(getKey(), value); - return super.setValue(value); - } - } -} diff --git a/src/main/java/net/openhft/collections/VanillaSharedReplicatedMap.java b/src/main/java/net/openhft/collections/VanillaSharedReplicatedMap.java index 9bf8a57d8..c8ad1e042 100644 --- a/src/main/java/net/openhft/collections/VanillaSharedReplicatedMap.java +++ b/src/main/java/net/openhft/collections/VanillaSharedReplicatedMap.java @@ -18,6 +18,7 @@ package net.openhft.collections; +import net.openhft.chronicle.map.ChronicleMap; import net.openhft.lang.Maths; import net.openhft.lang.collection.ATSDirectBitSet; import net.openhft.lang.io.*; @@ -79,8 +80,8 @@ import static net.openhft.lang.collection.DirectBitSet.NOT_FOUND; * @param the entries key type * @param the entries value type */ -class VanillaSharedReplicatedMap extends AbstractVanillaSharedMap - implements SharedHashMap, ReplicaExternalizable, EntryResolver, +class VanillaSharedReplicatedMap extends AbstractVanillaSharedHashMap + implements ChronicleMap, ReplicaExternalizable, EntryResolver, Closeable { static final int MAX_UNSIGNED_SHORT = Character.MAX_VALUE; @@ -129,7 +130,7 @@ class VanillaSharedReplicatedMap extends AbstractVanillaSharedMap } @Override - VanillaSharedMap.Segment createSegment(NativeBytes bytes, int index) { + VanillaSharedHashMap.Segment createSegment(NativeBytes bytes, int index) { return new Segment(bytes, index); } @@ -374,7 +375,7 @@ class VanillaSharedReplicatedMap extends AbstractVanillaSharedMap } - class Segment extends VanillaSharedMap.Segment { + class Segment extends VanillaSharedHashMap.Segment { private volatile IntIntMultiMap hashLookupLiveAndDeleted; private volatile IntIntMultiMap hashLookupLiveOnly; @@ -408,7 +409,7 @@ class VanillaSharedReplicatedMap extends AbstractVanillaSharedMap /** - * @see VanillaSharedMap.Segment#acquire(net.openhft.lang.io.Bytes, Object, Object, int, boolean) + * @see net.openhft.collections.VanillaSharedHashMap.Segment#acquire(net.openhft.lang.io.Bytes, Object, Object, int, boolean) */ V acquire(Bytes keyBytes, K key, V usingValue, int hash2, boolean create, long timestamp) { lock(); @@ -691,7 +692,7 @@ class VanillaSharedReplicatedMap extends AbstractVanillaSharedMap * put) this method is called only from usual put or acquire * @param searchedHashLookup the hash lookup that used to find the entry based on the key * @return offset of the written entry in the Segment bytes - * @see VanillaSharedMap.Segment#putEntry(net.openhft.lang.io.Bytes, Object, boolean) + * @see net.openhft.collections.VanillaSharedHashMap.Segment#putEntry(net.openhft.lang.io.Bytes, Object, boolean) */ private long putEntry(Bytes keyBytes, int hash2, V value, boolean usingValue, final int identifier, final long timestamp, @@ -739,7 +740,7 @@ class VanillaSharedReplicatedMap extends AbstractVanillaSharedMap } /** - * @see VanillaSharedMap.Segment#remove(net.openhft.lang.io.Bytes, Object, Object, int) + * @see net.openhft.collections.VanillaSharedHashMap.Segment#remove(net.openhft.lang.io.Bytes, Object, Object, int) */ public V remove(Bytes keyBytes, K key, V expectedValue, int hash2, final long timestamp, final byte identifier) { @@ -810,7 +811,7 @@ class VanillaSharedReplicatedMap extends AbstractVanillaSharedMap } /** - * @see VanillaSharedMap.Segment#remove(net.openhft.lang.io.Bytes, Object, Object, int) + * @see net.openhft.collections.VanillaSharedHashMap.Segment#remove(net.openhft.lang.io.Bytes, Object, Object, int) */ public V replace(Bytes keyBytes, K key, V expectedValue, V newValue, int hash2, long timestamp) { diff --git a/src/test/java/net/openhft/chronicle/map/ChronicleMap8Test.java b/src/test/java/net/openhft/chronicle/map/ChronicleMap8Test.java deleted file mode 100644 index 5158a878d..000000000 --- a/src/test/java/net/openhft/chronicle/map/ChronicleMap8Test.java +++ /dev/null @@ -1,1342 +0,0 @@ -package net.openhft.chronicle.map;/* - * Copyright 2014 Higher Frequency Trading - *

- * http://www.higherfrequencytrading.com - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * Written by Doug Lea with assistance from members of JCP JSR-166 - * Expert Group and released to the public domain, as explained at - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -/*import junit.framework.Test; -import junit.framework.TestSuite;*/ - -import net.openhft.collections.KeySetView; -import net.openhft.collections.SharedHashMapBuilder; -import net.openhft.collections.jrs166.JSR166TestCase; -import org.junit.Test; - -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.LongAdder; -import java.util.function.*; - -import static java.util.Spliterator.*; -import static org.junit.Assert.*; - -public class ChronicleMap8Test extends JSR166TestCase { - - private static final double EPSILON = 1E-5; - - - private static ChronicleMap newLongMap() { - //First create (or access if already created) the shared map - - final SharedHashMapBuilder builder = SharedHashMapBuilder.of(Long.class, Long.class); - - - //// don't include this, just to check it is as expected. - assertEquals(8, builder.minSegments()); - //// end of test - - String shmPath = System.getProperty("java.io.tmpdir") + System.getProperty("file.separator") + "SHMTest" + System.nanoTime(); - try { - return builder.file(new File(shmPath)).create(); - } catch (IOException e) { - throw new IllegalStateException(e); - } - - - // return new ChronicleMap(5); - - } - - private static ChronicleMap newIntegerStringMap() { - - - final SharedHashMapBuilder builder = SharedHashMapBuilder.of(Integer.class, - String.class); - - - //// don't include this, just to check it is as expected. - assertEquals(8, builder.minSegments()); - //// end of test - - String shmPath = System.getProperty("java.io.tmpdir") + System.getProperty("file.separator") + - "SHMTest" + System.nanoTime(); - try { - return builder.file(new File(shmPath)).create(); - } catch (IOException e) { - throw new IllegalStateException(e); - } - - - } - - /** - * Returns a new map from Integers 1-5 to Strings "A"-"E". - */ - private static ChronicleMap map5() { - ChronicleMap map = newIntegerStringMap(); - assertTrue(map.isEmpty()); - map.put(one, "A"); - map.put(two, "B"); - map.put(three, "C"); - map.put(four, "D"); - map.put(five, "E"); - System.out.println(map.toString()); - final boolean empty = map.isEmpty(); - assertFalse(empty); - assertEquals(5, map.size()); - return map; - } - - - /** - * Returns a new map from Integers 1-5 to Strings "A"-"E". - */ - private static ConcurrentHashMap map5ConcurrentHashMap() { - ConcurrentHashMap map = new ConcurrentHashMap(5); - assertTrue(map.isEmpty()); - map.put(one, "A"); - map.put(two, "B"); - map.put(three, "C"); - map.put(four, "D"); - map.put(five, "E"); - System.out.println(map.toString()); - final boolean empty = map.isEmpty(); - assertFalse(empty); - assertEquals(5, map.size()); - return map; - } - - /** - * getOrDefault returns value if present, else default - */ - @Test - public void testGetOrDefault() { - ChronicleMap map = map5(); - assertEquals(map.getOrDefault(one, "Z"), "A"); - assertEquals(map.getOrDefault(six, "Z"), "Z"); - } - - /** - * computeIfAbsent adds when the given key is not present - */ - @Test - public void testComputeIfAbsent() { - ChronicleMap map = map5(); - map.computeIfAbsent(six, (x) -> "Z"); - assertTrue(map.containsKey(six)); - } - - /** - * computeIfAbsent does not replace if the key is already present - */ - @Test - public void testComputeIfAbsent2() { - ChronicleMap map = map5(); - assertEquals("A", map.computeIfAbsent(one, (x) -> "Z")); - } - - /** - * computeIfAbsent does not add if function returns null - */ - @Test - public void testComputeIfAbsent3() { - ChronicleMap map = map5(); - map.computeIfAbsent(six, (x) -> null); - assertFalse(map.containsKey(six)); - } - - /** - * computeIfPresent does not replace if the key is already present - */ - @Test - public void testComputeIfPresent() { - ChronicleMap map = map5(); - map.computeIfPresent(six, (x, y) -> "Z"); - assertFalse(map.containsKey(six)); - } - - /** - * computeIfPresent adds when the given key is not present - */ - @Test - public void testComputeIfPresent2() { - ChronicleMap map = map5(); - assertEquals("Z", map.computeIfPresent(one, (x, y) -> "Z")); - } - - /** - * compute does not replace if the function returns null - */ - @Test - public void testCompute() { - ChronicleMap map = map5(); - map.compute(six, (x, y) -> null); - assertFalse(map.containsKey(six)); - } - - /** - * compute adds when the given key is not present - */ - @Test - public void testCompute2() { - ChronicleMap map = map5(); - assertEquals("Z", map.compute(six, (x, y) -> "Z")); - } - - /** - * compute replaces when the given key is present - */ - @Test - public void testCompute3() { - ChronicleMap map = map5(); - assertEquals("Z", map.compute(one, (x, y) -> "Z")); - } - - /** - * compute removes when the given key is present and function returns null - */ - @Test - public void testCompute4() { - ChronicleMap map = map5(); - final BiFunction remappingFunction = (x, y) -> { - return null; - }; - map.compute(one, remappingFunction); - assertFalse(map.containsKey(one)); - } - - /** - * merge adds when the given key is not present - */ - @Test - public void testMerge1() { - ChronicleMap map = map5(); - assertEquals("Y", map.merge(six, "Y", (x, y) -> "Z")); - } - - /** - * merge replaces when the given key is present - */ - @Test - public void testMerge2() { - ChronicleMap map = map5(); - assertEquals("Z", map.merge(one, "Y", (x, y) -> "Z")); - } - - /** - * merge removes when the given key is present and function returns null - */ - @Test - public void testMerge3() { - ChronicleMap map = map5(); - map.merge(one, "Y", (x, y) -> null); - assertFalse(map.containsKey(one)); - } - - static Set populatedSet(int n) { - Set a = ConcurrentHashMap.newKeySet(); - assertTrue(a.isEmpty()); - for (int i = 0; i < n; i++) - a.add(i); - assertFalse(a.isEmpty()); - assertEquals(n, a.size()); - return a; - } - - static Set populatedSet(Integer[] elements) { - Set a = ConcurrentHashMap.newKeySet(); - assertTrue(a.isEmpty()); - for (int i = 0; i < elements.length; i++) - a.add(elements[i]); - assertFalse(a.isEmpty()); - assertEquals(elements.length, a.size()); - return a; - } - - /* - * replaceAll replaces all matching values. - */ - @Test - public void testReplaceAll() { - ChronicleMap map = map5(); - - final BiFunction function = (x, y) -> { - return x > 3 ? "Z" : y; - }; - - map.replaceAll(function); - assertEquals("A", map.get(one)); - assertEquals("B", map.get(two)); - assertEquals("C", map.get(three)); - assertEquals("Z", map.get(four)); - assertEquals("Z", map.get(five)); - } - - /** - * Default-constructed set is empty - */ - @Test - public void testNewKeySet() { - Set a = ConcurrentHashMap.newKeySet(); - assertTrue(a.isEmpty()); - } - - /** - * keySet.add adds the key with the established value to the map; remove removes it. - */ - @Test - public void testKeySetAddRemove() { - ChronicleMap map = map5(); - final KeySetView set1 = map.keySet(); - final KeySetView set2 = map.keySet("true"); - set2.add(six); - assertTrue(set2.getMap() == map); - assertTrue(set1.getMap() == map); - assertEquals(set2.size(), map.size()); - assertEquals(set1.size(), map.size()); - assertTrue((Boolean.parseBoolean(map.get(six)))); - assertTrue(set1.contains(six)); - assertTrue(set2.contains(six)); - set2.remove(six); - assertNull(map.get(six)); - assertFalse(set1.contains(six)); - assertFalse(set2.contains(six)); - } - - - /** - * keySet.addAll adds each element from the given collection - */ - @Test - public void testAddAll() { - Set full = populatedSet(3); - Vector v = new Vector(); - v.add(three); - v.add(four); - v.add(five); - full.addAll(v); - assertEquals(6, full.size()); - } - - /** - * keySet.addAll adds each element from the given collection that did not already exist in the set - */ - @Test - public void testAddAll2() { - Set full = populatedSet(3); - Vector v = new Vector(); - v.add(three); - v.add(four); - v.add(one); // will not add this element - full.addAll(v); - assertEquals(5, full.size()); - } - - /** - * keySet.add will not add the element if it already exists in the set - */ - @Test - public void testAdd2() { - Set full = populatedSet(3); - full.add(one); - assertEquals(3, full.size()); - } - - /** - * keySet.add adds the element when it does not exist in the set - */ - @Test - public void testAdd3() { - Set full = populatedSet(3); - full.add(three); - assertTrue(full.contains(three)); - } - - /** - * keySet.add throws UnsupportedOperationException if no default mapped value - */ - @Test - public void testAdd4() { - Set full = map5().keySet(); - try { - full.add(three); - shouldThrow(); - } catch (UnsupportedOperationException e) { - } - } - - /** - * keySet.add throws NullPointerException if the specified key is null - */ - @Test - public void testAdd5() { - Set full = populatedSet(3); - try { - full.add(null); - shouldThrow(); - } catch (NullPointerException e) { - } - } - - - /** - * KeySetView.getMappedValue returns the map's mapped value - */ - @Test - public void testGetMappedValue() { - ChronicleMap map = map5(); - assertNull(map.keySet().getMappedValue()); - try { - map.keySet(null); - shouldThrow(); - } catch (NullPointerException e) { - } - - final net.openhft.collections.KeySetView set = map.keySet("A"); - - set.add(one); - set.add(six); - set.add(seven); - assertTrue(set.getMappedValue().equals("A")); - assertTrue(map.get(one).equals("A")); - assertTrue(map.get(six).equals("A")); - assertTrue(map.get(seven).equals("A")); - } - - - /* @Test - public void testConcurrentHashMapGetMappedValue() { - ConcurrentHashMap map = map5ConcurrentHashMap(); - assertNull(map.keySet().getMappedValue()); - try { - map.keySet(null); - shouldThrow(); - } catch (NullPointerException e) { - } - - final ConcurrentHashMap.KeySetView set = map.keySet("A"); - - set.add(one); - set.add(six); - set.add(seven); - assertTrue(set.getMappedValue() == "A"); - assertTrue(map.get(one) == "A"); - assertTrue(map.get(six) == "A"); - assertTrue(map.get(seven) == "A"); - } - */ - void checkSpliteratorCharacteristics(Spliterator sp, - int requiredCharacteristics) { - assertEquals(requiredCharacteristics, - requiredCharacteristics & sp.characteristics()); - } - - /** - * KeySetView.spliterator returns spliterator over the elements in this set - */ - @Test - public void testKeySetSpliterator() { - LongAdder adder = new LongAdder(); - ChronicleMap map = map5(); - Set set = map.keySet(); - Spliterator sp = set.spliterator(); - checkSpliteratorCharacteristics(sp, CONCURRENT | DISTINCT | NONNULL); - assertEquals(sp.estimateSize(), map.size()); - Spliterator sp2 = sp.trySplit(); - sp.forEachRemaining((Integer x) -> adder.add(x.longValue())); - long v = adder.sumThenReset(); - sp2.forEachRemaining((Integer x) -> adder.add(x.longValue())); - long v2 = adder.sum(); - assertEquals(v + v2, 15); - } - - /** - * keyset.clear removes all elements from the set - */ - @Test - public void testClear() { - Set full = populatedSet(3); - full.clear(); - assertEquals(0, full.size()); - } - - /** - * keyset.contains returns true for added elements - */ - @Test - public void testContains() { - Set full = populatedSet(3); - assertTrue(full.contains(one)); - assertFalse(full.contains(five)); - } - - /** - * KeySets with equal elements are equal - */ - @Test - public void testEquals() { - Set a = populatedSet(3); - Set b = populatedSet(3); - assertTrue(a.equals(b)); - assertTrue(b.equals(a)); - assertEquals(a.hashCode(), b.hashCode()); - a.add(m1); - assertFalse(a.equals(b)); - assertFalse(b.equals(a)); - b.add(m1); - assertTrue(a.equals(b)); - assertTrue(b.equals(a)); - assertEquals(a.hashCode(), b.hashCode()); - } - - /** - * KeySet.containsAll returns true for collections with subset of elements - */ - @Test - public void testContainsAll() { - Set full = populatedSet(3); - Vector v = new Vector(); - v.add(one); - v.add(two); - assertTrue(full.containsAll(v)); - v.add(six); - assertFalse(full.containsAll(v)); - } - - /** - * KeySet.isEmpty is true when empty, else false - */ - @Test - public void testIsEmpty() { - Set empty = ConcurrentHashMap.newKeySet(); - Set full = populatedSet(3); - assertTrue(empty.isEmpty()); - assertFalse(full.isEmpty()); - } - - /** - * KeySet.iterator() returns an iterator containing the elements of the set - */ - @Test - public void testIterator() { - Collection empty = ConcurrentHashMap.newKeySet(); - int size = 20; - assertFalse(empty.iterator().hasNext()); - try { - empty.iterator().next(); - shouldThrow(); - } catch (NoSuchElementException success) { - } - - Integer[] elements = new Integer[size]; - for (int i = 0; i < size; i++) - elements[i] = i; - Collections.shuffle(Arrays.asList(elements)); - Collection full = populatedSet(elements); - - Iterator it = full.iterator(); - for (int j = 0; j < size; j++) { - assertTrue(it.hasNext()); - it.next(); - } - assertFalse(it.hasNext()); - try { - it.next(); - shouldThrow(); - } catch (NoSuchElementException success) { - } - } - - /** - * KeySet.iterator.remove removes current element - */ - @Test - public void testIteratorRemove() { - Set q = populatedSet(3); - Iterator it = q.iterator(); - Object removed = it.next(); - it.remove(); - - it = q.iterator(); - assertFalse(it.next().equals(removed)); - assertFalse(it.next().equals(removed)); - assertFalse(it.hasNext()); - } - - /** - * KeySet.toString holds toString of elements - */ - @Test - public void testToString() { - assertEquals("[]", ConcurrentHashMap.newKeySet().toString()); - Set full = populatedSet(3); - String s = full.toString(); - for (int i = 0; i < 3; ++i) - assertTrue(s.contains(String.valueOf(i))); - } - - /** - * KeySet.removeAll removes all elements from the given collection - */ - @Test - public void testRemoveAll() { - Set full = populatedSet(3); - Vector v = new Vector(); - v.add(one); - v.add(two); - full.removeAll(v); - assertEquals(1, full.size()); - } - - /** - * KeySet.remove removes an element - */ - @Test - public void testRemove() { - Set full = populatedSet(3); - full.remove(one); - assertFalse(full.contains(one)); - assertEquals(2, full.size()); - } - - /** - * keySet.size returns the number of elements - */ - @Test - public void testSize() { - Set empty = ConcurrentHashMap.newKeySet(); - Set full = populatedSet(3); - assertEquals(3, full.size()); - assertEquals(0, empty.size()); - } - - /** - * KeySet.toArray() returns an Object array containing all elements from the set - */ - @Test - public void testToArray() { - Object[] a = ConcurrentHashMap.newKeySet().toArray(); - assertTrue(Arrays.equals(new Object[0], a)); - assertSame(Object[].class, a.getClass()); - int size = 20; - Integer[] elements = new Integer[size]; - for (int i = 0; i < size; i++) - elements[i] = i; - Collections.shuffle(Arrays.asList(elements)); - Collection full = populatedSet(elements); - - assertTrue(Arrays.asList(elements).containsAll(Arrays.asList(full.toArray()))); - assertTrue(full.containsAll(Arrays.asList(full.toArray()))); - assertSame(Object[].class, full.toArray().getClass()); - } - - /** - * toArray(Integer array) returns an Integer array containing all elements from the set - */ - @Test - public void testToArray2() { - Collection empty = ConcurrentHashMap.newKeySet(); - Integer[] a; - int size = 20; - - a = new Integer[0]; - assertSame(a, empty.toArray(a)); - - a = new Integer[size / 2]; - Arrays.fill(a, 42); - assertSame(a, empty.toArray(a)); - assertNull(a[0]); - for (int i = 1; i < a.length; i++) - assertEquals(42, (int) a[i]); - - Integer[] elements = new Integer[size]; - for (int i = 0; i < size; i++) - elements[i] = i; - Collections.shuffle(Arrays.asList(elements)); - Collection full = populatedSet(elements); - - Arrays.fill(a, 42); - assertTrue(Arrays.asList(elements).containsAll(Arrays.asList(full.toArray(a)))); - for (int i = 0; i < a.length; i++) - assertEquals(42, (int) a[i]); - assertSame(Integer[].class, full.toArray(a).getClass()); - - a = new Integer[size]; - Arrays.fill(a, 42); - assertSame(a, full.toArray(a)); - assertTrue(Arrays.asList(elements).containsAll(Arrays.asList(full.toArray(a)))); - } - - /** - * A deserialized serialized set is equal - */ - @Test - public void testSerialization() throws Exception { - int size = 20; - Set x = populatedSet(size); - Set y = serialClone(x); - - assertNotSame(x, y); - assertEquals(x.size(), y.size()); - assertEquals(x, y); - assertEquals(y, x); - } - - static final int SIZE = 10000; - static ChronicleMap longMap; - - static ChronicleMap longMap() { - if (longMap == null) { - longMap = newLongMap(); - for (int i = 0; i < SIZE; ++i) - longMap.put(Long.valueOf(i), Long.valueOf(2 * i)); - } - - return longMap; - } - - - // explicit function class to avoid type inference problems - static class AddKeys implements BiFunction, Map.Entry, Map.Entry> { - public Map.Entry apply(Map.Entry x, Map.Entry y) { - return new AbstractMap.SimpleEntry - (Long.valueOf(x.getKey().longValue() + y.getKey().longValue()), - Long.valueOf(1L)); - } - } - - /** - * forEachKeySequentially traverses all keys - */ - @Test - public void testForEachKeySequentially() { - LongAdder adder = new LongAdder(); - ChronicleMap m = longMap(); - final Consumer consumer = (Long x) -> adder.add(x.longValue()); - m.forEachKey(Long.MAX_VALUE, consumer); - assertEquals(adder.sum(), SIZE * (SIZE - 1) / 2); - } - - /** - * forEachValueSequentially traverses all values - */ - @Test - public void testForEachValueSequentially() { - LongAdder adder = new LongAdder(); - ChronicleMap m = (ChronicleMap) longMap(); - final Consumer action = (Long x) -> adder.add(x.longValue()); - m.forEachValue(Long.MAX_VALUE, action); - assertEquals(adder.sum(), SIZE * (SIZE - 1)); - } - - /** - * forEachSequentially traverses all mappings - */ - @Test - public void testForEachSequentially() { - LongAdder adder = new LongAdder(); - ChronicleMap m = longMap(); - final BiConsumer action = (Long x, Long y) -> adder.add(x.longValue() + y.longValue()); - m.forEach(Long.MAX_VALUE, action); - assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); - } - - /** - * forEachEntrySequentially traverses all entries - */ - @Test - public void testForEachEntrySequentially() { - LongAdder adder = new LongAdder(); - ChronicleMap m = longMap(); - final Consumer> action = (Map.Entry e) -> adder.add(e.getKey().longValue() + e.getValue().longValue()); - m.forEachEntry(Long.MAX_VALUE, action); - assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); - } - - /** - * forEachKeyInParallel traverses all keys - */ - @Test - public void testForEachKeyInParallel() { - LongAdder adder = new LongAdder(); - ChronicleMap m = longMap(); - final Consumer action = (Long x) -> adder.add(x.longValue()); - - m.forEachKey(1L, action); - assertEquals(adder.sum(), SIZE * (SIZE - 1) / 2); - } - - /** - * forEachValueInParallel traverses all values - */ - @Test - public void testForEachValueInParallel() { - LongAdder adder = new LongAdder(); - ChronicleMap m = longMap(); - final Consumer action = (Long x) -> adder.add(x.longValue()); - m.forEachValue(1L, action); - assertEquals(adder.sum(), SIZE * (SIZE - 1)); - } - - /** - * forEachInParallel traverses all mappings - */ - @Test - public void testForEachInParallel() { - LongAdder adder = new LongAdder(); - ChronicleMap m = longMap(); - final BiConsumer action = (Long x, Long y) -> adder.add(x.longValue() + y.longValue()); - m.forEach(1L, action); - assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); - } - - /** - * forEachEntryInParallel traverses all entries - */ - @Test - public void testForEachEntryInParallel() { - LongAdder adder = new LongAdder(); - ChronicleMap m = longMap(); - final Consumer> action = (Map.Entry e) -> adder.add(e.getKey().longValue() + e.getValue().longValue()); - m.forEachEntry(1L, action); - assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); - } - - /** - * Mapped forEachKeySequentially traverses the given transformations of all keys - */ - @Test - public void testMappedForEachKeySequentially() { - LongAdder adder = new LongAdder(); - ChronicleMap m = longMap(); - final Function transformer = (Long x) -> Long.valueOf(4 * x.longValue()); - final Consumer action = (Long x) -> adder.add(x.longValue()); - m.forEachKey(Long.MAX_VALUE, transformer, - action); - assertEquals(adder.sum(), 4 * SIZE * (SIZE - 1) / 2); - } - - /** - * Mapped forEachValueSequentially traverses the given transformations of all values - */ - @Test - public void testMappedForEachValueSequentially() { - LongAdder adder = new LongAdder(); - ChronicleMap m = longMap(); - final Function transformer = (Long x) -> Long.valueOf(4 * x.longValue()); - final Consumer action = (Long x) -> adder.add(x.longValue()); - m.forEachValue(Long.MAX_VALUE, transformer, - action); - assertEquals(adder.sum(), 4 * SIZE * (SIZE - 1)); - } - - /** - * Mapped forEachSequentially traverses the given transformations of all mappings - */ - @Test - public void testMappedForEachSequentially() { - LongAdder adder = new LongAdder(); - ChronicleMap m = longMap(); - final BiFunction transformer = (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()); - final Consumer action = (Long x) -> adder.add(x.longValue()); - m.forEach(Long.MAX_VALUE, transformer, - action); - assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); - } - - /** - * Mapped forEachEntrySequentially traverses the given transformations of all entries - */ - @Test - public void testMappedForEachEntrySequentially() { - LongAdder adder = new LongAdder(); - ChronicleMap m = longMap(); - final Function, Long> transformer = (Map.Entry e) -> Long.valueOf(e.getKey().longValue() + e.getValue().longValue()); - final Consumer action = (Long x) -> adder.add(x.longValue()); - m.forEachEntry(Long.MAX_VALUE, transformer, - action); - assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); - } - - /** - * Mapped forEachKeyInParallel traverses the given transformations of all keys - */ - @Test - public void testMappedForEachKeyInParallel() { - LongAdder adder = new LongAdder(); - ChronicleMap m = longMap(); - final Function transformer = (Long x) -> Long.valueOf(4 * x.longValue()); - final Consumer action = (Long x) -> adder.add(x.longValue()); - m.forEachKey(1L, transformer, - action); - assertEquals(adder.sum(), 4 * SIZE * (SIZE - 1) / 2); - } - - /** - * Mapped forEachValueInParallel traverses the given transformations of all values - */ - @Test - public void testMappedForEachValueInParallel() { - LongAdder adder = new LongAdder(); - ChronicleMap m = longMap(); - final Function transformer = (Long x) -> Long.valueOf(4 * x.longValue()); - final Consumer action = (Long x) -> adder.add(x.longValue()); - m.forEachValue(1L, transformer, - action); - assertEquals(adder.sum(), 4 * SIZE * (SIZE - 1)); - } - - /** - * Mapped forEachInParallel traverses the given transformations of all mappings - */ - @Test - public void testMappedForEachInParallel() { - LongAdder adder = new LongAdder(); - ChronicleMap m = longMap(); - final BiFunction transformer = (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()); - final Consumer action = (Long x) -> adder.add(x.longValue()); - m.forEach(1L, transformer, - action); - assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); - } - - /** - * Mapped forEachEntryInParallel traverses the given transformations of all entries - */ - @Test - public void testMappedForEachEntryInParallel() { - LongAdder adder = new LongAdder(); - ChronicleMap m = longMap(); - final Function, Long> transformer = (Map.Entry e) -> Long.valueOf(e.getKey().longValue() + e.getValue().longValue()); - final Consumer action = (Long x) -> adder.add(x.longValue()); - m.forEachEntry(1L, transformer, - action); - assertEquals(adder.sum(), 3 * SIZE * (SIZE - 1) / 2); - } - - /** - * reduceKeysSequentially accumulates across all keys, - */ - @Test - public void testReduceKeysSequentially() { - ChronicleMap m = longMap(); - Long r; - final BiFunction reducer = (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()); - r = m.reduceKeys(Long.MAX_VALUE, reducer); - assertEquals((long) r, (long) SIZE * (SIZE - 1) / 2); - } - - /** - * reduceValuesSequentially accumulates across all values - */ - @Test - public void testReduceValuesSequentially() { - ChronicleMap m = longMap(); - Long r; - final BiFunction reducer = (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()); - r = m.reduceKeys(Long.MAX_VALUE, reducer); - assertEquals((long) r, (long) SIZE * (SIZE - 1) / 2); - } - - /** - * reduceEntriesSequentially accumulates across all entries - */ - @Test - public void testReduceEntriesSequentially() { - ChronicleMap m = longMap(); - Map.Entry r; - final AddKeys reducer = new AddKeys(); - r = m.reduceEntries(Long.MAX_VALUE, reducer); - assertEquals(r.getKey().longValue(), (long) SIZE * (SIZE - 1) / 2); - } - - /** - * reduceKeysInParallel accumulates across all keys - */ - @Test - public void testReduceKeysInParallel() { - ChronicleMap m = longMap(); - Long r; - final BiFunction reducer = (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()); - r = m.reduceKeys(1L, reducer); - assertEquals((long) r, (long) SIZE * (SIZE - 1) / 2); - } - - /** - * reduceValuesInParallel accumulates across all values - */ - @Test - public void testReduceValuesInParallel() { - ChronicleMap m = longMap(); - Long r; - final BiFunction reducer = (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()); - r = m.reduceValues(1L, reducer); - assertEquals((long) r, (long) SIZE * (SIZE - 1)); - } - - /** - * reduceEntriesInParallel accumulate across all entries - */ - @Test - public void testReduceEntriesInParallel() { - ChronicleMap m = longMap(); - Map.Entry r; - r = m.reduceEntries(1L, new AddKeys()); - assertEquals(r.getKey().longValue(), (long) SIZE * (SIZE - 1) / 2); - } - - /** - * Mapped reduceKeysSequentially accumulates mapped keys - */ - @Test - public void testMapReduceKeysSequentially() { - ChronicleMap m = longMap(); - final Function transformer = (Long x) -> Long.valueOf(4 * x.longValue()); - final BiFunction reducer = (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()); - Long r = m.reduceKeys(Long.MAX_VALUE, transformer, - reducer); - assertEquals((long) r, (long) 4 * SIZE * (SIZE - 1) / 2); - } - - /** - * Mapped reduceValuesSequentially accumulates mapped values - */ - @Test - public void testMapReduceValuesSequentially() { - ChronicleMap m = longMap(); - final Function transformer = (Long x) -> Long.valueOf(4 * x.longValue()); - final BiFunction reducer = (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()); - Long r = m.reduceValues(Long.MAX_VALUE, transformer, - reducer); - assertEquals((long) r, (long) 4 * SIZE * (SIZE - 1)); - } - - /** - * reduceSequentially accumulates across all transformed mappings - */ - @Test - public void testMappedReduceSequentially() { - ChronicleMap m = longMap(); - final BiFunction transformer = (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()); - final BiFunction reducer = (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()); - Long r = m.reduce(Long.MAX_VALUE, transformer, - reducer); - - assertEquals((long) r, (long) 3 * SIZE * (SIZE - 1) / 2); - } - - /** - * Mapped reduceKeysInParallel, accumulates mapped keys - */ - @Test - public void testMapReduceKeysInParallel() { - ChronicleMap m = longMap(); - final Function transformer = (Long x) -> Long.valueOf(4 * x.longValue()); - final BiFunction reducer = (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()); - Long r = m.reduceKeys(1L, transformer, - reducer); - assertEquals((long) r, (long) 4 * SIZE * (SIZE - 1) / 2); - } - - /** - * Mapped reduceValuesInParallel accumulates mapped values - */ - @Test - public void testMapReduceValuesInParallel() { - ChronicleMap m = longMap(); - final Function transformer = (Long x) -> Long.valueOf(4 * x.longValue()); - final BiFunction reducer = (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()); - Long r = m.reduceValues(1L, transformer, - reducer); - assertEquals((long) r, (long) 4 * SIZE * (SIZE - 1)); - } - - /** - * reduceInParallel accumulate across all transformed mappings - */ - @Test - public void testMappedReduceInParallel() { - ChronicleMap m = longMap(); - Long r; - final BiFunction transformer = (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()); - final BiFunction reducer = (Long x, Long y) -> Long.valueOf(x.longValue() + y.longValue()); - r = m.reduce(1L, transformer, - reducer); - assertEquals((long) r, (long) 3 * SIZE * (SIZE - 1) / 2); - } - - /** - * reduceKeysToLongSequentially accumulates mapped keys - */ - @Test - public void testReduceKeysToLongSequentially() { - ChronicleMap m = longMap(); - final ToLongFunction transformer = (Long x) -> x.longValue(); - final LongBinaryOperator sum = Long::sum; - long lr = m.reduceKeysToLong(Long.MAX_VALUE, transformer, 0L, sum); - assertEquals(lr, (long) SIZE * (SIZE - 1) / 2); - } - - /** - * reduceKeysToIntSequentially accumulates mapped keys - */ - @Test - public void testReduceKeysToIntSequentially() { - ChronicleMap m = longMap(); - final ToIntFunction transformer = (Long x) -> x.intValue(); - final IntBinaryOperator sum = Integer::sum; - int ir = m.reduceKeysToInt(Long.MAX_VALUE, transformer, 0, sum); - assertEquals(ir, SIZE * (SIZE - 1) / 2); - } - - /** - * reduceKeysToDoubleSequentially accumulates mapped keys - */ - @Test - public void testReduceKeysToDoubleSequentially() { - ChronicleMap m = longMap(); - final ToDoubleFunction transformer = (Long x) -> x.doubleValue(); - double dr = m.reduceKeysToDouble(Long.MAX_VALUE, transformer, 0.0, Double::sum); - assertEquals(dr, (double) SIZE * (SIZE - 1) / 2, EPSILON); - } - - /** - * reduceValuesToLongSequentially accumulates mapped values - */ - @Test - public void testReduceValuesToLongSequentially() { - ChronicleMap m = longMap(); - final ToLongFunction transformer = (Long x) -> x.longValue(); - long lr = m.reduceValuesToLong(Long.MAX_VALUE, transformer, 0L, Long::sum); - assertEquals(lr, (long) SIZE * (SIZE - 1)); - } - - /** - * reduceValuesToIntSequentially accumulates mapped values - */ - @Test - public void testReduceValuesToIntSequentially() { - ChronicleMap m = longMap(); - final ToIntFunction transformer = (Long x) -> x.intValue(); - int ir = m.reduceValuesToInt(Long.MAX_VALUE, transformer, 0, Integer::sum); - assertEquals(ir, SIZE * (SIZE - 1)); - } - - /** - * reduceValuesToDoubleSequentially accumulates mapped values - */ - @Test - public void testReduceValuesToDoubleSequentially() { - ChronicleMap m = longMap(); - final ToDoubleFunction transformer = (Long x) -> x.doubleValue(); - double dr = m.reduceValuesToDouble(Long.MAX_VALUE, transformer, 0.0, Double::sum); - assertEquals(dr, (double) SIZE * (SIZE - 1), EPSILON); - } - - /** - * reduceKeysToLongInParallel accumulates mapped keys - */ - @Test - public void testReduceKeysToLongInParallel() { - ChronicleMap m = longMap(); - final ToLongFunction transformer = (Long x) -> x.longValue(); - long lr = m.reduceKeysToLong(1L, transformer, 0L, Long::sum); - assertEquals(lr, (long) SIZE * (SIZE - 1) / 2); - } - - /** - * reduceKeysToIntInParallel accumulates mapped keys - */ - @Test - public void testReduceKeysToIntInParallel() { - ChronicleMap m = longMap(); - final ToIntFunction transformer = (Long x) -> x.intValue(); - int ir = m.reduceKeysToInt(1L, transformer, 0, Integer::sum); - assertEquals(ir, SIZE * (SIZE - 1) / 2); - } - - /** - * reduceKeysToDoubleInParallel accumulates mapped values - */ - @Test - public void testReduceKeysToDoubleInParallel() { - ChronicleMap m = longMap(); - final ToDoubleFunction transformer = (Long x) -> x.doubleValue(); - double dr = m.reduceKeysToDouble(1L, transformer, 0.0, Double::sum); - assertEquals(dr, (double) SIZE * (SIZE - 1) / 2, 0.01); - } - - /** - * reduceValuesToLongInParallel accumulates mapped values - */ - @Test - public void testReduceValuesToLongInParallel() { - ChronicleMap m = longMap(); - final ToLongFunction transformer = (Long x) -> x.longValue(); - long lr = m.reduceValuesToLong(1L, transformer, 0L, Long::sum); - assertEquals(lr, (long) SIZE * (SIZE - 1)); - } - - /** - * reduceValuesToIntInParallel accumulates mapped values - */ - @Test - public void testReduceValuesToIntInParallel() { - ChronicleMap m = longMap(); - final ToIntFunction transformer = (Long x) -> x.intValue(); - int ir = m.reduceValuesToInt(1L, transformer, 0, Integer::sum); - assertEquals(ir, SIZE * (SIZE - 1)); - } - - /** - * reduceValuesToDoubleInParallel accumulates mapped values - */ - @Test - public void testReduceValuesToDoubleInParallel() { - ChronicleMap m = longMap(); - final ToDoubleFunction transformer = (Long x) -> x.doubleValue(); - double dr = m.reduceValuesToDouble(1L, transformer, 0.0, Double::sum); - assertEquals(dr, (double) SIZE * (SIZE - 1), EPSILON); - } - - /** - * searchKeysSequentially returns a non-null result of search function, or null if none - */ - @Test - public void testSearchKeysSequentially() { - ChronicleMap m = longMap(); - Long r; - final Function searchFunction = (Long x) -> x.longValue() == (long) (SIZE / 2) ? x : null; - r = m.searchKeys(Long.MAX_VALUE, searchFunction); - assertEquals((long) r, (long) (SIZE / 2)); - final Function searchFunction1 = (Long x) -> x.longValue() < 0L ? x : null; - r = m.searchKeys(Long.MAX_VALUE, searchFunction1); - assertNull(r); - } - - /** - * searchValuesSequentially returns a non-null result of search function, or null if none - */ - @Test - public void testSearchValuesSequentially() { - ChronicleMap m = longMap(); - Long r; - final Function searchFunction = (Long x) -> x.longValue() == (long) (SIZE / 2) ? x : null; - r = m.searchValues(Long.MAX_VALUE, searchFunction); - assertEquals((long) r, (long) (SIZE / 2)); - final Function searchFunction1 = (Long x) -> x.longValue() < 0L ? x : null; - r = m.searchValues(Long.MAX_VALUE, searchFunction1); - assertNull(r); - } - - /** - * searchSequentially returns a non-null result of search function, or null if none - */ - @Test - public void testSearchSequentially() { - ChronicleMap m = longMap(); - Long r; - final BiFunction searchFunction = (Long x, Long y) -> x.longValue() == (long) (SIZE / 2) ? x : null; - r = m.search(Long.MAX_VALUE, searchFunction); - assertEquals((long) r, (long) (SIZE / 2)); - final BiFunction searchFunction1 = (Long x, Long y) -> x.longValue() < 0L ? x : null; - r = m.search(Long.MAX_VALUE, searchFunction1); - assertNull(r); - } - - /** - * searchEntriesSequentially returns a non-null result of search function, or null if none - */ - @Test - public void testSearchEntriesSequentially() { - ChronicleMap m = longMap(); - Long r; - final Function, Long> searchFunction = (Map.Entry e) -> e.getKey().longValue() == (long) (SIZE / 2) ? e.getKey() : null; - r = m.searchEntries(Long.MAX_VALUE, searchFunction); - assertEquals((long) r, (long) (SIZE / 2)); - final Function, Long> searchFunction1 = (Map.Entry e) -> e.getKey().longValue() < 0L ? e.getKey() : null; - r = m.searchEntries(Long.MAX_VALUE, searchFunction1); - assertNull(r); - } - - /** - * searchKeysInParallel returns a non-null result of search function, or null if none - */ - @Test - public void testSearchKeysInParallel() { - ChronicleMap m = longMap(); - Long r; - final Function searchFunction = (Long x) -> x.longValue() == (long) (SIZE / 2) ? x : null; - r = m.searchKeys(1L, searchFunction); - assertEquals((long) r, (long) (SIZE / 2)); - final Function searchFunction1 = (Long x) -> x.longValue() < 0L ? x : null; - r = m.searchKeys(1L, searchFunction1); - assertNull(r); - } - - /** - * searchValuesInParallel returns a non-null result of search function, or null if none - */ - @Test - public void testSearchValuesInParallel() { - ChronicleMap m = longMap(); - Long r; - final Function searchFunction = (Long x) -> x.longValue() == (long) (SIZE / 2) ? x : null; - r = m.searchValues(1L, searchFunction); - assertEquals((long) r, (long) (SIZE / 2)); - final Function searchFunction1 = (Long x) -> x.longValue() < 0L ? x : null; - r = m.searchValues(1L, searchFunction1); - assertNull(r); - } - - /** - * searchInParallel returns a non-null result of search function, or null if none - */ - @Test - public void testSearchInParallel() { - ChronicleMap m = longMap(); - Long r; - final BiFunction searchFunction = (Long x, Long y) -> x.longValue() == (long) (SIZE / 2) ? x : null; - r = m.search(1L, searchFunction); - assertEquals((long) r, (long) (SIZE / 2)); - final BiFunction searchFunction1 = (Long x, Long y) -> x.longValue() < 0L ? x : null; - r = m.search(1L, searchFunction1); - assertNull(r); - } - - /** - * searchEntriesInParallel returns a non-null result of search function, or null if none - */ - @Test - public void testSearchEntriesInParallel() { - ChronicleMap m = longMap(); - Long r; - final Function, Long> searchFunction = (Map.Entry e) -> e.getKey().longValue() == (long) (SIZE / 2) ? e.getKey() : null; - r = m.searchEntries(1L, searchFunction); - assertEquals((long) r, (long) (SIZE / 2)); - final Function, Long> searchFunction1 = (Map.Entry e) -> e.getKey().longValue() < 0L ? e.getKey() : null; - r = m.searchEntries(1L, searchFunction1); - assertNull(r); - } - -} \ No newline at end of file diff --git a/src/test/java/net/openhft/chronicle/map/ConcurrentMap8Test.java b/src/test/java/net/openhft/chronicle/map/ConcurrentMap8Test.java new file mode 100644 index 000000000..c72a535a5 --- /dev/null +++ b/src/test/java/net/openhft/chronicle/map/ConcurrentMap8Test.java @@ -0,0 +1,632 @@ +package net.openhft.chronicle.map;/* + * Copyright 2014 Higher Frequency Trading + *

+ * http://www.higherfrequencytrading.com + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +/*import junit.framework.Test; +import junit.framework.TestSuite;*/ + +import net.openhft.collections.jrs166.JSR166TestCase; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.BiFunction; + +import static org.junit.Assert.*; + +public class ConcurrentMap8Test extends JSR166TestCase { + + private static final double EPSILON = 1E-5; + + + private static ConcurrentMap newLongMap() { + //First create (or access if already created) the shared map + + final ChronicleMapBuilder builder = ChronicleMapBuilder.of(Long.class, Long.class); + + + //// don't include this, just to check it is as expected. + assertEquals(8, builder.minSegments()); + //// end of test + + String shmPath = System.getProperty("java.io.tmpdir") + System.getProperty("file.separator") + "SHMTest" + System.nanoTime(); + try { + return builder.file(new File(shmPath)).create(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + + + // return new ConcurrentMap(5); + + } + + private static ConcurrentMap newIntegerStringMap() { + + + final ChronicleMapBuilder builder = ChronicleMapBuilder.of(Integer.class, + String.class); + + + //// don't include this, just to check it is as expected. + assertEquals(8, builder.minSegments()); + //// end of test + + String shmPath = System.getProperty("java.io.tmpdir") + System.getProperty("file.separator") + + "SHMTest" + System.nanoTime(); + try { + return builder.file(new File(shmPath)).create(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + + + } + + /** + * Returns a new map from Integers 1-5 to Strings "A"-"E". + */ + private static ConcurrentMap map5() { + ConcurrentMap map = newIntegerStringMap(); + assertTrue(map.isEmpty()); + map.put(one, "A"); + map.put(two, "B"); + map.put(three, "C"); + map.put(four, "D"); + map.put(five, "E"); + System.out.println(map.toString()); + final boolean empty = map.isEmpty(); + assertFalse(empty); + assertEquals(5, map.size()); + return map; + } + + + /** + * Returns a new map from Integers 1-5 to Strings "A"-"E". + */ + private static ConcurrentHashMap map5ConcurrentHashMap() { + ConcurrentHashMap map = new ConcurrentHashMap(5); + assertTrue(map.isEmpty()); + map.put(one, "A"); + map.put(two, "B"); + map.put(three, "C"); + map.put(four, "D"); + map.put(five, "E"); + System.out.println(map.toString()); + final boolean empty = map.isEmpty(); + assertFalse(empty); + assertEquals(5, map.size()); + return map; + } + + /** + * getOrDefault returns value if present, else default + */ + @Test + public void testGetOrDefault() { + ConcurrentMap map = map5(); + assertEquals(map.getOrDefault(one, "Z"), "A"); + assertEquals(map.getOrDefault(six, "Z"), "Z"); + } + + /** + * computeIfAbsent adds when the given key is not present + */ + @Test + public void testComputeIfAbsent() { + ConcurrentMap map = map5(); + map.computeIfAbsent(six, (x) -> "Z"); + assertTrue(map.containsKey(six)); + } + + /** + * computeIfAbsent does not replace if the key is already present + */ + @Test + public void testComputeIfAbsent2() { + ConcurrentMap map = map5(); + assertEquals("A", map.computeIfAbsent(one, (x) -> "Z")); + } + + /** + * computeIfAbsent does not add if function returns null + */ + @Test + public void testComputeIfAbsent3() { + ConcurrentMap map = map5(); + map.computeIfAbsent(six, (x) -> null); + assertFalse(map.containsKey(six)); + } + + /** + * computeIfPresent does not replace if the key is already present + */ + @Test + public void testComputeIfPresent() { + ConcurrentMap map = map5(); + map.computeIfPresent(six, (x, y) -> "Z"); + assertFalse(map.containsKey(six)); + } + + /** + * computeIfPresent adds when the given key is not present + */ + @Test + public void testComputeIfPresent2() { + ConcurrentMap map = map5(); + assertEquals("Z", map.computeIfPresent(one, (x, y) -> "Z")); + } + + /** + * compute does not replace if the function returns null + */ + @Test + public void testCompute() { + ConcurrentMap map = map5(); + map.compute(six, (x, y) -> null); + assertFalse(map.containsKey(six)); + } + + /** + * compute adds when the given key is not present + */ + @Test + public void testCompute2() { + ConcurrentMap map = map5(); + assertEquals("Z", map.compute(six, (x, y) -> "Z")); + } + + /** + * compute replaces when the given key is present + */ + @Test + public void testCompute3() { + ConcurrentMap map = map5(); + assertEquals("Z", map.compute(one, (x, y) -> "Z")); + } + + /** + * compute removes when the given key is present and function returns null + */ + @Test + public void testCompute4() { + ConcurrentMap map = map5(); + final BiFunction remappingFunction = (x, y) -> { + return null; + }; + map.compute(one, remappingFunction); + assertFalse(map.containsKey(one)); + } + + /** + * merge adds when the given key is not present + */ + @Test + public void testMerge1() { + ConcurrentMap map = map5(); + assertEquals("Y", map.merge(six, "Y", (x, y) -> "Z")); + } + + /** + * merge replaces when the given key is present + */ + @Test + public void testMerge2() { + ConcurrentMap map = map5(); + assertEquals("Z", map.merge(one, "Y", (x, y) -> "Z")); + } + + /** + * merge removes when the given key is present and function returns null + */ + @Test + public void testMerge3() { + ConcurrentMap map = map5(); + map.merge(one, "Y", (x, y) -> null); + assertFalse(map.containsKey(one)); + } + + static Set populatedSet(int n) { + Set a = ConcurrentHashMap.newKeySet(); + assertTrue(a.isEmpty()); + for (int i = 0; i < n; i++) + a.add(i); + assertFalse(a.isEmpty()); + assertEquals(n, a.size()); + return a; + } + + static Set populatedSet(Integer[] elements) { + Set a = ConcurrentHashMap.newKeySet(); + assertTrue(a.isEmpty()); + for (int i = 0; i < elements.length; i++) + a.add(elements[i]); + assertFalse(a.isEmpty()); + assertEquals(elements.length, a.size()); + return a; + } + + /* + * replaceAll replaces all matching values. + */ + @Test + public void testReplaceAll() { + ConcurrentMap map = map5(); + + final BiFunction function = (x, y) -> { + return x > 3 ? "Z" : y; + }; + + map.replaceAll(function); + assertEquals("A", map.get(one)); + assertEquals("B", map.get(two)); + assertEquals("C", map.get(three)); + assertEquals("Z", map.get(four)); + assertEquals("Z", map.get(five)); + } + + /** + * Default-constructed set is empty + */ + @Test + public void testNewKeySet() { + Set a = ConcurrentHashMap.newKeySet(); + assertTrue(a.isEmpty()); + } + + + /** + * keySet.addAll adds each element from the given collection + */ + @Test + public void testAddAll() { + Set full = populatedSet(3); + Vector v = new Vector(); + v.add(three); + v.add(four); + v.add(five); + full.addAll(v); + assertEquals(6, full.size()); + } + + /** + * keySet.addAll adds each element from the given collection that did not already exist in the set + */ + @Test + public void testAddAll2() { + Set full = populatedSet(3); + Vector v = new Vector(); + v.add(three); + v.add(four); + v.add(one); // will not add this element + full.addAll(v); + assertEquals(5, full.size()); + } + + /** + * keySet.add will not add the element if it already exists in the set + */ + @Test + public void testAdd2() { + Set full = populatedSet(3); + full.add(one); + assertEquals(3, full.size()); + } + + /** + * keySet.add adds the element when it does not exist in the set + */ + @Test + public void testAdd3() { + Set full = populatedSet(3); + full.add(three); + assertTrue(full.contains(three)); + } + + /** + * keySet.add throws UnsupportedOperationException if no default mapped value + */ + @Test + public void testAdd4() { + Set full = map5().keySet(); + try { + full.add(three); + shouldThrow(); + } catch (UnsupportedOperationException e) { + } + } + + /** + * keySet.add throws NullPointerException if the specified key is null + */ + @Test + public void testAdd5() { + Set full = populatedSet(3); + try { + full.add(null); + shouldThrow(); + } catch (NullPointerException e) { + } + } + + /** + * keyset.clear removes all elements from the set + */ + @Test + public void testClear() { + Set full = populatedSet(3); + full.clear(); + assertEquals(0, full.size()); + } + + /** + * keyset.contains returns true for added elements + */ + @Test + public void testContains() { + Set full = populatedSet(3); + assertTrue(full.contains(one)); + assertFalse(full.contains(five)); + } + + /** + * KeySets with equal elements are equal + */ + @Test + public void testEquals() { + Set a = populatedSet(3); + Set b = populatedSet(3); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + assertEquals(a.hashCode(), b.hashCode()); + a.add(m1); + assertFalse(a.equals(b)); + assertFalse(b.equals(a)); + b.add(m1); + assertTrue(a.equals(b)); + assertTrue(b.equals(a)); + assertEquals(a.hashCode(), b.hashCode()); + } + + /** + * KeySet.containsAll returns true for collections with subset of elements + */ + @Test + public void testContainsAll() { + Set full = populatedSet(3); + Vector v = new Vector(); + v.add(one); + v.add(two); + assertTrue(full.containsAll(v)); + v.add(six); + assertFalse(full.containsAll(v)); + } + + /** + * KeySet.isEmpty is true when empty, else false + */ + @Test + public void testIsEmpty() { + Set empty = ConcurrentHashMap.newKeySet(); + Set full = populatedSet(3); + assertTrue(empty.isEmpty()); + assertFalse(full.isEmpty()); + } + + /** + * KeySet.iterator() returns an iterator containing the elements of the set + */ + @Test + public void testIterator() { + Collection empty = ConcurrentHashMap.newKeySet(); + int size = 20; + assertFalse(empty.iterator().hasNext()); + try { + empty.iterator().next(); + shouldThrow(); + } catch (NoSuchElementException success) { + } + + Integer[] elements = new Integer[size]; + for (int i = 0; i < size; i++) + elements[i] = i; + Collections.shuffle(Arrays.asList(elements)); + Collection full = populatedSet(elements); + + Iterator it = full.iterator(); + for (int j = 0; j < size; j++) { + assertTrue(it.hasNext()); + it.next(); + } + assertFalse(it.hasNext()); + try { + it.next(); + shouldThrow(); + } catch (NoSuchElementException success) { + } + } + + /** + * KeySet.iterator.remove removes current element + */ + @Test + public void testIteratorRemove() { + Set q = populatedSet(3); + Iterator it = q.iterator(); + Object removed = it.next(); + it.remove(); + + it = q.iterator(); + assertFalse(it.next().equals(removed)); + assertFalse(it.next().equals(removed)); + assertFalse(it.hasNext()); + } + + /** + * KeySet.toString holds toString of elements + */ + @Test + public void testToString() { + assertEquals("[]", ConcurrentHashMap.newKeySet().toString()); + Set full = populatedSet(3); + String s = full.toString(); + for (int i = 0; i < 3; ++i) + assertTrue(s.contains(String.valueOf(i))); + } + + /** + * KeySet.removeAll removes all elements from the given collection + */ + @Test + public void testRemoveAll() { + Set full = populatedSet(3); + Vector v = new Vector(); + v.add(one); + v.add(two); + full.removeAll(v); + assertEquals(1, full.size()); + } + + /** + * KeySet.remove removes an element + */ + @Test + public void testRemove() { + Set full = populatedSet(3); + full.remove(one); + assertFalse(full.contains(one)); + assertEquals(2, full.size()); + } + + /** + * keySet.size returns the number of elements + */ + @Test + public void testSize() { + Set empty = ConcurrentHashMap.newKeySet(); + Set full = populatedSet(3); + assertEquals(3, full.size()); + assertEquals(0, empty.size()); + } + + /** + * KeySet.toArray() returns an Object array containing all elements from the set + */ + @Test + public void testToArray() { + Object[] a = ConcurrentHashMap.newKeySet().toArray(); + assertTrue(Arrays.equals(new Object[0], a)); + assertSame(Object[].class, a.getClass()); + int size = 20; + Integer[] elements = new Integer[size]; + for (int i = 0; i < size; i++) + elements[i] = i; + Collections.shuffle(Arrays.asList(elements)); + Collection full = populatedSet(elements); + + assertTrue(Arrays.asList(elements).containsAll(Arrays.asList(full.toArray()))); + assertTrue(full.containsAll(Arrays.asList(full.toArray()))); + assertSame(Object[].class, full.toArray().getClass()); + } + + /** + * toArray(Integer array) returns an Integer array containing all elements from the set + */ + @Test + public void testToArray2() { + Collection empty = ConcurrentHashMap.newKeySet(); + Integer[] a; + int size = 20; + + a = new Integer[0]; + assertSame(a, empty.toArray(a)); + + a = new Integer[size / 2]; + Arrays.fill(a, 42); + assertSame(a, empty.toArray(a)); + assertNull(a[0]); + for (int i = 1; i < a.length; i++) + assertEquals(42, (int) a[i]); + + Integer[] elements = new Integer[size]; + for (int i = 0; i < size; i++) + elements[i] = i; + Collections.shuffle(Arrays.asList(elements)); + Collection full = populatedSet(elements); + + Arrays.fill(a, 42); + assertTrue(Arrays.asList(elements).containsAll(Arrays.asList(full.toArray(a)))); + for (int i = 0; i < a.length; i++) + assertEquals(42, (int) a[i]); + assertSame(Integer[].class, full.toArray(a).getClass()); + + a = new Integer[size]; + Arrays.fill(a, 42); + assertSame(a, full.toArray(a)); + assertTrue(Arrays.asList(elements).containsAll(Arrays.asList(full.toArray(a)))); + } + + /** + * A deserialized serialized set is equal + */ + @Test + public void testSerialization() throws Exception { + int size = 20; + Set x = populatedSet(size); + Set y = serialClone(x); + + assertNotSame(x, y); + assertEquals(x.size(), y.size()); + assertEquals(x, y); + assertEquals(y, x); + } + + static final int SIZE = 10000; + static ConcurrentMap longMap; + + static ConcurrentMap longMap() { + if (longMap == null) { + longMap = newLongMap(); + for (int i = 0; i < SIZE; ++i) + longMap.put(Long.valueOf(i), Long.valueOf(2 * i)); + } + + return longMap; + } + + + // explicit function class to avoid type inference problems + static class AddKeys implements BiFunction, Map.Entry, Map.Entry> { + public Map.Entry apply(Map.Entry x, Map.Entry y) { + return new AbstractMap.SimpleEntry + (Long.valueOf(x.getKey().longValue() + y.getKey().longValue()), + Long.valueOf(1L)); + } + } + + +} \ No newline at end of file