blob: cf2074a7377dbc7f179fe5ffe4b254d2f19a000e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.parser;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import org.apache.olingo.commons.api.edm.Edm;
import org.apache.olingo.commons.api.edm.EdmComplexType;
import org.apache.olingo.commons.api.edm.EdmElement;
import org.apache.olingo.commons.api.edm.EdmEntitySet;
import org.apache.olingo.commons.api.edm.EdmEntityType;
import org.apache.olingo.commons.api.edm.EdmEnumType;
import org.apache.olingo.commons.api.edm.EdmFunction;
import org.apache.olingo.commons.api.edm.EdmNavigationProperty;
import org.apache.olingo.commons.api.edm.EdmPrimitiveType;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException;
import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind;
import org.apache.olingo.commons.api.edm.EdmProperty;
import org.apache.olingo.commons.api.edm.EdmReturnType;
import org.apache.olingo.commons.api.edm.EdmSingleton;
import org.apache.olingo.commons.api.edm.EdmStructuredType;
import org.apache.olingo.commons.api.edm.EdmType;
import org.apache.olingo.commons.api.edm.EdmTypeDefinition;
import org.apache.olingo.commons.api.edm.FullQualifiedName;
import org.apache.olingo.commons.api.edm.constants.EdmTypeKind;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.uri.UriParameter;
import org.apache.olingo.server.api.uri.UriResourceFunction;
import org.apache.olingo.server.api.uri.UriResourceLambdaVariable;
import org.apache.olingo.server.api.uri.UriResourceNavigation;
import org.apache.olingo.server.api.uri.UriResourcePartTyped;
import org.apache.olingo.server.api.uri.queryoption.AliasQueryOption;
import org.apache.olingo.server.api.uri.queryoption.expression.Alias;
import org.apache.olingo.server.api.uri.queryoption.expression.Binary;
import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind;
import org.apache.olingo.server.api.uri.queryoption.expression.Enumeration;
import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
import org.apache.olingo.server.api.uri.queryoption.expression.LambdaRef;
import org.apache.olingo.server.api.uri.queryoption.expression.Literal;
import org.apache.olingo.server.api.uri.queryoption.expression.Member;
import org.apache.olingo.server.api.uri.queryoption.expression.Method;
import org.apache.olingo.server.api.uri.queryoption.expression.MethodKind;
import org.apache.olingo.server.api.uri.queryoption.expression.TypeLiteral;
import org.apache.olingo.server.api.uri.queryoption.expression.Unary;
import org.apache.olingo.server.api.uri.queryoption.expression.UnaryOperatorKind;
import org.apache.olingo.server.core.uri.UriInfoImpl;
import org.apache.olingo.server.core.uri.UriResourceComplexPropertyImpl;
import org.apache.olingo.server.core.uri.UriResourceCountImpl;
import org.apache.olingo.server.core.uri.UriResourceEntitySetImpl;
import org.apache.olingo.server.core.uri.UriResourceFunctionImpl;
import org.apache.olingo.server.core.uri.UriResourceItImpl;
import org.apache.olingo.server.core.uri.UriResourceLambdaAllImpl;
import org.apache.olingo.server.core.uri.UriResourceLambdaAnyImpl;
import org.apache.olingo.server.core.uri.UriResourceLambdaVarImpl;
import org.apache.olingo.server.core.uri.UriResourceNavigationPropertyImpl;
import org.apache.olingo.server.core.uri.UriResourcePrimitivePropertyImpl;
import org.apache.olingo.server.core.uri.UriResourceRootImpl;
import org.apache.olingo.server.core.uri.UriResourceSingletonImpl;
import org.apache.olingo.server.core.uri.UriResourceStartingTypeFilterImpl;
import org.apache.olingo.server.core.uri.UriResourceTypedImpl;
import org.apache.olingo.server.core.uri.UriResourceWithKeysImpl;
import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind;
import org.apache.olingo.server.core.uri.queryoption.expression.AliasImpl;
import org.apache.olingo.server.core.uri.queryoption.expression.BinaryImpl;
import org.apache.olingo.server.core.uri.queryoption.expression.EnumerationImpl;
import org.apache.olingo.server.core.uri.queryoption.expression.LiteralImpl;
import org.apache.olingo.server.core.uri.queryoption.expression.MemberImpl;
import org.apache.olingo.server.core.uri.queryoption.expression.MethodImpl;
import org.apache.olingo.server.core.uri.queryoption.expression.TypeLiteralImpl;
import org.apache.olingo.server.core.uri.queryoption.expression.UnaryImpl;
import org.apache.olingo.server.core.uri.validator.UriValidationException;
public class ExpressionParser {
private static final Map<TokenKind, BinaryOperatorKind> tokenToBinaryOperator;
static {
Map<TokenKind, BinaryOperatorKind> temp = new EnumMap<>(TokenKind.class);
temp.put(TokenKind.OrOperator, BinaryOperatorKind.OR);
temp.put(TokenKind.AndOperator, BinaryOperatorKind.AND);
temp.put(TokenKind.EqualsOperator, BinaryOperatorKind.EQ);
temp.put(TokenKind.NotEqualsOperator, BinaryOperatorKind.NE);
temp.put(TokenKind.GreaterThanOperator, BinaryOperatorKind.GT);
temp.put(TokenKind.GreaterThanOrEqualsOperator, BinaryOperatorKind.GE);
temp.put(TokenKind.LessThanOperator, BinaryOperatorKind.LT);
temp.put(TokenKind.LessThanOrEqualsOperator, BinaryOperatorKind.LE);
temp.put(TokenKind.AddOperator, BinaryOperatorKind.ADD);
temp.put(TokenKind.SubOperator, BinaryOperatorKind.SUB);
temp.put(TokenKind.MulOperator, BinaryOperatorKind.MUL);
temp.put(TokenKind.DivOperator, BinaryOperatorKind.DIV);
temp.put(TokenKind.ModOperator, BinaryOperatorKind.MOD);
tokenToBinaryOperator = Collections.unmodifiableMap(temp);
}
// 'cast' and 'isof' are handled specially.
private static final Map<TokenKind, MethodKind> tokenToMethod;
static {
Map<TokenKind, MethodKind> temp = new EnumMap<>(TokenKind.class);
temp.put(TokenKind.CeilingMethod, MethodKind.CEILING);
temp.put(TokenKind.ConcatMethod, MethodKind.CONCAT);
temp.put(TokenKind.ContainsMethod, MethodKind.CONTAINS);
temp.put(TokenKind.DateMethod, MethodKind.DATE);
temp.put(TokenKind.DayMethod, MethodKind.DAY);
temp.put(TokenKind.EndswithMethod, MethodKind.ENDSWITH);
temp.put(TokenKind.FloorMethod, MethodKind.FLOOR);
temp.put(TokenKind.FractionalsecondsMethod, MethodKind.FRACTIONALSECONDS);
temp.put(TokenKind.GeoDistanceMethod, MethodKind.GEODISTANCE);
temp.put(TokenKind.GeoIntersectsMethod, MethodKind.GEOINTERSECTS);
temp.put(TokenKind.GeoLengthMethod, MethodKind.GEOLENGTH);
temp.put(TokenKind.HourMethod, MethodKind.HOUR);
temp.put(TokenKind.IndexofMethod, MethodKind.INDEXOF);
temp.put(TokenKind.LengthMethod, MethodKind.LENGTH);
temp.put(TokenKind.MaxdatetimeMethod, MethodKind.MAXDATETIME);
temp.put(TokenKind.MindatetimeMethod, MethodKind.MINDATETIME);
temp.put(TokenKind.MinuteMethod, MethodKind.MINUTE);
temp.put(TokenKind.MonthMethod, MethodKind.MONTH);
temp.put(TokenKind.NowMethod, MethodKind.NOW);
temp.put(TokenKind.RoundMethod, MethodKind.ROUND);
temp.put(TokenKind.SecondMethod, MethodKind.SECOND);
temp.put(TokenKind.StartswithMethod, MethodKind.STARTSWITH);
temp.put(TokenKind.SubstringMethod, MethodKind.SUBSTRING);
temp.put(TokenKind.TimeMethod, MethodKind.TIME);
temp.put(TokenKind.TolowerMethod, MethodKind.TOLOWER);
temp.put(TokenKind.TotaloffsetminutesMethod, MethodKind.TOTALOFFSETMINUTES);
temp.put(TokenKind.TotalsecondsMethod, MethodKind.TOTALSECONDS);
temp.put(TokenKind.ToupperMethod, MethodKind.TOUPPER);
temp.put(TokenKind.TrimMethod, MethodKind.TRIM);
temp.put(TokenKind.YearMethod, MethodKind.YEAR);
temp.put(TokenKind.SubstringofMethod, MethodKind.SUBSTRINGOF);
tokenToMethod = Collections.unmodifiableMap(temp);
}
private final Edm edm;
private final OData odata;
private UriTokenizer tokenizer;
private Deque<UriResourceLambdaVariable> lambdaVariables = new ArrayDeque<>();
private EdmType referringType;
private Collection<String> crossjoinEntitySetNames;
private Map<String, AliasQueryOption> aliases;
public ExpressionParser(final Edm edm, final OData odata) {
this.edm = edm;
this.odata = odata;
}
public Expression parse(UriTokenizer tokenizer, final EdmType referringType,
final Collection<String> crossjoinEntitySetNames, final Map<String, AliasQueryOption> aliases)
throws UriParserException, UriValidationException {
// Initialize tokenizer.
this.tokenizer = tokenizer;
this.referringType = referringType;
this.crossjoinEntitySetNames = crossjoinEntitySetNames;
this.aliases = aliases;
final Expression expression = parseExpression();
checkNoCollection(expression);
return expression;
}
private Expression parseExpression() throws UriParserException, UriValidationException {
Expression left = parseAnd();
while (tokenizer.next(TokenKind.OrOperator)) {
checkType(left, EdmPrimitiveTypeKind.Boolean);
checkNoCollection(left);
final Expression right = parseAnd();
checkType(right, EdmPrimitiveTypeKind.Boolean);
checkNoCollection(right);
left = new BinaryImpl(left, BinaryOperatorKind.OR, right,
odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean));
}
return left;
}
private Expression parseAnd() throws UriParserException, UriValidationException {
Expression left = parseExprEquality();
while (tokenizer.next(TokenKind.AndOperator)) {
checkType(left, EdmPrimitiveTypeKind.Boolean);
checkNoCollection(left);
final Expression right = parseExprEquality();
checkType(right, EdmPrimitiveTypeKind.Boolean);
checkNoCollection(right);
left = new BinaryImpl(left, BinaryOperatorKind.AND, right,
odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean));
}
return left;
}
private Expression parseExprEquality() throws UriParserException, UriValidationException {
Expression left = parseExprRel();
TokenKind operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.EqualsOperator, TokenKind.NotEqualsOperator);
// Null for everything other than EQ or NE
while (operatorTokenKind != null) {
final Expression right = parseExprEquality();
checkEqualityTypes(left, right);
left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right,
odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean));
operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.EqualsOperator, TokenKind.NotEqualsOperator);
}
return left;
}
private Expression parseExprRel() throws UriParserException, UriValidationException {
if (tokenizer.next(TokenKind.IsofMethod)) {
// The isof method is a terminal. So no further operators are allowed.
return parseIsOfOrCastMethod(MethodKind.ISOF);
} else {
Expression left = parseExprAdd();
TokenKind operatorTokenKind = ParserHelper.next(tokenizer,
TokenKind.GreaterThanOperator, TokenKind.GreaterThanOrEqualsOperator,
TokenKind.LessThanOperator, TokenKind.LessThanOrEqualsOperator);
// Null for everything other than GT or GE or LT or LE
while (operatorTokenKind != null) {
final Expression right = parseExprAdd();
checkRelationTypes(left, right);
left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right,
odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean));
operatorTokenKind = ParserHelper.next(tokenizer,
TokenKind.GreaterThanOperator, TokenKind.GreaterThanOrEqualsOperator,
TokenKind.LessThanOperator, TokenKind.LessThanOrEqualsOperator);
}
return left;
}
}
private Expression parseIsOfOrCastMethod(final MethodKind kind) throws UriParserException, UriValidationException {
// The TokenKind 'IsOfMethod' consumes also the opening parenthesis.
// The first parameter could be an expression or a type literal.
List<Expression> parameters = new ArrayList<>();
ParserHelper.bws(tokenizer);
parameters.add(parseExpression());
if (!(parameters.get(0) instanceof TypeLiteral)) {
// The first parameter is not a type literal, so there must be a second parameter.
ParserHelper.bws(tokenizer);
ParserHelper.requireNext(tokenizer, TokenKind.COMMA);
ParserHelper.bws(tokenizer);
parameters.add(parseExpression());
ParserHelper.bws(tokenizer);
// The second parameter must be a type literal.
if (!(parameters.get(1) instanceof TypeLiteral)) {
throw new UriParserSemanticException("Type literal expected.",
UriParserSemanticException.MessageKeys.INCOMPATIBLE_TYPE_FILTER);
}
}
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
return new MethodImpl(kind, parameters);
}
private Expression parseExprAdd() throws UriParserException, UriValidationException {
Expression left = parseExprMul();
TokenKind operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.AddOperator, TokenKind.SubOperator);
// Null for everything other than ADD or SUB
while (operatorTokenKind != null) {
final Expression right = parseExprMul();
final EdmType resultType = getAddSubTypeAndCheckLeftAndRight(left, right,
operatorTokenKind == TokenKind.SubOperator);
left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right, resultType);
operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.AddOperator, TokenKind.SubOperator);
}
return left;
}
private Expression parseExprMul() throws UriParserException, UriValidationException {
Expression left = parseExprUnary();
TokenKind operatorTokenKind = ParserHelper.next(tokenizer,
TokenKind.MulOperator, TokenKind.DivOperator, TokenKind.ModOperator);
// Null for everything other than MUL or DIV or MOD
while (operatorTokenKind != null) {
checkNumericType(left);
final Expression right = parseExprUnary();
checkNumericType(right);
left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right,
odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Double));
operatorTokenKind = ParserHelper.next(tokenizer,
TokenKind.MulOperator, TokenKind.DivOperator, TokenKind.ModOperator);
}
return left;
}
private Expression parseExprUnary() throws UriParserException, UriValidationException {
if (tokenizer.next(TokenKind.MinusOperator)) {
final Expression expression = parseExprPrimary();
if (!isType(getType(expression), EdmPrimitiveTypeKind.Duration)) {
checkNumericType(expression);
}
return new UnaryImpl(UnaryOperatorKind.MINUS, expression, getType(expression));
} else if (tokenizer.next(TokenKind.NotOperator)) {
final Expression expression = parseExprValue();
checkType(expression, EdmPrimitiveTypeKind.Boolean);
checkNoCollection(expression);
return new UnaryImpl(UnaryOperatorKind.NOT, expression, getType(expression));
} else if (tokenizer.next(TokenKind.CastMethod)) {
return parseIsOfOrCastMethod(MethodKind.CAST);
} else {
return parseExprPrimary();
}
}
private Expression parseExprPrimary() throws UriParserException, UriValidationException {
final Expression left = parseExprValue();
if (isEnumType(left) && tokenizer.next(TokenKind.HasOperator)) {
ParserHelper.requireNext(tokenizer, TokenKind.EnumValue);
final Expression right = createEnumExpression(tokenizer.getText());
return new BinaryImpl(left, BinaryOperatorKind.HAS, right,
odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean));
} else if (tokenizer.next(TokenKind.InOperator)) {
EdmType leftExprType = getType(left);
EdmPrimitiveTypeKind kinds = EdmPrimitiveTypeKind.valueOfFQN(leftExprType.getFullQualifiedName());
if (tokenizer.next(TokenKind.OPEN)) {
ParserHelper.bws(tokenizer);
List<Expression> expressionList = parseInExpr();
checkInExpressionTypes(expressionList, kinds);
return new BinaryImpl(left, BinaryOperatorKind.IN, expressionList,
odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean));
} else {
ParserHelper.bws(tokenizer);
final Expression right = parseExpression();
checkType(right, kinds);
return new BinaryImpl(left, BinaryOperatorKind.IN, right,
odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean));
}
}
return left;
}
/**
* @param expressionList
* @param kinds
* @throws UriParserException
* @throws UriParserSemanticException
*/
private void checkInExpressionTypes(List<Expression> expressionList, EdmPrimitiveTypeKind kinds)
throws UriParserException, UriParserSemanticException {
for (Expression expr : expressionList) {
EdmType inExprType = getType(expr);
if (!isType(inExprType, kinds)) {
throw new UriParserSemanticException("Incompatible types.",
UriParserSemanticException.MessageKeys.TYPES_NOT_COMPATIBLE,
inExprType == null ? "" : inExprType.getFullQualifiedName().getFullQualifiedNameAsString(),
kinds.getFullQualifiedName().getFullQualifiedNameAsString());
}
}
}
/**
* @param expressionList
* @throws UriParserException
* @throws UriValidationException
*/
private List<Expression> parseInExpr() throws UriParserException, UriValidationException {
List<Expression> expressionList = new ArrayList<>();
while(!tokenizer.next(TokenKind.CLOSE)) {
Expression expression = parseExpression();
expressionList.add(expression);
ParserHelper.bws(tokenizer);
while (tokenizer.next(TokenKind.COMMA)) {
ParserHelper.bws(tokenizer);
expression = parseExpression();
expressionList.add(expression);
ParserHelper.bws(tokenizer);
}
}
return expressionList;
}
private Expression parseExprValue() throws UriParserException, UriValidationException {
if (tokenizer.next(TokenKind.OPEN)) {
ParserHelper.bws(tokenizer);
final Expression expression = parseExpression();
ParserHelper.bws(tokenizer);
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
return expression;
}
if (tokenizer.next(TokenKind.ParameterAliasName)) {
final String name = tokenizer.getText();
if (aliases.containsKey(name)) {
return new AliasImpl(name,
ParserHelper.parseAliasValue(name, null, true, true, edm, referringType, aliases));
} else {
return new AliasImpl(name, null);
}
}
if (tokenizer.next(TokenKind.jsonArrayOrObject)) {
// There is no obvious way how the type could be determined.
return new LiteralImpl(tokenizer.getText(), null);
}
if (tokenizer.next(TokenKind.ROOT)) {
return parseFirstMemberExpr(TokenKind.ROOT);
}
if (tokenizer.next(TokenKind.IT)) {
return parseFirstMemberExpr(TokenKind.IT);
}
final TokenKind nextPrimitive = ParserHelper.nextPrimitiveValue(tokenizer);
if (nextPrimitive != null) {
return parsePrimitive(nextPrimitive);
}
final TokenKind nextMethod =
ParserHelper.next(tokenizer, tokenToMethod.keySet().toArray(new TokenKind[tokenToMethod.size()]));
if (nextMethod != null) {
return parseMethod(nextMethod);
}
if (tokenizer.next(TokenKind.QualifiedName)) {
return parseFirstMemberExpr(TokenKind.QualifiedName);
}
if (tokenizer.next(TokenKind.ODataIdentifier)) {
return parseFirstMemberExpr(TokenKind.ODataIdentifier);
}
throw new UriParserSyntaxException("Unexpected token.", UriParserSyntaxException.MessageKeys.SYNTAX);
}
private Expression parseMethod(final TokenKind nextMethod) throws UriParserException, UriValidationException {
// The method token text includes the opening parenthesis so that method calls can be recognized unambiguously.
// OData identifiers have to be considered after that.
final MethodKind methodKind = tokenToMethod.get(nextMethod);
return new MethodImpl(methodKind, parseMethodParameters(methodKind));
}
private Expression parsePrimitive(final TokenKind primitiveTokenKind) throws UriParserException {
final String primitiveValueLiteral = tokenizer.getText();
if (primitiveTokenKind == TokenKind.EnumValue) {
return createEnumExpression(primitiveValueLiteral);
} else {
EdmPrimitiveTypeKind primitiveTypeKind = ParserHelper.tokenToPrimitiveType.get(primitiveTokenKind);
if (primitiveTypeKind == EdmPrimitiveTypeKind.Int64) {
primitiveTypeKind = determineIntegerType(primitiveValueLiteral);
}
final EdmPrimitiveType type = primitiveTypeKind == null ?
// Null handling
null :
odata.createPrimitiveTypeInstance(primitiveTypeKind);
return new LiteralImpl(primitiveValueLiteral, type);
}
}
private EdmPrimitiveTypeKind determineIntegerType(final String intValueAsString) throws UriParserSyntaxException {
EdmPrimitiveTypeKind typeKind = null;
try {
final long value = Long.parseLong(intValueAsString);
if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
typeKind = EdmPrimitiveTypeKind.SByte;
} else if (value >= 0 && value <= 255) {
typeKind = EdmPrimitiveTypeKind.Byte;
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
typeKind = EdmPrimitiveTypeKind.Int16;
} else if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) {
typeKind = EdmPrimitiveTypeKind.Int32;
} else {
typeKind = EdmPrimitiveTypeKind.Int64;
}
} catch (final NumberFormatException e) {
// The number cannot be formatted wrongly because the tokenizer already checked the format
// but it is too large for Long and therefore too large for Edm.Int64.
typeKind = EdmPrimitiveTypeKind.Decimal;
}
return typeKind;
}
private List<Expression> parseMethodParameters(final MethodKind methodKind)
throws UriParserException, UriValidationException {
List<Expression> parameters = new ArrayList<>();
switch (methodKind) {
// Must have no parameter.
case NOW:
case MAXDATETIME:
case MINDATETIME:
ParserHelper.bws(tokenizer);
break;
// Must have one parameter.
case LENGTH:
case TOLOWER:
case TOUPPER:
case TRIM:
ParserHelper.bws(tokenizer);
final Expression stringParameter = parseExpression();
ParserHelper.bws(tokenizer);
checkType(stringParameter, EdmPrimitiveTypeKind.String);
checkNoCollection(stringParameter);
parameters.add(stringParameter);
break;
case YEAR:
case MONTH:
case DAY:
ParserHelper.bws(tokenizer);
final Expression dateParameter = parseExpression();
ParserHelper.bws(tokenizer);
checkType(dateParameter, EdmPrimitiveTypeKind.Date, EdmPrimitiveTypeKind.DateTimeOffset);
checkNoCollection(dateParameter);
parameters.add(dateParameter);
break;
case HOUR:
case MINUTE:
case SECOND:
case FRACTIONALSECONDS:
ParserHelper.bws(tokenizer);
final Expression timeParameter = parseExpression();
ParserHelper.bws(tokenizer);
checkType(timeParameter, EdmPrimitiveTypeKind.TimeOfDay, EdmPrimitiveTypeKind.DateTimeOffset);
checkNoCollection(timeParameter);
parameters.add(timeParameter);
break;
case DATE:
case TIME:
case TOTALOFFSETMINUTES:
ParserHelper.bws(tokenizer);
final Expression dateTimeParameter = parseExpression();
ParserHelper.bws(tokenizer);
checkType(dateTimeParameter, EdmPrimitiveTypeKind.DateTimeOffset);
checkNoCollection(dateTimeParameter);
parameters.add(dateTimeParameter);
break;
case TOTALSECONDS:
ParserHelper.bws(tokenizer);
final Expression durationParameter = parseExpression();
ParserHelper.bws(tokenizer);
checkType(durationParameter, EdmPrimitiveTypeKind.Duration);
checkNoCollection(durationParameter);
parameters.add(durationParameter);
break;
case ROUND:
case FLOOR:
case CEILING:
ParserHelper.bws(tokenizer);
final Expression decimalParameter = parseExpression();
ParserHelper.bws(tokenizer);
checkType(decimalParameter,
EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double);
checkNoCollection(decimalParameter);
parameters.add(decimalParameter);
break;
case GEOLENGTH:
ParserHelper.bws(tokenizer);
final Expression geoParameter = parseExpression();
ParserHelper.bws(tokenizer);
checkType(geoParameter,
EdmPrimitiveTypeKind.GeographyLineString, EdmPrimitiveTypeKind.GeometryLineString);
checkNoCollection(geoParameter);
parameters.add(geoParameter);
break;
// Must have two parameters.
case CONTAINS:
case ENDSWITH:
case STARTSWITH:
case INDEXOF:
case CONCAT:
case SUBSTRINGOF:
ParserHelper.bws(tokenizer);
final Expression stringParameter1 = parseExpression();
checkType(stringParameter1, EdmPrimitiveTypeKind.String);
checkNoCollection(stringParameter1);
parameters.add(stringParameter1);
ParserHelper.bws(tokenizer);
ParserHelper.requireNext(tokenizer, TokenKind.COMMA);
ParserHelper.bws(tokenizer);
final Expression stringParameter2 = parseExpression();
ParserHelper.bws(tokenizer);
checkType(stringParameter2, EdmPrimitiveTypeKind.String);
checkNoCollection(stringParameter2);
parameters.add(stringParameter2);
break;
case GEODISTANCE:
ParserHelper.bws(tokenizer);
final Expression geoParameter1 = parseExpression();
checkType(geoParameter1, EdmPrimitiveTypeKind.GeographyPoint, EdmPrimitiveTypeKind.GeometryPoint);
checkNoCollection(geoParameter1);
parameters.add(geoParameter1);
ParserHelper.bws(tokenizer);
ParserHelper.requireNext(tokenizer, TokenKind.COMMA);
ParserHelper.bws(tokenizer);
final Expression geoParameter2 = parseExpression();
ParserHelper.bws(tokenizer);
checkType(geoParameter2, EdmPrimitiveTypeKind.GeographyPoint, EdmPrimitiveTypeKind.GeometryPoint);
checkNoCollection(geoParameter2);
parameters.add(geoParameter2);
break;
case GEOINTERSECTS:
ParserHelper.bws(tokenizer);
final Expression geoPointParameter = parseExpression();
checkType(geoPointParameter,
EdmPrimitiveTypeKind.GeographyPoint, EdmPrimitiveTypeKind.GeometryPoint);
checkNoCollection(geoPointParameter);
parameters.add(geoPointParameter);
ParserHelper.bws(tokenizer);
ParserHelper.requireNext(tokenizer, TokenKind.COMMA);
ParserHelper.bws(tokenizer);
final Expression geoPolygonParameter = parseExpression();
ParserHelper.bws(tokenizer);
checkType(geoPolygonParameter,
EdmPrimitiveTypeKind.GeographyPolygon, EdmPrimitiveTypeKind.GeometryPolygon);
checkNoCollection(geoPolygonParameter);
parameters.add(geoPolygonParameter);
break;
// Can have two or three parameters.
case SUBSTRING:
ParserHelper.bws(tokenizer);
final Expression parameterFirst = parseExpression();
checkType(parameterFirst, EdmPrimitiveTypeKind.String);
checkNoCollection(parameterFirst);
parameters.add(parameterFirst);
ParserHelper.bws(tokenizer);
ParserHelper.requireNext(tokenizer, TokenKind.COMMA);
ParserHelper.bws(tokenizer);
final Expression parameterSecond = parseExpression();
ParserHelper.bws(tokenizer);
checkIntegerType(parameterSecond);
parameters.add(parameterSecond);
ParserHelper.bws(tokenizer);
if (tokenizer.next(TokenKind.COMMA)) {
ParserHelper.bws(tokenizer);
final Expression parameterThird = parseExpression();
ParserHelper.bws(tokenizer);
checkIntegerType(parameterThird);
parameters.add(parameterThird);
}
break;
// Can have one or two parameters. These methods are handled elsewhere.
case CAST:
case ISOF:
break;
}
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
return parameters;
}
private Expression parseFirstMemberExpr(final TokenKind lastTokenKind)
throws UriParserException, UriValidationException {
final UriInfoImpl uriInfo = new UriInfoImpl();
EdmType startTypeFilter = null;
if (lastTokenKind == TokenKind.ROOT) {
parseDollarRoot(uriInfo);
} else if (lastTokenKind == TokenKind.IT) {
parseDollarIt(uriInfo, referringType);
} else if (lastTokenKind == TokenKind.QualifiedName) {
// Special handling for leading type casts and type literals
final FullQualifiedName fullQualifiedName = new FullQualifiedName(tokenizer.getText());
EdmType filterType = edm.getEntityType(fullQualifiedName);
if (filterType == null) {
filterType = edm.getComplexType(fullQualifiedName);
}
if (filterType == null) {
filterType = getPrimitiveType(fullQualifiedName);
}
if (filterType == null) {
filterType = edm.getEnumType(fullQualifiedName);
}
if (filterType == null) {
filterType = edm.getTypeDefinition(fullQualifiedName);
}
if (filterType != null) {
if (tokenizer.next(TokenKind.SLASH)) {
// Leading type cast
checkStructuredTypeFilter(referringType, filterType);
startTypeFilter = filterType;
final TokenKind tokenKind = ParserHelper.next(tokenizer, TokenKind.QualifiedName, TokenKind.ODataIdentifier);
parseMemberExpression(tokenKind, uriInfo, new UriResourceStartingTypeFilterImpl(filterType, false), false);
} else {
// Type literal
return new TypeLiteralImpl(filterType);
}
} else {
// Must be bound or unbound function.
parseFunction(fullQualifiedName, uriInfo, referringType, true);
}
} else if (lastTokenKind == TokenKind.ODataIdentifier) {
parseFirstMemberODataIdentifier(uriInfo);
}
return new MemberImpl(uriInfo, startTypeFilter);
}
private EdmType getPrimitiveType(final FullQualifiedName fullQualifiedName) {
if (EdmPrimitiveType.EDM_NAMESPACE.equals(fullQualifiedName.getNamespace())) {
final EdmPrimitiveTypeKind primitiveTypeKind = EdmPrimitiveTypeKind.valueOf(fullQualifiedName.getName());
return primitiveTypeKind == null ? null : odata.createPrimitiveTypeInstance(primitiveTypeKind);
} else {
return null;
}
}
private void parseDollarRoot(UriInfoImpl uriInfo) throws UriParserException, UriValidationException {
UriResourceRootImpl rootResource = new UriResourceRootImpl(referringType, true);
uriInfo.addResourcePart(rootResource);
ParserHelper.requireNext(tokenizer, TokenKind.SLASH);
ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier);
final String name = tokenizer.getText();
UriResourcePartTyped resource = null;
final EdmEntitySet entitySet = edm.getEntityContainer().getEntitySet(name);
if (entitySet == null) {
final EdmSingleton singleton = edm.getEntityContainer().getSingleton(name);
if (singleton == null) {
throw new UriParserSemanticException("EntitySet or singleton expected.",
UriParserSemanticException.MessageKeys.UNKNOWN_PART, name);
} else {
resource = new UriResourceSingletonImpl(singleton);
}
} else {
ParserHelper.requireNext(tokenizer, TokenKind.OPEN);
final List<UriParameter> keyPredicates =
ParserHelper.parseKeyPredicate(tokenizer, entitySet.getEntityType(), null, edm, referringType, aliases);
resource = new UriResourceEntitySetImpl(entitySet).setKeyPredicates(keyPredicates);
}
uriInfo.addResourcePart(resource);
parseSingleNavigationExpr(uriInfo, resource);
}
private void parseDollarIt(UriInfoImpl uriInfo, final EdmType referringType)
throws UriParserException, UriValidationException {
UriResourceItImpl itResource = new UriResourceItImpl(referringType, false);
uriInfo.addResourcePart(itResource);
if (tokenizer.next(TokenKind.SLASH)) {
final TokenKind tokenKind = ParserHelper.next(tokenizer, TokenKind.QualifiedName, TokenKind.ODataIdentifier);
parseMemberExpression(tokenKind, uriInfo, itResource, true);
}
}
private void parseFirstMemberODataIdentifier(UriInfoImpl uriInfo) throws UriParserException, UriValidationException {
final String name = tokenizer.getText();
// For a crossjoin, the identifier must be an entity-set name.
if (crossjoinEntitySetNames != null && !crossjoinEntitySetNames.isEmpty()) {
if (crossjoinEntitySetNames.contains(name)) {
final UriResourceEntitySetImpl resource =
new UriResourceEntitySetImpl(edm.getEntityContainer().getEntitySet(name));
uriInfo.addResourcePart(resource);
if (tokenizer.next(TokenKind.SLASH)) {
final TokenKind tokenKind = ParserHelper.next(tokenizer, TokenKind.QualifiedName, TokenKind.ODataIdentifier);
parseMemberExpression(tokenKind, uriInfo, resource, true);
}
return;
} else {
throw new UriParserSemanticException("Unknown crossjoin entity set.",
UriParserSemanticException.MessageKeys.UNKNOWN_PART, name);
}
}
// Check if the OData identifier is a lambda variable, otherwise it must be a property.
UriResourceLambdaVariable lambdaVariable = null;
for (final UriResourceLambdaVariable variable : lambdaVariables) {
if (variable.getVariableName().equals(name)) {
lambdaVariable = variable;
break;
}
}
if (lambdaVariable != null) {
// Copy lambda variable into new resource, just in case ...
final UriResourceLambdaVariable lambdaResource =
new UriResourceLambdaVarImpl(lambdaVariable.getVariableName(), lambdaVariable.getType());
uriInfo.addResourcePart(lambdaResource);
if (tokenizer.next(TokenKind.SLASH)) {
final TokenKind tokenKind = ParserHelper.next(tokenizer, TokenKind.QualifiedName, TokenKind.ODataIdentifier);
parseMemberExpression(tokenKind, uriInfo, lambdaResource, true);
}
} else {
// Must be a property.
parseMemberExpression(TokenKind.ODataIdentifier, uriInfo, null, true);
}
}
private void parseMemberExpression(final TokenKind lastTokenKind, UriInfoImpl uriInfo,
final UriResourcePartTyped lastResource, final boolean allowTypeFilter)
throws UriParserException, UriValidationException {
if (lastTokenKind == TokenKind.QualifiedName) {
// Type cast to an entity type or complex type or bound function
final FullQualifiedName fullQualifiedName = new FullQualifiedName(tokenizer.getText());
final EdmEntityType edmEntityType = edm.getEntityType(fullQualifiedName);
if (edmEntityType != null) {
if (allowTypeFilter) {
setTypeFilter(lastResource, edmEntityType);
if (tokenizer.next(TokenKind.SLASH)) {
if (tokenizer.next(TokenKind.QualifiedName)) {
parseBoundFunction(fullQualifiedName, uriInfo, lastResource);
} else if (tokenizer.next(TokenKind.ODataIdentifier)) {
parsePropertyPathExpr(uriInfo, lastResource);
} else {
throw new UriParserSyntaxException("Expected OData Identifier or Full Qualified Name.",
UriParserSyntaxException.MessageKeys.SYNTAX);
}
}
} else {
throw new UriParserSemanticException("Type filters are not chainable.",
UriParserSemanticException.MessageKeys.TYPE_FILTER_NOT_CHAINABLE,
lastResource.getType().getFullQualifiedName().getFullQualifiedNameAsString(),
fullQualifiedName.getFullQualifiedNameAsString());
}
} else if (edm.getComplexType(fullQualifiedName) != null) {
if (allowTypeFilter) {
setTypeFilter(lastResource, edm.getComplexType(fullQualifiedName));
if (tokenizer.next(TokenKind.SLASH)) {
if (tokenizer.next(TokenKind.QualifiedName)) {
parseBoundFunction(fullQualifiedName, uriInfo, lastResource);
} else if (tokenizer.next(TokenKind.ODataIdentifier)) {
parsePropertyPathExpr(uriInfo, lastResource);
} else {
throw new UriParserSyntaxException("Expected OData Identifier or Full Qualified Name.",
UriParserSyntaxException.MessageKeys.SYNTAX);
}
}
} else {
throw new UriParserSemanticException("Type filters are not chainable.",
UriParserSemanticException.MessageKeys.TYPE_FILTER_NOT_CHAINABLE,
lastResource.getType().getFullQualifiedName().getFullQualifiedNameAsString(),
fullQualifiedName.getFullQualifiedNameAsString());
}
} else {
parseBoundFunction(fullQualifiedName, uriInfo, lastResource);
}
} else if (lastTokenKind == TokenKind.ODataIdentifier) {
parsePropertyPathExpr(uriInfo, lastResource);
} else {
throw new UriParserSyntaxException("Unexpected token.", UriParserSyntaxException.MessageKeys.SYNTAX);
}
}
private void setTypeFilter(UriResourcePartTyped lastResource, final EdmStructuredType entityTypeFilter)
throws UriParserException {
checkStructuredTypeFilter(lastResource.getType(), entityTypeFilter);
if (lastResource instanceof UriResourceTypedImpl) {
((UriResourceTypedImpl) lastResource).setTypeFilter(entityTypeFilter);
} else if (lastResource instanceof UriResourceWithKeysImpl) {
((UriResourceWithKeysImpl) lastResource).setEntryTypeFilter(entityTypeFilter);
}
}
private void parsePropertyPathExpr(UriInfoImpl uriInfo, final UriResourcePartTyped lastResource)
throws UriParserException, UriValidationException {
final String oDataIdentifier = tokenizer.getText();
final EdmType lastType = lastResource == null ? referringType : ParserHelper.getTypeInformation(lastResource);
if (!(lastType instanceof EdmStructuredType)) {
throw new UriParserSemanticException("Property paths must follow a structured type.",
UriParserSemanticException.MessageKeys.ONLY_FOR_STRUCTURAL_TYPES, oDataIdentifier);
}
final EdmStructuredType structuredType = (EdmStructuredType) lastType;
final EdmElement property = structuredType.getProperty(oDataIdentifier);
if (property == null) {
throw new UriParserSemanticException("Unknown property.",
UriParserSemanticException.MessageKeys.EXPRESSION_PROPERTY_NOT_IN_TYPE,
lastType.getFullQualifiedName().getFullQualifiedNameAsString(),
oDataIdentifier);
}
if (property.getType() instanceof EdmComplexType) {
final UriResourceComplexPropertyImpl complexResource =
new UriResourceComplexPropertyImpl((EdmProperty) property);
uriInfo.addResourcePart(complexResource);
if (property.isCollection()) {
if (tokenizer.next(TokenKind.SLASH)) {
parseCollectionPathExpr(uriInfo, complexResource);
}
} else {
parseComplexPathExpr(uriInfo, complexResource);
}
} else if (property instanceof EdmNavigationProperty) {
// Nav. property; maybe a collection
final UriResourceNavigationPropertyImpl navigationResource =
new UriResourceNavigationPropertyImpl((EdmNavigationProperty) property);
navigationResource.setKeyPredicates(
ParserHelper.parseNavigationKeyPredicate(tokenizer, (EdmNavigationProperty) property,
edm, referringType, aliases));
uriInfo.addResourcePart(navigationResource);
if (navigationResource.isCollection()) {
parseCollectionNavigationExpr(uriInfo, navigationResource);
} else {
parseSingleNavigationExpr(uriInfo, navigationResource);
}
} else {
// Primitive type or Enum type
final UriResourcePrimitivePropertyImpl primitiveResource =
new UriResourcePrimitivePropertyImpl((EdmProperty) property);
uriInfo.addResourcePart(primitiveResource);
if (property.isCollection()) {
if (tokenizer.next(TokenKind.SLASH)) {
parseCollectionPathExpr(uriInfo, primitiveResource);
}
} else {
parseSinglePathExpr(uriInfo, primitiveResource);
}
}
}
private void parseSingleNavigationExpr(UriInfoImpl uriInfo, final UriResourcePartTyped lastResource)
throws UriParserException, UriValidationException {
if (tokenizer.next(TokenKind.SLASH)) {
final TokenKind tokenKind = ParserHelper.next(tokenizer, TokenKind.QualifiedName, TokenKind.ODataIdentifier);
parseMemberExpression(tokenKind, uriInfo, lastResource, true);
}
}
private void parseCollectionNavigationExpr(UriInfoImpl uriInfo, UriResourcePartTyped lastResource)
throws UriParserException, UriValidationException {
boolean hasSlash = false;
if (tokenizer.next(TokenKind.SLASH)) {
hasSlash = true;
if (tokenizer.next(TokenKind.QualifiedName)) {
final FullQualifiedName qualifiedName = new FullQualifiedName(tokenizer.getText());
final EdmEntityType edmEntityType = edm.getEntityType(qualifiedName);
if (edmEntityType == null) {
parseBoundFunction(qualifiedName, uriInfo, lastResource);
} else {
setTypeFilter(lastResource, edmEntityType);
}
hasSlash = false;
}
}
if (!hasSlash && tokenizer.next(TokenKind.OPEN)) {
if (lastResource instanceof UriResourceNavigation) {
((UriResourceNavigationPropertyImpl) lastResource).setKeyPredicates(
ParserHelper.parseNavigationKeyPredicate(tokenizer,
((UriResourceNavigationPropertyImpl) lastResource).getProperty(), edm, referringType, aliases));
} else if (lastResource instanceof UriResourceFunction
&& ((UriResourceFunction) lastResource).getType() instanceof EdmEntityType) {
((UriResourceFunctionImpl) lastResource).setKeyPredicates(
ParserHelper.parseKeyPredicate(tokenizer,
(EdmEntityType) ((UriResourceFunction) lastResource).getType(),
null,
edm,
referringType,
aliases));
} else {
throw new UriParserSemanticException("Unknown or wrong resource type.",
UriParserSemanticException.MessageKeys.NOT_IMPLEMENTED, lastResource.toString());
}
parseSingleNavigationExpr(uriInfo, lastResource);
}
if (hasSlash || tokenizer.next(TokenKind.SLASH)) {
parseCollectionPathExpr(uriInfo, lastResource);
}
}
private void parseSinglePathExpr(UriInfoImpl uriInfo, final UriResourcePartTyped lastResource)
throws UriParserException, UriValidationException {
if (tokenizer.next(TokenKind.SLASH)) {
ParserHelper.requireNext(tokenizer, TokenKind.QualifiedName);
parseBoundFunction(new FullQualifiedName(tokenizer.getText()), uriInfo, lastResource);
}
}
private void parseComplexPathExpr(UriInfoImpl uriInfo, final UriResourcePartTyped lastResource)
throws UriParserException, UriValidationException {
if (tokenizer.next(TokenKind.SLASH)) {
if (tokenizer.next(TokenKind.QualifiedName)) {
final FullQualifiedName fullQualifiedName = new FullQualifiedName(tokenizer.getText());
final EdmComplexType edmComplexType = edm.getComplexType(fullQualifiedName);
if (edmComplexType != null) {
setTypeFilter(lastResource, edmComplexType);
if (tokenizer.next(TokenKind.SLASH)) {
parseComplexPathRestExpr(uriInfo, lastResource);
}
} else {
// Must be a bound function.
parseBoundFunction(fullQualifiedName, uriInfo, lastResource);
}
} else {
parseComplexPathRestExpr(uriInfo, lastResource);
}
}
}
private void parseComplexPathRestExpr(UriInfoImpl uriInfo, final UriResourcePartTyped lastResource)
throws UriParserException, UriValidationException {
if (tokenizer.next(TokenKind.QualifiedName)) {
final FullQualifiedName fullQualifiedName = new FullQualifiedName(tokenizer.getText());
// Must be a bound function.
parseBoundFunction(fullQualifiedName, uriInfo, lastResource);
} else if (tokenizer.next(TokenKind.ODataIdentifier)) {
parsePropertyPathExpr(uriInfo, lastResource);
} else {
throw new UriParserSyntaxException("Unexpected token.", UriParserSyntaxException.MessageKeys.SYNTAX);
}
}
private void parseCollectionPathExpr(UriInfoImpl uriInfo, final UriResourcePartTyped lastResource)
throws UriParserException, UriValidationException {
// The initial slash (see grammar) must have been checked and consumed by the caller.
if (tokenizer.next(TokenKind.COUNT)) {
uriInfo.addResourcePart(new UriResourceCountImpl());
} else if (tokenizer.next(TokenKind.ANY)) {
uriInfo.addResourcePart(parseLambdaRest(TokenKind.ANY, lastResource));
} else if (tokenizer.next(TokenKind.ALL)) {
uriInfo.addResourcePart(parseLambdaRest(TokenKind.ALL, lastResource));
} else if (tokenizer.next(TokenKind.QualifiedName)) {
parseBoundFunction(new FullQualifiedName(tokenizer.getText()), uriInfo, lastResource);
} else {
throw new UriParserSyntaxException("Unexpected token.", UriParserSyntaxException.MessageKeys.SYNTAX);
}
}
private void parseFunction(final FullQualifiedName fullQualifiedName, UriInfoImpl uriInfo,
final EdmType lastType, final boolean lastIsCollection) throws UriParserException, UriValidationException {
final List<UriParameter> parameters =
ParserHelper.parseFunctionParameters(tokenizer, edm, referringType, true, aliases);
final List<String> parameterNames = ParserHelper.getParameterNames(parameters);
final EdmFunction boundFunction = edm.getBoundFunction(fullQualifiedName,
lastType.getFullQualifiedName(), lastIsCollection, parameterNames);
if (boundFunction != null) {
ParserHelper.validateFunctionParameters(boundFunction, parameters, edm, referringType, aliases);
parseFunctionRest(uriInfo, boundFunction, parameters);
return;
}
final EdmFunction unboundFunction = edm.getUnboundFunction(fullQualifiedName, parameterNames);
if (unboundFunction != null) {
ParserHelper.validateFunctionParameters(unboundFunction, parameters, edm, referringType, aliases);
parseFunctionRest(uriInfo, unboundFunction, parameters);
return;
}
throw new UriParserSemanticException("No function '" + fullQualifiedName + "' found.",
UriParserSemanticException.MessageKeys.FUNCTION_NOT_FOUND, fullQualifiedName.getFullQualifiedNameAsString());
}
private void parseBoundFunction(final FullQualifiedName fullQualifiedName, UriInfoImpl uriInfo,
final UriResourcePartTyped lastResource) throws UriParserException, UriValidationException {
final EdmType type = lastResource.getType();
final List<UriParameter> parameters =
ParserHelper.parseFunctionParameters(tokenizer, edm, referringType, true, aliases);
final List<String> parameterNames = ParserHelper.getParameterNames(parameters);
final EdmFunction boundFunction = edm.getBoundFunction(fullQualifiedName,
type.getFullQualifiedName(), lastResource.isCollection(), parameterNames);
if (boundFunction == null) {
throw new UriParserSemanticException("Bound function '" + fullQualifiedName + "' not found.",
UriParserSemanticException.MessageKeys.FUNCTION_NOT_FOUND, fullQualifiedName.getFullQualifiedNameAsString());
}
ParserHelper.validateFunctionParameters(boundFunction, parameters, edm, referringType, aliases);
parseFunctionRest(uriInfo, boundFunction, parameters);
}
private void parseFunctionRest(UriInfoImpl uriInfo, final EdmFunction function,
final List<UriParameter> parameters) throws UriParserException, UriValidationException {
final UriResourceFunction functionResource = new UriResourceFunctionImpl(null, function, parameters);
uriInfo.addResourcePart(functionResource);
final EdmReturnType edmReturnType = function.getReturnType();
final EdmType edmType = edmReturnType.getType();
final boolean isCollection = edmReturnType.isCollection();
if (function.isComposable()) {
if (edmType instanceof EdmEntityType ) {
if (isCollection) {
parseCollectionNavigationExpr(uriInfo, functionResource);
} else {
parseSingleNavigationExpr(uriInfo, functionResource);
}
} else if (edmType instanceof EdmComplexType) {
if (isCollection) {
if (tokenizer.next(TokenKind.SLASH)) {
parseCollectionPathExpr(uriInfo, functionResource);
}
} else {
parseComplexPathExpr(uriInfo, functionResource);
}
} else if (edmType instanceof EdmPrimitiveType) {
if (isCollection) {
if (tokenizer.next(TokenKind.SLASH)) {
parseCollectionPathExpr(uriInfo, functionResource);
}
} else {
parseSinglePathExpr(uriInfo, functionResource);
}
}
} else if (tokenizer.next(TokenKind.SLASH)) {
throw new UriValidationException("Function is not composable.",
UriValidationException.MessageKeys.UNALLOWED_RESOURCE_PATH, "");
}
}
private UriResourcePartTyped parseLambdaRest(final TokenKind lastTokenKind, final UriResourcePartTyped lastResource)
throws UriParserException, UriValidationException {
ParserHelper.requireNext(tokenizer, TokenKind.OPEN);
if (lastTokenKind == TokenKind.ANY && tokenizer.next(TokenKind.CLOSE)) {
return new UriResourceLambdaAnyImpl(null, null);
}
ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier);
final String lambbdaVariable = tokenizer.getText();
ParserHelper.requireNext(tokenizer, TokenKind.COLON);
lambdaVariables.addFirst(new UriResourceLambdaVarImpl(lambbdaVariable,
lastResource == null ? referringType : lastResource.getType()));
// The ABNF suggests that the "lambaPredicateExpr" must contain at least one lambdaVariable,
// so arguably this could be checked in expression parsing or later in validation.
final Expression lambdaPredicateExpr = parseExpression();
lambdaVariables.removeFirst();
ParserHelper.requireNext(tokenizer, TokenKind.CLOSE);
if (lastTokenKind == TokenKind.ALL) {
return new UriResourceLambdaAllImpl(lambbdaVariable, lambdaPredicateExpr);
} else if (lastTokenKind == TokenKind.ANY) {
return new UriResourceLambdaAnyImpl(lambbdaVariable, lambdaPredicateExpr);
} else {
throw new UriParserSyntaxException("Unexpected token.", UriParserSyntaxException.MessageKeys.SYNTAX);
}
}
protected static EdmType getType(final Expression expression) throws UriParserException {
EdmType type;
if (expression instanceof Literal) {
type = ((Literal) expression).getType();
} else if (expression instanceof TypeLiteral) {
type = ((TypeLiteral) expression).getType();
} else if (expression instanceof Enumeration) {
type = ((Enumeration) expression).getType();
} else if (expression instanceof Member) {
type = ((Member) expression).getType();
} else if (expression instanceof Unary) {
type = ((UnaryImpl) expression).getType();
} else if (expression instanceof Binary) {
type = ((BinaryImpl) expression).getType();
} else if (expression instanceof Method) {
type = ((MethodImpl) expression).getType();
} else if (expression instanceof Alias) {
final AliasQueryOption alias = ((AliasImpl) expression).getAlias();
type = alias == null || alias.getValue() == null ? null : getType(alias.getValue());
} else if (expression instanceof LambdaRef) {
throw new UriParserSemanticException("Type determination not implemented.",
UriParserSemanticException.MessageKeys.NOT_IMPLEMENTED, expression.toString());
} else {
throw new UriParserSemanticException("Unknown expression type.",
UriParserSemanticException.MessageKeys.NOT_IMPLEMENTED, expression.toString());
}
if (type != null && type.getKind() == EdmTypeKind.DEFINITION) {
type = ((EdmTypeDefinition) type).getUnderlyingType();
}
return type;
}
private boolean isType(final EdmType type, final EdmPrimitiveTypeKind... kinds) throws UriParserException {
if (type == null) {
return true;
}
for (final EdmPrimitiveTypeKind kind : kinds) {
if (type.equals(odata.createPrimitiveTypeInstance(kind))) {
return true;
}
}
return false;
}
private void checkType(final Expression expression, final EdmPrimitiveTypeKind... kinds) throws UriParserException {
final EdmType type = getType(expression);
if (!isType(type, kinds)) {
throw new UriParserSemanticException("Incompatible types.",
UriParserSemanticException.MessageKeys.TYPES_NOT_COMPATIBLE,
type == null ? "" : type.getFullQualifiedName().getFullQualifiedNameAsString(),
Arrays.deepToString(kinds));
}
}
private void checkNoCollection(final Expression expression) throws UriParserException {
if (expression instanceof Member && ((Member) expression).isCollection()) {
throw new UriParserSemanticException("Collection not allowed.",
UriParserSemanticException.MessageKeys.COLLECTION_NOT_ALLOWED);
}
}
protected void checkIntegerType(final Expression expression) throws UriParserException {
checkNoCollection(expression);
checkType(expression,
EdmPrimitiveTypeKind.Int64, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int16,
EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte);
}
protected void checkNumericType(final Expression expression) throws UriParserException {
checkNoCollection(expression);
checkType(expression,
EdmPrimitiveTypeKind.Int64, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int16,
EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte,
EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double);
}
private void checkEqualityTypes(final Expression left, final Expression right) throws UriParserException {
checkNoCollection(left);
checkNoCollection(right);
final EdmType leftType = getType(left);
final EdmType rightType = getType(right);
if (leftType == null || rightType == null || leftType.equals(rightType)) {
return;
}
// Numeric promotion for Edm.Byte and Edm.SByte
if (isType(leftType, EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte)
&& isType(rightType, EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte)) {
return;
}
if (leftType.getKind() != EdmTypeKind.PRIMITIVE
|| rightType.getKind() != EdmTypeKind.PRIMITIVE
|| !(((EdmPrimitiveType) leftType).isCompatible((EdmPrimitiveType) rightType)
|| ((EdmPrimitiveType) rightType).isCompatible((EdmPrimitiveType) leftType))) {
throw new UriParserSemanticException("Incompatible types.",
UriParserSemanticException.MessageKeys.TYPES_NOT_COMPATIBLE,
leftType.getFullQualifiedName().getFullQualifiedNameAsString(),
rightType.getFullQualifiedName().getFullQualifiedNameAsString());
}
}
private EdmEnumType getEnumType(final String primitiveValueLiteral) throws UriParserException {
final String enumTypeName = primitiveValueLiteral.substring(0, primitiveValueLiteral.indexOf('\''));
final EdmEnumType type = edm.getEnumType(new FullQualifiedName(enumTypeName));
if (type == null) {
throw new UriParserSemanticException("Unknown Enum type '" + enumTypeName + "'.",
UriParserSemanticException.MessageKeys.UNKNOWN_TYPE, enumTypeName);
}
return type;
}
private boolean isEnumType(final Expression expression) throws UriParserException {
final EdmType expressionType = getType(expression);
return expressionType == null
|| expressionType.getKind() == EdmTypeKind.ENUM
|| isType(expressionType,
EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64,
EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte);
}
private Enumeration createEnumExpression(final String primitiveValueLiteral) throws UriParserException {
final EdmEnumType enumType = getEnumType(primitiveValueLiteral);
// The Enumeration interface could be extended to handle the value as a whole, in line with the primitive type.
try {
return new EnumerationImpl(enumType,
Arrays.asList(enumType.fromUriLiteral(primitiveValueLiteral).split(",")));
} catch (final EdmPrimitiveTypeException e) {
// This part should not be reached, so a general error message key can be re-used.
throw new UriParserSemanticException("Wrong enumeration value '" + primitiveValueLiteral + "'.", e,
UriParserSemanticException.MessageKeys.UNKNOWN_PART, primitiveValueLiteral);
}
}
private void checkRelationTypes(final Expression left, final Expression right) throws UriParserException {
checkNoCollection(left);
checkNoCollection(right);
final EdmType leftType = getType(left);
final EdmType rightType = getType(right);
checkType(left,
EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64,
EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte,
EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double,
EdmPrimitiveTypeKind.Boolean, EdmPrimitiveTypeKind.Guid, EdmPrimitiveTypeKind.String,
EdmPrimitiveTypeKind.Date, EdmPrimitiveTypeKind.TimeOfDay,
EdmPrimitiveTypeKind.DateTimeOffset, EdmPrimitiveTypeKind.Duration);
checkType(right,
EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64,
EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte,
EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double,
EdmPrimitiveTypeKind.Boolean, EdmPrimitiveTypeKind.Guid, EdmPrimitiveTypeKind.String,
EdmPrimitiveTypeKind.Date, EdmPrimitiveTypeKind.TimeOfDay,
EdmPrimitiveTypeKind.DateTimeOffset, EdmPrimitiveTypeKind.Duration);
if (leftType == null || rightType == null) {
return;
}
if (!(((EdmPrimitiveType) leftType).isCompatible((EdmPrimitiveType) rightType)
|| ((EdmPrimitiveType) rightType).isCompatible((EdmPrimitiveType) leftType))) {
throw new UriParserSemanticException("Incompatible types.",
UriParserSemanticException.MessageKeys.TYPES_NOT_COMPATIBLE,
leftType.getFullQualifiedName().getFullQualifiedNameAsString(),
rightType.getFullQualifiedName().getFullQualifiedNameAsString());
}
}
private EdmType getAddSubTypeAndCheckLeftAndRight(final Expression left, final Expression right, final boolean isSub)
throws UriParserException {
checkNoCollection(left);
checkNoCollection(right);
final EdmType leftType = getType(left);
final EdmType rightType = getType(right);
if (isType(leftType,
EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64,
EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte,
EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double)
&& isType(rightType,
EdmPrimitiveTypeKind.Int16, EdmPrimitiveTypeKind.Int32, EdmPrimitiveTypeKind.Int64,
EdmPrimitiveTypeKind.Byte, EdmPrimitiveTypeKind.SByte,
EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double)) {
// The result type must be able to handle the overflow,
// so we return always a wider type than the types of the operands.
if (isType(leftType, EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double)
|| isType(rightType,
EdmPrimitiveTypeKind.Decimal, EdmPrimitiveTypeKind.Single, EdmPrimitiveTypeKind.Double)) {
return odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Double);
} else if (isType(leftType, EdmPrimitiveTypeKind.Int64) || isType(rightType, EdmPrimitiveTypeKind.Int64)) {
return odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Decimal);
} else if (isType(leftType, EdmPrimitiveTypeKind.Int32) || isType(rightType, EdmPrimitiveTypeKind.Int32)) {
return odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Int64);
} else if (isType(leftType, EdmPrimitiveTypeKind.Int16) || isType(rightType, EdmPrimitiveTypeKind.Int16)) {
return odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Int32);
} else {
return odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Int16);
}
}
if ((isType(leftType, EdmPrimitiveTypeKind.DateTimeOffset)
|| isType(leftType, EdmPrimitiveTypeKind.Date)
|| isType(leftType, EdmPrimitiveTypeKind.Duration))
&& isType(rightType, EdmPrimitiveTypeKind.Duration)) {
return leftType;
}
if (isSub
&& (isType(leftType, EdmPrimitiveTypeKind.DateTimeOffset)
&& isType(rightType, EdmPrimitiveTypeKind.DateTimeOffset)
|| isType(leftType, EdmPrimitiveTypeKind.Date)
&& isType(rightType, EdmPrimitiveTypeKind.Date))) {
return odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Duration);
}
throw new UriParserSemanticException("Incompatible types.",
UriParserSemanticException.MessageKeys.TYPES_NOT_COMPATIBLE,
leftType.getFullQualifiedName().getFullQualifiedNameAsString(),
rightType.getFullQualifiedName().getFullQualifiedNameAsString());
}
private void checkStructuredTypeFilter(final EdmType type, final EdmType filterType)
throws UriParserException {
if (!(filterType instanceof EdmStructuredType && ((EdmStructuredType) filterType).compatibleTo(type))) {
throw new UriParserSemanticException("Incompatible type filter.",
UriParserSemanticException.MessageKeys.INCOMPATIBLE_TYPE_FILTER,
filterType.getFullQualifiedName().getFullQualifiedNameAsString());
}
}
}