From 0c45ee8aff17543acd4579c71c596c46ad2db4f9 Mon Sep 17 00:00:00 2001 From: leventov Date: Mon, 27 Oct 2014 20:05:26 +0300 Subject: [PATCH] HCOLL-136 HCOLL-137 HCOLL-153 HCOLL-160 1. Separated ChronicleMapBuilder (for non-Byteable values) and OffHeapUpdatableChronicleMapBuilder (for Byteable values) with common base AbstractChronicleMapBuilder (public just to inherit common javadocs) 2. Added ChronicleHashBuilder.keyDeserializationFactory() and AbstractChronicleMapBuilder.valueDeserializationFactory() (former ChronicleMapBuilder.valueFactory() method) 3. Changed ChronicleMap.get(), getUsing() API and impl to what was initially intended - now they ALWAYS returns null, if key is absent 4. acquireUsing() API, common for the cases with Byteable and non-Byteable values: puts defaultValue or defaultValueProvider.get(key), if the key is absent, then acquireUsing() behaves just like getUsing() 5. For off0heap updatable ChronicleMaps (Byteable values), added prepareValueBytes() configuration which formalizes zeroing out value bytes 6. Javadoc fixes and improvements --- .../common/ChronicleHashBuilder.java | 26 + .../chronicle/common/package-info.java | 26 + .../map/AbstractChronicleMapBuilder.java | 936 ++++++++++++++++++ .../net/openhft/chronicle/map/Alignment.java | 2 +- .../chronicle/map/ChannelProvider.java | 2 +- .../openhft/chronicle/map/ChronicleMap.java | 83 +- .../chronicle/map/ChronicleMapBuilder.java | 919 ++--------------- .../chronicle/map/ConstantValueProvider.java | 2 +- .../chronicle/map/DefaultValueProvider.java | 15 +- .../chronicle/map/MapEventListener.java | 6 +- .../chronicle/map/NullValueProvider.java | 2 +- .../OffHeapUpdatableChronicleMapBuilder.java | 98 ++ .../chronicle/map/PrepareValueBytes.java | 26 + .../map/PrepareValueBytesAsWriter.java | 56 ++ .../chronicle/map/ReplicatedChronicleMap.java | 110 +- .../chronicle/map/SerializationBuilder.java | 11 +- .../chronicle/map/StatelessChronicleMap.java | 4 +- .../openhft/chronicle/map/UdpReplicator.java | 2 +- .../chronicle/map/VanillaChronicleMap.java | 159 +-- .../chronicle/map/ZeroOutValueBytes.java | 34 + .../chronicle/set/ChronicleSetBuilder.java | 21 +- .../openhft/chronicle/set/package-info.java | 21 + .../chronicle/map/AcquireGetUsingMain.java | 2 +- .../chronicle/map/CHMLatencyTestMain.java | 2 +- .../net/openhft/chronicle/map/CHMTest5.java | 4 +- .../chronicle/map/ChronicleMapTest.java | 11 +- .../chronicle/map/EntryCountMapTest.java | 8 +- .../chronicle/map/ReplicationCheckingMap.java | 8 +- .../OpenJDKAndHashMapExamplesTest.java | 9 +- .../jrs166/map/StatelessChronicleMapTest.java | 8 +- 30 files changed, 1529 insertions(+), 1084 deletions(-) create mode 100644 src/main/java/net/openhft/chronicle/common/package-info.java create mode 100644 src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java create mode 100644 src/main/java/net/openhft/chronicle/map/OffHeapUpdatableChronicleMapBuilder.java create mode 100644 src/main/java/net/openhft/chronicle/map/PrepareValueBytes.java create mode 100644 src/main/java/net/openhft/chronicle/map/PrepareValueBytesAsWriter.java create mode 100644 src/main/java/net/openhft/chronicle/map/ZeroOutValueBytes.java create mode 100644 src/main/java/net/openhft/chronicle/set/package-info.java diff --git a/src/main/java/net/openhft/chronicle/common/ChronicleHashBuilder.java b/src/main/java/net/openhft/chronicle/common/ChronicleHashBuilder.java index 3036aa52f..6c5661272 100644 --- a/src/main/java/net/openhft/chronicle/common/ChronicleHashBuilder.java +++ b/src/main/java/net/openhft/chronicle/common/ChronicleHashBuilder.java @@ -21,7 +21,10 @@ package net.openhft.chronicle.common; import net.openhft.chronicle.map.*; import net.openhft.chronicle.set.ChronicleSet; import net.openhft.chronicle.set.ChronicleSetBuilder; +import net.openhft.lang.io.Bytes; import net.openhft.lang.io.serialization.*; +import net.openhft.lang.io.serialization.impl.AllocateInstanceObjectFactory; +import net.openhft.lang.io.serialization.impl.NewInstanceObjectFactory; import net.openhft.lang.io.serialization.impl.VanillaBytesMarshallerFactory; import net.openhft.lang.model.Byteable; import org.jetbrains.annotations.NotNull; @@ -345,6 +348,29 @@ public interface ChronicleHashBuilder keyMarshaller); + /** + * Configures factory which is used to create a new key instance, if key class is either + * {@link Byteable}, {@link BytesMarshallable} or {@link Externalizable} subclass in maps, + * created by this builder. If {@linkplain #keyMarshaller(BytesMarshaller) custom key + * marshaller} is configured, this configuration is unused, because it is incapsulated in + * {@link BytesMarshaller#read(Bytes)} method (without provided instance to read the data into), + * i. e. it's is the user-side responsibility. + * + *

Default key deserialization factory is {@link NewInstanceObjectFactory}, which creates + * a new key instance using {@link Class#newInstance()} default constructor. You could provide + * an {@link AllocateInstanceObjectFactory}, which uses {@code Unsafe.allocateInstance(Class)} + * (you might want to do this for better performance or if you don't want to initialize fields), + * or a factory which calls a key class constructor with some arguments, or a factory which + * internally delegates to instance pool or {@link ThreadLocal}, to reduce allocations. + * + * @param keyDeserializationFactory the key factory used to produce instances to deserialize + * data in + * @return this builder back + * @throws IllegalStateException if custom key marshaller is specified or key class is not + * either {@code Byteable}, {@code BytesMarshallable} or {@code Externalizable} + */ + B keyDeserializationFactory(@NotNull ObjectFactory keyDeserializationFactory); + /** * Specifies that key objects, queried with the hash containers, created by this builder, are * inherently immutable. Keys in {@link ChronicleMap} or {@link ChronicleSet} are not required diff --git a/src/main/java/net/openhft/chronicle/common/package-info.java b/src/main/java/net/openhft/chronicle/common/package-info.java new file mode 100644 index 000000000..68497fea7 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/common/package-info.java @@ -0,0 +1,26 @@ +/* + * 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. + */ + +/** + * Contains common interfaces and utilities for {@link net.openhft.chronicle.map.ChronicleMap + * ChronicleMaps} (net.openhft.chronicle.map package) and + * {@link net.openhft.chronicle.set.ChronicleSet ChronicleSets} + * (net.openhft.chronicle.set package). This package should be the root of + * {@code net.openhft.chronicle.map} and {@code net.openhft.chronicle.set} packages, moved to + * the same level because {@code net.openhft.chronicle} package is occupied by Chronicle Queue project. + */ +package net.openhft.chronicle.common; \ No newline at end of file diff --git a/src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java new file mode 100644 index 000000000..31b5d466c --- /dev/null +++ b/src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java @@ -0,0 +1,936 @@ +/* + * 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.chronicle.map; + +import net.openhft.chronicle.common.*; +import net.openhft.chronicle.common.serialization.MetaBytesInterop; +import net.openhft.chronicle.common.serialization.MetaBytesWriter; +import net.openhft.chronicle.common.serialization.MetaProvider; +import net.openhft.chronicle.common.threadlocal.Provider; +import net.openhft.chronicle.common.threadlocal.ThreadLocalCopies; +import net.openhft.chronicle.set.ChronicleSetBuilder; +import net.openhft.lang.Maths; +import net.openhft.lang.io.ByteBufferBytes; +import net.openhft.lang.io.Bytes; +import net.openhft.lang.io.BytesStore; +import net.openhft.lang.io.DirectStore; +import net.openhft.lang.io.serialization.*; +import net.openhft.lang.io.serialization.impl.AllocateInstanceObjectFactory; +import net.openhft.lang.io.serialization.impl.NewInstanceObjectFactory; +import net.openhft.lang.io.serialization.impl.VanillaBytesMarshallerFactory; +import net.openhft.lang.model.Byteable; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static net.openhft.chronicle.map.Objects.builderEquals; + +/** + * {@code AbstractChronicleMapBuilder} manages most of {@link ChronicleMap} configurations; has two + * concrete subclasses: {@link ChronicleMapBuilder} should be used to create maps with ordinary + * values, {@link OffHeapUpdatableChronicleMapBuilder} -- maps with {@link Byteable} values, which + * point directly to off-heap memory; could be used as a classic builder and/or factory. This means + * that in addition to the standard builder usage pattern:

{@code
+ * ChronicleMap map = ChronicleMapBuilder
+ *     .of(Key.class, Value.class)
+ *     .entries(100500)
+ *     // ... other configurations
+ *     .create();}
+ * one of concrete {@code AbstractChronicleMapBuilder} subclasses, {@link ChronicleMapBuilder} or + * {@link OffHeapUpdatableChronicleMapBuilder}, could be prepared and used to create many similar + * maps:
{@code
+ * OffHeapUpdatableChronicleMapBuilder builder = OffHeapUpdatableChronicleMapBuilder
+ *     .of(Key.class, Value.class)
+ *     .entries(100500);
+ *
+ * ChronicleMap map1 = builder.create();
+ * ChronicleMap map2 = builder.create();}
+ * i. e. created {@code ChronicleMap} instances don't depend on the builder. + * + *

{@code AbstractChronicleMapBuilder} and it's subclasses are mutable, see a note in + * {@link ChronicleHashBuilder} interface documentation. + * + *

Later in this documentation, "ChronicleMap" means "ChronicleMaps, created by {@code + * AbstractChronicleMapBuilder}", unless specified different, because theoretically someone might + * provide {@code ChronicleMap} implementations with completely different properties. + * + *

{@code ChronicleMap} ("ChronicleMaps, created by {@code AbstractChronicleMapBuilder}") + * currently doesn't support resizing. That is why you should always configure {@linkplain + * #entries(long) number of entries} you are going to insert into the created map at most. + * See {@link #entries(long)} method documentation for more information on this. + * + *

{@code ChronicleMap} allocates memory by equally sized chunks. This size is called {@linkplain + * #entrySize(int) entry size}, you are strongly recommended to configure it to achieve least memory + * consumption and best speed. See {@link #entrySize(int)} method documentation for more information + * on this. + * + * @param key type of the maps, produced by this builder + * @param value type of the maps, produced by this builder + * @see ChronicleMap + * @see ChronicleSetBuilder + */ +public abstract class AbstractChronicleMapBuilder> + implements Cloneable, ChronicleHashBuilder, B> { + + private static final Bytes EMPTY_BYTES = new ByteBufferBytes(ByteBuffer.allocate(0)); + private static final int DEFAULT_KEY_OR_VALUE_SIZE = 120; + + private static final int MAX_SEGMENTS = (1 << 30); + private static final int MAX_SEGMENTS_TO_CHAISE_COMPACT_MULTI_MAPS = (1 << 20); + + static final short UDP_REPLICATION_MODIFICATION_ITERATOR_ID = 128; + private static final Logger LOG = + LoggerFactory.getLogger(AbstractChronicleMapBuilder.class.getName()); + + + SerializationBuilder keyBuilder; + SerializationBuilder valueBuilder; + + private Map, Replicator> replicators = new HashMap, Replicator>(); + + // used when configuring the number of segments. + private int minSegments = -1; + private int actualSegments = -1; + // used when reading the number of entries per + private long actualEntriesPerSegment = -1L; + private int keySize = 0; + private K sampleKey; + private int valueSize = 0; + private V sampleValue; + private int entrySize = 0; + private Alignment alignment = Alignment.OF_4_BYTES; + private long entries = 1 << 20; // 1 million by default + private long lockTimeOut = 2000; + private TimeUnit lockTimeOutUnit = TimeUnit.MILLISECONDS; + private int metaDataBytes = 0; + private ChronicleHashErrorListener errorListener = ChronicleHashErrorListeners.logging(); + private boolean putReturnsNull = false; + private boolean removeReturnsNull = false; + + // replication + private TimeProvider timeProvider = TimeProvider.SYSTEM; + private BytesMarshallerFactory bytesMarshallerFactory; + private ObjectSerializer objectSerializer; + private MapEventListener> eventListener = + MapEventListeners.nop(); + private V defaultValue = null; + private DefaultValueProvider defaultValueProvider = NullValueProvider.INSTANCE; + private PrepareValueBytes prepareValueBytes = null; + private byte identifier = -1; + + private StatelessBuilder statelessBuilder; + private File file; + + AbstractChronicleMapBuilder(Class keyClass, Class valueClass) { + keyBuilder = new SerializationBuilder(keyClass, SerializationBuilder.Role.KEY); + valueBuilder = new SerializationBuilder(valueClass, SerializationBuilder.Role.VALUE); + } + + + private static long roundUpMapHeaderSize(long headerSize) { + long roundUp = (headerSize + 127L) & ~127L; + if (roundUp - headerSize < 64) + roundUp += 128; + return roundUp; + } + + @Override + public B clone() { + try { + @SuppressWarnings("unchecked") + final B result = (B) super.clone(); + result.keyBuilder = keyBuilder.clone(); + result.valueBuilder = valueBuilder.clone(); + return result; + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); + } + } + + abstract B self(); + + /** + * {@inheritDoc} + * + *

Example: if keys in your map(s) are English words in {@link String} form, keys size 10 + * (a bit more than average English word length) would be a good choice:

{@code
+     * ChronicleMap wordFrequencies = OffHeapUpdatableChronicleMapBuilder
+     *     .of(String.class, LongValue.class)
+     *     .entries(50000)
+     *     .keySize(10)
+     *     // shouldn't specify valueSize(), because it is statically known
+     *     .create();}
+ * (Note that 10 is chosen as key size in bytes despite strings in Java are UTF-16 encoded (and + * each character takes 2 bytes on-heap), because default off-heap {@link String} encoding is + * UTF-8 in {@code ChronicleMap}.) + * + * @see #constantKeySizeBySample(Object) + * @see #entrySize(int) + */ + @Override + public B keySize(int keySize) { + if (keySize <= 0) + throw new IllegalArgumentException("Key size must be positive"); + this.keySize = keySize; + return self(); + } + + /** + * {@inheritDoc} + * + *

For example, if your keys are Git commit hashes:

{@code
+     * Map gitCommitMessagesByHash =
+     *     ChronicleMapBuilder.of(byte[].class, String.class)
+     *     .constantKeySizeBySample(new byte[20])
+     *     .create();}
+ * + * @see #keySize(int) + */ + @Override + public B constantKeySizeBySample(K sampleKey) { + this.sampleKey = sampleKey; + return self(); + } + + private int keySize() { + return keyOrValueSize(keySize, keyBuilder); + } + + B valueSize(int valueSize) { + if (valueSize <= 0) + throw new IllegalArgumentException("Value size must be positive"); + this.valueSize = valueSize; + return self(); + } + + B constantValueSizeBySample(V sampleValue) { + this.sampleValue = sampleValue; + return self(); + } + + int valueSize() { + return keyOrValueSize(valueSize, valueBuilder); + } + + private int keyOrValueSize(int configuredSize, SerializationBuilder builder) { + if (configuredSize > 0) + return configuredSize; + // this means size is statically known + if (builder.sizeMarshaller().sizeEncodingSize(0L) == 0) + return (int) builder.sizeMarshaller().readSize(EMPTY_BYTES); + return DEFAULT_KEY_OR_VALUE_SIZE; + } + + /** + * {@inheritDoc} + * + *

In fully default case you can expect entry size to be about 256 bytes. But it is strongly + * recommended always to configure {@linkplain #keySize(int) key size} and {@linkplain + * #valueSize(int) value size}, if they couldn't be derived statically. + * + *

If entry size is not configured explicitly by calling this method, it is computed based on + * {@linkplain #metaDataBytes(int) meta data bytes}, plus {@linkplain #keySize(int) key size}, + * plus {@linkplain #valueSize(int) value size}, plus a few bytes required by implementations, + * with respect to {@linkplain #entryAndValueAlignment(Alignment) alignment}. + * + *

Note that the actual entrySize will be aligned to 4 (default {@linkplain + * #entryAndValueAlignment(Alignment) 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)}. + * + * @see #entryAndValueAlignment(Alignment) + * @see #entries(long) + */ + @Override + public B entrySize(int entrySize) { + if (entrySize <= 0) + throw new IllegalArgumentException("Entry Size must be positive"); + this.entrySize = entrySize; + return self(); + } + + int entrySize() { + if (entrySize > 0) + return entryAndValueAlignment().alignSize(entrySize); + int size = metaDataBytes; + int keySize = keySize(); + size += keyBuilder.sizeMarshaller().sizeEncodingSize(keySize); + size += keySize; + if (useReplication()) + size += ReplicatedChronicleMap.ADDITIONAL_ENTRY_BYTES; + int valueSize = valueSize(); + size += valueBuilder.sizeMarshaller().sizeEncodingSize(valueSize); + size = entryAndValueAlignment().alignSize(size); + size += valueSize; + // Some cache line heuristics + for (int i = 1; i <= 4; i++) { + int bound = i * 64; + // Not more than 5% oversize. + // DEFAULT_KEY_OR_VALUE_SIZE and this heuristic are specially adjusted to produce + // entry size 256 -- the default value prior to keySize and valueSize -- when key and + // value sizes are both not static nor configured, in both vanilla and replicated modes. + if (size < bound && (bound - size) <= bound / 20) { + size = bound; + break; + } + } + return entryAndValueAlignment().alignSize(size); + } + + /** + * Configures alignment strategy of address in memory of entries and independently of address in + * memory of values within entries in ChronicleMaps, created by this builder. + * + *

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 {@linkplain #entrySize(int) entry size} will be aligned according to this + * alignment. I. e. if you set {@code entrySize(20)} and {@link Alignment#OF_8_BYTES}, actual + * entry size will be 24 (20 aligned to 8 bytes). + * + *

Default is {@link Alignment#OF_4_BYTES}. + * + * @param alignment the new alignment of the maps constructed by this builder + * @return this {@code ChronicleMapBuilder} back + */ + public B entryAndValueAlignment(Alignment alignment) { + this.alignment = alignment; + return self(); + } + + Alignment entryAndValueAlignment() { + return alignment; + } + + @Override + public B entries(long entries) { + if (entries <= 0L) + throw new IllegalArgumentException("Entries should be positive, " + entries + " given"); + this.entries = entries; + return self(); + } + + long entries() { + return entries; + } + + @Override + public B actualEntriesPerSegment(long actualEntriesPerSegment) { + if (actualEntriesPerSegment <= 0L) + throw new IllegalArgumentException("entries per segment should be positive, " + + actualEntriesPerSegment + " given"); + if (tooManyEntriesPerSegment(actualEntriesPerSegment)) + throw new IllegalArgumentException("max entries per segment is " + + VanillaIntIntMultiMap.MAX_CAPACITY + ", " + actualEntriesPerSegment + " given"); + this.actualEntriesPerSegment = actualEntriesPerSegment; + return self(); + } + + private boolean tooManyEntriesPerSegment(long entriesPerSegment) { + return entriesPerSegment > VanillaIntIntMultiMap.MAX_CAPACITY; + } + + long actualEntriesPerSegment() { + if (actualEntriesPerSegment > 0L) + return actualEntriesPerSegment; + int actualSegments = actualSegments(); + long actualEntries = totalEntriesIfPoorDistribution(actualSegments); + long actualEntriesPerSegment = divideUpper(actualEntries, actualSegments); + if (tooManyEntriesPerSegment(actualEntriesPerSegment)) + throw new IllegalStateException("max entries per segment is " + + VanillaIntIntMultiMap.MAX_CAPACITY + " configured entries() and " + + "actualSegments() so that there should be " + actualEntriesPerSegment + + " entries per segment"); + return actualEntriesPerSegment; + } + + private long totalEntriesIfPoorDistribution(int segments) { + if (segments == 1) + return entries; + double poorDistEntriesScale = Math.log(segments) * entries; + if (segments <= 8) + return Math.min(entries * segments, + (long) (entries + poorDistEntriesScale * 0.08 + 64)); // 8% was min for tests + return (long) (entries + poorDistEntriesScale * 0.11 + 80); // 11% was min for tests + } + + @Override + public B minSegments(int minSegments) { + checkSegments(minSegments); + this.minSegments = minSegments; + return self(); + } + + int minSegments() { + return Math.max(estimateSegments(), minSegments); + } + + private int estimateSegments() { + for (int power = 0; power < 7; power++) { + if (entries < (1L << (7 + (2 * power)))) + return 1 << power; + } + // Heuristic -- number of segments ~= cube root from number of entries seems optimal + int maxSegments = 65536; + for (int segments = 4; segments < maxSegments; segments <<= 1) { + if (((long) segments) * segments * segments >= entries) + return segments; + } + return maxSegments; + } + + @Override + public B actualSegments(int actualSegments) { + checkSegments(actualSegments); + this.actualSegments = actualSegments; + return self(); + } + + private static void checkSegments(int segments) { + if (segments <= 0 || segments > MAX_SEGMENTS) + throw new IllegalArgumentException("segments should be positive, " + + segments + " given"); + if (segments > MAX_SEGMENTS) + throw new IllegalArgumentException("Max segments is " + MAX_SEGMENTS + ", " + + segments + " given"); + } + + int actualSegments() { + if (actualSegments > 0) + return actualSegments; + long shortMMapSegments = trySegments(VanillaShortShortMultiMap.MAX_CAPACITY, + MAX_SEGMENTS_TO_CHAISE_COMPACT_MULTI_MAPS); + if (shortMMapSegments > 0L) + return (int) shortMMapSegments; + long intMMapSegments = trySegments(VanillaIntIntMultiMap.MAX_CAPACITY, MAX_SEGMENTS); + if (intMMapSegments > 0L) + return (int) intMMapSegments; + throw new IllegalStateException("Max segments is " + MAX_SEGMENTS + ", configured so much" + + " entries() that builder automatically decided to use " + + (-intMMapSegments) + " segments"); + } + + private long trySegments(long maxSegmentCapacity, int maxSegments) { + long segments = divideUpper(totalEntriesIfPoorDistribution(minSegments()), + maxSegmentCapacity); + segments = Maths.nextPower2(Math.max(segments, minSegments()), 1L); + return segments <= maxSegments ? segments : -segments; + } + + private static long divideUpper(long dividend, long divisor) { + return ((dividend - 1L) / divisor) + 1L; + } + + private boolean canSupportShortShort() { + return entries > (long) minSegments() << 15; + } + + public B lockTimeOut(long lockTimeOut, TimeUnit unit) { + this.lockTimeOut = lockTimeOut; + lockTimeOutUnit = unit; + return self(); + } + + long lockTimeOut(TimeUnit unit) { + return unit.convert(lockTimeOut, lockTimeOutUnit); + } + + @Override + public B errorListener(ChronicleHashErrorListener errorListener) { + this.errorListener = errorListener; + return self(); + } + + ChronicleHashErrorListener errorListener() { + return errorListener; + } + + /** + * Configures if the maps created by this builder should return {@code null} instead of previous + * mapped values on {@link ChronicleMap#put(Object, Object) ChornicleMap.put(key, value)} + * calls. + * + *

{@link Map#put(Object, Object) Map.put()} returns the previous value, functionality which + * is rarely used but fairly cheap for {@link HashMap}. In the case, for an off heap collection, + * it has to create a new object and deserialize the data from off-heap memory. It's expensive + * for something you probably don't use. + * + *

By default, of cause, {@code ChronicleMap} conforms the general {@code Map} contract and + * returns the previous mapped value on {@code put()} calls. + * + * @param putReturnsNull {@code true} if you want {@link ChronicleMap#put(Object, Object) + * ChronicleMap.put()} to not return the value that was replaced but + * instead return {@code null} + * @return an instance of the map builder back + * @see #removeReturnsNull(boolean) + */ + public B putReturnsNull(boolean putReturnsNull) { + this.putReturnsNull = putReturnsNull; + return self(); + } + + boolean putReturnsNull() { + return putReturnsNull; + } + + /** + * Configures if the maps created by this builder should return {@code null} instead of the last + * mapped value on {@link ChronicleMap#remove(Object) ChronicleMap.remove(key)} calls. + * + *

{@link Map#remove(Object) Map.remove()} returns the previous value, functionality which is + * rarely used but fairly cheap for {@link HashMap}. In the case, for an off heap collection, it + * has to create a new object and deserialize the data from off-heap memory. It's expensive for + * something you probably don't use. + * + * @param removeReturnsNull {@code true} if you want {@link ChronicleMap#remove(Object) + * ChronicleMap.remove()} to not return the value of the removed entry + * but instead return {@code null} + * @return an instance of the map builder back + * @see #putReturnsNull(boolean) + */ + public B removeReturnsNull(boolean removeReturnsNull) { + this.removeReturnsNull = removeReturnsNull; + return self(); + } + + boolean removeReturnsNull() { + return removeReturnsNull; + } + + @Override + public B metaDataBytes(int metaDataBytes) { + if ((metaDataBytes & 0xFF) != metaDataBytes) + throw new IllegalArgumentException("MetaDataBytes must be [0..255] was " + metaDataBytes); + this.metaDataBytes = metaDataBytes; + return self(); + } + + + int metaDataBytes() { + return metaDataBytes; + } + + @Override + public String toString() { + return "ChronicleMapBuilder{" + + "actualSegments=" + pretty(actualSegments) + + ", minSegments=" + pretty(minSegments) + + ", actualEntriesPerSegment=" + pretty(actualEntriesPerSegment) + + ", keySize=" + pretty(keySize) + + ", sampleKeyForConstantSizeComputation=" + pretty(sampleKey) + + ", valueSize=" + pretty(valueSize) + + ", sampleValueForConstantSizeComputation=" + pretty(sampleValue) + + ", entrySize=" + pretty(entrySize) + + ", entryAndValueAlignment=" + entryAndValueAlignment() + + ", entries=" + entries() + + ", lockTimeOut=" + lockTimeOut + " " + lockTimeOutUnit + + ", metaDataBytes=" + metaDataBytes() + + ", errorListener=" + errorListener() + + ", putReturnsNull=" + putReturnsNull() + + ", removeReturnsNull=" + removeReturnsNull() + + ", timeProvider=" + timeProvider() + + ", bytesMarshallerFactory=" + pretty(bytesMarshallerFactory) + + ", objectSerializer=" + pretty(objectSerializer) + + ", keyBuilder=" + keyBuilder + + ", valueBuilder=" + valueBuilder + + ", eventListener=" + eventListener + + ", defaultValue=" + defaultValue + + ", defaultValueProvider=" + pretty(defaultValueProvider) + + ", prepareValueBytes=" + pretty(prepareValueBytes) + + '}'; + } + + private static String pretty(int value) { + return value > 0 ? value + "" : "not configured"; + } + + private static String pretty(Object obj) { + return obj != null ? obj + "" : "not configured"; + } + + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + @Override + public boolean equals(Object o) { + return builderEquals(this, o); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + public B replicators(byte identifier, ReplicationConfig... replicationConfigs) { + + this.identifier = identifier; + this.replicators.clear(); + for (ReplicationConfig replicationConfig : replicationConfigs) { + + Replicator replicator; + if (replicationConfig instanceof TcpReplicationConfig) { + replicator = Replicators.tcp((TcpReplicationConfig) replicationConfig); + } else if (replicationConfig instanceof UdpReplicationConfig) { + replicator = Replicators.udp((UdpReplicationConfig) replicationConfig); + } else + throw new UnsupportedOperationException(); + + + this.replicators.put(replicator.getClass(), replicator); + } + + return self(); + } + + + public B channel(ChannelProvider.ChronicleChannel chronicleChannel) { + this.identifier = chronicleChannel.identifier(); + this.replicators.clear(); + replicators.put(chronicleChannel.getClass(), chronicleChannel); + + return self(); + } + + @Override + public B disableReplication() { + identifier = -1; + replicators.clear(); + return self(); + } + + @Override + public B timeProvider(TimeProvider timeProvider) { + this.timeProvider = timeProvider; + return self(); + } + + TimeProvider timeProvider() { + return timeProvider; + } + + BytesMarshallerFactory bytesMarshallerFactory() { + return bytesMarshallerFactory == null ? + bytesMarshallerFactory = new VanillaBytesMarshallerFactory() : + bytesMarshallerFactory; + } + + @Override + public B bytesMarshallerFactory(BytesMarshallerFactory bytesMarshallerFactory) { + this.bytesMarshallerFactory = bytesMarshallerFactory; + return self(); + } + + ObjectSerializer objectSerializer() { + return objectSerializer == null ? + objectSerializer = BytesMarshallableSerializer.create( + bytesMarshallerFactory(), JDKObjectSerializer.INSTANCE) : + objectSerializer; + } + + @Override + public B objectSerializer(ObjectSerializer objectSerializer) { + this.objectSerializer = objectSerializer; + return self(); + } + + + /** + * {@inheritDoc} + * + *

Example: + */ + @Override + public B keyMarshaller(@NotNull BytesMarshaller keyMarshaller) { + keyBuilder.marshaller(keyMarshaller, null); + return self(); + } + + @Override + public B keyDeserializationFactory(@NotNull ObjectFactory keyDeserializationFactory) { + keyBuilder.factory(keyDeserializationFactory); + return self(); + } + + @Override + public B immutableKeys() { + keyBuilder.instancesAreMutable(false); + return self(); + } + + @Override + public B stateless(@NotNull StatelessBuilder statelessBuilder) { + this.statelessBuilder = statelessBuilder; + return self(); + } + + @Override + public B file(File file) throws IOException { + this.file = file; + return self(); + } + + /** + * Configures factory which is used to create a new value instance, if value class is either + * {@link Byteable}, {@link BytesMarshallable} or {@link Externalizable} subclass in maps, + * created by this builder. + * + *

Default value deserialization factory is {@link NewInstanceObjectFactory}, which creates + * a new value instance using {@link Class#newInstance()} default constructor. You could provide + * an {@link AllocateInstanceObjectFactory}, which uses {@code Unsafe.allocateInstance(Class)} + * (you might want to do this for better performance or if you don't want to initialize fields), + * or a factory which calls a value class constructor with some arguments, or a factory which + * internally delegates to instance pool or {@link ThreadLocal}, to reduce allocations. + * + * @param valueDeserializationFactory the value factory used to produce instances to deserialize + * data in + * @return this builder back + */ + public B valueDeserializationFactory(@NotNull ObjectFactory valueDeserializationFactory) { + valueBuilder.factory(valueDeserializationFactory); + return self(); + } + + public B eventListener(MapEventListener> eventListener) { + this.eventListener = eventListener; + return self(); + } + + MapEventListener> eventListener() { + return eventListener; + } + + /** + * Specifies the value to be put for each key queried in {@link ChronicleMap#acquireUsing + * acquireUsing()} method, if the key is absent in the map, created by this builder. + * + *

This configuration overrides any previous {@link #defaultValueProvider( + * DefaultValueProvider)} configuration to this {@code AbstractChronicleMapBuilder}. + * + * @param defaultValue the default value to be put to the map for absent keys during {@code + * acquireUsing()} calls + * @return this builder object back + */ + public B defaultValue(V defaultValue) { + this.defaultValue = defaultValue; + this.defaultValueProvider = null; + if (defaultValue == null) + defaultValueProvider = NullValueProvider.INSTANCE; + this.prepareValueBytes = null; + return self(); + } + + /** + * Specifies the function to obtain a value for the key during {@link ChronicleMap#acquireUsing + * acquireUsing()} calls, if the key is absent in the map, created by this builder. + * + *

This configuration overrides any previous {@link #defaultValue(Object)} configuration to + * this {@code AbstractChronicleMapBuilder}. + * + * @param defaultValueProvider the strategy to obtain a default value by the absent key + * @return this builder object back + */ + public B defaultValueProvider( + @NotNull DefaultValueProvider defaultValueProvider) { + this.defaultValueProvider = defaultValueProvider; + this.defaultValue = null; + this.prepareValueBytes = null; + return self(); + } + + B prepareValueBytesOnAcquire(@NotNull PrepareValueBytes prepareValueBytes) { + this.prepareValueBytes = prepareValueBytes; + this.defaultValue = null; + this.defaultValueProvider = null; + return self(); + } + + PrepareValueBytesAsWriter prepareValueBytesAsWriter() { + return new PrepareValueBytesAsWriter<>(prepareValueBytes, valueSize()); + } + + + /** + * Non-public because should be called only after {@link #preMapConstruction()} + */ + DefaultValueProvider defaultValueProvider() { + if (defaultValueProvider != null) + return defaultValueProvider; + if (defaultValue == null) + return null; + Object originalValueWriter = valueBuilder.interop(); + Provider writerProvider = Provider.of(originalValueWriter.getClass()); + ThreadLocalCopies copies = writerProvider.getCopies(null); + Object valueWriter = writerProvider.get(copies, originalValueWriter); + MetaProvider metaWriterProvider = valueBuilder.metaInteropProvider(); + copies = metaWriterProvider.getCopies(copies); + MetaBytesWriter metaValueWriter = metaWriterProvider.get(copies, + valueBuilder.metaInterop(), valueWriter, defaultValue); + return new ConstantValueProvider(defaultValue, metaValueWriter, valueWriter); + } + + public ChronicleMap createWithFile(File file) throws IOException { + for (int i = 0; i < 10; i++) { + if (file.exists() && file.length() > 0) { + FileInputStream fis = new FileInputStream(file); + ObjectInputStream ois = new ObjectInputStream(fis); + try { + VanillaChronicleMap map = + (VanillaChronicleMap) ois.readObject(); + map.headerSize = roundUpMapHeaderSize(fis.getChannel().position()); + map.createMappedStoreAndSegments(file); + return establishReplication(map); + } catch (ClassNotFoundException e) { + throw new IOException(e); + } finally { + ois.close(); + } + } + if (file.createNewFile() || file.length() == 0) { + break; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new IOException(e); + } + } + // new file + if (!file.exists()) + throw new FileNotFoundException("Unable to create " + file); + + VanillaChronicleMap map = newMap(); + + FileOutputStream fos = new FileOutputStream(file); + ObjectOutputStream oos = new ObjectOutputStream(fos); + try { + oos.writeObject(map); + oos.flush(); + map.headerSize = roundUpMapHeaderSize(fos.getChannel().position()); + map.createMappedStoreAndSegments(file); + } finally { + oos.close(); + } + + return establishReplication(map); + } + + + @Override + public ChronicleMap create() throws IOException { + + if (statelessBuilder != null) + return createStatelessMap(statelessBuilder); + + return (file != null) ? createWithFile(file) : createWithoutFile(); + } + + private ChronicleMap createWithoutFile() throws IOException { + VanillaChronicleMap map = newMap(); + BytesStore bytesStore = new DirectStore(JDKObjectSerializer.INSTANCE, + map.sizeInBytes(), true); + map.createMappedStoreAndSegments(bytesStore); + return establishReplication(map); + } + + private ChronicleMap createStatelessMap(StatelessBuilder statelessBuilder) throws IOException { + preMapConstruction(); + + final KeyValueSerializer keyValueSerializer + = new KeyValueSerializer(keyBuilder, valueBuilder); + + final Class kClass = keyBuilder.eClass; + final Class vClass = valueBuilder.eClass; + + return new StatelessChronicleMap( + keyValueSerializer, + statelessBuilder, + entrySize(), + kClass, + vClass); + } + + private VanillaChronicleMap newMap() throws IOException { + preMapConstruction(); + if (useReplication()) { + return new ReplicatedChronicleMap, + V, Object, MetaBytesWriter>(this); + } else { + return new VanillaChronicleMap, + V, Object, MetaBytesWriter>(this); + } + } + + private boolean useReplication() { + return identifier != -1; + } + + void preMapConstruction() { + keyBuilder.objectSerializer(objectSerializer()); + valueBuilder.objectSerializer(objectSerializer()); + + long maxSize = (long) entrySize() * figureBufferAllocationFactor(); + keyBuilder.maxSize(maxSize); + valueBuilder.maxSize(maxSize); + + if (sampleKey != null) + keyBuilder.constantSizeBySample(sampleKey); + if (sampleValue != null) + valueBuilder.constantSizeBySample(sampleValue); + } + + private ChronicleMap establishReplication(ChronicleMap map) + throws IOException { + if (map instanceof ReplicatedChronicleMap) { + ReplicatedChronicleMap result = (ReplicatedChronicleMap) map; + for (Replicator replicator : replicators.values()) { + Closeable token = replicator.applyTo(this, result, result, map); + if (replicators.size() == 1 && token.getClass() == UdpReplicator.class) { + 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" + ); + } + result.addCloseable(token); + } + } + return map; + } + + private int figureBufferAllocationFactor() { + // 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, entries() >> 10), + VanillaChronicleMap.MAX_ENTRY_OVERSIZE_FACTOR); + } + + public byte identifier() { + return identifier; + } +} + diff --git a/src/main/java/net/openhft/chronicle/map/Alignment.java b/src/main/java/net/openhft/chronicle/map/Alignment.java index d2ef30ae0..ebdd6728a 100644 --- a/src/main/java/net/openhft/chronicle/map/Alignment.java +++ b/src/main/java/net/openhft/chronicle/map/Alignment.java @@ -23,7 +23,7 @@ import net.openhft.lang.io.Bytes; /** * Memory addresses alignment strategies. * - * @see ChronicleMapBuilder#entryAndValueAlignment(Alignment) + * @see AbstractChronicleMapBuilder#entryAndValueAlignment(Alignment) */ public enum Alignment { /** diff --git a/src/main/java/net/openhft/chronicle/map/ChannelProvider.java b/src/main/java/net/openhft/chronicle/map/ChannelProvider.java index d6f2a61e2..fdff1ece9 100644 --- a/src/main/java/net/openhft/chronicle/map/ChannelProvider.java +++ b/src/main/java/net/openhft/chronicle/map/ChannelProvider.java @@ -409,7 +409,7 @@ public final class ChannelProvider implements Closeable { } @Override - protected Closeable applyTo(ChronicleMapBuilder builder, + protected Closeable applyTo(AbstractChronicleMapBuilder builder, Replica map, EntryExternalizable entryExternalizable, final ChronicleMap chronicleMap) { if (builder.entrySize() > maxEntrySize) { throw new IllegalArgumentException("During ReplicatingChannelBuilder setup, " + diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java index 0649643ef..23599052d 100644 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java @@ -22,7 +22,6 @@ import net.openhft.chronicle.common.ChronicleHash; import net.openhft.chronicle.common.serialization.BytesReader; import net.openhft.lang.io.Bytes; import net.openhft.lang.io.serialization.BytesMarshaller; -import net.openhft.lang.io.serialization.ObjectFactory; import net.openhft.lang.model.Byteable; import java.io.Closeable; @@ -38,11 +37,15 @@ import java.util.concurrent.ConcurrentMap; *

  • {@code ChronicleMap} flavors and properties
  • *
  • available configurations
  • * - * see {@link ChronicleMapBuilder} documentation. + * see {@link AbstractChronicleMapBuilder} documentation. * *

    Functionally this interface defines some methods supporting garbage-free off-heap programming: * {@link #getUsing(Object, Object)}, {@link #acquireUsing(Object, Object)}. * + *

    Roughly speaking, {@code ChronicleMap} compares keys and values by their binary serialized + * form, that shouldn't necessary be the same equality relation as defined by built-in {@link + * Object#equals(Object)} method, which is prescribed by general {@link Map} contract. + * *

    Note that {@code ChronicleMap} extends {@link Closeable}, don't forget * to {@linkplain #close() close} map when it is no longer needed. * @@ -62,15 +65,6 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { * Returns the value to which the specified key is mapped, or {@code null} if this map contains * no mapping for the key. * - *

    If the specified key if absent in the map, {@linkplain - * ChronicleMapBuilder#defaultValue(Object) default value} is taken or {@linkplain - * ChronicleMapBuilder#defaultValueProvider(DefaultValueProvider) default value provider} - * is called (depending on what was configured last in {@code ChronicleMapBuilder}). - * If it results to something different from {@code null}, this object is put to this map for - * the specified key, and then returned from this {@code get()} call. Otherwise, if default - * value is {@code null} and default value provider returns {@code null}, the map remains - * unchanged and {@code null} is returned. - * *

    If the value class allows reusing, particularly if it is a {@link Byteable} subclass, * consider {@link #getUsing(Object, Object)} method instead of this to reduce garbage creation. * @@ -90,40 +84,59 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { * {@code value} object via value marshaller's {@link BytesMarshaller#read(Bytes, Object) * read(Bytes, value)} or value reader's {@link BytesReader#read(Bytes, long, Object) * read(Bytes, size, value)} method, depending on what deserialization strategy is configured - * on this map builder. If the value deserializer is able to reuse the given {@code value} - * object, calling this method instead of {@link #get(Object)} could help to reduce garbage - * creation. + * on the builder, using which this map was constructed. If the value deserializer is able + * to reuse the given {@code value} object, calling this method instead of {@link #get(Object)} + * could help to reduce garbage creation. * *

    The provided {@code value} object is allowed to be {@code null}, in this case * {@code map.getUsing(key, null)} call is semantically equivalent to simple * {@code map.get(key)} call. * - *

    If the specified key if absent in the map, {@linkplain - * ChronicleMapBuilder#defaultValue(Object) default value} is taken or {@linkplain - * ChronicleMapBuilder#defaultValueProvider(DefaultValueProvider) default value provider} - * is called. If it results to something different from {@code null}, this object is put - * to this map for the specified key, and then a new value is returned from this - * {@code getUsing()} call. Otherwise, if default value is {@code null} and default value - * provider, if specified, returns {@code null}, the map remains unchanged and {@code null} - * is returned. The provided {@code value} is untouched anyway in this case. - * - * @param key the key whose associated value is to be returned - * @param value the object to read value data in, if possible - * @return the value mapped to the specified key after this method call, or {@code null} if no - * value is mapped + * @param key the key whose associated value is to be returned + * @param usingValue the object to read value data in, if possible + * @return the value to which the specified key is mapped, or {@code null} if this map contains + * no mapping for the key * @see #get(Object) - * @see ChronicleMapBuilder#valueMarshallerAndFactory(BytesMarshaller, ObjectFactory) + * @see #acquireUsing(Object, Object) + * @see ChronicleMapBuilder#valueMarshaller(BytesMarshaller) */ - V getUsing(K key, V value); + V getUsing(K key, V usingValue); /** - * Acquire a value for a key, creating if absent. If the value is Byteable, it will be assigned - * to reference the value, instead of copying the data. + * Acquire a value for a key, creating if absent. * - * @param key the key whose associated value is to be returned - * @param value to reuse if possible. If null, a new object will be created. - * @return value created or found. + *

    If the specified key if absent in the map, {@linkplain + * AbstractChronicleMapBuilder#defaultValue(Object) default value} is taken or {@linkplain + * AbstractChronicleMapBuilder#defaultValueProvider(DefaultValueProvider) default value + * provider} is called. Then this object is put to this map for the specified key. + * + *

    Then, either if the key was initially absent in the map or already present, + * the value is deserialized just as during {@link #getUsing(Object, Object) + * getUsing(key, usingValue)} call, passed the same {@code key} and {@code usingValue} as into + * this method call. This means, as in {@link #getUsing}, {@code usingValue} could safely be + * {@code null}, in this case a new value instance is created to deserialize the data. + * + *

    In code, {@code acquireUsing} is specified as

    {@code
    +     * V acquireUsing(K key, V usingValue) {
    +     *     if (!containsKey(key))
    +     *         put(key, defaultValue(key));
    +     *     return getUsing(key, usingValue);
    +     * }}
    Where {@code defaultValue(key)} returns either {@linkplain + * AbstractChronicleMapBuilder#defaultValue(Object) default value} or {@link + * AbstractChronicleMapBuilder#defaultValueProvider(DefaultValueProvider) defaultValueProvider.} + * + *

    If the {@code ChronicleMap} is off-heap updatable, i. e. created via {@link + * OffHeapUpdatableChronicleMapBuilder} builder (values are {@link Byteable}), there is one more + * option of what to do if the key is absent in the map, see {@link + * OffHeapUpdatableChronicleMapBuilder#prepareValueBytesOnAcquire(PrepareValueBytes)}. + * By default, value bytes are just zeroed out, no default value, either provided for key + * or constant, is put for the absent key. + * + * @param key the key whose associated value is to be returned + * @param usingValue the object to read value data in, if possible + * @return value to which the given key is mapping after this call, either found or created + * @see #getUsing(Object, Object) */ - V acquireUsing(K key, V value); + V acquireUsing(K key, V usingValue); } diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java index df2f5e776..755c59d8c 100644 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java @@ -1,7 +1,5 @@ /* - * Copyright 2014 Higher Frequency Trading - * - * http://www.higherfrequencytrading.com + * 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. @@ -18,204 +16,92 @@ package net.openhft.chronicle.map; -import net.openhft.chronicle.common.*; +import net.openhft.chronicle.common.ChronicleHashBuilder; import net.openhft.chronicle.common.serialization.AgileBytesMarshaller; -import net.openhft.chronicle.common.serialization.MetaBytesInterop; -import net.openhft.chronicle.common.serialization.MetaBytesWriter; -import net.openhft.chronicle.common.serialization.MetaProvider; -import net.openhft.chronicle.common.threadlocal.Provider; -import net.openhft.chronicle.common.threadlocal.ThreadLocalCopies; -import net.openhft.chronicle.set.ChronicleSetBuilder; -import net.openhft.lang.Maths; -import net.openhft.lang.io.ByteBufferBytes; import net.openhft.lang.io.Bytes; -import net.openhft.lang.io.BytesStore; -import net.openhft.lang.io.DirectStore; -import net.openhft.lang.io.serialization.*; -import net.openhft.lang.io.serialization.impl.VanillaBytesMarshallerFactory; +import net.openhft.lang.io.serialization.BytesMarshallable; +import net.openhft.lang.io.serialization.BytesMarshaller; +import net.openhft.lang.io.serialization.ObjectFactory; +import net.openhft.lang.io.serialization.ObjectSerializer; import net.openhft.lang.model.Byteable; import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.io.*; -import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; +import java.io.Externalizable; +import java.io.Serializable; -import static net.openhft.chronicle.map.Objects.builderEquals; /** - * {@code ChronicleMapBuilder} manages the whole set of {@link ChronicleMap} configurations, could - * be used as a classic builder and/or factory. This means that in addition to the standard builder - * usage pattern:

    {@code
    - * ChronicleMap map = ChronicleMapBuilder
    - *     .of(Key.class, Value.class)
    - *     .entries(100500)
    - *     // ... other configurations
    - *     .create();}
    - * {@code ChronicleMapBuilder} could be prepared and used to create many similar maps:
    {@code
    - * ChronicleMapBuilder builder = ChronicleMapBuilder
    - *     .of(Key.class, Value.class)
    - *     .entries(100500);
    - * 

    - * ChronicleMap map1 = builder.create(); - * ChronicleMap map2 = builder.create();}

    - * i. e. created {@code ChronicleMap} instances don't depend on the builder. + * {@code ChronicleMapBuilder} is intended to be used to configure {@link ChronicleMap}s with + * non-{@link Byteable} values, which don't point to off-heap memory directly, including primitives + * ({@link Long}, {@link Double}, etc.), {@link String}s and {@link CharSequence}s, values + * implementing {@link BytesMarshallable}, {@link Externalizable} or {@link Serializable} interface, + * or any other values for which {@linkplain #valueMarshaller(BytesMarshaller) custom marshaller} + * is provided. * - *

    Use static {@link #of(Class, Class)} method to obtain a {@code ChronicleMapBuilder} instance. + *

    Use static {@link #of(Class, Class) of(Key.class, Value.class)} method to obtain + * a {@code ChronicleMapBuilder} instance. * - *

    {@code ChronicleMapBuilder} is mutable, see a note in {@link net.openhft.chronicle.common.ChronicleHashBuilder} - * interface documentation. - * - *

    Later in this documentation, "ChronicleMap" means "ChronicleMaps, created by {@code - * ChronicleMapBuilder}", unless specified different, because theoretically someone might provide - * {@code ChronicleMap} implementations with completely different properties. - * - *

    {@code ChronicleMap} ("ChronicleMaps, created by {@code ChronicleMapBuilder}") currently - * doesn't support resizing. That is why you should always configure {@linkplain - * #entries(long) number of entries} you are going to insert into the created map at most. - * See {@link #entries(long)} method documentation for more information on this. - * - *

    {@code ChronicleMap} allocates memory by equally sized chunks. This size is called {@linkplain - * #entrySize(int) entry size}, you are strongly recommended to configure it to achieve least memory - * consumption and best speed. See {@link #entrySize(int)} method documentation for more information - * on this. - * - * @param key type of the maps, produced by this builder - * @param value type of the maps, produced by this builder - * @see ChronicleMap - * @see ChronicleSetBuilder + * @param key type of the maps, created by this builder + * @param value type of the maps, created by this builder + * @see OffHeapUpdatableChronicleMapBuilder + * @see AbstractChronicleMapBuilder + * @see ChronicleHashBuilder */ -public class ChronicleMapBuilder implements Cloneable, - ChronicleHashBuilder, ChronicleMapBuilder> { +public final class ChronicleMapBuilder + extends AbstractChronicleMapBuilder> { - private static final Bytes EMPTY_BYTES = new ByteBufferBytes(ByteBuffer.allocate(0)); - private static final int DEFAULT_KEY_OR_VALUE_SIZE = 120; - - private static final int MAX_SEGMENTS = (1 << 30); - private static final int MAX_SEGMENTS_TO_CHAISE_COMPACT_MULTI_MAPS = (1 << 20); - - static final short UDP_REPLICATION_MODIFICATION_ITERATOR_ID = 128; - private static final Logger LOG = LoggerFactory.getLogger(ChronicleMapBuilder.class.getName()); - - - SerializationBuilder keyBuilder; - SerializationBuilder valueBuilder; - - private Map, Replicator> replicators = new HashMap, Replicator>(); - - // used when configuring the number of segments. - private int minSegments = -1; - private int actualSegments = -1; - // used when reading the number of entries per - private long actualEntriesPerSegment = -1L; - private int keySize = 0; - private K sampleKey; - private int valueSize = 0; - private V sampleValue; - private int entrySize = 0; - private Alignment alignment = Alignment.OF_4_BYTES; - private long entries = 1 << 20; // 1 million by default - private long lockTimeOut = 2000; - private TimeUnit lockTimeOutUnit = TimeUnit.MILLISECONDS; - private int metaDataBytes = 0; - private ChronicleHashErrorListener errorListener = ChronicleHashErrorListeners.logging(); - private boolean putReturnsNull = false; - private boolean removeReturnsNull = false; - - // replication - private TimeProvider timeProvider = TimeProvider.SYSTEM; - private BytesMarshallerFactory bytesMarshallerFactory; - private ObjectSerializer objectSerializer; - private MapEventListener> eventListener = - MapEventListeners.nop(); - private V defaultValue = null; - private DefaultValueProvider defaultValueProvider = null; - private byte identifier = -1; - - private StatelessBuilder statelessBuilder; - private File file; - - ChronicleMapBuilder(Class keyClass, Class valueClass) { - - keyBuilder = new SerializationBuilder(keyClass, SerializationBuilder.Role.KEY); - - valueBuilder = new SerializationBuilder(valueClass, SerializationBuilder.Role.VALUE); - } - - public static ChronicleMapBuilder of(Class keyClass, Class valueClass) { + /** + * Returns a new {@code ChronicleMapBuilder} instance which is able to {@linkplain #create() + * create} maps with the specified key and value classes. + * + *

    {@code ChronicleMapBuilder} analyzes provided key and value classes and automatically + * chooses the most specific and effective serializer which it is aware about. Read + * the section about + * serialization in Chronicle Map manual for more information. + * + * @param keyClass class object used to infer key type and discover it's properties + * via reflection + * @param valueClass class object used to infer value type and discover it's properties + * via reflection + * @param key type of the maps, created by the returned builder + * @param value type of the maps, created by the returned builder + * @return a new builder for the given key and value classes + */ + public static ChronicleMapBuilder of( + @NotNull Class keyClass, @NotNull Class valueClass) { return new ChronicleMapBuilder(keyClass, valueClass); } - private static long roundUpMapHeaderSize(long headerSize) { - long roundUp = (headerSize + 127L) & ~127L; - if (roundUp - headerSize < 64) - roundUp += 128; - return roundUp; + ChronicleMapBuilder(Class keyClass, Class valueClass) { + super(keyClass, valueClass); } @Override - public ChronicleMapBuilder clone() { - try { - @SuppressWarnings("unchecked") - final ChronicleMapBuilder result = (ChronicleMapBuilder) super.clone(); - result.keyBuilder = keyBuilder.clone(); - result.valueBuilder = valueBuilder.clone(); - return result; - } catch (CloneNotSupportedException e) { - throw new AssertionError(e); - } + ChronicleMapBuilder self() { + return this; } /** * {@inheritDoc} * - *

    Example: if keys in your map(s) are English words in {@link String} form, keys size 10 - * (a bit more than average English word length) would be a good choice:

    {@code
    -     * ChronicleMap wordFrequencies = ChronicleMapBuilder
    -     *     .of(String.class, directClassFor(LongValue.class))
    -     *     .entries(50000)
    -     *     .keySize(10)
    -     *     // shouldn't specify valueSize(), because it is statically known
    -     *     .create();}
    - * (Note that 10 is chosen as key size in bytes despite strings in Java are UTF-16 encoded (and - * each character takes 2 bytes on-heap), because default off-heap {@link String} encoding is - * UTF-8 in {@code ChronicleMap}.) - * * @see #constantKeySizeBySample(Object) * @see #valueSize(int) * @see #entrySize(int) */ @Override public ChronicleMapBuilder keySize(int keySize) { - if (keySize <= 0) - throw new IllegalArgumentException("Key size must be positive"); - this.keySize = keySize; - return this; + return super.keySize(keySize); } /** * {@inheritDoc} * - *

    For example, if your keys are Git commit hashes:

    {@code
    -     * Map gitCommitMessagesByHash =
    -     *     ChronicleMapBuilder.of(byte[].class, String.class)
    -     *     .constantKeySizeBySample(new byte[20])
    -     *     .create();}
    - * * @see #keySize(int) * @see #constantValueSizeBySample(Object) */ @Override public ChronicleMapBuilder constantKeySizeBySample(K sampleKey) { - this.sampleKey = sampleKey; - return this; - } - - private int keySize() { - return keyOrValueSize(keySize, keyBuilder); + return super.constantKeySizeBySample(sampleKey); } /** @@ -223,8 +109,8 @@ public class ChronicleMapBuilder implements Cloneable, * created by this builder. If value size is always the same, call {@link * #constantValueSizeBySample(Object)} method instead of this one. * - *

    If value is a boxed primitive type or {@link Byteable} subclass, i. e. if value size is - * known statically, it is automatically accounted and shouldn't be specified by user. + *

    If value is a boxed primitive type, i. e. if value size is known statically, + * it is automatically accounted and shouldn't be specified by user. * *

    If value size varies moderately, specify the size higher than average, but lower than the * maximum possible, to minimize average memory overuse. If value size varies in a wide range, @@ -233,14 +119,13 @@ public class ChronicleMapBuilder implements Cloneable, * * @param valueSize number of bytes, taken by serialized form of values * @return this {@code ChronicleMapBuilder} back + * @see #constantValueSizeBySample(Object) * @see #keySize(int) * @see #entrySize(int) */ + @Override public ChronicleMapBuilder valueSize(int valueSize) { - if (valueSize <= 0) - throw new IllegalArgumentException("Value size must be positive"); - this.valueSize = valueSize; - return this; + return super.valueSize(valueSize); } /** @@ -259,426 +144,11 @@ public class ChronicleMapBuilder implements Cloneable, * @see #valueSize(int) * @see #constantKeySizeBySample(Object) */ - public ChronicleMapBuilder constantValueSizeBySample(V sampleValue) { - this.sampleValue = sampleValue; - return this; - } - - private int valueSize() { - return keyOrValueSize(valueSize, valueBuilder); - } - - private int keyOrValueSize(int configuredSize, SerializationBuilder builder) { - if (configuredSize > 0) - return configuredSize; - // this means size is statically known - if (builder.sizeMarshaller().sizeEncodingSize(0L) == 0) - return (int) builder.sizeMarshaller().readSize(EMPTY_BYTES); - return DEFAULT_KEY_OR_VALUE_SIZE; - } - - /** - * {@inheritDoc} - * - *

    In fully default case you can expect entry size to be about 256 bytes. But it is strongly - * recommended always to configure {@linkplain #keySize(int) key size} and {@linkplain - * #valueSize(int) value size}, if they couldn't be derived statically. - * - *

    If entry size is not configured explicitly by calling this method, it is computed based on - * {@linkplain #metaDataBytes(int) meta data bytes}, plus {@linkplain #keySize(int) key size}, - * plus {@linkplain #valueSize(int) value size}, plus a few bytes required by implementations, - * with respect to {@linkplain #entryAndValueAlignment(Alignment) alignment}. - * - *

    Note that the actual entrySize will be aligned to 4 (default {@linkplain - * #entryAndValueAlignment(Alignment) 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)}. - * - * @see #entryAndValueAlignment(Alignment) - * @see #entries(long) - */ - @Override - public ChronicleMapBuilder entrySize(int entrySize) { - if (entrySize <= 0) - throw new IllegalArgumentException("Entry Size must be positive"); - this.entrySize = entrySize; - return this; - } - - int entrySize() { - if (entrySize > 0) - return entryAndValueAlignment().alignSize(entrySize); - int size = metaDataBytes; - int keySize = keySize(); - size += keyBuilder.sizeMarshaller().sizeEncodingSize(keySize); - size += keySize; - if (useReplication()) - size += ReplicatedChronicleMap.ADDITIONAL_ENTRY_BYTES; - int valueSize = valueSize(); - size += valueBuilder.sizeMarshaller().sizeEncodingSize(valueSize); - size = entryAndValueAlignment().alignSize(size); - size += valueSize; - // Some cache line heuristics - for (int i = 1; i <= 4; i++) { - int bound = i * 64; - // Not more than 5% oversize. - // DEFAULT_KEY_OR_VALUE_SIZE and this heuristic are specially adjusted to produce - // entry size 256 -- the default value prior to keySize and valueSize -- when key and - // value sizes are both not static nor configured, in both vanilla and replicated modes. - if (size < bound && (bound - size) <= bound / 20) { - size = bound; - break; - } - } - return entryAndValueAlignment().alignSize(size); - } - - /** - * Configures alignment strategy of address in memory of entries and independently of address in - * memory of values within entries in ChronicleMaps, created by this builder. - * - *

    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 {@linkplain #entrySize(int) entry size} will be aligned according to this - * alignment. I. e. if you set {@code entrySize(20)} and {@link Alignment#OF_8_BYTES}, actual - * entry size will be 24 (20 aligned to 8 bytes). - * - *

    Default is {@link Alignment#OF_4_BYTES}. - * - * @param alignment the new alignment of the maps constructed by this builder - * @return this {@code ChronicleMapBuilder} back - */ - public ChronicleMapBuilder entryAndValueAlignment(Alignment alignment) { - this.alignment = alignment; - return this; - } - - Alignment entryAndValueAlignment() { - return alignment; - } @Override - public ChronicleMapBuilder entries(long entries) { - if (entries <= 0L) - throw new IllegalArgumentException("Entries should be positive, " + entries + " given"); - this.entries = entries; - return this; - } - - long entries() { - return entries; - } - - @Override - public ChronicleMapBuilder actualEntriesPerSegment(long actualEntriesPerSegment) { - if (actualEntriesPerSegment <= 0L) - throw new IllegalArgumentException("entries per segment should be positive, " + - actualEntriesPerSegment + " given"); - if (tooManyEntriesPerSegment(actualEntriesPerSegment)) - throw new IllegalArgumentException("max entries per segment is " + - VanillaIntIntMultiMap.MAX_CAPACITY + ", " + actualEntriesPerSegment + " given"); - this.actualEntriesPerSegment = actualEntriesPerSegment; - return this; - } - - private boolean tooManyEntriesPerSegment(long entriesPerSegment) { - return entriesPerSegment > VanillaIntIntMultiMap.MAX_CAPACITY; - } - - long actualEntriesPerSegment() { - if (actualEntriesPerSegment > 0L) - return actualEntriesPerSegment; - int actualSegments = actualSegments(); - long actualEntries = totalEntriesIfPoorDistribution(actualSegments); - long actualEntriesPerSegment = divideUpper(actualEntries, actualSegments); - if (tooManyEntriesPerSegment(actualEntriesPerSegment)) - throw new IllegalStateException("max entries per segment is " + - VanillaIntIntMultiMap.MAX_CAPACITY + " configured entries() and " + - "actualSegments() so that there should be " + actualEntriesPerSegment + - " entries per segment"); - return actualEntriesPerSegment; - } - - private long totalEntriesIfPoorDistribution(int segments) { - if (segments == 1) - return entries; - double poorDistEntriesScale = Math.log(segments) * entries; - if (segments <= 8) - return Math.min(entries * segments, - (long) (entries + poorDistEntriesScale * 0.08 + 64)); // 8% was min for tests - return (long) (entries + poorDistEntriesScale * 0.11 + 80); // 11% was min for tests - } - - @Override - public ChronicleMapBuilder minSegments(int minSegments) { - checkSegments(minSegments); - this.minSegments = minSegments; - return this; - } - - int minSegments() { - return Math.max(estimateSegments(), minSegments); - } - - private int estimateSegments() { - for (int power = 0; power < 7; power++) { - if (entries < (1L << (7 + (2 * power)))) - return 1 << power; - } - // Heuristic -- number of segments ~= cube root from number of entries seems optimal - int maxSegments = 65536; - for (int segments = 4; segments < maxSegments; segments <<= 1) { - if (((long) segments) * segments * segments >= entries) - return segments; - } - return maxSegments; - } - - @Override - public ChronicleMapBuilder actualSegments(int actualSegments) { - checkSegments(actualSegments); - this.actualSegments = actualSegments; - return this; - } - - private static void checkSegments(int segments) { - if (segments <= 0 || segments > MAX_SEGMENTS) - throw new IllegalArgumentException("segments should be positive, " + - segments + " given"); - if (segments > MAX_SEGMENTS) - throw new IllegalArgumentException("Max segments is " + MAX_SEGMENTS + ", " + - segments + " given"); - } - - int actualSegments() { - if (actualSegments > 0) - return actualSegments; - long shortMMapSegments = trySegments(VanillaShortShortMultiMap.MAX_CAPACITY, - MAX_SEGMENTS_TO_CHAISE_COMPACT_MULTI_MAPS); - if (shortMMapSegments > 0L) - return (int) shortMMapSegments; - long intMMapSegments = trySegments(VanillaIntIntMultiMap.MAX_CAPACITY, MAX_SEGMENTS); - if (intMMapSegments > 0L) - return (int) intMMapSegments; - throw new IllegalStateException("Max segments is " + MAX_SEGMENTS + ", configured so much" + - " entries() that builder automatically decided to use " + - (-intMMapSegments) + " segments"); - } - - private long trySegments(long maxSegmentCapacity, int maxSegments) { - long segments = divideUpper(totalEntriesIfPoorDistribution(minSegments()), - maxSegmentCapacity); - segments = Maths.nextPower2(Math.max(segments, minSegments()), 1L); - return segments <= maxSegments ? segments : -segments; - } - - private static long divideUpper(long dividend, long divisor) { - return ((dividend - 1L) / divisor) + 1L; - } - - private boolean canSupportShortShort() { - return entries > (long) minSegments() << 15; - } - - public ChronicleMapBuilder lockTimeOut(long lockTimeOut, TimeUnit unit) { - this.lockTimeOut = lockTimeOut; - lockTimeOutUnit = unit; - return this; - } - - long lockTimeOut(TimeUnit unit) { - return unit.convert(lockTimeOut, lockTimeOutUnit); - } - - @Override - public ChronicleMapBuilder errorListener(ChronicleHashErrorListener errorListener) { - this.errorListener = errorListener; - return this; - } - - ChronicleHashErrorListener errorListener() { - return errorListener; - } - - /** - * Configures if the maps created by this builder should return {@code null} instead of previous - * mapped values on {@link ChronicleMap#put(Object, Object) ChornicleMap.put(key, value)} - * calls. - * - *

    {@link Map#put(Object, Object) Map.put()} returns the previous value, functionality which - * is rarely used but fairly cheap for {@link HashMap}. In the case, for an off heap collection, - * it has to create a new object and deserialize the data from off-heap memory. It's expensive - * for something you probably don't use. - * - *

    By default, of cause, {@code ChronicleMap} conforms the general {@code Map} contract and - * returns the previous mapped value on {@code put()} calls. - * - * @param putReturnsNull {@code true} if you want {@link ChronicleMap#put(Object, Object) - * ChronicleMap.put()} to not return the value that was replaced but - * instead return {@code null} - * @return an instance of the map builder back - * @see #removeReturnsNull(boolean) - */ - public ChronicleMapBuilder putReturnsNull(boolean putReturnsNull) { - this.putReturnsNull = putReturnsNull; - return this; - } - - boolean putReturnsNull() { - return putReturnsNull; - } - - /** - * Configures if the maps created by this builder should return {@code null} instead of the last - * mapped value on {@link ChronicleMap#remove(Object) ChronicleMap.remove(key)} calls. - * - *

    {@link Map#remove(Object) Map.remove()} returns the previous value, functionality which is - * rarely used but fairly cheap for {@link HashMap}. In the case, for an off heap collection, it - * has to create a new object and deserialize the data from off-heap memory. It's expensive for - * something you probably don't use. - * - * @param removeReturnsNull {@code true} if you want {@link ChronicleMap#remove(Object) - * ChronicleMap.remove()} to not return the value of the removed entry - * but instead return {@code null} - * @return an instance of the map builder back - * @see #putReturnsNull(boolean) - */ - public ChronicleMapBuilder removeReturnsNull(boolean removeReturnsNull) { - this.removeReturnsNull = removeReturnsNull; - return this; - } - - boolean removeReturnsNull() { - return removeReturnsNull; - } - - @Override - public ChronicleMapBuilder metaDataBytes(int metaDataBytes) { - if ((metaDataBytes & 0xFF) != metaDataBytes) - throw new IllegalArgumentException("MetaDataBytes must be [0..255] was " + metaDataBytes); - this.metaDataBytes = metaDataBytes; - return this; - } - - - int metaDataBytes() { - return metaDataBytes; - } - - @Override - public String toString() { - return "ChronicleMapBuilder{" + - "actualSegments=" + pretty(actualSegments) + - ", minSegments=" + pretty(minSegments) + - ", actualEntriesPerSegment=" + pretty(actualEntriesPerSegment) + - ", keySize=" + pretty(keySize) + - ", sampleKeyForConstantSizeComputation=" + pretty(sampleKey) + - ", valueSize=" + pretty(valueSize) + - ", sampleValueForConstantSizeComputation=" + pretty(sampleValue) + - ", entrySize=" + pretty(entrySize) + - ", entryAndValueAlignment=" + entryAndValueAlignment() + - ", entries=" + entries() + - ", lockTimeOut=" + lockTimeOut + " " + lockTimeOutUnit + - ", metaDataBytes=" + metaDataBytes() + - ", errorListener=" + errorListener() + - ", putReturnsNull=" + putReturnsNull() + - ", removeReturnsNull=" + removeReturnsNull() + - ", timeProvider=" + timeProvider() + - ", bytesMarshallerFactory=" + pretty(bytesMarshallerFactory) + - ", objectSerializer=" + pretty(objectSerializer) + - ", keyBuilder=" + keyBuilder + - ", valueBuilder=" + valueBuilder + - ", eventListener=" + eventListener + - ", defaultValue=" + defaultValue + - ", defaultValueProvider=" + pretty(defaultValueProvider) + - '}'; - } - - private static String pretty(int value) { - return value > 0 ? value + "" : "not configured"; - } - - private static String pretty(Object obj) { - return obj != null ? obj + "" : "not configured"; - } - - @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") - @Override - public boolean equals(Object o) { - return builderEquals(this, o); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - public ChronicleMapBuilder replicators(byte identifier, ReplicationConfig... replicationConfigs) { - - this.identifier = identifier; - this.replicators.clear(); - for (ReplicationConfig replicationConfig : replicationConfigs) { - - Replicator replicator; - if (replicationConfig instanceof TcpReplicationConfig) { - replicator = Replicators.tcp((TcpReplicationConfig) replicationConfig); - } else if (replicationConfig instanceof UdpReplicationConfig) { - replicator = Replicators.udp((UdpReplicationConfig) replicationConfig); - } else - throw new UnsupportedOperationException(); - - - this.replicators.put(replicator.getClass(), replicator); - } - - return this; - } - - - public ChronicleMapBuilder channel(ChannelProvider.ChronicleChannel chronicleChannel) { - this.identifier = chronicleChannel.identifier(); - this.replicators.clear(); - replicators.put(chronicleChannel.getClass(), chronicleChannel); - - return this; - } - - @Override - public ChronicleMapBuilder disableReplication() { - identifier = -1; - replicators.clear(); - return this; - } - - @Override - public ChronicleMapBuilder timeProvider(TimeProvider timeProvider) { - this.timeProvider = timeProvider; - return this; - } - - TimeProvider timeProvider() { - return timeProvider; - } - - BytesMarshallerFactory bytesMarshallerFactory() { - return bytesMarshallerFactory == null ? - bytesMarshallerFactory = new VanillaBytesMarshallerFactory() : - bytesMarshallerFactory; - } - - @Override - public ChronicleMapBuilder bytesMarshallerFactory(BytesMarshallerFactory bytesMarshallerFactory) { - this.bytesMarshallerFactory = bytesMarshallerFactory; - return this; - } - - ObjectSerializer objectSerializer() { - return objectSerializer == null ? - objectSerializer = BytesMarshallableSerializer.create( - bytesMarshallerFactory(), JDKObjectSerializer.INSTANCE) : - objectSerializer; + public ChronicleMapBuilder constantValueSizeBySample( + @NotNull V sampleValue) { + return super.constantValueSizeBySample(sampleValue); } /** @@ -694,273 +164,68 @@ public class ChronicleMapBuilder implements Cloneable, * *

    This serializer is used to serialize both keys and values, if they both require this: * loosely typed, nullable, and custom {@linkplain #keyMarshaller(BytesMarshaller) key} and - * {@linkplain #valueMarshallerAndFactory(BytesMarshaller, ObjectFactory) value} marshallers are - * not configured. + * {@linkplain #valueMarshaller(BytesMarshaller) value} marshallers are not configured. */ @Override - public ChronicleMapBuilder objectSerializer(ObjectSerializer objectSerializer) { - this.objectSerializer = objectSerializer; - return this; + public ChronicleMapBuilder objectSerializer( + @NotNull ObjectSerializer objectSerializer) { + return super.objectSerializer(objectSerializer); } - /** * {@inheritDoc} * - *

    Example: + *

    If {@linkplain #valueMarshaller(BytesMarshaller) custom value marshaller} is configured, + * this configuration is unused, because it is incapsulated in {@link + * BytesMarshaller#read(Bytes)} method (without provided instance to read the data into), + * i. e. it's is the user-side responsibility. Actually this is just a convenience method + * supporting value marshaller configurations, made initially during {@link #of(Class, Class)} + * call, if the value class is {@link BytesMarshallable} or {@link Externalizable} subclass. + * + * @throws IllegalStateException if custom value marshaller is specified or value class is not + * either {@code BytesMarshallable} or {@code Externalizable} */ @Override - public ChronicleMapBuilder keyMarshaller(@NotNull BytesMarshaller keyMarshaller) { - keyBuilder.marshaller(keyMarshaller, null); - return this; - } - - @Override - public ChronicleMapBuilder immutableKeys() { - keyBuilder.instancesAreMutable(false); - return this; - } - - @Override - public ChronicleMapBuilder stateless(@NotNull StatelessBuilder statelessBuilder) { - this.statelessBuilder = statelessBuilder; - return this; - } - - @Override - public ChronicleMapBuilder file(File file) throws IOException { - this.file = file; - return this; - } - - public ChronicleMapBuilder valueMarshallerAndFactory( - @NotNull BytesMarshaller valueMarshaller, @NotNull ObjectFactory valueFactory) { - valueBuilder.marshaller(valueMarshaller, valueFactory); - return this; - } - - public ChronicleMapBuilder valueMarshallerAndFactory( - @NotNull AgileBytesMarshaller valueMarshaller, - @NotNull ObjectFactory valueFactory) { - valueBuilder.agileMarshaller(valueMarshaller, valueFactory); - return this; - } - - public ChronicleMapBuilder valueFactory(@NotNull ObjectFactory valueFactory) { - valueBuilder.factory(valueFactory); - return this; - } - - public ChronicleMapBuilder eventListener( - MapEventListener> eventListener) { - this.eventListener = eventListener; - return this; - } - - MapEventListener> eventListener() { - return eventListener; + public ChronicleMapBuilder valueDeserializationFactory( + @NotNull ObjectFactory valueDeserializationFactory) { + return super.valueDeserializationFactory(valueDeserializationFactory); } /** - * Specifies the value to be put for each key queried in {@link ChronicleMap#get get()} and - * {@link ChronicleMap#getUsing(Object, Object) getUsing()} methods, if the key is absent in the - * map. Then this default value is returned from query method. + * {@inheritDoc} * - *

    Setting default value to {@code null} is interpreted as map shouldn't put any default - * value for absent keys. This is by default. + *

    By default, the default value is set to {@code null}. * - *

    This configuration overrides any previous {@link #defaultValueProvider(DefaultValueProvider)} - * configuration to this {@code ChronicleMapBuilder}. - * - * @param defaultValue the default value to be put to the map for absent keys during {@code - * get()} and {@code getUsing()} calls and returned from these calls - * @return this builder object back + * @see #defaultValueProvider(DefaultValueProvider) */ + @Override public ChronicleMapBuilder defaultValue(V defaultValue) { - this.defaultValue = defaultValue; - this.defaultValueProvider = null; - return this; + return super.defaultValue(defaultValue); } /** - * Specifies the function to obtain a value for the key during {@link ChronicleMap#get get()} - * and {@link ChronicleMap#getUsing(Object, Object) getUsing()} calls, if the key is absent in - * the map. If the obtained value is non-null, it is put for the key in the map and then - * returned from current {@code get()} or {@code getUsing()} call. + * {@inheritDoc} * - *

    This configuration overrides any previous {@link #defaultValue(Object)} configuration to - * this {@code ChronicleMapBuilder}. + *

    By default, default value provider is not specified, {@link #defaultValue(Object) default + * value} is specified instead. * - * @param defaultValueProvider the strategy to obtain a default value by the absent key - * @return this builder object back + * @see #defaultValue(Object) */ - public ChronicleMapBuilder defaultValueProvider( - DefaultValueProvider defaultValueProvider) { - this.defaultValueProvider = defaultValueProvider; - return this; - } - - - /** - * Non-public because should be called only after {@link #preMapConstruction()} - */ - DefaultValueProvider defaultValueProvider() { - if (defaultValueProvider != null) - return defaultValueProvider; - if (defaultValue == null) - return NullValueProvider.INSTANCE; - Object originalValueWriter = valueBuilder.interop(); - Provider writerProvider = Provider.of(originalValueWriter.getClass()); - ThreadLocalCopies copies = writerProvider.getCopies(null); - Object valueWriter = writerProvider.get(copies, originalValueWriter); - MetaProvider metaWriterProvider = valueBuilder.metaInteropProvider(); - copies = metaWriterProvider.getCopies(copies); - MetaBytesWriter metaValueWriter = metaWriterProvider.get(copies, - valueBuilder.metaInterop(), valueWriter, defaultValue); - return new ConstantValueProvider(defaultValue, metaValueWriter, valueWriter); - } - - public ChronicleMap createWithFile(File file) throws IOException { - for (int i = 0; i < 10; i++) { - if (file.exists() && file.length() > 0) { - FileInputStream fis = new FileInputStream(file); - ObjectInputStream ois = new ObjectInputStream(fis); - try { - VanillaChronicleMap map = - (VanillaChronicleMap) ois.readObject(); - map.headerSize = roundUpMapHeaderSize(fis.getChannel().position()); - map.createMappedStoreAndSegments(file); - return establishReplication(map); - } catch (ClassNotFoundException e) { - throw new IOException(e); - } finally { - ois.close(); - } - } - if (file.createNewFile() || file.length() == 0) { - break; - } - try { - Thread.sleep(100); - } catch (InterruptedException e) { - throw new IOException(e); - } - } - // new file - if (!file.exists()) - throw new FileNotFoundException("Unable to create " + file); - - VanillaChronicleMap map = newMap(); - - FileOutputStream fos = new FileOutputStream(file); - ObjectOutputStream oos = new ObjectOutputStream(fos); - try { - oos.writeObject(map); - oos.flush(); - map.headerSize = roundUpMapHeaderSize(fos.getChannel().position()); - map.createMappedStoreAndSegments(file); - } finally { - oos.close(); - } - - return establishReplication(map); - } - - @Override - public ChronicleMap create() throws IOException { - - if (statelessBuilder != null) - return createStatelessMap(statelessBuilder); - - return (file != null) ? createWithFile(file) : createWithoutFile(); + public ChronicleMapBuilder defaultValueProvider( + @NotNull DefaultValueProvider defaultValueProvider) { + return super.defaultValueProvider(defaultValueProvider); } - private ChronicleMap createWithoutFile() throws IOException { - VanillaChronicleMap map = newMap(); - BytesStore bytesStore = new DirectStore(JDKObjectSerializer.INSTANCE, - map.sizeInBytes(), true); - map.createMappedStoreAndSegments(bytesStore); - return establishReplication(map); + public ChronicleMapBuilder valueMarshaller( + @NotNull BytesMarshaller valueMarshaller) { + valueBuilder.marshaller(valueMarshaller, null); + return this; } - private ChronicleMap createStatelessMap(StatelessBuilder statelessBuilder) throws IOException { - preMapConstruction(); - - final KeyValueSerializer keyValueSerializer - = new KeyValueSerializer(keyBuilder, valueBuilder); - - final Class kClass = keyBuilder.eClass; - final Class vClass = valueBuilder.eClass; - - return new StatelessChronicleMap( - keyValueSerializer, - statelessBuilder, - entrySize(), - kClass, - vClass); - } - - private VanillaChronicleMap newMap() throws IOException { - preMapConstruction(); - if (useReplication()) { - return new ReplicatedChronicleMap, - V, Object, MetaBytesWriter>(this); - } else { - return new VanillaChronicleMap, - V, Object, MetaBytesWriter>(this); - } - } - - private boolean useReplication() { - return identifier != -1; - } - - void preMapConstruction() { - keyBuilder.objectSerializer(objectSerializer()); - valueBuilder.objectSerializer(objectSerializer()); - - long maxSize = (long) entrySize() * figureBufferAllocationFactor(); - keyBuilder.maxSize(maxSize); - valueBuilder.maxSize(maxSize); - - if (sampleKey != null) - keyBuilder.constantSizeBySample(sampleKey); - if (sampleValue != null) - valueBuilder.constantSizeBySample(sampleValue); - } - - private ChronicleMap establishReplication(ChronicleMap map) - throws IOException { - if (map instanceof ReplicatedChronicleMap) { - ReplicatedChronicleMap result = (ReplicatedChronicleMap) map; - for (Replicator replicator : replicators.values()) { - Closeable token = replicator.applyTo(this, result, result, map); - if (replicators.size() == 1 && token.getClass() == UdpReplicator.class) { - 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" - ); - } - result.addCloseable(token); - } - } - return map; - } - - private int figureBufferAllocationFactor() { - // 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, entries() >> 10), - VanillaChronicleMap.MAX_ENTRY_OVERSIZE_FACTOR); - } - - public byte identifier() { - return identifier; + public ChronicleMapBuilder valueMarshaller( + @NotNull AgileBytesMarshaller valueMarshaller) { + valueBuilder.agileMarshaller(valueMarshaller, null); + return this; } } - diff --git a/src/main/java/net/openhft/chronicle/map/ConstantValueProvider.java b/src/main/java/net/openhft/chronicle/map/ConstantValueProvider.java index c4dedf426..d6f64e569 100644 --- a/src/main/java/net/openhft/chronicle/map/ConstantValueProvider.java +++ b/src/main/java/net/openhft/chronicle/map/ConstantValueProvider.java @@ -48,7 +48,7 @@ final class ConstantValueProvider implements DefaultValueProvider { } @Override - public V get(K key, V usingValue) { + public V get(K key) { return value; } } diff --git a/src/main/java/net/openhft/chronicle/map/DefaultValueProvider.java b/src/main/java/net/openhft/chronicle/map/DefaultValueProvider.java index f56d9428e..e21a6caef 100644 --- a/src/main/java/net/openhft/chronicle/map/DefaultValueProvider.java +++ b/src/main/java/net/openhft/chronicle/map/DefaultValueProvider.java @@ -22,26 +22,19 @@ import java.io.Serializable; /** * Default value computation strategy, used - * in {@link ChronicleMapBuilder#defaultValueProvider(DefaultValueProvider)} configuration. + * in {@link AbstractChronicleMapBuilder#defaultValueProvider(DefaultValueProvider)} configuration. * * @param map key class * @param map value class */ public interface DefaultValueProvider extends Serializable { /** - * Returns a value to be put and returned from {@link ChronicleMap#get(Object)} get()} or - * {@link ChronicleMap#getUsing(Object, Object)} call for the specified key, if it is absent - * in the map. - * - *

    If this method returns {@code null}, it isn't put for the key in the map. + * Returns a value to be put during {@link ChronicleMap#acquireUsing(Object, Object)} call + * for the specified key, if it is absent in the map. * * @param key key absent in the map - * @param usingValue an object provided as second parameter of current - * {@link ChronicleMap#getUsing(Object, Object)} call, or {@code null}, if this method is called - * within {@code get()} call. This object might be optionally initialized and returned back - * from this method. * @return value to be put for the specified key in the map, or {@code null}, if the key should * remain absent in the map */ - V get(K key, V usingValue); + V get(K key); } diff --git a/src/main/java/net/openhft/chronicle/map/MapEventListener.java b/src/main/java/net/openhft/chronicle/map/MapEventListener.java index c33986dbc..c7008e452 100644 --- a/src/main/java/net/openhft/chronicle/map/MapEventListener.java +++ b/src/main/java/net/openhft/chronicle/map/MapEventListener.java @@ -33,7 +33,7 @@ import java.io.Serializable; * class and override only methods corresponding the events you are interested in. * *

    To configure {@code MapEventListener} for {@code ChronicleMap}, use - * {@link ChronicleMapBuilder#eventListener(MapEventListener)} method. + * {@link AbstractChronicleMapBuilder#eventListener(MapEventListener)} method. * *

    {@link MapEventListeners} uninstantiable class contains several logging implementations and * the default {@linkplain MapEventListeners#nop() no-op implementation}. @@ -44,7 +44,7 @@ import java.io.Serializable; * @param key type of the maps, trackable by this event listener * @param value type of the maps, trackable by this event listener * @param {@code ChronicleMap} subtype, trackable by this event listener - * @see ChronicleMapBuilder#eventListener(MapEventListener) + * @see AbstractChronicleMapBuilder#eventListener(MapEventListener) * @see MapEventListeners */ public abstract class MapEventListener> @@ -75,7 +75,7 @@ public abstract class MapEventListener> * This method is call whenever a new value is put for the key in the map during calls of such * methods as {@link ChronicleMap#put put}, {@link ChronicleMap#putIfAbsent putIfAbsent}, * {@link ChronicleMap#replace(Object, Object, Object) replace}, etc. When a new value is - * {@linkplain ChronicleMapBuilder#defaultValue(Object) default} for the map or obtained during + * {@linkplain AbstractChronicleMapBuilder#defaultValue(Object) default} for the map or obtained during * {@link ChronicleMap#acquireUsing acquireUsing} call is put for the key, this method is called * as well. * diff --git a/src/main/java/net/openhft/chronicle/map/NullValueProvider.java b/src/main/java/net/openhft/chronicle/map/NullValueProvider.java index f37e7e185..9e79889e8 100644 --- a/src/main/java/net/openhft/chronicle/map/NullValueProvider.java +++ b/src/main/java/net/openhft/chronicle/map/NullValueProvider.java @@ -22,7 +22,7 @@ enum NullValueProvider implements DefaultValueProvider { INSTANCE; @Override - public Object get(Object key, Object usingValue) { + public Object get(Object key) { return null; } } diff --git a/src/main/java/net/openhft/chronicle/map/OffHeapUpdatableChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/OffHeapUpdatableChronicleMapBuilder.java new file mode 100644 index 000000000..50d0a1714 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/map/OffHeapUpdatableChronicleMapBuilder.java @@ -0,0 +1,98 @@ +/* + * 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.chronicle.map; + +import net.openhft.lang.model.Byteable; +import net.openhft.lang.model.DataValueClasses; +import org.jetbrains.annotations.NotNull; + +public final class OffHeapUpdatableChronicleMapBuilder + extends AbstractChronicleMapBuilder> { + + public static OffHeapUpdatableChronicleMapBuilder of( + @NotNull Class keyClass, @NotNull Class valueClass) { + if (!Byteable.class.isAssignableFrom(valueClass)) { + if (!valueClass.isInterface()) { + throw new IllegalArgumentException( + "Value class should be either Byteable subclass or interface," + + "allowing direct Byteable implementation generation"); + } + valueClass = DataValueClasses.directClassFor(valueClass); + } + return new OffHeapUpdatableChronicleMapBuilder(keyClass, valueClass); + } + + OffHeapUpdatableChronicleMapBuilder(Class keyClass, Class valueClass) { + super(keyClass, valueClass); + prepareValueBytesOnAcquire(new ZeroOutValueBytes(valueSize())); + } + + @Override + OffHeapUpdatableChronicleMapBuilder self() { + return this; + } + + /** + * {@inheritDoc} Also, it overrides any previous {@link #prepareValueBytesOnAcquire} + * configuration to this {@code OffHeapUpdatableChronicleMapBuilder}. + * + *

    By default, the default value is not specified, default {@linkplain + * #prepareValueBytesOnAcquire prepare value bytes routine} is specified instead. + * + * @see #defaultValueProvider(DefaultValueProvider) + * @see #prepareValueBytesOnAcquire(PrepareValueBytes) + */ + @Override + public OffHeapUpdatableChronicleMapBuilder defaultValue(V defaultValue) { + return super.defaultValue(defaultValue); + } + + /** + * {@inheritDoc} Also, it overrides any previous {@link #prepareValueBytesOnAcquire} + * configuration to this {@code OffHeapUpdatableChronicleMapBuilder}. + * + *

    By default, the default value provider is not specified, default {@linkplain + * #prepareValueBytesOnAcquire prepare value bytes routine} is specified instead. + * + * @see #defaultValue(Object) + * @see #prepareValueBytesOnAcquire(PrepareValueBytes) + */ + @Override + public OffHeapUpdatableChronicleMapBuilder defaultValueProvider( + @NotNull DefaultValueProvider defaultValueProvider) { + return super.defaultValueProvider(defaultValueProvider); + } + + /** + * Configures the procedure which is called on the bytes, which later the returned value is + * pointing to, if the key is absent, on {@link ChronicleMap#acquireUsing(Object, Object) + * acquireUsing()} call on maps, created by this builder. + * + *

    The default preparation callback zeroes out the value bytes. + * + * @param prepareValueBytes what to do with the value bytes before assigning them into the + * {@link Byteable} value to return from {@code acquireUsing()} call + * @return this builder back + * @see #defaultValue(Object) + * @see #defaultValueProvider(DefaultValueProvider) + */ + @Override + OffHeapUpdatableChronicleMapBuilder prepareValueBytesOnAcquire( + @NotNull PrepareValueBytes prepareValueBytes) { + return super.prepareValueBytesOnAcquire(prepareValueBytes); + } +} diff --git a/src/main/java/net/openhft/chronicle/map/PrepareValueBytes.java b/src/main/java/net/openhft/chronicle/map/PrepareValueBytes.java new file mode 100644 index 000000000..bcaca2470 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/map/PrepareValueBytes.java @@ -0,0 +1,26 @@ +/* + * 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.chronicle.map; + +import net.openhft.lang.io.Bytes; + +import java.io.Serializable; + +public interface PrepareValueBytes extends Serializable { + + void prepare(Bytes bytes, K key); +} diff --git a/src/main/java/net/openhft/chronicle/map/PrepareValueBytesAsWriter.java b/src/main/java/net/openhft/chronicle/map/PrepareValueBytesAsWriter.java new file mode 100644 index 000000000..7ed927c9d --- /dev/null +++ b/src/main/java/net/openhft/chronicle/map/PrepareValueBytesAsWriter.java @@ -0,0 +1,56 @@ +/* + * 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.chronicle.map; + +import net.openhft.chronicle.common.serialization.BytesWriter; +import net.openhft.chronicle.common.serialization.MetaBytesWriter; +import net.openhft.lang.io.Bytes; + +import java.io.Serializable; + +final class PrepareValueBytesAsWriter + implements BytesWriter, MetaBytesWriter>, + Serializable { + private static final long serialVersionUID = 0L; + private final PrepareValueBytes prepareValueBytes; + private final long valueSize; + + PrepareValueBytesAsWriter(PrepareValueBytes prepareValueBytes, long valueSize) { + this.prepareValueBytes = prepareValueBytes; + this.valueSize = valueSize; + } + + @Override + public long size(K k) { + return valueSize; + } + + @Override + public void write(Bytes bytes, K k) { + prepareValueBytes.prepare(bytes, k); + } + + @Override + public long size(PrepareValueBytesAsWriter writer, K k) { + return size(k); + } + + @Override + public void write(PrepareValueBytesAsWriter writer, Bytes bytes, K k) { + write(bytes, k); + } +} diff --git a/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java b/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java index 7b1618f47..9fecb2bfc 100644 --- a/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java @@ -106,7 +106,7 @@ class ReplicatedChronicleMap, private transient ModificationDelegator modificationDelegator; private transient long startOfModificationIterators; - public ReplicatedChronicleMap(@NotNull ChronicleMapBuilder builder) + public ReplicatedChronicleMap(@NotNull AbstractChronicleMapBuilder builder) throws IOException { super(builder); this.timeProvider = builder.timeProvider(); @@ -613,38 +613,39 @@ class ReplicatedChronicleMap, /** * @see VanillaChronicleMap.Segment#acquire */ - V acquire(ThreadLocalCopies copies, MKI metaKeyWriter, KI keyWriter, + V acquire(ThreadLocalCopies copies, MKI metaKeyInterop, KI keyInterop, K key, V usingValue, long hash2, boolean create, long timestamp) { if (create) writeLock(); else readLock(); try { - long keySize = metaKeyWriter.size(keyWriter, key); + long keySize = metaKeyInterop.size(keyInterop, key); MultiStoreBytes entry = acquireTmpBytes(); - long offset = searchKey(keyWriter, metaKeyWriter, key, keySize, hash2, entry, + long offset = searchKey(keyInterop, metaKeyInterop, key, keySize, hash2, entry, hashLookupLiveOnly); - if (offset >= 0) { - - // skip the timestamp, identifier and is deleted flag - entry.skip(ADDITIONAL_ENTRY_BYTES); - + if (offset >= 0L) { return onKeyPresentOnAcquire(copies, key, usingValue, offset, entry); } else { - boolean usingValuePassed = usingValue != null; - usingValue = tryObtainUsingValueOnAcquire(key, usingValue, create); - if (usingValue != null) { - // see VanillaChronicleMap.Segment.acquire() for explanation - // why `usingValue` is `create`. - offset = putEntryOnAcquire(copies, metaKeyWriter, keyWriter, key, keySize, - hash2, usingValue, create, timestamp); - incrementSize(); - if (usingValuePassed || !create) - notifyPut(offset, true, key, usingValue, posFromOffset(offset)); - return usingValue; - } else { + if (!create) return null; + if (defaultValueProvider != null) { + V defaultValue = defaultValueProvider.get(key); + copies = valueWriterProvider.getCopies(copies); + VW valueWriter = valueWriterProvider.get(copies, originalValueWriter); + copies = metaValueWriterProvider.getCopies(copies); + MetaBytesWriter metaValueWriter = metaValueWriterProvider.get( + copies, originalMetaValueWriter, valueWriter, defaultValue); + + offset = putEntry(metaKeyInterop, keyInterop, key, keySize, hash2, + localIdentifier, timestamp, hashLookupLiveOnly, + metaValueWriter, valueWriter, defaultValue); + } else { + offset = putEntry(metaKeyInterop, keyInterop, key, keySize, hash2, + localIdentifier, timestamp, hashLookupLiveOnly, + prepareValueBytesAsWriter, null, key); } + return onKeyAbsentOnAcquire(copies, key, keySize, usingValue, offset); } } finally { if (create) @@ -654,15 +655,11 @@ class ReplicatedChronicleMap, } } - private long putEntryOnAcquire(ThreadLocalCopies copies, - MKI metaKeyWriter, KI keyWriter, K key, - long keySize, long hash2, V value, boolean usingValue, - long timestamp) { - return putEntry(copies, metaKeyWriter, keyWriter, key, keySize, hash2, value, - usingValue, localIdentifier, timestamp, hashLookupLiveOnly); + @Override + void skipReplicationBytes(Bytes bytes) { + bytes.skip(ADDITIONAL_ENTRY_BYTES); } - /** * called from a remote node as part of replication */ @@ -858,7 +855,7 @@ class ReplicatedChronicleMap, // key is not found long offset = putEntry(copies, metaKeyWriter, keyWriter, key, keySize, hash2, value, - false, identifier, timestamp, hashLookup); + identifier, timestamp, hashLookup); incrementSize(); notifyPut(offset, true, key, value, posFromOffset(offset)); return null; @@ -905,9 +902,6 @@ class ReplicatedChronicleMap, * @param hash2 a hash of the {@code keyBytes}. Caller was searching for the * key in the {@code searchedHashLookup} using this hash. * @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 Byteable} interface, {@code - * false} if it should put itself * @param identifier the identifier of the outer CHM node * @param timestamp the timestamp when the entry was put (this could be later if * it was a remote put) this method is called only from usual @@ -916,30 +910,25 @@ class ReplicatedChronicleMap, * @return offset of the written entry in the Segment bytes * @see VanillaChronicleMap.Segment#putEntry */ - private long putEntry(ThreadLocalCopies copies, MKI metaKeyWriter, - KI keyWriter, K key, long keySize, long hash2, V value, - boolean usingValue, final int identifier, final long timestamp, - MultiMap searchedHashLookup) { + private long putEntry(ThreadLocalCopies copies, + MKI metaKeyInterop, KI keyInterop, K key, long keySize, long hash2, + V value, + int identifier, long timestamp, MultiMap searchedHashLookup) { + copies = valueWriterProvider.getCopies(copies); + VW valueWriter = valueWriterProvider.get(copies, originalValueWriter); + copies = metaValueWriterProvider.getCopies(copies); + MetaBytesWriter metaValueWriter = metaValueWriterProvider.get( + copies, originalMetaValueWriter, valueWriter, value); - // "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 valueSize; - MetaBytesWriter metaValueWriter = null; - VW valueWriter = null; - Byteable valueAsByteable = null; - if (!byteableValue) { - copies = valueWriterProvider.getCopies(copies); - valueWriter = valueWriterProvider.get(copies, originalValueWriter); - copies = metaValueWriterProvider.getCopies(copies); - metaValueWriter = metaValueWriterProvider.get( - copies, originalMetaValueWriter, valueWriter, value); - valueSize = metaValueWriter.size(valueWriter, value); - } else { - valueAsByteable = (Byteable) value; - valueSize = valueAsByteable.maxSize(); - } + return putEntry(metaKeyInterop, keyInterop, key, keySize, hash2, + identifier, timestamp, searchedHashLookup, metaValueWriter, valueWriter, value); + } + private long putEntry( + MKI metaKeyInterop, KI keyInterop, K key, long keySize, long hash2, + final int identifier, final long timestamp, MultiMap searchedHashLookup, + MetaBytesWriter metaElemWriter, EW elemWriter, E elem) { + long valueSize = metaElemWriter.size(elemWriter, elem); long entrySize = entrySize(keySize, valueSize); long pos = alloc(inBlocks(entrySize)); long offset = offsetFromPos(pos); @@ -947,14 +936,15 @@ class ReplicatedChronicleMap, NativeBytes entry = entry(offset); keySizeMarshaller.writeSize(entry, keySize); - metaKeyWriter.write(keyWriter, entry, key); + metaKeyInterop.write(keyInterop, entry, key); entry.writeLong(timestamp); entry.writeByte(identifier); entry.writeBoolean(false); - writeValueOnPutEntry(valueSize, metaValueWriter, valueWriter, value, valueAsByteable, - entry); + valueSizeMarshaller.writeSize(entry, valueSize); + alignment.alignPositionAddr(entry); + metaElemWriter.write(elemWriter, entry, elem); // we have to add it both to the live if (searchedHashLookup == hashLookupLiveAndDeleted) { @@ -1573,7 +1563,7 @@ abstract class Replicator { * already been applied to a map (or the specified number of * maps) */ - protected abstract Closeable applyTo(ChronicleMapBuilder builder, + protected abstract Closeable applyTo(AbstractChronicleMapBuilder builder, Replica map, Replica.EntryExternalizable entryExternalizable, final ChronicleMap chronicleMap) throws IOException; @@ -1589,7 +1579,7 @@ final class Replicators { return new Replicator() { @Override - protected Closeable applyTo(ChronicleMapBuilder builder, + protected Closeable applyTo(AbstractChronicleMapBuilder builder, Replica replica, Replica.EntryExternalizable entryExternalizable, final ChronicleMap chronicleMap) throws IOException { @@ -1610,7 +1600,7 @@ final class Replicators { return new Replicator() { @Override - protected Closeable applyTo(ChronicleMapBuilder builder, + protected Closeable applyTo(AbstractChronicleMapBuilder builder, Replica map, Replica.EntryExternalizable entryExternalizable, final ChronicleMap chronicleMap) throws IOException { return new UdpReplicator(map, entryExternalizable, replicationConfig, diff --git a/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java b/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java index a235cc686..47f52f9c3 100644 --- a/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java @@ -78,12 +78,9 @@ final class SerializationBuilder implements Cloneable { this.eClass = eClass; instancesAreMutable = instancesAreMutable(eClass); - ObjectFactory factory = null; - if (role == Role.VALUE) { - factory = concreteClass(eClass) && marshallerUseFactory(eClass) ? - new NewInstanceObjectFactory(eClass) : - (ObjectFactory) NullObjectFactory.of(); - } + ObjectFactory factory = concreteClass(eClass) && marshallerUseFactory(eClass) ? + new NewInstanceObjectFactory(eClass) : + NullObjectFactory.of(); if (concreteClass(eClass) && Byteable.class.isAssignableFrom(eClass)) { agileMarshaller(ByteableMarshaller.of((Class) eClass), factory); @@ -109,7 +106,7 @@ final class SerializationBuilder implements Cloneable { if (marshaller != null) marshaller(marshaller, factory); } - if (role == Role.VALUE && concreteClass(eClass) && marshallerUseFactory(eClass)) { + if (concreteClass(eClass) && marshallerUseFactory(eClass)) { factory(factory); } } diff --git a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java index 50e2beaae..03151ecc3 100644 --- a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java @@ -390,12 +390,12 @@ class StatelessChronicleMap implements ChronicleMap, Closeable { } - public synchronized V getUsing(K key, V value) { + public synchronized V getUsing(K key, V usingValue) { throw new UnsupportedOperationException("getUsing is not supported for stateless clients"); } - public synchronized V acquireUsing(K key, V value) { + public synchronized V acquireUsing(K key, V usingValue) { throw new UnsupportedOperationException("getUsing is not supported for stateless clients"); } diff --git a/src/main/java/net/openhft/chronicle/map/UdpReplicator.java b/src/main/java/net/openhft/chronicle/map/UdpReplicator.java index 4af947ed1..8bd9b889e 100644 --- a/src/main/java/net/openhft/chronicle/map/UdpReplicator.java +++ b/src/main/java/net/openhft/chronicle/map/UdpReplicator.java @@ -49,7 +49,7 @@ class UdpReplicator extends UdpChannelReplicator implements Replica.Modification super(replicationConfig, serializedEntrySize, replica.identifier()); Replica.ModificationIterator modificationIterator = replica.acquireModificationIterator( - ChronicleMapBuilder.UDP_REPLICATION_MODIFICATION_ITERATOR_ID, this); + AbstractChronicleMapBuilder.UDP_REPLICATION_MODIFICATION_ITERATOR_ID, this); setReader(new UdpSocketChannelEntryReader(serializedEntrySize, entryExternalizable)); diff --git a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java index b0b386d6f..8eed059e9 100644 --- a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java @@ -22,7 +22,6 @@ import net.openhft.chronicle.common.ChronicleHashErrorListener; import net.openhft.chronicle.common.serialization.*; import net.openhft.chronicle.common.threadlocal.Provider; import net.openhft.chronicle.common.threadlocal.ThreadLocalCopies; -import net.openhft.lang.Maths; import net.openhft.lang.collection.DirectBitSet; import net.openhft.lang.collection.SingleThreadedDirectBitSet; import net.openhft.lang.io.*; @@ -77,6 +76,7 @@ class VanillaChronicleMap, final MetaProvider metaValueWriterProvider; final ObjectFactory valueFactory; final DefaultValueProvider defaultValueProvider; + final PrepareValueBytesAsWriter prepareValueBytesAsWriter; final int metaDataBytes; // private final int replicas; @@ -100,7 +100,7 @@ class VanillaChronicleMap, private final HashSplitting hashSplitting; - public VanillaChronicleMap(ChronicleMapBuilder builder) throws IOException { + public VanillaChronicleMap(AbstractChronicleMapBuilder builder) throws IOException { SerializationBuilder keyBuilder = builder.keyBuilder; kClass = keyBuilder.eClass; @@ -122,6 +122,7 @@ class VanillaChronicleMap, metaValueWriterProvider = (MetaProvider) valueBuilder.metaInteropProvider(); valueFactory = valueBuilder.factory(); defaultValueProvider = builder.defaultValueProvider(); + prepareValueBytesAsWriter = builder.prepareValueBytesAsWriter(); lockTimeOutNS = builder.lockTimeOut(TimeUnit.NANOSECONDS); @@ -355,13 +356,13 @@ class VanillaChronicleMap, } @Override - public V getUsing(K key, V value) { - return lookupUsing(key, value, false); + public V getUsing(K key, V usingValue) { + return lookupUsing(key, usingValue, false); } @Override - public V acquireUsing(K key, V value) { - return lookupUsing(key, value, true); + public V acquireUsing(K key, V usingValue) { + return lookupUsing(key, usingValue, true); } V lookupUsing(K key, V value, boolean create) { @@ -712,18 +713,6 @@ class VanillaChronicleMap, 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 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(ThreadLocalCopies copies, MKI metaKeyInterop, KI keyInterop, K key, V usingValue, long hash2, boolean create) { if (create) @@ -738,22 +727,23 @@ class VanillaChronicleMap, if (offset >= 0L) { return onKeyPresentOnAcquire(copies, key, usingValue, offset, entry); } else { - boolean usingValuePassed = usingValue != null; - usingValue = tryObtainUsingValueOnAcquire(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(copies, metaKeyInterop, keyInterop, key, keySize, - usingValue, create); - incrementSize(); - if (usingValuePassed || !create) - notifyPut(offset, true, key, usingValue, posFromOffset(offset)); - return usingValue; - } else { + if (!create) return null; + if (defaultValueProvider != null) { + V defaultValue = defaultValueProvider.get(key); + copies = valueWriterProvider.getCopies(copies); + VW valueWriter = valueWriterProvider.get(copies, originalValueWriter); + copies = metaValueWriterProvider.getCopies(copies); + MetaBytesWriter metaValueWriter = metaValueWriterProvider.get( + copies, originalMetaValueWriter, valueWriter, defaultValue); + + offset = putEntry(metaKeyInterop, keyInterop, key, keySize, + metaValueWriter, valueWriter, defaultValue); + } else { + offset = putEntry(metaKeyInterop, keyInterop, key, keySize, + prepareValueBytesAsWriter, null, key); } + return onKeyAbsentOnAcquire(copies, key, keySize, usingValue, offset); } } finally { if (create) @@ -781,36 +771,25 @@ class VanillaChronicleMap, V onKeyPresentOnAcquire(ThreadLocalCopies copies, K key, V usingValue, long offset, MultiStoreBytes entry) { + skipReplicationBytes(entry); V v = readValue(copies, entry, usingValue); notifyGet(offset, key, v); return v; } - V tryObtainUsingValueOnAcquire(K key, V usingValue, boolean create) { - if (create) { - if (usingValue != null) { - return usingValue; - } else { - try { - usingValue = valueFactory.create(); - if (usingValue == null) { - throw new IllegalStateException("acquireUsing() method requires" + - "valueFactory.create() result to be non-null. " + - "By default it is so when value class is" + - "a Byteable/BytesMarshallable/Externalizable subclass." + - "Note that acquireUsing() anyway makes very little sense " + - "when value class is not a Byteable subclass."); - } - return usingValue; - } catch (Exception e) { - throw new IllegalStateException(e); - } - } - } else { - if (usingValue instanceof Byteable) - ((Byteable) usingValue).bytes(null, 0L); - return defaultValueProvider.get(key, usingValue); - } + V onKeyAbsentOnAcquire(ThreadLocalCopies copies, K key, long keySize, V usingValue, + long offset) { + NativeBytes entry = entry(offset); + entry.skip(keySizeMarshaller.sizeEncodingSize(keySize) + keySize); + skipReplicationBytes(entry); + V v = readValue(copies, entry, usingValue); + notifyPut(offset, true, key, v, posFromOffset(offset)); + notifyGet(offset, key, v); + return v; + } + + void skipReplicationBytes(Bytes bytes) { + // don't skip } V put(ThreadLocalCopies copies, MKI metaKeyInterop, KI keyInterop, K key, V value, @@ -834,8 +813,7 @@ class VanillaChronicleMap, } } // key is not found - long offset = putEntry(copies, metaKeyInterop, keyInterop, key, keySize, value, - false); + long offset = putEntry(copies, metaKeyInterop, keyInterop, key, keySize, value); incrementSize(); notifyPut(offset, true, key, value, posFromOffset(offset)); return null; @@ -860,37 +838,22 @@ class VanillaChronicleMap, return prevValue; } - /** - * Puts entry. If {@code value} implements {@link Byteable} interface and {@code usingValue} is {@code - * true}, the value is backed with the bytes of this entry. - * - * @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 Byteable} interface, {@code false} if it should put itself - * @return offset of the written entry in the Segment bytes - */ private long putEntry(ThreadLocalCopies copies, MKI metaKeyInterop, KI keyInterop, K key, - long keySize, V value, boolean usingValue) { + long keySize, V value) { + copies = valueWriterProvider.getCopies(copies); + VW valueWriter = valueWriterProvider.get(copies, originalValueWriter); + copies = metaValueWriterProvider.getCopies(copies); + MetaBytesWriter metaValueWriter = metaValueWriterProvider.get( + copies, originalMetaValueWriter, valueWriter, value); - // "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 valueSize; - MetaBytesWriter metaValueWriter = null; - VW valueWriter = null; - Byteable valueAsByteable = null; - if (!byteableValue) { - copies = valueWriterProvider.getCopies(copies); - valueWriter = valueWriterProvider.get(copies, originalValueWriter); - copies = metaValueWriterProvider.getCopies(copies); - metaValueWriter = metaValueWriterProvider.get( - copies, originalMetaValueWriter, valueWriter, value); - valueSize = metaValueWriter.size(valueWriter, value); - } else { - valueAsByteable = (Byteable) value; - valueSize = valueAsByteable.maxSize(); - } + return putEntry(metaKeyInterop, keyInterop, key, keySize, + metaValueWriter, valueWriter, value); + } + private long putEntry(MKI metaKeyInterop, KI keyInterop, K key, long keySize, + MetaBytesWriter metaElemWriter, EW elemWriter, + E elem) { + long valueSize = metaElemWriter.size(elemWriter, elem); long entrySize = entrySize(keySize, valueSize); long pos = alloc(inBlocks(entrySize)); long offset = offsetFromPos(pos); @@ -899,27 +862,13 @@ class VanillaChronicleMap, keySizeMarshaller.writeSize(entry, keySize); metaKeyInterop.write(keyInterop, entry, key); - - writeValueOnPutEntry(valueSize, metaValueWriter, valueWriter, value, valueAsByteable, - entry); - hashLookup.putAfterFailedSearch(pos); - return offset; - } - - void writeValueOnPutEntry(long valueSize, MetaBytesWriter metaValueWriter, - VW valueWriter, V value, Byteable valueAsByteable, - NativeBytes entry) { valueSizeMarshaller.writeSize(entry, valueSize); alignment.alignPositionAddr(entry); + metaElemWriter.write(elemWriter, entry, elem); - if (metaValueWriter != null) { - metaValueWriter.write(valueWriter, entry, value); - } else { - assert valueAsByteable != null; - long valueOffset = entry.positionAddr() - bytes.address(); - bytes.zeroOut(valueOffset, valueOffset + valueSize); - valueAsByteable.bytes(bytes, valueOffset); - } + hashLookup.putAfterFailedSearch(pos); + + return offset; } void clearMetaData(long offset) { diff --git a/src/main/java/net/openhft/chronicle/map/ZeroOutValueBytes.java b/src/main/java/net/openhft/chronicle/map/ZeroOutValueBytes.java new file mode 100644 index 000000000..67a4ea96f --- /dev/null +++ b/src/main/java/net/openhft/chronicle/map/ZeroOutValueBytes.java @@ -0,0 +1,34 @@ +/* + * 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.chronicle.map; + +import net.openhft.lang.io.Bytes; + +final class ZeroOutValueBytes implements PrepareValueBytes { + private static final long serialVersionUID = 0L; + private final long valueSize; + + ZeroOutValueBytes(long valueSize) { + this.valueSize = valueSize; + } + + @Override + public void prepare(Bytes bytes, K key) { + long pos = bytes.position(); + bytes.zeroOut(pos, pos + valueSize); + } +} diff --git a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java index e81221168..6bf45441d 100644 --- a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java +++ b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java @@ -25,6 +25,7 @@ import net.openhft.chronicle.common.TimeProvider; import net.openhft.chronicle.map.*; import net.openhft.lang.io.serialization.BytesMarshaller; import net.openhft.lang.io.serialization.BytesMarshallerFactory; +import net.openhft.lang.io.serialization.ObjectFactory; import net.openhft.lang.io.serialization.ObjectSerializer; import net.openhft.lang.io.serialization.impl.NullObjectFactory; import org.jetbrains.annotations.NotNull; @@ -52,8 +53,7 @@ public class ChronicleSetBuilder ChronicleSetBuilder(Class keyClass) { chronicleMapBuilder = ChronicleMapBuilder.of(keyClass, DummyValue.class) .entryAndValueAlignment(Alignment.NO_ALIGNMENT) - .valueMarshallerAndFactory(DummyValueMarshaller.INSTANCE, - NullObjectFactory.of()); + .valueMarshaller(DummyValueMarshaller.INSTANCE); } public static ChronicleSetBuilder of(Class keyClass) { @@ -244,6 +244,23 @@ public class ChronicleSetBuilder return this; } + /** + * {@inheritDoc} + * + *

    Actually this is just a convenience method supporting key marshaller configurations, made + * initially during {@link #of(Class)} call. Because if you {@linkplain + * #keyMarshaller(BytesMarshaller) configure} own custom key marshaller, this method doesn't + * take any effect on the maps constructed by this builder. + * + * @see #of(Class) + */ + @Override + public ChronicleSetBuilder keyDeserializationFactory( + @NotNull ObjectFactory keyDeserializationFactory) { + chronicleMapBuilder.keyDeserializationFactory(keyDeserializationFactory); + return this; + } + @Override public ChronicleSetBuilder immutableKeys() { chronicleMapBuilder.immutableKeys(); diff --git a/src/main/java/net/openhft/chronicle/set/package-info.java b/src/main/java/net/openhft/chronicle/set/package-info.java new file mode 100644 index 000000000..181cfdf71 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/set/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * Contains {@link net.openhft.chronicle.set.ChronicleSet} interface and + * {@link net.openhft.chronicle.set.ChronicleSetBuilder} class. + */ +package net.openhft.chronicle.set; \ No newline at end of file diff --git a/src/test/java/net/openhft/chronicle/map/AcquireGetUsingMain.java b/src/test/java/net/openhft/chronicle/map/AcquireGetUsingMain.java index db8ae2328..befa4148a 100644 --- a/src/test/java/net/openhft/chronicle/map/AcquireGetUsingMain.java +++ b/src/test/java/net/openhft/chronicle/map/AcquireGetUsingMain.java @@ -29,7 +29,7 @@ import static org.junit.Assert.fail; public class AcquireGetUsingMain { public static void main(String[] args) throws Exception { File file = new File(System.getProperty("java.io.tmpdir") + "/test1"); - ChronicleMap theSharedMap = ChronicleMapBuilder.of(String.class, Data.class).file(file).create(); + ChronicleMap theSharedMap = OffHeapUpdatableChronicleMapBuilder.of(String.class, Data.class).file(file).create(); Data data = DataValueClasses.newDirectReference(Data.class); String processType = "testkey"; if (theSharedMap.getUsing(processType, data) == null) { diff --git a/src/test/java/net/openhft/chronicle/map/CHMLatencyTestMain.java b/src/test/java/net/openhft/chronicle/map/CHMLatencyTestMain.java index 6b22de110..a9e8c3fd6 100644 --- a/src/test/java/net/openhft/chronicle/map/CHMLatencyTestMain.java +++ b/src/test/java/net/openhft/chronicle/map/CHMLatencyTestMain.java @@ -75,7 +75,7 @@ public class CHMLatencyTestMain { File file = File.createTempFile("testCHMLatency", "deleteme"); // File file = new File("testCHMLatency.deleteme"); file.delete(); - ChronicleMap countersMap = ChronicleMapBuilder.of(LongValue.class, LongValue.class) + ChronicleMap countersMap = OffHeapUpdatableChronicleMapBuilder.of(LongValue.class, LongValue.class) .entries(KEYS) .entrySize(24).create(); diff --git a/src/test/java/net/openhft/chronicle/map/CHMTest5.java b/src/test/java/net/openhft/chronicle/map/CHMTest5.java index 7520aeb13..948d1a796 100644 --- a/src/test/java/net/openhft/chronicle/map/CHMTest5.java +++ b/src/test/java/net/openhft/chronicle/map/CHMTest5.java @@ -34,8 +34,8 @@ public class CHMTest5 { public static void main(String[] args) throws Exception { //First create (or access if already created) the shared map - ChronicleMapBuilder builder = - ChronicleMapBuilder.of(String.class, CHMTest5Data.class) + OffHeapUpdatableChronicleMapBuilder builder = + OffHeapUpdatableChronicleMapBuilder.of(String.class, CHMTest5Data.class) .entries(1000); //// don't include this, just to check it is as expected. diff --git a/src/test/java/net/openhft/chronicle/map/ChronicleMapTest.java b/src/test/java/net/openhft/chronicle/map/ChronicleMapTest.java index 5dfd1d01f..a475bb424 100644 --- a/src/test/java/net/openhft/chronicle/map/ChronicleMapTest.java +++ b/src/test/java/net/openhft/chronicle/map/ChronicleMapTest.java @@ -110,13 +110,12 @@ public class ChronicleMapTest { private static ChronicleMap getSharedMap( long entries, int segments, int entrySize, Alignment alignment) throws IOException { - return ChronicleMapBuilder.of(CharSequence.class, LongValue.class) + return OffHeapUpdatableChronicleMapBuilder.of(CharSequence.class, LongValue.class) .entries(entries) .minSegments(segments) .entrySize(entrySize) .entryAndValueAlignment(alignment) - .valueMarshallerAndFactory(ByteableLongValueMarshaller.INSTANCE, - DirectLongValueFactory.INSTANCE).create(); + .create(); } private static void printStatus() { @@ -578,13 +577,11 @@ public class ChronicleMapTest { // int runs = Integer.getInteger("runs", 10); for (int runs : new int[]{10, 50, 250, 500, 1000, 2500}) { final long entries = runs * 1000 * 1000L; - ChronicleMapBuilder builder = ChronicleMapBuilder + OffHeapUpdatableChronicleMapBuilder builder = OffHeapUpdatableChronicleMapBuilder .of(CharSequence.class, IntValue.class) .entries(entries) .minSegments(1024) - .entrySize(20) - .valueMarshallerAndFactory(ByteableIntValueMarshaller.INSTANCE, - DirectIntValueFactory.INSTANCE); + .entrySize(20); final ChronicleMap map = builder.create(); diff --git a/src/test/java/net/openhft/chronicle/map/EntryCountMapTest.java b/src/test/java/net/openhft/chronicle/map/EntryCountMapTest.java index c6ca7641d..1440e0305 100644 --- a/src/test/java/net/openhft/chronicle/map/EntryCountMapTest.java +++ b/src/test/java/net/openhft/chronicle/map/EntryCountMapTest.java @@ -18,6 +18,7 @@ package net.openhft.chronicle.map; +import net.openhft.lang.model.DataValueClasses; import net.openhft.lang.values.LongValue; import org.junit.Ignore; import org.junit.Test; @@ -28,6 +29,7 @@ import java.util.Arrays; import java.util.Random; import static net.openhft.chronicle.map.Alignment.OF_4_BYTES; +import static net.openhft.lang.model.DataValueClasses.newDirectReference; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -138,7 +140,7 @@ public class EntryCountMapTest { private void testEntriesMaxSize(int segments, int minSize, int maxSize, int seed) throws IOException { int counter = minSize + new Random(seed + segments * 1000).nextInt(9999); ChronicleMap map = getSharedMap(minSize, segments, 32); - LongValue longValue = DirectLongValueFactory.INSTANCE.create(); + LongValue longValue = newDirectReference(LongValue.class); try { for (int j = 0; j < maxSize * 12 / 10 + 300; j++) { String key = "key:" + counter++; @@ -195,13 +197,11 @@ public class EntryCountMapTest { private static ChronicleMap getSharedMap( long entries, int segments, int entrySize, Alignment alignment) throws IOException { - ChronicleMapBuilder mapBuilder = ChronicleMapBuilder.of(CharSequence.class, LongValue.class) + OffHeapUpdatableChronicleMapBuilder mapBuilder = OffHeapUpdatableChronicleMapBuilder.of(CharSequence.class, LongValue.class) .entries(entries) .minSegments(segments) .entrySize(entrySize) .entryAndValueAlignment(alignment) - .valueMarshallerAndFactory(ByteableLongValueMarshaller.INSTANCE, - DirectLongValueFactory.INSTANCE) .file(getPersistenceFile()); return mapBuilder.create(); } diff --git a/src/test/java/net/openhft/chronicle/map/ReplicationCheckingMap.java b/src/test/java/net/openhft/chronicle/map/ReplicationCheckingMap.java index 886219c97..255308c93 100644 --- a/src/test/java/net/openhft/chronicle/map/ReplicationCheckingMap.java +++ b/src/test/java/net/openhft/chronicle/map/ReplicationCheckingMap.java @@ -242,13 +242,13 @@ public class ReplicationCheckingMap implements ChronicleMap { } @Override - public V getUsing(K key, V value) { - return map1.getUsing(key, value); + public V getUsing(K key, V usingValue) { + return map1.getUsing(key, usingValue); } @Override - public V acquireUsing(K key, V value) { - return map1.acquireUsing(key, value); + public V acquireUsing(K key, V usingValue) { + return map1.acquireUsing(key, usingValue); } @Override diff --git a/src/test/java/net/openhft/chronicle/map/fromdocs/OpenJDKAndHashMapExamplesTest.java b/src/test/java/net/openhft/chronicle/map/fromdocs/OpenJDKAndHashMapExamplesTest.java index 4cb1b3a38..35dc2aa58 100644 --- a/src/test/java/net/openhft/chronicle/map/fromdocs/OpenJDKAndHashMapExamplesTest.java +++ b/src/test/java/net/openhft/chronicle/map/fromdocs/OpenJDKAndHashMapExamplesTest.java @@ -21,6 +21,7 @@ package net.openhft.chronicle.map.fromdocs; import net.openhft.affinity.AffinitySupport; import net.openhft.chronicle.map.ChronicleMap; import net.openhft.chronicle.map.ChronicleMapBuilder; +import net.openhft.chronicle.map.OffHeapUpdatableChronicleMapBuilder; import net.openhft.lang.model.DataValueClasses; import org.junit.Test; @@ -55,8 +56,8 @@ public class OpenJDKAndHashMapExamplesTest { File file = new File(TMP + "/chm-myBondPortfolioCHM-" + System.nanoTime()); file.deleteOnExit(); - ChronicleMap chm = ChronicleMapBuilder - .of(String.class, directClassFor(BondVOInterface.class)) + ChronicleMap chm = OffHeapUpdatableChronicleMapBuilder + .of(String.class, BondVOInterface.class) .keySize(10) .file(file) .create(); @@ -76,8 +77,8 @@ public class OpenJDKAndHashMapExamplesTest { mpx1030.setAskPx(109.7); mpx1030.setBidPx(107.6); - ChronicleMap chmB = ChronicleMapBuilder - .of(String.class, directClassFor(BondVOInterface.class)) + ChronicleMap chmB = OffHeapUpdatableChronicleMapBuilder + .of(String.class, BondVOInterface.class) .keySize(10) .file(file) .create(); diff --git a/src/test/java/net/openhft/chronicle/map/jrs166/map/StatelessChronicleMapTest.java b/src/test/java/net/openhft/chronicle/map/jrs166/map/StatelessChronicleMapTest.java index 04dada102..520c2d31e 100644 --- a/src/test/java/net/openhft/chronicle/map/jrs166/map/StatelessChronicleMapTest.java +++ b/src/test/java/net/openhft/chronicle/map/jrs166/map/StatelessChronicleMapTest.java @@ -140,13 +140,13 @@ public class StatelessChronicleMapTest extends JSR166TestCase { } @Override - public V getUsing(K key, V value) { - return d.getUsing(key, value); + public V getUsing(K key, V usingValue) { + return d.getUsing(key, usingValue); } @Override - public V acquireUsing(K key, V value) { - return d.acquireUsing(key, value); + public V acquireUsing(K key, V usingValue) { + return d.acquireUsing(key, usingValue); } @Override