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 new file mode 100644 index 000000000..0e71de34d --- /dev/null +++ b/src/main/java/net/openhft/chronicle/hash/serialization/internal/DataValueBytesMarshallers.java @@ -0,0 +1,481 @@ +/* + * 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.BytesReader; +import net.openhft.chronicle.hash.serialization.BytesWriter; +import net.openhft.compiler.CompilerUtils; +import net.openhft.lang.io.Bytes; +import net.openhft.lang.io.serialization.ObjectFactory; +import net.openhft.lang.model.*; +import org.jetbrains.annotations.NotNull; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.SortedSet; +import java.util.concurrent.ConcurrentHashMap; + +import static net.openhft.lang.model.DataValueGenerator.*; + +public final class DataValueBytesMarshallers { + + private static final boolean dumpCode = Boolean.getBoolean("dvg.dumpCode"); + private static final Map readersClassMap = + new ConcurrentHashMap<>(); + private static final Map readersWithCustomFactoriesClassMap = + new ConcurrentHashMap<>(); + private static final Map writerClassMap = + new ConcurrentHashMap<>(); + + public static BytesReader acquireBytesReader(Class tClass) { + Class readerClass = acquireReaderClass(tClass); + try { + Field instanceField = readerClass.getDeclaredField("INSTANCE"); + Object reader = instanceField.get(null); + return (BytesReader) reader; + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new AssertionError(e); + } + } + + 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 { + Field instanceField = readerClass.getDeclaredField("INSTANCE"); + Object reader = instanceField.get(null); + return (BytesWriter) reader; + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new AssertionError(e); + } + } + + + public static Class acquireReaderClass(Class tClass) { + DataValueClasses.directClassFor(tClass); + Class readerClass = readersClassMap.get(tClass); + if (readerClass != null) + return readerClass; + DataValueModel dvmodel = DataValueModels.acquireModel(tClass); + for (Class clazz : dvmodel.nestedModels()) { + // touch them to make sure they are loaded. + Class clazz2 = acquireReaderClass(clazz); + } + String actual = generateBytesReader(tClass); + if (dumpCode) + LoggerFactory.getLogger(DataValueGenerator.class).info(actual); + ClassLoader classLoader = tClass.getClassLoader(); + String className = bytesReaderName(tClass, false); + try { + readerClass = classLoader.loadClass(className); + } catch (ClassNotFoundException ignored) { + try { + readerClass = CompilerUtils.CACHED_COMPILER + .loadFromJava(classLoader, className, actual); + } catch (ClassNotFoundException e) { + throw new AssertionError(e); + } + } + readersClassMap.put(tClass, readerClass); + return readerClass; + } + + public static Class acquireWriterClass(Class tClass) { + Class writerClass = writerClassMap.get(tClass); + if (writerClass != null) + return writerClass; + DataValueModel dvmodel = DataValueModels.acquireModel(tClass); + for (Class clazz : dvmodel.nestedModels()) { + // touch them to make sure they are loaded. + Class clazz2 = acquireWriterClass(clazz); + } + String actual = generateBytesWriter(tClass); + if (dumpCode) + LoggerFactory.getLogger(DataValueGenerator.class).info(actual); + ClassLoader classLoader = tClass.getClassLoader(); + String className = bytesWriterName(tClass, false); + try { + writerClass = classLoader.loadClass(className); + } catch (ClassNotFoundException ignored) { + try { + writerClass = CompilerUtils.CACHED_COMPILER + .loadFromJava(classLoader, className, actual); + } catch (ClassNotFoundException e) { + throw new AssertionError(e); + } + } + writerClassMap.put(tClass, writerClass); + return writerClass; + } + + public static Class acquireReaderWithCustomFactory(Class tClass) { + Class c = readersWithCustomFactoriesClassMap.get(tClass); + if (c != null) + return c; + acquireReaderClass(tClass); + String actual = generateWithCustomFactoryClass(tClass); + if (dumpCode) + LoggerFactory.getLogger(DataValueGenerator.class).info(actual); + ClassLoader classLoader = tClass.getClassLoader(); + String className = bytesReaderName(tClass, false) + "$WithCustomFactory"; + try { + c = classLoader.loadClass(className); + } catch (ClassNotFoundException ignored) { + try { + c = CompilerUtils.CACHED_COMPILER + .loadFromJava(classLoader, className, actual); + } catch (ClassNotFoundException e) { + throw new AssertionError(e); + } + } + readersWithCustomFactoriesClassMap.put(tClass, c); + return c; + } + + private static String generateWithCustomFactoryClass(Class tClass) { + DataValueModel dvModel = DataValueModels.acquireModel(tClass); + + SortedSet imported = newImported(); + imported.add(ObjectFactory.class); + imported.add(NotNull.class); + imported.add(dvModel.type()); + + String simpleName = tClass.getSimpleName(); + + String bytesReaderName = bytesReaderName(simpleName(tClass)); + String name = bytesReaderName + "$WithCustomFactory"; + String withCustomFactoryClass = + "public final class " + name + " extends " + bytesReaderName + " {\n" + + " private static final long serialVersionUID = 0L;\n" + + "\n" + + " @NotNull\n" + + " private final ObjectFactory<" + simpleName + "> factory;\n" + + "\n" + + " " + name + "(@NotNull ObjectFactory<" + + simpleName + "> factory) {\n" + + " this.factory = factory;\n" + + " }\n" + + "\n" + + " @Override\n" + + " " + simpleName + " getInstance() throws Exception {\n" + + " return factory.create();\n" + + " }\n" + + "}\n"; + + StringBuilder sb = new StringBuilder(); + appendPackage(dvModel, sb); + appendImported(imported, sb); + sb.append(withCustomFactoryClass); + return sb.toString(); + } + + + + private static String bytesReaderName(Class type, boolean simple) { + return (simple ? type.getSimpleName() : type.getName()) + "$$BytesReader"; + } + + private static String bytesReaderName(String className) { + return className + "$$BytesReader"; + } + + public static String bytesWriterName(Class type, boolean simple) { + return (simple ? simpleName(type) : type.getName()) + "$$BytesWriter"; + } + + private static String generateBytesReader(Class tClass) { + DataValueModel dvModel = DataValueModels.acquireModel(tClass); + + SortedSet imported = newImported(); + imported.add(BytesReader.class); + imported.add(Bytes.class); + imported.add(ByteableMarshaller.class); + imported.add(Byteable.class); + imported.add(dvModel.type()); + + StringBuilder read = generateReadBody(dvModel, imported); + + StringBuilder sb = new StringBuilder(); + appendPackage(dvModel, sb); + appendImported(imported, sb); + String simpleName = tClass.getSimpleName(); + String bytesReaderName = bytesReaderName(tClass, false); + String simpleReaderName = bytesReaderName(simpleName(tClass)); + sb.append("\npublic class ").append(simpleReaderName) + .append(" implements BytesReader<").append(simpleName).append("> {\n"); + declareSerialVersionUID(sb); + declareStaticInstance(sb, tClass); + generatePrivateConstructor(sb, simpleReaderName); + generateGetInstance(sb, tClass); + generateDelegatingRead(sb, simpleName); + generateRead(read, sb, simpleName, tClass); + sb.append("}\n"); + return sb.toString(); + } + + private static StringBuilder generateReadBody(DataValueModel dvModel, + SortedSet imported) { + StringBuilder read = new StringBuilder(); + + Map.Entry[] entries = + heapSizeOrderedFields(dvModel); + for (Map.Entry entry : entries) { + String name = entry.getKey(); + FieldModel model = entry.getValue(); + Class type = model.type(); + if (shouldImport(type)) + imported.add(type); + + Method setter = getSetter(model); + Method getter = getGetter(model); + + Method orderedSetter = getOrderedSetter(model); + Method volatileGetter = getVolatileGetter(model); + + Method defaultSetter = setter != null ? setter : orderedSetter; + Method defaultGetter = getter != null ? getter : volatileGetter; + if (!dvModel.isScalar(type)) { + if (model.isArray()) { + read.append(" for (int i = 0; i < ") + .append(model.indexSize().value()).append("; i++) {\n"); + read.append(" toReuse.") + .append(defaultSetter.getName()).append("(i, ") + .append(bytesReaderName(type, false)) + .append(".INSTANCE").append(".read(bytes, ") + .append(computeNonScalarOffset(dvModel, type)).append(", ") + .append(defaultGetter.getName()).append("(i)));"); + read.append(" }\n"); + } else { + read.append(" toReuse.") + .append(defaultSetter.getName()).append("(") + .append(bytesReaderName(type, false)) + .append(".INSTANCE").append(".read(bytes, ") + .append(computeNonScalarOffset(dvModel, type)).append(", ") + .append(defaultGetter.getName()).append("()));"); + } + } else { + if (model.isArray()) { + read.append(" for (int i = 0; i < ") + .append(model.indexSize().value()).append("; i++) {\n"); + read.append(" toReuse.").append(defaultSetter.getName()) + .append("(i, bytes.read").append(bytesType(type)).append("());\n"); + read.append(" }\n"); + } else { + read.append(" toReuse.").append(defaultSetter.getName()) + .append("(bytes.read").append(bytesType(type)).append("());\n"); + } + } + } + return read; + } + + private static String generateBytesWriter(Class tClass) { + DataValueModel dvModel = DataValueModels.acquireModel(tClass); + + SortedSet imported = newImported(); + imported.add(BytesWriter.class); + imported.add(Bytes.class); + imported.add(Byteable.class); + imported.add(dvModel.type()); + + String write = generateWriteBody(dvModel, imported); + + StringBuilder sb = new StringBuilder(); + appendPackage(dvModel, sb); + appendImported(imported, sb); + sb.append("public enum ").append(bytesWriterName(tClass, true)) + .append(" implements BytesWriter<").append(tClass.getSimpleName()).append("> {\n"); + sb.append(" INSTANCE;\n\n"); + generateSize(tClass, dvModel, sb); + generateWrite(tClass, dvModel, write, sb); + generateShouldNotBeNull(tClass, sb); + sb.append("}\n"); + return sb.toString(); + } + + private static void generateShouldNotBeNull(Class tClass, StringBuilder sb) { + sb.append(" private static void shouldNotBeNull() {\n"); + sb.append(" throw new NullPointerException(\"Sub-members of " + + tClass.getSimpleName() + " shouldn't be null for this writer. You should specify" + + " custom writer (e. g. using keyMarshallers()/valueMarshallers() methods of" + + "ChronicleMapBuilder, if you want to support null fields/array elements.\");\n"); + sb.append(" }\n"); + } + + private static void generateWrite(Class tClass, DataValueModel dvModel, String write, + StringBuilder sb) { + appendOverride(sb); + sb.append(" public void write(Bytes bytes, ") + .append(tClass.getSimpleName()).append(" e) {\n"); + int size = computeNonScalarOffset(dvModel, tClass); + if (size > 16) { + sb.append(" if (e instanceof Byteable) {\n"); + sb.append( + " Bytes eBytes = ((Byteable) e).bytes();\n" + + " if (eBytes != null) {\n" + + " bytes.write(eBytes, ((Byteable) e).offset(), " + + size + ");\n" + + " } else {\n" + + " throw new NullPointerException(" + + "\"You are trying to write a byteable object of \" +\n" + + " e.getClass() + \", \" +\n" + + " \"which bytes are not assigned. I. e. most likely " + + "the object is uninitialized.\");\n" + + " }\n"); + sb.append(" return;\n"); + sb.append(" }\n"); + } + sb.append(write); + sb.append(" }\n\n"); + } + + private static void generateSize(Class tClass, DataValueModel dvModel, StringBuilder sb) { + appendOverride(sb); + sb.append(" public long size(").append(tClass.getSimpleName()).append(" e) {\n"); + sb.append(" return ").append(computeNonScalarOffset(dvModel, tClass)).append(";\n"); + sb.append(" }\n\n"); + } + + private static String generateWriteBody(DataValueModel dvModel, SortedSet imported) { + StringBuilder write = new StringBuilder(); + + Map.Entry[] entries = + heapSizeOrderedFields(dvModel); + for (Map.Entry entry : entries) { + String name = entry.getKey(); + FieldModel model = entry.getValue(); + Class type = model.type(); + if (shouldImport(type)) + imported.add(type); + + Method setter = getSetter(model); + Method getter = getGetter(model); + + Method orderedSetter = getOrderedSetter(model); + Method volatileGetter = getVolatileGetter(model); + + Method defaultSetter = setter != null ? setter : orderedSetter; + Method defaultGetter = getter != null ? getter : volatileGetter; + if (!dvModel.isScalar(type)) { + if (model.isArray()) { + write.append(" for (int i = 0; i < ") + .append(model.indexSize().value()).append("; i++) {\n"); + write.append(" ") + .append(type.getName()).append(" ").append(" $ = e.") + .append(defaultGetter.getName()).append("(i);\n"); + write.append(" ") + .append("if ($ == null) shouldNotBeNull();\n"); + write.append(" ") + .append(bytesWriterName(type, false)) + .append(".INSTANCE").append(".write(bytes, $);\n"); + write.append(" }\n"); + } else { + write.append(" {") + .append(type.getName()).append(" ").append(" $ = e.") + .append(defaultGetter.getName()).append("();\n"); + write.append(" ") + .append("if ($ == null) shouldNotBeNull();\n"); + write.append(" ") + .append(bytesWriterName(type, false)) + .append(".INSTANCE").append(".write(bytes, $);}\n"); + } + } else { + if (model.isArray()) { + write.append(" for (int i = 0; i < ") + .append(model.indexSize().value()).append("; i++) {\n"); + write.append(" bytes.write").append(bytesType(type)) + .append("(e.").append(defaultGetter.getName()).append("(i));\n"); + write.append(" }\n"); + } else { + write.append(" bytes.write").append(bytesType(type)) + .append("(e.").append(defaultGetter.getName()).append("());\n"); + } + } + } + return write.toString(); + } + + private static void generateRead(StringBuilder read, StringBuilder sb, String simpleName, + Class cl) { + appendOverride(sb); + sb.append(" public ").append(simpleName).append(" read(Bytes bytes, long size, ") + .append(simpleName).append(" toReuse) {\n"); + 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(" " + + "ByteableMarshaller.setBytesAndOffset(((Byteable) toReuse), bytes);\n"); + sb.append(" bytes.skip(size);\n"); + sb.append(" return toReuse;\n"); + sb.append(" }\n"); + sb.append(read); + sb.append(" return toReuse;\n"); + sb.append(" } catch (Exception e) {\n"); + sb.append(" throw new IllegalStateException(e);\n"); + sb.append(" }\n"); + sb.append("}\n\n"); + } + + private static void generateDelegatingRead(StringBuilder sb, String simpleName) { + appendOverride(sb); + sb.append(" public ").append(simpleName).append(" read(Bytes bytes, long size) {\n"); + sb.append(" return read(bytes, size, null);\n"); + sb.append(" }\n\n"); + } + + private static void appendOverride(StringBuilder sb) { + sb.append(" @Override\n"); + } + + private static void generatePrivateConstructor(StringBuilder sb, String bytesReaderName) { + sb.append(" ").append(bytesReaderName).append("() {}\n\n"); + } + + private static void declareStaticInstance(StringBuilder sb, Class cl) { + String bytesReaderName = bytesReaderName(simpleName(cl)); + sb.append(" public static final ").append(bytesReaderName).append(" INSTANCE = new ") + .append(bytesReaderName).append("();\n\n"); + } + + private static StringBuilder declareSerialVersionUID(StringBuilder sb) { + return sb.append(" private static final long serialVersionUID = 0L;\n\n"); + } + + private static void generateGetInstance(StringBuilder sb, Class cl) { + sb.append(" ").append(cl.getCanonicalName()) + .append(" getInstance() throws Exception {\n"); + sb.append(" ").append("return new ").append(cl.getName()).append("$$Native();\n"); + sb.append(" ").append("}\n\n"); + } +} diff --git a/src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java index d3bffe682..a9eb45115 100755 --- a/src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/AbstractChronicleMapBuilder.java @@ -148,8 +148,6 @@ abstract class AbstractChronicleMapBuilder keyClass, Class valueClass) { keyBuilder = new SerializationBuilder(keyClass, SerializationBuilder.Role.KEY); valueBuilder = new SerializationBuilder(valueClass, SerializationBuilder.Role.VALUE); - if (valueClass == LongValue.class) - prepareValueBytesOnAcquire(new ZeroOutValueBytes(valueSize())); } protected static boolean offHeapReference(Class valueClass) { diff --git a/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java b/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java index 4f34d14f4..fb4be52d4 100644 --- a/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/SerializationBuilder.java @@ -18,6 +18,7 @@ package net.openhft.chronicle.map; +import net.openhft.lang.model.*; import net.openhft.lang.threadlocal.Provider; import net.openhft.lang.threadlocal.ThreadLocalCopies; import net.openhft.chronicle.hash.serialization.*; @@ -28,7 +29,6 @@ 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.io.serialization.impl.*; -import net.openhft.lang.model.Byteable; import net.openhft.lang.values.LongValue; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -38,6 +38,7 @@ import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Modifier; +import static net.openhft.chronicle.hash.serialization.SizeMarshallers.constant; import static net.openhft.chronicle.map.Objects.hash; import static net.openhft.chronicle.hash.serialization.SizeMarshallers.stopBit; import static net.openhft.chronicle.map.SerializationBuilder.Role.KEY; @@ -65,6 +66,7 @@ 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; @@ -83,6 +85,22 @@ final class SerializationBuilder implements Cloneable, Serializable{ new NewInstanceObjectFactory(eClass) : NullObjectFactory.of(); + if (eClass.isInterface()) { + try { + BytesReader reader = DataValueBytesMarshallers.acquireBytesReader(eClass); + 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)); + return; + } catch (Exception e) { + // ignore, fall through + } + } + if (concreteClass(eClass) && Byteable.class.isAssignableFrom(eClass)) { ByteableMarshaller byteableMarshaller = ByteableMarshaller.of((Class) eClass); sizeMarshaller(byteableMarshaller); @@ -129,6 +147,10 @@ final class SerializationBuilder implements Cloneable, Serializable{ } } + private void clearDefaults() { + dataValueClass = false; + } + @SuppressWarnings("unchecked") private BytesMarshaller chooseMarshaller(Class eClass, Class classForMarshaller) { if (BytesMarshallable.class.isAssignableFrom(eClass)) @@ -144,6 +166,7 @@ final class SerializationBuilder implements Cloneable, Serializable{ } public SerializationBuilder interop(BytesInterop interop) { + clearDefaults(); return copyingInterop(null) .setInterop(interop) .metaInterop(DelegatingMetaBytesInterop.>instance()) @@ -152,6 +175,7 @@ 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) @@ -161,6 +185,7 @@ final class SerializationBuilder implements Cloneable, Serializable{ } public SerializationBuilder marshaller(BytesMarshaller marshaller) { + clearDefaults(); return copyingInterop(CopyingInterop.FROM_MARSHALLER) .reader(BytesReaders.fromBytesMarshaller(marshaller)) .setInterop(marshaller) @@ -247,6 +272,7 @@ final class SerializationBuilder implements Cloneable, Serializable{ } public SerializationBuilder reader(BytesReader reader) { + clearDefaults(); this.reader = reader; return this; } @@ -282,6 +308,10 @@ final class SerializationBuilder implements Cloneable, Serializable{ @SuppressWarnings("unchecked") public SerializationBuilder factory(ObjectFactory factory) { + if (dataValueClass) { + reader(DataValueBytesMarshallers.acquireBytesReader(eClass, factory)); + return this; + } if (!marshallerUseFactory(eClass)) { throw new IllegalStateException("Default marshaller for " + eClass + " value don't use object factory"); diff --git a/src/test/java/eg/BGChronicleTest.java b/src/test/java/eg/BGChronicleTest.java index df831891b..a489cd0de 100644 --- a/src/test/java/eg/BGChronicleTest.java +++ b/src/test/java/eg/BGChronicleTest.java @@ -22,7 +22,8 @@ public class BGChronicleTest { byte[] value = new byte[11]; - public ChronicleMap startChronicleMapServer(int port) throws InterruptedException, IOException { + public ChronicleMap startChronicleMapServer(int port) + throws InterruptedException, IOException { TcpTransportAndNetworkConfig serverConfig = TcpTransportAndNetworkConfig.of(port) @@ -107,7 +108,8 @@ public class BGChronicleTest { final InetSocketAddress serverHostPort = new InetSocketAddress(hostNameOrIpAddress, port); - TcpTransportAndNetworkConfig clientConfig = TcpTransportAndNetworkConfig.of(port + 1, serverHostPort) + TcpTransportAndNetworkConfig clientConfig = + TcpTransportAndNetworkConfig.of(port + 1, serverHostPort) .heartBeatInterval(CHRONICLE_HEARTBEAT_SECONDS, TimeUnit.SECONDS).name("clientMap") .autoReconnectedUponDroppedConnection(true); diff --git a/src/test/java/net/openhft/chronicle/map/DataKeyValueTest.java b/src/test/java/net/openhft/chronicle/map/DataKeyValueTest.java new file mode 100644 index 000000000..7d2cb72e1 --- /dev/null +++ b/src/test/java/net/openhft/chronicle/map/DataKeyValueTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.openhft.chronicle.map; + +import net.openhft.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; + +public class DataKeyValueTest { + + @Test + public void dataKeyValueTest() { + ChronicleMap map = + OnHeapUpdatableChronicleMapBuilder.of(IntValue.class, LongValue.class) + .entries(1000).create(); + IntValue heapKey = DataValueClasses.newInstance(IntValue.class); + IntValue directKey = DataValueClasses.newDirectInstance(IntValue.class); + LongValue heapValue = DataValueClasses.newInstance(LongValue.class); + LongValue directValue = DataValueClasses.newDirectInstance(LongValue.class); + + heapKey.setValue(1); + heapValue.setValue(1); + map.put(heapKey, heapValue); + assertEquals(1, map.get(heapKey).getValue()); + assertEquals(1, map.getUsing(heapKey, directValue).getValue()); + + ((Byteable) directValue).bytes(null, 0L); + directKey.setValue(1); + map.getUsing(directKey, directValue).addValue(1); + assertEquals(2, map.getUsing(directKey, heapValue).getValue()); + + directKey.setValue(2); + heapValue.setValue(3); + map.put(directKey, heapValue); + assertEquals(3, map.get(directKey).getValue()); + } +}