/*
* Copyright 2013 NGDATA nv
*
* 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.lilyproject.repository.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.joda.time.DateTime;
import org.joda.time.DateTimeUtils;
import org.joda.time.DateTimeZone;
import org.lilyproject.bytes.api.ByteArray;
import org.lilyproject.bytes.api.DataInput;
import org.lilyproject.bytes.api.DataOutput;
import org.lilyproject.repository.api.Metadata;
import org.lilyproject.repository.api.MetadataBuilder;
/**
* Serialize and deserialize a {@link Metadata} object. The various value types supported by Metadata use a
* type-specific serialization, so that type information is retained and storage size optimal.
*/
public class MetadataSerDeser {
private MetadataSerDeser() {
}
/**
* Writes the metadata to the given output. It can be read back from a variable-length input using
* {@link #read(DataInput)}.
*/
public static void write(Metadata metadata, DataOutput output) {
// Write the fields
Map<String, Object> map = metadata.getMap();
output.writeVInt(map.size());
for (Map.Entry<String, Object> entry : map.entrySet()) {
// write the key
output.writeVUTF(entry.getKey());
// write the value
Object value = entry.getValue();
ValueSerDeser serdeser = CLASS_TO_SERDESER.get(value.getClass());
if (serdeser == null) {
throw new IllegalArgumentException("Unsupported kind of metadata value: type of '" + value + "' is "
+ value.getClass().getName());
}
output.writeByte(serdeser.getTypeByte());
serdeser.serialize(value, output);
}
// Write the deleted fields
Set<String> fieldsToDelete = metadata.getFieldsToDelete();
output.writeVInt(fieldsToDelete.size());
for (String field : fieldsToDelete) {
output.writeVUTF(field);
}
}
public static Metadata read(DataInput input) {
MetadataBuilder metadataBuilder = new MetadataBuilder();
// read fields
int size = input.readVInt();
for (int i = 0; i < size; i++) {
String key = input.readVUTF();
byte valueType = input.readByte();
ValueSerDeser serdeser = BYTE_TO_SERDESER.get(valueType);
if (serdeser == null) {
throw new IllegalArgumentException("Unsupported kind of metadata value: type byte " + (int)valueType);
}
Object value = serdeser.deserialize(input);
metadataBuilder.object(key, value);
}
// read deleted fields
size = input.readVInt();
for (int i = 0; i < size; i++) {
metadataBuilder.delete(input.readVUTF());
}
return metadataBuilder.build();
}
private static interface ValueSerDeser {
Class getTypeClass();
byte getTypeByte();
void serialize(Object value, DataOutput dataOutput);
Object deserialize(DataInput dataInput);
}
private static final ValueSerDeser STRING_SERDESER = new StringValueSerDeser();
private static class StringValueSerDeser implements ValueSerDeser {
@Override
public Class getTypeClass() {
return String.class;
}
@Override
public byte getTypeByte() {
return 1;
}
@Override
public void serialize(Object value, DataOutput dataOutput) {
dataOutput.writeVUTF((String)value);
}
@Override
public Object deserialize(DataInput dataInput) {
return dataInput.readVUTF();
}
}
private static final ValueSerDeser INT_SERDESER = new IntegerValueSerDeser();
private static class IntegerValueSerDeser implements ValueSerDeser {
@Override
public Class getTypeClass() {
return Integer.class;
}
@Override
public byte getTypeByte() {
return 2;
}
@Override
public void serialize(Object value, DataOutput dataOutput) {
dataOutput.writeVInt((Integer) value);
}
@Override
public Object deserialize(DataInput dataInput) {
return dataInput.readVInt();
}
}
private static final ValueSerDeser LONG_SERDESER = new LongValueSerDeser();
private static class LongValueSerDeser implements ValueSerDeser {
@Override
public Class getTypeClass() {
return Long.class;
}
@Override
public byte getTypeByte() {
return 3;
}
@Override
public void serialize(Object value, DataOutput dataOutput) {
dataOutput.writeVLong((Long) value);
}
@Override
public Object deserialize(DataInput dataInput) {
return dataInput.readVLong();
}
}
private static final ValueSerDeser FLOAT_SERDESER = new FloatValueSerDeser();
private static class FloatValueSerDeser implements ValueSerDeser {
@Override
public Class getTypeClass() {
return Float.class;
}
@Override
public byte getTypeByte() {
return 4;
}
@Override
public void serialize(Object value, DataOutput dataOutput) {
dataOutput.writeFloat((Float) value);
}
@Override
public Object deserialize(DataInput dataInput) {
return dataInput.readFloat();
}
}
private static final ValueSerDeser DOUBLE_SERDESER = new DoubleValueSerDeser();
private static class DoubleValueSerDeser implements ValueSerDeser {
@Override
public Class getTypeClass() {
return Double.class;
}
@Override
public byte getTypeByte() {
return 5;
}
@Override
public void serialize(Object value, DataOutput dataOutput) {
dataOutput.writeDouble((Double) value);
}
@Override
public Object deserialize(DataInput dataInput) {
return dataInput.readDouble();
}
}
private static final ValueSerDeser BOOLEAN_SERDESER = new BooleanValueSerDeser();
private static class BooleanValueSerDeser implements ValueSerDeser {
@Override
public Class getTypeClass() {
return Boolean.class;
}
@Override
public byte getTypeByte() {
return 6;
}
@Override
public void serialize(Object value, DataOutput dataOutput) {
dataOutput.writeBoolean((Boolean) value);
}
@Override
public Object deserialize(DataInput dataInput) {
return dataInput.readBoolean();
}
}
private static final ValueSerDeser BYTES_SERDESER = new BytesValueSerDeser();
private static class BytesValueSerDeser implements ValueSerDeser {
@Override
public Class getTypeClass() {
return ByteArray.class;
}
@Override
public byte getTypeByte() {
return 7;
}
@Override
public void serialize(Object value, DataOutput dataOutput) {
dataOutput.writeVInt(((ByteArray)value).length());
dataOutput.writeBytes(((ByteArray) value).getBytesUnsafe());
}
@Override
public Object deserialize(DataInput dataInput) {
int size = dataInput.readVInt();
return new ByteArray(dataInput.readBytes(size));
}
}
private static final ValueSerDeser DATETIME_SERDESER = new DateTimeValueSerDeser();
private static class DateTimeValueSerDeser implements ValueSerDeser {
@Override
public Class getTypeClass() {
return org.joda.time.DateTime.class;
}
@Override
public byte getTypeByte() {
return 8;
}
@Override
public void serialize(Object value, DataOutput dataOutput) {
DateTime dateTime = (DateTime) value;
dateTime = dateTime.toDateTime(DateTimeZone.UTC); //store as UTC
dataOutput.writeLong(dateTime.getMillis());
}
@Override
public Object deserialize(DataInput dataInput) {
// Always construct UTC such that it is not depending on the default timezone of the current host
return new DateTime(dataInput.readLong(), DateTimeZone.UTC);
}
}
private static final ValueSerDeser[] serdesers = new ValueSerDeser[] { STRING_SERDESER, INT_SERDESER, LONG_SERDESER,
FLOAT_SERDESER, DOUBLE_SERDESER, BOOLEAN_SERDESER, BYTES_SERDESER, DATETIME_SERDESER };
private static final Map<Class, ValueSerDeser> CLASS_TO_SERDESER = new HashMap<Class, ValueSerDeser>();
static {
for (ValueSerDeser serdeser : serdesers) {
CLASS_TO_SERDESER.put(serdeser.getTypeClass(), serdeser);
}
// This is just to protect against double usage of the same type class
if (CLASS_TO_SERDESER.size() != serdesers.length) {
throw new RuntimeException("Incorrect number of serdesers.");
}
}
private static final Map<Byte, ValueSerDeser> BYTE_TO_SERDESER = new HashMap<Byte, ValueSerDeser>();
static {
for (ValueSerDeser serdeser : serdesers) {
BYTE_TO_SERDESER.put(serdeser.getTypeByte(), serdeser);
}
// This is just to protect against double usage of the same type byte
if (BYTE_TO_SERDESER.size() != serdesers.length) {
throw new RuntimeException("Incorrect number of serdesers.");
}
}
}