org.pageseeder.flint.lucene.query.Predicate.java Source code

Java tutorial

Introduction

Here is the source code for org.pageseeder.flint.lucene.query.Predicate.java

Source

/*
 * 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;
    }

}