/*
* Copyright 2014 the original author or 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.gradle.internal.serialize;
import com.google.common.base.Objects;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class DefaultSerializerRegistry implements SerializerRegistry {
private final Map<Class<?>, Serializer<?>> serializerMap = new TreeMap<Class<?>, Serializer<?>>(new Comparator<Class<?>>() {
public int compare(Class<?> o1, Class<?> o2) {
return o1.getName().compareTo(o2.getName());
}
});
private final Set<Class<?>> javaSerialization = new HashSet<Class<?>>();
private final SerializerClassMatcherStrategy classMatcher;
public DefaultSerializerRegistry() {
this(true);
}
public DefaultSerializerRegistry(boolean supportClassHierarchy) {
this.classMatcher = supportClassHierarchy ? SerializerClassMatcherStrategy.HIERARCHY : SerializerClassMatcherStrategy.STRICT;
}
@Override
public <T> void register(Class<T> implementationType, Serializer<T> serializer) {
serializerMap.put(implementationType, serializer);
}
@Override
public <T> void useJavaSerialization(Class<T> implementationType) {
javaSerialization.add(implementationType);
}
@Override
public boolean canSerialize(Class<?> baseType) {
for (Class<?> candidate : serializerMap.keySet()) {
if (classMatcher.matches(baseType, candidate)) {
return true;
}
}
for (Class<?> candidate : javaSerialization) {
if (classMatcher.matches(baseType, candidate)) {
return true;
}
}
return false;
}
@Override
public <T> Serializer<T> build(Class<T> baseType) {
Map<Class<?>, Serializer<?>> matches = new LinkedHashMap<Class<?>, Serializer<?>>();
for (Map.Entry<Class<?>, Serializer<?>> entry : serializerMap.entrySet()) {
if (baseType.isAssignableFrom(entry.getKey())) {
matches.put(entry.getKey(), entry.getValue());
}
}
Set<Class<?>> matchingJavaSerialization = new LinkedHashSet<Class<?>>();
for (Class<?> candidate : javaSerialization) {
if (baseType.isAssignableFrom(candidate)) {
matchingJavaSerialization.add(candidate);
}
}
if (matches.isEmpty() && matchingJavaSerialization.isEmpty()) {
throw new IllegalArgumentException(String.format("Don't know how to serialize objects of type %s.", baseType.getName()));
}
if (matches.size() == 1 && matchingJavaSerialization.isEmpty()) {
return (Serializer<T>) matches.values().iterator().next();
}
return new TaggedTypeSerializer<T>(matches, matchingJavaSerialization);
}
private static class TypeInfo {
final int tag;
final boolean useForSubtypes;
final Serializer serializer;
private TypeInfo(int tag, boolean useForSubtypes, Serializer serializer) {
this.tag = tag;
this.useForSubtypes = useForSubtypes;
this.serializer = serializer;
}
}
private static class TaggedTypeSerializer<T> extends AbstractSerializer<T> {
private static final int JAVA_TYPE = 1; // Reserve 0 for null (to be added later)
private static final TypeInfo JAVA_SERIALIZATION = new TypeInfo(JAVA_TYPE, true, new DefaultSerializer<Object>());
private final Map<Class<?>, TypeInfo> serializersByType = new HashMap<Class<?>, TypeInfo>();
private final Map<Class<?>, TypeInfo> typeHierarchies = new HashMap<Class<?>, TypeInfo>();
private final TypeInfo[] serializersByTag;
public TaggedTypeSerializer(Map<Class<?>, Serializer<?>> serializerMap, Set<Class<?>> javaSerialization) {
serializersByTag = new TypeInfo[2 + serializerMap.size()];
serializersByTag[JAVA_TYPE] = JAVA_SERIALIZATION;
int nextTag = 2;
for (Map.Entry<Class<?>, Serializer<?>> entry : serializerMap.entrySet()) {
add(nextTag, entry.getKey(), entry.getValue());
nextTag++;
}
for (Class<?> type : javaSerialization) {
serializersByType.put(type, JAVA_SERIALIZATION);
typeHierarchies.put(type, JAVA_SERIALIZATION);
}
}
private void add(int tag, Class<?> type, Serializer<?> serializer) {
TypeInfo typeInfo = new TypeInfo(tag, type.equals(Throwable.class), serializer);
serializersByType.put(type, typeInfo);
serializersByTag[typeInfo.tag] = typeInfo;
if (typeInfo.useForSubtypes) {
typeHierarchies.put(type, typeInfo);
}
}
public T read(Decoder decoder) throws Exception {
int tag = decoder.readSmallInt();
TypeInfo typeInfo = tag >= serializersByTag.length ? null : serializersByTag[tag];
if (typeInfo == null) {
throw new IllegalArgumentException(String.format("Unexpected type tag %d found.", tag));
}
return (T) typeInfo.serializer.read(decoder);
}
public void write(Encoder encoder, T value) throws Exception {
TypeInfo typeInfo = map(value.getClass());
encoder.writeSmallInt(typeInfo.tag);
typeInfo.serializer.write(encoder, value);
}
@Override
public boolean equals(Object obj) {
if (!super.equals(obj)) {
return false;
}
TaggedTypeSerializer rhs = (TaggedTypeSerializer) obj;
return Objects.equal(serializersByType, rhs.serializersByType)
&& Objects.equal(typeHierarchies, rhs.typeHierarchies)
&& Arrays.equals(serializersByTag, rhs.serializersByTag);
}
@Override
public int hashCode() {
return Objects.hashCode(super.hashCode(), serializersByType, typeHierarchies, Arrays.hashCode(serializersByTag));
}
private TypeInfo map(Class<?> valueType) {
TypeInfo typeInfo = serializersByType.get(valueType);
if (typeInfo != null) {
return typeInfo;
}
for (Map.Entry<Class<?>, TypeInfo> entry : typeHierarchies.entrySet()) {
if (entry.getKey().isAssignableFrom(valueType)) {
return entry.getValue();
}
}
throw new IllegalArgumentException(String.format("Don't know how to serialize an object of type %s.", valueType.getName()));
}
}
private interface SerializerClassMatcherStrategy {
SerializerClassMatcherStrategy STRICT = new StrictSerializerMatcher();
SerializerClassMatcherStrategy HIERARCHY = new HierarchySerializerMatcher();
boolean matches(Class<?> baseType, Class<?> candidate);
}
private static final class HierarchySerializerMatcher implements SerializerClassMatcherStrategy {
@Override
public boolean matches(Class<?> baseType, Class<?> candidate) {
return baseType.isAssignableFrom(candidate);
}
}
private static class StrictSerializerMatcher implements SerializerClassMatcherStrategy {
@Override
public boolean matches(Class<?> baseType, Class<?> candidate) {
return baseType.equals(candidate);
}
}
}