[OLINGO-568] SearchParser negative tests

This commit is contained in:
Christian Amend 2015-11-13 14:56:29 +01:00
parent cef72e45ab
commit 8457c0f60a
6 changed files with 307 additions and 133 deletions

View File

@ -6,9 +6,9 @@
* 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
@ -21,6 +21,7 @@ package org.apache.olingo.server.core.uri.parser.search;
import org.apache.olingo.server.api.uri.queryoption.SearchOption;
import org.apache.olingo.server.api.uri.queryoption.search.SearchBinaryOperatorKind;
import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression;
import org.apache.olingo.server.core.uri.parser.search.SearchQueryToken.Token;
import org.apache.olingo.server.core.uri.queryoption.SearchOptionImpl;
import java.util.Iterator;
@ -31,13 +32,11 @@ public class SearchParser {
private Iterator<SearchQueryToken> tokens;
private SearchQueryToken token;
public SearchOption parse(String path, String value) {
public SearchOption parse(String path, String value) throws SearchParserException, SearchTokenizerException {
SearchTokenizer tokenizer = new SearchTokenizer();
SearchExpression searchExpression;
try {
tokens = tokenizer.tokenize(value).iterator();
nextToken();
searchExpression = processSearchExpression(null);
searchExpression = parseInternal(tokenizer.tokenize(value));
} catch (SearchTokenizerException e) {
return null;
}
@ -46,32 +45,45 @@ public class SearchParser {
return searchOption;
}
protected SearchExpression parseInternal(List<SearchQueryToken> tokens) {
protected SearchExpression parseInternal(List<SearchQueryToken> tokens) throws SearchParserException {
this.tokens = tokens.iterator();
nextToken();
if (token == null) {
throw new SearchParserException("No search String", SearchParserException.MessageKeys.NO_EXPRESSION_FOUND);
}
return processSearchExpression(null);
}
private SearchExpression processSearchExpression(SearchExpression left) {
if(token == null) {
private SearchExpression processSearchExpression(SearchExpression left) throws SearchParserException {
if (token == null) {
return left;
}
if (left == null && (isToken(SearchQueryToken.Token.AND) || isToken(SearchQueryToken.Token.OR))) {
throw new SearchParserException(token.getToken() + " needs a left operand.",
SearchParserException.MessageKeys.INVALID_BINARY_OPERATOR_POSITION, token.getToken().toString());
}
SearchExpression expression = left;
if(isToken(SearchQueryToken.Token.OPEN)) {
if (isToken(SearchQueryToken.Token.OPEN)) {
processOpen();
expression = processSearchExpression(left);
validateToken(SearchQueryToken.Token.CLOSE);
processClose();
} else if(isTerm()) {
} else if (isTerm()) {
expression = processTerm();
}
if(isToken(SearchQueryToken.Token.AND) || isTerm()) {
expression = processAnd(expression);
} else if(isToken(SearchQueryToken.Token.OR)) {
expression = processOr(expression);
} else if(isEof()) {
if (expression == null) {
throw new SearchParserException("Brackets must contain an expression.",
SearchParserException.MessageKeys.NO_EXPRESSION_FOUND);
}
if (isToken(SearchQueryToken.Token.AND) || isToken(SearchQueryToken.Token.OPEN) || isTerm()) {
expression = processAnd(expression);
} else if (isToken(SearchQueryToken.Token.OR)) {
expression = processOr(expression);
} else if (isEof()) {
return expression;
}
return expression;
@ -88,15 +100,17 @@ public class SearchParser {
}
private boolean isToken(SearchQueryToken.Token toCheckToken) {
if(token == null) {
if (token == null) {
return false;
}
return token.getToken() == toCheckToken;
}
private void validateToken(SearchQueryToken.Token toValidateToken) {
if(!isToken(toValidateToken)) {
throw illegalState();
private void validateToken(SearchQueryToken.Token toValidateToken) throws SearchParserException {
if (!isToken(toValidateToken)) {
String actualToken = token == null ? "null" : token.getToken().toString();
throw new SearchParserException("Expected " + toValidateToken + " but was " + actualToken,
SearchParserException.MessageKeys.EXPECTED_DIFFERENT_TOKEN, toValidateToken.toString(), actualToken);
}
}
@ -108,23 +122,27 @@ public class SearchParser {
nextToken();
}
private SearchExpression processAnd(SearchExpression left) {
if(isToken(SearchQueryToken.Token.AND)) {
private SearchExpression processAnd(SearchExpression left) throws SearchParserException {
if (isToken(SearchQueryToken.Token.AND)) {
nextToken();
}
SearchExpression se = left;
if(isTerm()) {
if (isTerm()) {
se = processTerm();
se = new SearchBinaryImpl(left, SearchBinaryOperatorKind.AND, se);
return processSearchExpression(se);
} else {
if (isToken(SearchQueryToken.Token.AND) || isToken(SearchQueryToken.Token.OR)) {
throw new SearchParserException("Operators must not be followed by an AND or an OR",
SearchParserException.MessageKeys.INVALID_OPERATOR_AFTER_AND, token.getToken().toString());
}
se = processSearchExpression(se);
return new SearchBinaryImpl(left, SearchBinaryOperatorKind.AND, se);
}
}
public SearchExpression processOr(SearchExpression left) {
if(isToken(SearchQueryToken.Token.OR)) {
public SearchExpression processOr(SearchExpression left) throws SearchParserException {
if (isToken(SearchQueryToken.Token.OR)) {
nextToken();
}
SearchExpression se = processSearchExpression(left);
@ -135,30 +153,31 @@ public class SearchParser {
return new RuntimeException();
}
private SearchExpression processNot() {
private SearchExpression processNot() throws SearchParserException {
nextToken();
SearchExpression searchExpression = processTerm();
if(searchExpression.isSearchTerm()) {
if (isToken(Token.WORD) || isToken(Token.PHRASE)) {
SearchExpression searchExpression = processTerm();
return new SearchUnaryImpl(searchExpression.asSearchTerm());
}
throw illegalState();
throw new SearchParserException("NOT must be followed by a term not a " + token.getToken(),
SearchParserException.MessageKeys.INVALID_NOT_OPERAND, token.getToken().toString());
}
private void nextToken() {
if(tokens.hasNext()) {
token = tokens.next();
if (tokens.hasNext()) {
token = tokens.next();
} else {
token = null;
}
}
private SearchExpression processTerm() {
if(isToken(SearchQueryToken.Token.NOT)) {
private SearchExpression processTerm() throws SearchParserException {
if (isToken(SearchQueryToken.Token.NOT)) {
return processNot();
}
if(isToken(SearchQueryToken.Token.PHRASE)) {
if (isToken(SearchQueryToken.Token.PHRASE)) {
return processPhrase();
} else if(isToken(SearchQueryToken.Token.WORD)) {
} else if (isToken(SearchQueryToken.Token.WORD)) {
return processWord();
}
throw illegalState();

View File

@ -0,0 +1,54 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.olingo.server.core.uri.parser.search;
import org.apache.olingo.server.core.uri.parser.UriParserSyntaxException;
public class SearchParserException extends UriParserSyntaxException {
private static final long serialVersionUID = 5781553037561337795L;
public static enum MessageKeys implements MessageKey {
/** parameter: operatorkind */
INVALID_BINARY_OPERATOR_POSITION,
/** parameter: operatorkind */
INVALID_NOT_OPERAND,
/** parameters: expectedToken actualToken */
EXPECTED_DIFFERENT_TOKEN,
NO_EXPRESSION_FOUND,
/** parameter: operatorkind */
INVALID_OPERATOR_AFTER_AND;
@Override
public String getKey() {
return name();
}
}
public SearchParserException(final String developmentMessage, final MessageKey messageKey,
final String... parameters) {
super(developmentMessage, messageKey, parameters);
}
public SearchParserException(final String developmentMessage, final Throwable cause, final MessageKey messageKey,
final String... parameters) {
super(developmentMessage, cause, messageKey, parameters);
}
}

View File

@ -6,9 +6,9 @@
* 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
@ -24,18 +24,18 @@ import java.util.List;
/**
* <code>
* searchExpr = ( OPEN BWS searchExpr BWS CLOSE
* / searchTerm
* ) [ searchOrExpr
* / searchAndExpr
* ]
* / searchTerm
* ) [ searchOrExpr
* / searchAndExpr
* ]
*
* searchOrExpr = RWS 'OR' RWS searchExpr
* searchAndExpr = RWS [ 'AND' RWS ] searchExpr
* searchOrExpr = RWS 'OR' RWS searchExpr
* searchAndExpr = RWS [ 'AND' RWS ] searchExpr
*
* searchTerm = [ 'NOT' RWS ] ( searchPhrase / searchWord )
* searchPhrase = quotation-mark 1*qchar-no-AMP-DQUOTE quotation-mark
* searchWord = 1*ALPHA ; Actually: any character from the Unicode categories L or Nl,
* ; but not the words AND, OR, and NOT
* searchTerm = [ 'NOT' RWS ] ( searchPhrase / searchWord )
* searchPhrase = quotation-mark 1*qchar-no-AMP-DQUOTE quotation-mark
* searchWord = 1*ALPHA ; Actually: any character from the Unicode categories L or Nl,
* ; but not the words AND, OR, and NOT
* </code>
*/
public class SearchTokenizer {
@ -65,7 +65,8 @@ public class SearchTokenizer {
}
public State forbidden(char c) throws SearchTokenizerException {
throw new SearchTokenizerException("Forbidden character for " + this.getClass().getName() + "->" + c);
throw new SearchTokenizerException("Forbidden character for " + this.getClass().getName() + "->" + c,
SearchTokenizerException.MessageKeys.FORBIDDEN_CHARACTER, "" + c);
}
public State finish() {
@ -97,20 +98,20 @@ public class SearchTokenizer {
}
/**
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* other-delims = "!" / "(" / ")" / "*" / "+" / "," / ";"
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* other-delims = "!" / "(" / ")" / "*" / "+" / "," / ";"
* qchar-unescaped = unreserved / pct-encoded-unescaped / other-delims / ":" / "@" / "/" / "?" / "$" / "'" / "="
* pct-encoded-unescaped = "%" ( "0" / "1" / "3" / "4" / "6" / "7" / "8" / "9" / A-to-F ) HEXDIG
* / "%" "2" ( "0" / "1" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / A-to-F )
* / "%" "5" ( DIGIT / "A" / "B" / "D" / "E" / "F" )
* pct-encoded-unescaped = "%" ( "0" / "1" / "3" / "4" / "6" / "7" / "8" / "9" / A-to-F ) HEXDIG
* / "%" "2" ( "0" / "1" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / A-to-F )
* / "%" "5" ( DIGIT / "A" / "B" / "D" / "E" / "F" )
*
* qchar-no-AMP-DQUOTE = qchar-unescaped / escape ( escape / quotation-mark )
* qchar-no-AMP-DQUOTE = qchar-unescaped / escape ( escape / quotation-mark )
*
* escape = "\" / "%5C" ; reverse solidus U+005C
* quotation-mark = DQUOTE / "%22"
* escape = "\" / "%5C" ; reverse solidus U+005C
* quotation-mark = DQUOTE / "%22"
*
* ALPHA = %x41-5A / %x61-7A
* DIGIT = %x30-39
* ALPHA = %x41-5A / %x61-7A
* DIGIT = %x30-39
* DQUOTE = %x22
*
* @param character which is checked
@ -137,10 +138,10 @@ public class SearchTokenizer {
|| '0' <= character && character <= '9'; // case 0..9
}
//BWS = *( SP / HTAB / "%20" / "%09" ) ; "bad" whitespace
//RWS = 1*( SP / HTAB / "%20" / "%09" ) ; "required" whitespace
// BWS = *( SP / HTAB / "%20" / "%09" ) ; "bad" whitespace
// RWS = 1*( SP / HTAB / "%20" / "%09" ) ; "required" whitespace
static boolean isWhitespace(final char character) {
//( SP / HTAB / "%20" / "%09" )
// ( SP / HTAB / "%20" / "%09" )
// TODO mibo: add missing whitespaces
return character == ' ' || character == '\t';
}
@ -158,29 +159,35 @@ public class SearchTokenizer {
private static abstract class LiteralState extends State {
protected final StringBuilder literal = new StringBuilder();
public LiteralState(Token t) {
super(t);
}
public LiteralState(Token t, char c) throws SearchTokenizerException {
super(t);
init(c);
}
public LiteralState(Token t, String initLiteral) {
super(t);
literal.append(initLiteral);
}
public State allowed(char c) {
literal.append(c);
return this;
}
@Override
public String getLiteral() {
return literal.toString();
}
public State init(char c) throws SearchTokenizerException {
if(isFinished()) {
throw new SearchTokenizerException(toString() + " is already finished.");
if (isFinished()) {
throw new SearchTokenizerException(toString() + " is already finished.",
SearchTokenizerException.MessageKeys.ALREADY_FINISHED);
}
literal.append(c);
return this;
@ -191,13 +198,14 @@ public class SearchTokenizer {
public SearchExpressionState() {
super(null);
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (c == CHAR_OPEN) {
return new OpenState();
} else if (isWhitespace(c)) {
return new RwsState();
} else if(c == CHAR_CLOSE) {
} else if (c == CHAR_CLOSE) {
return new CloseState();
} else {
return new SearchTermState().init(c);
@ -214,9 +222,10 @@ public class SearchTokenizer {
public SearchTermState() {
super(Token.TERM);
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if(c == CHAR_N) {
if (c == CHAR_N) {
return new NotState(c);
} else if (c == QUOTATION_MARK) {
return new SearchPhraseState(c);
@ -225,6 +234,7 @@ public class SearchTokenizer {
}
return forbidden(c);
}
@Override
public State init(char c) throws SearchTokenizerException {
return nextChar(c);
@ -234,15 +244,16 @@ public class SearchTokenizer {
private class SearchWordState extends LiteralState {
public SearchWordState(char c) throws SearchTokenizerException {
super(Token.WORD, c);
if(!isAllowedWord(c)) {
if (!isAllowedWord(c)) {
forbidden(c);
}
}
public SearchWordState(State toConsume) throws SearchTokenizerException {
super(Token.WORD, toConsume.getLiteral());
char[] chars = literal.toString().toCharArray();
for (char aChar : chars) {
if(!isAllowedWord(aChar)) {
if (!isAllowedWord(aChar)) {
forbidden(aChar);
}
}
@ -271,7 +282,7 @@ public class SearchTokenizer {
private class SearchPhraseState extends LiteralState {
public SearchPhraseState(char c) throws SearchTokenizerException {
super(Token.PHRASE, c);
if(c != QUOTATION_MARK) {
if (c != QUOTATION_MARK) {
forbidden(c);
}
}
@ -286,7 +297,7 @@ public class SearchTokenizer {
finish();
allowed(c);
return new SearchExpressionState();
} else if(isFinished()) {
} else if (isFinished()) {
return new SearchExpressionState().init(c);
}
return forbidden(c);
@ -298,6 +309,7 @@ public class SearchTokenizer {
super(Token.OPEN);
finish();
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
finish();
@ -323,56 +335,40 @@ public class SearchTokenizer {
private class NotState extends LiteralState {
public NotState(char c) throws SearchTokenizerException {
super(Token.NOT, c);
if(c != CHAR_N) {
if (c != CHAR_N) {
forbidden(c);
}
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (literal.length() == 1 && c == CHAR_O) {
return allowed(c);
} else if (literal.length() == 2 && c == CHAR_T) {
return allowed(c);
} else if(literal.length() == 3 && isWhitespace(c)) {
} else if (literal.length() == 3 && isWhitespace(c)) {
finish();
return new BeforePhraseOrWordRwsState();
}
return forbidden(c);
}
}
private class AndState extends LiteralState {
public AndState(char c) throws SearchTokenizerException {
super(Token.AND, c);
if(c != CHAR_A) {
if (c != CHAR_A) {
forbidden(c);
}
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (literal.length() == 1 && c == CHAR_N) {
return allowed(c);
} else if (literal.length() == 2 && c == CHAR_D) {
return allowed(c);
} else if(literal.length() == 3 && isWhitespace(c)) {
finish();
return new BeforeSearchExpressionRwsState();
} else {
return new SearchWordState(this);
}
}
}
private class OrState extends LiteralState {
public OrState(char c) throws SearchTokenizerException {
super(Token.OR, c);
if(c != CHAR_O) {
forbidden(c);
}
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (literal.length() == 1 && (c == CHAR_R)) {
return allowed(c);
} else if(literal.length() == 2 && isWhitespace(c)) {
} else if (literal.length() == 3 && isWhitespace(c)) {
finish();
return new BeforeSearchExpressionRwsState();
} else {
@ -381,12 +377,34 @@ public class SearchTokenizer {
}
}
// RWS 'OR' RWS searchExpr
private class OrState extends LiteralState {
public OrState(char c) throws SearchTokenizerException {
super(Token.OR, c);
if (c != CHAR_O) {
forbidden(c);
}
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (literal.length() == 1 && (c == CHAR_R)) {
return allowed(c);
} else if (literal.length() == 2 && isWhitespace(c)) {
finish();
return new BeforeSearchExpressionRwsState();
} else {
return new SearchWordState(this);
}
}
}
// RWS 'OR' RWS searchExpr
// RWS [ 'AND' RWS ] searchExpr
private class BeforeSearchExpressionRwsState extends State {
public BeforeSearchExpressionRwsState() {
super(null);
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (isWhitespace(c)) {
@ -401,11 +419,12 @@ public class SearchTokenizer {
public BeforePhraseOrWordRwsState() {
super(null);
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (isWhitespace(c)) {
return allowed(c);
} else if(c == '"') {
} else if (c == '"') {
return new SearchPhraseState(c);
} else {
return new SearchWordState(c);
@ -417,6 +436,7 @@ public class SearchTokenizer {
public RwsState() {
super(null);
}
@Override
public State nextChar(char c) throws SearchTokenizerException {
if (isWhitespace(c)) {
@ -438,10 +458,10 @@ public class SearchTokenizer {
* @param searchQuery search query to be tokenized
* @return list of tokens
* @throws SearchTokenizerException if something in query is not valid
* (based on OData search query ABNF)
* (based on OData search query ABNF)
*/
public List<SearchQueryToken> tokenize(final String searchQuery)
throws SearchTokenizerException {
throws SearchTokenizerException {
char[] chars = searchQuery.trim().toCharArray();
@ -455,7 +475,7 @@ public class SearchTokenizer {
state = next;
}
if(state.close().isFinished()) {
if (state.close().isFinished()) {
states.add(state);
}

View File

@ -18,11 +18,30 @@
*/
package org.apache.olingo.server.core.uri.parser.search;
public class SearchTokenizerException extends Exception {
import org.apache.olingo.server.core.uri.parser.UriParserSyntaxException;
public class SearchTokenizerException extends UriParserSyntaxException {
private static final long serialVersionUID = -8295456415309640166L;
public SearchTokenizerException(String message) {
super(message);
public static enum MessageKeys implements MessageKey {
/** parameter: character */
FORBIDDEN_CHARACTER,
ALREADY_FINISHED;
@Override
public String getKey() {
return name();
}
}
public SearchTokenizerException(final String developmentMessage, final MessageKey messageKey,
final String... parameters) {
super(developmentMessage, messageKey, parameters);
}
public SearchTokenizerException(final String developmentMessage, final Throwable cause, final MessageKey messageKey,
final String... parameters) {
super(developmentMessage, cause, messageKey, parameters);
}
}

View File

@ -33,7 +33,7 @@ import org.junit.Test;
public class SearchParserAndTokenizerTest {
@Test
public void basicParsing() throws SearchTokenizerException {
public void basicParsing() throws Exception {
SearchExpressionValidator.init("a")
.validate(with("a"));
SearchExpressionValidator.init("a AND b")
@ -172,17 +172,18 @@ public class SearchParserAndTokenizerTest {
Assert.fail("Expected exception " + exception.getClass().getSimpleName() + " was not thrown.");
}
private void validate(SearchExpression expectedSearchExpression) throws SearchTokenizerException {
private void validate(SearchExpression expectedSearchExpression) throws SearchTokenizerException,
SearchParserException {
final SearchExpression searchExpression = getSearchExpression();
Assert.assertEquals(expectedSearchExpression.toString(), searchExpression.toString());
}
private void validate(String expectedSearchExpression) throws SearchTokenizerException {
private void validate(String expectedSearchExpression) throws SearchTokenizerException, SearchParserException {
final SearchExpression searchExpression = getSearchExpression();
Assert.assertEquals(expectedSearchExpression, searchExpression.toString());
}
private SearchExpression getSearchExpression() {
private SearchExpression getSearchExpression() throws SearchParserException, SearchTokenizerException {
SearchParser tokenizer = new SearchParser();
SearchOption result = tokenizer.parse(null, searchQuery);
Assert.assertNotNull(result);
@ -195,5 +196,4 @@ public class SearchParserAndTokenizerTest {
}
}
}

View File

@ -21,6 +21,7 @@ package org.apache.olingo.server.core.uri.parser.search;
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 static org.mockito.Mockito.when;
@ -29,35 +30,35 @@ import java.util.List;
import org.apache.olingo.server.api.uri.queryoption.search.SearchBinaryOperatorKind;
import org.apache.olingo.server.api.uri.queryoption.search.SearchExpression;
import org.apache.olingo.server.core.uri.parser.search.SearchParserException.MessageKeys;
import org.apache.olingo.server.core.uri.parser.search.SearchQueryToken.Token;
import org.junit.Ignore;
import org.junit.Test;
public class SearchParserTest extends SearchParser {
@Test
public void simple() {
public void simple() throws Exception {
SearchExpression se = run(Token.WORD);
assertEquals("'word1'", se.toString());
assertTrue(se.isSearchTerm());
assertEquals("word1", se.asSearchTerm().getSearchTerm());
se = run(Token.PHRASE);
assertEquals("'phrase1'", se.toString());
assertTrue(se.isSearchTerm());
//TODO: Check if quotation marks should be part of the string we deliver
// TODO: Check if quotation marks should be part of the string we deliver
assertEquals("phrase1", se.asSearchTerm().getSearchTerm());
}
@Test
public void simpleAnd() {
public void simpleAnd() throws Exception {
SearchExpression se = run(Token.WORD, Token.AND, Token.WORD);
assertEquals("{'word1' AND 'word2'}", se.toString());
assertTrue(se.isSearchBinary());
assertEquals(SearchBinaryOperatorKind.AND, se.asSearchBinary().getOperator());
assertEquals("word1", se.asSearchBinary().getLeftOperand().asSearchTerm().getSearchTerm());
assertEquals("word2", se.asSearchBinary().getRightOperand().asSearchTerm().getSearchTerm());
se = run(Token.PHRASE, Token.AND, Token.PHRASE);
assertEquals("{'phrase1' AND 'phrase2'}", se.toString());
assertTrue(se.isSearchBinary());
@ -65,16 +66,16 @@ public class SearchParserTest extends SearchParser {
assertEquals("phrase1", se.asSearchBinary().getLeftOperand().asSearchTerm().getSearchTerm());
assertEquals("phrase2", se.asSearchBinary().getRightOperand().asSearchTerm().getSearchTerm());
}
@Test
public void simpleOr() {
public void simpleOr() throws Exception {
SearchExpression se = run(Token.WORD, Token.OR, Token.WORD);
assertEquals("{'word1' OR 'word2'}", se.toString());
assertTrue(se.isSearchBinary());
assertEquals(SearchBinaryOperatorKind.OR, se.asSearchBinary().getOperator());
assertEquals("word1", se.asSearchBinary().getLeftOperand().asSearchTerm().getSearchTerm());
assertEquals("word2", se.asSearchBinary().getRightOperand().asSearchTerm().getSearchTerm());
se = run(Token.PHRASE, Token.OR, Token.PHRASE);
assertEquals("{'phrase1' OR 'phrase2'}", se.toString());
assertTrue(se.isSearchBinary());
@ -82,16 +83,16 @@ public class SearchParserTest extends SearchParser {
assertEquals("phrase1", se.asSearchBinary().getLeftOperand().asSearchTerm().getSearchTerm());
assertEquals("phrase2", se.asSearchBinary().getRightOperand().asSearchTerm().getSearchTerm());
}
@Test
public void simpleImplicitAnd() {
public void simpleImplicitAnd() throws Exception {
SearchExpression se = run(Token.WORD, Token.WORD);
assertEquals("{'word1' AND 'word2'}", se.toString());
assertTrue(se.isSearchBinary());
assertEquals(SearchBinaryOperatorKind.AND, se.asSearchBinary().getOperator());
assertEquals("word1", se.asSearchBinary().getLeftOperand().asSearchTerm().getSearchTerm());
assertEquals("word2", se.asSearchBinary().getRightOperand().asSearchTerm().getSearchTerm());
se = run(Token.PHRASE, Token.PHRASE);
assertEquals("{'phrase1' AND 'phrase2'}", se.toString());
assertTrue(se.isSearchBinary());
@ -99,59 +100,119 @@ public class SearchParserTest extends SearchParser {
assertEquals("phrase1", se.asSearchBinary().getLeftOperand().asSearchTerm().getSearchTerm());
assertEquals("phrase2", se.asSearchBinary().getRightOperand().asSearchTerm().getSearchTerm());
}
@Test
public void simpleBrackets() {
public void simpleBrackets() throws Exception {
SearchExpression se = run(Token.OPEN, Token.WORD, Token.CLOSE);
assertEquals("'word1'", se.toString());
assertTrue(se.isSearchTerm());
assertEquals("word1", se.asSearchTerm().getSearchTerm());
se = run(Token.OPEN, Token.PHRASE, Token.CLOSE);
assertEquals("'phrase1'", se.toString());
assertTrue(se.isSearchTerm());
assertEquals("phrase1", se.asSearchTerm().getSearchTerm());
}
@Test
public void simpleNot() {
public void simpleNot() throws Exception {
SearchExpression se = run(Token.NOT, Token.WORD);
assertEquals("{NOT 'word1'}", se.toString());
assertTrue(se.isSearchUnary());
assertEquals("word1", se.asSearchUnary().getOperand().asSearchTerm().getSearchTerm());
se = run(Token.NOT, Token.PHRASE);
assertEquals("{NOT 'phrase1'}", se.toString());
assertTrue(se.isSearchUnary());
assertEquals("phrase1", se.asSearchUnary().getOperand().asSearchTerm().getSearchTerm());
}
@Test
public void precedenceLast() {
//word1 AND (word2 AND word3)
public void precedenceLast() throws Exception {
// word1 AND (word2 AND word3)
SearchExpression se = run(Token.WORD, Token.AND, Token.OPEN, Token.WORD, Token.AND, Token.WORD, Token.CLOSE);
assertEquals("{'word1' AND {'word2' AND 'word3'}}", se.toString());
}
@Test
public void precedenceFirst() {
//(word1 AND word2) AND word3
public void precedenceFirst() throws Exception {
// (word1 AND word2) AND word3
SearchExpression se = run(Token.OPEN, Token.WORD, Token.AND, Token.WORD, Token.CLOSE, Token.AND, Token.WORD);
assertEquals("{{'word1' AND 'word2'} AND 'word3'}", se.toString());
}
@Test
public void combinationAndOr() {
//word1 AND word2 OR word3
public void combinationAndOr() throws Exception {
// word1 AND word2 OR word3
SearchExpression se = run(Token.WORD, Token.AND, Token.WORD, Token.OR, Token.WORD);
assertEquals("{{'word1' AND 'word2'} OR 'word3'}", se.toString());
//word1 OR word2 AND word3
// word1 OR word2 AND word3
se = run(Token.WORD, Token.OR, Token.WORD, Token.AND, Token.WORD);
assertEquals("{'word1' OR {'word2' AND 'word3'}}", se.toString());
}
@Test
public void unnecessaryBrackets() throws Exception {
// (word1) (word2)
SearchExpression se = run(Token.OPEN, Token.WORD, Token.CLOSE, Token.OPEN, Token.WORD, Token.CLOSE);
assertEquals("{'word1' AND 'word2'}", se.toString());
}
private SearchExpression run(SearchQueryToken.Token... tokenArray) {
@Test
public void complex() throws Exception {
// ((word1 word2) word3) OR word4
SearchExpression se =
run(Token.OPEN, Token.OPEN, Token.WORD, Token.WORD, Token.CLOSE, Token.WORD, Token.CLOSE, Token.OR, Token.WORD);
assertEquals("{{{'word1' AND 'word2'} AND 'word3'} OR 'word4'}", se.toString());
}
@Test
public void doubleNot() throws Exception {
SearchExpression se = run(Token.NOT, Token.WORD, Token.AND, Token.NOT, Token.PHRASE);
assertEquals("{{NOT 'word1'} AND {NOT 'phrase1'}}", se.toString());
}
@Test
public void notAnd() throws Exception {
runEx(SearchParserException.MessageKeys.INVALID_NOT_OPERAND, Token.NOT, Token.AND);
}
@Test
public void doubleAnd() throws Exception {
runEx(SearchParserException.MessageKeys.INVALID_OPERATOR_AFTER_AND, Token.WORD, Token.AND, Token.AND, Token.WORD);
}
@Test
public void singleAnd() {
runEx(SearchParserException.MessageKeys.INVALID_BINARY_OPERATOR_POSITION, Token.AND);
}
@Test
public void singleOpenBracket() {
runEx(SearchParserException.MessageKeys.EXPECTED_DIFFERENT_TOKEN, Token.OPEN);
}
@Test
public void emptyBrackets() {
runEx(SearchParserException.MessageKeys.NO_EXPRESSION_FOUND, Token.OPEN, Token.CLOSE);
}
@Test
public void empty() {
Token[] emptyArray = new Token[0];
runEx(SearchParserException.MessageKeys.NO_EXPRESSION_FOUND, emptyArray);
}
private void runEx(MessageKeys key, Token... tokenArray) {
try {
run(tokenArray);
fail("Expected UriParserSyntaxException with key " + key);
} catch (SearchParserException e) {
assertEquals(key, e.getMessageKey());
}
}
private SearchExpression run(SearchQueryToken.Token... tokenArray) throws SearchParserException {
List<SearchQueryToken> tokenList = prepareTokens(tokenArray);
SearchExpression se = parseInternal(tokenList);
assertNotNull(se);
@ -172,6 +233,7 @@ public class SearchParserTest extends SearchParser {
when(token.getLiteral()).thenReturn("phrase" + phraseNumber);
phraseNumber++;
}
when(token.toString()).thenReturn("" + tokenArray[i]);
tokenList.add(token);
}
return tokenList;