Repository: olingo-odata4 Updated Branches: refs/heads/OLINGO-834_Filter_Parser [created] 7b23ad71a
[OLINGO-834] Filter parser refactoring first draft Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4/repo Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4/commit/7b23ad71 Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4/tree/7b23ad71 Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4/diff/7b23ad71 Branch: refs/heads/OLINGO-834_Filter_Parser Commit: 7b23ad71a7faab152841a3776b4db3ac9dffccf2 Parents: a47e9f6 Author: Christian Amend <[email protected]> Authored: Wed Dec 9 14:00:48 2015 +0100 Committer: Christian Amend <[email protected]> Committed: Wed Dec 9 14:11:46 2015 +0100 ---------------------------------------------------------------------- .../core/uri/expression/FilterParser.java | 562 +++++++++++++++++++ .../uri/queryoption/expression/AliasImpl.java | 8 + .../uri/queryoption/expression/BinaryImpl.java | 16 + .../uri/queryoption/expression/LiteralImpl.java | 14 + .../uri/queryoption/expression/MethodImpl.java | 24 + .../uri/queryoption/expression/UnaryImpl.java | 14 + .../core/uri/expression/FilterParserTest.java | 210 +++++++ 7 files changed, 848 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/7b23ad71/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/expression/FilterParser.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/expression/FilterParser.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/expression/FilterParser.java new file mode 100644 index 0000000..32cbc1a --- /dev/null +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/expression/FilterParser.java @@ -0,0 +1,562 @@ +/* + * 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.expression; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.olingo.commons.api.edm.EdmPrimitiveType; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; +import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; +import org.apache.olingo.server.api.uri.queryoption.expression.BinaryOperatorKind; +import org.apache.olingo.server.api.uri.queryoption.expression.Expression; +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.queryoption.expression.AliasImpl; +import org.apache.olingo.server.core.uri.queryoption.expression.BinaryImpl; +import org.apache.olingo.server.core.uri.queryoption.expression.ExpressionImpl; +import org.apache.olingo.server.core.uri.queryoption.expression.LiteralImpl; +import org.apache.olingo.server.core.uri.queryoption.expression.MethodImpl; +import org.apache.olingo.server.core.uri.queryoption.expression.UnaryImpl; + +public class FilterParser { + private Tokenizer tokenizer; + + private static final Map<TokenKind, BinaryOperatorKind> tokenToBinaryOperator; + static { + HashMap<TokenKind, BinaryOperatorKind> temp = new HashMap<FilterParser.TokenKind, BinaryOperatorKind>(); + temp.put(TokenKind.OR_OP, BinaryOperatorKind.OR); + temp.put(TokenKind.AND_OP, BinaryOperatorKind.AND); + + temp.put(TokenKind.EQ_OP, BinaryOperatorKind.EQ); + temp.put(TokenKind.NE_OP, 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.ADD_OP, BinaryOperatorKind.ADD); + temp.put(TokenKind.SUB_OP, BinaryOperatorKind.SUB); + + temp.put(TokenKind.MUL_OP, BinaryOperatorKind.MUL); + temp.put(TokenKind.DIV_OP, BinaryOperatorKind.DIV); + temp.put(TokenKind.MOD_OP, BinaryOperatorKind.MOD); + + tokenToBinaryOperator = Collections.unmodifiableMap(temp); + } + + private static final Map<TokenKind, UnaryOperatorKind> tokenToUnaryOperator; + static { + HashMap<TokenKind, UnaryOperatorKind> temp = new HashMap<FilterParser.TokenKind, UnaryOperatorKind>(); + temp.put(TokenKind.MINUS, UnaryOperatorKind.MINUS); + temp.put(TokenKind.NOT, UnaryOperatorKind.NOT); + tokenToUnaryOperator = Collections.unmodifiableMap(temp); + } + + private static final Map<TokenKind, MethodKind> tokenToMethod; + static { + HashMap<TokenKind, MethodKind> temp = new HashMap<FilterParser.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); + + tokenToMethod = Collections.unmodifiableMap(temp); + } + + private static final Map<TokenKind, EdmPrimitiveTypeKind> tokenToPrimitiveType; + static { + /* Enum and null are not present in the map. These have to be handled differently */ + HashMap<TokenKind, EdmPrimitiveTypeKind> temp = new HashMap<FilterParser.TokenKind, EdmPrimitiveTypeKind>(); + temp.put(TokenKind.PrimitiveBooleanValue, EdmPrimitiveTypeKind.Boolean); + temp.put(TokenKind.PrimitiveStringValue, 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); + + tokenToPrimitiveType = Collections.unmodifiableMap(temp); + } + + public Expression parse(Tokenizer tokenizer) { + // Initialize tokenizer + this.tokenizer = tokenizer; + + Expression exp = parseExpression(); + return exp; + } + + private Expression parseExpression() { + Expression left = parseAnd(); + + while (is(TokenKind.OR_OP) != null) { + tokenizer.getText(); + + Expression right = parseAnd(); + left = new BinaryImpl(left, BinaryOperatorKind.OR, right); + } + + return left; + } + + private Expression parseAnd() { + Expression left = parseExprEquality(); + while (is(TokenKind.AND_OP) != null) { + tokenizer.getText(); + + Expression right = parseExprEquality(); + left = new BinaryImpl(left, BinaryOperatorKind.AND, right); + } + return left; + } + + private Expression parseExprEquality() { + Expression left = parseExprRel(); + + TokenKind nextTokenKind = is(TokenKind.EQ_OP, TokenKind.NE_OP); + // 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); + } + + return left; + } + + private Expression parseExprRel() { + Expression left = parseExprAdd(); + + TokenKind nextTokenKind = is(TokenKind.GT_OP, TokenKind.GE_OP, TokenKind.LT_OP, TokenKind.LE_OP); + // 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); + } + + return left; + } + + private Expression parseExprAdd() { + Expression left = parseExprMul(); + + TokenKind nextTokenKind = is(TokenKind.ADD_OP, TokenKind.SUB_OP); + // 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); + } + + return left; + } + + private Expression parseExprMul() { + Expression left = parseExprUnary(); + + TokenKind nextTokenKind = is(TokenKind.MUL_OP, TokenKind.DIV_OP, TokenKind.MOD_OP); + // 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); + } + + return left; + } + + private Expression parseExprUnary() { + Expression left = null; + TokenKind nextTokenKind = is(TokenKind.MINUS, TokenKind.NOT); + // 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); + } + + if (left == null) { + left = parseExprValue(); + } + + return left; + } + + private Expression parseExprValue() { + if (is(TokenKind.OPEN) != null) { + tokenizer.getText(); + Expression exp = parseExpression(); + require(TokenKind.CLOSE); + return exp; + } + + if (is(TokenKind.ParameterAlias) != null) { + return new AliasImpl(tokenizer.getText()); + } + + if (is(TokenKind.RootExpr) != null) { + tokenizer.getText(); + // TODO: ConsumeRootExpression + } + + TokenKind nextPrimitive = isPrimitive(); + if (nextPrimitive != null) { + EdmPrimitiveTypeKind primitiveTypeKind = tokenToPrimitiveType.get(nextPrimitive); + EdmPrimitiveType type; + if (primitiveTypeKind == null) { + if (nextPrimitive == TokenKind.PrimitiveEnumValue) { + // TODO: Get enum type + type = null; + } else { + // Null handling + type = null; + } + } else { + type = EdmPrimitiveTypeFactory.getInstance(primitiveTypeKind); + } + return new LiteralImpl(tokenizer.getText(), type); + } + + TokenKind nextMethod = isMethod(); + if (nextMethod != null) { + MethodImpl methodImpl = new MethodImpl(tokenToMethod.get(nextMethod)); + // Consume Method + tokenizer.getText(); + if (is(TokenKind.CLOSE) != null) { + // Consume closing bracket + tokenizer.getText(); + } else { + // TODO: Remove Cast + methodImpl.addParameter((ExpressionImpl) parseExpression()); + while (is(TokenKind.COMMA) != null) { + tokenizer.getText(); + methodImpl.addParameter((ExpressionImpl) parseExpression()); + } + require(TokenKind.CLOSE); + } + + validateMethodParameters(methodImpl); + + return methodImpl; + } + + throw new RuntimeException("Unexpected token"); + } + + private void validateMethodParameters(MethodImpl methodImpl) { + // We might validate parameter types in the future + int size = methodImpl.getParameters().size(); + switch (methodImpl.getMethod()) { + // Must have two Parameters + case CONTAINS: + case ENDSWITH: + case STARTSWITH: + case INDEXOF: + case CONCAT: + case GEODISTANCE: + case GEOINTERSECTS: + if (size != 2) { + throw new RuntimeException("The method " + methodImpl.getMethod() + " needs exactly two parameters."); + } + break; + // Must have one parameter + case LENGTH: + case TOLOWER: + case TOUPPER: + case TRIM: + case YEAR: + case MONTH: + case DAY: + case HOUR: + case MINUTE: + case SECOND: + case FRACTIONALSECONDS: + case DATE: + case TIME: + case TOTALOFFSETMINUTES: + case TOTALSECONDS: + case ROUND: + case FLOOR: + case CEILING: + case GEOLENGTH: + if (size != 1) { + throw new RuntimeException("The method: '" + methodImpl.getMethod() + "' needs exactly one parameter."); + } + break; + // Must have no parameter + case NOW: + case MAXDATETIME: + case MINDATETIME: + if (size != 0) { + throw new RuntimeException("The method: '" + methodImpl.getMethod() + "' must have no parameters."); + } + break; + // Variable parameter number + case CAST: + case ISOF: + if (size == 1 || size == 2) { + throw new RuntimeException("The method: '" + methodImpl.getMethod() + "' must have one or two parameters."); + } + break; + case SUBSTRING: + if (size == 2 || size == 3) { + throw new RuntimeException("The method: '" + methodImpl.getMethod() + "' must have two or three parameters."); + } + break; + default: + throw new RuntimeException("Unkown method: '" + methodImpl.getMethod() + "'"); + } + } + + private String require(TokenKind required) { + if (is(required) == null) { + throw new RuntimeException("Requred token: " + required); + } + 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; + } + } + +} http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/7b23ad71/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/AliasImpl.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/AliasImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/AliasImpl.java index 09af93f..5309d73 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/AliasImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/AliasImpl.java @@ -27,6 +27,14 @@ public class AliasImpl extends ExpressionImpl implements Alias { private String parameterName; + public AliasImpl() { + //TODO: Delete Constructor + } + + public AliasImpl(String parameterName) { + this.parameterName = parameterName; + } + @Override public String getParameterName() { return parameterName; http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/7b23ad71/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/BinaryImpl.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/BinaryImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/BinaryImpl.java index a28f92c..c3530c0 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/BinaryImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/BinaryImpl.java @@ -31,6 +31,17 @@ public class BinaryImpl extends ExpressionImpl implements Binary { private ExpressionImpl left; private ExpressionImpl right; + public BinaryImpl() { + // TODO: Delete + } + + public BinaryImpl(Expression left, BinaryOperatorKind operator, Expression right) { + // TODO:DeleteCast + this.left = (ExpressionImpl) left; + this.operator = operator; + this.right = (ExpressionImpl) right; + } + @Override public BinaryOperatorKind getOperator() { return operator; @@ -67,4 +78,9 @@ public class BinaryImpl extends ExpressionImpl implements Binary { return visitor.visitBinaryOperator(operator, left, right); } + @Override + public String toString() { + return "{" + left + " " + operator.name() + " " + right + '}'; + } + } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/7b23ad71/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/LiteralImpl.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/LiteralImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/LiteralImpl.java index 1dd9fa7..e275fdd 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/LiteralImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/LiteralImpl.java @@ -29,6 +29,15 @@ public class LiteralImpl extends ExpressionImpl implements Literal { private String text; private EdmType type; + public LiteralImpl() { + + } + + public LiteralImpl(String text, EdmType type) { + this.text = text; + this.type = type; + } + @Override public String getText() { return text; @@ -54,4 +63,9 @@ public class LiteralImpl extends ExpressionImpl implements Literal { return visitor.visitLiteral(this); } + @Override + public String toString() { + return "" + text; + } + } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/7b23ad71/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MethodImpl.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MethodImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MethodImpl.java index 7104a9f..8175c85 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MethodImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/MethodImpl.java @@ -33,6 +33,14 @@ public class MethodImpl extends ExpressionImpl implements Method { private MethodKind method; private List<ExpressionImpl> parameters = new ArrayList<ExpressionImpl>(); + public MethodImpl() { + // TODO: Delete constructor + } + + public MethodImpl(MethodKind method) { + this.method = method; + } + @Override public MethodKind getMethod() { return method; @@ -66,4 +74,20 @@ public class MethodImpl extends ExpressionImpl implements Method { return visitor.visitMethodCall(method, userParameters); } + @Override + public String toString() { + String parametersString = "["; + boolean first = true; + for (Expression exp : parameters) { + if(first){ + first = false; + parametersString = parametersString + exp.toString(); + }else { + parametersString = parametersString + ", " + exp.toString(); + } + } + parametersString = parametersString + "]"; + return "{" + method + " " + parametersString + "}"; + } + } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/7b23ad71/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 796191f..f1edf91 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 @@ -30,6 +30,15 @@ public class UnaryImpl extends ExpressionImpl implements Unary { private UnaryOperatorKind operator; private ExpressionImpl expression; + public UnaryImpl() { + + } + + public UnaryImpl(UnaryOperatorKind operator, Expression expression) { + this.operator = operator; + this.expression = (ExpressionImpl) expression; + } + @Override public UnaryOperatorKind getOperator() { return operator; @@ -54,4 +63,9 @@ public class UnaryImpl extends ExpressionImpl implements Unary { return visitor.visitUnaryOperator(operator, operand); } + @Override + public String toString() { + return "{" + operator + " " + expression + '}'; + } + } http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/7b23ad71/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/expression/FilterParserTest.java ---------------------------------------------------------------------- diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/expression/FilterParserTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/expression/FilterParserTest.java new file mode 100644 index 0000000..7bfc369 --- /dev/null +++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/expression/FilterParserTest.java @@ -0,0 +1,210 @@ +/* + * 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.expression; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.ArrayList; + +import org.apache.olingo.server.api.uri.queryoption.expression.Expression; +import org.apache.olingo.server.core.uri.expression.FilterParser.TokenKind; +import org.apache.olingo.server.core.uri.expression.FilterParser.Tokenizer; +import org.apache.olingo.server.core.uri.expression.FilterParser.Token; +import org.junit.Test; + +public class FilterParserTest { + + @Test + public void equality() { + Expression expression = parseExpression(TokenKind.EQ_OP); + assertEquals("{5 EQ 5}", expression.toString()); + + expression = parseExpression(TokenKind.NE_OP); + assertEquals("{5 NE 5}", expression.toString()); + } + + @Test + public void relational() { + Expression expression = parseExpression(TokenKind.GT_OP); + assertEquals("{5 GT 5}", expression.toString()); + + expression = parseExpression(TokenKind.GE_OP); + assertEquals("{5 GE 5}", expression.toString()); + + expression = parseExpression(TokenKind.LT_OP); + assertEquals("{5 LT 5}", expression.toString()); + + expression = parseExpression(TokenKind.LE_OP); + assertEquals("{5 LE 5}", expression.toString()); + } + + @Test + public void additive() { + Expression expression = parseExpression(TokenKind.ADD_OP); + assertEquals("{5 ADD 5}", expression.toString()); + + expression = parseExpression(TokenKind.SUB_OP); + assertEquals("{5 SUB 5}", expression.toString()); + } + + @Test + public void multiplicative() { + Expression expression = parseExpression(TokenKind.MUL_OP); + assertEquals("{5 MUL 5}", expression.toString()); + + expression = parseExpression(TokenKind.DIV_OP); + assertEquals("{5 DIV 5}", expression.toString()); + + expression = parseExpression(TokenKind.MOD_OP); + assertEquals("{5 MOD 5}", expression.toString()); + } + + @Test + public void unary() { + 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 FilterParser().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 FilterParser().parse(tokenizer); + assertEquals("{not 5}", expression.toString()); + } + + @Test + public void grouping() { + 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 FilterParser().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 FilterParser().parse(tokenizer); + assertEquals("{- {5 ADD 5}}", expression.toString()); + } + + @Test + public void noParameterMethods() { + Expression expression = parseMethod(TokenKind.Now); + assertEquals("{now []}", expression.toString()); + + expression = parseMethod(TokenKind.Maxdatetime); + assertEquals("{maxdatetime []}", expression.toString()); + + expression = parseMethod(TokenKind.Mindatetime); + assertEquals("{mindatetime []}", expression.toString()); + } + + @Test + public void oneParameterMethods() { + Expression expression = parseMethod(TokenKind.Length, TokenKind.PrimitiveStringValue); + assertEquals("{length [String1]}", expression.toString()); + + expression = parseMethod(TokenKind.Tolower, TokenKind.PrimitiveStringValue); + assertEquals("{tolower [String1]}", expression.toString()); + + expression = parseMethod(TokenKind.Toupper, TokenKind.PrimitiveStringValue); + assertEquals("{toupper [String1]}", expression.toString()); + + expression = parseMethod(TokenKind.Trim, TokenKind.PrimitiveStringValue); + assertEquals("{trim [String1]}", expression.toString()); + + expression = parseMethod(TokenKind.Year, TokenKind.PrimitiveDateValue); + assertEquals("{year [Date1]}", expression.toString()); + + expression = parseMethod(TokenKind.Month, TokenKind.PrimitiveDateValue); + assertEquals("{month [Date1]}", expression.toString()); + + expression = parseMethod(TokenKind.Day, TokenKind.PrimitiveDateValue); + assertEquals("{day [Date1]}", expression.toString()); + + expression = parseMethod(TokenKind.Hour, TokenKind.PrimitiveDateTimeOffsetValue); + assertEquals("{hour [DateTimeOffset1]}", expression.toString()); + + expression = parseMethod(TokenKind.Minute, TokenKind.PrimitiveDateTimeOffsetValue); + assertEquals("{minute [DateTimeOffset1]}", expression.toString()); + + expression = parseMethod(TokenKind.Second, TokenKind.PrimitiveDateTimeOffsetValue); + assertEquals("{second [DateTimeOffset1]}", expression.toString()); + } + + @Test + public void twoParameterMethods() { + + } + + private Expression parseMethod(TokenKind... kind) { + 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; + } + tokens.add(new Token(kind[i], text)); + } + + tokens.add(new Token(TokenKind.CLOSE, "")); + Tokenizer tokenizer = new Tokenizer(tokens); + Expression expression = new FilterParser().parse(tokenizer); + assertNotNull(expression); + return expression; + } + + private Expression parseExpression(TokenKind operator) { + 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); + + Expression expression = new FilterParser().parse(tokenizer); + assertNotNull(expression); + return expression; + } +}
