content: package org.apache.lucene.search;
  
  /**
   * 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;
  
  /** Expert: Scoring functionality for phrase queries.
   * <br>A document is considered matching if it contains the phrase-query terms  
   * at "valid" positions. What "valid positions" are
   * depends on the type of the phrase query: for an exact phrase query terms are required 
   * to appear in adjacent locations, while for a sloppy phrase query some distance between 
   * the terms is allowed. The abstract method {@link #phraseFreq()} of extending classes
   * is invoked for each document containing all the phrase query terms, in order to 
   * compute the frequency of the phrase query in that document. A non zero frequency
   * means a match. 
   */
  abstract class PhraseScorer extends Scorer {
    protected byte[] norms;
    protected float value;
  
    PhrasePositions min, max;
  
    private float freq; //phrase frequency in current doc as computed by phraseFreq().
  
    PhraseScorer(Weight weight, PhraseQuery.PostingsAndFreq[] postings,
        Similarity similarity, byte[] norms) {
      super(similarity, weight);
      this.norms = norms;
      this.value = weight.getValue();
  
      // convert tps to a list of phrase positions.
      // note: phrase-position differs from term-position in that its position
      // reflects the phrase offset: pp.pos = tp.pos - offset.
      // this allows to easily identify a matching (exact) phrase 
      // when all PhrasePositions have exactly the same position.
      if (postings.length > 0) {
        min = new PhrasePositions(postings[0].postings, postings[0].position, 0);
        max = min;
        max.doc = -1;
        for (int i = 1; i < postings.length; i++) {
          PhrasePositions pp = new PhrasePositions(postings[i].postings, postings[i].position, i);
          max.next = pp;
          max = pp;
          max.doc = -1;
        }
        max.next = min; // make it cyclic for easier manipulation
      }
    }
  
    @Override
    public int docID() {
      return max.doc; 
    }
  
    @Override
    public int nextDoc() throws IOException {
      return advance(max.doc);
    }
  
    private boolean advanceMin(int target) throws IOException {
      if (!min.skipTo(target)) { 
        max.doc = NO_MORE_DOCS; // for further calls to docID() 
        return false;
      }
      min = min.next; // cyclic
      max = max.next; // cyclic
      return true;
    }
  
    @Override
    public float score() throws IOException {
      //System.out.println("scoring " + max.doc);
      float raw = getSimilarity().tf(freq) * value; // raw score
      return norms == null ? raw : raw * getSimilarity().decodeNormValue(norms[max.doc]); // normalize
    }
  
    @Override
    public int advance(int target) throws IOException {
      freq = 0.0f;
      if (!advanceMin(target)) {
        return NO_MORE_DOCS;
      }        
      boolean restart=false;
      while (freq == 0.0f) {
        while (min.doc < max.doc || restart) {
          restart = false;
          if (!advanceMin(max.doc)) {
            return NO_MORE_DOCS;
          }        
        }
        // found a doc with all of the terms
        freq = phraseFreq(); // check for phrase
        restart = true;
      } 
  
      // found a match
      return max.doc;
    }
    
    /**
     * phrase frequency in current doc as computed by phraseFreq().
     */
    @Override
    public final float freq() {
      return freq;
    }
  
    /**
     * For a document containing all the phrase query terms, compute the
     * frequency of the phrase in that document. 
     * A non zero frequency means a match.
     * <br>Note, that containing all phrase terms does not guarantee a match - they have to be found in matching locations.  
     * @return frequency of the phrase in current doc, 0 if not found. 
     */
    abstract float phraseFreq() throws IOException;
  
    @Override
    public String toString() { return "scorer(" + weight + ")"; }
   
  }
  