From 5556599fadd60320397d3a9d5db83b4730564756 Mon Sep 17 00:00:00 2001 From: Simon Willnauer Date: Sat, 5 Dec 2009 12:29:59 +0000 Subject: [PATCH] LUCENE-2039: Added extensible query parser which enables arbitrary parser extensions based on field naming scheme git-svn-id: https://svn.apache.org/repos/asf/lucene/java/trunk@887533 13f79535-47bb-0310-9956-ffa450edef68 --- contrib/CHANGES.txt | 5 + .../ext/ExtendableQueryParser.java | 142 ++++++++++++ .../queryParser/ext/ExtensionQuery.java | 63 +++++ .../lucene/queryParser/ext/Extensions.java | 217 ++++++++++++++++++ .../queryParser/ext/ParserExtension.java | 53 +++++ .../lucene/queryParser/ext/package.html | 22 ++ .../lucene/queryParser/ext/ExtensionStub.java | 33 +++ .../ext/TestExtendableQueryParser.java | 137 +++++++++++ .../queryParser/ext/TestExtensions.java | 78 +++++++ 9 files changed, 750 insertions(+) create mode 100644 contrib/misc/src/java/org/apache/lucene/queryParser/ext/ExtendableQueryParser.java create mode 100644 contrib/misc/src/java/org/apache/lucene/queryParser/ext/ExtensionQuery.java create mode 100644 contrib/misc/src/java/org/apache/lucene/queryParser/ext/Extensions.java create mode 100644 contrib/misc/src/java/org/apache/lucene/queryParser/ext/ParserExtension.java create mode 100644 contrib/misc/src/java/org/apache/lucene/queryParser/ext/package.html create mode 100644 contrib/misc/src/test/org/apache/lucene/queryParser/ext/ExtensionStub.java create mode 100644 contrib/misc/src/test/org/apache/lucene/queryParser/ext/TestExtendableQueryParser.java create mode 100644 contrib/misc/src/test/org/apache/lucene/queryParser/ext/TestExtensions.java diff --git a/contrib/CHANGES.txt b/contrib/CHANGES.txt index 403a66a81b6..7d305298db5 100644 --- a/contrib/CHANGES.txt +++ b/contrib/CHANGES.txt @@ -16,6 +16,11 @@ API Changes reader. (Eirik Bjørsnøs via Mike McCandless) New features + + * LUCENE-2039: Add a extensible query parser to contrib/misc. + ExtendableQueryParser enables arbitrary parser extensions based on a + customizable field naming scheme. + (Simon Willnauer) * LUCENE-2108: Spellchecker now safely supports concurrent modifications to the spell-index. Threads can safely obtain term suggestions while the spell- diff --git a/contrib/misc/src/java/org/apache/lucene/queryParser/ext/ExtendableQueryParser.java b/contrib/misc/src/java/org/apache/lucene/queryParser/ext/ExtendableQueryParser.java new file mode 100644 index 00000000000..680e35d2271 --- /dev/null +++ b/contrib/misc/src/java/org/apache/lucene/queryParser/ext/ExtendableQueryParser.java @@ -0,0 +1,142 @@ +package org.apache.lucene.queryParser.ext; + +/** + * 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. + */ + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.queryParser.QueryParser; +import org.apache.lucene.queryParser.ext.Extensions.Pair; +import org.apache.lucene.search.Query; +import org.apache.lucene.util.Version; + +/** + * The {@link ExtendableQueryParser} enables arbitrary query parser extension + * based on a customizable field naming scheme. The lucene query syntax allows + * implicit and explicit field definitions as query prefix followed by a colon + * (':') character. The {@link ExtendableQueryParser} allows to encode extension + * keys into the field symbol associated with a registered instance of + * {@link ParserExtension}. A customizable separation character separates the + * extension key from the actual field symbol. The {@link ExtendableQueryParser} + * splits (@see {@link Extensions#splitExtensionField(String, String)}) the + * extension key from the field symbol and tries to resolve the associated + * {@link ParserExtension}. If the parser can't resolve the key or the field + * token does not contain a separation character, {@link ExtendableQueryParser} + * yields the same behavior as its super class {@link QueryParser}. Otherwise, + * if the key is associated with a {@link ParserExtension} instance, the parser + * builds an instance of {@link ExtensionQuery} to be processed by + * {@link ParserExtension#parse(ExtensionQuery)}.If a extension field does not + * contain a field part the default field for the query will be used. + *

