Skip to content

Improve singular method #493

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions modello-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-testing</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@
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;
import java.util.Optional;

import org.codehaus.modello.ModelloException;
import org.codehaus.modello.ModelloParameterConstants;
Expand All @@ -57,6 +59,61 @@
public abstract class AbstractModelloGenerator implements ModelloGenerator {
private final Logger logger = LoggerFactory.getLogger(getClass());

private static final Map<String, String> PLURAL_EXCEPTIONS = new HashMap<>();

static {
// 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;

private File outputDirectory;
Expand All @@ -76,6 +133,7 @@ protected Logger getLogger() {
return logger;
}

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

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

licenseText = (List<String>) parameters.get(ModelloParameterConstants.LICENSE_TEXT);

Optional.ofNullable(parameters.get(ModelloParameterConstants.PLURAL_EXCEPTIONS))
.ifPresent(o -> PLURAL_EXCEPTIONS.putAll((Map<String, String>) o));
}

protected Model getModel() {
Expand Down Expand Up @@ -150,6 +211,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
*/
Expand Down Expand Up @@ -194,17 +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 (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("es") && name.endsWith("ches")) {
}
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("xes")) {
}
if (lower.endsWith("ses")) {
return name.substring(0, name.length() - 2);
} else if (name.endsWith("s") && (name.length() != 1)) {
}
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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
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",

// 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) {
assertEquals(
singular,
AbstractModelloGenerator.singular(plural),
"singular of: " + plural + " should be: " + singular);
}
}
3 changes: 3 additions & 0 deletions modello-maven-plugin/src/it/clone/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
<models>
<model>src/main/mdo/thing.mdo</model>
</models>
<pluralExceptions>
<someStringSets>ownSingularStringSet</someStringSets>
</pluralExceptions>
</configuration>
<executions>
<execution>
Expand Down
4 changes: 2 additions & 2 deletions modello-maven-plugin/src/it/clone/src/main/mdo/thing.mdo
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ under the License.
</association>
</field>
<field>
<name>someStringList</name>
<name>someStringLists</name>
<version>1.0.0</version>
<type>List</type>
<association>
Expand All @@ -113,7 +113,7 @@ under the License.
</association>
</field>
<field>
<name>someStringSet</name>
<name>someStringSets</name>
<version>1.0.0</version>
<type>Set</type>
<association>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() );
Expand Down Expand Up @@ -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() );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
* <p>
* As a kay we provide plural noun and as value we provide singular noun, eg:
* <pre>
* <kisses>kiss</kisses>
* </pre>
*
* @since 2.5.0
*/
@Parameter
private Map<String, String> pluralExceptions;

/**
* @since 1.0.1
*/
Expand Down Expand Up @@ -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(), ","));
}
Expand Down Expand Up @@ -224,6 +239,13 @@ public void execute() throws MojoExecutionException {
}
}

private Object keysToLower(Map<String, String> 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.
*/
Expand Down
Loading