content: package org.apache.lucene.search.spans;
  
  /**
   * 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 org.apache.lucene.index.IndexReader;
  import org.apache.lucene.index.Term;
  import org.apache.lucene.search.Query;
  import org.apache.lucene.util.ToStringUtils;
  
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Set;
  
  /** Removes matches which overlap with another SpanQuery. */
  public class SpanNotQuery extends SpanQuery implements Cloneable {
    private SpanQuery include;
    private SpanQuery exclude;
  
    /** Construct a SpanNotQuery matching spans from <code>include</code> which
     * have no overlap with spans from <code>exclude</code>.*/
    public SpanNotQuery(SpanQuery include, SpanQuery exclude) {
      this.include = include;
      this.exclude = exclude;
  
      if (!include.getField().equals(exclude.getField()))
        throw new IllegalArgumentException("Clauses must have same field.");
    }
  
    /** Return the SpanQuery whose matches are filtered. */
    public SpanQuery getInclude() { return include; }
  
    /** Return the SpanQuery whose matches must not overlap those returned. */
    public SpanQuery getExclude() { return exclude; }
  
    @Override
    public String getField() { return include.getField(); }
  
    @Override
    public void extractTerms(Set<Term> terms) { include.extractTerms(terms); }
  
    @Override
    public String toString(String field) {
      StringBuilder buffer = new StringBuilder();
      buffer.append("spanNot(");
      buffer.append(include.toString(field));
      buffer.append(", ");
      buffer.append(exclude.toString(field));
      buffer.append(")");
      buffer.append(ToStringUtils.boost(getBoost()));
      return buffer.toString();
    }
  
    @Override
    public Object clone() {
      SpanNotQuery spanNotQuery = new SpanNotQuery((SpanQuery)include.clone(),(SpanQuery) exclude.clone());
      spanNotQuery.setBoost(getBoost());
      return  spanNotQuery;
    }
  
    @Override
    public Spans getSpans(final IndexReader reader) throws IOException {
      return new Spans() {
          private Spans includeSpans = include.getSpans(reader);
          private boolean moreInclude = true;
  
          private Spans excludeSpans = exclude.getSpans(reader);
          private boolean moreExclude = excludeSpans.next();
  
          @Override
          public boolean next() throws IOException {
            if (moreInclude)                        // move to next include
              moreInclude = includeSpans.next();
  
            while (moreInclude && moreExclude) {
  
              if (includeSpans.doc() > excludeSpans.doc()) // skip exclude
                moreExclude = excludeSpans.skipTo(includeSpans.doc());
  
              while (moreExclude                    // while exclude is before
                     && includeSpans.doc() == excludeSpans.doc()
                     && excludeSpans.end() <= includeSpans.start()) {
                moreExclude = excludeSpans.next();  // increment exclude
              }
  
              if (!moreExclude                      // if no intersection
                  || includeSpans.doc() != excludeSpans.doc()
                  || includeSpans.end() <= excludeSpans.start())
                break;                              // we found a match
  
              moreInclude = includeSpans.next();    // intersected: keep scanning
            }
            return moreInclude;
          }
  
          @Override
          public boolean skipTo(int target) throws IOException {
            if (moreInclude)                        // skip include
              moreInclude = includeSpans.skipTo(target);
  
            if (!moreInclude)
              return false;
  
            if (moreExclude                         // skip exclude
                && includeSpans.doc() > excludeSpans.doc())
              moreExclude = excludeSpans.skipTo(includeSpans.doc());
  
            while (moreExclude                      // while exclude is before
                   && includeSpans.doc() == excludeSpans.doc()
                   && excludeSpans.end() <= includeSpans.start()) {
              moreExclude = excludeSpans.next();    // increment exclude
            }
  
            if (!moreExclude                      // if no intersection
                  || includeSpans.doc() != excludeSpans.doc()
                  || includeSpans.end() <= excludeSpans.start())
              return true;                          // we found a match
  
            return next();                          // scan to next match
          }
  
          @Override
          public int doc() { return includeSpans.doc(); }
          @Override
          public int start() { return includeSpans.start(); }
          @Override
          public int end() { return includeSpans.end(); }
  
        // TODO: Remove warning after API has been finalized
        @Override
        public Collection<byte[]> getPayload() throws IOException {
          ArrayList<byte[]> result = null;
          if (includeSpans.isPayloadAvailable()) {
            result = new ArrayList<byte[]>(includeSpans.getPayload());
          }
          return result;
        }
  
        // TODO: Remove warning after API has been finalized
        @Override
        public boolean isPayloadAvailable() {
          return includeSpans.isPayloadAvailable();
        }
  
        @Override
        public String toString() {
            return "spans(" + SpanNotQuery.this.toString() + ")";
          }
  
        };
    }
  
    @Override
    public Query rewrite(IndexReader reader) throws IOException {
      SpanNotQuery clone = null;
  
      SpanQuery rewrittenInclude = (SpanQuery) include.rewrite(reader);
      if (rewrittenInclude != include) {
        clone = (SpanNotQuery) this.clone();
        clone.include = rewrittenInclude;
      }
      SpanQuery rewrittenExclude = (SpanQuery) exclude.rewrite(reader);
      if (rewrittenExclude != exclude) {
        if (clone == null) clone = (SpanNotQuery) this.clone();
        clone.exclude = rewrittenExclude;
      }
  
      if (clone != null) {
        return clone;                        // some clauses rewrote
      } else {
        return this;                         // no clauses rewrote
      }
    }
  
      /** Returns true iff <code>o</code> is equal to this. */
    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (!(o instanceof SpanNotQuery)) return false;
  
      SpanNotQuery other = (SpanNotQuery)o;
      return this.include.equals(other.include)
              && this.exclude.equals(other.exclude)
              && this.getBoost() == other.getBoost();
    }
  
    @Override
    public int hashCode() {
      int h = include.hashCode();
      h = (h<<1) | (h >>> 31);  // rotate left
      h ^= exclude.hashCode();
      h = (h<<1) | (h >>> 31);  // rotate left
      h ^= Float.floatToRawIntBits(getBoost());
      return h;
    }
  
  }
  