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

com.dslplatform.json.processor.CompiledJsonAnnotationProcessor Maven / Gradle / Ivy

There is a newer version: 1.10.0
Show newest version
package com.dslplatform.json.processor;

import com.dslplatform.json.CompiledJson;
import com.dslplatform.json.DslJson;
import com.dslplatform.json.runtime.Settings;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.tools.Diagnostic;
import javax.tools.StandardLocation;
import java.io.*;
import java.lang.reflect.Type;
import java.util.*;

import static com.dslplatform.json.processor.Context.nonGenericObject;
import static com.dslplatform.json.processor.Context.typeOrClass;

@SupportedAnnotationTypes({"com.dslplatform.json.CompiledJson", "com.dslplatform.json.JsonAttribute", "com.dslplatform.json.JsonConverter", "com.fasterxml.jackson.annotation.JsonCreator", "javax.json.bind.annotation.JsonbCreator"})
@SupportedOptions({"dsljson.loglevel", "dsljson.annotation", "dsljson.unknown", "dsljson.inline", "dsljson.jackson", "dsljson.jsonb"})
public class CompiledJsonAnnotationProcessor extends AbstractProcessor {

	private static final Set JsonIgnore;
	private static final Map>> NonNullable;
	private static final Map PropertyAlias;
	private static final Map>> JsonRequired;
	private static final Set Constructors;
	private static final Map Indexes;
	private static final Map InlinedConverters;
	private static final Map Defaults;

	private static final String CONFIG = "META-INF/services/com.dslplatform.json.Configuration";

