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

com.sindicetech.siren.search.node.NodeBooleanQuery Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (c) 2014, Sindice Limited. All Rights Reserved.
 *
 * This file is part of the SIREn project.
 *
 * SIREn is a free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * SIREn 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with this program. If not, see .
 */

package com.sindicetech.siren.search.node;

import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.*;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.ToStringUtils;

import com.sindicetech.siren.search.node.TwigQuery.TwigWeight;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * A {@link NodeQuery} that matches a boolean combination of node queries, e.g.,
 * {@link NodeTermQuery}s, {@link NodePhraseQuery}s, {@link NodeBooleanQuery}s,
 * ...
 *
 * 

* * Code taken from {@link BooleanQuery} and adapted for the Siren use case. */ public class NodeBooleanQuery extends NodeQuery { private static int maxClauseCount = 1024; /** * Return the maximum number of clauses permitted, 1024 by default. Attempts * to add more than the permitted number of clauses cause * {@link TooManyClauses} to be thrown. * * @see #setMaxClauseCount(int) */ public static int getMaxClauseCount() { return maxClauseCount; } /** * Set the maximum number of clauses permitted per BooleanQuery. Default value * is 1024. */ public static void setMaxClauseCount(final int maxClauseCount) { if (maxClauseCount < 1) throw new IllegalArgumentException("maxClauseCount must be >= 1"); NodeBooleanQuery.maxClauseCount = maxClauseCount; } protected ArrayList clauses = new ArrayList(); /** Constructs an empty boolean query. */ public NodeBooleanQuery() {} @Override public void setLevelConstraint(final int levelConstraint) { super.setLevelConstraint(levelConstraint); // keep clauses synchronised for (final NodeBooleanClause clause : clauses) { clause.getQuery().setLevelConstraint(levelConstraint); } } @Override public void setNodeConstraint(final int lowerBound, final int upperBound) { super.setNodeConstraint(lowerBound, upperBound); // keep clauses synchronised for (final NodeBooleanClause clause : clauses) { clause.getQuery().setNodeConstraint(lowerBound, upperBound); } } @Override public void setAncestorPointer(final NodeQuery ancestor) { super.setAncestorPointer(ancestor); // keep clauses synchronised for (final NodeBooleanClause clause : clauses) { clause.getQuery().setAncestorPointer(ancestor); } } /** * Adds a clause to a boolean query. * * @throws TooManyClauses * if the new number of clauses exceeds the maximum clause number * @see #getMaxClauseCount() */ public void add(final NodeQuery query, final NodeBooleanClause.Occur occur) { this.add(new NodeBooleanClause(query, occur)); } /** * Adds a clause to a boolean query. * * @throws TooManyClauses * if the new number of clauses exceeds the maximum clause number * @see #getMaxClauseCount() */ public void add(final NodeBooleanClause clause) { if (clauses.size() >= maxClauseCount) { throw new TooManyClauses(); } clauses.add(clause); // keep the clause synchronised in term of constraint management clause.getQuery().setLevelConstraint(levelConstraint); clause.getQuery().setNodeConstraint(lowerBound, upperBound); clause.getQuery().setAncestorPointer(ancestor); } /** Returns the set of clauses in this query. */ public NodeBooleanClause[] getClauses() { return clauses.toArray(new NodeBooleanClause[clauses.size()]); } /** Returns the list of clauses in this query. */ public List clauses() { return clauses; } /** * Returns an iterator on the clauses in this query. It implements the * {@link Iterable} interface to make it possible to do: *

for (SirenBooleanClause clause : booleanQuery) {}
*/ public final Iterator iterator() { return this.clauses().iterator(); } /** * Expert: An abstract weight for queries with node boolean clauses. *

* This abstract class enables to use the {@link NodeBooleanScorer} by many * different query implementation. *

* It is subclassed by {@link NodeBooleanWeight}, {@link TwigWeight}. */ static abstract class AbstractNodeBooleanWeight extends Weight { protected ArrayList weights; public AbstractNodeBooleanWeight(final IndexSearcher searcher) throws IOException { this.initWeights(searcher); } /** * Return the weights of the node boolean clauses. */ public List getWeights() { return this.weights; } protected abstract void initWeights(final IndexSearcher searcher) throws IOException; } /** * Expert: the Weight for {@link NodeBooleanQuery}, used to * normalize, score and explain these queries. */ public class NodeBooleanWeight extends AbstractNodeBooleanWeight { public NodeBooleanWeight(final IndexSearcher searcher) throws IOException { super(searcher); } @Override protected void initWeights(final IndexSearcher searcher) throws IOException { weights = new ArrayList(clauses.size()); for (int i = 0; i < clauses.size(); i++) { final NodeBooleanClause c = clauses.get(i); final NodeQuery q = c.getQuery(); // pass to child query the node constraints q.setNodeConstraint(lowerBound, upperBound); q.setLevelConstraint(levelConstraint); // transfer ancestor pointer to child q.setAncestorPointer(ancestor); weights.add(q.createWeight(searcher)); } } @Override public String toString() { return "weight(" + NodeBooleanQuery.this + ")"; } @Override public Query getQuery() { return NodeBooleanQuery.this; } @Override public float getValueForNormalization() throws IOException { float sum = 0.0f; for (int i = 0; i < weights.size(); i++) { // call sumOfSquaredWeights for all clauses in case of side effects final float s = weights.get(i).getValueForNormalization(); // sum sub weights if (!clauses.get(i).isProhibited()) { // only add to sum for non-prohibited clauses sum += s; } } // boost each sub-weight sum *= NodeBooleanQuery.this.getBoost() * NodeBooleanQuery.this.getBoost(); return sum; } @Override public void normalize(final float norm, float topLevelBoost) { // incorporate boost topLevelBoost *= NodeBooleanQuery.this.getBoost(); for (final Weight w : weights) { // normalize all clauses, (even if prohibited in case of side affects) w.normalize(norm, topLevelBoost); } } @Override public Explanation explain(final AtomicReaderContext context, final int doc) throws IOException { final ComplexExplanation sumExpl = new ComplexExplanation(); sumExpl.setDescription("sum of:"); int coord = 0; float sum = 0.0f; boolean fail = false; final Iterator cIter = clauses.iterator(); for (final Weight w : weights) { final NodeBooleanClause c = cIter.next(); if (w.scorer(context, context.reader().getLiveDocs()) == null) { if (c.isRequired()) { fail = true; final Explanation r = new Explanation(0.0f, "no match on required " + "clause (" + c.getQuery().toString() + ")"); sumExpl.addDetail(r); } continue; } final Explanation e = w.explain(context, doc); if (e.isMatch()) { if (!c.isProhibited()) { sumExpl.addDetail(e); sum += e.getValue(); coord++; } else { final Explanation r = new Explanation(0.0f, "match on prohibited clause (" + c.getQuery().toString() + ")"); r.addDetail(e); sumExpl.addDetail(r); fail = true; } } else if (c.isRequired()) { final Explanation r = new Explanation(0.0f, "no match on required " + "clause (" + c.getQuery().toString() + ")"); r.addDetail(e); sumExpl.addDetail(r); fail = true; } } if (fail) { sumExpl.setMatch(Boolean.FALSE); sumExpl.setValue(0.0f); sumExpl.setDescription ("Failure to meet condition(s) of required/prohibited clause(s)"); return sumExpl; } sumExpl.setMatch(0 < coord ? Boolean.TRUE : Boolean.FALSE); sumExpl.setValue(sum); return sumExpl; } @Override public Scorer scorer(final AtomicReaderContext context, final Bits acceptDocs) throws IOException { final List required = new ArrayList(); final List prohibited = new ArrayList(); final List optional = new ArrayList(); final Iterator cIter = clauses.iterator(); for (final Weight w : weights) { final NodeBooleanClause c = cIter.next(); final NodeScorer subScorer = (NodeScorer) w.scorer(context, acceptDocs); if (subScorer == null) { if (c.isRequired()) { return null; } } else if (c.isRequired()) { required.add(subScorer); } else if (c.isProhibited()) { prohibited.add(subScorer); } else { optional.add(subScorer); } } if (required.size() == 0 && optional.size() == 0) { // no required and optional clauses. return null; } return new NodeBooleanScorer(this, required, prohibited, optional); } } @Override public Weight createWeight(final IndexSearcher searcher) throws IOException { return new NodeBooleanWeight(searcher); } @Override public Query rewrite(final IndexReader reader) throws IOException { if (clauses.size() == 1) { // optimize 1-clause queries final NodeBooleanClause c = clauses.get(0); if (!c.isProhibited()) { // just return clause // rewrite first NodeQuery query = (NodeQuery) c.getQuery().rewrite(reader); if (this.getBoost() != 1.0f) { // incorporate boost if (query == c.getQuery()) { // if rewrite was no-op query = (NodeQuery) query.clone(); // then clone before boost } query.setBoost(this.getBoost() * query.getBoost()); } // transfer constraints query.setNodeConstraint(lowerBound, upperBound); query.setLevelConstraint(levelConstraint); // transfer ancestor pointer query.setAncestorPointer(ancestor); return query; } } NodeBooleanQuery clone = null; // recursively rewrite for (int i = 0 ; i < clauses.size(); i++) { final NodeBooleanClause c = clauses.get(i); final NodeQuery query = (NodeQuery) c.getQuery().rewrite(reader); if (query != c.getQuery()) { // clause rewrote: must clone if (clone == null) { clone = (NodeBooleanQuery) this.clone(); } // transfer constraints query.setNodeConstraint(lowerBound, upperBound); query.setLevelConstraint(levelConstraint); // transfer ancestor pointer query.setAncestorPointer(ancestor); clone.clauses.set(i, new NodeBooleanClause(query, c.getOccur())); } } if (clone != null) { return clone; // some clauses rewrote } else { return this; // no clauses rewrote } } @Override public void extractTerms(final Set terms) { for (final NodeBooleanClause clause : clauses) { clause.getQuery().extractTerms(terms); } } @Override @SuppressWarnings("unchecked") public Query clone() { final NodeBooleanQuery clone = (NodeBooleanQuery) super.clone(); clone.clauses = (ArrayList) this.clauses.clone(); return clone; } /** Prints a user-readable version of this query. */ @Override public String toString(final String field) { final StringBuilder buffer = new StringBuilder(); final boolean hasBoost = (this.getBoost() != 1.0); buffer.append("("); for (int i = 0; i < clauses.size(); i++) { final NodeBooleanClause c = clauses.get(i); if (c.isProhibited()) buffer.append("-"); else if (c.isRequired()) buffer.append("+"); final Query subQuery = c.getQuery(); if (subQuery != null) { buffer.append(subQuery.toString(field)); } if (i != clauses.size() - 1) buffer.append(" "); } buffer.append(")"); if (hasBoost) { buffer.append(ToStringUtils.boost(this.getBoost())); } return buffer.toString(); } /** Returns true iff o is equal to this. */ @Override public boolean equals(final Object o) { if (!(o instanceof NodeBooleanQuery)) return false; final NodeBooleanQuery other = (NodeBooleanQuery) o; return (this.getBoost() == other.getBoost()) && this.clauses.equals(other.clauses) && this.levelConstraint == other.levelConstraint && this.lowerBound == other.lowerBound && this.upperBound == other.upperBound; } /** Returns a hash code value for this object. */ @Override public int hashCode() { return Float.floatToIntBits(this.getBoost()) ^ clauses.hashCode() ^ levelConstraint ^ upperBound ^ lowerBound; } /** * Thrown when an attempt is made to add more than {@link * #getMaxClauseCount()} clauses. This typically happens if * a PrefixQuery, FuzzyQuery, WildcardQuery, or TermRangeQuery * is expanded to many terms during search. */ public static class TooManyClauses extends RuntimeException { public TooManyClauses() { super("maxClauseCount is set to " + maxClauseCount); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy