gson-compile/gson-compile-processor-core/src/main/java/io/gitlab/jfronny/gson/compile/processor/core/value/ValueCreator.java

166 lines
6.4 KiB
Java

package io.gitlab.jfronny.gson.compile.processor.core.value;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.*;
import javax.lang.model.util.ElementFilter;
import java.lang.annotation.Annotation;
import java.util.*;
public class ValueCreator {
public static Class<? extends Annotation> preferAnnotation = null;
private final ProcessingEnvironment env;
public ValueCreator(ProcessingEnvironment env) {
this.env = env;
}
/**
* 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);
}
public Value fromStatic(TypeElement element) throws ElementException {
return create(new ConstructionSource.Static(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()) {
if (noArgConstructor != null) return noArgConstructor;
else throw new ElementException("Lacking constructor or factory method", klazz);
}
if (constructors.size() == 1) return constructors.get(0);
if (noArgConstructor != null) constructors.add(noArgConstructor);
if (preferAnnotation != null) {
List<ExecutableElement> preferred = new ArrayList<>();
for (ExecutableElement constructor : constructors) {
if (constructor.getAnnotationsByType(preferAnnotation).length != 0) {
preferred.add(constructor);
}
}
if (preferred.size() == 1) return preferred.get(0);
}
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);
}
}
}