
com.sap.cds.util.CdsModelUtils Maven / Gradle / Ivy
/************************************************************************
* © 2020-2022 SAP SE or an SAP affiliate company. All rights reserved. *
************************************************************************/
package com.sap.cds.util;
import static com.sap.cds.impl.AssociationAnalyzer.refElements;
import static java.util.stream.Collectors.toSet;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.fasterxml.jackson.databind.JsonNode;
import com.sap.cds.impl.parser.VersionParser;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnReference;
import com.sap.cds.ql.cqn.CqnReference.Segment;
import com.sap.cds.ql.cqn.CqnSelectListValue;
import com.sap.cds.ql.cqn.CqnStructuredTypeRef;
import com.sap.cds.ql.cqn.CqnValue;
import com.sap.cds.ql.cqn.CqnVisitor;
import com.sap.cds.reflect.CdsAnnotation;
import com.sap.cds.reflect.CdsAssociationType;
import com.sap.cds.reflect.CdsElement;
import com.sap.cds.reflect.CdsEntity;
import com.sap.cds.reflect.CdsModel;
import com.sap.cds.reflect.CdsStructuredType;
import com.sap.cds.reflect.CdsType;
import com.sap.cds.reflect.impl.CdsVersion;
import com.sap.cds.reflect.impl.DraftAdapter;
import com.sap.cds.reflect.impl.reader.model.CdsConstants;
public class CdsModelUtils {
private static final String UNDERSCORE = "_";
private static final CdsVersion DEFAULT_COMPILER_VERSION = new CdsVersion(1, 0, 0, 0);
private CdsModelUtils() {
}
public static boolean isSingleValued(CdsType associationElement) {
return associationElement.as(CdsAssociationType.class).getCardinality().getTargetMax().equals("1");
}
public static boolean isToMany(CdsType associationElement) {
return associationElement.as(CdsAssociationType.class).getCardinality().getTargetMax().equals("*");
}
public static boolean isManyTo(CdsType associationElement) {
return !associationElement.as(CdsAssociationType.class).getCardinality().getSourceMax().equals("1");
}
public static boolean managedToOne(CdsType associationElement) {
if (associationElement.isAssociation()) {
CdsAssociationType assoc = associationElement.as(CdsAssociationType.class);
return isSingleValued(assoc) && !assoc.onCondition().isPresent();
}
return false;
}
public static Set targetKeys(CdsElement assoc) {
return keyNames(assocType(assoc).getTarget());
}
public static Set assocKeys(CdsElement forwardMappedAssoc) {
Set assocKeys = refElements(forwardMappedAssoc).map(CdsElement::getName).collect(toSet());
if (assocKeys.isEmpty()) {
assocKeys = new OnConditionAnalyzer(forwardMappedAssoc, false).getFkMapping().values().stream()
.flatMap(CqnValue::ofRef).map(CqnReference::lastSegment).collect(toSet());
}
return assocKeys;
}
public static Set keyNames(CdsStructuredType type) {
return resolveKeys(type).collect(Collectors.toSet());
}
private static Stream resolveKeys(CdsStructuredType entity) {
return entity.keyElements().filter(k -> !k.isVirtual()).flatMap(CdsModelUtils::resolveKeys);
}
private static Stream resolveKeys(CdsElement element) {
if (element.getType().isAssociation()) {
return refElements(element) //
.flatMap(CdsModelUtils::resolveKeys) //
.map(k -> element.getName() + UNDERSCORE + k);
}
return Stream.of(element.getName());
}
private static CdsAssociationType assocType(CdsElement element) {
return element.getType().as(CdsAssociationType.class);
}
public static Optional findElement(CdsStructuredType struct, CqnElementRef ref) {
String path = relativePath(struct, ref.segments());
return struct.findElement(path);
}
public static CdsElement element(CdsStructuredType struct, CqnElementRef ref) {
return element(struct, ref.segments());
}
public static CdsElement element(CdsStructuredType struct, List extends Segment> segments) {
String path = relativePath(struct, segments);
return struct.getElement(path);
}
private static String relativePath(CdsStructuredType struct, List extends Segment> segments) {
StringBuilder path = new StringBuilder();
for (int i = 0; i < segments.size(); i++) {
String segmentId = segments.get(i).id();
if (i == 0 && (segmentId.equals(struct.getQualifiedName()) || segmentId.equals("$self"))) {
continue;
}
path.append('.');
path.append(segmentId);
}
return path.substring(1);
}
public static boolean isContextElementRef(CqnElementRef ref) {
switch (ref.firstSegment()) {
case "$now":
case "$at":
case "$user":
return true;
default:
return false;
}
}
public static List entities(CdsModel model, List extends Segment> segments) {
List entities = new ArrayList<>(segments.size());
Iterator extends Segment> iter = segments.iterator();
if (iter.hasNext()) {
CdsEntity e = model.getEntity(iter.next().id());
entities.add(e);
while (iter.hasNext()) {
e = e.getTargetOf(iter.next().id());
entities.add(e);
}
}
return entities;
}
public static List entities(CdsEntity root, List extends Segment> segments) {
LinkedList entities = new LinkedList<>();
entities.add(root);
boolean firstSegment = true;
for (Segment seg : segments) {
if (firstSegment && root.getQualifiedName().equals(seg.id())) {
continue;
}
entities.add(entities.getLast().getTargetOf(seg.id()));
firstSegment = false;
}
return entities;
}
public static CdsEntity entity(CdsEntity root, List extends Segment> segments) {
return ((LinkedList) entities(root, segments)).getLast();
}
public static CdsStructuredType target(CdsStructuredType root, List extends Segment> segments) {
CdsStructuredType target = root;
for (Segment seg : segments) {
target = target.getTargetOf(seg.id());
}
return target;
}
public static CdsEntity entity(CdsModel model, List extends Segment> segments) {
CdsEntity entity = model.getEntity(segments.get(0).id());
return entity(entity, segments);
}
public static CdsEntity entity(CdsModel model, CqnStructuredTypeRef ref) {
CdsEntity entity = model.getEntity(ref.firstSegment());
return entity(entity, ref.segments());
}
public static boolean isReverseAssociation(CdsElement assoc) {
CdsAssociationType association = assoc.getType();
if (association.getCardinality().getTargetMax().equalsIgnoreCase("*")) {
return true;
}
return referencesAllKeysOfSource(assoc);
}
public static CdsVersion compilerVersion(CdsModel model) {
String creator = model.getMeta(CdsConstants.CREATOR);
if (creator == null) {
return DEFAULT_COMPILER_VERSION;
}
try {
return VersionParser.parse(creator.substring("CDS Compiler v".length()));
} catch (IllegalArgumentException e) {
return DEFAULT_COMPILER_VERSION;
}
}
private static boolean referencesAllKeysOfSource(CdsElement assoc) {
CdsAssociationType association = assoc.getType();
Optional onCond = association.onCondition();
if (!onCond.isPresent()) {
return false;
}
Set sourceKeys = keyNames(assoc.getDeclaringType());
sourceKeys.remove(DraftAdapter.IS_ACTIVE_ENTITY); // TODO CDSJAVA-2385
CqnVisitor visitor = new CqnVisitor() {
@Override
public void visit(CqnElementRef ref) {
if (ref.lastSegment().equals("$self")) {
sourceKeys.clear();
} else if (ref.size() == 1) {
sourceKeys.remove(ref.lastSegment());
}
}
};
onCond.get().accept(visitor);
return sourceKeys.isEmpty();
}
public static String getDoc(JsonNode csn) {
if (csn.has("doc")) {
return csn.get("doc").asText();
}
return null;
}
public static boolean isCascading(CascadeType cascadeType, CdsElement association) {
CdsType type = association.getType();
if (type.isAssociation()) {
Optional> cascade = association.findAnnotation("cascade." + cascadeType);
if (!cascade.isPresent()) {
cascade = association.findAnnotation("cascade.all");
}
return cascade.map(CdsAnnotation::getValue)
.orElseGet(() -> type.as(CdsAssociationType.class).isComposition());
}
return false;
}
public static String getFullRefPath(CqnSelectListValue val) {
if (val.isRef()) {
return val.asRef().path();
}
return val.displayName();
}
public enum CascadeType {
ALL, INSERT, UPDATE, DELETE;
@Override
public String toString() {
return name().toLowerCase(Locale.US);
}
}
/**
* Iteratively resolves every managed to-one association that is found within
* the provided association and its association reference mapping until only
* simple type elements have been reached. The ref id paths towards these
* transitively targeted simple type elements are kept track of and returned
* together with these elements in a stream of maps.
*
* The resolution of these managed to-one associations potentially constructs a
* multi-level tree of ref id paths with the ref id of the provided CDS element
* as the root node and the transitively targeted (simple type) elements as leaf
* nodes.
*
* Each map of the stream contains the leaf node element as key and its
* respective ref id path as value.
*
* @param element an association or simple type element
* @return a stream of transitively targeted simple type elements and their
* respective ref id paths derived from the provided CDS element and its
* association mapping
*/
public static Stream
© 2015 - 2025 Weber Informatics LLC | Privacy Policy