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