	static {
		JsonIgnore = new HashSet<>();
		JsonIgnore.add("com.fasterxml.jackson.annotation.JsonIgnore");
		JsonIgnore.add("javax.json.bind.annotation.JsonbTransient");
		NonNullable = new HashMap<>();
		NonNullable.put("javax.validation.constraints.NotNull", null);
		NonNullable.put("javax.annotation.Nonnull", null);
		NonNullable.put("android.support.annotation.NonNull", null);
		NonNullable.put("org.jetbrains.annotations.NotNull", null);
		NonNullable.put(
				"javax.json.bind.annotation.JsonbNillable",
				Arrays.asList(
						new Analysis.AnnotationMapping<>("value()", null),
						new Analysis.AnnotationMapping<>("value()", true)));
		NonNullable.put(
				"javax.json.bind.annotation.JsonbProperty",
				Collections.singletonList(new Analysis.AnnotationMapping<>("nillable()", true)));
		PropertyAlias = new HashMap<>();
		PropertyAlias.put("com.fasterxml.jackson.annotation.JsonProperty", "value()");
		PropertyAlias.put("com.google.gson.annotations.SerializedName", "value()");
		PropertyAlias.put("javax.json.bind.annotation.JsonbProperty", "value()");
		JsonRequired = new HashMap<>();
		JsonRequired.put(
				"com.fasterxml.jackson.annotation.JsonProperty",
				Collections.singletonList(new Analysis.AnnotationMapping<>("required()", true)));
		Constructors = new HashSet<>();
		Constructors.add("com.fasterxml.jackson.annotation.JsonCreator");
		Constructors.add("javax.json.bind.annotation.JsonbCreator");
		Indexes = new HashMap<>();
		Indexes.put("com.fasterxml.jackson.annotation.JsonProperty", "index()");
		InlinedConverters = new HashMap<>();
		InlinedConverters.put("int", new OptimizedConverter("com.dslplatform.json.NumberConverter", "INT_WRITER", "serialize", "INT_READER", "deserializeInt", "0"));
		InlinedConverters.put("int[]", new OptimizedConverter("com.dslplatform.json.NumberConverter", "INT_ARRAY_WRITER", "serialize", "INT_ARRAY_READER"));
		InlinedConverters.put("java.lang.Integer", new OptimizedConverter("com.dslplatform.json.NumberConverter", "INT_WRITER", "serialize", "NULLABLE_INT_READER", "deserializeInt", null));
		InlinedConverters.put("long", new OptimizedConverter("com.dslplatform.json.NumberConverter", "LONG_WRITER", "serialize", "LONG_READER", "deserializeLong", "0L"));
		InlinedConverters.put("long[]", new OptimizedConverter("com.dslplatform.json.NumberConverter", "LONG_ARRAY_WRITER", "serialize", "LONG_ARRAY_READER"));
		InlinedConverters.put("java.lang.Long", new OptimizedConverter("com.dslplatform.json.NumberConverter", "LONG_WRITER", "serialize", "LONG_READER", "deserializeLong", null));
		InlinedConverters.put("float", new OptimizedConverter("com.dslplatform.json.NumberConverter", "FLOAT_WRITER", "serialize", "FLOAT_READER", "deserializeFloat", "0.0"));
		InlinedConverters.put("float[]", new OptimizedConverter("com.dslplatform.json.NumberConverter", "FLOAT_ARRAY_WRITER", "serialize", "FLOAT_ARRAY_READER"));
		InlinedConverters.put("java.lang.Float", new OptimizedConverter("com.dslplatform.json.NumberConverter", "FLOAT_WRITER", "serialize", "NULLABLE_FLOAT_READER", "deserializeFloat", null));
		InlinedConverters.put("double", new OptimizedConverter("com.dslplatform.json.NumberConverter", "DOUBLE_WRITER", "serialize", "DOUBLE_READER", "deserializeDouble", "0.0"));
		InlinedConverters.put("double[]", new OptimizedConverter("com.dslplatform.json.NumberConverter", "DOUBLE_ARRAY_WRITER", "serialize", "DOUBLE_ARRAY_READER"));
		InlinedConverters.put("java.lang.Double", new OptimizedConverter("com.dslplatform.json.NumberConverter", "DOUBLE_WRITER", "serialize", "NULLABLE_DOUBLE_READER", "deserializeDouble", null));
		InlinedConverters.put("boolean", new OptimizedConverter("com.dslplatform.json.BoolConverter", "WRITER", "serialize", "READER", "deserialize", "false"));
		InlinedConverters.put("boolean[]", new OptimizedConverter("com.dslplatform.json.BoolConverter", "ARRAY_WRITER", "serialize", "ARRAY_READER"));
		InlinedConverters.put("java.lang.Boolean", new OptimizedConverter("com.dslplatform.BoolConverter", "WRITER", "serialize", "NULLABLE_READER", "deserialize", null));
		InlinedConverters.put("java.lang.String", new OptimizedConverter("com.dslplatform.json.StringConverter", "WRITER", "serialize", "READER", "deserialize", null));
		InlinedConverters.put("java.util.UUID", new OptimizedConverter("com.dslplatform.json.UUIDConverter", "WRITER", "serialize", "READER", "deserialize", null));
		InlinedConverters.put("java.time.LocalDate", new OptimizedConverter("com.dslplatform.json.JavaTimeConverter", "LOCAL_DATE_WRITER", "serialize", "LOCAL_DATE_READER", "deserializeLocalDate", null));
		InlinedConverters.put("java.time.OffsetDateTime", new OptimizedConverter("com.dslplatform.json.JavaTimeConverter", "DATE_TIME_READER", "serialize", "DATE_TIME_WRITER", "deserializeDateTime", null));
		Defaults = new HashMap<>();
		Defaults.put("byte", "(byte)0");
		Defaults.put("boolean", "false");
		Defaults.put("int", "0");
		Defaults.put("long", "0L");
		Defaults.put("short", "(short)0");
		Defaults.put("double", "0.0");
		Defaults.put("float", "0.0f");
		Defaults.put("char", "'\0'");
		Defaults.put("java.util.OptionalLong", "java.util.OptionalLong.empty()");
		Defaults.put("java.util.OptionalInt", "java.util.OptionalInt.empty()");
		Defaults.put("java.util.OptionalDouble", "java.util.OptionalDouble.empty()");
		Defaults.put("java.util.Optional", "java.util.Optional.empty()");
	}

