[wip] @Singular support.

This commit is contained in:
Reinier Zwitserloot committed 2015-01-03 23:48:49 +01:00
1 parent ff2ae3b22c
commit f180ab3c17
10 files changed
+319 -37

No files matched your search

+38
View File
@@ -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.
* <p>
*/
@Target({FIELD, PARAMETER})
@Retention(SOURCE)
public @interface Singular {
String value() default "";
}
+8 -1
View File
@@ -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<String, String> unqualifiedToQualifiedMap;
private final String unqualified, qualified;
private boolean locked;
public TypeLibrary() {
unqualifiedToQualifiedMap = new HashMap<String, String>();
@@ -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(
@@ -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<FlagUsageType> 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;
@@ -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<String> SINGULAR_STORE; // intended to be immutable.
static {
SINGULAR_STORE = new ArrayList<String>();
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;
}
}
@@ -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 =
+24 -24
View File
@@ -66,40 +66,40 @@ public class JavacNode extends lombok.core.LombokNode<JavacAST, JavacNode, JCTre
public void traverse(JavacASTVisitor visitor) {
switch (this.getKind()) {
case COMPILATION_UNIT:
visitor.visitCompilationUnit(this, (JCCompilationUnit)get());
visitor.visitCompilationUnit(this, (JCCompilationUnit) get());
ast.traverseChildren(visitor, this);
visitor.endVisitCompilationUnit(this, (JCCompilationUnit)get());
visitor.endVisitCompilationUnit(this, (JCCompilationUnit) get());
break;
case TYPE:
visitor.visitType(this, (JCClassDecl)get());
visitor.visitType(this, (JCClassDecl) get());
ast.traverseChildren(visitor, this);
visitor.endVisitType(this, (JCClassDecl)get());
visitor.endVisitType(this, (JCClassDecl) get());
break;
case FIELD:
visitor.visitField(this, (JCVariableDecl)get());
visitor.visitField(this, (JCVariableDecl) get());
ast.traverseChildren(visitor, this);
visitor.endVisitField(this, (JCVariableDecl)get());
visitor.endVisitField(this, (JCVariableDecl) get());
break;
case METHOD:
visitor.visitMethod(this, (JCMethodDecl)get());
visitor.visitMethod(this, (JCMethodDecl) get());
ast.traverseChildren(visitor, this);
visitor.endVisitMethod(this, (JCMethodDecl)get());
visitor.endVisitMethod(this, (JCMethodDecl) get());
break;
case INITIALIZER:
visitor.visitInitializer(this, (JCBlock)get());
visitor.visitInitializer(this, (JCBlock) get());
ast.traverseChildren(visitor, this);
visitor.endVisitInitializer(this, (JCBlock)get());
visitor.endVisitInitializer(this, (JCBlock) get());
break;
case ARGUMENT:
JCMethodDecl parentMethod = (JCMethodDecl) up().get();
visitor.visitMethodArgument(this, (JCVariableDecl)get(), parentMethod);
visitor.visitMethodArgument(this, (JCVariableDecl) get(), parentMethod);
ast.traverseChildren(visitor, this);
visitor.endVisitMethodArgument(this, (JCVariableDecl)get(), parentMethod);
visitor.endVisitMethodArgument(this, (JCVariableDecl) get(), parentMethod);
break;
case LOCAL:
visitor.visitLocal(this, (JCVariableDecl)get());
visitor.visitLocal(this, (JCVariableDecl) get());
ast.traverseChildren(visitor, this);
visitor.endVisitLocal(this, (JCVariableDecl)get());
visitor.endVisitLocal(this, (JCVariableDecl) get());
break;
case STATEMENT:
visitor.visitStatement(this, get());
@@ -109,21 +109,21 @@ public class JavacNode extends lombok.core.LombokNode<JavacAST, JavacNode, JCTre
case ANNOTATION:
switch (up().getKind()) {
case TYPE:
visitor.visitAnnotationOnType((JCClassDecl)up().get(), this, (JCAnnotation)get());
visitor.visitAnnotationOnType((JCClassDecl) up().get(), this, (JCAnnotation) get());
break;
case FIELD:
visitor.visitAnnotationOnField((JCVariableDecl)up().get(), this, (JCAnnotation)get());
visitor.visitAnnotationOnField((JCVariableDecl) up().get(), this, (JCAnnotation) get());
break;
case METHOD:
visitor.visitAnnotationOnMethod((JCMethodDecl)up().get(), this, (JCAnnotation)get());
visitor.visitAnnotationOnMethod((JCMethodDecl) up().get(), this, (JCAnnotation) get());
break;
case ARGUMENT:
JCVariableDecl argument = (JCVariableDecl)up().get();
JCMethodDecl method = (JCMethodDecl)up().up().get();
visitor.visitAnnotationOnMethodArgument(argument, method, this, (JCAnnotation)get());
JCVariableDecl argument = (JCVariableDecl) up().get();
JCMethodDecl method = (JCMethodDecl) up().up().get();
visitor.visitAnnotationOnMethodArgument(argument, method, this, (JCAnnotation) get());
break;
case LOCAL:
visitor.visitAnnotationOnLocal((JCVariableDecl)up().get(), this, (JCAnnotation)get());
visitor.visitAnnotationOnLocal((JCVariableDecl) up().get(), this, (JCAnnotation) get());
break;
default:
throw new AssertionError("Annotion not expected as child of a " + up().getKind());
@@ -138,9 +138,9 @@ public class JavacNode extends lombok.core.LombokNode<JavacAST, JavacNode, JCTre
@Override public String getName() {
final Name n;
if (node instanceof JCClassDecl) n = ((JCClassDecl)node).name;
else if (node instanceof JCMethodDecl) n = ((JCMethodDecl)node).name;
else if (node instanceof JCVariableDecl) n = ((JCVariableDecl)node).name;
if (node instanceof JCClassDecl) n = ((JCClassDecl) node).name;
else if (node instanceof JCMethodDecl) n = ((JCMethodDecl) node).name;
else if (node instanceof JCVariableDecl) n = ((JCVariableDecl) node).name;
else n = null;
return n == null ? null : n.toString();
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2009-2014 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
@@ -41,6 +41,7 @@ import lombok.Data;
import lombok.Getter;
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
import lombok.core.LombokImmutableList;
import lombok.core.AnnotationValues.AnnotationValue;
import lombok.core.TypeResolver;
import lombok.core.configuration.NullCheckExceptionType;
@@ -445,9 +446,9 @@ public class JavacHandlerUtil {
return HandlerUtil.shouldReturnThis0(accessors, field.getAst());
}
public static JCExpression cloneSelfType(JavacNode field) {
JavacNode typeNode = field;
JavacTreeMaker maker = field.getTreeMaker();
public static JCExpression cloneSelfType(JavacNode childOfType) {
JavacNode typeNode = childOfType;
JavacTreeMaker maker = childOfType.getTreeMaker();
while (typeNode != null && typeNode.getKind() != Kind.TYPE) typeNode = typeNode.up();
if (typeNode != null && typeNode.get() instanceof JCClassDecl) {
JCClassDecl type = (JCClassDecl) typeNode.get();
@@ -985,6 +986,17 @@ public class JavacHandlerUtil {
return chainDots(node, -1, null, null, elems);
}
public static JCExpression chainDots(JavacNode node, LombokImmutableList<String> 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}
+2 -2
View File
@@ -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 {
}
@@ -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 {
}
@@ -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"));
}
}