Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public class CommonClassNames {
public static final ClassName refreshListener = ClassName.get("ru.tinkoff.kora.application.graph", "RefreshListener");

public static final ClassName koraGenerated = ClassName.get("ru.tinkoff.kora.common.annotation", "Generated");
public static final ClassName generatorModule = ClassName.get("ru.tinkoff.kora.common.annotation", "GeneratorModule");
public static final ClassName list = ClassName.get(List.class);

public static final ClassName config = ClassName.get("ru.tinkoff.kora.config.common", "Config");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ru.tinkoff.kora.common.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface GeneratorModule {
Class<?> generator();

Class<?>[] types();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package ru.tinkoff.kora.json.annotation.processor;

import com.squareup.javapoet.*;
import ru.tinkoff.kora.annotation.processor.common.*;

import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;

public class GeneratorModuleAnnotationProcessor extends AbstractKoraProcessor {
private JsonProcessor processor;

@Override
public Set<String> getSupportedAnnotationTypes() {
return Set.of(CommonClassNames.generatorModule.canonicalName());
}

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.processor = new JsonProcessor(processingEnv);
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (var annotation : annotations) {
var annotationTypeName = ClassName.get(annotation);
if (annotationTypeName.equals(CommonClassNames.generatorModule)) {
var modules = roundEnv.getElementsAnnotatedWith(annotation);
for (var module : modules) {
try {
this.generateModule(module);
} catch (ProcessingErrorException e) {
e.printError(this.processingEnv);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
return false;
}

private void generateModule(Element module) throws Exception {
var generatorModuleAnnotation = AnnotationUtils.findAnnotation(module, CommonClassNames.generatorModule);
var generatorModuleGenerator = AnnotationUtils.<TypeMirror>parseAnnotationValueWithoutDefault(generatorModuleAnnotation, "generator");
if (generatorModuleGenerator == null || !TypeName.get(generatorModuleGenerator).equals(JsonTypes.json)) {
// not a json generator
return;
}
var typesToProcess = AnnotationUtils.<List<TypeMirror>>parseAnnotationValueWithoutDefault(generatorModuleAnnotation, "types");
if (typesToProcess == null || typesToProcess.isEmpty()) {
return;
}
var packageElement = elements.getPackageOf(module);
var packageName = packageElement.getQualifiedName().toString();
var builder = TypeSpec.interfaceBuilder(NameUtils.generatedType(module, CommonClassNames.generatorModule))
.addOriginatingElement(module)
.addModifiers(Modifier.PUBLIC)
.addAnnotation(CommonClassNames.module)
.addAnnotation(AnnotationUtils.generated(this.getClass()));
var error = false;
for (int i = 0; i < typesToProcess.size(); i++) {
var jsonType = (DeclaredType) typesToProcess.get(i);
var jsonTypeElement = (TypeElement) jsonType.asElement();
try {
var expectedReaderName = ClassName.get(packageName, NameUtils.generatedType(module, CommonClassNames.generatorModule) + "_" + i + "_JsonReader");
var readerMethod = this.generateMapper(module, "reader" + i, expectedReaderName, jsonTypeElement, this.processor::generateReader);
builder.addMethod(readerMethod);

var expectedWriterName = ClassName.get(packageName, NameUtils.generatedType(module, CommonClassNames.generatorModule) + "_" + i + "_JsonWriter");
var writerMethod = this.generateMapper(module, "writer" + i, expectedWriterName, jsonTypeElement, this.processor::generateWriter);
builder.addMethod(writerMethod);
} catch (ProcessingErrorException e) {
error = true;
@SuppressWarnings("unchecked")
var annotationValue = (List<AnnotationValue>) generatorModuleAnnotation.getElementValues().entrySet().stream()
.filter(k -> k.getKey().getSimpleName().contentEquals("types"))
.map(Map.Entry::getValue)
.findFirst()
.get()
.getValue();
e.printError(this.processingEnv);
this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage(), module, generatorModuleAnnotation, annotationValue.get(i));
}
}
if (error) {
return;
}
var moduleType = builder.build();
var javaFile = JavaFile.builder(packageName, moduleType).build();
javaFile.writeTo(processingEnv.getFiler());
}

private MethodSpec generateMapper(Element module, String methodName, ClassName expectedMapperName, TypeElement jsonTypeElement, BiFunction<ClassName, TypeElement, TypeSpec> generator) {
var packageElement = elements.getPackageOf(module);
var packageName = packageElement.getQualifiedName().toString();
var mapper = generator.apply(expectedMapperName, jsonTypeElement);
var mapperBuilder = mapper.toBuilder();
mapperBuilder.originatingElements.clear();
mapperBuilder.addOriginatingElement(module);

CommonUtils.safeWriteTo(processingEnv, JavaFile.builder(packageName, mapperBuilder.build()).build());
return this.mapperMethod(methodName, expectedMapperName, mapper);
}

private MethodSpec mapperMethod(String methodName, ClassName mapperName, TypeSpec mapper) {
var readerConstructor = mapper.methodSpecs.stream().filter(m -> m.name.equals("<init>")).findFirst().get();
return MethodSpec.methodBuilder(methodName)
.addModifiers(Modifier.DEFAULT, Modifier.PUBLIC)
.addParameters(readerConstructor.parameters)
.returns(mapperName)
.addStatement("return new $T$L($L)", mapperName, mapper.typeVariables.isEmpty() ? "" : "<>", readerConstructor.parameters.stream().map(p -> CodeBlock.of("$N", p.name)).collect(CodeBlock.joining(",")))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package ru.tinkoff.kora.json.annotation.processor;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.squareup.javapoet.TypeSpec;
import ru.tinkoff.kora.annotation.processor.common.CommonUtils;
import ru.tinkoff.kora.annotation.processor.common.ComparableTypeMirror;
import ru.tinkoff.kora.annotation.processor.common.SealedTypeUtils;
import ru.tinkoff.kora.json.annotation.processor.reader.EnumReaderGenerator;
import ru.tinkoff.kora.json.annotation.processor.reader.JsonReaderGenerator;
Expand All @@ -19,14 +18,11 @@
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.util.Objects;

public class JsonProcessor {
private static final Logger log = LoggerFactory.getLogger(JsonProcessor.class);

private final ProcessingEnvironment processingEnv;
private final Elements elements;
private final Types types;
Expand Down Expand Up @@ -55,90 +51,41 @@ public JsonProcessor(ProcessingEnvironment processingEnv) {
}

public void generateReader(TypeElement jsonElement) {
var jsonElementType = jsonElement.asType();
var packageElement = JsonUtils.jsonClassPackage(this.elements, jsonElement);
var readerClassName = JsonUtils.jsonReaderName(this.types, jsonElementType);
var readerElement = this.elements.getTypeElement(packageElement + "." + readerClassName);
if (readerElement != null) {
return;
}
var packageName = elements.getPackageOf(jsonElement).getQualifiedName().toString();
var className = ClassName.get(packageName, JsonUtils.jsonReaderName(jsonElement));
var reader = generateReader(className, jsonElement);
var javaFile = JavaFile.builder(packageName, reader).build();
CommonUtils.safeWriteTo(this.processingEnv, javaFile);
}

public TypeSpec generateReader(ClassName target, TypeElement jsonElement) {
if (jsonElement.getKind() == ElementKind.ENUM) {
this.generateEnumReader(jsonElement);
return;
return this.enumReaderGenerator.generateForEnum(target, jsonElement);
}
if (jsonElement.getModifiers().contains(Modifier.SEALED)) {
this.generateSealedRootReader(jsonElement);
return;
return this.sealedReaderGenerator.generateSealedReader(target, jsonElement);
}
this.generateDtoReader(jsonElement, jsonElementType);
}

private void generateSealedRootReader(TypeElement jsonElement) {
var packageElement = JsonUtils.jsonClassPackage(this.elements, jsonElement);
var sealedReaderType = this.sealedReaderGenerator.generateSealedReader(jsonElement);

var javaFile = JavaFile.builder(packageElement, sealedReaderType).build();
CommonUtils.safeWriteTo(this.processingEnv, javaFile);
}

private void generateEnumReader(TypeElement jsonElement) {
var packageElement = JsonUtils.jsonClassPackage(this.elements, jsonElement);
var sealedReaderType = this.enumReaderGenerator.generateForEnum(jsonElement);

var javaFile = JavaFile.builder(packageElement, sealedReaderType).build();
CommonUtils.safeWriteTo(this.processingEnv, javaFile);
}

private void generateDtoReader(TypeElement typeElement, TypeMirror jsonTypeMirror) {
var packageElement = JsonUtils.jsonClassPackage(this.elements, typeElement);
var meta = Objects.requireNonNull(this.readerTypeMetaParser.parse(typeElement, jsonTypeMirror));
var readerType = Objects.requireNonNull(this.readerGenerator.generate(meta));

var javaFile = JavaFile.builder(packageElement, readerType).build();
CommonUtils.safeWriteTo(this.processingEnv, javaFile);
var jsonElementType = jsonElement.asType();
var meta = Objects.requireNonNull(this.readerTypeMetaParser.parse(jsonElement, jsonElementType));
return Objects.requireNonNull(this.readerGenerator.generate(target, meta));
}

private void generateEnumWriter(TypeElement jsonElement) {
var packageElement = JsonUtils.jsonClassPackage(this.elements, jsonElement);
var enumWriterType = this.enumWriterGenerator.generateEnumWriter(jsonElement);
var javaFile = JavaFile.builder(packageElement, enumWriterType).build();
public void generateWriter(TypeElement jsonElement) {
var packageName = elements.getPackageOf(jsonElement).getQualifiedName().toString();
var className = ClassName.get(packageName, JsonUtils.jsonWriterName(jsonElement));
var writer = generateWriter(className, jsonElement);
var javaFile = JavaFile.builder(packageName, writer).build();
CommonUtils.safeWriteTo(this.processingEnv, javaFile);
}

public void generateWriter(TypeElement jsonElement) {
var wrapper = new ComparableTypeMirror(this.types, jsonElement.asType());
var packageElement = JsonUtils.jsonClassPackage(this.elements, jsonElement);
var writerClassName = JsonUtils.jsonWriterName(this.types, wrapper.typeMirror());
var writerElement = this.elements.getTypeElement(packageElement + "." + writerClassName);
if (writerElement != null) {
return;
}
public TypeSpec generateWriter(ClassName targetName, TypeElement jsonElement) {
if (jsonElement.getKind() == ElementKind.ENUM) {
this.generateEnumWriter(jsonElement);
return;
return this.enumWriterGenerator.generateEnumWriter(targetName, jsonElement);
}
if (jsonElement.getModifiers().contains(Modifier.SEALED)) {
this.generateSealedWriter(jsonElement);
return;
return this.sealedWriterGenerator.generateSealedWriter(targetName, jsonElement, SealedTypeUtils.collectFinalPermittedSubtypes(types, elements, jsonElement));
}
this.tryGenerateWriter(jsonElement, jsonElement.asType());
}


private void generateSealedWriter(TypeElement jsonElement) {
var packageElement = JsonUtils.jsonClassPackage(this.elements, jsonElement);
var sealedWriterType = this.sealedWriterGenerator.generateSealedWriter(jsonElement, SealedTypeUtils.collectFinalPermittedSubtypes(types, elements, jsonElement));

var javaFile = JavaFile.builder(packageElement, sealedWriterType).build();
CommonUtils.safeWriteTo(this.processingEnv, javaFile);
}

private void tryGenerateWriter(TypeElement jsonElement, TypeMirror jsonTypeMirror) {
var meta = Objects.requireNonNull(this.writerTypeMetaParser.parse(jsonElement, jsonTypeMirror));
var packageElement = JsonUtils.jsonClassPackage(this.elements, jsonElement);
var writerType = Objects.requireNonNull(this.writerGenerator.generate(meta));

var javaFile = JavaFile.builder(packageElement, writerType).build();
CommonUtils.safeWriteTo(this.processingEnv, javaFile);
var meta = Objects.requireNonNull(this.writerTypeMetaParser.parse(jsonElement, jsonElement.asType()));
return Objects.requireNonNull(this.writerGenerator.generate(targetName, meta));
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
package ru.tinkoff.kora.json.annotation.processor;

import jakarta.annotation.Nullable;
import ru.tinkoff.kora.annotation.processor.common.AnnotationUtils;
import ru.tinkoff.kora.annotation.processor.common.NameUtils;
import ru.tinkoff.kora.annotation.processor.common.ProcessingErrorException;

import jakarta.annotation.Nullable;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.util.List;


public class JsonUtils {
public static String jsonClassPackage(Elements elements, Element typeElement) {
return elements.getPackageOf(typeElement).getQualifiedName().toString();
}

public static String jsonWriterName(Element typeElement) {
return NameUtils.generatedType(typeElement, "JsonWriter");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import ru.tinkoff.kora.annotation.processor.common.AnnotationUtils;
import ru.tinkoff.kora.annotation.processor.common.CommonClassNames;
import ru.tinkoff.kora.json.annotation.processor.JsonTypes;
import ru.tinkoff.kora.json.annotation.processor.JsonUtils;

import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
Expand All @@ -15,11 +14,11 @@

public class EnumReaderGenerator {

public TypeSpec generateForEnum(TypeElement typeElement) {
public TypeSpec generateForEnum(ClassName target, TypeElement typeElement) {
var typeName = ClassName.get(typeElement);
var enumValue = this.detectValueType(typeElement);

var typeBuilder = TypeSpec.classBuilder(JsonUtils.jsonReaderName(typeElement))
var typeBuilder = TypeSpec.classBuilder(target)
.addAnnotation(AnnotationSpec.builder(CommonClassNames.koraGenerated)
.addMember("value", CodeBlock.of("$S", JsonReaderGenerator.class.getCanonicalName()))
.build())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import ru.tinkoff.kora.annotation.processor.common.CommonClassNames;
import ru.tinkoff.kora.annotation.processor.common.CommonUtils;
import ru.tinkoff.kora.json.annotation.processor.JsonTypes;
import ru.tinkoff.kora.json.annotation.processor.JsonUtils;
import ru.tinkoff.kora.json.annotation.processor.KnownType;
import ru.tinkoff.kora.json.annotation.processor.reader.JsonClassReaderMeta.FieldMeta;
import ru.tinkoff.kora.json.annotation.processor.reader.ReaderFieldType.KnownTypeReaderMeta;
Expand All @@ -30,8 +29,8 @@ public JsonReaderGenerator(ProcessingEnvironment processingEnvironment) {
}

@Nullable
public TypeSpec generate(JsonClassReaderMeta meta) {
return this.generateForClass(meta);
public TypeSpec generate(ClassName target, JsonClassReaderMeta meta) {
return this.generateForClass(target, meta);
}

private boolean isNullable(JsonClassReaderMeta.FieldMeta field) {
Expand All @@ -45,8 +44,8 @@ private boolean isNullable(JsonClassReaderMeta.FieldMeta field) {
return CommonUtils.isNullable(field.parameter());
}

private TypeSpec generateForClass(JsonClassReaderMeta meta) {
var typeBuilder = TypeSpec.classBuilder(JsonUtils.jsonReaderName(meta.typeElement()))
private TypeSpec generateForClass(ClassName target, JsonClassReaderMeta meta) {
var typeBuilder = TypeSpec.classBuilder(target)
.addAnnotation(AnnotationSpec.builder(CommonClassNames.koraGenerated)
.addMember("value", CodeBlock.of("$S", JsonReaderGenerator.class.getCanonicalName()))
.build())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ public SealedInterfaceReaderGenerator(ProcessingEnvironment processingEnvironmen
this.elements = processingEnvironment.getElementUtils();
}

public TypeSpec generateSealedReader(TypeElement jsonElement) {
var typeName = JsonUtils.jsonReaderName(jsonElement);
var typeBuilder = TypeSpec.classBuilder(typeName)
public TypeSpec generateSealedReader(ClassName target, TypeElement jsonElement) {
var typeBuilder = TypeSpec.classBuilder(target)
.addAnnotation(AnnotationSpec.builder(CommonClassNames.koraGenerated)
.addMember("value", CodeBlock.of("$S", SealedInterfaceReaderGenerator.class.getCanonicalName()))
.build())
Expand Down
Loading