com.dslplatform.json.runtime.MixinDescription Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dsl-json-java8 Show documentation
Show all versions of dsl-json-java8 Show documentation
DSL Platform compatible Java JSON library (https://dsl-platform.com)
package com.dslplatform.json.runtime;
import com.dslplatform.json.*;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Set;
public final class MixinDescription implements JsonWriter.WriteObject, JsonReader.ReadObject {
private static final Charset utf8 = Charset.forName("UTF-8");
private static final int defaultTypeHash = DecodePropertyInfo.calcHash("$type");
private static final byte[] defaultObjectStart = "{\"$type\":".getBytes(utf8);
private final int typeHash;
private final byte[] objectStart;
private final Type manifest;
private final FormatDescription[] descriptions;
private final boolean alwaysSerialize;
private final boolean exactMatch;
private final boolean canObjectFormat;
private final boolean canArrayFormat;
private final String discriminator;
private final String discriminatorError;
public MixinDescription(
final Class manifest,
final DslJson json,
final FormatDescription[] descriptions) {
this(manifest, json, descriptions, null);
}
public MixinDescription(
final Class manifest,
final DslJson json,
final String discriminator,
final FormatDescription[] descriptions) {
this(manifest, json, descriptions, discriminator);
}
MixinDescription(
final Type manifest,
final DslJson json,
final FormatDescription[] descriptions,
@Nullable final String discriminator) {
if (manifest == null) throw new IllegalArgumentException("manifest can't be null");
if (descriptions == null || descriptions.length == 0) {
throw new IllegalArgumentException("descriptions can't be null or empty");
}
if (discriminator != null && (discriminator.length() == 0 || discriminator.contains("\""))) {
throw new IllegalArgumentException("Invalid discriminator provided: " + discriminator);
}
this.typeHash = discriminator == null ? defaultTypeHash : DecodePropertyInfo.calcHash(discriminator);
this.objectStart = discriminator == null ? defaultObjectStart : ("{\"" + discriminator + "\":").getBytes(utf8);
this.discriminator = discriminator == null ? "$type" : discriminator;
this.manifest = manifest;
this.descriptions = descriptions;
Set uniqueHashNames = new HashSet<>();
boolean canObject = false;
boolean canArray = false;
for (FormatDescription od : descriptions) {
uniqueHashNames.add(od.typeHash);
canObject = canObject || od.objectFormat != null;
canArray = canArray || od.arrayFormat != null;
}
this.alwaysSerialize = !json.omitDefaults;
this.canObjectFormat = canObject;
this.canArrayFormat = canArray;
this.exactMatch = uniqueHashNames.size() != descriptions.length;
this.discriminatorError = String.format("Expecting \"%s\" attribute as first element of mixin %s", this.discriminator, Reflection.typeDescription(manifest));
}
@Nullable
public T read(final JsonReader reader) throws IOException {
if (reader.wasNull()) return null;
if (reader.last() == '{' && canObjectFormat) {
return readObjectFormat(reader);
} else if (canArrayFormat && reader.last() == '[') {
return readArrayFormat(reader);
}
if (canObjectFormat && canArrayFormat) {
throw reader.newParseError("Expecting '{' or '[' for object start");
} else if (canObjectFormat) {
throw reader.newParseError("Expecting '{' for object start");
} else {
throw reader.newParseError("Expecting '[' for object start");
}
}
@Nullable
private T readObjectFormat(final JsonReader reader) throws IOException {
if (reader.getNextToken() != JsonWriter.QUOTE) {
throw reader.newParseError(discriminatorError);
}
if (reader.fillName() != typeHash) {
String name = reader.getLastName();
throw reader.newParseErrorFormat(discriminatorError, name.length() + 2, "Expecting \"%s\" attribute as first element of mixin %s. Found: '%s'", discriminator, Reflection.typeDescription(manifest), name);
}
reader.getNextToken();
final int hash = reader.calcHash();
for (final FormatDescription od : descriptions) {
if (od.objectFormat == null || od.typeHash != hash) continue;
if (exactMatch && !reader.wasLastName(od.typeName)) continue;
final FormatConverter ofd = od.objectFormat;
if (reader.getNextToken() == JsonWriter.COMMA) {
reader.getNextToken();
}
return ofd.readContent(reader);
}
throw new ConfigurationException("Unable to find decoder for '" + reader.getLastName() + "' for mixin: " + Reflection.typeDescription(manifest) + " which supports object format. Add @CompiledJson to specified type to allow deserialization into it");
}
@Nullable
private T readArrayFormat(final JsonReader reader) throws IOException {
if (reader.getNextToken() != JsonWriter.QUOTE) {
throw reader.newParseError(discriminatorError);
}
reader.getNextToken();
final int hash = reader.calcHash();
for (final FormatDescription od : descriptions) {
if (od.arrayFormat == null || od.typeHash != hash) continue;
if (exactMatch && !reader.wasLastName(od.typeName)) continue;
final FormatConverter afd = od.arrayFormat;
if (reader.getNextToken() == JsonWriter.COMMA) {
reader.getNextToken();
}
return afd.readContent(reader);
}
throw new ConfigurationException("Unable to find decoder for '" + reader.getLastName() + "' for mixin: " + Reflection.typeDescription(manifest) + " which supports array format. Add @CompiledJson to specified type to allow deserialization into it");
}
@Override
public void write(final JsonWriter writer, @Nullable final T instance) {
if (instance == null) {
writer.writeNull();
return;
}
final Class> current = instance.getClass();
for (FormatDescription od : descriptions) {
if (current != od.manifest) continue;
if (od.isObjectFormatFirst) {
writer.writeAscii(objectStart);
writer.writeAscii(od.quotedTypeName);
FormatConverter ofd = od.objectFormat;
if (alwaysSerialize) {
writer.writeByte(JsonWriter.COMMA);
final int pos = writer.size();
final long flushed = writer.flushed();
ofd.writeContentFull(writer, instance);
if (pos != writer.size() || flushed != writer.flushed()) {
writer.writeByte(JsonWriter.OBJECT_END);
} else {
writer.getByteBuffer()[writer.size() - 1] = JsonWriter.OBJECT_END;
}
} else {
writer.writeByte(JsonWriter.COMMA);
ofd.writeContentMinimal(writer, instance);
writer.getByteBuffer()[writer.size() - 1] = JsonWriter.OBJECT_END;
}
} else {
writer.writeByte(JsonWriter.ARRAY_START);
writer.writeAscii(od.quotedTypeName);
od.arrayFormat.writeContentFull(writer, instance);
writer.writeByte(JsonWriter.ARRAY_END);
}
return;
}
throw new ConfigurationException("Unable to find encoder for '" + instance.getClass() + "'. Add @CompiledJson to specified type to allow serialization from it");
}
}