/**
* 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;
//}
}