[OLINGO-834] ExpressionParser uses UriTokenizer Signed-off-by: Christian Amend <[email protected]>
Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/2f3bc286 Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/2f3bc286 Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/2f3bc286 Branch: refs/heads/OLINGO-834_Filter_Parser Commit: 2f3bc2866befb2f8f75a81ecb4952950dc3913df Parents: 208f26c Author: Klaus Straubinger <[email protected]> Authored: Fri Dec 11 16:48:38 2015 +0100 Committer: Christian Amend <[email protected]> Committed: Mon Dec 14 10:12:22 2015 +0100 ---------------------------------------------------------------------- .../core/uri/parser/ExpressionParser.java | 499 +++++++------------ .../olingo/server/core/uri/parser/Parser.java | 8 + .../server/core/uri/parser/ParserHelper.java | 65 +++ .../core/uri/parser/ResourcePathParser.java | 130 +++-- .../server/core/uri/parser/SelectParser.java | 15 +- .../server/core/uri/parser/UriTokenizer.java | 291 ++++++++++- .../uri/queryoption/expression/UnaryImpl.java | 2 +- .../core/uri/parser/ExpressionParserTest.java | 174 +++---- .../core/uri/parser/UriTokenizerTest.java | 313 +++++++----- 9 files changed, 841 insertions(+), 656 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/2f3bc286/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java index 854536d..3b04089 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ExpressionParser.java @@ -32,6 +32,7 @@ import org.apache.olingo.server.api.uri.queryoption.expression.Expression; 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.UnaryOperatorKind; +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.LiteralImpl; @@ -39,75 +40,73 @@ import org.apache.olingo.server.core.uri.queryoption.expression.MethodImpl; import org.apache.olingo.server.core.uri.queryoption.expression.UnaryImpl; public class ExpressionParser { - private Tokenizer tokenizer; - private static final Map<TokenKind, BinaryOperatorKind> tokenToBinaryOperator; static { - Map<TokenKind, BinaryOperatorKind> temp = new HashMap<ExpressionParser.TokenKind, BinaryOperatorKind>(); - temp.put(TokenKind.OR_OP, BinaryOperatorKind.OR); - temp.put(TokenKind.AND_OP, BinaryOperatorKind.AND); + Map<TokenKind, BinaryOperatorKind> temp = new HashMap<TokenKind, BinaryOperatorKind>(); + temp.put(TokenKind.OrOperator, BinaryOperatorKind.OR); + temp.put(TokenKind.AndOperator, BinaryOperatorKind.AND); - temp.put(TokenKind.EQ_OP, BinaryOperatorKind.EQ); - temp.put(TokenKind.NE_OP, BinaryOperatorKind.NE); + temp.put(TokenKind.EqualsOperator, BinaryOperatorKind.EQ); + temp.put(TokenKind.NotEqualsOperator, BinaryOperatorKind.NE); - temp.put(TokenKind.GT_OP, BinaryOperatorKind.GT); - temp.put(TokenKind.GE_OP, BinaryOperatorKind.GE); - temp.put(TokenKind.LT_OP, BinaryOperatorKind.LT); - temp.put(TokenKind.LE_OP, BinaryOperatorKind.LE); + 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.ADD_OP, BinaryOperatorKind.ADD); - temp.put(TokenKind.SUB_OP, BinaryOperatorKind.SUB); + temp.put(TokenKind.AddOperator, BinaryOperatorKind.ADD); + temp.put(TokenKind.SubOperator, BinaryOperatorKind.SUB); - temp.put(TokenKind.MUL_OP, BinaryOperatorKind.MUL); - temp.put(TokenKind.DIV_OP, BinaryOperatorKind.DIV); - temp.put(TokenKind.MOD_OP, BinaryOperatorKind.MOD); + temp.put(TokenKind.MulOperator, BinaryOperatorKind.MUL); + temp.put(TokenKind.DivOperator, BinaryOperatorKind.DIV); + temp.put(TokenKind.ModOperator, BinaryOperatorKind.MOD); tokenToBinaryOperator = Collections.unmodifiableMap(temp); } private static final Map<TokenKind, UnaryOperatorKind> tokenToUnaryOperator; static { - Map<TokenKind, UnaryOperatorKind> temp = new HashMap<ExpressionParser.TokenKind, UnaryOperatorKind>(); + Map<TokenKind, UnaryOperatorKind> temp = new HashMap<TokenKind, UnaryOperatorKind>(); temp.put(TokenKind.MINUS, UnaryOperatorKind.MINUS); - temp.put(TokenKind.NOT, UnaryOperatorKind.NOT); + temp.put(TokenKind.NotOperator, UnaryOperatorKind.NOT); tokenToUnaryOperator = Collections.unmodifiableMap(temp); } private static final Map<TokenKind, MethodKind> tokenToMethod; static { - Map<TokenKind, MethodKind> temp = new HashMap<ExpressionParser.TokenKind, MethodKind>(); - temp.put(TokenKind.Cast, MethodKind.CAST); - temp.put(TokenKind.Ceiling, MethodKind.CEILING); - temp.put(TokenKind.Concat, MethodKind.CONCAT); - temp.put(TokenKind.Contains, MethodKind.CONTAINS); - temp.put(TokenKind.Date, MethodKind.DATE); - temp.put(TokenKind.Day, MethodKind.DAY); - temp.put(TokenKind.Endswith, MethodKind.ENDSWITH); - temp.put(TokenKind.Floor, MethodKind.FLOOR); - temp.put(TokenKind.Fractionalseconds, MethodKind.FRACTIONALSECONDS); - temp.put(TokenKind.GeoDistance, MethodKind.GEODISTANCE); - temp.put(TokenKind.GeoIntersects, MethodKind.GEOINTERSECTS); - temp.put(TokenKind.GeoLength, MethodKind.GEOLENGTH); - temp.put(TokenKind.Hour, MethodKind.HOUR); - temp.put(TokenKind.Indexof, MethodKind.INDEXOF); - temp.put(TokenKind.Isof, MethodKind.ISOF); - temp.put(TokenKind.Length, MethodKind.LENGTH); - temp.put(TokenKind.Maxdatetime, MethodKind.MAXDATETIME); - temp.put(TokenKind.Mindatetime, MethodKind.MINDATETIME); - temp.put(TokenKind.Minute, MethodKind.MINUTE); - temp.put(TokenKind.Month, MethodKind.MONTH); - temp.put(TokenKind.Now, MethodKind.NOW); - temp.put(TokenKind.Round, MethodKind.ROUND); - temp.put(TokenKind.Second, MethodKind.SECOND); - temp.put(TokenKind.Startswith, MethodKind.STARTSWITH); - temp.put(TokenKind.Substring, MethodKind.SUBSTRING); - temp.put(TokenKind.Time, MethodKind.TIME); - temp.put(TokenKind.Tolower, MethodKind.TOLOWER); - temp.put(TokenKind.Totaloffsetminutes, MethodKind.TOTALOFFSETMINUTES); - temp.put(TokenKind.Totalseconds, MethodKind.TOTALSECONDS); - temp.put(TokenKind.Toupper, MethodKind.TOUPPER); - temp.put(TokenKind.Trim, MethodKind.TRIM); - temp.put(TokenKind.Year, MethodKind.YEAR); + Map<TokenKind, MethodKind> temp = new HashMap<TokenKind, MethodKind>(); + temp.put(TokenKind.CastMethod, MethodKind.CAST); + 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.IsofMethod, MethodKind.ISOF); + 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); tokenToMethod = Collections.unmodifiableMap(temp); } @@ -115,24 +114,26 @@ public class ExpressionParser { private static final Map<TokenKind, EdmPrimitiveTypeKind> tokenToPrimitiveType; static { /* Enum and null are not present in the map. These have to be handled differently */ - Map<TokenKind, EdmPrimitiveTypeKind> temp = new HashMap<ExpressionParser.TokenKind, EdmPrimitiveTypeKind>(); - temp.put(TokenKind.PrimitiveBooleanValue, EdmPrimitiveTypeKind.Boolean); - temp.put(TokenKind.PrimitiveStringValue, EdmPrimitiveTypeKind.String); + Map<TokenKind, EdmPrimitiveTypeKind> temp = new HashMap<TokenKind, EdmPrimitiveTypeKind>(); + temp.put(TokenKind.BooleanValue, EdmPrimitiveTypeKind.Boolean); + temp.put(TokenKind.StringValue, EdmPrimitiveTypeKind.String); // TODO:Check if int64 is correct here or if it has to be single instead - temp.put(TokenKind.PrimitiveIntegerValue, EdmPrimitiveTypeKind.Int64); - temp.put(TokenKind.PrimitiveGuidValue, EdmPrimitiveTypeKind.Guid); - temp.put(TokenKind.PrimitiveDateValue, EdmPrimitiveTypeKind.Date); - temp.put(TokenKind.PrimitiveDateTimeOffsetValue, EdmPrimitiveTypeKind.DateTimeOffset); - temp.put(TokenKind.PrimitiveTimeOfDayValue, EdmPrimitiveTypeKind.TimeOfDay); - temp.put(TokenKind.PrimitiveDecimalValue, EdmPrimitiveTypeKind.Decimal); - temp.put(TokenKind.PrimitiveDoubleValue, EdmPrimitiveTypeKind.Double); - temp.put(TokenKind.PrimitiveDurationValue, EdmPrimitiveTypeKind.Duration); - temp.put(TokenKind.PrimitiveBinaryValue, EdmPrimitiveTypeKind.Binary); + temp.put(TokenKind.IntegerValue, EdmPrimitiveTypeKind.Int64); + temp.put(TokenKind.GuidValue, EdmPrimitiveTypeKind.Guid); + temp.put(TokenKind.DateValue, EdmPrimitiveTypeKind.Date); + temp.put(TokenKind.DateTimeOffsetValue, EdmPrimitiveTypeKind.DateTimeOffset); + temp.put(TokenKind.TimeOfDayValue, EdmPrimitiveTypeKind.TimeOfDay); + temp.put(TokenKind.DecimalValue, EdmPrimitiveTypeKind.Decimal); + temp.put(TokenKind.DoubleValue, EdmPrimitiveTypeKind.Double); + temp.put(TokenKind.DurationValue, EdmPrimitiveTypeKind.Duration); + temp.put(TokenKind.BinaryValue, EdmPrimitiveTypeKind.Binary); tokenToPrimitiveType = Collections.unmodifiableMap(temp); } - public Expression parse(Tokenizer tokenizer) throws UriParserException { + private UriTokenizer tokenizer; + + public Expression parse(UriTokenizer tokenizer) throws UriParserException { // Initialize tokenizer. this.tokenizer = tokenizer; @@ -141,23 +142,17 @@ public class ExpressionParser { private Expression parseExpression() throws UriParserException { Expression left = parseAnd(); - - while (is(TokenKind.OR_OP) != null) { - tokenizer.getText(); - - Expression right = parseAnd(); + while (tokenizer.next(TokenKind.OrOperator)) { + final Expression right = parseAnd(); left = new BinaryImpl(left, BinaryOperatorKind.OR, right); } - return left; } private Expression parseAnd() throws UriParserException { Expression left = parseExprEquality(); - while (is(TokenKind.AND_OP) != null) { - tokenizer.getText(); - - Expression right = parseExprEquality(); + while (tokenizer.next(TokenKind.AndOperator)) { + final Expression right = parseExprEquality(); left = new BinaryImpl(left, BinaryOperatorKind.AND, right); } return left; @@ -165,110 +160,107 @@ public class ExpressionParser { private Expression parseExprEquality() throws UriParserException { Expression left = parseExprRel(); - - TokenKind nextTokenKind = is(TokenKind.EQ_OP, TokenKind.NE_OP); + TokenKind operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.EqualsOperator, TokenKind.NotEqualsOperator); // Null for everything other than EQ or NE - while (nextTokenKind != null) { - tokenizer.getText(); - - Expression right = parseExprEquality(); - left = new BinaryImpl(left, tokenToBinaryOperator.get(nextTokenKind), right); - nextTokenKind = is(TokenKind.EQ_OP, TokenKind.NE_OP); + while (operatorTokenKind != null) { + final Expression right = parseExprEquality(); + left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right); + operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.EqualsOperator, TokenKind.NotEqualsOperator); } - return left; } private Expression parseExprRel() throws UriParserException { Expression left = parseExprAdd(); - - TokenKind nextTokenKind = is(TokenKind.GT_OP, TokenKind.GE_OP, TokenKind.LT_OP, TokenKind.LE_OP); + 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 (nextTokenKind != null) { - tokenizer.getText(); - - Expression right = parseExprAdd(); - left = new BinaryImpl(left, tokenToBinaryOperator.get(nextTokenKind), right); - nextTokenKind = is(TokenKind.GT_OP, TokenKind.GE_OP, TokenKind.LT_OP, TokenKind.LE_OP); + while (operatorTokenKind != null) { + final Expression right = parseExprAdd(); + left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right); + operatorTokenKind = ParserHelper.next(tokenizer, + TokenKind.GreaterThanOperator, TokenKind.GreaterThanOrEqualsOperator, + TokenKind.LessThanOperator, TokenKind.LessThanOrEqualsOperator); } - return left; } private Expression parseExprAdd() throws UriParserException { Expression left = parseExprMul(); - - TokenKind nextTokenKind = is(TokenKind.ADD_OP, TokenKind.SUB_OP); + TokenKind operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.AddOperator, TokenKind.SubOperator); // Null for everything other than ADD or SUB - while (nextTokenKind != null) { - tokenizer.getText(); - - Expression right = parseExprMul(); - left = new BinaryImpl(left, tokenToBinaryOperator.get(nextTokenKind), right); - nextTokenKind = is(TokenKind.ADD_OP, TokenKind.SUB_OP); + while (operatorTokenKind != null) { + final Expression right = parseExprMul(); + left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right); + operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.AddOperator, TokenKind.SubOperator); } - return left; } private Expression parseExprMul() throws UriParserException { Expression left = parseExprUnary(); - - TokenKind nextTokenKind = is(TokenKind.MUL_OP, TokenKind.DIV_OP, TokenKind.MOD_OP); + TokenKind operatorTokenKind = ParserHelper.next(tokenizer, + TokenKind.MulOperator, TokenKind.DivOperator, TokenKind.ModOperator); // Null for everything other than MUL or DIV or MOD - while (nextTokenKind != null) { - tokenizer.getText(); - - Expression right = parseExprUnary(); - left = new BinaryImpl(left, tokenToBinaryOperator.get(nextTokenKind), right); - nextTokenKind = is(TokenKind.MUL_OP, TokenKind.DIV_OP, TokenKind.MOD_OP); + while (operatorTokenKind != null) { + final Expression right = parseExprUnary(); + left = new BinaryImpl(left, tokenToBinaryOperator.get(operatorTokenKind), right); + operatorTokenKind = ParserHelper.next(tokenizer, + TokenKind.MulOperator, TokenKind.DivOperator, TokenKind.ModOperator); } - return left; } private Expression parseExprUnary() throws UriParserException { Expression left = null; - TokenKind nextTokenKind = is(TokenKind.MINUS, TokenKind.NOT); + TokenKind operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.MINUS, TokenKind.NotOperator); // Null for everything other than - or NOT - while (nextTokenKind != null) { - tokenizer.getText(); - - Expression exp = parseExprValue(); - left = new UnaryImpl(tokenToUnaryOperator.get(nextTokenKind), exp); - nextTokenKind = is(TokenKind.MINUS, TokenKind.NOT); + while (operatorTokenKind != null) { + final Expression expression = parseExprValue(); + left = new UnaryImpl(tokenToUnaryOperator.get(operatorTokenKind), expression); + operatorTokenKind = ParserHelper.next(tokenizer, TokenKind.MINUS, TokenKind.NotOperator); } - if (left == null) { left = parseExprValue(); } - return left; } private Expression parseExprValue() throws UriParserException { - if (is(TokenKind.OPEN) != null) { - tokenizer.getText(); - Expression exp = parseExpression(); - require(TokenKind.CLOSE); - return exp; + if (tokenizer.next(TokenKind.OPEN)) { + final Expression expression = parseExpression(); + ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); + return expression; } - if (is(TokenKind.ParameterAlias) != null) { + if (tokenizer.next(TokenKind.ParameterAliasName)) { return new AliasImpl(tokenizer.getText()); } - if (is(TokenKind.RootExpr) != null) { - tokenizer.getText(); - // TODO: Consume $root Expression. + if (tokenizer.next(TokenKind.jsonArrayOrObject)) { + // TODO: Can the type be determined? + return new LiteralImpl(tokenizer.getText(), null); } - TokenKind nextPrimitive = isPrimitive(); + if (tokenizer.next(TokenKind.ROOT)) { + // TODO: Consume $root expression. + } + + if (tokenizer.next(TokenKind.IT)) { + // TODO: Consume $it expression. + } + + if (tokenizer.next(TokenKind.QualifiedName)) { + // TODO: Consume typecast or bound-function expression. + } + + TokenKind nextPrimitive = ParserHelper.nextPrimitive(tokenizer); if (nextPrimitive != null) { - EdmPrimitiveTypeKind primitiveTypeKind = tokenToPrimitiveType.get(nextPrimitive); + final EdmPrimitiveTypeKind primitiveTypeKind = tokenToPrimitiveType.get(nextPrimitive); EdmPrimitiveType type; if (primitiveTypeKind == null) { - if (nextPrimitive == TokenKind.PrimitiveEnumValue) { + if (nextPrimitive == TokenKind.EnumValue) { // TODO: Get enum type. type = null; } else { @@ -281,22 +273,16 @@ public class ExpressionParser { return new LiteralImpl(tokenizer.getText(), type); } - TokenKind nextMethod = isMethod(); + TokenKind nextMethod = nextMethod(); if (nextMethod != null) { MethodKind methodKind = tokenToMethod.get(nextMethod); List<Expression> parameters = new ArrayList<Expression>(); - // Consume Method name. - tokenizer.getText(); - if (is(TokenKind.CLOSE) != null) { - // Consume closing parenthesis. - tokenizer.getText(); - } else { - parameters.add(parseExpression()); - while (is(TokenKind.COMMA) != null) { - tokenizer.getText(); + // The method token text includes the opening parenthesis! + if (!tokenizer.next(TokenKind.CLOSE)) { + do { parameters.add(parseExpression()); - } - require(TokenKind.CLOSE); + } while (tokenizer.next(TokenKind.COMMA)); + ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); } MethodImpl methodImpl = new MethodImpl(methodKind, parameters); @@ -305,6 +291,10 @@ public class ExpressionParser { return methodImpl; } + if (tokenizer.next(TokenKind.ODataIdentifier)) { + // TODO: Consume property-path or lambda-variable expression. + } + throw new UriParserSyntaxException("Unexpected token", UriParserSyntaxException.MessageKeys.SYNTAX); } @@ -356,7 +346,7 @@ public class ExpressionParser { case NOW: case MAXDATETIME: case MINDATETIME: - if (size != 0) { + if (size > 0) { throw new UriParserSemanticException("The method '" + method.getMethod() + "' must have no parameters.", null); // TODO: message key } @@ -384,192 +374,39 @@ public class ExpressionParser { } } - private String require(TokenKind required) throws UriParserException { - if (is(required) == null) { - throw new UriParserSyntaxException("Required token: " + required, - UriParserSyntaxException.MessageKeys.SYNTAX); - } - return tokenizer.getText(); - } - - private TokenKind is(TokenKind... kind) { - for (int i = 0; i < kind.length; i++) { - if (tokenizer.next(kind[i])) { - return kind[i]; - } - } - return null; - } - - private TokenKind isMethod() { - return is(TokenKind.Cast, - TokenKind.Ceiling, - TokenKind.Concat, - TokenKind.Contains, - TokenKind.Date, - TokenKind.Day, - TokenKind.Endswith, - TokenKind.Floor, - TokenKind.Fractionalseconds, - TokenKind.GeoDistance, - TokenKind.GeoIntersects, - TokenKind.GeoLength, - TokenKind.Hour, - TokenKind.Indexof, - TokenKind.Isof, - TokenKind.Length, - TokenKind.Maxdatetime, - TokenKind.Mindatetime, - TokenKind.Minute, - TokenKind.Month, - TokenKind.Now, - TokenKind.Round, - TokenKind.Second, - TokenKind.Startswith, - TokenKind.Substring, - TokenKind.Time, - TokenKind.Tolower, - TokenKind.Totaloffsetminutes, - TokenKind.Totalseconds, - TokenKind.Toupper, - TokenKind.Trim, - TokenKind.Year); - } - - private TokenKind isPrimitive() { - return is(TokenKind.PrimitiveNullValue, - TokenKind.PrimitiveBooleanValue, - TokenKind.PrimitiveStringValue, - - // The order of the next seven expressions is important in order to avoid - // finding partly parsed tokens (counter-intuitive as it may be, even a GUID may start with digits ...). - TokenKind.PrimitiveDoubleValue, - TokenKind.PrimitiveDecimalValue, - TokenKind.PrimitiveGuidValue, - TokenKind.PrimitiveDateTimeOffsetValue, - TokenKind.PrimitiveDateValue, - TokenKind.PrimitiveTimeOfDayValue, - TokenKind.PrimitiveIntegerValue, - TokenKind.PrimitiveDurationValue, - TokenKind.PrimitiveBinaryValue, - TokenKind.PrimitiveEnumValue); - } - - public enum TokenKind { - // BINARY - OR_OP, - AND_OP, - - EQ_OP, - NE_OP, - - GT_OP, - GE_OP, - LT_OP, - LE_OP, - - ADD_OP, - SUB_OP, - - MUL_OP, - DIV_OP, - MOD_OP, - - MINUS, - NOT, - - // Grouping - OPEN, - CLOSE, - - // PrimitiveValues - PrimitiveNullValue, - PrimitiveBooleanValue, - - PrimitiveStringValue, - PrimitiveIntegerValue, - PrimitiveGuidValue, - PrimitiveDateValue, - PrimitiveDateTimeOffsetValue, - PrimitiveTimeOfDayValue, - PrimitiveDecimalValue, - PrimitiveDoubleValue, - PrimitiveDurationValue, - PrimitiveBinaryValue, - PrimitiveEnumValue, - - // ExpressionValues - ParameterAlias, - ArrayOrObject, - RootExpr, - IT, - - // BuiltInMethods - Cast, - Ceiling, - Concat, - Contains, - Date, - Day, - Endswith, - Floor, - Fractionalseconds, - GeoDistance, - GeoIntersects, - GeoLength, - Hour, - Indexof, - Isof, - Length, - Maxdatetime, - Mindatetime, - Minute, - Month, - Now, - Round, - Second, - Startswith, - Substring, - Time, - Tolower, - Totaloffsetminutes, - Totalseconds, - Toupper, - Trim, - Year, - COMMA - } - - public static class Token { - TokenKind kind; - String text; - - public Token(TokenKind kind, String text) { - this.kind = kind; - this.text = text; - } - } - - public static class Tokenizer { - private List<Token> tokens; - int counter = 0; - - public Tokenizer(List<Token> tokens) { - this.tokens = tokens; - } - - public boolean next(TokenKind expectedKind) { - if (counter < tokens.size() && expectedKind == tokens.get(counter).kind) { - return true; - } - return false; - } - - public String getText() { - String text = tokens.get(counter).text; - counter++; - return text; - } + private TokenKind nextMethod() { + return ParserHelper.next(tokenizer, + TokenKind.CastMethod, + TokenKind.CeilingMethod, + TokenKind.ConcatMethod, + TokenKind.ContainsMethod, + TokenKind.DateMethod, + TokenKind.DayMethod, + TokenKind.EndswithMethod, + TokenKind.FloorMethod, + TokenKind.FractionalsecondsMethod, + TokenKind.GeoDistanceMethod, + TokenKind.GeoIntersectsMethod, + TokenKind.GeoLengthMethod, + TokenKind.HourMethod, + TokenKind.IndexofMethod, + TokenKind.IsofMethod, + TokenKind.LengthMethod, + TokenKind.MaxdatetimeMethod, + TokenKind.MindatetimeMethod, + TokenKind.MinuteMethod, + TokenKind.MonthMethod, + TokenKind.NowMethod, + TokenKind.RoundMethod, + TokenKind.SecondMethod, + TokenKind.StartswithMethod, + TokenKind.SubstringMethod, + TokenKind.TimeMethod, + TokenKind.TolowerMethod, + TokenKind.TotaloffsetminutesMethod, + TokenKind.TotalsecondsMethod, + TokenKind.ToupperMethod, + TokenKind.TrimMethod, + TokenKind.YearMethod); } - } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/2f3bc286/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java index 0b53e69..2a994d3 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/Parser.java @@ -208,6 +208,14 @@ public class Parser { (UriParserException) e.getCause() : new UriParserSyntaxException("Syntax error", e, UriParserSyntaxException.MessageKeys.SYNTAX); } +// UriTokenizer filterTokenizer = new UriTokenizer(optionValue); +// systemOption = new FilterOptionImpl().setExpression( +// new ExpressionParser().parse(filterTokenizer)); +// if (!filterTokenizer.next(TokenKind.EOF)) { +// throw new UriParserSyntaxException("Illegal value of $filter option!", +// UriParserSyntaxException.MessageKeys.WRONG_VALUE_FOR_SYSTEM_QUERY_OPTION, +// optionName, optionValue); +// } } else if (optionName.equals(SystemQueryOptionKind.FORMAT.toString())) { FormatOptionImpl formatOption = new FormatOptionImpl(); http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/2f3bc286/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ParserHelper.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ParserHelper.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ParserHelper.java new file mode 100644 index 0000000..e811575 --- /dev/null +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ParserHelper.java @@ -0,0 +1,65 @@ +/* + * 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 org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind; + +public class ParserHelper { + + public static void requireNext(UriTokenizer tokenizer, final TokenKind required) throws UriParserException { + if (!tokenizer.next(required)) { + throw new UriParserSyntaxException("Expected token '" + required.toString() + "' not found.", + UriParserSyntaxException.MessageKeys.SYNTAX); + } + } + + public static void requireTokenEnd(UriTokenizer tokenizer) throws UriParserException { + requireNext(tokenizer, TokenKind.EOF); + } + + public static TokenKind next(UriTokenizer tokenizer, final TokenKind... kind) { + for (int i = 0; i < kind.length; i++) { + if (tokenizer.next(kind[i])) { + return kind[i]; + } + } + return null; + } + + public static TokenKind nextPrimitive(UriTokenizer tokenizer) { + return next(tokenizer, + TokenKind.NULL, + TokenKind.BooleanValue, + TokenKind.StringValue, + + // The order of the next seven expressions is important in order to avoid + // finding partly parsed tokens (counter-intuitive as it may be, even a GUID may start with digits ...). + TokenKind.DoubleValue, + TokenKind.DecimalValue, + TokenKind.GuidValue, + TokenKind.DateTimeOffsetValue, + TokenKind.DateValue, + TokenKind.TimeOfDayValue, + TokenKind.IntegerValue, + + TokenKind.DurationValue, + TokenKind.BinaryValue, + TokenKind.EnumValue); + } +} http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/2f3bc286/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java index 9852011..5d2fbde 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/ResourcePathParser.java @@ -115,9 +115,9 @@ public class ResourcePathParser { public UriInfoImpl parseDollarEntityTypeCast(final String pathSegment) throws UriParserException { UriInfoImpl uriInfo = new UriInfoImpl().setKind(UriInfoKind.entityId); tokenizer = new UriTokenizer(pathSegment); - requireNext(TokenKind.QualifiedName); + ParserHelper.requireNext(tokenizer, TokenKind.QualifiedName); final String name = tokenizer.getText(); - requireTokenEnd(); + ParserHelper.requireTokenEnd(tokenizer); final EdmEntityType type = edm.getEntityType(new FullQualifiedName(name)); if (type == null) { throw new UriParserSemanticException("Type '" + name + "' not found.", @@ -131,11 +131,11 @@ public class ResourcePathParser { public UriInfoImpl parseCrossjoinSegment(final String pathSegment) throws UriParserException { UriInfoImpl uriInfo = new UriInfoImpl().setKind(UriInfoKind.crossjoin); tokenizer = new UriTokenizer(pathSegment); - requireNext(TokenKind.CROSSJOIN); - requireNext(TokenKind.OPEN); + ParserHelper.requireNext(tokenizer, TokenKind.CROSSJOIN); + ParserHelper.requireNext(tokenizer, TokenKind.OPEN); // At least one entity-set name is mandatory. Try to fetch all. do { - requireNext(TokenKind.ODataIdentifier); + ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier); final String name = tokenizer.getText(); final EdmEntitySet edmEntitySet = edmEntityContainer.getEntitySet(name); if (edmEntitySet == null) { @@ -145,13 +145,13 @@ public class ResourcePathParser { uriInfo.addEntitySetName(name); } } while (tokenizer.next(TokenKind.COMMA)); - requireNext(TokenKind.CLOSE); - requireTokenEnd(); + ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); + ParserHelper.requireTokenEnd(tokenizer); return uriInfo; } private UriResource ref(final UriResource previous) throws UriParserException { - requireTokenEnd(); + ParserHelper.requireTokenEnd(tokenizer); requireTyped(previous, "$ref"); if (((UriResourcePartTyped) previous).getType() instanceof EdmEntityType) { return new UriResourceRefImpl(); @@ -162,7 +162,7 @@ public class ResourcePathParser { } private UriResource value(final UriResource previous) throws UriParserException { - requireTokenEnd(); + ParserHelper.requireTokenEnd(tokenizer); requireTyped(previous, "$value"); if (!((UriResourcePartTyped) previous).isCollection()) { return new UriResourceValueImpl(); @@ -173,7 +173,7 @@ public class ResourcePathParser { } private UriResource count(final UriResource previous) throws UriParserException { - requireTokenEnd(); + ParserHelper.requireTokenEnd(tokenizer); requireTyped(previous, "$count"); if (((UriResourcePartTyped) previous).isCollection()) { return new UriResourceCountImpl(); @@ -195,19 +195,19 @@ public class ResourcePathParser { entitySetResource.setKeyPredicates(keyPredicates); } - requireTokenEnd(); + ParserHelper.requireTokenEnd(tokenizer); return entitySetResource; } final EdmSingleton edmSingleton = edmEntityContainer.getSingleton(oDataIdentifier); if (edmSingleton != null) { - requireTokenEnd(); + ParserHelper.requireTokenEnd(tokenizer); return new UriResourceSingletonImpl().setSingleton(edmSingleton); } final EdmActionImport edmActionImport = edmEntityContainer.getActionImport(oDataIdentifier); if (edmActionImport != null) { - requireTokenEnd(); + ParserHelper.requireTokenEnd(tokenizer); return new UriResourceActionImpl().setActionImport(edmActionImport); } @@ -271,7 +271,7 @@ public class ResourcePathParser { UriParserSemanticException.MessageKeys.KEY_NOT_ALLOWED); } } - requireTokenEnd(); + ParserHelper.requireTokenEnd(tokenizer); return new UriResourceNavigationPropertyImpl() .setNavigationProperty(navigationProperty) .setKeyPredicates(keyPredicate); @@ -288,7 +288,7 @@ public class ResourcePathParser { previousType.getFullQualifiedName(), previousTyped.isCollection()); if (boundAction != null) { - requireTokenEnd(); + ParserHelper.requireTokenEnd(tokenizer); return new UriResourceActionImpl().setAction(boundAction); } EdmStructuredType type = edm.getEntityType(name); @@ -386,7 +386,7 @@ public class ResourcePathParser { edmProperty == null ? null : (EdmPrimitiveType) edmProperty.getType(), edmProperty == null ? false : edmProperty.isNullable())) { final String literalValue = tokenizer.getText(); - requireNext(TokenKind.CLOSE); + ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); return createUriParameter(edmProperty, edmKeyPropertyRef.getName(), literalValue); } else { throw new UriParserSemanticException("The key value is not valid.", @@ -424,10 +424,10 @@ public class ResourcePathParser { parameterNames.add(keyPredicateName); hasComma = tokenizer.next(TokenKind.COMMA); if (hasComma) { - requireNext(TokenKind.ODataIdentifier); + ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier); } } while (hasComma); - requireNext(TokenKind.CLOSE); + ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); return parameters; } @@ -440,7 +440,7 @@ public class ResourcePathParser { throw new UriValidationException(keyPredicateName + " is not a valid key property name.", UriValidationException.MessageKeys.INVALID_KEY_PROPERTY, keyPredicateName); } - requireNext(TokenKind.EQ); + ParserHelper.requireNext(tokenizer, TokenKind.EQ); if (tokenizer.next(TokenKind.COMMA) || tokenizer.next(TokenKind.CLOSE) || tokenizer.next(TokenKind.EOF)) { throw new UriParserSyntaxException("Key value expected.", UriParserSyntaxException.MessageKeys.SYNTAX); } @@ -512,7 +512,7 @@ public class ResourcePathParser { } ((UriResourceTypedImpl) previousTyped).setTypeFilter(type); } - requireTokenEnd(); + ParserHelper.requireTokenEnd(tokenizer); return null; } else { throw new UriParserSemanticException( @@ -572,53 +572,42 @@ public class ResourcePathParser { UriParserSemanticException.MessageKeys.KEY_NOT_ALLOWED); } } - requireTokenEnd(); + ParserHelper.requireTokenEnd(tokenizer); return resource; } private List<UriParameter> functionParameters() throws UriParserException { List<UriParameter> parameters = new ArrayList<UriParameter>(); - requireNext(TokenKind.OPEN); + ParserHelper.requireNext(tokenizer, TokenKind.OPEN); if (tokenizer.next(TokenKind.CLOSE)) { return parameters; } do { - requireNext(TokenKind.ODataIdentifier); + ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier); final String name = tokenizer.getText(); if (parameters.contains(name)) { throw new UriParserSemanticException("Duplicated function parameter " + name, UriParserSemanticException.MessageKeys.INVALID_KEY_VALUE, name); } - requireNext(TokenKind.EQ); + ParserHelper.requireNext(tokenizer, TokenKind.EQ); if (tokenizer.next(TokenKind.COMMA) || tokenizer.next(TokenKind.CLOSE) || tokenizer.next(TokenKind.EOF)) { throw new UriParserSyntaxException("Parameter value expected.", UriParserSyntaxException.MessageKeys.SYNTAX); } - if (nextPrimitiveValue()) { + if (tokenizer.next(TokenKind.ParameterAliasName)) { + parameters.add(new UriParameterImpl().setName(name).setAlias(tokenizer.getText())); + } else if (nextPrimitiveValue()) { final String literalValue = tokenizer.getText(); - UriParameterImpl parameter = new UriParameterImpl().setName(name); - parameters.add(literalValue.startsWith("@") ? - parameter.setAlias(literalValue) : - parameter.setText("null".equals(literalValue) ? null : literalValue)); + parameters.add(new UriParameterImpl().setName(name) + .setText("null".equals(literalValue) ? null : literalValue)); } else { throw new UriParserSemanticException("Wrong parameter value.", UriParserSemanticException.MessageKeys.INVALID_KEY_VALUE, ""); } } while (tokenizer.next(TokenKind.COMMA)); - requireNext(TokenKind.CLOSE); + ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); return parameters; } - private void requireNext(final TokenKind kind) throws UriParserException { - if (!tokenizer.next(kind)) { - throw new UriParserSyntaxException("Expected token '" + kind.toString() + "' not found.", - UriParserSyntaxException.MessageKeys.SYNTAX); - } - } - - private void requireTokenEnd() throws UriParserException { - requireNext(TokenKind.EOF); - } - private boolean nextPrimitiveTypeValue(final EdmPrimitiveType primitiveType, final boolean nullable) { final EdmPrimitiveType type = primitiveType instanceof EdmTypeDefinition ? ((EdmTypeDefinition) primitiveType).getUnderlyingType() : @@ -629,64 +618,63 @@ public class ResourcePathParser { return true; } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Boolean).equals(type)) { - return tokenizer.next(TokenKind.PrimitiveBooleanValue); + return tokenizer.next(TokenKind.BooleanValue); } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.String).equals(type)) { - return tokenizer.next(TokenKind.PrimitiveStringValue); + return tokenizer.next(TokenKind.StringValue); } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.SByte).equals(type) || odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Byte).equals(type) || odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Int16).equals(type) || odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Int32).equals(type) || odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Int64).equals(type)) { - return tokenizer.next(TokenKind.PrimitiveIntegerValue); + return tokenizer.next(TokenKind.IntegerValue); } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Guid).equals(type)) { - return tokenizer.next(TokenKind.PrimitiveGuidValue); + return tokenizer.next(TokenKind.GuidValue); } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Date).equals(type)) { - return tokenizer.next(TokenKind.PrimitiveDateValue); + return tokenizer.next(TokenKind.DateValue); } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.DateTimeOffset).equals(type)) { - return tokenizer.next(TokenKind.PrimitiveDateTimeOffsetValue); + return tokenizer.next(TokenKind.DateTimeOffsetValue); } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.TimeOfDay).equals(type)) { - return tokenizer.next(TokenKind.PrimitiveTimeOfDayValue); + return tokenizer.next(TokenKind.TimeOfDayValue); } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Decimal).equals(type)) { // The order is important. // A decimal value should not be parsed as integer and let the tokenizer stop at the decimal point. - return tokenizer.next(TokenKind.PrimitiveDecimalValue) - || tokenizer.next(TokenKind.PrimitiveIntegerValue); + return tokenizer.next(TokenKind.DecimalValue) + || tokenizer.next(TokenKind.IntegerValue); } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Double).equals(type)) { // The order is important. // A floating-point value should not be parsed as decimal and let the tokenizer stop at 'E'. // A decimal value should not be parsed as integer and let the tokenizer stop at the decimal point. - return tokenizer.next(TokenKind.PrimitiveDoubleValue) - || tokenizer.next(TokenKind.PrimitiveDecimalValue) - || tokenizer.next(TokenKind.PrimitiveIntegerValue); + return tokenizer.next(TokenKind.DoubleValue) + || tokenizer.next(TokenKind.DecimalValue) + || tokenizer.next(TokenKind.IntegerValue); } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Duration).equals(type)) { - return tokenizer.next(TokenKind.PrimitiveDurationValue); + return tokenizer.next(TokenKind.DurationValue); } else if (odata.createPrimitiveTypeInstance(EdmPrimitiveTypeKind.Binary).equals(type)) { - return tokenizer.next(TokenKind.PrimitiveBinaryValue); + return tokenizer.next(TokenKind.BinaryValue); } else if (type.getKind() == EdmTypeKind.ENUM) { - return tokenizer.next(TokenKind.PrimitiveEnumValue); + return tokenizer.next(TokenKind.EnumValue); } else { return false; } } private boolean nextPrimitiveValue() { - return tokenizer.next(TokenKind.ParameterAliasName) - || tokenizer.next(TokenKind.NULL) - || tokenizer.next(TokenKind.PrimitiveBooleanValue) - || tokenizer.next(TokenKind.PrimitiveStringValue) + return tokenizer.next(TokenKind.NULL) + || tokenizer.next(TokenKind.BooleanValue) + || tokenizer.next(TokenKind.StringValue) // The order of the next seven expressions is important in order to avoid // finding partly parsed tokens (counter-intuitive as it may be, even a GUID may start with digits ...). - || tokenizer.next(TokenKind.PrimitiveDoubleValue) - || tokenizer.next(TokenKind.PrimitiveDecimalValue) - || tokenizer.next(TokenKind.PrimitiveGuidValue) - || tokenizer.next(TokenKind.PrimitiveDateTimeOffsetValue) - || tokenizer.next(TokenKind.PrimitiveDateValue) - || tokenizer.next(TokenKind.PrimitiveTimeOfDayValue) - || tokenizer.next(TokenKind.PrimitiveIntegerValue) - - || tokenizer.next(TokenKind.PrimitiveDurationValue) - || tokenizer.next(TokenKind.PrimitiveBinaryValue) - || tokenizer.next(TokenKind.PrimitiveEnumValue); + || tokenizer.next(TokenKind.DoubleValue) + || tokenizer.next(TokenKind.DecimalValue) + || tokenizer.next(TokenKind.GuidValue) + || tokenizer.next(TokenKind.DateTimeOffsetValue) + || tokenizer.next(TokenKind.DateValue) + || tokenizer.next(TokenKind.TimeOfDayValue) + || tokenizer.next(TokenKind.IntegerValue) + + || tokenizer.next(TokenKind.DurationValue) + || tokenizer.next(TokenKind.BinaryValue) + || tokenizer.next(TokenKind.EnumValue); } } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/2f3bc286/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/SelectParser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/SelectParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/SelectParser.java index 3d933d2..b257e68 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/SelectParser.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/SelectParser.java @@ -92,7 +92,7 @@ public class SelectParser { if (type.compatibleTo(referencedType)) { item.setTypeFilter(type); if (tokenizer.next(TokenKind.SLASH)) { - requireNext(tokenizer, TokenKind.ODataIdentifier); + ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier); UriInfoImpl resource = new UriInfoImpl().setKind(UriInfoKind.resource); addSelectPath(tokenizer, type, resource); item.setResourcePath(resource); @@ -105,7 +105,7 @@ public class SelectParser { } } else { - requireNext(tokenizer, TokenKind.ODataIdentifier); + ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier); // The namespace or its alias could be a single OData identifier. final FullQualifiedName allOperationsInSchema = parseAllOperationsInSchema(tokenizer); if (allOperationsInSchema != null) { @@ -167,10 +167,10 @@ public class SelectParser { List<String> names = new ArrayList<String>(); if (tokenizer.next(TokenKind.OPEN)) { do { - requireNext(tokenizer, TokenKind.ODataIdentifier); + ParserHelper.requireNext(tokenizer, TokenKind.ODataIdentifier); names.add(tokenizer.getText()); } while (tokenizer.next(TokenKind.COMMA)); - requireNext(tokenizer, TokenKind.CLOSE); + ParserHelper.requireNext(tokenizer, TokenKind.CLOSE); } return names; } @@ -231,11 +231,4 @@ public class SelectParser { } } } - - private void requireNext(UriTokenizer tokenizer, final TokenKind kind) throws UriParserSyntaxException { - if (!tokenizer.next(kind)) { - throw new UriParserSyntaxException("Illegal $select expression.", - UriParserSyntaxException.MessageKeys.SYNTAX); - } - } } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/2f3bc286/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java index 8051573..a40f4ec 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/parser/UriTokenizer.java @@ -24,6 +24,8 @@ package org.apache.olingo.server.core.uri.parser; * Since only the index is "moved", backing out while parsing a token is easy and used throughout. * There is intentionally no method to push back tokens (although it would be easy to add such a method) * because this tokenizer should behave like a classical token-consuming tokenizer.</p> + * <p>Whitespace is not an extra token but consumed with the tokens that require whitespace. + * Optional whitespace is not supported.</p> */ public class UriTokenizer { @@ -35,6 +37,9 @@ public class UriTokenizer { VALUE, COUNT, CROSSJOIN, + ROOT, + IT, + OPEN, CLOSE, COMMA, @@ -44,6 +49,7 @@ public class UriTokenizer { EQ, STAR, PLUS, + MINUS, NULL, // variable-value tokens (convention: mixed case) @@ -51,20 +57,68 @@ public class UriTokenizer { QualifiedName, ParameterAliasName, - PrimitiveBooleanValue, - PrimitiveStringValue, - PrimitiveIntegerValue, - PrimitiveGuidValue, - PrimitiveDateValue, - PrimitiveDateTimeOffsetValue, - PrimitiveTimeOfDayValue, - PrimitiveDecimalValue, - PrimitiveDoubleValue, - PrimitiveDurationValue, - PrimitiveBinaryValue, - PrimitiveEnumValue, - - jsonArrayOrObject + BooleanValue, + StringValue, + IntegerValue, + GuidValue, + DateValue, + DateTimeOffsetValue, + TimeOfDayValue, + DecimalValue, + DoubleValue, + DurationValue, + BinaryValue, + EnumValue, + + jsonArrayOrObject, + + OrOperator, + AndOperator, + EqualsOperator, + NotEqualsOperator, + GreaterThanOperator, + GreaterThanOrEqualsOperator, + LessThanOperator, + LessThanOrEqualsOperator, + AddOperator, + SubOperator, + MulOperator, + DivOperator, + ModOperator, + NotOperator, + + CastMethod, + CeilingMethod, + ConcatMethod, + ContainsMethod, + DateMethod, + DayMethod, + EndswithMethod, + FloorMethod, + FractionalsecondsMethod, + GeoDistanceMethod, + GeoIntersectsMethod, + GeoLengthMethod, + HourMethod, + IndexofMethod, + IsofMethod, + LengthMethod, + MaxdatetimeMethod, + MindatetimeMethod, + MinuteMethod, + MonthMethod, + NowMethod, + RoundMethod, + SecondMethod, + StartswithMethod, + SubstringMethod, + TimeMethod, + TolowerMethod, + TotaloffsetminutesMethod, + TotalsecondsMethod, + ToupperMethod, + TrimMethod, + YearMethod } private final String parseString; @@ -111,6 +165,13 @@ public class UriTokenizer { case CROSSJOIN: found = nextConstant("$crossjoin"); break; + case ROOT: + found = nextConstant("$root"); + break; + case IT: + found = nextConstant("$it"); + break; + case OPEN: found = nextCharacter('('); break; @@ -138,6 +199,9 @@ public class UriTokenizer { case PLUS: found = nextCharacter('+'); break; + case MINUS: + found = nextCharacter('-'); + break; case NULL: found = nextConstant("null"); break; @@ -157,47 +221,189 @@ public class UriTokenizer { break; // Primitive Values - case PrimitiveBooleanValue: + case BooleanValue: found = nextBooleanValue(); break; - case PrimitiveStringValue: + case StringValue: found = nextStringValue(); break; - case PrimitiveIntegerValue: + case IntegerValue: found = nextIntegerValue(true); break; - case PrimitiveGuidValue: + case GuidValue: found = nextGuidValue(); break; - case PrimitiveDateValue: + case DateValue: found = nextDateValue(); break; - case PrimitiveDateTimeOffsetValue: + case DateTimeOffsetValue: found = nextDateTimeOffsetValue(); break; - case PrimitiveTimeOfDayValue: + case TimeOfDayValue: found = nextTimeOfDayValue(); break; - case PrimitiveDecimalValue: + case DecimalValue: found = nextDecimalValue(); break; - case PrimitiveDoubleValue: + case DoubleValue: found = nextDoubleValue(); break; - case PrimitiveDurationValue: + case DurationValue: found = nextDurationValue(); break; - case PrimitiveBinaryValue: + case BinaryValue: found = nextBinaryValue(); break; - case PrimitiveEnumValue: + case EnumValue: found = nextEnumValue(); break; - // Primitive Values + // Complex or Collection Value case jsonArrayOrObject: found = nextJsonArrayOrObject(); break; + + // Operators + case OrOperator: + found = nextBinaryOperator("or"); + break; + case AndOperator: + found = nextBinaryOperator("and"); + break; + case EqualsOperator: + found = nextBinaryOperator("eq"); + break; + case NotEqualsOperator: + found = nextBinaryOperator("ne"); + break; + case GreaterThanOperator: + found = nextBinaryOperator("gt"); + break; + case GreaterThanOrEqualsOperator: + found = nextBinaryOperator("ge"); + break; + case LessThanOperator: + found = nextBinaryOperator("lt"); + break; + case LessThanOrEqualsOperator: + found = nextBinaryOperator("le"); + break; + case AddOperator: + found = nextBinaryOperator("add"); + break; + case SubOperator: + found = nextBinaryOperator("sub"); + break; + case MulOperator: + found = nextBinaryOperator("mul"); + break; + case DivOperator: + found = nextBinaryOperator("div"); + break; + case ModOperator: + found = nextBinaryOperator("mod"); + break; + case NotOperator: + found = nextConstant("not") && nextWhitespace(); + break; + + // Methods + case CastMethod: + found = nextMethod("cast"); + break; + case CeilingMethod: + found = nextMethod("ceiling"); + break; + case ConcatMethod: + found = nextMethod("concat"); + break; + case ContainsMethod: + found = nextMethod("contains"); + break; + case DateMethod: + found = nextMethod("date"); + break; + case DayMethod: + found = nextMethod("day"); + break; + case EndswithMethod: + found = nextMethod("endswith"); + break; + case FloorMethod: + found = nextMethod("floor"); + break; + case FractionalsecondsMethod: + found = nextMethod("fractionalseconds"); + break; + case GeoDistanceMethod: + found = nextMethod("geo.distance"); + break; + case GeoIntersectsMethod: + found = nextMethod("geo.intersects"); + break; + case GeoLengthMethod: + found = nextMethod("geo.length"); + break; + case HourMethod: + found = nextMethod("hour"); + break; + case IndexofMethod: + found = nextMethod("indexof"); + break; + case IsofMethod: + found = nextMethod("isof"); + break; + case LengthMethod: + found = nextMethod("length"); + break; + case MaxdatetimeMethod: + found = nextMethod("maxdatetime"); + break; + case MindatetimeMethod: + found = nextMethod("mindatetime"); + break; + case MinuteMethod: + found = nextMethod("minute"); + break; + case MonthMethod: + found = nextMethod("month"); + break; + case NowMethod: + found = nextMethod("now"); + break; + case RoundMethod: + found = nextMethod("round"); + break; + case SecondMethod: + found = nextMethod("second"); + break; + case StartswithMethod: + found = nextMethod("startswith"); + break; + case SubstringMethod: + found = nextMethod("substring"); + break; + case TimeMethod: + found = nextMethod("time"); + break; + case TolowerMethod: + found = nextMethod("tolower"); + break; + case TotaloffsetminutesMethod: + found = nextMethod("totaloffsetminutes"); + break; + case TotalsecondsMethod: + found = nextMethod("totalseconds"); + break; + case ToupperMethod: + found = nextMethod("toupper"); + break; + case TrimMethod: + found = nextMethod("trim"); + break; + case YearMethod: + found = nextMethod("year"); + break; } if (found) { @@ -301,6 +507,37 @@ public class UriTokenizer { } /** + * Moves past whitespace (space or horizontal tabulator) characters if found; + * otherwise leaves the index unchanged. + * @return whether whitespace characters have been found at the current index + */ + private boolean nextWhitespace() { + int count = 0; + while (nextCharacter(' ') || nextCharacter('\t')) { + count++; + } + return count > 0; + } + + /** + * Moves past the given whitespace-surrounded operator constant if found; + * otherwise leaves the index unchanged. + * @return whether the operator has been found at the current index + */ + private boolean nextBinaryOperator(final String operator) { + return nextWhitespace() && nextConstant(operator) && nextWhitespace(); + } + + /** + * Moves past the given method name and its immediately following opening parenthesis if found; + * otherwise leaves the index unchanged. + * @return whether the method has been found at the current index + */ + private boolean nextMethod(final String methodName) { + return nextConstant(methodName) && nextCharacter('('); + } + + /** * Moves past an OData identifier if found; otherwise leaves the index unchanged. * @return whether an OData identifier has been found at the current index */ http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/2f3bc286/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/UnaryImpl.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/UnaryImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/UnaryImpl.java index 2438d27..910997e 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/UnaryImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/UnaryImpl.java @@ -53,6 +53,6 @@ public class UnaryImpl implements Unary { @Override public String toString() { - return "{" + operator + " " + expression + '}'; + return "{" + operator.name() + " " + expression + '}'; } } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/2f3bc286/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java index 7da823e..58f2a1f 100644 --- a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java +++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/ExpressionParserTest.java @@ -20,191 +20,157 @@ package org.apache.olingo.server.core.uri.parser; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; -import java.util.ArrayList; +import java.util.Locale; import org.apache.olingo.server.api.uri.queryoption.expression.Expression; -import org.apache.olingo.server.core.uri.parser.ExpressionParser.Token; -import org.apache.olingo.server.core.uri.parser.ExpressionParser.TokenKind; -import org.apache.olingo.server.core.uri.parser.ExpressionParser.Tokenizer; +import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind; import org.junit.Test; public class ExpressionParserTest { @Test public void equality() throws Exception { - Expression expression = parseExpression(TokenKind.EQ_OP); + Expression expression = parseExpression("5 eq 5"); assertEquals("{5 EQ 5}", expression.toString()); - expression = parseExpression(TokenKind.NE_OP); + expression = parseExpression("5 ne 5"); assertEquals("{5 NE 5}", expression.toString()); } @Test public void relational() throws Exception { - Expression expression = parseExpression(TokenKind.GT_OP); + Expression expression = parseExpression("5 gt 5"); assertEquals("{5 GT 5}", expression.toString()); - expression = parseExpression(TokenKind.GE_OP); + expression = parseExpression("5 ge 5"); assertEquals("{5 GE 5}", expression.toString()); - expression = parseExpression(TokenKind.LT_OP); + expression = parseExpression("5 lt 5"); assertEquals("{5 LT 5}", expression.toString()); - expression = parseExpression(TokenKind.LE_OP); + expression = parseExpression("5 le 5"); assertEquals("{5 LE 5}", expression.toString()); } @Test public void additive() throws Exception { - Expression expression = parseExpression(TokenKind.ADD_OP); + Expression expression = parseExpression("5 add 5"); assertEquals("{5 ADD 5}", expression.toString()); - expression = parseExpression(TokenKind.SUB_OP); + expression = parseExpression("5 sub 5"); assertEquals("{5 SUB 5}", expression.toString()); } @Test public void multiplicative() throws Exception { - Expression expression = parseExpression(TokenKind.MUL_OP); + Expression expression = parseExpression("5 mul 5"); assertEquals("{5 MUL 5}", expression.toString()); - expression = parseExpression(TokenKind.DIV_OP); + expression = parseExpression("5 div 5"); assertEquals("{5 DIV 5}", expression.toString()); - expression = parseExpression(TokenKind.MOD_OP); + expression = parseExpression("5 mod 5"); assertEquals("{5 MOD 5}", expression.toString()); } @Test public void unary() throws Exception { - ArrayList<Token> tokens = new ArrayList<Token>(); - tokens.add(new Token(TokenKind.MINUS, "")); - tokens.add(new Token(TokenKind.PrimitiveIntegerValue, "5")); - Tokenizer tokenizer = new Tokenizer(tokens); - Expression expression = new ExpressionParser().parse(tokenizer); - assertEquals("{- 5}", expression.toString()); - - tokens = new ArrayList<Token>(); - tokens.add(new Token(TokenKind.NOT, "")); - tokens.add(new Token(TokenKind.PrimitiveIntegerValue, "5")); - tokenizer = new Tokenizer(tokens); - expression = new ExpressionParser().parse(tokenizer); - assertEquals("{not 5}", expression.toString()); + Expression expression = parseExpression("-5"); + assertEquals("{MINUS 5}", expression.toString()); + + assertEquals("{MINUS -1}", parseExpression("--1").toString()); + + expression = parseExpression("not 5"); + assertEquals("{NOT 5}", expression.toString()); } @Test public void grouping() throws Exception { - ArrayList<Token> tokens = new ArrayList<Token>(); - tokens.add(new Token(TokenKind.MINUS, "")); - tokens.add(new Token(TokenKind.PrimitiveIntegerValue, "5")); - tokens.add(new Token(TokenKind.ADD_OP, "")); - tokens.add(new Token(TokenKind.PrimitiveIntegerValue, "5")); - Tokenizer tokenizer = new Tokenizer(tokens); - Expression expression = new ExpressionParser().parse(tokenizer); - assertEquals("{{- 5} ADD 5}", expression.toString()); - - tokens = new ArrayList<Token>(); - tokens.add(new Token(TokenKind.MINUS, "")); - tokens.add(new Token(TokenKind.OPEN, "")); - tokens.add(new Token(TokenKind.PrimitiveIntegerValue, "5")); - tokens.add(new Token(TokenKind.ADD_OP, "")); - tokens.add(new Token(TokenKind.PrimitiveIntegerValue, "5")); - tokens.add(new Token(TokenKind.CLOSE, "")); - tokenizer = new Tokenizer(tokens); - expression = new ExpressionParser().parse(tokenizer); - assertEquals("{- {5 ADD 5}}", expression.toString()); + Expression expression = parseExpression("-5 add 5"); + assertEquals("{{MINUS 5} ADD 5}", expression.toString()); + + expression = parseExpression("-(5 add 5)"); + assertEquals("{MINUS {5 ADD 5}}", expression.toString()); + } + + @Test + public void precedence() throws Exception { + assertEquals("{{MINUS 1} ADD {2 DIV 3}}", parseExpression("-1 add 2 div 3").toString()); + assertEquals("{true OR {{NOT false} AND true}}", parseExpression("true or not false and true").toString()); } @Test public void noParameterMethods() throws Exception { - Expression expression = parseMethod(TokenKind.Now); + Expression expression = parseMethod(TokenKind.NowMethod); assertEquals("{now []}", expression.toString()); - expression = parseMethod(TokenKind.Maxdatetime); + expression = parseMethod(TokenKind.MaxdatetimeMethod); assertEquals("{maxdatetime []}", expression.toString()); - expression = parseMethod(TokenKind.Mindatetime); + expression = parseMethod(TokenKind.MindatetimeMethod); assertEquals("{mindatetime []}", expression.toString()); } @Test public void oneParameterMethods() throws Exception { - Expression expression = parseMethod(TokenKind.Length, TokenKind.PrimitiveStringValue); - assertEquals("{length [String1]}", expression.toString()); - - expression = parseMethod(TokenKind.Tolower, TokenKind.PrimitiveStringValue); - assertEquals("{tolower [String1]}", expression.toString()); + final String stringValue = "'abc'"; + final String dateValue = "1234-12-25"; + final String dateTimeOffsetValue = "1234-12-25T11:12:13.456Z"; - expression = parseMethod(TokenKind.Toupper, TokenKind.PrimitiveStringValue); - assertEquals("{toupper [String1]}", expression.toString()); + Expression expression = parseMethod(TokenKind.LengthMethod, stringValue); + assertEquals("{length [" + stringValue + "]}", expression.toString()); - expression = parseMethod(TokenKind.Trim, TokenKind.PrimitiveStringValue); - assertEquals("{trim [String1]}", expression.toString()); + expression = parseMethod(TokenKind.TolowerMethod, stringValue); + assertEquals("{tolower [" + stringValue + "]}", expression.toString()); - expression = parseMethod(TokenKind.Year, TokenKind.PrimitiveDateValue); - assertEquals("{year [Date1]}", expression.toString()); + expression = parseMethod(TokenKind.ToupperMethod, stringValue); + assertEquals("{toupper [" + stringValue + "]}", expression.toString()); - expression = parseMethod(TokenKind.Month, TokenKind.PrimitiveDateValue); - assertEquals("{month [Date1]}", expression.toString()); + expression = parseMethod(TokenKind.TrimMethod, stringValue); + assertEquals("{trim [" + stringValue + "]}", expression.toString()); - expression = parseMethod(TokenKind.Day, TokenKind.PrimitiveDateValue); - assertEquals("{day [Date1]}", expression.toString()); + expression = parseMethod(TokenKind.YearMethod, dateValue); + assertEquals("{year [" + dateValue + "]}", expression.toString()); - expression = parseMethod(TokenKind.Hour, TokenKind.PrimitiveDateTimeOffsetValue); - assertEquals("{hour [DateTimeOffset1]}", expression.toString()); + expression = parseMethod(TokenKind.MonthMethod, dateValue); + assertEquals("{month [" + dateValue + "]}", expression.toString()); - expression = parseMethod(TokenKind.Minute, TokenKind.PrimitiveDateTimeOffsetValue); - assertEquals("{minute [DateTimeOffset1]}", expression.toString()); + expression = parseMethod(TokenKind.DayMethod, dateValue); + assertEquals("{day [" + dateValue + "]}", expression.toString()); - expression = parseMethod(TokenKind.Second, TokenKind.PrimitiveDateTimeOffsetValue); - assertEquals("{second [DateTimeOffset1]}", expression.toString()); - } + expression = parseMethod(TokenKind.HourMethod, dateTimeOffsetValue); + assertEquals("{hour [" + dateTimeOffsetValue + "]}", expression.toString()); - @Test - public void twoParameterMethods() { + expression = parseMethod(TokenKind.MinuteMethod, dateTimeOffsetValue); + assertEquals("{minute [" + dateTimeOffsetValue + "]}", expression.toString()); + expression = parseMethod(TokenKind.SecondMethod, dateTimeOffsetValue); + assertEquals("{second [" + dateTimeOffsetValue + "]}", expression.toString()); } - private Expression parseMethod(TokenKind... kind) throws UriParserException { - ArrayList<Token> tokens = new ArrayList<Token>(); - tokens.add(new Token(kind[0], "")); - - for (int i = 1; i < kind.length; i++) { - String text = null; - switch (kind[i]) { - case PrimitiveStringValue: - text = "String" + i; - break; - case PrimitiveDateValue: - text = "Date" + i; - break; - case PrimitiveDateTimeOffsetValue: - text = "DateTimeOffset" + i; - break; - default: - text = "" + i; - break; + private Expression parseMethod(TokenKind kind, String... parameters) throws UriParserException { + String expressionString = kind.name().substring(0, kind.name().indexOf("Method")) + .toLowerCase(Locale.ROOT).replace("geo", "geo.") + '('; + for (int i = 0; i < parameters.length; i++) { + if (i > 0) { + expressionString += ','; } - tokens.add(new Token(kind[i], text)); + expressionString += parameters[i]; } + expressionString += ')'; - tokens.add(new Token(TokenKind.CLOSE, "")); - Tokenizer tokenizer = new Tokenizer(tokens); - Expression expression = new ExpressionParser().parse(tokenizer); + Expression expression = parseExpression(expressionString); assertNotNull(expression); return expression; } - private Expression parseExpression(TokenKind operator) throws UriParserException { - ArrayList<Token> tokens = new ArrayList<Token>(); - tokens.add(new Token(TokenKind.PrimitiveIntegerValue, "5")); - tokens.add(new Token(operator, "")); - tokens.add(new Token(TokenKind.PrimitiveIntegerValue, "5")); - Tokenizer tokenizer = new Tokenizer(tokens); - + private Expression parseExpression(final String expressionString) throws UriParserException { + UriTokenizer tokenizer = new UriTokenizer(expressionString); Expression expression = new ExpressionParser().parse(tokenizer); assertNotNull(expression); + assertTrue(tokenizer.next(TokenKind.EOF)); return expression; } }
