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

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

There is a newer version: 3.2.0
Show newest version
/*******************************************************************
 * © 2019 SAP SE or an SAP affiliate company. All rights reserved. *
 *******************************************************************/
package com.sap.cds.generator.writer;

import static com.google.common.collect.Streams.concat;
import static com.sap.cds.generator.util.NamesUtils.checkForJavaKeyword;
import static com.sap.cds.generator.util.NamesUtils.getResolvedWrapperName;
import static com.sap.cds.generator.util.NamesUtils.isValidTechnicalEntity;
import static com.sap.cds.generator.util.NamesUtils.qualifiedContextname;
import static com.sap.cds.generator.util.NamesUtils.qualifiedWrapperBuilderName;
import static com.sap.cds.generator.util.NamesUtils.unqualifiedName;
import static com.sap.cds.generator.util.TypeUtils.builderClassName;
import static com.sap.cds.generator.util.TypeUtils.className;
import static com.sap.cds.generator.util.TypeUtils.logWarningForManyToManyWithStructElement;
import static com.sap.cds.generator.writer.SpecWriterUtil.getJavaDoc;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;

import javax.lang.model.element.Modifier;
import javax.tools.JavaFileObject;

import com.sap.cds.generator.Cds4jCodegen;
import com.sap.cds.generator.Configuration;
import com.sap.cds.generator.util.EntityWriterException;
import com.sap.cds.generator.util.GeneratedAnnotationUtil;
import com.sap.cds.generator.util.GeneratedFile;
import com.sap.cds.generator.util.GeneratedFile.Consumer;
import com.sap.cds.generator.util.NamesUtils;
import com.sap.cds.generator.util.UnSupportedModelException;
import com.sap.cds.impl.util.Pair;
import com.sap.cds.reflect.CdsAction;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsDefinition;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsEvent;
import com.sap.cds.reflect.CdsFunction;
import com.sap.cds.reflect.CdsKind;
import com.sap.cds.reflect.CdsModel;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.reflect.CdsVisitor;
import com.sap.cds.reflect.impl.CdsEventBuilder.EventProxy;
import com.sap.cds.reflect.impl.reader.model.CdsConstants;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeSpec;

public class ModelWriter implements CdsVisitor {

	private static final String CONTEXT_SUFFIX = "Context";
	private final Consumer consumer;
	private final Configuration config;
	private final CdsModel model;
	private final Map wrapperMap;
	private final Set builderInterfaces;
	private final boolean isEventContext;
	private final boolean readDocs;
	private final NamesUtils namesUtils;
	private final GeneratedAnnotationUtil generated;

	public ModelWriter(Consumer consumer, Configuration config, CdsModel model) {
		this.consumer = consumer;
		this.config = config;
		this.model = model;
		this.wrapperMap = new HashMap<>();
		this.builderInterfaces = new HashSet<>();
		this.isEventContext = Boolean.parseBoolean(config.getEventContext());
		this.namesUtils = new NamesUtils(config);
		this.generated = new GeneratedAnnotationUtil(config);
		this.readDocs = config.getDocs();
	}

	@Override
	public void visit(CdsModel model) {
		wrapperMap.values().forEach(this::generateWrapperInterface);
	}

	@Override
	public void visit(CdsEntity entity) {
		checkForJavaKeyword(entity.getQualifiedName());
		if (!namesUtils.isExcluded(entity.getQualifiedName())
				&& isValidTechnicalEntity(model, entity.getQualifiedName())) {
			collectWrapperInterfaces(entity);
			generateBuilderInterface(entity);
			generateConsumptionInterface(entity);
			if (isEventContext) {
				concat(entity.actions(), entity.functions()).forEach(a -> generateEventContextInterface(a, entity));
			}
		}
	}

	@Override
	public void visit(CdsEvent event) {
		checkForJavaKeyword(event.getQualifiedName());
		if (!namesUtils.isExcluded(event.getQualifiedName())) {
			collectWrapperInterfaces(event);
			generateBuilderInterface(event);
			generateConsumptionInterface(event);
			if (isEventContext) {
				generateEventContextInterface(event, null);
			}
		}
	}

	@Override
	public void visit(CdsAction action) {
		if (isEventContext && !namesUtils.isExcluded(action.getQualifiedName())) {
			collectWrapperInterfaces(action);
			generateEventContextInterface(action, null);
		}
	}

	@Override
	public void visit(CdsFunction function) {
		if (isEventContext && !namesUtils.isExcluded(function.getQualifiedName())) {
			collectWrapperInterfaces(function);
			generateEventContextInterface(function, null);
		}
	}

	@Override
	public void visit(CdsStructuredType struct) {
		if (!struct.getName().isEmpty()) {
			checkForJavaKeyword(struct.getQualifiedName());
			if (!namesUtils.isExcluded(struct.getQualifiedName())) {
				generateConsumptionInterface(struct);
				if (isTargetAspect(struct)) {
					// For every TargetAspect which contains structured elements and is target of
					// many to many composition, throw warning
					logWarningForManyToManyWithStructElement(model, struct);
					generateBuilderInterface(struct);
				}
			}
		}
	}

	private void collectWrapperInterfaces(CdsDefinition def) {
		String packageName = namesUtils.packageName(def);
		wrapperMap.put(packageName, def);
	}

