aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorReinier Zwitserloot <reinier@zwitserloot.com>2015-01-15 04:43:36 +0100
committerReinier Zwitserloot <reinier@zwitserloot.com>2015-01-15 04:43:36 +0100
commita05360a8eaba0de61f16f75816daf5a5af0a4567 (patch)
tree1f58ee60fe30397c1d0367799782225bc04483eb /src
parentd700d54ed6c7d171a6498b8727a8e4cfbe8454e2 (diff)
downloadlombok-a05360a8eaba0de61f16f75816daf5a5af0a4567.tar.gz
lombok-a05360a8eaba0de61f16f75816daf5a5af0a4567.tar.bz2
lombok-a05360a8eaba0de61f16f75816daf5a5af0a4567.zip
ecj @Builder @Singular support for j.u. sets and maps.
Diffstat (limited to 'src')
-rw-r--r--src/core/lombok/core/handlers/singulars.txt2
-rw-r--r--src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java51
-rw-r--r--src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java274
-rw-r--r--src/core/lombok/eclipse/handlers/HandleBuilder.java322
-rw-r--r--src/core/lombok/eclipse/handlers/SetGeneratedByVisitor.java651
-rw-r--r--src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java220
-rw-r--r--src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java146
-rw-r--r--src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java288
-rw-r--r--src/core/lombok/javac/handlers/HandleBuilder.java13
-rw-r--r--src/core/lombok/javac/handlers/JavacSingularsRecipes.java2
10 files changed, 1495 insertions, 474 deletions
diff --git a/src/core/lombok/core/handlers/singulars.txt b/src/core/lombok/core/handlers/singulars.txt
index 33ef5629..9976c76c 100644
--- a/src/core/lombok/core/handlers/singulars.txt
+++ b/src/core/lombok/core/handlers/singulars.txt
@@ -8,7 +8,7 @@ statuses = status
aliases = alias
alias = !
species = !
-Axes = axis
+Axes = !
-axes = axe
sexes = sex
Testes = testis
diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
index 8326e1d0..def9ee47 100644
--- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
+++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
@@ -143,7 +143,7 @@ public class EclipseHandlerUtil {
return getGeneratedBy(node) != null;
}
- public static ASTNode setGeneratedBy(ASTNode node, ASTNode source) {
+ public static <T extends ASTNode> T setGeneratedBy(T node, ASTNode source) {
ASTNode_generatedBy.set(node, source);
return node;
}
@@ -298,6 +298,10 @@ public class EclipseHandlerUtil {
return new SingleTypeReference(typeName, p);
}
+ public static TypeReference[] copyTypes(TypeReference[] refs) {
+ return copyTypes(refs, null);
+ }
+
/**
* Convenience method that creates a new array and copies each TypeReference in the source array via
* {@link #copyType(TypeReference, ASTNode)}.
@@ -312,6 +316,10 @@ public class EclipseHandlerUtil {
return outs;
}
+ public static TypeReference copyType(TypeReference ref) {
+ return copyType(ref, null);
+ }
+
/**
* You can't share TypeReference objects or subtle errors start happening.
* Unfortunately the TypeReference type hierarchy is complicated and there's no clone
@@ -336,22 +344,23 @@ public class EclipseHandlerUtil {
}
}
}
+
TypeReference typeRef = new ParameterizedQualifiedTypeReference(iRef.tokens, args, iRef.dimensions(), copy(iRef.sourcePositions));
- setGeneratedBy(typeRef, source);
+ if (source != null) setGeneratedBy(typeRef, source);
return typeRef;
}
if (ref instanceof ArrayQualifiedTypeReference) {
ArrayQualifiedTypeReference iRef = (ArrayQualifiedTypeReference) ref;
TypeReference typeRef = new ArrayQualifiedTypeReference(iRef.tokens, iRef.dimensions(), copy(iRef.sourcePositions));
- setGeneratedBy(typeRef, source);
+ if (source != null) setGeneratedBy(typeRef, source);
return typeRef;
}
if (ref instanceof QualifiedTypeReference) {
QualifiedTypeReference iRef = (QualifiedTypeReference) ref;
TypeReference typeRef = new QualifiedTypeReference(iRef.tokens, copy(iRef.sourcePositions));
- setGeneratedBy(typeRef, source);
+ if (source != null) setGeneratedBy(typeRef, source);
return typeRef;
}
@@ -368,14 +377,14 @@ public class EclipseHandlerUtil {
}
TypeReference typeRef = new ParameterizedSingleTypeReference(iRef.token, args, iRef.dimensions(), (long)iRef.sourceStart << 32 | iRef.sourceEnd);
- setGeneratedBy(typeRef, source);
+ if (source != null) setGeneratedBy(typeRef, source);
return typeRef;
}
if (ref instanceof ArrayTypeReference) {
ArrayTypeReference iRef = (ArrayTypeReference) ref;
TypeReference typeRef = new ArrayTypeReference(iRef.token, iRef.dimensions(), (long)iRef.sourceStart << 32 | iRef.sourceEnd);
- setGeneratedBy(typeRef, source);
+ if (source != null) setGeneratedBy(typeRef, source);
return typeRef;
}
@@ -386,14 +395,14 @@ public class EclipseHandlerUtil {
wildcard.sourceStart = original.sourceStart;
wildcard.sourceEnd = original.sourceEnd;
if (original.bound != null) wildcard.bound = copyType(original.bound, source);
- setGeneratedBy(wildcard, source);
+ if (source != null) setGeneratedBy(wildcard, source);
return wildcard;
}
if (ref instanceof SingleTypeReference) {
SingleTypeReference iRef = (SingleTypeReference) ref;
TypeReference typeRef = new SingleTypeReference(iRef.token, (long)iRef.sourceStart << 32 | iRef.sourceEnd);
- setGeneratedBy(typeRef, source);
+ if (source != null) setGeneratedBy(typeRef, source);
return typeRef;
}
@@ -444,8 +453,12 @@ public class EclipseHandlerUtil {
return typeMatches(type, node, ((Annotation)node.get()).type);
}
+ public static TypeReference cloneSelfType(EclipseNode context) {
+ return cloneSelfType(context, null);
+ }
+
public static TypeReference cloneSelfType(EclipseNode context, ASTNode source) {
- int pS = source.sourceStart, pE = source.sourceEnd;
+ int pS = source == null ? 0 : source.sourceStart, pE = source == null ? 0 : source.sourceEnd;
long p = (long)pS << 32 | pE;
EclipseNode type = context;
TypeReference result = null;
@@ -457,7 +470,7 @@ public class EclipseHandlerUtil {
int idx = 0;
for (TypeParameter param : typeDecl.typeParameters) {
TypeReference typeRef = new SingleTypeReference(param.name, (long)param.sourceStart << 32 | param.sourceEnd);
- setGeneratedBy(typeRef, source);
+ if (source != null) setGeneratedBy(typeRef, source);
refs[idx++] = typeRef;
}
result = new ParameterizedSingleTypeReference(typeDecl.name, refs, 0, p);
@@ -465,7 +478,7 @@ public class EclipseHandlerUtil {
result = new SingleTypeReference(((TypeDeclaration)type.get()).name, p);
}
}
- if (result != null) setGeneratedBy(result, source);
+ if (result != null && source != null) setGeneratedBy(result, source);
return result;
}
@@ -865,7 +878,7 @@ public class EclipseHandlerUtil {
}
static Expression createFieldAccessor(EclipseNode field, FieldAccess fieldAccess, ASTNode source) {
- int pS = source.sourceStart, pE = source.sourceEnd;
+ int pS = source == null ? 0 : source.sourceStart, pE = source == null ? 0 : source.sourceEnd;
long p = (long)pS << 32 | pE;
boolean lookForGetter = lookForGetter(field, fieldAccess);
@@ -881,14 +894,17 @@ public class EclipseHandlerUtil {
ref.receiver = new SingleNameReference(((TypeDeclaration)containerNode.get()).name, p);
} else {
Expression smallRef = new FieldReference(field.getName().toCharArray(), p);
- setGeneratedBy(smallRef, source);
+ if (source != null) setGeneratedBy(smallRef, source);
return smallRef;
}
} else {
ref.receiver = new ThisReference(pS, pE);
}
- setGeneratedBy(ref, source);
- setGeneratedBy(ref.receiver, source);
+
+ if (source != null) {
+ setGeneratedBy(ref, source);
+ setGeneratedBy(ref.receiver, source);
+ }
return ref;
}
@@ -1489,7 +1505,7 @@ public class EclipseHandlerUtil {
* with eclipse versions before 3.7.
*/
public static IntLiteral makeIntLiteral(char[] token, ASTNode source) {
- int pS = source.sourceStart, pE = source.sourceEnd;
+ int pS = source == null ? 0 : source.sourceStart, pE = source == null ? 0 : source.sourceEnd;
IntLiteral result;
try {
if (intLiteralConstructor != null) {
@@ -1504,7 +1520,8 @@ public class EclipseHandlerUtil {
} catch (InstantiationException e) {
throw Lombok.sneakyThrow(e);
}
- setGeneratedBy(result, source);
+
+ if (source != null) setGeneratedBy(result, source);
return result;
}
diff --git a/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java
new file mode 100644
index 00000000..0157552b
--- /dev/null
+++ b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java
@@ -0,0 +1,274 @@
+package lombok.eclipse.handlers;
+
+import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression;
+import org.eclipse.jdt.internal.compiler.ast.EqualExpression;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.FieldReference;
+import org.eclipse.jdt.internal.compiler.ast.IntLiteral;
+import org.eclipse.jdt.internal.compiler.ast.MessageSend;
+import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
+import org.eclipse.jdt.internal.compiler.ast.OperatorIds;
+import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.ThisReference;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.ast.Wildcard;
+import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
+
+import lombok.core.LombokImmutableList;
+import lombok.core.SpiLoadUtil;
+import lombok.core.TypeLibrary;
+import lombok.eclipse.EclipseNode;
+
+/*
+ * 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.
+ */
+public class EclipseSingularsRecipes {
+ private static final EclipseSingularsRecipes INSTANCE = new EclipseSingularsRecipes();
+ private final Map<String, EclipseSingularizer> singularizers = new HashMap<String, EclipseSingularizer>();
+ private final TypeLibrary singularizableTypes = new TypeLibrary();
+
+ private EclipseSingularsRecipes() {
+ try {
+ loadAll(singularizableTypes, singularizers);
+ singularizableTypes.lock();
+ } catch (IOException e) {
+ System.err.println("Lombok's @Singularizable feature is broken due to misconfigured SPI files: " + e);
+ }
+ }
+
+ private static void loadAll(TypeLibrary library, Map<String, EclipseSingularizer> map) throws IOException {
+ for (EclipseSingularizer handler : SpiLoadUtil.findServices(EclipseSingularizer.class, EclipseSingularizer.class.getClassLoader())) {
+ for (String type : handler.getSupportedTypes()) {
+ EclipseSingularizer existingSingularizer = map.get(type);
+ if (existingSingularizer != null) {
+ EclipseSingularizer toKeep = existingSingularizer.getClass().getName().compareTo(handler.getClass().getName()) > 0 ? handler : existingSingularizer;
+ System.err.println("Multiple singularizers found for type " + type + "; the alphabetically first class is used: " + toKeep.getClass().getName());
+ map.put(type, toKeep);
+ } else {
+ map.put(type, handler);
+ library.addType(type);
+ }
+ }
+ }
+ }
+
+ public static EclipseSingularsRecipes get() {
+ return INSTANCE;
+ }
+
+ public String toQualified(String typeReference) {
+ return singularizableTypes.toQualified(typeReference);
+ }
+
+ public EclipseSingularizer getSingularizer(String fqn) {
+ return singularizers.get(fqn);
+ }
+
+ public static final class SingularData {
+ private final EclipseNode annotation;
+ private final char[] singularName;
+ private final char[] pluralName;
+ private final List<TypeReference> typeArgs;
+ private final String targetFqn;
+ private final EclipseSingularizer singularizer;
+
+ public SingularData(EclipseNode annotation, char[] singularName, char[] pluralName, List<TypeReference> typeArgs, String targetFqn, EclipseSingularizer singularizer) {
+ this.annotation = annotation;
+ this.singularName = singularName;
+ this.pluralName = pluralName;
+ this.typeArgs = typeArgs;
+ this.targetFqn = targetFqn;
+ this.singularizer = singularizer;
+ }
+
+ public EclipseNode getAnnotation() {
+ return annotation;
+ }
+
+ public char[] getSingularName() {
+ return singularName;
+ }
+
+ public char[] getPluralName() {
+ return pluralName;
+ }
+
+ public List<TypeReference> getTypeArgs() {
+ return typeArgs;
+ }
+
+ public String getTargetFqn() {
+ return targetFqn;
+ }
+
+ public EclipseSingularizer getSingularizer() {
+ return singularizer;
+ }
+
+ public String getTargetSimpleType() {
+ int idx = targetFqn.lastIndexOf(".");
+ return idx == -1 ? targetFqn : targetFqn.substring(idx + 1);
+ }
+ }
+
+ public static abstract class EclipseSingularizer {
+ protected static final long[] NULL_POSS = {0L};
+ public abstract LombokImmutableList<String> getSupportedTypes();
+
+ public abstract java.util.List<EclipseNode> generateFields(SingularData data, EclipseNode builderType);
+ public abstract void generateMethods(SingularData data, EclipseNode builderType, boolean fluent, boolean chain);
+ public abstract void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName);
+
+ public boolean requiresCleaning() {
+ try {
+ return !getClass().getMethod("appendCleaningCode", SingularData.class, EclipseNode.class, List.class).getDeclaringClass().equals(EclipseSingularizer.class);
+ } catch (NoSuchMethodException e) {
+ return false;
+ }
+ }
+
+ public void appendCleaningCode(SingularData data, EclipseNode builderType, List<Statement> statements) {
+ }
+
+ // -- Utility methods --
+
+ /**
+ * Adds the requested number of type arguments to the provided type, copying each argument in {@code typeArgs}. If typeArgs is too long, the extra elements are ignored.
+ * If {@code typeArgs} is null or too short, {@code java.lang.Object} will be substituted for each missing type argument.
+ *
+ * @param count The number of type arguments requested.
+ * @param addExtends If {@code true}, all bounds are either '? extends X' or just '?'. If false, the reverse is applied, and '? extends Foo' is converted to Foo, '?' to Object, etc.
+ * @param node Some node in the same AST. Just used to obtain makers and contexts and such.
+ * @param type The type to add generics to.
+ * @param typeArgs the list of type args to clone.
+ * @param source The source annotation that is the root cause of this code generation.
+ */
+ protected TypeReference addTypeArgs(int count, boolean addExtends, EclipseNode node, TypeReference type, List<TypeReference> typeArgs) {
+ if (count < 0) throw new IllegalArgumentException("count is negative");
+ if (count == 0) return type;
+ List<TypeReference> arguments = new ArrayList<TypeReference>();
+
+ if (typeArgs != null) for (TypeReference orig : typeArgs) {
+ Wildcard wildcard = orig instanceof Wildcard ? (Wildcard) orig : null;
+ if (!addExtends) {
+ if (wildcard != null && (wildcard.kind == Wildcard.UNBOUND || wildcard.kind == Wildcard.SUPER)) {
+ arguments.add(new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS));
+ } else if (wildcard != null && wildcard.kind == Wildcard.EXTENDS) {
+ try {
+ arguments.add(copyType(wildcard.bound));
+ } catch (Exception e) {
+ arguments.add(new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS));
+ }
+ } else {
+ arguments.add(copyType(orig));
+ }
+ } else {
+ if (wildcard != null && (wildcard.kind == Wildcard.UNBOUND || wildcard.kind == Wildcard.SUPER)) {
+ Wildcard w = new Wildcard(Wildcard.UNBOUND);
+ arguments.add(w);
+ } else if (wildcard != null && wildcard.kind == Wildcard.EXTENDS) {
+ arguments.add(copyType(orig));
+ } else {
+ Wildcard w = new Wildcard(Wildcard.EXTENDS);
+ w.bound = copyType(orig);
+ arguments.add(w);
+ }
+ }
+ if (--count == 0) break;
+ }
+
+ while (count-- > 0) {
+ if (addExtends) {
+ arguments.add(new Wildcard(Wildcard.UNBOUND));
+ } else {
+ arguments.add(new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS));
+ }
+ }
+
+ if (type instanceof SingleTypeReference) {
+ type = new ParameterizedSingleTypeReference(((SingleTypeReference) type).token, arguments.toArray(new TypeReference[arguments.size()]), 0, 0L);
+ } else if (type instanceof QualifiedTypeReference) {
+ QualifiedTypeReference qtr = (QualifiedTypeReference) type;
+ TypeReference[][] trs = new TypeReference[qtr.tokens.length][];
+ trs[qtr.tokens.length - 1] = arguments.toArray(new TypeReference[arguments.size()]);
+ type = new ParameterizedQualifiedTypeReference(((QualifiedTypeReference) type).tokens, trs, 0, NULL_POSS);
+ } else {
+ node.addError("Don't know how to clone-and-parameterize type: " + type);
+ }
+
+ return type;
+ }
+
+ private static final char[] SIZE_TEXT = new char[] {'s', 'i', 'z', 'e'};
+
+ /** Generates 'this.<em>name</em>.size()' as an expression; if nullGuard is true, it's this.name == null ? 0 : this.name.size(). */
+ protected Expression getSize(EclipseNode builderType, char[] name, boolean nullGuard) {
+ MessageSend invoke = new MessageSend();
+ ThisReference thisRef = new ThisReference(0, 0);
+ FieldReference thisDotName = new FieldReference(name, 0L);
+ thisDotName.receiver = thisRef;
+ invoke.receiver = thisDotName;
+ invoke.selector = SIZE_TEXT;
+ if (!nullGuard) return invoke;
+
+ ThisReference cdnThisRef = new ThisReference(0, 0);
+ FieldReference cdnThisDotName = new FieldReference(name, 0L);
+ cdnThisDotName.receiver = cdnThisRef;
+ NullLiteral nullLiteral = new NullLiteral(0, 0);
+ EqualExpression isNull = new EqualExpression(cdnThisDotName, nullLiteral, OperatorIds.EQUAL_EQUAL);
+ IntLiteral zeroLiteral = makeIntLiteral(new char[] {'0'}, null);
+ ConditionalExpression conditional = new ConditionalExpression(isNull, zeroLiteral, invoke);
+ return conditional;
+ }
+
+ protected TypeReference cloneParamType(int index, List<TypeReference> typeArgs, EclipseNode builderType) {
+ if (typeArgs != null && typeArgs.size() > index) {
+ TypeReference originalType = typeArgs.get(index);
+ if (originalType instanceof Wildcard) {
+ Wildcard wOriginalType = (Wildcard) originalType;
+ if (wOriginalType.kind == Wildcard.EXTENDS) {
+ try {
+ return copyType(wOriginalType.bound);
+ } catch (Exception e) {
+ // fallthrough
+ }
+ }
+ } else {
+ return copyType(originalType);
+ }
+ }
+
+ return new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS);
+ }
+ }
+}
diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java
index 80f80ddd..365256c7 100644
--- a/src/core/lombok/eclipse/handlers/HandleBuilder.java
+++ b/src/core/lombok/eclipse/handlers/HandleBuilder.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
@@ -32,49 +32,74 @@ import java.util.List;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.Assignment;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.FalseLiteral;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.FieldReference;
+import org.eclipse.jdt.internal.compiler.ast.IfStatement;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.OperatorIds;
+import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ReturnStatement;
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference;
import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.ThisReference;
+import org.eclipse.jdt.internal.compiler.ast.TrueLiteral;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeParameter;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.ast.UnaryExpression;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
+import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
import org.mangosdk.spi.ProviderFor;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.ConfigurationKeys;
+import lombok.Singular;
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.eclipse.Eclipse;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
+import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer;
+import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData;
import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists;
import lombok.experimental.NonFinal;
@ProviderFor(EclipseAnnotationHandler.class)
@HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes.
public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
+ private static final char[] CLEAN_FIELD_NAME = "$lombokUnclean".toCharArray();
+ private static final char[] CLEAN_METHOD_NAME = "$lombokClean".toCharArray();
+
private static final boolean toBoolean(Object expr, boolean defaultValue) {
if (expr == null) return defaultValue;
return ((Boolean) expr).booleanValue();
}
+ private static class BuilderFieldData {
+ TypeReference type;
+ char[] name;
+ SingularData singularData;
+
+ List<EclipseNode> createdFields = new ArrayList<EclipseNode>();
+ }
+
@Override public void handle(AnnotationValues<Builder> annotation, Annotation ast, EclipseNode annotationNode) {
handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.BUILDER_FLAG_USAGE, "@Builder");
@@ -102,21 +127,21 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
EclipseNode parent = annotationNode.up();
- List<TypeReference> typesOfParameters = new ArrayList<TypeReference>();
- List<char[]> namesOfParameters = new ArrayList<char[]>();
+ List<BuilderFieldData> builderFields = new ArrayList<BuilderFieldData>();
TypeReference returnType;
TypeParameter[] typeParams;
TypeReference[] thrownExceptions;
char[] nameOfStaticBuilderMethod;
EclipseNode tdParent;
- AbstractMethodDeclaration fillParametersFrom = null;
+ EclipseNode fillParametersFrom = parent.get() instanceof AbstractMethodDeclaration ? parent : null;
+ boolean addCleaning = false;
if (parent.get() instanceof TypeDeclaration) {
tdParent = parent;
TypeDeclaration td = (TypeDeclaration) tdParent.get();
- List<EclipseNode> fields = new ArrayList<EclipseNode>();
+ List<EclipseNode> allFields = new ArrayList<EclipseNode>();
@SuppressWarnings("deprecation")
boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent));
for (EclipseNode fieldNode : HandleConstructor.findAllFields(tdParent)) {
@@ -125,12 +150,15 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
// non-final fields final, but @Value's handler hasn't done this yet, so we have to do this math ourselves.
// Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that.
if (fd.initialization != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue;
- namesOfParameters.add(removePrefixFromField(fieldNode));
- typesOfParameters.add(fd.type);
- fields.add(fieldNode);
+ BuilderFieldData bfd = new BuilderFieldData();
+ bfd.name = removePrefixFromField(fieldNode);
+ bfd.type = fd.type;
+ bfd.singularData = getSingularData(fieldNode);
+ builderFields.add(bfd);
+ allFields.add(fieldNode);
}
- new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, fields, null, SkipIfConstructorExists.I_AM_BUILDER, null,
+ new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, allFields, null, SkipIfConstructorExists.I_AM_BUILDER, null,
Collections.<Annotation>emptyList(), annotationNode);
returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p);
@@ -147,7 +175,6 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
tdParent = parent.up();
TypeDeclaration td = (TypeDeclaration) tdParent.get();
- fillParametersFrom = cd;
returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p);
typeParams = td.typeParameters;
thrownExceptions = cd.thrownExceptions;
@@ -160,7 +187,6 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
annotationNode.addError("@Builder is only supported on types, constructors, and static methods.");
return;
}
- fillParametersFrom = md;
returnType = copyType(md.returnType, ast);
typeParams = md.typeParameters;
thrownExceptions = md.thrownExceptions;
@@ -200,9 +226,14 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
}
if (fillParametersFrom != null) {
- if (fillParametersFrom.arguments != null) for (Argument a : fillParametersFrom.arguments) {
- namesOfParameters.add(a.name);
- typesOfParameters.add(a.type);
+ for (EclipseNode param : fillParametersFrom.down()) {
+ if (param.getKind() != Kind.ARGUMENT) continue;
+ BuilderFieldData bfd = new BuilderFieldData();
+ Argument arg = (Argument) param.get();
+ bfd.name = arg.name;
+ bfd.type = arg.type;
+ bfd.singularData = getSingularData(param);
+ builderFields.add(bfd);
}
}
@@ -212,11 +243,23 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
} else {
sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode);
}
- List<EclipseNode> fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast);
- List<AbstractMethodDeclaration> newMethods = new ArrayList<AbstractMethodDeclaration>();
- for (EclipseNode fieldNode : fieldNodes) {
- MethodDeclaration newMethod = makeSetterMethodForBuilder(builderType, fieldNode, annotationNode, fluent, chain);
- if (newMethod != null) newMethods.add(newMethod);
+
+ for (BuilderFieldData bfd : builderFields) {
+ if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) {
+ if (bfd.singularData.getSingularizer().requiresCleaning()) {
+ addCleaning = true;
+ break;
+ }
+ }
+ }
+
+ generateBuilderFields(builderType, builderFields);
+ if (addCleaning) {
+ FieldDeclaration cleanDecl = new FieldDeclaration(CLEAN_FIELD_NAME, 0, -1);
+ cleanDecl.declarationSourceEnd = -1;
+ cleanDecl.modifiers = ClassFileConstants.AccPrivate;
+ cleanDecl.type = TypeReference.baseTypeReference(TypeIds.T_boolean, 0);
+ injectField(builderType, cleanDecl);
}
if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) {
@@ -226,128 +269,181 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> {
if (cd != null) injectMethod(builderType, cd);
}
- for (AbstractMethodDeclaration newMethod : newMethods) injectMethod(builderType, newMethod);
+ for (BuilderFieldData bfd : builderFields) {
+ makeSetterMethodsForBuilder(builderType, bfd, annotationNode, fluent, chain);
+ }
+
if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) {
- MethodDeclaration md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, ast, thrownExceptions);
+ MethodDeclaration md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, builderFields, builderType, thrownExceptions, addCleaning);
if (md != null) injectMethod(builderType, md);
}
if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) {
+ List<EclipseNode> fieldNodes = new ArrayList<EclipseNode>();
+ for (BuilderFieldData bfd : builderFields) {
+ fieldNodes.addAll(bfd.createdFields);
+ }
MethodDeclaration md = HandleToString.createToString(builderType, fieldNodes, true, false, ast, FieldAccess.ALWAYS_FIELD);
if (md != null) injectMethod(builderType, md);
}
+ if (addCleaning) injectMethod(builderType, generateCleanMethod(builderFields, builderType));
+
if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) {
MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast);
if (md != null) injectMethod(tdParent, md);
}
+
+ builderType.get().traverse(new SetGeneratedByVisitor(ast), null);
}
- public MethodDeclaration generateBuilderMethod(String builderMethodName, String builderClassName, EclipseNode type, TypeParameter[] typeParams, ASTNode source) {
- int pS = source.sourceStart, pE = source.sourceEnd;
- long p = (long) pS << 32 | pE;
+ private MethodDeclaration generateCleanMethod(List<BuilderFieldData> builderFields, EclipseNode builderType) {
+ List<Statement> statements = new ArrayList<Statement>();
- MethodDeclaration out = new MethodDeclaration(
- ((CompilationUnitDeclaration) type.top().get()).compilationResult);
- out.selector = builderMethodName.toCharArray();
- out.modifiers = ClassFileConstants.AccPublic | ClassFileConstants.AccStatic;
- out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG;
- out.returnType = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p);
- out.typeParameters = copyTypeParams(typeParams, source);
- AllocationExpression invoke = new AllocationExpression();
- invoke.type = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p);
- out.statements = new Statement[] {new ReturnStatement(invoke, pS, pE)};
+ for (BuilderFieldData bfd : builderFields) {
+ if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) {
+ bfd.singularData.getSingularizer().appendCleaningCode(bfd.singularData, builderType, statements);
+ }
+ }
- out.traverse(new SetGeneratedByVisitor(source), ((TypeDeclaration) type.get()).scope);
- return out;
+ FieldReference thisUnclean = new FieldReference(CLEAN_FIELD_NAME, 0);
+ thisUnclean.receiver = new ThisReference(0, 0);
+ statements.add(new Assignment(thisUnclean, new FalseLiteral(0, 0), 0));
+ MethodDeclaration decl =new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult);
+ decl.selector = CLEAN_METHOD_NAME;
+ decl.modifiers = ClassFileConstants.AccPrivate;
+ decl.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG;
+ decl.returnType = TypeReference.baseTypeReference(TypeIds.T_void, 0);
+ decl.statements = statements.toArray(new Statement[0]);
+ return decl;
}
- public MethodDeclaration generateBuildMethod(String name, char[] staticName, TypeReference returnType, List<char[]> fieldNames, EclipseNode type, ASTNode source, TypeReference[] thrownExceptions) {
- int pS = source.sourceStart, pE = source.sourceEnd;
- long p = (long) pS << 32 | pE;
-
+ public MethodDeclaration generateBuildMethod(String name, char[] staticName, TypeReference returnType, List<BuilderFieldData> builderFields, EclipseNode type, TypeReference[] thrownExceptions, boolean addCleaning) {
MethodDeclaration out = new MethodDeclaration(
((CompilationUnitDeclaration) type.top().get()).compilationResult);
+ out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG;
+ List<Statement> statements = new ArrayList<Statement>();
+
+ if (addCleaning) {
+ FieldReference thisUnclean = new FieldReference(CLEAN_FIELD_NAME, 0);
+ thisUnclean.receiver = new ThisReference(0, 0);
+ Expression notClean = new UnaryExpression(thisUnclean, OperatorIds.NOT);
+ MessageSend invokeClean = new MessageSend();
+ invokeClean.selector = CLEAN_METHOD_NAME;
+ statements.add(new IfStatement(notClean, invokeClean, 0, 0));
+ }
+
+ for (BuilderFieldData bfd : builderFields) {
+ if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) {
+ bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, type, statements, bfd.name);
+ }
+ }
+
+ List<Expression> args = new ArrayList<Expression>();
+ for (BuilderFieldData bfd : builderFields) {
+ args.add(new SingleNameReference(bfd.name, 0L));
+ }
+
+ if (addCleaning) {
+ FieldReference thisUnclean = new FieldReference(CLEAN_FIELD_NAME, 0);
+ thisUnclean.receiver = new ThisReference(0, 0);
+ statements.add(new Assignment(thisUnclean, new TrueLiteral(0, 0), 0));
+ }
out.modifiers = ClassFileConstants.AccPublic;
- TypeDeclaration typeDecl = (TypeDeclaration) type.get();
out.selector = name.toCharArray();
- out.thrownExceptions = copyTypes(thrownExceptions, source);
+ out.thrownExceptions = copyTypes(thrownExceptions);
out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG;
out.returnType = returnType;
- List<Expression> assigns = new ArrayList<Expression>();
- for (char[] fieldName : fieldNames) {
- SingleNameReference nameRef = new SingleNameReference(fieldName, p);
- assigns.add(nameRef);
- }
-
- Statement statement;
-
if (staticName == null) {
AllocationExpression allocationStatement = new AllocationExpression();
- allocationStatement.type = copyType(out.returnType, source);
- allocationStatement.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]);
- statement = new ReturnStatement(allocationStatement, (int)(p >> 32), (int)p);
+ allocationStatement.type = copyType(out.returnType);
+ allocationStatement.arguments = args.isEmpty() ? null : args.toArray(new Expression[args.size()]);
+ statements.add(new ReturnStatement(allocationStatement, 0, 0));
} else {
MessageSend invoke = new