All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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