	private LogLevel logLevel = LogLevel.ERRORS;
	private AnnotationUsage annotationUsage = AnnotationUsage.IMPLICIT;
	private UnknownTypes unknownTypes = UnknownTypes.ERROR;
	private boolean allowInline = true;
	private boolean withJackson = false;
	private boolean withJsonb = false;

	private TypeElement jacksonCreatorElement;
	private DeclaredType jacksonCreatorType;
	private TypeElement jsonbCreatorElement;
	private DeclaredType jsonbCreatorType;

	@Override
	public synchronized void init(ProcessingEnvironment processingEnv) {
		super.init(processingEnv);
		Map options = processingEnv.getOptions();
		String ll = options.get("dsljson.loglevel");
		if (ll != null && ll.length() > 0) {
			logLevel = LogLevel.valueOf(ll);
		}
		String au = options.get("dsljson.annotation");
		if (au != null && au.length() > 0) {
			annotationUsage = AnnotationUsage.valueOf(au);
		}
		String unk = options.get("dsljson.unknown");
		if (unk != null && unk.length() > 0) {
			unknownTypes = UnknownTypes.valueOf(unk);
		}
		String inl = options.get("dsljson.inline");
		if (inl != null && inl.length() > 0) {
			allowInline = Boolean.parseBoolean(inl);
		}
		String jks = options.get("dsljson.jackson");
		if (jks != null && jks.length() > 0) {
			withJackson = Boolean.parseBoolean(jks);
		}
		String jsb = options.get("dsljson.jsonb");
		if (jsb != null && jsb.length() > 0) {
			withJsonb = Boolean.parseBoolean(jsb);
		}
		jacksonCreatorElement = processingEnv.getElementUtils().getTypeElement("com.fasterxml.jackson.annotation.JsonCreator");
		jacksonCreatorType = jacksonCreatorElement != null ? processingEnv.getTypeUtils().getDeclaredType(jacksonCreatorElement) : null;
		jsonbCreatorElement = processingEnv.getElementUtils().getTypeElement("javax.json.bind.annotation.JsonbCreator");
		jsonbCreatorType = jsonbCreatorElement != null ? processingEnv.getTypeUtils().getDeclaredType(jsonbCreatorElement) : null;
	}

	@Override
	public boolean process(Set annotations, RoundEnvironment roundEnv) {
		if (roundEnv.processingOver()) {
			return false;
		}
		final DslJson dslJson = new DslJson<>(Settings.withRuntime().includeServiceLoader());
		Set knownEncoders = dslJson.getRegisteredEncoders();
		Set knownDecoders = dslJson.getRegisteredDecoders();
		Set allTypes = new HashSet<>();
		for (Type t : knownEncoders) {
			if (knownDecoders.contains(t)) {
				allTypes.add(t.getTypeName());
			}
		}
		final Analysis analysis = new Analysis(
				processingEnv,
				annotationUsage,
				logLevel,
				allTypes,
				rawClass -> {
					try {
						Class raw = Class.forName(rawClass);
						return dslJson.canSerialize(raw) && dslJson.canDeserialize(raw);
					} catch (Exception ignore) {
						return false;
					}
				},
				JsonIgnore,
				NonNullable,
				PropertyAlias,
				JsonRequired,
				Constructors,
				Indexes,
				unknownTypes,
				false,
				true,
				true,
				true);
		Set compiledJsons = roundEnv.getElementsAnnotatedWith(analysis.compiledJsonElement);
		Set jacksonCreators = withJackson && jacksonCreatorElement != null ? roundEnv.getElementsAnnotatedWith(jacksonCreatorElement) : new HashSet<>();
		Set jsonbCreators = withJsonb && jsonbCreatorElement != null ? roundEnv.getElementsAnnotatedWith(jsonbCreatorElement) : new HashSet<>();
		if (!compiledJsons.isEmpty() || !jacksonCreators.isEmpty() || !jsonbCreators.isEmpty()) {
			Set jsonConverters = roundEnv.getElementsAnnotatedWith(analysis.converterElement);
			List configurations = analysis.processConverters(jsonConverters);
			analysis.processAnnotation(analysis.compiledJsonType, compiledJsons);
			if (!jacksonCreators.isEmpty() && jacksonCreatorType != null) {
				analysis.processAnnotation(jacksonCreatorType, jacksonCreators);
			}
			if (!jsonbCreators.isEmpty() && jsonbCreatorType != null) {
				analysis.processAnnotation(jsonbCreatorType, jsonbCreators);
			}
			Map structs = analysis.analyze();
			if (analysis.hasError()) {
				return false;
			}

			try {
				String className = "dsl_json_Annotation_Processor_External_Serialization";
				Writer writer = processingEnv.getFiler().createSourceFile(className).openWriter();
				buildCode(writer, structs, allowInline, allTypes);
				writer.close();
				writer = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", CONFIG).openWriter();
				writer.write(className);
				for (String conf : configurations) {
					writer.write('\n');
					writer.write(conf);
				}
				writer.close();
			} catch (IOException e) {
				processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed saving compiled json serialization files");
			}
		}
		return false;
	}

