/* * 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.lucene.search.suggest; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Comparator; import java.util.List; import java.util.Set; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.spell.Dictionary; import org.apache.lucene.store.DataInput; import org.apache.lucene.store.DataOutput; import org.apache.lucene.store.InputStreamDataInput; import org.apache.lucene.store.OutputStreamDataOutput; import org.apache.lucene.util.Accountable; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.PriorityQueue; /** * Simple Lookup interface for {@link CharSequence} suggestions. * @lucene.experimental */ public abstract class Lookup implements Accountable { /** * Result of a lookup. * @lucene.experimental */ public static final class LookupResult implements Comparable<LookupResult> { /** the key's text */ public final CharSequence key; /** Expert: custom Object to hold the result of a * highlighted suggestion. */ public final Object highlightKey; /** the key's weight */ public final long value; /** the key's payload (null if not present) */ public final BytesRef payload; /** the key's contexts (null if not present) */ public final Set<BytesRef> contexts; /** * Create a new result from a key+weight pair. */ public LookupResult(CharSequence key, long value) { this(key, null, value, null, null); } /** * Create a new result from a key+weight+payload triple. */ public LookupResult(CharSequence key, long value, BytesRef payload) { this(key, null, value, payload, null); } /** * Create a new result from a key+highlightKey+weight+payload triple. */ public LookupResult(CharSequence key, Object highlightKey, long value, BytesRef payload) { this(key, highlightKey, value, payload, null); } /** * Create a new result from a key+weight+payload+contexts triple. */ public LookupResult(CharSequence key, long value, BytesRef payload, Set<BytesRef> contexts) { this(key, null, value, payload, contexts); } /** * Create a new result from a key+weight+contexts triple. */ public LookupResult(CharSequence key, long value, Set<BytesRef> contexts) { this(key, null, value, null, contexts); } /** * Create a new result from a key+highlightKey+weight+payload+contexts triple. */ public LookupResult(CharSequence key, Object highlightKey, long value, BytesRef payload, Set<BytesRef> contexts) { this.key = key; this.highlightKey = highlightKey; this.value = value; this.payload = payload; this.contexts = contexts; } @Override public String toString() { return key + "/" + value; } /** Compare alphabetically. */ @Override public int compareTo(LookupResult o) { return CHARSEQUENCE_COMPARATOR.compare(key, o.key); } } /** * A simple char-by-char comparator for {@link CharSequence} */ public static final Comparator<CharSequence> CHARSEQUENCE_COMPARATOR = new CharSequenceComparator(); private static class CharSequenceComparator implements Comparator<CharSequence> { @Override public int compare(CharSequence o1, CharSequence o2) { final int l1 = o1.length(); final int l2 = o2.length(); final int aStop = Math.min(l1, l2); for (int i = 0; i < aStop; i++) { int diff = o1.charAt(i) - o2.charAt(i); if (diff != 0) { return diff; } } // One is a prefix of the other, or, they are equal: return l1 - l2; } } /** * A {@link PriorityQueue} collecting a fixed size of high priority {@link LookupResult} */ public static final class LookupPriorityQueue extends PriorityQueue<LookupResult> { // TODO: should we move this out of the interface into a utility class? /** * Creates a new priority queue of the specified size. */ public LookupPriorityQueue(int size) { super(size); } @Override protected boolean lessThan(LookupResult a, LookupResult b) { return a.value < b.value; } /** * Returns the top N results in descending order. * @return the top N results in descending order. */ public LookupResult[] getResults() { int size = size(); LookupResult[] res = new LookupResult[size]; for (int i = size - 1; i >= 0; i--) { res[i] = pop(); } return res; } } /** * Sole constructor. (For invocation by subclass * constructors, typically implicit.) */ public Lookup() {} /** Build lookup from a dictionary. Some implementations may require sorted * or unsorted keys from the dictionary's iterator - use * {@link SortedInputIterator} or * {@link UnsortedInputIterator} in such case. */ public void build(Dictionary dict) throws IOException { build(dict.getEntryIterator()); } /** * Calls {@link #load(DataInput)} after converting * {@link InputStream} to {@link DataInput} */ public boolean load(InputStream input) throws IOException { DataInput dataIn = new InputStreamDataInput(input); try { return load(dataIn); } finally { IOUtils.close(input); } } /** * Calls {@link #store(DataOutput)} after converting * {@link OutputStream} to {@link DataOutput} */ public boolean store(OutputStream output) throws IOException { DataOutput dataOut = new OutputStreamDataOutput(output); try { return store(dataOut); } finally { IOUtils.close(output); } } /** * Get the number of entries the lookup was built with * @return total number of suggester entries */ public abstract long getCount() throws IOException; /** * Builds up a new internal {@link Lookup} representation based on the given {@link InputIterator}. * The implementation might re-sort the data internally. */ public abstract void build(InputIterator inputIterator) throws IOException; /** * Look up a key and return possible completion for this key. * @param key lookup key. Depending on the implementation this may be * a prefix, misspelling, or even infix. * @param onlyMorePopular return only more popular results * @param num maximum number of results to return * @return a list of possible completions, with their relative weight (e.g. popularity) */ public List<LookupResult> lookup(CharSequence key, boolean onlyMorePopular, int num) throws IOException { return lookup(key, null, onlyMorePopular, num); } /** * Look up a key and return possible completion for this key. * @param key lookup key. Depending on the implementation this may be * a prefix, misspelling, or even infix. * @param contexts contexts to filter the lookup by, or null if all contexts are allowed; if the suggestion contains any of the contexts, it's a match * @param onlyMorePopular return only more popular results * @param num maximum number of results to return * @return a list of possible completions, with their relative weight (e.g. popularity) */ public abstract List<LookupResult> lookup(CharSequence key, Set<BytesRef> contexts, boolean onlyMorePopular, int num) throws IOException; /** * Look up a key and return possible completion for this key. * This needs to be overridden by all implementing classes as the default implementation just returns null * * @param key the lookup key * @param contextFilerQuery A query for further filtering the result of the key lookup * @param num maximum number of results to return * @param allTermsRequired true is all terms are required * @param doHighlight set to true if key should be highlighted * @return a list of suggestions/completions. The default implementation returns null, meaning each @Lookup implementation should override this and provide their own implementation * @throws IOException when IO exception occurs */ public List<LookupResult> lookup(CharSequence key, BooleanQuery contextFilerQuery, int num, boolean allTermsRequired, boolean doHighlight) throws IOException{ return null; } /** * Persist the constructed lookup data to a directory. Optional operation. * @param output {@link DataOutput} to write the data to. * @return true if successful, false if unsuccessful or not supported. * @throws IOException when fatal IO error occurs. */ public abstract boolean store(DataOutput output) throws IOException; /** * Discard current lookup data and load it from a previously saved copy. * Optional operation. * @param input the {@link DataInput} to load the lookup data. * @return true if completed successfully, false if unsuccessful or not supported. * @throws IOException when fatal IO error occurs. */ public abstract boolean load(DataInput input) throws IOException; }