diff --git a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java index 7a12a592d..8dce56d17 100755 --- a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java +++ b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java @@ -23,6 +23,7 @@ import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; import net.openhft.chronicle.hash.replication.TimeProvider; import net.openhft.chronicle.hash.serialization.*; import net.openhft.chronicle.map.ChronicleMap; +import net.openhft.chronicle.map.ChronicleMapBuilder; import net.openhft.chronicle.set.ChronicleSet; import net.openhft.chronicle.set.ChronicleSetBuilder; import net.openhft.lang.io.Bytes; @@ -46,7 +47,7 @@ import java.util.concurrent.TimeUnit; /** * This interface defines the meaning of configurations, common to {@link - * net.openhft.chronicle.map.ChronicleMapBuilder} and {@link ChronicleSetBuilder}, i. + * ChronicleMapBuilder} and {@link ChronicleSetBuilder}, i. * e. Chronicle hash container configurations. * *

{@code ChronicleHashBuilder} is mutable. Configuration methods mutate the builder and return @@ -57,7 +58,7 @@ import java.util.concurrent.TimeUnit; * @param the type of keys in hash containers, created by this builder * @param the container type, created by this builder, i. e. {@link ChronicleMap} or {@link * ChronicleSet} - * @param the concrete builder type, i. e. {@link net.openhft.chronicle.map.ChronicleMapBuilder} + * @param the concrete builder type, i. e. {@link ChronicleMapBuilder} * or {@link ChronicleSetBuilder} */ public interface ChronicleHashBuilderIf key is a boxed primitive type or {@link Byteable} subclass, i. e. if key size is known + * statically, it is automatically accounted and shouldn't be specified by user. + * * @param keySize number of bytes, taken by serialized form of keys * @return this builder back + * @throws IllegalStateException if key size is known statically and shouldn't be configured + * by user + * @throws IllegalArgumentException if the given {@code keySize} is non-positive * @see #constantKeySizeBySample(Object) * @see #entrySize(int) */ @@ -131,7 +138,7 @@ public interface ChronicleHashBuilderThere are three major patterns of this configuration usage:

  1. Key (and value, in * {@code ChronicleMap} case) sizes are constant. Configure them via {@link - * #constantKeySizeBySample(Object)} and {@link net.openhft.chronicle.map.ChronicleMapBuilder#constantValueSizeBySample(Object)} + * #constantKeySizeBySample(Object)} and {@link ChronicleMapBuilder#constantValueSizeBySample(Object)} * methods, and you will experience no memory waste at all.
  2. Key (and/or value size, in * {@code ChronicleMap} case) varies moderately. Specify them using corresponding methods, or * specify entry size directly by calling this method, by sizes somewhere between average and @@ -145,10 +152,11 @@ public interface ChronicleHashBuilderHowever, remember that
    • Operations with entries that span several "entry sizes" * are a bit slower, than entries which take a single "entry size". That is why "chunk" approach * is not recommended, when key (and/or value size) varies moderately.
    • The maximum - * number of chunks could be taken by an entry is 64. {@link IllegalArgumentException} is thrown - * on attempt to insert too large entry, compared to the configured or computed entry size.
    • - *
    • The number of "entries" {@linkplain #entries(long) in the whole map} and {@linkplain - * #actualEntriesPerSegment(long) per segment} is actually the number "chunks".
    + * number of chunks could be taken by an entry is {@link #maxEntryOversizeFactor(int)}, of max + * 64 chunks. {@link IllegalArgumentException} is thrown on attempt to insert too large entry, + * compared to the configured or computed entry size.
  3. The number of "entries" + * {@linkplain #entries(long) in the whole map} and {@linkplain #actualEntriesPerSegment(long) + * per segment} is actually the number "chunks".
  4. * *

    Example: if values in your {@code ChronicleMap} are adjacency lists of some social graph, * where nodes are represented as {@code long} ids, and adjacency lists are serialized in @@ -169,9 +177,22 @@ public interface ChronicleHashBuilder

  5. If key (and/or value, in {@code ChronicleMap} case) size varies * moderately, you should pass to this method the maximum number of entries + 5-25%, depending * on your data properties and configured {@linkplain #keySize(int) key}/{@linkplain - * net.openhft.chronicle.map.ChronicleMapBuilder#valueSize(int) + * ChronicleMapBuilder#valueSize(int) * value}/{@linkplain #entrySize(int) entry} sizes.
  6. If your data size varies in a wide * range, pass the maximum number of entries multiplied by average data size and divided by the * configured "entry size" (i. e. chunk size). See an example in the documentation to {@link @@ -292,7 +313,7 @@ public interface ChronicleHashBuilder keyMarshaller); + B keyMarshaller(@NotNull BytesMarshaller keyMarshaller); /** * Configures the marshallers, used to serialize/deserialize keys to/from off-heap memory in @@ -332,11 +353,10 @@ public interface ChronicleHashBuilderDefault key deserialization factory is {@link NewInstanceObjectFactory}, which creates a * new key instance using {@link Class#newInstance()} default constructor. You could provide an @@ -348,9 +368,8 @@ public interface ChronicleHashBuilder keyDeserializationFactory); @@ -401,8 +420,6 @@ public interface ChronicleHashBuilder statelessClient(InetSocketAddress remoteAddress); - ChronicleHashInstanceConfig instance(); /** @@ -414,12 +431,10 @@ public interface ChronicleHashBuilderThis method is a shortcut for {@code instance().create()}. * * @return a new off-heap hash container - * @throws IOException if any IO error relates to off-heap memory allocation, or establishing - * replication connections, occurs * @see #createPersistedTo(File) * @see #instance() */ - C create() throws IOException; + C create(); /** * Opens a hash container residing the specified file, or creates a new one if the file not yet @@ -450,5 +465,7 @@ public interface ChronicleHashBuilder statelessClient(InetSocketAddress remoteAddress); + + C createStatelessClient(InetSocketAddress serverAddress) throws IOException; } diff --git a/src/main/java/net/openhft/chronicle/hash/ChronicleHashInstanceConfig.java b/src/main/java/net/openhft/chronicle/hash/ChronicleHashInstanceConfig.java index 21b01661b..8afd3d188 100644 --- a/src/main/java/net/openhft/chronicle/hash/ChronicleHashInstanceConfig.java +++ b/src/main/java/net/openhft/chronicle/hash/ChronicleHashInstanceConfig.java @@ -53,5 +53,7 @@ public interface ChronicleHashInstanceConfig { ChronicleHashInstanceConfig persistedTo(File file); + ChronicleHashInstanceConfig name(String name); + C create() throws IOException; } diff --git a/src/main/java/net/openhft/chronicle/hash/StatelessClientConfig.java b/src/main/java/net/openhft/chronicle/hash/StatelessClientConfig.java index 01836241e..38add9b6a 100644 --- a/src/main/java/net/openhft/chronicle/hash/StatelessClientConfig.java +++ b/src/main/java/net/openhft/chronicle/hash/StatelessClientConfig.java @@ -18,8 +18,10 @@ package net.openhft.chronicle.hash; import net.openhft.chronicle.map.ChronicleMap; import net.openhft.chronicle.set.ChronicleSet; +import org.jetbrains.annotations.NotNull; import java.io.IOException; +import java.net.InetSocketAddress; import java.util.concurrent.TimeUnit; /** @@ -41,5 +43,7 @@ import java.util.concurrent.TimeUnit; public interface StatelessClientConfig { StatelessClientConfig timeout(long timeout, TimeUnit units); + StatelessClientConfig name(String name); + C create() throws IOException; } diff --git a/src/main/java/net/openhft/chronicle/hash/replication/AbstractReplication.java b/src/main/java/net/openhft/chronicle/hash/replication/AbstractReplication.java index 881fa91dd..e85fec8ff 100644 --- a/src/main/java/net/openhft/chronicle/hash/replication/AbstractReplication.java +++ b/src/main/java/net/openhft/chronicle/hash/replication/AbstractReplication.java @@ -35,6 +35,7 @@ public abstract class AbstractReplication implements Serializable { private final @Nullable RemoteNodeValidator remoteNodeValidator; + private final boolean bootstrapOnlyLocalEntries; // package-private to forbid subclassing from outside of the package AbstractReplication(byte localIdentifier, Builder builder) { @@ -42,14 +43,16 @@ public abstract class AbstractReplication implements Serializable { tcpConfig = builder.tcpConfig; udpConfig = builder.udpConfig; remoteNodeValidator = builder.remoteNodeValidator; + bootstrapOnlyLocalEntries = builder.bootstrapOnlyLocalEntries; } @Override public String toString() { - return ", localIdentifier=" + localIdentifier + + return "localIdentifier=" + localIdentifier + ", tcpConfig=" + tcpConfig + ", udpConfig=" + udpConfig + - ", remoteNodeValidator=" + remoteNodeValidator; + ", remoteNodeValidator=" + remoteNodeValidator + + ", bootstrapOnlyLocalEntries=" + bootstrapOnlyLocalEntries; } public byte identifier() { @@ -71,6 +74,10 @@ public abstract class AbstractReplication implements Serializable { return remoteNodeValidator; } + public boolean bootstrapOnlyLocalEntries() { + return bootstrapOnlyLocalEntries; + } + /** * Builder of {@link AbstractReplication} configurations. This class and it's subclasses are mutable, * configuration methods mutate the builder and return it back for convenient chaining. @@ -84,6 +91,7 @@ public abstract class AbstractReplication implements Serializable { private TcpTransportAndNetworkConfig tcpConfig = null; private UdpTransportConfig udpConfig = null; private RemoteNodeValidator remoteNodeValidator = null; + private boolean bootstrapOnlyLocalEntries = false; // package-private to forbid subclassing from outside of the package Builder() { @@ -115,6 +123,29 @@ public abstract class AbstractReplication implements Serializable { return (B) this; } + + /** + * Configures if the node, provided with replication, created by this builder, should + * replicate only local data, last updated with own identifier, or all data currently + * present on the node, when a new node joins the replication grid. + * + *

    Default configuration is {@code false}, potentially swamping new nodes with + * duplicates. However, this does guarantee that all the data is replicated over to the new + * node and is useful especially in the case that the originating node of some data is not + * currently running. + * + * @param bootstrapOnlyLocalEntries if a node provided with this replication should + * replicate only local data to the new nodes joining + * the replication network + * @return this builder back + */ + @NotNull + public B bootstrapOnlyLocalEntries(boolean bootstrapOnlyLocalEntries) { + this.bootstrapOnlyLocalEntries = bootstrapOnlyLocalEntries; + return (B) this; + } + + /** * Creates a Replication instance with the given node (server) identifier. * diff --git a/src/main/java/net/openhft/chronicle/hash/replication/ReplicationHub.java b/src/main/java/net/openhft/chronicle/hash/replication/ReplicationHub.java index d8a7ac3e3..81d1b1a7d 100644 --- a/src/main/java/net/openhft/chronicle/hash/replication/ReplicationHub.java +++ b/src/main/java/net/openhft/chronicle/hash/replication/ReplicationHub.java @@ -73,7 +73,7 @@ public final class ReplicationHub extends AbstractReplication { @Override public String toString() { return "ReplicationHub{" + super.toString() + - "maxEntrySize=" + maxEntrySize + + ", maxEntrySize=" + maxEntrySize + ", channels=" + Arrays.toString(channels) + '}'; } diff --git a/src/main/java/net/openhft/chronicle/hash/replication/SingleChronicleHashReplication.java b/src/main/java/net/openhft/chronicle/hash/replication/SingleChronicleHashReplication.java index 6032edfc2..a4d869c2b 100644 --- a/src/main/java/net/openhft/chronicle/hash/replication/SingleChronicleHashReplication.java +++ b/src/main/java/net/openhft/chronicle/hash/replication/SingleChronicleHashReplication.java @@ -29,6 +29,11 @@ public final class SingleChronicleHashReplication extends AbstractReplication { super(localIdentifier, builder); } + @Override + public String toString() { + return "SingleChronicleHashReplication{" + super.toString() + "}"; + } + public static final class Builder extends AbstractReplication.Builder { private Builder() {} diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/DeserializationFactoryConfigurableBytesReader.java b/src/main/java/net/openhft/chronicle/hash/serialization/DeserializationFactoryConfigurableBytesReader.java new file mode 100644 index 000000000..1c4444a24 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/hash/serialization/DeserializationFactoryConfigurableBytesReader.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.hash.serialization; + +import net.openhft.lang.io.serialization.ObjectFactory; + +public interface DeserializationFactoryConfigurableBytesReader> + extends BytesReader { + + R withDeserializationFactory(ObjectFactory deserializationFactory); +} diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/ByteableMarshaller.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/ByteableMarshaller.java index 7e7dd742c..ed8a20cde 100644 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/ByteableMarshaller.java +++ b/src/main/java/net/openhft/chronicle/hash/serialization/internal/ByteableMarshaller.java @@ -18,34 +18,28 @@ package net.openhft.chronicle.hash.serialization.internal; -import net.openhft.chronicle.hash.serialization.BytesInterop; -import net.openhft.chronicle.hash.serialization.BytesReader; -import net.openhft.chronicle.hash.serialization.Hasher; -import net.openhft.chronicle.hash.serialization.SizeMarshaller; +import net.openhft.chronicle.hash.serialization.*; import net.openhft.lang.io.Bytes; import net.openhft.lang.io.MultiStoreBytes; import net.openhft.lang.io.NativeBytes; import net.openhft.lang.io.serialization.*; -import net.openhft.lang.io.serialization.impl.AllocateInstanceObjectFactory; import net.openhft.lang.model.Byteable; import org.jetbrains.annotations.NotNull; -public class ByteableMarshaller - implements BytesInterop, BytesReader, SizeMarshaller { +public abstract class ByteableMarshaller + implements BytesInterop, + DeserializationFactoryConfigurableBytesReader>, + SizeMarshaller { private static final long serialVersionUID = 0L; public static ByteableMarshaller of(@NotNull Class eClass) { - return new ByteableMarshaller(eClass); + return new Default<>(eClass); } - public static ByteableMarshaller of(@NotNull Class eClass, - ObjectFactory factory) { - if (factory instanceof AllocateInstanceObjectFactory) { - Class allocatedClass = ((AllocateInstanceObjectFactory) factory).allocatedClass(); - return new Default(allocatedClass); - } else { - return new WithCustomFactory(eClass, factory); - } + @Override + public ByteableMarshaller withDeserializationFactory( + ObjectFactory deserializationFactory) { + return new WithCustomFactory<>(tClass, deserializationFactory); } @NotNull diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/BytesReaders.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/BytesReaders.java index 8fe37b984..db1c88600 100644 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/BytesReaders.java +++ b/src/main/java/net/openhft/chronicle/hash/serialization/internal/BytesReaders.java @@ -41,6 +41,18 @@ public final class BytesReaders { return new SimpleBytesReader(marshaller); } + /** + * Returns {@code BytesMarshaller} {@code m}, if the given reader is the result of {@link + * #fromBytesMarshaller(BytesMarshaller) fromBytesMarshaller(m)} call, {@code null} otherwise. + * + * @param reader reader to extract {@code BytesMarshaller} from + * @param type of the objects marshalled + * @return {@code BytesMarshaller} from which the specified reader was created, or {@code null} + */ + public static BytesMarshaller getBytesMarshaller(BytesReader reader) { + return reader instanceof SimpleBytesReader ? ((SimpleBytesReader) reader).marshaller : null; + } + private static class SimpleBytesReader implements BytesReader { private static final long serialVersionUID = 0L; diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/CopyingMetaBytesInterop.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/CopyingMetaBytesInterop.java index 31a897e16..ca3906aec 100644 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/CopyingMetaBytesInterop.java +++ b/src/main/java/net/openhft/chronicle/hash/serialization/internal/CopyingMetaBytesInterop.java @@ -24,6 +24,7 @@ import net.openhft.lang.io.Bytes; import net.openhft.lang.io.DirectBytes; import net.openhft.lang.io.DirectStore; import net.openhft.lang.io.serialization.BytesMarshaller; +import net.openhft.lang.io.serialization.JDKObjectSerializer; import net.openhft.lang.io.serialization.JDKZObjectSerializer; import net.openhft.lang.threadlocal.Provider; import net.openhft.lang.threadlocal.StatefulCopyable; @@ -203,7 +204,7 @@ public abstract class CopyingMetaBytesInterop implements MetaBytesInterop< return buffer = store.bytes(); } } else { - buffer = new DirectStore(JDKZObjectSerializer.INSTANCE, maxSize, false).bytes(); + buffer = new DirectStore(JDKObjectSerializer.INSTANCE, maxSize, false).bytes(); return buffer; } } diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/DataValueBytesMarshallers.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/DataValueBytesMarshallers.java index 0e71de34d..3cf898023 100644 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/DataValueBytesMarshallers.java +++ b/src/main/java/net/openhft/chronicle/hash/serialization/internal/DataValueBytesMarshallers.java @@ -18,6 +18,7 @@ package net.openhft.chronicle.hash.serialization.internal; import net.openhft.chronicle.hash.serialization.BytesReader; import net.openhft.chronicle.hash.serialization.BytesWriter; +import net.openhft.chronicle.hash.serialization.DeserializationFactoryConfigurableBytesReader; import net.openhft.compiler.CompilerUtils; import net.openhft.lang.io.Bytes; import net.openhft.lang.io.serialization.ObjectFactory; @@ -56,18 +57,6 @@ public final class DataValueBytesMarshallers { } } - public static BytesReader acquireBytesReader(Class tClass, ObjectFactory factory) { - Class readerClass = acquireReaderWithCustomFactory(tClass); - try { - Constructor constructor = readerClass.getConstructor(ObjectFactory.class); - Object reader = constructor.newInstance(factory); - return (BytesReader) reader; - } catch (IllegalAccessException | NoSuchMethodException | InstantiationException | - InvocationTargetException e) { - throw new AssertionError(e); - } - } - public static BytesWriter acquireBytesWriter(Class tClass) { Class readerClass = acquireWriterClass(tClass); try { @@ -106,6 +95,7 @@ public final class DataValueBytesMarshallers { } } readersClassMap.put(tClass, readerClass); + acquireReaderWithCustomFactory(tClass); return readerClass; } @@ -146,7 +136,7 @@ public final class DataValueBytesMarshallers { if (dumpCode) LoggerFactory.getLogger(DataValueGenerator.class).info(actual); ClassLoader classLoader = tClass.getClassLoader(); - String className = bytesReaderName(tClass, false) + "$WithCustomFactory"; + String className = withCustomFactoryName(tClass); try { c = classLoader.loadClass(className); } catch (ClassNotFoundException ignored) { @@ -161,6 +151,10 @@ public final class DataValueBytesMarshallers { return c; } + private static String withCustomFactoryName(Class tClass) { + return bytesReaderName(tClass, false) + "$WithCustomFactory"; + } + private static String generateWithCustomFactoryClass(Class tClass) { DataValueModel dvModel = DataValueModels.acquireModel(tClass); @@ -217,10 +211,14 @@ public final class DataValueBytesMarshallers { SortedSet imported = newImported(); imported.add(BytesReader.class); + imported.add(DeserializationFactoryConfigurableBytesReader.class); + imported.add(ObjectFactory.class); imported.add(Bytes.class); imported.add(ByteableMarshaller.class); imported.add(Byteable.class); imported.add(dvModel.type()); + imported.add(InvocationTargetException.class); + imported.add(Constructor.class); StringBuilder read = generateReadBody(dvModel, imported); @@ -231,10 +229,12 @@ public final class DataValueBytesMarshallers { String bytesReaderName = bytesReaderName(tClass, false); String simpleReaderName = bytesReaderName(simpleName(tClass)); sb.append("\npublic class ").append(simpleReaderName) - .append(" implements BytesReader<").append(simpleName).append("> {\n"); + .append(" implements DeserializationFactoryConfigurableBytesReader<") + .append(simpleName).append(", ").append(simpleReaderName).append("> {\n"); declareSerialVersionUID(sb); declareStaticInstance(sb, tClass); generatePrivateConstructor(sb, simpleReaderName); + generateWithDeserializationFactory(tClass, dvModel, sb, simpleName, simpleReaderName); generateGetInstance(sb, tClass); generateDelegatingRead(sb, simpleName); generateRead(read, sb, simpleName, tClass); @@ -242,6 +242,31 @@ public final class DataValueBytesMarshallers { return sb.toString(); } + private static void generateWithDeserializationFactory(Class tClass, + DataValueModel dvModel, + StringBuilder sb, String simpleName, + String simpleReaderName) { + appendOverride(sb); + sb.append(" public ").append(simpleReaderName) + .append(" withDeserializationFactory(ObjectFactory<").append(simpleName) + .append("> factory) {\n"); + sb.append(" try {\n"); + sb.append(" ") + .append("Class cfc = Class.forName(\"") + .append(getPackage(dvModel)) + .append(".").append(withCustomFactoryName(tClass)).append("\");\n"); + sb.append( + " Constructor constructor = cfc.getConstructor(ObjectFactory.class);\n" + + " Object reader = constructor.newInstance(factory);\n" + + " return (" + simpleReaderName + ") reader;\n" + + " } catch (ClassNotFoundException | IllegalAccessException | " + + "NoSuchMethodException | InstantiationException |\n" + + " InvocationTargetException e) {\n" + + " throw new AssertionError(e);\n" + + " }\n"); + sb.append(" }\n\n"); + } + private static StringBuilder generateReadBody(DataValueModel dvModel, SortedSet imported) { StringBuilder read = new StringBuilder(); @@ -272,7 +297,7 @@ public final class DataValueBytesMarshallers { .append(bytesReaderName(type, false)) .append(".INSTANCE").append(".read(bytes, ") .append(computeNonScalarOffset(dvModel, type)).append(", ") - .append(defaultGetter.getName()).append("(i)));"); + .append("toReuse.").append(defaultGetter.getName()).append("(i)));\n"); read.append(" }\n"); } else { read.append(" toReuse.") @@ -280,18 +305,30 @@ public final class DataValueBytesMarshallers { .append(bytesReaderName(type, false)) .append(".INSTANCE").append(".read(bytes, ") .append(computeNonScalarOffset(dvModel, type)).append(", ") - .append(defaultGetter.getName()).append("()));"); + .append("toReuse.").append(defaultGetter.getName()).append("()));\n"); } } else { if (model.isArray()) { read.append(" for (int i = 0; i < ") .append(model.indexSize().value()).append("; i++) {\n"); + if (CharSequence.class.isAssignableFrom(type)) + read.append(" long pos = bytes.position();\n"); read.append(" toReuse.").append(defaultSetter.getName()) .append("(i, bytes.read").append(bytesType(type)).append("());\n"); + if (CharSequence.class.isAssignableFrom(type)) + read.append(" bytes.position(pos + ") + .append(fieldSize(model)).append(");\n"); read.append(" }\n"); } else { - read.append(" toReuse.").append(defaultSetter.getName()) + read.append(" {\n"); + if (CharSequence.class.isAssignableFrom(type)) + read.append(" long pos = bytes.position();\n"); + read.append(" toReuse.").append(defaultSetter.getName()) .append("(bytes.read").append(bytesType(type)).append("());\n"); + if (CharSequence.class.isAssignableFrom(type)) + read.append(" bytes.position(pos + ") + .append(fieldSize(model)).append(");\n"); + read.append(" }\n"); } } } @@ -390,7 +427,7 @@ public final class DataValueBytesMarshallers { write.append(" for (int i = 0; i < ") .append(model.indexSize().value()).append("; i++) {\n"); write.append(" ") - .append(type.getName()).append(" ").append(" $ = e.") + .append(normalize(type)).append(" $ = e.") .append(defaultGetter.getName()).append("(i);\n"); write.append(" ") .append("if ($ == null) shouldNotBeNull();\n"); @@ -400,7 +437,7 @@ public final class DataValueBytesMarshallers { write.append(" }\n"); } else { write.append(" {") - .append(type.getName()).append(" ").append(" $ = e.") + .append(normalize(type)).append(" $ = e.") .append(defaultGetter.getName()).append("();\n"); write.append(" ") .append("if ($ == null) shouldNotBeNull();\n"); @@ -412,12 +449,25 @@ public final class DataValueBytesMarshallers { if (model.isArray()) { write.append(" for (int i = 0; i < ") .append(model.indexSize().value()).append("; i++) {\n"); + if (CharSequence.class.isAssignableFrom(type)) + write.append(" long pos = bytes.position();\n"); write.append(" bytes.write").append(bytesType(type)) .append("(e.").append(defaultGetter.getName()).append("(i));\n"); + if (CharSequence.class.isAssignableFrom(type)) + write.append(" bytes.position(pos + ") + .append(fieldSize(model)).append(");\n"); + write.append(" }\n"); } else { - write.append(" bytes.write").append(bytesType(type)) + write.append(" {\n"); + if (CharSequence.class.isAssignableFrom(type)) + write.append(" long pos = bytes.position();\n"); + write.append(" bytes.write").append(bytesType(type)) .append("(e.").append(defaultGetter.getName()).append("());\n"); + if (CharSequence.class.isAssignableFrom(type)) + write.append(" bytes.position(pos + ") + .append(fieldSize(model)).append(");\n"); + write.append(" }\n"); } } } @@ -432,8 +482,7 @@ public final class DataValueBytesMarshallers { sb.append(" try {\n"); sb.append(" if (toReuse == null)\n"); sb.append(" toReuse = getInstance();\n"); - sb.append(" if (toReuse instanceof Byteable && ") - .append("((Byteable) toReuse).bytes() == null) {\n"); + sb.append(" if (toReuse instanceof Byteable) {\n"); sb.append(" " + "ByteableMarshaller.setBytesAndOffset(((Byteable) toReuse), bytes);\n"); sb.append(" bytes.skip(size);\n"); diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/LongValueKeyMarshaller.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/LongValueKeyMarshaller.java deleted file mode 100644 index cf67d2d27..000000000 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/LongValueKeyMarshaller.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2014 Higher Frequency Trading - * - * http://www.higherfrequencytrading.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.openhft.chronicle.hash.serialization.internal; - -import net.openhft.chronicle.hash.serialization.BytesInterop; -import net.openhft.chronicle.hash.serialization.BytesReader; -import net.openhft.chronicle.hash.serialization.Hasher; -import net.openhft.chronicle.hash.serialization.SizeMarshaller; -import net.openhft.lang.io.Bytes; -import net.openhft.lang.model.Byteable; -import net.openhft.lang.model.DataValueClasses; -import net.openhft.lang.values.LongValue; - -public class LongValueKeyMarshaller - implements BytesInterop, BytesReader, SizeMarshaller { - private static final long serialVersionUID = 0L; - - public static final LongValueKeyMarshaller INSTANCE = new LongValueKeyMarshaller(); - - private static final Class heapLongValueClass = - DataValueClasses.heapClassFor(LongValue.class); - - LongValueKeyMarshaller() {} - - private Object readResolve() { - return INSTANCE; - } - - @Override - public long size(LongValue e) { - return 8L; - } - - @Override - public int sizeEncodingSize(long size) { - return 0; - } - - @Override - public void writeSize(Bytes bytes, long size) { - // do nothing - } - - @Override - public boolean startsWith(Bytes bytes, LongValue e) { - return e.getValue() == bytes.readLong(0); - } - - @Override - public long hash(LongValue e) { - return Hasher.hash(e.getValue()); - } - - @Override - public void write(Bytes bytes, LongValue e) { - bytes.writeLong(e.getValue()); - } - - @Override - public long readSize(Bytes bytes) { - return 8L; - } - - @Override - public LongValue read(Bytes bytes, long size) { - return read(bytes, size, null); - } - - @Override - public LongValue read(Bytes bytes, long size, LongValue toReuse) { - if (toReuse == null || - (toReuse instanceof Byteable && ((Byteable) toReuse).bytes() == null)) { - try { - toReuse = heapLongValueClass.newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - throw new AssertionError(e); - } - } - toReuse.setValue(bytes.readLong()); - return toReuse; - } -} diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/LongValueValueMarshaller.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/LongValueValueMarshaller.java deleted file mode 100644 index 3473e584b..000000000 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/LongValueValueMarshaller.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.openhft.chronicle.hash.serialization.internal; - -import net.openhft.lang.io.Bytes; -import net.openhft.lang.model.Byteable; -import net.openhft.lang.model.DataValueClasses; -import net.openhft.lang.values.LongValue; - -public final class LongValueValueMarshaller extends LongValueKeyMarshaller { - private static final long serialVersionUID = 0L; - - public static final LongValueValueMarshaller INSTANCE = new LongValueValueMarshaller(); - - private static final Class directLongValueClass = - DataValueClasses.directClassFor(LongValue.class); - - private LongValueValueMarshaller() {} - - private Object readResolve() { - return INSTANCE; - } - - @Override - public LongValue read(Bytes bytes, long size, LongValue toReuse) { - if (!(toReuse instanceof Byteable)) { - try { - toReuse = directLongValueClass.newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - throw new AssertionError(e); - } - } - Byteable value = (Byteable) toReuse; - ByteableMarshaller.setBytesAndOffset(value, bytes); - bytes.skip(8L); - return toReuse; - } -} diff --git a/src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java deleted file mode 100755 index bee59229d..000000000 --- a/src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java +++ /dev/null @@ -1,1017 +0,0 @@ -/* - * Copyright 2014 Higher Frequency Trading - * - * http://www.higherfrequencytrading.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.openhft.chronicle.map; - -import net.openhft.chronicle.hash.*; -import net.openhft.chronicle.hash.replication.*; -import net.openhft.chronicle.hash.serialization.BytesReader; -import net.openhft.chronicle.hash.serialization.BytesWriter; -import net.openhft.chronicle.hash.serialization.SizeMarshaller; -import net.openhft.chronicle.hash.serialization.internal.MetaBytesInterop; -import net.openhft.chronicle.hash.serialization.internal.MetaBytesWriter; -import net.openhft.chronicle.hash.serialization.internal.MetaProvider; -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 net.openhft.lang.threadlocal.Provider; -import net.openhft.lang.threadlocal.ThreadLocalCopies; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; -import java.lang.reflect.Constructor; -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -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 OnHeapUpdatableChronicleMapBuilder} should be used to create maps with ordinary values, - * {@link ChronicleMapBuilder} -- 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 = ChronicleMapOnHeapUpdatableBuilder
    - *     .of(Key.class, Value.class)
    - *     .entries(100500)
    - *     // ... other configurations
    - *     .create();}
    - * one of concrete {@code AbstractChronicleMapBuilder} subclasses, {@link OnHeapUpdatableChronicleMapBuilder} - * or {@link 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 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 - */ -abstract class AbstractChronicleMapBuilder> - implements Cloneable, ChronicleHashBuilder, B>, ChronicleMapBuilderI { - - static final byte UDP_REPLICATION_MODIFICATION_ITERATOR_ID = (byte)128; - 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); - private static final Logger LOG = - LoggerFactory.getLogger(AbstractChronicleMapBuilder.class.getName()); - - SerializationBuilder keyBuilder; - SerializationBuilder valueBuilder; - - private String name; - - // 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 = null; - private long entries = 1 << 20; // 1 million by default - private long lockTimeOut = 20000; - 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 = null; - private BytesMapEventListener bytesEventListener = null; - private V defaultValue = null; - private DefaultValueProvider defaultValueProvider = null; - private PrepareValueBytes prepareValueBytes = null; - - private SingleChronicleHashReplication singleHashReplication = null; - private InetSocketAddress[] pushToAddresses; - public boolean bootstrapOnlyLocalEntries = false; - private boolean disableOversizedEntries = false; - - AbstractChronicleMapBuilder(Class keyClass, Class valueClass) { - keyBuilder = new SerializationBuilder(keyClass, SerializationBuilder.Role.KEY); - valueBuilder = new SerializationBuilder(valueClass, SerializationBuilder.Role.VALUE); - } - - protected static boolean offHeapReference(Class valueClass) { - return Byteable.class.isAssignableFrom(valueClass); - } - - private static long roundUpMapHeaderSize(long headerSize) { - long roundUp = (headerSize + 127L) & ~127L; - if (roundUp - headerSize < 64) - roundUp += 128; - return roundUp; - } - - 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"); - } - - private static long divideUpper(long dividend, long divisor) { - return ((dividend - 1L) / divisor) + 1L; - } - - private static String pretty(int value) { - return value > 0 ? value + "" : "not configured"; - } - - private static String pretty(Object obj) { - return obj != null ? obj + "" : "not configured"; - } - - @Override - public ChronicleMapBuilderI pushTo(InetSocketAddress... addresses) { - this.pushToAddresses = addresses; - return this; - } - - @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 = ChronicleMapBuilder
    -     *     .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); - } - - public B valueSize(int valueSize) { - if (valueSize <= 0) - throw new IllegalArgumentException("Value size must be positive"); - this.valueSize = valueSize; - return self(); - } - - public 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.

    - * - * @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(boolean replicated) { - if (entrySize > 0) - return entryAndValueAlignment().alignSize(entrySize); - int size = metaDataBytes; - int keySize = keySize(); - size += keyBuilder.sizeMarshaller().sizeEncodingSize(keySize); - size += keySize; - if (replicated) - 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); - } - - public B entryAndValueAlignment(Alignment alignment) { - this.alignment = alignment; - return self(); - } - - Alignment entryAndValueAlignment() { - if (alignment != null) - return alignment; - return offHeapReference(valueBuilder.eClass) ? Alignment.OF_4_BYTES : Alignment.NO_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 " + - MultiMapFactory.MAX_CAPACITY + ", " + actualEntriesPerSegment + " given"); - this.actualEntriesPerSegment = actualEntriesPerSegment; - return self(); - } - - private boolean tooManyEntriesPerSegment(long entriesPerSegment) { - return entriesPerSegment > MultiMapFactory.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 " + - MultiMapFactory.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; - return Math.min(entries * segments, - (long) (entries + poorDistEntriesScale * 0.14 + 32)); // 12% 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() { - return (int) Math.min(Maths.nextPower2(entries / 32, 1), estimateSegementBasedOnSize()); - } - - private int estimateSegementBasedOnSize() { - // based on entries with multimap of 100 bytes. - long size = (long) (Math.log10(entrySize + 32) / 2 * entries); - if (size < 10 * 1000) - return 512; - if (size < 100 * 1000) - return 1024; - if (size < 1000 * 1000) - return 2 * 1024; - if (size < 10 * 1000 * 1000) - return 4 * 1024; - if (size < 100 * 1000 * 1000) - return 8 * 1024; - return 16 * 1024; - } - - @Override - public B actualSegments(int actualSegments) { - checkSegments(actualSegments); - this.actualSegments = actualSegments; - return self(); - } - - int actualSegments() { - if (actualSegments > 0) - return actualSegments; - long shortMMapSegments = trySegments(MultiMapFactory.I16_MAX_CAPACITY, - MAX_SEGMENTS_TO_CHAISE_COMPACT_MULTI_MAPS); - if (shortMMapSegments > 0L) - return (int) shortMMapSegments; - long intMMapSegments = trySegments(MultiMapFactory.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; - } - - int segmentHeaderSize() { - int segments = actualSegments(); - // reduce false sharing unless we have a lot of segments. - return segments <= 16 * 1024 ? 64 : 32; - } - - MultiMapFactory multiMapFactory() { - return MultiMapFactory.forCapacity(actualEntriesPerSegment()); - } - - 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{" + - "name=" + name + - ", 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) + - '}'; - } - - @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") - @Override - public boolean equals(Object o) { - return builderEquals(this, o); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @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 acquireObjectSerializer(ObjectSerializer defaultSerializer) { - return objectSerializer == null ? - BytesMarshallableSerializer.create(bytesMarshallerFactory(), defaultSerializer) : - objectSerializer; - } - - @Override - public B objectSerializer(ObjectSerializer objectSerializer) { - this.objectSerializer = objectSerializer; - return self(); - } - - /** - * {@inheritDoc} - * - *

    Example: - */ - @Override - public B keyMarshaller(@NotNull BytesMarshaller keyMarshaller) { - keyBuilder.marshaller(keyMarshaller); - return self(); - } - - @Override - public B keyMarshallers(@NotNull BytesWriter keyWriter, @NotNull BytesReader keyReader) { - keyBuilder.writer(keyWriter); - keyBuilder.reader(keyReader); - return self(); - } - - @Override - public B keySizeMarshaller(@NotNull SizeMarshaller keySizeMarshaller) { - keyBuilder.sizeMarshaller(keySizeMarshaller); - 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 StatelessClientConfig> statelessClient( - InetSocketAddress remoteAddress) { - return new StatelessMapConfig<>(this.clone(), remoteAddress); - } - - /** - * 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; - } - - public B bytesEventListener(BytesMapEventListener eventListener) { - this.bytesEventListener = eventListener; - return self(); - } - - BytesMapEventListener bytesEventListener() { - return bytesEventListener; - } - - /** - * 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(); - } - - public B prepareValueBytesOnAcquire(@NotNull PrepareValueBytes prepareValueBytes) { - this.prepareValueBytes = prepareValueBytes; - this.defaultValue = null; - this.defaultValueProvider = null; - return self(); - } - - PrepareValueBytesAsWriter prepareValueBytesAsWriter() { - if (prepareValueBytes == null) - return null; - return new PrepareValueBytesAsWriter<>(prepareValueBytes, valueSize()); - } - - /** - * Non-public because should be called only after {@link #preMapConstruction(boolean)} - */ - 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); - } - - @Override - public B replication(SingleChronicleHashReplication replication) { - this.singleHashReplication = replication; - return self(); - } - - @Override - public B replication(byte identifier) { - return replication(SingleChronicleHashReplication.builder().createWithId(identifier)); - } - - @Override - public B replication(byte identifier, TcpTransportAndNetworkConfig tcpTransportAndNetwork) { - return replication(SingleChronicleHashReplication.builder() - .tcpTransportAndNetwork(tcpTransportAndNetwork).createWithId(identifier)); - } - - @Override - public ChronicleHashInstanceConfig> instance() { - return new InstanceConfig<>(this.clone(), singleHashReplication, null, null); - } - - @Override - public ChronicleMap createPersistedTo(File file) throws IOException { - // clone() to make this builder instance thread-safe, because createWithFile() method - // computes some state based on configurations, but doesn't synchronize on configuration - // changes. - return clone().createWithFile(file, singleHashReplication, null); - } - - @Override - public ChronicleMap create() { - // clone() to make this builder instance thread-safe, because createWithoutFile() method - // computes some state based on configurations, but doesn't synchronize on configuration - // changes. - return clone().createWithoutFile(singleHashReplication, null); - } - - ChronicleMap create(InstanceConfig ib) throws IOException { - if (ib.file != null) { - return createWithFile(ib.file, ib.singleHashReplication, ib.channel); - } else { - return createWithoutFile(ib.singleHashReplication, ib.channel); - } - } - - ChronicleMap createWithFile(File file, SingleChronicleHashReplication singleHashReplication, - ReplicationChannel channel) throws IOException { - pushingToMapEventListener(); - for (int i = 0; i < 10; i++) { - if (file.exists() && file.length() > 0) { - try (FileInputStream fis = new FileInputStream(file); - ObjectInputStream ois = new ObjectInputStream(fis)) { - VanillaChronicleMap map = - (VanillaChronicleMap) ois.readObject(); - map.headerSize = roundUpMapHeaderSize(fis.getChannel().position()); - map.createMappedStoreAndSegments(file); - return establishReplication(map, singleHashReplication, channel); - } catch (ClassNotFoundException e) { - throw new IOException(e); - } - } - 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(singleHashReplication, channel); - - try (FileOutputStream fos = new FileOutputStream(file); - ObjectOutputStream oos = new ObjectOutputStream(fos)) { - oos.writeObject(map); - oos.flush(); - map.headerSize = roundUpMapHeaderSize(fos.getChannel().position()); - map.createMappedStoreAndSegments(file); - } - - return establishReplication(map, singleHashReplication, channel); - } - - /** - * @param identifier id - * @return map - */ - public ChronicleMap createReplicated(byte identifier) throws IOException { - pushingToMapEventListener(); - preMapConstruction(true); - VanillaChronicleMap map = - new ReplicatedChronicleMap, - V, Object, MetaBytesInterop>(this, identifier); - BytesStore bytesStore = new DirectStore(JDKZObjectSerializer.INSTANCE, - map.sizeInBytes(), true); - map.createMappedStoreAndSegments(bytesStore); - return map; - } - - ChronicleMap createWithoutFile( - SingleChronicleHashReplication singleHashReplication, ReplicationChannel channel) { - pushingToMapEventListener(); - try { - VanillaChronicleMap map = newMap(singleHashReplication, channel); - BytesStore bytesStore = new DirectStore(JDKZObjectSerializer.INSTANCE, - map.sizeInBytes(), true); - map.createMappedStoreAndSegments(bytesStore); - return establishReplication(map, singleHashReplication, channel); - } catch (IOException e) { - // file-less version should never trigger an IOException. - throw new AssertionError(e); - } - } - - private void pushingToMapEventListener() { - if (pushToAddresses == null || pushToAddresses.length == 0) { - return; - } - try { - Class pmel = Class.forName("com.higherfrequencytrading.chronicle.enterprise.map.PushingMapEventListener"); - Constructor constructor = pmel.getConstructor(ChronicleMap[].class); - // create a stateless client for each address - ChronicleMap[] statelessClients = new ChronicleMap[pushToAddresses.length]; - ChronicleMapBuilderI cmb = clone(); - cmb.pushTo((InetSocketAddress[]) null); - for (int i = 0; i < pushToAddresses.length; i++) { - statelessClients[i] = cmb.statelessClient(pushToAddresses[i]).create(); - } - eventListener = (MapEventListener) constructor.newInstance((Object) statelessClients); - } catch (ClassNotFoundException e) { - LoggerFactory.getLogger(getClass().getName()).warn("Chronicle Enterprise not found in the class path"); - } catch (Exception e) { - LoggerFactory.getLogger(getClass().getName()).error("PushingMapEventListener failed to load", e); - } - } - - ChronicleMap createStatelessMap(StatelessMapConfig statelessBuilder) - throws IOException { - pushingToMapEventListener(); - preMapConstruction(false); - return new StatelessChronicleMap( - statelessBuilder, - this); - } - - private VanillaChronicleMap newMap( - SingleChronicleHashReplication singleHashReplication, ReplicationChannel channel) throws IOException { - boolean replicated = singleHashReplication != null || channel != null; - preMapConstruction(replicated); - if (replicated) { - byte identifier; - if (singleHashReplication != null) { - identifier = singleHashReplication.identifier(); - } else { - identifier = channel.hub().identifier(); - } - return new ReplicatedChronicleMap, - V, Object, MetaBytesInterop>(this, identifier); - } else { - return new VanillaChronicleMap, - V, Object, MetaBytesInterop>(this); - } - } - - void preMapConstruction(boolean replicated) { - keyBuilder.objectSerializer(acquireObjectSerializer(JDKObjectSerializer.INSTANCE)); - valueBuilder.objectSerializer(acquireObjectSerializer(JDKObjectSerializer.INSTANCE)); - - long maxSize = (long) entrySize(replicated) * Math.min(maxEntryOversizeFactor(), figureBufferAllocationFactor()); - keyBuilder.maxSize(maxSize); - valueBuilder.maxSize(maxSize); - - if (sampleKey != null) - keyBuilder.constantSizeBySample(sampleKey); - if (sampleValue != null) - valueBuilder.constantSizeBySample(sampleValue); - } - - private ChronicleMap establishReplication( - VanillaChronicleMap map, SingleChronicleHashReplication singleHashReplication, - ReplicationChannel channel) throws IOException { - if (map instanceof ReplicatedChronicleMap) { - if (singleHashReplication != null && channel != null) { - throw new AssertionError("Only one non-null replication should be passed"); - } - ReplicatedChronicleMap result = (ReplicatedChronicleMap) map; - List replicators = new ArrayList<>(2); - if (singleHashReplication != null) { - if (singleHashReplication.tcpTransportAndNetwork() != null) - replicators.add(Replicators.tcp(singleHashReplication)); - if (singleHashReplication.udpTransport() != null) - replicators.add(Replicators.udp(singleHashReplication.udpTransport())); - } else { - ReplicationHub hub = channel.hub(); - int entrySize = entrySize(true); - if (entrySize > hub.maxEntrySize()) { - throw new IllegalArgumentException("During ChannelProviderBuilder setup, " + - "maxEntrySize=" + hub.maxEntrySize() + - " was specified, but map with " + - "entrySize=" + entrySize + " is attempted to apply to the replicator"); - } - ChannelProvider provider = ChannelProvider.getProvider(hub); - ChannelProvider.ChronicleChannel ch = provider.createChannel(channel.channelId()); - replicators.add(ch); - } - for (Replicator replicator : replicators) { - Closeable token = replicator.applyTo(this, result, result, map); - if (replicators.size() == 1 && token.getClass() == UdpReplicator.class) { - LOG.warn(Replicators.ONLY_UDP_WARN_MESSAGE); - } - 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.max(4L, entries() >> 10); - } - - @Override - public ChronicleMapBuilderI name(String name) { - this.name = name; - return this; - } - - @Override - public ChronicleMapBuilderI bootstrapOnlyLocalEntries(boolean bootstapOnlyLocalEntries) { - this.bootstrapOnlyLocalEntries = bootstapOnlyLocalEntries; - return this; - } - - public String name() { - return this.name; - } - - @Override - public ChronicleMapBuilderI disableOversizedEntries() { - this.disableOversizedEntries = true; - return this; - } - - public int maxEntryOversizeFactor() { - return disableOversizedEntries ? 1 : VanillaChronicleMap.MAX_ENTRY_OVERSIZE_FACTOR; - } -} - diff --git a/src/main/java/net/openhft/chronicle/map/BytesMapEventListener.java b/src/main/java/net/openhft/chronicle/map/BytesMapEventListener.java index 2746dd9fd..745221023 100644 --- a/src/main/java/net/openhft/chronicle/map/BytesMapEventListener.java +++ b/src/main/java/net/openhft/chronicle/map/BytesMapEventListener.java @@ -33,7 +33,7 @@ import java.io.Serializable; * by the serialized key itself.
  7. *
  8. From {@code valuePos} offset value area in the given {@code entry}, serialized value size * (using {@linkplain ChronicleMapBuilder#keySizeMarshaller(SizeMarshaller)}), then (optionally) - * alignment (see {@link OffHeapUpdatableChronicleMapBuilder#entryAndValueAlignment(Alignment)} + * alignment (see {@link ChronicleMapBuilder#entryAndValueAlignment(Alignment)} * ), followed by the serialized value itself.
  9. * */ diff --git a/src/main/java/net/openhft/chronicle/map/ChannelProvider.java b/src/main/java/net/openhft/chronicle/map/ChannelProvider.java index ea56231c2..bedc095cb 100644 --- a/src/main/java/net/openhft/chronicle/map/ChannelProvider.java +++ b/src/main/java/net/openhft/chronicle/map/ChannelProvider.java @@ -506,7 +506,7 @@ final class ChannelProvider implements Closeable { } @Override - protected Closeable applyTo(AbstractChronicleMapBuilder builder, + protected Closeable applyTo(ChronicleMapBuilder builder, Replica map, EntryExternalizable entryExternalizable, final ChronicleMap chronicleMap) { add(chronicleChannel, map, entryExternalizable); diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java index db2acbc35..d2f85c646 100755 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java @@ -36,7 +36,7 @@ import java.util.concurrent.ConcurrentMap; * *

    For information on

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

    Functionally this interface defines some methods supporting garbage-free off-heap programming: * {@link #getUsing(Object, Object)}, {@link #acquireUsing(Object, Object)}. @@ -98,7 +98,7 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { * no mapping for the key * @see #get(Object) * @see #acquireUsing(Object, Object) - * @see OnHeapUpdatableChronicleMapBuilder#valueMarshaller(BytesMarshaller) + * @see ChronicleMapBuilder#valueMarshaller(BytesMarshaller) */ V getUsing(K key, V usingValue); @@ -141,7 +141,7 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { * no mapping for the key * @see #get(Object) * @see #getUsing(Object, Object) - * @see OnHeapUpdatableChronicleMapBuilder#valueMarshaller(BytesMarshaller) + * @see ChronicleMapBuilder#valueMarshaller(BytesMarshaller) */ @NotNull ReadContext getUsingLocked(@NotNull K key, @NotNull V usingValue); @@ -149,8 +149,8 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { /** * Acquire a value for a key, creating if absent. * - *

    If the specified key is absent in the map, {@linkplain AbstractChronicleMapBuilder#defaultValue(Object) - * default value} is taken or {@linkplain AbstractChronicleMapBuilder#defaultValueProvider(DefaultValueProvider) + *

    If the specified key is absent in the map, {@linkplain ChronicleMapBuilder#defaultValue(Object) + * default value} is taken or {@linkplain ChronicleMapBuilder#defaultValueProvider(DefaultValueProvider) * default value provider} is called. Then this object is put to this map for the specified * key. * @@ -169,8 +169,8 @@ public interface ChronicleMap extends ConcurrentMap, ChronicleHash { * }} * * - * Where {@code defaultValue(key)} returns either {@linkplain AbstractChronicleMapBuilder#defaultValue(Object) - * default value} or {@link AbstractChronicleMapBuilder#defaultValueProvider(DefaultValueProvider) + * Where {@code defaultValue(key)} returns either {@linkplain ChronicleMapBuilder#defaultValue(Object) + * default value} or {@link ChronicleMapBuilder#defaultValueProvider(DefaultValueProvider) * defaultValueProvider.} * *

    If the {@code ChronicleMap} is off-heap updatable, i. e. created via {@link diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java index 5dcee5383..bb0a72da2 100755 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java @@ -1,104 +1,423 @@ +/* + * 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.hash.ChronicleHashBuilder; -import net.openhft.chronicle.hash.ChronicleHashErrorListener; -import net.openhft.chronicle.hash.ChronicleHashInstanceConfig; -import net.openhft.chronicle.hash.StatelessClientConfig; -import net.openhft.chronicle.hash.replication.SingleChronicleHashReplication; -import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; -import net.openhft.chronicle.hash.replication.TimeProvider; +import net.openhft.chronicle.hash.*; +import net.openhft.chronicle.hash.replication.*; +import net.openhft.chronicle.hash.serialization.BytesInterop; import net.openhft.chronicle.hash.serialization.BytesReader; import net.openhft.chronicle.hash.serialization.BytesWriter; +import net.openhft.chronicle.hash.serialization.DeserializationFactoryConfigurableBytesReader; import net.openhft.chronicle.hash.serialization.SizeMarshaller; -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.model.Byteable; -import net.openhft.lang.model.DataValueClasses; +import net.openhft.chronicle.hash.serialization.SizeMarshallers; +import net.openhft.chronicle.hash.serialization.internal.MetaBytesInterop; +import net.openhft.chronicle.hash.serialization.internal.MetaBytesWriter; +import net.openhft.chronicle.hash.serialization.internal.MetaProvider; +import net.openhft.chronicle.set.ChronicleSetBuilder; +import net.openhft.lang.Maths; +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.*; +import net.openhft.lang.threadlocal.Provider; +import net.openhft.lang.threadlocal.ThreadLocalCopies; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.IOException; +import java.io.*; +import java.lang.reflect.Constructor; import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static net.openhft.chronicle.map.Objects.builderEquals; +import static net.openhft.lang.model.DataValueGenerator.firstPrimitiveFieldType; /** - * {@code ChronicleMapBuilder} is intended to be used to create {@link ChronicleMap}s + * {@code ChronicleMapBuilder} manages {@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 = ChronicleMapOnHeapUpdatableBuilder
    + *     .of(Key.class, Value.class)
    + *     .entries(100500)
    + *     // ... other configurations
    + *     .create();}
    + * it could be prepared and used to create many similar maps:
    {@code
    + * ChronicleMapBuilder builder = ChronicleMapBuilder
    + *     .of(Key.class, Value.class)
    + *     .entries(100500);
      *
    - * @param  key type of the maps, created by this builder
    - * @param  value type of the maps, created by this builder
    - * @see AbstractChronicleMapBuilder
    - * @see net.openhft.chronicle.hash.ChronicleHashBuilder
    + * ChronicleMap map1 = builder.create();
    + * ChronicleMap map2 = builder.create();}
    + * i. e. created {@code ChronicleMap} instances don't depend on the builder. + * + *

    {@code ChronicleMapBuilder} is mutable, see a note in {@link 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 */ -public final class ChronicleMapBuilder implements ChronicleMapBuilderI, ChronicleHashBuilder, ChronicleMapBuilder> { +public final class ChronicleMapBuilder implements Cloneable, + ChronicleHashBuilder, ChronicleMapBuilder> { - final ChronicleMapBuilderI delegate; + static final byte UDP_REPLICATION_MODIFICATION_ITERATOR_ID = (byte)128; + 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); + private static final Logger LOG = + LoggerFactory.getLogger(ChronicleMapBuilder.class.getName()); + public static final long DEFAULT_STATELESS_CLIENT_TIMEOUT = TimeUnit.SECONDS.toMillis(10); + + SerializationBuilder keyBuilder; + SerializationBuilder valueBuilder; + + private String name; + + // 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 int maxEntryOversizeFactor = -1; + private Alignment alignment = null; + private long entries = 1 << 20; // 1 million by default + private long lockTimeOut = 20000; + 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 = null; + private BytesMapEventListener bytesEventListener = null; + private V defaultValue = null; + private DefaultValueProvider defaultValueProvider = null; + private PrepareValueBytes prepareValueBytes = null; + + private SingleChronicleHashReplication singleHashReplication = null; + private InetSocketAddress[] pushToAddresses; + + ChronicleMapBuilder(Class keyClass, Class valueClass) { + keyBuilder = new SerializationBuilder<>(keyClass, SerializationBuilder.Role.KEY); + valueBuilder = new SerializationBuilder<>(valueClass, SerializationBuilder.Role.VALUE); + } /** * 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 internal builder. - * - * @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 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) { - if (valueClass.isEnum()) - return new ChronicleMapBuilder(OnHeapUpdatableChronicleMapBuilder.of(keyClass, valueClass)); + return new ChronicleMapBuilder<>(keyClass, valueClass); + } - if (keyClass.isInterface() && !builtInType(keyClass)) - keyClass = DataValueClasses.directClassFor(keyClass); + private static long roundUpMapHeaderSize(long headerSize) { + long roundUp = (headerSize + 127L) & ~127L; + if (roundUp - headerSize < 64) + roundUp += 128; + return roundUp; + } - if ((valueClass.isInterface() && !builtInType(valueClass))) { - valueClass = DataValueClasses.directClassFor(valueClass); - } else if (!offHeapReference(valueClass)) { - return new ChronicleMapBuilder(OnHeapUpdatableChronicleMapBuilder.of(keyClass, valueClass)); + 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"); + } + + private static long divideUpper(long dividend, long divisor) { + return ((dividend - 1L) / divisor) + 1L; + } + + private static String pretty(int value) { + return value > 0 ? value + "" : "not configured"; + } + + private static String pretty(Object obj) { + return obj != null ? obj + "" : "not configured"; + } + + public ChronicleMapBuilder pushTo(InetSocketAddress... addresses) { + this.pushToAddresses = addresses; + return this; + } + + @Override + public ChronicleMapBuilder clone() { + try { + @SuppressWarnings("unchecked") + ChronicleMapBuilder result = + (ChronicleMapBuilder) super.clone(); + result.keyBuilder = keyBuilder.clone(); + result.valueBuilder = valueBuilder.clone(); + return result; + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); } - - ChronicleMapBuilderI builder = new OffHeapUpdatableChronicleMapBuilder(keyClass, valueClass); - return new ChronicleMapBuilder(builder); - } - - - - static boolean builtInType(Class clazz) { - return clazz.getClassLoader() == Class.class.getClassLoader(); - } - - ChronicleMapBuilder(ChronicleMapBuilderI delegate) { - this.delegate = delegate; - } - - static boolean offHeapReference(Class valueClass) { - return Byteable.class.isAssignableFrom(valueClass); } /** - * {@inheritDoc} With respect to {@linkplain #entryAndValueAlignment(Alignment) alignment}. + * {@inheritDoc} * - *

    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, and - * entry alignment is set to {@link Alignment#OF_4_BYTES}, the actual entry size will be 32 (30 - * aligned to 4 bytes). + *

    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, 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 #entryAndValueAlignment(Alignment) // * @see #entries(long) + * @throws IllegalStateException {@inheritDoc} + * @throws IllegalArgumentException {@inheritDoc} + * @see #constantKeySizeBySample(Object) + * @see #valueSize(int) + * @see #entrySize(int) + */ + @Override + public ChronicleMapBuilder keySize(int keySize) { + checkSizeIsNotStaticallyKnown(keyBuilder); + if (keySize <= 0) + throw new IllegalArgumentException("Key size must be positive"); + this.keySize = keySize; + return this; + } + + /** + * {@inheritDoc} + * + *

    For example, if your keys are Git commit hashes:

    {@code
    +     * Map gitCommitMessagesByHash =
    +     *     ChronicleMapBuilder.of(byte[].class, String.class)
    +     *     .constantKeySizeBySample(new byte[20])
    +     *     .immutableKeys()
    +     *     .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); + } + + /** + * Configures the optimal number of bytes, taken by serialized form of values, put into maps, + * created by this builder. If value size is always the same, call {@link + * #constantValueSizeBySample(Object)} method instead of this one. + * + *

    If values are of 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 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, + * it's better to use {@linkplain #entrySize(int) entry size} in "chunk" mode and configure it + * directly. + * + * @param valueSize number of bytes, taken by serialized form of values + * @return this builder back + * @throws IllegalStateException if value size is known statically and shouldn't be configured + * by user + * @throws IllegalArgumentException if the given {@code valueSize} is non-positive + * @see #constantValueSizeBySample(Object) + * @see #keySize(int) + * @see #entrySize(int) + */ + public ChronicleMapBuilder valueSize(int valueSize) { + checkSizeIsNotStaticallyKnown(valueBuilder); + if (valueSize <= 0) + throw new IllegalArgumentException("Value size must be positive"); + this.valueSize = valueSize; + return this; + } + + private static void checkSizeIsNotStaticallyKnown(SerializationBuilder builder) { + if (builder.sizeIsStaticallyKnown) + throw new IllegalStateException("Size of type " + builder.eClass + + " is statically known and shouldn't be specified manually"); + } + + /** + * Configures the constant number of bytes, taken by serialized form of values, put into maps, + * created by this builder. This is done by providing the {@code sampleValue}, all values should + * take the same number of bytes in serialized form, as this sample object. + * + *

    If values are of boxed primitive type or {@link Byteable} subclass, i. e. if value size + * is known statically, it is automatically accounted and this method shouldn't be called. + * + *

    If value size varies, method {@link #valueSize(int)} or {@link #entrySize(int)} should be + * called instead of this one. + * + * @param sampleValue the sample value + * @return this builder back + * @see #valueSize(int) + * @see #constantKeySizeBySample(Object) + */ + public ChronicleMapBuilder constantValueSizeBySample(V sampleValue) { + this.sampleValue = sampleValue; + return this; + } + + int valueSize() { + return keyOrValueSize(valueSize, valueBuilder); + } + + private int keyOrValueSize(int configuredSize, SerializationBuilder builder) { + if (configuredSize > 0) + return configuredSize; + if (builder.constantSizeMarshaller()) + return builder.pseudoReadConstantSize(); + 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 by the configured {@linkplain + * #entryAndValueAlignment(Alignment) entry and value alignment}. I. e. if you set entry size + * to 30, and entry alignment is set to {@link Alignment#OF_4_BYTES}, the actual entry size will + * be 32 (30 aligned to 4 bytes). + * + * @throws IllegalStateException is sizes of both keys and values of maps created by this + * builder are statically known, hence entry size shouldn't be configured by user + * @see #entryAndValueAlignment(Alignment) + * @see #entries(long) + * @see #maxEntryOversizeFactor(int) */ @Override public ChronicleMapBuilder entrySize(int entrySize) { - delegate.entrySize(entrySize); + if (keyBuilder.sizeIsStaticallyKnown && valueBuilder.sizeIsStaticallyKnown) { + throw new IllegalStateException("Sizes of key type: " + keyBuilder.eClass + " and " + + "value type: " + valueBuilder.eClass + " are both statically known, " + + "so entry size shouldn't be specified manually"); + } + if (entrySize <= 0) + throw new IllegalArgumentException("Entry Size must be positive"); + this.entrySize = entrySize; return this; } + int entrySize(boolean replicated) { + if (entrySize > 0) + return entryAndValueAlignment().alignSize(entrySize); + int size = metaDataBytes; + int keySize = keySize(); + size += keyBuilder.sizeMarshaller().sizeEncodingSize(keySize); + size += keySize; + if (replicated) + 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); + } + + @Override + public ChronicleMapBuilder maxEntryOversizeFactor(int maxEntryOversizeFactor) { + if (maxEntryOversizeFactor < 1 || maxEntryOversizeFactor > 64) + throw new IllegalArgumentException("maxEntryOversizeFactor should be in [1, 64] " + + "range, " + maxEntryOversizeFactor + " given"); + this.maxEntryOversizeFactor = maxEntryOversizeFactor; + return this; + } + + int maxEntryOversizeFactor() { + if (maxEntryOversizeFactor < 0) + return keyBuilder.constantSizeMarshaller() && valueBuilder.constantSizeMarshaller() ? + 1 : 64; + return maxEntryOversizeFactor; + } + /** * 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. @@ -111,115 +430,324 @@ public final class ChronicleMapBuilder implements ChronicleMapBuilderIDefault is {@link Alignment#OF_4_BYTES} for Byteable values. + *

    If values couldn't reference off-heap memory (i. e. it is not {@link Byteable} or + * "data value generated"), alignment configuration makes no sense and forbidden. + * + *

    Default is {@link Alignment#NO_ALIGNMENT} if values couldn't reference off-heap memory, + * otherwise chosen heuristically (configure explicitly for being sure and to compare + * performance in your case). * * @param alignment the new alignment of the maps constructed by this builder * @return this {@code ChronicleMapOnHeapUpdatableBuilder} back + * @throws IllegalStateException if values of maps, created by this builder, couldn't reference + * off-heap memory */ - @Override public ChronicleMapBuilder entryAndValueAlignment(Alignment alignment) { - delegate.entryAndValueAlignment(alignment); + this.alignment = alignment; + checkAlignmentOnlyIfValuesPossiblyReferenceOffHeap(); return this; } + private void checkAlignmentOnlyIfValuesPossiblyReferenceOffHeap() { + if (!valueBuilder.possibleOffHeapReferences() && + (alignment == Alignment.OF_4_BYTES || alignment == Alignment.OF_8_BYTES)) + throw new IllegalStateException("Entry and value alignment should be configured only " + + "if values might point to off-heap memory"); + } + + Alignment entryAndValueAlignment() { + if (alignment != null) + return alignment; + Class firstPrimitiveFieldType = firstPrimitiveFieldType(valueBuilder.eClass); + if (firstPrimitiveFieldType == long.class || firstPrimitiveFieldType == double.class) + return Alignment.OF_8_BYTES; + if (firstPrimitiveFieldType == int.class || firstPrimitiveFieldType == float.class) + return Alignment.OF_4_BYTES; + return Alignment.NO_ALIGNMENT; + } + /** - * {@inheritDoc} Also, it overrides any previous {@link #prepareValueBytesOnAcquire} - * configuration to this {@code ChronicleMapBuilder}. - * - *

    By default, the default value is not specified, default {@linkplain - * #prepareValueBytesOnAcquire prepare value bytes routine} is specified instead. - * - * @see #defaultValueProvider(DefaultValueProvider) - * @see #prepareValueBytesOnAcquire(PrepareValueBytes) + * Package-private for tests */ + + @Override - public ChronicleMapBuilder defaultValue(V defaultValue) { - delegate.defaultValue(defaultValue); + 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 " + + MultiMapFactory.MAX_CAPACITY + ", " + actualEntriesPerSegment + " given"); + this.actualEntriesPerSegment = actualEntriesPerSegment; + return this; + } + + private boolean tooManyEntriesPerSegment(long entriesPerSegment) { + return entriesPerSegment > MultiMapFactory.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 " + + MultiMapFactory.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; + return Math.min(entries * segments, + (long) (entries + poorDistEntriesScale * 0.14 + 32)); // 12% 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() { + return (int) Math.min(Maths.nextPower2(entries / 32, 1), estimateSegementBasedOnSize()); + } + + private int estimateSegementBasedOnSize() { + // based on entries with multimap of 100 bytes. + long size = (long) (Math.log10(entrySize + 32) / 2 * entries); + if (size < 10 * 1000) + return 512; + if (size < 100 * 1000) + return 1024; + if (size < 1000 * 1000) + return 2 * 1024; + if (size < 10 * 1000 * 1000) + return 4 * 1024; + if (size < 100 * 1000 * 1000) + return 8 * 1024; + return 16 * 1024; + } + + @Override + public ChronicleMapBuilder actualSegments(int actualSegments) { + checkSegments(actualSegments); + this.actualSegments = actualSegments; + return this; + } + + int actualSegments() { + if (actualSegments > 0) + return actualSegments; + long shortMMapSegments = trySegments(MultiMapFactory.I16_MAX_CAPACITY, + MAX_SEGMENTS_TO_CHAISE_COMPACT_MULTI_MAPS); + if (shortMMapSegments > 0L) + return (int) shortMMapSegments; + long intMMapSegments = trySegments(MultiMapFactory.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; + } + + int segmentHeaderSize() { + int segments = actualSegments(); + // reduce false sharing unless we have a lot of segments. + return segments <= 16 * 1024 ? 64 : 32; + } + + MultiMapFactory multiMapFactory() { + return MultiMapFactory.forCapacity(actualEntriesPerSegment()); + } + + 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 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. See {@link PrepareValueBytes} for more - * information. + * 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. * - *

    The default preparation callback zeroes out the value bytes. + *

    {@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. * - * @param prepareValueBytes what to do with the value bytes before assigning them into the - * {@link net.openhft.lang.model.Byteable} value to return from {@code - * acquireUsing()} call + *

    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 this builder back - * @see PrepareValueBytes - * @see #defaultValue(Object) - * @see #defaultValueProvider(DefaultValueProvider) + * @see #removeReturnsNull(boolean) */ - @Override - public ChronicleMapBuilder prepareValueBytesOnAcquire(@NotNull PrepareValueBytes prepareValueBytes) { - delegate.prepareValueBytesOnAcquire(prepareValueBytes); + public ChronicleMapBuilder putReturnsNull(boolean putReturnsNull) { + this.putReturnsNull = putReturnsNull; return this; } - @Override - public ChronicleMapBuilder keySize(int keySize) { - delegate.keySize(keySize); - return this; - } - - @Override - public ChronicleMapBuilder constantKeySizeBySample(K sampleKey) { - delegate.constantKeySizeBySample(sampleKey); - return this; + boolean putReturnsNull() { + return putReturnsNull; } /** - * Configures the optimal number of bytes, taken by serialized form of values, put into maps, - * created by this builder. If value size is always the same, call {@link - * #constantValueSizeBySample(Object)} method instead of this one. + * 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. * - *

    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. + *

    {@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. * - *

    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, - * it's better to use {@linkplain #entrySize(int) entry size} in "chunk" mode and configure it - * directly. + *

    By default, of cause, {@code ChronicleMap} conforms the general {@code Map} contract + * and returns the mapped value on {@code remove()} calls. * - * @param valueSize number of bytes, taken by serialized form of values - * @return this {@code ChronicleMapOnHeapUpdatableBuilder} back - * @see #constantValueSizeBySample(Object) - * @see #keySize(int) - * @see #entrySize(int) - */ - @Override - public ChronicleMapBuilder valueSize(int valueSize) { - delegate.valueSize(valueSize); - return this; - } - - /** - * Configures the constant number of bytes, taken by serialized form of values, put into maps, - * created by this builder. This is done by providing the {@code sampleValue}, all values should - * take the same number of bytes in serialized form, as this sample object. - * - *

    If values are of boxed primitive type or {@link net.openhft.lang.model.Byteable} subclass, - * i. e. if value size is known statically, it is automatically accounted and this method - * shouldn't be called. - * - *

    If value size varies, method {@link #valueSize(int)} or {@link #entrySize(int)} should be - * called instead of this one. - * - * @param sampleValue the sample value + * @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 this builder back - * @see #valueSize(int) - * @see #constantKeySizeBySample(Object) + * @see #putReturnsNull(boolean) */ - @Override - public ChronicleMapBuilder constantValueSizeBySample(@NotNull V sampleValue) { - delegate.constantValueSizeBySample(sampleValue); + public ChronicleMapBuilder removeReturnsNull(boolean removeReturnsNull) { + this.removeReturnsNull = removeReturnsNull; return this; } + boolean removeReturnsNull() { + return removeReturnsNull; + } + + @Override + public ChronicleMapBuilder metaDataBytes(int metaDataBytes) { + if (metaDataBytes < 0 || metaDataBytes > 255) + 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{" + + "name=" + name + + ", 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) + + '}'; + } + + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + @Override + public boolean equals(Object o) { + return builderEquals(this, o); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @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 acquireObjectSerializer(ObjectSerializer defaultSerializer) { + return objectSerializer == null ? + BytesMarshallableSerializer.create(bytesMarshallerFactory(), defaultSerializer) : + objectSerializer; + } + /** * {@inheritDoc} * @@ -230,81 +758,97 @@ public final class ChronicleMapBuilder implements ChronicleMapBuilderI + * + *

    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 #valueMarshaller(BytesMarshaller) value} marshallers are not configured. */ @Override - public ChronicleMapBuilder objectSerializer(@NotNull ObjectSerializer objectSerializer) { - delegate.objectSerializer(objectSerializer); + public ChronicleMapBuilder objectSerializer(ObjectSerializer objectSerializer) { + this.objectSerializer = objectSerializer; + return this; + } + + @Override + public ChronicleMapBuilder keyMarshaller( + @NotNull BytesMarshaller keyMarshaller) { + keyBuilder.marshaller(keyMarshaller); + return this; + } + + @Override + public ChronicleMapBuilder keyMarshallers( + @NotNull BytesWriter keyWriter, @NotNull BytesReader keyReader) { + keyBuilder.writer(keyWriter); + keyBuilder.reader(keyReader); + return this; + } + + @Override + public ChronicleMapBuilder keySizeMarshaller(@NotNull SizeMarshaller keySizeMarshaller) { + keyBuilder.sizeMarshaller(keySizeMarshaller); return this; } /** - * {@inheritDoc} - * - * @throws IllegalStateException if custom value marshaller is specified or value class is not - * either {@code BytesMarshallable} or {@code Externalizable} + * @throws IllegalStateException {@inheritDoc} + * @see #valueDeserializationFactory(ObjectFactory) */ - @Override - public ChronicleMapBuilder valueDeserializationFactory(@NotNull ObjectFactory valueDeserializationFactory) { - delegate.valueDeserializationFactory(valueDeserializationFactory); + public ChronicleMapBuilder keyDeserializationFactory( + @NotNull ObjectFactory keyDeserializationFactory) { + keyBuilder.factory(keyDeserializationFactory); return this; } - /** - * {@inheritDoc} - * - *

    By default, default value provider is not specified, {@link #defaultValue(Object) default - * value} is specified instead. - * - * @see #defaultValue(Object) - */ @Override - public ChronicleMapBuilder defaultValueProvider(@NotNull DefaultValueProvider defaultValueProvider) { - delegate.defaultValueProvider(defaultValueProvider); + public ChronicleMapBuilder immutableKeys() { + keyBuilder.instancesAreMutable(false); return this; } /** * Configures the {@code BytesMarshaller} used to serialize/deserialize values to/from off-heap - * memory in maps, created by this builder. See the - * section about serialization in ChronicleMap manual for more information. + * memory in maps, created by this builder. See the section about serialization + * in ChronicleMap manual for more information. * * @param valueMarshaller the marshaller used to serialize values * @return this builder back - * @see #valueMarshallers(net.openhft.chronicle.hash.serialization.BytesWriter, - * net.openhft.chronicle.hash.serialization.BytesReader) - * @see ChronicleMapBuilder + * @see #valueMarshallers(BytesWriter, BytesReader) * @see #objectSerializer(ObjectSerializer) + * @see #keyMarshaller(BytesMarshaller) */ - @Override public ChronicleMapBuilder valueMarshaller( @NotNull BytesMarshaller valueMarshaller) { - delegate.valueMarshaller(valueMarshaller); + valueBuilder.marshaller(valueMarshaller); return this; } /** - * Configures the marshallers, used to serialize/deserialize values to/from off-heap memory in - * maps, created by this builder. See the - * section about serialization in ChronicleMap manual for more information. + * Configures the marshallers, used to serialize/deserialize values to/from off-heap memory + * in maps, created by this builder. See the section about serialization + * in ChronicleMap manual for more information. * - *

    Configuring marshalling this way results to a little bit more compact in-memory layout of - * the map, comparing to a single interface configuration: {@link #valueMarshaller(BytesMarshaller)}. + *

    Configuring marshalling this way results to a little bit more compact in-memory layout + * of the map, comparing to a single interface configuration: {@link #valueMarshaller( + * BytesMarshaller)}. * - *

    Passing {@link net.openhft.chronicle.hash.serialization.BytesInterop} instead of plain - * {@link net.openhft.chronicle.hash.serialization.BytesWriter} is, of cause, possible, but - * currently pointless for values. + *

    Passing {@link BytesInterop} instead of plain {@link BytesWriter} is, of cause, possible, + * but currently pointless for values. * - * @param valueWriter the new value object → {@link net.openhft.lang.io.Bytes} writer - * (interop) strategy - * @param valueReader the new {@link net.openhft.lang.io.Bytes} → value object reader - * strategy + * @param valueWriter the new value object → {@link Bytes} writer (interop) strategy + * @param valueReader the new {@link Bytes} → value object reader strategy * @return this builder back * @see #valueMarshaller(BytesMarshaller) + * @see #valueSizeMarshaller(SizeMarshaller) + * @see #keyMarshallers(BytesWriter, BytesReader) */ - @Override - public ChronicleMapBuilder valueMarshallers(@NotNull BytesWriter valueWriter, @NotNull BytesReader valueReader) { - delegate.valueMarshallers(valueWriter, valueReader); + public ChronicleMapBuilder valueMarshallers( + @NotNull BytesWriter valueWriter, @NotNull BytesReader valueReader) { + valueBuilder.writer(valueWriter); + valueBuilder.reader(valueReader); return this; } @@ -312,218 +856,411 @@ public final class ChronicleMapBuilder implements ChronicleMapBuilderIDefault value size marshaller is so-called {@linkplain net.openhft.chronicle.hash.serialization.SizeMarshallers#stopBit() - * stop bit encoding marshalling}. If {@linkplain #constantValueSizeBySample(Object) constant - * value size} is configured, or defaulted if the value type is always constant and {@code - * ChronicleHashBuilder} implementation knows about it, this configuration takes no effect, - * because a special {@link net.openhft.chronicle.hash.serialization.SizeMarshaller} - * implementation, which doesn't actually do any marshalling, and just returns the known - * constant size on {@link net.openhft.chronicle.hash.serialization.SizeMarshaller#readSize( - *net.openhft.lang.io.Bytes)} calls, is used instead of any {@code SizeMarshaller} configured - * using this method. + *

    Default value size marshaller is so-called {@linkplain SizeMarshallers#stopBit() stop bit + * encoding marshalling}, unless {@link #constantValueSizeBySample(Object)} or the builder + * statically knows the value size is constant -- {@link SizeMarshallers#constant(long)} or + * equivalents are used by default in these cases. * - * @param valueSizeMarshaller the new marshaller, used to serialize actual value sizes to - * off-heap memory + * @param valueSizeMarshaller the new marshaller, used to serialize actual value sizes + * to off-heap memory * @return this builder back + * @see #keySizeMarshaller(SizeMarshaller) */ - @Override - public ChronicleMapBuilder valueSizeMarshaller(@NotNull SizeMarshaller valueSizeMarshaller) { - delegate.valueSizeMarshaller(valueSizeMarshaller); - return this; - } - - @Override - public ChronicleMapBuilder entries(long l) { - delegate.entries(l); + public ChronicleMapBuilder valueSizeMarshaller( + @NotNull SizeMarshaller valueSizeMarshaller) { + valueBuilder.sizeMarshaller(valueSizeMarshaller); return this; } /** - * each map can be identified by a name + * Configures factory which is used to create a new value instance, if value class is either + * {@link Byteable}, {@link BytesMarshallable} or {@link Externalizable} subclass, or value type + * is eligible for data value generation, or configured custom value reader is an instance of + * {@link DeserializationFactoryConfigurableBytesReader}, in maps, created by this builder. * - * @param name the name of the map - * @return self + *

    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 + * @throws IllegalStateException if it is not possible to apply deserialization factory to + * value deserializers, currently configured for this builder + * @see #keyDeserializationFactory(ObjectFactory) */ - @Override - public ChronicleMapBuilder name(String name) { - delegate.name(name); + public ChronicleMapBuilder valueDeserializationFactory( + @NotNull ObjectFactory valueDeserializationFactory) { + valueBuilder.factory(valueDeserializationFactory); return this; } - @Override - public String name() { - return delegate.name(); - } - - @Override - public ChronicleMapBuilder replication(SingleChronicleHashReplication withId) { - delegate.replication(withId); + public ChronicleMapBuilder eventListener(MapEventListener eventListener) { + this.eventListener = eventListener; return this; } - @Override - public ChronicleMapBuilder pushTo(InetSocketAddress... addresses) { - delegate.pushTo(addresses); + MapEventListener eventListener() { + return eventListener; + } + + public ChronicleMapBuilder bytesEventListener(BytesMapEventListener eventListener) { + this.bytesEventListener = eventListener; return this; } - @Override - public ChronicleMap create() { - return delegate.create(); + BytesMapEventListener bytesEventListener() { + return bytesEventListener; } - @Override - public ChronicleMapBuilder actualSegments(int actualSegments) { - delegate.actualSegments(actualSegments); + /** + * 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)} and {@link #prepareDefaultValueBytes(PrepareValueBytes)} + * configurations to this builder. + * + * @param defaultValue the default value to be put to the map for absent keys during {@code + * acquireUsing()} calls + * @return this builder object back + * @see #defaultValueProvider(DefaultValueProvider) + * @see #prepareDefaultValueBytes(PrepareValueBytes) + */ + public ChronicleMapBuilder defaultValue(V defaultValue) { + this.defaultValue = defaultValue; + this.defaultValueProvider = null; + if (defaultValue == null) + defaultValueProvider = NullValueProvider.INSTANCE; + this.prepareValueBytes = null; return this; } - @Override - public ChronicleMapBuilder minSegments(int minSegments) { - delegate.minSegments(minSegments); + /** + * 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)} and {@link + * #prepareDefaultValueBytes(PrepareValueBytes)} configurations to this builder. + * + * @param defaultValueProvider the strategy to obtain a default value by the absent key + * @return this builder object back + * @see #defaultValue(Object) + * @see #prepareDefaultValueBytes(PrepareValueBytes) + */ + public ChronicleMapBuilder defaultValueProvider( + @NotNull DefaultValueProvider defaultValueProvider) { + this.defaultValueProvider = defaultValueProvider; + this.defaultValue = null; + this.prepareValueBytes = null; return this; } - @Override - public ChronicleMapBuilder actualEntriesPerSegment(long actualEntriesPerSegment) { - delegate.actualEntriesPerSegment(actualEntriesPerSegment); + /** + * Configures the procedure which is called on the bytes, which later the returned value is + * pointing to or deserialized from, if the key is absent, on {@link ChronicleMap#acquireUsing + * acquireUsing()} call on maps, created by this builder. See {@link PrepareValueBytes} for more + * information. + * + *

    This method of value initialization on {@code acquireUsing()} calls is allowed only if + * value size is constant. Otherwise you should use either {@link #defaultValue(Object)} or + * {@link #defaultValueProvider(DefaultValueProvider)} methods. + * + *

    This configuration overrides any previous {@link #defaultValue(Object)} and {@link + * #defaultValueProvider(DefaultValueProvider)} configurations to 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 or deserializing the value from, + * to return from {@code acquireUsing()} call + * @return this builder back + * @throws IllegalStateException is value size is not constant + * @see PrepareValueBytes + * @see #defaultValue(Object) + * @see #defaultValueProvider(DefaultValueProvider) + */ + public ChronicleMapBuilder prepareDefaultValueBytes( + @NotNull PrepareValueBytes prepareValueBytes) { + this.prepareValueBytes = prepareValueBytes; + this.defaultValue = null; + this.defaultValueProvider = null; + checkPrepareValueBytesOnlyIfConstantValueSize(); return this; } + private void checkPrepareValueBytesOnlyIfConstantValueSize() { + if (prepareValueBytes != null && !valueBuilder.constantSizeMarshaller()) + throw new IllegalStateException("Prepare value bytes could be used only if " + + "value size is constant"); + } + + PrepareValueBytesAsWriter prepareValueBytesAsWriter() { + PrepareValueBytes prepareValueBytes = this.prepareValueBytes; + if ((prepareValueBytes == null && defaultValueProvider() != null) || + !valueBuilder.constantSizeMarshaller()) + return null; + if (prepareValueBytes == null) + prepareValueBytes = new ZeroOutValueBytes<>(valueSize()); + return new PrepareValueBytesAsWriter<>(prepareValueBytes, valueSize()); + } + + /** + * Non-public because should be called only after {@link #preMapConstruction(boolean)} + */ + 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); + } + @Override - public ChronicleMapBuilder lockTimeOut(long lockTimeOut, TimeUnit unit) { - delegate.lockTimeOut(lockTimeOut, unit); + public ChronicleMapBuilder replication(SingleChronicleHashReplication replication) { + this.singleHashReplication = replication; return this; } - @Override - public ChronicleMapBuilder errorListener(ChronicleHashErrorListener errorListener) { - delegate.errorListener(errorListener); - return this; - } - - @Override - public ChronicleMapBuilder metaDataBytes(int metaDataBytes) { - delegate.metaDataBytes(metaDataBytes); - return this; - } - - @Override - public ChronicleMapBuilder timeProvider(TimeProvider timeProvider) { - delegate.timeProvider(timeProvider); - return this; - } - - @Override - public ChronicleMapBuilder bytesMarshallerFactory(BytesMarshallerFactory bytesMarshallerFactory) { - delegate.bytesMarshallerFactory(bytesMarshallerFactory); - return this; - } - - @Override - public ChronicleMapBuilder keyMarshaller(BytesMarshaller keyMarshaller) { - delegate.keyMarshaller(keyMarshaller); - return this; - } - - @Override - public ChronicleMapBuilder keyMarshallers(BytesWriter keyWriter, BytesReader keyReader) { - delegate.keyMarshallers(keyWriter, keyReader); - return this; - } - - @Override - public ChronicleMapBuilder keySizeMarshaller(SizeMarshaller keySizeMarshaller) { - delegate.keySizeMarshaller(keySizeMarshaller); - return this; - } - - @Override - public ChronicleMapBuilder keyDeserializationFactory(ObjectFactory keyDeserializationFactory) { - delegate.keyDeserializationFactory(keyDeserializationFactory); - return this; - } - - @Override - public ChronicleMapBuilder immutableKeys() { - delegate.immutableKeys(); - return this; - } - - @Override - public StatelessClientConfig> statelessClient(InetSocketAddress remoteAddress) { - return delegate.statelessClient(remoteAddress); - } - - @Override - public ChronicleHashInstanceConfig> instance() { - return delegate.instance(); - } - @Override public ChronicleMapBuilder replication(byte identifier) { - delegate.replication(identifier); - return this; + return replication(SingleChronicleHashReplication.builder().createWithId(identifier)); } @Override - public ChronicleMapBuilder replication(byte identifier, TcpTransportAndNetworkConfig tcpTransportAndNetwork) { - delegate.replication(identifier, tcpTransportAndNetwork); - return this; + public ChronicleMapBuilder replication( + byte identifier, TcpTransportAndNetworkConfig tcpTransportAndNetwork) { + return replication(SingleChronicleHashReplication.builder() + .tcpTransportAndNetwork(tcpTransportAndNetwork).createWithId(identifier)); } @Override - public ChronicleMapBuilder clone() { - delegate.clone(); - return this; + public ChronicleHashInstanceConfig> instance() { + return new MapInstanceConfig<>(this.clone(), singleHashReplication, null, null, null, + new AtomicBoolean(false)); } @Override - public ChronicleMapBuilder putReturnsNull(boolean b) { - delegate.putReturnsNull(b); - return this; + public ChronicleMap createStatelessClient(InetSocketAddress serverAddress) + throws IOException { + return statelessClient(serverAddress).create(); } - @Override - public ChronicleMapBuilder removeReturnsNull(boolean b) { - delegate.removeReturnsNull(b); - return this; - } - - @Override - public ChronicleMapBuilder eventListener(MapEventListener eventListener) { - delegate.eventListener(eventListener); - return this; - } - - @Override - public ChronicleMapBuilderI bytesEventListener(BytesMapEventListener eventListener) { - delegate.bytesEventListener(eventListener); - return this; + ChronicleMap createStatelessClient(StatelessMapConfig statelessClientConfig) + throws IOException { + pushingToMapEventListener(); + preMapConstruction(false); + return new StatelessChronicleMap<>(statelessClientConfig, this); } @Override public ChronicleMap createPersistedTo(File file) throws IOException { - return delegate.createPersistedTo(file); + // clone() to make this builder instance thread-safe, because createWithFile() method + // computes some state based on configurations, but doesn't synchronize on configuration + // changes. + return clone().createWithFile(file, singleHashReplication, null); } - /** - * @param bootstrapOnlyLocalEntries if set to true - when a new node joins a TCP replication - * grid, the new node will be populated with data, only for the - * nodes that created that data. Otherwise, all the nodes will - * publish all the data they have ( potentially swamping the - * new node with duplicates ) however this does guarantee that - * all the data is replicated over to the new node, and is - * useful especially in the case that the originating node is - * not currently running. - */ - public ChronicleMapBuilder bootstrapOnlyLocalEntries(boolean bootstrapOnlyLocalEntries) { - delegate.bootstrapOnlyLocalEntries(bootstrapOnlyLocalEntries); - return this; + @Override + public StatelessClientConfig> statelessClient( + InetSocketAddress remoteAddress) { + return new StatelessMapConfig<>(clone(), remoteAddress, DEFAULT_STATELESS_CLIENT_TIMEOUT, + null, new AtomicBoolean(false)); } - public ChronicleMapBuilder disableOversizedEntries() { - delegate.disableOversizedEntries(); - return this; + @Override + public ChronicleMap create() { + // clone() to make this builder instance thread-safe, because createWithoutFile() method + // computes some state based on configurations, but doesn't synchronize on configuration + // changes. + return clone().createWithoutFile(singleHashReplication, null); + } + + ChronicleMap create(MapInstanceConfig ib) throws IOException { + if (ib.file != null) { + return createWithFile(ib.file, ib.singleHashReplication, ib.channel); + } else { + return createWithoutFile(ib.singleHashReplication, ib.channel); + } + } + + ChronicleMap createWithFile(File file, SingleChronicleHashReplication singleHashReplication, + ReplicationChannel channel) throws IOException { + pushingToMapEventListener(); + for (int i = 0; i < 10; i++) { + if (file.exists() && file.length() > 0) { + try (FileInputStream fis = new FileInputStream(file); + ObjectInputStream ois = new ObjectInputStream(fis)) { + VanillaChronicleMap map = + (VanillaChronicleMap) ois.readObject(); + map.headerSize = roundUpMapHeaderSize(fis.getChannel().position()); + map.createMappedStoreAndSegments(file); + return establishReplication(map, singleHashReplication, channel); + } catch (ClassNotFoundException e) { + throw new IOException(e); + } + } + 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(singleHashReplication, channel); + + try (FileOutputStream fos = new FileOutputStream(file); + ObjectOutputStream oos = new ObjectOutputStream(fos)) { + oos.writeObject(map); + oos.flush(); + map.headerSize = roundUpMapHeaderSize(fos.getChannel().position()); + map.createMappedStoreAndSegments(file); + } + + return establishReplication(map, singleHashReplication, channel); + } + + ChronicleMap createWithoutFile( + SingleChronicleHashReplication singleHashReplication, ReplicationChannel channel) { + try { + pushingToMapEventListener(); + VanillaChronicleMap map = newMap(singleHashReplication, channel); + BytesStore bytesStore = new DirectStore(JDKObjectSerializer.INSTANCE, + map.sizeInBytes(), true); + map.createMappedStoreAndSegments(bytesStore); + return establishReplication(map, singleHashReplication, channel); + } catch (IOException e) { + // file-less version should never trigger an IOException. + throw new AssertionError(e); + } + } + + private void pushingToMapEventListener() { + if (pushToAddresses == null || pushToAddresses.length == 0) { + return; + } + try { + Class pmel = Class.forName( + "com.higherfrequencytrading.chronicle.enterprise.map.PushingMapEventListener"); + Constructor constructor = pmel.getConstructor(ChronicleMap[].class); + // create a stateless client for each address + ChronicleMap[] statelessClients = new ChronicleMap[pushToAddresses.length]; + ChronicleMapBuilder cmb = clone(); + cmb.pushTo((InetSocketAddress[]) null); + for (int i = 0; i < pushToAddresses.length; i++) { + statelessClients[i] = cmb.createStatelessClient(pushToAddresses[i]); + } + eventListener = + (MapEventListener) constructor.newInstance((Object) statelessClients); + } catch (ClassNotFoundException e) { + LoggerFactory.getLogger(getClass().getName()) + .warn("Chronicle Enterprise not found in the class path"); + } catch (Exception e) { + LoggerFactory.getLogger(getClass().getName()) + .error("PushingMapEventListener failed to load", e); + } + } + + private VanillaChronicleMap newMap( + SingleChronicleHashReplication singleHashReplication, ReplicationChannel channel) throws IOException { + boolean replicated = singleHashReplication != null || channel != null; + preMapConstruction(replicated); + if (replicated) { + AbstractReplication replication; + if (singleHashReplication != null) { + replication = singleHashReplication; + } else { + replication = channel.hub(); + } + return new ReplicatedChronicleMap, + V, Object, MetaBytesInterop>(this, replication); + } else { + return new VanillaChronicleMap, + V, Object, MetaBytesInterop>(this); + } + } + + void preMapConstruction(boolean replicated) { + keyBuilder.objectSerializer(acquireObjectSerializer(JDKObjectSerializer.INSTANCE)); + valueBuilder.objectSerializer(acquireObjectSerializer(JDKObjectSerializer.INSTANCE)); + + long maxSize = (long) entrySize(replicated) * figureBufferAllocationFactor(); + keyBuilder.maxSize(maxSize); + valueBuilder.maxSize(maxSize); + + if (sampleKey != null) + keyBuilder.constantSizeBySample(sampleKey); + if (sampleValue != null) + valueBuilder.constantSizeBySample(sampleValue); + stateChecks(); + } + + private void stateChecks() { + checkAlignmentOnlyIfValuesPossiblyReferenceOffHeap(); + checkPrepareValueBytesOnlyIfConstantValueSize(); + } + + private ChronicleMap establishReplication( + VanillaChronicleMap map, + SingleChronicleHashReplication singleHashReplication, + ReplicationChannel channel) throws IOException { + if (map instanceof ReplicatedChronicleMap) { + if (singleHashReplication != null && channel != null) { + throw new AssertionError("Only one non-null replication should be passed"); + } + ReplicatedChronicleMap result = (ReplicatedChronicleMap) map; + List replicators = new ArrayList<>(2); + if (singleHashReplication != null) { + if (singleHashReplication.tcpTransportAndNetwork() != null) + replicators.add(Replicators.tcp(singleHashReplication)); + if (singleHashReplication.udpTransport() != null) + replicators.add(Replicators.udp(singleHashReplication.udpTransport())); + } else { + ReplicationHub hub = channel.hub(); + int entrySize = entrySize(true); + if (entrySize > hub.maxEntrySize()) { + throw new IllegalArgumentException("During ChannelProviderBuilder setup, " + + "maxEntrySize=" + hub.maxEntrySize() + + " was specified, but map with " + + "entrySize=" + entrySize + " is attempted to apply to the replicator"); + } + ChannelProvider provider = ChannelProvider.getProvider(hub); + ChannelProvider.ChronicleChannel ch = provider.createChannel(channel.channelId()); + replicators.add(ch); + } + for (Replicator replicator : replicators) { + Closeable token = replicator.applyTo(this, result, result, map); + if (replicators.size() == 1 && token.getClass() == UdpReplicator.class) { + LOG.warn(Replicators.ONLY_UDP_WARN_MESSAGE); + } + 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); } } + diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderI.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderI.java deleted file mode 100755 index 99cc3455b..000000000 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilderI.java +++ /dev/null @@ -1,319 +0,0 @@ -package net.openhft.chronicle.map; - -import net.openhft.chronicle.hash.ChronicleHashErrorListener; -import net.openhft.chronicle.hash.ChronicleHashInstanceConfig; -import net.openhft.chronicle.hash.StatelessClientConfig; -import net.openhft.chronicle.hash.replication.SingleChronicleHashReplication; -import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; -import net.openhft.chronicle.hash.replication.TimeProvider; -import net.openhft.chronicle.hash.serialization.BytesReader; -import net.openhft.chronicle.hash.serialization.BytesWriter; -import net.openhft.chronicle.hash.serialization.SizeMarshaller; -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 org.jetbrains.annotations.NotNull; - -import java.io.File; -import java.io.IOException; -import java.io.Serializable; -import java.net.InetSocketAddress; -import java.util.concurrent.TimeUnit; - -/** - * @author Rob Austin. - */ -public interface ChronicleMapBuilderI extends Serializable { - - /** - * {@inheritDoc} 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, and - * entry alignment is set to {@link Alignment#OF_4_BYTES}, the actual entry size will be 32 (30 - * aligned to 4 bytes). - * - * @see #entryAndValueAlignment(Alignment) // * @see #entries(long) - */ - ChronicleMapBuilderI entrySize(int entrySize); - - /** - * 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} for Byteable values. - * - * @param alignment the new alignment of the maps constructed by this builder - * @return this {@code ChronicleMapOnHeapUpdatableBuilder} back - */ - ChronicleMapBuilderI entryAndValueAlignment(Alignment alignment); - - /** - * {@inheritDoc} Also, it overrides any previous {@link #prepareValueBytesOnAcquire} - * configuration to this {@code ChronicleMapBuilder}. - * - *

    By default, the default value is not specified, default {@linkplain - * #prepareValueBytesOnAcquire prepare value bytes routine} is specified instead. - * - * @see #defaultValueProvider(DefaultValueProvider) - * @see #prepareValueBytesOnAcquire(PrepareValueBytes) - */ - ChronicleMapBuilderI defaultValue(V defaultValue); - - /** - * 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. See {@link PrepareValueBytes} for more - * information. - * - *

    The default preparation callback zeroes out the value bytes. - * - * @param prepareValueBytes what to do with the value bytes before assigning them into the - * {@link net.openhft.lang.model.Byteable} value to return from {@code - * acquireUsing()} call - * @return this builder back - * @see PrepareValueBytes - * @see #defaultValue(Object) - * @see #defaultValueProvider(DefaultValueProvider) - */ - ChronicleMapBuilderI prepareValueBytesOnAcquire( - @NotNull PrepareValueBytes prepareValueBytes); - - /** - * {@inheritDoc} - * - * @see #constantKeySizeBySample(Object) - * @see #valueSize(int) - * @see #entrySize(int) - */ - ChronicleMapBuilderI keySize(int keySize); - - /** - * {@inheritDoc} - * - * @see #keySize(int) - * @see #constantValueSizeBySample(Object) - */ - - ChronicleMapBuilderI constantKeySizeBySample(K sampleKey); - - /** - * Configures the optimal number of bytes, taken by serialized form of values, put into maps, - * 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, 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, - * it's better to use {@linkplain #entrySize(int) entry size} in "chunk" mode and configure it - * directly. - * - * @param valueSize number of bytes, taken by serialized form of values - * @return this {@code ChronicleMapOnHeapUpdatableBuilder} back - * @see #constantValueSizeBySample(Object) - * @see #keySize(int) - * @see #entrySize(int) - */ - - ChronicleMapBuilderI valueSize(int valueSize); - - /** - * Configures the constant number of bytes, taken by serialized form of values, put into maps, - * created by this builder. This is done by providing the {@code sampleValue}, all values should - * take the same number of bytes in serialized form, as this sample object. - * - *

    If values are of boxed primitive type or {@link net.openhft.lang.model.Byteable} subclass, - * i. e. if value size is known statically, it is automatically accounted and this method - * shouldn't be called. - * - *

    If value size varies, method {@link #valueSize(int)} or {@link #entrySize(int)} should be - * called instead of this one. - * - * @param sampleValue the sample value - * @return this builder back - * @see #valueSize(int) - * @see #constantKeySizeBySample(Object) - */ - - ChronicleMapBuilderI constantValueSizeBySample( - @NotNull V sampleValue); - - /** - * {@inheritDoc} - * - *

    Example:

    {@code Map map =
    -     *     ChronicleMapBuilder.of(Key.class, Value.class)
    -     *     .entries(1_000_000)
    -     *     .keySize(50).valueSize(200)
    -     *     // this class hasn't implemented yet, just for example
    -     *     .objectSerializer(new KryoObjectSerializer())
    -     *     .create();}
    - */ - - ChronicleMapBuilderI objectSerializer( - @NotNull ObjectSerializer objectSerializer); - - /** - * {@inheritDoc} - * - * @throws IllegalStateException if custom value marshaller is specified or value class is not - * either {@code BytesMarshallable} or {@code Externalizable} - */ - - ChronicleMapBuilderI valueDeserializationFactory( - @NotNull ObjectFactory valueDeserializationFactory); - - /** - * {@inheritDoc} - * - *

    By default, default value provider is not specified, {@link #defaultValue(Object) default - * value} is specified instead. - * - * @see #defaultValue(Object) - */ - - ChronicleMapBuilderI defaultValueProvider( - @NotNull DefaultValueProvider defaultValueProvider); - - /** - * Configures the {@code BytesMarshaller} used to serialize/deserialize values to/from off-heap - * memory in maps, created by this builder. See the - * section about serialization in ChronicleMap manual for more information. - * - * @param valueMarshaller the marshaller used to serialize values - * @return this builder back - * @see #valueMarshallers(net.openhft.chronicle.hash.serialization.BytesWriter, - * net.openhft.chronicle.hash.serialization.BytesReader) - * @see ChronicleMapBuilder - * @see #objectSerializer(ObjectSerializer) - */ - ChronicleMapBuilderI valueMarshaller( - @NotNull BytesMarshaller valueMarshaller); - - /** - * Configures the marshallers, used to serialize/deserialize values to/from off-heap memory in - * maps, created by this builder. See the - * section about serialization in ChronicleMap manual for more information. - * - *

    Configuring marshalling this way results to a little bit more compact in-memory layout of - * the map, comparing to a single interface configuration: {@link #valueMarshaller(BytesMarshaller)}. - * - *

    Passing {@link net.openhft.chronicle.hash.serialization.BytesInterop} instead of plain - * {@link net.openhft.chronicle.hash.serialization.BytesWriter} is, of cause, possible, but - * currently pointless for values. - * - * @param valueWriter the new value object → {@link net.openhft.lang.io.Bytes} writer - * (interop) strategy - * @param valueReader the new {@link net.openhft.lang.io.Bytes} → value object reader - * strategy - * @return this builder back - * @see #valueMarshaller(BytesMarshaller) - */ - ChronicleMapBuilderI valueMarshallers( - @NotNull BytesWriter valueWriter, @NotNull BytesReader valueReader); - - /** - * Configures the marshaller used to serialize actual value sizes to off-heap memory in maps, - * created by this builder. - * - *

    Default value size marshaller is so-called {@linkplain net.openhft.chronicle.hash.serialization.SizeMarshallers#stopBit() - * stop bit encoding marshalling}. If {@linkplain #constantValueSizeBySample(Object) constant - * value size} is configured, or defaulted if the value type is always constant and {@code - * ChronicleHashBuilder} implementation knows about it, this configuration takes no effect, - * because a special {@link net.openhft.chronicle.hash.serialization.SizeMarshaller} - * implementation, which doesn't actually do any marshalling, and just returns the known - * constant size on {@link net.openhft.chronicle.hash.serialization.SizeMarshaller#readSize( - *net.openhft.lang.io.Bytes)} calls, is used instead of any {@code SizeMarshaller} configured - * using this method. - * - * @param valueSizeMarshaller the new marshaller, used to serialize actual value sizes to - * off-heap memory - * @return this builder back - */ - ChronicleMapBuilderI valueSizeMarshaller( - @NotNull SizeMarshaller valueSizeMarshaller); - - ChronicleMapBuilderI entries(long l); - - ChronicleMapBuilderI replication(SingleChronicleHashReplication withId); - - ChronicleMapBuilderI pushTo(InetSocketAddress... addresses); - - ChronicleMap create(); - - ChronicleMapBuilderI actualSegments(int actualSegments); - - ChronicleMapBuilderI minSegments(int minSegments); - - ChronicleMapBuilderI actualEntriesPerSegment(long actualEntriesPerSegment); - - ChronicleMapBuilderI lockTimeOut(long lockTimeOut, TimeUnit unit); - - ChronicleMapBuilderI errorListener(ChronicleHashErrorListener errorListener); - - ChronicleMapBuilderI metaDataBytes(int metaDataBytes); - - ChronicleMapBuilderI timeProvider(TimeProvider timeProvider); - - ChronicleMapBuilderI bytesMarshallerFactory(BytesMarshallerFactory bytesMarshallerFactory); - - ChronicleMapBuilderI keyMarshaller(BytesMarshaller keyMarshaller); - - ChronicleMapBuilderI keyMarshallers(BytesWriter keyWriter, BytesReader keyReader); - - ChronicleMapBuilderI keySizeMarshaller(SizeMarshaller keySizeMarshaller); - - ChronicleMapBuilderI keyDeserializationFactory(ObjectFactory keyDeserializationFactory); - - ChronicleMapBuilderI immutableKeys(); - - ChronicleMap createPersistedTo(File file) throws IOException; - - StatelessClientConfig> statelessClient(InetSocketAddress remoteAddress); - - ChronicleHashInstanceConfig> instance(); - - ChronicleMapBuilderI replication(byte identifier); - - ChronicleMapBuilderI replication(byte identifier, TcpTransportAndNetworkConfig tcpTransportAndNetwork); - - ChronicleMapBuilderI clone(); - - ChronicleMapBuilderI eventListener(MapEventListener eventListener); - - ChronicleMapBuilderI bytesEventListener(BytesMapEventListener eventListener); - - ChronicleMapBuilderI putReturnsNull(boolean b); - - ChronicleMapBuilderI removeReturnsNull(boolean b); - - ChronicleMapBuilderI name(String name); - - /** - * @param bootstapOnlyLocalEntries if set to true - when a new node joins a TCP replication - * grid, the new node will be populated with data, only for the - * nodes that created that data. Otherwise, all the nodes will - * publish all the data they have ( potentially swamping the new - * node with duplicates ) however this does guarantee that all - * the data is replicated over to the new node, and is useful - * especially in the case that the originating node is not - * currently running. - * @return self - */ - ChronicleMapBuilderI bootstrapOnlyLocalEntries(boolean bootstapOnlyLocalEntries); - - String name(); - - ChronicleMapBuilderI disableOversizedEntries(); -} diff --git a/src/main/java/net/openhft/chronicle/map/DefaultValueProvider.java b/src/main/java/net/openhft/chronicle/map/DefaultValueProvider.java index a07c03982..7a2d5ea52 100644 --- a/src/main/java/net/openhft/chronicle/map/DefaultValueProvider.java +++ b/src/main/java/net/openhft/chronicle/map/DefaultValueProvider.java @@ -22,7 +22,7 @@ import java.io.Serializable; /** * Default value computation strategy, used - * in {@link AbstractChronicleMapBuilder#defaultValueProvider(DefaultValueProvider)} configuration. + * in {@link ChronicleMapBuilder#defaultValueProvider(DefaultValueProvider)} configuration. * * @param map key class * @param map value class diff --git a/src/main/java/net/openhft/chronicle/map/MapEventListener.java b/src/main/java/net/openhft/chronicle/map/MapEventListener.java index 5cf73f8b9..8535d1b29 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 - * AbstractChronicleMapBuilder#eventListener(MapEventListener)} method. + * ChronicleMapBuilder#eventListener(MapEventListener)} method. * *

    See {@link #logging(String)} implementation. * @@ -45,7 +45,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 - * @see AbstractChronicleMapBuilder#eventListener(MapEventListener) + * @see ChronicleMapBuilder#eventListener(MapEventListener) */ public abstract class MapEventListener implements Serializable { private static final long serialVersionUID = 0L; @@ -107,7 +107,7 @@ public abstract class MapEventListener implements Serializable { * This method is called 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 - * AbstractChronicleMapBuilder#defaultValue(Object) default} for the map or obtained during + * ChronicleMapBuilder#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/InstanceConfig.java b/src/main/java/net/openhft/chronicle/map/MapInstanceConfig.java similarity index 58% rename from src/main/java/net/openhft/chronicle/map/InstanceConfig.java rename to src/main/java/net/openhft/chronicle/map/MapInstanceConfig.java index 5e75a6492..e6dd4eab5 100644 --- a/src/main/java/net/openhft/chronicle/map/InstanceConfig.java +++ b/src/main/java/net/openhft/chronicle/map/MapInstanceConfig.java @@ -23,61 +23,68 @@ import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; import java.io.File; import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; -final class InstanceConfig +final class MapInstanceConfig implements ChronicleHashInstanceConfig> { - final AbstractChronicleMapBuilder mapBuilder; + final ChronicleMapBuilder mapBuilder; final SingleChronicleHashReplication singleHashReplication; final ReplicationChannel channel; final File file; + final String name; - private boolean used = false; + final AtomicBoolean used; - InstanceConfig(AbstractChronicleMapBuilder mapBuilder, - SingleChronicleHashReplication singleHashReplication, - ReplicationChannel channel, - File file) { + MapInstanceConfig(ChronicleMapBuilder mapBuilder, + SingleChronicleHashReplication singleHashReplication, + ReplicationChannel channel, + File file, String name, AtomicBoolean used) { this.mapBuilder = mapBuilder; this.singleHashReplication = singleHashReplication; this.channel = channel; this.file = file; + this.name = name; + this.used = used; } @Override - public ChronicleHashInstanceConfig> replicated( + public MapInstanceConfig replicated( byte identifier, TcpTransportAndNetworkConfig tcpTransportAndNetwork) { return replicated(SingleChronicleHashReplication.builder() .tcpTransportAndNetwork(tcpTransportAndNetwork).createWithId(identifier)); } @Override - public ChronicleHashInstanceConfig> replicated( - SingleChronicleHashReplication replication) { - return new InstanceConfig<>(mapBuilder, replication, null, file); + public MapInstanceConfig replicated(SingleChronicleHashReplication replication) { + return new MapInstanceConfig<>(mapBuilder, replication, null, file, name, used); } @Override - public ChronicleHashInstanceConfig> replicatedViaChannel( - ReplicationChannel channel) { - return new InstanceConfig<>(mapBuilder, null, channel, file); + public MapInstanceConfig replicatedViaChannel(ReplicationChannel channel) { + return new MapInstanceConfig<>(mapBuilder, null, channel, file, name, used); } @Override - public ChronicleHashInstanceConfig> persistedTo(File file) { - return new InstanceConfig<>(mapBuilder, singleHashReplication, channel, file); + public MapInstanceConfig persistedTo(File file) { + return new MapInstanceConfig<>(mapBuilder, singleHashReplication, channel, file, name, used); + } + + @Override + public MapInstanceConfig name(String name) { + return new MapInstanceConfig<>(mapBuilder, singleHashReplication, channel, file, name, used); } @Override public synchronized ChronicleMap create() throws IOException { - if (used) + if (!used.getAndSet(true)) { + return mapBuilder.create(this); + } else { throw new IllegalStateException( - "A ChronicleMap has already been created using this instance config. " + - "Create a new instance config (builder.instance() or call any " + - "configuration method on this instance config) to create a new " + + "A ChronicleMap has already been created using this instance config chain. " + + "Create a new instance config (builder.instance()) to create a new " + "ChronicleMap instance"); - used = true; - return mapBuilder.create(this); + } } } diff --git a/src/main/java/net/openhft/chronicle/map/OffHeapUpdatableChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/OffHeapUpdatableChronicleMapBuilder.java deleted file mode 100644 index e94a9383a..000000000 --- a/src/main/java/net/openhft/chronicle/map/OffHeapUpdatableChronicleMapBuilder.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.openhft.chronicle.map; - -import net.openhft.chronicle.hash.serialization.BytesReader; -import net.openhft.chronicle.hash.serialization.BytesWriter; -import net.openhft.chronicle.hash.serialization.SizeMarshaller; -import net.openhft.lang.io.serialization.BytesMarshaller; -import net.openhft.lang.model.Byteable; -import net.openhft.lang.model.DataValueClasses; -import org.jetbrains.annotations.NotNull; - -final class OffHeapUpdatableChronicleMapBuilder - extends AbstractChronicleMapBuilder> { - - public static ChronicleMapBuilderI of( - @NotNull Class keyClass, @NotNull Class valueClass) { - if (valueClass.isEnum()) - return OnHeapUpdatableChronicleMapBuilder.of(keyClass, valueClass); - - if (keyClass.isInterface() && keyClass != CharSequence.class) { - keyClass = DataValueClasses.directClassFor(keyClass); - } - - if ((valueClass.isInterface() && valueClass != CharSequence.class)) { - valueClass = DataValueClasses.directClassFor(valueClass); - } else if (!offHeapReference(valueClass)) { - return OnHeapUpdatableChronicleMapBuilder.of(keyClass, valueClass); - } - return new OffHeapUpdatableChronicleMapBuilder(keyClass, valueClass); - } - - OffHeapUpdatableChronicleMapBuilder(Class keyClass, Class valueClass) { - super(keyClass, valueClass); - prepareValueBytesOnAcquire(new ZeroOutValueBytes(valueSize())); - } - - @Override - OffHeapUpdatableChronicleMapBuilder self() { - return this; - } - - @Override - public OffHeapUpdatableChronicleMapBuilder valueSize(int valueSize) { - throw new UnsupportedOperationException(); - } - - /** - * {@inheritDoc} 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, and entry - * alignment is set to {@link Alignment#OF_4_BYTES}, the actual entry size will be 32 (30 aligned to 4 - * bytes). - * - * @see #entryAndValueAlignment(Alignment) - * @see #entries(long) - */ - @Override - public OffHeapUpdatableChronicleMapBuilder entrySize(int entrySize) { - return super.entrySize(entrySize); - } - - /** - * 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} for Byteable values. - * - * @param alignment the new alignment of the maps constructed by this builder - * @return this {@code ChronicleMapOnHeapUpdatableBuilder} back - */ - @Override - public OffHeapUpdatableChronicleMapBuilder entryAndValueAlignment(Alignment alignment) { - return super.entryAndValueAlignment(alignment); - } - - /** - * {@inheritDoc} Also, it overrides any previous {@link #prepareValueBytesOnAcquire} configuration to this - * {@code ChronicleMapBuilder}. - * - *

    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 ChronicleMapBuilder}. - * - *

    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. See {@link PrepareValueBytes} for more information. - * - *

    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 PrepareValueBytes - * @see #defaultValue(Object) - * @see #defaultValueProvider(DefaultValueProvider) - */ - @Override - public OffHeapUpdatableChronicleMapBuilder prepareValueBytesOnAcquire( - @NotNull PrepareValueBytes prepareValueBytes) { - return super.prepareValueBytesOnAcquire(prepareValueBytes); - } - - public ChronicleMapBuilderI valueMarshaller(@NotNull BytesMarshaller valueMarshaller) { - throw new UnsupportedOperationException("not supported for this combination of key/value type"); - } - - public ChronicleMapBuilderI valueMarshallers(@NotNull BytesWriter valueWriter, @NotNull BytesReader valueReader) { - throw new UnsupportedOperationException("not supported for this combination of key/value type"); - } - - public ChronicleMapBuilderI valueSizeMarshaller(@NotNull SizeMarshaller valueSizeMarshaller) { - throw new UnsupportedOperationException("not supported for this combination of key/value type"); - } - - @Override - public OffHeapUpdatableChronicleMapBuilder constantValueSizeBySample(V sampleValue) { - throw new UnsupportedOperationException("not supported for this combination of key/value type"); - } - -} diff --git a/src/main/java/net/openhft/chronicle/map/OnHeapUpdatableChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/OnHeapUpdatableChronicleMapBuilder.java deleted file mode 100644 index 058dc94cb..000000000 --- a/src/main/java/net/openhft/chronicle/map/OnHeapUpdatableChronicleMapBuilder.java +++ /dev/null @@ -1,282 +0,0 @@ -/* - * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.openhft.chronicle.map; - -import net.openhft.chronicle.hash.ChronicleHashBuilder; -import net.openhft.chronicle.hash.serialization.BytesReader; -import net.openhft.chronicle.hash.serialization.BytesWriter; -import net.openhft.chronicle.hash.serialization.SizeMarshaller; -import net.openhft.lang.io.Bytes; -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 java.io.Externalizable; -import java.io.Serializable; - -/** - * {@code ChronicleMapOnHeapUpdatableBuilder} 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) of(Key.class, Value.class)} method to obtain a {@code ChronicleMapOnHeapUpdatableBuilder} - * instance. - * - * @param key type of the maps, created by this builder - * @param value type of the maps, created by this builder - * @see ChronicleMapBuilder - * @see AbstractChronicleMapBuilder - * @see ChronicleHashBuilder - */ -final class OnHeapUpdatableChronicleMapBuilder - extends AbstractChronicleMapBuilder> { - - /** - * Returns a new {@code ChronicleMapOnHeapUpdatableBuilder} instance which is able to {@linkplain - * #create() create} maps with the specified key and value classes.

    {@code - * ChronicleMapOnHeapUpdatableBuilder} 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 ChronicleMapBuilderI of( - @NotNull Class keyClass, @NotNull Class valueClass) { - return new OnHeapUpdatableChronicleMapBuilder(keyClass, valueClass); - } - - OnHeapUpdatableChronicleMapBuilder(Class keyClass, Class valueClass) { - super(keyClass, valueClass); - } - - @Override - OnHeapUpdatableChronicleMapBuilder self() { - return this; - } - - /** - * {@inheritDoc} - * - * @see #constantKeySizeBySample(Object) - * @see #valueSize(int) - * @see #entrySize(int) - */ - @Override - public OnHeapUpdatableChronicleMapBuilder keySize(int keySize) { - return super.keySize(keySize); - } - - /** - * {@inheritDoc} - * - * @see #keySize(int) - * @see #constantValueSizeBySample(Object) - */ - @Override - public OnHeapUpdatableChronicleMapBuilder constantKeySizeBySample(K sampleKey) { - return super.constantKeySizeBySample(sampleKey); - } - - /** - * Configures the optimal number of bytes, taken by serialized form of values, put into maps, 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, 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, it's better to use {@linkplain - * #entrySize(int) entry size} in "chunk" mode and configure it directly. - * - * @param valueSize number of bytes, taken by serialized form of values - * @return this {@code ChronicleMapOnHeapUpdatableBuilder} back - * @see #constantValueSizeBySample(Object) - * @see #keySize(int) - * @see #entrySize(int) - */ - @Override - public OnHeapUpdatableChronicleMapBuilder valueSize(int valueSize) { - return super.valueSize(valueSize); - } - - /** - * Configures the constant number of bytes, taken by serialized form of values, put into maps, created by - * this builder. This is done by providing the {@code sampleValue}, all values should take the same number - * of bytes in serialized form, as this sample object. If values are of boxed primitive type or - * {@link Byteable} subclass, i. e. if value size is known statically, it is automatically accounted and - * this method shouldn't be called. If value size varies, method {@link #valueSize(int)} or {@link - * #entrySize(int)} should be called instead of this one. - * - * @param sampleValue the sample value - * @return this builder back - * @see #valueSize(int) - * @see #constantKeySizeBySample(Object) - */ - - @Override - public OnHeapUpdatableChronicleMapBuilder constantValueSizeBySample( - @NotNull V sampleValue) { - return super.constantValueSizeBySample(sampleValue); - } - - /** - * {@inheritDoc}

    - *

    Example:

    {@code Map map =
    -     *     ChronicleMapBuilder.of(Key.class, Value.class)
    -     *     .entries(1_000_000)
    -     *     .keySize(50).valueSize(200)
    -     *     // this class hasn't implemented yet, just for example
    -     *     .objectSerializer(new KryoObjectSerializer())
    -     *     .create();}
    - *

    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 - * #valueMarshaller(BytesMarshaller) value} marshallers are not configured. - */ - @Override - public OnHeapUpdatableChronicleMapBuilder objectSerializer( - @NotNull ObjectSerializer objectSerializer) { - return super.objectSerializer(objectSerializer); - } - - /** - * {@inheritDoc} 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 OnHeapUpdatableChronicleMapBuilder valueDeserializationFactory( - @NotNull ObjectFactory valueDeserializationFactory) { - return super.valueDeserializationFactory(valueDeserializationFactory); - } - - /** - * {@inheritDoc}

    By default, the default value is set to {@code null}. - * - * @see #defaultValueProvider(DefaultValueProvider) - */ - @Override - public OnHeapUpdatableChronicleMapBuilder defaultValue(V defaultValue) { - return super.defaultValue(defaultValue); - } - - /** - * {@inheritDoc}

    By default, default value provider is not specified, {@link #defaultValue(Object) - * default value} is specified instead. - * - * @see #defaultValue(Object) - */ - @Override - public OnHeapUpdatableChronicleMapBuilder defaultValueProvider( - @NotNull DefaultValueProvider defaultValueProvider) { - return super.defaultValueProvider(defaultValueProvider); - } - - /** - * Configures the {@code BytesMarshaller} used to serialize/deserialize values to/from off-heap memory in - * maps, created by this builder. See the - * section about serialization in ChronicleMap manual for more information. - * - * @param valueMarshaller the marshaller used to serialize values - * @return this builder back - * @see #valueMarshallers(BytesWriter, BytesReader) - * @see ChronicleMapBuilder - * @see #objectSerializer(ObjectSerializer) - */ - public OnHeapUpdatableChronicleMapBuilder valueMarshaller( - @NotNull BytesMarshaller valueMarshaller) { - valueBuilder.marshaller(valueMarshaller); - return this; - } - - /** - * Configures the marshallers, used to serialize/deserialize values to/from off-heap memory in maps, - * created by this builder. See the - * section about serialization in ChronicleMap manual for more information.

    Configuring - * marshalling this way results to a little bit more compact in-memory layout of the map, comparing to a - * single interface configuration: {@link #valueMarshaller(BytesMarshaller)}.

    Passing {@link - * net.openhft.chronicle.hash.serialization.BytesInterop} instead of plain {@link BytesWriter} is, of - * cause, possible, but currently pointless for values. - * - * @param valueWriter the new value object → {@link Bytes} writer (interop) strategy - * @param valueReader the new {@link Bytes} → value object reader strategy - * @return this builder back - * @see #valueMarshaller(BytesMarshaller) - */ - public OnHeapUpdatableChronicleMapBuilder valueMarshallers( - @NotNull BytesWriter valueWriter, @NotNull BytesReader valueReader) { - valueBuilder.writer(valueWriter); - valueBuilder.reader(valueReader); - return this; - } - - /** - * Configures the marshaller used to serialize actual value sizes to off-heap memory in maps, created by - * this builder.

    Default value size marshaller is so-called {@linkplain - * net.openhft.chronicle.hash.serialization.SizeMarshallers#stopBit() stop bit encoding marshalling}. If - * {@linkplain #constantValueSizeBySample(Object) constant value size} is configured, or defaulted if the - * value type is always constant and {@code ChronicleHashBuilder} implementation knows about it, this - * configuration takes no effect, because a special {@link SizeMarshaller} implementation, which doesn't - * actually do any marshalling, and just returns the known constant size on {@link - * SizeMarshaller#readSize(Bytes)} calls, is used instead of any {@code SizeMarshaller} configured using - * this method. - * - * @param valueSizeMarshaller the new marshaller, used to serialize actual value sizes to off-heap memory - * @return this builder back - */ - public OnHeapUpdatableChronicleMapBuilder valueSizeMarshaller( - @NotNull SizeMarshaller valueSizeMarshaller) { - valueBuilder.sizeMarshaller(valueSizeMarshaller); - return this; - } - - /** - * 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. See {@link PrepareValueBytes} for more information.

    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 PrepareValueBytes - * @see #defaultValue(Object) - * @see #defaultValueProvider(DefaultValueProvider) - */ - @Override - public OnHeapUpdatableChronicleMapBuilder prepareValueBytesOnAcquire( - @NotNull PrepareValueBytes prepareValueBytes) { - throw new UnsupportedOperationException("not supported for this combination of key/value type"); - } - - public OnHeapUpdatableChronicleMapBuilder entryAndValueAlignment(Alignment a) { - throw new UnsupportedOperationException("not supported for this combination of key/value type"); - } - -} diff --git a/src/main/java/net/openhft/chronicle/map/PrepareValueBytes.java b/src/main/java/net/openhft/chronicle/map/PrepareValueBytes.java index 87462f6a8..949d48e9f 100644 --- a/src/main/java/net/openhft/chronicle/map/PrepareValueBytes.java +++ b/src/main/java/net/openhft/chronicle/map/PrepareValueBytes.java @@ -31,7 +31,7 @@ import java.io.Serializable; * PrepareValueBytes) configure} custom {@code PrepareValueBytes} strategy in {@link * ChronicleMapBuilder}:

    {@code
      * ChronicleMapBuilder.of(Key.class, LongValue.class)
    - *     .prepareValueBytesOnAcquire((bytes, k) -> bytes.writeLong(1L))
    + *     .prepareDefaultValueBytes((bytes, k) -> bytes.writeLong(1L))
      *     ...
      *     .create();}
    * diff --git a/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java b/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java index 24f960bd5..a5b71fda7 100644 --- a/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/ReplicatedChronicleMap.java @@ -18,6 +18,7 @@ package net.openhft.chronicle.map; +import net.openhft.chronicle.hash.replication.AbstractReplication; import net.openhft.chronicle.hash.replication.TimeProvider; import net.openhft.chronicle.hash.serialization.BytesInterop; import net.openhft.chronicle.hash.serialization.BytesReader; @@ -105,14 +106,14 @@ final class ReplicatedChronicleMap builder, - byte identifier) + public ReplicatedChronicleMap(@NotNull ChronicleMapBuilder builder, + AbstractReplication replication) throws IOException { super(builder); this.timeProvider = builder.timeProvider(); - this.localIdentifier = identifier; - this.bootstrapOnlyLocalEntries = builder.bootstrapOnlyLocalEntries; + this.localIdentifier = replication.identifier(); + this.bootstrapOnlyLocalEntries = replication.bootstrapOnlyLocalEntries(); if (localIdentifier == -1) { throw new IllegalStateException("localIdentifier should not be -1"); @@ -274,7 +275,7 @@ final class ReplicatedChronicleMap>instance(), @@ -835,7 +836,7 @@ final class ReplicatedChronicleMap readValue, boolean resultUnused, byte identifier, long timeStamp) { - writeLock(null); + writeLock(); try { return putWithoutLock(copies, segmentState, metaKeyInterop, keyInterop, key, keySize, toKey, @@ -1015,7 +1016,7 @@ final class ReplicatedChronicleMap getValueInterops, VB expectedValue, InstanceOrBytesToInstance toValue, long hash2, ReadValue readValue, boolean resultUnused) { - writeLock(null); + writeLock(); try { return removeWithoutLock(copies, segmentState, metaKeyInterop, keyInterop, key, keySize, toKey, @@ -1158,7 +1159,7 @@ final class ReplicatedChronicleMap implements FindByName { public static final int MAP_BY_NAME_CHANNEL = 1; private final AtomicInteger nextFreeChannel = new AtomicInteger(2); - private final Map map; + private final Map map; private final ReplicationHub replicationHub; - public static class ChronicleMapBuilderWithChannelId implements Serializable { - ChronicleHashBuilder, ?> chronicleMapBuilder; - int channelId; - } - /** * @throws IOException */ @@ -61,34 +54,35 @@ class ReplicationHubFindByName implements FindByName { this.replicationHub = replicationHub; ReplicationChannel channel = replicationHub.createChannel((short) MAP_BY_NAME_CHANNEL); - final MapEventListener listener = new - MapEventListener() { + final MapEventListener listener = + new MapEventListener() { // creates a map based on the details that are sent to the map of builders @Override - public void onPut(CharSequence key, ChronicleMapBuilderWithChannelId value, - ChronicleMapBuilderWithChannelId replacedValue) { + public void onPut(CharSequence key, MapInstanceConfig value, + MapInstanceConfig replacedValue) { super.onPut(key, value, replacedValue); boolean added = replacedValue == null; if (!added || value == null) return; // establish the new map based on this channel ID - LOG.info("create new map for name=" + value.chronicleMapBuilder.name() - + ",channelId=" + value.channelId); + LOG.info("create new map for name=" + value.name + + ",channelId=" + value.channel.channelId()); try { - toReplicatedViaChannel(value.chronicleMapBuilder, value.channelId).create(); + toReplicatedViaChannel(value.mapBuilder, value.channel.channelId()).create(); } catch (IllegalStateException e) { // channel is already created - LOG.debug("while creating channel for name=" + value.chronicleMapBuilder.name() - + ",channelId=" + value.channelId, e); + LOG.debug("while creating channel for name=" + value.name + + ",channelId=" + value.channel.channelId(), e); } catch (IOException e) { LOG.error("", e); } } }; - this.map = (Map) of(CharSequence.class, ChronicleMapBuilderWithChannelId.class) + this.map = (Map) ChronicleMapBuilder + .of(CharSequence.class, MapInstanceConfig.class) .entrySize(3000) .entries(128) .eventListener(listener) @@ -99,34 +93,32 @@ class ReplicationHubFindByName implements FindByName { LOG.debug("map=" + map); } - public T create(ChronicleMapBuilder builder) throws IllegalArgumentException, - IOException, TimeoutException, InterruptedException { + public T create( + MapInstanceConfig config) + throws IOException, TimeoutException, InterruptedException { int withChannelId = nextFreeChannel.incrementAndGet(); - ChronicleMapBuilderWithChannelId builderWithChannelId = new - ChronicleMapBuilderWithChannelId(); - builderWithChannelId.channelId = withChannelId; - builderWithChannelId.chronicleMapBuilder = (ChronicleHashBuilder)builder; - map.put(builder.name(), builderWithChannelId); + map.put(config.name, + config.replicatedViaChannel(replicationHub.createChannel(withChannelId))); - return (T) get(builder.name()).chronicleMapBuilder.create(); + return (T) get(config.name).mapBuilder.create(); } public T from(String name) throws IllegalArgumentException, IOException, TimeoutException, InterruptedException { - return (T) replicatedViaChannel(name).create(); + return (T) get(name).create(); } - ChronicleMapBuilderWithChannelId get(String name) throws IllegalArgumentException, + MapInstanceConfig get(String name) throws IllegalArgumentException, TimeoutException, InterruptedException { - ChronicleMapBuilderWithChannelId chronicleMapBuilder = waitTillEntryReceived(5000, name); - if (chronicleMapBuilder == null) + MapInstanceConfig config = waitTillEntryReceived(5000, name); + if (config == null) throw new IllegalArgumentException("A map name=" + name + " can not be found."); - return chronicleMapBuilder; + return config; } /** @@ -135,23 +127,19 @@ class ReplicationHubFindByName implements FindByName { * @param timeOutMs timeout in milliseconds * @throws InterruptedException */ - private ChronicleMapBuilderWithChannelId waitTillEntryReceived(final int timeOutMs, String name) throws TimeoutException, InterruptedException { + private MapInstanceConfig waitTillEntryReceived(final int timeOutMs, String name) + throws TimeoutException, InterruptedException { int t = 0; for (; t < timeOutMs; t++) { - ChronicleMapBuilderWithChannelId builder = map.get(name); + MapInstanceConfig config = map.get(name); - if (builder != null) - return builder; + if (config != null) + return config; Thread.sleep(1); } throw new TimeoutException("timed out wait for map name=" + name); } - private ChronicleHashInstanceConfig replicatedViaChannel(String name) throws TimeoutException, InterruptedException { - ChronicleMapBuilderWithChannelId builder = get(name); - return toReplicatedViaChannel(builder.chronicleMapBuilder, builder.channelId); - } - /** * similar to {@link ChronicleMapBuilder#createPersistedTo(File file) } * @@ -163,9 +151,10 @@ class ReplicationHubFindByName implements FindByName { * @throws IOException * @see ChronicleMapBuilder#createPersistedTo(File file) */ - public T createPersistedTo(String name, File file) throws IllegalArgumentException, + public T createPersistedTo(String name, File file) + throws IllegalArgumentException, IOException, TimeoutException, InterruptedException { - return (T) replicatedViaChannel(name).persistedTo(file).create(); + return (T) get(name).persistedTo(file).create(); } private ChronicleHashInstanceConfig toReplicatedViaChannel(ChronicleMapBuilder builder) { diff --git a/src/main/java/net/openhft/chronicle/map/Replicator.java b/src/main/java/net/openhft/chronicle/map/Replicator.java index 7580d519d..a6307c3f2 100644 --- a/src/main/java/net/openhft/chronicle/map/Replicator.java +++ b/src/main/java/net/openhft/chronicle/map/Replicator.java @@ -47,7 +47,7 @@ abstract class Replicator { * already been applied to a map (or the specified number of * maps) */ - protected abstract Closeable applyTo(AbstractChronicleMapBuilder builder, + protected abstract Closeable applyTo(ChronicleMapBuilder builder, Replica map, Replica.EntryExternalizable entryExternalizable, final ChronicleMap chronicleMap) throws IOException; } diff --git a/src/main/java/net/openhft/chronicle/map/Replicators.java b/src/main/java/net/openhft/chronicle/map/Replicators.java index df38cbc8e..1db4347a9 100644 --- a/src/main/java/net/openhft/chronicle/map/Replicators.java +++ b/src/main/java/net/openhft/chronicle/map/Replicators.java @@ -40,7 +40,7 @@ final class Replicators { static Replicator tcp(final AbstractReplication replication) { return new Replicator() { @Override - protected Closeable applyTo(@NotNull final AbstractChronicleMapBuilder builder, + protected Closeable applyTo(@NotNull final ChronicleMapBuilder builder, @NotNull final Replica replica, @NotNull final Replica.EntryExternalizable entryExternalizable, final ChronicleMap chronicleMap) @@ -64,7 +64,7 @@ final class Replicators { final UdpTransportConfig replicationConfig) { return new Replicator() { @Override - protected Closeable applyTo(@NotNull final AbstractChronicleMapBuilder builder, + protected Closeable applyTo(@NotNull final ChronicleMapBuilder builder, @NotNull final Replica map, @NotNull final Replica.EntryExternalizable entryExternalizable, final ChronicleMap chronicleMap) diff --git a/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java b/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java index 7c87d2ee9..f7385edac 100644 --- a/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java @@ -20,6 +20,7 @@ package net.openhft.chronicle.map; import net.openhft.chronicle.hash.serialization.*; import net.openhft.chronicle.hash.serialization.internal.*; +import net.openhft.lang.io.ByteBufferBytes; import net.openhft.lang.io.Bytes; import net.openhft.lang.io.serialization.BytesMarshallable; import net.openhft.lang.io.serialization.BytesMarshaller; @@ -32,7 +33,6 @@ import net.openhft.lang.model.DataValueModel; import net.openhft.lang.model.DataValueModels; import net.openhft.lang.threadlocal.Provider; import net.openhft.lang.threadlocal.ThreadLocalCopies; -import net.openhft.lang.values.LongValue; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -43,16 +43,18 @@ import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URL; +import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; import static net.openhft.chronicle.hash.serialization.SizeMarshallers.constant; import static net.openhft.chronicle.hash.serialization.SizeMarshallers.stopBit; import static net.openhft.chronicle.map.Objects.hash; -import static net.openhft.chronicle.map.SerializationBuilder.Role.KEY; final class SerializationBuilder implements Cloneable, Serializable { + private static final Bytes EMPTY_BYTES = new ByteBufferBytes(ByteBuffer.allocate(0)); + private static boolean concreteClass(Class c) { return !c.isInterface() && !Modifier.isAbstract(c.getModifiers()); } @@ -79,7 +81,6 @@ final class SerializationBuilder implements Cloneable, Serializable { private final Role role; final Class eClass; private boolean instancesAreMutable; - private boolean dataValueClass = false; private SizeMarshaller sizeMarshaller = stopBit(); private BytesReader reader; private Object interop; @@ -88,10 +89,17 @@ final class SerializationBuilder implements Cloneable, Serializable { private MetaProvider metaInteropProvider; private long maxSize; + final boolean sizeIsStaticallyKnown; + @SuppressWarnings("unchecked") SerializationBuilder(Class eClass, Role role) { this.role = role; this.eClass = eClass; + configureByDefault(eClass, role); + sizeIsStaticallyKnown = constantSizeMarshaller(); + } + + private void configureByDefault(Class eClass, Role role) { instancesAreMutable = instancesAreMutable(eClass); ObjectFactory factory = concreteClass(eClass) && marshallerUseFactory(eClass) ? @@ -104,7 +112,6 @@ final class SerializationBuilder implements Cloneable, Serializable { BytesWriter writer = DataValueBytesMarshallers.acquireBytesWriter(eClass); DataValueModel model = DataValueModels.acquireModel(eClass); int size = DataValueGenerator.computeNonScalarOffset(model, eClass); - dataValueClass = true; reader(reader); writer(writer); sizeMarshaller(constant((long) size)); @@ -144,12 +151,6 @@ final class SerializationBuilder implements Cloneable, Serializable { } else if (eClass == char[].class) { reader((BytesReader) CharArrayMarshaller.INSTANCE); interop((BytesInterop) CharArrayMarshaller.INSTANCE); - } else if (eClass == LongValue.class) { - LongValueKeyMarshaller marshaller = role == KEY ? - LongValueKeyMarshaller.INSTANCE : LongValueValueMarshaller.INSTANCE; - sizeMarshaller(marshaller); - reader((BytesReader) marshaller); - interop((BytesInterop) marshaller); } else if (concreteClass(eClass)) { BytesMarshaller marshaller = chooseMarshaller(eClass, eClass); if (marshaller != null) @@ -160,8 +161,23 @@ final class SerializationBuilder implements Cloneable, Serializable { } } - private void clearDefaults() { - dataValueClass = false; + boolean possibleOffHeapReferences() { + if (reader instanceof CharSequenceReader) + return false; + if (reader instanceof VoidMarshaller) + return false; + // exclude some known classes, most notably boxed primitive types + if (!instancesAreMutable(eClass)) + return false; + if (eClass.isArray() && (eClass.getComponentType().isPrimitive() || + instancesAreMutable(eClass.getComponentType()))) + return false; + BytesMarshaller marshallerAsReader = BytesReaders.getBytesMarshaller(reader); + if (marshallerAsReader instanceof SerializableMarshaller || + marshallerAsReader instanceof ExternalizableMarshaller) + return false; + // otherwise reader could possibly keep the given bytes addresses and update off-heap memory + return true; } @SuppressWarnings("unchecked") @@ -179,7 +195,6 @@ final class SerializationBuilder implements Cloneable, Serializable { } public SerializationBuilder interop(BytesInterop interop) { - clearDefaults(); return copyingInterop(null) .setInterop(interop) .metaInterop(DelegatingMetaBytesInterop.>instance()) @@ -188,7 +203,6 @@ final class SerializationBuilder implements Cloneable, Serializable { } public SerializationBuilder writer(BytesWriter writer) { - clearDefaults(); if (writer instanceof BytesInterop) return interop((BytesInterop) writer); return copyingInterop(CopyingInterop.FROM_WRITER) @@ -198,7 +212,6 @@ final class SerializationBuilder implements Cloneable, Serializable { } public SerializationBuilder marshaller(BytesMarshaller marshaller) { - clearDefaults(); return copyingInterop(CopyingInterop.FROM_MARSHALLER) .reader(BytesReaders.fromBytesMarshaller(marshaller)) .setInterop(marshaller) @@ -253,6 +266,16 @@ final class SerializationBuilder implements Cloneable, Serializable { metaInterop = metaInteropProvider.get(copies, this.metaInterop, interop, sampleObject); } long constantSize = metaInterop.size(interop, sampleObject); + if (sizeIsStaticallyKnown) { + int expectedConstantSize = pseudoReadConstantSize(); + if (constantSize != expectedConstantSize) { + throw new IllegalStateException("Although configuring constant size by sample " + + "is not forbidden for types which size we already know statically, they " + + "should be the same. For " + eClass + " we know constant size is " + + expectedConstantSize + " statically, configured sample is " + sampleObject + + " which size in serialized form is " + constantSize); + } + } // set max size once more, because current maxSize could be x64 or x2 larger than needed maxSize(constantSize); sizeMarshaller(SizeMarshallers.constant(constantSize)); @@ -275,6 +298,14 @@ final class SerializationBuilder implements Cloneable, Serializable { return sizeMarshaller; } + boolean constantSizeMarshaller() { + return sizeMarshaller().sizeEncodingSize(0L) == 0; + } + + int pseudoReadConstantSize() { + return (int) sizeMarshaller().readSize(EMPTY_BYTES); + } + public SerializationBuilder sizeMarshaller(SizeMarshaller sizeMarshaller) { this.sizeMarshaller = sizeMarshaller; return this; @@ -285,7 +316,6 @@ final class SerializationBuilder implements Cloneable, Serializable { } public SerializationBuilder reader(BytesReader reader) { - clearDefaults(); this.reader = reader; return this; } @@ -321,27 +351,20 @@ final class SerializationBuilder implements Cloneable, Serializable { @SuppressWarnings("unchecked") public SerializationBuilder factory(ObjectFactory factory) { - if (dataValueClass) { - reader(DataValueBytesMarshallers.acquireBytesReader(eClass, factory)); + if (reader instanceof DeserializationFactoryConfigurableBytesReader) { + DeserializationFactoryConfigurableBytesReader newReader = + ((DeserializationFactoryConfigurableBytesReader) reader) + .withDeserializationFactory(factory); + reader(newReader); + if (newReader instanceof BytesInterop) + interop((BytesInterop) newReader); + if (newReader instanceof SizeMarshaller) + sizeMarshaller((SizeMarshaller) newReader); return this; } if (!marshallerUseFactory(eClass)) { throw new IllegalStateException("Default marshaller for " + eClass + " value don't use object factory"); - } else if (interop instanceof ByteableMarshaller) { - if (factory instanceof AllocateInstanceObjectFactory) { - ByteableMarshaller byteableMarshaller = - ByteableMarshaller.of((Class) eClass); - sizeMarshaller(byteableMarshaller) - .reader(byteableMarshaller) - .interop(byteableMarshaller); - } else { - ByteableMarshaller byteableMarshaller = - ByteableMarshaller.of((Class) eClass, (ObjectFactory) factory); - sizeMarshaller(byteableMarshaller) - .reader(byteableMarshaller) - .interop(byteableMarshaller); - } } else if (interop instanceof BytesMarshallableMarshaller) { if (factory instanceof AllocateInstanceObjectFactory) { interop = new BytesMarshallableMarshaller( diff --git a/src/main/java/net/openhft/chronicle/map/SharedSegment.java b/src/main/java/net/openhft/chronicle/map/SharedSegment.java index 496332344..adc5ab3e6 100644 --- a/src/main/java/net/openhft/chronicle/map/SharedSegment.java +++ b/src/main/java/net/openhft/chronicle/map/SharedSegment.java @@ -34,8 +34,6 @@ interface SharedSegment { */ AutoCloseable readLock(@Nullable VanillaChronicleMap.SegmentState segmentState); - AutoCloseable writeLock(@Nullable VanillaChronicleMap.SegmentState segmentState); - Map.Entry getEntry(@NotNull VanillaChronicleMap.SegmentState segmentState, long pos); void readUnlock(); diff --git a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java index 23bd41e4a..72b054d37 100644 --- a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java @@ -64,7 +64,7 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon private final ByteBuffer connectionOutBuffer = ByteBuffer.wrap(connectionByte); private final String name; @NotNull - private final AbstractChronicleMapBuilder chronicleMapBuilder; + private final ChronicleMapBuilder chronicleMapBuilder; private volatile ByteBuffer outBuffer; private volatile ByteBufferBytes outBytes; @@ -140,7 +140,8 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon private final AtomicLong transactionID = new AtomicLong(0); StatelessChronicleMap(@NotNull final StatelessMapConfig config, - @NotNull final AbstractChronicleMapBuilder chronicleMapBuilder) throws IOException { + @NotNull final ChronicleMapBuilder chronicleMapBuilder) + throws IOException { this.chronicleMapBuilder = chronicleMapBuilder; this.config = config; keyReaderWithSize = new ReaderWithSize<>(chronicleMapBuilder.keyBuilder); @@ -156,7 +157,7 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon this.vClass = chronicleMapBuilder.valueBuilder.eClass; this.kClass = chronicleMapBuilder.keyBuilder.eClass; - this.name = chronicleMapBuilder.name(); + this.name = config.name(); attemptConnect(config.remoteAddress()); outBuffer = allocateDirect(maxEntrySize).order(ByteOrder.nativeOrder()); @@ -192,8 +193,9 @@ class StatelessChronicleMap implements ChronicleMap, Closeable, Clon if (executorService == null) { synchronized (this) { if (executorService == null) - executorService = Executors.newSingleThreadExecutor(new NamedThreadFactory(chronicleMapBuilder.name() + - "-stateless-client-async-" + name, + //TODO the same name appears twice + executorService = Executors.newSingleThreadExecutor( + new NamedThreadFactory(config.name() +"-stateless-client-async-" + name, true)); } } diff --git a/src/main/java/net/openhft/chronicle/map/StatelessMapConfig.java b/src/main/java/net/openhft/chronicle/map/StatelessMapConfig.java index 2a06b045f..3d7e77ed2 100644 --- a/src/main/java/net/openhft/chronicle/map/StatelessMapConfig.java +++ b/src/main/java/net/openhft/chronicle/map/StatelessMapConfig.java @@ -23,20 +23,28 @@ import net.openhft.chronicle.hash.StatelessClientConfig; import java.io.IOException; import java.net.InetSocketAddress; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; /** * @author Rob Austin. */ class StatelessMapConfig implements StatelessClientConfig> { - private final AbstractChronicleMapBuilder mapBuilder; + private final ChronicleMapBuilder mapBuilder; private final InetSocketAddress remoteAddress; - private long timeoutMs = TimeUnit.SECONDS.toMillis(10); + private final long timeoutMs; + private final String name; - StatelessMapConfig(AbstractChronicleMapBuilder mapBuilder, - InetSocketAddress remoteAddress) { + private final AtomicBoolean used; + + StatelessMapConfig(ChronicleMapBuilder mapBuilder, + InetSocketAddress remoteAddress, long timeoutMs, + String name, AtomicBoolean used) { this.mapBuilder = mapBuilder; this.remoteAddress = remoteAddress; + this.timeoutMs = timeoutMs; + this.name = name; + this.used = used; } public long timeoutMs() { @@ -49,12 +57,28 @@ class StatelessMapConfig implements StatelessClientConfig timeout(long timeout, TimeUnit units) { - this.timeoutMs = units.toMillis(timeout); - return this; + return new StatelessMapConfig<>(mapBuilder, remoteAddress, units.toMillis(timeout), + name, used); + } + + @Override + public StatelessClientConfig> name(String name) { + return new StatelessMapConfig<>(mapBuilder, remoteAddress, timeoutMs, name, used); + } + + String name() { + return name; } @Override public ChronicleMap create() throws IOException { - return mapBuilder.createStatelessMap(this); + if (!used.getAndSet(true)) { + return mapBuilder.createStatelessClient(this); + } else { + throw new IllegalStateException( + "A stateless client has already been created using this config chain. " + + "Create a new StatelessClientConfig (builder.statelessClient()) " + + "to create a new stateless client"); + } } } diff --git a/src/main/java/net/openhft/chronicle/map/UdpReplicator.java b/src/main/java/net/openhft/chronicle/map/UdpReplicator.java index 3aa8ef3a3..970eeaa2d 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 @@ final class UdpReplicator extends UdpChannelReplicator implements Replica.Modifi super(replicationConfig, serializedEntrySize, replica.identifier()); Replica.ModificationIterator modificationIterator = replica.acquireModificationIterator( - AbstractChronicleMapBuilder.UDP_REPLICATION_MODIFICATION_ITERATOR_ID, this); + ChronicleMapBuilder.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 48a8219c9..28a21ab88 100755 --- a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java @@ -60,8 +60,6 @@ import static java.lang.Math.max; import static java.lang.Thread.currentThread; import static java.nio.ByteBuffer.allocateDirect; import static net.openhft.chronicle.map.Asserts.assertNotNull; -import static net.openhft.chronicle.map.ChronicleMapBuilder.builtInType; -import static net.openhft.chronicle.map.ChronicleMapBuilder.offHeapReference; import static net.openhft.lang.MemoryUnit.*; class VanillaChronicleMap, @@ -106,7 +104,7 @@ class VanillaChronicleMap, private final ChronicleHashErrorListener errorListener; private final int segmentHeaderSize; private final HashSplitting hashSplitting; - final boolean isNativeValueClass; + final Class nativeValueClass; final MultiMapFactory multiMapFactory; final int maxEntryOversizeFactor; transient Provider> keyReaderProvider; @@ -119,7 +117,7 @@ class VanillaChronicleMap, transient long headerSize; transient Set> entrySet; - public VanillaChronicleMap(AbstractChronicleMapBuilder builder) throws IOException { + public VanillaChronicleMap(ChronicleMapBuilder builder) throws IOException { SerializationBuilder keyBuilder = builder.keyBuilder; kClass = keyBuilder.eClass; @@ -133,7 +131,19 @@ class VanillaChronicleMap, SerializationBuilder valueBuilder = builder.valueBuilder; vClass = valueBuilder.eClass; - isNativeValueClass = vClass.getName().endsWith("$$Native"); + if (vClass.getName().endsWith("$$Native")) { + nativeValueClass = vClass; + } else if (vClass.isInterface()) { + Class nativeValueClass = null; + try { + nativeValueClass = DataValueClasses.directClassFor(vClass); + } catch (Exception e) { + // fall through + } + this.nativeValueClass = nativeValueClass; + } else { + nativeValueClass = null; + } valueSizeMarshaller = valueBuilder.sizeMarshaller(); originalValueReader = valueBuilder.reader(); @@ -649,22 +659,8 @@ class VanillaChronicleMap, static T newInstance(Class interfaceClass, boolean isKey) { - - - if (interfaceClass.isEnum()) - return isKey ? DataValueClasses.newDirectReference(interfaceClass) - : DataValueClasses.newDirectInstance(interfaceClass); - - else if (!offHeapReference(interfaceClass) && !interfaceClass.isInterface() && - !builtInType(interfaceClass)) { - - // if (()) - // interfaceClass = DataValueClasses.directClassFor(interfaceClass); - return DataValueClasses.newInstance(interfaceClass); - - } - return DataValueClasses.newDirectInstance(interfaceClass); - + return isKey ? DataValueClasses.newInstance(interfaceClass) : + DataValueClasses.newDirectInstance(interfaceClass); } private XStreamConverter xStreamConverter; @@ -835,8 +831,10 @@ class VanillaChronicleMap, long segmentHash = segmentHash(hash); Segment segment = segments[segmentNum]; + boolean nativeValueClass = usingValue != null && + usingValue.getClass() == this.nativeValueClass; MutableLockedEntry lock = (lockType == LockType.WRITE_LOCK) - ? segment.writeLock(segmentState) + ? segment.writeLock(segmentState, nativeValueClass) : segment.readLock(segmentState); V v = segment.acquireWithoutLock(copies, segmentState, @@ -852,7 +850,7 @@ class VanillaChronicleMap, lock.keySize = keySize; lock.segmentHash = segmentHash; - if (!isNativeValueClass && lockType == LockType.WRITE_LOCK && v == null) + if (!nativeValueClass && lockType == LockType.WRITE_LOCK && v == null) v = usingValue; lock.value(v); @@ -1558,7 +1556,7 @@ class VanillaChronicleMap, /** * reads the the number of entries in this segment */ - private long size() { + long size() { // any negative value is in error state. return max(0L, this.segmentHeader.readVolatileLong(SIZE_OFFSET)); } @@ -1595,10 +1593,12 @@ class VanillaChronicleMap, } long startWriteLock = 0; + final WriteLocked writeLock() { + return writeLock(null, false); + } - @Override - public final WriteLocked writeLock( - @Nullable SegmentState segmentState) { + final WriteLocked writeLock( + SegmentState segmentState, boolean nativeValueClass) { startWriteLock = System.nanoTime(); while (true) { final boolean success = segmentHeader.tryRWWriteLock(LOCK_OFFSET, lockTimeOutNS); @@ -1612,7 +1612,7 @@ class VanillaChronicleMap, */ if (success) { if (segmentState != null) { - if (isNativeValueClass) { + if (nativeValueClass) { return segmentState.nativeWriteLocked(this); } else { return segmentState.heapWriteLocked(this); @@ -1839,7 +1839,7 @@ class VanillaChronicleMap, long hash2, boolean replaceIfPresent, ReadValue readValue, boolean resultUnused) { shouldNotBeCalledFromReplicatedChronicleMap("Segment.put"); - writeLock(null); + writeLock(); try { return putWithoutLock(copies, segmentState, metaKeyInterop, keyInterop, key, keySize, toKey, @@ -2080,7 +2080,7 @@ class VanillaChronicleMap, GetValueInterops getValueInterops, VB expectedValue, InstanceOrBytesToInstance toValue, long hash2, ReadValue readValue, boolean resultUnused) { - writeLock(null); + writeLock(); try { return removeWithoutLock(copies, segmentState, metaKeyInterop, keyInterop, key, keySize, toKey, @@ -2257,7 +2257,7 @@ class VanillaChronicleMap, ReadValue readValue, InstanceOrBytesToInstance toValue, long hash2) { segmentStateNotNullImpliesCopiesNotNull(copies, segmentState); - writeLock(null); + writeLock(); try { hashLookup.startSearch(hash2); MultiStoreBytes entry = null; @@ -2409,7 +2409,7 @@ class VanillaChronicleMap, private void clear() { shouldNotBeCalledFromReplicatedChronicleMap("Segment.clear"); - writeLock(null); + writeLock(); try { hashLookup.clear(); freeList.clear(); @@ -2558,7 +2558,7 @@ class VanillaChronicleMap, final Segment segment = segments[segIndex]; final long pos = returnedPos; try { - segment.writeLock(null); + segment.writeLock(); if (segment.hashLookup().getPositions().isClear(pos)) { // The case: // 1. iterator.next() - thread 1 @@ -2567,7 +2567,7 @@ class VanillaChronicleMap, // 3. iterator.remove() - thread 1 segment.writeUnlock(); // not re-entrant. VanillaChronicleMap.this.remove(returnedEntry.getKey()); - segment.writeLock(null); + segment.writeLock(); } else { removePresent(segment, pos); } diff --git a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java index d669d3bec..6ecb96ac3 100755 --- a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java +++ b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java @@ -52,7 +52,7 @@ import java.util.concurrent.TimeUnit; * @see ChronicleSet * @see net.openhft.chronicle.map.ChronicleMapBuilder */ -public class ChronicleSetBuilder +public final class ChronicleSetBuilder implements ChronicleHashBuilder, ChronicleSetBuilder> { private ChronicleMapBuilder chronicleMapBuilder; @@ -64,7 +64,7 @@ public class ChronicleSetBuilder } public static ChronicleSetBuilder of(Class keyClass) { - return new ChronicleSetBuilder(keyClass); + return new ChronicleSetBuilder<>(keyClass); } @Override @@ -153,6 +153,12 @@ public class ChronicleSetBuilder return this; } + @Override + public ChronicleSetBuilder maxEntryOversizeFactor(int maxEntryOversizeFactor) { + chronicleMapBuilder.maxEntryOversizeFactor(maxEntryOversizeFactor); + return this; + } + @Override public ChronicleSetBuilder entries(long entries) { chronicleMapBuilder.entries(entries); @@ -227,7 +233,7 @@ public class ChronicleSetBuilder } @Override - public ChronicleSetBuilder keyMarshaller(@NotNull BytesMarshaller keyMarshaller) { + public ChronicleSetBuilder keyMarshaller(@NotNull BytesMarshaller keyMarshaller) { chronicleMapBuilder.keyMarshaller(keyMarshaller); return this; } @@ -293,31 +299,26 @@ public class ChronicleSetBuilder } @Override - public ChronicleHashInstanceConfig> instance() { - return new InstanceConfig<>(chronicleMapBuilder.instance()); + public ChronicleSet createStatelessClient(InetSocketAddress serverAddress) + throws IOException { + return statelessClient(serverAddress).create(); } @Override - public ChronicleSet create() throws IOException { + public ChronicleHashInstanceConfig> instance() { + return new SetInstanceConfig<>(chronicleMapBuilder.instance()); + } + + @Override + public ChronicleSet create() { final ChronicleMap map = chronicleMapBuilder.create(); - return new SetFromMap(map); + return new SetFromMap<>(map); } @Override public ChronicleSet createPersistedTo(File file) throws IOException { ChronicleMap map = chronicleMapBuilder.createPersistedTo(file); - return new SetFromMap(map); + return new SetFromMap<>(map); } - - @Override - public String name() { - return chronicleMapBuilder.name(); - } - - public ChronicleSetBuilder name(String name) { - chronicleMapBuilder.name(name); - return this; - } - } diff --git a/src/main/java/net/openhft/chronicle/set/InstanceConfig.java b/src/main/java/net/openhft/chronicle/set/SetInstanceConfig.java similarity index 57% rename from src/main/java/net/openhft/chronicle/set/InstanceConfig.java rename to src/main/java/net/openhft/chronicle/set/SetInstanceConfig.java index 9d3f43f6c..337168998 100644 --- a/src/main/java/net/openhft/chronicle/set/InstanceConfig.java +++ b/src/main/java/net/openhft/chronicle/set/SetInstanceConfig.java @@ -25,47 +25,55 @@ import net.openhft.chronicle.map.ChronicleMap; import java.io.File; import java.io.IOException; -final class InstanceConfig implements ChronicleHashInstanceConfig> { +final class SetInstanceConfig implements ChronicleHashInstanceConfig> { private final ChronicleHashInstanceConfig> mapConfig; - private boolean used; - - InstanceConfig(ChronicleHashInstanceConfig> mapConfig) { + SetInstanceConfig(ChronicleHashInstanceConfig> mapConfig) { this.mapConfig = mapConfig; } @Override public ChronicleHashInstanceConfig> replicated( byte identifier, TcpTransportAndNetworkConfig tcpTransportAndNetwork) { - return new InstanceConfig<>(mapConfig.replicated(identifier, tcpTransportAndNetwork)); + return new SetInstanceConfig<>(mapConfig.replicated(identifier, tcpTransportAndNetwork)); } @Override - public ChronicleHashInstanceConfig> replicated(SingleChronicleHashReplication replication) { - return new InstanceConfig<>(mapConfig.replicated(replication)); + public ChronicleHashInstanceConfig> replicated( + SingleChronicleHashReplication replication) { + return new SetInstanceConfig<>(mapConfig.replicated(replication)); } @Override public ChronicleHashInstanceConfig> replicatedViaChannel( ReplicationChannel channel) { - return new InstanceConfig<>(mapConfig.replicatedViaChannel(channel)); + return new SetInstanceConfig<>(mapConfig.replicatedViaChannel(channel)); } @Override public ChronicleHashInstanceConfig> persistedTo(File file) { - return new InstanceConfig<>(mapConfig.persistedTo(file)); + return new SetInstanceConfig<>(mapConfig.persistedTo(file)); + } + + @Override + public ChronicleHashInstanceConfig> name(String name) { + return new SetInstanceConfig<>(mapConfig.name(name)); } @Override public synchronized ChronicleSet create() throws IOException { - if (used) - throw new IllegalStateException( - "A ChronicleSet has already been created using this instance config. " + - "Create a new instance config (builder.instance() or call any " + - "configuration method on this instance config) to create a new " + - "ChronicleSet instance"); - used = true; - return new SetFromMap<>(mapConfig.create()); + try { + return new SetFromMap<>(mapConfig.create()); + } catch (IllegalStateException e) { + if (e.getMessage() != null && e.getMessage().startsWith("A ChronicleMap")) { + throw new IllegalStateException( + "A ChronicleSet has already been created using this instance config. " + + "Create a new instance config (builder.instance()) to create " + + "a new ChronicleSet instance"); + } else { + throw e; + } + } } } diff --git a/src/main/java/net/openhft/chronicle/set/StatelessSetConfig.java b/src/main/java/net/openhft/chronicle/set/StatelessSetConfig.java index 983d68604..c5ab66a84 100644 --- a/src/main/java/net/openhft/chronicle/set/StatelessSetConfig.java +++ b/src/main/java/net/openhft/chronicle/set/StatelessSetConfig.java @@ -35,6 +35,11 @@ final class StatelessSetConfig implements StatelessClientConfig(mapClientBuilder.timeout(timeout, units)); } + @Override + public StatelessClientConfig> name(String name) { + return new StatelessSetConfig<>(mapClientBuilder.name(name)); + } + @Override public ChronicleSet create() throws IOException { return new SetFromMap<>(mapClientBuilder.create()); diff --git a/src/test/java/eg/TestNesterInterfaceMarshal.java b/src/test/java/eg/TestNesterInterfaceMarshal.java index cf36ad7cc..e244394b5 100644 --- a/src/test/java/eg/TestNesterInterfaceMarshal.java +++ b/src/test/java/eg/TestNesterInterfaceMarshal.java @@ -5,7 +5,6 @@ import net.openhft.chronicle.map.ChronicleMap; import net.openhft.chronicle.map.ChronicleMapBuilder; import net.openhft.lang.model.DataValueClasses; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; import java.net.InetSocketAddress; diff --git a/src/test/java/net/openhft/chronicle/map/CHMTest5.java b/src/test/java/net/openhft/chronicle/map/CHMTest5.java index 27fb7abeb..4f94f21bc 100644 --- a/src/test/java/net/openhft/chronicle/map/CHMTest5.java +++ b/src/test/java/net/openhft/chronicle/map/CHMTest5.java @@ -39,7 +39,7 @@ public class CHMTest5 { .entries(1000); //// don't include this, just to check it is as expected. - assertEquals(8, ((AbstractChronicleMapBuilder) builder.delegate).minSegments()); + assertEquals(8, builder.minSegments()); //// end of test String chmPath = System.getProperty("java.io.tmpdir") + System.getProperty("file.separator") + "CHMTest5"; diff --git a/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java b/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java index 4860e2903..1e09107c8 100644 --- a/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java +++ b/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java @@ -241,9 +241,9 @@ public class CHMUseCasesTest { public void testIntegerIntegerMap() throws ExecutionException, InterruptedException { try (ChronicleMap map = ChronicleMapBuilder .of(Integer.class, Integer.class) - .entrySize(8) - .disableOversizedEntries() // disabled for testing purposes only. .create()) { + assertEquals(8, ((VanillaChronicleMap) map).entrySize); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); Integer key1; Integer key2; Integer value1; @@ -295,9 +295,9 @@ public class CHMUseCasesTest { public void testLongLongMap() throws ExecutionException, InterruptedException { try (ChronicleMap map = ChronicleMapBuilder .of(Long.class, Long.class) - .entrySize(16) - .disableOversizedEntries() // disabled for testing purposes only. .create()) { + assertEquals(16, ((VanillaChronicleMap) map).entrySize); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); map.put(1L, 11L); assertEquals((Long) 11L, map.get(1L)); @@ -338,9 +338,9 @@ public class CHMUseCasesTest { public void testDoubleDoubleMap() throws ExecutionException, InterruptedException { try (ChronicleMap map = ChronicleMapBuilder .of(Double.class, Double.class) - .disableOversizedEntries() // disabled for testing purposes only. - .entrySize(16) .create()) { + assertEquals(16, ((VanillaChronicleMap) map).entrySize); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); map.put(1.0, 11.0); assertEquals((Double) 11.0, map.get(1.0)); @@ -382,8 +382,8 @@ public class CHMUseCasesTest { public void testByteArrayByteArrayMap() throws ExecutionException, InterruptedException { try (ChronicleMap map = ChronicleMapBuilder .of(byte[].class, byte[].class) - .disableOversizedEntries() // disabled for testing purposes only. - .entrySize(12) + .keySize(4).valueSize(4) + .maxEntryOversizeFactor(1) .create()) { byte[] key1 = {1, 1, 1, 1}; byte[] key2 = {2, 2, 2, 2}; @@ -432,9 +432,9 @@ public class CHMUseCasesTest { .of(ByteBuffer.class, ByteBuffer.class) .keyMarshaller(ByteBufferMarshaller.INSTANCE) .valueMarshaller(ByteBufferMarshaller.INSTANCE) - .disableOversizedEntries() // disabled for testing purposes only. - // TODO does this have to be 14 bytes? - .entrySize(14) + .keySize(8) + .valueSize(8) + .maxEntryOversizeFactor(1) .create()) { ByteBuffer key1 = ByteBuffer.wrap(new byte[]{1, 1, 1, 1}); ByteBuffer key2 = ByteBuffer.wrap(new byte[]{2, 2, 2, 2}); @@ -539,8 +539,8 @@ public class CHMUseCasesTest { .of(ByteBuffer.class, ByteBuffer.class) .valueMarshaller(ByteBufferMarshaller.INSTANCE) // we should not have to to this ! .keyMarshaller(ByteBufferMarshaller.INSTANCE) // we should not have to to this ! - .disableOversizedEntries() // disabled for testing purposes only. - .entrySize(12) + .keySize(5).valueSize(5) + .maxEntryOversizeFactor(1) .create()) { ByteBuffer key1 = ByteBuffer.wrap(new byte[]{1, 1, 1, 1}); ByteBuffer key2 = ByteBuffer.wrap(new byte[]{2, 2, 2, 2}); @@ -591,9 +591,9 @@ public class CHMUseCasesTest { public void testIntValueIntValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(IntValue.class, IntValue.class) - .disableOversizedEntries() // disabled for testing purposes only. - .entrySize(8) .create()) { + assertEquals(8, ((VanillaChronicleMap) map).entrySize); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); IntValue key1 = map.newKeyInstance(); IntValue key2 = map.newKeyInstance(); IntValue value1 = map.newValueInstance(); @@ -677,9 +677,9 @@ public class CHMUseCasesTest { // TODO once this is working, merge the next test. try (ChronicleMap map = ChronicleMapBuilder .of(UnsignedIntValue.class, UnsignedIntValue.class) - .disableOversizedEntries() // disabled for testing purposes only. - .entrySize(8) .create()) { + assertEquals(8, ((VanillaChronicleMap) map).entrySize); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); UnsignedIntValue key1 = map.newKeyInstance(); UnsignedIntValue value1 = map.newValueInstance(); @@ -781,9 +781,9 @@ public class CHMUseCasesTest { public void testIntValueShortValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(IntValue.class, ShortValue.class) - .disableOversizedEntries() // disabled for testing purposes only. - .entrySize(6) .create()) { + assertEquals(6, ((VanillaChronicleMap) map).entrySize); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); IntValue key1 = map.newKeyInstance(); IntValue key2 = map.newKeyInstance(); ShortValue value1 = map.newValueInstance(); @@ -863,10 +863,10 @@ public class CHMUseCasesTest { public void testIntValueUnsignedShortValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(IntValue.class, UnsignedShortValue.class) - .entrySize(6) - .disableOversizedEntries() // disabled for testing purposes only. .create()) { - + // TODO should be 6 + assertEquals(8, ((VanillaChronicleMap) map).entrySize); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); IntValue key1 = map.newKeyInstance(); IntValue key2 = map.newKeyInstance(); UnsignedShortValue value1 = map.newValueInstance(); @@ -946,10 +946,9 @@ public class CHMUseCasesTest { public void testIntValueCharValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(IntValue.class, CharValue.class) - .disableOversizedEntries() // disabled for testing purposes only. - .entrySize(6) .create()) { - + assertEquals(6, ((VanillaChronicleMap) map).entrySize); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); IntValue key1 = map.newKeyInstance(); IntValue key2 = map.newKeyInstance(); CharValue value1 = map.newValueInstance(); @@ -1029,10 +1028,11 @@ public class CHMUseCasesTest { public void testIntValueUnsignedByteMap() { try (ChronicleMap map = ChronicleMapBuilder .of(IntValue.class, UnsignedByteValue.class) - .entrySize(5) - .disableOversizedEntries() // disabled for testing purposes only. .create()) { - + // TODO should be 5, but shorter fields based on range doesn't seem to be implemented + // on data value generation level yet + assertEquals(8, ((VanillaChronicleMap) map).entrySize); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); IntValue key1 = map.newKeyInstance(); IntValue key2 = map.newKeyInstance(); @@ -1115,11 +1115,9 @@ public class CHMUseCasesTest { public void testIntValueBooleanValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(IntValue.class, BooleanValue.class) - .disableOversizedEntries() // disabled for testing purposes only. - .entrySize(5) .create()) { - - + assertEquals(5, ((VanillaChronicleMap) map).entrySize); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); IntValue key1 = map.newKeyInstance(); IntValue key2 = map.newKeyInstance(); @@ -1201,9 +1199,9 @@ public class CHMUseCasesTest { public void testFloatValueFloatValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(FloatValue.class, FloatValue.class) - .entrySize(8) - .disableOversizedEntries() // disabled for testing purposes only. .create()) { + assertEquals(8, ((VanillaChronicleMap) map).entrySize); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); FloatValue key1 = map.newKeyInstance(); FloatValue key2 = map.newKeyInstance(); @@ -1285,8 +1283,9 @@ public class CHMUseCasesTest { public void testDoubleValueDoubleValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(DoubleValue.class, DoubleValue.class) - .entrySize(16) .create()) { + assertEquals(16, ((VanillaChronicleMap) map).entrySize); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); DoubleValue key1 = map.newKeyInstance(); DoubleValue key2 = map.newKeyInstance(); @@ -1370,9 +1369,9 @@ public class CHMUseCasesTest { public void testLongValueLongValueMap() { try (ChronicleMap map = ChronicleMapBuilder .of(LongValue.class, LongValue.class) - .entrySize(16) - .disableOversizedEntries() // disabled for testing purposes only. .create()) { + assertEquals(16, ((VanillaChronicleMap) map).entrySize); + assertEquals(1, ((VanillaChronicleMap) map).maxEntryOversizeFactor); LongValue key1 = map.newKeyInstance(); LongValue key2 = map.newKeyInstance(); diff --git a/src/test/java/net/openhft/chronicle/map/DataKeyValueTest.java b/src/test/java/net/openhft/chronicle/map/DataKeyValueTest.java index 7d2cb72e1..dd3aef559 100644 --- a/src/test/java/net/openhft/chronicle/map/DataKeyValueTest.java +++ b/src/test/java/net/openhft/chronicle/map/DataKeyValueTest.java @@ -20,7 +20,6 @@ import net.openhft.lang.model.Byteable; import net.openhft.lang.model.DataValueClasses; import net.openhft.lang.values.IntValue; import net.openhft.lang.values.LongValue; -import org.junit.Assert; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -30,7 +29,7 @@ public class DataKeyValueTest { @Test public void dataKeyValueTest() { ChronicleMap map = - OnHeapUpdatableChronicleMapBuilder.of(IntValue.class, LongValue.class) + ChronicleMapBuilder.of(IntValue.class, LongValue.class) .entries(1000).create(); IntValue heapKey = DataValueClasses.newInstance(IntValue.class); IntValue directKey = DataValueClasses.newDirectInstance(IntValue.class); diff --git a/src/test/java/net/openhft/chronicle/map/DefaultValueTest.java b/src/test/java/net/openhft/chronicle/map/DefaultValueTest.java index d0b773361..7e47e4bf0 100644 --- a/src/test/java/net/openhft/chronicle/map/DefaultValueTest.java +++ b/src/test/java/net/openhft/chronicle/map/DefaultValueTest.java @@ -37,7 +37,7 @@ public class DefaultValueTest { ArrayList defaultValue = new ArrayList(); defaultValue.add(42); - try (ChronicleMap> map = OnHeapUpdatableChronicleMapBuilder + try (ChronicleMap> map = ChronicleMapBuilder .of(String.class, (Class>) ((Class) List.class)) .defaultValue(defaultValue).createPersistedTo(file)) { ArrayList using = new ArrayList(); @@ -49,7 +49,7 @@ public class DefaultValueTest { } ArrayList using = new ArrayList(); - try (ChronicleMap> map = OnHeapUpdatableChronicleMapBuilder + try (ChronicleMap> map = ChronicleMapBuilder .of(String.class, (Class>) ((Class) List.class)) .createPersistedTo(file)) { assertEquals(defaultValue, map.acquireUsing("c", using)); diff --git a/src/test/java/net/openhft/chronicle/map/FirstPrimitiveFieldTest.java b/src/test/java/net/openhft/chronicle/map/FirstPrimitiveFieldTest.java new file mode 100644 index 000000000..d3e24dd12 --- /dev/null +++ b/src/test/java/net/openhft/chronicle/map/FirstPrimitiveFieldTest.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.chronicle.map.fromdocs.BondVOInterface; +import net.openhft.lang.model.DataValueGenerator; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; + +import static org.hamcrest.CoreMatchers.hasItem; + +public class FirstPrimitiveFieldTest { + @Test + public void firstPrimitiveFieldTest() { + Assert.assertThat(Arrays.asList(long.class, double.class), + hasItem(DataValueGenerator.firstPrimitiveFieldType(BondVOInterface.class))); + } +} diff --git a/src/test/java/net/openhft/chronicle/map/IntValueMapTest.java b/src/test/java/net/openhft/chronicle/map/IntValueMapTest.java index b92da7ed3..76e3ea92a 100644 --- a/src/test/java/net/openhft/chronicle/map/IntValueMapTest.java +++ b/src/test/java/net/openhft/chronicle/map/IntValueMapTest.java @@ -36,7 +36,7 @@ public class IntValueMapTest { @Test public void test() throws IOException { - try (final ChronicleMap map = OnHeapUpdatableChronicleMapBuilder + try (final ChronicleMap map = ChronicleMapBuilder .of(IntValue.class, CharSequence.class) .entries(20000) .keyMarshaller(ByteableIntValueMarshaller.INSTANCE).create()) { diff --git a/src/test/java/net/openhft/chronicle/map/Issue42Test.java b/src/test/java/net/openhft/chronicle/map/Issue42Test.java index 0bb6ff305..7d078c7f2 100755 --- a/src/test/java/net/openhft/chronicle/map/Issue42Test.java +++ b/src/test/java/net/openhft/chronicle/map/Issue42Test.java @@ -32,7 +32,7 @@ public class Issue42Test { if (!Jvm.isWindows()) return; - try (final ChronicleMap map = OnHeapUpdatableChronicleMapBuilder + try (final ChronicleMap map = ChronicleMapBuilder .of(CharSequence.class, CharSequence.class) .entrySize(18) .entries(1500000) diff --git a/src/test/java/net/openhft/chronicle/map/MapByNameTest.java b/src/test/java/net/openhft/chronicle/map/MapByNameTest.java index 6fe67ea66..2859031f6 100644 --- a/src/test/java/net/openhft/chronicle/map/MapByNameTest.java +++ b/src/test/java/net/openhft/chronicle/map/MapByNameTest.java @@ -16,6 +16,7 @@ package net.openhft.chronicle.map; +import net.openhft.chronicle.hash.ChronicleHashInstanceConfig; import net.openhft.chronicle.hash.replication.ReplicationHub; import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; import org.junit.Before; @@ -45,16 +46,19 @@ public class MapByNameTest { } @Test + @Ignore("builder is not serializable") public void testSerializingBuilder() throws TimeoutException, InterruptedException, IOException { - final ChronicleMapBuilder builder = ChronicleMapBuilder.of(CharSequence.class, CharSequence.class) + final MapInstanceConfig config = + (MapInstanceConfig) + ChronicleMapBuilder.of(CharSequence.class, CharSequence.class) .entries(2) .minSegments(2) - .name("map1") .replication((byte) 1, TcpTransportAndNetworkConfig.of(8244)) - .removeReturnsNull(true); + .removeReturnsNull(true) + .instance().name("map1"); - final ChronicleMap map = findMapByName.create(builder); + final ChronicleMap map = findMapByName.create(config); map.put("hello", "world"); assertEquals(map.get("hello"), "world"); @@ -71,14 +75,16 @@ public class MapByNameTest { NodeDiscovery nodeDiscovery = new NodeDiscovery(); - ChronicleMapBuilder builder = ChronicleMapBuilder.of(CharSequence - .class, CharSequence.class) + MapInstanceConfig config = + (MapInstanceConfig) + ChronicleMapBuilder.of(CharSequence.class, CharSequence.class) .minSegments(2) - .name("myMap") - .removeReturnsNull(true); + .removeReturnsNull(true) + .instance() + .name("myMap"); ReplicationHubFindByName mapByName = nodeDiscovery.mapByName(); - // mapByName.add(builder); + // mapByName.add(config); ChronicleMap myMap2 = mapByName.from("myMap"); } @@ -89,14 +95,16 @@ public class MapByNameTest { final ReplicationHubFindByName mapByName = nodeDiscovery.mapByName(); Thread.sleep(2000); - ChronicleMapBuilder builder = ChronicleMapBuilder.of(CharSequence - .class, CharSequence.class) + MapInstanceConfig config = + (MapInstanceConfig) + ChronicleMapBuilder.of(CharSequence.class, CharSequence.class) .minSegments(2) .entries(2) - .name("myMap6") - .removeReturnsNull(true); + .removeReturnsNull(true) + .instance() + .name("myMap6"); - ChronicleMap map = mapByName.create(builder); + ChronicleMap map = mapByName.create(config); map.put("hello", "world"); // final ChronicleMap map = mapByName.from("myMap5"); diff --git a/src/test/java/net/openhft/chronicle/map/NoTcpReplicationSoakTest.java b/src/test/java/net/openhft/chronicle/map/NoTcpReplicationSoakTest.java index b1003d1de..f0a487a9f 100644 --- a/src/test/java/net/openhft/chronicle/map/NoTcpReplicationSoakTest.java +++ b/src/test/java/net/openhft/chronicle/map/NoTcpReplicationSoakTest.java @@ -44,17 +44,19 @@ public class NoTcpReplicationSoakTest { map1 = (ReplicatedChronicleMap) ChronicleMapBuilder.of(Integer.class, CharSequence.class) - .name("map1") .replication((byte) 1) .timeProvider(timeProvider) + .instance() + .name("map1") .create(); } { map2 = (ReplicatedChronicleMap) ChronicleMapBuilder.of(Integer.class, CharSequence.class) - .name("map2") .replication((byte) 2) .timeProvider(timeProvider) + .instance() + .name("map2") .create(); } diff --git a/src/test/java/net/openhft/chronicle/map/SerializerTest.java b/src/test/java/net/openhft/chronicle/map/SerializerTest.java index ca7bf70a0..53543409d 100644 --- a/src/test/java/net/openhft/chronicle/map/SerializerTest.java +++ b/src/test/java/net/openhft/chronicle/map/SerializerTest.java @@ -70,9 +70,8 @@ public class SerializerTest { final ByteBufferBytes out = new ByteBufferBytes(ByteBuffer.allocateDirect(1024)); ByteBufferBytes in = out.slice(); - ChronicleMapBuilder cBuilder = ChronicleMapBuilder.of(Integer.class, valueClass); - - OnHeapUpdatableChronicleMapBuilder builder = (OnHeapUpdatableChronicleMapBuilder) cBuilder.delegate; + ChronicleMapBuilder builder = + ChronicleMapBuilder.of(Integer.class, valueClass); builder.preMapConstruction(false); @@ -95,9 +94,7 @@ public class SerializerTest { final ByteBufferBytes out = new ByteBufferBytes(ByteBuffer.allocateDirect(1024)); ByteBufferBytes in = out.slice(); - ChronicleMapBuilder cBuilder = ChronicleMapBuilder.of(clazz, Integer.class); - - OnHeapUpdatableChronicleMapBuilder builder = (OnHeapUpdatableChronicleMapBuilder) cBuilder.delegate; + ChronicleMapBuilder builder = ChronicleMapBuilder.of(clazz, Integer.class); builder.preMapConstruction(false); { diff --git a/src/test/java/net/openhft/chronicle/map/TcpReplicationSoakTest.java b/src/test/java/net/openhft/chronicle/map/TcpReplicationSoakTest.java index 3d17c22d3..0617e8298 100644 --- a/src/test/java/net/openhft/chronicle/map/TcpReplicationSoakTest.java +++ b/src/test/java/net/openhft/chronicle/map/TcpReplicationSoakTest.java @@ -38,25 +38,29 @@ public class TcpReplicationSoakTest { { final TcpTransportAndNetworkConfig tcpConfig1 = TcpTransportAndNetworkConfig.of(s_port, endpoint).autoReconnectedUponDroppedConnection(true).name(" map1") - .heartBeatInterval(1, TimeUnit.SECONDS); + .heartBeatInterval(1, TimeUnit.SECONDS) + .packetSize(1024 * 64); map1 = ChronicleMapBuilder.of(Integer.class, CharSequence.class) .entries(Builder.SIZE + Builder.SIZE) .actualSegments(1) - .name("map1") .replication((byte) 1, tcpConfig1) + .instance() + .name("map1") .create(); } { final TcpTransportAndNetworkConfig tcpConfig2 = TcpTransportAndNetworkConfig.of (s_port + 1).autoReconnectedUponDroppedConnection(true).name("map2") - .heartBeatInterval(1, TimeUnit.SECONDS); + .heartBeatInterval(1, TimeUnit.SECONDS) + .packetSize(1024 * 64); map2 = ChronicleMapBuilder.of(Integer.class, CharSequence.class) .entries(Builder.SIZE + Builder.SIZE) - .name("map2") .replication((byte) 2, tcpConfig2) + .instance() + .name("map2") .create(); } 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 8c7bea72d..a7f4f48bf 100644 --- a/src/test/java/net/openhft/chronicle/map/fromdocs/OpenJDKAndHashMapExamplesTest.java +++ b/src/test/java/net/openhft/chronicle/map/fromdocs/OpenJDKAndHashMapExamplesTest.java @@ -56,7 +56,7 @@ public class OpenJDKAndHashMapExamplesTest { file.deleteOnExit(); ChronicleMap chm = ChronicleMapBuilder - .of(String.class, BondVOInterface.class) + .of(String.class, DataValueClasses.directClassFor(BondVOInterface.class)) .keySize(10) .createPersistedTo(file);