package doser.lucene.query; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.apache.lucene.index.AtomicReaderContext; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Term; import org.apache.lucene.search.Explanation; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.Scorer; import org.apache.lucene.search.Weight; import org.apache.lucene.util.Bits; public class LearnToRankQuery extends Query implements Iterable<LearnToRankClause>, Cloneable { // NOPMD by quh on 28.02.14 // 11:00 /** * Expert: the Weight for LearnToRankQuery, used to normalize, score and * explain these queries. * * <p> * NOTE: this API and implementation is subject to change suddenly in the * next release. * </p> */ public class LearnToRankWeight extends Weight { private final List<LearnToRankClause> optionalClauses; private final List<LearnToRankClause> requiredClauses; // private final boolean termConjunction; public LearnToRankWeight(final IndexSearcher searcher) throws IOException { // Check for required and optional weights final List<LearnToRankClause> requiredClauses = new LinkedList<LearnToRankClause>(); final List<LearnToRankClause> optionalClauses = new LinkedList<LearnToRankClause>(); // boolean termConjunction = true; for (int i = 0; i < clausesList.size(); i++) { final LearnToRankClause clause = clausesList.get(i); final Weight cweight = clause.getQuery().createWeight(searcher); if (clause.isMustOccur()) { requiredClauses.add(clause); } else { optionalClauses.add(clause); } // if (!(clause.isMustOccur() && w instanceof TermWeight)) { // termConjunction = false; // } clause.setW(cweight); } this.requiredClauses = requiredClauses; this.optionalClauses = optionalClauses; // this.termConjunction = termConjunction; } public float coord(final int overlap, final int maxOverlap) { // LUCENE-4300: in most cases of maxOverlap=1, BQ rewrites itself // away, // so coord() is not applied. But when BQ cannot optimize itself // away // for a single clause (minNrShouldMatch, prohibited clausesList, // etc), // its // important not to apply coord(1,1) for consistency, it might not // be 1.0F return maxOverlap == 1 ? 1F : (overlap / (float) maxOverlap); } // // private Scorer createConjunctionTermScorer(AtomicReaderContext // context, // Bits acceptDocs) throws IOException { // // // TODO: fix scorer API to specify "needsScores" up // // front, so we can do match-only if caller doesn't // // needs scores // // final DocsAndFreqs[] docsAndFreqs = new DocsAndFreqs[weights.length]; // for (int i = 0; i < docsAndFreqs.length; i++) { // final TermWeight weight = (TermWeight) weights[i]; // final Scorer scorer = weight.scorer(context, true, false, // acceptDocs); // if (scorer == null) { // return null; // } else { // assert scorer instanceof TermScorer; // docsAndFreqs[i] = new DocsAndFreqs( // (LearnToRankTermScorer) scorer); // } // } // return new ConjunctionTermScorer(this, coord( // docsAndFreqs.length, docsAndFreqs.length), docsAndFreqs); // } /** * Explanation is not necessary to create a working LearnToRank Query. * Method content will be created later! :-) */ @Override public Explanation explain(final AtomicReaderContext context, final int doc) throws IOException { return new Explanation(); } @Override public Query getQuery() { return LearnToRankQuery.this; } /** * Possibility to additionally boost the featureclauses with a weight w * but this is not recommended! Boost should be one! */ @Override public float getValueForNormalization() throws IOException { return 1f; } @Override public void normalize(final float norm, final float topLevelBoost) { final float boost = topLevelBoost * getBoost(); for (final LearnToRankClause learnToRankClause : requiredClauses) { final LearnToRankClause clause = learnToRankClause; clause.getW().normalize(norm, boost); } for (final LearnToRankClause learnToRankClause : optionalClauses) { final LearnToRankClause clause = learnToRankClause; clause.getW().normalize(norm, boost); } } @Override public Scorer scorer(AtomicReaderContext context, Bits acceptDocs) throws IOException { // if (termConjunction) { // Wird bewusst ausgeklammert um irgendwann durch Zufall // rauszufinden was hier genau passiert. // // specialized scorer for term conjunctions // return createConjunctionTermScorer(context, acceptDocs); // new Exception().printStackTrace(); // } final List<Scorer> requiredScorer = new ArrayList<Scorer>(); final List<Scorer> optionalScorer = new ArrayList<Scorer>(); final List<LearnToRankClause> reqClausesWNull = new ArrayList<LearnToRankClause>(); final List<LearnToRankClause> optClausesWNull = new ArrayList<LearnToRankClause>(); for (final LearnToRankClause learnToRankClause : requiredClauses) { final LearnToRankClause clause = learnToRankClause; final Scorer subscorer = clause.getW().scorer(context, acceptDocs); if (subscorer == null) { return null; } else { requiredScorer.add(subscorer); reqClausesWNull.add(clause); } } for (final LearnToRankClause learnToRankClause : optionalClauses) { final LearnToRankClause clause = learnToRankClause; final Scorer subscorer = clause.getW().scorer(context, acceptDocs); if (subscorer != null) { optionalScorer.add(subscorer); optClausesWNull.add(clause); } } final LearnToRankClause[] reqClausesArr = new LearnToRankClause[reqClausesWNull .size()]; final LearnToRankClause[] optClausesArr = new LearnToRankClause[optClausesWNull .size()]; reqClausesWNull.toArray(reqClausesArr); optClausesWNull.toArray(optClausesArr); if ((reqClausesArr.length == 0) && (optClausesArr.length == 0)) { return null; } final Scorer scorer = new LearnToRankScorer(this, optionalScorer, requiredScorer, optClausesArr, reqClausesArr, context.docBase); return scorer; } } private ArrayList<LearnToRankClause> clausesList; public LearnToRankQuery() { clausesList = new ArrayList<LearnToRankClause>(); } /** * Adds a clause to a LearnToRank query. */ public LearnToRankClause add(final Query query, final String name, final boolean mustOccur) { final LearnToRankClause clause = new LearnToRankClause(query, name, mustOccur); clausesList.add(clause); return clause; } /** Returns the list of clausesList in this query. */ public List<LearnToRankClause> clauses() { return clausesList; } @Override @SuppressWarnings("unchecked") public LearnToRankQuery clone() { final LearnToRankQuery clone = (LearnToRankQuery) super.clone(); clone.clausesList = (ArrayList<LearnToRankClause>) clausesList.clone(); return clone; } public double[] createFeatureVector(final int docIndex) { final double[] result = new double[clausesList.size()]; for (int i = 0; i < clausesList.size(); i++) { result[i] = clausesList.get(i).getFeatureValue(docIndex); } return result; } @Override public Weight createWeight(final IndexSearcher searcher) throws IOException { return new LearnToRankWeight(searcher); } /** Returns true iff <code>o</code> is equal to this. */ @Override public boolean equals(final Object obj) { if (!(obj instanceof LearnToRankQuery)) { return false; } final LearnToRankQuery other = (LearnToRankQuery) obj; return (getBoost() == other.getBoost()) && clausesList.equals(other.clausesList); } @Override public void extractTerms(final Set<Term> terms) { // Not needed up to now } /** Returns the set of clausesList in this query. */ public LearnToRankClause[] getClauses() { return clausesList.toArray(new LearnToRankClause[clausesList.size()]); } /** Returns a hash code value for this object. */ @Override public int hashCode() { return Float.floatToIntBits(getBoost()) ^ clausesList.hashCode(); } /** * Returns an iterator on the clausesList in this query. It implements the * {@link Iterable} interface to make it possible to do: * * <pre class="prettyprint"> * for (FeatureClause clause : featureQuery) { * } * </pre> */ @Override public Iterator<LearnToRankClause> iterator() { return clauses().iterator(); } /** * Expert: called to re-write queries into primitive queries. For example, a * PrefixQuery will be rewritten into a BooleanQuery that consists of * TermQuerys. * * But this method is not used or optimized in this class! */ @Override public Query rewrite(final IndexReader reader) throws IOException { for (int i = 0; i < clausesList.size(); i++) { final Query query = clausesList.get(i).getQuery().rewrite(reader); clausesList.get(i).setQuery(query); } return this; } @Override public String toString(final String field) { return "This is a Learn To Rank query whose string representation is not implemented."; } }