180 lines
7.6 KiB
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);
|
|
}
|
|
}
|
|
}
|