1
2 import java.util.ArrayList;
3 import java.util.List;
4 import java.util.regex.Matcher;
5 import java.util.regex.Pattern;
6
7 /**
8 * Transforms words (from singular to plural, from camelCase to under_score, etc.). I got bored of doing Real Work...
9 *
10 * @author chuyeow
11 */
12 public class Inflector {
13
14 // Pfft, can't think of a better name, but this is needed to avoid the price of initializing the pattern on each call.
15 private static final Pattern UNDERSCORE_PATTERN_1 = Pattern.compile("([A-Z]+)([A-Z][a-z])");
16 private static final Pattern UNDERSCORE_PATTERN_2 = Pattern.compile("([a-z\\d])([A-Z])");
17
18 private static List<RuleAndReplacement> plurals = new ArrayList<RuleAndReplacement>();
19 private static List<RuleAndReplacement> singulars = new ArrayList<RuleAndReplacement>();
20 private static List<String> uncountables = new ArrayList<String>();
21
22 private static Inflector instance; // (Pseudo-)Singleton instance.
23
24 private Inflector() {
25 // Woo, you can't touch me.
26
27 initialize();
28 }
29
30 private void initialize() {
31 plural("$", "s");
32 plural("s$", "s");
33 plural("(ax|test)is$", "$1es");
34 plural("(octop|vir)us$", "$1i");
35 plural("(alias|status)$", "$1es");
36 plural("(bu)s$", "$1es");
37 plural("(buffal|tomat)o$", "$1oes");
38 plural("([ti])um$", "$1a");
39 plural("sis$", "ses");
40 plural("(?:([^f])fe|([lr])f)$", "$1$2ves");
41 plural("(hive)$", "$1s");
42 plural("([^aeiouy]|qu)y$", "$1ies");
43 plural("([^aeiouy]|qu)ies$", "$1y");
44 plural("(x|ch|ss|sh)$", "$1es");
45 plural("(matr|vert|ind)ix|ex$", "$1ices");
46 plural("([m|l])ouse$", "$1ice");
47 plural("(ox)$", "$1en");
48 plural("(quiz)$", "$1zes");
49
50 singular("s$", "");
51 singular("(n)ews$", "$1ews");
52 singular("([ti])a$", "$1um");
53 singular("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis");
54 singular("(^analy)ses$", "$1sis");
55 singular("([^f])ves$", "$1fe");
56 singular("(hive)s$", "$1");
57 singular("(tive)s$", "$1");
58 singular("([lr])ves$", "$1f");
59 singular("([^aeiouy]|qu)ies$", "$1y");
60 singular("(s)eries$", "$1eries");
61 singular("(m)ovies$", "$1ovie");
62 singular("(x|ch|ss|sh)es$", "$1");
63 singular("([m|l])ice$", "$1ouse");
64 singular("(bus)es$", "$1");
65 singular("(o)es$", "$1");
66 singular("(shoe)s$", "$1");
67 singular("(cris|ax|test)es$", "$1is");
68 singular("([octop|vir])i$", "$1us");
69 singular("(alias|status)es$", "$1");
70 singular("^(ox)en", "$1");
71 singular("(vert|ind)ices$", "$1ex");
72 singular("(matr)ices$", "$1ix");
73 singular("(quiz)zes$", "$1");
74
75 irregular("person", "people");
76 irregular("man", "men");
77 irregular("child", "children");
78 irregular("sex", "sexes");
79 irregular("move", "moves");
80
81 uncountable(new String[] {"equipment", "information", "rice", "money", "species", "series", "fish", "sheep"});
82 }
83
84 public static Inflector getInstance() {
85 if (instance == null) {
86 instance = new Inflector();
87 }
88 return instance;
89 }
90
91 public String underscore(String camelCasedWord) {
92
93 // Regexes in Java are fucking stupid...
94 String underscoredWord = UNDERSCORE_PATTERN_1.matcher(camelCasedWord).replaceAll("$1_$2");
95 underscoredWord = UNDERSCORE_PATTERN_2.matcher(underscoredWord).replaceAll("$1_$2");
96 underscoredWord = underscoredWord.replace('-', '_').toLowerCase();
97
98 return underscoredWord;
99 }
100
101 public String pluralize(String word) {
102 if (uncountables.contains(word.toLowerCase())) {
103 return word;
104 }
105 return replaceWithFirstRule(word, plurals);
106 }
107
108 public String singularize(String word) {
109 if (uncountables.contains(word.toLowerCase())) {
110 return word;
111 }
112 return replaceWithFirstRule(word, singulars);
113 }
114
115 private String replaceWithFirstRule(String word, List<RuleAndReplacement> ruleAndReplacements) {
116
117 for (RuleAndReplacement rar : ruleAndReplacements) {
118 String rule = rar.getRule();
119 String replacement = rar.getReplacement();
120
121 // Return if we find a match.
122 Matcher matcher = Pattern.compile(rule, Pattern.CASE_INSENSITIVE).matcher(word);
123 if (matcher.find()) {
124 return matcher.replaceAll(replacement);
125 }
126 }
127 return word;
128 }
129
130 public String tableize(String className) {
131 return pluralize(underscore(className));
132 }
133
134 public String tableize(Class klass) {
135 // Strip away package name - we only want the 'base' class name.
136 String className = klass.getName().replace(klass.getPackage().getName()+".", "");
137 return tableize(className);
138 }
139
140 public static void plural(String rule, String replacement) {
141 plurals.add(0, new RuleAndReplacement(rule, replacement));
142 }
143
144 public static void singular(String rule, String replacement) {
145 singulars.add(0, new RuleAndReplacement(rule, replacement));
146 }
147
148 public static void irregular(String singular, String plural) {
149 plural(singular, plural);
150 singular(plural, singular);
151 }
152
153 public static void uncountable(String... words) {
154 for (String word : words) {
155 uncountables.add(word);
156 }
157 }
158 }
159
160
161 // Ugh, no open structs in Java (not-natively at least).
162 class RuleAndReplacement {
163 private String rule;
164 private String replacement;
165 public RuleAndReplacement(String rule, String replacement) {
166 this.rule = rule;
167 this.replacement = replacement;
168 }
169 public String getReplacement() {
170 return replacement;
171 }
172 public void setReplacement(String replacement) {
173 this.replacement = replacement;
174 }
175 public String getRule() {
176 return rule;
177 }
178 public void setRule(String rule) {
179 this.rule = rule;
180 }
181 }