From bf4001cf404735eb1e0cf347501109f9948535fc Mon Sep 17 00:00:00 2001 From: Slawomir Jaranowski Date: Tue, 13 May 2025 18:35:00 +0200 Subject: [PATCH 1/3] Improve singular method --- modello-core/pom.xml | 5 +++ .../plugin/AbstractModelloGenerator.java | 21 +++++++++--- .../plugin/AbstractModelloGeneratorTest.java | 34 +++++++++++++++++++ .../plugin/java/JavaModelloGenerator.java | 4 +-- 4 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 modello-core/src/test/java/org/codehaus/modello/plugin/AbstractModelloGeneratorTest.java diff --git a/modello-core/pom.xml b/modello-core/pom.xml index b4d99bf64..0ae29f85f 100644 --- a/modello-core/pom.xml +++ b/modello-core/pom.xml @@ -48,6 +48,11 @@ junit-jupiter-api test + + org.junit.jupiter + junit-jupiter-params + test + org.codehaus.plexus plexus-testing diff --git a/modello-core/src/main/java/org/codehaus/modello/plugin/AbstractModelloGenerator.java b/modello-core/src/main/java/org/codehaus/modello/plugin/AbstractModelloGenerator.java index f58a8c28c..e3b849fd4 100644 --- a/modello-core/src/main/java/org/codehaus/modello/plugin/AbstractModelloGenerator.java +++ b/modello-core/src/main/java/org/codehaus/modello/plugin/AbstractModelloGenerator.java @@ -32,6 +32,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -57,6 +58,14 @@ public abstract class AbstractModelloGenerator implements ModelloGenerator { private final Logger logger = LoggerFactory.getLogger(getClass()); + private static final Map PLURAL_EXCEPTION = new HashMap<>(); + + static { + PLURAL_EXCEPTION.put("children", "child"); + PLURAL_EXCEPTION.put("licenses", "license"); + PLURAL_EXCEPTION.put("series", "series"); + } + private Model model; private File outputDirectory; @@ -150,6 +159,7 @@ protected boolean isClassInModel(String fieldType, Model model) { /** * Return the child fields of this class. + * * @param modelClass current class * @return the list of fields of this class */ @@ -198,11 +208,14 @@ public static String singular(String name) { return name; } - if (name.endsWith("ies")) { + if (PLURAL_EXCEPTION.containsKey(name)) { + return PLURAL_EXCEPTION.get(name); + } else if (name.endsWith("ies")) { return name.substring(0, name.length() - 3) + "y"; - } else if (name.endsWith("es") && name.endsWith("ches")) { - return name.substring(0, name.length() - 2); - } else if (name.endsWith("xes")) { + } else if (name.endsWith("zzes")) { + return name.substring(0, name.length() - 3); + } else if (name.endsWith("ches") || name.endsWith("xes") || name.endsWith("ses") || name.endsWith("oes") + || name.endsWith("shes")) { return name.substring(0, name.length() - 2); } else if (name.endsWith("s") && (name.length() != 1)) { return name.substring(0, name.length() - 1); diff --git a/modello-core/src/test/java/org/codehaus/modello/plugin/AbstractModelloGeneratorTest.java b/modello-core/src/test/java/org/codehaus/modello/plugin/AbstractModelloGeneratorTest.java new file mode 100644 index 000000000..99fbb9b3e --- /dev/null +++ b/modello-core/src/test/java/org/codehaus/modello/plugin/AbstractModelloGeneratorTest.java @@ -0,0 +1,34 @@ +package org.codehaus.modello.plugin; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AbstractModelloGeneratorTest { + + @CsvSource( { + ",", + "'',''", + "s,s", + "aliases, alias", + "babies, baby", + "fezzes, fez", + "foxes, fox", + "ids, id", + "licenses, license", + "lunches, lunch", + "potatoes, potato", + "repositories, repository", + "roles, role", + "rushes, rush", + "series, series" + }) + @ParameterizedTest + public void testSingular(String plural, String singular) { + assertEquals( + singular, + AbstractModelloGenerator.singular(plural), + "singular of: " + plural + " should be: " + singular); + } +} diff --git a/modello-plugins/modello-plugin-java/src/main/java/org/codehaus/modello/plugin/java/JavaModelloGenerator.java b/modello-plugins/modello-plugin-java/src/main/java/org/codehaus/modello/plugin/java/JavaModelloGenerator.java index 890c26c2a..19cdeb5f0 100644 --- a/modello-plugins/modello-plugin-java/src/main/java/org/codehaus/modello/plugin/java/JavaModelloGenerator.java +++ b/modello-plugins/modello-plugin-java/src/main/java/org/codehaus/modello/plugin/java/JavaModelloGenerator.java @@ -1665,7 +1665,7 @@ private void createAdder(ModelAssociation modelAssociation, JClass jClass, boole jClass.addMethod(adder); } else { - String adderName = "add" + singular(capitalise(singular(fieldName))); + String adderName = "add" + capitalise(singular(fieldName)); JMethod adder; if (isBuilderMethod) { @@ -1711,7 +1711,7 @@ private void createAdder(ModelAssociation modelAssociation, JClass jClass, boole return; } - JMethod remover = new JMethod("remove" + singular(capitalise(fieldName))); + JMethod remover = new JMethod("remove" + capitalise(singular(fieldName))); remover.addParameter(new JParameter(addType, parameterName)); From cf33c69f3d729b7f9e6fc89f388809258d248938 Mon Sep 17 00:00:00 2001 From: Slawomir Jaranowski Date: Wed, 14 May 2025 22:49:16 +0200 Subject: [PATCH 2/3] Improve singular method - next try --- .../modello/ModelloParameterConstants.java | 6 + .../plugin/AbstractModelloGenerator.java | 103 +++++++++++++--- .../plugin/AbstractModelloGeneratorTest.java | 114 +++++++++++++++--- modello-maven-plugin/src/it/clone/pom.xml | 3 + .../src/it/clone/src/main/mdo/thing.mdo | 4 +- .../clone/src/test/java/test/CloneTest.java | 10 +- .../maven/AbstractModelloGeneratorMojo.java | 22 ++++ 7 files changed, 225 insertions(+), 37 deletions(-) diff --git a/modello-core/src/main/java/org/codehaus/modello/ModelloParameterConstants.java b/modello-core/src/main/java/org/codehaus/modello/ModelloParameterConstants.java index 820dbe545..572144dc2 100644 --- a/modello-core/src/main/java/org/codehaus/modello/ModelloParameterConstants.java +++ b/modello-core/src/main/java/org/codehaus/modello/ModelloParameterConstants.java @@ -87,5 +87,11 @@ public class ModelloParameterConstants { */ public static final String LICENSE_TEXT = "modello.license.text"; + /** + * Additional plural to singular exceptions + * @since 2.5.0 + */ + public static final String PLURAL_EXCEPTIONS = "modello.plural.exceptions"; + private ModelloParameterConstants() {} } diff --git a/modello-core/src/main/java/org/codehaus/modello/plugin/AbstractModelloGenerator.java b/modello-core/src/main/java/org/codehaus/modello/plugin/AbstractModelloGenerator.java index e3b849fd4..b0233c499 100644 --- a/modello-core/src/main/java/org/codehaus/modello/plugin/AbstractModelloGenerator.java +++ b/modello-core/src/main/java/org/codehaus/modello/plugin/AbstractModelloGenerator.java @@ -35,6 +35,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import org.codehaus.modello.ModelloException; import org.codehaus.modello.ModelloParameterConstants; @@ -58,12 +59,59 @@ public abstract class AbstractModelloGenerator implements ModelloGenerator { private final Logger logger = LoggerFactory.getLogger(getClass()); - private static final Map PLURAL_EXCEPTION = new HashMap<>(); + private static final Map PLURAL_EXCEPTIONS = new HashMap<>(); static { - PLURAL_EXCEPTION.put("children", "child"); - PLURAL_EXCEPTION.put("licenses", "license"); - PLURAL_EXCEPTION.put("series", "series"); + // Irregular names + PLURAL_EXCEPTIONS.put("children", "child"); + PLURAL_EXCEPTIONS.put("feet", "foot"); + PLURAL_EXCEPTIONS.put("geese", "goose"); + PLURAL_EXCEPTIONS.put("indices", "index"); + PLURAL_EXCEPTIONS.put("men", "man"); + PLURAL_EXCEPTIONS.put("mice", "mouse"); + PLURAL_EXCEPTIONS.put("people", "person"); + PLURAL_EXCEPTIONS.put("teeth", "tooth"); + PLURAL_EXCEPTIONS.put("women", "woman"); + + // Invariant names + PLURAL_EXCEPTIONS.put("aircraft", "aircraft"); + PLURAL_EXCEPTIONS.put("bison", "bison"); + PLURAL_EXCEPTIONS.put("deer", "deer"); + PLURAL_EXCEPTIONS.put("elk", "elk"); + PLURAL_EXCEPTIONS.put("fish", "fish"); + PLURAL_EXCEPTIONS.put("series", "series"); + PLURAL_EXCEPTIONS.put("sheep", "sheep"); + PLURAL_EXCEPTIONS.put("species", "species"); + + // Special "oes" exceptions + PLURAL_EXCEPTIONS.put("buffaloes", "buffalo"); + PLURAL_EXCEPTIONS.put("cargoes", "cargo"); + PLURAL_EXCEPTIONS.put("echoes", "echo"); + PLURAL_EXCEPTIONS.put("goes", "go"); + PLURAL_EXCEPTIONS.put("haloes", "halo"); + PLURAL_EXCEPTIONS.put("heroes", "hero"); + PLURAL_EXCEPTIONS.put("mosquitoes", "mosquito"); + PLURAL_EXCEPTIONS.put("noes", "no"); + PLURAL_EXCEPTIONS.put("potatoes", "potato"); + PLURAL_EXCEPTIONS.put("tomatoes", "tomato"); + PLURAL_EXCEPTIONS.put("torpedoes", "torpedo"); + PLURAL_EXCEPTIONS.put("vetoes", "veto"); + PLURAL_EXCEPTIONS.put("volcanoes", "volcano"); + + // Special "ses" exceptions + PLURAL_EXCEPTIONS.put("horses", "horse"); + PLURAL_EXCEPTIONS.put("licenses", "license"); + PLURAL_EXCEPTIONS.put("phases", "phase"); + + // Special "zzes" exceptions + PLURAL_EXCEPTIONS.put("fezzes", "fez"); + PLURAL_EXCEPTIONS.put("whizzes", "whiz"); + + // Special "ies" exceptions + PLURAL_EXCEPTIONS.put("movies", "movie"); + + // Special "ves" exceptions + PLURAL_EXCEPTIONS.put("relatives", "relative"); } private Model model; @@ -85,6 +133,7 @@ protected Logger getLogger() { return logger; } + @SuppressWarnings("uncheked") protected void initialize(Model model, Map parameters) throws ModelloException { this.model = model; @@ -100,6 +149,9 @@ protected void initialize(Model model, Map parameters) throws Mo encoding = (String) parameters.get(ModelloParameterConstants.ENCODING); licenseText = (List) parameters.get(ModelloParameterConstants.LICENSE_TEXT); + + Optional.ofNullable(parameters.get(ModelloParameterConstants.PLURAL_EXCEPTIONS)) + .ifPresent(o -> PLURAL_EXCEPTIONS.putAll((Map) o)); } protected Model getModel() { @@ -204,20 +256,43 @@ protected String capitalise(String str) { } public static String singular(String name) { - if (StringUtils.isEmpty(name)) { - return name; + if (name == null || name.isEmpty()) return name; + + String lower = name.toLowerCase(); + + if (PLURAL_EXCEPTIONS.containsKey(lower)) { + return PLURAL_EXCEPTIONS.get(lower); } - if (PLURAL_EXCEPTION.containsKey(name)) { - return PLURAL_EXCEPTION.get(name); - } else if (name.endsWith("ies")) { + // Suffix-based rules + if (lower.endsWith("ies") && name.length() > 3) { return name.substring(0, name.length() - 3) + "y"; - } else if (name.endsWith("zzes")) { - return name.substring(0, name.length() - 3); - } else if (name.endsWith("ches") || name.endsWith("xes") || name.endsWith("ses") || name.endsWith("oes") - || name.endsWith("shes")) { + } + if (lower.endsWith("aves") || lower.endsWith("lves") || lower.endsWith("rves")) { + return name.substring(0, name.length() - 3) + "f"; + } + if (lower.endsWith("ves") && !lower.endsWith("fves")) { + return name.substring(0, name.length() - 3) + "fe"; + } + if (lower.endsWith("zzes")) { + return name.substring(0, name.length() - 2); + } + if (lower.endsWith("sses")) { return name.substring(0, name.length() - 2); - } else if (name.endsWith("s") && (name.length() != 1)) { + } + if (lower.endsWith("ses")) { + return name.substring(0, name.length() - 2); + } + if (lower.endsWith("ches") || lower.endsWith("shes")) { + return name.substring(0, name.length() - 2); + } + if (lower.endsWith("xes")) { + return name.substring(0, name.length() - 2); + } + if (lower.endsWith("oes")) { + return name.substring(0, name.length() - 1); + } + if (lower.endsWith("s") && name.length() > 1) { return name.substring(0, name.length() - 1); } diff --git a/modello-core/src/test/java/org/codehaus/modello/plugin/AbstractModelloGeneratorTest.java b/modello-core/src/test/java/org/codehaus/modello/plugin/AbstractModelloGeneratorTest.java index 99fbb9b3e..edc30e04c 100644 --- a/modello-core/src/test/java/org/codehaus/modello/plugin/AbstractModelloGeneratorTest.java +++ b/modello-core/src/test/java/org/codehaus/modello/plugin/AbstractModelloGeneratorTest.java @@ -7,22 +7,104 @@ class AbstractModelloGeneratorTest { - @CsvSource( { - ",", - "'',''", - "s,s", - "aliases, alias", - "babies, baby", - "fezzes, fez", - "foxes, fox", - "ids, id", - "licenses, license", - "lunches, lunch", - "potatoes, potato", - "repositories, repository", - "roles, role", - "rushes, rush", - "series, series" + @CsvSource({ + ",", + "'',''", + "s,s", + + // Known exceptions + "men, man", + "women, woman", + "children,child", + "mice, mouse", + "people, person", + "teeth, tooth", + "feet, foot", + "geese, goose", + "series, series", + "species, species", + "sheep, sheep", + "fish, fish", + "deer, deer", + "aircraft, aircraft", + "heroes, hero", + "potatoes, potato", + "tomatoes, tomato", + "echoes, echo", + "vetoes, veto", + "torpedoes, torpedo", + "cargoes, cargo", + "haloes, halo", + "mosquitoes, mosquito", + "buffaloes, buffalo", + "bison, bison", + "elk, elk", + + // Regular plural forms with suffixes + "voes, voe", + "hoes, hoe", + "canoes, canoe", + "toes, toe", + "foes, foe", + "oboes, oboe", + "noes, no", + "boxes, box", + "wishes, wish", + "dishes, dish", + "brushes, brush", + "classes, class", + "buzzes, buzz", + "cars, car", + "dogs, dog", + "cats, cat", + "horses, horse", + "fezzes, fez", + "whizzes, whiz", + "foxes, fox", + + // Some test cases with different rules + "wolves, wolf", + "knives, knife", + "leaves, leaf", + "wives, wife", + "lives, life", + "babies, baby", + "parties, party", + "cities, city", + "buses, bus", + "boxes, box", + "churches, church", + "matches, match", + "watches, watch", + "riches, rich", + "dresses, dress", + "crosses, cross", + "lunches, lunch", + "relatives, relative", + + // More edge cases + "heroes, hero", + "vetoes, veto", + "torpedoes, torpedo", + "tomatoes, tomato", + "potatoes, potato", + "echoes, echo", + "mosquitoes, mosquito", + "buffaloes, buffalo", + "volcanoes, volcano", + "goes, go", + "indices, index", + "phases, phase", + "kisses, kiss", + "movies, movie", + "shoes, shoe", + + // other examples + "aliases, alias", + "ids, id", + "licenses, license", + "repositories, repository", + "roles, role", }) @ParameterizedTest public void testSingular(String plural, String singular) { diff --git a/modello-maven-plugin/src/it/clone/pom.xml b/modello-maven-plugin/src/it/clone/pom.xml index a3600e97c..063ae02ca 100644 --- a/modello-maven-plugin/src/it/clone/pom.xml +++ b/modello-maven-plugin/src/it/clone/pom.xml @@ -56,6 +56,9 @@ src/main/mdo/thing.mdo + + ownSingularStringSet + diff --git a/modello-maven-plugin/src/it/clone/src/main/mdo/thing.mdo b/modello-maven-plugin/src/it/clone/src/main/mdo/thing.mdo index aa8a806d2..9a5de71ca 100644 --- a/modello-maven-plugin/src/it/clone/src/main/mdo/thing.mdo +++ b/modello-maven-plugin/src/it/clone/src/main/mdo/thing.mdo @@ -104,7 +104,7 @@ under the License. - someStringList + someStringLists 1.0.0 List @@ -113,7 +113,7 @@ under the License. - someStringSet + someStringSets 1.0.0 Set diff --git a/modello-maven-plugin/src/it/clone/src/test/java/test/CloneTest.java b/modello-maven-plugin/src/it/clone/src/test/java/test/CloneTest.java index 41f8e167c..e824dfcec 100644 --- a/modello-maven-plugin/src/it/clone/src/test/java/test/CloneTest.java +++ b/modello-maven-plugin/src/it/clone/src/test/java/test/CloneTest.java @@ -37,7 +37,7 @@ public void testClone() orig.setSomeDate( new Date() ); orig.setSomeDom( new Xpp3Dom( "test" ) ); orig.addSomeStringList( "string" ); - orig.addSomeStringSet( "string" ); + orig.addOwnSingularStringSet( "string" ); orig.setDeepThingy( new Thingy() ); orig.addDeepThingyList( new Thingy() ); orig.addDeepThingySet( new Thingy() ); @@ -67,10 +67,10 @@ public void testClone() assertEquals( orig.getSomeDom(), copy.getSomeDom() ); assertNotSame( orig.getSomeDom(), copy.getSomeDom() ); - assertEquals( orig.getSomeStringList(), copy.getSomeStringList() ); - assertNotSame( orig.getSomeStringList(), copy.getSomeStringList() ); - assertEquals( orig.getSomeStringSet(), copy.getSomeStringSet() ); - assertNotSame( orig.getSomeStringSet(), copy.getSomeStringSet() ); + assertEquals( orig.getSomeStringLists(), copy.getSomeStringLists() ); + assertNotSame( orig.getSomeStringLists(), copy.getSomeStringLists() ); + assertEquals( orig.getSomeStringSets(), copy.getSomeStringSets() ); + assertNotSame( orig.getSomeStringSets(), copy.getSomeStringSets() ); assertNotSame( orig.getDeepThingy(), copy.getDeepThingy() ); assertNotSame( orig.getDeepThingyList(), copy.getDeepThingyList() ); diff --git a/modello-maven-plugin/src/main/java/org/codehaus/modello/maven/AbstractModelloGeneratorMojo.java b/modello-maven-plugin/src/main/java/org/codehaus/modello/maven/AbstractModelloGeneratorMojo.java index 7dc03da8f..936df9dcb 100644 --- a/modello-maven-plugin/src/main/java/org/codehaus/modello/maven/AbstractModelloGeneratorMojo.java +++ b/modello-maven-plugin/src/main/java/org/codehaus/modello/maven/AbstractModelloGeneratorMojo.java @@ -115,6 +115,19 @@ public abstract class AbstractModelloGeneratorMojo extends AbstractMojo { @Parameter private File licenseFile; + /** + * Additional exceptions to the singularization rules, changing plural noun to singular. + *

+ * As a kay we provide plural noun and as value we provide singular noun, eg: + *

+     *     kiss
+     * 
+ * + * @since 2.5.0 + */ + @Parameter + private Map pluralExceptions; + /** * @since 1.0.1 */ @@ -180,6 +193,8 @@ public void execute() throws MojoExecutionException { parameters.put(ModelloParameterConstants.PACKAGE_WITH_VERSION, Boolean.toString(packageWithVersion)); + parameters.put(ModelloParameterConstants.PLURAL_EXCEPTIONS, keysToLower(pluralExceptions)); + if (!packagedVersions.isEmpty()) { parameters.put(ModelloParameterConstants.ALL_VERSIONS, StringUtils.join(packagedVersions.iterator(), ",")); } @@ -224,6 +239,13 @@ public void execute() throws MojoExecutionException { } } + private Object keysToLower(Map maps) { + if (maps == null) { + return null; + } + return maps.entrySet().stream().collect(Collectors.toMap(e -> e.getKey().toLowerCase(), Map.Entry::getValue)); + } + /** * Performs execute on a single specified model. */ From 31295ab7e79a12722f092fb76fd0a99a529e65d5 Mon Sep 17 00:00:00 2001 From: Slawomir Jaranowski Date: Fri, 23 May 2025 20:42:08 +0200 Subject: [PATCH 3/3] Next examples --- .../plugin/AbstractModelloGenerator.java | 19 +++++++++++++++++++ .../plugin/AbstractModelloGeneratorTest.java | 3 +++ 2 files changed, 22 insertions(+) diff --git a/modello-core/src/main/java/org/codehaus/modello/plugin/AbstractModelloGenerator.java b/modello-core/src/main/java/org/codehaus/modello/plugin/AbstractModelloGenerator.java index b0233c499..e4fb59eec 100644 --- a/modello-core/src/main/java/org/codehaus/modello/plugin/AbstractModelloGenerator.java +++ b/modello-core/src/main/java/org/codehaus/modello/plugin/AbstractModelloGenerator.java @@ -111,6 +111,7 @@ public abstract class AbstractModelloGenerator implements ModelloGenerator { PLURAL_EXCEPTIONS.put("movies", "movie"); // Special "ves" exceptions + PLURAL_EXCEPTIONS.put("archives", "archive"); PLURAL_EXCEPTIONS.put("relatives", "relative"); } @@ -260,6 +261,15 @@ public static String singular(String name) { String lower = name.toLowerCase(); + if (!lower.equals(name)) { + // we can have a case like otherArchives + String[] split = splitByUpperCase(name); + if (split != null && PLURAL_EXCEPTIONS.containsKey(split[1])) { + String plural = PLURAL_EXCEPTIONS.get(split[1]); + return split[0] + Character.toUpperCase(plural.charAt(0)) + plural.substring(1); + } + } + if (PLURAL_EXCEPTIONS.containsKey(lower)) { return PLURAL_EXCEPTIONS.get(lower); } @@ -299,6 +309,15 @@ public static String singular(String name) { return name; } + private static String[] splitByUpperCase(String name) { + for (int i = name.length() - 1; i >= 0; i--) { + if (Character.isUpperCase(name.charAt(i))) { + return new String[] {name.substring(0, i), name.substring(i).toLowerCase()}; + } + } + return null; + } + public static String uncapitalise(String str) { if (StringUtils.isEmpty(str)) { return str; diff --git a/modello-core/src/test/java/org/codehaus/modello/plugin/AbstractModelloGeneratorTest.java b/modello-core/src/test/java/org/codehaus/modello/plugin/AbstractModelloGeneratorTest.java index edc30e04c..30ddb729d 100644 --- a/modello-core/src/test/java/org/codehaus/modello/plugin/AbstractModelloGeneratorTest.java +++ b/modello-core/src/test/java/org/codehaus/modello/plugin/AbstractModelloGeneratorTest.java @@ -63,6 +63,9 @@ class AbstractModelloGeneratorTest { "foxes, fox", // Some test cases with different rules + "archives, archive", + "otherArchives, otherArchive", + "Archives, Archive", "wolves, wolf", "knives, knife", "leaves, leaf",