--- /dev/null
+package org.apache.lucene.search.suggest.jaspell;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.search.spell.SortedIterator;
+import org.apache.lucene.search.spell.TermFreqIterator;
+import org.apache.lucene.search.suggest.Lookup;
+import org.apache.lucene.search.suggest.UnsortedTermFreqIteratorWrapper;
+import org.apache.lucene.search.suggest.jaspell.JaspellTernarySearchTrie.TSTNode;
+
+public class JaspellLookup extends Lookup {
+ JaspellTernarySearchTrie trie = new JaspellTernarySearchTrie();
+ private boolean usePrefix = true;
+ private int editDistance = 2;
+
+ @Override
+ public void build(TermFreqIterator tfit) throws IOException {
+ if (tfit instanceof SortedIterator) {
+ // make sure it's unsorted
+ tfit = new UnsortedTermFreqIteratorWrapper(tfit);
+ }
+ trie = new JaspellTernarySearchTrie();
+ trie.setMatchAlmostDiff(editDistance);
+ while (tfit.hasNext()) {
+ String key = tfit.next();
+ float freq = tfit.freq();
+ if (key.length() == 0) {
+ continue;
+ }
+ trie.put(key, new Float(freq));
+ }
+ }
+
+ @Override
+ public boolean add(String key, Object value) {
+ trie.put(key, value);
+ // XXX
+ return false;
+ }
+
+ @Override
+ public Object get(String key) {
+ return trie.get(key);
+ }
+
+ @Override
+ public List<LookupResult> lookup(String key, boolean onlyMorePopular, int num) {
+ List<LookupResult> res = new ArrayList<LookupResult>();
+ List<String> list;
+ int count = onlyMorePopular ? num * 2 : num;
+ if (usePrefix) {
+ list = trie.matchPrefix(key, count);
+ } else {
+ list = trie.matchAlmost(key, count);
+ }
+ if (list == null || list.size() == 0) {
+ return res;
+
+ }
+ int maxCnt = Math.min(num, list.size());
+ if (onlyMorePopular) {
+ LookupPriorityQueue queue = new LookupPriorityQueue(num);
+ for (String s : list) {
+ float freq = (Float)trie.get(s);
+ queue.insertWithOverflow(new LookupResult(s, freq));
+ }
+ for (LookupResult lr : queue.getResults()) {
+ res.add(lr);
+ }
+ } else {
+ for (int i = 0; i < maxCnt; i++) {
+ String s = list.get(i);
+ float freq = (Float)trie.get(s);
+ res.add(new LookupResult(s, freq));
+ }
+ }
+ return res;
+ }
+
+ public static final String FILENAME = "jaspell.dat";
+ private static final byte LO_KID = 0x01;
+ private static final byte EQ_KID = 0x02;
+ private static final byte HI_KID = 0x04;
+ private static final byte HAS_VALUE = 0x08;
+
+
+ @Override
+ public boolean load(File storeDir) throws IOException {
+ File data = new File(storeDir, FILENAME);
+ if (!data.exists() || !data.canRead()) {
+ return false;
+ }
+ DataInputStream in = new DataInputStream(new FileInputStream(data));
+ TSTNode root = trie.new TSTNode('\0', null);
+ try {
+ readRecursively(in, root);
+ trie.setRoot(root);
+ } finally {
+ in.close();
+ }
+ return true;
+ }
+
+ private void readRecursively(DataInputStream in, TSTNode node) throws IOException {
+ node.splitchar = in.readChar();
+ byte mask = in.readByte();
+ if ((mask & HAS_VALUE) != 0) {
+ node.data = new Float(in.readFloat());
+ }
+ if ((mask & LO_KID) != 0) {
+ TSTNode kid = trie.new TSTNode('\0', node);
+ node.relatives[TSTNode.LOKID] = kid;
+ readRecursively(in, kid);
+ }
+ if ((mask & EQ_KID) != 0) {
+ TSTNode kid = trie.new TSTNode('\0', node);
+ node.relatives[TSTNode.EQKID] = kid;
+ readRecursively(in, kid);
+ }
+ if ((mask & HI_KID) != 0) {
+ TSTNode kid = trie.new TSTNode('\0', node);
+ node.relatives[TSTNode.HIKID] = kid;
+ readRecursively(in, kid);
+ }
+ }
+
+ @Override
+ public boolean store(File storeDir) throws IOException {
+ if (!storeDir.exists() || !storeDir.isDirectory() || !storeDir.canWrite()) {
+ return false;
+ }
+ TSTNode root = trie.getRoot();
+ if (root == null) { // empty tree
+ return false;
+ }
+ File data = new File(storeDir, FILENAME);
+ DataOutputStream out = new DataOutputStream(new FileOutputStream(data));
+ try {
+ writeRecursively(out, root);
+ out.flush();
+ } finally {
+ out.close();
+ }
+ return true;
+ }
+
+ private void writeRecursively(DataOutputStream out, TSTNode node) throws IOException {
+ if (node == null) {
+ return;
+ }
+ out.writeChar(node.splitchar);
+ byte mask = 0;
+ if (node.relatives[TSTNode.LOKID] != null) mask |= LO_KID;
+ if (node.relatives[TSTNode.EQKID] != null) mask |= EQ_KID;
+ if (node.relatives[TSTNode.HIKID] != null) mask |= HI_KID;
+ if (node.data != null) mask |= HAS_VALUE;
+ out.writeByte(mask);
+ if (node.data != null) {
+ out.writeFloat((Float)node.data);
+ }
+ writeRecursively(out, node.relatives[TSTNode.LOKID]);
+ writeRecursively(out, node.relatives[TSTNode.EQKID]);
+ writeRecursively(out, node.relatives[TSTNode.HIKID]);
+ }
+}