Home » lucene-3.0.1-src » org.apache » lucene » search » function » [javadoc | source]

    1   package org.apache.lucene.search.function;
    2   
    3   /**
    4    * Licensed to the Apache Software Foundation (ASF) under one or more
    5    * contributor license agreements.  See the NOTICE file distributed with
    6    * this work for additional information regarding copyright ownership.
    7    * The ASF licenses this file to You under the Apache License, Version 2.0
    8    * (the "License"); you may not use this file except in compliance with
    9    * the License.  You may obtain a copy of the License at
   10    *
   11    *     http://www.apache.org/licenses/LICENSE-2.0
   12    *
   13    * Unless required by applicable law or agreed to in writing, software
   14    * distributed under the License is distributed on an "AS IS" BASIS,
   15    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   16    * See the License for the specific language governing permissions and
   17    * limitations under the License.
   18    */
   19   
   20   import java.io.IOException;
   21   import java.util.Set;
   22   import java.util.Arrays;
   23   
   24   import org.apache.lucene.index.IndexReader;
   25   import org.apache.lucene.index.Term;
   26   import org.apache.lucene.search.ComplexExplanation;
   27   import org.apache.lucene.search.Explanation;
   28   import org.apache.lucene.search.Query;
   29   import org.apache.lucene.search.Weight;
   30   import org.apache.lucene.search.Scorer;
   31   import org.apache.lucene.search.Searcher;
   32   import org.apache.lucene.search.Similarity;
   33   import org.apache.lucene.util.ToStringUtils;
   34   
   35   /**
   36    * Query that sets document score as a programmatic function of several (sub) scores:
   37    * <ol>
   38    *    <li>the score of its subQuery (any query)</li>
   39    *    <li>(optional) the score of its ValueSourceQuery (or queries).
   40    *        For most simple/convenient use cases this query is likely to be a 
   41    *        {@link org.apache.lucene.search.function.FieldScoreQuery FieldScoreQuery}</li>
   42    * </ol>
   43    * Subclasses can modify the computation by overriding {@link #getCustomScoreProvider}.
   44    * 
   45    * <p><font color="#FF0000">
   46    * WARNING: The status of the <b>search.function</b> package is experimental. 
   47    * The APIs introduced here might change in the future and will not be 
   48    * supported anymore in such a case.</font>
   49    */
   50   public class CustomScoreQuery extends Query {
   51   
   52     private Query subQuery;
   53     private ValueSourceQuery[] valSrcQueries; // never null (empty array if there are no valSrcQueries).
   54     private boolean strict = false; // if true, valueSource part of query does not take part in weights normalization.  
   55     
   56     /**
   57      * Create a CustomScoreQuery over input subQuery.
   58      * @param subQuery the sub query whose scored is being customed. Must not be null. 
   59      */
   60     public CustomScoreQuery(Query subQuery) {
   61       this(subQuery, new ValueSourceQuery[0]);
   62     }
   63   
   64     /**
   65      * Create a CustomScoreQuery over input subQuery and a {@link ValueSourceQuery}.
   66      * @param subQuery the sub query whose score is being customized. Must not be null.
   67      * @param valSrcQuery a value source query whose scores are used in the custom score
   68      * computation. For most simple/convenient use case this would be a 
   69      * {@link org.apache.lucene.search.function.FieldScoreQuery FieldScoreQuery}.
   70      * This parameter is optional - it can be null.
   71      */
   72     public CustomScoreQuery(Query subQuery, ValueSourceQuery valSrcQuery) {
   73   	  this(subQuery, valSrcQuery!=null ? // don't want an array that contains a single null.. 
   74           new ValueSourceQuery[] {valSrcQuery} : new ValueSourceQuery[0]);
   75     }
   76   
   77     /**
   78      * Create a CustomScoreQuery over input subQuery and a {@link ValueSourceQuery}.
   79      * @param subQuery the sub query whose score is being customized. Must not be null.
   80      * @param valSrcQueries value source queries whose scores are used in the custom score
   81      * computation. For most simple/convenient use case these would be 
   82      * {@link org.apache.lucene.search.function.FieldScoreQuery FieldScoreQueries}.
   83      * This parameter is optional - it can be null or even an empty array.
   84      */
   85     public CustomScoreQuery(Query subQuery, ValueSourceQuery... valSrcQueries) {
   86       this.subQuery = subQuery;
   87       this.valSrcQueries = valSrcQueries!=null?
   88           valSrcQueries : new ValueSourceQuery[0];
   89       if (subQuery == null) throw new IllegalArgumentException("<subquery> must not be null!");
   90     }
   91   
   92     /*(non-Javadoc) @see org.apache.lucene.search.Query#rewrite(org.apache.lucene.index.IndexReader) */
   93     @Override
   94     public Query rewrite(IndexReader reader) throws IOException {
   95       CustomScoreQuery clone = null;
   96       
   97       final Query sq = subQuery.rewrite(reader);
   98       if (sq != subQuery) {
   99         clone = (CustomScoreQuery) clone();
  100         clone.subQuery = sq;
  101       }
  102   
  103       for(int i = 0; i < valSrcQueries.length; i++) {
  104         final ValueSourceQuery v = (ValueSourceQuery) valSrcQueries[i].rewrite(reader);
  105         if (v != valSrcQueries[i]) {
  106           if (clone == null) clone = (CustomScoreQuery) clone();
  107           clone.valSrcQueries[i] = v;
  108         }
  109       }
  110       
  111       return (clone == null) ? this : clone;
  112     }
  113   
  114     /*(non-Javadoc) @see org.apache.lucene.search.Query#extractTerms(java.util.Set) */
  115     @Override
  116     public void extractTerms(Set<Term> terms) {
  117       subQuery.extractTerms(terms);
  118       for(int i = 0; i < valSrcQueries.length; i++) {
  119         valSrcQueries[i].extractTerms(terms);
  120       }
  121     }
  122   
  123     /*(non-Javadoc) @see org.apache.lucene.search.Query#clone() */
  124     @Override
  125     public Object clone() {
  126       CustomScoreQuery clone = (CustomScoreQuery)super.clone();
  127       clone.subQuery = (Query) subQuery.clone();
  128       clone.valSrcQueries = new ValueSourceQuery[valSrcQueries.length];
  129       for(int i = 0; i < valSrcQueries.length; i++) {
  130         clone.valSrcQueries[i] = (ValueSourceQuery) valSrcQueries[i].clone();
  131       }
  132       return clone;
  133     }
  134   
  135     /* (non-Javadoc) @see org.apache.lucene.search.Query#toString(java.lang.String) */
  136     @Override
  137     public String toString(String field) {
  138       StringBuilder sb = new StringBuilder(name()).append("(");
  139       sb.append(subQuery.toString(field));
  140       for(int i = 0; i < valSrcQueries.length; i++) {
  141         sb.append(", ").append(valSrcQueries[i].toString(field));
  142       }
  143       sb.append(")");
  144       sb.append(strict?" STRICT" : "");
  145       return sb.toString() + ToStringUtils.boost(getBoost());
  146     }
  147   
  148     /** Returns true if <code>o</code> is equal to this. */
  149     @Override
  150     public boolean equals(Object o) {
  151       if (getClass() != o.getClass()) {
  152         return false;
  153       }
  154       CustomScoreQuery other = (CustomScoreQuery)o;
  155       if (this.getBoost() != other.getBoost() ||
  156           !this.subQuery.equals(other.subQuery) ||
  157           this.strict != other.strict ||
  158           this.valSrcQueries.length != other.valSrcQueries.length) {
  159         return false;
  160       }
  161       return Arrays.equals(valSrcQueries, other.valSrcQueries);
  162     }
  163   
  164     /** Returns a hash code value for this object. */
  165     @Override
  166     public int hashCode() {
  167       return (getClass().hashCode() + subQuery.hashCode() + Arrays.hashCode(valSrcQueries))
  168         ^ Float.floatToIntBits(getBoost()) ^ (strict ? 1234 : 4321);
  169     }
  170     
  171     /**
  172      * Returns a {@link CustomScoreProvider} that calculates the custom scores
  173      * for the given {@link IndexReader}. The default implementation returns a default
  174      * implementation as specified in the docs of {@link CustomScoreProvider}.
  175      * @since 2.9.2
  176      */
  177     protected CustomScoreProvider getCustomScoreProvider(IndexReader reader) throws IOException {
  178       // when deprecated methods are removed, do not extend class here, just return new default CustomScoreProvider
  179       return new CustomScoreProvider(reader) {
  180         
  181         @Override
  182         public float customScore(int doc, float subQueryScore, float valSrcScores[]) throws IOException {
  183           return CustomScoreQuery.this.customScore(doc, subQueryScore, valSrcScores);
  184         }
  185         
  186         @Override
  187         public float customScore(int doc, float subQueryScore, float valSrcScore) throws IOException {
  188           return CustomScoreQuery.this.customScore(doc, subQueryScore, valSrcScore);
  189         }
  190         
  191         @Override
  192         public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpls[]) throws IOException {
  193           return CustomScoreQuery.this.customExplain(doc, subQueryExpl, valSrcExpls);
  194         }
  195         
  196         @Override
  197         public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpl) throws IOException {
  198           return CustomScoreQuery.this.customExplain(doc, subQueryExpl, valSrcExpl);
  199         }
  200         
  201       };
  202     }
  203   
  204     /**
  205      * Compute a custom score by the subQuery score and a number of 
  206      * ValueSourceQuery scores.
  207      * @deprecated Will be removed in Lucene 3.1.
  208      * The doc is relative to the current reader, which is
  209      * unknown to CustomScoreQuery when using per-segment search (since Lucene 2.9).
  210      * Please override {@link #getCustomScoreProvider} and return a subclass
  211      * of {@link CustomScoreProvider} for the given {@link IndexReader}.
  212      * @see CustomScoreProvider#customScore(int,float,float[])
  213      */
  214     @Deprecated
  215     public float customScore(int doc, float subQueryScore, float valSrcScores[]) {
  216       if (valSrcScores.length == 1) {
  217         return customScore(doc, subQueryScore, valSrcScores[0]);
  218       }
  219       if (valSrcScores.length == 0) {
  220         return customScore(doc, subQueryScore, 1);
  221       }
  222       float score = subQueryScore;
  223       for(int i = 0; i < valSrcScores.length; i++) {
  224         score *= valSrcScores[i];
  225       }
  226       return score;
  227     }
  228   
  229     /**
  230      * Compute a custom score by the subQuery score and the ValueSourceQuery score.
  231      * @deprecated Will be removed in Lucene 3.1.
  232      * The doc is relative to the current reader, which is
  233      * unknown to CustomScoreQuery when using per-segment search (since Lucene 2.9).
  234      * Please override {@link #getCustomScoreProvider} and return a subclass
  235      * of {@link CustomScoreProvider} for the given {@link IndexReader}.
  236      * @see CustomScoreProvider#customScore(int,float,float)
  237      */
  238     @Deprecated
  239     public float customScore(int doc, float subQueryScore, float valSrcScore) {
  240       return subQueryScore * valSrcScore;
  241     }
  242   
  243     /**
  244      * Explain the custom score.
  245      * @deprecated Will be removed in Lucene 3.1.
  246      * The doc is relative to the current reader, which is
  247      * unknown to CustomScoreQuery when using per-segment search (since Lucene 2.9).
  248      * Please override {@link #getCustomScoreProvider} and return a subclass
  249      * of {@link CustomScoreProvider} for the given {@link IndexReader}.
  250      * @see CustomScoreProvider#customExplain(int,Explanation,Explanation[])
  251      */
  252     @Deprecated
  253     public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpls[]) {
  254       if (valSrcExpls.length == 1) {
  255         return customExplain(doc, subQueryExpl, valSrcExpls[0]);
  256       }
  257       if (valSrcExpls.length == 0) {
  258         return subQueryExpl;
  259       }
  260       float valSrcScore = 1;
  261       for (int i = 0; i < valSrcExpls.length; i++) {
  262         valSrcScore *= valSrcExpls[i].getValue();
  263       }
  264       Explanation exp = new Explanation( valSrcScore * subQueryExpl.getValue(), "custom score: product of:");
  265       exp.addDetail(subQueryExpl);
  266       for (int i = 0; i < valSrcExpls.length; i++) {
  267         exp.addDetail(valSrcExpls[i]);
  268       }
  269       return exp;
  270     }
  271   
  272     /**
  273      * Explain the custom score.
  274      * @deprecated Will be removed in Lucene 3.1.
  275      * The doc is relative to the current reader, which is
  276      * unknown to CustomScoreQuery when using per-segment search (since Lucene 2.9).
  277      * Please override {@link #getCustomScoreProvider} and return a subclass
  278      * of {@link CustomScoreProvider} for the given {@link IndexReader}.
  279      * @see CustomScoreProvider#customExplain(int,Explanation,Explanation[])
  280      */
  281     @Deprecated
  282     public Explanation customExplain(int doc, Explanation subQueryExpl, Explanation valSrcExpl) {
  283       float valSrcScore = 1;
  284       if (valSrcExpl != null) {
  285         valSrcScore *= valSrcExpl.getValue();
  286       }
  287       Explanation exp = new Explanation( valSrcScore * subQueryExpl.getValue(), "custom score: product of:");
  288       exp.addDetail(subQueryExpl);
  289       exp.addDetail(valSrcExpl);
  290       return exp;
  291     }
  292   
  293     //=========================== W E I G H T ============================
  294     
  295     private class CustomWeight extends Weight {
  296       Similarity similarity;
  297       Weight subQueryWeight;
  298       Weight[] valSrcWeights;
  299       boolean qStrict;
  300   
  301       public CustomWeight(Searcher searcher) throws IOException {
  302         this.similarity = getSimilarity(searcher);
  303         this.subQueryWeight = subQuery.weight(searcher);
  304         this.valSrcWeights = new Weight[valSrcQueries.length];
  305         for(int i = 0; i < valSrcQueries.length; i++) {
  306           this.valSrcWeights[i] = valSrcQueries[i].createWeight(searcher);
  307         }
  308         this.qStrict = strict;
  309       }
  310   
  311       /*(non-Javadoc) @see org.apache.lucene.search.Weight#getQuery() */
  312       @Override
  313       public Query getQuery() {
  314         return CustomScoreQuery.this;
  315       }
  316   
  317       /*(non-Javadoc) @see org.apache.lucene.search.Weight#getValue() */
  318       @Override
  319       public float getValue() {
  320         return getBoost();
  321       }
  322   
  323       /*(non-Javadoc) @see org.apache.lucene.search.Weight#sumOfSquaredWeights() */
  324       @Override
  325       public float sumOfSquaredWeights() throws IOException {
  326         float sum = subQueryWeight.sumOfSquaredWeights();
  327         for(int i = 0; i < valSrcWeights.length; i++) {
  328           if (qStrict) {
  329             valSrcWeights[i].sumOfSquaredWeights(); // do not include ValueSource part in the query normalization
  330           } else {
  331             sum += valSrcWeights[i].sumOfSquaredWeights();
  332           }
  333         }
  334         sum *= getBoost() * getBoost(); // boost each sub-weight
  335         return sum ;
  336       }
  337   
  338       /*(non-Javadoc) @see org.apache.lucene.search.Weight#normalize(float) */
  339       @Override
  340       public void normalize(float norm) {
  341         norm *= getBoost(); // incorporate boost
  342         subQueryWeight.normalize(norm);
  343         for(int i = 0; i < valSrcWeights.length; i++) {
  344           if (qStrict) {
  345             valSrcWeights[i].normalize(1); // do not normalize the ValueSource part
  346           } else {
  347             valSrcWeights[i].normalize(norm);
  348           }
  349         }
  350       }
  351   
  352       @Override
  353       public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder, boolean topScorer) throws IOException {
  354         // Pass true for "scoresDocsInOrder", because we
  355         // require in-order scoring, even if caller does not,
  356         // since we call advance on the valSrcScorers.  Pass
  357         // false for "topScorer" because we will not invoke
  358         // score(Collector) on these scorers:
  359         Scorer subQueryScorer = subQueryWeight.scorer(reader, true, false);
  360         if (subQueryScorer == null) {
  361           return null;
  362         }
  363         Scorer[] valSrcScorers = new Scorer[valSrcWeights.length];
  364         for(int i = 0; i < valSrcScorers.length; i++) {
  365            valSrcScorers[i] = valSrcWeights[i].scorer(reader, true, topScorer);
  366         }
  367         return new CustomScorer(similarity, reader, this, subQueryScorer, valSrcScorers);
  368       }
  369   
  370       @Override
  371       public Explanation explain(IndexReader reader, int doc) throws IOException {
  372         Explanation explain = doExplain(reader, doc);
  373         return explain == null ? new Explanation(0.0f, "no matching docs") : explain;
  374       }
  375       
  376       private Explanation doExplain(IndexReader reader, int doc) throws IOException {
  377         Explanation subQueryExpl = subQueryWeight.explain(reader, doc);
  378         if (!subQueryExpl.isMatch()) {
  379           return subQueryExpl;
  380         }
  381         // match
  382         Explanation[] valSrcExpls = new Explanation[valSrcWeights.length];
  383         for(int i = 0; i < valSrcWeights.length; i++) {
  384           valSrcExpls[i] = valSrcWeights[i].explain(reader, doc);
  385         }
  386         Explanation customExp = CustomScoreQuery.this.getCustomScoreProvider(reader).customExplain(doc,subQueryExpl,valSrcExpls);
  387         float sc = getValue() * customExp.getValue();
  388         Explanation res = new ComplexExplanation(
  389           true, sc, CustomScoreQuery.this.toString() + ", product of:");
  390         res.addDetail(customExp);
  391         res.addDetail(new Explanation(getValue(), "queryBoost")); // actually using the q boost as q weight (== weight value)
  392         return res;
  393       }
  394   
  395       @Override
  396       public boolean scoresDocsOutOfOrder() {
  397         return false;
  398       }
  399       
  400     }
  401   
  402   
  403     //=========================== S C O R E R ============================
  404     
  405     /**
  406      * A scorer that applies a (callback) function on scores of the subQuery.
  407      */
  408     private class CustomScorer extends Scorer {
  409       private final float qWeight;
  410       private Scorer subQueryScorer;
  411       private Scorer[] valSrcScorers;
  412       private IndexReader reader;
  413       private final CustomScoreProvider provider;
  414       private float vScores[]; // reused in score() to avoid allocating this array for each doc 
  415   
  416       // constructor
  417       private CustomScorer(Similarity similarity, IndexReader reader, CustomWeight w,
  418           Scorer subQueryScorer, Scorer[] valSrcScorers) throws IOException {
  419         super(similarity);
  420         this.qWeight = w.getValue();
  421         this.subQueryScorer = subQueryScorer;
  422         this.valSrcScorers = valSrcScorers;
  423         this.reader = reader;
  424         this.vScores = new float[valSrcScorers.length];
  425         this.provider = CustomScoreQuery.this.getCustomScoreProvider(reader);
  426       }
  427   
  428       @Override
  429       public int nextDoc() throws IOException {
  430         int doc = subQueryScorer.nextDoc();
  431         if (doc != NO_MORE_DOCS) {
  432           for (int i = 0; i < valSrcScorers.length; i++) {
  433             valSrcScorers[i].advance(doc);
  434           }
  435         }
  436         return doc;
  437       }
  438   
  439       @Override
  440       public int docID() {
  441         return subQueryScorer.docID();
  442       }
  443       
  444       /*(non-Javadoc) @see org.apache.lucene.search.Scorer#score() */
  445       @Override
  446       public float score() throws IOException {
  447         for (int i = 0; i < valSrcScorers.length; i++) {
  448           vScores[i] = valSrcScorers[i].score();
  449         }
  450         return qWeight * provider.customScore(subQueryScorer.docID(), subQueryScorer.score(), vScores);
  451       }
  452   
  453       @Override
  454       public int advance(int target) throws IOException {
  455         int doc = subQueryScorer.advance(target);
  456         if (doc != NO_MORE_DOCS) {
  457           for (int i = 0; i < valSrcScorers.length; i++) {
  458             valSrcScorers[i].advance(doc);
  459           }
  460         }
  461         return doc;
  462       }
  463     }
  464   
  465     @Override
  466     public Weight createWeight(Searcher searcher) throws IOException {
  467       return new CustomWeight(searcher);
  468     }
  469   
  470     /**
  471      * Checks if this is strict custom scoring.
  472      * In strict custom scoring, the ValueSource part does not participate in weight normalization.
  473      * This may be useful when one wants full control over how scores are modified, and does 
  474      * not care about normalizing by the ValueSource part.
  475      * One particular case where this is useful if for testing this query.   
  476      * <P>
  477      * Note: only has effect when the ValueSource part is not null.
  478      */
  479     public boolean isStrict() {
  480       return strict;
  481     }
  482   
  483     /**
  484      * Set the strict mode of this query. 
  485      * @param strict The strict mode to set.
  486      * @see #isStrict()
  487      */
  488     public void setStrict(boolean strict) {
  489       this.strict = strict;
  490     }
  491   
  492     /**
  493      * A short name of this query, used in {@link #toString(String)}.
  494      */
  495     public String name() {
  496       return "custom";
  497     }
  498   
  499   }

Home » lucene-3.0.1-src » org.apache » lucene » search » function » [javadoc | source]