content: /**
   * 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.
   */
  
  package org.apache.solr.search.function;
  
  import org.apache.lucene.index.IndexReader;
  import org.apache.lucene.search.*;
  import org.apache.solr.search.SolrIndexReader;
  
  import java.io.IOException;
  import java.util.Set;
  import java.util.Map;
  
  
  /**
   * Returns a score for each document based on a ValueSource,
   * often some function of the value of a field.
   *
   * <b>Note: This API is experimental and may change in non backward-compatible ways in the future</b>
   *
   * @version $Id: FunctionQuery.java 1065312 2011-01-30 16:08:25Z rmuir $
   */
  public class FunctionQuery extends Query {
    ValueSource func;
  
    /**
     * @param func defines the function to be used for scoring
     */
    public FunctionQuery(ValueSource func) {
      this.func=func;
    }
  
    /** @return The associated ValueSource */
    public ValueSource getValueSource() {
      return func;
    }
  
    @Override
    public Query rewrite(IndexReader reader) throws IOException {
      return this;
    }
  
    @Override
    public void extractTerms(Set terms) {}
  
    protected class FunctionWeight extends Weight {
      protected Searcher searcher;
      protected float queryNorm;
      protected float queryWeight;
      protected Map context;
  
      public FunctionWeight(Searcher searcher) throws IOException {
        this.searcher = searcher;
        this.context = func.newContext();
        func.createWeight(context, searcher);
      }
  
      @Override
      public Query getQuery() {
        return FunctionQuery.this;
      }
  
      @Override
      public float getValue() {
        return queryWeight;
      }
  
      @Override
      public float sumOfSquaredWeights() throws IOException {
        queryWeight = getBoost();
        return queryWeight * queryWeight;
      }
  
      @Override
      public void normalize(float norm) {
        this.queryNorm = norm;
        queryWeight *= this.queryNorm;
      }
  
      @Override
      public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder, boolean topScorer) throws IOException {
        return new AllScorer(getSimilarity(searcher), reader, this);
      }
  
      @Override
      public Explanation explain(IndexReader reader, int doc) throws IOException {
        SolrIndexReader topReader = (SolrIndexReader)reader;
        SolrIndexReader[] subReaders = topReader.getLeafReaders();
        int[] offsets = topReader.getLeafOffsets();
        int readerPos = SolrIndexReader.readerIndex(doc, offsets);
        int readerBase = offsets[readerPos];
        return ((AllScorer)scorer(subReaders[readerPos], true, true)).explain(doc-readerBase);
      }
    }
  
    protected class AllScorer extends Scorer {
      final IndexReader reader;
      final FunctionWeight weight;
      final int maxDoc;
      final float qWeight;
      int doc=-1;
      final DocValues vals;
      final boolean hasDeletions;
  
      public AllScorer(Similarity similarity, IndexReader reader, FunctionWeight w) throws IOException {
        super(similarity, w);
        this.weight = w;
        this.qWeight = w.getValue();
        this.reader = reader;
        this.maxDoc = reader.maxDoc();
        this.hasDeletions = reader.hasDeletions();
        vals = func.getValues(weight.context, reader);
      }
  
      @Override
      public int docID() {
        return doc;
      }
  
      @Override
      // instead of matching all docs, we could also embed a query.
      // the score could either ignore the subscore, or boost it.
      // Containment:  floatline(foo:myTerm, "myFloatField", 1.0, 0.0f)
      // Boost:        foo:myTerm^floatline("myFloatField",1.0,0.0f)
      public int nextDoc() throws IOException {
        for(;;) {
          ++doc;
          if (doc>=maxDoc) {
            return doc=NO_MORE_DOCS;
          }
          if (hasDeletions && reader.isDeleted(doc)) continue;
          return doc;
        }
      }
  
      @Override
      public int advance(int target) throws IOException {
        // this will work even if target==NO_MORE_DOCS
        doc=target-1;
        return nextDoc();
      }
  
      // instead of matching all docs, we could also embed a query.
      // the score could either ignore the subscore, or boost it.
      // Containment:  floatline(foo:myTerm, "myFloatField", 1.0, 0.0f)
      // Boost:        foo:myTerm^floatline("myFloatField",1.0,0.0f)
      public boolean next() throws IOException {
        for(;;) {
          ++doc;
          if (doc>=maxDoc) {
            return false;
          }
          if (hasDeletions && reader.isDeleted(doc)) continue;
          // todo: maybe allow score() to throw a specific exception
          // and continue on to the next document if it is thrown...
          // that may be useful, but exceptions aren't really good
          // for flow control.
          return true;
        }
      }
  
      public int doc() {
        return doc;
      }
  
      @Override
      public float score() throws IOException {
        float score = qWeight * vals.floatVal(doc);
  
        // Current Lucene priority queues can't handle NaN and -Infinity, so
        // map to -Float.MAX_VALUE. This conditional handles both -infinity
        // and NaN since comparisons with NaN are always false.
        return score>Float.NEGATIVE_INFINITY ? score : -Float.MAX_VALUE;
      }
  
      public boolean skipTo(int target) throws IOException {
        doc=target-1;
        return next();
      }
  
      public Explanation explain(int doc) throws IOException {
        float sc = qWeight * vals.floatVal(doc);
  
        Explanation result = new ComplexExplanation
          (true, sc, "FunctionQuery(" + func + "), product of:");
  
        result.addDetail(vals.explain(doc));
        result.addDetail(new Explanation(getBoost(), "boost"));
        result.addDetail(new Explanation(weight.queryNorm,"queryNorm"));
        return result;
      }
    }
  
  
    @Override
    public Weight createWeight(Searcher searcher) throws IOException {
      return new FunctionQuery.FunctionWeight(searcher);
    }
  
  
    /** Prints a user-readable version of this query. */
    @Override
    public String toString(String field)
    {
      float boost = getBoost();
      return (boost!=1.0?"(":"") + func.toString()
              + (boost==1.0 ? "" : ")^"+boost);
    }
  
  
    /** Returns true if <code>o</code> is equal to this. */
    @Override
    public boolean equals(Object o) {
      if (FunctionQuery.class != o.getClass()) return false;
      FunctionQuery other = (FunctionQuery)o;
      return this.getBoost() == other.getBoost()
              && this.func.equals(other.func);
    }
  
    /** Returns a hash code value for this object. */
    @Override
    public int hashCode() {
      return func.hashCode()*31 + Float.floatToIntBits(getBoost());
    }
  
  }
  