net.zerobuilder.compiler.analyse.DtoGoalElement Maven / Gradle / Ivy
The newest version!
package net.zerobuilder.compiler.analyse;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeVariableName;
import net.zerobuilder.Builder;
import net.zerobuilder.Style;
import net.zerobuilder.Updater;
import net.zerobuilder.compiler.generate.Access;
import net.zerobuilder.compiler.generate.DtoContext;
import net.zerobuilder.compiler.generate.DtoGoalDetails.AbstractRegularDetails;
import net.zerobuilder.compiler.generate.DtoGoalDetails.BeanGoalDetails;
import net.zerobuilder.compiler.generate.DtoGoalDetails.ConstructorGoalDetails;
import net.zerobuilder.compiler.generate.DtoGoalDetails.InstanceMethodGoalDetails;
import net.zerobuilder.compiler.generate.DtoGoalDetails.StaticMethodGoalDetails;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;
import static javax.lang.model.element.ElementKind.CONSTRUCTOR;
import static javax.lang.model.element.Modifier.STATIC;
import static net.zerobuilder.compiler.analyse.DtoGoalElement.ModuleChoice.BUILDER;
import static net.zerobuilder.compiler.analyse.DtoGoalElement.ModuleChoice.UPDATER;
import static net.zerobuilder.compiler.common.LessTypes.asTypeElement;
import static net.zerobuilder.compiler.common.LessTypes.isDeclaredType;
import static net.zerobuilder.compiler.generate.ZeroUtil.downcase;
import static net.zerobuilder.compiler.generate.ZeroUtil.simpleName;
import static net.zerobuilder.compiler.generate.ZeroUtil.transform;
final class DtoGoalElement {
interface GoalElementCases {
R regularGoal(AbstractRegularGoalElement goal);
R beanGoal(BeanGoalElement goal);
}
private static Function asFunction(GoalElementCases cases) {
return goal -> goal.accept(cases);
}
interface AbstractGoalElement {
R accept(GoalElementCases goalElementCases);
}
interface RegularGoalElementCases {
R general(RegularGoalElement regular);
R projectable(RegularProjectableGoalElement projectable);
}
interface AbstractRegularGoalElement extends AbstractGoalElement {
R accept(RegularGoalElementCases cases);
}
private static Function asFunction(RegularGoalElementCases cases) {
return element -> element.accept(cases);
}
static Function regularGoalElementCases(
Function super RegularGoalElement, ? extends R> generalFunction,
Function super RegularProjectableGoalElement, ? extends R> projectableFunction) {
return asFunction(new RegularGoalElementCases() {
@Override
public R general(RegularGoalElement regular) {
return generalFunction.apply(regular);
}
@Override
public R projectable(RegularProjectableGoalElement projectable) {
return projectableFunction.apply(projectable);
}
});
}
static final Function executableElement =
regularGoalElementCases(
general -> general.executableElement,
projectable -> projectable.executableElement);
static Function goalElementCases(
Function super AbstractRegularGoalElement, ? extends R> regularGoalFunction,
Function super BeanGoalElement, ? extends R> beanGoalFunction) {
return asFunction(new GoalElementCases() {
@Override
public R regularGoal(AbstractRegularGoalElement executableGoal) {
return regularGoalFunction.apply(executableGoal);
}
@Override
public R beanGoal(BeanGoalElement beanGoal) {
return beanGoalFunction.apply(beanGoal);
}
});
}
private static final Function regularGoalName =
regularGoalElementCases(
general -> general.details.name,
projected -> projected.details.name);
static final Function goalName =
goalElementCases(
regularGoalName,
bean -> bean.details.name);
static final Function element =
goalElementCases(
regularGoalElementCases(
regular -> regular.executableElement,
projected -> projected.executableElement),
bean -> bean.beanType);
static final class RegularGoalElement implements AbstractRegularGoalElement {
final AbstractRegularDetails details;
final ExecutableElement executableElement;
final GoalModifiers goalAnnotation;
final DtoContext.GoalContext context;
final Style style;
private RegularGoalElement(ExecutableElement element, AbstractRegularDetails details,
DtoContext.GoalContext context, Style style) {
this.goalAnnotation = GoalModifiers.create(element);
this.details = details;
this.executableElement = element;
this.context = context;
this.style = style;
}
@Override
public R accept(GoalElementCases goalElementCases) {
return goalElementCases.regularGoal(this);
}
@Override
public R accept(RegularGoalElementCases cases) {
return cases.general(this);
}
}
static final class RegularProjectableGoalElement implements AbstractRegularGoalElement {
final AbstractRegularDetails details;
final ExecutableElement executableElement;
final GoalModifiers goalAnnotation;
final DtoContext.GoalContext context;
private RegularProjectableGoalElement(ExecutableElement element, AbstractRegularDetails details, DtoContext.GoalContext context) {
this.goalAnnotation = GoalModifiers.create(element);
this.details = details;
this.executableElement = element;
this.context = context;
}
@Override
public R accept(GoalElementCases goalElementCases) {
return goalElementCases.regularGoal(this);
}
@Override
public R accept(RegularGoalElementCases cases) {
return cases.projectable(this);
}
}
private static List parameterNames(ExecutableElement element) {
return transform(element.getParameters(),
parameter -> parameter.getSimpleName().toString());
}
static final class BeanGoalElement implements AbstractGoalElement {
final BeanGoalDetails details;
final TypeElement beanType;
final ModuleChoice moduleChoice;
private BeanGoalElement(ClassName goalType, String name, TypeElement beanType,
ModuleChoice moduleChoice, DtoContext.GoalContext context) {
this.moduleChoice = moduleChoice;
this.details = new BeanGoalDetails(goalType, name, Access.PUBLIC, context);
this.beanType = beanType;
}
static List create(TypeElement beanType, DtoContext.GoalContext context) {
ClassName goalType = ClassName.get(beanType);
String name = downcase(simpleName(goalType));
List goalOptions = Arrays.asList(BUILDER, UPDATER);
return transform(goalOptions,
goalOption -> new BeanGoalElement(goalType, name, beanType, goalOption, context));
}
@Override
public R accept(GoalElementCases goalElementCases) {
return goalElementCases.beanGoal(this);
}
}
enum ModuleChoice {
UPDATER, BUILDER
}
private static List goalOptions(ExecutableElement element) {
ArrayList options = new ArrayList<>(2);
if (element.getAnnotation(Builder.class) != null) {
options.add(BUILDER);
}
if (element.getAnnotation(Updater.class) != null) {
options.add(UPDATER);
}
return options;
}
static TypeName goalType(ExecutableElement goal) {
switch (goal.getKind()) {
case CONSTRUCTOR:
return ClassName.get(goal.getEnclosingElement().asType());
default:
return TypeName.get(goal.getReturnType());
}
}
static Function> createRegular(DtoContext.GoalContext context) {
return element -> {
TypeName goalType = goalType(element);
GoalModifiers modifiers = GoalModifiers.create(element);
List goalOptions = goalOptions(element);
String methodName = element.getSimpleName().toString();
return transform(goalOptions,
goalOption ->
goalOption == BUILDER ?
createBuilderGoal(element, goalType, modifiers, methodName,
parameterNames(element), context) :
createUpdaterGoal(element, goalType, modifiers, methodName,
parameterNames(element), context));
};
}
private static AbstractRegularGoalElement createUpdaterGoal(ExecutableElement element, TypeName goalType,
GoalModifiers goalModifiers,
String methodName,
List parameterNames,
DtoContext.GoalContext context) {
if (element.getKind() == CONSTRUCTOR) {
return new RegularProjectableGoalElement(element, ConstructorGoalDetails.create(
ClassName.get(asTypeElement(element.getEnclosingElement().asType())),
goalModifiers.goalName, parameterNames, goalModifiers.access, instanceTypevars(element),
goalModifiers.lifecycle), context);
}
AbstractRegularDetails details =
element.getModifiers().contains(STATIC) ?
StaticMethodGoalDetails.create(goalType, goalModifiers.goalName, parameterNames, methodName,
goalModifiers.access, methodTypevars(element), goalModifiers.lifecycle) :
InstanceMethodGoalDetails.create(goalType, goalModifiers.goalName, parameterNames, methodName,
goalModifiers.access,
methodTypevars(element),
instanceTypevars(element),
returnTypeInstanceTypevars(element),
goalModifiers.lifecycle);
return new RegularProjectableGoalElement(element, details, context);
}
private static List instanceTypevars(ExecutableElement element) {
TypeElement type = asTypeElement(element.getEnclosingElement().asType());
return transform(type.getTypeParameters(), TypeVariableName::get);
}
private static List returnTypeInstanceTypevars(ExecutableElement element) {
if (!isDeclaredType(element.getReturnType())) {
return emptyList();
}
TypeElement type = asTypeElement(element.getReturnType());
return transform(type.getTypeParameters(), TypeVariableName::get);
}
private static List methodTypevars(ExecutableElement element) {
return element.getTypeParameters().stream()
.map(TypeVariableName::get)
.collect(toList());
}
private static AbstractRegularGoalElement createBuilderGoal(ExecutableElement element, TypeName goalType,
GoalModifiers goalModifiers,
String methodName,
List parameterNames,
DtoContext.GoalContext context) {
Builder builderAnnotation = element.getAnnotation(Builder.class);
if (element.getKind() == CONSTRUCTOR) {
ConstructorGoalDetails details = ConstructorGoalDetails.create(
ClassName.get(asTypeElement(element.getEnclosingElement().asType())),
goalModifiers.goalName, parameterNames, goalModifiers.access, instanceTypevars(element),
goalModifiers.lifecycle);
return new RegularGoalElement(element, details, context, builderAnnotation.style());
}
AbstractRegularDetails details =
element.getModifiers().contains(STATIC) ?
StaticMethodGoalDetails.create(goalType, goalModifiers.goalName, parameterNames, methodName,
goalModifiers.access,
methodTypevars(element), goalModifiers.lifecycle) :
InstanceMethodGoalDetails.create(goalType, goalModifiers.goalName, parameterNames, methodName,
goalModifiers.access,
methodTypevars(element),
instanceTypevars(element),
returnTypeInstanceTypevars(element),
goalModifiers.lifecycle);
return new RegularGoalElement(element, details, context, builderAnnotation.style());
}
private DtoGoalElement() {
throw new UnsupportedOperationException("no instances");
}
}