All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pageseeder.flint.lucene.query.SuggestionQuery Maven / Gradle / Ivy

The newest version!
/*
 * 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 org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.*;
import org.pageseeder.flint.lucene.search.Terms;
import org.pageseeder.flint.lucene.util.Beta;
import org.pageseeder.xmlwriter.XMLWriter;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;

/**
 * A Search query for use as auto suggest.
 *
 * @author Christophe Lauret
 * @version 21 July 2010
 */
@Beta
public final class SuggestionQuery implements SearchQuery {

  /**
   * The list of terms.
   */
  private final List _terms;

  /**
   * The condition that all the suggested results must meet.
   */
  private final Query _condition;

  /**
   * If the main query uses 'OR' or 'AND' between term results.
   */
  private final boolean _unionTermResults;

  /**
   * The underlying boolean query.
   */
  private BooleanQuery query = null;

  /**
   * If there are too many clauses in the query.
   */
  private boolean tooManyPrefixes = false;

  /**
   * Create a new auto-suggest query for the specified list of terms with no condition.
   *
   * @param terms     The list of terms that should be matched.
   */
  public SuggestionQuery(List terms) {
    this(terms, null);
  }

  /**
   * Create a new auto-suggest query for the specified list of terms with no condition.
   *
   * @param terms             The list of terms that should be matched.
   * @param unionTermResults  If the suggest query uses 'OR' or 'AND' between term results.
   */
  public SuggestionQuery(List terms, boolean unionTermResults) {
    this(terms, null, unionTermResults);
  }

  /**
   * Create a new auto-suggest query for the specified list of terms.
   *
   * @param terms     The list of terms that should be matched.
   * @param condition The condition that must be met by all suggested results (may be null).
   */
  public SuggestionQuery(List terms, Query condition) {
    this(terms, condition, true);
  }

  /**
   * Create a new auto-suggest query for the specified list of terms.
   *
   * @param terms             The list of terms that should be matched.
   * @param condition         The condition that must be met by all suggested results (may be null).
   * @param unionTermResults  If the suggest query uses 'OR' or 'AND' between term results.
   */
  public SuggestionQuery(List terms, Query condition, boolean unionTermResults) {
    this._terms = terms;
    this._condition = condition;
    this._unionTermResults = unionTermResults;
  }

  /**
   * Computes the list of terms to generate the actual suggestion query.
   *
   * @param reader Computes the list.
   * @throws IOException should an error occurs while reading the index.
   */
  public void compute(IndexReader reader) throws IOException {
    BooleanQuery mainQuery = this._unionTermResults ? computeORQuery(reader) : computeANDQuery(reader);
    // Any condition ?
    if (this._condition == null) {
      this.query = mainQuery;
    } else {
      // combine with condition then
      BooleanQuery.Builder dad = new BooleanQuery.Builder();
      dad.add(this._condition, Occur.MUST);
      dad.add(mainQuery, Occur.MUST);
      this.query = dad.build();
    }
  }

  public boolean isIncomplete() {
    return this.tooManyPrefixes;
  }

  /**
   * Prints this query as XML on the specified XML writer.
   *
   * 

XML: *

{@code
   *  
   *    
   *      
   *      
   *      
   *      ...
   *    
   *    
   *     
   *     ...
   *    
   *  
   * }
* * @see Query#toString() * * @param xml The XML writer to use. * * @throws IOException If thrown by */ @Override public void toXML(XMLWriter xml) throws IOException { xml.openElement("suggestion-query"); if (this.tooManyPrefixes) xml.attribute("incomplete", "true"); xml.openElement("terms"); for (Term t : this._terms) { xml.openElement("term"); xml.attribute("field", t.field()); xml.attribute("text", t.text()); xml.closeElement(); } xml.closeElement(); if (this._condition != null) { xml.openElement("condition"); xml.writeText(this._condition.toString()); xml.closeElement(); } xml.closeElement(); } /** * {@inheritDoc} */ @Override public Query toQuery() { return this.query; } /** * {@inheritDoc} */ @Override public boolean isEmpty() { return this._terms.isEmpty(); } /** * Always sort by relevance. *

{@inheritDoc} */ @Override public Sort getSort() { return Sort.RELEVANCE; } // ------------------------------------------------------- // private helpers // ------------------------------------------------------- private BooleanQuery computeORQuery(IndexReader reader) throws IOException { // Generate the query BooleanQuery.Builder builder = new BooleanQuery.Builder(); try { for (Term term : this._terms) { // find prefixed terms List values = Terms.prefix(reader, term); for (String v : values) { addTermQuery(term, v, builder); } } } catch (IndexSearcher.TooManyClauses ex) { this.tooManyPrefixes = true; } return builder.build(); } private BooleanQuery computeANDQuery(IndexReader reader) throws IOException { // one term, simpler query if (this._terms.size() == 1) { BooleanQuery.Builder builder = new BooleanQuery.Builder(); Term only = this._terms.get(0); // find prefixed terms List values = Terms.prefix(reader, only); try { for (String v : values) { addTermQuery(only, v, builder); } } catch (IndexSearcher.TooManyClauses ex) { this.tooManyPrefixes = true; } // if empty, will match nothing, that's ok return builder.build(); } // group queries by word HashMap wordQueries = new HashMap<>(); // Compute the list of terms for (Term term : this._terms) { try { BooleanQuery.Builder q = wordQueries.computeIfAbsent(term.text(), s -> new BooleanQuery.Builder()); // find prefixed terms List values = Terms.prefix(reader, term); for (String v : values) { addTermQuery(term, v, q); } } catch (IndexSearcher.TooManyClauses ex) { this.tooManyPrefixes = true; } } BooleanQuery.Builder bq = new BooleanQuery.Builder(); for (BooleanQuery.Builder subq : wordQueries.values()) { BooleanQuery q = subq.build(); if (!q.clauses().isEmpty()) bq.add(subq.build(), Occur.MUST); } return bq.build(); } private void addTermQuery(Term original, String value, BooleanQuery.Builder query) throws IndexSearcher.TooManyClauses { if (value.equals(original.text())) { query.add(new BoostQuery(new TermQuery(original), 2.0f), Occur.SHOULD); } else { query.add(new TermQuery(new Term(original.field(), value)), Occur.SHOULD); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy