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 @@ -11,7 +11,9 @@ public class JsonTypes {
public static final ClassName jsonReaderAnnotation = ClassName.get("ru.tinkoff.kora.json.common.annotation", "JsonReader");
public static final ClassName jsonWriterAnnotation = ClassName.get("ru.tinkoff.kora.json.common.annotation", "JsonWriter");

public static final ClassName jsonValue = ClassName.get("ru.tinkoff.kora.json.common", "JsonValue");
public static final ClassName jsonNullable = ClassName.get("ru.tinkoff.kora.json.common", "JsonNullable");
public static final ClassName jsonUndefined = ClassName.get("ru.tinkoff.kora.json.common", "JsonUndefined");
public static final ClassName jsonReader = ClassName.get("ru.tinkoff.kora.json.common", "JsonReader");
public static final ClassName jsonWriter = ClassName.get("ru.tinkoff.kora.json.common", "JsonWriter");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import java.util.Arrays;
import java.util.BitSet;
Expand Down Expand Up @@ -45,6 +44,17 @@ private boolean isNullable(JsonClassReaderMeta.FieldMeta field) {
return CommonUtils.isNullable(field.parameter());
}

private boolean isUndefined(JsonClassReaderMeta.FieldMeta field) {
if (field.parameter().asType().getKind().isPrimitive()) {
return false;
}
if (field.typeMeta() != null && (field.typeMeta().isJsonUndefined())) {
return true;
}

return CommonUtils.isNullable(field.parameter());
}

private TypeSpec generateForClass(JsonClassReaderMeta meta) {
var typeBuilder = TypeSpec.classBuilder(JsonUtils.jsonReaderName(meta.typeElement()))
.addAnnotation(AnnotationUtils.generated(JsonReaderGenerator.class))
Expand All @@ -71,9 +81,9 @@ private TypeSpec generateForClass(JsonClassReaderMeta meta) {
assertTokenType(method, "START_OBJECT");

if (meta.fields().size() <= 32) {
method.addStatement("var __receivedFields = new int[]{NULLABLE_FIELDS_RECEIVED}");
method.addStatement("var __receivedFields = new int[]{UNDEFINED_FIELDS_RECEIVED}");
} else {
method.addStatement("var __receivedFields = ($T) NULLABLE_FIELDS_RECEIVED.clone()", BitSet.class);
method.addStatement("var __receivedFields = ($T) UNDEFINED_FIELDS_RECEIVED.clone()", BitSet.class);
}
method.addCode("\n");

Expand Down Expand Up @@ -158,7 +168,7 @@ private void addBitSet(TypeSpec.Builder typeBuilder, JsonClassReaderMeta meta) {
var sb = new StringBuilder();
for (int i = meta.fields().size() - 1; i >= 0; i--) {
var f = meta.fields().get(i);
sb.append(isNullable(f) ? "1" : "0");
sb.append(isUndefined(f) || isNullable(f) ? "1" : "0");
}
var nullableFieldsReceived = meta.fields().isEmpty()
? "0"
Expand All @@ -171,23 +181,23 @@ private void addBitSet(TypeSpec.Builder typeBuilder, JsonClassReaderMeta meta) {
.addField(FieldSpec.builder(TypeName.INT, "ALL_FIELDS_RECEIVED", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer(CodeBlock.of(allFieldsReceived))
.build())
.addField(FieldSpec.builder(TypeName.INT, "NULLABLE_FIELDS_RECEIVED", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.addField(FieldSpec.builder(TypeName.INT, "UNDEFINED_FIELDS_RECEIVED", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer(CodeBlock.of(nullableFieldsReceived))
.build());
} else {
typeBuilder
.addField(ClassName.get(BitSet.class), "ALL_FIELDS_RECEIVED", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.addField(ClassName.get(BitSet.class), "NULLABLE_FIELDS_RECEIVED", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL);
.addField(ClassName.get(BitSet.class), "UNDEFINED_FIELDS_RECEIVED", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL);
var fieldReceivedInitBlock = CodeBlock.builder()
.add("""
ALL_FIELDS_RECEIVED = new $T($L);
ALL_FIELDS_RECEIVED.set(0, $L);
NULLABLE_FIELDS_RECEIVED = new $T($L);
UNDEFINED_FIELDS_RECEIVED = new $T($L);
""", BitSet.class, meta.fields().size(), meta.fields().size(), BitSet.class, meta.fields().size());
for (int i = 0; i < meta.fields().size(); i++) {
var field = meta.fields().get(i);
if (isNullable(field)) {
fieldReceivedInitBlock.add("NULLABLE_FIELDS_RECEIVED.set($L);\n", i);
if (isUndefined(field) || isNullable(field)) {
fieldReceivedInitBlock.add("UNDEFINED_FIELDS_RECEIVED.set($L);\n", i);
}
}
typeBuilder.addStaticBlock(fieldReceivedInitBlock.build());
Expand Down Expand Up @@ -238,8 +248,12 @@ private void addFieldVariables(MethodSpec.Builder method, JsonClassReaderMeta me
} else {
method.addCode(" = 0;\n");
}
} else if (field.typeMeta() != null && field.typeMeta().isJsonNullable()) {
method.addCode(" = $T.undefined();\n", JsonTypes.jsonNullable);
} else if (field.typeMeta() != null && field.typeMeta().jsonValueType() != null) {
switch (field.typeMeta().jsonValueType()) {
case VALUE -> method.addCode(" = $T.undefined();\n", JsonTypes.jsonValue);
case NULLABLE -> method.addCode(" = $T.nullValue();\n", JsonTypes.jsonNullable);
case UNDEFINED -> method.addCode(" = $T.undefined();\n", JsonTypes.jsonUndefined);
}
} else {
method.addCode(" = null;\n");
}
Expand Down Expand Up @@ -328,9 +342,26 @@ private MethodSpec readParamMethod(int index, int size, FieldMeta field) {
method.addCode("__receivedFields[0] = __receivedFields[0] | (1 << $L);\n", index);
}
}
method.addCode("return $L.read(__parser);\n", this.readerFieldName(field));

CodeBlock prefix;
if (field.typeMeta().jsonValueType() != null) {
prefix = switch (field.typeMeta().jsonValueType()) {
case VALUE -> CodeBlock.of("return $T.ofNullable(", JsonTypes.jsonValue);
case NULLABLE -> CodeBlock.of("return $T.ofNullable(", JsonTypes.jsonNullable);
case UNDEFINED -> CodeBlock.of("return $T.of(", JsonTypes.jsonUndefined);
};
} else {
prefix = CodeBlock.of("return ");
}

CodeBlock suffix = field.typeMeta().jsonValueType() != null
? CodeBlock.of(")")
: CodeBlock.of("");

method.addCode("$L$L.read(__parser)$L;\n", prefix, this.readerFieldName(field), suffix);
return method.build();
}

method.addStatement("var __token = __parser.nextToken()");
if (field.typeMeta() instanceof KnownTypeReaderMeta meta) {
method.addModifiers(Modifier.STATIC);
Expand All @@ -341,26 +372,38 @@ private MethodSpec readParamMethod(int index, int size, FieldMeta field) {
block.add("__receivedFields[0] = __receivedFields[0] | (1 << $L);\n", index);
}

CodeBlock prefix = field.typeMeta().isJsonNullable()
? CodeBlock.of("return $T.ofNullable(", JsonTypes.jsonNullable)
: CodeBlock.of("return ");
CodeBlock prefix;
if (field.typeMeta().jsonValueType() != null) {
prefix = switch (field.typeMeta().jsonValueType()) {
case VALUE -> CodeBlock.of("return $T.ofNullable(", JsonTypes.jsonValue);
case NULLABLE -> CodeBlock.of("return $T.ofNullable(", JsonTypes.jsonNullable);
case UNDEFINED -> CodeBlock.of("return $T.of(", JsonTypes.jsonUndefined);
};
} else {
prefix = CodeBlock.of("return ");
}

CodeBlock suffix = field.typeMeta().isJsonNullable()
CodeBlock suffix = field.typeMeta().jsonValueType() != null
? CodeBlock.of(")")
: CodeBlock.of("");

boolean isJsonNullable = field.typeMeta().isJsonNullable();
block.add(readKnownType(field.jsonName(), prefix, suffix, meta.knownType(), isNullable(field), isJsonNullable, meta.typeMirror()));
block.add(readKnownType(field.jsonName(), prefix, suffix, meta, isNullable(field)));
method.addCode(block.build());
return method.build();
}

if (field.typeMeta() != null && field.typeMeta().isJsonNullable()) {
var type = switch (field.typeMeta().jsonValueType()) {
case VALUE -> JsonTypes.jsonValue;
case NULLABLE -> JsonTypes.jsonNullable;
default -> throw new UnsupportedOperationException("Unknown jsonValueType: " + field.typeMeta().jsonValueType());
};

method.addCode("""
if (__token == $T.VALUE_NULL) {
return $T.nullValue();
}
""", JsonTypes.jsonToken, JsonTypes.jsonNullable);
""", JsonTypes.jsonToken, type);
} else if (isNullable(field)) {
method.addCode("""
if (__token == $T.VALUE_NULL) {
Expand All @@ -380,7 +423,27 @@ private MethodSpec readParamMethod(int index, int size, FieldMeta field) {
}

if (field.typeMeta() != null && field.typeMeta().isJsonNullable()) {
method.addStatement("return $T.ofNullable($L.read(__parser))", JsonTypes.jsonNullable, readerFieldName(field));
var type = switch (field.typeMeta().jsonValueType()) {
case VALUE -> JsonTypes.jsonValue;
case NULLABLE -> JsonTypes.jsonNullable;
default -> throw new UnsupportedOperationException("Unknown jsonValueType: " + field.typeMeta().jsonValueType());
};

if (size > 32) {
method.addStatement("__receivedFields.set($L)", index);
} else {
method.addStatement("__receivedFields[0] = __receivedFields[0] | (1 << $L)", index);
}

method.addStatement("return $T.ofNullable($L.read(__parser))", type, readerFieldName(field));
} else if (field.typeMeta() != null && field.typeMeta().jsonValueType() == ReaderFieldType.JsonValueType.UNDEFINED) {
if (size > 32) {
method.addStatement("__receivedFields.set($L)", index);
} else {
method.addStatement("__receivedFields[0] = __receivedFields[0] | (1 << $L)", index);
}

method.addStatement("return $T.of($L.read(__parser))", JsonTypes.jsonUndefined, readerFieldName(field));
} else {
method.addStatement("return $L.read(__parser)", readerFieldName(field));
}
Expand All @@ -391,7 +454,8 @@ private String readerMethodName(FieldMeta field) {
return "read_" + field.parameter().getSimpleName().toString();
}

private CodeBlock readKnownType(String jsonName, CodeBlock prefix, CodeBlock suffix, KnownType.KnownTypesEnum knownType, boolean nullable, boolean jsonNullable, TypeMirror typeMirror) {
private CodeBlock readKnownType(String jsonName, CodeBlock prefix, CodeBlock suffix, KnownTypeReaderMeta meta, boolean nullable) {
KnownType.KnownTypesEnum knownType = meta.knownType();
var method = CodeBlock.builder();
var code = switch (knownType) {
case STRING -> CodeBlock.of("""
Expand Down Expand Up @@ -447,9 +511,16 @@ private CodeBlock readKnownType(String jsonName, CodeBlock prefix, CodeBlock suf
}""",
JsonTypes.jsonToken, prefix, UUID.class, suffix);
};

method.add(code);
if (jsonNullable) {
method.add(" else if (__token == $T.VALUE_NULL) {$>\nreturn $T.nullValue();$<\n}", JsonTypes.jsonToken, JsonTypes.jsonNullable);
if (meta.isJsonNullable()) {
var type = switch (meta.jsonValueType()) {
case VALUE -> JsonTypes.jsonValue;
case NULLABLE -> JsonTypes.jsonNullable;
default -> throw new UnsupportedOperationException("Unknown jsonValueType: " + meta.jsonValueType());
};

method.add(" else if (__token == $T.VALUE_NULL) {$>\nreturn $T.nullValue();$<\n}", JsonTypes.jsonToken, type);
} else if (nullable) {
method.add(" else if (__token == $T.VALUE_NULL) {$>\nreturn null;$<\n}", JsonTypes.jsonToken);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
package ru.tinkoff.kora.json.annotation.processor.reader;

import org.jspecify.annotations.Nullable;
import ru.tinkoff.kora.json.annotation.processor.KnownType;

import javax.lang.model.type.TypeMirror;

public interface ReaderFieldType {

boolean isJsonNullable();
enum JsonValueType {
VALUE,
NULLABLE,
UNDEFINED
}

@Nullable
JsonValueType jsonValueType();

default boolean isJsonNullable() {
return jsonValueType() != null
&& (jsonValueType() == ReaderFieldType.JsonValueType.VALUE || jsonValueType() == ReaderFieldType.JsonValueType.NULLABLE);
}

default boolean isJsonUndefined() {
return jsonValueType() != null
&& (jsonValueType() == ReaderFieldType.JsonValueType.VALUE || jsonValueType() == JsonValueType.UNDEFINED);
}

TypeMirror typeMirror();

record KnownTypeReaderMeta(KnownType.KnownTypesEnum knownType, TypeMirror typeMirror, boolean isJsonNullable) implements ReaderFieldType {}
record KnownTypeReaderMeta(KnownType.KnownTypesEnum knownType, TypeMirror typeMirror, JsonValueType jsonValueType) implements ReaderFieldType {}

record UnknownTypeReaderMeta(TypeMirror typeMirror, boolean isJsonNullable) implements ReaderFieldType {}
record UnknownTypeReaderMeta(TypeMirror typeMirror, JsonValueType jsonValueType) implements ReaderFieldType {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,26 @@ public JsonClassReaderMeta parse(TypeElement jsonClass, TypeMirror typeMirror) t

@Nullable
public ReaderFieldType parseReaderFieldType(TypeMirror jsonClass) {
var isJsonNullable = false;
var realType = jsonClass;
if (jsonClass instanceof DeclaredType dt && JsonTypes.jsonNullable.canonicalName().equals((dt.asElement()).toString())) {
realType = dt.getTypeArguments().get(0);
isJsonNullable = true;
ReaderFieldType.JsonValueType jsonValueType = null;
TypeMirror realType = jsonClass;
if (jsonClass instanceof DeclaredType dt) {
if (JsonTypes.jsonValue.canonicalName().equals((dt.asElement()).toString())) {
realType = dt.getTypeArguments().getFirst();
jsonValueType = ReaderFieldType.JsonValueType.VALUE;
} else if (JsonTypes.jsonNullable.canonicalName().equals((dt.asElement()).toString())) {
realType = dt.getTypeArguments().getFirst();
jsonValueType = ReaderFieldType.JsonValueType.NULLABLE;
} else if (JsonTypes.jsonUndefined.canonicalName().equals((dt.asElement()).toString())) {
realType = dt.getTypeArguments().getFirst();
jsonValueType = ReaderFieldType.JsonValueType.UNDEFINED;
}
}

var knownType = this.knownTypes.detect(realType);
if (knownType != null) {
return new KnownTypeReaderMeta(knownType, realType, isJsonNullable);
return new KnownTypeReaderMeta(knownType, realType, jsonValueType);
} else {
return new ReaderFieldType.UnknownTypeReaderMeta(realType, isJsonNullable);
return new ReaderFieldType.UnknownTypeReaderMeta(realType, jsonValueType);
}
}

Expand All @@ -81,14 +89,14 @@ private ExecutableElement findJsonConstructor(TypeElement typeElement) {
);
}
if (constructors.size() == 1) {
return constructors.get(0);
return constructors.getFirst();
}

var jsonReaderConstructors = constructors.stream()
.filter(e -> AnnotationUtils.findAnnotation(e, JsonTypes.jsonReaderAnnotation) != null)
.toList();
if (jsonReaderConstructors.size() == 1) {
return jsonReaderConstructors.get(0);
return jsonReaderConstructors.getFirst();
}
if (!jsonReaderConstructors.isEmpty()) {
throw new ProcessingErrorException("Class: %s\nIn order to generate JsonReader class must have one public constructor or constructor annotated with any of @Json/@JsonReader"
Expand All @@ -101,7 +109,7 @@ private ExecutableElement findJsonConstructor(TypeElement typeElement) {
.filter(e -> AnnotationUtils.findAnnotation(e, JsonTypes.json) != null)
.toList();
if (jsonConstructors.size() == 1) {
return jsonConstructors.get(0);
return jsonConstructors.getFirst();
}
if (!jsonConstructors.isEmpty()) {
throw new ProcessingErrorException("Class: %s\nIn order to generate JsonReader class must have one public constructor or constructor annotated with any of @Json/@JsonReader"
Expand All @@ -114,7 +122,7 @@ private ExecutableElement findJsonConstructor(TypeElement typeElement) {
.filter(c -> !c.getParameters().isEmpty())
.toList();
if (nonEmpty.size() == 1) {
return nonEmpty.get(0);
return nonEmpty.getFirst();
}
throw new ProcessingErrorException("Class: %s\nIn order to generate JsonReader class must have one public constructor or constructor annotated with any of @Json/@JsonReader"
.formatted(typeElement),
Expand Down
Loading
Loading