You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by ch...@apache.org on 2016/01/12 14:08:23 UTC

[22/30] olingo-odata4 git commit: [OLINGO-834] $expand parser in Java + clean-up

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/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 f505a21..3b673a6 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
@@ -40,6 +40,15 @@ public class UriTokenizer {
     ROOT,
     IT,
 
+    EXPAND,
+    FILTER,
+    LEVELS,
+    ORDERBY,
+    SEARCH,
+    SELECT,
+    SKIP,
+    TOP,
+
     ANY,
     ALL,
 
@@ -53,8 +62,9 @@ public class UriTokenizer {
     EQ,
     STAR,
     PLUS,
-    MINUS,
+
     NULL,
+    MAX,
 
     // variable-value tokens (convention: mixed case)
     ODataIdentifier,
@@ -76,6 +86,13 @@ public class UriTokenizer {
 
     jsonArrayOrObject,
 
+    Word,
+    Phrase,
+
+    OrOperatorSearch,
+    AndOperatorSearch,
+    NotOperatorSearch,
+
     OrOperator,
     AndOperator,
     EqualsOperator,
@@ -90,6 +107,7 @@ public class UriTokenizer {
     MulOperator,
     DivOperator,
     ModOperator,
+    MinusOperator,
     NotOperator,
 
     CastMethod,
@@ -161,6 +179,10 @@ public class UriTokenizer {
     boolean found = false;
     final int previousIndex = index;
     switch (allowedTokenKind) {
+    case EOF:
+      found = index >= parseString.length();
+      break;
+
     // Constants
     case REF:
       found = nextConstant("$ref");
@@ -181,6 +203,31 @@ public class UriTokenizer {
       found = nextConstant("$it");
       break;
 
+    case EXPAND:
+      found = nextConstant("$expand");
+      break;
+    case FILTER:
+      found = nextConstant("$filter");
+      break;
+    case LEVELS:
+      found = nextConstant("$levels");
+      break;
+    case ORDERBY:
+      found = nextConstant("$orderby");
+      break;
+    case SEARCH:
+      found = nextConstant("$search");
+      break;
+    case SELECT:
+      found = nextConstant("$select");
+      break;
+    case SKIP:
+      found = nextConstant("$skip");
+      break;
+    case TOP:
+      found = nextConstant("$top");
+      break;
+
     case ANY:
       found = nextConstant("any");
       break;
@@ -218,14 +265,12 @@ public class UriTokenizer {
     case PLUS:
       found = nextCharacter('+');
       break;
-    case MINUS:
-      found = nextMinus();
-      break;
+
     case NULL:
       found = nextConstant("null");
       break;
-    case EOF:
-      found = index >= parseString.length();
+    case MAX:
+      found = nextConstant("max");
       break;
 
     // Identifiers
@@ -282,6 +327,25 @@ public class UriTokenizer {
       found = nextJsonArrayOrObject();
       break;
 
+    // Search
+    case Word:
+      found = nextWord();
+      break;
+    case Phrase:
+      found = nextPhrase();
+      break;
+
+    // Operators in Search Expressions
+    case OrOperatorSearch:
+      found = nextBinaryOperator("OR");
+      break;
+    case AndOperatorSearch:
+      found = nextAndOperatorSearch();
+      break;
+    case NotOperatorSearch:
+      found = nextUnaryOperator("NOT");
+      break;
+
     // Operators
     case OrOperator:
       found = nextBinaryOperator("or");
@@ -325,8 +389,12 @@ public class UriTokenizer {
     case ModOperator:
       found = nextBinaryOperator("mod");
       break;
+    case MinusOperator:
+      // To avoid unnecessary minus operators for negative numbers, we have to check what follows the minus sign.
+      found = nextCharacter('-') && !nextDigit() && !nextConstant("INF");
+      break;
     case NotOperator:
-      found = nextConstant("not") && nextWhitespace();
+      found = nextUnaryOperator("not");
       break;
 
     // Methods
@@ -444,27 +512,6 @@ public class UriTokenizer {
     return found;
   }
 
-  private boolean nextMinus() {
-    if(parseString.startsWith("-", index)) {
-      final int lastGoodIndex = index;
-      
-      if(nextDoubleValue()) {
-        index = lastGoodIndex;
-        return false;
-      } else if(nextDecimalValue()) {
-        index = lastGoodIndex;
-        return false;
-      } else if(nextIntegerValue(true)) {
-        index = lastGoodIndex;
-        return false;
-      } else {
-        index++;
-        return true;
-      }
-    }
-    return false;
-  }
-
   /**
    * Moves past the given string constant if found; otherwise leaves the index unchanged.
    * @return whether the constant has been found at the current index
@@ -569,34 +616,6 @@ public class UriTokenizer {
     }
     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 (required) whitespace and the given suffix name if found;
-   * otherwise leaves the index unchanged.
-   * @return whether the suffix has been found at the current index
-   */
-  private boolean nextSuffix(final String suffixName) {
-    return nextWhitespace() && nextConstant(suffixName);
-  }
-
   /**
    * Moves past an OData identifier if found; otherwise leaves the index unchanged.
    * @return whether an OData identifier has been found at the current index
@@ -650,6 +669,38 @@ public class UriTokenizer {
     }
   }
 
+  /**
+   * Moves past the given whitespace-surrounded operator constant if found.
+   * @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 whitespace-suffixed operator constant if found.
+   * @return whether the operator has been found at the current index
+   */
+  private boolean nextUnaryOperator(final String operator) {
+    return nextConstant(operator) && nextWhitespace();
+  }
+
+  /**
+   * Moves past the given method name and its immediately following opening parenthesis if found.
+   * @return whether the method has been found at the current index
+   */
+  private boolean nextMethod(final String methodName) {
+    return nextConstant(methodName) && nextCharacter('(');
+  }
+
+  /**
+   * Moves past (required) whitespace and the given suffix name if found.
+   * @return whether the suffix has been found at the current index
+   */
+  private boolean nextSuffix(final String suffixName) {
+    return nextWhitespace() && nextConstant(suffixName);
+  }
+
   private boolean nextParameterAliasName() {
     return nextCharacter('@') && nextODataIdentifier();
   }
@@ -978,4 +1029,52 @@ public class UriTokenizer {
       return false;
     }
   }
+
+  private boolean nextAndOperatorSearch() {
+    if (nextWhitespace()) {
+      final int lastGoodIndex = index;
+      if (nextUnaryOperator("OR")) {
+        return false;
+      } else if (!(nextUnaryOperator("AND"))) {
+        index = lastGoodIndex;
+      }
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  private boolean nextWord() {
+    int count = 0;
+    while (index < parseString.length()) {
+      final int code = parseString.codePointAt(index);
+      if (Character.isUnicodeIdentifierStart(code)) {
+        count++;
+        // Unicode characters outside of the Basic Multilingual Plane are represented as two Java characters.
+        index += Character.isSupplementaryCodePoint(code) ? 2 : 1;
+      } else {
+        break;
+      }
+    }
+    final String word = parseString.substring(index - count, index);
+    return count > 0 && !("OR".equals(word) || "AND".equals(word) || "NOT".equals(word));
+  }
+
+  private boolean nextPhrase() {
+    if (nextCharacter('"')) {
+      do {
+        if (nextCharacter('\\')) {
+          if (!(nextCharacter('\\') || nextCharacter('"'))) {
+            return false;
+          }
+        } else if (nextCharacter('"')) {
+          return true;
+        } else {
+          index++;
+        }
+      } while (index < parseString.length());
+      return false;
+    }
+    return false;
+  }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/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 c7d7c20..8c8dab9 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
@@ -41,4 +41,8 @@ public class AliasImpl implements Alias {
     return visitor.visitAlias(parameterName);
   }
 
+  @Override
+  public String toString() {
+    return parameterName;
+  }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/EnumerationImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/EnumerationImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/EnumerationImpl.java
index 256b8d1..a238104 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/EnumerationImpl.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/EnumerationImpl.java
@@ -53,4 +53,10 @@ public class EnumerationImpl implements Enumeration {
   public <T> T accept(final ExpressionVisitor<T> visitor) throws ExpressionVisitException, ODataApplicationException {
     return visitor.visitEnum(type, values);
   }
+
+  @Override
+  public String toString() {
+    return type == null ? null :
+      type.getFullQualifiedName().getFullQualifiedNameAsString() + getValues();
+  }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/LambdaRefImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/LambdaRefImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/LambdaRefImpl.java
index 824943a..16232b8 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/LambdaRefImpl.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/LambdaRefImpl.java
@@ -40,4 +40,9 @@ public class LambdaRefImpl implements LambdaRef {
   public <T> T accept(final ExpressionVisitor<T> visitor) throws ExpressionVisitException, ODataApplicationException {
     return visitor.visitLambdaReference(variableText);
   }
+
+  @Override
+  public String toString() {
+    return variableText;
+  }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/TypeLiteralImpl.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/TypeLiteralImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/TypeLiteralImpl.java
index 336c203..6a2a1c6 100644
--- a/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/TypeLiteralImpl.java
+++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/uri/queryoption/expression/TypeLiteralImpl.java
@@ -41,4 +41,9 @@ public class TypeLiteralImpl implements TypeLiteral {
   public <T> T accept(final ExpressionVisitor<T> visitor) throws ExpressionVisitException, ODataApplicationException {
     return visitor.visitTypeLiteral(type);
   }
+
+  @Override
+  public String toString() {
+    return type == null ? null : type.getFullQualifiedName().getFullQualifiedNameAsString();
+  }
 }

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
----------------------------------------------------------------------
diff --git a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
index 0b43f70..e178fed 100644
--- a/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
+++ b/lib/server-core/src/main/resources/server-core-exceptions-i18n.properties
@@ -81,11 +81,11 @@ UriParserSemanticException.COMPLEX_PROPERTY_OF_ENTITY_TYPE_EXPECTED=A complex pr
 UriParserSemanticException.NOT_FOR_ENTITY_TYPE=Not allowed for entity type.
 UriParserSemanticException.PREVIOUS_PART_TYPED=The previous part is typed.
 UriParserSemanticException.RESOURCE_NOT_FOUND=Cannot find EntitySet, Singleton, ActionImport or FunctionImport with name '%1$s'.
-UriParserSemanticException.NOT_IMPLEMENTED=%1$s is not implemented!
-UriParserSemanticException.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT=Namespace is not allowed for Entity Sets, Singeltons, Action Imports and Function Imports. Found '%1$s'.
-UriParserSemanticException.COMPLEX_PARAMETER_IN_RESOURCE_PATH=Complex parameters must not appear in resource path segments. Found: '%1$s'.
+UriParserSemanticException.NOT_IMPLEMENTED='%1$s' is not implemented!
+UriParserSemanticException.NAMESPACE_NOT_ALLOWED_AT_FIRST_ELEMENT=Namespace is not allowed for Entity Sets, Singletons, Action Imports and Function Imports; found '%1$s'.
+UriParserSemanticException.COMPLEX_PARAMETER_IN_RESOURCE_PATH=Complex parameters must not appear in resource path segments; found: '%1$s'.
 UriParserSemanticException.FUNCTION_IMPORT_NOT_ALLOWED=Function Imports are not allowed in $filter or $orderby. Found: '%1$s'.
-UriParserSemanticException.TYPES_NOT_COMPATIBLE=Types are not compatible. Left type: '%1$s', right type: '%1$s'.
+UriParserSemanticException.TYPES_NOT_COMPATIBLE=The types '%1$s' and '%2$s' are not compatible.
 
 UriValidationException.UNSUPPORTED_QUERY_OPTION=The query option '%1$s' is not supported.
 UriValidationException.UNSUPPORTED_URI_KIND=The URI kind '%1$s' is not supported.

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/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 4ab7fce..94d5373 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
@@ -22,9 +22,11 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
 
 import java.util.Locale;
 
+import org.apache.olingo.commons.api.edm.Edm;
 import org.apache.olingo.server.api.OData;
 import org.apache.olingo.server.api.uri.queryoption.expression.Expression;
 import org.apache.olingo.server.core.uri.parser.UriTokenizer.TokenKind;
@@ -260,9 +262,17 @@ public class ExpressionParserTest {
     expression = parseMethod(TokenKind.SubstringMethod, "'abc'", "1");
     assertEquals("{substring ['abc', 1]}", expression.toString());
 
+    assertEquals("{cast [Edm.SByte]}", parseMethod(TokenKind.CastMethod, "Edm.SByte").toString());
+    assertEquals("{cast [42, Edm.SByte]}", parseMethod(TokenKind.CastMethod, "42", "Edm.SByte").toString());
+
+    assertEquals("{isof [Edm.SByte]}", parseMethod(TokenKind.IsofMethod, "Edm.SByte").toString());
+    assertEquals("{isof [42, Edm.SByte]}", parseMethod(TokenKind.IsofMethod, "42", "Edm.SByte").toString());
+
     wrongExpression("substring('abc')");
     wrongExpression("substring('abc',1,2,3)");
     wrongExpression("substring(1,2)");
+    wrongExpression("cast(1,2)");
+    wrongExpression("isof(Edm.Int16,2)");
   }
 
   private Expression parseMethod(TokenKind kind, String... parameters)
@@ -288,7 +298,7 @@ public class ExpressionParserTest {
   private Expression parseExpression(final String expressionString)
       throws UriParserException, UriValidationException {
     UriTokenizer tokenizer = new UriTokenizer(expressionString);
-    Expression expression = new ExpressionParser(null, odata).parse(tokenizer, null, null);
+    Expression expression = new ExpressionParser(mock(Edm.class), odata).parse(tokenizer, null, null);
     assertNotNull(expression);
     assertTrue(tokenizer.next(TokenKind.EOF));
     return expression;
@@ -296,7 +306,7 @@ public class ExpressionParserTest {
 
   private void wrongExpression(final String expressionString) {
     try {
-      new ExpressionParser(null, odata).parse(new UriTokenizer(expressionString), null, null);
+      new ExpressionParser(mock(Edm.class), odata).parse(new UriTokenizer(expressionString), null, null);
       fail("Expected exception not thrown.");
     } catch (final UriParserException e) {
       assertNotNull(e);

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java
index af45e80..e130457 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/UriTokenizerTest.java
@@ -76,7 +76,7 @@ public class UriTokenizerTest {
     assertTrue(tokenizer.next(TokenKind.STAR));
     assertTrue(tokenizer.next(TokenKind.SLASH));
     assertTrue(tokenizer.next(TokenKind.PLUS));
-    assertTrue(tokenizer.next(TokenKind.MINUS));
+    assertTrue(tokenizer.next(TokenKind.MinusOperator));
     assertTrue(tokenizer.next(TokenKind.EOF));
 
     tokenizer = new UriTokenizer("any(a:true) or all(b:false)");
@@ -97,6 +97,45 @@ public class UriTokenizerTest {
   }
 
   @Test
+  public void systemQueryOptions() {
+    UriTokenizer tokenizer = new UriTokenizer("$expand=*;$filter=true;$levels=max;$orderby=false");
+    assertTrue(tokenizer.next(TokenKind.EXPAND));
+    assertTrue(tokenizer.next(TokenKind.EQ));
+    assertTrue(tokenizer.next(TokenKind.STAR));
+    assertTrue(tokenizer.next(TokenKind.SEMI));
+    assertTrue(tokenizer.next(TokenKind.FILTER));
+    assertTrue(tokenizer.next(TokenKind.EQ));
+    assertTrue(tokenizer.next(TokenKind.BooleanValue));
+    assertTrue(tokenizer.next(TokenKind.SEMI));
+    assertTrue(tokenizer.next(TokenKind.LEVELS));
+    assertTrue(tokenizer.next(TokenKind.EQ));
+    assertTrue(tokenizer.next(TokenKind.MAX));
+    assertTrue(tokenizer.next(TokenKind.SEMI));
+    assertTrue(tokenizer.next(TokenKind.ORDERBY));
+    assertTrue(tokenizer.next(TokenKind.EQ));
+    assertTrue(tokenizer.next(TokenKind.BooleanValue));
+    assertTrue(tokenizer.next(TokenKind.EOF));
+
+    tokenizer = new UriTokenizer("$search=A;$select=*;$skip=1;$top=2");
+    assertTrue(tokenizer.next(TokenKind.SEARCH));
+    assertTrue(tokenizer.next(TokenKind.EQ));
+    assertTrue(tokenizer.next(TokenKind.ODataIdentifier));
+    assertTrue(tokenizer.next(TokenKind.SEMI));
+    assertTrue(tokenizer.next(TokenKind.SELECT));
+    assertTrue(tokenizer.next(TokenKind.EQ));
+    assertTrue(tokenizer.next(TokenKind.STAR));
+    assertTrue(tokenizer.next(TokenKind.SEMI));
+    assertTrue(tokenizer.next(TokenKind.SKIP));
+    assertTrue(tokenizer.next(TokenKind.EQ));
+    assertTrue(tokenizer.next(TokenKind.IntegerValue));
+    assertTrue(tokenizer.next(TokenKind.SEMI));
+    assertTrue(tokenizer.next(TokenKind.TOP));
+    assertTrue(tokenizer.next(TokenKind.EQ));
+    assertTrue(tokenizer.next(TokenKind.IntegerValue));
+    assertTrue(tokenizer.next(TokenKind.EOF));
+  }
+
+  @Test
   public void identifier() {
     assertTrue(new UriTokenizer("name").next(TokenKind.ODataIdentifier));
     assertTrue(new UriTokenizer("_name").next(TokenKind.ODataIdentifier));
@@ -390,11 +429,11 @@ public class UriTokenizerTest {
     assertTrue(tokenizer.next(TokenKind.IntegerValue));
     assertTrue(tokenizer.next(TokenKind.EOF));
 
-    tokenizer = new UriTokenizer("1ne 2");
+    tokenizer = new UriTokenizer("-1ne 2");
     assertTrue(tokenizer.next(TokenKind.IntegerValue));
     assertFalse(tokenizer.next(TokenKind.NotEqualsOperator));
 
-    tokenizer = new UriTokenizer("1 ne2");
+    tokenizer = new UriTokenizer("1 ne-2");
     assertTrue(tokenizer.next(TokenKind.IntegerValue));
     assertFalse(tokenizer.next(TokenKind.NotEqualsOperator));
 
@@ -404,6 +443,11 @@ public class UriTokenizerTest {
     assertTrue(tokenizer.next(TokenKind.IntegerValue));
     assertTrue(tokenizer.next(TokenKind.EOF));
 
+    assertTrue(new UriTokenizer("-x").next(TokenKind.MinusOperator));
+    assertFalse(new UriTokenizer("-1").next(TokenKind.MinusOperator));
+    assertFalse(new UriTokenizer("-INF").next(TokenKind.MinusOperator));
+    assertFalse(new UriTokenizer("+").next(TokenKind.MinusOperator));
+
     assertFalse(new UriTokenizer("nottrue").next(TokenKind.NotOperator));
     assertFalse(new UriTokenizer("no true").next(TokenKind.NotOperator));
 
@@ -484,6 +528,38 @@ public class UriTokenizerTest {
     wrongToken(TokenKind.DescSuffix, " desc", 'D');
   }
 
+  @Test
+  public void search() {
+    UriTokenizer tokenizer = new UriTokenizer("a AND b OR NOT \"c\" d");
+    assertTrue(tokenizer.next(TokenKind.Word));
+    assertTrue(tokenizer.next(TokenKind.AndOperatorSearch));
+    assertTrue(tokenizer.next(TokenKind.Word));
+    assertFalse(tokenizer.next(TokenKind.AndOperatorSearch));
+    assertTrue(tokenizer.next(TokenKind.OrOperatorSearch));
+    assertTrue(tokenizer.next(TokenKind.NotOperatorSearch));
+    assertTrue(tokenizer.next(TokenKind.Phrase));
+    assertTrue(tokenizer.next(TokenKind.AndOperatorSearch));
+    assertTrue(tokenizer.next(TokenKind.Word));
+    assertFalse(tokenizer.next(TokenKind.AndOperatorSearch));
+    assertFalse(tokenizer.next(TokenKind.Word));
+    assertFalse(tokenizer.next(TokenKind.Phrase));
+    assertTrue(tokenizer.next(TokenKind.EOF));
+
+    assertTrue(new UriTokenizer("\"a\\\\x\\\"\"").next(TokenKind.Phrase));
+    assertFalse(new UriTokenizer("\"a\\\"").next(TokenKind.Phrase));
+    assertFalse(new UriTokenizer("\"a\\x\"").next(TokenKind.Phrase));
+    wrongToken(TokenKind.Phrase, "\"a\"", '\\');
+
+    final String outsideBmpLetter = String.valueOf(Character.toChars(0x10330));
+    assertTrue(new UriTokenizer("\"" + outsideBmpLetter + "\"").next(TokenKind.Phrase));
+
+    assertTrue(new UriTokenizer(outsideBmpLetter).next(TokenKind.Word));
+    assertFalse(new UriTokenizer("1").next(TokenKind.Word));
+    assertFalse(new UriTokenizer("AND").next(TokenKind.Word));
+    assertFalse(new UriTokenizer("OR").next(TokenKind.Word));
+    assertFalse(new UriTokenizer("NOT").next(TokenKind.Word));
+  }
+
   private void wrongToken(final TokenKind kind, final String value, final char disturbCharacter) {
     assertFalse(new UriTokenizer(disturbCharacter + value).next(kind));
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/8925274c/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchParserAndTokenizerTest.java
----------------------------------------------------------------------
diff --git a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchParserAndTokenizerTest.java b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchParserAndTokenizerTest.java
index e028cfe..f3e50a2 100644
--- a/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchParserAndTokenizerTest.java
+++ b/lib/server-core/src/test/java/org/apache/olingo/server/core/uri/parser/search/SearchParserAndTokenizerTest.java
@@ -35,7 +35,7 @@ public class SearchParserAndTokenizerTest {
     assertQuery("a AND b AND c").resultsIn("{{'a' AND 'b'} AND 'c'}");
     assertQuery("a OR b").resultsIn("{'a' OR 'b'}");
     assertQuery("a OR b OR c").resultsIn("{{'a' OR 'b'} OR 'c'}");
-    
+
     assertQuery("NOT a NOT b").resultsIn("{{NOT 'a'} AND {NOT 'b'}}");
     assertQuery("NOT a AND NOT b").resultsIn("{{NOT 'a'} AND {NOT 'b'}}");
     assertQuery("NOT a OR NOT b").resultsIn("{{NOT 'a'} OR {NOT 'b'}}");
@@ -59,16 +59,16 @@ public class SearchParserAndTokenizerTest {
     assertQuery("a AND (b OR c)").resultsIn("{'a' AND {'b' OR 'c'}}");
     assertQuery("(a OR b) AND NOT c").resultsIn("{{'a' OR 'b'} AND {NOT 'c'}}");
     assertQuery("(a OR B) AND (c OR d AND NOT e OR (f))")
-            .resultsIn("{{'a' OR 'B'} AND {{'c' OR {'d' AND {NOT 'e'}}} OR 'f'}}");
+        .resultsIn("{{'a' OR 'B'} AND {{'c' OR {'d' AND {NOT 'e'}}} OR 'f'}}");
     assertQuery("(a OR B) (c OR d NOT e OR (f))")
-      .resultsIn("{{'a' OR 'B'} AND {{'c' OR {'d' AND {NOT 'e'}}} OR 'f'}}");
+        .resultsIn("{{'a' OR 'B'} AND {{'c' OR {'d' AND {NOT 'e'}}} OR 'f'}}");
     assertQuery("((((a))))").resultsIn("'a'");
     assertQuery("((((a)))) ((((a))))").resultsIn("{'a' AND 'a'}");
     assertQuery("((((a)))) OR ((((a))))").resultsIn("{'a' OR 'a'}");
     assertQuery("((((((a)))) ((((c))) OR (((C)))) ((((a))))))").resultsIn("{{'a' AND {'c' OR 'C'}} AND 'a'}");
     assertQuery("((((\"a\")))) OR ((((\"a\"))))").resultsIn("{'a' OR 'a'}");
   }
-  
+
   @Test
   public void parseImplicitAnd() throws Exception {
     assertQuery("a b").resultsIn("{'a' AND 'b'}");
@@ -103,7 +103,7 @@ public class SearchParserAndTokenizerTest {
     assertQuery("((a AND b OR c)").resultsIn(SearchParserException.MessageKeys.MISSING_CLOSE);
     assertQuery("a AND (b OR c").resultsIn(SearchParserException.MessageKeys.MISSING_CLOSE);
     assertQuery("(a AND ((b OR c)").resultsIn(SearchParserException.MessageKeys.MISSING_CLOSE);
-    
+
     assertQuery("NOT NOT a").resultsIn(SearchParserException.MessageKeys.INVALID_NOT_OPERAND);
     assertQuery("NOT (a)").resultsIn(SearchParserException.MessageKeys.TOKENIZER_EXCEPTION);
   }
@@ -186,7 +186,6 @@ public class SearchParserAndTokenizerTest {
     //    <Input>http://serviceRoot/Products?$search=blue</Input>
     assertQuery("blue").resultsIn("'blue'");
 
-
     // below cases can not be tested here
     //    <TestCase Name="5.1.7 Search - on entity container" Rule="odataUri">
     //    <Input>http://serviceRoot/Model.Container/$all?$search=blue</Input>
@@ -194,68 +193,47 @@ public class SearchParserAndTokenizerTest {
     //    <Input>http://serviceRoot/$all?$search=blue</Input>
   }
 
-
   private static Validator assertQuery(String searchQuery) {
-    return Validator.init(searchQuery);
+    return new Validator(searchQuery);
   }
 
   private static class Validator {
-    private boolean log;
     private final String searchQuery;
 
     private Validator(String searchQuery) {
       this.searchQuery = searchQuery;
     }
 
-    private static Validator init(String searchQuery) {
-      return new Validator(searchQuery);
-    }
-
-    @SuppressWarnings("unused")
-    private Validator withLogging() {
-      log = true;
-      return this;
-    }
-
-    private void resultsIn(SearchParserException.MessageKey key)
-            throws SearchTokenizerException {
+    private void resultsIn(SearchParserException.MessageKey key) throws SearchTokenizerException {
       try {
         resultsIn(searchQuery);
       } catch (SearchParserException e) {
         Assert.assertEquals("SearchParserException with unexpected message '" + e.getMessage() +
             "' was thrown.", key, e.getMessageKey());
-        if(log) {
-          System.out.println("Caught SearchParserException with message key " +
-              e.getMessageKey() + " and message " + e.getMessage());
-        }
         return;
       }
       Assert.fail("SearchParserException with message key " + key.getKey() + " was not thrown.");
     }
-    
+
     public void resultsInExpectedTerm(final String actualToken) throws SearchTokenizerException {
       try {
         resultsIn(searchQuery);
-      } catch(SearchParserException e) {
+      } catch (SearchParserException e) {
         Assert.assertEquals(SearchParserException.MessageKeys.EXPECTED_DIFFERENT_TOKEN, e.getMessageKey());
         Assert.assertEquals("Expected PHRASE||WORD found: " + actualToken, e.getMessage());
       }
     }
-    
+
     private void resultsIn(String expectedSearchExpression) throws SearchTokenizerException, SearchParserException {
       final SearchExpression searchExpression = getSearchExpression();
       Assert.assertEquals(expectedSearchExpression, searchExpression.toString());
     }
 
     private SearchExpression getSearchExpression() throws SearchParserException, SearchTokenizerException {
-      SearchParser tokenizer = new SearchParser();
-      SearchOption result = tokenizer.parse(searchQuery);
+      SearchOption result = new SearchParser().parse(searchQuery);
       Assert.assertNotNull(result);
       final SearchExpression searchExpression = result.getSearchExpression();
       Assert.assertNotNull(searchExpression);
-      if (log) {
-        System.out.println(searchExpression);
-      }
       return searchExpression;
     }
   }