/** * personium.io * Modifications copyright 2014 FUJITSU LIMITED * * Licensed 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. * -------------------------------------------------- * This code is based on ExpressionParser.java of odata4j-core, and some modifications * for personium.io are applied by us. * - Add support for query containing '_' in tokenize(), readWord(). * - Add support for $expand query in parseExpandQuery(). * -------------------------------------------------- * The copyright and the license text of the original code is as follows: */ /**************************************************************************** * Copyright (c) 2010 odata4j * * Licensed 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 com.fujitsu.dc.core.odata; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.core4j.Enumerable; import org.core4j.Func1; import org.core4j.Func2; import org.joda.time.DateTime; import org.joda.time.LocalDateTime; import org.joda.time.LocalTime; import org.odata4j.core.Guid; import org.odata4j.core.Throwables; import org.odata4j.expression.BoolCommonExpression; import org.odata4j.expression.CommonExpression; import org.odata4j.expression.EntitySimpleProperty; import org.odata4j.expression.Expression; import org.odata4j.expression.ExpressionParser; import org.odata4j.expression.ExpressionParser.AggregateFunction; import org.odata4j.expression.ExpressionParser.TokenType; import org.odata4j.expression.OrderByExpression; import org.odata4j.expression.OrderByExpression.Direction; import org.odata4j.expression.StringLiteral; import org.odata4j.internal.InternalUtil; import org.odata4j.repack.org.apache.commons.codec.DecoderException; import org.odata4j.repack.org.apache.commons.codec.binary.Hex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * DcExpressionParserクラス. * OData4jのライブラリのソースよりコピー */ public class DcExpressionParser { static Logger log = LoggerFactory.getLogger(DcExpressionParser.class); private static final int TOKEN_SIZE_3 = 3; private static final int TOKEN_SIZE_4 = 4; private static final int TOKEN_SIZE_5 = 5; private static final int TOKEN_SIZE_6 = 6; private static final int BINARY_EXPRESSION_CONSTANT = 3; private DcExpressionParser() { } /** * Methods. */ private static class Methods { public static final String CAST = "cast"; public static final String ISOF = "isof"; public static final String ENDSWITH = "endswith"; public static final String STARTSWITH = "startswith"; public static final String SUBSTRINGOF = "substringof"; public static final String INDEXOF = "indexof"; public static final String REPLACE = "replace"; public static final String TOLOWER = "tolower"; public static final String TOUPPER = "toupper"; public static final String TRIM = "trim"; public static final String SUBSTRING = "substring"; public static final String CONCAT = "concat"; public static final String LENGTH = "length"; public static final String YEAR = "year"; public static final String MONTH = "month"; public static final String DAY = "day"; public static final String HOUR = "hour"; public static final String MINUTE = "minute"; public static final String SECOND = "second"; public static final String ROUND = "round"; public static final String FLOOR = "floor"; public static final String CEILING = "ceiling"; } private static Set<String> methods = Enumerable.create( Methods.CAST, Methods.ISOF, Methods.ENDSWITH, Methods.STARTSWITH, Methods.SUBSTRINGOF, Methods.INDEXOF, Methods.REPLACE, Methods.TOLOWER, Methods.TOUPPER, Methods.TRIM, Methods.SUBSTRING, Methods.CONCAT, Methods.LENGTH, Methods.YEAR, Methods.MONTH, Methods.DAY, Methods.HOUR, Methods.MINUTE, Methods.SECOND, Methods.ROUND, Methods.FLOOR, Methods.CEILING).toSet(); /** * tokenizer. * OData4jのtokenizerでは、'_'を含むとエラーとしているため独自に定義 * @param value value * @return トークン */ public static List<Token> tokenize(String value) { List<Token> rt = new ArrayList<Token>(); int current = 0; int end = 0; while (true) { if (current == value.length()) { return rt; } char c = value.charAt(current); if (Character.isWhitespace(c)) { end = readWhitespace(value, current); rt.add(new Token(TokenType.WHITESPACE, value.substring(current, end))); current = end; } else if (c == '\'') { end = readQuotedString(value, current + 1); rt.add(new Token(TokenType.QUOTED_STRING, value.substring(current, end))); current = end; } else if (Character.isLetter(c)) { end = readWord(value, current + 1); rt.add(new Token(TokenType.WORD, value.substring(current, end))); current = end; } else if (c == '_') { end = readWord(value, current + 1); rt.add(new Token(TokenType.WORD, value.substring(current, end))); current = end; } else if (Character.isDigit(c)) { end = readDigits(value, current + 1); rt.add(new Token(TokenType.NUMBER, value.substring(current, end))); current = end; } else if (c == '(') { rt.add(new Token(TokenType.OPENPAREN, Character.toString(c))); current++; } else if (c == ')') { rt.add(new Token(TokenType.CLOSEPAREN, Character.toString(c))); current++; } else if (c == '-') { if (Character.isDigit(value.charAt(current + 1))) { end = readDigits(value, current + 1); rt.add(new Token(TokenType.NUMBER, value.substring(current, end))); current = end; } else { rt.add(new Token(TokenType.SYMBOL, Character.toString(c))); current++; } } else if (",.+=:".indexOf(c) > -1) { rt.add(new Token(TokenType.SYMBOL, Character.toString(c))); current++; } else { dumpTokens(rt); throw new RuntimeException("Unable to tokenize: " + value + " current: " + current + " rem: " + value.substring(current)); } } } /** * orderbyのパース. * @param value orderbyの値 * @return パース結果 */ public static List<OrderByExpression> parseOrderBy(String value) { List<Token> tokens = tokenize(value); // dump(value,tokens,null); List<CommonExpression> expressions = readExpressions(tokens); if (ExpressionParser.DUMP_EXPRESSION_INFO) { dump(value, tokens, Enumerable.create(expressions).toArray(CommonExpression.class)); } return Enumerable.create(expressions).select(new Func1<CommonExpression, OrderByExpression>() { public OrderByExpression apply(CommonExpression input) { if (input instanceof OrderByExpression) { return (OrderByExpression) input; } return Expression.orderBy(input, Direction.ASCENDING); // default to asc } }).toList(); } /** * filterのパース. * @param value filterの値 * @return パース結果 */ public static CommonExpression parse(String value) { List<Token> tokens = tokenize(value); // dump(value,tokens,null); CommonExpression rt = readExpression(tokens); if (ExpressionParser.DUMP_EXPRESSION_INFO) { dump(value, tokens, rt); } return rt; } /** * selectのパース. * @param value selectの値 * @return パース結果 */ public static List<EntitySimpleProperty> parseExpand(String value) { List<Token> tokens = tokenize(value); // dump(value,tokens,null); List<CommonExpression> expressions = readExpressions(tokens); // since we support currently simple properties only we have to // confine ourselves to EntitySimpleProperties. return Enumerable.create(expressions).select(new Func1<CommonExpression, EntitySimpleProperty>() { public EntitySimpleProperty apply(CommonExpression input) { if (input instanceof EntitySimpleProperty) { return (EntitySimpleProperty) input; } return null; } }).toList(); } /** * expandのパース. * @param value expandの値 * @return パース結果 */ public static List<EntitySimpleProperty> parseExpandQuery(String value) { List<Token> tokens = tokenize(value); for (Token token : tokens) { if (!token.value.equals(",") && !token.value.startsWith("_")) { throw new RuntimeException("Invalid navigationProperty name.(" + token.toString() + ")"); } } List<CommonExpression> expressions = readExpressions(tokens); // since we support currently simple properties only we have to // confine ourselves to EntitySimpleProperties. return Enumerable.create(expressions).select(new Func1<CommonExpression, EntitySimpleProperty>() { public EntitySimpleProperty apply(CommonExpression input) { if (input instanceof EntitySimpleProperty) { return (EntitySimpleProperty) input; } return null; } }).toList(); } /** * dumpTokens. * @param tokens tokenリスト */ public static void dumpTokens(List<Token> tokens) { for (Token t : tokens) { if (t.type != null) { log.debug(t.type.toString() + t.toString()); } } } /** * processParentheses. * @param tokens tokenリスト * @return tokenリスト */ public static List<Token> processParentheses(List<Token> tokens) { List<Token> rt = new ArrayList<Token>(); for (int i = 0; i < tokens.size(); i++) { Token openToken = tokens.get(i); if (openToken.type == TokenType.OPENPAREN) { int afterParenIdx = i + 1; // is this a method call or any/all aggregate function? String methodName = null; String aggregateSource = null; String aggregateVariable = null; AggregateFunction aggregateFunction = AggregateFunction.none; int k = i - 1; while (k > 0 && tokens.get(k).type == TokenType.WHITESPACE) { k--; } if (k >= 0) { Token methodNameToken = tokens.get(k); if (methodNameToken.type == TokenType.WORD) { if (methods.contains(methodNameToken.value)) { methodName = methodNameToken.value; // this isn't strictly correct. I think the parser has issues // with sequences of WORD, WHITESPACE, WORD, etc. I'm not sure I've // ever seen a token type of WHITESPACE producer by a lexer.. } else if (methodNameToken.value.endsWith("/any") || methodNameToken.value.endsWith("/all")) { aggregateSource = methodNameToken.value.substring(0, methodNameToken.value.length() - TOKEN_SIZE_3); aggregateFunction = Enum.valueOf(AggregateFunction.class, methodNameToken.value.substring(methodNameToken.value.length() - TOKEN_SIZE_3)); // to get things rolling I'm going to lookahead and require a very strict // sequence of tokens: // i + 1 must be a WORD // i + 2 must be a SYMBOL ':' // or, for any, i + 1 can be CLOSEPAREN int ni = i + 1; Token ntoken = null; if (ni < tokens.size()) { ntoken = tokens.get(ni); } processParenthesesCheckToken(aggregateFunction, ntoken); if (ntoken != null) { if (ntoken.type == TokenType.WORD) { aggregateVariable = ntoken.value; ni += 1; ntoken = null; if (ni < tokens.size()) { ntoken = tokens.get(ni); } if (ntoken == null || ntoken.type != TokenType.SYMBOL || !ntoken.value.equals(":")) { String reason; if (ntoken == null) { reason = "eof"; } else { reason = ntoken.toString(); } throw new RuntimeException("expected ':', found: " + reason); } // now we can parse the predicate, starting after the ':' afterParenIdx = ni + 1; } else { // any(), easiest to early out here List<Token> tokensIncludingParens = tokens.subList(k, ni + 1); CommonExpression any = Expression.any( Expression.simpleProperty(aggregateSource)); ExpressionToken et = new ExpressionToken(any, tokensIncludingParens); rt.subList(rt.size() - (i - k), rt.size()).clear(); rt.add(et); return rt; } } } } } // find matching close paren int stack = 0; int start = i; List<CommonExpression> methodArguments = new ArrayList<CommonExpression>(); for (int j = afterParenIdx; j < tokens.size(); j++) { Token closeToken = tokens.get(j); if (closeToken.type == TokenType.OPENPAREN) { stack++; } else if (methodName != null && stack == 0 && closeToken.type == TokenType.SYMBOL && closeToken.value.equals(",")) { List<Token> tokensInsideComma = tokens.subList(start + 1, j); CommonExpression expressionInsideComma = readExpression(tokensInsideComma); methodArguments.add(expressionInsideComma); start = j; } else if (closeToken.type == TokenType.CLOSEPAREN) { if (stack > 0) { stack--; continue; } if (methodName != null) { methodCall(tokens, rt, i, methodName, k, start, methodArguments, j); } else if (aggregateVariable != null) { List<Token> tokensIncludingParens = tokens.subList(k, j + 1); List<Token> tokensInsideParens = tokens.subList(afterParenIdx, j); CommonExpression expressionInsideParens = readExpression(tokensInsideParens); if (!(expressionInsideParens instanceof BoolCommonExpression)) { throw new RuntimeException("illegal any predicate"); } CommonExpression any = Expression.aggregate( aggregateFunction, Expression.simpleProperty(aggregateSource), aggregateVariable, (BoolCommonExpression) expressionInsideParens); ExpressionToken et = new ExpressionToken(any, tokensIncludingParens); rt.subList(rt.size() - (i - k), rt.size()).clear(); rt.add(et); } else { List<Token> tokensIncludingParens = tokens.subList(i, j + 1); List<Token> tokensInsideParens = tokens.subList(i + 1, j); // paren expression: replace t ( t t t ) t with t et t CommonExpression expressionInsideParens = readExpression(tokensInsideParens); CommonExpression exp = null; if (expressionInsideParens instanceof BoolCommonExpression) { exp = Expression.boolParen(expressionInsideParens); } else { exp = Expression.paren(expressionInsideParens); } ExpressionToken et = new ExpressionToken(exp, tokensIncludingParens); rt.add(et); } i = j; } } } else { rt.add(openToken); } } return rt; } private static void methodCall(List<Token> tokens, List<Token> rt, int i, String methodName, int k, int start, List<CommonExpression> methodArguments, int j) { List<Token> tokensIncludingParens = tokens.subList(k, j + 1); List<Token> tokensInsideParens = tokens.subList(start + 1, j); CommonExpression expressionInsideParens = readExpression(tokensInsideParens); methodArguments.add(expressionInsideParens); // method call expression: replace t mn ( t t , t t ) t with t et t CommonExpression methodCall = methodCall(methodName, methodArguments); ExpressionToken et = new ExpressionToken(methodCall, tokensIncludingParens); rt.subList(rt.size() - (i - k), rt.size()).clear(); rt.add(et); } private static void processParenthesesCheckToken(AggregateFunction aggregateFunction, Token ntoken) { if (ntoken == null || (aggregateFunction == AggregateFunction.all && ntoken.type != TokenType.WORD) || (aggregateFunction == AggregateFunction.any && ntoken.type != TokenType.WORD && ntoken.type != TokenType.CLOSEPAREN)) { String reason; if (ntoken == null) { reason = "eof"; } else { reason = ntoken.toString(); } throw new RuntimeException("unexpected token: " + reason); } } private static CommonExpression methodCall(String methodName, List<CommonExpression> methodArguments) { if (methodName.equals(Methods.CAST) && methodArguments.size() == 1) { CommonExpression arg = methodArguments.get(0); assertType(arg, StringLiteral.class); String type = ((StringLiteral) arg).getValue(); return Expression.cast(type); } else if (methodName.equals(Methods.CAST) && methodArguments.size() == 2) { CommonExpression arg1 = methodArguments.get(0); CommonExpression arg2 = methodArguments.get(1); assertType(arg2, StringLiteral.class); String type = ((StringLiteral) arg2).getValue(); return Expression.cast(arg1, type); } else if (methodName.equals(Methods.ISOF) && methodArguments.size() == 1) { CommonExpression arg = methodArguments.get(0); assertType(arg, StringLiteral.class); String type = ((StringLiteral) arg).getValue(); return Expression.isof(type); } else if (methodName.equals(Methods.ISOF) && methodArguments.size() == 2) { CommonExpression arg1 = methodArguments.get(0); CommonExpression arg2 = methodArguments.get(1); assertType(arg2, StringLiteral.class); String type = ((StringLiteral) arg2).getValue(); return Expression.isof(arg1, type); } else if (methodName.equals(Methods.ENDSWITH) && methodArguments.size() == 2) { CommonExpression arg1 = methodArguments.get(0); CommonExpression arg2 = methodArguments.get(1); return Expression.endsWith(arg1, arg2); } else if (methodName.equals(Methods.STARTSWITH) && methodArguments.size() == 2) { CommonExpression arg1 = methodArguments.get(0); CommonExpression arg2 = methodArguments.get(1); return Expression.startsWith(arg1, arg2); } else if (methodName.equals(Methods.SUBSTRINGOF) && methodArguments.size() == 1) { CommonExpression arg1 = methodArguments.get(0); return Expression.substringOf(arg1); } else if (methodName.equals(Methods.SUBSTRINGOF) && methodArguments.size() == 2) { CommonExpression arg1 = methodArguments.get(0); CommonExpression arg2 = methodArguments.get(1); return Expression.substringOf(arg1, arg2); } else if (methodName.equals(Methods.INDEXOF) && methodArguments.size() == 2) { CommonExpression arg1 = methodArguments.get(0); CommonExpression arg2 = methodArguments.get(1); return Expression.indexOf(arg1, arg2); } else if (methodName.equals(Methods.REPLACE) && methodArguments.size() == TOKEN_SIZE_3) { CommonExpression arg1 = methodArguments.get(0); CommonExpression arg2 = methodArguments.get(1); CommonExpression arg3 = methodArguments.get(2); return Expression.replace(arg1, arg2, arg3); } else if (methodName.equals(Methods.TOLOWER) && methodArguments.size() == 1) { CommonExpression arg1 = methodArguments.get(0); return Expression.toLower(arg1); } else if (methodName.equals(Methods.TOUPPER) && methodArguments.size() == 1) { CommonExpression arg1 = methodArguments.get(0); return Expression.toUpper(arg1); } else if (methodName.equals(Methods.TRIM) && methodArguments.size() == 1) { CommonExpression arg1 = methodArguments.get(0); return Expression.trim(arg1); } else if (methodName.equals(Methods.SUBSTRING) && methodArguments.size() == 2) { CommonExpression arg1 = methodArguments.get(0); CommonExpression arg2 = methodArguments.get(1); return Expression.substring(arg1, arg2); } else if (methodName.equals(Methods.SUBSTRING) && methodArguments.size() == TOKEN_SIZE_3) { CommonExpression arg1 = methodArguments.get(0); CommonExpression arg2 = methodArguments.get(1); CommonExpression arg3 = methodArguments.get(2); return Expression.substring(arg1, arg2, arg3); } else if (methodName.equals(Methods.CONCAT) && methodArguments.size() == 2) { CommonExpression arg1 = methodArguments.get(0); CommonExpression arg2 = methodArguments.get(1); return Expression.concat(arg1, arg2); } else if (methodName.equals(Methods.LENGTH) && methodArguments.size() == 1) { CommonExpression arg1 = methodArguments.get(0); return Expression.length(arg1); } else if (methodName.equals(Methods.YEAR) && methodArguments.size() == 1) { CommonExpression arg1 = methodArguments.get(0); return Expression.year(arg1); } else if (methodName.equals(Methods.MONTH) && methodArguments.size() == 1) { CommonExpression arg1 = methodArguments.get(0); return Expression.month(arg1); } else if (methodName.equals(Methods.DAY) && methodArguments.size() == 1) { CommonExpression arg1 = methodArguments.get(0); return Expression.day(arg1); } else if (methodName.equals(Methods.HOUR) && methodArguments.size() == 1) { CommonExpression arg1 = methodArguments.get(0); return Expression.hour(arg1); } else if (methodName.equals(Methods.MINUTE) && methodArguments.size() == 1) { CommonExpression arg1 = methodArguments.get(0); return Expression.minute(arg1); } else if (methodName.equals(Methods.SECOND) && methodArguments.size() == 1) { CommonExpression arg1 = methodArguments.get(0); return Expression.second(arg1); } else if (methodName.equals(Methods.CEILING) && methodArguments.size() == 1) { CommonExpression arg1 = methodArguments.get(0); return Expression.ceiling(arg1); } else if (methodName.equals(Methods.FLOOR) && methodArguments.size() == 1) { CommonExpression arg1 = methodArguments.get(0); return Expression.floor(arg1); } else if (methodName.equals(Methods.ROUND) && methodArguments.size() == 1) { CommonExpression arg1 = methodArguments.get(0); return Expression.round(arg1); } else { throw new RuntimeException("Implement method " + methodName); } } private static void dump(String value, List<Token> tokens, CommonExpression... expressions) { String msg = "[" + value + "] -> " + Enumerable.create(tokens).join(""); if (expressions != null) { msg = msg + " -> " + Enumerable.create(expressions).select(new Func1<CommonExpression, String>() { public String apply(CommonExpression input) { return Expression.asPrintString(input); } }).join(","); } if (msg != null) { log.debug(msg); } } private static CommonExpression processBinaryExpression(List<Token> tokens, String op, Func2<CommonExpression, CommonExpression, CommonExpression> fn) { int ts = tokens.size(); for (int i = 0; i < ts; i++) { Token t = tokens.get(i); if (i < ts - 2 && t.type == TokenType.WHITESPACE && tokens.get(i + 2).type == TokenType.WHITESPACE && tokens.get(i + 1).type == TokenType.WORD && tokens.get(i + 1).value.equals(op)) { CommonExpression lhs = readExpression(tokens.subList(0, i)); CommonExpression rhs = readExpression(tokens.subList(i + BINARY_EXPRESSION_CONSTANT, ts)); return fn.apply(lhs, rhs); } } return null; } private static CommonExpression processUnaryExpression(List<Token> tokens, String op, boolean whitespaceRequired, Func1<CommonExpression, CommonExpression> fn) { int ts = tokens.size(); for (int i = 0; i < ts; i++) { Token t = tokens.get(i); if (i < ts - 1 && (t.type == TokenType.WORD || t.type == TokenType.SYMBOL) && (!whitespaceRequired || tokens.get(i + 1).type == TokenType.WHITESPACE) && t.value.equals(op)) { int idx = 0; if (whitespaceRequired) { idx = 2; } else { idx = 1; } CommonExpression expression = readExpression(tokens.subList(i + idx, ts)); return fn.apply(expression); } } return null; } private static <T extends CommonExpression> void assertType(CommonExpression expression, Class<T> type) { if (!type.isAssignableFrom(expression.getClass())) { throw new RuntimeException("Expected " + type.getSimpleName()); } } private static List<CommonExpression> readExpressions(List<Token> tokens) { List<CommonExpression> rt = new ArrayList<CommonExpression>(); int stack = 0; int start = 0; for (int i = 0; i < tokens.size(); i++) { Token token = tokens.get(i); if (token.type == TokenType.OPENPAREN) { stack++; } else if (token.type == TokenType.CLOSEPAREN) { stack--; } else if (stack == 0 && token.type == TokenType.SYMBOL && token.value.equals(",")) { List<Token> tokensInsideComma = tokens.subList(start, i); CommonExpression expressionInsideComma = readExpression(tokensInsideComma); rt.add(expressionInsideComma); start = i + 1; } else if (i == tokens.size() - 1) { List<Token> tokensInside = tokens.subList(start, i + 1); CommonExpression expressionInside = readExpression(tokensInside); rt.add(expressionInside); } } return rt; } private static CommonExpression readExpression(List<Token> tokens) { CommonExpression rt = null; tokens = trimWhitespace(tokens); // OrderBy asc, desc Token lastToken = tokens.get(tokens.size() - 1); if (lastToken.type == TokenType.WORD && (lastToken.value.equals("asc") || lastToken.value.equals("desc"))) { Direction direction; if (lastToken.value.equals("asc")) { direction = Direction.ASCENDING; } else { direction = Direction.DESCENDING; } return Expression.orderBy( readExpression(tokens.subList(0, tokens.size() - 1)), direction); } // Grouping (highest precedence) tokens = processParentheses(tokens); // now we have a list of tokens with no explicit parens // process literals rt = readExpressionLiteralProcess(tokens); if (rt != null) { return rt; } // single token expression if (tokens.size() == 1) { final Token token = tokens.get(0); if (token.type == TokenType.QUOTED_STRING) { return Expression.string(unquote(token.value)); } else if (token.type == TokenType.WORD) { if (token.value.equals("null")) { return Expression.null_(); } if (token.value.equals("true")) { return Expression.boolean_(true); } if (token.value.equals("false")) { return Expression.boolean_(false); } return Expression.simpleProperty(token.value); } else if (token.type == TokenType.NUMBER) { try { int value = Integer.parseInt(token.value); return Expression.integral(value); } catch (NumberFormatException e) { long value = Long.parseLong(token.value); return Expression.int64(value); } } else if (token.type == TokenType.EXPRESSION) { return ((ExpressionToken) token).expression; } else { throw new RuntimeException("Unexpected"); } } // process operators from least to highest precedence rt = readExpressionPrecedenceOperator(tokens); if (rt != null) { return rt; } throw new RuntimeException("Unable to read expression with tokens: " + tokens); } private static CommonExpression readExpressionLiteralProcess(List<Token> tokens) { // literals with prefixes if (tokens.size() == 2 && tokens.get(0).type == TokenType.WORD && tokens.get(1).type == TokenType.QUOTED_STRING) { String word = tokens.get(0).value; String value = unquote(tokens.get(1).value); if (word.equals("datetime")) { DateTime dt = InternalUtil.parseDateTime(value); return Expression.dateTime(new LocalDateTime(dt)); } else if (word.equals("time")) { LocalTime t = InternalUtil.parseTime(value); return Expression.time(t); } else if (word.equals("datetimeoffset")) { DateTime dt = InternalUtil.parseDateTime(value); return Expression.dateTimeOffset(dt); } else if (word.equals("guid")) { // odata: dddddddd-dddd-dddd-dddddddddddd // java: dddddddd-dd-dd-dddd-dddddddddddd // value = value.substring(0, 11) + "-" + value.substring(11); return Expression.guid(Guid.fromString(value)); } else if (word.equals("decimal")) { return Expression.decimal(new BigDecimal(value)); } else if (word.equals("X") || word.equals("binary")) { try { byte[] bValue = Hex.decodeHex(value.toCharArray()); return Expression.binary(bValue); } catch (DecoderException e) { throw Throwables.propagate(e); } } } // long literal: 1234L if (tokens.size() == 2 && tokens.get(0).type == TokenType.NUMBER && tokens.get(1).type == TokenType.WORD && tokens.get(1).value.equals("L")) { long longValue = Long.parseLong(tokens.get(0).value); return Expression.int64(longValue); } // single literal: 2f if (tokens.size() == 2 && tokens.get(0).type == TokenType.NUMBER && tokens.get(1).type == TokenType.WORD && tokens.get(1).value.equals("f")) { float floatValue = Float.parseFloat(tokens.get(0).value); return Expression.single(floatValue); } // single literal: 2.0f if (tokens.size() == TOKEN_SIZE_4 && tokens.get(0).type == TokenType.NUMBER && tokens.get(1).type == TokenType.SYMBOL && tokens.get(1).value.equals(".") && tokens.get(2).type == TokenType.NUMBER && tokens.get(TOKEN_SIZE_3).value.equals("f")) { float floatValue = Float.parseFloat(tokens.get(0).value + "." + tokens.get(2).value); return Expression.single(floatValue); } // double literal: 2.0 if (tokens.size() == TOKEN_SIZE_3 && tokens.get(0).type == TokenType.NUMBER && tokens.get(1).type == TokenType.SYMBOL && tokens.get(1).value.equals(".") && tokens.get(2).type == TokenType.NUMBER) { double doubleValue = Double.parseDouble(tokens.get(0).value + "." + tokens.get(2).value); return Expression.double_(doubleValue); } // double literal: 1E+10 if (tokens.size() == TOKEN_SIZE_4 && tokens.get(0).type == TokenType.NUMBER && tokens.get(1).type == TokenType.WORD && tokens.get(1).value.equals("E") && tokens.get(2).type == TokenType.SYMBOL && tokens.get(2).value.equals("+") && tokens.get(TOKEN_SIZE_3).type == TokenType.NUMBER) { double doubleValue = Double.parseDouble(tokens.get(0).value + "E+" + tokens.get(TOKEN_SIZE_3).value); return Expression.double_(doubleValue); } // double literal: 1E-10 if (tokens.size() == TOKEN_SIZE_3 && tokens.get(0).type == TokenType.NUMBER && tokens.get(1).type == TokenType.WORD && tokens.get(1).value.equals("E") && tokens.get(2).type == TokenType.NUMBER) { int e = Integer.parseInt(tokens.get(2).value); if (e < 1) { double doubleValue = Double.parseDouble(tokens.get(0).value + "E" + tokens.get(2).value); return Expression.double_(doubleValue); } } // double literal: 1.2E+10 if (tokens.size() == TOKEN_SIZE_6 && tokens.get(0).type == TokenType.NUMBER && tokens.get(1).type == TokenType.SYMBOL && tokens.get(1).value.equals(".") && tokens.get(2).type == TokenType.NUMBER && tokens.get(TOKEN_SIZE_3).type == TokenType.WORD && tokens.get(TOKEN_SIZE_3).value.equals("E") && tokens.get(TOKEN_SIZE_4).type == TokenType.SYMBOL && tokens.get(TOKEN_SIZE_4).value.equals("+") && tokens.get(TOKEN_SIZE_5).type == TokenType.NUMBER) { double doubleValue = Double.parseDouble(tokens.get(0).value + "." + tokens.get(2).value + "E+" + tokens.get(TOKEN_SIZE_5).value); return Expression.double_(doubleValue); } // double literal: 1.2E-10 if (tokens.size() == TOKEN_SIZE_5 && tokens.get(0).type == TokenType.NUMBER && tokens.get(1).type == TokenType.SYMBOL && tokens.get(1).value.equals(".") && tokens.get(2).type == TokenType.NUMBER && tokens.get(TOKEN_SIZE_3).type == TokenType.WORD && tokens.get(TOKEN_SIZE_3).value.equals("E") && tokens.get(TOKEN_SIZE_4).type == TokenType.NUMBER) { int e = Integer.parseInt(tokens.get(TOKEN_SIZE_4).value); if (e < 1) { double doubleValue = Double.parseDouble(tokens.get(0).value + "." + tokens.get(2).value + "E" + tokens.get(TOKEN_SIZE_4).value); return Expression.double_(doubleValue); } } // decimal literal: 1234M or 1234m if (tokens.size() == 2 && tokens.get(0).type == TokenType.NUMBER && tokens.get(1).type == TokenType.WORD && tokens.get(1).value.equalsIgnoreCase("M")) { BigDecimal decimalValue = new BigDecimal(tokens.get(0).value); return Expression.decimal(decimalValue); } // decimal literal: 2.0m if (tokens.size() == TOKEN_SIZE_4 && tokens.get(0).type == TokenType.NUMBER && tokens.get(1).type == TokenType.SYMBOL && tokens.get(1).value.equals(".") && tokens.get(2).type == TokenType.NUMBER && tokens.get(TOKEN_SIZE_3).value.equalsIgnoreCase("m")) { BigDecimal decimalValue = new BigDecimal(tokens.get(0).value + "." + tokens.get(2).value); return Expression.decimal(decimalValue); } // TODO literals: byteLiteral, sbyteliteral return null; } private static CommonExpression readExpressionPrecedenceOperator(List<Token> tokens) { CommonExpression rt = null; // Conditional OR: or rt = processBinaryExpression(tokens, "or", new Func2<CommonExpression, CommonExpression, CommonExpression>() { public CommonExpression apply(CommonExpression lhs, CommonExpression rhs) { assertType(lhs, BoolCommonExpression.class); assertType(rhs, BoolCommonExpression.class); return Expression.or((BoolCommonExpression) lhs, (BoolCommonExpression) rhs); } }); if (rt != null) { return rt; } // Conditional AND: and rt = processBinaryExpression(tokens, "and", new Func2<CommonExpression, CommonExpression, CommonExpression>() { public CommonExpression apply(CommonExpression lhs, CommonExpression rhs) { assertType(lhs, BoolCommonExpression.class); assertType(rhs, BoolCommonExpression.class); return Expression.and((BoolCommonExpression) lhs, (BoolCommonExpression) rhs); } }); if (rt != null) { return rt; } // Equality: eq ne rt = processBinaryExpression(tokens, "eq", new Func2<CommonExpression, CommonExpression, CommonExpression>() { public CommonExpression apply(CommonExpression lhs, CommonExpression rhs) { return Expression.eq(lhs, rhs); } }); if (rt != null) { return rt; } rt = processBinaryExpression(tokens, "ne", new Func2<CommonExpression, CommonExpression, CommonExpression>() { public CommonExpression apply(CommonExpression lhs, CommonExpression rhs) { return Expression.ne(lhs, rhs); } }); if (rt != null) { return rt; } // Relational and type testing: lt, gt, le, ge, isof(T) , isof(x,T) rt = processBinaryExpression(tokens, "lt", new Func2<CommonExpression, CommonExpression, CommonExpression>() { public CommonExpression apply(CommonExpression lhs, CommonExpression rhs) { return Expression.lt(lhs, rhs); } }); if (rt != null) { return rt; } rt = processBinaryExpression(tokens, "gt", new Func2<CommonExpression, CommonExpression, CommonExpression>() { public CommonExpression apply(CommonExpression lhs, CommonExpression rhs) { return Expression.gt(lhs, rhs); } }); if (rt != null) { return rt; } rt = processBinaryExpression(tokens, "le", new Func2<CommonExpression, CommonExpression, CommonExpression>() { public CommonExpression apply(CommonExpression lhs, CommonExpression rhs) { return Expression.le(lhs, rhs); } }); if (rt != null) { return rt; } rt = processBinaryExpression(tokens, "ge", new Func2<CommonExpression, CommonExpression, CommonExpression>() { public CommonExpression apply(CommonExpression lhs, CommonExpression rhs) { return Expression.ge(lhs, rhs); } }); if (rt != null) { return rt; } // Additive: add, sub rt = processBinaryExpression(tokens, "add", new Func2<CommonExpression, CommonExpression, CommonExpression>() { public CommonExpression apply(CommonExpression lhs, CommonExpression rhs) { return Expression.add(lhs, rhs); } }); if (rt != null) { return rt; } rt = processBinaryExpression(tokens, "sub", new Func2<CommonExpression, CommonExpression, CommonExpression>() { public CommonExpression apply(CommonExpression lhs, CommonExpression rhs) { return Expression.sub(lhs, rhs); } }); if (rt != null) { return rt; } // Multiplicative: mul, div, mod rt = processBinaryExpression(tokens, "mul", new Func2<CommonExpression, CommonExpression, CommonExpression>() { public CommonExpression apply(CommonExpression lhs, CommonExpression rhs) { return Expression.mul(lhs, rhs); } }); if (rt != null) { return rt; } rt = processBinaryExpression(tokens, "div", new Func2<CommonExpression, CommonExpression, CommonExpression>() { public CommonExpression apply(CommonExpression lhs, CommonExpression rhs) { return Expression.div(lhs, rhs); } }); if (rt != null) { return rt; } rt = processBinaryExpression(tokens, "mod", new Func2<CommonExpression, CommonExpression, CommonExpression>() { public CommonExpression apply(CommonExpression lhs, CommonExpression rhs) { return Expression.mod(lhs, rhs); } }); if (rt != null) { return rt; } // Unary: not x, -x, cast(T), cast(x,T) rt = processUnaryExpression(tokens, "not", true, new Func1<CommonExpression, CommonExpression>() { public CommonExpression apply(CommonExpression expression) { return Expression.not(expression); } }); if (rt != null) { return rt; } rt = processUnaryExpression(tokens, "-", false, new Func1<CommonExpression, CommonExpression>() { public CommonExpression apply(CommonExpression expression) { return Expression.negate(expression); } }); return rt; } private static String unquote(String singleQuotedValue) { return singleQuotedValue.substring(1, singleQuotedValue.length() - 1).replace("''", "'"); } private static List<Token> trimWhitespace(List<Token> tokens) { int start = 0; while (tokens.get(start).type == TokenType.WHITESPACE) { start++; } int end = tokens.size() - 1; while (tokens.get(end).type == TokenType.WHITESPACE) { end--; } return tokens.subList(start, end + 1); } private static int readDigits(String value, int start) { int rt = start; while (rt < value.length() && Character.isDigit(value.charAt(rt))) { rt++; } return rt; } private static int readWord(String value, int start) { int rt = start; while (rt < value.length() && (Character.isLetterOrDigit(value.charAt(rt)) || value.charAt(rt) == '/' || value.charAt(rt) == '_' || value.charAt(rt) == '-')) { rt++; } return rt; } private static int readQuotedString(String value, int start) { int rt = start; while (value.charAt(rt) != '\'' || (rt < value.length() - 1 && value.charAt(rt + 1) == '\'')) { if (value.charAt(rt) != '\'') { rt++; } else { rt += 2; } } rt++; return rt; } private static int readWhitespace(String value, int start) { int rt = start; while (rt < value.length() && Character.isWhitespace(value.charAt(rt))) { rt++; } return rt; } /** * Tokenクラス. */ public static class Token { /** TokenType. **/ private final TokenType type; /** TokenValue. **/ private final String value; /** * コンストラクタ. * @param type type * @param value value */ public Token(TokenType type, String value) { this.type = type; this.value = value; } @Override public String toString() { return "[" + value + "]"; } } /** * ExpressionTokenクラス. */ private static class ExpressionToken extends Token { private final CommonExpression expression; private final List<Token> tokens; ExpressionToken(CommonExpression expression, List<Token> tokens) { super(TokenType.EXPRESSION, null); this.expression = expression; this.tokens = tokens; } @Override public String toString() { return Enumerable.create(tokens).join(""); } } }