1
2
3
4
5
6
7
8
9
10
11
12
13
14
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<String, String> sing2plural = new HashMap<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
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
73
74 addToPluralReplacementPattern("^([a-zA-Z]+)([^aAeEiIoOuU])[oO]$", "$1$2oes");
75 addToSingularReplacementPattern("^([a-zA-Z]+)([^aAeEiIoOuU])[oO][eE][sS]$", "$1$2o");
76
77
78 addToPluralReplacementPattern("^([a-zA-Z]+)([^aAeEiIoOuU])[yY]$", "$1$2ies");
79 addToSingularReplacementPattern("^([a-zA-Z]+)([^aAeEiIoOuU])[iI][eE][sS]$", "$1$2y");
80
81
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;
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;
120 } else {
121 return upCase ? singular.toUpperCase() : singular;
122 }
123 }
124 }
125
126
127
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
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
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 }