Java tutorial
/* * Copyright 2015 Allette Systems (Australia) * http://www.allette.com.au * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.pageseeder.flint.lucene.query; import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.queryparser.classic.MultiFieldQueryParser; import org.apache.lucene.queryparser.classic.ParseException; import org.apache.lucene.search.Query; import org.pageseeder.flint.lucene.search.Fields; import org.pageseeder.flint.lucene.util.Beta; import org.pageseeder.xmlwriter.XMLWritable; import org.pageseeder.xmlwriter.XMLWriter; /** * A simple parameter to represent a Lucene predicate produced by a query parser. * * <p>The <i>predicate</i> is similar to the {@link Question} except that it can be used for * more powerful search; it is however more difficult to produce similar predicates. * * <p>It acts on one or multiple fields, each field can have a different boost level. * * <p>Use the factory methods to create new predicate. * * @author Christophe Lauret (Weborganic) * @version 16 August 2010 */ @Beta public final class Predicate implements SearchParameter, XMLWritable { /** * The lucene predicate entered by the user. */ private final String _predicate; /** * The field names mapped to their boost value. */ private final Map<String, Float> _fields; /** * The computed query. */ private Query _query = null; // Constructors // ============================================================================================== /** * Creates a new question. * * <p>This is a low level API constructor; to ensure that this class works well, ensure that the * fields cannot be modified externally and that field names do not include empty strings. * * @param fields The fields to search mapped to their respective boost. * @param predicate The text before parsing. * * @throws NullPointerException If either argument is <code>null</code>. */ protected Predicate(Map<String, Float> fields, String predicate) throws NullPointerException { if (fields == null) throw new NullPointerException("fields"); if (predicate == null) throw new NullPointerException("predicate"); this._fields = fields; this._predicate = predicate; } // Methods // ============================================================================================== /** * Returns the list of fields this question applies to. * * @return the list of fields this question applies to. */ public Collection<String> fields() { return this._fields.keySet(); } /** * Returns the underlying predicate string. * * @return the underlying predicate string. */ public String predicate() { return this._predicate; } /** * Returns the boost value for the specified field. * * @param field the name of the field. * * @return the corresponding boost value. */ public float getBoost(String field) { Float boost = this._fields.get(field); return boost != null ? boost.floatValue() : 1.0f; } /** * A question is empty if either the predicate or the fields are empty. * * @return <code>true</code> if either the predicate or the fields are empty; * <code>false</code> if the predicate has a value and there is at least one field. */ @Override public boolean isEmpty() { return this._predicate.isEmpty() || this._fields.isEmpty(); } /** * Computes the query for this question. * * <p>This only needs to be done once. * * @param analyzer The analyser used by the underlying index. * * @throws ParseException If the question could not be parsed properly. */ private void compute(Analyzer analyzer) throws ParseException { String[] fields = this._fields.keySet().toArray(new String[] {}); MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, analyzer); this._query = parser.parse(this._predicate); } /** * Computes the query for this question using the {@link StandardAnalyzer}. * * <p>This method ignores any Lucene specific syntax by removing it from the input string. * * @throws ParseException If the question could not be parsed properly. */ private void compute() throws ParseException { compute(new StandardAnalyzer()); } /** * Returns this object as Lucene query instance. * * @see #isEmpty() * * @return this object as a Lucene query instance, or <code>null</code> if this query is empty. * @throws IllegalStateException if the query has not been computed before - should not happen if using factory * methods. */ @Override public Query toQuery() throws IllegalStateException { // Return null if empty if (isEmpty()) return null; // Query was not computed if (this._query == null) throw new IllegalStateException("Query has not been computed - call compute(Analyzer)"); return this._query; } /** * {@inheritDoc} */ @Override public void toXML(XMLWriter xml) throws IOException { xml.openElement("predicate", true); // indicate whether this search term is empty xml.attribute("is-empty", Boolean.toString(isEmpty())); if (this._query != null) { xml.attribute("query", this._query.toString()); } // details of the question for (Entry<String, Float> field : this._fields.entrySet()) { xml.openElement("field"); Float boost = field.getValue(); xml.attribute("boost", boost != null ? boost.toString() : "1.0"); xml.writeText(field.getKey()); xml.closeElement(); } xml.element("text", this._predicate); xml.closeElement(); } /** * {@inheritDoc} */ @Override public String toString() { return this._predicate + " in " + this._fields.entrySet(); } // Factory methods // ============================================================================================== /** * A factory method to create a new predicate and compute it using the Lucene {@link MultiFieldQueryParser}. * * @param field The default field for the predicate. * @param predicate The predicate to parse * @param analyzer The analyser to use when parsing the predicate. * * @return a new predicate. * * @throws ParseException if the predicate could not be parsed. */ public static Predicate newPredicate(String field, String predicate, Analyzer analyzer) throws ParseException { Map<String, Float> fields = Collections.singletonMap(field, 1.0f); Predicate q = new Predicate(fields, predicate); q.compute(analyzer); return q; } /** * A factory method to create a new predicate and compute it using the Lucene {@link MultiFieldQueryParser}. * * @param fields The list of default fields for the predicate. * @param predicate The predicate to parse * @param analyzer The analyser to use when parsing the predicate. * * @return a new predicate. * * @throws ParseException if the predicate could not be parsed. */ public static Predicate newPredicate(List<String> fields, String predicate, Analyzer analyzer) throws ParseException { List<String> names = Fields.filterNames(fields); Map<String, Float> map = Fields.asBoostMap(names); Predicate q = new Predicate(map, predicate); q.compute(analyzer); return q; } /** * A factory method to create a new question and compute it using the Lucene {@link MultiFieldQueryParser}. * * @param fields The list of fields for the question. * @param predicate The predicate to parse * @param analyzer The analyser to use when parsing the predicate. * * @return a new predicate. * * @throws ParseException if the predicate could not be parsed. */ public static Predicate newPredicate(Map<String, Float> fields, String predicate, Analyzer analyzer) throws ParseException { Predicate q = new Predicate(fields, predicate); q.compute(analyzer); return q; } /** * A factory method to create a new question and compute it using the Lucene {@link MultiFieldQueryParser} * and the {@link StandardAnalyzer}. * * @param fields The list of fields for the question. * @param predicate The predicate to parse * * @return a new predicate. * * @throws ParseException if the predicate could not be parsed. */ public static Predicate newPredicate(List<String> fields, String predicate) throws ParseException { List<String> names = Fields.filterNames(fields); Map<String, Float> map = Fields.asBoostMap(names); Predicate q = new Predicate(map, predicate); q.compute(); return q; } /** * A factory method to create a new question and compute it using the Lucene {@link MultiFieldQueryParser} * and the {@link StandardAnalyzer}. * * @param fields The list of fields for the question. * @param predicate The predicate to parse * * @return a new predicate. * * @throws ParseException if the predicate could not be parsed. */ public static Predicate newPredicate(Map<String, Float> fields, String predicate) throws ParseException { Predicate q = new Predicate(fields, predicate); q.compute(); return q; } }