+ * To guarantee that an extension field is processed with its associated + * extension, the extension query part must escape any special characters like + * '*' or '['. If the extension query contains any whitespace characters, the + * extension query part must be enclosed in quotes. + * Example ('_' used as separation character): + *

+ *   title_customExt:"Apache Lucene\?" OR content_customExt:prefix\*
+ * 
+ * + * Search on the default field: + *
+ *   _customExt:"Apache Lucene\?" OR _customExt:prefix\*
+ * 
+ *

+ *

+ * The {@link ExtendableQueryParser} itself does not implement the logic how + * field and extension key are separated or ordered. All logic regarding the + * extension key and field symbol parsing is located in {@link Extensions}. + * Customized extension schemes should be implemented by sub-classing + * {@link Extensions}. + *

+ *

+ * For details about the default encoding scheme see {@link Extensions}. + *

+ * + * @see Extensions + * @see ParserExtension + * @see ExtensionQuery + */ +public class ExtendableQueryParser extends QueryParser { + + private final String defaultField; + private final Extensions extensions; + + /** + * Default empty extensions instance + */ + private static final Extensions DEFAULT_EXTENSION = new Extensions(); + + /** + * Creates a new {@link ExtendableQueryParser} instance + * + * @param matchVersion + * the lucene version to use. + * @param f + * the default query field + * @param a + * the analyzer used to find terms in a query string + */ + public ExtendableQueryParser(final Version matchVersion, final String f, + final Analyzer a) { + this(matchVersion, f, a, DEFAULT_EXTENSION); + + } + + /** + * Creates a new {@link ExtendableQueryParser} instance + * + * @param matchVersion + * the lucene version to use. + * @param f + * the default query field + * @param a + * the analyzer used to find terms in a query string + * @param ext + * the query parser extensions + */ + public ExtendableQueryParser(final Version matchVersion, final String f, + final Analyzer a, final Extensions ext) { + super(matchVersion, f, a); + this.defaultField = f; + this.extensions = ext; + } + + /** + * Returns the extension field delimiter character. + * + * @return the extension field delimiter character. + */ + public char getExtensionFieldDelimiter() { + return extensions.getExtensionFieldDelimiter(); + } + + @Override + protected Query getFieldQuery(final String field, final String queryText) + throws ParseException { + final Pair splitExtensionField = this.extensions + .splitExtensionField(defaultField, field); + final ParserExtension extension = this.extensions + .getExtension(splitExtensionField.cud); + if (extension != null) { + return extension.parse(new ExtensionQuery(splitExtensionField.cur, + queryText)); + } + return super.getFieldQuery(field, queryText); + } + +} diff --git a/contrib/misc/src/java/org/apache/lucene/queryParser/ext/ExtensionQuery.java b/contrib/misc/src/java/org/apache/lucene/queryParser/ext/ExtensionQuery.java new file mode 100644 index 00000000000..ae68682915d --- /dev/null +++ b/contrib/misc/src/java/org/apache/lucene/queryParser/ext/ExtensionQuery.java @@ -0,0 +1,63 @@ +package org.apache.lucene.queryParser.ext; + +/** + * 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. + */ + +/** + * {@link ExtensionQuery} holds all query components extracted from the original + * query string like the query field and the extension query string. + * + * @see Extensions + * @see ExtendableQueryParser + * @see ParserExtension + */ +public class ExtensionQuery { + + private final String field; + private final String rawQueryString; + + /** + * Creates a new {@link ExtensionQuery} + * + * @param field + * the query field + * @param rawQueryString + * the raw extension query string + */ + public ExtensionQuery(String field, String rawQueryString) { + this.field = field; + this.rawQueryString = rawQueryString; + } + + /** + * Returns the query field + * + * @return the query field + */ + public String getField() { + return field; + } + + /** + * Returns the raw extension query string + * + * @return the raw extension query string + */ + public String getRawQueryString() { + return rawQueryString; + } +} diff --git a/contrib/misc/src/java/org/apache/lucene/queryParser/ext/Extensions.java b/contrib/misc/src/java/org/apache/lucene/queryParser/ext/Extensions.java new file mode 100644 index 00000000000..edf763d785b --- /dev/null +++ b/contrib/misc/src/java/org/apache/lucene/queryParser/ext/Extensions.java @@ -0,0 +1,217 @@ +package org.apache.lucene.queryParser.ext; + +/** + * 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. + */ +import java.util.HashMap; +import java.util.Map; + +import org.apache.lucene.queryParser.QueryParser; + +/** + * The {@link Extensions} class represents an extension mapping to associate + * {@link ParserExtension} instances with extension keys. An extension key is a + * string encoded into a Lucene standard query parser field symbol recognized by + * {@link ExtendableQueryParser}. The query parser passes each extension field + * token to {@link #splitExtensionField(String, String)} to separate the + * extension key from the field identifier. + *

+ * In addition to the key to extension mapping this class also defines the field + * name overloading scheme. {@link ExtendableQueryParser} uses the given + * extension to split the actual field name and extension key by calling + * {@link #splitExtensionField(String, String)}. To change the order or the key + * / field name encoding scheme users can subclass {@link Extensions} to + * implement their own. + * + * @see ExtendableQueryParser + * @see ParserExtension + */ +public class Extensions { + private final Map extensions = new HashMap(); + private final char extensionFieldDelimiter; + /** + * The default extension field delimiter character. This constant is set to + * ':' + */ + public static final char DEFAULT_EXTENSION_FIELD_DELIMITER = ':'; + + /** + * Creates a new {@link Extensions} instance with the + * {@link #DEFAULT_EXTENSION_FIELD_DELIMITER} as a delimiter character. + */ + public Extensions() { + this(DEFAULT_EXTENSION_FIELD_DELIMITER); + } + + /** + * Creates a new {@link Extensions} instance + * + * @param extensionFieldDelimiter + * the extensions field delimiter character + */ + public Extensions(char extensionFieldDelimiter) { + this.extensionFieldDelimiter = extensionFieldDelimiter; + } + + /** + * Adds a new {@link ParserExtension} instance associated with the given key. + * + * @param key + * the parser extension key + * @param extension + * the parser extension + */ + public void add(String key, ParserExtension extension) { + this.extensions.put(key, extension); + } + + /** + * Returns the {@link ParserExtension} instance for the given key or + * null if no extension can be found for the key. + * + * @param key + * the extension key + * @return the {@link ParserExtension} instance for the given key or + * null if no extension can be found for the key. + */ + public final ParserExtension getExtension(String key) { + return this.extensions.get(key); + } + + /** + * Returns the extension field delimiter + * + * @return the extension field delimiter + */ + public char getExtensionFieldDelimiter() { + return extensionFieldDelimiter; + } + + /** + * Splits a extension field and returns the field / extension part as a + * {@link Pair}. This method tries to split on the first occurrence of the + * extension field delimiter, if the delimiter is not present in the string + * the result will contain a null value for the extension key and + * the given field string as the field value. If the given extension field + * string contains no field identifier the result pair will carry the given + * default field as the field value. + * + * @param defaultField + * the default query field + * @param field + * the extension field string + * @return a {@link Pair} with the field name as the {@link Pair#cur} and the + * extension key as the {@link Pair#cud} + */ + public Pair splitExtensionField(String defaultField, + String field) { + int indexOf = field.indexOf(this.extensionFieldDelimiter); + if (indexOf < 0) + return new Pair(field, null); + final String indexField = indexOf == 0 ? defaultField : field.substring(0, + indexOf); + final String extensionKey = field.substring(indexOf + 1); + return new Pair(indexField, extensionKey); + + } + + /** + * Escapes an extension field. The default implementation is equivalent to + * {@link QueryParser#escape(String)}. + * + * @param extfield + * the extension field identifier + * @return the extension field identifier with all special chars escaped with + * a backslash character. + */ + public String escapeExtensionField(String extfield) { + return QueryParser.escape(extfield); + } + + /** + * Builds an extension field string from a given extension key and the default + * query field. The default field and the key are delimited with the extension + * field delimiter character. This method makes no assumption about the order + * of the extension key and the field. By default the extension key is + * appended to the end of the returned string while the field is added to the + * beginning. Special Query characters are escaped in the result. + *

+ * Note: {@link Extensions} subclasses must maintain the contract between + * {@link #buildExtensionField(String)} and + * {@link #splitExtensionField(String, String)} where the latter inverts the + * former. + *

+ */ + public String buildExtensionField(String extensionKey) { + return buildExtensionField(extensionKey, ""); + } + + /** + * Builds an extension field string from a given extension key and the + * extensions field. The field and the key are delimited with the extension + * field delimiter character. This method makes no assumption about the order + * of the extension key and the field. By default the extension key is + * appended to the end of the returned string while the field is added to the + * beginning. Special Query characters are escaped in the result. + *

+ * Note: {@link Extensions} subclasses must maintain the contract between + * {@link #buildExtensionField(String, String)} and + * {@link #splitExtensionField(String, String)} where the latter inverts the + * former. + *

+ * + * @param extensionKey + * the extension key + * @param field + * the field to apply the extension on. + * @return escaped extension field identifier + * @see #buildExtensionField(String) to use the default query field + */ + public String buildExtensionField(String extensionKey, String field) { + StringBuilder builder = new StringBuilder(field); + builder.append(this.extensionFieldDelimiter); + builder.append(extensionKey); + return escapeExtensionField(builder.toString()); + } + + /** + * This class represents a generic pair. + * + * @param + * the pairs first element + * @param + * the pairs last element of the pair. + */ + public static class Pair { + + public final Cur cur; + public final Cud cud; + + /** + * Creates a new Pair + * + * @param cur + * the pairs first element + * @param cud + * the pairs last element + */ + public Pair(Cur cur, Cud cud) { + this.cur = cur; + this.cud = cud; + } + } + +} diff --git a/contrib/misc/src/java/org/apache/lucene/queryParser/ext/ParserExtension.java b/contrib/misc/src/java/org/apache/lucene/queryParser/ext/ParserExtension.java new file mode 100644 index 00000000000..b173858c897 --- /dev/null +++ b/contrib/misc/src/java/org/apache/lucene/queryParser/ext/ParserExtension.java @@ -0,0 +1,53 @@ +package org.apache.lucene.queryParser.ext; + +/** + * 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. + */ + +import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.queryParser.QueryParser; +import org.apache.lucene.search.Query; + +/** + * This class represents an extension base class to the Lucene standard + * {@link QueryParser}. The {@link QueryParser} is generated by the JavaCC + * parser generator. Changing or adding functionality or syntax in the standard + * query parser requires changes to the JavaCC source file. To enable extending + * the standard query parser without changing the JavaCC sources and re-generate + * the parser the {@link ParserExtension} can be customized and plugged into an + * instance of {@link ExtendableQueryParser}, a direct subclass of + * {@link QueryParser}. + * + * @see Extensions + * @see ExtendableQueryParser + */ +public abstract class ParserExtension { + + /** + * Processes the given {@link ExtensionQuery} and returns a corresponding + * {@link Query} instance. Subclasses must either return a {@link Query} + * instance or raise a {@link ParseException}. This method must not return + * null. + * + * @param query + * the extension query + * @return a new query instance + * @throws ParseException + * if the query can not be parsed. + */ + public abstract Query parse(final ExtensionQuery query) throws ParseException; + +} diff --git a/contrib/misc/src/java/org/apache/lucene/queryParser/ext/package.html b/contrib/misc/src/java/org/apache/lucene/queryParser/ext/package.html new file mode 100644 index 00000000000..13549a8e59b --- /dev/null +++ b/contrib/misc/src/java/org/apache/lucene/queryParser/ext/package.html @@ -0,0 +1,22 @@ + + + + +Extendable QueryParser provides a simple and flexible extension mechanism by overloading query field names. + + diff --git a/contrib/misc/src/test/org/apache/lucene/queryParser/ext/ExtensionStub.java b/contrib/misc/src/test/org/apache/lucene/queryParser/ext/ExtensionStub.java new file mode 100644 index 00000000000..63ce2b369f8 --- /dev/null +++ b/contrib/misc/src/test/org/apache/lucene/queryParser/ext/ExtensionStub.java @@ -0,0 +1,33 @@ +package org.apache.lucene.queryParser.ext; + +import org.apache.lucene.index.Term; +import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; + +/** + * 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. + */ + +class ExtensionStub extends ParserExtension { + + @Override + public Query parse(ExtensionQuery components) throws ParseException { + return new TermQuery(new Term(components.getField(), components + .getRawQueryString())); + } + +} \ No newline at end of file diff --git a/contrib/misc/src/test/org/apache/lucene/queryParser/ext/TestExtendableQueryParser.java b/contrib/misc/src/test/org/apache/lucene/queryParser/ext/TestExtendableQueryParser.java new file mode 100644 index 00000000000..863cfe2dece --- /dev/null +++ b/contrib/misc/src/test/org/apache/lucene/queryParser/ext/TestExtendableQueryParser.java @@ -0,0 +1,137 @@ +package org.apache.lucene.queryParser.ext; + +/** + * 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. + */ + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.SimpleAnalyzer; +import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.queryParser.QueryParser; +import org.apache.lucene.queryParser.TestQueryParser; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.util.Version; + +/** + * Testcase for the class {@link ExtendableQueryParser} + */ +public class TestExtendableQueryParser extends TestQueryParser { + private static char[] DELIMITERS = new char[] { + Extensions.DEFAULT_EXTENSION_FIELD_DELIMITER, '-', '|' }; + + public TestExtendableQueryParser(String name) { + super(name); + } + + @Override + public QueryParser getParser(Analyzer a) throws Exception { + return getParser(a, null); + } + + public QueryParser getParser(Analyzer a, Extensions extensions) + throws Exception { + if (a == null) + a = new SimpleAnalyzer(); + QueryParser qp = extensions == null ? new ExtendableQueryParser( + Version.LUCENE_CURRENT, "field", a) : new ExtendableQueryParser( + Version.LUCENE_CURRENT, "field", a, extensions); + qp.setDefaultOperator(QueryParser.OR_OPERATOR); + return qp; + } + + public void testUnescapedExtDelimiter() throws Exception { + Extensions ext = newExtensions(':'); + ext.add("testExt", new ExtensionStub()); + ExtendableQueryParser parser = (ExtendableQueryParser) getParser(null, ext); + try { + parser.parse("aField:testExt:\"foo \\& bar\""); + fail("extension field delimiter is not escaped"); + } catch (ParseException e) { + } + } + + public void testExtFieldUnqoted() throws Exception { + for (int i = 0; i < DELIMITERS.length; i++) { + Extensions ext = newExtensions(DELIMITERS[i]); + ext.add("testExt", new ExtensionStub()); + ExtendableQueryParser parser = (ExtendableQueryParser) getParser(null, + ext); + String field = ext.buildExtensionField("testExt", "aField"); + Query query = parser.parse(String.format("%s:foo bar", field)); + assertTrue("expected instance of BooleanQuery but was " + + query.getClass(), query instanceof BooleanQuery); + BooleanQuery bquery = (BooleanQuery) query; + BooleanClause[] clauses = bquery.getClauses(); + assertEquals(2, clauses.length); + BooleanClause booleanClause = clauses[0]; + query = booleanClause.getQuery(); + assertTrue("expected instance of TermQuery but was " + query.getClass(), + query instanceof TermQuery); + TermQuery tquery = (TermQuery) query; + assertEquals("aField", tquery.getTerm() + .field()); + assertEquals("foo", tquery.getTerm().text()); + + booleanClause = clauses[1]; + query = booleanClause.getQuery(); + assertTrue("expected instance of TermQuery but was " + query.getClass(), + query instanceof TermQuery); + tquery = (TermQuery) query; + assertEquals("field", tquery.getTerm().field()); + assertEquals("bar", tquery.getTerm().text()); + } + } + + public void testExtDefaultField() throws Exception { + for (int i = 0; i < DELIMITERS.length; i++) { + Extensions ext = newExtensions(DELIMITERS[i]); + ext.add("testExt", new ExtensionStub()); + ExtendableQueryParser parser = (ExtendableQueryParser) getParser(null, + ext); + String field = ext.buildExtensionField("testExt"); + Query parse = parser.parse(String.format("%s:\"foo \\& bar\"", field)); + assertTrue("expected instance of TermQuery but was " + parse.getClass(), + parse instanceof TermQuery); + TermQuery tquery = (TermQuery) parse; + assertEquals("field", tquery.getTerm().field()); + assertEquals("foo & bar", tquery.getTerm().text()); + } + } + + public Extensions newExtensions(char delimiter) { + return new Extensions(delimiter); + } + + public void testExtField() throws Exception { + for (int i = 0; i < DELIMITERS.length; i++) { + Extensions ext = newExtensions(DELIMITERS[i]); + ext.add("testExt", new ExtensionStub()); + ExtendableQueryParser parser = (ExtendableQueryParser) getParser(null, + ext); + String field = ext.buildExtensionField("testExt", "afield"); + Query parse = parser.parse(String.format("%s:\"foo \\& bar\"", field)); + assertTrue("expected instance of TermQuery but was " + parse.getClass(), + parse instanceof TermQuery); + TermQuery tquery = (TermQuery) parse; + assertEquals("afield", tquery.getTerm().field()); + assertEquals("foo & bar", tquery.getTerm().text()); + } + } + +} diff --git a/contrib/misc/src/test/org/apache/lucene/queryParser/ext/TestExtensions.java b/contrib/misc/src/test/org/apache/lucene/queryParser/ext/TestExtensions.java new file mode 100644 index 00000000000..bcfee1b5e53 --- /dev/null +++ b/contrib/misc/src/test/org/apache/lucene/queryParser/ext/TestExtensions.java @@ -0,0 +1,78 @@ +package org.apache.lucene.queryParser.ext; + +/** + * 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. + */ + +import org.apache.lucene.util.LuceneTestCase; + +/** + * Testcase for the {@link Extensions} class + */ +public class TestExtensions extends LuceneTestCase { + + private Extensions ext; + + protected void setUp() throws Exception { + super.setUp(); + this.ext = new Extensions(); + } + + public void testBuildExtensionField() { + assertEquals("field\\:key", ext.buildExtensionField("key", "field")); + assertEquals("\\:key", ext.buildExtensionField("key")); + + ext = new Extensions('.'); + assertEquals("field.key", ext.buildExtensionField("key", "field")); + assertEquals(".key", ext.buildExtensionField("key")); + } + + public void testSplitExtensionField() { + assertEquals("field\\:key", ext.buildExtensionField("key", "field")); + assertEquals("\\:key", ext.buildExtensionField("key")); + + ext = new Extensions('.'); + assertEquals("field.key", ext.buildExtensionField("key", "field")); + assertEquals(".key", ext.buildExtensionField("key")); + } + + public void testAddGetExtension() { + ParserExtension extension = new ExtensionStub(); + assertNull(ext.getExtension("foo")); + ext.add("foo", extension); + assertSame(extension, ext.getExtension("foo")); + ext.add("foo", null); + assertNull(ext.getExtension("foo")); + } + + public void testGetExtDelimiter() { + assertEquals(Extensions.DEFAULT_EXTENSION_FIELD_DELIMITER, this.ext + .getExtensionFieldDelimiter()); + ext = new Extensions('?'); + assertEquals('?', this.ext.getExtensionFieldDelimiter()); + } + + public void testEscapeExtension() { + assertEquals("abc\\:\\?\\{\\}\\[\\]\\\\\\(\\)\\+\\-\\!\\~", ext + .escapeExtensionField("abc:?{}[]\\()+-!~")); + try { + ext.escapeExtensionField(null); + fail("should throw NPE - escape string is null"); + } catch (NullPointerException e) { + // + } + } +}