/*
* Copyright (c) 2015-2016, Christoph Engelbert (aka noctarius) and
* contributors. All rights reserved.
*
* 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 com.noctarius.tengi.spi.serialization.impl;
import com.noctarius.tengi.core.config.MarshallerConfiguration;
import com.noctarius.tengi.core.exception.NoSuchMarshallerException;
import com.noctarius.tengi.core.exception.UnknownTypeException;
import com.noctarius.tengi.core.impl.ExceptionUtil;
import com.noctarius.tengi.core.impl.Validate;
import com.noctarius.tengi.core.model.Identifier;
import com.noctarius.tengi.core.model.Message;
import com.noctarius.tengi.core.serialization.Identifiable;
import com.noctarius.tengi.core.serialization.TypeId;
import com.noctarius.tengi.core.serialization.codec.Decoder;
import com.noctarius.tengi.core.serialization.codec.Encoder;
import com.noctarius.tengi.core.serialization.debugger.DebuggableMarshaller;
import com.noctarius.tengi.core.serialization.debugger.DebuggableProtocol;
import com.noctarius.tengi.core.serialization.marshaller.Marshaller;
import com.noctarius.tengi.core.serialization.marshaller.MarshallerFilter;
import com.noctarius.tengi.spi.buffer.ReadableMemoryBuffer;
import com.noctarius.tengi.spi.serialization.Protocol;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.Reader;
import java.net.URL;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class DefaultProtocol
implements Protocol, DebuggableProtocol, DefaultProtocolConstants {
private final Map<Short, Class<?>> typeById = new ConcurrentHashMap<>();
private final Map<Class<?>, Short> reverseTypeId = new ConcurrentHashMap<>();
private final ConcurrentMap<Class<?>, Marshaller> marshallerCache = new ConcurrentHashMap<>();
private final Map<MarshallerFilter, Marshaller> marshallers = new HashMap<>();
private final Map<Short, Marshaller> marshallerById = new ConcurrentHashMap<>();
private final Map<Marshaller, Short> reverseMarshallerId = new ConcurrentHashMap<>();
public DefaultProtocol(Collection<MarshallerConfiguration> marshallerConfigurations) {
this(null, marshallerConfigurations);
}
public DefaultProtocol(InputStream is, Collection<MarshallerConfiguration> marshallerConfigurations) {
ClassLoader classLoader = getClass().getClassLoader();
registerInternalTypes(classLoader);
typesInitializer(classLoader.getResourceAsStream(TYPE_MANIFEST_FILENAME));
if (is != null) {
typesInitializer(is);
}
registerInternalMarshallers();
registerMarshallers(marshallerConfigurations);
}
@Override
public String getMimeType() {
return PROTOCOL_MIME_TYPE;
}
@Override
public void writeTypeId(Object value, Encoder encoder) {
Class<?> type;
if (value instanceof Class) {
type = (Class<?>) value;
} else {
type = value.getClass();
}
Short typeId = reverseTypeId.get(type);
if (typeId == null) {
throw new UnknownTypeException("TypeId for type '" + type.getName() + "' not found. Not registered?");
}
encoder.writeShort("typeId", typeId);
}
@Override
public <T> Class<T> readTypeId(Decoder decoder) {
short typeId = decoder.readShort();
return (Class<T>) typeById.get(typeId);
}
@Override
public Object readTypeObject(Decoder decoder) {
try {
Class<?> clazz = readTypeId(decoder);
return clazz.newInstance();
} catch (Exception e) {
throw ExceptionUtil.rethrow(e);
}
}
@Override
public Class<?> findType(Decoder decoder) {
ReadableMemoryBuffer memoryBuffer = decoder.getReadableMemoryBuffer();
int readerIndex = memoryBuffer.readerIndex();
try {
short typeId = decoder.readShort();
Class<?> clazz = typeById.get(typeId);
if (clazz != null) {
return clazz;
}
Marshaller<?> marshaller = marshallerById.get(typeId);
if (marshaller != null && marshaller instanceof DebuggableMarshaller) {
return ((DebuggableMarshaller<?>) marshaller).findType(decoder, this);
}
return marshaller.getClass();
} finally {
memoryBuffer.readerIndex(readerIndex);
}
}
@Override
public <O> O readObject(String fieldName, Decoder decoder)
throws Exception {
short typeId = decoder.readShort();
Marshaller marshaller = marshallerById.get(typeId);
return (O) marshaller.unmarshall(fieldName, decoder, this);
}
@Override
public <O> void writeObject(String fieldName, O object, Encoder encoder)
throws Exception {
Validate.notNull("object", object);
Marshaller marshaller = computeMarshaller(object);
encoder.writeShort("marshallerId", findMarshallerId(marshaller));
marshaller.marshall(fieldName, object, encoder, this);
}
private void registerInternalTypes(ClassLoader classLoader) {
try {
Enumeration<URL> resources = classLoader.getResources(TYPE_DEFAULT_MANIFEST_FILENAME);
while (resources.hasMoreElements()) {
typesInitializer(resources.nextElement().openStream());
}
} catch (Exception e) {
throw ExceptionUtil.rethrow(e);
}
}
private void registerInternalMarshallers() {
// External types
registerMarshaller(PacketMarshallerFilter.INSTANCE, PacketMarshaller.INSTANCE);
registerMarshaller(MarshallableMarshallerFilter.INSTANCE, MarshallableMarshaller.INSTANCE);
registerMarshaller(EnumerableMarshallerFilter.INSTANCE, EnumerableMarshaller.INSTANCE);
registerMarshaller(EnumMarshallerFilter.INSTANCE, EnumMarshaller.INSTANCE);
// Internal types
registerMarshaller(Message.class, CommonMarshaller.MessageMarshaller.INSTANCE);
registerMarshaller(Identifier.class, CommonMarshaller.IdentifierMarshaller.INSTANCE);
registerMarshaller(Byte.class, CommonMarshaller.ByteMarshaller.INSTANCE);
registerMarshaller(byte.class, CommonMarshaller.ByteMarshaller.INSTANCE);
registerMarshaller(Short.class, CommonMarshaller.ShortMarshaller.INSTANCE);
registerMarshaller(short.class, CommonMarshaller.ShortMarshaller.INSTANCE);
registerMarshaller(Character.class, CommonMarshaller.CharMarshaller.INSTANCE);
registerMarshaller(char.class, CommonMarshaller.CharMarshaller.INSTANCE);
registerMarshaller(Integer.class, CommonMarshaller.IntegerMarshaller.INSTANCE);
registerMarshaller(int.class, CommonMarshaller.IntegerMarshaller.INSTANCE);
registerMarshaller(Long.class, CommonMarshaller.LongMarshaller.INSTANCE);
registerMarshaller(long.class, CommonMarshaller.LongMarshaller.INSTANCE);
registerMarshaller(Float.class, CommonMarshaller.FloatMarshaller.INSTANCE);
registerMarshaller(float.class, CommonMarshaller.FloatMarshaller.INSTANCE);
registerMarshaller(Double.class, CommonMarshaller.DoubleMarshaller.INSTANCE);
registerMarshaller(double.class, CommonMarshaller.DoubleMarshaller.INSTANCE);
registerMarshaller(String.class, CommonMarshaller.StringMarshaller.INSTANCE);
registerMarshaller(byte[].class, CommonMarshaller.ByteArrayMarshaller.INSTANCE);
}
private void registerMarshallers(Collection<MarshallerConfiguration> marshallerConfigurations) {
marshallerConfigurations.forEach((config) -> registerMarshaller(config.getMarshallerFilter(), config.getMarshaller()));
}
private void registerMarshaller(MarshallerFilter filter, Marshaller marshaller) {
short marshallerId = findMarshallerId(marshaller);
marshallers.put(filter, marshaller);
marshallerById.put(marshallerId, marshaller);
reverseMarshallerId.put(marshaller, marshallerId);
}
private <O> void registerMarshaller(Class<O> clazz, Marshaller marshaller) {
short marshallerId = findMarshallerId(marshaller);
marshallerCache.put(clazz, marshaller);
marshallerById.put(marshallerId, marshaller);
reverseMarshallerId.put(marshaller, marshallerId);
}
private void typesInitializer(InputStream is) {
if (is == null) {
return;
}
try {
Reader pipeReader = new InputStreamReader(is, "UTF-8");
BufferedReader reader = new LineNumberReader(pipeReader);
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (!line.isEmpty() && !line.startsWith("#")) {
registerTypeId(line);
}
}
} catch (Exception e) {
throw ExceptionUtil.rethrow(e);
}
}
private void registerTypeId(String className)
throws Exception {
Class<?> clazz = Class.forName(className);
TypeId annotation = clazz.getAnnotation(TypeId.class);
if (annotation == null) {
throw new UnknownTypeException("Registered serialization type is not annotated with @TypeId");
}
short typeId = annotation.value();
reverseTypeId.put(clazz, typeId);
typeById.put(typeId, clazz);
}
private <O> short findMarshallerId(Marshaller<O> marshaller) {
Short marshallerId = reverseMarshallerId.get(marshaller);
if (marshallerId != null) {
return marshallerId;
}
if (marshaller instanceof Identifiable) {
return ((Identifiable<Short>) marshaller).identifier();
}
TypeId annotation = marshaller.getClass().getAnnotation(TypeId.class);
if (annotation == null) {
throw new UnknownTypeException("Registered marshaller type is not annotated with @TypeId");
}
return annotation.value();
}
private Marshaller computeMarshaller(Object object) {
Class<?> clazz = object.getClass();
Marshaller marshaller = marshallerCache.get(clazz);
if (marshaller != null) {
return marshaller;
}
marshaller = testMarshaller(object, PacketMarshallerFilter.INSTANCE, PacketMarshaller.INSTANCE);
if (marshaller != null) {
return marshaller;
}
marshaller = testMarshaller(object, MarshallableMarshallerFilter.INSTANCE, MarshallableMarshaller.INSTANCE);
if (marshaller != null) {
return marshaller;
}
for (Map.Entry<MarshallerFilter, Marshaller> entry : marshallers.entrySet()) {
marshaller = testMarshaller(object, entry.getKey(), entry.getValue());
if (marshaller != null) {
return marshaller;
}
}
throw new NoSuchMarshallerException("No suitable marshaller found for type '" + clazz.getName() + "'");
}
private Marshaller testMarshaller(Object object, MarshallerFilter filter, Marshaller marshaller) {
MarshallerFilter.Result result = filter.accept(object);
if (result == MarshallerFilter.Result.AcceptedAndCache) {
marshallerCache.putIfAbsent(object.getClass(), marshaller);
return marshaller;
} else if (result == MarshallerFilter.Result.Accepted) {
return marshaller;
}
return null;
}
}