
com.dslplatform.json.DslJson Maven / Gradle / Ivy
Show all versions of dsl-json Show documentation
package com.dslplatform.json;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.sql.ResultSet;
import java.util.*;
import java.util.concurrent.*;
/**
* Main DSL-JSON class.
* Easiest way to use the library is to create an DslJson<Object> instance and reuse it within application.
* DslJson has optional constructor for specifying default readers/writers.
*
* During initialization DslJson will use ServiceLoader API to load registered services.
* This is done through `META-INF/services/com.dslplatform.json.CompiledJson` file.
*
* DslJson can fallback to another serializer in case when it doesn't know how to handle specific type.
* This can be specified by Fallback interface during initialization.
*
* If you wish to use compile time databinding @CompiledJson annotation must be specified on the target class
* or implicit reference to target class must exists from a class with @CompiledJson annotation.
*
* Usage example:
*
* DslJson<Object> dsl = new DslJson<>();
* dsl.serialize(instance, OutputStream);
* POJO pojo = dsl.deserialize(POJO.class, InputStream);
*
*
* For best performance use serialization API with JsonWriter and byte[] as target.
* JsonWriter is reused via thread local variable. When custom JsonWriter's are used, reusing them will yield maximum performance.
* JsonWriter can be reused via reset methods.
* For best deserialization performance prefer byte[] API instead of InputStream API.
* JsonReader is reused via thread local variable. When custom JsonReaders are used, reusing them will yield maximum performance.
* JsonReader can be reused via process methods.
*
* During deserialization TContext can be used to pass data into deserialized classes.
* This is useful when deserializing domain objects which require state or service provider.
* For example DSL Platform entities require service locator to be able to perform lazy load.
*
* DslJson doesn't have a String or Reader API since it's optimized for processing bytes and streams.
* If you wish to process String, use String.getBytes("UTF-8") as argument for DslJson.
* Only UTF-8 is supported for encoding and decoding JSON.
*
* DslJson<Object> dsl = new DslJson<>();
* JsonWriter writer = dsl.newWriter();
* dsl.serialize(writer, instance);
* String json = writer.toString(); //JSON as string - avoid using JSON as Strings whenever possible
* byte[] input = json.getBytes("UTF-8");
* POJO pojo = dsl.deserialize(POJO.class, input, input.length);
*
*
* @param used for library specialization. If unsure, use Object
*/
public class DslJson implements UnknownSerializer, TypeLookup {
private static final Object unknownValue = new Object();
/**
* The context of this instance.
* Can be used for library specialization
*/
@Nullable
public final TContext context;
@Nullable
protected final Fallback fallback;
/**
* Should properties with default values be omitted from the resulting JSON?
* This will leave out nulls, empty collections, zeros and other attributes with default values
* which can be reconstructed from schema information
*/
public final boolean omitDefaults;
/**
* When object supports array format, eg. [prop1, prop2, prop3] this value must be enabled before
* object will be serialized in such a way. Regardless of this value deserialization will support all formats.
*/
public final boolean allowArrayFormat;
protected final StringCache keyCache;
protected final StringCache valuesCache;
protected final List> writerFactories = new CopyOnWriteArrayList>();
private final int settingsWriters;
protected final List> readerFactories = new CopyOnWriteArrayList>();
private final int settingsReaders;
protected final List> binderFactories = new CopyOnWriteArrayList>();
private final int settingsBinders;
private final JsonReader.ErrorInfo errorInfo;
private final JsonReader.DoublePrecision doublePrecision;
private final JsonReader.UnknownNumberParsing unknownNumbers;
private final int maxNumberDigits;
private final int maxStringSize;
protected final ThreadLocal localWriter;
protected final ThreadLocal localReader;
private final ExternalConverterAnalyzer externalConverterAnalyzer;
private final Map, Boolean> creatorMarkers;
public interface Fallback {
void serialize(@Nullable Object instance, OutputStream stream) throws IOException;
@Nullable
Object deserialize(@Nullable TContext context, Type manifest, byte[] body, int size) throws IOException;
@Nullable
Object deserialize(@Nullable TContext context, Type manifest, InputStream stream) throws IOException;
}
public interface ConverterFactory {
@Nullable
T tryCreate(Type manifest, DslJson dslJson);
}
/**
* Configuration for DslJson options.
* By default key cache is enabled. Everything else is not configured.
* To load `META-INF/services` call `includeServiceLoader()`
*
* @param DslJson context
*/
public static class Settings {
private TContext context;
private boolean javaSpecifics;
private Fallback fallback;
private boolean omitDefaults;
private boolean allowArrayFormat;
private StringCache keyCache = new SimpleStringCache();
private StringCache valuesCache;
private int fromServiceLoader;
private JsonReader.ErrorInfo errorInfo = JsonReader.ErrorInfo.WITH_STACK_TRACE;
private JsonReader.DoublePrecision doublePrecision = JsonReader.DoublePrecision.DEFAULT;
private JsonReader.UnknownNumberParsing unknownNumbers = JsonReader.UnknownNumberParsing.LONG_AND_BIGDECIMAL;
private int maxNumberDigits = 512;
private int maxStringBuffer = 128 * 1024 * 1024;
private final List configurations = new ArrayList();
private final List> writerFactories = new ArrayList>();
private final List> readerFactories = new ArrayList>();
private final List> binderFactories = new ArrayList>();
private final Set classLoaders = new HashSet();
private final Map, Boolean> creatorMarkers = new HashMap, Boolean>();
/**
* Pass in context for DslJson.
* Context will be available in JsonReader for objects which needs it.
*
* @param context context propagated to JsonReaders
* @return itself
*/
public Settings withContext(@Nullable TContext context) {
this.context = context;
return this;
}
/**
* Enable converters for Java specific types (Graphics API) not available on Android.
*
* @param javaSpecifics should register Java specific converters
* @return itself
*/
public Settings withJavaConverters(boolean javaSpecifics) {
this.javaSpecifics = javaSpecifics;
return this;
}
/**
* Will be eventually replaced with writer/reader factories.
* Used by DslJson to call into when trying to serialize/deserialize object which is not supported.
*
* @param fallback how to handle unsupported type
* @return which fallback to use in case of unsupported type
*/
@Deprecated
public Settings fallbackTo(@Nullable Fallback fallback) {
this.fallback = fallback;
return this;
}
/**
* DslJson can exclude some properties from resulting JSON which it can reconstruct fully from schema information.
* Eg. int with value 0 can be omitted since that is default value for the type.
* Null values can be excluded since they are handled the same way as missing property.
*
* @param omitDefaults should exclude default values from resulting JSON
* @return itself
*/
public Settings skipDefaultValues(boolean omitDefaults) {
this.omitDefaults = omitDefaults;
return this;
}
/**
* Some encoders/decoders support writing objects in array format.
* For encoder to write objects in such format, Array format must be defined before the Default and minified formats
* and array format must be allowed via this setting.
* If objects support multiple formats decoding will work regardless of this setting.
*
* @param allowArrayFormat allow serialization via array format
* @return itself
*/
public Settings allowArrayFormat(boolean allowArrayFormat) {
this.allowArrayFormat = allowArrayFormat;
return this;
}
/**
* Use specific key cache implementation.
* Key cache is enabled by default and it's used when deserializing unstructured objects such as Map<String, Object>
* to avoid allocating new String key instance. Instead StringCache will provide a new or an old instance.
* This improves memory usage and performance since there is usually small number of keys.
* It does have some performance overhead, but this is dependant on the implementation.
*
* To disable key cache, provide null for it.
*
* @param keyCache which key cache to use
* @return itself
*/
public Settings useKeyCache(@Nullable StringCache keyCache) {
this.keyCache = keyCache;
return this;
}
/**
* Use specific string values cache implementation.
* By default string values cache is disabled.
*
* To support memory restricted scenarios where there is limited number of string values,
* values cache can be used.
*
* Not every "JSON string" will use this cache... eg UUID, LocalDate don't create an instance of string
* and therefore don't use this cache.
*
* @param valuesCache which values cache to use
* @return itself
*/
public Settings useStringValuesCache(@Nullable StringCache valuesCache) {
this.valuesCache = valuesCache;
return this;
}
/**
* DslJson will iterate over converter factories when requested type is unknown.
* Registering writer converter factory allows for constructing JSON converter lazily.
*
* @param writer registered writer factory
* @return itself
*/
@SuppressWarnings("unchecked")
public Settings resolveWriter(ConverterFactory extends JsonWriter.WriteObject> writer) {
if (writer == null) throw new IllegalArgumentException("writer can't be null");
if (writerFactories.contains(writer)) {
throw new IllegalArgumentException("writer already registered");
}
writerFactories.add((ConverterFactory) writer);
return this;
}
/**
* DslJson will iterate over converter factories when requested type is unknown.
* Registering reader converter factory allows for constructing JSON converter lazily.
*
* @param reader registered reader factory
* @return itself
*/
@SuppressWarnings("unchecked")
public Settings resolveReader(ConverterFactory extends JsonReader.ReadObject> reader) {
if (reader == null) throw new IllegalArgumentException("reader can't be null");
if (readerFactories.contains(reader)) {
throw new IllegalArgumentException("reader already registered");
}
readerFactories.add((ConverterFactory) reader);
return this;
}
/**
* DslJson will iterate over converter factories when requested type is unknown.
* Registering binder converter factory allows for constructing JSON converter lazily.
*
* @param binder registered binder factory
* @return itself
*/
@SuppressWarnings("unchecked")
public Settings resolveBinder(ConverterFactory extends JsonReader.BindObject> binder) {
if (binder == null) throw new IllegalArgumentException("binder can't be null");
if (binderFactories.contains(binder)) {
throw new IllegalArgumentException("binder already registered");
}
binderFactories.add((ConverterFactory) binder);
return this;
}
/**
* Load converters using thread local ClassLoader.
* Will scan through `META-INF/services/com.dslplatform.json.Configuration` file and register implementation during startup.
* This will pick up compile time databindings if they are available in specific folder.
*
* Note that gradle on Android has issues with preserving that file, in which case it can be provided manually.
* DslJson will fall back to "expected" class name if it doesn't find anything during scanning.
*
* @return itself
*/
public Settings includeServiceLoader() {
return includeServiceLoader(Thread.currentThread().getContextClassLoader());
}
/**
* Load converters using provided `ClassLoader` instance
* Will scan through `META-INF/services/com.dslplatform.json.Configuration` file and register implementation during startup.
* This will pick up compile time databindings if they are available in specific folder.
*
* Note that gradle on Android has issues with preserving that file, in which case it can be provided manually.
* DslJson will fall back to "expected" class name if it doesn't find anything during scanning.
*
* @param loader ClassLoader to use
* @return itself
*/
public Settings includeServiceLoader(ClassLoader loader) {
if (loader == null) throw new IllegalArgumentException("loader can't be null");
classLoaders.add(loader);
for (Configuration c : ServiceLoader.load(Configuration.class, loader)) {
boolean hasConfiguration = false;
Class> manifest = c.getClass();
for (Configuration cur : configurations) {
if (cur.getClass() == manifest) {
hasConfiguration = true;
break;
}
}
if (!hasConfiguration) {
fromServiceLoader++;
configurations.add(c);
}
}
return this;
}
/**
* By default doubles are not deserialized into an exact value in some rare edge cases.
*
* @param errorInfo information about error in parsing exception
* @return itself
*/
public Settings errorInfo(JsonReader.ErrorInfo errorInfo) {
if (errorInfo == null) throw new IllegalArgumentException("errorInfo can't be null");
this.errorInfo = errorInfo;
return this;
}
/**
* By default doubles are not deserialized into an exact value in some rare edge cases.
*
* @param precision type of double deserialization
* @return itself
*/
public Settings doublePrecision(JsonReader.DoublePrecision precision) {
if (precision == null) throw new IllegalArgumentException("precision can't be null");
this.doublePrecision = precision;
return this;
}
/**
* When processing JSON without a schema numbers can be deserialized in various ways:
*
* - as longs and decimals
* - as longs and doubles
* - as decimals only
* - as doubles only
*
* Default is as long and BigDecimal
*
* @param unknownNumbers how to deserialize numbers without a schema
* @return itself
*/
public Settings unknownNumbers(JsonReader.UnknownNumberParsing unknownNumbers) {
if (unknownNumbers == null) throw new IllegalArgumentException("unknownNumbers can't be null");
this.unknownNumbers = unknownNumbers;
return this;
}
/**
* Specify maximum allowed size for digits buffer. Default is 512.
* Digits buffer is used when processing strange/large input numbers.
*
* @param size maximum allowed size for digit buffer
* @return itself
*/
public Settings limitDigitsBuffer(int size) {
if (size < 1) throw new IllegalArgumentException("size can't be smaller than 1");
this.maxNumberDigits = size;
return this;
}
/**
* Specify maximum allowed size for string buffer. Default is 128MB
* To protect against malicious inputs, maximum allowed string buffer can be reduced.
*
* @param size maximum size of buffer in bytes
* @return itself
*/
public Settings limitStringBuffer(int size) {
if (size < 1) throw new IllegalArgumentException("size can't be smaller than 1");
this.maxStringBuffer = size;
return this;
}
/**
* When there are multiple constructors, pick the one marked with annotation.
* When markers is allowed on non public targets, attempt at visibility change will be done in runtime.
*
* @param marker annotation used for marking constructor or static method factory
* @param expandVisibility should consider annotation declared on non public accessor
* @return itself
*/
public Settings creatorMarker(Class extends Annotation> marker, boolean expandVisibility) {
if (marker == null) throw new IllegalArgumentException("marker can't be null");
this.creatorMarkers.put(marker, expandVisibility);
return this;
}
/**
* Configure DslJson with custom Configuration during startup.
* Configurations are extension points for setting up readers/writers during DslJson initialization.
*
* @param conf custom extensibility point
* @return itself
*/
public Settings with(Configuration conf) {
if (conf == null) throw new IllegalArgumentException("conf can't be null");
configurations.add(conf);
return this;
}
private Settings with(Iterable confs) {
if (confs != null) {
for (Configuration c : confs)
configurations.add(c);
}
return this;
}
}
/**
* Simple initialization entry point.
* Will provide null for TContext
* Java graphics readers/writers will not be registered.
* Fallback will not be configured.
* Key cache will be enables, values cache will be disabled.
* Default ServiceLoader.load method will be used to setup services from META-INF
*/
public DslJson() {
this(new Settings().includeServiceLoader());
}
/**
* Will be removed. Use DslJson(Settings) instead.
* Fully configurable entry point.
*
* @param context context instance which can be provided to deserialized objects. Use null if not sure
* @param javaSpecifics register Java graphics specific classes such as java.awt.Point, Image, ...
* @param fallback in case of unsupported type, try serialization/deserialization through external API
* @param omitDefaults should serialization produce minified JSON (omit nulls and default values)
* @param keyCache parsed keys can be cached (this is only used in small subset of parsing)
* @param serializers additional serializers/deserializers which will be immediately registered into readers/writers
*/
@Deprecated
public DslJson(
@Nullable final TContext context,
final boolean javaSpecifics,
@Nullable final Fallback fallback,
final boolean omitDefaults,
@Nullable final StringCache keyCache,
final Iterable serializers) {
this(new Settings()
.withContext(context)
.withJavaConverters(javaSpecifics)
.fallbackTo(fallback)
.skipDefaultValues(omitDefaults)
.useKeyCache(keyCache)
.with(serializers)
);
}
/**
* Fully configurable entry point.
* Provide settings for DSL-JSON initialization.
*
* @param settings DSL-JSON configuration
*/
public DslJson(final Settings settings) {
if (settings == null) throw new IllegalArgumentException("settings can't be null");
final DslJson self = this;
this.localWriter = new ThreadLocal() {
@Override
protected JsonWriter initialValue() {
return new JsonWriter(4096, self);
}
};
this.localReader = new ThreadLocal() {
@Override
protected JsonReader initialValue() {
return new JsonReader(new byte[4096], 4096, self.context, new char[64], self.keyCache, self.valuesCache, self, self.errorInfo, self.doublePrecision, self.unknownNumbers, self.maxNumberDigits, self.maxStringSize);
}
};
this.context = settings.context;
this.fallback = settings.fallback;
this.omitDefaults = settings.omitDefaults;
this.allowArrayFormat = settings.allowArrayFormat;
this.keyCache = settings.keyCache;
this.valuesCache = settings.valuesCache;
this.unknownNumbers = settings.unknownNumbers;
this.errorInfo = settings.errorInfo;
this.doublePrecision = settings.doublePrecision;
this.maxNumberDigits = settings.maxNumberDigits;
this.maxStringSize = settings.maxStringBuffer;
this.writerFactories.addAll(settings.writerFactories);
this.settingsWriters = settings.writerFactories.size();
this.readerFactories.addAll(settings.readerFactories);
this.settingsReaders = settings.readerFactories.size();
this.binderFactories.addAll(settings.binderFactories);
this.settingsBinders = settings.binderFactories.size();
this.externalConverterAnalyzer = new ExternalConverterAnalyzer(settings.classLoaders);
this.creatorMarkers = new HashMap, Boolean>(settings.creatorMarkers);
BinaryConverter.registerDefault(this);
BoolConverter.registerDefault(this);
if (settings.javaSpecifics) {
registerJavaSpecifics(this);
XmlConverter.registerDefault(this);
}
ObjectConverter.registerDefault(this);
NetConverter.registerDefault(this);
NumberConverter.registerDefault(this);
UUIDConverter.registerDefault(this);
StringConverter.registerDefault(this);
JavaTimeConverter.registerDefault(this);
registerWriter(ResultSet.class, new ResultSetConverter(this));
for (Configuration serializer : settings.configurations) {
serializer.configure(this);
}
if (!settings.classLoaders.isEmpty() && settings.fromServiceLoader == 0) {
//TODO: workaround common issue with failed services registration. try to load common external name if exists
loadDefaultConverters(this, settings.classLoaders, "dsl_json_Annotation_Processor_External_Serialization");
loadDefaultConverters(this, settings.classLoaders, "dsl_json.json.ExternalSerialization");
loadDefaultConverters(this, settings.classLoaders, "dsl_json_ExternalSerialization");
}
}
/**
* Simplistic string cache implementation.
* It uses a fixed String[] structure in which it caches string value based on it's hash.
* Eg, hash & mask provide index into the structure. Different string with same hash will overwrite the previous one.
*/
public static class SimpleStringCache implements StringCache {
private final int mask;
private final String[] cache;
/**
* Will use String[] with 1024 elements.
*/
public SimpleStringCache() {
this(10);
}
public SimpleStringCache(int log2Size) {
int size = 2;
for (int i = 1; i < log2Size; i++) {
size *= 2;
}
mask = size - 1;
cache = new String[size];
}
/**
* Calculates hash of the provided "string" and looks it up from the String[]
* It it doesn't exists of a different string is already there a new String instance is created
* and saved into the String[]
*
* @param chars buffer into which string was parsed
* @param len the string length inside the buffer
* @return String instance matching the char[]/int pair
*/
@Override
public String get(char[] chars, int len) {
long hash = 0x811c9dc5;
for (int i = 0; i < len; i++) {
hash ^= (byte) chars[i];
hash *= 0x1000193;
}
final int index = (int) hash & mask;
final String value = cache[index];
if (value == null) return createAndPut(index, chars, len);
if (value.length() != len) return createAndPut(index, chars, len);
for (int i = 0; i < value.length(); i++) {
if (value.charAt(i) != chars[i]) return createAndPut(index, chars, len);
}
return value;
}
private String createAndPut(int index, char[] chars, int len) {
final String value = new String(chars, 0, len);
cache[index] = value;
return value;
}
}
/**
* Create a writer bound to this DSL-JSON.
* Ideally it should be reused.
* Bound writer can use lookups to find custom writers.
* This can be used to serialize unknown types such as Object.class
*
* @return bound writer
*/
public JsonWriter newWriter() {
return new JsonWriter(this);
}
/**
* Create a writer bound to this DSL-JSON.
* Ideally it should be reused.
* Bound writer can use lookups to find custom writers.
* This can be used to serialize unknown types such as Object.class
*
* @param size initial buffer size
* @return bound writer
*/
public JsonWriter newWriter(int size) {
return new JsonWriter(size, this);
}
/**
* Create a writer bound to this DSL-JSON.
* Ideally it should be reused.
* Bound writer can use lookups to find custom writers.
* This can be used to serialize unknown types such as Object.class
*
* @param buffer initial buffer
* @return bound writer
*/
public JsonWriter newWriter(byte[] buffer) {
if (buffer == null) throw new IllegalArgumentException("null value provided for buffer");
return new JsonWriter(buffer, this);
}
/**
* Create a reader bound to this DSL-JSON.
* Bound reader can reuse key cache (which is used during Map deserialization)
* This reader can be reused via process method.
*
* @return bound reader
*/
public JsonReader newReader() {
return new JsonReader(new byte[4096], 4096, context, new char[64], keyCache, valuesCache, this, errorInfo, doublePrecision, unknownNumbers, maxNumberDigits, maxStringSize);
}
/**
* Create a reader bound to this DSL-JSON.
* Bound reader can reuse key cache (which is used during Map deserialization)
* This reader can be reused via process method.
*
* @param bytes input bytes
* @return bound reader
*/
public JsonReader newReader(byte[] bytes) {
return new JsonReader(bytes, bytes.length, context, new char[64], keyCache, valuesCache, this, errorInfo, doublePrecision, unknownNumbers, maxNumberDigits, maxStringSize);
}
/**
* Create a reader bound to this DSL-JSON.
* Bound reader can reuse key cache (which is used during Map deserialization)
* This reader can be reused via process method.
*
* @param bytes input bytes
* @param length use input bytes up to specified length
* @return bound reader
*/
public JsonReader newReader(byte[] bytes, int length) {
return new JsonReader(bytes, length, context, new char[64], keyCache, valuesCache, this, errorInfo, doublePrecision, unknownNumbers, maxNumberDigits, maxStringSize);
}
/**
* Create a reader bound to this DSL-JSON.
* Bound reader can reuse key cache (which is used during Map deserialization)
* Pass in initial string buffer.
* This reader can be reused via process method.
*
* @param bytes input bytes
* @param length use input bytes up to specified length
* @param tmp string parsing buffer
* @return bound reader
*/
public JsonReader newReader(byte[] bytes, int length, char[] tmp) {
return new JsonReader(bytes, length, context, tmp, keyCache, valuesCache, this, errorInfo, doublePrecision, unknownNumbers, maxNumberDigits, maxStringSize);
}
/**
* Create a reader bound to this DSL-JSON.
* Bound reader can reuse key cache (which is used during Map deserialization)
* Created reader can be reused (using process method).
* This is convenience method for creating a new reader and binding it to stream.
*
* @param stream input stream
* @param buffer temporary buffer
* @return bound reader
* @throws java.io.IOException unable to read from stream
*/
public JsonReader newReader(InputStream stream, byte[] buffer) throws IOException {
final JsonReader reader = newReader(buffer);
reader.process(stream);
return reader;
}
/**
* Create a reader bound to this DSL-JSON.
* Bound reader can reuse key cache (which is used during Map deserialization)
* This method id Deprecated since it should be avoided.
* It's better to use byte[] or InputStream based readers
*
* @param input JSON string
* @return bound reader
*/
@Deprecated
public JsonReader newReader(String input) {
final byte[] bytes = input.getBytes(StandardCharsets.UTF_8);
return new JsonReader(bytes, bytes.length, context, new char[64], keyCache, valuesCache, this, errorInfo, doublePrecision, unknownNumbers, maxNumberDigits, maxStringSize);
}
private static void loadDefaultConverters(final DslJson json, Set loaders, final String name) {
for (ClassLoader loader : loaders) {
try {
Class> external = loader.loadClass(name);
Configuration instance = (Configuration) external.newInstance();
instance.configure(json);
} catch (NoClassDefFoundError ignore) {
} catch (Exception ignore) {
}
}
}
static void registerJavaSpecifics(final DslJson json) {
JavaGeomConverter.registerDefault(json);
XmlConverter.registerDefault(json);
}
private final Map defaults = new ConcurrentHashMap();
public void registerDefault(Class manifest, T instance) {
defaults.put(manifest, instance);
}
@SuppressWarnings("unchecked")
public boolean registerWriterFactory(ConverterFactory extends JsonWriter.WriteObject> factory) {
if (factory == null) throw new IllegalArgumentException("factory can't be null");
if (writerFactories.contains(factory)) return false;
writerFactories.add(writerFactories.size() - settingsWriters, (ConverterFactory) factory);
return true;
}
@SuppressWarnings("unchecked")
public boolean registerReaderFactory(ConverterFactory extends JsonReader.ReadObject> factory) {
if (factory == null) throw new IllegalArgumentException("factory can't be null");
if (readerFactories.contains(factory)) return false;
readerFactories.add(readerFactories.size() - settingsReaders, (ConverterFactory) factory);
return true;
}
@SuppressWarnings("unchecked")
public boolean registerBinderFactory(ConverterFactory extends JsonReader.BindObject> factory) {
if (factory == null) throw new IllegalArgumentException("factory can't be null");
if (binderFactories.contains(factory)) return false;
binderFactories.add(binderFactories.size() - settingsBinders, (ConverterFactory) factory);
return true;
}
@Nullable
public final Object getDefault(@Nullable Type manifest) {
if (manifest == null) return null;
Object instance = defaults.get(manifest);
if (instance != null) return instance;
final Class> rawType;
if (manifest instanceof Class>) {
rawType = (Class>) manifest;
} else if (manifest instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) manifest;
rawType = (Class>) pt.getRawType();
} else return null;
if (rawType.isPrimitive()) {
return Array.get(Array.newInstance(rawType, 1), 0);
}
return defaults.get(rawType);
}
private final ConcurrentMap, JsonReader.ReadJsonObject> objectReaders =
new ConcurrentHashMap, JsonReader.ReadJsonObject>();
private final ConcurrentMap readers = new ConcurrentHashMap();
private final ConcurrentMap binders = new ConcurrentHashMap();
private final ConcurrentMap writers = new ConcurrentHashMap();
public final Set getRegisteredDecoders() {
return readers.keySet();
}
public final Set getRegisteredBinders() {
return binders.keySet();
}
public final Set getRegisteredEncoders() {
return writers.keySet();
}
public final Map, Boolean> getRegisteredCreatorMarkers() {
return creatorMarkers;
}
/**
* Register custom reader for specific type (JSON -> instance conversion).
* Reader is used for conversion from input byte[] -> target object instance
*
* Types registered through @CompiledJson annotation should be registered automatically through
* ServiceLoader.load method and you should not be registering them manually.
*
* If null is registered for a reader this will disable deserialization of specified type
*
* @param manifest specified type
* @param reader provide custom implementation for reading JSON into an object instance
* @param type
* @param type or subtype
*/
public void registerReader(final Class manifest, @Nullable final JsonReader.ReadObject reader) {
if (reader == null) readers.remove(manifest);
else readers.put(manifest, reader);
}
/**
* Register custom reader for specific type (JSON -> instance conversion).
* Reader is used for conversion from input byte[] -> target object instance
*
* Types registered through @CompiledJson annotation should be registered automatically through
* ServiceLoader.load method and you should not be registering them manually.
*
* If null is registered for a reader this will disable deserialization of specified type
*
* @param manifest specified type
* @param reader provide custom implementation for reading JSON into an object instance
* @return old registered value
*/
@Nullable
public JsonReader.ReadObject registerReader(final Type manifest, @Nullable final JsonReader.ReadObject> reader) {
if (reader == null) return readers.remove(manifest);
try {
return readers.get(manifest);
} finally {
readers.put(manifest, reader);
}
}
/**
* Register custom binder for specific type (JSON -> instance conversion).
* Binder is used for conversion from input byte[] -> existing target object instance.
* It's similar to reader, with the difference that it accepts target instance.
*
* Types registered through @CompiledJson annotation should be registered automatically through
* ServiceLoader.load method and you should not be registering them manually.
*
* If null is registered for a binder this will disable binding of specified type
*
* @param manifest specified type
* @param binder provide custom implementation for binding JSON to an object instance
* @param type
* @param type or subtype
*/
public void registerBinder(final Class manifest, @Nullable final JsonReader.BindObject binder) {
if (binder == null) binders.remove(manifest);
else binders.put(manifest, binder);
}
/**
* Register custom binder for specific type (JSON -> instance conversion).
* Binder is used for conversion from input byte[] -> existing target object instance.
* It's similar to reader, with the difference that it accepts target instance.
*
* Types registered through @CompiledJson annotation should be registered automatically through
* ServiceLoader.load method and you should not be registering them manually.
*
* If null is registered for a binder this will disable binding of specified type
*
* @param manifest specified type
* @param binder provide custom implementation for binding JSON to an object instance
*/
public void registerBinder(final Type manifest, @Nullable final JsonReader.BindObject> binder) {
if (binder == null) binders.remove(manifest);
else binders.put(manifest, binder);
}
/**
* Register custom writer for specific type (instance -> JSON conversion).
* Writer is used for conversion from object instance -> output byte[]
*
* Types registered through @CompiledJson annotation should be registered automatically through
* ServiceLoader.load method and you should not be registering them manually.
*
* If null is registered for a writer this will disable serialization of specified type
*
* @param manifest specified type
* @param writer provide custom implementation for writing JSON from object instance
* @param type
*/
public void registerWriter(final Class manifest, @Nullable final JsonWriter.WriteObject writer) {
if (writer == null) {
writerMap.remove(manifest);
writers.remove(manifest);
} else {
writerMap.put(manifest, manifest);
writers.put(manifest, writer);
}
}
/**
* Register custom writer for specific type (instance -> JSON conversion).
* Writer is used for conversion from object instance -> output byte[]
*
* Types registered through @CompiledJson annotation should be registered automatically through
* ServiceLoader.load method and you should not be registering them manually.
*
* If null is registered for a writer this will disable serialization of specified type
*
* @param manifest specified type
* @param writer provide custom implementation for writing JSON from object instance
* @return old registered value
*/
@Nullable
public JsonWriter.WriteObject registerWriter(final Type manifest, @Nullable final JsonWriter.WriteObject> writer) {
if (writer == null) return writers.remove(manifest);
try {
return writers.get(manifest);
} finally {
writers.put(manifest, writer);
}
}
private final ConcurrentMap, Class>> writerMap = new ConcurrentHashMap, Class>>();
/**
* Try to find registered writer for provided type.
* If writer is not found, null will be returned.
* If writer for exact type is not found, type hierarchy will be scanned for base writer.
*
* Writer is used for conversion from object instance into JSON representation.
*
* @param manifest specified type
* @return writer for specified type if found
*/
@Nullable
public JsonWriter.WriteObject> tryFindWriter(final Type manifest) {
JsonWriter.WriteObject writer = writers.get(manifest);
if (writer != null) return writer;
final Type actualType = extractActualType(manifest);
if (actualType != manifest) {
writer = writers.get(actualType);
if (writer != null) {
writers.putIfAbsent(manifest, writer);
return writer;
}
}
if (actualType instanceof Class>) {
final Class> signature = (Class>) actualType;
if (JsonObject.class.isAssignableFrom(signature)) {
writers.putIfAbsent(manifest, OBJECT_WRITER);
return OBJECT_WRITER;
}
}
writer = lookupFromFactories(manifest, actualType, writerFactories, writers);
if (writer != null) {
if (manifest instanceof Class> && actualType == manifest) {
writers.putIfAbsent(manifest, writer);
}
return writer;
}
if (!(actualType instanceof Class>)) return null;
Class> found = writerMap.get(actualType);
if (found != null) {
return writers.get(found);
}
Class> container = (Class>) actualType;
final ArrayList> signatures = new ArrayList>();
findAllSignatures(container, signatures);
for (final Class> sig : signatures) {
writer = writers.get(sig);
if (writer == null) {
writer = lookupFromFactories(manifest, sig, writerFactories, writers);
}
if (writer != null) {
writerMap.putIfAbsent(container, sig);
return writer;
}
}
return null;
}
private static Type extractActualType(final Type manifest) {
if (manifest instanceof WildcardType) {
WildcardType wt = (WildcardType) manifest;
if (wt.getUpperBounds().length == 1 && wt.getLowerBounds().length == 0) {
return wt.getUpperBounds()[0];
}
}
return manifest;
}
private void checkExternal(final Type manifest, final ConcurrentMap cache) {
if (manifest instanceof Class>) {
externalConverterAnalyzer.tryFindConverter((Class>) manifest, this);
} else if (manifest instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) manifest;
Type container = pt.getRawType();
externalConverterAnalyzer.tryFindConverter((Class>) container, this);
for (Type arg : pt.getActualTypeArguments()) {
if (!cache.containsKey(arg)) {
Type actualType = extractActualType(arg);
if (actualType != arg && !cache.containsKey(actualType)) {
checkExternal(actualType, cache);
}
}
}
}
}
@Nullable
private T lookupFromFactories(
final Type signature,
final Type manifest,
final List> factories,
final ConcurrentMap cache) {
if (manifest instanceof Class>) {
Class> raw = (Class>) manifest;
externalConverterAnalyzer.tryFindConverter(raw, this);
T found = cache.get(manifest);
if (found != null) return found;
if (raw.getTypeParameters().length > 0) {
checkExternal(manifest, cache);
}
} else if (manifest instanceof ParameterizedType) {
checkExternal(manifest, cache);
}
for (ConverterFactory wrt : factories) {
final T converter = wrt.tryCreate(manifest, this);
if (converter != null) {
cache.putIfAbsent(signature, converter);
return converter;
}
}
return null;
}
/**
* Try to find registered reader for provided type.
* If reader is not found, null will be returned.
* Exact match must be found, type hierarchy will not be scanned for alternative readers.
*
* If you wish to use alternative reader for specific type, register it manually with something along the lines of
*
* DslJson dslJson = ...
* dslJson.registerReader(Interface.class, dslJson.tryFindReader(Implementation.class));
*
*
* @param manifest specified type
* @return found reader for specified type
*/
@Nullable
public JsonReader.ReadObject> tryFindReader(final Type manifest) {
JsonReader.ReadObject found = readers.get(manifest);
if (found != null) return found;
final Type actualType = extractActualType(manifest);
if (actualType != manifest) {
found = readers.get(actualType);
if (found != null) {
readers.putIfAbsent(manifest, found);
return found;
}
}
if (actualType instanceof Class>) {
final Class> signature = (Class>) actualType;
if (JsonObject.class.isAssignableFrom(signature)) {
final JsonReader.ReadJsonObject> decoder = getObjectReader(signature);
if (decoder != null) {
found = convertToReader(decoder);
readers.putIfAbsent(manifest, found);
return found;
}
}
}
return lookupFromFactories(manifest, actualType, readerFactories, readers);
}
/**
* Try to find registered binder for provided type.
* If binder is not found, null will be returned.
* Exact match must be found, type hierarchy will not be scanned for alternative binders.
*
* If you wish to use alternative binder for specific type, register it manually with something along the lines of
*
* DslJson dslJson = ...
* dslJson.registerBinder(Interface.class, dslJson.tryFindBinder(Implementation.class));
*
*
* @param manifest specified type
* @return found reader for specified type
*/
@Nullable
public JsonReader.BindObject> tryFindBinder(final Type manifest) {
JsonReader.BindObject found = binders.get(manifest);
if (found != null) return found;
final Type actualType = extractActualType(manifest);
if (actualType != manifest) {
found = binders.get(actualType);
if (found != null) {
binders.putIfAbsent(manifest, found);
return found;
}
}
return lookupFromFactories(manifest, actualType, binderFactories, binders);
}
/**
* Try to find registered writer for provided type.
* If writer is not found, null will be returned.
* If writer for exact type is not found, type hierarchy will be scanned for base writer.
*
* Writer is used for conversion from object instance into JSON representation.
*
* @param manifest specified class
* @param specified type
* @return found writer for specified class or null
*/
@SuppressWarnings("unchecked")
@Nullable
public JsonWriter.WriteObject tryFindWriter(final Class manifest) {
return (JsonWriter.WriteObject) tryFindWriter((Type) manifest);
}
/**
* Try to find registered reader for provided type.
* If reader is not found, null will be returned.
* Exact match must be found, type hierarchy will not be scanned for alternative reader.
*
* If you wish to use alternative reader for specific type, register it manually with something along the lines of
*
* DslJson dslJson = ...
* dslJson.registerReader(Interface.class, dslJson.tryFindReader(Implementation.class));
*
*
* @param manifest specified class
* @param specified type
* @return found reader for specified class or null
*/
@SuppressWarnings("unchecked")
@Nullable
public JsonReader.ReadObject tryFindReader(final Class manifest) {
return (JsonReader.ReadObject) tryFindReader((Type) manifest);
}
/**
* Try to find registered binder for provided type.
* If binder is not found, null will be returned.
* Exact match must be found, type hierarchy will not be scanned for alternative binder.
*
* If you wish to use alternative binder for specific type, register it manually with something along the lines of
*
* DslJson dslJson = ...
* dslJson.registerBinder(Interface.class, dslJson.tryFindBinder(Implementation.class));
*
*
* @param manifest specified class
* @param specified type
* @return found reader for specified class or null
*/
@SuppressWarnings("unchecked")
@Nullable
public JsonReader.BindObject tryFindBinder(final Class manifest) {
return (JsonReader.BindObject) tryFindBinder((Type) manifest);
}
private static void findAllSignatures(final Class> manifest, final ArrayList> found) {
if (found.contains(manifest)) {
return;
}
found.add(manifest);
final Class> superClass = manifest.getSuperclass();
if (superClass != null && superClass != Object.class) {
findAllSignatures(superClass, found);
}
for (final Class> iface : manifest.getInterfaces()) {
findAllSignatures(iface, found);
}
}
@SuppressWarnings("unchecked")
@Nullable
private JsonReader.ReadJsonObject probeForObjectReader(Class> manifest, Object instance) {
Object found;
try {
found = manifest.getField("JSON_READER").get(instance);
} catch (Exception ignore) {
try {
found = manifest.getMethod("JSON_READER").invoke(instance);
} catch (Exception ignore2) {
try {
found = manifest.getMethod("getJSON_READER").invoke(instance);
} catch (Exception ignore3) {
return null;
}
}
}
return found instanceof JsonReader.ReadJsonObject
? (JsonReader.ReadJsonObject)found
: null;
}
@SuppressWarnings("unchecked")
@Nullable
protected final JsonReader.ReadJsonObject getObjectReader(final Class> manifest) {
try {
JsonReader.ReadJsonObject reader = objectReaders.get(manifest);
if (reader == null) {
reader = probeForObjectReader(manifest, null);
if (reader == null) {
//probe in few special places
try {
Object companion = manifest.getField("Companion").get(null);
reader = probeForObjectReader(companion.getClass(), companion);
} catch (Exception ignore) {
return null;
}
}
if (reader != null) {
objectReaders.putIfAbsent(manifest, reader);
}
}
return reader;
} catch (final Exception ignore) {
return null;
}
}
public void serializeMap(final Map value, final JsonWriter sw) throws IOException {
sw.writeByte(JsonWriter.OBJECT_START);
final int size = value.size();
if (size > 0) {
final Iterator> iterator = value.entrySet().iterator();
Map.Entry kv = iterator.next();
sw.writeString(kv.getKey());
sw.writeByte(JsonWriter.SEMI);
serialize(sw, kv.getValue());
for (int i = 1; i < size; i++) {
sw.writeByte(JsonWriter.COMMA);
kv = iterator.next();
sw.writeString(kv.getKey());
sw.writeByte(JsonWriter.SEMI);
serialize(sw, kv.getValue());
}
}
sw.writeByte(JsonWriter.OBJECT_END);
}
@Deprecated
@Nullable
public static Object deserializeObject(final JsonReader reader) throws IOException {
return ObjectConverter.deserializeObject(reader);
}
/**
* Will be removed
* @param reader JSON reader
* @return deseralized list
* @throws IOException error during parsing
*/
@Deprecated
public static ArrayList