]> gitweb.fperrin.net Git - Dictionary.git/blob - src/com/hughes/android/dictionary/engine/Index.java
go
[Dictionary.git] / src / com / hughes / android / dictionary / engine / Index.java
1 /**
2  * 
3  */
4 package com.hughes.android.dictionary.engine;
5
6 import java.io.IOException;
7 import java.io.PrintStream;
8 import java.io.RandomAccessFile;
9 import java.util.ArrayList;
10 import java.util.Collection;
11 import java.util.List;
12 import java.util.concurrent.atomic.AtomicBoolean;
13
14 import com.hughes.util.CachingList;
15 import com.hughes.util.raf.RAFList;
16 import com.hughes.util.raf.RAFSerializable;
17 import com.hughes.util.raf.RAFSerializer;
18 import com.hughes.util.raf.UniformRAFList;
19 import com.ibm.icu.text.Collator;
20
21 public final class Index implements RAFSerializable<Index> {
22   
23   static final int CACHE_SIZE = 5000;
24   
25   final Dictionary dict;
26   
27   public final String shortName;
28   public final String longName;
29   
30   // persisted: tells how the entries are sorted.
31   public final Language sortLanguage;
32     
33   // persisted
34   public final List<IndexEntry> sortedIndexEntries;
35
36   // One big list!
37   // Various sub-types.
38   // persisted
39   public final List<RowBase> rows;
40   
41   public final boolean swapPairEntries;
42   
43   // --------------------------------------------------------------------------
44   
45   public Index(final Dictionary dict, final String shortName, final String longName, final Language sortLanguage, final boolean swapPairEntries) {
46     this.dict = dict;
47     this.shortName = shortName;
48     this.longName = longName;
49     this.sortLanguage = sortLanguage;
50     this.swapPairEntries = swapPairEntries;
51     sortedIndexEntries = new ArrayList<IndexEntry>();
52     rows = new ArrayList<RowBase>();
53   }
54   
55   public Index(final Dictionary dict, final RandomAccessFile raf) throws IOException {
56     this.dict = dict;
57     shortName = raf.readUTF();
58     longName = raf.readUTF();
59     final String languageCode = raf.readUTF();
60     sortLanguage = Language.lookup(languageCode);
61     swapPairEntries = raf.readBoolean();
62     if (sortLanguage == null) {
63       throw new IOException("Unsupported language: " + languageCode);
64     }
65     sortedIndexEntries = CachingList.create(RAFList.create(raf, IndexEntry.SERIALIZER, raf.getFilePointer()), CACHE_SIZE);
66     rows = CachingList.create(UniformRAFList.create(raf, new RowBase.Serializer(this), raf.getFilePointer()), CACHE_SIZE);
67   }
68   
69   @Override
70   public void write(final RandomAccessFile raf) throws IOException {
71     raf.writeUTF(shortName);
72     raf.writeUTF(longName);
73     raf.writeUTF(sortLanguage.getSymbol());
74     raf.writeBoolean(swapPairEntries);
75     RAFList.write(raf, sortedIndexEntries, IndexEntry.SERIALIZER);
76     UniformRAFList.write(raf, (Collection<RowBase>) rows, new RowBase.Serializer(this), 5);
77   }
78
79   public void print(final PrintStream out) {
80     for (final RowBase row : rows) {
81       row.print(out);
82     }
83   }
84   
85   public static final class IndexEntry implements RAFSerializable<Index.IndexEntry> {
86     public final String token;
87     public final int startRow;
88     
89     static final RAFSerializer<IndexEntry> SERIALIZER = new RAFSerializer<IndexEntry> () {
90       @Override
91       public IndexEntry read(RandomAccessFile raf) throws IOException {
92         return new IndexEntry(raf);
93       }
94       @Override
95       public void write(RandomAccessFile raf, IndexEntry t) throws IOException {
96         t.write(raf);
97       }};
98       
99     public IndexEntry(final String token, final int startRow) {
100       assert token.equals(token.trim());
101       assert token.length() > 0;
102       this.token = token;
103       this.startRow = startRow;
104     }
105     
106     public IndexEntry(final RandomAccessFile raf) throws IOException {
107       token = raf.readUTF();
108       startRow = raf.readInt();
109     }
110     
111     public void write(RandomAccessFile raf) throws IOException {
112       raf.writeUTF(token);
113       raf.writeInt(startRow);
114     }
115
116     public String toString() {
117       return token + "@" + startRow;
118     }
119   }
120   
121   public IndexEntry findInsertionPoint(String token, final AtomicBoolean interrupted) {
122     token = sortLanguage.textNorm(token, true);
123
124     int start = 0;
125     int end = sortedIndexEntries.size();
126     
127     final Collator sortCollator = sortLanguage.getSortCollator();
128     while (start < end) {
129       final int mid = (start + end) / 2;
130       if (interrupted.get()) {
131         return null;
132       }
133       final IndexEntry midEntry = sortedIndexEntries.get(mid);
134
135       final int comp = sortCollator.compare(token, sortLanguage.textNorm(midEntry.token, true));
136       if (comp == 0) {
137         final int result = windBackCase(token, mid, sortCollator, interrupted);
138         return sortedIndexEntries.get(result);
139       } else if (comp < 0) {
140 //        Log.d("THAD", "Upper bound: " + midEntry);
141         end = mid;
142       } else {
143 //        Log.d("THAD", "Lower bound: " + midEntry);
144         start = mid + 1;
145       }
146     }
147
148     // If we search for a substring of a string that's in there, return that.
149     int result = Math.min(start, sortedIndexEntries.size() - 1);
150     result = windBackCase(sortLanguage.textNorm(sortedIndexEntries.get(result).token, true), result, sortCollator, interrupted);
151     return sortedIndexEntries.get(result);
152   }
153   
154   public static final class SearchResult {
155     public final IndexEntry insertionPoint;
156     public final IndexEntry longestPrefix;
157     public final String longestPrefixString;
158     public final boolean success;
159     
160     public SearchResult(IndexEntry insertionPoint, IndexEntry longestPrefix,
161         String longestPrefixString, boolean success) {
162       this.insertionPoint = insertionPoint;
163       this.longestPrefix = longestPrefix;
164       this.longestPrefixString = longestPrefixString;
165       this.success = success;
166     }
167   }
168   
169   public SearchResult findLongestSubstring(String token, final AtomicBoolean interrupted) {
170     if (token.length() == 0) {
171       return new SearchResult(sortedIndexEntries.get(0), sortedIndexEntries.get(0), "", true);
172     }
173     IndexEntry insertionPoint = null;
174     IndexEntry result = null;
175     boolean unmodified = true;
176     while (!interrupted.get() && token.length() > 0) {
177       result = findInsertionPoint(token, interrupted);
178       if (result == null) {
179         return null;
180       }
181       if (unmodified) {
182         insertionPoint = result;
183       }
184       if (sortLanguage.textNorm(result.token, true).startsWith(sortLanguage.textNorm(token, true))) {
185         return new SearchResult(insertionPoint, result, token, unmodified);
186       }
187       unmodified = false;
188       token = token.substring(0, token.length() - 1);      
189     }
190     return new SearchResult(insertionPoint, sortedIndexEntries.get(0), "", false);
191   }
192   
193   private final int windBackCase(final String token, int result, final Collator sortCollator, final AtomicBoolean interrupted) {
194     while (result > 0 && sortCollator.compare(sortLanguage.textNorm(sortedIndexEntries.get(result - 1).token, true), token) >= 0) {
195       --result;
196       if (interrupted.get()) {
197         return result;
198       }
199     }
200     return result;
201   }
202
203
204 }