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

com.sap.cds.impl.parser.TokenParser Maven / Gradle / Ivy

There is a newer version: 3.8.0
Show newest version
/*******************************************************************
 * © 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