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

com.sap.cds.generator.writer.CreateEventContextInterfaceVisitor Maven / Gradle / Ivy

The newest version!
/************************************************************************
 * © 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.CaseFormatHelper.lowercaseFirst;
import static com.sap.cds.generator.util.CaseFormatHelper.toUpperUnderscore;
import static com.sap.cds.generator.util.NamesUtils.className;
import static com.sap.cds.generator.util.NamesUtils.getterName;
import static com.sap.cds.generator.util.NamesUtils.methodName;
import static com.sap.cds.generator.util.NamesUtils.setterName;
import static com.sap.cds.generator.util.TypeUtils.addStaticFactoryMethods;
import static com.sap.cds.generator.util.TypeUtils.getArrayTypeName;
import static com.sap.cds.generator.util.TypeUtils.getAttributeType;
import static com.sap.cds.generator.writer.SpecWriterUtil.setJavaDoc;
import static com.sap.cds.generator.writer.Types.RESULT_ARGUMENT;
import static com.sap.cds.generator.writer.Types.RETURN_TYPE;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.lang.model.element.Modifier;

import com.sap.cds.generator.Configuration;
import com.sap.cds.generator.MethodStyle;
import com.sap.cds.generator.util.NamesUtils;
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.ql.CdsOptionalArguments;
import com.sap.cds.reflect.CdsAction;
import com.sap.cds.reflect.CdsArrayedType;
import com.sap.cds.reflect.CdsDefinition;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEvent;
import com.sap.cds.reflect.CdsFunction;
import com.sap.cds.reflect.CdsKind;
import com.sap.cds.reflect.CdsOperation;
import com.sap.cds.reflect.CdsParameter;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.reflect.CdsType;
import com.sap.cds.reflect.CdsVisitor;
import com.sap.cds.reflect.impl.CdsElementBuilder;
import com.sap.cds.reflect.impl.CdsParameterBuilder;
import com.sap.cds.reflect.impl.CdsStructuredTypeBuilder;
import com.sap.cds.util.CdsModelUtils;
import com.palantir.javapoet.AnnotationSpec;
import com.palantir.javapoet.ClassName;
import com.palantir.javapoet.CodeBlock;
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;
import com.palantir.javapoet.WildcardTypeName;

public class CreateEventContextInterfaceVisitor implements CdsVisitor {

	private static final ParameterizedTypeName MAP_STR2OBJ = ParameterizedTypeName.get(Types.MAP,
		Types.STRING, WildcardTypeName.subtypeOf(Object.class));
	private static final String PROXYMETHOD_ARG_NAME = "entityName";
	private final TypeSpec.Builder builder;
	private final Configuration cfg;
	private final boolean bounded;
	private final String boundEntityName;
	private final Context context;
	private final ClassName className;

	CreateEventContextInterfaceVisitor(TypeSpec.Builder builder, ClassName className, String boundEntityName, Context context) {
		this.builder = builder;
		this.className = className;
		this.cfg = context.config();
		this.boundEntityName = boundEntityName;
		this.builder.addSuperinterface(Types.EVENT_CONTEXT);
		this.bounded = boundEntityName != null;
		this.context = context;
	}

	@Override
	public void visit(CdsAction action) {
		addGetCqnMethod();
		addSetCqnMethod();
		addStaticQualifiedAttribute(action);
		prepareDefinition(action);
	}

	@Override
	public void visit(CdsFunction function) {
		addGetCqnMethod();
		addSetCqnMethod();
		addStaticQualifiedAttribute(function);
		prepareDefinition(function);
	}

	@Override
	public void visit(CdsEvent event) {
		// setData and getData uses event as the result type,
		// not the event context that we have in this
		ClassName eventClassName = className(cfg, event);
		addGetter(eventClassName);
		addSetter(eventClassName);

		addStaticQualifiedAttribute(event);
		addStaticProxyMethod();
		builder.addAnnotation(eventNameAnnotation(event.getName(), "$S"));
	}

	private void addGetter(ClassName event) {
		String methodName;

		if (cfg.getMethodStyle() == MethodStyle.BEAN) {
			methodName = "getData";
		} else {
			methodName = "data";
		}
		MethodSpec.Builder resultGetterBuilder = MethodSpec.methodBuilder(methodName)
			.returns(event).addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT);
		builder.addMethod(resultGetterBuilder.build());
	}

	private void addSetter(ClassName event) {
		String methodName;
		TypeName returnType = TypeName.VOID;

		if (cfg.getMethodStyle() == MethodStyle.BEAN) {
			methodName = "setData";
		} else {
			methodName = "data";
			returnType = className;
		}
		MethodSpec.Builder resultGetterBuilder = MethodSpec.methodBuilder(methodName).returns(returnType)
			.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
			.addParameter(event, "event");
		builder.addMethod(resultGetterBuilder.build());
	}

	private void addGetCqnMethod() {
		if (bounded) {
			String methodName;

			if (cfg.getMethodStyle() == MethodStyle.BEAN) {
				methodName = "getCqn";
			} else {
				methodName = "cqn";
			}
			MethodSpec.Builder resultGetterBuilder = MethodSpec.methodBuilder(methodName)
				.returns(Types.CQN_SELECT)
				.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT);
			builder.addMethod(resultGetterBuilder.build());
		}
	}

	private void addSetCqnMethod() {
		if (bounded) {
			String methodName = "cqn";
			TypeName returnType;

			if (cfg.getMethodStyle() == MethodStyle.BEAN) {
				returnType = TypeName.VOID;
				methodName = "setCqn";
			} else {
				returnType = className;
			}
			MethodSpec.Builder resultGetterBuilder = MethodSpec.methodBuilder(methodName).returns(returnType)
				.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
				.addParameter(Types.CQN_SELECT, "select");
			builder.addMethod(resultGetterBuilder.build());
		}
	}

	private void addStaticQualifiedAttribute(CdsDefinition def) {
		String name = def.getName();

		FieldSpec staticField = FieldSpec.builder(String.class, "CDS_NAME")
			.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("$S", name).build();
		builder.addField(staticField);
	}

	private void prepareDefinition(CdsOperation operation) {
		if (bounded) {
			addStaticProxyMethodBound();
		}
		addResultMethod(operation);
		addStaticProxyMethod();
		builder.addAnnotation(eventNameAnnotation(operation.getName(), "$S"));

		if (SpecWriterUtil.isGroupOptionalParameters(context.config(), operation)) {
			List> optionalParameterBuilders = SpecWriterUtil.optionalParameters(operation)
					.map(p -> new CdsElementBuilder<>(p.annotations().toList(), p.getName(), null, false, false,
							p.isNotNull(), false, p.getDefaultValue().orElse(null), "").type(p.getType()))
					.collect(Collectors.toList());

			String qualifiedName = String.join(".", operation.getQualifier(), NamesUtils.OPTIONAL_ARGS_TYPE_NAME);

			CdsStructuredTypeBuilder optParametersTypeBuilder = new CdsStructuredTypeBuilder<>(
					Collections.emptyList(), qualifiedName, NamesUtils.OPTIONAL_ARGS_TYPE_NAME, CdsKind.TYPE, "");

			optParametersTypeBuilder.addElements(optionalParameterBuilders);

			CdsParameter optionalsParam = new CdsParameterBuilder(Collections.emptyList(),
					NamesUtils.OPTIONAL_ARGS_PARAMETER_NAME, optParametersTypeBuilder, t -> null, "", false).build();

			generateInnerInterface(className, optionalsParam.getType(), optionalsParam, true);
		}
	}

	private void addResultMethod(CdsOperation operation) {
		TypeName resultType = null;
		TypeName setReturnType;

		Optional opt = operation.returnType();
		if (opt.isEmpty()) {
			return;
		}
		CdsType returnType = opt.get();

		if (TypeUtils.isAnonymousType(returnType, cfg)) {
			resultType = generateInnerInterface(className, returnType, null);
		} else {
			resultType = getAttributeType(className, returnType, cfg);
		}
		if (resultType == null) {
			resultType = TypeUtils.getOperationResultType(null, operation, returnType, cfg);
			if (resultType == null) {
				// Since actions might or might not return a value
				return;
			}
		}
		String setter;
		String getter;
		if (cfg.getMethodStyle() == MethodStyle.BEAN) {
			setReturnType = TypeName.VOID;
			setter = "setResult";
			getter = "getResult";
		} else {
			setReturnType = className;
			setter = getter = "result";
		}

		MethodSpec.Builder resultSetterBuilder = MethodSpec.methodBuilder(setter).returns(setReturnType)
			.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT).addParameter(resultType, RESULT_ARGUMENT);

		MethodSpec.Builder resultGetterBuilder = MethodSpec.methodBuilder(getter).returns(resultType)
			.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT);
		builder.addMethod(resultSetterBuilder.build());
		builder.addMethod(resultGetterBuilder.build());
	}

	private TypeName generateInnerInterface(ClassName parent, CdsType returnType, CdsParameter parameter, boolean isCdsOptionalArgument) {
		Stream elements;
		// Either fixed name return type or an inner class with raw name of the parameter
		ClassName innerInterfaceName = parameter == null ? parent.nestedClass(RETURN_TYPE) : className(cfg, parent, parameter);
		TypeName resultType = innerInterfaceName;

		TypeSpec.Builder innerInterfaceBuilder = TypeSpec.interfaceBuilder(innerInterfaceName).addModifiers(Modifier.PUBLIC,
			Modifier.STATIC).addSuperinterface(Types.CDS_DATA);

		if (returnType.isStructured()) {
			elements = returnType.as(CdsStructuredType.class).elements();
		} else {
			resultType = getArrayTypeName(resultType);
			elements = returnType.as(CdsArrayedType.class).getItemsType().as(CdsStructuredType.class).elements();
		}
		elements.forEach(
				e -> e.accept(new CreateConsumptionInterfaceVisitor(innerInterfaceBuilder, innerInterfaceName, context, Set.of())));
		addStaticFactoryMethods(builder, innerInterfaceName, innerInterfaceBuilder);

		// mark the inner interface as an {@link CdsOptionalArgument}
		if (isCdsOptionalArgument) {
			innerInterfaceBuilder.addAnnotation(CdsOptionalArguments.class);
		}

		builder.addType(innerInterfaceBuilder.build());
		return resultType;
	}

	private TypeName generateInnerInterface(ClassName parent, CdsType returnType, CdsParameter parameter) {
		return generateInnerInterface(parent, returnType, parameter, false);
	}

	private void addStaticProxyMethod() {
		MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("create").returns(className)
			.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
		if (bounded) {
			methodBuilder.addParameter(TypeName.get(String.class), PROXYMETHOD_ARG_NAME);
		}

		CodeBlock.Builder codeBuilder = CodeBlock.builder();
		codeBuilder.addStatement("return EventContext.create($T.class, $N)", className,
			bounded ? PROXYMETHOD_ARG_NAME : "null");

		methodBuilder.addCode(codeBuilder.build());
		builder.addMethod(methodBuilder.build());
	}

	private void addStaticProxyMethodBound() {
		MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("create").returns(className)
			.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
		CodeBlock.Builder codeBuilder = CodeBlock.builder();
		codeBuilder.addStatement("return EventContext.create($T.class, $S)", className, this.boundEntityName);

		methodBuilder.addCode(codeBuilder.build());
		builder.addMethod(methodBuilder.build());
	}

	private static AnnotationSpec eventNameAnnotation(String cdsName, String format) {
		return AnnotationSpec.builder(Types.EVENT_NAME).addMember("value", format, cdsName).build();
	}

	@Override
	public void visit(CdsParameter parameter) {
		addStaticAttribute(parameter);
		TypeName resultType;
		CdsType returnType = parameter.getType();
		if (TypeUtils.isAnonymousType(returnType, cfg)) {
			resultType = generateInnerInterface(className, returnType, parameter);
			addGetterMethod(parameter, resultType);
			addSetterMethod(parameter, resultType);
		} else {
			addParamGetter(parameter);
			addParamSetter(parameter);
		}
	}

	private void addParamGetter(CdsParameter parameter) {
		TypeName returnType = getAttributeType(className, parameter.getType(), cfg);
		addGetterMethod(parameter, returnType);
	}

	private void addGetterMethod(CdsParameter parameter, TypeName returnType) {
		if (returnType == null || TypeUtils.isIgnored(parameter)) {
			return;
		}
		String getter;
		if (cfg.getMethodStyle().equals(MethodStyle.BEAN)) {
			getter = getterName(cfg, parameter);
		} else {
			getter = methodName(cfg, parameter);
		}
		MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(getter).returns(returnType)
			.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT);
		addJavaDoc(parameter, methodBuilder);
		addCdsNameAnnotation(methodBuilder, parameter.getName(), getter);
		builder.addMethod(methodBuilder.build());
	}

	private void addParamSetter(CdsParameter parameter) {
		TypeName paramType = getSetterParam(parameter.getType());
		addSetterMethod(parameter, paramType);
	}

	private void addSetterMethod(CdsParameter parameter, TypeName paramType) {
		if (paramType == null || TypeUtils.isIgnored(parameter)) {
			return;
		}

		String setter;
		TypeName returnType;
		if (cfg.getMethodStyle() == MethodStyle.BEAN) {
			returnType = TypeName.VOID;
			setter = setterName(cfg, parameter);
		} else {
			returnType = className;
			setter = methodName(cfg, parameter);
		}

		MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(setter).returns(returnType)
			.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
			.addParameter(paramType, methodName(cfg, parameter));
		addJavaDoc(parameter, methodBuilder);
		addCdsNameAnnotation(methodBuilder, parameter.getName(), setter);
		builder.addMethod(methodBuilder.build());
	}

	private TypeName getSetterParam(CdsType type) {
		if (type.isAssociation()) {
			if (CdsModelUtils.isSingleValued(type)) {
				return MAP_STR2OBJ;
			}
			return TypeUtils.listOf(WildcardTypeName.subtypeOf(MAP_STR2OBJ));
		}
		return getAttributeType(className, type, cfg);
	}

	private static AnnotationSpec cdsNameAnnotation(String cdsName, String format) {
		return AnnotationSpec.builder(CdsName.class).addMember("value", format, cdsName).build();
	}

	private static MethodSpec.Builder addCdsNameAnnotation(MethodSpec.Builder builder, String cdsName,
		String javaName) {
		if (!cdsName.equals(propertyName(javaName))) {
			builder.addAnnotation(cdsNameAnnotation(toUpperUnderscore(cdsName), "$L"));
		}
		return builder;
	}

	private static String propertyName(String javaName) {
		if (javaName.startsWith("set") || javaName.startsWith("get")) {
			javaName = lowercaseFirst(javaName.substring(3));
		}
		return javaName;
	}

	private void addStaticAttribute(CdsParameter parameter) {
		String name = toUpperUnderscore(parameter.getName());
		FieldSpec staticField = FieldSpec.builder(String.class, name)
			.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("$S", parameter.getName())
			.build();
		builder.addField(staticField);
	}

	private void addJavaDoc(CdsParameter param, MethodSpec.Builder methodBuilder) {
		if (cfg.getDocs()) {
			setJavaDoc(param, methodBuilder);
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy