import java.io.IOException;\r
import java.io.RandomAccessFile;\r
import java.util.ArrayList;\r
-import java.util.Comparator;\r
import java.util.List;\r
import java.util.concurrent.atomic.AtomicBoolean;\r
\r
IndexEntry.RAF_FACTORY);\r
\r
final List<Entry> entries;\r
- final Language[] languages = new Language[2];\r
+ final LanguageData[] languageDatas = new LanguageData[2];\r
\r
- public Dictionary(final String lang0, final String lang1) {\r
- languages[0] = new Language(lang0, Entry.LANG1);\r
- languages[1] = new Language(lang1, Entry.LANG2);\r
+ public Dictionary(final Language language0, final Language language1) {\r
+ languageDatas[0] = new LanguageData(language0, Entry.LANG1);\r
+ languageDatas[1] = new LanguageData(language1, Entry.LANG2);\r
entries = new ArrayList<Entry>();\r
}\r
\r
public Dictionary(final RandomAccessFile raf) throws IOException {\r
entries = CachingList.create(FileList.create(raf, ENTRY_SERIALIZER, raf\r
.getFilePointer()), 10000);\r
- languages[0] = new Language(raf, Entry.LANG1);\r
- languages[1] = new Language(raf, Entry.LANG2);\r
+ languageDatas[0] = new LanguageData(raf, Entry.LANG1);\r
+ languageDatas[1] = new LanguageData(raf, Entry.LANG2);\r
}\r
\r
public void write(RandomAccessFile raf) throws IOException {\r
FileList.write(raf, entries, ENTRY_SERIALIZER);\r
- languages[0].write(raf);\r
- languages[1].write(raf);\r
+ languageDatas[0].write(raf);\r
+ languageDatas[1].write(raf);\r
}\r
\r
- final class Language implements RAFSerializable<Language> {\r
+ final class LanguageData implements RAFSerializable<LanguageData> {\r
+ final Language language;\r
final byte lang;\r
- final String symbol;\r
final List<Row> rows;\r
final List<IndexEntry> sortedIndex;\r
- final Comparator<String> comparator = EntryFactory.entryFactory\r
- .getEntryComparator();\r
\r
- Language(final String symbol, final byte lang) {\r
+ LanguageData(final Language language, final byte lang) {\r
+ this.language = language;\r
this.lang = lang;\r
- this.symbol = symbol;\r
rows = new ArrayList<Row>();\r
sortedIndex = new ArrayList<IndexEntry>();\r
}\r
\r
- Language(final RandomAccessFile raf, final byte lang) throws IOException {\r
+ LanguageData(final RandomAccessFile raf, final byte lang) throws IOException {\r
+ language = Language.lookup(raf.readUTF());\r
+ if (language == null) {\r
+ throw new RuntimeException("Unknown language.");\r
+ }\r
this.lang = lang;\r
- symbol = raf.readUTF();\r
rows = CachingList.create(UniformFileList.create(raf, ROW_SERIALIZER, raf\r
.getFilePointer()), 10000);\r
sortedIndex = CachingList.create(FileList.create(raf,\r
}\r
\r
public void write(final RandomAccessFile raf) throws IOException {\r
- raf.writeUTF(symbol);\r
+ raf.writeUTF(language.symbol);\r
UniformFileList.write(raf, rows, ROW_SERIALIZER, 4);\r
FileList.write(raf, sortedIndex, INDEX_ENTRY_SERIALIZER);\r
}\r
}\r
final IndexEntry midEntry = sortedIndex.get(mid);\r
\r
- final int comp = comparator.compare(word, midEntry.word.toLowerCase());\r
+ final int comp = language.tokenComparator.compare(word, midEntry.word.toLowerCase());\r
if (comp == 0) {\r
int result = mid;\r
- while (result > 0 && comparator.compare(word, sortedIndex.get(result - 1).word.toLowerCase()) == 0) {\r
+ while (result > 0 && language.tokenComparator.compare(word, sortedIndex.get(result - 1).word.toLowerCase()) == 0) {\r
--result;\r
if (interrupted.get()) {\r
return result;\r
start = mid + 1;\r
}\r
}\r
- return start;\r
+ return Math.min(sortedIndex.size() - 1, start);\r
+ }\r
+\r
+ public int getTokenRow(final int rowIndex) {\r
+ int r = rowIndex;\r
+ Row row;\r
+ while (true) {\r
+ row = rows.get(r); \r
+ if (row.isToken() || row.tokenRow != -1) {\r
+ break;\r
+ }\r
+ --r;\r
+ }\r
+ final int result = row.isToken() ? r : row.tokenRow;\r
+ for (; r <= rowIndex; ++r) {\r
+ rows.get(r).tokenRow = result;\r
+ }\r
+ assert rows.get(result).isToken();\r
+ return result;\r
}\r
}\r
\r
public static final class Row implements RAFSerializable<Row> {\r
final int index;\r
\r
+ int tokenRow = -1;\r
+\r
public Row(final int index) {\r
this.index = index;\r
}\r
import java.util.concurrent.atomic.AtomicBoolean;
import android.app.ListActivity;
+import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.ContextMenu;
+import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TableRow;
import android.widget.TextView;
import android.widget.AdapterView.OnItemLongClickListener;
+import android.widget.AdapterView.OnItemSelectedListener;
import com.hughes.android.dictionary.Dictionary.IndexEntry;
-import com.hughes.android.dictionary.Dictionary.Language;
+import com.hughes.android.dictionary.Dictionary.LanguageData;
import com.hughes.android.dictionary.Dictionary.Row;
public class DictionaryActivity extends ListActivity {
private RandomAccessFile dictRaf = null;
private Dictionary dictionary = null;
- private Language activeLanguge = null;
+ private LanguageData activeLangaugeData = null;
private File wordList = new File("/sdcard/wordList.txt");
private SearchOperation searchOperation = null;
// private List<Entry> entries = Collections.emptyList();
private DictionaryListAdapter dictionaryListAdapter = new DictionaryListAdapter();
- private int selectedRow = -1;
+ private int selectedRowIndex = -1;
+ private int selectedTokenRowIndex = -1;
/** Called when the activity is first created. */
@Override
try {
dictRaf = new RandomAccessFile("/sdcard/de-en.dict", "r");
dictionary = new Dictionary(dictRaf);
- activeLanguge = dictionary.languages[Entry.LANG1];
+ activeLangaugeData = dictionary.languageDatas[Entry.LANG1];
} catch (Exception e) {
throw new RuntimeException(e);
}
setListAdapter(dictionaryListAdapter);
onSearchTextChange("");
+
+ // Language button.
final Button langButton = (Button) findViewById(R.id.LangButton);
langButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
}});
updateLangButton();
+ final Button upButton = (Button) findViewById(R.id.UpButton);
+ upButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ final int destRowIndex;
+ final Row tokenRow = activeLangaugeData.rows.get(selectedTokenRowIndex);
+ assert tokenRow.isToken();
+ final int prevTokenIndex = tokenRow.getIndex() - 1;
+ if (selectedRowIndex == selectedTokenRowIndex && selectedRowIndex > 0) {
+ destRowIndex = activeLangaugeData.sortedIndex.get(prevTokenIndex).startRow;
+ } else {
+ destRowIndex = selectedTokenRowIndex;
+ }
+ jumpToRow(destRowIndex);
+ }});
+ final Button downButton = (Button) findViewById(R.id.DownButton);
+ downButton.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ final Row tokenRow = activeLangaugeData.rows.get(selectedTokenRowIndex);
+ assert tokenRow.isToken();
+ final int nextTokenIndex = tokenRow.getIndex() + 1;
+ final int destRowIndex;
+ if (nextTokenIndex < activeLangaugeData.sortedIndex.size()) {
+ destRowIndex = activeLangaugeData.sortedIndex.get(nextTokenIndex).startRow;
+ } else {
+ destRowIndex = activeLangaugeData.rows.size() - 1;
+ }
+ jumpToRow(destRowIndex);
+ }});
+
+ // Context menu.
registerForContextMenu(getListView());
+
+ getListView().setOnItemSelectedListener(new OnItemSelectedListener() {
+ public void onItemSelected(AdapterView<?> arg0, View arg1, int rowIndex,
+ long arg3) {
+ Log.d("THAD", "onItemSelected: " + rowIndex);
+ selectedRowIndex = rowIndex;
+ selectedTokenRowIndex = activeLangaugeData.getTokenRow(rowIndex);
+ updateSearchText();
+ }
+
+ public void onNothingSelected(AdapterView<?> arg0) {
+ }});
+
+
getListView().setOnItemLongClickListener((new OnItemLongClickListener() {
- public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int arg2,
+ public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int rowIndex,
long arg3) {
- selectedRow = arg2;
+ selectedRowIndex = rowIndex;
return false;
}
}));
}
public String getSelectedRowText() {
- return activeLanguge.rowToString(activeLanguge.rows.get(selectedRow));
+ return activeLangaugeData.rowToString(activeLangaugeData.rows.get(selectedRowIndex));
}
private MenuItem switchLanguageMenuItem = null;
@Override
public boolean onPrepareOptionsMenu(final Menu menu) {
- switchLanguageMenuItem.setTitle(String.format("Switch to %s", dictionary.languages[Entry.otherLang(activeLanguge.lang)].symbol));
+ switchLanguageMenuItem.setTitle(String.format("Switch to %s", dictionary.languageDatas[Entry.otherLang(activeLangaugeData.lang)].language.symbol));
return super.onPrepareOptionsMenu(menu);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
- if (selectedRow == -1) {
+ if (selectedRowIndex == -1) {
return;
}
final MenuItem addToWordlist = menu.add("Add to wordlist.");
}
void switchLanguage() {
- activeLanguge = dictionary.languages[(activeLanguge == dictionary.languages[0]) ? 1 : 0];
+ activeLangaugeData = dictionary.languageDatas[(activeLangaugeData == dictionary.languageDatas[0]) ? 1 : 0];
updateLangButton();
dictionaryListAdapter.notifyDataSetChanged();
final EditText searchText = (EditText) findViewById(R.id.SearchText);
void updateLangButton() {
final Button langButton = (Button) findViewById(R.id.LangButton);
- langButton.setText(dictionary.languages[activeLanguge.lang].symbol.toUpperCase());
+ langButton.setText(activeLangaugeData.language.symbol);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (event.getUnicodeChar() != 0) {
+ final EditText searchText = (EditText) findViewById(R.id.SearchText);
+ if (!searchText.hasFocus()) {
+ searchText.setText("" + (char)event.getUnicodeChar());
+ onSearchTextChange(searchText.getText().toString());
+ searchText.requestFocus();
+ }
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
}
@Override
protected void onListItemClick(ListView l, View v, int row, long id) {
- selectedRow = row;
+ selectedRowIndex = row;
Log.d("THAD", "Clicked: " + getSelectedRowText());
openContextMenu(getListView());
}
searchExecutor.execute(searchOperation);
}
- private void jumpToRow(final int row) {
- selectedRow = row;
- getListView().setSelection(row);
+ private void jumpToRow(final int rowIndex) {
+ selectedRowIndex = rowIndex;
+ selectedTokenRowIndex = activeLangaugeData.getTokenRow(rowIndex);
+ getListView().setSelection(rowIndex);
+ getListView().setSelected(true);
+ updateSearchText();
+ }
+
+ private void updateSearchText() {
+ final EditText searchText = (EditText) findViewById(R.id.SearchText);
+ if (!searchText.hasFocus()) {
+ searchText.setText(activeLangaugeData.rowToString(activeLangaugeData.rows.get(selectedTokenRowIndex)));
+ }
}
private final class SearchOperation implements Runnable {
public void run() {
Log.d("THAD", "SearchOperation: " + searchText);
- final int indexLocation = activeLanguge.lookup(searchText, interrupted);
+ final int indexLocation = activeLangaugeData.lookup(searchText, interrupted);
if (interrupted.get()) {
return;
}
- final IndexEntry indexEntry = activeLanguge.sortedIndex
+ final IndexEntry indexEntry = activeLangaugeData.sortedIndex
.get(indexLocation);
uiHandler.post(new Runnable() {
public void run() {
private class DictionaryListAdapter extends BaseAdapter {
public int getCount() {
- return activeLanguge.rows.size();
+ return activeLangaugeData.rows.size();
}
- public Dictionary.Row getItem(int position) {
- assert position < activeLanguge.rows.size();
- return activeLanguge.rows.get(position);
+ public Dictionary.Row getItem(int rowIndex) {
+ assert rowIndex < activeLangaugeData.rows.size();
+ return activeLangaugeData.rows.get(rowIndex);
}
- public long getItemId(int position) {
- return position;
+ public long getItemId(int rowIndex) {
+ return rowIndex;
}
- public View getView(final int position, final View convertView,
+ public View getView(final int rowIndex, final View convertView,
final ViewGroup parent) {
- final Row row = getItem(position);
+ final Row row = getItem(rowIndex);
+
+ // Token row.
if (row.isToken()) {
TextView result = null;
if (convertView instanceof TextView) {
} else {
result = new TextView(parent.getContext());
}
- result.setText(activeLanguge.rowToString(row));
+// result.setBackgroundColor(Color.WHITE);
+ result.setText(activeLangaugeData.rowToString(row));
result.setTextAppearance(parent.getContext(),
android.R.style.TextAppearance_Large);
return result;
}
- TableLayout result = null;
- if (convertView instanceof TableLayout) {
- result = (TableLayout) convertView;
- } else {
- result = new TableLayout(parent.getContext());
- }
+ // Entry row(s).
+ final TableLayout result = new TableLayout(parent.getContext());
- TableRow tableRow = null;
- if (result.getChildCount() != 1) {
- result.removeAllViews();
- tableRow = new TableRow(result.getContext());
+ final Entry entry = dictionary.entries.get(row.getIndex());
+ final int rowCount = entry.getRowCount();
+ for (int r = 0; r < rowCount; ++r) {
+ final TableRow tableRow = new TableRow(result.getContext());
+// if (r > 0) {
+// tableRow.setBackgroundColor(Color.DKGRAY);
+// }
+
+ TextView column1 = new TextView(tableRow.getContext());
+ TextView column2 = new TextView(tableRow.getContext());
+ final TableRow.LayoutParams layoutParams = new TableRow.LayoutParams();
+ layoutParams.weight = 0.5f;
+
+ if (r>0){
+ final TextView spacer = new TextView(tableRow.getContext());
+ spacer.setText(r == 0 ? "\95 " : " \95 ");
+ tableRow.addView(spacer);
+ }
+ tableRow.addView(column1, layoutParams);
+ if (r>0){
+ final TextView spacer = new TextView(tableRow.getContext());
+ spacer.setText(r == 0 ? "\95 " : " \95 ");
+ tableRow.addView(spacer);
+ }
+ tableRow.addView(column2, layoutParams);
+
+ column1.setWidth(1);
+ column2.setWidth(1);
+ // column1.setTextAppearance(parent.getContext(), android.R.style.Text);
+
+ // TODO: highlight query word in entries.
+ column1.setText(entry.getAllText(activeLangaugeData.lang)[r]);
+ column2.setText(entry.getAllText(Entry.otherLang(activeLangaugeData.lang))[r]);
+
result.addView(tableRow);
- } else {
- tableRow = (TableRow) result.getChildAt(0);
- }
- TextView column1 = null;
- TextView column2 = null;
- if (tableRow.getChildCount() != 2
- || !(tableRow.getChildAt(0) instanceof TextView)
- || !(tableRow.getChildAt(1) instanceof TextView)) {
- tableRow.removeAllViews();
- column1 = new TextView(tableRow.getContext());
- column2 = new TextView(tableRow.getContext());
- tableRow.addView(column1);
- tableRow.addView(column2);
- } else {
- column1 = (TextView) tableRow.getChildAt(0);
- column2 = (TextView) tableRow.getChildAt(1);
}
- column1.setWidth(100);
- column2.setWidth(100);
- // column1.setTextAppearance(parent.getContext(), android.R.style.Text);
- final Entry entry = dictionary.entries.get(row.getIndex());
- column1.setText(entry.getAllText(activeLanguge.lang));
- column2.setText(entry.getAllText(Entry.otherLang(activeLanguge.lang)));
- // result.setTextAppearance(parent.getContext(),
- // android.R.style.TextAppearance_Small);
+
return result;
-
}
- }
+ } // DictionaryListAdapter
private class DictionaryTextWatcher implements TextWatcher {
public void afterTextChanged(Editable searchText) {
\r
import java.io.IOException;\r
import java.io.RandomAccessFile;\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.LinkedHashMap;\r
+import java.util.LinkedHashSet;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.Set;\r
import java.util.regex.Pattern;\r
\r
import com.hughes.util.raf.RAFFactory;\r
static final byte LANG1 = 0;\r
static final byte LANG2 = 1;\r
\r
- static final Pattern lineSplitPattern = Pattern.compile("\\s+::\\s+");\r
+ static final Pattern lineSplitPattern = Pattern.compile("\\s::\\s");\r
+ static final Pattern sublineSplitPattern = Pattern.compile("\\s\\|\\s");\r
\r
- final String lang1;\r
- final String lang2;\r
+ final String[] lang1;\r
+ final String[] lang2;\r
\r
+// public Entry(final String lang1, final String lang2) {\r
+// this.lang1 = new String[] {lang1};\r
+// this.lang2 = new String[] {lang2};\r
+// }\r
+\r
+ Entry(final String[] lang1, final String[] lang2) {\r
+ this.lang1 = lang1;\r
+ this.lang2 = lang2;\r
+ }\r
+\r
+ public static final RAFFactory<Entry> RAF_FACTORY = new RAFFactory<Entry>() {\r
+ public Entry create(RandomAccessFile raf) throws IOException {\r
+ final int rows = raf.readByte();\r
+ final String[] lang1 = new String[rows];\r
+ final String[] lang2 = new String[rows];\r
+ for (int i = 0; i < lang1.length; ++i) {\r
+ lang1[i] = raf.readUTF();\r
+ lang2[i] = raf.readUTF();\r
+ }\r
+ return new Entry(lang1, lang2);\r
+ }};\r
+ public void write(RandomAccessFile raf) throws IOException {\r
+ assert lang1.length == (byte) lang1.length;\r
+ raf.writeByte(lang1.length);\r
+ for (int i = 0; i < lang1.length; ++i) {\r
+ raf.writeUTF(lang1[i]);\r
+ raf.writeUTF(lang2[i]);\r
+ }\r
+ }\r
+\r
@Override\r
public boolean equals(Object o) {\r
if (!(o instanceof Entry)) {\r
return false;\r
}\r
final Entry that = (Entry) o;\r
- return that.lang1.equals(lang1) && that.lang2.equals(lang2);\r
+ return Arrays.deepEquals(this.lang1, that.lang1) && Arrays.deepEquals(this.lang2, that.lang2); \r
}\r
\r
@Override\r
public int hashCode() {\r
- return lang1.hashCode() + lang2.hashCode();\r
+ return Arrays.deepHashCode(lang1) + Arrays.deepHashCode(lang2);\r
}\r
\r
@Override\r
return getRawText();\r
}\r
\r
- public Entry(String lang1, String lang2) {\r
- this.lang1 = lang1;\r
- this.lang2 = lang2;\r
- }\r
-\r
- public static final RAFFactory<Entry> RAF_FACTORY = new RAFFactory<Entry>() {\r
- public Entry create(RandomAccessFile raf) throws IOException {\r
- final String lang1 = raf.readUTF();\r
- final String lang2 = raf.readUTF();\r
- return new Entry(lang1, lang2);\r
- }};\r
- public void write(RandomAccessFile raf) throws IOException {\r
- raf.writeUTF(lang1);\r
- raf.writeUTF(lang2);\r
+ public int getRowCount() {\r
+ assert lang1.length == lang2.length;\r
+ return lang1.length;\r
}\r
\r
- String getAllText(final byte lang) {\r
+ String[] getAllText(final byte lang) {\r
if (lang == LANG1) {\r
return lang1;\r
}\r
return lang2;\r
}\r
\r
- String getIndexableText(final byte lang) {\r
- String text = getAllText(lang);\r
- text = text.replaceAll("[\"\\.!?,]", "");\r
- text = text.replaceAll("\\{[^}]+\\}", "");\r
- return text;\r
- }\r
-\r
- String getFormattedEntry(final byte lang) {\r
- return getAllText(lang) + "\n" + getAllText(otherLang(lang));\r
- }\r
-\r
String getRawText() {\r
- return getAllText(LANG1) + " :: " + getAllText(LANG2);\r
+ return Arrays.asList(getAllText(LANG1)) + "\t" + Arrays.asList(getAllText(LANG2));\r
}\r
\r
static byte otherLang(final byte lang) {\r
assert lang == LANG1 || lang == LANG2;\r
return lang == LANG1 ? LANG2 : LANG1;\r
}\r
-\r
\r
\r
- static Entry parseFromLine(final String line) {\r
+ static Entry parseFromLine(String line, final boolean hasMultipleSubentries) {\r
+ line = line.replaceAll("<", "<");\r
+ line = line.replaceAll(">", ">");\r
final String[] parts = lineSplitPattern.split(line);\r
if (parts.length != 2) {\r
System.err.println("Entry:" + "Invalid line: " + line);\r
return null;\r
}\r
- return new Entry(parts[0], parts[1]);\r
+ if (!hasMultipleSubentries) {\r
+ return new Entry(new String[] {parts[0].trim()}, new String[] {parts[1].trim()});\r
+ }\r
+ \r
+ final String[] lang1 = sublineSplitPattern.split(" " + parts[0].trim() + " ");\r
+ final String[] lang2 = sublineSplitPattern.split(" " + parts[1].trim() + " ");\r
+ if (lang1.length != lang2.length) {\r
+ System.err.println("Entry:" + "Invalid subline: " + line);\r
+ return null;\r
+ }\r
+ for (int i = 0; i < lang1.length; ++i) {\r
+ lang1[i] = lang1[i].trim();\r
+ lang2[i] = lang2[i].trim();\r
+ }\r
+ return new Entry(lang1, lang2);\r
+ }\r
+ \r
+ static final Map<String, String> bracketToClose = new LinkedHashMap<String, String>();\r
+ static {\r
+ bracketToClose.put("\"", "\"");\r
+ bracketToClose.put(" '", "' ");\r
+ }\r
+ \r
+ static final Pattern WHITESPACE = Pattern.compile("\\s+");\r
+ \r
+ public Set<String> getIndexableTokens(final byte lang) {\r
+ final Set<String> result = new LinkedHashSet<String>();\r
+ String text = Arrays.asList(getAllText(lang)).toString();\r
+\r
+ text = text.replaceAll("fig\\.", " ");\r
+ text = text.replaceAll("\\{[^\\}]+}", " ");\r
+ text = text.replaceAll("\"-", "-");\r
+ text = text.replaceAll("-\"", "-");\r
+ text = text.replaceAll("[\":/\\()<>\\[\\],;?!.]", " ");\r
+ result.addAll(Arrays.asList(WHITESPACE.split(text)));\r
+\r
+ text = text.replaceAll("[-]", " ");\r
+ result.addAll(Arrays.asList(WHITESPACE.split(text)));\r
+ \r
+ final Set<String> result2 = new LinkedHashSet<String>();\r
+ for (final String token : result) {\r
+ if (isIndexable(token)) {\r
+ result2.add(token);\r
+ }\r
+ }\r
+ return result2;\r
+ }\r
+\r
+ static boolean isIndexable(final String text) {\r
+ // Does it have an alpha-numeric anywhere?\r
+ return text.matches(".*\\w.*");\r
+ }\r
+ \r
+ static List<String> getTextInside(final String text, final String start, final String end) {\r
+ final List<String> result = new ArrayList<String>();\r
+ int startPos = 0;\r
+ while ((startPos = text.indexOf(start)) != -1) {\r
+ final int endPos = text.indexOf(end, startPos + 1);\r
+ result.add(text.substring(startPos + 1, endPos));\r
+ startPos = endPos + 1;\r
+ }\r
+ return result;\r
}\r
\r
-}\r
+}
\ No newline at end of file
+++ /dev/null
-package com.hughes.android.dictionary;\r
-\r
-import java.util.Comparator;\r
-\r
-import com.hughes.util.StringUtil;\r
-\r
-public class EntryFactory {\r
- \r
- static final EntryFactory entryFactory = new EntryFactory();\r
-\r
- public String normalizeToken(final String token) {\r
- return token.toLowerCase().replaceAll("ß", "ss").replaceAll("ä", "a")\r
- .replaceAll("ae", "a").replaceAll("ö", "o").replaceAll("oe", "o")\r
- .replaceAll("ü", "u").replaceAll("ue", "u").replaceAll("[^A-Za-z0-9]",\r
- "");\r
- }\r
- \r
- public Comparator<String> getEntryComparator() {\r
- return new Comparator<String>() {\r
- public int compare(final String s1, final String s2) {\r
- final String norm1 = normalizeToken(s1);\r
- final String norm2 = normalizeToken(s2);\r
- final int c = norm1.compareTo(norm2);\r
- if (c != 0) {\r
- return c;\r
- }\r
- return StringUtil.reverse(s1).compareTo(StringUtil.reverse(s2));\r
- }};\r
- }\r
-\r
-}\r
+++ /dev/null
-package com.hughes.android.dictionary;\r
-\r
-import java.io.IOException;\r
-import java.io.RandomAccessFile;\r
-import java.util.Map;\r
-import java.util.Set;\r
-import java.util.TreeMap;\r
-\r
-import com.hughes.util.LRUCacheMap;\r
-\r
-\r
-public final class Index {\r
- \r
- final String filename;\r
- final RandomAccessFile file;\r
- \r
- final Node root;\r
- final Map<Integer,Node> indexOffsetToNode = new LRUCacheMap<Integer,Node>(5000);\r
- \r
- \r
- public Index(final String filename) throws IOException {\r
- this.filename = filename;\r
- file = new RandomAccessFile(filename, "r");\r
- root = getNode(new NodeHandle("", 0));\r
- }\r
- \r
- public Node lookup(final String normalizedToken) throws IOException {\r
- return lookup(normalizedToken, 0, root);\r
- }\r
- \r
- private Node lookup(final String normalizedToken, final int pos, final Node node) throws IOException {\r
- if (pos == normalizedToken.length()) {\r
- return node;\r
- }\r
- \r
- // Check whether any prefix of the token is a child.\r
- for (int i = pos + 1; i <= normalizedToken.length(); ++i) {\r
- final NodeHandle childHandle = node.children.get(normalizedToken.substring(pos, i));\r
- if (childHandle != null) {\r
- return lookup(normalizedToken, i, childHandle.getNode());\r
- }\r
- }\r
- \r
- // Check whether any child starts with what's left of text.\r
- final String remainder = normalizedToken.substring(pos);\r
- for (final Map.Entry<String, NodeHandle> childHandle : node.children.entrySet()) {\r
- if (childHandle.getKey().startsWith(remainder)) {\r
- return getNode(childHandle.getValue());\r
- }\r
- }\r
- \r
- return node;\r
- }\r
- \r
- private Node getNode(final NodeHandle nodeHandle) throws IOException {\r
- Node node = indexOffsetToNode.get(nodeHandle.indexOffset);\r
- if (node == null) {\r
- node = new Node(nodeHandle);\r
- indexOffsetToNode.put(nodeHandle.indexOffset, node);\r
- }\r
- return node;\r
- }\r
- \r
- final class NodeHandle {\r
- final String normalizedToken;\r
- final int indexOffset;\r
-\r
- NodeHandle(final String normalizedToken, final int indexOffset) throws IOException {\r
- this.normalizedToken = normalizedToken;\r
- this.indexOffset = indexOffset;\r
- }\r
- \r
- Node getNode() throws IOException {\r
- return Index.this.getNode(this);\r
- }\r
- }\r
-\r
- final class Node {\r
- final NodeHandle nodeHandle;\r
- final TreeMap<String,NodeHandle> children;\r
- final Map<String,int[]> tokenToOffsets = new TreeMap<String, int[]>(EntryFactory.entryFactory.getEntryComparator());\r
- final int descendantTokenCount;\r
- final int descendantEntryCount;\r
- \r
- Node(final NodeHandle nodeHandle) throws IOException {\r
- this.nodeHandle = nodeHandle;\r
- \r
- file.seek(nodeHandle.indexOffset);\r
- \r
- // Read children to offset.\r
- final int numChildren = file.readInt();\r
- children = new TreeMap<String, NodeHandle>();\r
- for (int i = 0; i < numChildren; ++i) {\r
- final String chunk = file.readUTF().intern();\r
- if (chunk.length() == 0) {\r
- throw new IOException("Empty string chunk.");\r
- }\r
- children.put(chunk, new NodeHandle(nodeHandle.normalizedToken + chunk, file.readInt()));\r
- }\r
- \r
- // Read tokens.\r
- final int numTokens = file.readInt();\r
- for (int i = 0; i < numTokens; ++i) {\r
- final String token = file.readUTF();\r
- assert EntryFactory.entryFactory.normalizeToken(token).equals(nodeHandle.normalizedToken);\r
- final int[] offsets = new int[file.readInt()];\r
- for (int j = 0; j < offsets.length; ++j) {\r
- offsets[j]= file.readInt();\r
- }\r
- tokenToOffsets.put(token, offsets);\r
- }\r
- \r
- // TODO: move this up, and defer the loading of the other stuff until it's needed.\r
- descendantTokenCount = file.readInt();\r
- descendantEntryCount = file.readInt();\r
- }\r
- \r
- @Override\r
- public String toString() {\r
- return String.format("%s(%d,%d)", nodeHandle.normalizedToken, getThisCount(), getDescendantCount());\r
- }\r
- \r
- public int getDescendantCount() {\r
- return descendantEntryCount + descendantTokenCount;\r
- }\r
- \r
- public int getThisCount() {\r
- int count = tokenToOffsets.size();\r
- for (final int[] offsets : tokenToOffsets.values()) {\r
- count += offsets.length;\r
- }\r
- return count;\r
- }\r
-\r
- public Object getDescendant(int position) throws IOException {\r
- assert position < getDescendantCount(); \r
-\r
-// System.out.println("getD: " + this + ", " + position);\r
- if (position < getThisCount()) {\r
- for (final Map.Entry<String, int[]> tokenEntry : tokenToOffsets.entrySet()) {\r
- if (position == 0) {\r
- return tokenEntry.getKey();\r
- }\r
- --position;\r
- if (position < tokenEntry.getValue().length) {\r
- return tokenEntry.getValue()[position];\r
- }\r
- position -= tokenEntry.getValue().length;\r
- }\r
- assert false;\r
- }\r
- position -= getThisCount();\r
- \r
- \r
- for (final Map.Entry<String,NodeHandle> childEntry : children.entrySet()) {\r
- final Node child = childEntry.getValue().getNode();\r
- if (position < child.getDescendantCount()) {\r
- return child.getDescendant(position);\r
- }\r
- position -= child.getDescendantCount();\r
- }\r
- assert false;\r
- return null;\r
- }\r
-\r
- public void getDescendantEntryOffsets(final Set<Integer> entryOffsets, int maxSize) throws IOException {\r
- for (final int[] offsets : tokenToOffsets.values()) {\r
- for (final int offset : offsets) {\r
- if (entryOffsets.size() >= maxSize) {\r
- return;\r
- }\r
- entryOffsets.add(offset);\r
- }\r
- }\r
- if (entryOffsets.size() >= maxSize) {\r
- return;\r
- }\r
- for (final Map.Entry<String, NodeHandle> childEntry : children.entrySet()) {\r
- final Node child = childEntry.getValue().getNode();\r
- child.getDescendantEntryOffsets(entryOffsets, maxSize);\r
- if (entryOffsets.size() >= maxSize) {\r
- return;\r
- }\r
- }\r
- }\r
- }\r
- \r
-}\r
+++ /dev/null
-package com.hughes.android.dictionary;\r
-\r
-import java.io.DataInputStream;\r
-import java.io.DataOutputStream;\r
-import java.io.IOException;\r
-import java.io.Serializable;\r
-\r
-\r
-public final class MemoryIndex {\r
- \r
- private static final long serialVersionUID = 3375180767865334065L;\r
-\r
- static final class Node implements Serializable {\r
-\r
- private static final long serialVersionUID = 8824115665859184225L;\r
-\r
- final String[] chars;\r
- final Node[] children;\r
- final int[] offsets;\r
- \r
- Node(final int numChildren, final int numOffsets) {\r
- chars = new String[numChildren];\r
- children = new Node[numChildren];\r
- offsets = new int[numOffsets];\r
- }\r
- \r
- int descendantCount() {\r
- int total = 1;\r
- for (final Node child : children) {\r
- total += child.descendantCount();\r
- }\r
- return total;\r
- }\r
- \r
- Node(final DataInputStream is) throws IOException {\r
- final int numChildren = is.readInt();\r
- chars = new String[numChildren];\r
- children = new Node[numChildren];\r
- for (int i = 0; i < numChildren; ++i) {\r
- chars[i] = is.readUTF().intern();\r
- children[i] = new Node(is);\r
- }\r
- final int numOffsets = is.readInt();\r
- offsets = new int[numOffsets];\r
- for (int i = 0; i < numOffsets; ++i) {\r
- offsets[i] = is.readInt();\r
- }\r
- }\r
- \r
- void write(final DataOutputStream os) throws IOException {\r
- os.writeInt(chars.length);\r
- for (int i = 0; i < chars.length; i++) {\r
- os.writeUTF(chars[i]);\r
- children[i].write(os);\r
- }\r
- os.writeInt(offsets.length);\r
- for (int i = 0; i < offsets.length; i++) {\r
- os.writeInt(offsets[i]);\r
- }\r
- }\r
- }\r
- \r
-}\r