/* * Copyright (C) 2015 higherfrequencytrading.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ package net.openhft.chronicle.map; import net.openhft.chronicle.core.io.Closeable; import net.openhft.chronicle.hash.Data; import net.openhft.chronicle.hash.impl.VanillaChronicleHash; import net.openhft.chronicle.hash.serialization.BytesReader; import net.openhft.chronicle.hash.serialization.SizeMarshaller; import net.openhft.chronicle.hash.serialization.internal.MetaBytesInterop; import net.openhft.chronicle.hash.serialization.internal.MetaProvider; import net.openhft.chronicle.hash.serialization.internal.SerializationBuilder; import net.openhft.chronicle.map.impl.*; import net.openhft.chronicle.map.impl.CompiledMapIterationContext; import net.openhft.chronicle.map.impl.CompiledMapQueryContext; import net.openhft.chronicle.map.impl.ret.InstanceReturnValue; import net.openhft.lang.io.Bytes; import net.openhft.lang.model.DataValueClasses; 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.IOException; import java.io.ObjectInputStream; import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; import static net.openhft.chronicle.map.ChronicleMapBuilder.greatestCommonDivisor; public class VanillaChronicleMap, V, VI, MVI extends MetaBytesInterop, R> extends VanillaChronicleHash, MapSegmentContext, ExternalMapQueryContext> implements AbstractChronicleMap { private static final Logger LOG = LoggerFactory.getLogger(VanillaChronicleMap.class); private static final long serialVersionUID = 3L; ///////////////////////////////////////////////// // Value Data model final Class vClass; final Class nativeValueClass; public final SizeMarshaller valueSizeMarshaller; public final BytesReader originalValueReader; public final VI originalValueInterop; public final MVI originalMetaValueInterop; public final MetaProvider metaValueInteropProvider; public final ConstantValueProvider constantValueProvider; public final boolean constantlySizedEntry; public transient Provider> valueReaderProvider; public transient Provider valueInteropProvider; ///////////////////////////////////////////////// // Event listener and meta data public final int metaDataBytes; ///////////////////////////////////////////////// // Behavior final boolean putReturnsNull; final boolean removeReturnsNull; ///////////////////////////////////////////////// // Memory management and dependent fields public final Alignment alignment; public final int worstAlignment; public final boolean couldNotDetermineAlignmentBeforeAllocation; transient Set> entrySet; public transient MapEntryOperations entryOperations; public transient MapMethods methods; public transient DefaultValueProvider defaultValueProvider; transient ThreadLocal queryCxt; transient ThreadLocal iterCxt; public VanillaChronicleMap(ChronicleMapBuilder builder, boolean replicated) throws IOException { super(builder, replicated); SerializationBuilder valueBuilder = builder.valueBuilder; vClass = valueBuilder.eClass; 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(); originalValueInterop = (VI) valueBuilder.interop(); originalMetaValueInterop = (MVI) valueBuilder.metaInterop(); metaValueInteropProvider = (MetaProvider) valueBuilder.metaInteropProvider(); constantValueProvider = builder.constantValueProvider(); constantlySizedEntry = builder.constantlySizedEntries(); // Event listener and meta data metaDataBytes = builder.metaDataBytes(); // Behavior putReturnsNull = builder.putReturnsNull(); removeReturnsNull = builder.removeReturnsNull(); // Concurrency (number of segments), memory management and dependent fields alignment = builder.valueAlignment(); worstAlignment = builder.worstAlignment(replicated); int alignment = this.alignment.alignment(); couldNotDetermineAlignmentBeforeAllocation = greatestCommonDivisor((int) chunkSize, alignment) != alignment; initTransientsFromBuilder(builder); initTransients(); } void initTransientsFromBuilder(ChronicleMapBuilder builder) { this.entryOperations = (MapEntryOperations) builder.entryOperations; this.methods = (MapMethods) builder.methods; this.defaultValueProvider = builder.defaultValueProvider; } @Override public void initTransients() { super.initTransients(); ownInitTransients(); } private void ownInitTransients() { valueReaderProvider = Provider.of((Class) originalValueReader.getClass()); valueInteropProvider = Provider.of((Class) originalValueInterop.getClass()); if (constantValueProvider != null && constantValueProvider.wasDeserialized()) { ThreadLocalCopies copies = valueReaderProvider.getCopies(null); BytesReader valueReader = valueReaderProvider.get(copies, originalValueReader); constantValueProvider.initTransients(valueReader); } initQueryContext(); initIterationContext(); } void initQueryContext() { queryCxt = new ThreadLocal>() { @Override protected CompiledMapQueryContext initialValue() { return new CompiledMapQueryContext<>(VanillaChronicleMap.this); } }; } void initIterationContext() { iterCxt = new ThreadLocal>() { @Override protected CompiledMapIterationContext initialValue() { return new CompiledMapIterationContext<>(VanillaChronicleMap.this); } }; } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); ownInitTransients(); } public final V checkValue(Object value) { if (!vClass.isInstance(value)) { throw new ClassCastException("Value must be a " + vClass.getName() + " but was a " + value.getClass()); } return (V) value; } @Override public V newValueInstance() { if (vClass == CharSequence.class || vClass == StringBuilder.class) return (V) new StringBuilder(); return newInstance(vClass, false); } @Override public K newKeyInstance() { return newInstance(kClass, true); } @Override public Class keyClass() { return kClass; } @Override public Class valueClass() { return vClass; } static T newInstance(Class aClass, boolean isKey) { try { // TODO optimize -- save heap class / direct class in transient fields and call // newInstance() on them directly return isKey ? DataValueClasses.newInstance(aClass) : DataValueClasses.newDirectInstance(aClass); } catch (Exception e) { if (e.getCause() instanceof IllegalStateException) throw e; if (aClass.isInterface()) throw new IllegalStateException("It not possible to create a instance from " + "interface=" + aClass.getSimpleName() + " we recommend you create an " + "instance in the usual way.", e); try { return (T) aClass.newInstance(); } catch (Exception e1) { throw new IllegalStateException("It has not been possible to create a instance " + "of class=" + aClass.getSimpleName() + ", Note : its more efficient if your chronicle map is configured with " + "interface key " + "and value types rather than classes, as this method is able to use " + "interfaces to generate off heap proxies that point straight at your " + "data. " + "In this case you have used a class and chronicle is unable to create an " + "instance of this class has it does not have a default constructor. " + "If your class is mutable, we " + "recommend you create and instance of your class=" + aClass.getSimpleName() + " in the usual way, rather than using this method.", e); } } } @NotNull @Override public final Closeable acquireContext(K key, V usingValue) { QueryContextInterface q = queryContext(key); // TODO optimize to update lock in certain cases try { q.writeLock().lock(); acquireUsingBody(q, usingValue); return q.acquireHandle(); } catch (Throwable e) { try { q.close(); } catch (Throwable suppressed) { e.addSuppressed(suppressed); } throw e; } } private static void checkAcquiredUsing(V acquired, V using) { if (acquired != using) { throw new IllegalArgumentException("acquire*() MUST reuse the given " + "value. Given value " + using + " cannot be reused to read " + acquired); } } @NotNull @Override public final Set> entrySet() { return (entrySet != null) ? entrySet : (entrySet = newEntrySet()); } @Override public int hashCode() { return mapHashCode(); } @Override public boolean equals(Object obj) { return mapEquals(obj); } @Override public String toString() { return mapToString(); } @Override public void clear() { forEachEntry(c -> c.context().remove(c)); } public final long readValueSize(Bytes entry) { long valueSize = valueSizeMarshaller.readSize(entry); alignment.alignPositionAddr(entry); return valueSize; } private CompiledMapQueryContext q() { return (CompiledMapQueryContext) queryCxt.get(); } public QueryContextInterface mapContext() { CompiledMapQueryContext q = q().getContext(); q.initUsed(true); return q; } private CompiledMapIterationContext i() { return (CompiledMapIterationContext) iterCxt.get(); } public IterationContextInterface iterationContext() { CompiledMapIterationContext c = i().getContext(); c.initUsed(true); return c; } @Override @NotNull public QueryContextInterface queryContext(Object key) { checkKey(key); QueryContextInterface q = mapContext(); q.inputKeyInstanceValue().initKey((K) key); q.initInputKey(q.inputKeyInstanceValue()); return q; } @Override @NotNull public QueryContextInterface queryContext(Data key) { QueryContextInterface q = mapContext(); q.initInputKey(key); return q; } @Override public MapSegmentContext segmentContext(int segmentIndex) { IterationContextInterface c = iterationContext(); c.initTheSegmentIndex(segmentIndex); return c; } @Override public V get(Object key) { try (QueryContextInterface q = queryContext(key)) { methods.get(q, q.defaultReturnValue()); return q.defaultReturnValue().returnValue(); } } @Override public V getUsing(K key, V usingValue) { try (QueryContextInterface q = queryContext(key)) { q.usingReturnValue().initUsingReturnValue(usingValue); methods.get(q, q.usingReturnValue()); return q.usingReturnValue().returnValue(); } } @Override public V acquireUsing(K key, V usingValue) { try (QueryContextInterface q = queryContext(key)) { V returnValue = acquireUsingBody(q, usingValue); return returnValue; } } private V acquireUsingBody(QueryContextInterface q, V usingValue) { q.usingReturnValue().initUsingReturnValue(usingValue); methods.acquireUsing(q, q.usingReturnValue()); V returnValue = q.usingReturnValue().returnValue(); checkAcquiredUsing(returnValue, usingValue); return returnValue; } @Override public V putIfAbsent(K key, V value) { checkValue(value); try (QueryContextInterface q = queryContext(key)) { q.inputValueInstanceValue().initValue(value); methods.putIfAbsent(q, q.inputValueInstanceValue(), q.defaultReturnValue()); return q.defaultReturnValue().returnValue(); } } @Override public boolean remove(Object key, Object value) { if (value == null) return false; // ConcurrentHashMap compatibility V v = checkValue(value); try (QueryContextInterface q = queryContext(key)) { q.inputValueInstanceValue().initValue(v); return methods.remove(q, q.inputValueInstanceValue()); } } @Override public boolean replace(K key, V oldValue, V newValue) { checkValue(oldValue); checkValue(newValue); try (QueryContextInterface q = queryContext(key)) { q.inputValueInstanceValue().initValue(oldValue); return methods.replace(q, q.inputValueInstanceValue(), q.wrapValueAsData(newValue)); } } @Override public V replace(K key, V value) { checkValue(value); try (QueryContextInterface q = queryContext(key)) { q.inputValueInstanceValue().initValue(value); methods.replace(q, q.inputValueInstanceValue(), q.defaultReturnValue()); return q.defaultReturnValue().returnValue(); } } @Override public boolean containsKey(Object key) { try (QueryContextInterface q = queryContext(key)) { return methods.containsKey(q); } } @Override public V put(K key, V value) { checkValue(value); try (QueryContextInterface q = queryContext(key)) { q.inputValueInstanceValue().initValue(value); InstanceReturnValue returnValue = putReturnsNull ? NullReturnValue.get() : q.defaultReturnValue(); methods.put(q, q.inputValueInstanceValue(), returnValue); return returnValue.returnValue(); } } @Override public V remove(Object key) { try (QueryContextInterface q = queryContext(key)) { InstanceReturnValue returnValue = removeReturnsNull ? NullReturnValue.get() : q.defaultReturnValue(); methods.remove(q, returnValue); return returnValue.returnValue(); } } @Override public V merge(K key, V value, BiFunction remappingFunction) { try (QueryContextInterface q = queryContext(key)) { q.inputValueInstanceValue().initValue(value); methods.merge(q, q.inputValueInstanceValue(), remappingFunction, q.defaultReturnValue()); return q.defaultReturnValue().returnValue(); } } @Override public V compute(K key, BiFunction remappingFunction) { try (QueryContextInterface q = queryContext(key)) { methods.compute(q, remappingFunction, q.defaultReturnValue()); return q.defaultReturnValue().returnValue(); } } @Override public V computeIfAbsent(K key, Function mappingFunction) { try (QueryContextInterface q = queryContext(key)) { methods.computeIfAbsent(q, mappingFunction, q.defaultReturnValue()); return q.defaultReturnValue().returnValue(); } } @Override public V computeIfPresent(K key, BiFunction remappingFunction) { try (QueryContextInterface q = queryContext(key)) { methods.computeIfPresent(q, remappingFunction, q.defaultReturnValue()); return q.defaultReturnValue().returnValue(); } } }