1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 package org.modeshape.common.text;
25
26 import java.util.HashSet;
27 import java.util.LinkedList;
28 import java.util.Set;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31 import net.jcip.annotations.ThreadSafe;
32 import org.modeshape.common.util.CheckArg;
33
34
35
36
37
38
39
40 @ThreadSafe
41 public class Inflector {
42
43 protected static final Inflector INSTANCE = new Inflector();
44
45 public static final Inflector getInstance() {
46 return INSTANCE;
47 }
48
49 protected class Rule {
50
51 protected final String expression;
52 protected final Pattern expressionPattern;
53 protected final String replacement;
54
55 protected Rule( String expression,
56 String replacement ) {
57 this.expression = expression;
58 this.replacement = replacement != null ? replacement : "";
59 this.expressionPattern = Pattern.compile(this.expression, Pattern.CASE_INSENSITIVE);
60 }
61
62
63
64
65
66
67
68
69 protected String apply( String input ) {
70 Matcher matcher = this.expressionPattern.matcher(input);
71 if (!matcher.find()) return null;
72 return matcher.replaceAll(this.replacement);
73 }
74
75 @Override
76 public int hashCode() {
77 return expression.hashCode();
78 }
79
80 @Override
81 public boolean equals( Object obj ) {
82 if (obj == this) return true;
83 if (obj != null && obj.getClass() == this.getClass()) {
84 final Rule that = (Rule)obj;
85 if (this.expression.equalsIgnoreCase(that.expression)) return true;
86 }
87 return false;
88 }
89
90 @Override
91 public String toString() {
92 return expression + ", " + replacement;
93 }
94 }
95
96 private LinkedList<Rule> plurals = new LinkedList<Rule>();
97 private LinkedList<Rule> singulars = new LinkedList<Rule>();
98
99
100
101
102 private final Set<String> uncountables = new HashSet<String>();
103
104 public Inflector() {
105 initialize();
106 }
107
108 protected Inflector( Inflector original ) {
109 this.plurals.addAll(original.plurals);
110 this.singulars.addAll(original.singulars);
111 this.uncountables.addAll(original.uncountables);
112 }
113
114 @Override
115 public Inflector clone() {
116 return new Inflector(this);
117 }
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146 public String pluralize( Object word ) {
147 if (word == null) return null;
148 String wordStr = word.toString().trim();
149 if (wordStr.length() == 0) return wordStr;
150 if (isUncountable(wordStr)) return wordStr;
151 for (Rule rule : this.plurals) {
152 String result = rule.apply(wordStr);
153 if (result != null) return result;
154 }
155 return wordStr;
156 }
157
158 public String pluralize( Object word,
159 int count ) {
160 if (word == null) return null;
161 if (count == 1 || count == -1) {
162 return word.toString();
163 }
164 return pluralize(word);
165 }
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190 public String singularize( Object word ) {
191 if (word == null) return null;
192 String wordStr = word.toString().trim();
193 if (wordStr.length() == 0) return wordStr;
194 if (isUncountable(wordStr)) return wordStr;
195 for (Rule rule : this.singulars) {
196 String result = rule.apply(wordStr);
197 if (result != null) return result;
198 }
199 return wordStr;
200 }
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223 public String lowerCamelCase( String lowerCaseAndUnderscoredWord,
224 char... delimiterChars ) {
225 return camelCase(lowerCaseAndUnderscoredWord, false, delimiterChars);
226 }
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249 public String upperCamelCase( String lowerCaseAndUnderscoredWord,
250 char... delimiterChars ) {
251 return camelCase(lowerCaseAndUnderscoredWord, true, delimiterChars);
252 }
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281 public String camelCase( String lowerCaseAndUnderscoredWord,
282 boolean uppercaseFirstLetter,
283 char... delimiterChars ) {
284 if (lowerCaseAndUnderscoredWord == null) return null;
285 lowerCaseAndUnderscoredWord = lowerCaseAndUnderscoredWord.trim();
286 if (lowerCaseAndUnderscoredWord.length() == 0) return "";
287 if (uppercaseFirstLetter) {
288 String result = lowerCaseAndUnderscoredWord;
289
290 if (delimiterChars != null) {
291 for (char delimiterChar : delimiterChars) {
292 result = result.replace(delimiterChar, '_');
293 }
294 }
295
296
297 return replaceAllWithUppercase(result, "(^|_)(.)", 2);
298 }
299 if (lowerCaseAndUnderscoredWord.length() < 2) return lowerCaseAndUnderscoredWord;
300 return "" + Character.toLowerCase(lowerCaseAndUnderscoredWord.charAt(0))
301 + camelCase(lowerCaseAndUnderscoredWord, true, delimiterChars).substring(1);
302 }
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325 public String underscore( String camelCaseWord,
326 char... delimiterChars ) {
327 if (camelCaseWord == null) return null;
328 String result = camelCaseWord.trim();
329 if (result.length() == 0) return "";
330 result = result.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2");
331 result = result.replaceAll("([a-z\\d])([A-Z])", "$1_$2");
332 result = result.replace('-', '_');
333 if (delimiterChars != null) {
334 for (char delimiterChar : delimiterChars) {
335 result = result.replace(delimiterChar, '_');
336 }
337 }
338 return result.toLowerCase();
339 }
340
341
342
343
344
345
346
347 public String capitalize( String words ) {
348 if (words == null) return null;
349 String result = words.trim();
350 if (result.length() == 0) return "";
351 if (result.length() == 1) return result.toUpperCase();
352 return "" + Character.toUpperCase(result.charAt(0)) + result.substring(1).toLowerCase();
353 }
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373 public String humanize( String lowerCaseAndUnderscoredWords,
374 String... removableTokens ) {
375 if (lowerCaseAndUnderscoredWords == null) return null;
376 String result = lowerCaseAndUnderscoredWords.trim();
377 if (result.length() == 0) return "";
378
379 result = result.replaceAll("_id$", "");
380
381 if (removableTokens != null) {
382 for (String removableToken : removableTokens) {
383 result = result.replaceAll(removableToken, "");
384 }
385 }
386 result = result.replaceAll("_+", " ");
387 return capitalize(result);
388 }
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408 public String titleCase( String words,
409 String... removableTokens ) {
410 String result = humanize(words, removableTokens);
411 result = replaceAllWithUppercase(result, "\\b([a-z])", 1);
412 return result;
413 }
414
415
416
417
418
419
420
421
422 public String ordinalize( int number ) {
423 int remainder = number % 100;
424 String numberStr = Integer.toString(number);
425 if (11 <= number && number <= 13) return numberStr + "th";
426 remainder = number % 10;
427 if (remainder == 1) return numberStr + "st";
428 if (remainder == 2) return numberStr + "nd";
429 if (remainder == 3) return numberStr + "rd";
430 return numberStr + "th";
431 }
432
433
434
435
436
437
438
439
440
441
442
443
444 public boolean isUncountable( String word ) {
445 if (word == null) return false;
446 String trimmedLower = word.trim().toLowerCase();
447 return this.uncountables.contains(trimmedLower);
448 }
449
450
451
452
453
454
455 public Set<String> getUncountables() {
456 return uncountables;
457 }
458
459 public void addPluralize( String rule,
460 String replacement ) {
461 final Rule pluralizeRule = new Rule(rule, replacement);
462 this.plurals.addFirst(pluralizeRule);
463 }
464
465 public void addSingularize( String rule,
466 String replacement ) {
467 final Rule singularizeRule = new Rule(rule, replacement);
468 this.singulars.addFirst(singularizeRule);
469 }
470
471 public void addIrregular( String singular,
472 String plural ) {
473 CheckArg.isNotEmpty(singular, "singular rule");
474 CheckArg.isNotEmpty(plural, "plural rule");
475 String singularRemainder = singular.length() > 1 ? singular.substring(1) : "";
476 String pluralRemainder = plural.length() > 1 ? plural.substring(1) : "";
477 addPluralize("(" + singular.charAt(0) + ")" + singularRemainder + "$", "$1" + pluralRemainder);
478 addSingularize("(" + plural.charAt(0) + ")" + pluralRemainder + "$", "$1" + singularRemainder);
479 }
480
481 public void addUncountable( String... words ) {
482 if (words == null || words.length == 0) return;
483 for (String word : words) {
484 if (word != null) uncountables.add(word.trim().toLowerCase());
485 }
486 }
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503 protected static String replaceAllWithUppercase( String input,
504 String regex,
505 int groupNumberToUppercase ) {
506 Pattern underscoreAndDotPattern = Pattern.compile(regex);
507 Matcher matcher = underscoreAndDotPattern.matcher(input);
508 StringBuffer sb = new StringBuffer();
509 while (matcher.find()) {
510 matcher.appendReplacement(sb, matcher.group(groupNumberToUppercase).toUpperCase());
511 }
512 matcher.appendTail(sb);
513 return sb.toString();
514 }
515
516
517
518
519 public void clear() {
520 this.uncountables.clear();
521 this.plurals.clear();
522 this.singulars.clear();
523 }
524
525 protected void initialize() {
526 Inflector inflect = this;
527 inflect.addPluralize("$", "s");
528 inflect.addPluralize("s$", "s");
529 inflect.addPluralize("(ax|test)is$", "$1es");
530 inflect.addPluralize("(octop|vir)us$", "$1i");
531 inflect.addPluralize("(octop|vir)i$", "$1i");
532 inflect.addPluralize("(alias|status)$", "$1es");
533 inflect.addPluralize("(bu)s$", "$1ses");
534 inflect.addPluralize("(buffal|tomat)o$", "$1oes");
535 inflect.addPluralize("([ti])um$", "$1a");
536 inflect.addPluralize("([ti])a$", "$1a");
537 inflect.addPluralize("sis$", "ses");
538 inflect.addPluralize("(?:([^f])fe|([lr])f)$", "$1$2ves");
539 inflect.addPluralize("(hive)$", "$1s");
540 inflect.addPluralize("([^aeiouy]|qu)y$", "$1ies");
541 inflect.addPluralize("(x|ch|ss|sh)$", "$1es");
542 inflect.addPluralize("(matr|vert|ind)ix|ex$", "$1ices");
543 inflect.addPluralize("([m|l])ouse$", "$1ice");
544 inflect.addPluralize("([m|l])ice$", "$1ice");
545 inflect.addPluralize("^(ox)$", "$1en");
546 inflect.addPluralize("(quiz)$", "$1zes");
547
548 inflect.addPluralize("(people|men|children|sexes|moves|stadiums)$", "$1");
549 inflect.addPluralize("(oxen|octopi|viri|aliases|quizzes)$", "$1");
550
551 inflect.addSingularize("s$", "");
552 inflect.addSingularize("(s|si|u)s$", "$1s");
553 inflect.addSingularize("(n)ews$", "$1ews");
554 inflect.addSingularize("([ti])a$", "$1um");
555 inflect.addSingularize("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis");
556 inflect.addSingularize("(^analy)ses$", "$1sis");
557 inflect.addSingularize("(^analy)sis$", "$1sis");
558 inflect.addSingularize("([^f])ves$", "$1fe");
559 inflect.addSingularize("(hive)s$", "$1");
560 inflect.addSingularize("(tive)s$", "$1");
561 inflect.addSingularize("([lr])ves$", "$1f");
562 inflect.addSingularize("([^aeiouy]|qu)ies$", "$1y");
563 inflect.addSingularize("(s)eries$", "$1eries");
564 inflect.addSingularize("(m)ovies$", "$1ovie");
565 inflect.addSingularize("(x|ch|ss|sh)es$", "$1");
566 inflect.addSingularize("([m|l])ice$", "$1ouse");
567 inflect.addSingularize("(bus)es$", "$1");
568 inflect.addSingularize("(o)es$", "$1");
569 inflect.addSingularize("(shoe)s$", "$1");
570 inflect.addSingularize("(cris|ax|test)is$", "$1is");
571 inflect.addSingularize("(cris|ax|test)es$", "$1is");
572 inflect.addSingularize("(octop|vir)i$", "$1us");
573 inflect.addSingularize("(octop|vir)us$", "$1us");
574 inflect.addSingularize("(alias|status)es$", "$1");
575 inflect.addSingularize("(alias|status)$", "$1");
576 inflect.addSingularize("^(ox)en", "$1");
577 inflect.addSingularize("(vert|ind)ices$", "$1ex");
578 inflect.addSingularize("(matr)ices$", "$1ix");
579 inflect.addSingularize("(quiz)zes$", "$1");
580
581 inflect.addIrregular("person", "people");
582 inflect.addIrregular("man", "men");
583 inflect.addIrregular("child", "children");
584 inflect.addIrregular("sex", "sexes");
585 inflect.addIrregular("move", "moves");
586 inflect.addIrregular("stadium", "stadiums");
587
588 inflect.addUncountable("equipment", "information", "rice", "money", "species", "series", "fish", "sheep");
589 }
590
591 }