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.util;
  
  import org.apache.lucene.document.Document;
  import org.apache.lucene.queryParser.ParseException;
  import org.apache.lucene.queryParser.QueryParser;
  import org.apache.lucene.search.*;
  import org.apache.lucene.search.BooleanClause.Occur;
  import org.apache.solr.common.SolrDocument;
  import org.apache.solr.common.SolrDocumentList;
  import org.apache.solr.common.SolrException;
  import org.apache.solr.common.params.AppendedSolrParams;
  import org.apache.solr.common.params.DefaultSolrParams;
  import org.apache.solr.common.params.SolrParams;
  import org.apache.solr.common.params.CommonParams;
  import org.apache.solr.common.params.UpdateParams;
  import org.apache.solr.common.util.NamedList;
  import org.apache.solr.common.util.SimpleOrderedMap;
  import org.apache.solr.common.util.StrUtils;
  import org.apache.solr.core.SolrCore;
  import org.apache.solr.highlight.SolrHighlighter;
  import org.apache.solr.request.SolrQueryRequest;
  import org.apache.solr.request.uninverted.UnInvertedField;
  import org.apache.solr.response.SolrQueryResponse;
  import org.apache.solr.schema.IndexSchema;
  import org.apache.solr.schema.SchemaField;
  import org.apache.solr.search.*;
  import org.apache.solr.update.DocumentBuilder;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.io.IOException;
  import java.util.*;
  import java.util.regex.Pattern;
  import java.lang.reflect.Method;
  import java.lang.reflect.InvocationTargetException;
  
  /**
   * <p>Utilities that may be of use to RequestHandlers.</p>
   *
   * <p>
   * Many of these functions have code that was stolen/mutated from
   * StandardRequestHandler.
   * </p>
   *
   * <p>:TODO: refactor StandardRequestHandler to use these utilities</p>
   *
   * <p>:TODO: Many "standard" functionality methods are not cognisant of
   * default parameter settings.  
   */
  public class SolrPluginUtils {
    final static Logger log = LoggerFactory.getLogger( SolrPluginUtils.class );
  
    /**
     * Set defaults on a SolrQueryRequest.
     *
     * RequestHandlers can use this method to ensure their defaults are
     * visible to other components such as the response writer
     */
    public static void setDefaults(SolrQueryRequest req, SolrParams defaults) {
      setDefaults(req, defaults, null, null);
    }
    
    /**
     * Set default-ish params on a SolrQueryRequest.
     *
     * RequestHandlers can use this method to ensure their defaults and
     * overrides are visible to other components such as the response writer
     *
     * @param req The request whose params we are interested i
     * @param defaults values to be used if no values are specified in the request params
     * @param appends values to be appended to those from the request (or defaults) when dealing with multi-val params, or treated as another layer of defaults for singl-val params.
     * @param invariants values which will be used instead of any request, or default values, regardless of context.
     */
    public static void setDefaults(SolrQueryRequest req, SolrParams defaults,
                                   SolrParams appends, SolrParams invariants) {
      
        SolrParams p = req.getParams();
        if (defaults != null) {
          p = new DefaultSolrParams(p,defaults);
        }
        if (appends != null) {
          p = new AppendedSolrParams(p,appends);
        }
        if (invariants != null) {
          p = new DefaultSolrParams(invariants,p);
        }
        req.setParams(p);
    }
  
  
    /**
     * standard param for field list
     *
     * @deprecated Use org.apache.solr.common.params.CommonParams.FL.
     */
    @Deprecated
    public static String FL = CommonParams.FL;
  
    /**
     * SolrIndexSearch.numDocs(Query,Query) freaks out if the filtering
     * query is null, so we use this workarround.
     */
    public static int numDocs(SolrIndexSearcher s, Query q, Query f)
      throws IOException {
  
      return (null == f) ? s.getDocSet(q).size() : s.numDocs(q,f);
          
    }
      
    /**
     * Returns the param, or the default if it's empty or not specified.
     * @deprecated use SolrParam.get(String,String)
     */
    @Deprecated
    public static String getParam(SolrQueryRequest req,
                                  String param, String def) {
          
      String v = req.getParam(param);
      // Note: parameters passed but given only white-space value are
      // considered equivalent to passing nothing for that parameter.
      if (null == v || "".equals(v.trim())) {
        return def;
      }
      return v;
    }
      
    /**
     * Treats the param value as a Number, returns the default if nothing is
     * there or if it's not a number.
     * @deprecated use SolrParam.getFloat(String,float)
     */
    @Deprecated
    public static Number getNumberParam(SolrQueryRequest req,
                                        String param, Number def) {
          
      Number r = def;
      String v = req.getParam(param);
      if (null == v || "".equals(v.trim())) {
        return r;
      }
      try {
        r = new Float(v);
      } catch (NumberFormatException e) {
        /* :NOOP" */
      }
      return r;
    }
          
    /**
     * Treats parameter value as a boolean.  The string 'false' is false; 
     * any other non-empty string is true.
     * @deprecated use SolrParam.getBool(String,boolean)
     */
    @Deprecated
    public static boolean getBooleanParam(SolrQueryRequest req,
                                         String param, boolean def) {        
      String v = req.getParam(param);
      if (null == v || "".equals(v.trim())) {
        return def;
      }
      return !"false".equals(v.trim());
    }
      
    private final static Pattern splitList=Pattern.compile(",| ");
    
    /** Split a value that may contain a comma, space of bar separated list. */
    public static String[] split(String value){
       return splitList.split(value.trim(), 0);
    }
  
    /**
     * Assumes the standard query param of "fl" to specify the return fields
     * @see #setReturnFields(String,SolrQueryResponse)
     */
    public static int setReturnFields(SolrQueryRequest req,
                                      SolrQueryResponse res) {
  
      return setReturnFields(req.getParams().get(CommonParams.FL), res);
    }
  
    /**
     * Given a space seperated list of field names, sets the field list on the
     * SolrQueryResponse.
     *
     * @return bitfield of SolrIndexSearcher flags that need to be set
     */
    public static int setReturnFields(String fl,
                                      SolrQueryResponse res) {
      int flags = 0;
      if (fl != null) {
        // TODO - this could become more efficient if widely used.
        // TODO - should field order be maintained?
        String[] flst = split(fl);
        if (flst.length > 0 && !(flst.length==1 && flst[0].length()==0)) {
          Set<String> set = new LinkedHashSet<String>();
          for (String fname : flst) {
            if("score".equalsIgnoreCase(fname))
              flags |= SolrIndexSearcher.GET_SCORES;
            set.add(fname);
          }
          res.setReturnFields(set);
        }
      }
      return flags;
    }
  
    /**
     * Pre-fetch documents into the index searcher's document cache.
     *
     * This is an entirely optional step which you might want to perform for
     * the following reasons:
     *
     * <ul>
     *     <li>Locates the document-retrieval costs in one spot, which helps
     *     detailed performance measurement</li>
     *   
     *     <li>Determines a priori what fields will be needed to be fetched by
     *     various subtasks, like response writing and highlighting.  This
     *     minimizes the chance that many needed fields will be loaded lazily.
     *     (it is more efficient to load all the field we require normally).</li>
     * </ul>
     *
     * If lazy field loading is disabled, this method does nothing.
     */
    public static void optimizePreFetchDocs(DocList docs,
                                            Query query,
                                            SolrQueryRequest req,
                                            SolrQueryResponse res) throws IOException {
      SolrIndexSearcher searcher = req.getSearcher();
      if(!searcher.enableLazyFieldLoading) {
        // nothing to do
        return;
      }
  
      Set<String> fieldFilter = null;
      Set<String> returnFields = res.getReturnFields();
      if(returnFields != null) {
        // copy return fields list
        fieldFilter = new HashSet<String>(returnFields);
        // add highlight fields
        SolrHighlighter highligher = req.getCore().getHighlighter();
        if(highligher.isHighlightingEnabled(req.getParams())) {
          for(String field: highligher.getHighlightFields(query, req, null)) 
            fieldFilter.add(field);        
        }
        // fetch unique key if one exists.
        SchemaField keyField = req.getSearcher().getSchema().getUniqueKeyField();
        if(null != keyField)
            fieldFilter.add(keyField.getName());  
      }
  
      // get documents
      DocIterator iter = docs.iterator();
      for (int i=0; i<docs.size(); i++) {
        searcher.doc(iter.nextDoc(), fieldFilter);
      }
    }
  
    /**
     * <p>
     * Returns a NamedList containing many "standard" pieces of debugging
     * information.
     * </p>
     *
     * <ul>
     * <li>rawquerystring - the 'q' param exactly as specified by the client
     * </li>
     * <li>querystring - the 'q' param after any preprocessing done by the plugin
     * </li>
     * <li>parsedquery - the main query executed formated by the Solr
     *     QueryParsing utils class (which knows about field types)
     * </li>
     * <li>parsedquery_toString - the main query executed formated by it's
     *     own toString method (in case it has internal state Solr
     *     doesn't know about)
     * </li>
     * <li>expain - the list of score explanations for each document in
     *     results against query.
     * </li>
     * <li>otherQuery - the query string specified in 'explainOther' query param.
     * </li>
     * <li>explainOther - the list of score explanations for each document in
     *     results against 'otherQuery'
     * </li>
     * </ul>
     *
     * @param req the request we are dealing with
     * @param userQuery the users query as a string, after any basic
     *                  preprocessing has been done
     * @param query the query built from the userQuery
     *              (and perhaps other clauses) that identifies the main
     *              result set of the response.
     * @param results the main result set of the response
     * @deprecated Use doStandardDebug(SolrQueryRequest,String,Query,DocList) with setDefaults
     */
    @Deprecated
    public static NamedList doStandardDebug(SolrQueryRequest req,
                                            String userQuery,
                                            Query query,
                                            DocList results,
                                            org.apache.solr.util.CommonParams params)
      throws IOException {
          
      String debug = getParam(req, CommonParams.DEBUG_QUERY, params.debugQuery);
  
      NamedList dbg = null;
      if (debug!=null) {
        dbg = new SimpleOrderedMap();
  
        /* userQuery may have been pre-processes .. expose that */
        dbg.add("rawquerystring", req.getQueryString());
        dbg.add("querystring", userQuery);
  
        /* QueryParsing.toString isn't perfect, use it to see converted
         * values, use regular toString to see any attributes of the
         * underlying Query it may have missed.
         */
        dbg.add("parsedquery",QueryParsing.toString(query, req.getSchema()));
        dbg.add("parsedquery_toString", query.toString());
              
        dbg.add("explain", getExplainList
                (query, results, req.getSearcher(), req.getSchema()));
        String otherQueryS = req.getParam("explainOther");
        if (otherQueryS != null && otherQueryS.length() > 0) {
          DocList otherResults = doSimpleQuery
            (otherQueryS,req,0,10);
          dbg.add("otherQuery",otherQueryS);
          dbg.add("explainOther", getExplainList
                  (query, otherResults,
                   req.getSearcher(),
                   req.getSchema()));
        }
      }
  
      return dbg;
    }
  
  
    /**
     * <p>
     * Returns a NamedList containing many "standard" pieces of debugging
     * information.
     * </p>
     *
     * <ul>
     * <li>rawquerystring - the 'q' param exactly as specified by the client
     * </li>
     * <li>querystring - the 'q' param after any preprocessing done by the plugin
     * </li>
     * <li>parsedquery - the main query executed formated by the Solr
     *     QueryParsing utils class (which knows about field types)
     * </li>
     * <li>parsedquery_toString - the main query executed formated by it's
     *     own toString method (in case it has internal state Solr
     *     doesn't know about)
     * </li>
     * <li>explain - the list of score explanations for each document in
     *     results against query.
     * </li>
     * <li>otherQuery - the query string specified in 'explainOther' query param.
     * </li>
     * <li>explainOther - the list of score explanations for each document in
     *     results against 'otherQuery'
     * </li>
     * </ul>
     *
     * @param req the request we are dealing with
     * @param userQuery the users query as a string, after any basic
     *                  preprocessing has been done
     * @param query the query built from the userQuery
     *              (and perhaps other clauses) that identifies the main
     *              result set of the response.
     * @param results the main result set of the response
     */
    public static NamedList doStandardDebug(SolrQueryRequest req,
                                            String userQuery,
                                            Query query,
                                            DocList results)
      throws IOException {
  
      String debug = req.getParams().get(CommonParams.DEBUG_QUERY);
  
      NamedList dbg = null;
      if (debug!=null) {
        dbg = new SimpleOrderedMap();
  
        SolrIndexSearcher searcher = req.getSearcher();
        IndexSchema schema = req.getSchema();
  
        boolean explainStruct
          = req.getParams().getBool(CommonParams.EXPLAIN_STRUCT,false);
        
        /* userQuery may have been pre-processes .. expose that */
        dbg.add("rawquerystring", req.getParams().get(CommonParams.Q));
        dbg.add("querystring", userQuery);
  
        /* QueryParsing.toString isn't perfect, use it to see converted
         * values, use regular toString to see any attributes of the
         * underlying Query it may have missed.
         */
        dbg.add("parsedquery",QueryParsing.toString(query, schema));
        dbg.add("parsedquery_toString", query.toString());
  
        NamedList<Explanation> explain 
          = getExplanations(query, results, searcher, schema);
        dbg.add("explain", explainStruct ?
                explanationsToNamedLists(explain) : 
                explanationsToStrings(explain));
  
        String otherQueryS = req.getParams().get(CommonParams.EXPLAIN_OTHER);
        if (otherQueryS != null && otherQueryS.length() > 0) {
          DocList otherResults = doSimpleQuery
            (otherQueryS,req,0,10);
          dbg.add("otherQuery",otherQueryS);
          NamedList<Explanation> explainO
            = getExplanations(query, otherResults, searcher, schema);
          dbg.add("explainOther", explainStruct ?
                  explanationsToNamedLists(explainO) : 
                  explanationsToStrings(explainO));
        }
      }
  
      return dbg;
    }
  
    public static NamedList<Object> explanationToNamedList(Explanation e) {
      NamedList<Object> out = new SimpleOrderedMap<Object>();
  
      out.add("match", e.isMatch());
      out.add("value", e.getValue());
      out.add("description", e.getDescription());
  
      Explanation[] details = e.getDetails();
  
      // short circut out
      if (null == details || 0 == details.length) return out;
  
      List<NamedList<Object>> kids
        = new ArrayList<NamedList<Object>>(details.length);
      for (Explanation d : details) {
        kids.add(explanationToNamedList(d));
      }
      out.add("details", kids);
  
      return out;
    }
    
    public static NamedList<NamedList<Object>> explanationsToNamedLists
      (NamedList<Explanation> explanations) {
  
      NamedList<NamedList<Object>> out 
        = new SimpleOrderedMap<NamedList<Object>>();
      for (Map.Entry<String,Explanation> entry : explanations) {
        out.add(entry.getKey(), explanationToNamedList(entry.getValue()));
      }
      return out;
    }
  
    /**
     * Generates an NamedList of Explanations for each item in a list of docs.
     *
     * @param query The Query you want explanations in the context of
     * @param docs The Documents you want explained relative that query
     */
    public static NamedList<Explanation> getExplanations
      (Query query, 
       DocList docs, 
       SolrIndexSearcher searcher, 
       IndexSchema schema) throws IOException {
      
      NamedList<Explanation> explainList = new SimpleOrderedMap<Explanation>();
      DocIterator iterator = docs.iterator();
      for (int i=0; i<docs.size(); i++) {
        int id = iterator.nextDoc();
  
        Document doc = searcher.doc(id);
        String strid = schema.printableUniqueKey(doc);
  
        explainList.add(strid, searcher.explain(query, id) );
      }
      return explainList;
    }
  
    private static NamedList<String> explanationsToStrings
      (NamedList<Explanation> explanations) {
  
      NamedList<String> out = new SimpleOrderedMap<String>();
      for (Map.Entry<String,Explanation> entry : explanations) {
        out.add(entry.getKey(), "\n"+entry.getValue().toString());
      }
      return out;
    }
  
    /**
     * Executes a basic query
     * Generates an list of Explanations for each item in a list of docs.
     *
     * @param query The Query you want explanations in the context of
     * @param docs The Documents you want explained relative that query
     * @deprecated this returns the explanations as Strings, instead it 
     *    is recommeded to use getExplanations and call toString() 
     *    yourself, or use explanationsToNamedLists
     */
    @Deprecated
    public static NamedList getExplainList(Query query, DocList docs,
                                           SolrIndexSearcher searcher,
                                           IndexSchema schema)
      throws IOException {
  
      return explanationsToStrings(getExplanations(query,docs,searcher,schema));
    }
  
    /**
     * Executes a basic query in lucene syntax
     */
    public static DocList doSimpleQuery(String sreq,
                                        SolrQueryRequest req,
                                        int start, int limit) throws IOException {
      List<String> commands = StrUtils.splitSmart(sreq,';');
  
      String qs = commands.size() >= 1 ? commands.get(0) : "";
      try {
      Query query = QParser.getParser(qs, null, req).getQuery();
  
      // If the first non-query, non-filter command is a simple sort on an indexed field, then
      // we can use the Lucene sort ability.
      Sort sort = null;
      if (commands.size() >= 2) {
        sort = QueryParsing.parseSort(commands.get(1), req);
      }
  
      DocList results = req.getSearcher().getDocList(query,(DocSet)null, sort, start, limit);
      return results;
      } catch (ParseException e) {
        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error parsing query: " + qs);
      }
  
    }
      
    /**
     * Given a string containing fieldNames and boost info,
     * converts it to a Map from field name to boost info.
     *
     * <p>
     * Doesn't care if boost info is negative, you're on your own.
     * </p>
     * <p>
     * Doesn't care if boost info is missing, again: you're on your own.
     * </p>
     *
     * @param in a String like "fieldOne^2.3 fieldTwo fieldThree^-0.4"
     * @return Map of fieldOne =&gt; 2.3, fieldTwo =&gt; null, fieldThree =&gt; -0.4
     */
    public static Map<String,Float> parseFieldBoosts(String in) {
      return parseFieldBoosts(new String[]{in});
    }
    /**
     * Like <code>parseFieldBoosts(String)</code>, but parses all the strings
     * in the provided array (which may be null).
     *
     * @param fieldLists an array of Strings eg. <code>{"fieldOne^2.3", "fieldTwo", fieldThree^-0.4}</code>
     * @return Map of fieldOne =&gt; 2.3, fieldTwo =&gt; null, fieldThree =&gt; -0.4
     */
    public static Map<String,Float> parseFieldBoosts(String[] fieldLists) {
      if (null == fieldLists || 0 == fieldLists.length) {
        return new HashMap<String,Float>();
      }
      Map<String, Float> out = new HashMap<String,Float>(7);
      for (String in : fieldLists) {
        if (null == in || "".equals(in.trim()))
          continue;
        String[] bb = in.trim().split("\\s+");
        for (String s : bb) {
          String[] bbb = s.split("\\^");
          out.put(bbb[0], 1 == bbb.length ? null : Float.valueOf(bbb[1]));
        }
      }
      return out;
    }
    /**
     * Given a string containing functions with optional boosts, returns
     * an array of Queries representing those functions with the specified
     * boosts.
     * <p>
     * NOTE: intra-function whitespace is not allowed.
     * </p>
     * @see #parseFieldBoosts
     * @deprecated
     */
    @Deprecated
    public static List<Query> parseFuncs(IndexSchema s, String in)
      throws ParseException {
    
      Map<String,Float> ff = parseFieldBoosts(in);
      List<Query> funcs = new ArrayList<Query>(ff.keySet().size());
      for (String f : ff.keySet()) {
        Query fq = QueryParsing.parseFunction(f, s);
        Float b = ff.get(f);
        if (null != b) {
          fq.setBoost(b);
        }
        funcs.add(fq);
      }
      return funcs;
    }
  
      
    /**
     * Checks the number of optional clauses in the query, and compares it
     * with the specification string to determine the proper value to use.
     *
     * <p>
     * Details about the specification format can be found
     * <a href="doc-files/min-should-match.html">here</a>
     * </p>
     *
     * <p>A few important notes...</p>
     * <ul>
     * <li>
     * If the calculations based on the specification determine that no
     * optional clauses are needed, BooleanQuerysetMinMumberShouldMatch
     * will never be called, but the usual rules about BooleanQueries
     * still apply at search time (a BooleanQuery containing no required
     * clauses must still match at least one optional clause)
     * <li>
     * <li>
     * No matter what number the calculation arrives at,
     * BooleanQuery.setMinShouldMatch() will never be called with a
     * value greater then the number of optional clauses (or less then 1)
     * </li>
     * </ul>
     *
     * <p>:TODO: should optimize the case where number is same
     * as clauses to just make them all "required"
     * </p>
     */
    public static void setMinShouldMatch(BooleanQuery q, String spec) {
  
      int optionalClauses = 0;
      for (BooleanClause c : q.clauses()) {
        if (c.getOccur() == Occur.SHOULD) {
          optionalClauses++;
        }
      }
  
      int msm = calculateMinShouldMatch(optionalClauses, spec);
      if (0 < msm) {
        q.setMinimumNumberShouldMatch(msm);
      }
    }
  
    /**
     * helper exposed for UnitTests
     * @see #setMinShouldMatch
     */
    static int calculateMinShouldMatch(int optionalClauseCount, String spec) {
  
      int result = optionalClauseCount;
          
  
      if (-1 < spec.indexOf("<")) {
        /* we have conditional spec(s) */
              
        for (String s : spec.trim().split(" ")) {
          String[] parts = s.split("<");
          int upperBound = (new Integer(parts[0])).intValue();
          if (optionalClauseCount <= upperBound) {
            return result;
          } else {
            result = calculateMinShouldMatch
              (optionalClauseCount, parts[1]);
          }
        }
        return result;
      }
  
      /* otherwise, simple expresion */
  
      if (-1 < spec.indexOf("%")) {
        /* percentage */
        int percent = new Integer(spec.replace("%","")).intValue();
        float calc = (result * percent) / 100f;
        result = calc < 0 ? result + (int)calc : (int)calc;
      } else {
        int calc = (new Integer(spec)).intValue();
        result = calc < 0 ? result + calc : calc;
      }
  
      return (optionalClauseCount < result ?
              optionalClauseCount : (result < 0 ? 0 : result));
                    
    }
      
      
    /**
     * Recursively walks the "from" query pulling out sub-queries and
     * adding them to the "to" query.
     *
     * <p>
     * Boosts are multiplied as needed.  Sub-BooleanQueryies which are not
     * optional will not be flattened.  From will be mangled durring the walk,
     * so do not attempt to reuse it.
     * </p>
     */
    public static void flattenBooleanQuery(BooleanQuery to, BooleanQuery from) {
  
      for (BooleanClause clause : from.clauses()) {
        
        Query cq = clause.getQuery();
        cq.setBoost(cq.getBoost() * from.getBoost());
              
        if (cq instanceof BooleanQuery
            && !clause.isRequired()
            && !clause.isProhibited()) {
                  
          /* we can recurse */
          flattenBooleanQuery(to, (BooleanQuery)cq);
                  
        } else {
          to.add(clause);
        }
      }
    }
  
    /**
     * Escapes all special characters except '"', '-', and '+'
     *
     * @see QueryParser#escape
     */
    public static CharSequence partialEscape(CharSequence s) {
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
        if (c == '\\' || c == '!' || c == '(' || c == ')' ||
            c == ':'  || c == '^' || c == '[' || c == ']' ||
            c == '{'  || c == '}' || c == '~' || c == '*' || c == '?'
            ) {
          sb.append('\\');
        }
        sb.append(c);
      }
      return sb;
    }
  
    // Pattern to detect dangling operator(s) at end of query
    // \s+[-+\s]+$
    private final static Pattern DANGLING_OP_PATTERN = Pattern.compile( "\\s+[-+\\s]+$" );
    // Pattern to detect consecutive + and/or - operators
    // \s+[+-](?:\s*[+-]+)+
    private final static Pattern CONSECUTIVE_OP_PATTERN = Pattern.compile( "\\s+[+-](?:\\s*[+-]+)+" );    
  
    /**
     * Strips operators that are used illegally, otherwise reuturns it's
     * input.  Some examples of illegal user queries are: "chocolate +-
     * chip", "chocolate - - chip", and "chocolate chip -".
     */
    public static CharSequence stripIllegalOperators(CharSequence s) {
      String temp = CONSECUTIVE_OP_PATTERN.matcher( s ).replaceAll( " " );
      return DANGLING_OP_PATTERN.matcher( temp ).replaceAll( "" );
    }
  
    /**
     * Returns it's input if there is an even (ie: balanced) number of
     * '"' characters -- otherwise returns a String in which all '"'
     * characters are striped out.
     */
    public static CharSequence stripUnbalancedQuotes(CharSequence s) {
      int count = 0;
      for (int i = 0; i < s.length(); i++) {
        if (s.charAt(i) == '\"') { count++; }
      }
      if (0 == (count & 1)) {
        return s;
      }
      return s.toString().replace("\"","");
    }
  
    /**
     * A subclass of SolrQueryParser that supports aliasing fields for
     * constructing DisjunctionMaxQueries.
     */
    public static class DisjunctionMaxQueryParser extends SolrQueryParser {
  
      /** A simple container for storing alias info
       * @see #aliases
       */
      protected static class Alias {
        public float tie;
        public Map<String,Float> fields;
      }
  
      /**
       * Where we store a map from field name we expect to see in our query
       * string, to Alias object containing the fields to use in our
       * DisjunctionMaxQuery and the tiebreaker to use.
       */
      protected Map<String,Alias> aliases = new HashMap<String,Alias>(3);
      public DisjunctionMaxQueryParser(QParser qp, String defaultField) {
        super(qp,defaultField);
        // don't trust that our parent class won't ever change it's default
        setDefaultOperator(QueryParser.Operator.OR);
      }
      public DisjunctionMaxQueryParser(IndexSchema s, String defaultField) {
        super(s,defaultField);
        // don't trust that our parent class won't ever change it's default
        setDefaultOperator(QueryParser.Operator.OR);
      }
      public DisjunctionMaxQueryParser(IndexSchema s) {
        this(s,null);
      }
  
      /**
       * Add an alias to this query parser.
       *
       * @param field the field name that should trigger alias mapping
       * @param fieldBoosts the mapping from fieldname to boost value that
       *                    should be used to build up the clauses of the
       *                    DisjunctionMaxQuery.
       * @param tiebreaker to the tiebreaker to be used in the
       *                   DisjunctionMaxQuery
       * @see SolrPluginUtils#parseFieldBoosts
       */
      public void addAlias(String field, float tiebreaker,
                           Map<String,Float> fieldBoosts) {
  
        Alias a = new Alias();
        a.tie = tiebreaker;
        a.fields = fieldBoosts;
        aliases.put(field, a);
      }
  
      /**
       * Delegates to the super class unless the field has been specified
       * as an alias -- in which case we recurse on each of
       * the aliased fields, and the results are composed into a
       * DisjunctionMaxQuery.  (so yes: aliases which point at other
       * aliases should work)
       */
      @Override
      protected Query getFieldQuery(String field, String queryText, boolean quoted)
        throws ParseException {
              
        if (aliases.containsKey(field)) {
                  
          Alias a = aliases.get(field);
          DisjunctionMaxQuery q = new DisjunctionMaxQuery(a.tie);
  
          /* we might not get any valid queries from delegation,
           * in which case we should return null
           */
          boolean ok = false;
                  
          for (String f : a.fields.keySet()) {
  
            Query sub = getFieldQuery(f,queryText,quoted);
            if (null != sub) {
              if (null != a.fields.get(f)) {
                sub.setBoost(a.fields.get(f));
              }
              q.add(sub);
              ok = true;
            }
          }
          return ok ? q : null;
  
        } else {
          try {
            return super.getFieldQuery(field, queryText, quoted);
          } catch (Exception e) {
            return null;
          }
        }
      }
          
    }
  
    /**
     * Determines the correct Sort based on the request parameter "sort"
     *
     * @return null if no sort is specified.
     */
    public static Sort getSort(SolrQueryRequest req) {
  
      String sort = req.getParams().get(CommonParams.SORT);
      if (null == sort || sort.equals("")) {
        return null;
      }
  
      SolrException sortE = null;
      Sort ss = null;
      try {
        ss = QueryParsing.parseSort(sort, req);
      } catch (SolrException e) {
        sortE = e;
      }
  
      if ((null == ss) || (null != sortE)) {
        /* we definitely had some sort of sort string from the user,
         * but no SortSpec came out of it
         */
        SolrCore.log.warn("Invalid sort \""+sort+"\" was specified, ignoring", sortE);
        return null;
      }
          
      return ss;
    }
  
    /**
     * Builds a list of Query objects that should be used to filter results
     * @see CommonParams#FQ
     * @return null if no filter queries
     */
    public static List<Query> parseFilterQueries(SolrQueryRequest req) throws ParseException {
      return parseQueryStrings(req, req.getParams().getParams(CommonParams.FQ));
    }
  
    /** Turns an array of query strings into a List of Query objects.
     *
     * @return null if no queries are generated
     */
    public static List<Query> parseQueryStrings(SolrQueryRequest req, 
                                                String[] queries) throws ParseException {    
      if (null == queries || 0 == queries.length) return null;
      List<Query> out = new ArrayList<Query>(queries.length);
      for (String q : queries) {
        if (null != q && 0 != q.trim().length()) {
          out.add(QParser.getParser(q, null, req).getQuery());
        }
      }
      return out;
    }
  
    /**
     * A CacheRegenerator that can be used whenever the items in the cache
     * are not dependant on the current searcher.
     *
     * <p>
     * Flat out copies the oldKey=&gt;oldVal pair into the newCache
     * </p>
     */
    public static class IdentityRegenerator implements CacheRegenerator {
      public boolean regenerateItem(SolrIndexSearcher newSearcher,
                                    SolrCache newCache,
                                    SolrCache oldCache,
                                    Object oldKey,
                                    Object oldVal)
        throws IOException {
  
        newCache.put(oldKey,oldVal);
        return true;
      }
    }
  
    /**
     * Convert a DocList to a SolrDocumentList
     *
     * The optional param "ids" is populated with the lucene document id
     * for each SolrDocument.
     *
     * @param docs The {@link org.apache.solr.search.DocList} to convert
     * @param searcher The {@link org.apache.solr.search.SolrIndexSearcher} to use to load the docs from the Lucene index
     * @param fields The names of the Fields to load
     * @param ids A map to store the ids of the docs
     * @return The new {@link org.apache.solr.common.SolrDocumentList} containing all the loaded docs
     * @throws java.io.IOException if there was a problem loading the docs
     * @since solr 1.4
     */
    public static SolrDocumentList docListToSolrDocumentList(
        DocList docs, 
        SolrIndexSearcher searcher, 
        Set<String> fields, 
        Map<SolrDocument, Integer> ids ) throws IOException
    {
      DocumentBuilder db = new DocumentBuilder(searcher.getSchema());
      SolrDocumentList list = new SolrDocumentList();
      list.setNumFound(docs.matches());
      list.setMaxScore(docs.maxScore());
      list.setStart(docs.offset());
  
      DocIterator dit = docs.iterator();
      while (dit.hasNext()) {
        int docid = dit.nextDoc();
  
        Document luceneDoc = searcher.doc(docid, fields);
        SolrDocument doc = new SolrDocument();
        db.loadStoredFields(doc, luceneDoc);
  
        // this may be removed if XMLWriter gets patched to
        // include score from doc iterator in solrdoclist
        if (docs.hasScores()) {
          doc.addField("score", dit.score());
        } else {
          doc.addField("score", 0.0f);
        }
  
        list.add( doc );
  
        if( ids != null ) {
          ids.put( doc, new Integer(docid) );
        }
      }
      return list;
    }
  
  
  
    /**
     * Given a SolrQueryResponse replace the DocList if it is in the result.  
     * Otherwise add it to the response
     * 
     * @since solr 1.4
     */
    public static void addOrReplaceResults(SolrQueryResponse rsp, SolrDocumentList docs) 
    {
      NamedList vals = rsp.getValues();
      int idx = vals.indexOf( "response", 0 );
      if( idx >= 0 ) {
        log.debug("Replacing DocList with SolrDocumentList " + docs.size());
        vals.setVal( idx, docs );
      }
      else {
        log.debug("Adding SolrDocumentList response" + docs.size());
        vals.add( "response", docs );
      }
    }
    
    public static void invokeSetters(Object bean, NamedList initArgs) {
      if (initArgs == null) return;
      Class clazz = bean.getClass();
      Method[] methods = clazz.getMethods();
      Iterator<Map.Entry<String, Object>> iterator = initArgs.iterator();
      while (iterator.hasNext()) {
        Map.Entry<String, Object> entry = iterator.next();
        String key = entry.getKey();
        String setterName = "set" + String.valueOf(Character.toUpperCase(key.charAt(0))) + key.substring(1);
        Method method = null;
        try {
          for (Method m : methods) {
            if (m.getName().equals(setterName) && m.getParameterTypes().length == 1) { 
              method = m;
              break;
            }
          }
          if (method == null) {
            throw new RuntimeException("no setter corrresponding to '" + key + "' in " + clazz.getName());
          }
          Class pClazz = method.getParameterTypes()[0];
          Object val = entry.getValue();
          method.invoke(bean, val);
        } catch (InvocationTargetException e1) {
          throw new RuntimeException("Error invoking setter " + setterName + " on class : " + clazz.getName(), e1);
        } catch (IllegalAccessException e1) {
          throw new RuntimeException("Error invoking setter " + setterName + " on class : " + clazz.getName(), e1);
        }
      }
    }
    
    /**
     * This method helps resolve if the deprecated "update.processor" request parameter is used  
     * and logs a warning if it is. In a future version, this method will be removed and
     * Solr will assume "update.chain" and not look for "update.processor"
     * @param params an instance of SolrParams from the request
     * @param log an instance of a slf4j logger to log a warning in case of deprecated param usage
     * @return null if neither is specified, else the value of the param specified
     * @deprecated
     */
    public static String resolveUpdateChainParam(SolrParams params, Logger log) {
      if(params.get(UpdateParams.UPDATE_CHAIN_DEPRECATED) != null && log != null) {
        log.warn("Use of deprecated update request parameter "+UpdateParams.UPDATE_CHAIN_DEPRECATED+
             " detected. Please use the new parameter "+UpdateParams.UPDATE_CHAIN+" instead, as support"+
             " for "+UpdateParams.UPDATE_CHAIN_DEPRECATED+" will be removed in a later version.");
      }
      return (params.get(UpdateParams.UPDATE_CHAIN) != null) 
        ? params.get(UpdateParams.UPDATE_CHAIN) 
        : params.get(UpdateParams.UPDATE_CHAIN_DEPRECATED);
    }
  }
  
  
  
  
  
  
  
  