159 lines
6.1 KiB
Java
159 lines
6.1 KiB
Java
package io.gitlab.jfronny.gson.compile.processor.core.value;
|
|
|
|
import io.gitlab.jfronny.gson.compile.annotations.GPrefer;
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* 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.typeUtils, 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.typeUtils, 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.typeUtils, 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.enclosedElements);
|
|
if (constructors.size() == 1) {
|
|
ExecutableElement constructor = constructors[0];
|
|
if (constructor.parameters.isEmpty) {
|
|
noArgConstructor = constructor;
|
|
constructors.remove(0);
|
|
}
|
|
}
|
|
for (ExecutableElement method : ElementFilter.methodsIn(klazz.enclosedElements)) {
|
|
Set<Modifier> modifiers = method.modifiers;
|
|
if (modifiers.contains(Modifier.STATIC)
|
|
&& !modifiers.contains(Modifier.PRIVATE)
|
|
&& method.returnType.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[0];
|
|
if (noArgConstructor != null) constructors.add(noArgConstructor);
|
|
List<ExecutableElement> preferred = new ArrayList<>();
|
|
for (ExecutableElement constructor : constructors) {
|
|
if (constructor.getAnnotationsByType(GPrefer.class).length != 0) {
|
|
preferred.add(constructor);
|
|
}
|
|
}
|
|
if (preferred.size() == 1) return preferred[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.kind != kind) {
|
|
throw new IllegalArgumentException("Expected " + kind + " but got: " + element);
|
|
}
|
|
}
|
|
}
|