/*
* Copyright (C) 2012, 2016 higherfrequencytrading.com
* Copyright (C) 2016 Roman Leventov
*
* 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 <http://www.gnu.org/licenses/>.
*/
package net.openhft.chronicle.hash.serialization.impl;
import net.openhft.chronicle.bytes.Byteable;
import net.openhft.chronicle.bytes.BytesMarshallable;
import net.openhft.chronicle.core.OS;
import net.openhft.chronicle.core.util.ReadResolvable;
import net.openhft.chronicle.hash.serialization.*;
import net.openhft.chronicle.values.ValueModel;
import net.openhft.chronicle.values.Values;
import net.openhft.chronicle.wire.Marshallable;
import java.io.Externalizable;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import static net.openhft.chronicle.hash.serialization.SizeMarshaller.constant;
import static net.openhft.chronicle.hash.serialization.SizeMarshaller.stopBit;
public final class SerializationBuilder<T> implements Cloneable {
public final Class<T> tClass;
public final boolean sizeIsStaticallyKnown;
private SizeMarshaller sizeMarshaller = stopBit();
private SizedReader<T> reader;
private DataAccess<T> dataAccess;
@SuppressWarnings("unchecked")
public SerializationBuilder(Class<T> tClass) {
this.tClass = tClass;
configureByDefault(tClass);
sizeIsStaticallyKnown = constantSizeMarshaller();
}
private static boolean concreteClass(Class c) {
return !c.isInterface() && !Modifier.isAbstract(c.getModifiers());
}
private static void checkNonMarshallableEnum(Class c) {
if (Enum.class.isAssignableFrom(c) && (Marshallable.class.isAssignableFrom(c) ||
ReadResolvable.class.isAssignableFrom(c))) {
throw new IllegalArgumentException(c + ": since Chronicle Map 3.9.0, enum marshaller " +
"shouldn't be a Java enum and implement " + Marshallable.class.getName() +
" or " + ReadResolvable.class.getName() + ". There are problems with " +
"serializing/deserializing them in Chronicle Map header. Emulate enums by " +
"static final fields");
}
}
@SuppressWarnings("unchecked")
private void configureByDefault(Class<T> tClass) {
if (tClass.isPrimitive()) {
throw new IllegalArgumentException(
"Chronicle Map's key or value type cannot be primitive, " + tClass +
" type is given");
}
if (tClass.isInterface() && Values.isValueInterfaceOrImplClass(tClass)) {
try {
// Acquire a model before assigning readers/writers
// if the interface is not a value interface
ValueModel valueModel = ValueModel.acquire(tClass);
reader((BytesReader<T>) new ValueReader<>(tClass));
dataAccess(new ValueDataAccess<>(tClass));
sizeMarshaller(constant((long) valueModel.sizeInBytes()));
return;
} catch (Exception e) {
try {
tClass = Values.nativeClassFor(tClass);
} catch (Exception ex) {
// ignore, fall through
}
// ignore, fall through
}
}
if (concreteClass(tClass) && Byteable.class.isAssignableFrom(tClass)) {
reader(new ByteableSizedReader<>((Class) tClass));
dataAccess((DataAccess<T>) new ByteableDataAccess<>((Class) tClass));
try {
long byteableSize = ((Byteable) OS.memory().allocateInstance(tClass)).maxSize();
sizeMarshaller(constant(byteableSize));
} catch (InstantiationException e) {
throw new IllegalStateException(e);
}
} else if (tClass == CharSequence.class) {
reader((SizedReader<T>) CharSequenceSizedReader.INSTANCE);
dataAccess((DataAccess<T>) new CharSequenceUtf8DataAccess());
} else if (tClass == StringBuilder.class) {
reader((SizedReader<T>) StringBuilderSizedReader.INSTANCE);
dataAccess((DataAccess<T>) new StringBuilderUtf8DataAccess());
} else if (tClass == String.class) {
reader((SizedReader<T>) new StringSizedReader());
dataAccess((DataAccess<T>) new StringUtf8DataAccess());
} else if (tClass == Boolean.class) {
reader((SizedReader<T>) BooleanMarshaller.INSTANCE);
notReusingWriter((SizedWriter<T>) BooleanMarshaller.INSTANCE);
sizeMarshaller(constant(1));
} else if (tClass == Long.class) {
reader((SizedReader<T>) LongMarshaller.INSTANCE);
dataAccess((DataAccess<T>) new LongDataAccess());
sizeMarshaller(constant(8));
} else if (tClass == Double.class) {
reader((SizedReader<T>) DoubleMarshaller.INSTANCE);
dataAccess((DataAccess<T>) new DoubleDataAccess());
sizeMarshaller(constant(8));
} else if (tClass == Integer.class) {
reader((SizedReader<T>) IntegerMarshaller.INSTANCE);
dataAccess((DataAccess<T>) new IntegerDataAccess_3_13());
sizeMarshaller(constant(4));
} else if (tClass == byte[].class) {
reader((SizedReader<T>) ByteArraySizedReader.INSTANCE);
dataAccess((DataAccess<T>) new ByteArrayDataAccess());
} else if (tClass == ByteBuffer.class) {
reader((SizedReader<T>) ByteBufferSizedReader.INSTANCE);
dataAccess((DataAccess<T>) new ByteBufferDataAccess());
} else if (concreteClass(tClass) && BytesMarshallable.class.isAssignableFrom(tClass)) {
reader((BytesReader<T>) new BytesMarshallableReader<>((Class) tClass));
dataAccess(new BytesMarshallableDataAccess<>((Class) tClass));
} else if (concreteClass(tClass) && Externalizable.class.isAssignableFrom(tClass)) {
reader((BytesReader<T>) new ExternalizableReader<>((Class) tClass));
dataAccess(new ExternalizableDataAccess<>((Class) tClass));
} else {
reader((SizedReader<T>) new SerializableReader<>());
dataAccess((DataAccess<T>) new SerializableDataAccess<>());
}
}
public void reader(SizedReader<T> reader) {
checkNonMarshallableEnum(reader.getClass());
this.reader = reader;
}
public void reader(BytesReader<T> reader) {
checkNonMarshallableEnum(reader.getClass());
this.reader = new BytesAsSizedReader<>(reader);
}
public SizedReader<T> reader() {
return StatefulCopyable.copyIfNeeded(reader);
}
public void dataAccess(DataAccess<T> dataAccess) {
checkNonMarshallableEnum(dataAccess.getClass());
this.dataAccess = dataAccess;
}
public DataAccess<T> dataAccess() {
return dataAccess.copy();
}
public void writer(SizedWriter<? super T> writer) {
checkNonMarshallableEnum(writer.getClass());
dataAccess(new SizedMarshallableDataAccess<>(tClass, reader, writer));
}
private void notReusingWriter(SizedWriter<? super T> writer) {
checkNonMarshallableEnum(writer.getClass());
dataAccess(new NotReusingSizedMarshallableDataAccess<>(tClass, reader, writer));
}
public void writer(BytesWriter<? super T> writer) {
checkNonMarshallableEnum(writer.getClass());
dataAccess(new ExternalBytesMarshallableDataAccess<>(tClass, reader, writer));
}
public long serializationSize(T sampleObject) {
return dataAccess().getData(sampleObject).size();
}
public long constantSizeBySample(T sampleObject) {
long constantSize = serializationSize(sampleObject);
if (constantSizeMarshaller()) {
long expectedConstantSize = constantSize();
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 " + tClass + " we know constant size is " +
expectedConstantSize + " statically, configured sample is " + sampleObject +
" which size in serialized form is " + constantSize);
}
}
sizeMarshaller(constant(constantSize));
return constantSize;
}
public SizeMarshaller sizeMarshaller() {
return sizeMarshaller;
}
public boolean constantSizeMarshaller() {
SizeMarshaller marshaller = sizeMarshaller();
boolean feature1 = marshaller.storingLength(marshaller.maxStorableSize()) == 0;
boolean feature2 = marshaller.minStorableSize() == marshaller.maxStorableSize();
if (feature1 && feature2)
return true;
if (!feature1 && !feature2)
return false;
throw new IllegalStateException("SizeMarshaller " + marshaller + " has only 1 of 2 " +
"constant features: storingLength == 0 and minStorableSize == maxStorableSize." +
" Should have both");
}
public boolean constantStoringLengthSizeMarshaller() {
SizeMarshaller marshaller = sizeMarshaller();
long minStorableSize = marshaller.minStorableSize();
long maxStorableSize = marshaller.maxStorableSize();
return marshaller.minStoringLengthOfSizesInRange(minStorableSize, maxStorableSize) ==
marshaller.maxStoringLengthOfSizesInRange(minStorableSize, maxStorableSize);
}
public long constantSize() {
if (sizeMarshaller().minStorableSize() != sizeMarshaller().maxStorableSize())
throw new AssertionError();
return sizeMarshaller().minStorableSize();
}
public SerializationBuilder<T> sizeMarshaller(SizeMarshaller sizeMarshaller) {
checkNonMarshallableEnum(sizeMarshaller.getClass());
this.sizeMarshaller = sizeMarshaller;
return this;
}
@Override
public SerializationBuilder<T> clone() {
try {
//noinspection unchecked
return (SerializationBuilder<T>) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
}