java-commons/commons-serialize-databind/src/main/java/io/gitlab/jfronny/commons/serialize/databind/ObjectMapper.java

180 lines
7.6 KiB
Java

package io.gitlab.jfronny.commons.serialize.databind;
import io.gitlab.jfronny.commons.concurrent.ScopedValue;
import io.gitlab.jfronny.commons.concurrent.WithScopedValue;
import io.gitlab.jfronny.commons.serialize.databind.api.SerializerFor;
import io.gitlab.jfronny.commons.serialize.databind.api.TypeAdapter;
import io.gitlab.jfronny.commons.serialize.databind.api.TypeToken;
import io.gitlab.jfronny.commons.serialize.MalformedDataException;
import io.gitlab.jfronny.commons.serialize.SerializeReader;
import io.gitlab.jfronny.commons.serialize.SerializeWriter;
import io.gitlab.jfronny.commons.serialize.databind.impl.HierarchyAdapter;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class ObjectMapper implements WithScopedValue<ObjectMapper> {
public static final ScopedValue<ObjectMapper> CURRENT = new ScopedValue<>();
private final ThreadLocal<Map<TypeToken<?>, TypeAdapter<?>>> threadLocalAdapterResults = new ThreadLocal<>();
private final ConcurrentMap<TypeToken<?>, TypeAdapter<?>> typeTokenCache = new ConcurrentHashMap<>();
private final List<TypeAdapterFactory> factories = new ArrayList<>();
private final Map<TypeToken<?>, TypeAdapter<?>> explicitAdapters = new ConcurrentHashMap<>();
private final Map<TypeToken<?>, TypeAdapter<?>> hierarchyAdapters = new ConcurrentHashMap<>();
public ObjectMapper() {
ServiceLoader.load(TypeAdapterFactory.class).forEach(this::registerTypeAdapterFactory);
ServiceLoader.load(TypeAdapter.class).forEach(this::registerTypeAdapter);
}
public ObjectMapper registerTypeAdapter(Type type, TypeAdapter<?> adapter, boolean hierarchical) {
(hierarchical ? hierarchyAdapters : explicitAdapters).put(TypeToken.get(type), Objects.requireNonNull(adapter));
return this;
}
public ObjectMapper registerTypeAdapter(TypeAdapter<?> adapter) {
SerializerFor annotation = adapter.getClass().getAnnotation(SerializerFor.class);
if (annotation == null) {
throw new IllegalArgumentException("TypeAdapter must be annotated with @SerializerFor to register it without specifying the target type: " + adapter.getClass().getName());
}
adapter = annotation.nullSafe() ? adapter.nullSafe() : adapter;
for (Class<?> target : annotation.targets()) {
registerTypeAdapter(target, adapter, annotation.hierarchical());
}
return this;
}
public ObjectMapper registerTypeAdapterFactory(TypeAdapterFactory factory) {
factories.addFirst(Objects.requireNonNull(factory));
return this;
}
public <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>, T> T deserialize(Class<T> type, Reader reader) throws TEx, MalformedDataException {
return this.<T, TEx, MalformedDataException>withContext(() -> getAdapter(type).deserialize(reader));
}
public <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>, T> void serialize(T value, Writer writer) throws TEx, MalformedDataException {
this.<TEx, MalformedDataException>withContext(() -> getAdapter((Class<T>) value.getClass()).serialize(value, writer));
}
public <T> TypeAdapter<T> getAdapter(Class<T> type) {
return getAdapter(TypeToken.get(type));
}
public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
Objects.requireNonNull(type, "type must not be null");
if (explicitAdapters.containsKey(type)) {
return (TypeAdapter<T>) explicitAdapters.get(type);
}
for (TypeToken<?> token : hierarchyAdapters.keySet()) {
if (token.isAssignableFrom(type)) {
// We know the adapters type is a superclass of the requested type
TypeAdapter<T> adapter = new HierarchyAdapter<>((TypeAdapter) hierarchyAdapters.get(token), type.getRawType());
explicitAdapters.put(type, adapter);
return adapter;
}
}
TypeAdapter<?> cached = typeTokenCache.get(type);
if (cached != null) {
return (TypeAdapter<T>) cached;
}
Map<TypeToken<?>, TypeAdapter<?>> threadCalls = threadLocalAdapterResults.get();
boolean isInitialAdapterRequest = false;
if (threadCalls == null) {
threadCalls = new HashMap<>();
threadLocalAdapterResults.set(threadCalls);
isInitialAdapterRequest = true;
} else {
TypeAdapter<T> ongoingCall = (TypeAdapter<T>) threadCalls.get(type);
if (ongoingCall != null) {
return ongoingCall;
}
}
TypeAdapter<T> candidate = null;
try {
FutureTypeAdapter<T> call = new FutureTypeAdapter<>();
threadCalls.put(type, call);
for (TypeAdapterFactory factory : factories) {
candidate = factory.create(this, type);
if (candidate != null) {
call.setDelegate(candidate);
// Replace future adapter with actual adapter
threadCalls.put(type, candidate);
break;
}
}
} finally {
if (isInitialAdapterRequest) {
threadLocalAdapterResults.remove();
}
}
if (candidate == null) {
throw new IllegalArgumentException("JfCommons ObjectMapper cannot handle " + type);
}
if (isInitialAdapterRequest) {
/*
* Publish resolved adapters to all threads
* Can only do this for the initial request because cyclic dependency TypeA -> TypeB -> TypeA
* would otherwise publish adapter for TypeB which uses not yet resolved adapter for TypeA
* See https://github.com/google/gson/issues/625
*/
typeTokenCache.putAll(threadCalls);
}
return candidate;
}
@Override
public ScopedValue<ObjectMapper> getAttached() {
return CURRENT;
}
@Override
public ObjectMapper self() {
return this;
}
static class FutureTypeAdapter<T> extends SerializationDelegatingTypeAdapter<T> {
private TypeAdapter<T> delegate = null;
public void setDelegate(TypeAdapter<T> typeAdapter) {
if (delegate != null) {
throw new AssertionError("Delegate is already set");
}
delegate = typeAdapter;
}
private TypeAdapter<T> delegate() {
TypeAdapter<T> delegate = this.delegate;
if (delegate == null) {
// Can occur when adapter is leaked to other thread or when adapter is used for
// (de-)serialization
// directly within the TypeAdapterFactory which requested it
throw new IllegalStateException(
"Adapter for type with cyclic dependency has been used"
+ " before dependency has been resolved");
}
return delegate;
}
@Override
public TypeAdapter<T> getSerializationDelegate() {
return delegate();
}
@Override
public <TEx extends Exception, Reader extends SerializeReader<TEx, Reader>> T deserialize(Reader reader) throws TEx, MalformedDataException {
return delegate().deserialize(reader);
}
@Override
public <TEx extends Exception, Writer extends SerializeWriter<TEx, Writer>> void serialize(T value, Writer writer) throws TEx, MalformedDataException {
delegate().serialize(value, writer);
}
}
}