pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / src / java / org / apache / lucene / search / function / CustomScoreQuery.java
diff --git a/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/search/function/CustomScoreQuery.java b/lucene-java-3.5.0/lucene/src/java/org/apache/lucene/search/function/CustomScoreQuery.java
new file mode 100755 (executable)
index 0000000..8d33ee8
--- /dev/null
@@ -0,0 +1,386 @@
+package org.apache.lucene.search.function;
+
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+import java.io.IOException;
+import java.util.Set;
+import java.util.Arrays;
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.ComplexExplanation;
+import org.apache.lucene.search.Explanation;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Weight;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.search.Searcher;
+import org.apache.lucene.search.Similarity;
+import org.apache.lucene.util.ToStringUtils;
+
+/**
+ * Query that sets document score as a programmatic function of several (sub) scores:
+ * <ol>
+ *    <li>the score of its subQuery (any query)</li>
+ *    <li>(optional) the score of its ValueSourceQuery (or queries).
+ *        For most simple/convenient use cases this query is likely to be a 
+ *        {@link org.apache.lucene.search.function.FieldScoreQuery FieldScoreQuery}</li>
+ * </ol>
+ * Subclasses can modify the computation by overriding {@link #getCustomScoreProvider}.
+ * 
+ * @lucene.experimental
+ */
+public class CustomScoreQuery extends Query {
+
+  private Query subQuery;
+  private ValueSourceQuery[] valSrcQueries; // never null (empty array if there are no valSrcQueries).
+  private boolean strict = false; // if true, valueSource part of query does not take part in weights normalization.  
+  
+  /**
+   * Create a CustomScoreQuery over input subQuery.
+   * @param subQuery the sub query whose scored is being customized. Must not be null. 
+   */
+  public CustomScoreQuery(Query subQuery) {
+    this(subQuery, new ValueSourceQuery[0]);
+  }
+
+  /**
+   * Create a CustomScoreQuery over input subQuery and a {@link ValueSourceQuery}.
+   * @param subQuery the sub query whose score is being customized. Must not be null.
+   * @param valSrcQuery a value source query whose scores are used in the custom score
+   * computation. For most simple/convenient use case this would be a 
+   * {@link org.apache.lucene.search.function.FieldScoreQuery FieldScoreQuery}.
+   * This parameter is optional - it can be null.
+   */
+  public CustomScoreQuery(Query subQuery, ValueSourceQuery valSrcQuery) {
+         this(subQuery, valSrcQuery!=null ? // don't want an array that contains a single null.. 
+        new ValueSourceQuery[] {valSrcQuery} : new ValueSourceQuery[0]);
+  }
+
+  /**
+   * Create a CustomScoreQuery over input subQuery and a {@link ValueSourceQuery}.
+   * @param subQuery the sub query whose score is being customized. Must not be null.
+   * @param valSrcQueries value source queries whose scores are used in the custom score
+   * computation. For most simple/convenient use case these would be 
+   * {@link org.apache.lucene.search.function.FieldScoreQuery FieldScoreQueries}.
+   * This parameter is optional - it can be null or even an empty array.
+   */
+  public CustomScoreQuery(Query subQuery, ValueSourceQuery... valSrcQueries) {
+    this.subQuery = subQuery;
+    this.valSrcQueries = valSrcQueries!=null?
+        valSrcQueries : new ValueSourceQuery[0];
+    if (subQuery == null) throw new IllegalArgumentException("<subquery> must not be null!");
+  }
+
+  /*(non-Javadoc) @see org.apache.lucene.search.Query#rewrite(org.apache.lucene.index.IndexReader) */
+  @Override
+  public Query rewrite(IndexReader reader) throws IOException {
+    CustomScoreQuery clone = null;
+    
+    final Query sq = subQuery.rewrite(reader);
+    if (sq != subQuery) {
+      clone = (CustomScoreQuery) clone();
+      clone.subQuery = sq;
+    }
+
+    for(int i = 0; i < valSrcQueries.length; i++) {
+      final ValueSourceQuery v = (ValueSourceQuery) valSrcQueries[i].rewrite(reader);
+      if (v != valSrcQueries[i]) {
+        if (clone == null) clone = (CustomScoreQuery) clone();
+        clone.valSrcQueries[i] = v;
+      }
+    }
+    
+    return (clone == null) ? this : clone;
+  }
+
+  /*(non-Javadoc) @see org.apache.lucene.search.Query#extractTerms(java.util.Set) */
+  @Override
+  public void extractTerms(Set<Term> terms) {
+    subQuery.extractTerms(terms);
+    for(int i = 0; i < valSrcQueries.length; i++) {
+      valSrcQueries[i].extractTerms(terms);
+    }
+  }
+
+  /*(non-Javadoc) @see org.apache.lucene.search.Query#clone() */
+  @Override
+  public Object clone() {
+    CustomScoreQuery clone = (CustomScoreQuery)super.clone();
+    clone.subQuery = (Query) subQuery.clone();
+    clone.valSrcQueries = new ValueSourceQuery[valSrcQueries.length];
+    for(int i = 0; i < valSrcQueries.length; i++) {
+      clone.valSrcQueries[i] = (ValueSourceQuery) valSrcQueries[i].clone();
+    }
+    return clone;
+  }
+
+  /* (non-Javadoc) @see org.apache.lucene.search.Query#toString(java.lang.String) */
+  @Override
+  public String toString(String field) {
+    StringBuilder sb = new StringBuilder(name()).append("(");
+    sb.append(subQuery.toString(field));
+    for(int i = 0; i < valSrcQueries.length; i++) {
+      sb.append(", ").append(valSrcQueries[i].toString(field));
+    }
+    sb.append(")");
+    sb.append(strict?" STRICT" : "");
+    return sb.toString() + ToStringUtils.boost(getBoost());
+  }
+
+  /** Returns true if <code>o</code> is equal to this. */
+  @Override
+  public boolean equals(Object o) {
+    if (this == o)
+      return true;
+    if (!super.equals(o))
+      return false;
+    if (getClass() != o.getClass()) {
+      return false;
+    }
+    CustomScoreQuery other = (CustomScoreQuery)o;
+    if (this.getBoost() != other.getBoost() ||
+        !this.subQuery.equals(other.subQuery) ||
+        this.strict != other.strict ||
+        this.valSrcQueries.length != other.valSrcQueries.length) {
+      return false;
+    }
+    return Arrays.equals(valSrcQueries, other.valSrcQueries);
+  }
+
+  /** Returns a hash code value for this object. */
+  @Override
+  public int hashCode() {
+    return (getClass().hashCode() + subQuery.hashCode() + Arrays.hashCode(valSrcQueries))
+      ^ Float.floatToIntBits(getBoost()) ^ (strict ? 1234 : 4321);
+  }
+  
+  /**
+   * Returns a {@link CustomScoreProvider} that calculates the custom scores
+   * for the given {@link IndexReader}. The default implementation returns a default
+   * implementation as specified in the docs of {@link CustomScoreProvider}.
+   * @since 2.9.2
+   */
+  protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) throws IOException {
+    return new CustomScoreProvider(reader);
+  }
+
+  //=========================== W E I G H T ============================
+  
+  private class CustomWeight extends Weight {
+    Similarity similarity;
+    Weight subQueryWeight;
+    Weight[] valSrcWeights;
+    boolean qStrict;
+
+    public CustomWeight(Searcher searcher) throws IOException {
+      this.similarity = getSimilarity(searcher);
+      this.subQueryWeight = subQuery.createWeight(searcher);
+      this.valSrcWeights = new Weight[valSrcQueries.length];
+      for(int i = 0; i < valSrcQueries.length; i++) {
+        this.valSrcWeights[i] = valSrcQueries[i].createWeight(searcher);
+      }
+      this.qStrict = strict;
+    }
+
+    /*(non-Javadoc) @see org.apache.lucene.search.Weight#getQuery() */
+    @Override
+    public Query getQuery() {
+      return CustomScoreQuery.this;
+    }
+
+    /*(non-Javadoc) @see org.apache.lucene.search.Weight#getValue() */
+    @Override
+    public float getValue() {
+      return getBoost();
+    }
+
+    /*(non-Javadoc) @see org.apache.lucene.search.Weight#sumOfSquaredWeights() */
+    @Override
+    public float sumOfSquaredWeights() throws IOException {
+      float sum = subQueryWeight.sumOfSquaredWeights();
+      for(int i = 0; i < valSrcWeights.length; i++) {
+        if (qStrict) {
+          valSrcWeights[i].sumOfSquaredWeights(); // do not include ValueSource part in the query normalization
+        } else {
+          sum += valSrcWeights[i].sumOfSquaredWeights();
+        }
+      }
+      sum *= getBoost() * getBoost(); // boost each sub-weight
+      return sum ;
+    }
+
+    /*(non-Javadoc) @see org.apache.lucene.search.Weight#normalize(float) */
+    @Override
+    public void normalize(float norm) {
+      norm *= getBoost(); // incorporate boost
+      subQueryWeight.normalize(norm);
+      for(int i = 0; i < valSrcWeights.length; i++) {
+        if (qStrict) {
+          valSrcWeights[i].normalize(1); // do not normalize the ValueSource part
+        } else {
+          valSrcWeights[i].normalize(norm);
+        }
+      }
+    }
+
+    @Override
+    public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder, boolean topScorer) throws IOException {
+      // Pass true for "scoresDocsInOrder", because we
+      // require in-order scoring, even if caller does not,
+      // since we call advance on the valSrcScorers.  Pass
+      // false for "topScorer" because we will not invoke
+      // score(Collector) on these scorers:
+      Scorer subQueryScorer = subQueryWeight.scorer(reader, true, false);
+      if (subQueryScorer == null) {
+        return null;
+      }
+      Scorer[] valSrcScorers = new Scorer[valSrcWeights.length];
+      for(int i = 0; i < valSrcScorers.length; i++) {
+         valSrcScorers[i] = valSrcWeights[i].scorer(reader, true, topScorer);
+      }
+      return new CustomScorer(similarity, reader, this, subQueryScorer, valSrcScorers);
+    }
+
+    @Override
+    public Explanation explain(IndexReader reader, int doc) throws IOException {
+      Explanation explain = doExplain(reader, doc);
+      return explain == null ? new Explanation(0.0f, "no matching docs") : explain;
+    }
+    
+    private Explanation doExplain(IndexReader reader, int doc) throws IOException {
+      Explanation subQueryExpl = subQueryWeight.explain(reader, doc);
+      if (!subQueryExpl.isMatch()) {
+        return subQueryExpl;
+      }
+      // match
+      Explanation[] valSrcExpls = new Explanation[valSrcWeights.length];
+      for(int i = 0; i < valSrcWeights.length; i++) {
+        valSrcExpls[i] = valSrcWeights[i].explain(reader, doc);
+      }
+      Explanation customExp = CustomScoreQuery.this.getCustomScoreProvider(reader).customExplain(doc,subQueryExpl,valSrcExpls);
+      float sc = getValue() * customExp.getValue();
+      Explanation res = new ComplexExplanation(
+        true, sc, CustomScoreQuery.this.toString() + ", product of:");
+      res.addDetail(customExp);
+      res.addDetail(new Explanation(getValue(), "queryBoost")); // actually using the q boost as q weight (== weight value)
+      return res;
+    }
+
+    @Override
+    public boolean scoresDocsOutOfOrder() {
+      return false;
+    }
+    
+  }
+
+
+  //=========================== S C O R E R ============================
+  
+  /**
+   * A scorer that applies a (callback) function on scores of the subQuery.
+   */
+  private class CustomScorer extends Scorer {
+    private final float qWeight;
+    private Scorer subQueryScorer;
+    private Scorer[] valSrcScorers;
+    private final CustomScoreProvider provider;
+    private float vScores[]; // reused in score() to avoid allocating this array for each doc 
+
+    // constructor
+    private CustomScorer(Similarity similarity, IndexReader reader, CustomWeight w,
+        Scorer subQueryScorer, Scorer[] valSrcScorers) throws IOException {
+      super(similarity,w);
+      this.qWeight = w.getValue();
+      this.subQueryScorer = subQueryScorer;
+      this.valSrcScorers = valSrcScorers;
+      this.vScores = new float[valSrcScorers.length];
+      this.provider = CustomScoreQuery.this.getCustomScoreProvider(reader);
+    }
+
+    @Override
+    public int nextDoc() throws IOException {
+      int doc = subQueryScorer.nextDoc();
+      if (doc != NO_MORE_DOCS) {
+        for (int i = 0; i < valSrcScorers.length; i++) {
+          valSrcScorers[i].advance(doc);
+        }
+      }
+      return doc;
+    }
+
+    @Override
+    public int docID() {
+      return subQueryScorer.docID();
+    }
+    
+    /*(non-Javadoc) @see org.apache.lucene.search.Scorer#score() */
+    @Override
+    public float score() throws IOException {
+      for (int i = 0; i < valSrcScorers.length; i++) {
+        vScores[i] = valSrcScorers[i].score();
+      }
+      return qWeight * provider.customScore(subQueryScorer.docID(), subQueryScorer.score(), vScores);
+    }
+
+    @Override
+    public int advance(int target) throws IOException {
+      int doc = subQueryScorer.advance(target);
+      if (doc != NO_MORE_DOCS) {
+        for (int i = 0; i < valSrcScorers.length; i++) {
+          valSrcScorers[i].advance(doc);
+        }
+      }
+      return doc;
+    }
+  }
+
+  @Override
+  public Weight createWeight(Searcher searcher) throws IOException {
+    return new CustomWeight(searcher);
+  }
+
+  /**
+   * Checks if this is strict custom scoring.
+   * In strict custom scoring, the ValueSource part does not participate in weight normalization.
+   * This may be useful when one wants full control over how scores are modified, and does 
+   * not care about normalizing by the ValueSource part.
+   * One particular case where this is useful if for testing this query.   
+   * <P>
+   * Note: only has effect when the ValueSource part is not null.
+   */
+  public boolean isStrict() {
+    return strict;
+  }
+
+  /**
+   * Set the strict mode of this query. 
+   * @param strict The strict mode to set.
+   * @see #isStrict()
+   */
+  public void setStrict(boolean strict) {
+    this.strict = strict;
+  }
+
+  /**
+   * A short name of this query, used in {@link #toString(String)}.
+   */
+  public String name() {
+    return "custom";
+  }
+
+}