OSDN Git Service

bac83f0301f3573f872ddce4e42c2feffe741e28
[neighbornote/NeighborNote.git] / src / com / swabunga / spell / engine / SpellDictionaryASpell.java
1 /*\r
2 Jazzy - a Java library for Spell Checking\r
3 Copyright (C) 2001 Mindaugas Idzelis\r
4 Full text of license can be found in LICENSE.txt\r
5 \r
6 This library is free software; you can redistribute it and/or\r
7 modify it under the terms of the GNU Lesser General Public\r
8 License as published by the Free Software Foundation; either\r
9 version 2.1 of the License, or (at your option) any later version.\r
10 \r
11 This library is distributed in the hope that it will be useful,\r
12 but WITHOUT ANY WARRANTY; without even the implied warranty of\r
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
14 Lesser General Public License for more details.\r
15 \r
16 You should have received a copy of the GNU Lesser General Public\r
17 License along with this library; if not, write to the Free Software\r
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\r
19 */\r
20 /* Created by bgalbs on Jan 30, 2003 at 11:45:25 PM */\r
21 package com.swabunga.spell.engine;\r
22 \r
23 import java.io.File;\r
24 import java.io.IOException;\r
25 import java.io.Reader;\r
26 import java.security.InvalidParameterException;\r
27 import java.util.Collections;\r
28 import java.util.Enumeration;\r
29 import java.util.Hashtable;\r
30 import java.util.Iterator;\r
31 import java.util.LinkedList;\r
32 import java.util.List;\r
33 import java.util.Vector;\r
34 \r
35 /**\r
36  * Container for various methods that any <code>SpellDictionary</code> will use.\r
37  * This class is based on the original Jazzy aspell port.\r
38  * <p/>\r
39  * Derived classes will need words list files as spell checking reference. \r
40  * Words list file is a dictionary with one word per line. There are many \r
41  * open source dictionary files, see: \r
42  * <a href="http://wordlist.sourceforge.net/">\r
43  * http://wordlist.sourceforge.net/</a>\r
44  * <p/>\r
45  * You can choose words lists form <a href="http://aspell.net/">aspell</a> \r
46  * many differents languages dictionaries. To grab some, install \r
47  * <code>aspell</code> and the dictionaries you require. Then run aspell \r
48  * specifying the name of the dictionary and the words list file to dump it \r
49  * into, for example:\r
50  * <pre>\r
51  * aspell --master=fr-40 dump master > fr-40.txt\r
52  * </pre>\r
53  * Note: the number following the language is the size indicator. A bigger\r
54  * number gives a more extensive language coverage. Size 40 is more than \r
55  * adequate for many usages.\r
56  * <p/>\r
57  * For some languages, Aspell can also supply you with the phonetic file. \r
58  * On Windows, go into aspell <code>data</code> directory and copy the \r
59  * phonetic file corresponding to your language, for example the \r
60  * <code>fr_phonet.dat</code> for the <code>fr</code> language. The phonetic\r
61  * file should be in directory <code>/usr/share/aspell</code> on Unix.\r
62  *\r
63  * @see GenericTransformator GenericTransformator for information on \r
64  * phonetic files.\r
65  */\r
66 public abstract class SpellDictionaryASpell implements SpellDictionary {\r
67 \r
68 \r
69   /** The reference to a Transformator, used to transform a word into it's phonetic code. */\r
70   protected Transformator tf;\r
71 \r
72   /**\r
73    * Constructs a new SpellDictionaryASpell\r
74    * @param phonetic The file to use for phonetic transformation of the \r
75    * words list. If <code>phonetic</code> is null, the the transformation\r
76    * uses {@link DoubleMeta} transformation.\r
77    * @throws java.io.IOException  indicates problems reading the phonetic \r
78    * information\r
79    */\r
80   public SpellDictionaryASpell(File phonetic) throws IOException {\r
81     if (phonetic == null)\r
82       tf = new DoubleMeta();\r
83     else\r
84       tf = new GenericTransformator(phonetic);\r
85   }\r
86 \r
87   /**\r
88    * Constructs a new SpellDictionaryASpell\r
89    * @param phonetic The file to use for phonetic transformation of the \r
90    * words list. If <code>phonetic</code> is null, the the transformation\r
91    * uses {@link DoubleMeta} transformation.\r
92    * @param encoding Uses the character set encoding specified\r
93    * @throws java.io.IOException  indicates problems reading the phonetic \r
94    * information\r
95    */\r
96   public SpellDictionaryASpell(File phonetic, String encoding) throws IOException {\r
97     if (phonetic == null)\r
98       tf = new DoubleMeta();\r
99     else\r
100       tf = new GenericTransformator(phonetic, encoding);\r
101   }\r
102 \r
103   /**\r
104    * Constructs a new SpellDictionaryASpell\r
105    * @param phonetic The Reader to use for phonetic transformation of the \r
106    * words list. If <code>phonetic</code> is null, the the transformation\r
107    * uses {@link DoubleMeta} transformation.\r
108    * @throws java.io.IOException  indicates problems reading the phonetic \r
109    * information\r
110    */\r
111   public SpellDictionaryASpell(Reader phonetic) throws IOException {\r
112     if (phonetic == null)\r
113       tf = new DoubleMeta();\r
114     else\r
115       tf = new GenericTransformator(phonetic);\r
116   }\r
117 \r
118   /**\r
119    * Returns a list of Word objects that are the suggestions to an\r
120    * incorrect word. \r
121    * <p>\r
122    * This method is only needed to provide backward compatibility.\r
123    * @see #getSuggestions(String, int, int[][])\r
124    * @param word Suggestions for given misspelt word\r
125    * @param threshold The lower boundary of similarity to misspelt word\r
126    * @return Vector a List of suggestions\r
127    */\r
128   @SuppressWarnings("unchecked")\r
129 public List getSuggestions(String word, int threshold) {\r
130         \r
131         return getSuggestions(word,threshold,null);\r
132         \r
133   }\r
134 \r
135   /**\r
136    * Returns a list of Word objects that are the suggestions to an\r
137    * incorrect word.\r
138    * <p>\r
139    * @param word Suggestions for given misspelt word\r
140    * @param threshold The lower boundary of similarity to misspelt word\r
141    * @param matrix Two dimensional int array used to calculate\r
142    * edit distance. Allocating this memory outside of the function will greatly improve efficiency. \r
143    * @return Vector a List of suggestions\r
144    */\r
145   @SuppressWarnings("unchecked")\r
146 public List getSuggestions(String word, int threshold, int[][] matrix) {\r
147 \r
148         int i;\r
149         int j;\r
150         \r
151         if(matrix == null)\r
152                 matrix = new int[0][0];\r
153         \r
154     Hashtable nearmisscodes = new Hashtable();\r
155     String code = getCode(word);\r
156 \r
157     // add all words that have the same phonetics\r
158     nearmisscodes.put(code, code);\r
159     Vector phoneticList = getWordsFromCode(word, nearmisscodes);\r
160 \r
161     // do some tranformations to pick up more results\r
162     //interchange\r
163     nearmisscodes = new Hashtable();\r
164     char[] charArray = word.toCharArray();\r
165     char a;\r
166     char b ;\r
167     \r
168     for (i = 0; i < word.length() - 1; i++) {\r
169       a = charArray[i];\r
170       b = charArray[i + 1];\r
171       charArray[i] = b;\r
172       charArray[i + 1] = a;\r
173       String s = getCode(new String(charArray));\r
174       nearmisscodes.put(s, s);\r
175       charArray[i] = a;\r
176       charArray[i + 1] = b;\r
177     }\r
178 \r
179     char[] replacelist = tf.getReplaceList();\r
180 \r
181     //change\r
182     charArray = word.toCharArray();\r
183     char original; \r
184     for (i = 0; i < word.length(); i++) {\r
185       original = charArray[i];\r
186       for (j = 0; j < replacelist.length; j++) {\r
187         charArray[i] = replacelist[j];\r
188         String s = getCode(new String(charArray));\r
189         nearmisscodes.put(s, s);\r
190       }\r
191       charArray[i] = original;\r
192     }\r
193 \r
194     //add\r
195     charArray = (word += " ").toCharArray();\r
196     int iy = charArray.length - 1;\r
197     while (true) {\r
198       for (j = 0; j < replacelist.length; j++) {\r
199         charArray[iy] = replacelist[j];\r
200         String s = getCode(new String(charArray));\r
201         nearmisscodes.put(s, s);\r
202       }\r
203       if (iy == 0)\r
204         break;\r
205       charArray[iy] = charArray[iy - 1];\r
206       --iy;\r
207     }\r
208 \r
209     //delete\r
210     word = word.trim();\r
211     charArray = word.toCharArray();\r
212     char[] charArray2 = new char[charArray.length - 1];\r
213     for (int ix = 0; ix < charArray2.length; ix++) {\r
214       charArray2[ix] = charArray[ix];\r
215     }\r
216     \r
217     a = charArray[charArray.length - 1];\r
218     int ii = charArray2.length;\r
219     while (true) {\r
220       String s = getCode(new String(charArray));\r
221       nearmisscodes.put(s, s);\r
222       if (ii == 0)\r
223         break;\r
224       b = a;\r
225       a = charArray2[ii - 1];\r
226       charArray2[ii - 1] = b;\r
227       --ii;\r
228     }\r
229 \r
230     nearmisscodes.remove(code); //already accounted for in phoneticList\r
231 \r
232     Vector wordlist = getWordsFromCode(word, nearmisscodes);\r
233 \r
234     if (wordlist.size() == 0 && phoneticList.size() == 0)\r
235       addBestGuess(word, phoneticList, matrix);\r
236 \r
237 \r
238     // We sort a Vector at the end instead of maintaining a\r
239     // continously sorted TreeSet because everytime you add a collection\r
240     // to a treeset it has to be resorted. It's better to do this operation\r
241     // once at the end.\r
242 \r
243     Collections.sort(phoneticList, new Word()); //always sort phonetic matches along the top\r
244     Collections.sort(wordlist, new Word()); //the non-phonetic matches can be listed below\r
245 \r
246     phoneticList.addAll(wordlist);\r
247     return phoneticList;\r
248   }\r
249 \r
250   /**\r
251    * When we don't come up with any suggestions (probably because the threshold was too strict),\r
252    * then pick the best guesses from the those words that have the same phonetic code.\r
253    * <p>\r
254    * This method is only needed to provide backward compatibility.\r
255    * @see addBestGuess(String word, Vector wordList, int[][] matrix)\r
256    * @param word - the word we are trying spell correct\r
257    * @param wordList - the linked list that will get the best guess\r
258    */\r
259   @SuppressWarnings({ "unused", "unchecked" })\r
260 private void addBestGuess(String word, Vector wordList) {\r
261         addBestGuess(word,wordList,null);\r
262   }\r
263   \r
264   /**\r
265    * When we don't come up with any suggestions (probably because the threshold was too strict),\r
266    * then pick the best guesses from the those words that have the same phonetic code.\r
267    * @param word - the word we are trying spell correct\r
268    * @param Two dimensional array of int used to calculate \r
269    * edit distance. Allocating this memory outside of the function will greatly improve efficiency. \r
270    * @param wordList - the linked list that will get the best guess\r
271    */\r
272   @SuppressWarnings("unchecked")\r
273 private void addBestGuess(String word, Vector wordList, int[][] matrix) {\r
274         if(matrix == null)\r
275                 matrix = new int[0][0];\r
276         \r
277     if (wordList.size() != 0)\r
278       throw new InvalidParameterException("the wordList vector must be empty");\r
279 \r
280     int bestScore = Integer.MAX_VALUE;\r
281     \r
282     String code = getCode(word);\r
283     List simwordlist = getWords(code);\r
284 \r
285     LinkedList candidates = new LinkedList();\r
286 \r
287     for (Iterator j = simwordlist.iterator(); j.hasNext();) {\r
288       String similar = (String) j.next();\r
289       int distance = EditDistance.getDistance(word, similar, matrix);\r
290       if (distance <= bestScore) {\r
291         bestScore = distance;\r
292         Word goodGuess = new Word(similar, distance);\r
293         candidates.add(goodGuess);\r
294       }\r
295     }\r
296 \r
297     //now, only pull out the guesses that had the best score\r
298     for (Iterator iter = candidates.iterator(); iter.hasNext();) {\r
299       Word candidate = (Word) iter.next();\r
300       if (candidate.getCost() == bestScore)\r
301         wordList.add(candidate);\r
302     }\r
303 \r
304   }\r
305 \r
306   @SuppressWarnings("unchecked")\r
307 private Vector getWordsFromCode(String word, Hashtable codes) {\r
308     Configuration config = Configuration.getConfiguration();\r
309     Vector result = new Vector();\r
310     int[][] matrix = new int[0][0]; \r
311     final int configDistance = config.getInteger(Configuration.SPELL_THRESHOLD);\r
312 \r
313     for (Enumeration i = codes.keys(); i.hasMoreElements();) {\r
314       String code = (String) i.nextElement();\r
315 \r
316       List simwordlist = getWords(code);\r
317       for (Iterator iter = simwordlist.iterator(); iter.hasNext();) {\r
318         String similar = (String) iter.next();\r
319         int distance = EditDistance.getDistance(word, similar, matrix);\r
320         if (distance < configDistance) {\r
321           Word w = new Word(similar, distance);\r
322           result.addElement(w);\r
323         }\r
324       }\r
325     }\r
326     return result;\r
327   }\r
328 \r
329   /**\r
330    * Returns the phonetic code representing the word.\r
331    * @param word The word we want the phonetic code.\r
332    * @return The value of the phonetic code for the word.\r
333    */\r
334   public String getCode(String word) {\r
335     return tf.transform(word);\r
336   }\r
337 \r
338   /**\r
339    * Returns a list of words that have the same phonetic code.\r
340    * @param phoneticCode The phonetic code common to the list of words\r
341    * @return A list of words having the same phonetic code\r
342    */\r
343   @SuppressWarnings("unchecked")\r
344 protected abstract List getWords(String phoneticCode);\r
345 \r
346   /**\r
347    * Returns true if the word is correctly spelled against the current word list.\r
348    */\r
349   @SuppressWarnings("unchecked")\r
350 public boolean isCorrect(String word) {\r
351     List possible = getWords(getCode(word));\r
352     if (possible.contains(word))\r
353       return true;\r
354     //JMH should we always try the lowercase version. If I dont then capitalised\r
355     //words are always returned as incorrect.\r
356     else if (possible.contains(word.toLowerCase()))\r
357       return true;\r
358     return false;\r
359   }\r
360 }\r