Support maps
ci/woodpecker/push/woodpecker Pipeline was successful
Details
ci/woodpecker/push/woodpecker Pipeline was successful
Details
This commit is contained in:
parent
9f32d3b25d
commit
0fc1d3bce6
|
@ -14,6 +14,7 @@ The goal of this AP is to
|
|||
- Collections (Sets, Lists, Queues, Deques)
|
||||
- java.util.Date as iso8601
|
||||
- Enums
|
||||
- Maps with string, primitive, enum or UUID keys
|
||||
|
||||
## Used properties
|
||||
Use `@GPrefer` to choose one construction method if multiple are available
|
||||
|
@ -30,7 +31,6 @@ Use `@GPrefer` to choose one construction method if multiple are available
|
|||
- Several utility methods in the generated class for reading from/writing to various sources
|
||||
|
||||
## TODO
|
||||
- Maps with string, primitive or enum keys
|
||||
- Support for nested types from libraries
|
||||
- Static classes (for configs)
|
||||
|
||||
|
|
|
@ -58,6 +58,14 @@ public class Main {
|
|||
@GComment("Yes!")
|
||||
public boolean primitive;
|
||||
public ExamplePojo2[] recursiveTest;
|
||||
|
||||
public Map<EeE, Example4> map1;
|
||||
|
||||
public Map<String, String> map2;
|
||||
|
||||
public Map<Integer, String> map3;
|
||||
|
||||
public Map<UUID, String> map4;
|
||||
}
|
||||
|
||||
@GSerializable
|
||||
|
|
|
@ -31,11 +31,7 @@ public abstract class Adapter<T extends Adapter<T>.Hydrated> {
|
|||
instance.other = other;
|
||||
instance.type = type;
|
||||
instance.code = code;
|
||||
try {
|
||||
instance.unboxedType = typeUtils.unboxedType(type);
|
||||
} catch (IllegalArgumentException e) {
|
||||
instance.unboxedType = type;
|
||||
}
|
||||
instance.unboxedType = instance.unbox(type);
|
||||
instance.name = propName;
|
||||
instance.argName = "_" + propName;
|
||||
instance.adapterName = "adapter_" + propName;
|
||||
|
@ -72,5 +68,13 @@ public abstract class Adapter<T extends Adapter<T>.Hydrated> {
|
|||
protected void generateWrite(CodeBlock.Builder code, TypeMirror type, String name, List<? extends AnnotationMirror> annotations, Runnable writeGet) {
|
||||
Adapters.generateWrite(klazz, code, typeVariables, other, type, name, annotations, sourceElement, message, writeGet);
|
||||
}
|
||||
|
||||
protected TypeMirror unbox(TypeMirror type) {
|
||||
try {
|
||||
return typeUtils.unboxedType(type);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ public class Adapters {
|
|||
new EnumAdapter(),
|
||||
new ArrayAdapter(),
|
||||
new CollectionAdapter(),
|
||||
new MapAdapter(),
|
||||
new OtherSerializableAdapter(),
|
||||
new ReflectAdapter()
|
||||
);
|
||||
|
|
|
@ -40,18 +40,20 @@ public class DateAdapter extends Adapter<DateAdapter.Hydrated> {
|
|||
}
|
||||
|
||||
if (!found) {
|
||||
CodeBlock.Builder kode = CodeBlock.builder();
|
||||
kode.beginControlFlow("try")
|
||||
.addStatement("return $T.parse(date, new $T(0))", Cl.GISO8601UTILS, ParsePosition.class)
|
||||
.nextControlFlow("catch ($T e)", ParseException.class)
|
||||
.addStatement("throw new $T(\"Failed Parsing '\" + date + \"' as Date\", e)", Cl.GSON_SYNTAX_EXCEPTION)
|
||||
.endControlFlow();
|
||||
klazz.addMethod(
|
||||
MethodSpec.methodBuilder("parseDate")
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||
.returns(Date.class)
|
||||
.addParameter(String.class, "date")
|
||||
.addCode(kode.build())
|
||||
.addCode(
|
||||
CodeBlock.builder()
|
||||
.beginControlFlow("try")
|
||||
.addStatement("return $T.parse(date, new $T(0))", Cl.GISO8601UTILS, ParsePosition.class)
|
||||
.nextControlFlow("catch ($T e)", ParseException.class)
|
||||
.addStatement("throw new $T(\"Failed Parsing '\" + date + \"' as Date\", e)", Cl.GSON_SYNTAX_EXCEPTION)
|
||||
.endControlFlow()
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -41,19 +41,20 @@ public class EnumAdapter extends Adapter<EnumAdapter.Hydrated> {
|
|||
|
||||
@Override
|
||||
public void generateRead() {
|
||||
CodeBlock.Builder kode = CodeBlock.builder();
|
||||
kode.beginControlFlow("for ($1T t : $1T.values())", tel)
|
||||
.addStatement("if (t.name().equals(value)) return t")
|
||||
.endControlFlow()
|
||||
.addStatement("return null");
|
||||
|
||||
String methodName = "read$" + name;
|
||||
klazz.addMethod(
|
||||
MethodSpec.methodBuilder(methodName)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||
.returns(TypeName.get(tel))
|
||||
.addParameter(String.class, "value")
|
||||
.addCode(kode.build())
|
||||
.addCode(
|
||||
CodeBlock.builder()
|
||||
.beginControlFlow("for ($1T t : $1T.values())", tel)
|
||||
.addStatement("if (t.name().equals(value)) return t")
|
||||
.endControlFlow()
|
||||
.addStatement("return null")
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
);
|
||||
code.add("$N(reader.nextString())", methodName);
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.adapter.impl;
|
||||
|
||||
import com.squareup.javapoet.*;
|
||||
import io.gitlab.jfronny.gson.compile.processor.Cl;
|
||||
import io.gitlab.jfronny.gson.compile.processor.TypeHelper;
|
||||
import io.gitlab.jfronny.gson.compile.processor.adapter.Adapter;
|
||||
import io.gitlab.jfronny.gson.compile.processor.util.ValUtils;
|
||||
|
||||
import javax.lang.model.element.ElementKind;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
public class MapAdapter extends Adapter<MapAdapter.Hydrated> {
|
||||
@Override
|
||||
public Hydrated instantiate() {
|
||||
return new Hydrated();
|
||||
}
|
||||
|
||||
public class Hydrated extends Adapter<MapAdapter.Hydrated>.Hydrated {
|
||||
private static final List<Class<? extends Map>> SUPPORTED = List.of(LinkedHashMap.class, HashMap.class, TreeMap.class);
|
||||
|
||||
private DeclaredType type;
|
||||
private TypeName implType;
|
||||
private TypeMirror componentType1;
|
||||
private TypeMirror componentType2;
|
||||
|
||||
@Override
|
||||
public boolean applies() {
|
||||
return type != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterHydrate() {
|
||||
type = TypeHelper.asDeclaredType(super.type);
|
||||
componentType1 = componentType2 = null;
|
||||
if (type == null) return;
|
||||
List<? extends TypeMirror> typeArguments = type.getTypeArguments();
|
||||
if (typeArguments.size() != 2) {
|
||||
type = null;
|
||||
} else {
|
||||
componentType1 = typeArguments.get(0);
|
||||
if (!isValidKey(componentType1)) {
|
||||
type = null;
|
||||
componentType1 = null;
|
||||
return;
|
||||
}
|
||||
componentType2 = typeArguments.get(1);
|
||||
String ts = TypeHelper.asDeclaredType(typeUtils.erasure(type)).asElement().toString();
|
||||
if (Map.class.getCanonicalName().equals(ts)) {
|
||||
implType = TypeName.get(SUPPORTED.get(0));
|
||||
return;
|
||||
}
|
||||
for (Class<?> klazz : SUPPORTED) {
|
||||
if (klazz.getCanonicalName().equals(ts)) {
|
||||
implType = TypeName.get(klazz);
|
||||
return;
|
||||
}
|
||||
}
|
||||
type = null;
|
||||
componentType1 = componentType2 = null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidKey(TypeMirror tm) {
|
||||
if (tm.toString().equals(String.class.getCanonicalName())) return true;
|
||||
if (tm.toString().equals(UUID.class.getCanonicalName())) return true;
|
||||
if (unbox(tm).getKind().isPrimitive()) return true;
|
||||
if (isEnum(tm)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void generateConvertKey(CodeBlock.Builder kode) {
|
||||
if (componentType1.toString().equals(String.class.getCanonicalName())) {
|
||||
kode.add("name");
|
||||
}
|
||||
else if (componentType1.toString().equals(UUID.class.getCanonicalName())) {
|
||||
kode.add("name == null ? null : $T.fromString(name)", UUID.class);
|
||||
}
|
||||
else if (unbox(componentType1).getKind().isPrimitive()) {
|
||||
kode.add("name == null ? null : " + switch (unbox(componentType1).getKind()) {
|
||||
case BOOLEAN -> "Boolean.parseBoolean(name)";
|
||||
case BYTE -> "Byte.parseByte(name)";
|
||||
case SHORT -> "Short.parseShort(name)";
|
||||
case INT -> "Integer.parseInt(name)";
|
||||
case LONG -> "Long.parseLong(name)";
|
||||
case CHAR -> "name.length() == 0 ? '\\0' : name.charAt(0)";
|
||||
case FLOAT -> "Float.parseFloat(name)";
|
||||
case DOUBLE -> "Double.parseDouble(name)";
|
||||
default -> throw new IllegalArgumentException("Unsupported primitive: " + unbox(componentType1).getKind());
|
||||
});
|
||||
}
|
||||
else if (isEnum(componentType1)) {
|
||||
String methodName = "readName$" + name;
|
||||
boolean found = false;
|
||||
for (MethodSpec spec : klazz.methodSpecs) {
|
||||
if (spec.name.equals(methodName)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
klazz.addMethod(
|
||||
MethodSpec.methodBuilder(methodName)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||
.returns(TypeName.get(componentType1))
|
||||
.addParameter(String.class, "value")
|
||||
.addCode(
|
||||
CodeBlock.builder()
|
||||
.beginControlFlow("for ($1T t : $1T.values())", componentType1)
|
||||
.addStatement("if (t.name().equals(value)) return t")
|
||||
.endControlFlow()
|
||||
.addStatement("return null")
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
kode.add("$N(name)", methodName);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEnum(TypeMirror tm) {
|
||||
DeclaredType declared = TypeHelper.asDeclaredType(tm);
|
||||
return declared != null && declared.asElement().getKind() == ElementKind.ENUM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateWrite(Runnable writeGet) {
|
||||
code.addStatement("writer.beginObject()");
|
||||
code.add("for ($T.Entry<$T, $T> $N : (", Map.class, componentType1, componentType2, argName);
|
||||
writeGet.run();
|
||||
code.beginControlFlow(").entrySet())")
|
||||
.add("if ($N.getKey() != null || writer.getSerializeNulls()) writer.value(", argName);
|
||||
if (isEnum(componentType1)) {
|
||||
code.add("$N.getKey() == null ? null : $N.getKey().name()", argName, argName);
|
||||
} else {
|
||||
code.add("$T.toString($N.getKey())", Objects.class, argName);
|
||||
}
|
||||
code.addStatement(")")
|
||||
.addStatement("$T value$N = $N.getValue()", componentType2, argName, argName)
|
||||
.beginControlFlow("if (value$N == null)", argName)
|
||||
.addStatement("if (writer.getSerializeNulls()) writer.nullValue()")
|
||||
.nextControlFlow("else");
|
||||
generateWrite(code, componentType2, "value" + argName, componentType2.getAnnotationMirrors(), () -> code.add("value" + argName));
|
||||
code.endControlFlow().endControlFlow().addStatement("writer.endObject()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateRead() {
|
||||
CodeBlock.Builder kode = CodeBlock.builder();
|
||||
kode.addStatement("$T map = new $T<>()", typeName, implType);
|
||||
|
||||
kode.addStatement("reader.beginObject()")
|
||||
.beginControlFlow("while (reader.hasNext())")
|
||||
.addStatement("String name = reader.nextName()")
|
||||
.beginControlFlow("if (reader.peek() == $T.NULL)", Cl.GSON_TOKEN)
|
||||
.addStatement("reader.nextNull()")
|
||||
.add("map.put(");
|
||||
generateConvertKey(kode);
|
||||
kode.addStatement(", null)")
|
||||
.nextControlFlow("else")
|
||||
.add("map.put(");
|
||||
generateConvertKey(kode);
|
||||
kode.add(", ");
|
||||
generateRead(kode, componentType2, argName, componentType2.getAnnotationMirrors());
|
||||
kode.addStatement(")")
|
||||
.endControlFlow()
|
||||
.endControlFlow()
|
||||
.addStatement("reader.endObject()")
|
||||
.addStatement("return map");
|
||||
|
||||
String methodName = "read$" + name;
|
||||
klazz.addMethod(
|
||||
MethodSpec.methodBuilder(methodName)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC)
|
||||
.returns(typeName)
|
||||
.addParameter(Cl.GSON_READER, "reader")
|
||||
.addException(IOException.class)
|
||||
.addCode(kode.build())
|
||||
.build()
|
||||
);
|
||||
code.add("$N(reader)", methodName);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue