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.request;
  
  import org.apache.log4j.Logger;
  import org.apache.lucene.index.Term;
  import org.apache.lucene.index.TermEnum;
  import org.apache.lucene.index.IndexReader;
  import org.apache.lucene.index.TermDocs;
  import org.apache.lucene.queryParser.ParseException;
  import org.apache.lucene.search.*;
  import org.apache.lucene.search.grouping.AbstractAllGroupHeadsCollector;
  import org.apache.lucene.util.FixedBitSet;
  import org.apache.lucene.util.OpenBitSet;
  
  import org.apache.solr.common.SolrException;
  import org.apache.solr.common.SolrException.ErrorCode;
  import org.apache.solr.common.params.FacetParams;
  import org.apache.solr.common.params.RequiredSolrParams;
  import org.apache.solr.common.params.SolrParams;
  import org.apache.solr.common.params.CommonParams;
  import org.apache.solr.common.params.FacetParams.FacetRangeOther;
  import org.apache.solr.common.params.FacetParams.FacetRangeInclude;
  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.request.mdrill.MdrillDetail;
  import org.apache.solr.request.mdrill.MdrillGroupBy;
  import org.apache.solr.request.uninverted.UnInvertedField;
  import org.apache.solr.schema.*;
  import org.apache.solr.search.*;
  import org.apache.solr.util.BoundedTreeSet;
  import org.apache.solr.util.DateMathParser;
  import org.apache.solr.handler.component.ResponseBuilder;
  
  import java.io.IOException;
  import java.util.*;
  
  
  public class SimpleFacets {
      private static Logger LOG = Logger.getLogger(SimpleFacets.class);
  
    /** The main set of documents all facet counts should be relative to */
    protected DocSet docs;
    /** Configuration params behavior should be driven by */
    protected SolrParams params;
    protected SolrParams required;
    /** Searcher to use for all calculations */
    protected SolrIndexSearcher searcher;
    protected SolrQueryRequest req;
    protected ResponseBuilder rb;
  
    protected SimpleOrderedMap facetResponse;
  
    public final Date NOW = new Date();
  
    // per-facet values
    SolrParams localParams; // localParams on this particular facet command
    String facetValue;      // the field to or query to facet on (minus local params)
    DocSet base;            // the base docset for this particular facet
    String key;             // what name should the results be stored under
  
    public SimpleFacets(SolrQueryRequest req,
                        DocSet docs,
                        SolrParams params) {
      this(req,docs,params,null);
    }
  
    public SimpleFacets(SolrQueryRequest req,
                        DocSet docs,
                        SolrParams params,
                        ResponseBuilder rb) {
      this.req = req;
      this.searcher = req.getSearcher();
      this.base = this.docs = docs;
      this.params = params;
      this.required = new RequiredSolrParams(params);
      this.rb = rb;
    }
  
  //
  //  void parseParams(String type, String param) throws ParseException, IOException {
  //    localParams = QueryParsing.getLocalParams(param, req.getParams());
  //    base = docs;
  //    facetValue = param;
  //    key = param;
  //
  //    if (localParams == null) return;
  //
  //    // remove local params unless it's a query
  //    if (type != FacetParams.FACET_QUERY) {
  //      facetValue = localParams.get(CommonParams.VALUE);
  //    }
  //
  //    // reset set the default key now that localParams have been removed
  //    key = facetValue;
  //
  //    // allow explicit set of the key
  //    key = localParams.get(CommonParams.OUTPUT_KEY, key);
  //
  //    // figure out if we need a new base DocSet
  //    String excludeStr = localParams.get(CommonParams.EXCLUDE);
  //    if (excludeStr == null) return;
  //
  //    Map tagMap = (Map)req.getContext().get("tags");
  //    if (tagMap != null && rb != null) {
  //      List<String> excludeTagList = StrUtils.splitSmart(excludeStr,',');
  //
  //      IdentityHashMap<Query,Boolean> excludeSet = new IdentityHashMap<Query,Boolean>();
  //      for (String excludeTag : excludeTagList) {
  //        Object olst = tagMap.get(excludeTag);
  //        // tagMap has entries of List<String,List<QParser>>, but subject to change in the future
  //        if (!(olst instanceof Collection)) continue;
  //        for (Object o : (Collection)olst) {
  //          if (!(o instanceof QParser)) continue;
  //          QParser qp = (QParser)o;
  //          excludeSet.put(qp.getQuery(), Boolean.TRUE);
  //        }
  //      }
  //      if (excludeSet.size() == 0) return;
  //
  //      List<Query> qlist = new ArrayList<Query>();
  //
  //      // add the base query
  //      if (!excludeSet.containsKey(rb.getQuery())) {
  //        qlist.add(rb.getQuery());
  //      }
  //
  //      // add the filters
  //      if (rb.getFilters() != null) {
  //        for (Query q : rb.getFilters()) {
  //          if (!excludeSet.containsKey(q)) {
  //            qlist.add(q);
  //          }
  //        }
  //      }
  //
  //      // get the new base docset for this facet
  //      DocSet base = searcher.getDocSet(qlist);
  //      if (rb.grouping() && rb.getGroupingSpec().isTruncateGroups()) {
  //        Grouping grouping = new Grouping(searcher, null, rb.getQueryCommand(), false, 0, false);
  //        if (rb.getGroupingSpec().getFields().length > 0) {
  //          grouping.addFieldCommand(rb.getGroupingSpec().getFields()[0], req);
  //        } else {
  //          this.base = base;
  //          return;
  //        }
  //        AbstractAllGroupHeadsCollector allGroupHeadsCollector = grouping.getCommands().get(0).createAllGroupCollector();
  //        searcher.search(new MatchAllDocsQuery(), base.getTopFilter(), allGroupHeadsCollector);
  //        int maxDoc = searcher.maxDoc();
  //        FixedBitSet fixedBitSet = allGroupHeadsCollector.retrieveGroupHeads(maxDoc);
  //        long[] bits = fixedBitSet.getBits();
  //        this.base = new BitDocSet(new OpenBitSet(bits, bits.length));
  //      } else {
  //        this.base = base;
  //      }
  //    }
  //
  //  }
  
  
    /**
     * Looks at various Params to determing if any simple Facet Constraint count
     * computations are desired.
     *
     * @see #getFacetQueryCounts
     * @see #getFacetFieldCounts
     * @see #getFacetDateCounts
     * @see #getFacetRangeCounts
     * @see FacetParams#FACET
     * @return a NamedList of Facet Count info or null
     */
    public NamedList getFacetCounts() {
  
      // if someone called this method, benefit of the doubt: assume true
      if (!params.getBool(FacetParams.FACET,true))
        return null;
  
      facetResponse = new SimpleOrderedMap();
      try {
        facetResponse.add("facet_queries", new SimpleOrderedMap());//getFacetQueryCounts()
        facetResponse.add("facet_fields", new SimpleOrderedMap());
        facetResponse.add("facet_dates",  new SimpleOrderedMap());//getFacetDateCounts()
        facetResponse.add("facet_ranges",new SimpleOrderedMap() );//getFacetRangeCounts()
  
      } catch (Throwable e) {
      	LOG.error("getFacetCounts",e);
          throw new SolrException(ErrorCode.SERVER_ERROR, e);
        }
      return facetResponse;
    }
  
    /**
     * Returns a list of facet counts for each of the facet queries 
     * specified in the params
     *
     * @see FacetParams#FACET_QUERY
     */
  //  public NamedList getFacetQueryCounts() throws IOException,ParseException {
  //
  //    NamedList res = new SimpleOrderedMap();
  //
  //    /* Ignore CommonParams.DF - could have init param facet.query assuming
  //     * the schema default with query param DF intented to only affect Q.
  //     * If user doesn't want schema default for facet.query, they should be
  //     * explicit.
  //     */
  //    // SolrQueryParser qp = searcher.getSchema().getSolrQueryParser(null);
  //
  //    String[] facetQs = params.getParams(FacetParams.FACET_QUERY);
  //
  //    if (null != facetQs && 0 != facetQs.length) {
  //      for (String q : facetQs) {
  //        parseParams(FacetParams.FACET_QUERY, q);
  //
  //        // TODO: slight optimization would prevent double-parsing of any localParams
  //        Query qobj = QParser.getParser(q, null, req).getQuery();
  //        res.add(key, searcher.numDocs(qobj, base));
  //      }
  //    }
  //
  //    return res;
  //  }
  
  
  //  public NamedList getTermCounts(String field,DocSet dc,boolean iscross,boolean isrow) throws IOException {
  //    Integer mincount = params.getFieldInt(field, FacetParams.FACET_MINCOUNT);
  //    if (mincount==null) {
  //      Boolean zeros = params.getFieldBool(field, FacetParams.FACET_ZEROS);
  //      mincount = (zeros!=null && !zeros) ? 1 : 0;
  //    }
  //    
  //    return this.getTermCounts(field,mincount, dc, iscross, isrow);
  //  }
    
  //  
  //  public NamedList getTermCounts(String field,int mincount,DocSet dc,boolean iscross,boolean isrow) throws IOException {
  //	    int offset = params.getFieldInt(field, FacetParams.FACET_OFFSET, 0);
  //	    int limit = params.getFieldInt(field, FacetParams.FACET_LIMIT, 100);
  //	    if (limit == 0) return new NamedList();
  //	    boolean missing = params.getFieldBool(field, FacetParams.FACET_MISSING, false);
  //	    String sort = params.getFieldParam(field, FacetParams.FACET_SORT, limit>0 ? FacetParams.FACET_SORT_COUNT : FacetParams.FACET_SORT_INDEX);
  //	    String prefix = params.getFieldParam(field,FacetParams.FACET_PREFIX);
  //
  //
  //	    SchemaField sf = searcher.getSchema().getField(field);
  //	    FieldType ft = sf.getType();
  //
  //	    // determine what type of faceting method to use
  //	    String method = params.getFieldParam(field, FacetParams.FACET_METHOD);
  //	    boolean enumMethod = FacetParams.FACET_METHOD_enum.equals(method);
  //	    if (method == null && ft instanceof BoolField) {
  //	      enumMethod = true;
  //	    }
  //
  //	    if (TrieField.getMainValuePrefix(ft) != null) {
  //	      enumMethod = false;
  //	    }
  //
  //	    return getTermCountsLimit(field, dc, offset, limit, mincount, missing, sort, prefix, enumMethod,iscross,isrow);
  //	  }
    
    
  
  
  //  /**
  //   * Returns a list of value constraints and the associated facet counts 
  //   * for each facet field specified in the params.
  // * @throws Exception 
  //   *
  //   * @see FacetParams#FACET_FIELD
  //   * @see #getFieldMissingCount
  //   * @see #getFacetTermEnumCounts
  //   */
  //  public NamedList getFacetFieldCounts()
  //          throws Exception {
  //
  //    NamedList res = new SimpleOrderedMap();
  //    String[] facetFs = params.getParams(FacetParams.FACET_FIELD);
  //    if (null == facetFs) {
  //       return res;
  //    }
  //    
  //    boolean isdetail = params.getBool(FacetParams.FACET_CROSS_DETAIL,false);
  //    if(isdetail)
  //    {
  //    	MdrillDetail crossObj=new MdrillDetail(searcher, params,this.req);
  //        res.add("solrCorssFields_s", crossObj.getBySchemaReader(facetFs,this.base));
  //    }else{
  //
  //    	MdrillGroupBy crossObj=new MdrillGroupBy(searcher, params,this.req);
  //        res.add("solrCorssFields_s", crossObj.getBySchemaReader(facetFs,this.base));
  //    }
  //    
  //    return res;
  //  }
    
    
  //  public NamedList getTermCountsUniq(String f,DocSet dc,boolean iscross,boolean isrow) throws ParseException, IOException
  //  {
  //
  //      parseParams(FacetParams.FACET_FIELD, f);
  //      String termList = localParams == null ? null : localParams.get(CommonParams.TERMS);
  //      if (termList != null) {
  //        return getListedTermCounts(facetValue, termList,dc);
  //      } else {
  //        return getTermCounts(facetValue,dc,iscross,isrow);
  //      }
  //  }
    
  //  public NamedList getTermCountsUniq(String f,int mincount,DocSet dc,boolean iscross,boolean isrow) throws ParseException, IOException
  //  {
  //
  //      parseParams(FacetParams.FACET_FIELD, f);
  //      String termList = localParams == null ? null : localParams.get(CommonParams.TERMS);
  //      if (termList != null) {
  //        return getListedTermCounts(facetValue, termList,dc);
  //      } else {
  //        return getTermCounts(facetValue,mincount,dc,iscross,isrow);
  //      }
  //  }
    
  // 
  //  public NamedList getTermCountsLimit(String field,DocSet dc,int offset,int limit ,Integer mincount, boolean missing,String sort,String prefix,boolean enumMethod,boolean iscross,boolean isRow) throws IOException {
  //		if (limit == 0) {
  //			return new NamedList();
  //		}
  //
  //	    NamedList counts;
  //	    SchemaField sf = searcher.getSchema().getField(field);
  //	    FieldType ft = sf.getType();
  //
  //
  //	    boolean multiToken = sf.multiValued() || ft.multiValuedFieldCache();
  //
  //	    if (TrieField.usedMainPrefix(ft)) {
  //	      multiToken = true;
  //	    }
  //
  //	    // unless the enum method is explicitly specified, use a counting method.
  //	    if (enumMethod) {
  //	      counts = getFacetTermEnumCounts(searcher, dc, field, offset, limit, mincount,missing,sort,prefix);
  //	    } else {
  //	      if (multiToken) {
  //	        UnInvertedField uif = UnInvertedField.getUnInvertedField(field, searcher);
  //	        counts = uif.getCounts(searcher, dc, offset, limit, mincount,missing,sort,prefix,iscross,isRow);
  //	      } else {
  //	        counts = getFieldCacheCounts(searcher, dc, field, offset,limit, mincount, missing, sort, prefix);
  //	      }
  //	    }
  //
  //	    return counts;
  //	  }
  //  
  //  
  //  
  
  
  //  private NamedList getListedTermCounts(String field, String termList,DocSet dc) throws IOException {
  //    FieldType ft = searcher.getSchema().getFieldType(field);
  //    List<String> terms = StrUtils.splitSmart(termList, ",", true);
  //    NamedList res = new NamedList();
  //    Term t = new Term(field);
  //    for (String term : terms) {
  //      String internal = ft.toInternal(term);
  //      int count = searcher.numDocs(new TermQuery(t.createTerm(internal)), dc);
  //      res.add(term, count);
  //    }
  //    return res;    
  //  }
  
  //
  //  /**
  //   * Returns a count of the documents in the set which do not have any 
  //   * terms for for the specified field.
  //   *
  //   * @see FacetParams#FACET_MISSING
  //   */
  //  public static int getFieldMissingCount(SolrIndexSearcher searcher, DocSet docs, String fieldName)
  //    throws IOException {
  //
  //    DocSet hasVal = searcher.getDocSet
  //      (new TermRangeQuery(fieldName, null, null, false, false));
  //    return docs.andNotSize(hasVal);
  //  }
  //
  //
  //  // first element of the fieldcache is null, so we need this comparator.
  //  private static final Comparator nullStrComparator = new Comparator() {
  //        public int compare(Object o1, Object o2) {
  //          if (o1==null) return (o2==null) ? 0 : -1;
  //          else if (o2==null) return 1;
  //          return ((String)o1).compareTo((String)o2);
  //        }
  //      }; 
  
  //  /**
  //   * Use the Lucene FieldCache to get counts for each unique field value in <code>docs</code>.
  //   * The field must have at most one indexed token per document.
  //   */
  //  public static NamedList getFieldCacheCounts(SolrIndexSearcher searcher, DocSet docs, String fieldName, int offset, int limit, int mincount, boolean missing, String sort, String prefix) throws IOException {
  //    // TODO: If the number of terms is high compared to docs.size(), and zeros==false,
  //    //  we should use an alternate strategy to avoid
  //    //  1) creating another huge int[] for the counts
  //    //  2) looping over that huge int[] looking for the rare non-zeros.
  //    //
  //    // Yet another variation: if docs.size() is small and termvectors are stored,
  //    // then use them instead of the FieldCache.
  //    //
  //
  //    // TODO: this function is too big and could use some refactoring, but
  //    // we also need a facet cache, and refactoring of SimpleFacets instead of
  //    // trying to pass all the various params around.
  //
  //    FieldType ft = searcher.getSchema().getFieldType(fieldName);
  //    NamedList res = new NamedList();
  //
  //    FieldCache.StringIndex si = FieldCache.DEFAULT.getStringIndex(searcher.getReader(), fieldName);
  //    final String[] terms = si.lookup;
  //    final int[] termNum = si.order;
  //
  //    if (prefix!=null && prefix.length()==0) prefix=null;
  //
  //    int startTermIndex, endTermIndex;
  //    if (prefix!=null) {
  //      startTermIndex = Arrays.binarySearch(terms,prefix,nullStrComparator);
  //      if (startTermIndex<0) startTermIndex=-startTermIndex-1;
  //      // find the end term.  \uffff isn't a legal unicode char, but only compareTo
  //      // is used, so it should be fine, and is guaranteed to be bigger than legal chars.
  //      endTermIndex = Arrays.binarySearch(terms,prefix+"\uffff\uffff\uffff\uffff",nullStrComparator);
  //      endTermIndex = -endTermIndex-1;
  //    } else {
  //      startTermIndex=1;
  //      endTermIndex=terms.length;
  //    }
  //
  //    final int nTerms=endTermIndex-startTermIndex;
  //
  //    if (nTerms>0 && docs.size() >= mincount) {
  //
  //      // count collection array only needs to be as big as the number of terms we are
  //      // going to collect counts for.
  //      final int[] counts = new int[nTerms];
  //
  //      DocIterator iter = docs.iterator();
  //      while (iter.hasNext()) {
  //        int term = termNum[iter.nextDoc()];
  //        int arrIdx = term-startTermIndex;
  //        if (arrIdx>=0 && arrIdx<nTerms) counts[arrIdx]++;
  //      }
  //
  //      // IDEA: we could also maintain a count of "other"... everything that fell outside
  //      // of the top 'N'
  //
  //      int off=offset;
  //      int lim=limit>=0 ? limit : Integer.MAX_VALUE;
  //
  //      if (sort.equals(FacetParams.FACET_SORT_COUNT) || sort.equals(FacetParams.FACET_SORT_COUNT_LEGACY)) {
  //        int maxsize = limit>0 ? offset+limit : Integer.MAX_VALUE-1;
  //        maxsize = Math.min(maxsize, nTerms);
  //        final BoundedTreeSet<CountPair<String,Integer>> queue = new BoundedTreeSet<CountPair<String,Integer>>(maxsize);
  //        int min=mincount-1;  // the smallest value in the top 'N' values
  //        for (int i=0; i<nTerms; i++) {
  //          int c = counts[i];
  //          if (c>min) {
  //            // NOTE: we use c>min rather than c>=min as an optimization because we are going in
  //            // index order, so we already know that the keys are ordered.  This can be very
  //            // important if a lot of the counts are repeated (like zero counts would be).
  //            queue.add(new CountPair<String,Integer>(terms[startTermIndex+i], c));
  //            if (queue.size()>=maxsize) min=queue.last().val;
  //          }
  //        }
  //        // now select the right page from the results
  //        for (CountPair<String,Integer> p : queue) {
  //          if (--off>=0) continue;
  //          if (--lim<0) break;
  //          res.add(ft.indexedToReadable(p.key), p.val);
  //        }
  //      } else {
  //        // add results in index order
  //        int i=0;
  //        if (mincount<=0) {
  //          // if mincount<=0, then we won't discard any terms and we know exactly
  //          // where to start.
  //          i=off;
  //          off=0;
  //        }
  //
  //        for (; i<nTerms; i++) {          
  //          int c = counts[i];
  //          if (c<mincount || --off>=0) continue;
  //          if (--lim<0) break;
  //          res.add(ft.indexedToReadable(terms[startTermIndex+i]), c);
  //        }
  //      }
  //    }
  //
  //    if (missing) {
  //      res.add(null, getFieldMissingCount(searcher,docs,fieldName));
  //    }
  //    
  //    return res;
  //  }
  
  //
  //  /**
  //   * Returns a list of terms in the specified field along with the 
  //   * corresponding count of documents in the set that match that constraint.
  //   * This method uses the FilterCache to get the intersection count between <code>docs</code>
  //   * and the DocSet for each term in the filter.
  //   *
  //   * @see FacetParams#FACET_LIMIT
  //   * @see FacetParams#FACET_ZEROS
  //   * @see FacetParams#FACET_MISSING
  //   */
  //  public NamedList getFacetTermEnumCounts(SolrIndexSearcher searcher, DocSet docs, String field, int offset, int limit, int mincount, boolean missing, String sort, String prefix)
  //    throws IOException {
  //
  //    /* :TODO: potential optimization...
  //    * cache the Terms with the highest docFreq and try them first
  //    * don't enum if we get our max from them
  //    */
  //
  //    // Minimum term docFreq in order to use the filterCache for that term.
  //    int minDfFilterCache = params.getFieldInt(field, FacetParams.FACET_ENUM_CACHE_MINDF, 0);
  //
  //    IndexSchema schema = searcher.getSchema();
  //    IndexReader r = searcher.getReader();
  //    FieldType ft = schema.getFieldType(field);
  //
  //    final int maxsize = limit>=0 ? offset+limit : Integer.MAX_VALUE-1;    
  //    final BoundedTreeSet<CountPair<String,Integer>> queue = (sort.equals("count") || sort.equals("true")) ? new BoundedTreeSet<CountPair<String,Integer>>(maxsize) : null;
  //    final NamedList res = new NamedList();
  //
  //    int min=mincount-1;  // the smallest value in the top 'N' values    
  //    int off=offset;
  //    int lim=limit>=0 ? limit : Integer.MAX_VALUE;
  //
  //    String startTerm = prefix==null ? "" : ft.toInternal(prefix);
  //    TermEnum te = r.terms(new Term(field,startTerm));
  //    TermDocs td = r.termDocs();
  //    SolrIndexSearcher.TermDocsState tdState = new SolrIndexSearcher.TermDocsState();
  //    tdState.tenum = te;
  //    tdState.tdocs = td;
  //
  //    if (docs.size() >= mincount) { 
  //    do {
  //      Term t = te.term();
  //
  //      if (null == t || ! t.field().equals(field))
  //        break;
  //
  //      if (prefix!=null && !t.text().startsWith(prefix)) break;
  //
  //      int df = te.docFreq();
  //
  //      // If we are sorting, we can use df>min (rather than >=) since we
  //      // are going in index order.  For certain term distributions this can
  //      // make a large difference (for example, many terms with df=1).
  //      if (df>0 && df>min) {
  //        int c;
  //
  //        if (df >= minDfFilterCache) {
  //          // use the filter cache
  //          c = docs.intersectionSize( searcher.getPositiveDocSet(new TermQuery(t), tdState) );
  //        } else {
  //          // iterate over TermDocs to calculate the intersection
  //          td.seek(te);
  //          c=0;
  //          while (td.next()) {
  //            if (docs.exists(td.doc())) c++;
  //          }
  //        }
  //
  //        if (sort.equals("count") || sort.equals("true")) {
  //          if (c>min) {
  //            queue.add(new CountPair<String,Integer>(t.text(), c));
  //            if (queue.size()>=maxsize) min=queue.last().val;
  //          }
  //        } else {
  //          if (c >= mincount && --off<0) {
  //            if (--lim<0) break;
  //            res.add(ft.indexedToReadable(t.text()), c);
  //          }
  //        }
  //      }
  //    } while (te.next());
  //    }
  //
  //    if (sort.equals("count") || sort.equals("true")) {
  //      for (CountPair<String,Integer> p : queue) {
  //        if (--off>=0) continue;
  //        if (--lim<0) break;
  //        res.add(ft.indexedToReadable(p.key), p.val);
  //      }
  //    }
  //
  //    if (missing) {
  //      res.add(null, getFieldMissingCount(searcher,docs,field));
  //    }
  //
  //    te.close();
  //    td.close();    
  //
  //    return res;
  //  }
  
  //  /**
  //   * Returns a list of value constraints and the associated facet counts 
  //   * for each facet date field, range, and interval specified in the
  //   * SolrParams
  //   *
  //   * @see FacetParams#FACET_DATE
  //   * @deprecated Use getFacetRangeCounts which is more generalized
  //   */
  //  @Deprecated
  //  public NamedList getFacetDateCounts()
  //    throws IOException, ParseException {
  //
  //    final NamedList resOuter = new SimpleOrderedMap();
  //    final String[] fields = params.getParams(FacetParams.FACET_DATE);
  //
  //    if (null == fields || 0 == fields.length) return resOuter;
  //
  //    for (String f : fields) {
  //      getFacetDateCounts(f, resOuter);
  //    }
  //
  //    return resOuter;
  //  }
  
  //  /**
  //   * @deprecated Use getFacetRangeCounts which is more generalized
  //   */
  //  @Deprecated
  //  public void getFacetDateCounts(String dateFacet, NamedList resOuter)
  //      throws IOException, ParseException {
  //
  //    final IndexSchema schema = searcher.getSchema();
  //
  //    parseParams(FacetParams.FACET_DATE, dateFacet);
  //    String f = facetValue;
  //
  //
  //    final NamedList resInner = new SimpleOrderedMap();
  //    resOuter.add(key, resInner);
  //    final SchemaField sf = schema.getField(f);
  //    if (! (sf.getType() instanceof DateField)) {
  //      throw new SolrException
  //          (SolrException.ErrorCode.BAD_REQUEST,
  //              "Can not date facet on a field which is not a DateField: " + f);
  //    }
  //    final DateField ft = (DateField) sf.getType();
  //    final String startS
  //        = required.getFieldParam(f,FacetParams.FACET_DATE_START);
  //    final Date start;
  //    try {
  //      start = ft.parseMath(NOW, startS);
  //    } catch (SolrException e) {
  //      throw new SolrException
  //          (SolrException.ErrorCode.BAD_REQUEST,
  //              "date facet 'start' is not a valid Date string: " + startS, e);
  //    }
  //    final String endS
  //        = required.getFieldParam(f,FacetParams.FACET_DATE_END);
  //    Date end; // not final, hardend may change this
  //    try {
  //      end = ft.parseMath(NOW, endS);
  //    } catch (SolrException e) {
  //      throw new SolrException
  //          (SolrException.ErrorCode.BAD_REQUEST,
  //              "date facet 'end' is not a valid Date string: " + endS, e);
  //    }
  //
  //    if (end.before(start)) {
  //      throw new SolrException
  //          (SolrException.ErrorCode.BAD_REQUEST,
  //              "date facet 'end' comes before 'start': "+endS+" < "+startS);
  //    }
  //
  //    final String gap = required.getFieldParam(f,FacetParams.FACET_DATE_GAP);
  //    final DateMathParser dmp = new DateMathParser(ft.UTC, Locale.US);
  //    dmp.setNow(NOW);
  //
  //    final int minCount = params.getFieldInt(f,FacetParams.FACET_MINCOUNT, 0);
  //
  //    String[] iStrs = params.getFieldParams(f,FacetParams.FACET_DATE_INCLUDE);
  //    // Legacy support for default of [lower,upper,edge] for date faceting
  //    // this is not handled by FacetRangeInclude.parseParam because
  //    // range faceting has differnet defaults
  //    final EnumSet<FacetRangeInclude> include = 
  //      (null == iStrs || 0 == iStrs.length ) ?
  //      EnumSet.of(FacetRangeInclude.LOWER, 
  //                 FacetRangeInclude.UPPER, 
  //                 FacetRangeInclude.EDGE)
  //      : FacetRangeInclude.parseParam(iStrs);
  //
  //    try {
  //      Date low = start;
  //      while (low.before(end)) {
  //        dmp.setNow(low);
  //        String label = ft.toExternal(low);
  //
  //        Date high = dmp.parseMath(gap);
  //        if (end.before(high)) {
  //          if (params.getFieldBool(f,FacetParams.FACET_DATE_HARD_END,false)) {
  //            high = end;
  //          } else {
  //            end = high;
  //          }
  //        }
  //        if (high.before(low)) {
  //          throw new SolrException
  //              (SolrException.ErrorCode.BAD_REQUEST,
  //                  "date facet infinite loop (is gap negative?)");
  //        }
  //        final boolean includeLower =
  //            (include.contains(FacetRangeInclude.LOWER) ||
  //                (include.contains(FacetRangeInclude.EDGE) && low.equals(start)));
  //        final boolean includeUpper =
  //            (include.contains(FacetRangeInclude.UPPER) ||
  //                (include.contains(FacetRangeInclude.EDGE) && high.equals(end)));
  //
  //        final int count = rangeCount(sf,low,high,includeLower,includeUpper);
  //        if (count >= minCount) {
  //          resInner.add(label, count);
  //        }
  //        low = high;
  //      }
  //    } catch (java.text.ParseException e) {
  //      throw new SolrException
  //          (SolrException.ErrorCode.BAD_REQUEST,
  //              "date facet 'gap' is not a valid Date Math string: " + gap, e);
  //    }
  //
  //    // explicitly return the gap and end so all the counts
  //    // (including before/after/between) are meaningful - even if mincount
  //    // has removed the neighboring ranges
  //    resInner.add("gap", gap);
  //    resInner.add("start", start);
  //    resInner.add("end", end);
  //
  //    final String[] othersP =
  //        params.getFieldParams(f,FacetParams.FACET_DATE_OTHER);
  //    if (null != othersP && 0 < othersP.length ) {
  //      final Set<FacetRangeOther> others = EnumSet.noneOf(FacetRangeOther.class);
  //
  //      for (final String o : othersP) {
  //        others.add(FacetRangeOther.get(o));
  //      }
  //
  //      // no matter what other values are listed, we don't do
  //      // anything if "none" is specified.
  //      if (! others.contains(FacetRangeOther.NONE) ) {
  //        boolean all = others.contains(FacetRangeOther.ALL);
  //
  //        if (all || others.contains(FacetRangeOther.BEFORE)) {
  //          // include upper bound if "outer" or if first gap doesn't already include it
  //          resInner.add(FacetRangeOther.BEFORE.toString(),
  //              rangeCount(sf,null,start,
  //                  false,
  //                  (include.contains(FacetRangeInclude.OUTER) ||
  //                      (! (include.contains(FacetRangeInclude.LOWER) ||
  //                          include.contains(FacetRangeInclude.EDGE))))));
  //        }
  //        if (all || others.contains(FacetRangeOther.AFTER)) {
  //          // include lower bound if "outer" or if last gap doesn't already include it
  //          resInner.add(FacetRangeOther.AFTER.toString(),
  //              rangeCount(sf,end,null,
  //                  (include.contains(FacetRangeInclude.OUTER) ||
  //                      (! (include.contains(FacetRangeInclude.UPPER) ||
  //                          include.contains(FacetRangeInclude.EDGE)))),
  //                  false));
  //        }
  //        if (all || others.contains(FacetRangeOther.BETWEEN)) {
  //          resInner.add(FacetRangeOther.BETWEEN.toString(),
  //              rangeCount(sf,start,end,
  //                  (include.contains(FacetRangeInclude.LOWER) ||
  //                      include.contains(FacetRangeInclude.EDGE)),
  //                  (include.contains(FacetRangeInclude.UPPER) ||
  //                      include.contains(FacetRangeInclude.EDGE))));
  //        }
  //      }
  //    }
  //  }
  
    
  //  /**
  //   * Returns a list of value constraints and the associated facet
  //   * counts for each facet numerical field, range, and interval
  //   * specified in the SolrParams
  //   *
  //   * @see FacetParams#FACET_RANGE
  //   */
  //
  //  public NamedList getFacetRangeCounts() throws IOException, ParseException {
  //    final NamedList resOuter = new SimpleOrderedMap();
  //    final String[] fields = params.getParams(FacetParams.FACET_RANGE);
  //
  //    if (null == fields || 0 == fields.length) return resOuter;
  //
  //    for (String f : fields) {
  //      getFacetRangeCounts(f, resOuter);
  //    }
  //
  //    return resOuter;
  //  }
  //
  //  void getFacetRangeCounts(String facetRange, NamedList resOuter)
  //      throws IOException, ParseException {
  //
  //    final IndexSchema schema = searcher.getSchema();
  //
  //    parseParams(FacetParams.FACET_RANGE, facetRange);
  //    String f = facetValue;
  //
  //    final SchemaField sf = schema.getField(f);
  //    final FieldType ft = sf.getType();
  //
  //    RangeEndpointCalculator calc = null;
  //
  //    if (ft instanceof TrieField) {
  //      final TrieField trie = (TrieField)ft;
  //
  //      switch (trie.getType()) {
  //        case FLOAT:
  //          calc = new FloatRangeEndpointCalculator(sf);
  //          break;
  //        case DOUBLE:
  //          calc = new DoubleRangeEndpointCalculator(sf);
  //          break;
  //        case INTEGER:
  //          calc = new IntegerRangeEndpointCalculator(sf);
  //          break;
  //        case LONG:
  //          calc = new LongRangeEndpointCalculator(sf);
  //          break;
  //        default:
  //          throw new SolrException
  //              (SolrException.ErrorCode.BAD_REQUEST,
  //                  "Unable to range facet on tried field of unexpected type:" + f);
  //      }
  //    } else if (ft instanceof DateField) {
  //      calc = new DateRangeEndpointCalculator(sf, NOW);
  //    } else if (ft instanceof SortableIntField) {
  //      calc = new IntegerRangeEndpointCalculator(sf);
  //    } else if (ft instanceof SortableLongField) {
  //      calc = new LongRangeEndpointCalculator(sf);
  //    } else if (ft instanceof SortableFloatField) {
  //      calc = new FloatRangeEndpointCalculator(sf);
  //    } else if (ft instanceof SortableDoubleField) {
  //      calc = new DoubleRangeEndpointCalculator(sf);
  //    } else {
  //      throw new SolrException
  //          (SolrException.ErrorCode.BAD_REQUEST,
  //              "Unable to range facet on field:" + sf);
  //    }
  //
  //    resOuter.add(key, getFacetRangeCounts(sf, calc));
  //  }
  //
  //  private <T extends Comparable<T>> NamedList getFacetRangeCounts
  //    (final SchemaField sf, 
  //     final RangeEndpointCalculator<T> calc) throws IOException {
  //    
  //    final String f = sf.getName();
  //    final NamedList res = new SimpleOrderedMap();
  //    final NamedList counts = new NamedList();
  //    res.add("counts", counts);
  //
  //    final T start = calc.getValue(required.getFieldParam(f,FacetParams.FACET_RANGE_START));
  //    // not final, hardend may change this
  //    T end = calc.getValue(required.getFieldParam(f,FacetParams.FACET_RANGE_END));
  //    if (end.compareTo(start) < 0) {
  //      throw new SolrException
  //        (SolrException.ErrorCode.BAD_REQUEST,
  //         "range facet 'end' comes before 'start': "+end+" < "+start);
  //    }
  //    
  //    final String gap = required.getFieldParam(f, FacetParams.FACET_RANGE_GAP);
  //    // explicitly return the gap.  compute this early so we are more 
  //    // likely to catch parse errors before attempting math
  //    res.add("gap", calc.getGap(gap));
  //    
  //    final int minCount = params.getFieldInt(f,FacetParams.FACET_MINCOUNT, 0);
  //    
  //    final EnumSet<FacetRangeInclude> include = FacetRangeInclude.parseParam
  //      (params.getFieldParams(f,FacetParams.FACET_RANGE_INCLUDE));
  //    
  //    T low = start;
  //    
  //    while (low.compareTo(end) < 0) {
  //      T high = calc.addGap(low, gap);
  //      if (end.compareTo(high) < 0) {
  //        if (params.getFieldBool(f,FacetParams.FACET_RANGE_HARD_END,false)) {
  //          high = end;
  //        } else {
  //          end = high;
  //        }
  //      }
  //      if (high.compareTo(low) < 0) {
  //        throw new SolrException
  //          (SolrException.ErrorCode.BAD_REQUEST,
  //           "range facet infinite loop (is gap negative? did the math overflow?)");
  //      }
  //      
  //      final boolean includeLower = 
  //        (include.contains(FacetRangeInclude.LOWER) ||
  //         (include.contains(FacetRangeInclude.EDGE) && 
  //          0 == low.compareTo(start)));
  //      final boolean includeUpper = 
  //        (include.contains(FacetRangeInclude.UPPER) ||
  //         (include.contains(FacetRangeInclude.EDGE) && 
  //          0 == high.compareTo(end)));
  //      
  //      final String lowS = calc.formatValue(low);
  //      final String highS = calc.formatValue(high);
  //
  //      final int count = rangeCount(sf, lowS, highS,
  //                                   includeLower,includeUpper);
  //      if (count >= minCount) {
  //        counts.add(lowS, count);
  //      }
  //      
  //      low = high;
  //    }
  //    
  //    // explicitly return the start and end so all the counts 
  //    // (including before/after/between) are meaningful - even if mincount
  //    // has removed the neighboring ranges
  //    res.add("start", start);
  //    res.add("end", end);
  //    
  //    final String[] othersP =
  //      params.getFieldParams(f,FacetParams.FACET_RANGE_OTHER);
  //    if (null != othersP && 0 < othersP.length ) {
  //      Set<FacetRangeOther> others = EnumSet.noneOf(FacetRangeOther.class);
  //      
  //      for (final String o : othersP) {
  //        others.add(FacetRangeOther.get(o));
  //      }
  //      
  //      // no matter what other values are listed, we don't do
  //      // anything if "none" is specified.
  //      if (! others.contains(FacetRangeOther.NONE) ) {
  //        
  //        boolean all = others.contains(FacetRangeOther.ALL);
  //        final String startS = calc.formatValue(start);
  //        final String endS = calc.formatValue(end);
  //
  //        if (all || others.contains(FacetRangeOther.BEFORE)) {
  //          // include upper bound if "outer" or if first gap doesn't already include it
  //          res.add(FacetRangeOther.BEFORE.toString(),
  //                  rangeCount(sf,null,startS,
  //                             false,
  //                             (include.contains(FacetRangeInclude.OUTER) ||
  //                              (! (include.contains(FacetRangeInclude.LOWER) ||
  //                                  include.contains(FacetRangeInclude.EDGE))))));
  //          
  //        }
  //        if (all || others.contains(FacetRangeOther.AFTER)) {
  //          // include lower bound if "outer" or if last gap doesn't already include it
  //          res.add(FacetRangeOther.AFTER.toString(),
  //                  rangeCount(sf,endS,null,
  //                             (include.contains(FacetRangeInclude.OUTER) ||
  //                              (! (include.contains(FacetRangeInclude.UPPER) ||
  //                                  include.contains(FacetRangeInclude.EDGE)))),  
  //                             false));
  //        }
  //        if (all || others.contains(FacetRangeOther.BETWEEN)) {
  //         res.add(FacetRangeOther.BETWEEN.toString(),
  //                 rangeCount(sf,startS,endS,
  //                            (include.contains(FacetRangeInclude.LOWER) ||
  //                             include.contains(FacetRangeInclude.EDGE)),
  //                            (include.contains(FacetRangeInclude.UPPER) ||
  //                             include.contains(FacetRangeInclude.EDGE))));
  //         
  //        }
  //      }
  //    }
  //    return res;
  //  }  
  //  
  //  /**
  //   * Macro for getting the numDocs of range over docs
  //   * @see SolrIndexSearcher#numDocs
  //   * @see TermRangeQuery
  //   */
  //  protected int rangeCount(SchemaField sf, String low, String high,
  //                           boolean iLow, boolean iHigh) throws IOException {
  //    Query rangeQ = sf.getType().getRangeQuery(null, sf, low, high, iLow, iHigh);
  //    return searcher.numDocs(rangeQ ,base);
  //  }
  
  //  /**
  //   * @deprecated Use rangeCount(SchemaField,String,String,boolean,boolean) which is more generalized
  //   */
  //  @Deprecated
  //  protected int rangeCount(SchemaField sf, Date low, Date high,
  //                           boolean iLow, boolean iHigh) throws IOException {
  //    Query rangeQ = ((DateField)(sf.getType())).getRangeQuery(null, sf,low,high,iLow,iHigh);
  //    return searcher.numDocs(rangeQ ,base);
  //  }
    
    /**
     * A simple key=>val pair whose natural order is such that 
     * <b>higher</b> vals come before lower vals.
     * In case of tie vals, then <b>lower</b> keys come before higher keys.
     */
    public static class CountPair<K extends Comparable<? super K>, V extends Comparable<? super V>>
      implements Comparable<CountPair<K,V>> {
  
      public CountPair(K k, V v) {
        key = k; val = v;
      }
      public K key;
      public V val;
      @Override
      public int hashCode() {
        return key.hashCode() ^ val.hashCode();
      }
      @Override
      public boolean equals(Object o) {
        return (o instanceof CountPair)
          && (0 == this.compareTo((CountPair<K,V>) o));
      }
      public int compareTo(CountPair<K,V> o) {
        int vc = o.val.compareTo(val);
        return (0 != vc ? vc : key.compareTo(o.key));
      }
    }
  
  
  //  /**
  //   * Perhaps someday instead of having a giant "instanceof" case 
  //   * statement to pick an impl, we can add a "RangeFacetable" marker 
  //   * interface to FieldTypes and they can return instances of these 
  //   * directly from some method -- but until then, keep this locked down 
  //   * and private.
  //   */
  //  private static abstract class RangeEndpointCalculator<T extends Comparable<T>> {
  //    protected final SchemaField field;
  //    public RangeEndpointCalculator(final SchemaField field) {
  //      this.field = field;
  //    }
  //
  //    /**
  //     * Formats a Range endpoint for use as a range label name in the response.
  //     * Default Impl just uses toString()
  //     */
  //    public String formatValue(final T val) {
  //      return val.toString();
  //    }
  //    /**
  //     * Parses a String param into an Range endpoint value throwing 
  //     * a useful exception if not possible
  //     */
  //    public final T getValue(final String rawval) {
  //      try {
  //        return parseVal(rawval);
  //      } catch (Exception e) {
  //        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
  //                                "Can't parse value "+rawval+" for field: " + 
  //                                field.getName(), e);
  //      }
  //    }
  //    /**
  //     * Parses a String param into an Range endpoint. 
  //     * Can throw a low level format exception as needed.
  //     */
  //    protected abstract T parseVal(final String rawval) 
  //      throws java.text.ParseException;
  //
  //    /** 
  //     * Parses a String param into a value that represents the gap and 
  //     * can be included in the response, throwing 
  //     * a useful exception if not possible.
  //     *
  //     * Note: uses Object as the return type instead of T for things like 
  //     * Date where gap is just a DateMathParser string 
  //     */
  //    public final Object getGap(final String gap) {
  //      try {
  //        return parseGap(gap);
  //      } catch (Exception e) {
  //        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
  //                                "Can't parse gap "+gap+" for field: " + 
  //                                field.getName(), e);
  //      }
  //    }
  //
  //    /**
  //     * Parses a String param into a value that represents the gap and 
  //     * can be included in the response. 
  //     * Can throw a low level format exception as needed.
  //     *
  //     * Default Impl calls parseVal
  //     */
  //    protected Object parseGap(final String rawval) 
  //      throws java.text.ParseException {
  //      return parseVal(rawval);
  //    }
  //
  //    /**
  //     * Adds the String gap param to a low Range endpoint value to determine 
  //     * the corrisponding high Range endpoint value, throwing 
  //     * a useful exception if not possible.
  //     */
  //    public final T addGap(T value, String gap) {
  //      try {
  //        return parseAndAddGap(value, gap);
  //      } catch (Exception e) {
  //        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
  //                                "Can't add gap "+gap+" to value " + value +
  //                                " for field: " + field.getName(), e);
  //      }
  //    }
  //    /**
  //     * Adds the String gap param to a low Range endpoint value to determine 
  //     * the corrisponding high Range endpoint value.
  //     * Can throw a low level format exception as needed.
  //     */
  //    protected abstract T parseAndAddGap(T value, String gap) 
  //      throws java.text.ParseException;
  //
  //  }
  
  //  private static class FloatRangeEndpointCalculator 
  //    extends RangeEndpointCalculator<Float> {
  //
  //    public FloatRangeEndpointCalculator(final SchemaField f) { super(f); }
  //    @Override
  //    protected Float parseVal(String rawval) {
  //      return Float.valueOf(rawval);
  //    }
  //    @Override
  //    public Float parseAndAddGap(Float value, String gap) {
  //      return new Float(value.floatValue() + Float.valueOf(gap).floatValue());
  //    }
  //  }
  //  private static class DoubleRangeEndpointCalculator 
  //    extends RangeEndpointCalculator<Double> {
  //
  //    public DoubleRangeEndpointCalculator(final SchemaField f) { super(f); }
  //    @Override
  //    protected Double parseVal(String rawval) {
  //      return Double.valueOf(rawval);
  //    }
  //    @Override
  //    public Double parseAndAddGap(Double value, String gap) {
  //      return new Double(value.doubleValue() + Double.valueOf(gap).doubleValue());
  //    }
  //  }
  //  private static class IntegerRangeEndpointCalculator 
  //    extends RangeEndpointCalculator<Integer> {
  //
  //    public IntegerRangeEndpointCalculator(final SchemaField f) { super(f); }
  //    @Override
  //    protected Integer parseVal(String rawval) {
  //      return Integer.valueOf(rawval);
  //    }
  //    @Override
  //    public Integer parseAndAddGap(Integer value, String gap) {
  //      return new Integer(value.intValue() + Integer.valueOf(gap).intValue());
  //    }
  //  }
  //  private static class LongRangeEndpointCalculator 
  //    extends RangeEndpointCalculator<Long> {
  //
  //    public LongRangeEndpointCalculator(final SchemaField f) { super(f); }
  //    @Override
  //    protected Long parseVal(String rawval) {
  //      return Long.valueOf(rawval);
  //    }
  //    @Override
  //    public Long parseAndAddGap(Long value, String gap) {
  //      return new Long(value.longValue() + Long.valueOf(gap).longValue());
  //    }
  //  }
  //  private static class DateRangeEndpointCalculator 
  //    extends RangeEndpointCalculator<Date> {
  //    private final Date now;
  //    public DateRangeEndpointCalculator(final SchemaField f, 
  //                                       final Date now) { 
  //      super(f); 
  //      this.now = now;
  //      if (! (field.getType() instanceof DateField) ) {
  //        throw new IllegalArgumentException
  //          ("SchemaField must use filed type extending DateField");
  //      }
  //    }
  //    @Override
  //    public String formatValue(Date val) {
  //      return ((DateField)field.getType()).toExternal(val);
  //    }
  //    @Override
  //    protected Date parseVal(String rawval) {
  //      return ((DateField)field.getType()).parseMath(now, rawval);
  //    }
  //    @Override
  //    protected Object parseGap(final String rawval) {
  //      return rawval;
  //    }
  //    @Override
  //    public Date parseAndAddGap(Date value, String gap) throws java.text.ParseException {
  //      final DateMathParser dmp = new DateMathParser(DateField.UTC, Locale.US);
  //      dmp.setNow(value);
  //      return dmp.parseMath(gap);
  //    }
  //  }
  //private SimpleFacets getOuterType() {
  //	return SimpleFacets.this;
  //}
    
  }
  
  