Skip to content

Commit 6cb3ab2

Browse files
Improve and add exceptions for singular method (#493)
1 parent 1dabc08 commit 6cb3ab2

File tree

9 files changed

+277
-15
lines changed

9 files changed

+277
-15
lines changed

modello-core/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@
4848
<artifactId>junit-jupiter-api</artifactId>
4949
<scope>test</scope>
5050
</dependency>
51+
<dependency>
52+
<groupId>org.junit.jupiter</groupId>
53+
<artifactId>junit-jupiter-params</artifactId>
54+
<scope>test</scope>
55+
</dependency>
5156
<dependency>
5257
<groupId>org.codehaus.plexus</groupId>
5358
<artifactId>plexus-testing</artifactId>

modello-core/src/main/java/org/codehaus/modello/ModelloParameterConstants.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,11 @@ public class ModelloParameterConstants {
8787
*/
8888
public static final String LICENSE_TEXT = "modello.license.text";
8989

90+
/**
91+
* Additional plural to singular exceptions
92+
* @since 2.5.0
93+
*/
94+
public static final String PLURAL_EXCEPTIONS = "modello.plural.exceptions";
95+
9096
private ModelloParameterConstants() {}
9197
}

modello-core/src/main/java/org/codehaus/modello/plugin/AbstractModelloGenerator.java

Lines changed: 113 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@
3232
import java.nio.file.Path;
3333
import java.util.ArrayList;
3434
import java.util.Arrays;
35+
import java.util.HashMap;
3536
import java.util.List;
3637
import java.util.Map;
38+
import java.util.Optional;
3739

3840
import org.codehaus.modello.ModelloException;
3941
import org.codehaus.modello.ModelloParameterConstants;
@@ -57,6 +59,62 @@
5759
public abstract class AbstractModelloGenerator implements ModelloGenerator {
5860
private final Logger logger = LoggerFactory.getLogger(getClass());
5961

62+
private static final Map<String, String> PLURAL_EXCEPTIONS = new HashMap<>();
63+
64+
static {
65+
// Irregular names
66+
PLURAL_EXCEPTIONS.put("children", "child");
67+
PLURAL_EXCEPTIONS.put("feet", "foot");
68+
PLURAL_EXCEPTIONS.put("geese", "goose");
69+
PLURAL_EXCEPTIONS.put("indices", "index");
70+
PLURAL_EXCEPTIONS.put("men", "man");
71+
PLURAL_EXCEPTIONS.put("mice", "mouse");
72+
PLURAL_EXCEPTIONS.put("people", "person");
73+
PLURAL_EXCEPTIONS.put("teeth", "tooth");
74+
PLURAL_EXCEPTIONS.put("women", "woman");
75+
76+
// Invariant names
77+
PLURAL_EXCEPTIONS.put("aircraft", "aircraft");
78+
PLURAL_EXCEPTIONS.put("bison", "bison");
79+
PLURAL_EXCEPTIONS.put("deer", "deer");
80+
PLURAL_EXCEPTIONS.put("elk", "elk");
81+
PLURAL_EXCEPTIONS.put("fish", "fish");
82+
PLURAL_EXCEPTIONS.put("series", "series");
83+
PLURAL_EXCEPTIONS.put("sheep", "sheep");
84+
PLURAL_EXCEPTIONS.put("species", "species");
85+
86+
// Special "oes" exceptions
87+
PLURAL_EXCEPTIONS.put("buffaloes", "buffalo");
88+
PLURAL_EXCEPTIONS.put("cargoes", "cargo");
89+
PLURAL_EXCEPTIONS.put("echoes", "echo");
90+
PLURAL_EXCEPTIONS.put("goes", "go");
91+
PLURAL_EXCEPTIONS.put("haloes", "halo");
92+
PLURAL_EXCEPTIONS.put("heroes", "hero");
93+
PLURAL_EXCEPTIONS.put("mosquitoes", "mosquito");
94+
PLURAL_EXCEPTIONS.put("noes", "no");
95+
PLURAL_EXCEPTIONS.put("potatoes", "potato");
96+
PLURAL_EXCEPTIONS.put("tomatoes", "tomato");
97+
PLURAL_EXCEPTIONS.put("torpedoes", "torpedo");
98+
PLURAL_EXCEPTIONS.put("vetoes", "veto");
99+
PLURAL_EXCEPTIONS.put("volcanoes", "volcano");
100+
101+
// Special "ses" exceptions
102+
PLURAL_EXCEPTIONS.put("horses", "horse");
103+
PLURAL_EXCEPTIONS.put("licenses", "license");
104+
PLURAL_EXCEPTIONS.put("phases", "phase");
105+
106+
// Special "zzes" exceptions
107+
PLURAL_EXCEPTIONS.put("fezzes", "fez");
108+
PLURAL_EXCEPTIONS.put("whizzes", "whiz");
109+
110+
// Special "ies" exceptions
111+
PLURAL_EXCEPTIONS.put("movies", "movie");
112+
113+
// Special "ves" exceptions
114+
PLURAL_EXCEPTIONS.put("archives", "archive");
115+
PLURAL_EXCEPTIONS.put("relatives", "relative");
116+
}
117+
60118
private Model model;
61119

62120
private File outputDirectory;
@@ -76,6 +134,7 @@ protected Logger getLogger() {
76134
return logger;
77135
}
78136

137+
@SuppressWarnings("uncheked")
79138
protected void initialize(Model model, Map<String, Object> parameters) throws ModelloException {
80139
this.model = model;
81140

@@ -91,6 +150,9 @@ protected void initialize(Model model, Map<String, Object> parameters) throws Mo
91150
encoding = (String) parameters.get(ModelloParameterConstants.ENCODING);
92151

93152
licenseText = (List<String>) parameters.get(ModelloParameterConstants.LICENSE_TEXT);
153+
154+
Optional.ofNullable(parameters.get(ModelloParameterConstants.PLURAL_EXCEPTIONS))
155+
.ifPresent(o -> PLURAL_EXCEPTIONS.putAll((Map<String, String>) o));
94156
}
95157

96158
protected Model getModel() {
@@ -150,6 +212,7 @@ protected boolean isClassInModel(String fieldType, Model model) {
150212

151213
/**
152214
* Return the child fields of this class.
215+
*
153216
* @param modelClass current class
154217
* @return the list of fields of this class
155218
*/
@@ -194,23 +257,67 @@ protected String capitalise(String str) {
194257
}
195258

196259
public static String singular(String name) {
197-
if (StringUtils.isEmpty(name)) {
198-
return name;
260+
if (name == null || name.isEmpty()) return name;
261+
262+
String lower = name.toLowerCase();
263+
264+
if (!lower.equals(name)) {
265+
// we can have a case like otherArchives
266+
String[] split = splitByUpperCase(name);
267+
if (split != null && PLURAL_EXCEPTIONS.containsKey(split[1])) {
268+
String plural = PLURAL_EXCEPTIONS.get(split[1]);
269+
return split[0] + Character.toUpperCase(plural.charAt(0)) + plural.substring(1);
270+
}
199271
}
200272

201-
if (name.endsWith("ies")) {
273+
if (PLURAL_EXCEPTIONS.containsKey(lower)) {
274+
return PLURAL_EXCEPTIONS.get(lower);
275+
}
276+
277+
// Suffix-based rules
278+
if (lower.endsWith("ies") && name.length() > 3) {
202279
return name.substring(0, name.length() - 3) + "y";
203-
} else if (name.endsWith("es") && name.endsWith("ches")) {
280+
}
281+
if (lower.endsWith("aves") || lower.endsWith("lves") || lower.endsWith("rves")) {
282+
return name.substring(0, name.length() - 3) + "f";
283+
}
284+
if (lower.endsWith("ves") && !lower.endsWith("fves")) {
285+
return name.substring(0, name.length() - 3) + "fe";
286+
}
287+
if (lower.endsWith("zzes")) {
204288
return name.substring(0, name.length() - 2);
205-
} else if (name.endsWith("xes")) {
289+
}
290+
if (lower.endsWith("sses")) {
206291
return name.substring(0, name.length() - 2);
207-
} else if (name.endsWith("s") && (name.length() != 1)) {
292+
}
293+
if (lower.endsWith("ses")) {
294+
return name.substring(0, name.length() - 2);
295+
}
296+
if (lower.endsWith("ches") || lower.endsWith("shes")) {
297+
return name.substring(0, name.length() - 2);
298+
}
299+
if (lower.endsWith("xes")) {
300+
return name.substring(0, name.length() - 2);
301+
}
302+
if (lower.endsWith("oes")) {
303+
return name.substring(0, name.length() - 1);
304+
}
305+
if (lower.endsWith("s") && name.length() > 1) {
208306
return name.substring(0, name.length() - 1);
209307
}
210308

211309
return name;
212310
}
213311

312+
private static String[] splitByUpperCase(String name) {
313+
for (int i = name.length() - 1; i >= 0; i--) {
314+
if (Character.isUpperCase(name.charAt(i))) {
315+
return new String[] {name.substring(0, i), name.substring(i).toLowerCase()};
316+
}
317+
}
318+
return null;
319+
}
320+
214321
public static String uncapitalise(String str) {
215322
if (StringUtils.isEmpty(str)) {
216323
return str;
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package org.codehaus.modello.plugin;
2+
3+
import org.junit.jupiter.params.ParameterizedTest;
4+
import org.junit.jupiter.params.provider.CsvSource;
5+
6+
import static org.junit.jupiter.api.Assertions.assertEquals;
7+
8+
class AbstractModelloGeneratorTest {
9+
10+
@CsvSource({
11+
",",
12+
"'',''",
13+
"s,s",
14+
15+
// Known exceptions
16+
"men, man",
17+
"women, woman",
18+
"children,child",
19+
"mice, mouse",
20+
"people, person",
21+
"teeth, tooth",
22+
"feet, foot",
23+
"geese, goose",
24+
"series, series",
25+
"species, species",
26+
"sheep, sheep",
27+
"fish, fish",
28+
"deer, deer",
29+
"aircraft, aircraft",
30+
"heroes, hero",
31+
"potatoes, potato",
32+
"tomatoes, tomato",
33+
"echoes, echo",
34+
"vetoes, veto",
35+
"torpedoes, torpedo",
36+
"cargoes, cargo",
37+
"haloes, halo",
38+
"mosquitoes, mosquito",
39+
"buffaloes, buffalo",
40+
"bison, bison",
41+
"elk, elk",
42+
43+
// Regular plural forms with suffixes
44+
"voes, voe",
45+
"hoes, hoe",
46+
"canoes, canoe",
47+
"toes, toe",
48+
"foes, foe",
49+
"oboes, oboe",
50+
"noes, no",
51+
"boxes, box",
52+
"wishes, wish",
53+
"dishes, dish",
54+
"brushes, brush",
55+
"classes, class",
56+
"buzzes, buzz",
57+
"cars, car",
58+
"dogs, dog",
59+
"cats, cat",
60+
"horses, horse",
61+
"fezzes, fez",
62+
"whizzes, whiz",
63+
"foxes, fox",
64+
65+
// Some test cases with different rules
66+
"archives, archive",
67+
"otherArchives, otherArchive",
68+
"Archives, Archive",
69+
"wolves, wolf",
70+
"knives, knife",
71+
"leaves, leaf",
72+
"wives, wife",
73+
"lives, life",
74+
"babies, baby",
75+
"parties, party",
76+
"cities, city",
77+
"buses, bus",
78+
"boxes, box",
79+
"churches, church",
80+
"matches, match",
81+
"watches, watch",
82+
"riches, rich",
83+
"dresses, dress",
84+
"crosses, cross",
85+
"lunches, lunch",
86+
"relatives, relative",
87+
88+
// More edge cases
89+
"heroes, hero",
90+
"vetoes, veto",
91+
"torpedoes, torpedo",
92+
"tomatoes, tomato",
93+
"potatoes, potato",
94+
"echoes, echo",
95+
"mosquitoes, mosquito",
96+
"buffaloes, buffalo",
97+
"volcanoes, volcano",
98+
"goes, go",
99+
"indices, index",
100+
"phases, phase",
101+
"kisses, kiss",
102+
"movies, movie",
103+
"shoes, shoe",
104+
105+
// other examples
106+
"aliases, alias",
107+
"ids, id",
108+
"licenses, license",
109+
"repositories, repository",
110+
"roles, role",
111+
})
112+
@ParameterizedTest
113+
public void testSingular(String plural, String singular) {
114+
assertEquals(
115+
singular,
116+
AbstractModelloGenerator.singular(plural),
117+
"singular of: " + plural + " should be: " + singular);
118+
}
119+
}

modello-maven-plugin/src/it/clone/pom.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@
5656
<models>
5757
<model>src/main/mdo/thing.mdo</model>
5858
</models>
59+
<pluralExceptions>
60+
<someStringSets>ownSingularStringSet</someStringSets>
61+
</pluralExceptions>
5962
</configuration>
6063
<executions>
6164
<execution>

modello-maven-plugin/src/it/clone/src/main/mdo/thing.mdo

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ under the License.
104104
</association>
105105
</field>
106106
<field>
107-
<name>someStringList</name>
107+
<name>someStringLists</name>
108108
<version>1.0.0</version>
109109
<type>List</type>
110110
<association>
@@ -113,7 +113,7 @@ under the License.
113113
</association>
114114
</field>
115115
<field>
116-
<name>someStringSet</name>
116+
<name>someStringSets</name>
117117
<version>1.0.0</version>
118118
<type>Set</type>
119119
<association>

modello-maven-plugin/src/it/clone/src/test/java/test/CloneTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public void testClone()
3737
orig.setSomeDate( new Date() );
3838
orig.setSomeDom( new Xpp3Dom( "test" ) );
3939
orig.addSomeStringList( "string" );
40-
orig.addSomeStringSet( "string" );
40+
orig.addOwnSingularStringSet( "string" );
4141
orig.setDeepThingy( new Thingy() );
4242
orig.addDeepThingyList( new Thingy() );
4343
orig.addDeepThingySet( new Thingy() );
@@ -67,10 +67,10 @@ public void testClone()
6767
assertEquals( orig.getSomeDom(), copy.getSomeDom() );
6868
assertNotSame( orig.getSomeDom(), copy.getSomeDom() );
6969

70-
assertEquals( orig.getSomeStringList(), copy.getSomeStringList() );
71-
assertNotSame( orig.getSomeStringList(), copy.getSomeStringList() );
72-
assertEquals( orig.getSomeStringSet(), copy.getSomeStringSet() );
73-
assertNotSame( orig.getSomeStringSet(), copy.getSomeStringSet() );
70+
assertEquals( orig.getSomeStringLists(), copy.getSomeStringLists() );
71+
assertNotSame( orig.getSomeStringLists(), copy.getSomeStringLists() );
72+
assertEquals( orig.getSomeStringSets(), copy.getSomeStringSets() );
73+
assertNotSame( orig.getSomeStringSets(), copy.getSomeStringSets() );
7474

7575
assertNotSame( orig.getDeepThingy(), copy.getDeepThingy() );
7676
assertNotSame( orig.getDeepThingyList(), copy.getDeepThingyList() );

0 commit comments

Comments
 (0)