| /* |
| * 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()); |
| } |
| } |
| } |