1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.plugin.autotag

File FrenchStemmer.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart0.png
83% of files have more coverage

Code metrics

158
268
20
1
694
449
144
0.54
13.4
20
7.2

Classes

Class Line # Actions
FrenchStemmer 30 268 0% 144 446
0.00%
 

Contributing tests

No tests hitting this source file were found.

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.plugin.autotag;
21   
22    /**
23    * A stemmer for French words. The algorithm is based on the work of Dr Martin Porter on his snowball project<br>
24    * refer to http://snowball.sourceforge.net/french/stemmer.html<br>
25    * (French stemming algorithm) for details
26    *
27    * @version $Id: 43711ae634469c5964aa9f24b5a78cd944bf078b $
28    * @author Patrick Talbot
29    */
 
30    public class FrenchStemmer
31    {
32    /**
33    * Buffer for the terms while stemming them.
34    */
35    private StringBuilder sb = new StringBuilder();
36   
37    /**
38    * A temporary buffer, used to reconstruct R2.
39    */
40    private StringBuilder tb = new StringBuilder();
41   
42    /**
43    * Region R0 is equal to the whole buffer.
44    */
45    private String R0;
46   
47    /**
48    * Region RV "If the word begins with two vowels, RV is the region after the third letter, otherwise the region
49    * after the first vowel not at the beginning of the word, or the end of the word if these positions cannot be
50    * found.".
51    */
52    private String RV;
53   
54    /**
55    * Region R1 "R1 is the region after the first non-vowel following a vowel or is the null region at the end of the
56    * word if there is no such non-vowel".
57    */
58    private String R1;
59   
60    /**
61    * Region R2 "R2 is the region after the first non-vowel in R1 following a vowel or is the null region at the end of
62    * the word if there is no such non-vowel".
63    */
64    private String R2;
65   
66    /**
67    * Set to true if we need to perform step 2.
68    */
69    private boolean suite;
70   
71    /**
72    * Set to true if the buffer was modified.
73    */
74    private boolean modified;
75   
76    /**
77    * Stemms the given term to a unique <tt>discriminator</tt>.
78    *
79    * @param term java.langString The term that should be stemmed
80    * @return java.lang.String Discriminator for <tt>term</tt>
81    */
 
82  0 toggle public String stem(String term)
83    {
84  0 if (!isStemmable(term)) {
85  0 return term;
86    }
87   
88    // Use lowercase for medium stemming.
89  0 term = term.toLowerCase();
90   
91    // Reset the StringBuffer.
92  0 this.sb.delete(0, this.sb.length());
93  0 this.sb.insert(0, term);
94   
95    // reset the booleans
96  0 this.modified = false;
97  0 this.suite = false;
98   
99  0 this.sb = treatVowels(this.sb);
100   
101  0 setStrings();
102   
103  0 step1();
104   
105  0 if (!this.modified || this.suite) {
106  0 if (this.RV != null) {
107  0 this.suite = step2a();
108  0 if (!this.suite) {
109  0 step2b();
110    }
111    }
112    }
113   
114  0 if (this.modified || this.suite) {
115  0 step3();
116    } else {
117  0 step4();
118    }
119   
120  0 step5();
121   
122  0 step6();
123   
124  0 return this.sb.toString();
125    }
126   
127    /**
128    * Sets the search region Strings. It needs to be done each time the buffer was modified.
129    */
 
130  0 toggle private void setStrings()
131    {
132    // set the strings
133  0 this.R0 = this.sb.toString();
134  0 this.RV = retrieveRV(this.sb);
135  0 this.R1 = retrieveR(this.sb);
136  0 if (this.R1 != null) {
137  0 this.tb.delete(0, this.tb.length());
138  0 this.tb.insert(0, this.R1);
139  0 this.R2 = retrieveR(this.tb);
140    } else {
141  0 this.R2 = null;
142    }
143    }
144   
145    /**
146    * First step of the Porter Algorithmn. Refer to http://snowball.sourceforge.net/french/stemmer.html for an
147    * explanation.
148    */
 
149  0 toggle private void step1()
150    {
151  0 String[] suffix = {"ances", "iqUes", "ismes", "ables", "istes", "ance", "iqUe", "isme", "able", "iste"};
152  0 deleteFrom(this.R2, suffix);
153   
154  0 replaceFrom(this.R2, new String[] {"logies", "logie"}, "log");
155  0 replaceFrom(this.R2, new String[] {"usions", "utions", "usion", "ution"}, "u");
156  0 replaceFrom(this.R2, new String[] {"ences", "ence"}, "ent");
157   
158  0 String[] search = {"atrices", "ateurs", "ations", "atrice", "ateur", "ation"};
159  0 deleteButSuffixFromElseReplace(this.R2, search, "ic", true, this.R0, "iqU");
160   
161  0 deleteButSuffixFromElseReplace(this.R2, new String[] {"ements", "ement"}, "eus", false, this.R0,
162    "eux");
163  0 deleteButSuffixFrom(this.R2, new String[] {"ements", "ement"}, "ativ", false);
164  0 deleteButSuffixFrom(this.R2, new String[] {"ements", "ement"}, "iv", false);
165  0 deleteButSuffixFrom(this.R2, new String[] {"ements", "ement"}, "abl", false);
166  0 deleteButSuffixFrom(this.R2, new String[] {"ements", "ement"}, "iqU", false);
167   
168  0 deleteFromIfTestVowelBeforeIn(this.R1, new String[] {"issements", "issement"}, false, this.R0);
169  0 deleteFrom(this.RV, new String[] {"ements", "ement"});
170   
171  0 deleteButSuffixFromElseReplace(this.R2, new String[] {"it\u00e9s", "it\u00e9"}, "abil", false,
172    this.R0, "abl");
173  0 deleteButSuffixFromElseReplace(this.R2, new String[] {"it\u00e9s", "it\u00e9"}, "ic", false,
174    this.R0, "iqU");
175  0 deleteButSuffixFrom(this.R2, new String[] {"it\u00e9s", "it\u00e9"}, "iv", true);
176   
177  0 String[] autre = {"ifs", "ives", "if", "ive"};
178  0 deleteButSuffixFromElseReplace(this.R2, autre, "icat", false, this.R0, "iqU");
179  0 deleteButSuffixFromElseReplace(this.R2, autre, "at", true, this.R2, "iqU");
180   
181  0 replaceFrom(this.R0, new String[] {"eaux"}, "eau");
182   
183  0 replaceFrom(this.R1, new String[] {"aux"}, "al");
184   
185  0 deleteButSuffixFromElseReplace(this.R2, new String[] {"euses", "euse"}, "", true, this.R1, "eux");
186   
187  0 deleteFrom(this.R2, new String[] {"eux"});
188   
189    // if one of the next steps is performed, we will need to perform step2a
190  0 boolean temp = false;
191  0 temp = replaceFrom(this.RV, new String[] {"amment"}, "ant");
192  0 if (temp) {
193  0 this.suite = true;
194    }
195  0 temp = replaceFrom(this.RV, new String[] {"emment"}, "ent");
196  0 if (temp) {
197  0 this.suite = true;
198    }
199  0 temp = deleteFromIfTestVowelBeforeIn(this.RV, new String[] {"ments", "ment"}, true, this.RV);
200  0 if (temp) {
201  0 this.suite = true;
202    }
203    }
204   
205    /**
206    * Second step (A) of the Porter Algorithmn. Will be performed if nothing changed from the first step or changed
207    * were done in the amment, emment, ments or ment suffixes. Refer to
208    * http://snowball.sourceforge.net/french/stemmer.html for an explanation.
209    *
210    * @return boolean - true if something changed in the StringBuffer
211    */
 
212  0 toggle private boolean step2a()
213    {
214  0 String[] search =
215    {"\u00eemes", "\u00eetes", "iraIent", "irait", "irais", "irai", "iras", "ira",
216    "irent", "iriez", "irez", "irions", "irons", "iront", "issaIent", "issais",
217    "issantes", "issante", "issants", "issant", "issait", "issais", "issions", "issons",
218    "issiez", "issez", "issent", "isses", "isse", "ir", "is", "\u00eet", "it", "ies",
219    "ie", "i"};
220  0 return deleteFromIfTestVowelBeforeIn(this.RV, search, false, this.RV);
221    }
222   
223    /**
224    * Second step (B) of the Porter Algorithmn. Will be performed if step 2 A was performed unsuccessfully. Refer to
225    * http://snowball.sourceforge.net/french/stemmer.html for an explanation.
226    */
 
227  0 toggle private void step2b()
228    {
229  0 String[] suffix =
230    {"eraIent", "erais", "erait", "erai", "eras", "erions", "eriez", "erons", "eront",
231    "erez", "\u00e8rent", "era", "\u00e9es", "iez", "\u00e9e", "\u00e9s", "er", "ez",
232    "\u00e9"};
233  0 deleteFrom(this.RV, suffix);
234   
235  0 String[] search =
236    {"assions", "assiez", "assent", "asses", "asse", "aIent", "antes", "aIent", "Aient",
237    "ante", "\u00e2mes", "\u00e2tes", "ants", "ant", "ait", "a\u00eet", "ais", "Ait",
238    "A\u00eet", "Ais", "\u00e2t", "as", "ai", "Ai", "a"};
239  0 deleteButSuffixFrom(this.RV, search, "e", true);
240   
241  0 deleteFrom(this.R2, new String[] {"ions"});
242    }
243   
244    /**
245    * Third step of the Porter Algorithmn. Refer to http://snowball.sourceforge.net/french/stemmer.html for an
246    * explanation.
247    */
 
248  0 toggle private void step3()
249    {
250  0 if (this.sb.length() > 0) {
251  0 char ch = this.sb.charAt(this.sb.length() - 1);
252  0 if (ch == 'Y') {
253  0 this.sb.setCharAt(this.sb.length() - 1, 'i');
254  0 setStrings();
255  0 } else if (ch == '\u00E7') {
256  0 this.sb.setCharAt(this.sb.length() - 1, 'c');
257  0 setStrings();
258    }
259    }
260    }
261   
262    /**
263    * Fourth step of the Porter Algorithmn. Refer to http://snowball.sourceforge.net/french/stemmer.html for an
264    * explanation.
265    */
 
266  0 toggle private void step4()
267    {
268  0 if (this.sb.length() > 1) {
269  0 char ch = this.sb.charAt(this.sb.length() - 1);
270  0 if (ch == 's') {
271  0 char b = this.sb.charAt(this.sb.length() - 2);
272  0 if (b != 'a' && b != 'i' && b != 'o' && b != 'u' && b != '\u00E8' && b != 's') {
273  0 this.sb.delete(this.sb.length() - 1, this.sb.length());
274  0 setStrings();
275    }
276    }
277    }
278  0 boolean found = deleteFromIfPrecededIn(this.R2, new String[] {"ion"}, this.RV, "s");
279  0 if (!found) {
280  0 found = deleteFromIfPrecededIn(this.R2, new String[] {"ion"}, this.RV, "t");
281    }
282   
283  0 replaceFrom(this.RV, new String[] {"I\u00e8re", "i\u00e8re", "Ier", "ier"}, "i");
284  0 deleteFrom(this.RV, new String[] {"e"});
285  0 deleteFromIfPrecededIn(this.RV, new String[] {"\u00eb"}, this.R0, "gu");
286    }
287   
288    /**
289    * Fifth step of the Porter Algorithmn. Refer to http://snowball.sourceforge.net/french/stemmer.html for an
290    * explanation.
291    */
 
292  0 toggle private void step5()
293    {
294  0 if (this.R0 != null) {
295  0 if (this.R0.endsWith("enn") || this.R0.endsWith("onn") || this.R0.endsWith("ett")
296    || this.R0.endsWith("ell") || this.R0.endsWith("eill")) {
297  0 this.sb.delete(this.sb.length() - 1, this.sb.length());
298  0 setStrings();
299    }
300    }
301    }
302   
303    /**
304    * Sixth (and last!) step of the Porter Algorithmn. Refer to http://snowball.sourceforge.net/french/stemmer.html for
305    * an explanation.
306    */
 
307  0 toggle private void step6()
308    {
309  0 if (this.R0 != null && this.R0.length() > 0) {
310  0 boolean seenVowel = false;
311  0 boolean seenConson = false;
312  0 int pos = -1;
313  0 for (int i = this.R0.length() - 1; i > -1; i--) {
314  0 char ch = this.R0.charAt(i);
315  0 if (isVowel(ch)) {
316  0 if (!seenVowel) {
317  0 if (ch == '\u00E9') {
318  0 pos = i;
319  0 break;
320    }
321    }
322  0 seenVowel = true;
323    } else {
324  0 if (seenVowel) {
325  0 break;
326    } else {
327  0 seenConson = true;
328    }
329    }
330    }
331  0 if (pos > -1 && seenConson && !seenVowel) {
332  0 this.sb.setCharAt(pos, 'e');
333    }
334    }
335    }
336   
337    /**
338    * Delete a suffix searched in zone "source" if zone "from" contains prefix + search string.
339    *
340    * @param source java.lang.String - the primary source zone for search
341    * @param search java.lang.String[] - the strings to search for suppression
342    * @param from java.lang.String - the secondary source zone for search
343    * @param prefix java.lang.String - the prefix to add to the search string to test
344    * @return boolean - true if modified
345    */
 
346  0 toggle private boolean deleteFromIfPrecededIn(String source, String[] search, String from,
347    String prefix)
348    {
349  0 boolean found = false;
350  0 if (source != null) {
351  0 for (int i = 0; i < search.length; i++) {
352  0 if (source.endsWith(search[i])) {
353  0 if (from != null && from.endsWith(prefix + search[i])) {
354  0 this.sb.delete(this.sb.length() - search[i].length(), this.sb.length());
355  0 found = true;
356  0 setStrings();
357  0 break;
358    }
359    }
360    }
361    }
362  0 return found;
363    }
364   
365    /**
366    * Delete a suffix searched in zone "source" if the preceding letter is (or isn't) a vowel
367    *
368    * @param source java.lang.String - the primary source zone for search
369    * @param search java.lang.String[] - the strings to search for suppression
370    * @param vowel boolean - true if we need a vowel before the search string
371    * @param from java.lang.String - the secondary source zone for search (where vowel could be)
372    * @return boolean - true if modified
373    */
 
374  0 toggle private boolean deleteFromIfTestVowelBeforeIn(String source, String[] search, boolean vowel,
375    String from)
376    {
377  0 boolean found = false;
378  0 if (source != null && from != null) {
379  0 for (int i = 0; i < search.length; i++) {
380  0 if (source.endsWith(search[i])) {
381  0 if ((search[i].length() + 1) <= from.length()) {
382  0 boolean test = isVowel(this.sb.charAt(this.sb.length() - (search[i].length() + 1)));
383  0 if (test == vowel) {
384  0 this.sb.delete(this.sb.length() - search[i].length(), this.sb.length());
385  0 this.modified = true;
386  0 found = true;
387  0 setStrings();
388  0 break;
389    }
390    }
391    }
392    }
393    }
394  0 return found;
395    }
396   
397    /**
398    * Delete a suffix searched in zone "source" if preceded by the prefix
399    *
400    * @param source java.lang.String - the primary source zone for search
401    * @param search java.lang.String[] - the strings to search for suppression
402    * @param prefix java.lang.String - the prefix to add to the search string to test
403    * @param without boolean - true if it will be deleted even without prefix found
404    */
 
405  0 toggle private void deleteButSuffixFrom(String source, String[] search, String prefix,
406    boolean without)
407    {
408  0 if (source != null) {
409  0 for (int i = 0; i < search.length; i++) {
410  0 if (source.endsWith(prefix + search[i])) {
411  0 this.sb.delete(this.sb.length() - (prefix.length() + search[i].length()), this.sb.length());
412  0 this.modified = true;
413  0 setStrings();
414  0 break;
415  0 } else if (without && source.endsWith(search[i])) {
416  0 this.sb.delete(this.sb.length() - search[i].length(), this.sb.length());
417  0 this.modified = true;
418  0 setStrings();
419  0 break;
420    }
421    }
422    }
423    }
424   
425    /**
426    * Delete a suffix searched in zone "source" if preceded by prefix<br>
427    * or replace it with the replace string if preceded by the prefix in the zone "from"<br>
428    * or delete the suffix if specified
429    *
430    * @param source java.lang.String - the primary source zone for search
431    * @param search java.lang.String[] - the strings to search for suppression
432    * @param prefix java.lang.String - the prefix to add to the search string to test
433    * @param without boolean - true if it will be deleted even without prefix found
434    */
 
435  0 toggle private void deleteButSuffixFromElseReplace(String source, String[] search, String prefix,
436    boolean without, String from, String replace)
437    {
438  0 if (source != null) {
439  0 for (int i = 0; i < search.length; i++) {
440  0 if (source.endsWith(prefix + search[i])) {
441  0 this.sb.delete(this.sb.length() - (prefix.length() + search[i].length()), this.sb.length());
442  0 this.modified = true;
443  0 setStrings();
444  0 break;
445  0 } else if (from != null && from.endsWith(prefix + search[i])) {
446  0 this.sb.replace(this.sb.length() - (prefix.length() + search[i].length()), this.sb.length(),
447    replace);
448  0 this.modified = true;
449  0 setStrings();
450  0 break;
451  0 } else if (without && source.endsWith(search[i])) {
452  0 this.sb.delete(this.sb.length() - search[i].length(), this.sb.length());
453  0 this.modified = true;
454  0 setStrings();
455  0 break;
456    }
457    }
458    }
459    }
460   
461    /**
462    * Replace a search string with another within the source zone
463    *
464    * @param source java.lang.String - the source zone for search
465    * @param search java.lang.String[] - the strings to search for replacement
466    * @param replace java.lang.String - the replacement string
467    */
 
468  0 toggle private boolean replaceFrom(String source, String[] search, String replace)
469    {
470  0 boolean found = false;
471  0 if (source != null) {
472  0 for (int i = 0; i < search.length; i++) {
473  0 if (source.endsWith(search[i])) {
474  0 this.sb.replace(this.sb.length() - search[i].length(), this.sb.length(), replace);
475  0 this.modified = true;
476  0 found = true;
477  0 setStrings();
478  0 break;
479    }
480    }
481    }
482  0 return found;
483    }
484   
485    /**
486    * Delete a search string within the source zone
487    *
488    * @param source the source zone for search
489    * @param suffix the strings to search for suppression
490    */
 
491  0 toggle private void deleteFrom(String source, String[] suffix)
492    {
493  0 if (source != null) {
494  0 for (int i = 0; i < suffix.length; i++) {
495  0 if (source.endsWith(suffix[i])) {
496  0 this.sb.delete(this.sb.length() - suffix[i].length(), this.sb.length());
497  0 this.modified = true;
498  0 setStrings();
499  0 break;
500    }
501    }
502    }
503    }
504   
505    /**
506    * Test if a char is a french vowel, including accentuated ones
507    *
508    * @param ch the char to test
509    * @return boolean - true if the char is a vowel
510    */
 
511  0 toggle private boolean isVowel(char ch)
512    {
513  0 switch (ch) {
514  0 case 'a':
515  0 case 'e':
516  0 case 'i':
517  0 case 'o':
518  0 case 'u':
519  0 case 'y':
520  0 case '\u00E0': // A WITH GRAVE
521  0 case '\u00E2': // A WITH CIRCUMFLEX
522  0 case '\u00E8': // E WITH GRAVE
523  0 case '\u00E9': // E WITH ACUTE
524  0 case '\u00EA': // E WITH CIRCUMFLEX
525  0 case '\u00EB': // E WITH DIAERESIS
526  0 case '\u00EE': // I WITH CIRCUMFLEX
527  0 case '\u00EF': // I WITH DIAERESIS
528  0 case '\u00F4': // O WITH CIRCUMFLEX
529  0 case '\u00F9': // U WITH GRAVE
530  0 case '\u00FB': // U WITH CIRCUMFLEX
531  0 case '\u00FC': // U WITH DIAERESIS
532  0 return true;
533  0 default:
534  0 return false;
535    }
536    }
537   
538    /**
539    * Retrieve the "R zone" (1 or 2 depending on the buffer) and return the corresponding string<br>
540    * "R is the region after the first non-vowel following a vowel or is the null region at the end of the word if
541    * there is no such non-vowel"<br>
542    *
543    * @param buffer java.lang.StringBuffer - the in buffer
544    * @return java.lang.String - the resulting string
545    */
 
546  0 toggle private String retrieveR(StringBuilder buffer)
547    {
548  0 int len = buffer.length();
549  0 int pos = -1;
550  0 for (int c = 0; c < len; c++) {
551  0 if (isVowel(buffer.charAt(c))) {
552  0 pos = c;
553  0 break;
554    }
555    }
556  0 if (pos > -1) {
557  0 int consonne = -1;
558  0 for (int c = pos; c < len; c++) {
559  0 if (!isVowel(buffer.charAt(c))) {
560  0 consonne = c;
561  0 break;
562    }
563    }
564  0 if (consonne > -1 && (consonne + 1) < len) {
565  0 return buffer.substring(consonne + 1, len);
566    } else {
567  0 return null;
568    }
569    } else {
570  0 return null;
571    }
572    }
573   
574    /**
575    * Retrieve the "RV zone" from a buffer an return the corresponding string<br>
576    * "If the word begins with two vowels, RV is the region after the third letter, otherwise the region after the
577    * first vowel not at the beginning of the word, or the end of the word if these positions cannot be found."<br>
578    *
579    * @param buffer java.lang.StringBuffer - the in buffer
580    * @return java.lang.String - the resulting string
581    */
 
582  0 toggle private String retrieveRV(StringBuilder buffer)
583    {
584  0 int len = buffer.length();
585  0 if (buffer.length() > 3) {
586  0 if (isVowel(buffer.charAt(0)) && isVowel(buffer.charAt(1))) {
587  0 return buffer.substring(3, len);
588    } else {
589  0 int pos = 0;
590  0 for (int c = 1; c < len; c++) {
591  0 if (isVowel(buffer.charAt(c))) {
592  0 pos = c;
593  0 break;
594    }
595    }
596  0 if (pos + 1 < len) {
597  0 return buffer.substring(pos + 1, len);
598    } else {
599  0 return null;
600    }
601    }
602    } else {
603  0 return null;
604    }
605    }
606   
607    /**
608    * Turns u and i preceded AND followed by a vowel to UpperCase<br>
609    * Turns y preceded OR followed by a vowel to UpperCase<br>
610    * Turns u preceded by q to UpperCase<br>
611    *
612    * @param buffer java.util.StringBuffer - the buffer to treat
613    * @return java.util.StringBuffer - the treated buffer
614    */
 
615  0 toggle private StringBuilder treatVowels(StringBuilder buffer)
616    {
617  0 for (int c = 0; c < buffer.length(); c++) {
618  0 char ch = buffer.charAt(c);
619   
620  0 if (c == 0) // first char
621    {
622  0 if (buffer.length() > 1) {
623  0 if (ch == 'y' && isVowel(buffer.charAt(c + 1))) {
624  0 buffer.setCharAt(c, 'Y');
625    }
626    }
627  0 } else if (c == buffer.length() - 1) // last char
628    {
629  0 if (ch == 'u' && buffer.charAt(c - 1) == 'q') {
630  0 buffer.setCharAt(c, 'U');
631    }
632  0 if (ch == 'y' && isVowel(buffer.charAt(c - 1))) {
633  0 buffer.setCharAt(c, 'Y');
634    }
635    } else // other cases
636    {
637  0 if (ch == 'u') {
638  0 if (buffer.charAt(c - 1) == 'q') {
639  0 buffer.setCharAt(c, 'U');
640  0 } else if (isVowel(buffer.charAt(c - 1)) && isVowel(buffer.charAt(c + 1))) {
641  0 buffer.setCharAt(c, 'U');
642    }
643    }
644  0 if (ch == 'i') {
645  0 if (isVowel(buffer.charAt(c - 1)) && isVowel(buffer.charAt(c + 1))) {
646  0 buffer.setCharAt(c, 'I');
647    }
648    }
649  0 if (ch == 'y') {
650  0 if (isVowel(buffer.charAt(c - 1)) || isVowel(buffer.charAt(c + 1))) {
651  0 buffer.setCharAt(c, 'Y');
652    }
653    }
654    }
655    }
656   
657  0 return buffer;
658    }
659   
660    /**
661    * Checks a term if it can be processed correctly.
662    *
663    * @return boolean - true if, and only if, the given term consists in letters.
664    */
 
665  0 toggle private boolean isStemmable(String term)
666    {
667  0 boolean upper = false;
668  0 int first = -1;
669  0 for (int c = 0; c < term.length(); c++) {
670    // Discard terms that contain non-letter characters.
671  0 if (!Character.isLetter(term.charAt(c))) {
672  0 return false;
673    }
674    // Discard terms that contain multiple uppercase letters.
675  0 if (Character.isUpperCase(term.charAt(c))) {
676  0 if (upper) {
677  0 return false;
678    }
679    // First encountered uppercase letter, set flag and save
680    // position.
681    else {
682  0 first = c;
683  0 upper = true;
684    }
685    }
686    }
687    // Discard the term if it contains a single uppercase letter that
688    // is not starting the term.
689  0 if (first > 0) {
690  0 return false;
691    }
692  0 return true;
693    }
694    }