diff --git a/src/core/lombok/Singular.java b/src/core/lombok/Singular.java
new file mode 100644
index 00000000..7f22b008
--- /dev/null
+++ b/src/core/lombok/Singular.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013-2014 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * The singular annotation is used together with {@code @Builder} to create single element 'add' methods in the builder for collections.
+ *
+ */
+@Target({FIELD, PARAMETER})
+@Retention(SOURCE)
+public @interface Singular {
+ String value() default "";
+}
diff --git a/src/core/lombok/core/TypeLibrary.java b/src/core/lombok/core/TypeLibrary.java
index c0e9dc43..dc557c47 100644
--- a/src/core/lombok/core/TypeLibrary.java
+++ b/src/core/lombok/core/TypeLibrary.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2013 The Project Lombok Authors.
+ * Copyright (C) 2009-2015 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -37,6 +37,7 @@ import java.util.Map;
public class TypeLibrary {
private final Map unqualifiedToQualifiedMap;
private final String unqualified, qualified;
+ private boolean locked;
public TypeLibrary() {
unqualifiedToQualifiedMap = new HashMap();
@@ -44,6 +45,10 @@ public class TypeLibrary {
qualified = null;
}
+ public void lock() {
+ this.locked = true;
+ }
+
private TypeLibrary(String fqnSingleton) {
unqualifiedToQualifiedMap = null;
qualified = fqnSingleton;
@@ -53,6 +58,7 @@ public class TypeLibrary {
} else {
unqualified = fqnSingleton.substring(idx + 1);
}
+ locked = true;
}
public static TypeLibrary createLibraryForSingleType(String fqnSingleton) {
@@ -65,6 +71,7 @@ public class TypeLibrary {
* @param fullyQualifiedTypeName the FQN type name, such as 'java.lang.String'.
*/
public void addType(String fullyQualifiedTypeName) {
+ if (locked) throw new IllegalStateException("locked");
fullyQualifiedTypeName = fullyQualifiedTypeName.replace("$", ".");
int idx = fullyQualifiedTypeName.lastIndexOf('.');
if (idx == -1) throw new IllegalArgumentException(
diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java
index 621d8760..87462921 100644
--- a/src/core/lombok/core/handlers/HandlerUtil.java
+++ b/src/core/lombok/core/handlers/HandlerUtil.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013-2014 The Project Lombok Authors.
+ * Copyright (C) 2013-2015 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -87,6 +87,9 @@ public class HandlerUtil {
return true;
}
+ public static String autoSingularize(String plural) {
+ return Singulars.autoSingularize(plural);
+ }
public static void handleFlagUsage(LombokNode, ?, ?> node, ConfigurationKey key, String featureName) {
FlagUsageType fut = node.getAst().readConfiguration(key);
@@ -303,7 +306,7 @@ public class HandlerUtil {
return booleanPrefix + fName.substring(2);
}
- return buildName(isBoolean ? booleanPrefix : normalPrefix, fName);
+ return buildAccessorName(isBoolean ? booleanPrefix : normalPrefix, fName);
}
/**
@@ -375,8 +378,8 @@ public class HandlerUtil {
if (adhereToFluent && fluent) {
names.add(baseName);
} else {
- names.add(buildName(normalPrefix, baseName));
- if (!normalPrefix.equals(booleanPrefix)) names.add(buildName(booleanPrefix, baseName));
+ names.add(buildAccessorName(normalPrefix, baseName));
+ if (!normalPrefix.equals(booleanPrefix)) names.add(buildAccessorName(booleanPrefix, baseName));
}
}
@@ -407,7 +410,7 @@ public class HandlerUtil {
* @param suffix Something like {@code running}.
* @return prefix + smartly title-cased suffix. For example, {@code setRunning}.
*/
- private static String buildName(String prefix, String suffix) {
+ public static String buildAccessorName(String prefix, String suffix) {
if (suffix.length() == 0) return prefix;
if (prefix.length() == 0) return suffix;
diff --git a/src/core/lombok/core/handlers/Singulars.java b/src/core/lombok/core/handlers/Singulars.java
new file mode 100644
index 00000000..87895790
--- /dev/null
+++ b/src/core/lombok/core/handlers/Singulars.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.core.handlers;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Singulars {
+ private static final List SINGULAR_STORE; // intended to be immutable.
+
+ static {
+ SINGULAR_STORE = new ArrayList();
+
+ try {
+ InputStream in = Singulars.class.getResourceAsStream("singulars.txt");
+ try {
+ BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
+ for (String line = br.readLine(); line != null; line = br.readLine()) {
+ line = line.trim();
+ if (line.startsWith("#") || line.isEmpty()) continue;
+ if (line.endsWith(" =")) {
+ SINGULAR_STORE.add(line.substring(0, line.length() - 2));
+ SINGULAR_STORE.add("");
+ continue;
+ }
+
+ int idx = line.indexOf(" = ");
+ SINGULAR_STORE.add(line.substring(0, idx));
+ SINGULAR_STORE.add(line.substring(idx + 3));
+ }
+ } finally {
+ try {
+ in.close();
+ } catch (Throwable ignore) {}
+ }
+ } catch (IOException e) {
+ SINGULAR_STORE.clear();
+ }
+ }
+
+ public static String autoSingularize(String in) {
+ final int inLen = in.length();
+ for (int i = 0; i < SINGULAR_STORE.size(); i+= 2) {
+ final String lastPart = SINGULAR_STORE.get(i);
+ final boolean wholeWord = Character.isUpperCase(lastPart.charAt(0));
+ final int endingOnly = lastPart.charAt(0) == '-' ? 1 : 0;
+ final int len = lastPart.length();
+ if (inLen < len) continue;
+ if (!in.regionMatches(true, inLen - len + endingOnly, lastPart, endingOnly, len - endingOnly)) continue;
+ if (wholeWord && inLen != len && !Character.isUpperCase(in.charAt(inLen - len))) continue;
+
+ String replacement = SINGULAR_STORE.get(i + 1);
+ if (replacement.equals("!")) return null;
+
+ boolean capitalizeFirst = !replacement.isEmpty() && Character.isUpperCase(in.charAt(inLen - len + endingOnly));
+ String pre = in.substring(0, inLen - len + endingOnly);
+ String post = capitalizeFirst ? Character.toUpperCase(replacement.charAt(0)) + replacement.substring(1) : replacement;
+ return pre + post;
+ }
+
+ return null;
+ }
+}
diff --git a/src/core/lombok/core/handlers/singulars.txt b/src/core/lombok/core/handlers/singulars.txt
new file mode 100644
index 00000000..33ef5629
--- /dev/null
+++ b/src/core/lombok/core/handlers/singulars.txt
@@ -0,0 +1,54 @@
+#Based on https://github.com/rails/rails/blob/efff6c1fd4b9e2e4c9f705a45879373cb34a5b0e/activesupport/lib/active_support/inflections.rb
+
+quizzes = quiz
+matrices = matrix
+indices = index
+vertices = vertex
+statuses = status
+aliases = alias
+alias = !
+species = !
+Axes = axis
+-axes = axe
+sexes = sex
+Testes = testis
+movies = movie
+octopodes = octopus
+buses = bus
+Mice = mouse
+Lice = louse
+News = !
+# We could add more detail (axemen, boatsmen, boogymen, cavemen, gentlemen, etc, but (A) there's stuff like 'cerumen', and (B) the 'men' ending is common in singulars and other languages.)
+# Therefore, the odds of a mistake are too high, so other than these 2 well known cases, so force the explicit singular.
+Men = man
+Women = woman
+minutiae = minutia
+shoes = shoe
+synopses = synopsis
+prognoses = prognosis
+theses = thesis
+diagnoses = diagnosis
+bases = base
+analyses = analysis
+Crises = crisis
+children = child
+moves = move
+zombies = zombie
+-quies = quy
+-us = !
+-is = !
+series = !
+-ies = y
+-oes = o
+hives = hive
+-tives = tive
+-sses = ss
+-ches = ch
+-xes = x
+-shes = sh
+-lves = lf
+-rves = rf
+-ves = fe
+-ss = !
+-us = !
+-s =
diff --git a/src/core/lombok/javac/JavacNode.java b/src/core/lombok/javac/JavacNode.java
index 6eef36eb..727692ac 100644
--- a/src/core/lombok/javac/JavacNode.java
+++ b/src/core/lombok/javac/JavacNode.java
@@ -66,40 +66,40 @@ public class JavacNode extends lombok.core.LombokNode elems) {
+ assert elems != null;
+
+ JavacTreeMaker maker = node.getTreeMaker();
+ JCExpression e = null;
+ for (String elem : elems) {
+ if (e == null) e = maker.Ident(node.toName(elem));
+ else e = maker.Select(e, node.toName(elem));
+ }
+ return e;
+ }
/**
* In javac, dotted access of any kind, from {@code java.lang.String} to {@code var.methodName}
* is represented by a fold-left of {@code Select} nodes with the leftmost string represented by
@@ -1013,7 +1025,6 @@ public class JavacHandlerUtil {
return e;
}
-
/**
* In javac, dotted access of any kind, from {@code java.lang.String} to {@code var.methodName}
diff --git a/test/core/src/lombok/RunAllTests.java b/test/core/src/lombok/RunAllTests.java
index 9f56b45b..1ca76af5 100644
--- a/test/core/src/lombok/RunAllTests.java
+++ b/test/core/src/lombok/RunAllTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011-2014 The Project Lombok Authors.
+ * Copyright (C) 2011-2015 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -26,6 +26,6 @@ import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
-@SuiteClasses({lombok.transform.RunTransformTests.class, lombok.bytecode.RunBytecodeTests.class, lombok.core.configuration.RunConfigurationTests.class})
+@SuiteClasses({lombok.transform.RunTransformTests.class, lombok.bytecode.RunBytecodeTests.class, lombok.core.configuration.RunConfigurationTests.class, lombok.core.RunCoreTests.class})
public class RunAllTests {
}
diff --git a/test/core/src/lombok/core/RunCoreTests.java b/test/core/src/lombok/core/RunCoreTests.java
new file mode 100644
index 00000000..8ac7cf81
--- /dev/null
+++ b/test/core/src/lombok/core/RunCoreTests.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.core;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+@RunWith(Suite.class)
+@SuiteClasses({TestSingulars.class})
+public class RunCoreTests {
+}
diff --git a/test/core/src/lombok/core/TestSingulars.java b/test/core/src/lombok/core/TestSingulars.java
new file mode 100644
index 00000000..7e5e2617
--- /dev/null
+++ b/test/core/src/lombok/core/TestSingulars.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Project Lombok Authors.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package lombok.core;
+
+import static lombok.core.handlers.Singulars.autoSingularize;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class TestSingulars {
+ @Test
+ public void testSingulars() {
+ assertEquals("axis", autoSingularize("axes"));
+ assertEquals("adjective", autoSingularize("adjectives"));
+ assertEquals("bus", autoSingularize("buses"));
+ assertEquals("octopus", autoSingularize("octopodes"));
+ assertEquals(null, autoSingularize("octopi"));
+ assertEquals("elf", autoSingularize("elves"));
+ assertEquals("jack", autoSingularize("jacks"));
+ assertEquals("colloquy", autoSingularize("colloquies"));
+ assertEquals(null, autoSingularize("series"));
+ assertEquals("man", autoSingularize("men"));
+ assertEquals(null, autoSingularize("highwaymen"));
+ assertEquals("caveMan", autoSingularize("caveMen"));
+ assertEquals(null, autoSingularize("jackss"));
+ assertEquals(null, autoSingularize("virus"));
+ assertEquals("quiz", autoSingularize("quizzes"));
+ assertEquals("database", autoSingularize("databases"));
+ assertEquals("dataBase", autoSingularize("dataBases"));
+ assertEquals("Query", autoSingularize("Queries"));
+ assertEquals("Movie", autoSingularize("Movies"));
+ }
+}