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 java.io.IOException;
  import java.lang.reflect.Method;
  
  import org.apache.lucene.index.IndexReader;
  import org.apache.lucene.index.Term;
  import org.apache.lucene.search.MultiTermQuery;
  import org.apache.lucene.search.Query;
  import org.apache.lucene.search.TopTermsRewrite;
  import org.apache.lucene.search.ScoringRewrite;
  import org.apache.lucene.search.BooleanClause.Occur; // javadocs only
  
  /**
   * Wraps any {@link MultiTermQuery} as a {@link SpanQuery}, 
   * so it can be nested within other SpanQuery classes.
   * <p>
   * The query is rewritten by default to a {@link SpanOrQuery} containing
   * the expanded terms, but this can be customized. 
   * <p>
   * Example:
   * <blockquote><pre>
   * {@code
   * WildcardQuery wildcard = new WildcardQuery(new Term("field", "bro?n"));
   * SpanQuery spanWildcard = new SpanMultiTermQueryWrapper<WildcardQuery>(wildcard);
   * // do something with spanWildcard, such as use it in a SpanFirstQuery
   * }
   * </pre></blockquote>
   */
  public class SpanMultiTermQueryWrapper<Q extends MultiTermQuery> extends SpanQuery {
    protected final Q query;
    private Method getFieldMethod = null, getTermMethod = null;
  
    /**
     * Create a new SpanMultiTermQueryWrapper. 
     * 
     * @param query Query to wrap.
     * <p>
     * NOTE: This will call {@link MultiTermQuery#setRewriteMethod(MultiTermQuery.RewriteMethod)}
     * on the wrapped <code>query</code>, changing its rewrite method to a suitable one for spans.
     * Be sure to not change the rewrite method on the wrapped query afterwards! Doing so will
     * throw {@link UnsupportedOperationException} on rewriting this query!
     * In Lucene 3.x, MultiTermQuery allows queries to rewrite to different field names, but SpanQuery
     * needs a fixed field. The wrapped query must therefore support getField() or getTerm().
     * @throws IllegalArgumentException if the wrapped query does not provide getField() or getTerm().
     */
    public SpanMultiTermQueryWrapper(Q query) {
      this.query = query;
      
      MultiTermQuery.RewriteMethod method = query.getRewriteMethod();
      if (method instanceof TopTermsRewrite) {
        final int pqsize = ((TopTermsRewrite) method).getSize();
        setRewriteMethod(new TopTermsSpanBooleanQueryRewrite(pqsize));
      } else {
        setRewriteMethod(SCORING_SPAN_QUERY_REWRITE); 
      }
      
      // In Lucene 3.x, MTQ has no fixed field, we need to get it by reflection.
      // If the underlying query does not allow to get a constant field, we throw IAE:
      try {
        getFieldMethod = query.getClass().getMethod("getField");
      } catch (Exception e1) {
        try {
          getTermMethod = query.getClass().getMethod("getTerm");
        } catch (Exception e2) {
          try {
            getTermMethod = query.getClass().getMethod("getPrefix");
          } catch (Exception e3) {
            throw new IllegalArgumentException("SpanMultiTermQueryWrapper can only wrap MultiTermQueries"+
              " that can return a field name using getField() or getTerm()");
          }
        }
      }
    }
    
    /**
     * Expert: returns the rewriteMethod
     */
    public final SpanRewriteMethod getRewriteMethod() {
      final MultiTermQuery.RewriteMethod m = query.getRewriteMethod();
      if (!(m instanceof SpanRewriteMethod))
        throw new UnsupportedOperationException("You can only use SpanMultiTermQueryWrapper with a suitable SpanRewriteMethod.");
      return (SpanRewriteMethod) m;
    }
  
    /**
     * Expert: sets the rewrite method. This only makes sense
     * to be a span rewrite method.
     */
    public final void setRewriteMethod(SpanRewriteMethod rewriteMethod) {
      query.setRewriteMethod(rewriteMethod);
    }
    
    @Override
    public Spans getSpans(IndexReader reader) throws IOException {
      throw new UnsupportedOperationException("Query should have been rewritten");
    }
  
    @Override
    public String getField() {
      try {
        if (getFieldMethod != null) {
          return (String) getFieldMethod.invoke(query);
        } else {
          assert getTermMethod != null;
          return ((Term) getTermMethod.invoke(query)).field();
        }
      } catch (Exception e) {
        throw new RuntimeException("Cannot invoke getField() or getTerm() on wrapped query.", e);
      }
    }
  
    @Override
    public String toString(String field) {
      StringBuilder builder = new StringBuilder();
      builder.append("SpanMultiTermQueryWrapper(");
      builder.append(query.toString(field));
      builder.append(")");
      return builder.toString();
    }
  
    @Override
    public Query rewrite(IndexReader reader) throws IOException {
      final Query q = query.rewrite(reader);
      if (!(q instanceof SpanQuery))
        throw new UnsupportedOperationException("You can only use SpanMultiTermQueryWrapper with a suitable SpanRewriteMethod.");
      return q;
    }
    
    @Override
    public int hashCode() {
      return 31 * query.hashCode();
    }
  
    @Override
    public boolean equals(Object obj) {
      if (this == obj) return true;
      if (obj == null) return false;
      if (getClass() != obj.getClass()) return false;
      final SpanMultiTermQueryWrapper other = (SpanMultiTermQueryWrapper) obj;
      return query.equals(other.query);
    }
  
    /** Abstract class that defines how the query is rewritten. */
    public static abstract class SpanRewriteMethod extends MultiTermQuery.RewriteMethod {
      @Override
      public abstract SpanQuery rewrite(IndexReader reader, MultiTermQuery query) throws IOException;
    }
  
    /**
     * A rewrite method that first translates each term into a SpanTermQuery in a
     * {@link Occur#SHOULD} clause in a BooleanQuery, and keeps the
     * scores as computed by the query.
     * 
     * @see #setRewriteMethod
     */
    public final static SpanRewriteMethod SCORING_SPAN_QUERY_REWRITE = new SpanRewriteMethod() {
      private final ScoringRewrite<SpanOrQuery> delegate = new ScoringRewrite<SpanOrQuery>() {
        @Override
        protected SpanOrQuery getTopLevelQuery() {
          return new SpanOrQuery();
        }
  
        @Override
        protected void addClause(SpanOrQuery topLevel, Term term, float boost) {
          final SpanTermQuery q = new SpanTermQuery(term);
          q.setBoost(boost);
          topLevel.addClause(q);
        }
      };
      
      @Override
      public SpanQuery rewrite(IndexReader reader, MultiTermQuery query) throws IOException {
        return delegate.rewrite(reader, query);
      }
  
      // Make sure we are still a singleton even after deserializing
      protected Object readResolve() {
        return SCORING_SPAN_QUERY_REWRITE;
      }
    };
    
    /**
     * A rewrite method that first translates each term into a SpanTermQuery in a
     * {@link Occur#SHOULD} clause in a BooleanQuery, and keeps the
     * scores as computed by the query.
     * 
     * <p>
     * This rewrite method only uses the top scoring terms so it will not overflow
     * the boolean max clause count.
     * 
     * @see #setRewriteMethod
     */
    public static final class TopTermsSpanBooleanQueryRewrite extends SpanRewriteMethod  {
      private final TopTermsRewrite<SpanOrQuery> delegate;
    
      /** 
       * Create a TopTermsSpanBooleanQueryRewrite for 
       * at most <code>size</code> terms.
       */
      public TopTermsSpanBooleanQueryRewrite(int size) {
        delegate = new TopTermsRewrite<SpanOrQuery>(size) {
          @Override
          protected int getMaxSize() {
            return Integer.MAX_VALUE;
          }
      
          @Override
          protected SpanOrQuery getTopLevelQuery() {
            return new SpanOrQuery();
          }
  
          @Override
          protected void addClause(SpanOrQuery topLevel, Term term, float boost) {
            final SpanTermQuery q = new SpanTermQuery(term);
            q.setBoost(boost);
            topLevel.addClause(q);
          }
        };
      }
      
      /** return the maximum priority queue size */
      public int getSize() {
        return delegate.getSize();
      }
  
      @Override
      public SpanQuery rewrite(IndexReader reader, MultiTermQuery query) throws IOException {
        return delegate.rewrite(reader, query);
      }
    
      @Override
      public int hashCode() {
        return 31 * delegate.hashCode();
      }
  
      @Override
      public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        final TopTermsSpanBooleanQueryRewrite other = (TopTermsSpanBooleanQueryRewrite) obj;
        return delegate.equals(other.delegate);
      }
      
    }
    
  }
  