
com.sap.cds.impl.parser.TokenParser Maven / Gradle / Ivy
/*******************************************************************
* © 2019 SAP SE or an SAP affiliate company. All rights reserved. *
*******************************************************************/
package com.sap.cds.impl.parser;
import static com.sap.cds.impl.builder.model.StructuredTypeRefImpl.typeRef;
import static com.sap.cds.impl.parser.token.CqnPlainImpl.plain;
import static com.sap.cds.impl.parser.token.RefSegmentImpl.refSegment;
import java.text.MessageFormat;
import java.time.temporal.Temporal;
import java.util.List;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Lists;
import com.sap.cds.CdsException;
import com.sap.cds.impl.builder.model.ContainmentTest;
import com.sap.cds.impl.builder.model.CqnNull;
import com.sap.cds.impl.builder.model.CqnParam;
import com.sap.cds.impl.builder.model.ElementRefImpl;
import com.sap.cds.impl.builder.model.Join;
import com.sap.cds.impl.builder.model.ScalarFunctionCall;
import com.sap.cds.impl.parser.builder.ExpressionBuilder;
import com.sap.cds.impl.parser.token.CqnBinLiteral;
import com.sap.cds.impl.parser.token.CqnBoolLiteral;
import com.sap.cds.impl.parser.token.CqnNumLiteral;
import com.sap.cds.impl.parser.token.CqnPlainImpl;
import com.sap.cds.impl.parser.token.CqnStrLiteral;
import com.sap.cds.impl.parser.token.CqnTempLiteral;
import com.sap.cds.impl.parser.token.RefSegmentImpl;
import com.sap.cds.ql.CdsDataException;
import com.sap.cds.ql.RefSegment;
import com.sap.cds.ql.StructuredTypeRef;
import com.sap.cds.ql.Value;
import com.sap.cds.ql.cqn.CqnElementRef;
import com.sap.cds.ql.cqn.CqnExpression;
import com.sap.cds.ql.cqn.CqnFunc;
import com.sap.cds.ql.cqn.CqnJoin;
import com.sap.cds.ql.cqn.CqnJoin.Type;
import com.sap.cds.ql.cqn.CqnLiteral;
import com.sap.cds.ql.cqn.CqnParameter;
import com.sap.cds.ql.cqn.CqnPredicate;
import com.sap.cds.ql.cqn.CqnReference.Segment;
import com.sap.cds.ql.cqn.CqnSource;
import com.sap.cds.ql.cqn.CqnSyntaxException;
import com.sap.cds.ql.cqn.CqnToken;
import com.sap.cds.ql.cqn.CqnValue;
import com.sap.cds.reflect.CdsBaseType;
import com.sap.cds.reflect.CdsSimpleType;
import com.sap.cds.reflect.CdsType;
import com.sap.cds.util.CdsTypeUtils;
public class TokenParser {
private static final Logger logger = LoggerFactory.getLogger(TokenParser.class);
private TokenParser() {
}
public static CqnToken parse(JsonNode node) {
switch (node.getNodeType()) {
case STRING:
String plain = node.asText();
if ("null".equalsIgnoreCase(plain)) {
return CqnNull.getInstance();
}
return plain(plain);
case OBJECT:
if (node.has("SELECT")) {
return SelectParser.parse(node.toString());
}
if (node.has("join")) {
return join(node);
}
return parseValue(node);
default:
throw new CqnSyntaxException("invalid CQN: " + node.toString());
}
}
@SuppressWarnings("unchecked")
static T parseValue(JsonNode node) {
if (node.has("param") && node.get("param").booleanValue()) {
return (T) param(node);
}
if (node.has("ref")) {
return (T) elementRef(node);
}
if (node.has("val")) {
return (T) val(node);
}
if (node.has("xpr")) {
return (T) xpr(node);
}
if (node.has("func")) {
return (T) func(node);
}
if (node.getNodeType() == JsonNodeType.STRING) {
return (T) CqnPlainImpl.plain(node.asText());
}
throw new CqnSyntaxException("invalid CQN: " + node.toString());
}
private static CqnParameter param(JsonNode objNode) {
return CqnParam.param(objNode.get("ref").get(0).asText());
}
public static CqnValue val(JsonNode node) {
final Value> literal;
if (node.has("literal")) {
literal = literalTypeVal(node);
} else {
literal = jsonTypeVal(node);
}
if (node.has("cast")) {
literal.type(node.get("cast").get("type").asText());
}
return literal;
}
private static Value> jsonTypeVal(JsonNode node) {
JsonNode value = node.get("val");
switch (value.getNodeType()) {
case STRING:
return new CqnStrLiteral(value.asText());
case NUMBER:
return new CqnNumLiteral<>(value.numberValue());
case BOOLEAN:
return CqnBoolLiteral.valueOf(value.asBoolean());
case NULL:
return CqnNull.getInstance();
default:
throw new CqnSyntaxException("invalid CQN: " + value.toString());
}
}
private static Value> literalTypeVal(JsonNode node) {
String literalType = node.get("literal").asText();
JsonNode value = node.get("val");
switch (literalType) {
case "timestamp":
return new CqnTempLiteral<>((Temporal) value(value, CdsBaseType.TIMESTAMP));
case "time":
return new CqnTempLiteral<>((Temporal) value(value, CdsBaseType.TIME));
case "date":
return new CqnTempLiteral<>((Temporal) value(value, CdsBaseType.DATE));
case "number":
return new CqnNumLiteral<>((Number) value(value, CdsBaseType.DECIMAL));
case "x":
case "hex":
return new CqnBinLiteral((byte[]) value(value, CdsBaseType.BINARY));
case "boolean":
case "string":
Value> jsonTypeVal = jsonTypeVal(node);
checkTypeCompatability(jsonTypeVal.asLiteral(), literalType, value.asText());
return jsonTypeVal;
case "null":
return jsonTypeVal(node);
default:
throw new CqnSyntaxException("Invalid CQN literal type: " + literalType.toString());
}
}
private static void checkTypeCompatability(CqnLiteral> cqnLiteral, String literalType, String value) {
String cqnLiteralType = cqnLiteral.type()
.orElseThrow(() -> new CqnSyntaxException("CDS type not set for: " + cqnLiteral));
if (!literalType.equals(CdsTypeUtils.cdsTypeShortName(cqnLiteralType))) {
throw new CqnSyntaxException(
MessageFormat.format("Value {0} cannot be converted to type {1}", value, literalType));
}
}
public static CqnFunc func(JsonNode node) {
String name = node.get("func").asText();
CqnFunc func = null;
if (node.has("args")) {
ArrayNode jsonArray = (ArrayNode) node.get("args");
CqnValue[] args = new CqnValue[jsonArray.size()];
int i = 0;
for (JsonNode arg : jsonArray) {
args[i] = parseValue(arg);
i++;
}
String lowerName = name.toLowerCase(Locale.US);
switch (lowerName) {
case "startswith":
func = ContainmentTest.startsWith(args[0], args[1]);
break;
case "contains":
if (args.length < 3) {
func = ContainmentTest.contains(args[0], args[1]);
} else {
boolean caseInsensitive = args[2].asLiteral().asBoolean().value().booleanValue();
func = ContainmentTest.contains(args[0], args[1], caseInsensitive);
}
break;
case "endswith":
func = ContainmentTest.endsWith(args[0], args[1]);
break;
default:
func = ScalarFunctionCall.create(name, args);
}
} else {
func = ScalarFunctionCall.create(name);
}
if (node.has("cast")) {
String cdsType = ((ObjectNode) node.get("cast")).get("type").asText();
((Value>) func).type(cdsType);
}
return func;
}
public static CqnExpression xpr(JsonNode node) {
JsonNode xprNode = node.get("xpr");
CqnExpression expression;
if (xprNode.isArray()) {
expression = ExpressionParser.parse((ArrayNode) xprNode);
} else {
expression = ExpressionParser.parse(xprNode);
}
return ExpressionBuilder.xpr(expression.tokens()).build();
}
public static Object value(JsonNode value, CdsBaseType type) {
switch (value.getNodeType()) {
case STRING:
case NUMBER:
case BOOLEAN:
String txt = value.asText();
try {
return CdsTypeUtils.parse(type, txt);
} catch (CdsDataException ex) {
throw new CqnSyntaxException(ex.getMessage(), ex);
}
case NULL:
return null;
default:
throw new CqnSyntaxException("invalid CQN: " + value.toString());
}
}
public static Object defaultValue(JsonNode defValNode, CdsType type) {
if (defValNode == null || type == null) {
return null;
}
JsonNode valNode = defValNode.get("val");
if (type.isSimple()) {
if (valNode != null) {
CdsBaseType cdsBaseType = type.as(CdsSimpleType.class).getType();
return value(valNode, cdsBaseType);
}
return null;
}
throw new CdsException("Default values in parameters are not supported for type: " + type.getQualifiedName());
}
public static StructuredTypeRef ref(JsonNode node) {
StructuredTypeRef typeRef = typeRef(segments(node));
if (node.has("as")) {
typeRef.as(node.get("as").asText());
}
return typeRef;
}
public static CqnElementRef elementRef(JsonNode objNode) {
return ElementRefImpl.element(segments(objNode));
}
private static List segments(JsonNode objNode) {
if (objNode.has("ref")) {
List refObjects = Lists.newArrayList();
ArrayNode jsonArray = (ArrayNode) objNode.get("ref");
for (JsonNode node : jsonArray) {
if (node.isTextual()) {
refObjects.add(refSegment(node.asText()));
} else {
refObjects.add(parseExpRefObject(node));
}
}
return refObjects;
} else {
// Allow processing of unrecognized ObjectNode to support joins in FROM in views
logger.warn("The ObjectNode is not a reference: " + objNode);
List refObjects = Lists.newArrayList();
refObjects.add(refSegment(objNode.asText()));
return refObjects;
}
}
private static Segment parseExpRefObject(JsonNode node) {
RefSegment segment = RefSegmentImpl.refSegment(node.get("id").asText());
if (node.has("where")) {
segment.filter(ExpressionParser.parsePredicate(node.get("where")));
}
return segment;
}
public static CqnJoin join(JsonNode from) {
CqnJoin.Type type = Type.joinType(from.get("join").asText());
ArrayNode args = (ArrayNode) from.get("args");
CqnSource left = source(args.get(0));
CqnSource right = source(args.get(1));
CqnPredicate joinPredicate = null;
if (from.has("on")) {
joinPredicate = ExpressionParser.parsePredicate(from.get("on"));
}
Join join = new Join(type, left, right, joinPredicate);
return join;
}
static CqnSource source(JsonNode src) {
if (src.has("ref")) {
return ref(src);
}
if (src.has("join")) {
return join(src);
}
if (src.has("SELECT")) {
return SelectParser.parse(src.toString());
}
throw new CqnSyntaxException("invalid CQN: " + src.toString());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy