Java tutorial
/** * 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.handler.admin; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.*; import org.apache.commons.lang.BooleanUtils; import org.apache.lucene.index.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Fieldable; import org.apache.lucene.index.FieldInfo.IndexOptions; import static org.apache.lucene.index.FieldInfo.IndexOptions.DOCS_ONLY; import static org.apache.lucene.index.FieldInfo.IndexOptions.DOCS_AND_FREQS; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermRangeQuery; import org.apache.lucene.search.TopDocs; import org.apache.lucene.store.Directory; import org.apache.lucene.util.PriorityQueue; import org.apache.solr.analysis.CharFilterFactory; import org.apache.solr.analysis.TokenFilterFactory; import org.apache.solr.analysis.TokenizerChain; import org.apache.solr.analysis.TokenizerFactory; import org.apache.solr.common.SolrException; import org.apache.solr.common.luke.FieldFlag; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.NamedList; import org.apache.solr.common.util.SimpleOrderedMap; import org.apache.solr.common.util.Base64; import org.apache.solr.handler.RequestHandlerBase; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.schema.FieldType; import org.apache.solr.schema.IndexSchema; import org.apache.solr.schema.SchemaField; import org.apache.solr.search.SolrIndexSearcher; /** * This handler exposes the internal lucene index. It is inspired by and * modeled on Luke, the Lucene Index Browser by Andrzej Bialecki. * http://www.getopt.org/luke/ * <p> * NOTE: the response format is still likely to change. It should be designed so * that it works nicely with an XSLT transformation. Until we have a nice * XSLT front end for /admin, the format is still open to change. * </p> * * For more documentation see: * http://wiki.apache.org/solr/LukeRequestHandler * * @version $Id$ * @since solr 1.2 */ public class LukeRequestHandler extends RequestHandlerBase { private static Logger log = LoggerFactory.getLogger(LukeRequestHandler.class); public static final String NUMTERMS = "numTerms"; public static final String DOC_ID = "docId"; public static final String ID = "id"; public static final String REPORT_DOC_COUNT = "reportDocCount"; public static final int DEFAULT_COUNT = 10; static final int HIST_ARRAY_SIZE = 33; @Override public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception { IndexSchema schema = req.getSchema(); SolrIndexSearcher searcher = req.getSearcher(); IndexReader reader = searcher.getReader(); SolrParams params = req.getParams(); int numTerms = params.getInt(NUMTERMS, DEFAULT_COUNT); // Always show the core lucene info Map<String, TopTermQueue> topTerms = new TreeMap<String, TopTermQueue>(); // If no doc is given, show all fields and top terms Set<String> fields = null; String fl = params.get(CommonParams.FL); if (fl != null) { fields = new TreeSet<String>(Arrays.asList(fl.split("[,\\s]+"))); } if ("schema".equals(params.get("show"))) { numTerms = 0; // Abort any statistics gathering. } rsp.add("index", getIndexInfo(reader, numTerms, topTerms, fields)); Integer docId = params.getInt(DOC_ID); if (docId == null && params.get(ID) != null) { // Look for something with a given solr ID SchemaField uniqueKey = schema.getUniqueKeyField(); String v = uniqueKey.getType().toInternal(params.get(ID)); Term t = new Term(uniqueKey.getName(), v); docId = searcher.getFirstMatch(t); if (docId < 0) { throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "Can't find document: " + params.get(ID)); } } // Read the document from the index if (docId != null) { Document doc = null; try { doc = reader.document(docId); } catch (Exception ex) { } if (doc == null) { throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "Can't find document: " + docId); } SimpleOrderedMap<Object> info = getDocumentFieldsInfo(doc, docId, reader, schema); SimpleOrderedMap<Object> docinfo = new SimpleOrderedMap<Object>(); docinfo.add("docId", docId); docinfo.add("lucene", info); docinfo.add("solr", doc); rsp.add("doc", docinfo); } else if ("schema".equals(params.get("show"))) { rsp.add("schema", getSchemaInfo(req.getSchema())); } else { boolean reportDocCount = true; String reportDoc = params.get(REPORT_DOC_COUNT, "false"); if ("1".equals(reportDoc)) reportDocCount = true; else if ("0".equals(reportDoc)) reportDocCount = false; else reportDocCount = BooleanUtils.toBoolean(reportDoc); rsp.add("fields", getIndexedFieldsInfo(searcher, fields, numTerms, topTerms, reportDocCount)); } // Add some generally helpful information NamedList<Object> info = new SimpleOrderedMap<Object>(); info.add("key", getFieldFlagsKey()); info.add("NOTE", "Document Frequency (df) is not updated when a document is marked for deletion. df values include deleted documents."); rsp.add("info", info); rsp.setHttpCaching(false); } /** * @return a string representing a Fieldable's flags. */ private static String getFieldFlags(Fieldable f) { IndexOptions opts = (f == null) ? null : f.getIndexOptions(); StringBuilder flags = new StringBuilder(); flags.append((f != null && f.isIndexed()) ? FieldFlag.INDEXED.getAbbreviation() : '-'); flags.append((f != null && f.isTokenized()) ? FieldFlag.TOKENIZED.getAbbreviation() : '-'); flags.append((f != null && f.isStored()) ? FieldFlag.STORED.getAbbreviation() : '-'); flags.append((false) ? FieldFlag.MULTI_VALUED.getAbbreviation() : '-'); // SchemaField Specific flags.append((f != null && f.isTermVectorStored()) ? FieldFlag.TERM_VECTOR_STORED.getAbbreviation() : '-'); flags.append((f != null && f.isStoreOffsetWithTermVector()) ? FieldFlag.TERM_VECTOR_OFFSET.getAbbreviation() : '-'); flags.append( (f != null && f.isStorePositionWithTermVector()) ? FieldFlag.TERM_VECTOR_POSITION.getAbbreviation() : '-'); flags.append((f != null && f.getOmitNorms()) ? FieldFlag.OMIT_NORMS.getAbbreviation() : '-'); flags.append((f != null && DOCS_ONLY == opts) ? FieldFlag.OMIT_TF.getAbbreviation() : '-'); flags.append((f != null && DOCS_AND_FREQS == opts) ? FieldFlag.OMIT_POSITIONS.getAbbreviation() : '-'); flags.append((f != null && f.isLazy()) ? FieldFlag.LAZY.getAbbreviation() : '-'); flags.append((f != null && f.isBinary()) ? FieldFlag.BINARY.getAbbreviation() : '-'); flags.append((false) ? FieldFlag.SORT_MISSING_FIRST.getAbbreviation() : '-'); // SchemaField Specific flags.append((false) ? FieldFlag.SORT_MISSING_LAST.getAbbreviation() : '-'); // SchemaField Specific return flags.toString(); } /** * @return a string representing a SchemaField's flags. */ private static String getFieldFlags(SchemaField f) { FieldType t = (f == null) ? null : f.getType(); // see: http://www.nabble.com/schema-field-properties-tf3437753.html#a9585549 boolean lazy = false; // "lazy" is purely a property of reading fields boolean binary = false; // Currently not possible StringBuilder flags = new StringBuilder(); flags.append((f != null && f.indexed()) ? FieldFlag.INDEXED.getAbbreviation() : '-'); flags.append((t != null && t.isTokenized()) ? FieldFlag.TOKENIZED.getAbbreviation() : '-'); flags.append((f != null && f.stored()) ? FieldFlag.STORED.getAbbreviation() : '-'); flags.append((f != null && f.multiValued()) ? FieldFlag.MULTI_VALUED.getAbbreviation() : '-'); flags.append((f != null && f.storeTermVector()) ? FieldFlag.TERM_VECTOR_STORED.getAbbreviation() : '-'); flags.append((f != null && f.storeTermOffsets()) ? FieldFlag.TERM_VECTOR_OFFSET.getAbbreviation() : '-'); flags.append( (f != null && f.storeTermPositions()) ? FieldFlag.TERM_VECTOR_POSITION.getAbbreviation() : '-'); flags.append((f != null && f.omitNorms()) ? FieldFlag.OMIT_NORMS.getAbbreviation() : '-'); flags.append((f != null && f.omitTermFreqAndPositions()) ? FieldFlag.OMIT_TF.getAbbreviation() : '-'); flags.append((f != null && f.omitPositions()) ? FieldFlag.OMIT_POSITIONS.getAbbreviation() : '-'); flags.append((lazy) ? FieldFlag.LAZY.getAbbreviation() : '-'); flags.append((binary) ? FieldFlag.BINARY.getAbbreviation() : '-'); flags.append((f != null && f.sortMissingFirst()) ? FieldFlag.SORT_MISSING_FIRST.getAbbreviation() : '-'); flags.append((f != null && f.sortMissingLast()) ? FieldFlag.SORT_MISSING_LAST.getAbbreviation() : '-'); return flags.toString(); } /** * @return a key to what each character means */ public static SimpleOrderedMap<String> getFieldFlagsKey() { SimpleOrderedMap<String> key = new SimpleOrderedMap<String>(); for (FieldFlag f : FieldFlag.values()) { key.add(String.valueOf(f.getAbbreviation()), f.getDisplay()); } return key; } private static SimpleOrderedMap<Object> getDocumentFieldsInfo(Document doc, int docId, IndexReader reader, IndexSchema schema) throws IOException { SimpleOrderedMap<Object> finfo = new SimpleOrderedMap<Object>(); for (Object o : doc.getFields()) { Fieldable fieldable = (Fieldable) o; SimpleOrderedMap<Object> f = new SimpleOrderedMap<Object>(); SchemaField sfield = schema.getFieldOrNull(fieldable.name()); FieldType ftype = (sfield == null) ? null : sfield.getType(); f.add("type", (ftype == null) ? null : ftype.getTypeName()); f.add("schema", getFieldFlags(sfield)); f.add("flags", getFieldFlags(fieldable)); Term t = new Term(fieldable.name(), ftype != null ? ftype.storedToIndexed(fieldable) : fieldable.stringValue()); f.add("value", (ftype == null) ? null : ftype.toExternal(fieldable)); // TODO: this really should be "stored" f.add("internal", fieldable.stringValue()); // may be a binary number byte[] arr = fieldable.getBinaryValue(); if (arr != null) { f.add("binary", Base64.byteArrayToBase64(arr, 0, arr.length)); } f.add("boost", fieldable.getBoost()); f.add("docFreq", t.text() == null ? 0 : reader.docFreq(t)); // this can be 0 for non-indexed fields // If we have a term vector, return that if (fieldable.isTermVectorStored()) { try { TermFreqVector v = reader.getTermFreqVector(docId, fieldable.name()); if (v != null) { SimpleOrderedMap<Integer> tfv = new SimpleOrderedMap<Integer>(); for (int i = 0; i < v.size(); i++) { tfv.add(v.getTerms()[i], v.getTermFrequencies()[i]); } f.add("termVector", tfv); } } catch (Exception ex) { log.warn("error writing term vector", ex); } } finfo.add(fieldable.name(), f); } return finfo; } @SuppressWarnings("unchecked") private static SimpleOrderedMap<Object> getIndexedFieldsInfo(final SolrIndexSearcher searcher, final Set<String> fields, final int numTerms, Map<String, TopTermQueue> ttinfo, boolean reportDocCount) throws Exception { IndexReader reader = searcher.getReader(); IndexSchema schema = searcher.getSchema(); Set<String> fieldNames = new TreeSet<String>(); for (FieldInfo fieldInfo : reader.getFieldInfos()) { fieldNames.add(fieldInfo.name); } // Walk the term enum and keep a priority queue for each map in our set SimpleOrderedMap<Object> finfo = new SimpleOrderedMap<Object>(); for (String fieldName : fieldNames) { if (fields != null && !fields.contains(fieldName) && !fields.contains("*")) { continue; // if a field is specified, only them } SimpleOrderedMap<Object> f = new SimpleOrderedMap<Object>(); SchemaField sfield = schema.getFieldOrNull(fieldName); FieldType ftype = (sfield == null) ? null : sfield.getType(); f.add("type", (ftype == null) ? null : ftype.getTypeName()); f.add("schema", getFieldFlags(sfield)); if (sfield != null && schema.isDynamicField(sfield.getName()) && schema.getDynamicPattern(sfield.getName()) != null) { f.add("dynamicBase", schema.getDynamicPattern(sfield.getName())); } // If numTerms==0, the call is just asking for a quick field list TopTermQueue topTerms = ttinfo.get(fieldName); if (ttinfo != null && sfield != null && sfield.indexed()) { if (numTerms > 0) { Document doc = null; int totalHits = 0; if (reportDocCount) { Query q = new TermRangeQuery(fieldName, null, null, false, false); TopDocs top = searcher.search(q, 1); if (top.totalHits > 0) { doc = searcher.doc(top.scoreDocs[0].doc); totalHits = top.totalHits; } } else if (topTerms != null) { TermDocs td = reader.termDocs(); td.seek(topTerms.getTopTerm()); if (td.next()) { totalHits = 1; doc = reader.document(td.doc()); } } if (totalHits > 0) { // Doc won't be null if totalHits > 0 // Find a document with this field try { Fieldable fld = doc.getFieldable(fieldName); if (fld != null) { f.add("index", getFieldFlags(fld)); } else { // it is a non-stored field... f.add("index", "(unstored field)"); } } catch (Exception ex) { log.warn("error reading field: " + fieldName); } } if (reportDocCount) { f.add("docs", totalHits); } } if (topTerms != null) { f.add("distinct", topTerms.distinctTerms); // Include top terms f.add("topTerms", topTerms.toNamedList(searcher.getSchema())); // Add a histogram f.add("histogram", topTerms.histogram.toNamedList()); } } // Add the field finfo.add(fieldName, f); } return finfo; } /** * Return info from the index */ private static SimpleOrderedMap<Object> getSchemaInfo(IndexSchema schema) { Map<String, List<String>> typeusemap = new TreeMap<String, List<String>>(); Map<String, Object> fields = new TreeMap<String, Object>(); SchemaField uniqueField = schema.getUniqueKeyField(); for (SchemaField f : schema.getFields().values()) { populateFieldInfo(schema, typeusemap, fields, uniqueField, f); } Map<String, Object> dynamicFields = new TreeMap<String, Object>(); for (SchemaField f : schema.getDynamicFieldPrototypes()) { populateFieldInfo(schema, typeusemap, dynamicFields, uniqueField, f); } SimpleOrderedMap<Object> types = new SimpleOrderedMap<Object>(); Map<String, FieldType> sortedTypes = new TreeMap<String, FieldType>(schema.getFieldTypes()); for (FieldType ft : sortedTypes.values()) { SimpleOrderedMap<Object> field = new SimpleOrderedMap<Object>(); field.add("fields", typeusemap.get(ft.getTypeName())); field.add("tokenized", ft.isTokenized()); field.add("className", ft.getClass().getName()); field.add("indexAnalyzer", getAnalyzerInfo(ft.getAnalyzer())); field.add("queryAnalyzer", getAnalyzerInfo(ft.getQueryAnalyzer())); types.add(ft.getTypeName(), field); } // Must go through this to maintain binary compatbility. Putting a TreeMap into a resp leads to casting errors SimpleOrderedMap<Object> finfo = new SimpleOrderedMap<Object>(); SimpleOrderedMap<Object> fieldsSimple = new SimpleOrderedMap<Object>(); for (Map.Entry<String, Object> ent : fields.entrySet()) { fieldsSimple.add(ent.getKey(), ent.getValue()); } finfo.add("fields", fieldsSimple); SimpleOrderedMap<Object> dynamicSimple = new SimpleOrderedMap<Object>(); for (Map.Entry<String, Object> ent : dynamicFields.entrySet()) { dynamicSimple.add(ent.getKey(), ent.getValue()); } finfo.add("dynamicFields", dynamicSimple); finfo.add("fields", fields); finfo.add("dynamicFields", dynamicFields); finfo.add("uniqueKeyField", null == uniqueField ? null : uniqueField.getName()); finfo.add("defaultSearchField", schema.getDefaultSearchFieldName()); finfo.add("types", types); return finfo; } private static SimpleOrderedMap<Object> getAnalyzerInfo(Analyzer analyzer) { SimpleOrderedMap<Object> aninfo = new SimpleOrderedMap<Object>(); aninfo.add("className", analyzer.getClass().getName()); if (analyzer instanceof TokenizerChain) { TokenizerChain tchain = (TokenizerChain) analyzer; CharFilterFactory[] cfiltfacs = tchain.getCharFilterFactories(); SimpleOrderedMap<Map<String, Object>> cfilters = new SimpleOrderedMap<Map<String, Object>>(); for (CharFilterFactory cfiltfac : cfiltfacs) { Map<String, Object> tok = new HashMap<String, Object>(); String className = cfiltfac.getClass().getName(); tok.put("className", className); tok.put("args", cfiltfac.getArgs()); cfilters.add(className.substring(className.lastIndexOf('.') + 1), tok); } if (cfilters.size() > 0) { aninfo.add("charFilters", cfilters); } SimpleOrderedMap<Object> tokenizer = new SimpleOrderedMap<Object>(); TokenizerFactory tfac = tchain.getTokenizerFactory(); tokenizer.add("className", tfac.getClass().getName()); tokenizer.add("args", tfac.getArgs()); aninfo.add("tokenizer", tokenizer); TokenFilterFactory[] filtfacs = tchain.getTokenFilterFactories(); SimpleOrderedMap<Map<String, Object>> filters = new SimpleOrderedMap<Map<String, Object>>(); for (TokenFilterFactory filtfac : filtfacs) { Map<String, Object> tok = new HashMap<String, Object>(); String className = filtfac.getClass().getName(); tok.put("className", className); tok.put("args", filtfac.getArgs()); filters.add(className.substring(className.lastIndexOf('.') + 1), tok); } if (filters.size() > 0) { aninfo.add("filters", filters); } } return aninfo; } private static void populateFieldInfo(IndexSchema schema, Map<String, List<String>> typeusemap, Map<String, Object> fields, SchemaField uniqueField, SchemaField f) { FieldType ft = f.getType(); SimpleOrderedMap<Object> field = new SimpleOrderedMap<Object>(); field.add("type", ft.getTypeName()); field.add("flags", getFieldFlags(f)); if (f.isRequired()) { field.add("required", f.isRequired()); } if (f.getDefaultValue() != null) { field.add("default", f.getDefaultValue()); } if (f == uniqueField) { field.add("uniqueKey", true); } if (ft.getAnalyzer().getPositionIncrementGap(f.getName()) != 0) { field.add("positionIncrementGap", ft.getAnalyzer().getPositionIncrementGap(f.getName())); } field.add("copyDests", schema.getCopyFields(f.getName())); field.add("copySources", schema.getCopySources(f.getName())); fields.put(f.getName(), field); List<String> v = typeusemap.get(ft.getTypeName()); if (v == null) { v = new ArrayList<String>(); } v.add(f.getName()); typeusemap.put(ft.getTypeName(), v); } public static SimpleOrderedMap<Object> getIndexInfo(IndexReader reader, boolean countTerms) throws IOException { return getIndexInfo(reader, countTerms ? 1 : 0, null, null); } public static SimpleOrderedMap<Object> getIndexInfo(IndexReader reader, int numTerms, Map<String, TopTermQueue> topTerms, Set<String> fields) throws IOException { Directory dir = reader.directory(); SimpleOrderedMap<Object> indexInfo = new SimpleOrderedMap<Object>(); indexInfo.add("numDocs", reader.numDocs()); indexInfo.add("maxDoc", reader.maxDoc()); if (numTerms > 0) { TermEnum terms = null; try { terms = reader.terms(); int totalUniqueTerms = 0; String lastField = ""; int[] buckets = new int[HIST_ARRAY_SIZE]; TopTermQueue tiq = null; String field = ""; while (terms.next()) { totalUniqueTerms++; field = terms.term().field(); if (!field.equals(lastField)) { // We're switching fields, wrap up the one we've just accumulated // stats for and get ready for the next one. if (tiq != null) { // Histogram from last time through is the one to use tiq.histogram.add(buckets); topTerms.put(lastField, tiq); tiq = null; } lastField = field; // Skip fields not in fl list (if specified) if (fields != null && !fields.contains(field) && !fields.contains("*")) { continue; } tiq = new TopTermQueue(numTerms + 1); for (int idx = 0; idx < buckets.length; ++idx) { buckets[idx] = 0; } } // When we're on a field we don't care about, tiq is null if (tiq == null) { continue; } int freq = terms.docFreq(); int slot = 32 - Integer.numberOfLeadingZeros(Math.max(0, freq - 1)); buckets[slot] = buckets[slot] + 1; // Compute distinct terms for every field tiq.distinctTerms++; if (terms.docFreq() > tiq.minFreq) { tiq.add(new TopTermQueue.TermInfo(terms.term(), terms.docFreq())); if (tiq.size() > numTerms) { // if tiq full tiq.pop(); // remove lowest in tiq tiq.minFreq = ((TopTermQueue.TermInfo) tiq.top()).docFreq; // reset minFreq } } } // We fall off the end and have to add the last field! if (tiq != null) { tiq.histogram.add(buckets); topTerms.put(field, tiq); } indexInfo.add("numTerms", totalUniqueTerms); } finally { if (terms != null) terms.close(); } } indexInfo.add("version", reader.getVersion()); // TODO? Is this different then: IndexReader.getCurrentVersion( dir )? indexInfo.add("segmentCount", reader.getSequentialSubReaders().length); indexInfo.add("current", reader.isCurrent()); indexInfo.add("hasDeletions", reader.hasDeletions()); indexInfo.add("directory", dir); indexInfo.add("lastModified", new Date(IndexReader.lastModified(dir))); return indexInfo; } //////////////////////// SolrInfoMBeans methods ////////////////////// @Override public String getDescription() { return "Lucene Index Browser. Inspired and modeled after Luke: http://www.getopt.org/luke/"; } @Override public String getVersion() { return "$Revision$"; } @Override public String getSourceId() { return "$Id$"; } @Override public String getSource() { return "$URL$"; } @Override public URL[] getDocs() { try { return new URL[] { new URL("http://wiki.apache.org/solr/LukeRequestHandler") }; } catch (MalformedURLException ex) { return null; } } /////////////////////////////////////////////////////////////////////////////////////// static class TermHistogram { int _maxBucket = -1; int _buckets[] = new int[HIST_ARRAY_SIZE]; public void add(int[] buckets) { for (int idx = 0; idx < buckets.length; ++idx) { if (buckets[idx] != 0) _maxBucket = idx; } for (int idx = 0; idx <= _maxBucket; ++idx) { _buckets[idx] = buckets[idx]; } } // TODO? should this be a list or a map? public NamedList<Integer> toNamedList() { NamedList<Integer> nl = new NamedList<Integer>(); for (int bucket = 0; bucket <= _maxBucket; bucket++) { nl.add("" + (1 << bucket), _buckets[bucket]); } return nl; } } /** * Private internal class that counts up frequent terms */ private static class TopTermQueue extends PriorityQueue { static class TermInfo { TermInfo(Term t, int df) { term = t; docFreq = df; } int docFreq; Term term; } public int minFreq = 0; public int distinctTerms = 0; public TermHistogram histogram; TopTermQueue(int size) { initialize(size); histogram = new TermHistogram(); } @Override protected final boolean lessThan(Object a, Object b) { TermInfo termInfoA = (TermInfo) a; TermInfo termInfoB = (TermInfo) b; return termInfoA.docFreq < termInfoB.docFreq; } /** * This is a destructive call... the queue is empty at the end */ public NamedList<Integer> toNamedList(IndexSchema schema) { // reverse the list.. List<TermInfo> aslist = new LinkedList<TermInfo>(); while (size() > 0) { aslist.add(0, (TermInfo) pop()); } NamedList<Integer> list = new NamedList<Integer>(); for (TermInfo i : aslist) { String txt = i.term.text(); SchemaField ft = schema.getFieldOrNull(i.term.field()); if (ft != null) { txt = ft.getType().indexedToReadable(txt); } list.add(txt, i.docFreq); } return list; } public Term getTopTerm() { return ((TermInfo) top()).term; } } }