org.derive4j.processor.GettersDerivator Maven / Gradle / Ivy
/*
* Copyright (c) 2018, Jean-Baptiste Giraudeau
*
* This file is part of "Derive4J - Annotation Processor".
*
* "Derive4J - Annotation Processor" is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* "Derive4J - Annotation Processor" is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with "Derive4J - Annotation Processor". If not, see .
*/
package org.derive4j.processor;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.NameAllocator;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeVariableName;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.lang.model.element.Modifier;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import org.derive4j.processor.api.Derivator;
import org.derive4j.processor.api.DeriveResult;
import org.derive4j.processor.api.DeriveUtils;
import org.derive4j.processor.api.DerivedCodeSpec;
import org.derive4j.processor.api.OptionModel;
import org.derive4j.processor.api.model.AlgebraicDataType;
import org.derive4j.processor.api.model.DataArgument;
import org.derive4j.processor.api.model.DataConstructor;
import org.derive4j.processor.api.model.MultipleConstructorsSupport;
import org.derive4j.processor.api.model.TypeRestriction;
import static org.derive4j.processor.Utils.asBoxedType;
import static org.derive4j.processor.Utils.joinStringsAsArguments;
import static org.derive4j.processor.api.DeriveResult.result;
import static org.derive4j.processor.api.model.DataConstructions.caseOf;
final class GettersDerivator implements Derivator {
private final DeriveUtils deriveUtils;
GettersDerivator(DeriveUtils deriveUtils) {
this.deriveUtils = deriveUtils;
}
@Override
public DeriveResult derive(AlgebraicDataType adt) {
return result(
adt.fields().stream().map(da -> deriveGetter(da, adt)).reduce(DerivedCodeSpec.none(), DerivedCodeSpec::append));
}
private DerivedCodeSpec deriveGetter(DataArgument field, AlgebraicDataType adt) {
return isLens(field, adt.dataConstruction().constructors())
? generateLensGetter(field, adt)
: generateOptionalGetter(field, adt);
}
private DerivedCodeSpec generateOptionalGetter(DataArgument field, AlgebraicDataType adt) {
String arg = asParameterName(adt);
OptionModel optionModel = deriveUtils.optionModel(adt.deriveConfig().flavour());
DeclaredType returnType = deriveUtils.types().getDeclaredType(optionModel.typeElement(),
field.type().accept(asBoxedType, deriveUtils.types()));
return caseOf(adt.dataConstruction()).multipleConstructors(MultipleConstructorsSupport.cases()
.visitorDispatch((visitorParam, visitorType, constructors) -> visitorDispatchOptionalGetterImpl(optionModel,
adt, visitorType, constructors, arg, field, returnType))
.functionsDispatch(constructors -> functionsDispatchOptionalGetterImpl(optionModel, adt, arg, constructors,
field, returnType)))
.otherwise(DerivedCodeSpec::none);
}
private DerivedCodeSpec visitorDispatchOptionalGetterImpl(OptionModel optionModel, AlgebraicDataType adt,
DeclaredType visitorType, List constructors, String arg, DataArgument field,
DeclaredType returnType) {
Function> returnTypeArg = tv -> deriveUtils.types().isSameType(tv,
adt.matchMethod().returnTypeVariable()) ? Optional.of(returnType) : Optional.empty();
Function> otherTypeArgs = tv -> Optional
.of(deriveUtils.elements().getTypeElement(Object.class.getName()).asType());
FieldSpec getterField = FieldSpec
.builder(TypeName.get(deriveUtils.resolve(deriveUtils.resolve(visitorType, returnTypeArg), otherTypeArgs)),
Utils.uncapitalize(field.fieldName() + "Getter"))
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer("$T.$L($L)", adt.deriveConfig().targetClass().className(),
MapperDerivator.visitorLambdaFactoryName(adt), optionalGetterLambdas(arg, optionModel, constructors, field))
.build();
MethodSpec getter;
if (adt.typeConstructor().typeVariables().isEmpty()) {
getter = getterBuilder(adt, arg, field, returnType)
.addStatement("return $L.$L($L)", arg, adt.matchMethod().element().getSimpleName(), getterField.name)
.build();
} else {
getter = getterBuilder(adt, arg, field, returnType)
.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
.addMember("value", "{$S, $S}", "unchecked", "rawtypes")
.build())
.addStatement("return ($T) $L.$L(($T) $L)", TypeName.get(returnType), arg,
adt.matchMethod().element().getSimpleName(), TypeName.get(deriveUtils.types().erasure(visitorType)),
getterField.name)
.build();
}
return DerivedCodeSpec.codeSpec(getterField, getter);
}
private DerivedCodeSpec generateLensGetter(DataArgument field, AlgebraicDataType adt) {
String arg = asParameterName(adt);
return caseOf(adt.dataConstruction())
.multipleConstructors(MultipleConstructorsSupport.cases()
.visitorDispatch((visitorParam, visitorType, constructors) -> visitorDispatchLensGetterImpl(adt, arg,
visitorType, field))
.functionsDispatch(constructors -> functionsDispatchLensGetterImpl(adt, arg, field)))
.oneConstructor(constructor -> functionsDispatchLensGetterImpl(adt, arg, field))
.noConstructor(DerivedCodeSpec::none);
}
private DerivedCodeSpec visitorDispatchLensGetterImpl(AlgebraicDataType adt, String arg, DeclaredType visitorType,
DataArgument field) {
Function> returnTypeArg = tv -> deriveUtils.types().isSameType(tv,
adt.matchMethod().returnTypeVariable())
? Optional.of(asBoxedType.visit(field.type(), deriveUtils.types()))
: Optional.empty();
Function> otherTypeArgs = tv -> Optional
.of(deriveUtils.elements().getTypeElement(Object.class.getName()).asType());
FieldSpec getterField = FieldSpec
.builder(TypeName.get(deriveUtils.resolve(deriveUtils.resolve(visitorType, returnTypeArg), otherTypeArgs)),
Utils.uncapitalize(field.fieldName() + "Getter"))
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer("$T.$L($L)", adt.deriveConfig().targetClass().className(),
MapperDerivator.visitorLambdaFactoryName(adt), lensGetterLambda(arg, adt, field))
.build();
final MethodSpec getter;
if (adt.typeConstructor().typeVariables().isEmpty()) {
getter = getterBuilder(adt, arg, field, field.type())
.addStatement("return $L.$L($L)", arg, adt.matchMethod().element().getSimpleName(), getterField.name)
.build();
} else {
getter = getterBuilder(adt, arg, field, field.type())
.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class)
.addMember("value", "{$S, $S}", "unchecked", "rawtypes")
.build())
.addStatement("return ($T) $L.$L(($T) $L)", TypeName.get(field.type()), arg,
adt.matchMethod().element().getSimpleName(), TypeName.get(deriveUtils.types().erasure(visitorType)),
getterField.name)
.build();
}
return DerivedCodeSpec.codeSpec(getterField, getter);
}
private static DerivedCodeSpec functionsDispatchOptionalGetterImpl(OptionModel optionModel, AlgebraicDataType adt,
String arg, List constructors, DataArgument field, DeclaredType returnType) {
return DerivedCodeSpec.methodSpec(getterBuilder(adt, arg, field, returnType).addCode(CodeBlock.builder()
.add("return $L.$L(", arg, adt.matchMethod().element().getSimpleName())
.add(optionalGetterLambdas(arg, optionModel, constructors, field))
.add(");")
.build()).build());
}
private static MethodSpec.Builder getterBuilder(AlgebraicDataType adt, String arg, DataArgument field,
TypeMirror type) {
return MethodSpec.methodBuilder("get" + Utils.capitalize(field.fieldName()))
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addTypeVariables(
adt.typeConstructor().typeVariables().stream().map(TypeVariableName::get).collect(Collectors.toList()))
.addParameter(TypeName.get(adt.typeConstructor().declaredType()), arg)
.returns(TypeName.get(type));
}
private static CodeBlock optionalGetterLambdas(String arg, OptionModel optionModel,
List constructors, DataArgument field) {
NameAllocator nameAllocator = new NameAllocator();
nameAllocator.newName(arg);
return constructors.stream().map(constructor -> {
CodeBlock.Builder caseImplBuilder = CodeBlock.builder().add("($L) -> $T.",
joinStringsAsArguments(Stream.concat(
constructor.arguments().stream().map(DataArgument::fieldName).map(
fn -> nameAllocator.clone().newName(fn, fn + " field")),
constructor.typeRestrictions().stream().map(TypeRestriction::typeEq).map(DataArgument::fieldName).map(
fn -> nameAllocator.clone().newName(fn, fn + " field")))),
ClassName.get(optionModel.typeElement()));
if (constructor.arguments().stream().anyMatch(da -> da.fieldName().equals(field.fieldName()))) {
caseImplBuilder.add("$L($L)", optionModel.someConstructor().getSimpleName(),
nameAllocator.clone().newName(field.fieldName(), field.fieldName() + " field"));
} else {
caseImplBuilder.add("$L()", optionModel.noneConstructor().getSimpleName());
}
return caseImplBuilder.build();
}).reduce((cb1, cb2) -> CodeBlock.builder().add(cb1).add(",\n").add(cb2).build()).orElse(
CodeBlock.builder().build());
}
private static DerivedCodeSpec functionsDispatchLensGetterImpl(AlgebraicDataType adt, String arg,
DataArgument field) {
return DerivedCodeSpec.methodSpec(getterBuilder(adt, arg, field, field.type()).addStatement("return $L.$L($L)", arg,
adt.matchMethod().element().getSimpleName(), lensGetterLambda(arg, adt, field)).build());
}
private static String lensGetterLambda(String arg, AlgebraicDataType adt, DataArgument field) {
NameAllocator nameAllocator = new NameAllocator();
nameAllocator.newName(arg);
return joinStringsAsArguments(adt.dataConstruction()
.constructors()
.stream()
.map(dc -> '('
+ joinStringsAsArguments(Stream.concat(
dc.arguments().stream().map(DataArgument::fieldName).map(
fn -> nameAllocator.clone().newName(fn, fn + " field")),
dc.typeRestrictions().stream().map(TypeRestriction::typeEq).map(DataArgument::fieldName).map(
fn -> nameAllocator.clone().newName(fn, fn + " field"))))
+ ") -> " + nameAllocator.clone().newName(field.fieldName(), field.fieldName() + " field")));
}
private static String asParameterName(AlgebraicDataType adt) {
return Utils.uncapitalize(adt.typeConstructor().typeElement().getSimpleName().toString());
}
private static boolean isLens(DataArgument field, List constructors) {
return constructors.stream()
.allMatch(dc -> dc.arguments().stream().anyMatch(da -> da.fieldName().equals(field.fieldName())));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy