All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dslplatform.json.runtime.MixinDescription Maven / Gradle / Ivy

The newest version!
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, ExplicitDescription {

	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");
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy