From f180ab3c17eb78b5c36821203fb4afc26ba6ddbb Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Sat, 3 Jan 2015 23:46:29 +0100 Subject: [wip] @Singular support. --- src/core/lombok/Singular.java | 38 ++++++++++ src/core/lombok/core/TypeLibrary.java | 9 ++- src/core/lombok/core/handlers/HandlerUtil.java | 13 ++-- src/core/lombok/core/handlers/Singulars.java | 86 ++++++++++++++++++++++ src/core/lombok/core/handlers/singulars.txt | 54 ++++++++++++++ src/core/lombok/javac/JavacNode.java | 48 ++++++------ .../lombok/javac/handlers/JavacHandlerUtil.java | 21 ++++-- test/core/src/lombok/RunAllTests.java | 4 +- test/core/src/lombok/core/RunCoreTests.java | 31 ++++++++ test/core/src/lombok/core/TestSingulars.java | 52 +++++++++++++ 10 files changed, 319 insertions(+), 37 deletions(-) create mode 100644 src/core/lombok/Singular.java create mode 100644 src/core/lombok/core/handlers/Singulars.java create mode 100644 src/core/lombok/core/handlers/singulars.txt create mode 100644 test/core/src/lombok/core/RunCoreTests.java create mode 100644 test/core/src/lombok/core/TestSingulars.java 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")); + } +} -- cgit