	@Override
	public SourceVersion getSupportedSourceVersion() {
		SourceVersion latest = SourceVersion.latest();
		if ("RELEASE_9".equals(latest.name())) {
			return latest;
		} else if ("RELEASE_10".equals(latest.name())) {
			return latest;
		}
		return SourceVersion.RELEASE_8;
	}

	private static void buildCode(final Writer code, final Map structs, final boolean allowInline, final Set knownTypes) throws IOException {
		final Context context = new Context(code, allowInline, InlinedConverters, Defaults, structs, knownTypes);
		final DescriptionTemplate descriptionTemplate = new DescriptionTemplate(context);
		final InlinedTemplate inlinedTemplate = new InlinedTemplate(context);
		final EnumTemplate enumTemplate = new EnumTemplate(context);
		code.append("public class dsl_json_Annotation_Processor_External_Serialization implements com.dslplatform.json.Configuration {\n");
		code.append("\tprivate static final java.nio.charset.Charset utf8 = java.nio.charset.Charset.forName(\"UTF-8\");\n");
		code.append("\t@Override\n");
		code.append("\tpublic void configure(com.dslplatform.json.DslJson json) {\n");
		for (Map.Entry kv : structs.entrySet()) {
			final String className = kv.getKey();
			final StructInfo si = kv.getValue();
			if (si.type == ObjectType.CLASS && si.constructor != null && !si.attributes.isEmpty()) {
				String descriptionName = si.name;
				if (si.formats.contains(CompiledJson.Format.OBJECT)) {
					if (allowInline) {
						code.append("\t\tObject_").append(si.name).append(" object_").append(si.name);
						code.append(" = new Object_").append(si.name).append("(json);\n");
					} else {
						code.append("\t\tcom.dslplatform.json.runtime.ObjectFormatDescription object_").append(si.name);
						code.append(" = register_object_").append(si.name).append("(json);\n");
					}
					descriptionName = "object_" + si.name;
				}
				if (si.formats.contains(CompiledJson.Format.ARRAY)) {
					if (allowInline) {
						code.append("\t\tArray_").append(si.name).append(" array_").append(si.name);
						code.append(" = new Array_").append(si.name).append("(json);\n");
					} else {
						code.append("\t\tcom.dslplatform.json.runtime.ArrayFormatDescription array_").append(si.name);
						code.append(" = register_array_").append(si.name).append("(json);\n");
					}
					descriptionName = "array_" + si.name;
				}
				if (si.formats.contains(CompiledJson.Format.OBJECT) && si.formats.contains(CompiledJson.Format.ARRAY)) {
					descriptionName = si.name;
					code.append("\t\tcom.dslplatform.json.runtime.FormatDescription ").append(descriptionName).append(" = new com.dslplatform.json.runtime.FormatDescription(\n");
					code.append("\t\t\t").append(className).append(".class,\n");
					code.append("\t\t\tobject_").append(si.name).append(",\n");
					code.append("\t\t\tarray_").append(si.name).append(",\n");
					if (si.isObjectFormatFirst) code.append("\t\t\ttrue,\n");
					else code.append("\t\t\tfalse,\n");
					String typeAlias = si.deserializeName.isEmpty() ? className : si.deserializeName;
					code.append("\t\t\t\"").append(typeAlias).append("\",\n");
					code.append("\t\t\tjson);\n");
					if (si.hasEmptyCtor()) {
						code.append("\t\tjson.registerBinder(").append(className).append(".class, ").append(si.name).append(");\n");
					}
					code.append("\t\tjson.registerReader(").append(className).append(".class, ").append(si.name).append(");\n");
					code.append("\t\tjson.registerWriter(").append(className).append(".class, ").append(si.name).append(");\n");
				} else {
					if (si.hasEmptyCtor()) {
						code.append("\t\tjson.registerBinder(").append(className).append(".class, ").append(descriptionName).append(");\n");
					}
					code.append("\t\tjson.registerReader(").append(className).append(".class, ").append(descriptionName).append(");\n");
					code.append("\t\tjson.registerWriter(").append(className).append(".class, ").append(descriptionName).append(");\n");
				}
			} else if (si.type == ObjectType.CONVERTER) {
				String type = typeOrClass(nonGenericObject(className), className);
				code.append("\t\tjson.registerWriter(").append(type).append(", ").append(si.converter).append(".").append(si.converterWriter).append(");\n");
				code.append("\t\tjson.registerReader(").append(type).append(", ").append(si.converter).append(".").append(si.converterReader).append(");\n");
			} else if (si.type == ObjectType.ENUM) {
				code.append("\t\tEnum_").append(si.name).append(" ").append(si.name);
				code.append(" = new Enum_").append(si.name).append("();\n");
				code.append("\t\tjson.registerWriter(").append(className).append(".class, ").append(si.name).append(");\n");
				code.append("\t\tjson.registerReader(").append(className).append(".class, ").append(si.name).append(");\n");
			}
		}
		for (Map.Entry kv : structs.entrySet()) {
			StructInfo si = kv.getValue();
			if (si.type == ObjectType.MIXIN && !si.implementations.isEmpty()) {
				code.append("\t\tregister_").append(si.name).append("(json");
				for (StructInfo im : si.implementations) {
					if (im.formats.contains(CompiledJson.Format.OBJECT) && im.formats.contains(CompiledJson.Format.ARRAY)) {
						code.append(", ").append(im.name);
					} else if (im.formats.contains(CompiledJson.Format.OBJECT)) {
						code.append(", object_").append(im.name);
					} else if (im.formats.contains(CompiledJson.Format.ARRAY)) {
						code.append(", array_").append(im.name);
					}
				}
				code.append(");\n");
			}
			if (si.type == ObjectType.MIXIN && si.deserializeAs != null) {
				String typeMixin = typeOrClass(nonGenericObject(kv.getKey()), kv.getKey());
				StructInfo target = si.deserializeTarget();
				code.append("\t\tjson.registerReader(").append(typeMixin).append(", ");
				if (!target.formats.contains(CompiledJson.Format.OBJECT)) {
					code.append("array_");
				} else if (!target.formats.contains(CompiledJson.Format.ARRAY)) {
					code.append("object_");
				}
				code.append(target.name).append(");\n");
			}
		}
		code.append("\t}\n");
		for (Map.Entry it : structs.entrySet()) {
			StructInfo si = it.getValue();
			String className = it.getKey();
			if (si.type == ObjectType.CLASS && !si.attributes.isEmpty()) {
				if (si.hasEmptyCtor()) {
					if (si.formats.contains(CompiledJson.Format.OBJECT)) {
						if (allowInline) inlinedTemplate.emptyCtorObject(si, className);
						else descriptionTemplate.emptyCtorObject(si, className);
					}
					if (si.formats.contains(CompiledJson.Format.ARRAY)) {
						if (allowInline) inlinedTemplate.emptyCtorArray(si, className);
						else descriptionTemplate.emptyCtorArray(si, className);
					}
				} else if (si.constructor != null) {
					if (!allowInline) {
						descriptionTemplate.createBuilder(si, className);
					}
					if (si.formats.contains(CompiledJson.Format.OBJECT)) {
						if (allowInline) inlinedTemplate.fromCtorObject(si, className);
						else descriptionTemplate.fromCtorObject(si, className);
					}
					if (si.formats.contains(CompiledJson.Format.ARRAY)) {
						if (allowInline) inlinedTemplate.fromCtorArray(si, className);
						else descriptionTemplate.fromCtorArray(si, className);
					}
				}
			} else if (si.type == ObjectType.MIXIN && !si.implementations.isEmpty()) {
				if (si.deserializeAs == null) mixin(code, false, si, className);
				else mixin(code, true, si, className);
			} else if (si.type == ObjectType.ENUM) {
				enumTemplate.create(si, className);
			}
		}
		code.append("}\n");
	}

