/**
* Copyright (c) Codice Foundation
* <p>
* This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or any later version.
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. A copy of the GNU Lesser General Public License
* is distributed along with this program and can be found at
* <http://www.gnu.org/licenses/lgpl.html>.
*/
package org.codice.ddf.opensearch.query;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.codice.ddf.endpoints.ASTNode;
import org.codice.ddf.endpoints.KeywordFilterGenerator;
import org.codice.ddf.endpoints.KeywordTextParser;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.MethodRule;
import org.junit.rules.TestWatchman;
import org.junit.runners.model.FrameworkMethod;
import org.opengis.filter.Filter;
import org.parboiled.Parboiled;
import org.parboiled.errors.ErrorUtils;
import org.parboiled.parserunners.ReportingParseRunner;
import org.parboiled.parserunners.TracingParseRunner;
import org.parboiled.support.ParseTreeUtils;
import org.parboiled.support.ParsingResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ddf.catalog.filter.FilterBuilder;
import ddf.catalog.filter.proxy.builder.GeotoolsFilterBuilder;
public class TestKeywordTextParser {
// private static final Logger LOGGER = Logger.getLogger(TestKeywordTextParser.class);
private static final Logger LOGGER = LoggerFactory.getLogger(OpenSearchQueryTest.class);
@Rule
public MethodRule watchman = new TestWatchman() {
public void starting(FrameworkMethod method) {
LOGGER.debug("*************************** STARTING: {} **************************",
method.getName());
}
public void finished(FrameworkMethod method) {
LOGGER.debug("*************************** END: {} **************************",
method.getName());
}
};
@BeforeClass
public static void setUpBeforeClass() throws Exception {
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
}
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
@Test
public void testPositives() {
// TODO add more expressions to test
List<String> inputs = new ArrayList<String>();
inputs.add("נּ€");
inputs.add("A AND B");
inputs.add("A AND B AND C");
inputs.add("A OR B");
inputs.add("(A OR B) ");
inputs.add(" (\"B\")");
inputs.add("( B)");
inputs.add("(\"A OR B \" )");
inputs.add("A NOT B");
inputs.add("A B");
inputs.add("AND");
inputs.add("\"AND\"");
inputs.add("test tester3");
inputs.add("A B OR C");
inputs.add("A \"B\" OR C");
inputs.add("A \"test\" OR C");
inputs.add("A B OR \"C\"");
inputs.add(
"$%'^&_+` ''''veryInterest*ingKeyword!@#$%^&_+-=`~1234567890[]{}\\|?/><,.:; OR %^&_+-=*`~123");
inputs.add("test TeSt1 OR another3test");
inputs.add(
"A B C D E A N D N O T O R F G H I \"J 1 2 3 4 5 6\" 2 * 7 8 9 # $ % ^ ! } [ ` ; ' <");
inputs.add("(\"A14356377856nyin8o789l;;l453 234l56;23$$#%#$@^#@&&!\" B) OR C");
inputs.add("(A AND B) OR C");
inputs.add("(A AND B) NOT ((C OR D) AND (B NOT A)) OR E");
inputs.add("A AND B AND C AND D OR E NOT F");
inputs.add("A B AND C D OR E NOT F");
inputs.add("(A B AND C D) OR E NOT F");
inputs.add("A NOT A");
inputs.add("A (NOT C) D");
inputs.add("(((Apple) AND (((Orange) OR Banana))))");
inputs.add("A B");
inputs.add("(\"A14356377856nyin8o789l;;l453 234l56;23$$#%#$@^#@&&!\" B) OR C");
inputs.add("( A )");
inputs.add(" AND ");
inputs.add(" keyword2 ");
inputs.add(" AND");
inputs.add("AND ");
inputs.add(" AND ");
inputs.add("\" AND \"");
inputs.add("(\"Keyword \")");
inputs.add("\" Keyword\"");
inputs.add("( \"Keyword\")");
inputs.add("A ( OR ) B");
inputs.add("((((((((((((\"stuff stuff2\"))))))))))) OR C)");
inputs.add("(\"A14356377856nyin8o789l;;l453 234l56;23$$#%#$@^#@&&!\" B) OR C");
for (String input : inputs) {
KeywordTextParser parser = Parboiled.createParser(KeywordTextParser.class);
ParsingResult<?> result = new ReportingParseRunner(parser.inputPhrase()).run(input);
LOGGER.debug("input = {}\t\t=====>result matched = {}", input, result.matched);
assertEquals(
"Failed on input [" + input + "]. Parse Error [" + getErrorOutput(result) + "]",
0,
result.parseErrors.size());
assertEquals("Failed to parse [" + input + "] properly.",
input,
ParseTreeUtils.getNodeText(result.parseTreeRoot, result.inputBuffer));
}
}
@Test
public void testNegatives() {
// TODO add more expressions to test
List<String> inputs = new ArrayList<String>();
// these should fail even with loose parsing
inputs.add("");
inputs.add("()");
inputs.add("( )");
inputs.add(" ");
inputs.add("( Keyword");
inputs.add("(Keyword");
inputs.add("Keyword)");
inputs.add("Keyword )");
inputs.add("product2 NOT))(((( anothertitle");
inputs.add("(A AND B) NOT ((C OR D) AND (B NOT A) OR E");
inputs.add("(A AND B) NOT ((C\" AND (B NOT A)) OR E");
inputs.add("(A AND B) NOT (\"C\" AND \"B)) OR E");
inputs.add("(A AND B) NOT (\"A \"C\"\" AND (B)) OR E");
inputs.add("(\"A)()()(((()))()((()))))()(((((()))((\" B) OR C"); // this could be made valid
// if an escape character
// were introduced
inputs.add("() (stuff) OR C");
inputs.add("(((((((((((stuff))))))))))) OR C)"); // one missing leading parenthesis
inputs.add("((((((((((((stuff)))))))))) OR C)"); // one missing trailing parenthesis
inputs.add("((((((((((((\"stuff (stuff2)\"))))))))))) OR C)");
inputs.add("(\"A)()()(((()))()((()))))()(((((()))((\" B) OR C");
inputs.add("\"\"");
inputs.add("\"");
for (String input : inputs) {
KeywordTextParser parser = Parboiled.createParser(KeywordTextParser.class);
ParsingResult<?> result = new ReportingParseRunner(parser.inputPhrase()).run(input);
LOGGER.debug("input = {}\t\t=====>result matched = {}", input, result.matched);
assertThat("[" + input + "] should have failed.",
result.parseErrors.size(),
greaterThan(0));
}
}
@Test
public void testSpacing() {
// TODO add more expressions to test
List<String> inputs = new ArrayList<String>();
inputs.add(" A B OR C");
inputs.add(" A B OR C ");
inputs.add(" A B OR C ");
inputs.add(" A B OR C");
inputs.add(" A B OR C");
inputs.add("A B OR C ");
inputs.add(" A B OR C NOT D AND E ");
for (String input : inputs) {
KeywordTextParser parser = Parboiled.createParser(KeywordTextParser.class);
ParsingResult<?> result = new ReportingParseRunner(parser.inputPhrase()).run(input);
assertEquals(
"Failed on input [" + input + "]. Parse Error [" + getErrorOutput(result) + "]",
0,
result.parseErrors.size());
assertEquals("Failed to parse [" + input + "] properly.",
input,
ParseTreeUtils.getNodeText(result.parseTreeRoot, result.inputBuffer));
}
}
// We have been using this for debugging purposes, its not meant to be a test.
@Ignore
@Test
public void trace() {
Map<String, String> inputToOutput = new LinkedHashMap<String, String>();
FilterBuilder filterBuilder = new GeotoolsFilterBuilder();
List<String> inputs = new ArrayList<String>();
// inputs.add("A \"(test test2)\" OR test2");
inputs.add("A B C D");
for (String input : inputs) {
KeywordTextParser parser = Parboiled.createParser(KeywordTextParser.class);
ParsingResult<ASTNode> result = new TracingParseRunner(parser.inputPhrase()).run(input);
// ParsingResult<ASTNode> result = new
// ReportingParseRunner(parser.inputPhrase()).run(input);
KeywordFilterGenerator kfg = new KeywordFilterGenerator(filterBuilder);
Filter filter = kfg.getFilterFromASTNode(result.resultValue);
inputToOutput.put(input, filter.toString());
// visualize(result);
}
for (Map.Entry<String, String> iteration : inputToOutput.entrySet()) {
System.out.println(iteration.getKey() + " : " + iteration.getValue());
}
}
/**
* Use this method when you want the tree to be printed to System.out for debugging purposes
*
* @param result
*/
protected void visualize(ParsingResult<?> result) {
String output = ParseTreeUtils.printNodeTree(result);
System.out.println(output);
System.out.println("PARSE ERROR: " + (!result.parseErrors.isEmpty() ?
ErrorUtils.printParseError(result.parseErrors.get(0)) :
"NOTHING"));
}
protected String getErrorOutput(ParsingResult<?> result) {
if (!result.parseErrors.isEmpty()) {
return ErrorUtils.printParseError(result.parseErrors.get(0));
}
return "";
}
}