HCOLL-136 HCOLL-137 HCOLL-153 HCOLL-160

1. Separated ChronicleMapBuilder (for non-Byteable values) and
OffHeapUpdatableChronicleMapBuilder (for Byteable values) with common base
AbstractChronicleMapBuilder (public just to inherit common javadocs)

2. Added ChronicleHashBuilder.keyDeserializationFactory() and
AbstractChronicleMapBuilder.valueDeserializationFactory() (former
ChronicleMapBuilder.valueFactory() method)

3. Changed ChronicleMap.get(), getUsing() API and impl to what was
initially intended - now they ALWAYS returns null, if key is absent

4. acquireUsing() API, common for the cases with Byteable and non-Byteable
values: puts defaultValue or defaultValueProvider.get(key), if the key is
absent, then acquireUsing() behaves just like getUsing()

5. For off0heap updatable ChronicleMaps (Byteable values), added
prepareValueBytes() configuration which formalizes zeroing out value bytes

6. Javadoc fixes and improvements
This commit is contained in:
leventov
2014-10-27 20:15:26 +03:00
parent b374a9bd50
commit 0c45ee8aff
30 changed files with 1529 additions and 1084 deletions
@@ -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<K, C extends ChronicleHash,
*/
B keyMarshaller(@NotNull BytesMarshaller<K> 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.
*
* <p>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<K> 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
@@ -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} (<code>net.openhft.chronicle.map</code> package) and
* {@link net.openhft.chronicle.set.ChronicleSet ChronicleSets}
* (<code>net.openhft.chronicle.set</code> 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 <a
* href="https://github.com/OpenHFT/Chronicle-Queue">Chronicle Queue</a> project.
*/
package net.openhft.chronicle.common;
@@ -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: <pre>{@code
* ChronicleMap<Key, Value> map = ChronicleMapBuilder
* .of(Key.class, Value.class)
* .entries(100500)
* // ... other configurations
* .create();}</pre>
* one of concrete {@code AbstractChronicleMapBuilder} subclasses, {@link ChronicleMapBuilder} or
* {@link OffHeapUpdatableChronicleMapBuilder}, could be prepared and used to create many similar
* maps: <pre>{@code
* OffHeapUpdatableChronicleMapBuilder<Key, Value> builder = OffHeapUpdatableChronicleMapBuilder
* .of(Key.class, Value.class)
* .entries(100500);
*
* ChronicleMap<Key, Value> map1 = builder.create();
* ChronicleMap<Key, Value> map2 = builder.create();}</pre>
* i. e. created {@code ChronicleMap} instances don't depend on the builder.
*
* <p>{@code AbstractChronicleMapBuilder} and it's subclasses are mutable, see a note in
* {@link ChronicleHashBuilder} interface documentation.
*
* <p>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.
*
* <p>{@code ChronicleMap} ("ChronicleMaps, created by {@code AbstractChronicleMapBuilder}")
* currently doesn't support resizing. That is why you should <i>always</i> configure {@linkplain
* #entries(long) number of entries} you are going to insert into the created map <i>at most</i>.
* See {@link #entries(long)} method documentation for more information on this.
*
* <p>{@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 <K> key type of the maps, produced by this builder
* @param <V> value type of the maps, produced by this builder
* @see ChronicleMap
* @see ChronicleSetBuilder
*/
public abstract class AbstractChronicleMapBuilder<K, V,
B extends AbstractChronicleMapBuilder<K, V, B>>
implements Cloneable, ChronicleHashBuilder<K, ChronicleMap<K, V>, 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<K> keyBuilder;
SerializationBuilder<V> valueBuilder;
private Map<Class<? extends Replicator>, Replicator> replicators = new HashMap<Class<? extends Replicator>, 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<K, V, ChronicleMap<K, V>> eventListener =
MapEventListeners.nop();
private V defaultValue = null;
private DefaultValueProvider<K, V> defaultValueProvider = NullValueProvider.INSTANCE;
private PrepareValueBytes<K> prepareValueBytes = null;
private byte identifier = -1;
private StatelessBuilder statelessBuilder;
private File file;
AbstractChronicleMapBuilder(Class<K> keyClass, Class<V> valueClass) {
keyBuilder = new SerializationBuilder<K>(keyClass, SerializationBuilder.Role.KEY);
valueBuilder = new SerializationBuilder<V>(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}
*
* <p>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: <pre>{@code
* ChronicleMap<String, LongValue> wordFrequencies = OffHeapUpdatableChronicleMapBuilder
* .of(String.class, LongValue.class)
* .entries(50000)
* .keySize(10)
* // shouldn't specify valueSize(), because it is statically known
* .create();}</pre>
* (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}
*
* <p>For example, if your keys are Git commit hashes:<pre>{@code
* Map<byte[], String> gitCommitMessagesByHash =
* ChronicleMapBuilder.of(byte[].class, String.class)
* .constantKeySizeBySample(new byte[20])
* .create();}</pre>
*
* @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}
*
* <p>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.
*
* <p>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}.
*
* <p>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.
*
* <p>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.
*
* <p>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).
*
* <p>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.
*
* <p>{@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.
*
* <p>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.
*
* <p>{@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}
*
* <p>Example:
*/
@Override
public B keyMarshaller(@NotNull BytesMarshaller<K> keyMarshaller) {
keyBuilder.marshaller(keyMarshaller, null);
return self();
}
@Override
public B keyDeserializationFactory(@NotNull ObjectFactory<K> 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.
*
* <p>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<V> valueDeserializationFactory) {
valueBuilder.factory(valueDeserializationFactory);
return self();
}
public B eventListener(MapEventListener<K, V, ChronicleMap<K, V>> eventListener) {
this.eventListener = eventListener;
return self();
}
MapEventListener<K, V, ChronicleMap<K, V>> 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.
*
* <p>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.
*
* <p>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<K, V> defaultValueProvider) {
this.defaultValueProvider = defaultValueProvider;
this.defaultValue = null;
this.prepareValueBytes = null;
return self();
}
B prepareValueBytesOnAcquire(@NotNull PrepareValueBytes<K> prepareValueBytes) {
this.prepareValueBytes = prepareValueBytes;
this.defaultValue = null;
this.defaultValueProvider = null;
return self();
}
PrepareValueBytesAsWriter<K> prepareValueBytesAsWriter() {
return new PrepareValueBytesAsWriter<>(prepareValueBytes, valueSize());
}
/**
* Non-public because should be called only after {@link #preMapConstruction()}
*/
DefaultValueProvider<K, V> 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<K, V>(defaultValue, metaValueWriter, valueWriter);
}
public ChronicleMap<K, V> 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<K, ?, ?, V, ?, ?> map =
(VanillaChronicleMap<K, ?, ?, V, ?, ?>) 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<K, ?, ?, V, ?, ?> 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<K, V> create() throws IOException {
if (statelessBuilder != null)
return createStatelessMap(statelessBuilder);
return (file != null) ? createWithFile(file) : createWithoutFile();
}
private ChronicleMap<K, V> createWithoutFile() throws IOException {
VanillaChronicleMap<K, ?, ?, V, ?, ?> map = newMap();
BytesStore bytesStore = new DirectStore(JDKObjectSerializer.INSTANCE,
map.sizeInBytes(), true);
map.createMappedStoreAndSegments(bytesStore);
return establishReplication(map);
}
private ChronicleMap<K, V> createStatelessMap(StatelessBuilder statelessBuilder) throws IOException {
preMapConstruction();
final KeyValueSerializer<K, V> keyValueSerializer
= new KeyValueSerializer<K, V>(keyBuilder, valueBuilder);
final Class<K> kClass = keyBuilder.eClass;
final Class<V> vClass = valueBuilder.eClass;
return new StatelessChronicleMap<K, V>(
keyValueSerializer,
statelessBuilder,
entrySize(),
kClass,
vClass);
}
private VanillaChronicleMap<K, ?, ?, V, ?, ?> newMap() throws IOException {
preMapConstruction();
if (useReplication()) {
return new ReplicatedChronicleMap<K, Object, MetaBytesInterop<K, Object>,
V, Object, MetaBytesWriter<V, Object>>(this);
} else {
return new VanillaChronicleMap<K, Object, MetaBytesInterop<K, Object>,
V, Object, MetaBytesWriter<V, Object>>(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<K, V> establishReplication(ChronicleMap<K, V> 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;
}
}
@@ -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 {
/**
@@ -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, " +
@@ -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;
* <li>{@code ChronicleMap} flavors and properties</li>
* <li>available configurations</li>
* </ul>
* see {@link ChronicleMapBuilder} documentation.
* see {@link AbstractChronicleMapBuilder} documentation.
*
* <p>Functionally this interface defines some methods supporting garbage-free off-heap programming:
* {@link #getUsing(Object, Object)}, {@link #acquireUsing(Object, Object)}.
*
* <p>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.
*
* <p>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<K, V> extends ConcurrentMap<K, V>, ChronicleHash {
* Returns the value to which the specified key is mapped, or {@code null} if this map contains
* no mapping for the key.
*
* <p>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.
*
* <p>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<K, V> extends ConcurrentMap<K, V>, 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.
*
* <p>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.
*
* <p>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.
* <p>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.
*
* <p>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.
*
* <p>In code, {@code acquireUsing} is specified as <pre>{@code
* V acquireUsing(K key, V usingValue) {
* if (!containsKey(key))
* put(key, defaultValue(key));
* return getUsing(key, usingValue);
* }}</pre> Where {@code defaultValue(key)} returns either {@linkplain
* AbstractChronicleMapBuilder#defaultValue(Object) default value} or {@link
* AbstractChronicleMapBuilder#defaultValueProvider(DefaultValueProvider) defaultValueProvider.}
*
* <p>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);
}
File diff suppressed because it is too large Load Diff
@@ -48,7 +48,7 @@ final class ConstantValueProvider<K, V> implements DefaultValueProvider<K, V> {
}
@Override
public V get(K key, V usingValue) {
public V get(K key) {
return value;
}
}
@@ -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 <K> map key class
* @param <V> map value class
*/
public interface DefaultValueProvider<K, V> 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.
*
* <p>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);
}
@@ -33,7 +33,7 @@ import java.io.Serializable;
* class and override only methods corresponding the events you are interested in.
*
* <p>To configure {@code MapEventListener} for {@code ChronicleMap}, use
* {@link ChronicleMapBuilder#eventListener(MapEventListener)} method.
* {@link AbstractChronicleMapBuilder#eventListener(MapEventListener)} method.
*
* <p>{@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 <K> key type of the maps, trackable by this event listener
* @param <V> value type of the maps, trackable by this event listener
* @param <M> {@code ChronicleMap} subtype, trackable by this event listener
* @see ChronicleMapBuilder#eventListener(MapEventListener)
* @see AbstractChronicleMapBuilder#eventListener(MapEventListener)
* @see MapEventListeners
*/
public abstract class MapEventListener<K, V, M extends ChronicleMap<K, V>>
@@ -75,7 +75,7 @@ public abstract class MapEventListener<K, V, M extends ChronicleMap<K, V>>
* 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.
*
@@ -22,7 +22,7 @@ enum NullValueProvider implements DefaultValueProvider {
INSTANCE;
@Override
public Object get(Object key, Object usingValue) {
public Object get(Object key) {
return null;
}
}
@@ -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<K, V>
extends AbstractChronicleMapBuilder<K, V, OffHeapUpdatableChronicleMapBuilder<K, V>> {
public static <K, V> OffHeapUpdatableChronicleMapBuilder<K, V> of(
@NotNull Class<K> keyClass, @NotNull Class<V> 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<K, V>(keyClass, valueClass);
}
OffHeapUpdatableChronicleMapBuilder(Class<K> keyClass, Class<V> valueClass) {
super(keyClass, valueClass);
prepareValueBytesOnAcquire(new ZeroOutValueBytes<K>(valueSize()));
}
@Override
OffHeapUpdatableChronicleMapBuilder<K, V> self() {
return this;
}
/**
* {@inheritDoc} Also, it overrides any previous {@link #prepareValueBytesOnAcquire}
* configuration to this {@code OffHeapUpdatableChronicleMapBuilder}.
*
* <p>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<K, V> defaultValue(V defaultValue) {
return super.defaultValue(defaultValue);
}
/**
* {@inheritDoc} Also, it overrides any previous {@link #prepareValueBytesOnAcquire}
* configuration to this {@code OffHeapUpdatableChronicleMapBuilder}.
*
* <p>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<K, V> defaultValueProvider(
@NotNull DefaultValueProvider<K, V> 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.
*
* <p>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<K, V> prepareValueBytesOnAcquire(
@NotNull PrepareValueBytes<K> prepareValueBytes) {
return super.prepareValueBytesOnAcquire(prepareValueBytes);
}
}
@@ -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<K> extends Serializable {
void prepare(Bytes bytes, K key);
}
@@ -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<K>
implements BytesWriter<K>, MetaBytesWriter<K, PrepareValueBytesAsWriter<K>>,
Serializable {
private static final long serialVersionUID = 0L;
private final PrepareValueBytes<K> prepareValueBytes;
private final long valueSize;
PrepareValueBytesAsWriter(PrepareValueBytes<K> 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<K> writer, K k) {
return size(k);
}
@Override
public void write(PrepareValueBytesAsWriter<K> writer, Bytes bytes, K k) {
write(bytes, k);
}
}
@@ -106,7 +106,7 @@ class ReplicatedChronicleMap<K, KI, MKI extends MetaBytesInterop<K, KI>,
private transient ModificationDelegator modificationDelegator;
private transient long startOfModificationIterators;
public ReplicatedChronicleMap(@NotNull ChronicleMapBuilder<K, V> builder)
public ReplicatedChronicleMap(@NotNull AbstractChronicleMapBuilder<K, V, ?> builder)
throws IOException {
super(builder);
this.timeProvider = builder.timeProvider();
@@ -613,38 +613,39 @@ class ReplicatedChronicleMap<K, KI, MKI extends MetaBytesInterop<K, KI>,
/**
* @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<V, VW> 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<K, KI, MKI extends MetaBytesInterop<K, KI>,
}
}
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<K, KI, MKI extends MetaBytesInterop<K, KI>,
// 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<K, KI, MKI extends MetaBytesInterop<K, KI>,
* @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 <s>(this could be later if
* it was a remote put)</s> this method is called only from usual
@@ -916,30 +910,25 @@ class ReplicatedChronicleMap<K, KI, MKI extends MetaBytesInterop<K, KI>,
* @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<V, VW> 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<V, VW> 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 <E, EW> long putEntry(
MKI metaKeyInterop, KI keyInterop, K key, long keySize, long hash2,
final int identifier, final long timestamp, MultiMap searchedHashLookup,
MetaBytesWriter<E, EW> 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<K, KI, MKI extends MetaBytesInterop<K, KI>,
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,
@@ -78,12 +78,9 @@ final class SerializationBuilder<E> implements Cloneable {
this.eClass = eClass;
instancesAreMutable = instancesAreMutable(eClass);
ObjectFactory<E> factory = null;
if (role == Role.VALUE) {
factory = concreteClass(eClass) && marshallerUseFactory(eClass) ?
new NewInstanceObjectFactory<E>(eClass) :
(ObjectFactory<E>) NullObjectFactory.<E>of();
}
ObjectFactory<E> factory = concreteClass(eClass) && marshallerUseFactory(eClass) ?
new NewInstanceObjectFactory<E>(eClass) :
NullObjectFactory.<E>of();
if (concreteClass(eClass) && Byteable.class.isAssignableFrom(eClass)) {
agileMarshaller(ByteableMarshaller.of((Class) eClass), factory);
@@ -109,7 +106,7 @@ final class SerializationBuilder<E> implements Cloneable {
if (marshaller != null)
marshaller(marshaller, factory);
}
if (role == Role.VALUE && concreteClass(eClass) && marshallerUseFactory(eClass)) {
if (concreteClass(eClass) && marshallerUseFactory(eClass)) {
factory(factory);
}
}
@@ -390,12 +390,12 @@ class StatelessChronicleMap<K, V> implements ChronicleMap<K, V>, 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");
}
@@ -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));
@@ -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<K, KI, MKI extends MetaBytesInterop<K, KI>,
final MetaProvider<V, VW, MVW> metaValueWriterProvider;
final ObjectFactory<V> valueFactory;
final DefaultValueProvider<K, V> defaultValueProvider;
final PrepareValueBytesAsWriter<K> prepareValueBytesAsWriter;
final int metaDataBytes;
// private final int replicas;
@@ -100,7 +100,7 @@ class VanillaChronicleMap<K, KI, MKI extends MetaBytesInterop<K, KI>,
private final HashSplitting hashSplitting;
public VanillaChronicleMap(ChronicleMapBuilder<K, V> builder) throws IOException {
public VanillaChronicleMap(AbstractChronicleMapBuilder<K, V, ?> builder) throws IOException {
SerializationBuilder<K> keyBuilder = builder.keyBuilder;
kClass = keyBuilder.eClass;
@@ -122,6 +122,7 @@ class VanillaChronicleMap<K, KI, MKI extends MetaBytesInterop<K, KI>,
metaValueWriterProvider = (MetaProvider) valueBuilder.metaInteropProvider();
valueFactory = valueBuilder.factory();
defaultValueProvider = builder.defaultValueProvider();
prepareValueBytesAsWriter = builder.prepareValueBytesAsWriter();
lockTimeOutNS = builder.lockTimeOut(TimeUnit.NANOSECONDS);
@@ -355,13 +356,13 @@ class VanillaChronicleMap<K, KI, MKI extends MetaBytesInterop<K, KI>,
}
@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<K, KI, MKI extends MetaBytesInterop<K, KI>,
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: <ol><li>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).</li> <li>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.</li></ol>
*
* @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<K, KI, MKI extends MetaBytesInterop<K, KI>,
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<V, VW> 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<K, KI, MKI extends MetaBytesInterop<K, KI>,
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<K, KI, MKI extends MetaBytesInterop<K, KI>,
}
}
// 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<K, KI, MKI extends MetaBytesInterop<K, KI>,
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<V, VW> 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<V, VW> 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 <E, EW> long putEntry(MKI metaKeyInterop, KI keyInterop, K key, long keySize,
MetaBytesWriter<E, EW> 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<K, KI, MKI extends MetaBytesInterop<K, KI>,
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<V, VW> 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) {
@@ -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<K> implements PrepareValueBytes<K> {
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);
}
}
@@ -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<E>
ChronicleSetBuilder(Class<E> keyClass) {
chronicleMapBuilder = ChronicleMapBuilder.of(keyClass, DummyValue.class)
.entryAndValueAlignment(Alignment.NO_ALIGNMENT)
.valueMarshallerAndFactory(DummyValueMarshaller.INSTANCE,
NullObjectFactory.<DummyValue>of());
.valueMarshaller(DummyValueMarshaller.INSTANCE);
}
public static <K> ChronicleSetBuilder<K> of(Class<K> keyClass) {
@@ -244,6 +244,23 @@ public class ChronicleSetBuilder<E>
return this;
}
/**
* {@inheritDoc}
*
* <p>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<E> keyDeserializationFactory(
@NotNull ObjectFactory<E> keyDeserializationFactory) {
chronicleMapBuilder.keyDeserializationFactory(keyDeserializationFactory);
return this;
}
@Override
public ChronicleSetBuilder<E> immutableKeys() {
chronicleMapBuilder.immutableKeys();
@@ -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;
@@ -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<String, Data> theSharedMap = ChronicleMapBuilder.of(String.class, Data.class).file(file).create();
ChronicleMap<String, Data> 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) {
@@ -75,7 +75,7 @@ public class CHMLatencyTestMain {
File file = File.createTempFile("testCHMLatency", "deleteme");
// File file = new File("testCHMLatency.deleteme");
file.delete();
ChronicleMap<LongValue, LongValue> countersMap = ChronicleMapBuilder.of(LongValue.class, LongValue.class)
ChronicleMap<LongValue, LongValue> countersMap = OffHeapUpdatableChronicleMapBuilder.of(LongValue.class, LongValue.class)
.entries(KEYS)
.entrySize(24).create();
@@ -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<String, CHMTest5Data> builder =
ChronicleMapBuilder.of(String.class, CHMTest5Data.class)
OffHeapUpdatableChronicleMapBuilder<String, CHMTest5Data> builder =
OffHeapUpdatableChronicleMapBuilder.of(String.class, CHMTest5Data.class)
.entries(1000);
//// don't include this, just to check it is as expected.
@@ -110,13 +110,12 @@ public class ChronicleMapTest {
private static ChronicleMap<CharSequence, LongValue> 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<CharSequence, IntValue> builder = ChronicleMapBuilder
OffHeapUpdatableChronicleMapBuilder<CharSequence, IntValue> builder = OffHeapUpdatableChronicleMapBuilder
.of(CharSequence.class, IntValue.class)
.entries(entries)
.minSegments(1024)
.entrySize(20)
.valueMarshallerAndFactory(ByteableIntValueMarshaller.INSTANCE,
DirectIntValueFactory.INSTANCE);
.entrySize(20);
final ChronicleMap<CharSequence, IntValue> map = builder.create();
@@ -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<CharSequence, LongValue> 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<CharSequence, LongValue> getSharedMap(
long entries, int segments, int entrySize, Alignment alignment)
throws IOException {
ChronicleMapBuilder<CharSequence, LongValue> mapBuilder = ChronicleMapBuilder.of(CharSequence.class, LongValue.class)
OffHeapUpdatableChronicleMapBuilder<CharSequence, LongValue> 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();
}
@@ -242,13 +242,13 @@ public class ReplicationCheckingMap<K, V> implements ChronicleMap<K, V> {
}
@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
@@ -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<String, BondVOInterface> chm = ChronicleMapBuilder
.of(String.class, directClassFor(BondVOInterface.class))
ChronicleMap<String, BondVOInterface> 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<String, BondVOInterface> chmB = ChronicleMapBuilder
.of(String.class, directClassFor(BondVOInterface.class))
ChronicleMap<String, BondVOInterface> chmB = OffHeapUpdatableChronicleMapBuilder
.of(String.class, BondVOInterface.class)
.keySize(10)
.file(file)
.create();
@@ -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