View Javadoc

1   /*
2    * Copyright 2006 Outsource Cafe, Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the 'License')
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *    http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an 'AS IS' BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.javagen.agile.oo.naming;
17  
18  import java.util.HashMap;
19  import java.util.Map;
20  import java.util.regex.Pattern;
21  
22  import org.javagen.agile.core.util.RegexRenamer;
23  
24  /***
25   * Translate nouns from singular to plural and back again.  This class supports a two-tier
26   * approach to handle both regular and irregular noun pluralization by supporting both
27   * regular-expression and lookup-table replacement respectively.
28   * <p>
29   * By default to-plural and to-singular grammer rules for English regular nouns
30   * are created.  Additional regular expression-replacement pattern rules can be added
31   * using the <code>addToPluralReplacementPattern</code> and 
32   * <code>addToSingularReplacementPattern</code> methods.
33   * <p>
34   * By default these pluralization rules are added for English language support:
35   * <ol>
36   * <li>sibilant ending rule: dish-dishes, glass-glasses, witch-witches</li>
37   * <li>-oes rule: hero-heroes, potato-potatoes, volcano-volcanoes</li>
38   * <li>-ies rule: cherry-cherries, lady-ladies</li>
39   * <li>-s suffix rule: boy-boys, girl-girls, cat-cats, chair-chairs, judge-judges, phase-phases</li>
40   * </ol>
41   * Irregular plurals must be added explicitly to the <code>singularToPlural</code> 
42   * lookup table.  The reverse <code>pluralToSingularl</code> table will be created 
43   * automatically:
44   * <p>
45   * <pre>
46   * Map&lt;String, String> sing2plural = new HashMap&lt;String, String>();
47   * sing2plural.put("Aircraft","Aircraft");
48   * sing2plural.put("Child","Children");
49   * serviceImpl.setSingularToPlural(sing2plural);
50   * </pre>
51   * Case is preserved for lowercase, uppercase and camelBack naming conventions.  
52   * Lookup keys are not case-sensitive.
53   * 
54   * @see http://en.wikipedia.org/wiki/English_plural
55   * @author Richard Easterling
56   */
57  public class RegularPlurals implements Pluralisation {
58  
59      private RegexRenamer toPluralRegexRenamer;
60      private RegexRenamer toSingularRegexRenamer;
61      private Map<String, String> singularToPlural;
62      private Map<String, String> pluralToSingular;
63      private static Pattern ALL_UPPER_CASE = Pattern.compile("^[^a-z]+$");
64  
65      /***
66       * Setup regular noun to-plural and to-singular grammer rules for English language.
67       */
68      public RegularPlurals() {
69          //sibilant ending rule dish-dishes, glass-glasses, witch-witches
70          addToPluralReplacementPattern("^([a-zA-Z]+)([sS][hH]|[sS][sS]|[tT][cC][hH])$", "$1$2es");
71          addToSingularReplacementPattern("^([a-zA-Z]+)([sS][hH]|[sS][sS]|[tT][cC][hH])[eE][sS]$", "$1$2");
72          //-oes rule: most nouns ending in o preceded by a consonant also form their plurals by adding -es
73          //hero-heroes, potato-potatoes, volcano-volcanoes
74          addToPluralReplacementPattern("^([a-zA-Z]+)([^aAeEiIoOuU])[oO]$", "$1$2oes");
75          addToSingularReplacementPattern("^([a-zA-Z]+)([^aAeEiIoOuU])[oO][eE][sS]$", "$1$2o");
76          //-ies rule: nouns ending in a y preceded by a consonant drop the y and add -ies
77          //cherry-cherries, lady-ladies
78          addToPluralReplacementPattern("^([a-zA-Z]+)([^aAeEiIoOuU])[yY]$", "$1$2ies");
79          addToSingularReplacementPattern("^([a-zA-Z]+)([^aAeEiIoOuU])[iI][eE][sS]$", "$1$2y");
80          //-s suffix rule: 
81          //boy-boys, girl-girls, cat-cats, chair-chairs, judge-judges, phase-phases,
82          addToPluralReplacementPattern("^([a-zA-Z]+)([^sS])$", "$1$2s"); 
83          addToSingularReplacementPattern("^([a-zA-Z]+)[sS]$", "$1");
84      }
85      
86      /***
87       * Convert singular noun to plural form.
88       */
89      public String toPlural(String singular) {
90          if (singular==null || singular.length()==0)
91              throw new java.lang.IllegalArgumentException("singular param can't be null");
92          boolean upCase = isAllUpperCase(singular);
93          String pluralLookup = lookupPlural(singular);
94          if (pluralLookup!=null) {
95              return upCase ? pluralLookup.toUpperCase() : pluralLookup;
96          } else {
97              String plural = toPluralRegexRenamer.rename(singular);
98              if (plural==null) {
99                  return singular; //assume it's already plural
100             } else {
101                 return upCase ? plural.toUpperCase() : plural;
102             }
103         }
104     }
105 
106     /***
107      * Convert plural noun to singular form.
108      */
109     public String toSingular(String plural) {
110         if (plural==null || plural.length()==0)
111             throw new java.lang.IllegalArgumentException("plural param can't be null");
112         String singular = toSingularRegexRenamer.rename(plural);
113         boolean upCase = isAllUpperCase(plural);
114         String singularLookup = lookupSingular(plural);
115         if (singularLookup!=null) {
116             return upCase ? singularLookup.toUpperCase() : singularLookup;
117         } else {
118             if (singular==null) {
119                 return plural; //assume it's already singular
120             } else {
121                 return upCase ? singular.toUpperCase() : singular;
122             }
123         }
124     }
125 
126     ///////////////////////////////////////////////////////////////////////////
127     // support methods:
128     ///////////////////////////////////////////////////////////////////////////
129     
130     /***
131      * Lookup irregular plural noun.  Checks if it's already plural.
132      */
133    protected String lookupPlural(String singular) {
134        if (singularToPlural==null)
135            return null;
136        String key = singular.toLowerCase();
137        String plural = singularToPlural.get(key);
138        return (plural==null && pluralToSingular.containsKey(key)) ? singular : plural;
139    }
140 
141    /***
142     * Lookup irregular singular noun.  Checks if it's already singular.
143     */
144    protected String lookupSingular(String plural) {
145        if (pluralToSingular==null)
146            return null;
147        String key = plural.toLowerCase();
148        String singular = pluralToSingular.get(key);
149        return (singular==null && singularToPlural.containsKey(key)) ? plural : singular;
150    }
151 
152     private boolean isAllUpperCase(String text) {
153         return ALL_UPPER_CASE.matcher(text).matches();
154     }
155 
156     ///////////////////////////////////////////////////////////////////////////
157     // getters and setters:
158     ///////////////////////////////////////////////////////////////////////////
159 
160     /***
161      * Add a regex-replacementPattern pair to pluralization rules.
162      * A reverse rule should be added to the singular rules using <code>addToSingularReplacementPattern</code>.
163      */
164     public void addToPluralReplacementPattern(String regex, String replacementPattern) {
165         if (toPluralRegexRenamer == null)
166             toPluralRegexRenamer = new RegexRenamer();
167         toPluralRegexRenamer.addReplacementPattern(regex, replacementPattern);
168     }
169     
170     /***
171      * Add a regex-replacementPattern pair to pluralization rules.
172      * A reverse rule should be added to the plural rules using <code>addToPluralReplacementPattern</code>.
173      */
174     public void addToSingularReplacementPattern(String regex, String replacementPattern) {
175         if (toSingularRegexRenamer == null)
176             toSingularRegexRenamer = new RegexRenamer();
177         toSingularRegexRenamer.addReplacementPattern(regex, replacementPattern);
178     }
179     
180     public RegexRenamer getToPluralRegexRenamer() {
181         return toPluralRegexRenamer;
182     }
183 
184     public void setToPluralRegexRenamer(RegexRenamer toPluralRegexRenamer) {
185         this.toPluralRegexRenamer = toPluralRegexRenamer;
186     }
187 
188     public RegexRenamer getToSingularRegexRenamer() {
189         return toSingularRegexRenamer;
190     }
191 
192     public void setToSingularRegexRenamer(RegexRenamer toSingularRegexRenamer) {
193         this.toSingularRegexRenamer = toSingularRegexRenamer;
194     }
195 
196     public Map<String, String> getSingularToPlural() {
197         return singularToPlural;
198     }
199 
200     /***
201      * Setter for both <code>singularToPlural</code> and <code>pluralToSingular</code> maps.
202      * @param singularToPlural map of singular-plural nouns.
203      */
204     public void setSingularToPlural(Map<String, String> singularToPlural) {
205         if (singularToPlural==null) {
206             this.singularToPlural = null;
207         } else {
208             this.singularToPlural = new HashMap<String, String>(singularToPlural.size());
209             for(Map.Entry<String, String> entity : singularToPlural.entrySet()) {
210                 this.singularToPlural.put(entity.getKey().toLowerCase(), entity.getValue());
211             }
212         }
213         //set up reverse lookup - from plural to singular
214         if (singularToPlural==null) {
215             pluralToSingular = null;
216         } else {
217             pluralToSingular = new HashMap<String, String>(singularToPlural.size());
218             for(Map.Entry<String, String> entity : singularToPlural.entrySet()) {
219                 pluralToSingular.put(entity.getValue().toLowerCase(), entity.getKey());
220             }
221         }
222     }
223 
224     public Map<String, String> getPluralToSingular() {
225         return pluralToSingular;
226     }
227 
228 }