	private void generateWrapperInterface(CdsDefinition def) {
		String qualifiedName = def.getQualifiedName();
		String name = def.getName();
		String packageName = namesUtils.packageName(def);
		String qualifiedWrapperName = qualifiedWrapperBuilderName(def, config.getClassNameSuffix(), true);
		while (builderInterfaces.contains(qualifiedWrapperName.toLowerCase(Locale.US))) {
			qualifiedWrapperName = getResolvedWrapperName(qualifiedWrapperName, config.getClassNameSuffix());
		}

		TypeSpec.Builder builder = TypeSpec.interfaceBuilder(unqualifiedName(qualifiedWrapperName))
				.addModifiers(Modifier.PUBLIC);

		generated.addTo(builder);

		if (WrapperInterfaceCreator.create(builder, model, config)
				.generateInterface(qualifiedContextname(qualifiedName, name))) {
			writeType(packageName, builder.build());
		}
	}

	private void generateConsumptionInterface(CdsDefinition def) {
		TypeSpec.Builder builder = createInterfaceSpecBuilder(className(def.getName()));
		addJavadoc(builder, def, () -> !(def instanceof EventProxy) && readDocs);

		String qualifiedTypeName = namesUtils.qualifiedJavaClassName(def);
		def.accept(CreateConsumptionInterfaceVisitor.create(builder, config, qualifiedTypeName));

		def.getAnnotationValue(CdsConstants.ANNOTATION_JAVA_EXTENDS.substring(1), Collections.emptyList())
				.stream().map(model::getStructuredType).map(this::throwErrorOnExtendEntity)
				.map(namesUtils::qualifiedJavaClassName).map(ClassName::bestGuess).forEach(builder::addSuperinterface);

		generated.addTo(builder);

		TypeSpec typeSpec = builder.build();
		String packageName = namesUtils.packageName(def);
		writeType(packageName, typeSpec);
	}

	private boolean isFuncOrAction(CdsDefinition def) {
		return def.getKind().equals(CdsKind.ACTION) || def.getKind().equals(CdsKind.FUNCTION);
	}

	private void generateEventContextInterface(CdsDefinition def, CdsDefinition boundEntity) {
		if (Cds4jCodegen.isIgnored(def)) {
			return;
		}

		String defName = def.getName();
		String defQualifiedName = def.getQualifiedName();

		if (isFuncOrAction(def)) {
			Pair qualifiedName = NamesUtils.getEffectiveNames(def);
			defName = qualifiedName.left;
			defQualifiedName = qualifiedName.right;
		}

		TypeSpec.Builder builder = createInterfaceSpecBuilder(className(defName + CONTEXT_SUFFIX));
		addJavadoc(builder, def, () -> readDocs);

		String boundEntityName = boundEntity == null ? null : boundEntity.getQualifiedName();

		def.accept(CreateEventContextInterfaceVisitor.create(builder, config, boundEntityName, defQualifiedName, namesUtils));

		generated.addTo(builder);

		TypeSpec typeSpec = builder.build();

		String packageName = boundEntity != null ? namesUtils.packageName(boundEntity) : namesUtils.packageName(def);
		writeType(packageName, typeSpec);
	}

	private CdsStructuredType throwErrorOnExtendEntity(CdsStructuredType struct) {
		if (struct.getKind().equals(CdsKind.ENTITY) && !struct.as(CdsEntity.class).isAbstract()) {
			throw new UnSupportedModelException(
					"The '@cds.java.extends' annotation does not support extending an entity.");
		}
		return struct;
	}

	private void generateBuilderInterface(CdsDefinition def) {
		TypeSpec.Builder builder = createInterfaceSpecBuilder(builderClassName(def));
		addJavadoc(builder, def, () -> readDocs);

		def.accept(CreateBuilderInterfaceVisitor.create(builder, config, namesUtils));

		builderInterfaces
				.add(qualifiedWrapperBuilderName(def, config.getClassNameSuffix(), false).toLowerCase(Locale.US));

		generated.addTo(builder);

		TypeSpec typeSpec = builder.build();
		String packageName = namesUtils.packageName(def);
		writeType(packageName, typeSpec);
	}

	private void addJavadoc(TypeSpec.Builder builder, CdsDefinition def, Supplier doRead) {
		if (doRead.get()) {
			getJavaDoc(def).ifPresent(a -> builder.addJavadoc(a.replace("$", "$$")));
		}
	}

	private static TypeSpec.Builder createInterfaceSpecBuilder(ClassName type) {
		return TypeSpec.interfaceBuilder(type).addModifiers(Modifier.PUBLIC);
	}

	private boolean isTargetAspect(CdsStructuredType struct) {
		return model.entities().flatMap(CdsStructuredType::associations)
				.map(a -> a.getType().as(CdsAssociationType.class).getTargetAspect())
				.anyMatch(ta -> ta.isPresent() && ta.get().getName().equals(struct.getName()));
	}

	private void writeType(String packageName, TypeSpec typeSpec) {
		final JavaFile javaFile = JavaFile.builder(packageName, typeSpec).build();
		final JavaFileObject fileObject = javaFile.toJavaFileObject();
		GeneratedFile jpaFile = new GeneratedFile() {
			@Override
			public URI getUri() {
				return fileObject.toUri();
			}

			@Override
			public InputStream getContent() throws IOException {
				return fileObject.openInputStream();
			}
		};
		try {
			consumer.accept(jpaFile);
		} catch (IOException e) {
			String message = String.format("Exception while writing to file %s.", jpaFile.getUri());
			throw new EntityWriterException(message, e);
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy