package org.corfudb.protocols.wireprotocol;
import com.google.common.collect.*;
import com.google.common.reflect.TypeToken;
import io.netty.buffer.ByteBuf;
import org.corfudb.runtime.view.Layout;
import org.corfudb.util.JSONUtils;
import java.lang.invoke.*;
import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by mwei on 8/1/16.
*/
public interface ICorfuPayload<T> {
@FunctionalInterface
interface PayloadConstructor<T> {
T construct(ByteBuf buf);
}
ConcurrentHashMap<Class<?>, PayloadConstructor<?>>
constructorMap = new ConcurrentHashMap<>
(ImmutableMap.<Class<?>, PayloadConstructor<?>>builder()
.put(Byte.class, ByteBuf::readByte)
.put(Integer.class, ByteBuf::readInt)
.put(Long.class, ByteBuf::readLong)
.put(Boolean.class, ByteBuf::readBoolean)
.put(Double.class, ByteBuf::readDouble)
.put(Float.class, ByteBuf::readFloat)
.put(String.class, x -> {
int numBytes = x.readInt();
byte[] bytes = new byte[numBytes];
x.readBytes(bytes);
return new String(bytes);
})
.put(Layout.class, x -> {
int length = x.readInt();
byte[] byteArray = new byte[length];
x.readBytes(byteArray, 0, length);
String str = new String(byteArray, StandardCharsets.UTF_8);
Layout layout = JSONUtils.parser.fromJson(str, Layout.class);
return layout;
})
.put(IMetadata.DataRank.class, x ->
new IMetadata.DataRank(x.readLong(), new UUID(x.readLong(), x.readLong())))
.put(UUID.class, x -> new UUID(x.readLong(), x.readLong()))
.put(byte[].class, x -> {
int length = x.readInt();
byte[] bytes = new byte[length];
x.readBytes(bytes);
return bytes;
})
.put(ByteBuf.class, x -> {
int bytes = x.readInt();
ByteBuf b = x.retainedSlice(x.readerIndex(), bytes);
x.readerIndex(x.readerIndex() + bytes);
return b;
})
.build());
/** A lookup representing the context we'll use to do lookups. */
java.lang.invoke.MethodHandles.Lookup lookup = MethodHandles.lookup();
@SuppressWarnings("unchecked")
static <T> T fromBuffer(ByteBuf buf, Class<T> cls) {
if (constructorMap.containsKey(cls)) {
return (T) constructorMap.get(cls).construct(buf);
}
else {
if (cls.isEnum()) {
// we only know how to deal with enums with a typemap...
try {
Map<Byte, T> enumMap = (Map<Byte, T>) cls.getDeclaredField("typeMap").get(null);
constructorMap.put(cls, x -> enumMap.get(x.readByte()));
return (T) constructorMap.get(cls).construct(buf);
} catch (NoSuchFieldException e) {
throw new RuntimeException("only enums with a typeMap are supported!");
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
if (ICorfuPayload.class.isAssignableFrom(cls)) {
// Grab the constructor and get convert it to a lambda.
try {
Constructor t = cls.getConstructor(ByteBuf.class);
MethodHandle mh = lookup.unreflectConstructor(t);
MethodType mt = MethodType.methodType(Object.class, ByteBuf.class);
try {
constructorMap.put(cls, (PayloadConstructor<T>) LambdaMetafactory.metafactory(lookup,
"construct", MethodType.methodType(PayloadConstructor.class),
mt, mh, mh.type())
.getTarget().invokeExact());
return (T) constructorMap.get(cls).construct(buf);
} catch (Throwable th) {
throw new RuntimeException(th);
}
} catch (NoSuchMethodException nsme) {
throw new RuntimeException("CorfuPayloads must include a ByteBuf constructor!");
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
throw new RuntimeException("Unknown class " + cls + " for deserialization");
}
/** A really simple flat map implementation. The first entry is the size of the map as an int,
* and the next entries are each key followed by its value. Maps of maps are currently not supported...
* @param buf The buffer to deserialize.
* @param keyClass The class of the keys.
* @param valueClass The class of the values.
* @param <K> The type of the keys.
* @param <V> The type of the values.
* @return
*/
static <K,V> Map<K,V> mapFromBuffer(ByteBuf buf, Class<K> keyClass, Class<V> valueClass) {
int numEntries = buf.readInt();
ImmutableMap.Builder<K,V> builder = ImmutableMap.builder();
for (int i = 0; i < numEntries; i++) {
builder.put(fromBuffer(buf, keyClass), fromBuffer(buf, valueClass));
}
return builder.build();
}
/** A really simple flat set implementation. The first entry is the size of the set as an int,
* and the next entries are each value..
* @param buf The buffer to deserialize.
* @param valueClass The class of the values.
* @param <V> The type of the values.
* @return
*/
static <V> Set<V> setFromBuffer(ByteBuf buf, Class<V> valueClass) {
int numEntries = buf.readInt();
ImmutableSet.Builder<V> builder = ImmutableSet.builder();
for (int i = 0; i < numEntries; i++) {
builder.add(fromBuffer(buf, valueClass));
}
return builder.build();
}
/** A really simple flat list implementation. The first entry is the size of the set as an int,
* and the next entries are each value..
* @param <V> The type of the values.
* @param buf The buffer to deserialize.
* @param valueClass The class of the values.
* @return
*/
static <V> List<V> listFromBuffer(ByteBuf buf, Class<V> valueClass) {
int numEntries = buf.readInt();
ImmutableList.Builder<V> builder = ImmutableList.builder();
for (int i = 0; i < numEntries; i++) {
builder.add(fromBuffer(buf, valueClass));
}
return builder.build();
}
/** A really simple flat set implementation. The first entry is the size of the set as an int,
* and the next entries are each value..
* @param buf The buffer to deserialize.
* @param valueClass The class of the values.
* @param <V> The type of the values.
* @return
*/
static <V extends Comparable<V>> RangeSet<V> rangeSetFromBuffer(ByteBuf buf, Class<V> valueClass) {
int numEntries = buf.readInt();
ImmutableRangeSet.Builder<V> rs = ImmutableRangeSet.builder();
for (int i = 0; i < numEntries; i++) {
BoundType upperType = buf.readBoolean() ? BoundType.CLOSED : BoundType.OPEN;
V upper = fromBuffer(buf, valueClass);
BoundType lowerType = buf.readBoolean() ? BoundType.CLOSED : BoundType.OPEN;
V lower = fromBuffer(buf, valueClass);
rs.add(Range.range(lower, lowerType, upper, upperType));
}
return rs.build();
}
/** A really simple flat set implementation. The first entry is the size of the set as an int,
* and the next entries are each value..
* @param buf The buffer to deserialize.
* @param valueClass The class of the values.
* @param <V> The type of the values.
* @return
*/
static <V extends Comparable<V>> Range<V> rangeFromBuffer(ByteBuf buf, Class<V> valueClass) {
BoundType upperType = buf.readBoolean() ? BoundType.CLOSED : BoundType.OPEN;
V upper = fromBuffer(buf, valueClass);
BoundType lowerType = buf.readBoolean() ? BoundType.CLOSED : BoundType.OPEN;
V lower = fromBuffer(buf, valueClass);
return Range.range(lower, lowerType, upper, upperType);
}
static <K extends Enum<K> & ITypedEnum<K>,V> EnumMap<K,V> enumMapFromBuffer(ByteBuf buf, Class<K> keyClass,
Class<V> objectClass) {
EnumMap<K, V> metadataMap =
new EnumMap<>(keyClass);
byte numEntries = buf.readByte();
while (numEntries > 0 && buf.isReadable()) {
K type = fromBuffer(buf, keyClass);
V value = (V)fromBuffer(buf, type.getComponentType());
metadataMap.put(type, value);
numEntries--;
}
return metadataMap;
}
@SuppressWarnings("unchecked")
static <T> T fromBuffer(ByteBuf buf, TypeToken<T> token) {
Class<?> rawType = token.getRawType();
if (rawType.isAssignableFrom(Map.class)) {
return (T) mapFromBuffer(buf, token.resolveType(Map.class.getTypeParameters()[0]).getRawType(),
token.resolveType(Map.class.getTypeParameters()[1]).getRawType());
} else if (rawType.isAssignableFrom(Set.class)) {
return (T) setFromBuffer(buf, token.resolveType(Set.class.getTypeParameters()[0]).getRawType());
}
return (T) fromBuffer(buf, rawType);
}
@SuppressWarnings("unchecked")
static <T> void serialize(ByteBuf buffer, T payload) {
// If it's an ICorfuPayload, use the defined serializer.
if (payload instanceof ICorfuPayload) {
((ICorfuPayload) payload).doSerialize(buffer);
}
// Otherwise serialize the primitive type.
else if (payload instanceof Byte) {
buffer.writeByte((Byte) payload);
} else if (payload instanceof Short) {
buffer.writeShort((Short) payload);
} else if (payload instanceof Integer) {
buffer.writeInt((Integer) payload);
} else if (payload instanceof Long) {
buffer.writeLong((Long) payload);
} else if (payload instanceof Boolean) {
buffer.writeBoolean((Boolean) payload);
} else if (payload instanceof Double) {
buffer.writeDouble((Double) payload);
} else if (payload instanceof Float) {
buffer.writeFloat((Float) payload);
} else if (payload instanceof byte[]) {
buffer.writeInt(((byte[]) payload).length);
buffer.writeBytes((byte[]) payload);
}
// and some standard non prims as well
else if (payload instanceof String) {
byte[] s = ((String) payload).getBytes();
buffer.writeInt(s.length);
buffer.writeBytes(s);
} else if (payload instanceof UUID) {
buffer.writeLong(((UUID) payload).getMostSignificantBits());
buffer.writeLong(((UUID) payload).getLeastSignificantBits());
}
// and some collection types
else if (payload instanceof EnumMap) {
EnumMap<?,?> map = (EnumMap<?,?>) payload;
buffer.writeByte(map.size());
map.entrySet().stream().forEach(x -> {
serialize(buffer, x.getKey());
serialize(buffer, x.getValue());
});
}
else if (payload instanceof RangeSet) {
Set<Range<?>> rs = (((RangeSet) payload).asRanges());
buffer.writeInt(rs.size());
rs.stream().forEach(x -> {
buffer.writeBoolean(x.upperBoundType() == BoundType.CLOSED);
serialize(buffer, x.upperEndpoint());
buffer.writeBoolean(x.upperBoundType() == BoundType.CLOSED);
serialize(buffer, x.lowerEndpoint());
});
}
else if (payload instanceof Range) {
Range<?> r = (Range) payload;
buffer.writeBoolean(r.upperBoundType() == BoundType.CLOSED);
serialize(buffer, r.upperEndpoint());
buffer.writeBoolean(r.upperBoundType() == BoundType.CLOSED);
serialize(buffer, r.lowerEndpoint());
}
else if (payload instanceof Map) {
Map<?,?> map = (Map<?,?>) payload;
buffer.writeInt(map.size());
map.entrySet().stream().forEach(x -> {
serialize(buffer, x.getKey());
serialize(buffer, x.getValue());
});
} else if (payload instanceof Set) {
Set<?> set = (Set<?>) payload;
buffer.writeInt(set.size());
set.stream().forEach(x -> {
serialize(buffer, x);
});
}
else if (payload instanceof List) {
List<?> list = (List<?>) payload;
buffer.writeInt(list.size());
list.stream().forEach(x -> {
serialize(buffer, x);
});
}
else if (payload instanceof Layout) {
byte[] b = JSONUtils.parser.toJson(payload).getBytes();
buffer.writeInt(b.length);
buffer.writeBytes(b);
}
// and if its a bytebuf
else if (payload instanceof ByteBuf) {
ByteBuf b = ((ByteBuf) payload).slice();
b.resetReaderIndex();
int bytes = b.readableBytes();
buffer.writeInt(bytes);
buffer.writeBytes(b, bytes);
} else if (payload instanceof IMetadata.DataRank) {
IMetadata.DataRank rank = (IMetadata.DataRank)payload;
buffer.writeLong(rank.getRank());
buffer.writeLong(rank.getUuid().getMostSignificantBits());
buffer.writeLong(rank.getUuid().getLeastSignificantBits());
}
else {
throw new RuntimeException("Unknown class " + payload.getClass()
+ " for serialization");
}
}
void doSerialize(ByteBuf buf);
}