]> 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   static final class IndexEntry implements RAFSerializable<Index.IndexEntry> {
86     String token;
87     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     final IndexEntry insertionPoint;
156     final IndexEntry longestPrefix;
157     final String longestPrefixString;
158     
159     public SearchResult(IndexEntry insertionPoint, IndexEntry longestPrefix,
160         String longestPrefixString) {
161       this.insertionPoint = insertionPoint;
162       this.longestPrefix = longestPrefix;
163       this.longestPrefixString = longestPrefixString;
164     }
165   }
166   
167   public SearchResult findLongestSubstring(String token, final AtomicBoolean interrupted) {
168     IndexEntry insertionPoint = null;
169     IndexEntry result = null;
170     while (!interrupted.get() && token.length() > 0) {
171       result = findInsertionPoint(token, interrupted);
172       if (result == null) {
173         return null;
174       }
175       if (insertionPoint == null) {
176         insertionPoint = result;
177       }
178       if (sortLanguage.textNorm(result.token, true).startsWith(sortLanguage.textNorm(token, true))) {
179         return new SearchResult(insertionPoint, result, token);
180       }
181       token = token.substring(0, token.length() - 1);      
182     }
183     return new SearchResult(insertionPoint, sortedIndexEntries.get(0), "");
184   }
185   
186   private final int windBackCase(final String token, int result, final Collator sortCollator, final AtomicBoolean interrupted) {
187     while (result > 0 && sortCollator.compare(sortLanguage.textNorm(sortedIndexEntries.get(result - 1).token, true), token) >= 0) {
188       --result;
189       if (interrupted.get()) {
190         return result;
191       }
192     }
193     return result;
194   }
195
196
197 }