// Copyright 2017 JanusGraph Authors
//
// 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 org.janusgraph.graphdb.database.serialize;
import com.google.common.base.Preconditions;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import org.janusgraph.core.*;
import org.janusgraph.core.attribute.*;
import org.janusgraph.core.schema.*;
import org.janusgraph.diskstorage.ScanBuffer;
import org.janusgraph.diskstorage.StaticBuffer;
import org.janusgraph.diskstorage.WriteBuffer;
import org.janusgraph.diskstorage.idmanagement.ConflictAvoidanceMode;
import org.janusgraph.diskstorage.util.WriteByteBuffer;
import org.janusgraph.diskstorage.util.time.TimestampProviders;
import org.janusgraph.graphdb.database.idhandling.VariableLong;
import org.janusgraph.graphdb.database.log.LogTxStatus;
import org.janusgraph.graphdb.database.management.MgmtLogType;
import org.janusgraph.graphdb.database.serialize.attribute.*;
import org.janusgraph.graphdb.internal.ElementCategory;
import org.janusgraph.graphdb.internal.Order;
import org.janusgraph.graphdb.internal.RelationCategory;
import org.janusgraph.graphdb.internal.JanusGraphSchemaCategory;
import org.janusgraph.graphdb.log.StandardTransactionId;
import org.janusgraph.graphdb.types.ParameterType;
import org.janusgraph.core.schema.SchemaStatus;
import org.janusgraph.graphdb.types.TypeDefinitionCategory;
import org.janusgraph.graphdb.types.TypeDefinitionDescription;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.TraverserSet;
import org.apache.tinkerpop.gremlin.structure.Direction;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @author Matthias Broecheler (me@matthiasb.com)
*/
public class StandardSerializer implements AttributeHandler, Serializer {
/**
* This offset is used by user registration to make sure they don't collide with internal class
* registrations. It must be ensured that this number is LARGER than any internally used
* registration number.
* This value may NEVER EVER be changed or compatibility to older versions breaks
*/
private static final int CLASS_REGISTRATION_OFFSET = 100;
private static final int MAX_REGISTRATION_NO = 100000;
private final BiMap<Integer,Class> registrations;
private final Map<Class,AttributeSerializer> handlers;
public StandardSerializer() {
handlers = new HashMap<>(60);
registrations = HashBiMap.create(60);
//Setup
registerClassInternal(1, Object.class, new ObjectSerializer());
//Primitive data types
registerClassInternal(10, Byte.class, new ByteSerializer());
registerClassInternal(11,Short.class, new ShortSerializer());
registerClassInternal(12,Integer.class, new IntegerSerializer());
registerClassInternal(13,Long.class, new LongSerializer());
registerClassInternal(14,Character.class, new CharacterSerializer());
registerClassInternal(15,Boolean.class, new BooleanSerializer());
registerClassInternal(16,Date.class, new DateSerializer());
registerClassInternal(17,Geoshape.class, new Geoshape.GeoshapeSerializer());
registerClassInternal(18,String.class, new StringSerializer()); //supports null serialization
registerClassInternal(19,Float.class, new FloatSerializer());
registerClassInternal(20,Double.class, new DoubleSerializer());
registerClassInternal(21,UUID.class, new UUIDSerializer());
//Arrays (support null serialization)
registerClassInternal(22,byte[].class, new ByteArraySerializer());
registerClassInternal(23,short[].class, new ShortArraySerializer());
registerClassInternal(24,int[].class, new IntArraySerializer());
registerClassInternal(25,long[].class, new LongArraySerializer());
registerClassInternal(26,float[].class, new FloatArraySerializer());
registerClassInternal(27,double[].class, new DoubleArraySerializer());
registerClassInternal(28,char[].class, new CharArraySerializer());
registerClassInternal(29,boolean[].class, new BooleanArraySerializer());
registerClassInternal(30, String[].class, new StringArraySerializer());
//Needed by JanusGraph
registerClassInternal(41,TypeDefinitionCategory.class, new EnumSerializer<>(TypeDefinitionCategory.class));
registerClassInternal(42,JanusGraphSchemaCategory.class, new EnumSerializer<>(JanusGraphSchemaCategory.class));
registerClassInternal(43,ParameterType.class, new EnumSerializer<>(ParameterType.class));
registerClassInternal(44,RelationCategory.class, new EnumSerializer<>(RelationCategory.class));
registerClassInternal(45,Order.class, new EnumSerializer<>(Order.class));
registerClassInternal(46,Multiplicity.class, new EnumSerializer<>(Multiplicity.class));
registerClassInternal(47,Cardinality.class, new EnumSerializer<>(Cardinality.class));
registerClassInternal(48,Direction.class, new EnumSerializer<>(Direction.class));
registerClassInternal(49,ElementCategory.class, new EnumSerializer<>(ElementCategory.class));
registerClassInternal(50,ConsistencyModifier.class, new EnumSerializer<>(ConsistencyModifier.class));
registerClassInternal(51,SchemaStatus.class, new EnumSerializer<>(SchemaStatus.class));
registerClassInternal(52,LogTxStatus.class, new EnumSerializer<>(LogTxStatus.class));
registerClassInternal(53,MgmtLogType.class, new EnumSerializer<>(MgmtLogType.class));
registerClassInternal(54,TimestampProviders.class, new EnumSerializer<>(TimestampProviders.class));
registerClassInternal(55,TimeUnit.class, new EnumSerializer<>(TimeUnit.class));
registerClassInternal(56,Mapping.class, new EnumSerializer<>(Mapping.class));
registerClassInternal(57,ConflictAvoidanceMode.class, new EnumSerializer<>(ConflictAvoidanceMode.class));
registerClassInternal(60,Class.class, new ClassSerializer());
registerClassInternal(61,Parameter.class, new ParameterSerializer());
registerClassInternal(62,Parameter[].class, new ParameterArraySerializer());
registerClassInternal(63,TypeDefinitionDescription.class, new TypeDefinitionDescriptionSerializer());
//Needed for configuration and transaction logging
registerClassInternal(64,Duration.class, new DurationSerializer());
registerClassInternal(65,Instant.class, new InstantSerializer());
registerClassInternal(66,StandardTransactionId.class, new StandardTransactionIdSerializer());
registerClassInternal(67,TraverserSet.class, new SerializableSerializer());
registerClassInternal(68,HashMap.class, new SerializableSerializer());
}
@Override
public synchronized <V> void registerClass(int registrationNo, Class<V> datatype, AttributeSerializer<V> serializer) {
Preconditions.checkArgument(registrationNo >= 0 && registrationNo < MAX_REGISTRATION_NO, "Registration number" +
" out of range [0,%s]: %s", MAX_REGISTRATION_NO, registrationNo);
registerClassInternal(CLASS_REGISTRATION_OFFSET + registrationNo, datatype, serializer);
}
public synchronized <V> void registerClassInternal(int registrationNo, Class<? extends V> datatype, AttributeSerializer<V> serializer) {
Preconditions.checkArgument(registrationNo>0); //must be bigger than 0 since 0 is used to indicate null values
Preconditions.checkNotNull(datatype);
Preconditions.checkArgument(!handlers.containsKey(datatype), "DataType has already been registered: %s", datatype);
Preconditions.checkArgument(!registrations.containsKey(registrationNo), "A datatype has already been registered for no: %s",registrationNo);
Preconditions.checkNotNull(serializer,"Need to provide a serializer for datatype: %s",datatype);
registrations.put(registrationNo, datatype);
if (serializer instanceof SerializerInjected) ((SerializerInjected)serializer).setSerializer(this);
handlers.put(datatype, serializer);
}
private static Class normalizeDataType(Class datatype) {
Class superClass = datatype.getSuperclass();
if (null != superClass && superClass.isEnum()) return superClass;
if (Instant.class.equals(datatype)) return Instant.class;
return datatype;
}
@Override
public boolean validDataType(Class datatype) {
return handlers.containsKey(normalizeDataType(datatype));
}
private<T> AttributeSerializer<T> getSerializer(Class<T> datatype) {
AttributeSerializer<T> serializer = handlers.get(normalizeDataType(datatype));
Preconditions.checkArgument(serializer!=null,"Datatype is not supported by database since no serializer has been registered: %s",datatype);
return serializer;
}
private int getDataTypeRegistration(Class datatype) {
Integer registrationNo = registrations.inverse().get(normalizeDataType(datatype));
Preconditions.checkArgument(registrationNo!=null,"Datatype is not supported by database since no serializer has been registered: %s",datatype);
assert registrationNo>0;
return registrationNo;
}
private Class getDataType(int registrationNo) {
Class clazz = registrations.get(registrationNo);
Preconditions.checkArgument(clazz!=null,"Encountered missing datatype registration for number: %s",registrationNo);
return clazz;
}
@Override
public <V> void verifyAttribute(Class<V> datatype, Object value) {
Preconditions.checkNotNull(datatype);
Preconditions.checkNotNull(value);
AttributeSerializer handler = getSerializer(datatype);
if (handler!=null) handler.verifyAttribute(value);
}
@Override
public <V> V convert(Class<V> datatype, Object value) {
Preconditions.checkNotNull(datatype);
Preconditions.checkNotNull(value);
AttributeSerializer handler = getSerializer(datatype);
if (handler!=null) return (V)handler.convert(value);
else return null;
}
@Override
public boolean isOrderPreservingDatatype(Class<?> datatype) {
return (getSerializer(datatype) instanceof OrderPreservingSerializer);
}
private static<V> OrderPreservingSerializer<V> ensureOrderPreserving(AttributeSerializer<V> serializer, Class<V> type) {
Preconditions.checkArgument(serializer instanceof OrderPreservingSerializer,"Registered serializer for datatype does not support order: %s",type);
return (OrderPreservingSerializer)serializer;
}
private boolean supportsNullSerialization(Class type) {
return getSerializer(type) instanceof SupportsNullSerializer;
}
@Override
public <T> T readObjectByteOrder(ScanBuffer buffer, Class<T> type) {
return readObjectInternal(buffer,type,true);
}
@Override
public <T> T readObject(ScanBuffer buffer, Class<T> type) {
return readObjectInternal(buffer,type,false);
}
@Override
public <T> T readObjectNotNull(ScanBuffer buffer, Class<T> type) {
return readObjectNotNullInternal(buffer,type,false);
}
private <T> T readObjectInternal(ScanBuffer buffer, Class<T> type, boolean byteOrder) {
if (supportsNullSerialization(type)) {
AttributeSerializer<T> s = getSerializer(type);
if (byteOrder) return ensureOrderPreserving(s,type).readByteOrder(buffer);
else return s.read(buffer);
} else {
//Read flag for null or not
byte flag = buffer.getByte();
if (flag==-1) {
return null;
} else {
Preconditions.checkArgument(flag==0,"Invalid flag encountered in serialization: %s. Corrupted data.",flag);
return readObjectNotNullInternal(buffer,type,byteOrder);
}
}
}
private <T> T readObjectNotNullInternal(ScanBuffer buffer, Class<T> type, boolean byteOrder) {
AttributeSerializer<T> s = getSerializer(type);
if (byteOrder) {
return ensureOrderPreserving(s,type).readByteOrder(buffer);
} else {
return s.read(buffer);
}
}
@Override
public Object readClassAndObject(ScanBuffer buffer) {
long registrationNo = VariableLong.readPositive(buffer);
if (registrationNo==0) return null;
Class datatype = getDataType((int)registrationNo);
return readObjectNotNullInternal(buffer, datatype, false);
}
@Override
public DataOutput getDataOutput(int initialCapacity) {
return new StandardDataOutput(initialCapacity);
}
@Override
public void close() throws IOException {
//Nothing to close
}
private class StandardDataOutput extends WriteByteBuffer implements DataOutput {
private StandardDataOutput(int initialCapacity) {
super(initialCapacity);
}
@Override
public DataOutput writeObjectByteOrder(Object object, Class type) {
Preconditions.checkArgument(StandardSerializer.this.isOrderPreservingDatatype(type),"Invalid serializer for class: %s",type);
return writeObjectInternal(object,type,true);
}
@Override
public DataOutput writeObject(Object object, Class type) {
return writeObjectInternal(object,type,false);
}
@Override
public DataOutput writeObjectNotNull(Object object) {
return writeObjectNotNullInternal(object,false);
}
private DataOutput writeObjectInternal(Object object, Class type, boolean byteOrder) {
if (supportsNullSerialization(type)) {
AttributeSerializer s = getSerializer(type);
if (byteOrder) ensureOrderPreserving(s,type).writeByteOrder(this,object);
else s.write(this, object);
} else {
//write flag for null or not
if (object==null) {
putByte((byte)-1);
} else {
putByte((byte)0);
writeObjectNotNullInternal(object,byteOrder);
}
}
return this;
}
private DataOutput writeObjectNotNullInternal(Object object, boolean byteOrder) {
Preconditions.checkNotNull(object);
Class type = object.getClass();
AttributeSerializer s = getSerializer(type);
if (byteOrder) {
ensureOrderPreserving(s,type).writeByteOrder(this,object);
} else {
s.write(this, object);
}
return this;
}
@Override
public DataOutput writeClassAndObject(Object object) {
if (object==null) VariableLong.writePositive(this,0);
else {
Class type = object.getClass();
VariableLong.writePositive(this,getDataTypeRegistration(type));
writeObjectNotNullInternal(object,false);
}
return this;
}
@Override
public DataOutput putLong(long val) {
super.putLong(val);
return this;
}
@Override
public DataOutput putInt(int val) {
super.putInt(val);
return this;
}
@Override
public DataOutput putShort(short val) {
super.putShort(val);
return this;
}
@Override
public WriteBuffer putBoolean(boolean val) {
super.putBoolean(val);
return this;
}
@Override
public DataOutput putByte(byte val) {
super.putByte(val);
return this;
}
@Override
public DataOutput putBytes(byte[] val) {
super.putBytes(val);
return this;
}
@Override
public DataOutput putBytes(final StaticBuffer val) {
super.putBytes(val);
return this;
}
@Override
public DataOutput putChar(char val) {
super.putChar(val);
return this;
}
@Override
public DataOutput putFloat(float val) {
super.putFloat(val);
return this;
}
@Override
public DataOutput putDouble(double val) {
super.putDouble(val);
return this;
}
}
private class ClassSerializer implements OrderPreservingSerializer<Class>, SupportsNullSerializer {
private final IntegerSerializer ints = new IntegerSerializer();
@Override
public Class readByteOrder(ScanBuffer buffer) {
return getClass(ints.readByteOrder(buffer));
}
@Override
public void writeByteOrder(WriteBuffer buffer, Class attribute) {
ints.writeByteOrder(buffer, attribute == null ? 0 : getDataTypeRegistration(attribute));
}
@Override
public Class read(ScanBuffer buffer) {
return getClass(VariableLong.readPositive(buffer));
}
private final Class getClass(long registrationNo) {
assert registrationNo<Integer.MAX_VALUE && registrationNo>=0;
if (registrationNo==0) return null;
return getDataType((int) registrationNo);
}
@Override
public void write(WriteBuffer buffer, Class attribute) {
VariableLong.writePositive(buffer,attribute==null?0:getDataTypeRegistration(attribute));
}
@Override
public void verifyAttribute(Class value) {
//Accept all values
}
@Override
public Class convert(Object value) {
if (value instanceof Class) return (Class)value;
else return null;
}
}
}