3232import java .nio .file .Path ;
3333import java .util .ArrayList ;
3434import java .util .Arrays ;
35+ import java .util .HashMap ;
3536import java .util .List ;
3637import java .util .Map ;
38+ import java .util .Optional ;
3739
3840import org .codehaus .modello .ModelloException ;
3941import org .codehaus .modello .ModelloParameterConstants ;
5759public 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 ;
0 commit comments