Support for 1d classes of primitives
This commit is contained in:
commit
e2f59601c0
|
@ -0,0 +1,39 @@
|
|||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
|
@ -0,0 +1,2 @@
|
|||
group = "io.gitlab.jfronny"
|
||||
version = "1.0-SNAPSHOT"
|
|
@ -0,0 +1,19 @@
|
|||
plugins {
|
||||
id("java")
|
||||
}
|
||||
|
||||
group = "io.gitlab.jfronny.gson"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1")
|
||||
}
|
||||
|
||||
tasks.getByName<Test>("test") {
|
||||
useJUnitPlatform()
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package io.gitlab.jfronny.gson.compile.annotations;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface GPrefer {
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package io.gitlab.jfronny.gson.compile.annotations;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface GSerializable {
|
||||
Class<?> with() default void.class;
|
||||
boolean generateAdapter() default false;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
plugins {
|
||||
`java-library`
|
||||
}
|
||||
|
||||
group = "io.gitlab.jfronny.gson"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://gitlab.com/api/v4/projects/35745143/packages/maven") // Commons
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api("io.gitlab.jfronny:commons-gson:2022.10.22+20-29-33")
|
||||
}
|
||||
|
||||
tasks.getByName<Test>("test") {
|
||||
useJUnitPlatform()
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package io.gitlab.jfronny.gson.compile.core;
|
||||
|
||||
import io.gitlab.jfronny.commons.serialize.gson.api.v1.GsonHolder;
|
||||
|
||||
public class CCore {
|
||||
public static final GsonHolder HOLDER = new GsonHolder();
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package io.gitlab.jfronny.gson.compile.core;
|
||||
|
||||
import io.gitlab.jfronny.gson.TypeAdapter;
|
||||
import io.gitlab.jfronny.gson.stream.JsonReader;
|
||||
import io.gitlab.jfronny.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class Test {
|
||||
public static void main(String[] args) {
|
||||
new TypeAdapter<String>() {
|
||||
@Override
|
||||
public void write(JsonWriter jsonWriter, String s) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String read(JsonReader jsonReader) throws IOException {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
plugins {
|
||||
id("java")
|
||||
}
|
||||
|
||||
group = "io.gitlab.jfronny.gson"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://gitlab.com/api/v4/projects/35030495/packages/maven") // Gson
|
||||
maven("https://gitlab.com/api/v4/projects/35745143/packages/maven") // Commons
|
||||
}
|
||||
|
||||
dependencies {
|
||||
annotationProcessor(project(":gson-compile-processor"))
|
||||
compileOnly(project(":gson-compile-annotations"))
|
||||
implementation(project(":gson-compile-core"))
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package io.gitlab.jfronny.gson;
|
||||
|
||||
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
|
||||
import io.gitlab.jfronny.gson.stream.JsonReader;
|
||||
import io.gitlab.jfronny.gson.stream.JsonWriter;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello world!");
|
||||
//JsonReader
|
||||
}
|
||||
|
||||
@GSerializable(generateAdapter = true)
|
||||
public static class ExamplePojo {
|
||||
public String someValue;
|
||||
public Boolean someBool;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
plugins {
|
||||
id("java")
|
||||
}
|
||||
|
||||
group = "io.gitlab.jfronny.gson"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://gitlab.com/api/v4/projects/35745143/packages/maven")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":gson-compile-annotations"))
|
||||
implementation("org.jetbrains:annotations:23.0.0")
|
||||
implementation("io.gitlab.jfronny:commons:2022.10.22+20-29-33")
|
||||
implementation("com.squareup:javapoet:1.13.0")
|
||||
}
|
||||
|
||||
tasks.getByName<Test>("test") {
|
||||
useJUnitPlatform()
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor;
|
||||
|
||||
import com.squareup.javapoet.ClassName;
|
||||
|
||||
public class Const {
|
||||
public static final String PREFIX = "GC_";
|
||||
public static final String ADAPTER_PREFIX = "adapter_";
|
||||
public static final String ARG_PREFIX = "_";
|
||||
public static final String READ = "read";
|
||||
public static final String WRITE = "write";
|
||||
|
||||
public static final ClassName TYPE_ADAPTER = ClassName.get("io.gitlab.jfronny.gson", "TypeAdapter");
|
||||
public static final ClassName TYPE_ADAPTER_FACTORY = ClassName.get("io.gitlab.jfronny.gson", "TypeAdapterFactory");
|
||||
public static final ClassName GSON_ELEMENT = ClassName.get("io.gitlab.jfronny.gson", "JsonElement");
|
||||
public static final ClassName GSON_WRITER = ClassName.get("io.gitlab.jfronny.gson.stream", "JsonWriter");
|
||||
public static final ClassName GSON_READER = ClassName.get("io.gitlab.jfronny.gson.stream", "JsonReader");
|
||||
public static final ClassName GSON_TREE_READER = ClassName.get("io.gitlab.jfronny.gson.internal.bind", "JsonTreeReader");
|
||||
public static final ClassName GSON_TREE_WRITER = ClassName.get("io.gitlab.jfronny.gson.internal.bind", "JsonTreeWriter");
|
||||
static final ClassName JSON_ADAPTER = ClassName.get("io.gitlab.jfronny.gson.annotations", "JsonAdapter");
|
||||
static final ClassName TYPE_TOKEN = ClassName.get("io.gitlab.jfronny.gson.reflect", "TypeToken");
|
||||
static final ClassName GSON_TOKEN = ClassName.get("io.gitlab.jfronny.gson.stream", "JsonToken");
|
||||
|
||||
public static final ClassName CCORE = ClassName.get("io.gitlab.jfronny.gson.compile.core", "CCore");
|
||||
}
|
|
@ -0,0 +1,588 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor;
|
||||
|
||||
import com.squareup.javapoet.*;
|
||||
import io.gitlab.jfronny.commons.StringFormatter;
|
||||
import io.gitlab.jfronny.gson.compile.annotations.GSerializable;
|
||||
import io.gitlab.jfronny.gson.compile.processor.util.AbstractProcessor2;
|
||||
import io.gitlab.jfronny.gson.compile.processor.util.SupportedAnnotationTypes2;
|
||||
import io.gitlab.jfronny.gson.compile.processor.util.valueprocessor.*;
|
||||
import io.gitlab.jfronny.gson.compile.processor.util.valueprocessor.Properties;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.*;
|
||||
import javax.lang.model.util.*;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
//TODO support for records
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_17)
|
||||
@SupportedAnnotationTypes2({GSerializable.class})
|
||||
public class GsonCompileProcessor extends AbstractProcessor2 {
|
||||
private Messager message;
|
||||
private Types typeUtils;
|
||||
private Filer filer;
|
||||
private Set<ClassName> seen;
|
||||
private ValueCreator valueCreator;
|
||||
private Elements elements;
|
||||
|
||||
@Override
|
||||
public synchronized void init(ProcessingEnvironment processingEnv) {
|
||||
super.init(processingEnv);
|
||||
message = processingEnv.getMessager();
|
||||
filer = processingEnv.getFiler();
|
||||
typeUtils = processingEnv.getTypeUtils();
|
||||
elements = processingEnv.getElementUtils();
|
||||
seen = new LinkedHashSet<>();
|
||||
valueCreator = new ValueCreator(processingEnv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
|
||||
Set<ToProcess> toProcesses = new LinkedHashSet<>();
|
||||
for (TypeElement annotation : annotations) {
|
||||
for (Element element : roundEnvironment.getElementsAnnotatedWith(annotation)) {
|
||||
for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
|
||||
if (mirror.getAnnotationType().toString().equals(GSerializable.class.getCanonicalName())) {
|
||||
var bld = new Object() {
|
||||
TypeMirror with = null;
|
||||
Boolean generateAdapter = null;
|
||||
};
|
||||
elements.getElementValuesWithDefaults(mirror).forEach((executableElement, value) -> {
|
||||
String name = executableElement.getSimpleName().toString();
|
||||
switch (name) {
|
||||
case "with" -> {
|
||||
if (bld.with != null) throw new IllegalArgumentException("Duplicate annotation parameter: with");
|
||||
bld.with = (TypeMirror) value.getValue();
|
||||
}
|
||||
case "generateAdapter" -> {
|
||||
if (bld.generateAdapter != null) throw new IllegalArgumentException("Duplicate annotation parameter: generateAdapter");
|
||||
bld.generateAdapter = (Boolean) value.getValue();
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Unexpected annotation parameter: " + name);
|
||||
}
|
||||
});
|
||||
if (bld.with == null) throw new IllegalArgumentException("Missing annotation parameter: with");
|
||||
if (bld.generateAdapter == null) throw new IllegalArgumentException("Missing annotation parameter: generateAdapter");
|
||||
if (bld.with.toString().equals("void")) bld.with = null;
|
||||
if (bld.with != null && bld.generateAdapter) throw new IllegalArgumentException("Adapter for " + element + " already exists, not generating another!");
|
||||
toProcesses.add(new ToProcess((TypeElement) element, bld.with, bld.generateAdapter));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ToProcess toProcess : toProcesses) {
|
||||
try {
|
||||
process(toProcess.element, toProcess.adapter, toProcess.generateAdapter);
|
||||
} catch (IOException | ElementException e) {
|
||||
message.printMessage(Diagnostic.Kind.ERROR, "GsonCompile threw an exception: " + StringFormatter.toString(e), toProcess.element);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
record ToProcess(TypeElement element, @Nullable TypeMirror adapter, boolean generateAdapter) {}
|
||||
|
||||
private void process(TypeElement classElement, @Nullable TypeMirror adapter, boolean generateAdapter) throws IOException, ElementException {
|
||||
ClassName className = ClassName.get(classElement);
|
||||
if (!seen.add(className)) return; // Don't process the same class more than once
|
||||
|
||||
ClassName generatedClassName = ClassName.get(className.packageName(), Const.PREFIX + String.join("_", className.simpleNames()));
|
||||
TypeName classType = TypeName.get(classElement.asType());
|
||||
List<TypeVariableName> typeVariables = new ArrayList<>();
|
||||
if (classType instanceof ParameterizedTypeName type) {
|
||||
for (TypeName argument : type.typeArguments) {
|
||||
typeVariables.add(TypeVariableName.get(argument.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
TypeSpec.Builder spec = TypeSpec.classBuilder(generatedClassName.simpleName())
|
||||
.addOriginatingElement(classElement)
|
||||
.addTypeVariables(typeVariables)
|
||||
.addModifiers(Modifier.PUBLIC);
|
||||
|
||||
if (adapter != null) {
|
||||
generateDelegateToAdapter(spec, classType, adapter);
|
||||
} else {
|
||||
if (generateAdapter) {
|
||||
generateDelegatingAdapter(spec, classType, generatedClassName);
|
||||
}
|
||||
generateSerialisation(spec, classType, classElement, typeVariables);
|
||||
}
|
||||
|
||||
generateAuxiliary(spec, classType);
|
||||
|
||||
//TODO register adapter as available and use in generated adapters
|
||||
|
||||
JavaFile javaFile = JavaFile.builder(className.packageName(), spec.build())
|
||||
.skipJavaLangImports(true)
|
||||
.indent(" ")
|
||||
.build();
|
||||
javaFile.writeTo(filer);
|
||||
message.printMessage(Diagnostic.Kind.NOTE, "Processed " + className);
|
||||
}
|
||||
|
||||
private static void generateDelegatingAdapter(TypeSpec.Builder spec, TypeName classType, ClassName generatedClassName) {
|
||||
spec.addType(
|
||||
TypeSpec.classBuilder("Adapter")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.superclass(ParameterizedTypeName.get(Const.TYPE_ADAPTER, classType))
|
||||
.addMethod(MethodSpec.methodBuilder("write")
|
||||
.addAnnotation(Override.class)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addParameter(Const.GSON_WRITER, "writer")
|
||||
.addParameter(classType, "value")
|
||||
.addException(IOException.class)
|
||||
.addCode(generatedClassName.simpleName() + ".write(writer, value);")
|
||||
.build())
|
||||
.addMethod(MethodSpec.methodBuilder("read")
|
||||
.addAnnotation(Override.class)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addParameter(Const.GSON_READER, "reader")
|
||||
.addException(IOException.class)
|
||||
.returns(classType)
|
||||
.addCode("return " + generatedClassName.simpleName() + ".read(reader);")
|
||||
.build())
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
private static void generateDelegateToAdapter(TypeSpec.Builder spec, TypeName classType, TypeMirror adapter) {
|
||||
TypeName adapterType = TypeName.get(adapter);
|
||||
spec.addField(
|
||||
FieldSpec.builder(adapterType, "ADAPTER", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
|
||||
.initializer("new $T()", adapterType)
|
||||
.build()
|
||||
);
|
||||
spec.addMethod(
|
||||
MethodSpec.methodBuilder(Const.READ)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Const.GSON_READER, "reader")
|
||||
.addException(IOException.class)
|
||||
.returns(classType)
|
||||
.addCode("return ADAPTER.read(reader);")
|
||||
.build()
|
||||
);
|
||||
spec.addMethod(
|
||||
MethodSpec.methodBuilder(Const.READ)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Const.GSON_READER, "writer")
|
||||
.addParameter(classType, "value")
|
||||
.addException(IOException.class)
|
||||
.returns(classType)
|
||||
.addCode("ADAPTER.write(reader, value);")
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
private static void generateAuxiliary(TypeSpec.Builder spec, TypeName classType) {
|
||||
spec.addMethod(
|
||||
MethodSpec.methodBuilder(Const.READ)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(TypeName.get(Reader.class), "in")
|
||||
.addException(IOException.class)
|
||||
.returns(classType)
|
||||
.addCode("""
|
||||
try ($1T reader = new $1T(in)) {
|
||||
return read(reader);
|
||||
}""", Const.GSON_READER)
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
MethodSpec.methodBuilder(Const.READ)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(TypeName.get(String.class), "json")
|
||||
.addException(IOException.class)
|
||||
.returns(classType)
|
||||
.addCode("""
|
||||
try ($1T reader = new $1T(json)) {
|
||||
return read(reader);
|
||||
}""", StringReader.class)
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
MethodSpec.methodBuilder(Const.READ)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Const.GSON_ELEMENT, "tree")
|
||||
.addException(IOException.class)
|
||||
.returns(classType)
|
||||
.addCode("""
|
||||
try ($1T reader = new $1T(tree)) {
|
||||
return read(reader);
|
||||
}""", Const.GSON_TREE_READER)
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
MethodSpec.methodBuilder(Const.WRITE)
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Writer.class, "out")
|
||||
.addParameter(classType, "value")
|
||||
.addException(IOException.class)
|
||||
.addCode("""
|
||||
try ($1T writer = new $1T(out)) {
|
||||
write(writer, value);
|
||||
}""", Const.GSON_WRITER)
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
MethodSpec.methodBuilder("toJson")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(classType, "value")
|
||||
.addException(IOException.class)
|
||||
.returns(String.class)
|
||||
.addCode("""
|
||||
try ($1T writer = new $1T()) {
|
||||
write(writer, value);
|
||||
return writer.toString();
|
||||
}""", StringWriter.class)
|
||||
.build()
|
||||
);
|
||||
|
||||
spec.addMethod(
|
||||
MethodSpec.methodBuilder("toJsonTree")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(classType, "value")
|
||||
.addException(IOException.class)
|
||||
.returns(Const.GSON_ELEMENT)
|
||||
.addCode("""
|
||||
try ($1T writer = new $1T()) {
|
||||
write(writer, value);
|
||||
return writer.get();
|
||||
}""", Const.GSON_TREE_WRITER)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
private void generateSerialisation(TypeSpec.Builder spec, TypeName classType, TypeElement classElement, List<TypeVariableName> typeVariables) throws ElementException {
|
||||
Value value = valueCreator.from(classElement);
|
||||
ConstructionSource constructionSource = value.getConstructionSource();
|
||||
Properties properties = value.getProperties();
|
||||
|
||||
// public static void write(JsonWriter writer, T value) throws IOException
|
||||
{
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
code.beginControlFlow("if (value == null)")
|
||||
.addStatement("writer.nullValue()")
|
||||
.addStatement("return")
|
||||
.endControlFlow();
|
||||
|
||||
code.addStatement("writer.beginObject()");
|
||||
for (Property.Field field : properties.fields) {
|
||||
code.addStatement("writer.name($S)", getSerializedName(field));
|
||||
generateWrite(field, spec, code, "writer", "value.$N", typeVariables);
|
||||
}
|
||||
for (Property.Getter getter : properties.getters) {
|
||||
code.addStatement("writer.name($S)", getSerializedName(getter));
|
||||
generateWrite(getter, spec, code, "writer", "value.$N()", typeVariables);
|
||||
}
|
||||
code.addStatement("writer.endObject()");
|
||||
|
||||
spec.addMethod(MethodSpec.methodBuilder("write")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Const.GSON_WRITER, "writer")
|
||||
.addParameter(classType, "value")
|
||||
.addException(IOException.class)
|
||||
.addCode(code.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
// public static T read(JsonReader reader) throws IOException
|
||||
{
|
||||
CodeBlock.Builder code = CodeBlock.builder();
|
||||
code.beginControlFlow("if (reader.peek() == $T.NULL)", Const.GSON_TOKEN)
|
||||
.addStatement("reader.nextNull()")
|
||||
.addStatement("return null")
|
||||
.endControlFlow();
|
||||
|
||||
boolean isEmpty = true;
|
||||
for (Property<?> param : properties.names) {
|
||||
isEmpty = false;
|
||||
code.addStatement("$T $L = $L", param.getType(), Const.ARG_PREFIX + param.getName(), getDefaultValue(param.getType()));
|
||||
}
|
||||
if (isEmpty) {
|
||||
code.addStatement("reader.skipValue()");
|
||||
} else {
|
||||
code.addStatement("reader.beginObject()")
|
||||
.beginControlFlow("while (reader.hasNext())")
|
||||
.beginControlFlow("switch (reader.nextName())");
|
||||
for (Property<?> param : properties.names) {
|
||||
String read = generateRead(param, spec, "reader", typeVariables);
|
||||
if (param.getType().getKind().isPrimitive()) {
|
||||
code.add("case $S -> ", getSerializedName(param));
|
||||
code.addStatement("$L = $L", Const.ARG_PREFIX + param.getName(), read);
|
||||
} else {
|
||||
code.beginControlFlow("case $S ->", getSerializedName(param))
|
||||
.beginControlFlow("if (reader.peek() == $T.NULL)", Const.GSON_TOKEN)
|
||||
.addStatement("reader.nextNull()")
|
||||
.addStatement("$L = null", Const.ARG_PREFIX + param.getName())
|
||||
.endControlFlow("else $L = $L", Const.ARG_PREFIX + param.getName(), read)
|
||||
.endControlFlow();
|
||||
}
|
||||
}
|
||||
code.add("default -> ")
|
||||
.addStatement("reader.skipValue()");
|
||||
|
||||
code.endControlFlow()
|
||||
.endControlFlow()
|
||||
.addStatement("reader.endObject()");
|
||||
}
|
||||
|
||||
ClassName creatorName = ClassName.get((TypeElement) constructionSource.getConstructionElement().getEnclosingElement());
|
||||
if (constructionSource instanceof ConstructionSource.Builder builder) {
|
||||
String args = properties.constructorParams.stream().map(s -> Const.ARG_PREFIX + s.getName()).collect(Collectors.joining(", "));
|
||||
if (constructionSource.isConstructor()) {
|
||||
code.add("return new $T($L)", builder.getBuilderClass(), args);
|
||||
} else {
|
||||
code.add("return $T.$L($L)", creatorName, classElement.getSimpleName(), args);
|
||||
}
|
||||
code.add("\n").indent();
|
||||
for (Property.BuilderParam param : properties.builderParams) {
|
||||
code.add(".$L($L)\n", param.getCallableName(), Const.ARG_PREFIX + param.getName());
|
||||
}
|
||||
code.add(".$L();\n", builder.getBuildMethod().getSimpleName()).unindent();
|
||||
} else {
|
||||
String args = properties.params.stream().map(s -> Const.ARG_PREFIX + s.getName()).collect(Collectors.joining(", "));
|
||||
if (constructionSource.isConstructor()) {
|
||||
code.addStatement("return new $T($L)", classType, args);
|
||||
} else {
|
||||
code.addStatement("return $T.$L($L)", creatorName, constructionSource.getConstructionElement().getSimpleName(), args);
|
||||
}
|
||||
}
|
||||
|
||||
spec.addMethod(MethodSpec.methodBuilder("read")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.returns(classType)
|
||||
.addParameter(Const.GSON_READER, "reader")
|
||||
.addException(IOException.class)
|
||||
.addCode(code.build())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
private void generateWrite(Property<?> prop, TypeSpec.Builder klazz, CodeBlock.Builder code, String writer, String get, List<TypeVariableName> typeVariables) {
|
||||
TypeMirror unboxed = unbox(prop.getType());
|
||||
if (unbox(prop.getType()).getKind().isPrimitive() || prop.getType().toString().equals(String.class.getCanonicalName())) {
|
||||
if (prop.getType().equals(unboxed)) {
|
||||
code.addStatement("$L.value(" + get + ")", writer, prop.getCallableName());
|
||||
} else {
|
||||
String pName = Const.ARG_PREFIX + prop.getName();
|
||||
code.addStatement("$T $L = " + get, prop.getType(), pName, prop.getCallableName())
|
||||
.beginControlFlow("if ($L == null)", pName)
|
||||
.addStatement("$L.nullValue()", writer)
|
||||
.endControlFlow("else $L.value($L)", writer, pName);
|
||||
}
|
||||
return;
|
||||
}
|
||||
//TODO handle lists, sets, arrays, other common types (-> https://github.com/bluelinelabs/LoganSquare/blob/development/docs/TypeConverters.md)
|
||||
//TODO support comment annotations
|
||||
code.addStatement(getAdapter(prop, klazz, typeVariables) + ".write(" + writer + ", " + get + ")", prop.getCallableName());
|
||||
}
|
||||
|
||||
private String generateRead(Property<?> prop, TypeSpec.Builder klazz, String reader, List<TypeVariableName> typeVariables) {
|
||||
TypeMirror unboxed = unbox(prop.getType());
|
||||
if (unboxed.getKind().isPrimitive()) {
|
||||
return switch (unboxed.getKind()) {
|
||||
case BOOLEAN -> reader + ".nextBoolean()";
|
||||
case BYTE -> "(byte) " + reader + ".nextInt()";
|
||||
case SHORT -> "(short) " + reader + ".nextInt()";
|
||||
case INT -> reader + ".nextInt()";
|
||||
case LONG -> reader + ".nextLong()";
|
||||
case CHAR -> "(char) " + reader + ".nextInt()";
|
||||
case FLOAT -> "(float) " + reader + ".nextDouble()";
|
||||
case DOUBLE -> reader + ".nextDouble()";
|
||||
default -> throw new IllegalArgumentException("Unsupported primitive: " + unboxed.getKind());
|
||||
};
|
||||
}
|
||||
if (prop.getType().toString().equals(String.class.getCanonicalName())) {
|
||||
return reader + ".nextString()";
|
||||
}
|
||||
//TODO handle lists, sets, arrays, other common types (-> https://github.com/bluelinelabs/LoganSquare/blob/development/docs/TypeConverters.md)
|
||||
return getAdapter(prop, klazz, typeVariables) + ".read(" + reader + ")";
|
||||
}
|
||||
|
||||
private String getAdapter(Property<?> prop, TypeSpec.Builder klazz, List<TypeVariableName> typeVariables) {
|
||||
String typeAdapterName = Const.ADAPTER_PREFIX + prop.getName();
|
||||
for (FieldSpec spec : klazz.fieldSpecs) {
|
||||
if (spec.name.equals(typeAdapterName)) return typeAdapterName;
|
||||
}
|
||||
DeclaredType typeAdapterClass = findTypeAdapterClass(prop.getAnnotations());
|
||||
if (typeAdapterClass != null) {
|
||||
if (isInstance(typeAdapterClass, Const.TYPE_ADAPTER.toString())) {
|
||||
klazz.addField(
|
||||
FieldSpec.builder(TypeName.get(typeAdapterClass), typeAdapterName)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
|
||||
.initializer("new $T()", typeAdapterClass)
|
||||
.build()
|
||||
);
|
||||
} else if (isInstance(typeAdapterClass, Const.TYPE_ADAPTER_FACTORY.toString())) {
|
||||
TypeName typeAdapterType = ParameterizedTypeName.get(Const.TYPE_ADAPTER, TypeName.get(prop.getType()).box());
|
||||
CodeBlock.Builder block = CodeBlock.builder();
|
||||
block.add("new $T().create($T.HOLDER.getGson(), ", typeAdapterClass, Const.CCORE);
|
||||
appendFieldTypeToken(block, prop, typeVariables, false);
|
||||
block.add(")");
|
||||
klazz.addField(
|
||||
FieldSpec.builder(typeAdapterType, typeAdapterName)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
|
||||
.initializer(block.build())
|
||||
.build()
|
||||
);
|
||||
} else message.printMessage(Diagnostic.Kind.ERROR, "@JsonAdapter value must by TypeAdapter or TypeAdapterFactory reference.", prop.getElement());
|
||||
} else {
|
||||
//TODO handle known custom type adapters and return proper static class
|
||||
TypeName typeAdapterType = ParameterizedTypeName.get(Const.TYPE_ADAPTER, TypeName.get(prop.getType()).box());
|
||||
CodeBlock.Builder block = CodeBlock.builder();
|
||||
block.add("$T.HOLDER.getGson().getAdapter(", Const.CCORE);
|
||||
appendFieldTypeToken(block, prop, typeVariables, true);
|
||||
block.add(")");
|
||||
klazz.addField(
|
||||
FieldSpec.builder(typeAdapterType, typeAdapterName)
|
||||
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
|
||||
.initializer(block.build())
|
||||
.build()
|
||||
);
|
||||
}
|
||||
return typeAdapterName;
|
||||
}
|
||||
|
||||
private void appendFieldTypeToken(CodeBlock.Builder block, Property<?> property, List<TypeVariableName> typeVariables, boolean allowClassType) {
|
||||
TypeMirror type = property.getType();
|
||||
TypeName typeName = TypeName.get(type);
|
||||
|
||||
if (isComplexType(type)) {
|
||||
TypeName typeTokenType = ParameterizedTypeName.get(Const.TYPE_TOKEN, typeName);
|
||||
List<? extends TypeMirror> typeParams = getGenericTypes(type);
|
||||
if (typeParams.isEmpty()) {
|
||||
block.add("new $T() {}", typeTokenType);
|
||||
} else {
|
||||
block.add("($T) $T.getParameterized($T.class, ", typeTokenType, Const.TYPE_TOKEN, typeUtils.erasure(type));
|
||||
for (Iterator<? extends TypeMirror> iterator = typeParams.iterator(); iterator.hasNext(); ) {
|
||||
TypeMirror typeParam = iterator.next();
|
||||
int typeIndex = typeVariables.indexOf(TypeVariableName.get(typeParam.toString()));
|
||||
block.add("(($T)typeToken.getType()).getActualTypeArguments()[$L]", ParameterizedType.class, typeIndex);
|
||||
if (iterator.hasNext()) {
|
||||
block.add(", ");
|
||||
}
|
||||
}
|
||||
block.add(")");
|
||||
}
|
||||
} else if (isGenericType(type)) {
|
||||
TypeName typeTokenType = ParameterizedTypeName.get(Const.TYPE_TOKEN, typeName);
|
||||
int typeIndex = typeVariables.indexOf(TypeVariableName.get(property.getType().toString()));
|
||||
block.add("($T) $T.get((($T)typeToken.getType()).getActualTypeArguments()[$L])",
|
||||
typeTokenType, Const.TYPE_TOKEN, ParameterizedType.class, typeIndex);
|
||||
} else {
|
||||
if (allowClassType) {
|
||||
block.add("$T.class", typeName);
|
||||
} else {
|
||||
block.add("TypeToken.get($T.class)", typeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isComplexType(TypeMirror type) {
|
||||
Element element = typeUtils.asElement(type);
|
||||
if (!(element instanceof TypeElement typeElement)) return false;
|
||||
return !typeElement.getTypeParameters().isEmpty();
|
||||
}
|
||||
|
||||
private boolean isGenericType(TypeMirror type) {
|
||||
return type.getKind() == TypeKind.TYPEVAR;
|
||||
}
|
||||
|
||||
private List<? extends TypeMirror> getGenericTypes(TypeMirror type) {
|
||||
DeclaredType declaredType = asDeclaredType(type);
|
||||
if (declaredType == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
ArrayList<TypeMirror> result = new ArrayList<>();
|
||||
for (TypeMirror argType : declaredType.getTypeArguments()) {
|
||||
if (argType.getKind() == TypeKind.TYPEVAR) {
|
||||
result.add(argType);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String getDefaultValue(TypeMirror type) {
|
||||
switch (type.getKind()) {
|
||||
case BYTE:
|
||||
case SHORT:
|
||||
case INT:
|
||||
case LONG:
|
||||
case FLOAT:
|
||||
case CHAR:
|
||||
case DOUBLE:
|
||||
return "0";
|
||||
case BOOLEAN:
|
||||
return "false";
|
||||
default:
|
||||
return "null";
|
||||
}
|
||||
}
|
||||
|
||||
private TypeMirror unbox(TypeMirror type) {
|
||||
try {
|
||||
return typeUtils.unboxedType(type);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSerializedName(Property<?> property) {
|
||||
for (AnnotationMirror annotationMirror : property.getAnnotations()) {
|
||||
if (annotationMirror.getAnnotationType().asElement().toString().equals("com.google.gson.annotations.SerializedName")) {
|
||||
return (String) annotationMirror.getElementValues().values().iterator().next().getValue();
|
||||
}
|
||||
}
|
||||
return property.getName();
|
||||
}
|
||||
|
||||
private DeclaredType findTypeAdapterClass(List<? extends AnnotationMirror> annotations) {
|
||||
for (AnnotationMirror annotation : annotations) {
|
||||
String typeName = annotation.getAnnotationType().toString();
|
||||
if (typeName.equals(Const.JSON_ADAPTER.toString())) {
|
||||
Map<? extends ExecutableElement, ? extends AnnotationValue> elements = annotation.getElementValues();
|
||||
if (!elements.isEmpty()) {
|
||||
AnnotationValue value = elements.values().iterator().next();
|
||||
return (DeclaredType) value.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isInstance(DeclaredType type, String parentClassName) {
|
||||
if (type == null) return false;
|
||||
TypeElement element = (TypeElement) type.asElement();
|
||||
for (TypeMirror interfaceType : element.getInterfaces()) {
|
||||
if (typeUtils.erasure(interfaceType).toString().equals(parentClassName)) return true;
|
||||
}
|
||||
TypeMirror superclassType = element.getSuperclass();
|
||||
if (superclassType != null) {
|
||||
if (typeUtils.erasure(superclassType).toString().equals(parentClassName)) {
|
||||
return true;
|
||||
} else {
|
||||
return isInstance(asDeclaredType(superclassType), parentClassName);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static DeclaredType asDeclaredType(TypeMirror type) {
|
||||
return type.accept(new SimpleTypeVisitor14<>() {
|
||||
@Override
|
||||
public DeclaredType visitDeclared(DeclaredType t, Object o) {
|
||||
return t;
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.util;
|
||||
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class AbstractProcessor2 extends AbstractProcessor {
|
||||
@Override
|
||||
public Set<String> getSupportedAnnotationTypes() {
|
||||
return Optional.ofNullable(this.getClass().getAnnotation(SupportedAnnotationTypes.class))
|
||||
.map(SupportedAnnotationTypes::value)
|
||||
.map(Set::of)
|
||||
.or(() -> Optional.ofNullable(this.getClass().getAnnotation(SupportedAnnotationTypes2.class))
|
||||
.map(SupportedAnnotationTypes2::value)
|
||||
.map(Arrays::stream)
|
||||
.map(s -> s.map(Class::getCanonicalName).collect(Collectors.toSet()))
|
||||
).orElse(Set.of());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class DelegateList<T> implements List<T> {
|
||||
private final List<T> delegate;
|
||||
|
||||
public DelegateList(List<T> delegate) {
|
||||
Objects.requireNonNull(delegate);
|
||||
this.delegate = delegate instanceof DelegateList<T> dl ? dl.unwrap() : delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return delegate.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return delegate.contains(o);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return delegate.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<? super T> action) {
|
||||
delegate.forEach(action);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return delegate.toArray();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public <T1> T1[] toArray(@NotNull T1[] t1s) {
|
||||
return delegate.toArray(t1s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T1> T1[] toArray(IntFunction<T1[]> generator) {
|
||||
return delegate.toArray(generator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(T t) {
|
||||
return delegate.add(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return delegate.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(@NotNull Collection<?> collection) {
|
||||
return delegate.containsAll(collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(@NotNull Collection<? extends T> collection) {
|
||||
return delegate.addAll(collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int i, @NotNull Collection<? extends T> collection) {
|
||||
return delegate.addAll(i, collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(@NotNull Collection<?> collection) {
|
||||
return delegate.removeAll(collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeIf(Predicate<? super T> filter) {
|
||||
return delegate.removeIf(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(@NotNull Collection<?> collection) {
|
||||
return delegate.retainAll(collection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceAll(UnaryOperator<T> operator) {
|
||||
delegate.replaceAll(operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sort(Comparator<? super T> c) {
|
||||
delegate.sort(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
delegate.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(int i) {
|
||||
return delegate.get(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T set(int i, T t) {
|
||||
return delegate.set(i, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int i, T t) {
|
||||
delegate.add(i, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T remove(int i) {
|
||||
return delegate.remove(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
return delegate.indexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
return delegate.lastIndexOf(o);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ListIterator<T> listIterator() {
|
||||
return delegate.listIterator();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ListIterator<T> listIterator(int i) {
|
||||
return delegate.listIterator(i);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<T> subList(int i, int i1) {
|
||||
return delegate.subList(i, i1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<T> spliterator() {
|
||||
return delegate.spliterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<T> stream() {
|
||||
return delegate.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<T> parallelStream() {
|
||||
return delegate.parallelStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return delegate.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof DelegateList<?> dl ? delegate.equals(dl.delegate) : delegate.equals(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return delegate.toString();
|
||||
}
|
||||
|
||||
private List<T> unwrap() {
|
||||
return delegate instanceof DelegateList<T> dl ? dl.unwrap() : delegate;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.util;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@Documented
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface SupportedAnnotationTypes2 {
|
||||
Class<?>[] value();
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.util.valueprocessor;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.ElementFilter;
|
||||
import javax.lang.model.util.Types;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* How a [Value] can be constructed. Either a constructor, factory method, or builder.
|
||||
*/
|
||||
public sealed interface ConstructionSource {
|
||||
/**
|
||||
* The target [Value] class to construct.
|
||||
*/
|
||||
TypeElement getTargetClass();
|
||||
/**
|
||||
* The executable element to construct the [Value]. This may be a constructor, factory method, or builder.
|
||||
*/
|
||||
ExecutableElement getConstructionElement();
|
||||
/**
|
||||
* If this source is a constructor (either of the value or the builder).
|
||||
*/
|
||||
boolean isConstructor();
|
||||
/**
|
||||
* If this source is a builder.
|
||||
*/
|
||||
boolean isBuilder();
|
||||
|
||||
final class Constructor implements ConstructionSource {
|
||||
private final ExecutableElement constructor;
|
||||
private TypeElement targetClass;
|
||||
|
||||
public Constructor(ExecutableElement constructor) {
|
||||
this.constructor = Objects.requireNonNull(constructor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeElement getTargetClass() {
|
||||
return targetClass != null ? targetClass : (targetClass = (TypeElement) constructor.getEnclosingElement());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableElement getConstructionElement() {
|
||||
return constructor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstructor() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBuilder() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
final class Factory implements ConstructionSource {
|
||||
private final Types types;
|
||||
private final ExecutableElement method;
|
||||
private TypeElement targetClass;
|
||||
|
||||
public Factory(Types types, ExecutableElement method) {
|
||||
this.types = types;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeElement getTargetClass() {
|
||||
return targetClass != null ? targetClass : (targetClass = (TypeElement) types.asElement(method.getReturnType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableElement getConstructionElement() {
|
||||
return method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstructor() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBuilder() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
sealed abstract class Builder implements ConstructionSource {
|
||||
public abstract TypeElement getBuilderClass();
|
||||
public abstract ExecutableElement getBuildMethod();
|
||||
|
||||
@Override
|
||||
public boolean isBuilder() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
final class BuilderConstructor extends Builder {
|
||||
private final Types types;
|
||||
private final ExecutableElement constructor;
|
||||
private TypeElement targetClass;
|
||||
private TypeElement builderClass;
|
||||
private ExecutableElement buildMethod;
|
||||
|
||||
public BuilderConstructor(Types types, ExecutableElement constructor) {
|
||||
this.types = types;
|
||||
this.constructor = constructor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeElement getTargetClass() {
|
||||
return targetClass != null ? targetClass : (targetClass = (TypeElement) types.asElement(getBuildMethod().getReturnType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableElement getConstructionElement() {
|
||||
return constructor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstructor() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeElement getBuilderClass() {
|
||||
return builderClass != null ? builderClass : (builderClass = (TypeElement) constructor.getEnclosingElement());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableElement getBuildMethod() {
|
||||
return buildMethod != null ? buildMethod : (buildMethod = findBuildMethod((TypeElement) constructor.getEnclosingElement()));
|
||||
}
|
||||
}
|
||||
|
||||
final class BuilderFactory extends Builder {
|
||||
private final Types types;
|
||||
private final ExecutableElement method;
|
||||
private TypeElement targetClass;
|
||||
private TypeElement builderClass;
|
||||
private ExecutableElement buildMethod;
|
||||
|
||||
public BuilderFactory(Types types, ExecutableElement method) {
|
||||
this.types = types;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeElement getTargetClass() {
|
||||
return targetClass != null ? targetClass : (targetClass = (TypeElement) types.asElement(getBuildMethod().getReturnType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableElement getConstructionElement() {
|
||||
return method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstructor() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeElement getBuilderClass() {
|
||||
return builderClass != null ? builderClass : (builderClass = (TypeElement) types.asElement(method.getReturnType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableElement getBuildMethod() {
|
||||
return buildMethod != null ? buildMethod : (buildMethod = findBuildMethod((TypeElement) types.asElement(method.getReturnType())));
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
static ExecutableElement findBuildMethod(TypeElement builderClass) {
|
||||
// Ok, maybe there is just one possible builder method.
|
||||
{
|
||||
ExecutableElement candidate = null;
|
||||
boolean foundMultipleCandidates = false;
|
||||
boolean isCandidateReasonableBuilderMethodName = false;
|
||||
for (ExecutableElement method : ElementFilter.methodsIn(builderClass.getEnclosedElements())) {
|
||||
if (isPossibleBuilderMethod(method, builderClass)) {
|
||||
if (candidate == null) {
|
||||
candidate = method;
|
||||
} else {
|
||||
// Multiple possible methods, keep the one with a reasonable builder name if possible.
|
||||
foundMultipleCandidates = true;
|
||||
isCandidateReasonableBuilderMethodName = isCandidateReasonableBuilderMethodName || isReasonableBuilderMethodName(candidate);
|
||||
if (isCandidateReasonableBuilderMethodName) {
|
||||
if (isReasonableBuilderMethodName(method)) {
|
||||
// both reasonable, too ambiguous.
|
||||
candidate = null;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
candidate = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (candidate != null && (!foundMultipleCandidates || isCandidateReasonableBuilderMethodName)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
// Last try, check to see if the immediate parent class makes sense.
|
||||
{
|
||||
Element candidate = builderClass.getEnclosingElement();
|
||||
if (candidate.getKind() == ElementKind.CLASS) {
|
||||
for (ExecutableElement method : ElementFilter.methodsIn(builderClass.getEnclosedElements())) {
|
||||
if (method.getReturnType().equals(candidate.asType()) && method.getParameters().isEmpty()) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Well, I give up.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A possible builder method has no parameters and a return type of the class we want to
|
||||
* construct. Therefore, the return type is not going to be void, primitive, or a platform
|
||||
* class.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
static boolean isPossibleBuilderMethod(ExecutableElement method, TypeElement builderClass) {
|
||||
if (!method.getParameters().isEmpty()) return false;
|
||||
TypeMirror returnType = method.getReturnType();
|
||||
if (returnType.getKind() == TypeKind.VOID) return false;
|
||||
if (returnType.getKind().isPrimitive()) return false;
|
||||
if (returnType.equals(builderClass.asType())) return false;
|
||||
String returnTypeName = returnType.toString();
|
||||
return !(returnTypeName.startsWith("java.") || returnTypeName.startsWith("javax.") || returnTypeName.startsWith("android."));
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
static boolean isReasonableBuilderMethodName(ExecutableElement method) {
|
||||
String methodName = method.getSimpleName().toString().toLowerCase(Locale.ROOT);
|
||||
return methodName.startsWith("build") || methodName.startsWith("create");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.util.valueprocessor;
|
||||
|
||||
import javax.annotation.processing.Messager;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ElementException extends Exception {
|
||||
private final List<Message> messages;
|
||||
|
||||
public ElementException(String message, Element element) {
|
||||
this(List.of(new Message(message, element)));
|
||||
}
|
||||
|
||||
public ElementException(List<Message> messages) {
|
||||
super(messages.stream().map(Message::message).collect(Collectors.joining("\n")));
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
public void printMessage(Messager messager) {
|
||||
for (Message message : messages) {
|
||||
if (message.element != null) messager.printMessage(Diagnostic.Kind.ERROR, message.message, message.element);
|
||||
else messager.printMessage(Diagnostic.Kind.ERROR, message.message);
|
||||
}
|
||||
}
|
||||
|
||||
public static record Message(String message, Element element) {}
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.util.valueprocessor;
|
||||
|
||||
import io.gitlab.jfronny.gson.compile.processor.util.DelegateList;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import javax.lang.model.util.ElementFilter;
|
||||
import javax.lang.model.util.Types;
|
||||
import java.util.*;
|
||||
|
||||
public class Properties extends DelegateList<Property<?>> {
|
||||
public final List<Property<?>> names;
|
||||
public final List<Property.Param> params;
|
||||
public final List<Property.Field> fields;
|
||||
public final List<Property.Getter> getters;
|
||||
public final List<Property.ConstructorParam> constructorParams;
|
||||
public final List<Property.BuilderParam> builderParams;
|
||||
|
||||
public static Properties build(Types types, ConstructionSource constructionSource) throws ElementException {
|
||||
Builder builder = new Builder(types);
|
||||
// constructor params
|
||||
for (VariableElement param : constructionSource.getConstructionElement().getParameters()) {
|
||||
builder.addConstructorParam(param);
|
||||
}
|
||||
|
||||
if (constructionSource instanceof ConstructionSource.Builder csb) {
|
||||
var builderClass = csb.getBuilderClass();
|
||||
for (ExecutableElement method : ElementFilter.methodsIn(builderClass.getEnclosedElements())) {
|
||||
builder.addBuilderParam(builderClass.asType(), method);
|
||||
}
|
||||
}
|
||||
|
||||
var targetClass = constructionSource.getTargetClass();
|
||||
builder.addFieldsAndGetters(targetClass);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private Properties(List<Property<?>> names,
|
||||
List<Property.Param> params,
|
||||
List<Property.Field> fields,
|
||||
List<Property.Getter> getters,
|
||||
List<Property.ConstructorParam> constructorParams,
|
||||
List<Property.BuilderParam> builderParams) {
|
||||
super(names);
|
||||
this.names = Objects.requireNonNull(names);
|
||||
this.params = Objects.requireNonNull(params);
|
||||
this.fields = Objects.requireNonNull(fields);
|
||||
this.getters = Objects.requireNonNull(getters);
|
||||
this.constructorParams = Objects.requireNonNull(constructorParams);
|
||||
this.builderParams = Objects.requireNonNull(builderParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Properties other)) return false;
|
||||
return names.equals(other.names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return names.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return names.toString();
|
||||
}
|
||||
|
||||
private static class Builder {
|
||||
private static final Set<String> METHODS_TO_SKIP = Set.of("hashCode", "toString", "clone");
|
||||
|
||||
private final Types types;
|
||||
public final List<Property<?>> names = new ArrayList<>();
|
||||
public final List<Property.Param> params = new ArrayList<>();
|
||||
public final List<Property.Field> fields = new ArrayList<>();
|
||||
public final List<Property.Getter> getters = new ArrayList<>();
|
||||
public final List<Property.ConstructorParam> constructorParams = new ArrayList<>();
|
||||
public final List<Property.BuilderParam> builderParams = new ArrayList<>();
|
||||
|
||||
public Builder(Types types) {
|
||||
this.types = types;
|
||||
}
|
||||
|
||||
public void addFieldsAndGetters(TypeElement targetClass) {
|
||||
// getters
|
||||
for (ExecutableElement method : ElementFilter.methodsIn(targetClass.getEnclosedElements())) {
|
||||
addGetter(targetClass, method);
|
||||
}
|
||||
|
||||
// fields
|
||||
for (VariableElement field : ElementFilter.fieldsIn(targetClass.getEnclosedElements())) {
|
||||
addField(field);
|
||||
}
|
||||
|
||||
for (TypeMirror superInterface : targetClass.getInterfaces()) {
|
||||
addFieldsAndGetters((TypeElement) types.asElement(superInterface));
|
||||
}
|
||||
|
||||
TypeMirror superclass = targetClass.getSuperclass();
|
||||
if (superclass.getKind() != TypeKind.NONE && !superclass.toString().equals("java.lang.Object")) {
|
||||
addFieldsAndGetters((TypeElement) types.asElement(superclass));
|
||||
}
|
||||
}
|
||||
|
||||
public void addGetter(TypeElement classElement, ExecutableElement method) {
|
||||
Set<Modifier> modifiers = method.getModifiers();
|
||||
if (modifiers.contains(Modifier.PRIVATE)
|
||||
|| modifiers.contains(Modifier.STATIC)
|
||||
|| method.getReturnType().getKind() == TypeKind.VOID
|
||||
|| !method.getParameters().isEmpty()
|
||||
|| isMethodToSkip(classElement, method)) {
|
||||
return;
|
||||
}
|
||||
getters.add(new Property.Getter(method));
|
||||
}
|
||||
|
||||
public void addField(VariableElement field) {
|
||||
Set<Modifier> modifiers = field.getModifiers();
|
||||
if (modifiers.contains(Modifier.STATIC)) return;
|
||||
fields.add(new Property.Field(field));
|
||||
}
|
||||
|
||||
public void addConstructorParam(VariableElement param) {
|
||||
Property.ConstructorParam prop = new Property.ConstructorParam(param);
|
||||
constructorParams.add(prop);
|
||||
params.add(prop);
|
||||
}
|
||||
|
||||
public void addBuilderParam(TypeMirror builderType, ExecutableElement method) {
|
||||
if (method.getReturnType().equals(builderType) && method.getParameters().size() == 1) {
|
||||
Property.BuilderParam prop = new Property.BuilderParam(method);
|
||||
builderParams.add(prop);
|
||||
params.add(prop);
|
||||
}
|
||||
}
|
||||
|
||||
public Properties build() throws ElementException {
|
||||
stripBeans(getters);
|
||||
removeExtraBuilders();
|
||||
removeGettersForTransientFields();
|
||||
mergeSerializeNames(params, fields, getters);
|
||||
removeExtraFields();
|
||||
names.addAll(params);
|
||||
fields.stream().filter(f -> !containsName(names, f)).forEach(names::add);
|
||||
getters.stream().filter(f -> !containsName(names, f)).forEach(names::add);
|
||||
return new Properties(names, params, fields, getters, constructorParams, builderParams);
|
||||
}
|
||||
|
||||
private void stripBeans(List<Property.Getter> getters) {
|
||||
if (getters.stream().allMatch(Property.Getter::isBean)) {
|
||||
for (Property.Getter getter : getters) {
|
||||
getter.stripBean();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeExtraBuilders() {
|
||||
for (int i = builderParams.size() - 1; i >= 0; i--) {
|
||||
Property.BuilderParam builderParam = builderParams.get(i);
|
||||
if (containsName(constructorParams, builderParam)) {
|
||||
builderParams.remove(i);
|
||||
params.remove(builderParam);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeExtraFields() {
|
||||
for (int i = fields.size() - 1; i >= 0; i--) {
|
||||
Property.Field field = fields.get(i);
|
||||
Set<Modifier> modifiers = field.element.getModifiers();
|
||||
if (modifiers.contains(Modifier.PRIVATE)
|
||||
|| modifiers.contains(Modifier.TRANSIENT)
|
||||
|| containsName(getters, field)) {
|
||||
fields.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeGettersForTransientFields() {
|
||||
for (int i = getters.size() - 1; i >= 0; i--) {
|
||||
Property.Getter getter = getters.get(i);
|
||||
Property<?> field = findName(fields, getter);
|
||||
if (field != null && field.element.getModifiers().contains(Modifier.TRANSIENT)) {
|
||||
getters.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMethodToSkip(TypeElement classElement, ExecutableElement method) {
|
||||
String name = method.getSimpleName().toString();
|
||||
if (METHODS_TO_SKIP.contains(name)) {
|
||||
return true;
|
||||
}
|
||||
return isKotlinClass(classElement) && name.matches("component[0-9]+");
|
||||
}
|
||||
}
|
||||
|
||||
private static void merge(Property<?>[] properties) throws ElementException {
|
||||
if (properties.length == 0) return;
|
||||
List<AnnotationMirror> annotations = null;
|
||||
for (Property<?> name : properties) {
|
||||
if (name == null) continue;
|
||||
if (!name.getAnnotations().isEmpty()) {
|
||||
if (annotations == null) annotations = new ArrayList<>(name.getAnnotations());
|
||||
else {
|
||||
for (AnnotationMirror annotation : name.getAnnotations()) {
|
||||
if (annotations.contains(annotation)) {
|
||||
throw new ElementException("Duplicate annotation " + annotation + " found on " + name, name.element);
|
||||
} else annotations.add(annotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (annotations != null) {
|
||||
for (Property<?> name : properties) {
|
||||
if (name == null) continue;
|
||||
name.setAnnotations(annotations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private static void mergeSerializeNames(List<? extends Property<?>>... propertyLists) throws ElementException {
|
||||
if (propertyLists.length == 0) return;
|
||||
for (Property<?> name : propertyLists[0]) {
|
||||
var names = new Property<?>[propertyLists.length];
|
||||
names[0] = name;
|
||||
for (int i = 1; i < propertyLists.length; i++) {
|
||||
names[i] = findName(propertyLists[i], name);
|
||||
}
|
||||
merge(names);
|
||||
}
|
||||
}
|
||||
|
||||
private static <N extends Property<?>> N findName(List<N> names, Property<?> property) {
|
||||
return names.stream().filter(n -> n.getName().equals(property.getName())).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
private static boolean containsName(List<? extends Property<?>> properties, Property<?> property) {
|
||||
return findName(properties, property) != null;
|
||||
}
|
||||
|
||||
private static boolean isKotlinClass(TypeElement element) {
|
||||
return element.getAnnotationMirrors().stream().anyMatch(m -> m.getAnnotationType().toString().equals("kotlin.Metadata"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,160 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.util.valueprocessor;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
import java.util.List;
|
||||
|
||||
public abstract sealed class Property<T extends Element> {
|
||||
protected final T element;
|
||||
private List<? extends AnnotationMirror> annotations;
|
||||
|
||||
public Property(T element) {
|
||||
this.element = element;
|
||||
this.annotations = element.getAnnotationMirrors();
|
||||
}
|
||||
|
||||
public T getElement() {
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the property. For fields and params this is the name in code. For getters, it may have the 'get' or
|
||||
* 'is' prefix stripped.
|
||||
* @see #getCallableName()
|
||||
*/
|
||||
public String getName() {
|
||||
return element.getSimpleName().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* The actual name of the property. This will not have any 'get' or 'is' prefix stripped.
|
||||
* @see #getName()
|
||||
*/
|
||||
public String getCallableName() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* The property's type.
|
||||
*/
|
||||
public TypeMirror getType() {
|
||||
return element.asType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotations relevant to the property. These may be copied from another source. For example, if this is a getter
|
||||
* it may contain the annotations on the backing private field.
|
||||
*/
|
||||
public List<? extends AnnotationMirror> getAnnotations() {
|
||||
return annotations;
|
||||
}
|
||||
|
||||
public void setAnnotations(List<? extends AnnotationMirror> annotations) {
|
||||
this.annotations = annotations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName() + ": " + getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
if (this == obj) return true;
|
||||
if (!(obj instanceof Property<?> other)) return false;
|
||||
return element.equals(other.element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return element.hashCode();
|
||||
}
|
||||
|
||||
public static final class Field extends Property<VariableElement> {
|
||||
public Field(VariableElement element) {
|
||||
super(element);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Getter extends Property<ExecutableElement> {
|
||||
private static final String BEAN_PREFIX = "get";
|
||||
private static final String BEAN_PREFIX_BOOL = "is";
|
||||
|
||||
private boolean stripBean = false;
|
||||
|
||||
public Getter(ExecutableElement element) {
|
||||
super(element);
|
||||
}
|
||||
|
||||
public boolean isBean() {
|
||||
return getBeanPrefix() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
String name = super.getName();
|
||||
if (stripBean) {
|
||||
String prefix = getBeanPrefix();
|
||||
if (prefix != null) {
|
||||
return Character.toLowerCase(name.charAt(prefix.length())) + name.substring(prefix.length() + 1);
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCallableName() {
|
||||
return super.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeMirror getType() {
|
||||
return element.getReturnType();
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public void stripBean() {
|
||||
stripBean = true;
|
||||
}
|
||||
|
||||
private String getBeanPrefix() {
|
||||
String name = super.getName();
|
||||
if (element.getReturnType().getKind() == TypeKind.BOOLEAN) {
|
||||
if (name.length() > BEAN_PREFIX_BOOL.length() && name.startsWith(BEAN_PREFIX_BOOL)) {
|
||||
return BEAN_PREFIX_BOOL;
|
||||
}
|
||||
}
|
||||
return name.length() > BEAN_PREFIX.length() && name.startsWith(BEAN_PREFIX) ? BEAN_PREFIX : null;
|
||||
}
|
||||
}
|
||||
|
||||
public static sealed abstract class Param extends Property<VariableElement> {
|
||||
public Param(VariableElement element) {
|
||||
super(element);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ConstructorParam extends Param {
|
||||
public ConstructorParam(VariableElement element) {
|
||||
super(element);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class BuilderParam extends Param {
|
||||
private final ExecutableElement method;
|
||||
|
||||
public BuilderParam(ExecutableElement method) {
|
||||
super(method.getParameters().get(0));
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCallableName() {
|
||||
return method.getSimpleName().toString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.util.valueprocessor;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class Value {
|
||||
private final ProcessingEnvironment env;
|
||||
private final ConstructionSource constructionSource;
|
||||
private final TypeElement element;
|
||||
private Properties properties;
|
||||
|
||||
public Value(ProcessingEnvironment env, ConstructionSource constructionSource) {
|
||||
this.env = env;
|
||||
this.constructionSource = constructionSource;
|
||||
this.element = constructionSource.getTargetClass();
|
||||
}
|
||||
|
||||
public Properties getProperties() throws ElementException {
|
||||
return properties != null ? properties : (properties = Properties.build(env.getTypeUtils(), constructionSource));
|
||||
}
|
||||
|
||||
public ConstructionSource getConstructionSource() {
|
||||
return constructionSource;
|
||||
}
|
||||
|
||||
public TypeElement getElement() {
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Value value)) return false;
|
||||
return element.equals(value.element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return element.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
return element.toString() + '{' + getProperties().stream().map(Objects::toString).collect(Collectors.joining(", "));
|
||||
} catch (ElementException e) {
|
||||
return element.toString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package io.gitlab.jfronny.gson.compile.processor.util.valueprocessor;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.util.ElementFilter;
|
||||
import java.util.*;
|
||||
|
||||
public class ValueCreator {
|
||||
private final ProcessingEnvironment env;
|
||||
|
||||
public ValueCreator(ProcessingEnvironment env) {
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
public Value from(Element element) throws ElementException {
|
||||
return from(element, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [Value] from the given element. This element can be the [TypeElement] of the target class, or a
|
||||
* specific constructor or factory method. If [isBuilder] is true, then the element represents the builder class,
|
||||
* constructor or factory method.
|
||||
*/
|
||||
public Value from(Element element, boolean isBuilder) throws ElementException {
|
||||
if (element instanceof TypeElement tel) {
|
||||
return isBuilder ? fromBuilderClass(tel) : fromClass(tel);
|
||||
} else if (element instanceof ExecutableElement xel) {
|
||||
if (xel.getKind() == ElementKind.CONSTRUCTOR) {
|
||||
return isBuilder ? fromBuilderConstructor(xel) : fromConstructor(xel);
|
||||
} else {
|
||||
return isBuilder ? fromBuilderFactory(xel) : fromFactory(xel);
|
||||
}
|
||||
} else throw new IllegalArgumentException("Expected TypeElement or ExecutableElement but got: " + element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [Value] from the given constructor element. ex:
|
||||
* ```
|
||||
* public class Value {
|
||||
* > public Value(int arg1) { ... }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
public Value fromConstructor(ExecutableElement constructor) {
|
||||
checkKind(constructor, ElementKind.CONSTRUCTOR);
|
||||
return create(new ConstructionSource.Constructor(constructor));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [Value] from the given builder's constructor element. ex:
|
||||
* ```
|
||||
* public class Builder {
|
||||
* > public Builder() { ... }
|
||||
* public Value build() { ... }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
public Value fromBuilderConstructor(ExecutableElement constructor) {
|
||||
checkKind(constructor, ElementKind.CONSTRUCTOR);
|
||||
return create(new ConstructionSource.BuilderConstructor(env.getTypeUtils(), constructor));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [Value] from the given factory method element. ex:
|
||||
* ```
|
||||
* public class Value {
|
||||
* > public static Value create(int arg) { ... }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
public Value fromFactory(ExecutableElement factory) {
|
||||
checkKind(factory, ElementKind.METHOD);
|
||||
return create(new ConstructionSource.Factory(env.getTypeUtils(), factory));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [Value] from the given builder factory method element. ex:
|
||||
* ```
|
||||
* public class Value {
|
||||
* > public static Builder builder() { ... }
|
||||
* public static class Builder { ... }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
public Value fromBuilderFactory(ExecutableElement builderFactory) {
|
||||
checkKind(builderFactory, ElementKind.METHOD);
|
||||
return create(new ConstructionSource.BuilderFactory(env.getTypeUtils(), builderFactory));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [Value] from the given class. ex:
|
||||
* ```
|
||||
* > public class Value { ... }
|
||||
* ```
|
||||
*/
|
||||
public Value fromClass(TypeElement targetClass) throws ElementException {
|
||||
ExecutableElement creator = findConstructorOrFactory(targetClass);
|
||||
return creator.getKind() == ElementKind.CONSTRUCTOR ? fromConstructor(creator) : fromFactory(creator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [Value] from the given builder class. ex:
|
||||
* ```
|
||||
* > public class Builder {
|
||||
* public Value build() { ... }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
public Value fromBuilderClass(TypeElement builderClass) throws ElementException {
|
||||
ExecutableElement creator = findConstructorOrFactory(builderClass);
|
||||
return creator.getKind() == ElementKind.CONSTRUCTOR ? fromBuilderConstructor(creator) : fromBuilderFactory(creator);
|
||||
}
|
||||
|
||||
private Value create(ConstructionSource constructionSource) {
|
||||
return new Value(env, constructionSource);
|
||||
}
|
||||
|
||||
private static ExecutableElement findConstructorOrFactory(TypeElement klazz) throws ElementException {
|
||||
ExecutableElement noArgConstructor = null;
|
||||
List<ExecutableElement> constructors = ElementFilter.constructorsIn(klazz.getEnclosedElements());
|
||||
if (constructors.size() == 1) {
|
||||
ExecutableElement constructor = constructors.get(0);
|
||||
if (constructor.getParameters().isEmpty()) {
|
||||
noArgConstructor = constructor;
|
||||
constructors.remove(0);
|
||||
}
|
||||
}
|
||||
for (ExecutableElement method : ElementFilter.methodsIn(klazz.getEnclosedElements())) {
|
||||
Set<Modifier> modifiers = method.getModifiers();
|
||||
if (modifiers.contains(Modifier.STATIC)
|
||||
&& !modifiers.contains(Modifier.PRIVATE)
|
||||
&& method.getReturnType().equals(klazz.asType())) {
|
||||
constructors.add(method);
|
||||
}
|
||||
}
|
||||
if (constructors.isEmpty() && noArgConstructor != null) {
|
||||
constructors.add(noArgConstructor);
|
||||
}
|
||||
if (constructors.size() == 1) {
|
||||
return constructors.get(0);
|
||||
} else {
|
||||
List<ElementException.Message> messages = new ArrayList<>();
|
||||
messages.add(new ElementException.Message("More than one constructor or factory method found.", klazz));
|
||||
constructors.stream().map(s -> new ElementException.Message(" " + s, s)).forEach(messages::add);
|
||||
throw new ElementException(messages);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkKind(Element element, ElementKind kind) {
|
||||
if (element.getKind() != kind) {
|
||||
throw new IllegalArgumentException("Expected " + kind + " but got: " + element);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
io.gitlab.jfronny.gson.compile.processor.GsonCompileProcessor
|
|
@ -0,0 +1,5 @@
|
|||
rootProject.name = "gson-compile"
|
||||
include("gson-compile-core")
|
||||
include("gson-compile-processor")
|
||||
include("gson-compile-annotations")
|
||||
include("gson-compile-example")
|
Loading…
Reference in New Issue