Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.dslplatform.json.DslJson Maven / Gradle / Ivy
package com.dslplatform.json;
import org.w3c.dom.Element;
import java.io.*;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.net.InetAddress;
import java.net.URI;
import java.nio.charset.Charset;
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 Charset UTF8 = Charset.forName("UTF-8");
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;
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();
/**
* 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;
}
/**
* 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);
registerReader(byte[].class, BinaryConverter.Base64Reader);
registerWriter(byte[].class, BinaryConverter.Base64Writer);
registerReader(boolean.class, BoolConverter.READER);
registerWriter(boolean.class, BoolConverter.WRITER);
registerDefault(boolean.class, false);
registerReader(boolean[].class, BoolConverter.ARRAY_READER);
registerWriter(boolean[].class, BoolConverter.ARRAY_WRITER);
registerReader(Boolean.class, BoolConverter.NULLABLE_READER);
registerWriter(Boolean.class, BoolConverter.WRITER);
if (settings.javaSpecifics) {
registerJavaSpecifics(this);
}
registerReader(LinkedHashMap.class, ObjectConverter.MapReader);
registerReader(HashMap.class, ObjectConverter.MapReader);
registerReader(Map.class, ObjectConverter.MapReader);
registerWriter(Map.class, new JsonWriter.WriteObject() {
@Override
public void write(JsonWriter writer, @Nullable Map value) {
if (value == null) {
writer.writeNull();
} else {
try {
serializeMap(value, writer);
} catch (IOException ex) {
throw new SerializationException(ex);
}
}
}
});
registerReader(URI.class, NetConverter.UriReader);
registerWriter(URI.class, NetConverter.UriWriter);
registerReader(InetAddress.class, NetConverter.AddressReader);
registerWriter(InetAddress.class, NetConverter.AddressWriter);
registerReader(double.class, NumberConverter.DOUBLE_READER);
registerWriter(double.class, NumberConverter.DOUBLE_WRITER);
registerDefault(double.class, 0.0);
registerReader(double[].class, NumberConverter.DOUBLE_ARRAY_READER);
registerWriter(double[].class, NumberConverter.DOUBLE_ARRAY_WRITER);
registerReader(Double.class, NumberConverter.NULLABLE_DOUBLE_READER);
registerWriter(Double.class, NumberConverter.DOUBLE_WRITER);
registerReader(float.class, NumberConverter.FLOAT_READER);
registerWriter(float.class, NumberConverter.FLOAT_WRITER);
registerDefault(float.class, 0.0f);
registerReader(float[].class, NumberConverter.FLOAT_ARRAY_READER);
registerWriter(float[].class, NumberConverter.FLOAT_ARRAY_WRITER);
registerReader(Float.class, NumberConverter.NULLABLE_FLOAT_READER);
registerWriter(Float.class, NumberConverter.FLOAT_WRITER);
registerReader(int.class, NumberConverter.INT_READER);
registerWriter(int.class, NumberConverter.INT_WRITER);
registerDefault(int.class, 0);
registerReader(int[].class, NumberConverter.INT_ARRAY_READER);
registerWriter(int[].class, NumberConverter.INT_ARRAY_WRITER);
registerReader(Integer.class, NumberConverter.NULLABLE_INT_READER);
registerWriter(Integer.class, NumberConverter.INT_WRITER);
registerReader(short.class, NumberConverter.SHORT_READER);
registerWriter(short.class, NumberConverter.SHORT_WRITER);
registerDefault(short.class, (short)0);
registerReader(short[].class, NumberConverter.SHORT_ARRAY_READER);
registerWriter(short[].class, NumberConverter.SHORT_ARRAY_WRITER);
registerReader(Short.class, NumberConverter.NULLABLE_SHORT_READER);
registerWriter(Short.class, NumberConverter.SHORT_WRITER);
registerReader(long.class, NumberConverter.LONG_READER);
registerWriter(long.class, NumberConverter.LONG_WRITER);
registerDefault(long.class, 0L);
registerReader(long[].class, NumberConverter.LONG_ARRAY_READER);
registerWriter(long[].class, NumberConverter.LONG_ARRAY_WRITER);
registerReader(Long.class, NumberConverter.NULLABLE_LONG_READER);
registerWriter(Long.class, NumberConverter.LONG_WRITER);
registerReader(BigDecimal.class, NumberConverter.DecimalReader);
registerWriter(BigDecimal.class, NumberConverter.DecimalWriter);
registerReader(String.class, StringConverter.READER);
registerWriter(String.class, StringConverter.WRITER);
registerReader(UUID.class, UUIDConverter.READER);
registerWriter(UUID.class, UUIDConverter.WRITER);
registerReader(Number.class, NumberConverter.NumberReader);
registerWriter(CharSequence.class, StringConverter.WRITER_CHARS);
registerReader(StringBuilder.class, StringConverter.READER_BUILDER);
registerReader(StringBuffer.class, StringConverter.READER_BUFFER);
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(UTF8);
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) {
json.registerReader(java.awt.geom.Point2D.Double.class, JavaGeomConverter.LocationReader);
json.registerReader(java.awt.geom.Point2D.class, JavaGeomConverter.LocationReader);
json.registerWriter(java.awt.geom.Point2D.class, JavaGeomConverter.LocationWriter);
json.registerReader(java.awt.Point.class, JavaGeomConverter.PointReader);
json.registerWriter(java.awt.Point.class, JavaGeomConverter.PointWriter);
json.registerReader(java.awt.geom.Rectangle2D.Double.class, JavaGeomConverter.RectangleReader);
json.registerReader(java.awt.geom.Rectangle2D.class, JavaGeomConverter.RectangleReader);
json.registerWriter(java.awt.geom.Rectangle2D.class, JavaGeomConverter.RectangleWriter);
json.registerReader(java.awt.image.BufferedImage.class, JavaGeomConverter.ImageReader);
json.registerReader(java.awt.Image.class, JavaGeomConverter.ImageReader);
json.registerWriter(java.awt.Image.class, JavaGeomConverter.ImageWriter);
json.registerReader(Element.class, XmlConverter.Reader);
json.registerWriter(Element.class, XmlConverter.Writer);
}
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();
}
/**
* 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) 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>) {
externalConverterAnalyzer.tryFindConverter((Class>) manifest, this);
T found = cache.get(manifest);
if (found != null) return found;
} 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 deserializeList(final JsonReader reader) throws IOException {
return ObjectConverter.deserializeList(reader);
}
/**
* Will be removed
* @param reader JSON reader
* @return deserialized map
* @throws IOException error during parsing
*/
@Deprecated
public static LinkedHashMap deserializeMap(final JsonReader reader) throws IOException {
return ObjectConverter.deserializeMap(reader);
}
private static Object convertResultToArray(Class> elementType, List> result) {
if (elementType.isPrimitive()) {
if (boolean.class.equals(elementType)) {
boolean[] array = new boolean[result.size()];
for (int i = 0; i < result.size(); i++) {
array[i] = (Boolean) result.get(i);
}
return array;
} else if (int.class.equals(elementType)) {
int[] array = new int[result.size()];
for (int i = 0; i < result.size(); i++) {
array[i] = (Integer) result.get(i);
}
return array;
} else if (long.class.equals(elementType)) {
long[] array = new long[result.size()];
for (int i = 0; i < result.size(); i++) {
array[i] = (Long) result.get(i);
}
return array;
} else if (short.class.equals(elementType)) {
short[] array = new short[result.size()];
for (int i = 0; i < result.size(); i++) {
array[i] = (Short) result.get(i);
}
return array;
} else if (byte.class.equals(elementType)) {
byte[] array = new byte[result.size()];
for (int i = 0; i < result.size(); i++) {
array[i] = (Byte) result.get(i);
}
return array;
} else if (float.class.equals(elementType)) {
float[] array = new float[result.size()];
for (int i = 0; i < result.size(); i++) {
array[i] = (Float) result.get(i);
}
return array;
} else if (double.class.equals(elementType)) {
double[] array = new double[result.size()];
for (int i = 0; i < result.size(); i++) {
array[i] = (Double) result.get(i);
}
return array;
} else if (char.class.equals(elementType)) {
char[] array = new char[result.size()];
for (int i = 0; i < result.size(); i++) {
array[i] = (Character) result.get(i);
}
return array;
}
}
return result.toArray((Object[]) Array.newInstance(elementType, 0));
}
/**
* Check if DslJson knows how to serialize a type.
* It will check if a writer for such type exists or can be used.
*
* @param manifest type to check
* @return can serialize this type into JSON
*/
public final boolean canSerialize(final Type manifest) {
JsonWriter.WriteObject writer = writers.get(manifest);
if (writer != null) return true;
if (manifest instanceof Class>) {
final Class> content = (Class>) manifest;
if (JsonObject.class.isAssignableFrom(content)) {
return true;
}
if (JsonObject[].class.isAssignableFrom(content)) {
return true;
}
if (tryFindWriter(manifest) != null) {
return true;
}
if (content.isArray()) {
return !content.getComponentType().isArray()
&& !Collection.class.isAssignableFrom(content.getComponentType())
&& canSerialize(content.getComponentType());
}
}
if (manifest instanceof ParameterizedType) {
final ParameterizedType pt = (ParameterizedType) manifest;
if (pt.getActualTypeArguments().length == 1) {
final Class> container = (Class>) pt.getRawType();
if (container.isArray() || Collection.class.isAssignableFrom(container)) {
final Type content = pt.getActualTypeArguments()[0];
return content instanceof Class> && JsonObject.class.isAssignableFrom((Class>) content)
|| tryFindWriter(content) != null;
}
}
} else if (manifest instanceof GenericArrayType) {
final GenericArrayType gat = (GenericArrayType) manifest;
return gat.getGenericComponentType() instanceof Class>
&& JsonObject.class.isAssignableFrom((Class>) gat.getGenericComponentType())
|| tryFindWriter(gat.getGenericComponentType()) != null;
}
for (ConverterFactory wrt : writerFactories) {
if (wrt.tryCreate(manifest, this) != null) {
return true;
}
}
return false;
}
/**
* Check if DslJson knows how to deserialize a type.
* It will check if a reader for such type exists or can be used.
*
* @param manifest type to check
* @return can read this type from JSON
*/
public final boolean canDeserialize(final Type manifest) {
if (tryFindReader(manifest) != null) {
return true;
}
if (manifest instanceof Class>) {
final Class> objectType = (Class>) manifest;
if (objectType.isArray()) {
return !objectType.getComponentType().isArray()
&& !Collection.class.isAssignableFrom(objectType.getComponentType())
&& canDeserialize(objectType.getComponentType());
}
}
if (manifest instanceof ParameterizedType) {
final ParameterizedType pt = (ParameterizedType) manifest;
if (pt.getActualTypeArguments().length == 1) {
final Class> container = (Class>) pt.getRawType();
if (container.isArray() || Collection.class.isAssignableFrom(container)) {
final Type content = pt.getActualTypeArguments()[0];
if (tryFindReader(content) != null) {
return true;
}
}
}
} else if (manifest instanceof GenericArrayType) {
final Type content = ((GenericArrayType) manifest).getGenericComponentType();
return tryFindReader(content) != null;
}
return false;
}
/**
* Reusable deserialize API.
* For maximum performance `JsonReader` should be reused (otherwise small buffer will be allocated for processing)
* and `JsonReader.ReadObject` should be prepared (otherwise a lookup will be required).
*
* This is mostly convenience API since it starts the processing of the JSON by calling getNextToken on JsonReader,
* checks for null and calls converter.read(input).
*
* @param specified type
* @param converter target reader
* @param input input JSON
* @return deserialized instance
* @throws IOException error during deserialization
*/
@Nullable
public T deserialize(
final JsonReader.ReadObject converter,
final JsonReader input) throws IOException {
if (converter == null) {
throw new IllegalArgumentException("converter can't be null");
}
if (input == null) {
throw new IllegalArgumentException("input can't be null");
}
input.getNextToken();
return converter.read(input);
}
/**
* Convenient deserialize API for working with bytes.
* Deserialize provided byte input into target object.
*
* Since JSON is often though of as a series of char,
* most libraries will convert inputs into a sequence of chars and do processing on them.
* DslJson will treat input as a sequence of bytes which allows for various optimizations.
*
* @param manifest target type
* @param body input JSON
* @param size length
* @param target type
* @return deserialized instance
* @throws IOException error during deserialization
*/
@SuppressWarnings("unchecked")
@Nullable
public TResult deserialize(
final Class manifest,
final byte[] body,
final int size) throws IOException {
if (manifest == null) {
throw new IllegalArgumentException("manifest can't be null");
}
if (body == null) {
throw new IllegalArgumentException("body can't be null");
}
final JsonReader json = localReader.get().process(body, size);
try {
json.getNextToken();
final JsonReader.ReadObject> simpleReader = tryFindReader(manifest);
if (simpleReader != null) {
return (TResult) simpleReader.read(json);
}
if (manifest.isArray()) {
if (json.wasNull()) {
return null;
} else if (json.last() != '[') {
throw json.newParseError("Expecting '[' for array start");
}
final Class> elementManifest = manifest.getComponentType();
final List> list = deserializeList(elementManifest, body, size);
if (list == null) {
return null;
}
return (TResult) convertResultToArray(elementManifest, list);
}
if (fallback != null) {
return (TResult) fallback.deserialize(context, manifest, body, size);
}
throw createErrorMessage(manifest);
} finally {
json.reset();
}
}
/**
* Deserialize API for working with bytes.
* Deserialize provided byte input into target object.
*
* Since JSON is often though of as a series of char,
* most libraries will convert inputs into a sequence of chars and do processing on them.
* DslJson will treat input as a sequence of bytes which allows for various optimizations.
*
* @param manifest target type
* @param body input JSON
* @param size length
* @return deserialized instance
* @throws IOException error during deserialization
*/
@Nullable
public Object deserialize(
final Type manifest,
final byte[] body,
final int size) throws IOException {
if (manifest instanceof Class>) {
return deserialize((Class>) manifest, body, size);
}
if (manifest == null) {
throw new IllegalArgumentException("manifest can't be null");
}
if (body == null) {
throw new IllegalArgumentException("body can't be null");
}
final JsonReader json = localReader.get().process(body, size);
try {
json.getNextToken();
final Object result = deserializeWith(manifest, json);
if (result != unknownValue) return result;
if (fallback != null) {
return fallback.deserialize(context, manifest, body, size);
}
throw new ConfigurationException("Unable to find reader for provided type: " + manifest + " and fallback serialization is not registered.\n" +
"Try initializing DslJson with custom fallback in case of unsupported objects or register specified type using registerReader into " + getClass());
} finally {
json.reset();
}
}
@SuppressWarnings("unchecked")
@Nullable
private Object deserializeWith(Type manifest, JsonReader json) throws IOException {
final JsonReader.ReadObject> simpleReader = tryFindReader(manifest);
if (simpleReader != null) {
return simpleReader.read(json);
}
if (manifest instanceof ParameterizedType) {
final ParameterizedType pt = (ParameterizedType) manifest;
if (pt.getActualTypeArguments().length == 1) {
final Type content = pt.getActualTypeArguments()[0];
final Class> container = (Class>) pt.getRawType();
if (container.isArray() || Collection.class.isAssignableFrom(container)) {
if (json.wasNull()) {
return null;
} else if (json.last() != '[') {
throw json.newParseError("Expecting '[' for array start");
}
if (json.getNextToken() == ']') {
if (container.isArray()) {
returnEmptyArray(content);
}
return new ArrayList(0);
}
final JsonReader.ReadObject> contentReader = tryFindReader(content);
if (contentReader != null) {
final ArrayList> result = json.deserializeNullableCollection(contentReader);
if (container.isArray()) {
return returnAsArray(content, result);
}
return result;
}
}
}
} else if (manifest instanceof GenericArrayType) {
if (json.wasNull()) {
return null;
} else if (json.last() != '[') {
throw json.newParseError("Expecting '[' for array start");
}
final Type content = ((GenericArrayType) manifest).getGenericComponentType();
if (json.getNextToken() == ']') {
return returnEmptyArray(content);
}
final JsonReader.ReadObject> contentReader = tryFindReader(content);
if (contentReader != null) {
final ArrayList> result = json.deserializeNullableCollection(contentReader);
return returnAsArray(content, result);
}
}
return unknownValue;
}
private static Object returnAsArray(final Type content, final ArrayList> result) {
if (content instanceof Class>) {
return convertResultToArray((Class>) content, result);
}
if (content instanceof ParameterizedType) {
final ParameterizedType cpt = (ParameterizedType) content;
return result.toArray((Object[]) Array.newInstance((Class>) cpt.getRawType(), 0));
}
return result.toArray();
}
private static Object returnEmptyArray(Type content) {
if (content instanceof Class>) {
return Array.newInstance((Class>) content, 0);
}
if (content instanceof ParameterizedType) {
final ParameterizedType pt = (ParameterizedType) content;
return Array.newInstance((Class>) pt.getRawType(), 0);
}
return new Object[0];
}
private IOException createErrorMessage(final Class> manifest) {
final ArrayList> signatures = new ArrayList>();
findAllSignatures(manifest, signatures);
for (final Class> sig : signatures) {
if (readers.containsKey(sig)) {
if (sig.equals(manifest)) {
return new IOException("Reader for provided type: " + manifest + " is disabled and fallback serialization is not registered (converter is registered as null).\n" +
"Try initializing system with custom fallback or don't register null for " + manifest);
}
return new IOException("Unable to find reader for provided type: " + manifest + " and fallback serialization is not registered.\n" +
"Found reader for: " + sig + " so try deserializing into that instead?\n" +
"Alternatively, try initializing system with custom fallback or register specified type using registerReader into " + getClass());
}
}
return new IOException("Unable to find reader for provided type: " + manifest + " and fallback serialization is not registered.\n" +
"Try initializing DslJson with custom fallback in case of unsupported objects or register specified type using registerReader into " + getClass());
}
/**
* Convenient deserialize list API for working with bytes.
* Deserialize provided byte input into target object.
*
* Since JSON is often though of as a series of char,
* most libraries will convert inputs into a sequence of chars and do processing on them.
* DslJson will treat input as a sequence of bytes which allows for various optimizations.
*
* @param manifest target type
* @param body input JSON
* @param size length
* @param target element type
* @return deserialized list instance
* @throws IOException error during deserialization
*/
@SuppressWarnings("unchecked")
@Nullable
public List deserializeList(
final Class manifest,
final byte[] body,
final int size) throws IOException {
if (manifest == null) throw new IllegalArgumentException("manifest can't be null");
if (body == null) throw new IllegalArgumentException("body can't be null");
if (size == 4 && body[0] == 'n' && body[1] == 'u' && body[2] == 'l' && body[3] == 'l') {
return null;
} else if (size == 2 && body[0] == '[' && body[1] == ']') {
return new ArrayList(0);
}
final JsonReader json = localReader.get().process(body, size);
try {
if (json.getNextToken() != '[') {
if (json.wasNull()) {
return null;
}
throw json.newParseError("Expecting '[' for list start");
}
if (json.getNextToken() == ']') {
return new ArrayList(0);
}
//leave for now in to avoid overhead of going through redirection via generic tryFindReader
if (JsonObject.class.isAssignableFrom(manifest)) {
final JsonReader.ReadJsonObject reader = getObjectReader(manifest);
if (reader != null) {
return (List) json.deserializeNullableCollection(reader);
}
}
final JsonReader.ReadObject> simpleReader = tryFindReader(manifest);
if (simpleReader != null) {
return json.deserializeNullableCollection(simpleReader);
}
if (fallback != null) {
final Object array = Array.newInstance(manifest, 0);
final TResult[] result = (TResult[]) fallback.deserialize(context, array.getClass(), body, size);
if (result == null) {
return null;
}
final ArrayList list = new ArrayList(result.length);
for (TResult aResult : result) {
list.add(aResult);
}
return list;
}
throw createErrorMessage(manifest);
} finally {
json.reset();
}
}
/**
* This is deprecated to avoid using it.
* Use deserializeList method without the buffer argument instead.
*
* Convenient deserialize list API for working with streams.
* Deserialize provided stream input into target object.
* Use buffer for internal conversion from stream into byte[] for partial processing.
* This method creates a new instance of JsonReader.
* There is also deserializeList without the buffer which reuses thread local reader.
*
* Since JSON is often though of as a series of char,
* most libraries will convert inputs into a sequence of chars and do processing on them.
* DslJson will treat input as a sequence of bytes which allows for various optimizations.
*
* When working on InputStream DslJson will process JSON in chunks of byte[] inputs.
* Provided buffer will be used as input for partial processing.
*
* For best performance buffer should be reused.
*
* @param manifest target type
* @param stream input JSON
* @param buffer buffer used for InputStream -> byte[] conversion
* @param target element type
* @return deserialized list
* @throws IOException error during deserialization
*/
@SuppressWarnings("unchecked")
@Nullable
public List deserializeList(
final Class manifest,
final InputStream stream,
final byte[] buffer) throws IOException {
if (manifest == null) throw new IllegalArgumentException("manifest can't be null");
if (stream == null) throw new IllegalArgumentException("stream can't be null");
if (buffer == null) throw new IllegalArgumentException("buffer can't be null");
return deserializeList(manifest, newReader(stream, buffer), stream);
}
/**
* Convenient deserialize list API for working with streams.
* Deserialize provided stream input into target object.
*
* Since JSON is often though of as a series of char,
* most libraries will convert inputs into a sequence of chars and do processing on them.
* DslJson will treat input as a sequence of bytes which allows for various optimizations.
*
* When working on InputStream DslJson will process JSON in chunks of byte[] inputs.
*
*
* @param manifest target type
* @param stream input JSON
* @param target element type
* @return deserialized list
* @throws IOException error during deserialization
*/
@SuppressWarnings("unchecked")
@Nullable
public List deserializeList(
final Class manifest,
final InputStream stream) throws IOException {
if (manifest == null) throw new IllegalArgumentException("manifest can't be null");
if (stream == null) throw new IllegalArgumentException("stream can't be null");
final JsonReader json = localReader.get().process(stream);
try {
return deserializeList(manifest, json, stream);
} finally {
json.reset();
}
}
@SuppressWarnings("unchecked")
@Nullable
private List deserializeList(
final Class manifest,
JsonReader json,
InputStream stream) throws IOException {
if (json.getNextToken() != '[') {
if (json.wasNull()) {
return null;
}
throw json.newParseError("Expecting '[' for list start");
}
if (json.getNextToken() == ']') {
return new ArrayList(0);
}
//leave for now in to avoid overhead of going through redirection via generic tryFindReader
if (JsonObject.class.isAssignableFrom(manifest)) {
final JsonReader.ReadJsonObject reader = getObjectReader(manifest);
if (reader != null) {
return (List) json.deserializeNullableCollection(reader);
}
}
final JsonReader.ReadObject simpleReader = tryFindReader(manifest);
if (simpleReader != null) {
return json.deserializeNullableCollection(simpleReader);
}
if (fallback != null) {
final Object array = Array.newInstance(manifest, 0);
final TResult[] result = (TResult[]) fallback.deserialize(context, array.getClass(), new RereadStream(json.buffer, stream));
if (result == null) {
return null;
}
final ArrayList list = new ArrayList(result.length);
for (TResult aResult : result) {
list.add(aResult);
}
return list;
}
throw createErrorMessage(manifest);
}
/**
* Convenient deserialize API for working with streams.
* Deserialize provided stream input into target object.
* This method accepts a buffer and will create a new reader using provided buffer.
* This buffer is used for internal conversion from stream into byte[] for partial processing.
* There is also method without the buffer which reuses local thread reader for processing.
*
* Since JSON is often though of as a series of char,
* most libraries will convert inputs into a sequence of chars and do processing on them.
* DslJson will treat input as a sequence of bytes which allows for various optimizations.
*
* When working on InputStream DslJson will process JSON in chunks of byte[] inputs.
* Provided buffer will be used as input for partial processing.
*
* For best performance buffer should be reused.
*
* @param manifest target type
* @param stream input JSON
* @param buffer buffer used for InputStream -> byte[] conversion
* @param target type
* @return deserialized instance
* @throws IOException error during deserialization
*/
@SuppressWarnings("unchecked")
@Nullable
public TResult deserialize(
final Class manifest,
final InputStream stream,
final byte[] buffer) throws IOException {
if (manifest == null) {
throw new IllegalArgumentException("manifest can't be null");
}
if (stream == null) {
throw new IllegalArgumentException("stream can't be null");
}
if (buffer == null) {
throw new IllegalArgumentException("buffer can't be null");
}
return deserialize(manifest, newReader(stream, buffer), stream);
}
/**
* Convenient deserialize API for working with streams.
* Deserialize provided stream input into target object.
* This method reuses thread local reader for processing input stream.
*
* Since JSON is often though of as a series of char,
* most libraries will convert inputs into a sequence of chars and do processing on them.
* DslJson will treat input as a sequence of bytes which allows for various optimizations.
*
* When working on InputStream DslJson will process JSON in chunks of byte[] inputs.
*
*
* @param manifest target type
* @param stream input JSON
* @param target type
* @return deserialized instance
* @throws IOException error during deserialization
*/
@SuppressWarnings("unchecked")
@Nullable
public TResult deserialize(
final Class manifest,
final InputStream stream) throws IOException {
if (manifest == null) {
throw new IllegalArgumentException("manifest can't be null");
}
if (stream == null) {
throw new IllegalArgumentException("stream can't be null");
}
final JsonReader json = localReader.get().process(stream);
try {
return deserialize(manifest, json, stream);
} finally {
json.reset();
}
}
@SuppressWarnings("unchecked")
@Nullable
private TResult deserialize(
final Class manifest,
final JsonReader json,
final InputStream stream) throws IOException {
json.getNextToken();
final JsonReader.ReadObject> simpleReader = tryFindReader(manifest);
if (simpleReader != null) {
return (TResult) simpleReader.read(json);
}
if (manifest.isArray()) {
if (json.wasNull()) {
return null;
} else if (json.last() != '[') {
throw json.newParseError("Expecting '[' for array start");
}
final Class> elementManifest = manifest.getComponentType();
if (json.getNextToken() == ']') {
return (TResult) Array.newInstance(elementManifest, 0);
}
//leave for now in to avoid overhead of going through redirection via generic tryFindReader
if (JsonObject.class.isAssignableFrom(elementManifest)) {
final JsonReader.ReadJsonObject objectReader = getObjectReader(elementManifest);
if (objectReader != null) {
List> list = json.deserializeNullableCollection(objectReader);
return (TResult) convertResultToArray(elementManifest, list);
}
}
final JsonReader.ReadObject> simpleElementReader = tryFindReader(elementManifest);
if (simpleElementReader != null) {
List> list = json.deserializeNullableCollection(simpleElementReader);
return (TResult) convertResultToArray(elementManifest, list);
}
}
if (fallback != null) {
return (TResult) fallback.deserialize(context, manifest, new RereadStream(json.buffer, stream));
}
throw createErrorMessage(manifest);
}
/**
* Deserialize API for working with streams.
* Deserialize provided stream input into target object.
* Use buffer for internal conversion from stream into byte[] for partial processing.
* This method creates a new instance of JsonReader for processing the stream.
* There is also a method without the byte[] buffer which reuses thread local reader.
*
* Since JSON is often though of as a series of char,
* most libraries will convert inputs into a sequence of chars and do processing on them.
* DslJson will treat input as a sequence of bytes which allows for various optimizations.
*
* When working on InputStream DslJson will process JSON in chunks of byte[] inputs.
* Provided buffer will be used as input for partial processing.
*
* For best performance buffer should be reused.
*
* @param manifest target type
* @param stream input JSON
* @param buffer buffer used for InputStream -> byte[] conversion
* @return deserialized instance
* @throws IOException error during deserialization
*/
@Nullable
public Object deserialize(
final Type manifest,
final InputStream stream,
final byte[] buffer) throws IOException {
if (manifest instanceof Class>) {
return deserialize((Class>) manifest, stream, buffer);
}
if (manifest == null) {
throw new IllegalArgumentException("manifest can't be null");
}
if (stream == null) {
throw new IllegalArgumentException("stream can't be null");
}
if (buffer == null) {
throw new IllegalArgumentException("buffer can't be null");
}
final JsonReader json = newReader(stream, buffer);
json.getNextToken();
final Object result = deserializeWith(manifest, json);
if (result != unknownValue) return result;
if (fallback != null) {
return fallback.deserialize(context, manifest, new RereadStream(buffer, stream));
}
throw new ConfigurationException("Unable to find reader for provided type: " + manifest + " and fallback serialization is not registered.\n" +
"Try initializing DslJson with custom fallback in case of unsupported objects or register specified type using registerReader into " + getClass());
}
/**
* Deserialize API for working with streams.
* Deserialize provided stream input into target object.
* This method reuses thread local reader for processing JSON input.
*
* Since JSON is often though of as a series of char,
* most libraries will convert inputs into a sequence of chars and do processing on them.
*
* When working on InputStream DslJson will process JSON in chunks of byte[] inputs.
*
*
* @param manifest target type
* @param stream input JSON
* @return deserialized instance
* @throws IOException error during deserialization
*/
@Nullable
public Object deserialize(
final Type manifest,
final InputStream stream) throws IOException {
if (manifest instanceof Class>) {
return deserialize((Class>) manifest, stream);
}
if (manifest == null) {
throw new IllegalArgumentException("manifest can't be null");
}
if (stream == null) {
throw new IllegalArgumentException("stream can't be null");
}
final JsonReader json = localReader.get().process(stream);
try {
json.getNextToken();
final Object result = deserializeWith(manifest, json);
if (result != unknownValue) return result;
if (fallback != null) {
return fallback.deserialize(context, manifest, new RereadStream(json.buffer, stream));
}
throw new ConfigurationException("Unable to find reader for provided type: " + manifest + " and fallback serialization is not registered.\n" +
"Try initializing DslJson with custom fallback in case of unsupported objects or register specified type using registerReader into " + getClass());
} finally {
json.reset();
}
}
static class RereadStream extends InputStream {
private final byte[] buffer;
private final InputStream stream;
private boolean usingBuffer;
private int position;
RereadStream(byte[] buffer, InputStream stream) {
this.buffer = buffer;
this.stream = stream;
usingBuffer = true;
}
@Override
public int read() throws IOException {
if (usingBuffer) {
if (position < buffer.length) {
return buffer[position++];
} else usingBuffer = false;
}
return stream.read();
}
@Override
public int read(byte[] buf) throws IOException {
if (usingBuffer) {
return super.read(buf);
}
return stream.read(buf);
}
@Override
public int read(byte[] buf, int off, int len) throws IOException {
if (usingBuffer) {
return super.read(buf, off, len);
}
return stream.read(buf, off, len);
}
}
private static final Iterator EMPTY_ITERATOR = new Iterator() {
@Override
public boolean hasNext() {
return false;
}
@Override
public void remove() {
}
@Nullable
@Override
public Object next() {
return null;
}
};
/**
* Streaming API for collection deserialization.
* DslJson will create iterator based on provided manifest info.
* It will attempt to deserialize from stream on each next() invocation.
* This method requires buffer instance for partial stream processing.
* It will create a new instance of JsonReader.
* There is also a method without the buffer which will reuse thread local reader.
*
* Useful for processing very large streams if only one instance from collection is required at once.
*
* Stream will be processed in chunks of specified buffer byte[].
* It will block on reading until buffer is full or end of stream is detected.
*
* @param manifest type info
* @param stream JSON data stream
* @param type info
* @return Iterator to instances deserialized from input JSON
* @throws IOException if reader is not found or there is an error processing input stream
*/
@SuppressWarnings("unchecked")
@Nullable
public Iterator iterateOver(
final Class manifest,
final InputStream stream) throws IOException {
if (manifest == null) {
throw new IllegalArgumentException("manifest can't be null");
}
if (stream == null) {
throw new IllegalArgumentException("stream can't be null");
}
final JsonReader json = localReader.get();
json.process(stream);
return iterateOver(manifest, json, stream);
}
/**
* Streaming API for collection deserialization.
* DslJson will create iterator based on provided manifest info.
* It will attempt to deserialize from stream on each next() invocation.
* This method requires buffer instance for partial stream processing.
* It will create a new instance of JsonReader.
* There is also a method without the buffer which will reuse thread local reader.
*
* Useful for processing very large streams if only one instance from collection is required at once.
*
* Stream will be processed in chunks of specified buffer byte[].
* It will block on reading until buffer is full or end of stream is detected.
*
* @param manifest type info
* @param stream JSON data stream
* @param buffer size of processing chunk
* @param type info
* @return Iterator to instances deserialized from input JSON
* @throws IOException if reader is not found or there is an error processing input stream
*/
@SuppressWarnings("unchecked")
@Nullable
public Iterator iterateOver(
final Class manifest,
final InputStream stream,
final byte[] buffer) throws IOException {
if (manifest == null) {
throw new IllegalArgumentException("manifest can't be null");
}
if (stream == null) {
throw new IllegalArgumentException("stream can't be null");
}
if (buffer == null) {
throw new IllegalArgumentException("buffer can't be null");
}
return iterateOver(manifest, newReader(stream, buffer), stream);
}
@SuppressWarnings("unchecked")
@Nullable
private Iterator iterateOver(
final Class manifest,
final JsonReader json,
final InputStream stream) throws IOException {
if (json.getNextToken() != '[') {
if (json.wasNull()) {
return null;
}
throw json.newParseError("Expecting '[' for iterator start");
}
if (json.getNextToken() == ']') {
return EMPTY_ITERATOR;
}
//leave for now in to avoid overhead of going through redirection via generic tryFindReader
if (JsonObject.class.isAssignableFrom(manifest)) {
final JsonReader.ReadJsonObject reader = getObjectReader(manifest);
if (reader != null) {
return json.iterateOver(reader);
}
}
final JsonReader.ReadObject> simpleReader = tryFindReader(manifest);
if (simpleReader != null) {
return json.iterateOver(simpleReader);
}
if (fallback != null) {
final Object array = Array.newInstance(manifest, 0);
final TResult[] result = (TResult[]) fallback.deserialize(context, array.getClass(), new RereadStream(json.buffer, stream));
if (result == null) {
return null;
}
final ArrayList list = new ArrayList(result.length);
for (TResult aResult : result) {
list.add(aResult);
}
return list.iterator();
}
throw createErrorMessage(manifest);
}
private final JsonWriter.WriteObject OBJECT_WRITER = new JsonWriter.WriteObject() {
@Override
public void write(JsonWriter writer, @Nullable JsonObject value) {
if (value == null) writer.writeNull();
else value.serialize(writer, omitDefaults);
}
};
private JsonReader.ReadObject convertToReader(final JsonReader.ReadJsonObject decoder) {
return new JsonReader.ReadObject() {
@Override
public T read(JsonReader reader) throws IOException {
if (reader.wasNull()) return null;
else if (reader.last() != '{') throw reader.newParseError("Expecting '{' for object start");
reader.getNextToken();
return decoder.deserialize(reader);
}
};
}
private final JsonWriter.WriteObject OBJECT_ARRAY_WRITER = new JsonWriter.WriteObject() {
@Override
public void write(JsonWriter writer, @Nullable Object value) {
serialize(writer, (JsonObject[]) value);
}
};
private static final JsonWriter.WriteObject CHAR_ARRAY_WRITER = new JsonWriter.WriteObject() {
@Override
public void write(JsonWriter writer, @Nullable Object value) {
StringConverter.serialize(new String((char[]) value), writer);
}
};
private final JsonWriter.WriteObject NULL_WRITER = new JsonWriter.WriteObject() {
@Override
public void write(JsonWriter writer, @Nullable Object value) {
writer.writeNull();
}
};
@SuppressWarnings("unchecked")
private JsonWriter.WriteObject getOrCreateWriter(@Nullable final Object instance, final Class> instanceManifest) throws IOException {
if (instance instanceof JsonObject) {
return OBJECT_WRITER;
}
if (instance instanceof JsonObject[]) {
return OBJECT_ARRAY_WRITER;
}
final Class> manifest = instanceManifest != null ? instanceManifest : instance.getClass();
if (instanceManifest != null) {
if (JsonObject.class.isAssignableFrom(manifest)) {
return OBJECT_WRITER;
}
}
final JsonWriter.WriteObject simpleWriter = tryFindWriter(manifest);
if (simpleWriter != null) {
return simpleWriter;
}
if (manifest.isArray()) {
final Class> elementManifest = manifest.getComponentType();
if (char.class == elementManifest) {
return CHAR_ARRAY_WRITER;
} else {
final JsonWriter.WriteObject elementWriter = tryFindWriter(elementManifest);
if (elementWriter != null) {
//TODO: cache writer for next lookup
return new JsonWriter.WriteObject() {
@Override
public void write(JsonWriter writer, @Nullable Object value) {
writer.serialize((Object[]) value, elementWriter);
}
};
}
}
}
if (instance instanceof Collection || Collection.class.isAssignableFrom(manifest)) {
return new JsonWriter.WriteObject() {
@Override
public void write(JsonWriter writer, @Nullable final Object value) {
final Collection items = (Collection) value;
Class> baseType = null;
final Iterator iterator = items.iterator();
//TODO: pick lowest common denominator!?
do {
final Object item = iterator.next();
if (item != null) {
Class> elementType = item.getClass();
if (elementType != baseType) {
if (baseType == null || elementType.isAssignableFrom(baseType)) {
baseType = elementType;
}
}
}
} while (iterator.hasNext());
if (baseType == null) {
writer.writeByte(JsonWriter.ARRAY_START);
writer.writeNull();
for (int i = 1; i < items.size(); i++) {
writer.writeAscii(",null");
}
writer.writeByte(JsonWriter.ARRAY_END);
} else if (JsonObject.class.isAssignableFrom(baseType)) {
serialize(writer, (Collection) items);
} else {
final JsonWriter.WriteObject elementWriter = tryFindWriter(baseType);
if (elementWriter != null) {
writer.serialize(items, elementWriter);
} else if (fallback != null) {
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
stream.reset();
try {
fallback.serialize(value, stream);
} catch (IOException ex) {
throw new SerializationException(ex);
}
writer.writeAscii(stream.toByteArray());
} else {
throw new ConfigurationException("Unable to serialize provided object. Failed to find serializer for: " + items.getClass());
}
}
}
};
}
throw new ConfigurationException("Unable to serialize provided object. Failed to find serializer for: " + manifest);
}
/**
* Streaming API for collection serialization.
*
* It will iterate over entire iterator and serialize each instance into target output stream.
* After each instance serialization it will copy JSON into target output stream.
* During each serialization reader will be looked up based on next() instance which allows
* serializing collection with different types.
* If collection contains all instances of the same type, prefer the other streaming API.
*
* If reader is not found an IOException will be thrown
*
* If JsonWriter is provided it will be used, otherwise a new instance will be internally created.
*
* @param iterator input data
* @param stream target JSON stream
* @param writer temporary buffer for serializing a single item. Can be null
* @param input data type
* @throws IOException reader is not found, there is an error during serialization or problem with writing to target stream
*/
@SuppressWarnings("unchecked")
public void iterateOver(
final Iterator iterator,
final OutputStream stream,
@Nullable final JsonWriter writer) throws IOException {
if (iterator == null) {
throw new IllegalArgumentException("iterator can't be null");
}
if (stream == null) {
throw new IllegalArgumentException("stream can't be null");
}
stream.write(JsonWriter.ARRAY_START);
if (!iterator.hasNext()) {
stream.write(JsonWriter.ARRAY_END);
return;
}
final JsonWriter buffer = writer == null ? new JsonWriter(this) : writer;
T item = iterator.next();
Class> lastManifest = null;
JsonWriter.WriteObject lastWriter = null;
if (item != null) {
lastManifest = item.getClass();
lastWriter = getOrCreateWriter(item, lastManifest);
buffer.reset();
try {
lastWriter.write(buffer, item);
} catch (ConfigurationException e) {
throw e;
} catch (Exception e) {
throw new IOException(e);
}
buffer.toStream(stream);
} else {
stream.write(NULL);
}
while (iterator.hasNext()) {
stream.write(JsonWriter.COMMA);
item = iterator.next();
if (item != null) {
final Class> currentManifest = item.getClass();
if (lastWriter == null || lastManifest == null || !lastManifest.equals(currentManifest)) {
lastManifest = currentManifest;
lastWriter = getOrCreateWriter(item, lastManifest);
}
buffer.reset();
try {
lastWriter.write(buffer, item);
} catch (ConfigurationException e) {
throw e;
} catch (Exception e) {
throw new IOException(e);
}
buffer.toStream(stream);
} else {
stream.write(NULL);
}
}
stream.write(JsonWriter.ARRAY_END);
}
/**
* Streaming API for collection serialization.
*
* It will iterate over entire iterator and serialize each instance into target output stream.
* After each instance serialization it will copy JSON into target output stream.
*
* If reader is not found an IOException will be thrown
*
* If JsonWriter is provided it will be used, otherwise a new instance will be internally created.
*
* @param iterator input data
* @param manifest type of elements in collection
* @param stream target JSON stream
* @param writer temporary buffer for serializing a single item. Can be null
* @param input data type
* @throws IOException reader is not found, there is an error during serialization or problem with writing to target stream
*/
@SuppressWarnings("unchecked")
public void iterateOver(
final Iterator iterator,
final Class manifest,
final OutputStream stream,
@Nullable final JsonWriter writer) throws IOException {
if (iterator == null) {
throw new IllegalArgumentException("iterator can't be null");
}
if (manifest == null) {
throw new IllegalArgumentException("manifest can't be null");
}
if (stream == null) {
throw new IllegalArgumentException("stream can't be null");
}
final JsonWriter buffer = writer == null ? new JsonWriter(this) : writer;
final JsonWriter.WriteObject instanceWriter = getOrCreateWriter(null, manifest);
stream.write(JsonWriter.ARRAY_START);
T item = iterator.next();
if (item != null) {
buffer.reset();
try {
instanceWriter.write(buffer, item);
} catch (ConfigurationException e) {
throw e;
} catch (Exception e) {
throw new IOException(e);
}
buffer.toStream(stream);
} else {
stream.write(NULL);
}
while (iterator.hasNext()) {
stream.write(JsonWriter.COMMA);
item = iterator.next();
if (item != null) {
buffer.reset();
try {
instanceWriter.write(buffer, item);
} catch (ConfigurationException e) {
throw e;
} catch (Exception e) {
throw new IOException(e);
}
buffer.toStream(stream);
} else {
stream.write(NULL);
}
}
stream.write(JsonWriter.ARRAY_END);
}
/**
* Use writer.serialize instead
*
* @param writer writer
* @param array items
* @param type
*/
@Deprecated
public void serialize(final JsonWriter writer, @Nullable final T[] array) {
if (array == null) {
writer.writeNull();
return;
}
writer.writeByte(JsonWriter.ARRAY_START);
if (array.length != 0) {
T item = array[0];
if (item != null) {
item.serialize(writer, omitDefaults);
} else {
writer.writeNull();
}
for (int i = 1; i < array.length; i++) {
writer.writeByte(JsonWriter.COMMA);
item = array[i];
if (item != null) {
item.serialize(writer, omitDefaults);
} else {
writer.writeNull();
}
}
}
writer.writeByte(JsonWriter.ARRAY_END);
}
/**
* Use writer.serialize instead
*
* @param writer writer
* @param array items
* @param len part of array
* @param type
*/
@Deprecated
public void serialize(final JsonWriter writer, final T[] array, final int len) {
if (writer == null) {
throw new IllegalArgumentException("writer can't be null");
}
if (array == null) {
writer.writeNull();
return;
}
writer.writeByte(JsonWriter.ARRAY_START);
if (len != 0) {
T item = array[0];
if (item != null) {
item.serialize(writer, omitDefaults);
} else {
writer.writeNull();
}
for (int i = 1; i < len; i++) {
writer.writeByte(JsonWriter.COMMA);
item = array[i];
if (item != null) {
item.serialize(writer, omitDefaults);
} else {
writer.writeNull();
}
}
}
writer.writeByte(JsonWriter.ARRAY_END);
}
/**
* Use writer.serialize instead
*
* @param writer writer
* @param list items
* @param type
*/
@Deprecated
public void serialize(final JsonWriter writer, @Nullable final List list) {
if (writer == null) {
throw new IllegalArgumentException("writer can't be null");
}
if (list == null) {
writer.writeNull();
return;
}
writer.writeByte(JsonWriter.ARRAY_START);
if (list.size() != 0) {
T item = list.get(0);
if (item != null) {
item.serialize(writer, omitDefaults);
} else {
writer.writeNull();
}
for (int i = 1; i < list.size(); i++) {
writer.writeByte(JsonWriter.COMMA);
item = list.get(i);
if (item != null) {
item.serialize(writer, omitDefaults);
} else {
writer.writeNull();
}
}
}
writer.writeByte(JsonWriter.ARRAY_END);
}
/**
* Use writer.serialize instead
*
* @param writer writer
* @param collection items
* @param type
*/
@Deprecated
public void serialize(final JsonWriter writer, @Nullable final Collection collection) {
if (writer == null) {
throw new IllegalArgumentException("writer can't be null");
}
if (collection == null) {
writer.writeNull();
return;
}
writer.writeByte(JsonWriter.ARRAY_START);
if (!collection.isEmpty()) {
final Iterator it = collection.iterator();
T item = it.next();
if (item != null) {
item.serialize(writer, omitDefaults);
} else {
writer.writeNull();
}
while (it.hasNext()) {
writer.writeByte(JsonWriter.COMMA);
item = it.next();
if (item != null) {
item.serialize(writer, omitDefaults);
} else {
writer.writeNull();
}
}
}
writer.writeByte(JsonWriter.ARRAY_END);
}
/**
* Generic serialize API.
* Based on provided type manifest converter will be chosen.
* If converter is not found method will return false.
*
* Resulting JSON will be written into provided writer argument.
* In case of successful serialization true will be returned.
*
* For best performance writer argument should be reused.
*
* @param writer where to write resulting JSON
* @param manifest type manifest
* @param value instance to serialize
* @return successful serialization
*/
@SuppressWarnings("unchecked")
public boolean serialize(final JsonWriter writer, final Type manifest, @Nullable final Object value) {
if (writer == null) {
throw new IllegalArgumentException("writer can't be null");
}
if (value == null) {
writer.writeNull();
return true;
} else if (value instanceof JsonObject) {
((JsonObject) value).serialize(writer, omitDefaults);
return true;
} else if (value instanceof JsonObject[]) {
serialize(writer, (JsonObject[]) value);
return true;
}
final JsonWriter.WriteObject simpleWriter = tryFindWriter(manifest);
if (simpleWriter != null) {
simpleWriter.write(writer, value);
return true;
}
Class> container = null;
if (manifest instanceof Class>) {
container = (Class>) manifest;
}
if (container != null && container.isArray()) {
if (Array.getLength(value) == 0) {
writer.writeAscii("[]");
return true;
}
final Class> elementManifest = container.getComponentType();
if (char.class == elementManifest) {
//TODO? char[] !?
StringConverter.serialize(new String((char[]) value), writer);
return true;
} else {
final JsonWriter.WriteObject elementWriter = (JsonWriter.WriteObject) tryFindWriter(elementManifest);
if (elementWriter != null) {
writer.serialize((Object[]) value, elementWriter);
return true;
}
}
}
if (value instanceof Collection) {
final Collection items = (Collection) value;
if (items.isEmpty()) {
writer.writeAscii("[]");
return true;
}
Class> baseType = null;
final Iterator iterator = items.iterator();
final boolean isList = items instanceof List;
final List