	private static void mixin(final Writer code, final boolean writeOnly, final StructInfo si, final String className) throws IOException {
		final String mixinType = writeOnly ? "MixinWriter" : "MixinDescription";
		code.append("\tprivate static com.dslplatform.json.runtime.").append(mixinType).append("<").append(className).append("> register_").append(si.name).append("(com.dslplatform.json.DslJson json");
		for(StructInfo im : si.implementations) {
			if (im.formats.contains(CompiledJson.Format.OBJECT) && im.formats.contains(CompiledJson.Format.ARRAY)) {
				code.append(", com.dslplatform.json.runtime.FormatDescription ").append(im.name);
			} else if (im.formats.contains(CompiledJson.Format.OBJECT)) {
				code.append(", com.dslplatform.json.runtime.FormatConverter object_").append(im.name);
			} else if (im.formats.contains(CompiledJson.Format.ARRAY)) {
				code.append(", com.dslplatform.json.runtime.FormatConverter array_").append(im.name);
			}
		}
		code.append(") {\n");
		code.append("\t\tcom.dslplatform.json.runtime.").append(mixinType).append("<").append(className).append("> description = new com.dslplatform.json.runtime.").append(mixinType).append("<>(\n");
		code.append("\t\t\t").append(className).append(".class,\n");
		code.append("\t\t\tjson,\n");
		code.append("\t\t\tnew com.dslplatform.json.runtime.FormatDescription[] {\n");
		int i = si.implementations.size();
		for (StructInfo im : si.implementations) {
			if (im.formats.contains(CompiledJson.Format.OBJECT) && im.formats.contains(CompiledJson.Format.ARRAY)) {
				code.append("\t\t\t").append(im.name);
			} else {
				code.append("\t\t\t\tnew com.dslplatform.json.runtime.FormatDescription(");
				code.append(im.element.getQualifiedName()).append(".class, ");
				if (im.formats.contains(CompiledJson.Format.OBJECT)) {
					code.append("object_").append(im.name).append(", ");
				} else {
					code.append("null, ");
				}
				if (im.formats.contains(CompiledJson.Format.ARRAY)) {
					code.append("array_").append(im.name).append(", ");
				} else {
					code.append("null, ");
				}
				if (im.isObjectFormatFirst) code.append("true, ");
				else code.append("false, ");
				String typeAlias = im.deserializeName.isEmpty()
						? im.element.getQualifiedName().toString()
						: im.deserializeName;
				code.append("\"").append(typeAlias).append("\", json)");
			}
			i--;
			if (i > 0) code.append(",\n");
		}
		code.append("\n\t\t\t}\n");
		code.append("\t\t);\n");
		if (!writeOnly) {
			code.append("\t\tjson.registerReader(").append(className).append(".class, description);\n");
		}
		code.append("\t\tjson.registerWriter(").append(className).append(".class, description);\n");
		code.append("\t\treturn description;\n");
		code.append("\t}\n");
	}
}