com.sap.cds.generator.writer.CreateBuilderInterfaceVisitor Maven / Gradle / Ivy
/************************************************************************
* © 2019-2024 SAP SE or an SAP affiliate company. All rights reserved. *
************************************************************************/
package com.sap.cds.generator.writer;
import static com.sap.cds.generator.util.NamesUtils.className;
import static com.sap.cds.generator.util.NamesUtils.methodName;
import static com.sap.cds.generator.util.NamesUtils.rawName;
import static com.sap.cds.generator.util.NamesUtils.suffixedClassName;
import static com.sap.cds.generator.util.TypeUtils.getAnonymousElements;
import static com.sap.cds.generator.util.TypeUtils.getReturnType;
import static com.sap.cds.generator.util.TypeUtils.isAnonymousAspect;
import static com.sap.cds.generator.writer.SpecWriterUtil.addCdsNameAnnotation;
import static com.sap.cds.generator.writer.SpecWriterUtil.addStaticField;
import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.PUBLIC;
import java.util.stream.Stream;
import javax.lang.model.element.Modifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sap.cds.generator.Configuration;
import com.sap.cds.generator.util.TypeUtils;
import com.sap.cds.generator.writer.ModelWriter.Context;
import com.sap.cds.ql.CdsName;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsDefinition;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsEvent;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.reflect.CdsType;
import com.sap.cds.reflect.CdsVisitor;
import com.palantir.javapoet.AnnotationSpec;
import com.palantir.javapoet.ClassName;
import com.palantir.javapoet.FieldSpec;
import com.palantir.javapoet.MethodSpec;
import com.palantir.javapoet.ParameterizedTypeName;
import com.palantir.javapoet.TypeName;
import com.palantir.javapoet.TypeSpec;
public class CreateBuilderInterfaceVisitor implements CdsVisitor {
private static final Logger logger = LoggerFactory.getLogger(CreateBuilderInterfaceVisitor.class);
private static final String FILTER = "filter";
private final TypeSpec.Builder entityClass;
private final Configuration cfg;
private final ClassName builderClassName;
private final ClassName entityClassName;
private final Context context;
CreateBuilderInterfaceVisitor(TypeSpec.Builder builder, ClassName builderClassName, ClassName entityClassName, Context context) {
this.entityClass = builder;
this.context = context;
this.cfg = context.config();
// Builder interface require the pair of names: the name of the top-level interface for the builder itself
this.builderClassName = builderClassName;
// and the name of the entity class which will be used as a prefix
this.entityClassName = entityClassName;
}
@Override
public void visit(CdsEntity entity) {
generateInterface(entity);
}
@Override
public void visit(CdsEvent event) {
generateInterface(event);
}
@Override
public void visit(CdsStructuredType struct) {
generateInterface(struct);
}
@Override
public void visit(CdsElement attribute) {
if (context.isTenantDiscriminator(attribute)) {
return;
}
if (attribute.getType().isStructured()) {
addStructuredAttribute(attribute);
} else if (attribute.getType().isAssociation()) {
addAssociationAttribute(attribute);
} else {
addAttribute(attribute);
}
}
private void generateInterface(CdsType def) {
ClassName innerBuilderClassName = suffixedClassName(context.config(), def);
entityClass.addSuperinterface(ParameterizedTypeName.get(Types.STRUCTURED_TYPE, innerBuilderClassName));
entityClass.addAnnotation(
AnnotationSpec.builder(CdsName.class).addMember("value", "$S", def.getQualifiedName()).build());
addStaticQualifiedAttribute(def);
}
private void addStructuredAttribute(CdsElement attribute) {
if (!TypeUtils.isIgnored(attribute)) {
TypeName returnType;
if (attribute.getType().as(CdsStructuredType.class).isAnonymous()) {
Stream elements =
attribute.getType().as(CdsStructuredType.class).elements();
ClassName innerInterfaceName = suffixedClassName(cfg, builderClassName, attribute);
addInnerInterface(innerInterfaceName, className(cfg, entityClassName, attribute),
elements);
returnType = innerInterfaceName;
} else {
returnType =
suffixedClassName(cfg, attribute.getType().as(CdsStructuredType.class));
}
// this should have been rawName, but unfortunately methodName was chosen by mistake
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(methodName(cfg, attribute))
.addModifiers(PUBLIC, ABSTRACT)
.returns(returnType);
if (addCdsNameAnnotation(cfg, methodBuilder, attribute)) {
addStaticField(entityClass, attribute);
}
entityClass.addMethod(methodBuilder.build());
}
}
private void addInnerInterface(ClassName interfaceName, ClassName entityClassName, Stream elements) {
TypeSpec.Builder innerInterfaceBuilder = TypeSpec.interfaceBuilder(interfaceName)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
innerInterfaceBuilder.addSuperinterface(
ParameterizedTypeName.get(Types.STRUCTURED_TYPE, interfaceName));
CreateBuilderInterfaceVisitor visitor = new CreateBuilderInterfaceVisitor(innerInterfaceBuilder,
interfaceName, entityClassName, context);
elements.forEach(e -> e.accept(visitor));
entityClass.addType(innerInterfaceBuilder.build());
}
private void addStaticQualifiedAttribute(CdsDefinition def) {
String qualifiedName = def.getQualifiedName();
FieldSpec staticField = FieldSpec.builder(String.class, "CDS_NAME")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("$S", qualifiedName)
.build();
entityClass.addField(staticField);
}
private void addAssociationAttribute(CdsElement attribute) {
if (isAnonymousAspect(attribute)) {
addInnerInterface(suffixedClassName(cfg, builderClassName, attribute),
className(cfg, entityClassName, attribute),
getAnonymousElements(attribute));
}
addMainFunction(attribute);
addFilterFunction(attribute);
}
private void addAttribute(CdsElement attribute) {
if (!TypeUtils.isIgnored(attribute)) {
ParameterizedTypeName returnType = ParameterizedTypeName.get(Types.REFERENCE,
getReturnType(entityClassName, attribute, cfg));
MethodSpec.Builder methodBuilder = MethodSpec
//REVISIT: should we normalize this name
.methodBuilder(rawName(attribute))
.addModifiers(PUBLIC, ABSTRACT)
.returns(returnType);
if (addCdsNameAnnotation(cfg, methodBuilder, attribute)) {
addStaticField(entityClass, attribute);
}
entityClass.addMethod(methodBuilder.build());
}
}
private void addMainFunction(CdsElement attribute) {
TypeName returnType = getTargetName(attribute);
if (!TypeUtils.isIgnored(attribute)) {
entityClass.addMethod(
MethodSpec.methodBuilder(rawName(attribute)).addModifiers(PUBLIC, ABSTRACT).returns(returnType).build());
} else {
logger.error("Unable to generate method %s for %s".formatted(attribute.getName(), returnType));
}
}
private void addFilterFunction(CdsElement attribute) {
if (!attribute.getName().equalsIgnoreCase(FILTER)) {
ClassName returnType = getTargetName(attribute);
if (!TypeUtils.isIgnored(attribute)) {
entityClass.addMethod(
MethodSpec.methodBuilder(rawName(attribute))
.addParameter(ParameterizedTypeName.get(Types.FUNCTION, returnType,
Types.PREDICATE), FILTER).addModifiers(PUBLIC, ABSTRACT).returns(returnType).build());
} else {
logger.error("Unable to generate filter method %s for %s".formatted(attribute.getName(), returnType));
}
} else {
logger.warn("""
There is a name clash between the element 'filter' of entity '%s'\
and a method in the super interface StructuredType.\
A filter function accepting predicates will not be generated.
""".formatted(builderClassName.simpleName()));
}
}
public ClassName getTargetName(CdsElement element) {
CdsAssociationType association = element.getType();
CdsType target = association.getTargetAspect().orElseGet(association::getTarget);
if (target.getQualifiedName().isEmpty()) {
return suffixedClassName(cfg, builderClassName, element);
}
return suffixedClassName(cfg, target);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy