aboutsummaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorReinier Zwitserloot <reinier@zwitserloot.com>2013-07-22 23:23:46 +0200
committerReinier Zwitserloot <reinier@zwitserloot.com>2013-07-22 23:23:46 +0200
commit45697b50816df79475a8bb69dc89ff68747fbfe6 (patch)
tree25cb023eec1f74baf5063cc5a58a5351ee43d6f0 /src/core
parent4c03e3d220900431085897878d4888bf530b31ec (diff)
parentdeed98be16e5099af52d951fc611f86a82a42858 (diff)
downloadlombok-45697b50816df79475a8bb69dc89ff68747fbfe6.tar.gz
lombok-45697b50816df79475a8bb69dc89ff68747fbfe6.tar.bz2
lombok-45697b50816df79475a8bb69dc89ff68747fbfe6.zip
Merge branch 'master' into jdk8. Also added some major fixes whilst merging.
Conflicts: src/core/lombok/javac/handlers/JavacHandlerUtil.java src/utils/lombok/javac/CommentCatcher.java src/utils/lombok/javac/Javac.java
Diffstat (limited to 'src/core')
-rw-r--r--src/core/lombok/Data.java4
-rw-r--r--src/core/lombok/NonNull.java16
-rw-r--r--src/core/lombok/Value.java58
-rw-r--r--src/core/lombok/bytecode/PreventNullAnalysisRemover.java9
-rw-r--r--src/core/lombok/bytecode/SneakyThrowsRemover.java119
-rw-r--r--src/core/lombok/core/AST.java61
-rw-r--r--src/core/lombok/core/AnnotationProcessor.java7
-rw-r--r--src/core/lombok/core/AnnotationValues.java19
-rw-r--r--src/core/lombok/core/ImportList.java43
-rw-r--r--src/core/lombok/core/LombokInternalAliasing.java52
-rw-r--r--src/core/lombok/core/LombokNode.java74
-rw-r--r--src/core/lombok/core/PostCompiler.java4
-rw-r--r--src/core/lombok/core/TransformationsUtil.java25
-rw-r--r--src/core/lombok/core/TypeLibrary.java72
-rw-r--r--src/core/lombok/core/TypeResolver.java95
-rw-r--r--src/core/lombok/core/Version.java4
-rw-r--r--src/core/lombok/core/handlers/HandlerUtil.java48
-rw-r--r--src/core/lombok/eclipse/EclipseAST.java48
-rw-r--r--src/core/lombok/eclipse/EclipseAnnotationHandler.java2
-rw-r--r--src/core/lombok/eclipse/EclipseAstProblemView.java9
-rw-r--r--src/core/lombok/eclipse/EclipseImportList.java136
-rw-r--r--src/core/lombok/eclipse/HandlerLibrary.java41
-rw-r--r--src/core/lombok/eclipse/TransformEclipseAST.java2
-rw-r--r--src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java94
-rw-r--r--src/core/lombok/eclipse/handlers/HandleBuilder.java376
-rw-r--r--src/core/lombok/eclipse/handlers/HandleConstructor.java55
-rw-r--r--src/core/lombok/eclipse/handlers/HandleData.java3
-rw-r--r--src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java23
-rw-r--r--src/core/lombok/eclipse/handlers/HandleFieldDefaults.java10
-rw-r--r--src/core/lombok/eclipse/handlers/HandleGetter.java122
-rw-r--r--src/core/lombok/eclipse/handlers/HandleLog.java15
-rw-r--r--src/core/lombok/eclipse/handlers/HandleSetter.java4
-rw-r--r--src/core/lombok/eclipse/handlers/HandleSneakyThrows.java24
-rw-r--r--src/core/lombok/eclipse/handlers/HandleToString.java26
-rw-r--r--src/core/lombok/eclipse/handlers/HandleValue.java5
-rw-r--r--src/core/lombok/eclipse/handlers/HandleWither.java2
-rw-r--r--src/core/lombok/eclipse/handlers/NonNullHandler.java147
-rw-r--r--src/core/lombok/experimental/Builder.java137
-rw-r--r--src/core/lombok/experimental/Value.java2
-rw-r--r--src/core/lombok/extern/apachecommons/CommonsLog.java3
-rw-r--r--src/core/lombok/extern/java/Log.java3
-rw-r--r--src/core/lombok/extern/log4j/Log4j.java3
-rw-r--r--src/core/lombok/extern/log4j/Log4j2.java62
-rw-r--r--src/core/lombok/extern/slf4j/Slf4j.java3
-rw-r--r--src/core/lombok/extern/slf4j/XSlf4j.java3
-rw-r--r--src/core/lombok/javac/FindTypeVarScanner.java2
-rw-r--r--src/core/lombok/javac/HandlerLibrary.java32
-rw-r--r--src/core/lombok/javac/Javac6BasedLombokOptions.java3
-rw-r--r--src/core/lombok/javac/JavacAST.java89
-rw-r--r--src/core/lombok/javac/JavacAnnotationHandler.java2
-rw-r--r--src/core/lombok/javac/JavacImportList.java112
-rw-r--r--src/core/lombok/javac/JavacResolution.java4
-rw-r--r--src/core/lombok/javac/LombokOptions.java4
-rw-r--r--src/core/lombok/javac/apt/Processor.java4
-rw-r--r--src/core/lombok/javac/handlers/HandleBuilder.java330
-rw-r--r--src/core/lombok/javac/handlers/HandleCleanup.java1
-rw-r--r--src/core/lombok/javac/handlers/HandleConstructor.java38
-rw-r--r--src/core/lombok/javac/handlers/HandleData.java3
-rw-r--r--src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java22
-rw-r--r--src/core/lombok/javac/handlers/HandleFieldDefaults.java10
-rw-r--r--src/core/lombok/javac/handlers/HandleGetter.java73
-rw-r--r--src/core/lombok/javac/handlers/HandleLog.java17
-rw-r--r--src/core/lombok/javac/handlers/HandleSetter.java42
-rw-r--r--src/core/lombok/javac/handlers/HandleSneakyThrows.java29
-rw-r--r--src/core/lombok/javac/handlers/HandleToString.java26
-rw-r--r--src/core/lombok/javac/handlers/HandleValue.java12
-rw-r--r--src/core/lombok/javac/handlers/HandleWither.java5
-rw-r--r--src/core/lombok/javac/handlers/JavacHandlerUtil.java217
-rw-r--r--src/core/lombok/javac/handlers/NonNullHandler.java148
69 files changed, 2750 insertions, 545 deletions
diff --git a/src/core/lombok/Data.java b/src/core/lombok/Data.java
index ee6f2fcb..bbc8d920 100644
--- a/src/core/lombok/Data.java
+++ b/src/core/lombok/Data.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 The Project Lombok Authors.
+ * Copyright (C) 2009-2013 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
@@ -39,7 +39,7 @@ import java.lang.annotation.Target;
* @see RequiredArgsConstructor
* @see ToString
* @see EqualsAndHashCode
- * @see lombok.experimental.Value
+ * @see lombok.Value
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
diff --git a/src/core/lombok/NonNull.java b/src/core/lombok/NonNull.java
index 5f5d8ed2..96813170 100644
--- a/src/core/lombok/NonNull.java
+++ b/src/core/lombok/NonNull.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Project Lombok Authors.
+ * Copyright (C) 2009-2013 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
@@ -28,12 +28,14 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
- * Lombok is smart enough to translate any annotation named {@code @NonNull} in any casing and
- * with any package name to the return type of generated getters and the parameter of generated setters and constructors,
- * as well as generate the appropriate null checks in the setter and constructor.
- *
- * You can use this annotation for the purpose, though you can also use JSR305's annotation, findbugs's, pmd's, or IDEA's, or just
- * about anyone elses. As long as it is named {@code @NonNull}.
+ * If put on a parameter, lombok will insert a null-check at the start of the method / constructor's body, throwing a
+ * {@code NullPointerException} with the parameter's name as message. If put on a field, any generated method assigning
+ * a value to this field will also produce these nullchecks.
+ * <p>
+ * Note that any annotation named {@code NonNull} with any casing and any package will result in nullchecks produced for
+ * generated methods (and the annotation will be copied to the getter return type and any parameters of generated methods),
+ * but <em>only</em> this annotation, if present on a parameter, will result in a null check inserted into your otherwise
+ * handwritten method.
*
* WARNING: If the java community ever does decide on supporting a single {@code @NonNull} annotation (for example via JSR305), then
* this annotation will <strong>be deleted</strong> from the lombok package. If the need to update an import statement scares
diff --git a/src/core/lombok/Value.java b/src/core/lombok/Value.java
new file mode 100644
index 00000000..2cffe15b
--- /dev/null
+++ b/src/core/lombok/Value.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012-2013 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Generates a lot of code which fits with a class that is a representation of an immutable entity.
+ * <p>
+ * Equivalent to {@code @Getter @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) @RequiredArgsConstructor @ToString @EqualsAndHashCode}.
+ * <p>
+ * Complete documentation is found at <a href="http://projectlombok.org/features/experimental/Value.html">the project lombok features page for &#64;Value</a>.
+ *
+ * @see lombok.Getter
+ * @see lombok.experimental.FieldDefaults
+ * @see lombok.RequiredArgsConstructor
+ * @see lombok.ToString
+ * @see lombok.EqualsAndHashCode
+ * @see lombok.Data
+ */
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.SOURCE)
+public @interface Value {
+ /**
+ * If you specify a static constructor name, then the generated constructor will be private, and
+ * instead a static factory method is created that other classes can use to create instances.
+ * We suggest the name: "of", like so:
+ *
+ * <pre>
+ * public @Data(staticConstructor = "of") class Point { final int x, y; }
+ * </pre>
+ *
+ * Default: No static constructor, instead the normal constructor is public.
+ */
+ String staticConstructor() default "";
+}
diff --git a/src/core/lombok/bytecode/PreventNullAnalysisRemover.java b/src/core/lombok/bytecode/PreventNullAnalysisRemover.java
index a3f18b40..751e691f 100644
--- a/src/core/lombok/bytecode/PreventNullAnalysisRemover.java
+++ b/src/core/lombok/bytecode/PreventNullAnalysisRemover.java
@@ -44,12 +44,12 @@ public class PreventNullAnalysisRemover implements PostCompilerTransformation {
byte[] fixedByteCode = fixJSRInlining(original);
ClassReader reader = new ClassReader(fixedByteCode);
- ClassWriter writer = new FixedClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+ ClassWriter writer = new FixedClassWriter(reader, 0);
final AtomicBoolean changesMade = new AtomicBoolean();
- class PreventNullanalysisVisitor extends MethodVisitor {
- PreventNullanalysisVisitor(MethodVisitor mv) {
+ class PreventNullAnalysisVisitor extends MethodVisitor {
+ PreventNullAnalysisVisitor(MethodVisitor mv) {
super(Opcodes.ASM4, mv);
}
@@ -61,6 +61,7 @@ public class PreventNullAnalysisRemover implements PostCompilerTransformation {
if (hit && !"(Ljava/lang/Object;)Ljava/lang/Object;".equals(desc)) hit = false;
if (hit) {
changesMade.set(true);
+ if (System.getProperty("lombok.debugAsmOnly", null) != null) super.visitMethodInsn(opcode, owner, name, desc); // DEBUG for issue 470!
} else {
super.visitMethodInsn(opcode, owner, name, desc);
}
@@ -69,7 +70,7 @@ public class PreventNullAnalysisRemover implements PostCompilerTransformation {
reader.accept(new ClassVisitor(Opcodes.ASM4, writer) {
@Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
- return new PreventNullanalysisVisitor(super.visitMethod(access, name, desc, signature, exceptions));
+ return new PreventNullAnalysisVisitor(super.visitMethod(access, name, desc, signature, exceptions));
}
}, 0);
return changesMade.get() ? writer.toByteArray() : null;
diff --git a/src/core/lombok/bytecode/SneakyThrowsRemover.java b/src/core/lombok/bytecode/SneakyThrowsRemover.java
index 8143d860..914c313a 100644
--- a/src/core/lombok/bytecode/SneakyThrowsRemover.java
+++ b/src/core/lombok/bytecode/SneakyThrowsRemover.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010-2012 The Project Lombok Authors.
+ * Copyright (C) 2010-2013 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,19 +32,21 @@ import org.mangosdk.spi.ProviderFor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@ProviderFor(PostCompilerTransformation.class)
public class SneakyThrowsRemover implements PostCompilerTransformation {
- @Override public byte[] applyTransformations(byte[] original, String fileName, DiagnosticsReceiver diagnostics) {
+ @Override public byte[] applyTransformations(byte[] original, String fileName, final DiagnosticsReceiver diagnostics) {
if (!new ClassFileMetaData(original).usesMethod("lombok/Lombok", "sneakyThrow")) return null;
byte[] fixedByteCode = fixJSRInlining(original);
ClassReader reader = new ClassReader(fixedByteCode);
- ClassWriter writer = new FixedClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+ ClassWriter writer = new ClassWriter(reader, 0);
final AtomicBoolean changesMade = new AtomicBoolean();
@@ -53,6 +55,8 @@ public class SneakyThrowsRemover implements PostCompilerTransformation {
super(Opcodes.ASM4, mv);
}
+ private boolean methodInsnQueued = false;
+
@Override public void visitMethodInsn(int opcode, String owner, String name, String desc) {
if (
opcode == Opcodes.INVOKESTATIC &&
@@ -60,12 +64,117 @@ public class SneakyThrowsRemover implements PostCompilerTransformation {
"lombok/Lombok".equals(owner) &&
"(Ljava/lang/Throwable;)Ljava/lang/RuntimeException;".equals(desc)) {
- changesMade.set(true);
- super.visitInsn(Opcodes.ATHROW);
+ if (System.getProperty("lombok.debugAsmOnly", null) != null) {
+ super.visitMethodInsn(opcode, owner, name, desc); // DEBUG for issue 470!
+ } else {
+ methodInsnQueued = true;
+ }
} else {
super.visitMethodInsn(opcode, owner, name, desc);
}
}
+
+ private void handleQueue() {
+ if (!methodInsnQueued) return;
+ super.visitMethodInsn(Opcodes.INVOKESTATIC, "lombok/Lombok", "sneakyThrow", "(Ljava/lang/Throwable;)Ljava/lang/RuntimeException;");
+ methodInsnQueued = false;
+ diagnostics.addWarning("Proper usage is: throw lombok.Lombok.sneakyThrow(someException);. You did not 'throw' it. Because of this, the call to sneakyThrow " +
+ "remains in your classfile and you will need lombok.jar on the classpath at runtime.");
+ }
+
+ @Override public void visitInsn(int arg0) {
+ if (methodInsnQueued && arg0 == Opcodes.ATHROW) {
+ changesMade.set(true);
+ // As expected, the required ATHROW. We can now safely 'eat' the previous call.
+ methodInsnQueued = false;
+ }
+ handleQueue();
+ super.visitInsn(arg0);
+ }
+ @Override public void visitFrame(int arg0, int arg1, Object[] arg2, int arg3, Object[] arg4) {
+ handleQueue();
+ super.visitFrame(arg0, arg1, arg2, arg3, arg4);
+ }
+
+ @Override public void visitIincInsn(int arg0, int arg1) {
+ handleQueue();
+ super.visitIincInsn(arg0, arg1);
+ }
+
+ @Override public void visitFieldInsn(int arg0, String arg1, String arg2, String arg3) {
+ handleQueue();
+ super.visitFieldInsn(arg0, arg1, arg2, arg3);
+ }
+
+ @Override public void visitIntInsn(int arg0, int arg1) {
+ handleQueue();
+ super.visitIntInsn(arg0, arg1);
+ }
+
+ @Override public void visitEnd() {
+ handleQueue();
+ super.visitEnd();
+ }
+
+ @Override public void visitInvokeDynamicInsn(String arg0, String arg1, Handle arg2, Object... arg3) {
+ handleQueue();
+ super.visitInvokeDynamicInsn(arg0, arg1, arg2, arg3);
+ }
+
+ @Override public void visitLabel(Label arg0) {
+ handleQueue();
+ super.visitLabel(arg0);
+ }
+
+ @Override public void visitJumpInsn(int arg0, Label arg1) {
+ handleQueue();
+ super.visitJumpInsn(arg0, arg1);
+ }
+
+ @Override public void visitLdcInsn(Object arg0) {
+ handleQueue();
+ super.visitLdcInsn(arg0);
+ }
+
+ @Override public void visitLocalVariable(String arg0, String arg1, String arg2, Label arg3, Label arg4, int arg5) {
+ handleQueue();
+ super.visitLocalVariable(arg0, arg1, arg2, arg3, arg4, arg5);
+ }
+
+ @Override public void visitMaxs(int arg0, int arg1) {
+ handleQueue();
+ super.visitMaxs(arg0, arg1);
+ }
+
+ @Override public void visitLookupSwitchInsn(Label arg0, int[] arg1, Label[] arg2) {
+ handleQueue();
+ super.visitLookupSwitchInsn(arg0, arg1, arg2);
+ }
+
+ @Override public void visitMultiANewArrayInsn(String arg0, int arg1) {
+ handleQueue();
+ super.visitMultiANewArrayInsn(arg0, arg1);
+ }
+
+ @Override public void visitVarInsn(int arg0, int arg1) {
+ handleQueue();
+ super.visitVarInsn(arg0, arg1);
+ }
+
+ @Override public void visitTryCatchBlock(Label arg0, Label arg1, Label arg2, String arg3) {
+ handleQueue();
+ super.visitTryCatchBlock(arg0, arg1, arg2, arg3);
+ }
+
+ @Override public void visitTableSwitchInsn(int arg0, int arg1, Label arg2, Label... arg3) {
+ handleQueue();
+ super.visitTableSwitchInsn(arg0, arg1, arg2, arg3);
+ }
+
+ @Override public void visitTypeInsn(int arg0, String arg1) {
+ handleQueue();
+ super.visitTypeInsn(arg0, arg1);
+ }
}
reader.accept(new ClassVisitor(Opcodes.ASM4, writer) {
diff --git a/src/core/lombok/core/AST.java b/src/core/lombok/core/AST.java
index 68f36412..6fed0252 100644
--- a/src/core/lombok/core/AST.java
+++ b/src/core/lombok/core/AST.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009 The Project Lombok Authors.
+ * Copyright (C) 2009-2013 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
@@ -30,7 +30,6 @@ import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
@@ -54,15 +53,15 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>,
private L top;
private final String fileName;
private final String packageDeclaration;
- private final Collection<String> imports;
- Map<N, Void> identityDetector = new IdentityHashMap<N, Void>();
+ private final ImportList imports;
+ Map<N, N> identityDetector = new IdentityHashMap<N, N>();
private Map<N, L> nodeMap = new IdentityHashMap<N, L>();
private boolean changed = false;
- protected AST(String fileName, String packageDeclaration, Collection<String> imports) {
+ protected AST(String fileName, String packageDeclaration, ImportList imports) {
this.fileName = fileName == null ? "(unknown).java" : fileName;
this.packageDeclaration = packageDeclaration;
- this.imports = Collections.unmodifiableCollection(new ArrayList<String>(imports));
+ this.imports = imports;
}
public void setChanged() {
@@ -96,7 +95,7 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>,
*
* Example: "java.util.IOException".
*/
- public final Collection<String> getImportStatements() {
+ public final ImportList getImportList() {
return imports;
}
@@ -106,7 +105,7 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>,
*/
protected L putInMap(L node) {
nodeMap.put(node.get(), node);
- identityDetector.put(node.get(), null);
+ identityDetector.put(node.get(), node.get());
return node;
}
@@ -118,7 +117,7 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>,
/** Clears the registry that avoids endless loops, and empties the node map. The existing node map
* object is left untouched, and instead a new map is created. */
protected void clearState() {
- identityDetector = new IdentityHashMap<N, Void>();
+ identityDetector = new IdentityHashMap<N, N>();
nodeMap = new IdentityHashMap<N, L>();
}
@@ -128,9 +127,7 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>,
* case you should do nothing lest the AST build process loops endlessly.
*/
protected boolean setAndGetAsHandled(N node) {
- if (identityDetector.containsKey(node)) return true;
- identityDetector.put(node, null);
- return false;
+ return identityDetector.put(node, node) != null;
}
public String getFileName() {
@@ -147,6 +144,24 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>,
return nodeMap.get(node);
}
+ /**
+ * Returns the JLS spec version that the compiler uses to parse and compile this AST.
+ * For example, if -source 1.6 is on the command line, this will return {@code 6}.
+ */
+ public int getSourceVersion() {
+ return 6;
+ }
+
+ /**
+ * Returns the latest version of the java language specification supported by the host compiler.
+ * For example, if compiling with javac v1.7, this returns {@code 7}.
+ *
+ * NB: Even if -source (lower than maximum) is specified, this method still returns the maximum supported number.
+ */
+ public int getLatestJavaSpecSupported() {
+ return 6;
+ }
+
@SuppressWarnings({"unchecked", "rawtypes"})
L replaceNewWithExistingOld(Map<N, L> oldNodes, L newNode) {
L oldNode = oldNodes.get(newNode.get());
@@ -159,8 +174,8 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>,
oldChild.parent = targetNode;
}
- targetNode.children.clear();
- ((List)targetNode.children).addAll(children);
+ targetNode.children = LombokImmutableList.copyOf(children);
+
return targetNode;
}
@@ -217,14 +232,12 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>,
}
}
- for (Class<?> statementType : getStatementTypes()) {
- if (statementType.isAssignableFrom(t)) {
- f.setAccessible(true);
- fields.add(new FieldAccess(f, dim));
- break;
- }
+ if (shouldDrill(c, t, f.getName())) {
+ f.setAccessible(true);
+ fields.add(new FieldAccess(f, dim));
}
}
+
getFields(c.getSuperclass(), fields);
}
@@ -241,6 +254,14 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>,
* though some platforms (such as Eclipse) group these under one common supertype. */
protected abstract Collection<Class<? extends N>> getStatementTypes();
+ protected boolean shouldDrill(Class<?> parentType, Class<?> childType, String fieldName) {
+ for (Class<?> statementType : getStatementTypes()) {
+ if (statementType.isAssignableFrom(childType)) return true;
+ }
+
+ return false;
+ }
+
/**
* buildTree implementation that uses reflection to find all child nodes by way of inspecting
* the fields. */
diff --git a/src/core/lombok/core/AnnotationProcessor.java b/src/core/lombok/core/AnnotationProcessor.java
index 8c5f7fba..e9cf3891 100644
--- a/src/core/lombok/core/AnnotationProcessor.java
+++ b/src/core/lombok/core/AnnotationProcessor.java
@@ -164,12 +164,9 @@ public class AnnotationProcessor extends AbstractProcessor {
}
}
- boolean handled = false;
- for (ProcessorDescriptor proc : active) {
- handled |= proc.process(annotations, roundEnv);
- }
+ for (ProcessorDescriptor proc : active) proc.process(annotations, roundEnv);
- return handled;
+ return false;
}
/**
diff --git a/src/core/lombok/core/AnnotationValues.java b/src/core/lombok/core/AnnotationValues.java
index df056dd4..d04797cb 100644
--- a/src/core/lombok/core/AnnotationValues.java
+++ b/src/core/lombok/core/AnnotationValues.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 The Project Lombok Authors.
+ * Copyright (C) 2009-2013 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
@@ -435,23 +435,16 @@ public class AnnotationValues<A extends Annotation> {
}
/* 2. Walk through non-star imports and search for a match. */ {
- for (String im : ast == null ? Collections.<String>emptyList() : ast.getImportStatements()) {
- if (im.endsWith(".*")) continue;
- int idx = im.lastIndexOf('.');
- String simple = idx == -1 ? im : im.substring(idx+1);
- if (simple.equals(prefix)) {
- return im + typeName.substring(prefix.length());
- }
+ if (prefix.equals(typeName)) {
+ String fqn = ast.getImportList().getFullyQualifiedNameForSimpleName(typeName);
+ if (fqn != null) return fqn;
}
}
/* 3. Walk through star imports and, if they start with "java.", use Class.forName based resolution. */ {
- List<String> imports = ast == null ? Collections.<String>emptyList() : new ArrayList<String>(ast.getImportStatements());
- imports.add("java.lang.*");
- for (String im : imports) {
- if (!im.endsWith(".*") || !im.startsWith("java.")) continue;
+ for (String potential : ast.getImportList().applyNameToStarImports("java", typeName)) {
try {
- Class<?> c = Class.forName(im.substring(0, im.length()-1) + typeName);
+ Class<?> c = Class.forName(potential);
if (c != null) return c.getName();
} catch (Throwable t) {
//Class.forName failed for whatever reason - it most likely does not exist, continue.
diff --git a/src/core/lombok/core/ImportList.java b/src/core/lombok/core/ImportList.java
new file mode 100644
index 00000000..95e266c4
--- /dev/null
+++ b/src/core/lombok/core/ImportList.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2013 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 java.util.Collection;
+
+public interface ImportList {
+ /**
+ * If there is an explicit import of the stated unqualified type name, return that. Otherwise, return null.
+ */
+ String getFullyQualifiedNameForSimpleName(String unqualified);
+
+ /**
+ * Returns true if the package name is explicitly star-imported, OR the packageName refers to this source file's own package name, OR packageName is 'java.lang'.
+ */
+ boolean hasStarImport(String packageName);
+
+ /**
+ * Takes all explicit non-static star imports whose first element is equal to {@code startsWith}, replaces the star with {@code unqualified}, and returns these.
+ */
+ Collection<String> applyNameToStarImports(String startsWith, String unqualified);
+
+ String applyUnqualifiedNameToPackage(String unqualified);
+}
diff --git a/src/core/lombok/core/LombokInternalAliasing.java b/src/core/lombok/core/LombokInternalAliasing.java
new file mode 100644
index 00000000..4fd7b29d
--- /dev/null
+++ b/src/core/lombok/core/LombokInternalAliasing.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013 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 java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class LombokInternalAliasing {
+ public static final Map<String, String> IMPLIED_EXTRA_STAR_IMPORTS;
+ public static final Map<String, String> ALIASES;
+
+ /**
+ * Provide a fully qualified name (FQN), and the canonical version of this is returned.
+ */
+ public static String processAliases(String in) {
+ if (in == null) return null;
+ for (Map.Entry<String, String> e : ALIASES.entrySet()) {
+ if (in.equals(e.getKey())) return e.getValue();
+ }
+ return in;
+ }
+
+ static {
+ Map<String, String> m = new HashMap<String, String>();
+ m.put("lombok.experimental", "lombok");
+ IMPLIED_EXTRA_STAR_IMPORTS = Collections.unmodifiableMap(m);
+
+ m = new HashMap<String, String>();
+ m.put("lombok.experimental.Value", "lombok.Value");
+ ALIASES = Collections.unmodifiableMap(m);
+ }
+}
diff --git a/src/core/lombok/core/LombokNode.java b/src/core/lombok/core/LombokNode.java
index eac59806..07c62151 100644
--- a/src/core/lombok/core/LombokNode.java
+++ b/src/core/lombok/core/LombokNode.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2011 The Project Lombok Authors.
+ * Copyright (C) 2009-2013 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
@@ -42,7 +42,7 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A,
protected final A ast;
protected final Kind kind;
protected final N node;
- protected final List<L> children;
+ protected LombokImmutableList<L> children;
protected L parent;
/** structurally significant are those nodes that can be annotated in java 1.6 or are method-like toplevels,
@@ -62,7 +62,7 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A,
this.ast = ast;
this.kind = kind;
this.node = node;
- this.children = children == null ? new ArrayList<L>() : children;
+ this.children = children != null ? LombokImmutableList.copyOf(children) : LombokImmutableList.<L>of();
for (L child : this.children) {
child.parent = (L) this;
if (!child.isStructurallySignificant)
@@ -91,12 +91,12 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A,
}
/**
- * Convenient shortcut to the owning ast object's {@code getImportStatements} method.
+ * Convenient shortcut to the owning ast object's {@code getImportList} method.
*
- * @see AST#getImportStatements()
+ * @see AST#getImportList()
*/
- public Collection<String> getImportStatements() {
- return ast.getImportStatements();
+ public ImportList getImportList() {
+ return ast.getImportList();
}
/**
@@ -120,35 +120,6 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A,
return node;
}
- /**
- * Replaces the AST node represented by this node object with the provided node. This node must
- * have a parent, obviously, for this to work.
- *
- * Also affects the underlying (Eclipse/javac) AST.
- */
- @SuppressWarnings({"rawtypes", "unchecked"})
- public L replaceWith(N newN, Kind newNodeKind) {
- ast.setChanged();
- L newNode = ast.buildTree(newN, newNodeKind);
- newNode.parent = parent;
- for (int i = 0; i < parent.children.size(); i++) {
- if (parent.children.get(i) == this) ((List)parent.children).set(i, newNode);
- }
-
- parent.replaceChildNode(get(), newN);
- return newNode;
- }
-
- /**
- * Replaces the stated node with a new one. The old node must be a direct child of this node.
- *
- * Also affects the underlying (Eclipse/javac) AST.
- */
- public void replaceChildNode(N oldN, N newN) {
- ast.setChanged();
- ast.replaceStatementInNode(get(), oldN, newN);
- }
-
public Kind getKind() {
return kind;
}
@@ -204,12 +175,27 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A,
/**
* Returns all children nodes.
+ */
+ public LombokImmutableList<L> down() {
+ return children;
+ }
+
+ /**
+ * Convenient shortcut to the owning ast object's getLatestJavaSpecSupported method.
+ *
+ * @see AST#getLatestJavaSpecSupported()
+ */
+ public int getLatestJavaSpecSupported() {
+ return ast.getLatestJavaSpecSupported();
+ }
+
+ /**
+ * Convenient shortcut to the owning ast object's getSourceVersion method.
*
- * A copy is created, so changing the list has no effect. Also, while iterating through this list,
- * you may add, remove, or replace children without causing {@code ConcurrentModificationException}s.
+ * @see AST#getSourceVersion()
*/
- public Collection<L> down() {
- return new ArrayList<L>(children);
+ public int getSourceVersion() {
+ return ast.getSourceVersion();
}
/**
@@ -235,13 +221,13 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A,
*
* Does not change the underlying (javac/Eclipse) AST, only the wrapped view.
*/
- @SuppressWarnings({"rawtypes", "unchecked"})
+ @SuppressWarnings({"unchecked"})
public L add(N newChild, Kind newChildKind) {
ast.setChanged();
L n = ast.buildTree(newChild, newChildKind);
if (n == null) return null;
n.parent = (L) this;
- ((List)children).add(n);
+ children = children.append(n);
return n;
}
@@ -267,7 +253,7 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A,
for (LombokNode child : children) child.gatherAndRemoveChildren(map);
ast.identityDetector.remove(get());
map.put(get(), (L) this);
- children.clear();
+ children = LombokImmutableList.of();
ast.getNodeMap().remove(get());
}
@@ -278,7 +264,7 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A,
*/
public void removeChild(L child) {
ast.setChanged();
- children.remove(child);
+ children = children.removeElement(child);
}
/**
diff --git a/src/core/lombok/core/PostCompiler.java b/src/core/lombok/core/PostCompiler.java
index 1e40cca2..4a099862 100644
--- a/src/core/lombok/core/PostCompiler.java
+++ b/src/core/lombok/core/PostCompiler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Project Lombok Authors.
+ * Copyright (C) 2010-2013 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
@@ -33,6 +33,7 @@ public final class PostCompiler {
private static List<PostCompilerTransformation> transformations;
public static byte[] applyTransformations(byte[] original, String fileName, DiagnosticsReceiver diagnostics) {
+ if (System.getProperty("lombok.disablePostCompiler", null) != null) return original;
init(diagnostics);
byte[] previous = original;
for (PostCompilerTransformation transformation : transformations) {
@@ -59,6 +60,7 @@ public final class PostCompiler {
}
public static OutputStream wrapOutputStream(final OutputStream originalStream, final String fileName, final DiagnosticsReceiver diagnostics) throws IOException {
+ if (System.getProperty("lombok.disablePostCompiler", null) != null) return originalStream;
return new ByteArrayOutputStream() {
@Override public void close() throws IOException {
// no need to call super
diff --git a/src/core/lombok/core/TransformationsUtil.java b/src/core/lombok/core/TransformationsUtil.java
index 921c27d6..8959ad7a 100644
--- a/src/core/lombok/core/TransformationsUtil.java
+++ b/src/core/lombok/core/TransformationsUtil.java
@@ -22,13 +22,25 @@
package lombok.core;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import lombok.Value;
import lombok.experimental.Accessors;
+import lombok.experimental.FieldDefaults;
+import lombok.experimental.Wither;
/**
* Container for static utility methods useful for some of the standard lombok transformations, regardless of
@@ -39,6 +51,13 @@ public class TransformationsUtil {
//Prevent instantiation
}
+ @SuppressWarnings({"all", "unchecked", "deprecation"})
+ public static final List<Class<? extends java.lang.annotation.Annotation>> INVALID_ON_BUILDERS = Collections.unmodifiableList(
+ Arrays.<Class<? extends java.lang.annotation.Annotation>>asList(
+ Getter.class, Setter.class, Wither.class, ToString.class, EqualsAndHashCode.class,
+ RequiredArgsConstructor.class, AllArgsConstructor.class, NoArgsConstructor.class,
+ Data.class, Value.class, lombok.experimental.Value.class, FieldDefaults.class));
+
/**
* Given the name of a field, return the 'base name' of that field. For example, {@code fFoobar} becomes {@code foobar} if {@code f} is in the prefix list.
* For prefixes that end in a letter character, the next character must be a non-lowercase character (i.e. {@code hashCode} is not {@code ashCode} even if
@@ -159,12 +178,12 @@ public class TransformationsUtil {
if (fieldName.length() == 0) return null;
- Accessors ac = accessors.getInstance();
- fieldName = removePrefix(fieldName, ac.prefix());
+ Accessors ac = accessors == null ? null : accessors.getInstance();
+ fieldName = removePrefix(fieldName, ac == null ? new String[0] : ac.prefix());
if (fieldName == null) return null;
String fName = fieldName.toString();
- if (adhereToFluent && ac.fluent()) return fName;
+ if (adhereToFluent && ac != null && ac.fluent()) return fName;
if (isBoolean && fName.startsWith("is") && fieldName.length() > 2 && !Character.isLowerCase(fieldName.charAt(2))) {
// The field is for example named 'isRunning'.
diff --git a/src/core/lombok/core/TypeLibrary.java b/src/core/lombok/core/TypeLibrary.java
index 0e3f7bf4..c0e9dc43 100644
--- a/src/core/lombok/core/TypeLibrary.java
+++ b/src/core/lombok/core/TypeLibrary.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 The Project Lombok Authors.
+ * Copyright (C) 2009-2013 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
@@ -21,12 +21,7 @@
*/
package lombok.core;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
/**
@@ -40,24 +35,23 @@ import java.util.Map;
* <ul><li>foo.Spork</li><li>Spork</li><li>foo.*</li></ul>
*/
public class TypeLibrary {
- private final Map<String, List<String>> keyToFqnMap;
- private final String singletonValue;
- private final List<String> singletonKeys;
+ private final Map<String, String> unqualifiedToQualifiedMap;
+ private final String unqualified, qualified;
public TypeLibrary() {
- keyToFqnMap = new HashMap<String, List<String>>();
- singletonKeys = null;
- singletonValue = null;
+ unqualifiedToQualifiedMap = new HashMap<String, String>();
+ unqualified = null;
+ qualified = null;
}
private TypeLibrary(String fqnSingleton) {
- keyToFqnMap = null;
- singletonValue = fqnSingleton;
+ unqualifiedToQualifiedMap = null;
+ qualified = fqnSingleton;
int idx = fqnSingleton.lastIndexOf('.');
if (idx == -1) {
- singletonKeys = Collections.singletonList(fqnSingleton);
+ unqualified = fqnSingleton;
} else {
- singletonKeys = Arrays.asList(fqnSingleton, fqnSingleton.substring(idx + 1), fqnSingleton.substring(0, idx) + ".*");
+ unqualified = fqnSingleton.substring(idx + 1);
}
}
@@ -71,42 +65,34 @@ public class TypeLibrary {
* @param fullyQualifiedTypeName the FQN type name, such as 'java.lang.String'.
*/
public void addType(String fullyQualifiedTypeName) {
- if (keyToFqnMap == null) throw new IllegalStateException("SingleType library");
+ fullyQualifiedTypeName = fullyQualifiedTypeName.replace("$", ".");
int idx = fullyQualifiedTypeName.lastIndexOf('.');
if (idx == -1) throw new IllegalArgumentException(
"Only fully qualified types are allowed (and stuff in the default package is not palatable to us either!)");
+ String unqualified = fullyQualifiedTypeName.substring(idx + 1);
+ if (unqualifiedToQualifiedMap == null) throw new IllegalStateException("SingleType library");
- fullyQualifiedTypeName = fullyQualifiedTypeName.replace("$", ".");
- final String simpleName = fullyQualifiedTypeName.substring(idx +1);
- final String packageName = fullyQualifiedTypeName.substring(0, idx);
-
- if (keyToFqnMap.put(fullyQualifiedTypeName, Collections.singletonList(fullyQualifiedTypeName)) != null) return;
-
- addToMap(simpleName, fullyQualifiedTypeName);
- addToMap(packageName + ".*", fullyQualifiedTypeName);
- }
-
- private TypeLibrary addToMap(String keyName, String fullyQualifiedTypeName) {
- List<String> list = keyToFqnMap.get(keyName);
- if (list == null) {
- list = new ArrayList<String>();
- keyToFqnMap.put(keyName, list);
+ unqualifiedToQualifiedMap.put(unqualified, fullyQualifiedTypeName);
+ unqualifiedToQualifiedMap.put(fullyQualifiedTypeName, fullyQualifiedTypeName);
+ for (Map.Entry<String, String> e : LombokInternalAliasing.ALIASES.entrySet()) {
+ if (fullyQualifiedTypeName.equals(e.getValue())) unqualifiedToQualifiedMap.put(e.getKey(), fullyQualifiedTypeName);
}
-
- list.add(fullyQualifiedTypeName);
- return this;
}
/**
- * Returns all items in the type library that may be a match to the provided type.
+ * Translates an unqualified name such as 'String' to 'java.lang.String', _if_ you added 'java.lang.String' to the library via the {@code addType} method.
+ * Also returns the input if it is equal to a fully qualified name added to this type library.
*
- * @param typeReference something like 'String', 'java.lang.String', or 'java.lang.*'.
- * @return A list of Fully Qualified Names for all types in the library that fit the reference.
+ * Returns null if it does not match any type in this type library.
*/
- public Collection<String> findCompatible(String typeReference) {
- if (singletonKeys != null) return singletonKeys.contains(typeReference) ? Collections.singletonList(singletonValue) : Collections.<String>emptyList();
-
- List<String> result = keyToFqnMap.get(typeReference);
- return result == null ? Collections.<String>emptyList() : Collections.unmodifiableList(result);
+ public String toQualified(String typeReference) {
+ if (unqualifiedToQualifiedMap == null) {
+ if (typeReference.equals(unqualified) || typeReference.equals(qualified)) return qualified;
+ for (Map.Entry<String, String> e : LombokInternalAliasing.ALIASES.entrySet()) {
+ if (e.getKey().equals(typeReference)) return e.getValue();
+ }
+ return null;
+ }
+ return unqualifiedToQualifiedMap.get(typeReference);
}
}
diff --git a/src/core/lombok/core/TypeResolver.java b/src/core/lombok/core/TypeResolver.java
index 27f0bfc1..287a085f 100644
--- a/src/core/lombok/core/TypeResolver.java
+++ b/src/core/lombok/core/TypeResolver.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2011 The Project Lombok Authors.
+ * Copyright (C) 2009-2013 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
@@ -21,11 +21,6 @@
*/
package lombok.core;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
import lombok.core.AST.Kind;
/**
@@ -35,60 +30,47 @@ import lombok.core.AST.Kind;
* and this importer also can't find inner types from superclasses/interfaces.
*/
public class TypeResolver {
- private Collection<String> imports;
+ private ImportList imports;
/**
* Creates a new TypeResolver that can be used to resolve types in a source file with the given package and import statements.
*/
- public TypeResolver(String packageString, Collection<String> importStrings) {
- this.imports = makeImportList(packageString, importStrings);
- }
-
- private static Collection<String> makeImportList(String packageString, Collection<String> importStrings) {
- Set<String> imports = new HashSet<String>();
- if (packageString != null) imports.add(packageString + ".*");
- imports.addAll(importStrings == null ? Collections.<String>emptySet() : importStrings);
- imports.add("java.lang.*");
- return imports;
+ public TypeResolver(ImportList importList) {
+ this.imports = importList;
}
public boolean typeMatches(LombokNode<?, ?, ?> context, String fqn, String typeRef) {
- return !findTypeMatches(context, TypeLibrary.createLibraryForSingleType(fqn), typeRef).isEmpty();
+ return typeRefToFullyQualifiedName(context, TypeLibrary.createLibraryForSingleType(fqn), typeRef) != null;
}
- /**
- * Finds type matches for the stated type reference. The provided context is scanned for local type names
- * that shadow type names listed in import statements. If such a shadowing occurs, no matches are returned
- * for any shadowed types, as you would expect.
- */
- public Collection<String> findTypeMatches(LombokNode<?, ?, ?> context, TypeLibrary library, String typeRef) {
+ public String typeRefToFullyQualifiedName(LombokNode<?, ?, ?> context, TypeLibrary library, String typeRef) {
+ typeRef = LombokInternalAliasing.processAliases(typeRef);
// When asking if 'Foo' could possibly be referring to 'bar.Baz', the answer is obviously no.
- Collection<String> potentialMatches = library.findCompatible(typeRef);
- if (potentialMatches.isEmpty()) return Collections.emptyList();
+ String qualified = library.toQualified(typeRef);
+ if (qualified == null) return null;
- // If input type appears to be fully qualified, we found a winner.
- int idx = typeRef.indexOf('.');
- if (idx > -1) return potentialMatches;
+ // When asking if 'lombok.Getter' could possibly be referring to 'lombok.Getter', the answer is obviously yes.
+ if (typeRef.equals(qualified)) return typeRef;
- // If there's an import statement that explicitly imports a 'Getter' that isn't any of our potentials, return no matches,
- // because if you want to know if 'Foo' could refer to 'bar.Foo' when 'baz.Foo' is explicitly imported, the answer is no.
- if (nameConflictInImportList(typeRef, potentialMatches)) return Collections.emptyList();
+ // When asking if 'Getter' could possibly be referring to 'lombok.Getter' if 'import lombok.Getter;' is in the source file, the answer is yes.
+ String fromExplicitImport = imports.getFullyQualifiedNameForSimpleName(typeRef);
+ if (fromExplicitImport != null) {
+ // ... and if 'import foobar.Getter;' is in the source file, the answer is no.
+ return fromExplicitImport.equals(qualified) ? qualified : null;
+ }
- // Check if any of our potentials are even imported in the first place. If not: no matches.
- // Note that (ourPackage.*) is added to the imports.
- potentialMatches = eliminateImpossibleMatches(potentialMatches, library);
- if (potentialMatches.isEmpty()) return Collections.emptyList();
+ // When asking if 'Getter' could possibly be referring to 'lombok.Getter' and 'import lombok.*; / package lombok;' isn't in the source file. the answer is no.
+ String pkgName = qualified.substring(0, qualified.length() - typeRef.length() - 1);
+ if (!imports.hasStarImport(pkgName)) return null;
- // Now the hard part - inner classes or method local classes in our own scope.
- // For method locals, this refers to any statements that are 'above' the type reference with the same name.
- // For inners, this refers to siblings of us or any parent node that are type declarations.
+ // Now the hard part: Given that there is a star import, 'Getter' most likely refers to 'lombok.Getter', but type shadowing may occur in which case it doesn't.
LombokNode<?, ?, ?> n = context;
mainLoop:
while (n != null) {
if (n.getKind() == Kind.TYPE && typeRef.equals(n.getName())) {
// Our own class or one of our outer classes is named 'typeRef' so that's what 'typeRef' is referring to, not one of our type library classes.
- return Collections.emptyList();
+ return null;
}
if (n.getKind() == Kind.STATEMENT || n.getKind() == Kind.LOCAL) {
@@ -99,7 +81,7 @@ public class TypeResolver {
for (LombokNode<?, ?, ?> child : newN.down()) {
// We found a method local with the same name above our code. That's the one 'typeRef' is referring to, not
// anything in the type library we're trying to find, so, no matches.
- if (child.getKind() == Kind.TYPE && typeRef.equals(child.getName())) return Collections.emptyList();
+ if (child.getKind() == Kind.TYPE && typeRef.equals(child.getName())) return null;
if (child == n) break;
}
}
@@ -110,41 +92,14 @@ public class TypeResolver {
if (n.getKind() == Kind.TYPE || n.getKind() == Kind.COMPILATION_UNIT) {
for (LombokNode<?, ?, ?> child : n.down()) {
// Inner class that's visible to us has 'typeRef' as name, so that's the one being referred to, not one of our type library classes.
- if (child.getKind() == Kind.TYPE && typeRef.equals(child.getName())) return Collections.emptyList();
+ if (child.getKind() == Kind.TYPE && typeRef.equals(child.getName())) return null;
}
}
n = n.directUp();
}
- // No class in this source file is a match, therefore the potential matches found via the import statements must be it. Return those.
- return potentialMatches;
- }
-
- private Collection<String> eliminateImpossibleMatches(Collection<String> potentialMatches, TypeLibrary library) {
- Set<String> results = new HashSet<String>();
-
- for (String importedType : imports) {
- Collection<String> reduced = new HashSet<String>(library.findCompatible(importedType));
- reduced.retainAll(potentialMatches);
- results.addAll(reduced);
- }
-
- return results;
- }
-
- private boolean nameConflictInImportList(String simpleName, Collection<String> potentialMatches) {
- for (String importedType : imports) {
- if (!toSimpleName(importedType).equals(simpleName)) continue;
- if (potentialMatches.contains(importedType)) continue;
- return true;
- }
- return false;
- }
-
- private static String toSimpleName(String typeName) {
- int idx = typeName.lastIndexOf('.');
- return idx == -1 ? typeName : typeName.substring(idx+1);
+ return qualified;
}
}
diff --git a/src/core/lombok/core/Version.java b/src/core/lombok/core/Version.java
index b9239978..bd83c1f8 100644
--- a/src/core/lombok/core/Version.java
+++ b/src/core/lombok/core/Version.java
@@ -28,8 +28,8 @@ public class Version {
// ** CAREFUL ** - this class must always compile with 0 dependencies (it must not refer to any other sources or libraries).
// Note: In 'X.Y.Z', if Z is odd, its a snapshot build built from the repository, so many different 0.10.3 versions can exist, for example.
// Official builds always end in an even number. (Since 0.10.2).
- private static final String VERSION = "0.11.7";
- private static final String RELEASE_NAME = "Dashing Kakapo";
+ private static final String VERSION = "0.12.1";
+ private static final String RELEASE_NAME = "Angry Butterfy";
private Version() {
//Prevent instantiation
diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java
new file mode 100644
index 00000000..3d386054
--- /dev/null
+++ b/src/core/lombok/core/handlers/HandlerUtil.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2013 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 lombok.core.JavaIdentifiers;
+import lombok.core.LombokNode;
+
+public class HandlerUtil {
+ private HandlerUtil() {}
+
+ /** Checks if the given name is a valid identifier.
+ *
+ * If it is, this returns {@code true} and does nothing else.
+ * If it isn't, this returns {@code false} and adds an error message to the supplied node.
+ */
+ public static boolean checkName(String nameSpec, String identifier, LombokNode<?, ?, ?> errorNode) {
+ if (identifier.isEmpty()) {
+ errorNode.addError(nameSpec + " cannot be the empty string.");
+ return false;
+ }
+
+ if (!JavaIdentifiers.isValidJavaIdentifier(identifier)) {
+ errorNode.addError(nameSpec + " must be a valid java identifier.");
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/src/core/lombok/eclipse/EclipseAST.java b/src/core/lombok/eclipse/EclipseAST.java
index 8ab42140..370b40fc 100644
--- a/src/core/lombok/eclipse/EclipseAST.java
+++ b/src/core/lombok/eclipse/EclipseAST.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2010 The Project Lombok Authors.
+ * Copyright (C) 2009-2013 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
@@ -24,12 +24,14 @@ package lombok.eclipse;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
+import org.eclipse.jdt.internal.compiler.CompilationResult;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import lombok.Lombok;
import lombok.core.AST;
+import lombok.core.LombokImmutableList;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
@@ -55,7 +57,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> {
* @param ast The compilation unit, which serves as the top level node in the tree to be built.
*/
public EclipseAST(CompilationUnitDeclaration ast) {
- super(toFileName(ast), packageDeclaration(ast), imports(ast));
+ super(toFileName(ast), packageDeclaration(ast), new EclipseImportList(ast));
this.compilationUnitDeclaration = ast;
setTop(buildCompilationUnit(ast));
this.completeParse = isComplete(ast);
@@ -67,16 +69,18 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> {
return pkg == null ? null : Eclipse.toQualifiedName(pkg.getImportName());
}
- private static Collection<String> imports(CompilationUnitDeclaration cud) {
- List<String> imports = new ArrayList<String>();
- if (cud.imports == null) return imports;
- for (ImportReference imp : cud.imports) {
- if (imp == null) continue;
- String qualifiedName = Eclipse.toQualifiedName(imp.getImportName());
- if ((imp.bits & ASTNode.OnDemand) != 0) qualifiedName += ".*";
- imports.add(qualifiedName);
- }
- return imports;
+ @Override public int getSourceVersion() {
+ long sl = compilationUnitDeclaration.problemReporter.options.sourceLevel;
+ long cl = compilationUnitDeclaration.problemReporter.options.complianceLevel;
+ sl >>= 16;
+ cl >>= 16;
+ if (sl == 0) sl = cl;
+ if (cl == 0) cl = sl;
+ return Math.min((int)(sl - 44), (int)(cl - 44));
+ }
+
+ @Override public int getLatestJavaSpecSupported() {
+ return Eclipse.getEcjCompilerVersion();
}
/**
@@ -88,8 +92,10 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> {
}
void traverseChildren(EclipseASTVisitor visitor, EclipseNode node) {
- for (EclipseNode child : node.down()) {
- child.traverse(visitor);
+ LombokImmutableList<EclipseNode> children = node.down();
+ int len = children.size();
+ for (int i = 0; i < len; i++) {
+ children.get(i).traverse(visitor);
}
}
@@ -118,7 +124,8 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> {
}
void addToCompilationResult() {
- addProblemToCompilationResult((CompilationUnitDeclaration) top().get(),
+ CompilationUnitDeclaration cud = (CompilationUnitDeclaration) top().get();
+ addProblemToCompilationResult(cud.getFileName(), cud.compilationResult,
isWarning, message, sourceStart, sourceEnd);
}
}
@@ -142,11 +149,10 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> {
* Adds a problem to the provided CompilationResult object so that it will show up
* in the Problems/Warnings view.
*/
- public static void addProblemToCompilationResult(CompilationUnitDeclaration ast,
+ public static void addProblemToCompilationResult(char[] fileNameArray, CompilationResult result,
boolean isWarning, String message, int sourceStart, int sourceEnd) {
- if (ast.compilationResult == null) return;
try {
- EcjReflectionCheck.addProblemToCompilationResult.invoke(null, ast, isWarning, message, sourceStart, sourceEnd);
+ EcjReflectionCheck.addProblemToCompilationResult.invoke(null, fileNameArray, result, isWarning, message, sourceStart, sourceEnd);
} catch (NoClassDefFoundError e) {
//ignore, we don't have access to the correct ECJ classes, so lombok can't possibly
//do anything useful here.
@@ -163,7 +169,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> {
//do anything useful here.
}
}
-
+
private final CompilationUnitDeclaration compilationUnitDeclaration;
private boolean completeParse;
@@ -353,7 +359,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> {
}
private static class EcjReflectionCheck {
- private static final String CUD_TYPE = "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration";
+ private static final String COMPILATIONRESULT_TYPE = "org.eclipse.jdt.internal.compiler.CompilationResult";
public static Method addProblemToCompilationResult;
public static final Throwable problem;
@@ -362,7 +368,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> {
Throwable problem_ = null;
Method m = null;
try {
- m = EclipseAstProblemView.class.getMethod("addProblemToCompilationResult", Class.forName(CUD_TYPE), boolean.class, String.class, int.class, int.class);
+ m = EclipseAstProblemView.class.getMethod("addProblemToCompilationResult", char[].class, Class.forName(COMPILATIONRESULT_TYPE), boolean.class, String.class, int.class, int.class);
} catch (Throwable t) {
// That's problematic, but as long as no local classes are used we don't actually need it.
// Better fail on local classes than crash altogether.
diff --git a/src/core/lombok/eclipse/EclipseAnnotationHandler.java b/src/core/lombok/eclipse/EclipseAnnotationHandler.java
index 84304339..ca9965f7 100644
--- a/src/core/lombok/eclipse/EclipseAnnotationHandler.java
+++ b/src/core/lombok/eclipse/EclipseAnnotationHandler.java
@@ -29,7 +29,7 @@ import lombok.core.SpiLoadUtil;
*
* You MUST replace 'T' with a specific annotation type, such as:
*
- * {@code public class HandleGetter implements EclipseAnnotationHandler<Getter>}
+ * {@code public class HandleGetter extends EclipseAnnotationHandler<Getter>}
*
* Because this generics parameter is inspected to figure out which class you're interested in.
*
diff --git a/src/core/lombok/eclipse/EclipseAstProblemView.java b/src/core/lombok/eclipse/EclipseAstProblemView.java
index a2d5b833..c1179666 100644
--- a/src/core/lombok/eclipse/EclipseAstProblemView.java
+++ b/src/core/lombok/eclipse/EclipseAstProblemView.java
@@ -3,7 +3,6 @@ package lombok.eclipse;
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.internal.compiler.CompilationResult;
-import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.problem.DefaultProblem;
import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities;
import org.eclipse.jdt.internal.compiler.util.Util;
@@ -13,14 +12,12 @@ public class EclipseAstProblemView {
* Adds a problem to the provided CompilationResult object so that it will show up
* in the Problems/Warnings view.
*/
- public static void addProblemToCompilationResult(CompilationUnitDeclaration ast,
+ public static void addProblemToCompilationResult(char[] fileNameArray, CompilationResult result,
boolean isWarning, String message, int sourceStart, int sourceEnd) {
- if (ast.compilationResult == null) return;
- char[] fileNameArray = ast.getFileName();
+ if (result == null) return;
if (fileNameArray == null) fileNameArray = "(unknown).java".toCharArray();
int lineNumber = 0;
int columnNumber = 1;
- CompilationResult result = ast.compilationResult;
int[] lineEnds = null;
lineNumber = sourceStart >= 0
? Util.getLineNumber(sourceStart, lineEnds = result.getLineSeparatorPositions(), 0, lineEnds.length-1)
@@ -33,7 +30,7 @@ public class EclipseAstProblemView {
fileNameArray, message, 0, new String[0],
isWarning ? ProblemSeverities.Warning : ProblemSeverities.Error,
sourceStart, sourceEnd, lineNumber, columnNumber);
- ast.compilationResult.record(ecProblem, null);
+ result.record(ecProblem, null);
}
private static class LombokProblem extends DefaultProblem {
diff --git a/src/core/lombok/eclipse/EclipseImportList.java b/src/core/lombok/eclipse/EclipseImportList.java
new file mode 100644
index 00000000..69246b3c
--- /dev/null
+++ b/src/core/lombok/eclipse/EclipseImportList.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2013 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.eclipse;
+
+import static lombok.eclipse.Eclipse.toQualifiedName;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import lombok.core.ImportList;
+import lombok.core.LombokInternalAliasing;
+
+import org.eclipse.jdt.internal.compiler.ast.ASTNode;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.ImportReference;
+
+public class EclipseImportList implements ImportList {
+ private ImportReference[] imports;
+ private ImportReference pkg;
+
+ public EclipseImportList(CompilationUnitDeclaration cud) {
+ this.pkg = cud.currentPackage;
+ this.imports = cud.imports;
+ }
+
+ @Override public String getFullyQualifiedNameForSimpleName(String unqualified) {
+ if (imports != null) {
+ outer:
+ for (ImportReference imp : imports) {
+ if ((imp.bits & ASTNode.OnDemand) != 0) continue;
+ char[][] tokens = imp.tokens;
+ char[] token = tokens.length == 0 ? new char[0] : tokens[tokens.length - 1];
+ int len = token.length;
+ if (len != unqualified.length()) continue;
+ for (int i = 0; i < len; i++) if (token[i] != unqualified.charAt(i)) continue outer;
+ return LombokInternalAliasing.processAliases(toQualifiedName(tokens));
+ }
+ }
+ return null;
+ }
+
+ @Override public boolean hasStarImport(String packageName) {
+ for (Map.Entry<String, String> e : LombokInternalAliasing.IMPLIED_EXTRA_STAR_IMPORTS.entrySet()) {
+ if (e.getValue().equals(packageName) && hasStarImport(e.getKey())) return true;
+ }
+ if (isEqual(packageName, pkg)) return true;
+ if ("java.lang".equals(packageName)) return true;
+ if (imports != null) for (ImportReference imp : imports) {
+ if ((imp.bits & ASTNode.OnDemand) == 0) continue;
+ if (imp.isStatic()) continue;
+ if (isEqual(packageName, imp)) return true;
+ }
+ return false;
+ }
+
+ private static boolean isEqual(String packageName, ImportReference pkgOrStarImport) {
+ if (pkgOrStarImport == null || pkgOrStarImport.tokens == null || pkgOrStarImport.tokens.length == 0) return packageName.isEmpty();
+ int pos = 0;
+ int len = packageName.length();
+ for (int i = 0; i < pkgOrStarImport.tokens.length; i++) {
+ if (i != 0) {
+ if (pos >= len) return false;
+ if (packageName.charAt(pos++) != '.') return false;
+ }
+ for (int j = 0; j < pkgOrStarImport.tokens[i].length; j++) {
+ if (pos >= len) return false;
+ if (packageName.charAt(pos++) != pkgOrStarImport.tokens[i][j]) return false;
+ }
+ }
+ return true;
+ }
+
+ @Override public Collection<String> applyNameToStarImports(String startsWith, String name) {
+ List<String> out = Collections.emptyList();
+
+ if (pkg != null && pkg.tokens != null && pkg.tokens.length != 0) {
+ char[] first = pkg.tokens[0];
+ int len = first.length;
+ boolean match = true;
+ if (startsWith.length() == len) {
+ for (int i = 0; match && i < len; i++) {
+ if (startsWith.charAt(i) != first[i]) match = false;
+ }
+ if (match) out.add(toQualifiedName(pkg.tokens) + "." + name);
+ }
+ }
+
+ if (imports != null) {
+ outer:
+ for (ImportReference imp : imports) {
+ if ((imp.bits & ASTNode.OnDemand) == 0) continue;
+ if (imp.isStatic()) continue;
+ if (imp.tokens == null || imp.tokens.length == 0) continue;
+ char[] firstToken = imp.tokens[0];
+ if (firstToken.length != startsWith.length()) continue;
+ for (int i = 0; i < firstToken.length; i++) if (startsWith.charAt(i) != firstToken[i]) continue outer;
+ String fqn = toQualifiedName(imp.tokens) + "." + name;
+ if (out.isEmpty()) out = Collections.singletonList(fqn);
+ else if (out.size() == 1) {
+ out = new ArrayList<String>(out);
+ out.add(fqn);
+ } else {
+ out.add(fqn);
+ }
+ }
+ }
+ return out;
+ }
+
+ @Override public String applyUnqualifiedNameToPackage(String unqualified) {
+ if (pkg == null || pkg.tokens == null || pkg.tokens.length == 0) return unqualified;
+ return toQualifiedName(pkg.tokens) + "." + unqualified;
+ }
+}
diff --git a/src/core/lombok/eclipse/HandlerLibrary.java b/src/core/lombok/eclipse/HandlerLibrary.java
index 56744793..242e923c 100644
--- a/src/core/lombok/eclipse/HandlerLibrary.java
+++ b/src/core/lombok/eclipse/HandlerLibrary.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 The Project Lombok Authors.
+ * Copyright (C) 2009-2013 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
@@ -218,30 +218,27 @@ public class HandlerLibrary {
* @param annotation 'node.get()' - convenience parameter.
*/
public void handleAnnotation(CompilationUnitDeclaration ast, EclipseNode annotationNode, org.eclipse.jdt.internal.compiler.ast.Annotation annotation, long priority) {
- String pkgName = annotationNode.getPackageDeclaration();
- Collection<String> imports = annotationNode.getImportStatements();
-
- TypeResolver resolver = new TypeResolver(pkgName, imports);
+ TypeResolver resolver = new TypeResolver(annotationNode.getImportList());
TypeReference rawType = annotation.type;
if (rawType == null) return;
- for (String fqn : resolver.findTypeMatches(annotationNode, typeLibrary, toQualifiedName(annotation.type.getTypeName()))) {
- AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn);
- if (container == null) continue;
- if (priority != container.getPriority()) continue;
-
- if (!annotationNode.isCompleteParse() && container.deferUntilPostDiet()) {
- if (needsHandling(annotation)) container.preHandle(annotation, annotationNode);
- continue;
- }
-
- try {
- if (checkAndSetHandled(annotation)) container.handle(annotation, annotationNode);
- } catch (AnnotationValueDecodeFail fail) {
- fail.owner.setError(fail.getMessage(), fail.idx);
- } catch (Throwable t) {
- error(ast, String.format("Lombok annotation handler %s failed", container.handler.getClass()), t);
- }
+ String fqn = resolver.typeRefToFullyQualifiedName(annotationNode, typeLibrary, toQualifiedName(annotation.type.getTypeName()));
+ if (fqn == null) return;
+ AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn);
+ if (container == null) return;
+ if (priority != container.getPriority()) return;
+
+ if (!annotationNode.isCompleteParse() && container.deferUntilPostDiet()) {
+ if (needsHandling(annotation)) container.preHandle(annotation, annotationNode);
+ return;
+ }
+
+ try {
+ if (checkAndSetHandled(annotation)) container.handle(annotation, annotationNode);
+ } catch (AnnotationValueDecodeFail fail) {
+ fail.owner.setError(fail.getMessage(), fail.idx);
+ } catch (Throwable t) {
+ error(ast, String.format("Lombok annotation handler %s failed", container.handler.getClass()), t);
}
}
diff --git a/src/core/lombok/eclipse/TransformEclipseAST.java b/src/core/lombok/eclipse/TransformEclipseAST.java
index 47e620f6..11caf5c2 100644
--- a/src/core/lombok/eclipse/TransformEclipseAST.java
+++ b/src/core/lombok/eclipse/TransformEclipseAST.java
@@ -144,7 +144,7 @@ public class TransformEclipseAST {
try {
String message = "Lombok can't parse this source: " + t.toString();
- EclipseAST.addProblemToCompilationResult(ast, false, message, 0, 0);
+ EclipseAST.addProblemToCompilationResult(ast.getFileName(), ast.compilationResult, false, message, 0, 0);
t.printStackTrace();
} catch (Throwable t2) {
try {
diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
index 78780522..9bd634f7 100644
--- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
+++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java
@@ -22,6 +22,7 @@
package lombok.eclipse.handlers;
import static lombok.eclipse.Eclipse.*;
+import static lombok.core.TransformationsUtil.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -61,6 +62,7 @@ import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
import org.eclipse.jdt.internal.compiler.ast.ArrayQualifiedTypeReference;
import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference;
+import org.eclipse.jdt.internal.compiler.ast.Block;
import org.eclipse.jdt.internal.compiler.ast.CastExpression;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
@@ -142,7 +144,7 @@ public class EclipseHandlerUtil {
} catch (NoClassDefFoundError e) { //standalone ecj does not jave Platform, ILog, IStatus, and friends.
new TerminalLogger().error(message, bundleName, error);
}
- if (cud != null) EclipseAST.addProblemToCompilationResult(cud, false, message + " - See error log.", 0, 0);
+ if (cud != null) EclipseAST.addProblemToCompilationResult(cud.getFileName(), cud.compilationResult, false, message + " - See error log.", 0, 0);
}
/**
@@ -290,11 +292,34 @@ public class EclipseHandlerUtil {
if (!lastPartA.equals(lastPartB)) return false;
String typeName = toQualifiedName(typeRef.getTypeName());
- TypeResolver resolver = new TypeResolver(node.getPackageDeclaration(), node.getImportStatements());
+ TypeResolver resolver = new TypeResolver(node.getImportList());
return resolver.typeMatches(node, type.getName(), typeName);
}
+ public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(EclipseNode typeNode, EclipseNode errorNode) {
+ List<String> disallowed = null;
+ for (EclipseNode child : typeNode.down()) {
+ for (Class<? extends java.lang.annotation.Annotation> annType : INVALID_ON_BUILDERS) {
+ if (annotationTypeMatches(annType, child)) {
+ if (disallowed == null) disallowed = new ArrayList<String>();
+ disallowed.add(annType.getSimpleName());
+ }
+ }
+ }
+
+ int size = disallowed == null ? 0 : disallowed.size();
+ if (size == 0) return;
+ if (size == 1) {
+ errorNode.addError("@" + disallowed.get(0) + " is not allowed on builder classes.");
+ return;
+ }
+ StringBuilder out = new StringBuilder();
+ for (String a : disallowed) out.append("@").append(a).append(", ");
+ out.setLength(out.length() - 2);
+ errorNode.addError(out.append(" are not allowed on builder classes.").toString());
+ }
+
public static Annotation copyAnnotation(Annotation annotation, ASTNode source) {
int pS = source.sourceStart, pE = source.sourceEnd;
@@ -358,6 +383,20 @@ public class EclipseHandlerUtil {
return out;
}
+ public static TypeReference namePlusTypeParamsToTypeReference(char[] typeName, TypeParameter[] params, long p) {
+ if (params != null && params.length > 0) {
+ TypeReference[] refs = new TypeReference[params.length];
+ int idx = 0;
+ for (TypeParameter param : params) {
+ TypeReference typeRef = new SingleTypeReference(param.name, p);
+ refs[idx++] = typeRef;
+ }
+ return new ParameterizedSingleTypeReference(typeName, refs, 0, p);
+ }
+
+ return new SingleTypeReference(typeName, p);
+ }
+
/**
* Convenience method that creates a new array and copies each TypeReference in the source array via
* {@link #copyType(TypeReference, ASTNode)}.
@@ -830,15 +869,20 @@ public class EclipseHandlerUtil {
private static final Object MARKER = new Object();
static void registerCreatedLazyGetter(FieldDeclaration field, char[] methodName, TypeReference returnType) {
- if (!nameEquals(returnType.getTypeName(), "boolean") || returnType.dimensions() > 0) return;
- generatedLazyGettersWithPrimitiveBoolean.put(field, MARKER);
+ if (isBoolean(returnType)) {
+ generatedLazyGettersWithPrimitiveBoolean.put(field, MARKER);
+ }
+ }
+
+ public static boolean isBoolean(TypeReference typeReference) {
+ return nameEquals(typeReference.getTypeName(), "boolean") && typeReference.dimensions() == 0;
}
private static GetterMethod findGetter(EclipseNode field) {
FieldDeclaration fieldDeclaration = (FieldDeclaration) field.get();
boolean forceBool = generatedLazyGettersWithPrimitiveBoolean.containsKey(fieldDeclaration);
TypeReference fieldType = fieldDeclaration.type;
- boolean isBoolean = forceBool || (nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0);
+ boolean isBoolean = forceBool || isBoolean(fieldType);
EclipseNode typeNode = field.up();
for (String potentialGetterName : toAllGetterNames(field, isBoolean)) {
@@ -1207,15 +1251,15 @@ public class EclipseHandlerUtil {
* Inserts a field into an existing type. The type must represent a {@code TypeDeclaration}.
* The field carries the &#64;{@link SuppressWarnings}("all") annotation.
*/
- public static void injectFieldSuppressWarnings(EclipseNode type, FieldDeclaration field) {
+ public static EclipseNode injectFieldSuppressWarnings(EclipseNode type, FieldDeclaration field) {
field.annotations = createSuppressWarningsAll(field, field.annotations);
- injectField(type, field);
+ return injectField(type, field);
}
/**
* Inserts a field into an existing type. The type must represent a {@code TypeDeclaration}.
*/
- public static void injectField(EclipseNode type, FieldDeclaration field) {
+ public static EclipseNode injectField(EclipseNode type, FieldDeclaration field) {
TypeDeclaration parent = (TypeDeclaration) type.get();
if (parent.fields == null) {
@@ -1242,7 +1286,7 @@ public class EclipseHandlerUtil {
}
}
- type.add(field, Kind.FIELD);
+ return type.add(field, Kind.FIELD);
}
private static boolean isEnumConstant(final FieldDeclaration field) {
@@ -1252,7 +1296,7 @@ public class EclipseHandlerUtil {
/**
* Inserts a method into an existing type. The type must represent a {@code TypeDeclaration}.
*/
- public static void injectMethod(EclipseNode type, AbstractMethodDeclaration method) {
+ public static EclipseNode injectMethod(EclipseNode type, AbstractMethodDeclaration method) {
method.annotations = createSuppressWarningsAll(method, method.annotations);
TypeDeclaration parent = (TypeDeclaration) type.get();
@@ -1285,7 +1329,29 @@ public class EclipseHandlerUtil {
parent.methods = newArray;
}
- type.add(method, Kind.METHOD);
+ return type.add(method, Kind.METHOD);
+ }
+
+ /**
+ * Adds an inner type (class, interface, enum) to the given type. Cannot inject top-level types.
+ *
+ * @param typeNode parent type to inject new type into
+ * @param type New type (class, interface, etc) to inject.
+ */
+ public static EclipseNode injectType(final EclipseNode typeNode, final TypeDeclaration type) {
+ type.annotations = createSuppressWarningsAll(type, type.annotations);
+ TypeDeclaration parent = (TypeDeclaration) typeNode.get();
+
+ if (parent.memberTypes == null) {
+ parent.memberTypes = new TypeDeclaration[] { type };
+ } else {
+ TypeDeclaration[] newArray = new TypeDeclaration[parent.memberTypes.length + 1];
+ System.arraycopy(parent.memberTypes, 0, newArray, 0, parent.memberTypes.length);
+ newArray[parent.memberTypes.length] = type;
+ parent.memberTypes = newArray;
+ }
+
+ return typeNode.add(type, Kind.TYPE);
}
private static final char[] ALL = "all".toCharArray();
@@ -1334,7 +1400,11 @@ public class EclipseHandlerUtil {
EqualExpression equalExpression = new EqualExpression(varName, nullLiteral, OperatorIds.EQUAL_EQUAL);
equalExpression.sourceStart = pS; equalExpression.statementEnd = equalExpression.sourceEnd = pE;
setGeneratedBy(equalExpression, source);
- IfStatement ifStatement = new IfStatement(equalExpression, throwStatement, 0, 0);
+ Block throwBlock = new Block(0);
+ throwBlock.statements = new Statement[] {throwStatement};
+ throwBlock.sourceStart = pS; throwBlock.sourceEnd = pE;
+ setGeneratedBy(throwBlock, source);
+ IfStatement ifStatement = new IfStatement(equalExpression, throwBlock, 0, 0);
setGeneratedBy(ifStatement, source);
return ifStatement;
}
diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java
new file mode 100644
index 00000000..70110a9c
--- /dev/null
+++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2013 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.eclipse.handlers;
+
+import static lombok.eclipse.Eclipse.*;
+import static lombok.core.handlers.HandlerUtil.*;
+import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+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.AllocationExpression;
+import org.eclipse.jdt.internal.compiler.ast.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
+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.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.MessageSend;
+import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
+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.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.TypeParameter;
+import org.eclipse.jdt.internal.compiler.ast.TypeReference;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
+import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
+import org.mangosdk.spi.ProviderFor;
+
+import lombok.AccessLevel;
+import lombok.core.AST.Kind;
+import lombok.core.AnnotationValues;
+import lombok.core.HandlerPriority;
+import lombok.core.TransformationsUtil;
+import lombok.eclipse.Eclipse;
+import lombok.eclipse.EclipseAnnotationHandler;
+import lombok.eclipse.EclipseNode;
+import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists;
+import lombok.experimental.Builder;
+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> {
+ @Override public void handle(AnnotationValues<Builder> annotation, Annotation ast, EclipseNode annotationNode) {
+ long p = (long) ast.sourceStart << 32 | ast.sourceEnd;
+
+ Builder builderInstance = annotation.getInstance();
+ String builderMethodName = builderInstance.builderMethodName();
+ String buildMethodName = builderInstance.buildMethodName();
+ String builderClassName = builderInstance.builderClassName();
+
+ if (builderMethodName == null) builderMethodName = "builder";
+ if (buildMethodName == null) builderMethodName = "build";
+ if (builderClassName == null) builderClassName = "";
+
+ if (!checkName("builderMethodName", builderMethodName, annotationNode)) return;
+ if (!checkName("buildMethodName", buildMethodName, annotationNode)) return;
+ if (!builderClassName.isEmpty()) {
+ if (!checkName("builderClassName", builderClassName, annotationNode)) return;
+ }
+
+ EclipseNode parent = annotationNode.up();
+
+ List<TypeReference> typesOfParameters = new ArrayList<TypeReference>();
+ List<char[]> namesOfParameters = new ArrayList<char[]>();
+ TypeReference returnType;
+ TypeParameter[] typeParams;
+ TypeReference[] thrownExceptions;
+ char[] nameOfStaticBuilderMethod;
+ EclipseNode tdParent;
+
+ AbstractMethodDeclaration fillParametersFrom = null;
+
+ if (parent.get() instanceof TypeDeclaration) {
+ tdParent = parent;
+ TypeDeclaration td = (TypeDeclaration) tdParent.get();
+
+ List<EclipseNode> fields = new ArrayList<EclipseNode>();
+ @SuppressWarnings("deprecation")
+ boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent));
+ for (EclipseNode fieldNode : HandleConstructor.findAllFields(tdParent)) {
+ FieldDeclaration fd = (FieldDeclaration) fieldNode.get();
+ // final fields with an initializer cannot be written to, so they can't be 'builderized'. Unfortunately presence of @Value makes
+ // 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(fd.name);
+ typesOfParameters.add(fd.type);
+ fields.add(fieldNode);
+ }
+
+ new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, fields, null, SkipIfConstructorExists.I_AM_BUILDER, true, Collections.<Annotation>emptyList(), ast);
+
+ returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p);
+ typeParams = td.typeParameters;
+ thrownExceptions = null;
+ nameOfStaticBuilderMethod = null;
+ if (builderClassName.isEmpty()) builderClassName = new String(td.name) + "Builder";
+ } else if (parent.get() instanceof ConstructorDeclaration) {
+ ConstructorDeclaration cd = (ConstructorDeclaration) parent.get();
+ if (cd.typeParameters != null && cd.typeParameters.length > 0) {
+ annotationNode.addError("@Builder is not supported on constructors with constructor type parameters.");
+ return;
+ }
+
+ tdParent = parent.up();
+ TypeDeclaration td = (TypeDeclaration) tdParent.get();
+ fillParametersFrom = cd;
+ returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p);
+ typeParams = td.typeParameters;
+ thrownExceptions = cd.thrownExceptions;
+ nameOfStaticBuilderMethod = null;
+ if (builderClassName.isEmpty()) builderClassName = new String(cd.selector) + "Builder";
+ } else if (parent.get() instanceof MethodDeclaration) {
+ MethodDeclaration md = (MethodDeclaration) parent.get();
+ tdParent = parent.up();
+ if (!md.isStatic()) {
+ 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;
+ nameOfStaticBuilderMethod = md.selector;
+ if (builderClassName.isEmpty()) {
+ char[] token;
+ if (md.returnType instanceof QualifiedTypeReference) {
+ char[][] tokens = ((QualifiedTypeReference) md.returnType).tokens;
+ token = tokens[tokens.length - 1];
+ } else if (md.returnType instanceof SingleTypeReference) {
+ token = ((SingleTypeReference) md.returnType).token;
+ if (!(md.returnType instanceof ParameterizedSingleTypeReference) && typeParams != null) {
+ for (TypeParameter tp : typeParams) {
+ if (Arrays.equals(tp.name, token)) {
+ annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type.");
+ return;
+ }
+ }
+ }
+ } else {
+ annotationNode.addError("Unexpected kind of return type on annotated method. Specify 'builderClassName' to solve this problem.");
+ return;
+ }
+
+ if (Character.isLowerCase(token[0])) {
+ char[] newToken = new char[token.length];
+ System.arraycopy(token, 1, newToken, 1, token.length - 1);
+ newToken[0] = Character.toTitleCase(token[0]);
+ token = newToken;
+ }
+
+ builderClassName = new String(token) + "Builder";
+ }
+ } else {
+ annotationNode.addError("@Builder is only supported on types, constructors, and static methods.");
+ return;
+ }
+
+ if (fillParametersFrom != null) {
+ if (fillParametersFrom.arguments != null) for (Argument a : fillParametersFrom.arguments) {
+ namesOfParameters.add(a.name);
+ typesOfParameters.add(a.type);
+ }
+ }
+
+ EclipseNode builderType = findInnerClass(tdParent, builderClassName);
+ if (builderType == null) {
+ builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast);
+ } 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, ast, builderInstance.fluent(), builderInstance.chain());
+ if (newMethod != null) newMethods.add(newMethod);
+ }
+
+ if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) {
+ ConstructorDeclaration cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, builderType, Collections.<EclipseNode>emptyList(), true, ast, Collections.<Annotation>emptyList());
+ if (cd != null) injectMethod(builderType, cd);
+ }
+
+ for (AbstractMethodDeclaration newMethod : newMethods) injectMethod(builderType, newMethod);
+ if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) {
+ MethodDeclaration md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, ast, thrownExceptions);
+ if (md != null) injectMethod(builderType, md);
+ }
+
+ if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) {
+ MethodDeclaration md = HandleToString.createToString(builderType, fieldNodes, true, false, ast, FieldAccess.ALWAYS_FIELD);
+ if (md != null) injectMethod(builderType, md);
+ }
+
+ if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) {
+ MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast);
+ if (md != null) injectMethod(tdParent, md);
+ }
+ }
+
+ private 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;
+
+ 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)};
+
+ out.traverse(new SetGeneratedByVisitor(source), ((TypeDeclaration) type.get()).scope);
+ return out;
+ }
+
+ private 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;
+
+ MethodDeclaration out = new MethodDeclaration(
+ ((CompilationUnitDeclaration) type.top().get()).compilationResult);
+
+ out.modifiers = ClassFileConstants.AccPublic;
+ TypeDeclaration typeDecl = (TypeDeclaration) type.get();
+ out.selector = name.toCharArray();
+ out.thrownExceptions = copyTypes(thrownExceptions, source);
+ 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);
+ } else {
+ MessageSend invoke = new MessageSend();
+ invoke.selector = staticName;
+ invoke.receiver = new SingleNameReference(type.up().getName().toCharArray(), p);
+ TypeParameter[] tps = ((TypeDeclaration) type.get()).typeParameters;
+ if (tps != null) {
+ TypeReference[] trs = new TypeReference[tps.length];
+ for (int i = 0; i < trs.length; i++) {
+ trs[i] = new SingleTypeReference(tps[i].name, p);
+ }
+ invoke.typeArguments = trs;
+ }
+ invoke.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]);
+ if (returnType instanceof SingleTypeReference && Arrays.equals(TypeBinding.VOID.simpleName, ((SingleTypeReference) returnType).token)) {
+ statement = invoke;
+ } else {
+ statement = new ReturnStatement(invoke, (int)(p >> 32), (int)p);
+ }
+ }
+
+ out.statements = new Statement[] { statement };
+
+ out.traverse(new SetGeneratedByVisitor(source), typeDecl.scope);
+ return out;
+ }
+
+ private List<EclipseNode> addFieldsToBuilder(EclipseNode builderType, List<char[]> namesOfParameters, List<TypeReference> typesOfParameters, ASTNode source) {
+ int len = namesOfParameters.size();
+ TypeDeclaration td = (TypeDeclaration) builderType.get();
+ FieldDeclaration[] existing = td.fields;
+ if (existing == null) existing = new FieldDeclaration[0];
+
+ List<EclipseNode> out = new ArrayList<EclipseNode>();
+
+ top:
+ for (int i = len - 1; i >= 0; i--) {
+ char[] name = namesOfParameters.get(i);
+ for (FieldDeclaration exists : existing) {
+ if (Arrays.equals(exists.name, name)) {
+ out.add(builderType.getNodeFor(exists));
+ continue top;
+ }
+ }
+ TypeReference fieldReference = copyType(typesOfParameters.get(i), source);
+ FieldDeclaration newField = new FieldDeclaration(name, 0, 0);
+ newField.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG;
+ newField.modifiers = ClassFileConstants.AccPrivate;
+ newField.type = fieldReference;
+ out.add(injectField(builderType, newField));
+ }
+
+ Collections.reverse(out);
+
+ return out;
+ }
+
+ private static final AbstractMethodDeclaration[] EMPTY = {};
+
+ private MethodDeclaration makeSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, ASTNode source, boolean fluent, boolean chain) {
+ TypeDeclaration td = (TypeDeclaration) builderType.get();
+ AbstractMethodDeclaration[] existing = td.methods;
+ if (existing == null) existing = EMPTY;
+ int len = existing.length;
+ FieldDeclaration fd = (FieldDeclaration) fieldNode.get();
+ char[] name = fd.name;
+
+ for (int i = 0; i < len; i++) {
+ if (!(existing[i] instanceof MethodDeclaration)) continue;
+ char[] existingName = existing[i].selector;
+ if (Arrays.equals(name, existingName)) return null;
+ }
+
+ boolean isBoolean = isBoolean(fd.type);
+ String setterName = fluent ? fieldNode.getName() : TransformationsUtil.toSetterName(null, fieldNode.getName(), isBoolean);
+
+ return HandleSetter.createSetter(td, fieldNode, setterName, chain, ClassFileConstants.AccPublic,
+ source, Collections.<Annotation>emptyList(), Collections.<Annotation>emptyList());
+ }
+
+ private EclipseNode findInnerClass(EclipseNode parent, String name) {
+ char[] c = name.toCharArray();
+ for (EclipseNode child : parent.down()) {
+ if (child.getKind() != Kind.TYPE) continue;
+ TypeDeclaration td = (TypeDeclaration) child.get();
+ if (Arrays.equals(td.name, c)) return child;
+ }
+ return null;
+ }
+
+ private EclipseNode makeBuilderClass(EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, ASTNode source) {
+ TypeDeclaration parent = (TypeDeclaration) tdParent.get();
+ TypeDeclaration builder = new TypeDeclaration(parent.compilationResult);
+ builder.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG;
+ builder.modifiers |= ClassFileConstants.AccPublic | ClassFileConstants.AccStatic;
+ builder.typeParameters = copyTypeParams(typeParams, source);
+ builder.name = builderClassName.toCharArray();
+ builder.traverse(new SetGeneratedByVisitor(source), (ClassScope) null);
+ return injectType(tdParent, builder);
+ }
+}
diff --git a/src/core/lombok/eclipse/handlers/HandleConstructor.java b/src/core/lombok/eclipse/handlers/HandleConstructor.java
index 8ccad77f..1ae680d9 100644
--- a/src/core/lombok/eclipse/handlers/HandleConstructor.java
+++ b/src/core/lombok/eclipse/handlers/HandleConstructor.java
@@ -39,6 +39,7 @@ import lombok.core.AnnotationValues;
import lombok.core.TransformationsUtil;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
+import lombok.experimental.Builder;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
@@ -53,18 +54,14 @@ import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.FieldReference;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
-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.SingleMemberAnnotation;
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.StringLiteral;
import org.eclipse.jdt.internal.compiler.ast.ThisReference;
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.classfmt.ClassFileConstants;
import org.mangosdk.spi.ProviderFor;
@@ -82,7 +79,7 @@ public class HandleConstructor {
List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor=", annotationNode);
- new HandleConstructor().generateConstructor(typeNode, level, fields, staticName, false, false, onConstructor, ast);
+ new HandleConstructor().generateConstructor(typeNode, level, fields, staticName, SkipIfConstructorExists.NO, false, onConstructor, ast);
}
}
@@ -100,7 +97,7 @@ public class HandleConstructor {
List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@RequiredArgsConstructor(onConstructor=", annotationNode);
- new HandleConstructor().generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, false, suppressConstructorProperties, onConstructor, ast);
+ new HandleConstructor().generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, onConstructor, ast);
}
}
@@ -117,7 +114,7 @@ public class HandleConstructor {
return fields;
}
- private static List<EclipseNode> findAllFields(EclipseNode typeNode) {
+ static List<EclipseNode> findAllFields(EclipseNode typeNode) {
List<EclipseNode> fields = new ArrayList<EclipseNode>();
for (EclipseNode child : typeNode.down()) {
if (child.getKind() != Kind.FIELD) continue;
@@ -146,7 +143,7 @@ public class HandleConstructor {
List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor=", annotationNode);
- new HandleConstructor().generateConstructor(typeNode, level, findAllFields(typeNode), staticName, false, suppressConstructorProperties, onConstructor, ast);
+ new HandleConstructor().generateConstructor(typeNode, level, findAllFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, onConstructor, ast);
}
}
@@ -164,25 +161,34 @@ public class HandleConstructor {
return true;
}
- public void generateRequiredArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) {
+ public void generateRequiredArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) {
generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, skipIfConstructorExists, false, onConstructor, source);
}
- public void generateAllArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) {
+ public void generateAllArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) {
generateConstructor(typeNode, level, findAllFields(typeNode), staticName, skipIfConstructorExists, false, onConstructor, source);
}
- public void generateConstructor(EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, String staticName, boolean skipIfConstructorExists, boolean suppressConstructorProperties, List<Annotation> onConstructor, ASTNode source) {
+ public enum SkipIfConstructorExists {
+ YES, NO, I_AM_BUILDER;
+ }
+
+ public void generateConstructor(EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, String staticName, SkipIfConstructorExists skipIfConstructorExists, boolean suppressConstructorProperties, List<Annotation> onConstructor, ASTNode source) {
boolean staticConstrRequired = staticName != null && !staticName.equals("");
- if (skipIfConstructorExists && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return;
- if (skipIfConstructorExists) {
+ if (skipIfConstructorExists != SkipIfConstructorExists.NO && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return;
+ if (skipIfConstructorExists != SkipIfConstructorExists.NO) {
for (EclipseNode child : typeNode.down()) {
if (child.getKind() == Kind.ANNOTATION) {
- if (annotationTypeMatches(NoArgsConstructor.class, child) ||
+ boolean skipGeneration = (annotationTypeMatches(NoArgsConstructor.class, child) ||
annotationTypeMatches(AllArgsConstructor.class, child) ||
- annotationTypeMatches(RequiredArgsConstructor.class, child)) {
-
+ annotationTypeMatches(RequiredArgsConstructor.class, child));
+
+ if (!skipGeneration && skipIfConstructorExists == SkipIfConstructorExists.YES) {
+ skipGeneration = annotationTypeMatches(Builder.class, child);
+ }
+
+ if (skipGeneration) {
if (staticConstrRequired) {
// @Data has asked us to generate a constructor, but we're going to skip this instruction, as an explicit 'make a constructor' annotation
// will take care of it. However, @Data also wants a specific static name; this will be ignored; the appropriate way to do this is to use
@@ -235,7 +241,7 @@ public class HandleConstructor {
return new Annotation[] { ann };
}
- private ConstructorDeclaration createConstructor(
+ static ConstructorDeclaration createConstructor(
AccessLevel level, EclipseNode type, Collection<EclipseNode> fields,
boolean suppressConstructorProperties, ASTNode source, List<Annotation> onConstructor) {
@@ -307,7 +313,7 @@ public class HandleConstructor {
return constructor;
}
- private boolean isLocalType(EclipseNode type) {
+ private static boolean isLocalType(EclipseNode type) {
Kind kind = type.up().getKind();
if (kind == Kind.COMPILATION_UNIT) return false;
if (kind == Kind.TYPE) return isLocalType(type.up());
@@ -321,18 +327,9 @@ public class HandleConstructor {
MethodDeclaration constructor = new MethodDeclaration(
((CompilationUnitDeclaration) type.top().get()).compilationResult);
- constructor.modifiers = toEclipseModifier(level) | Modifier.STATIC;
+ constructor.modifiers = toEclipseModifier(level) | ClassFileConstants.AccStatic;
TypeDeclaration typeDecl = (TypeDeclaration) type.get();
- if (typeDecl.typeParameters != null && typeDecl.typeParameters.length > 0) {
- TypeReference[] refs = new TypeReference[typeDecl.typeParameters.length];
- int idx = 0;
- for (TypeParameter param : typeDecl.typeParameters) {
- TypeReference typeRef = new SingleTypeReference(param.name, (long)param.sourceStart << 32 | param.sourceEnd);
- setGeneratedBy(typeRef, source);
- refs[idx++] = typeRef;
- }
- constructor.returnType = new ParameterizedSingleTypeReference(typeDecl.name, refs, 0, p);
- } else constructor.returnType = new SingleTypeReference(((TypeDeclaration)type.get()).name, p);
+ constructor.returnType = EclipseHandlerUtil.namePlusTypeParamsToTypeReference(typeDecl.name, typeDecl.typeParameters, p);
constructor.annotations = null;
constructor.selector = name.toCharArray();
constructor.thrownExceptions = null;
diff --git a/src/core/lombok/eclipse/handlers/HandleData.java b/src/core/lombok/eclipse/handlers/HandleData.java
index 3a43bd3f..aa309489 100644
--- a/src/core/lombok/eclipse/handlers/HandleData.java
+++ b/src/core/lombok/eclipse/handlers/HandleData.java
@@ -28,6 +28,7 @@ import lombok.Data;
import lombok.core.AnnotationValues;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
+import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
@@ -64,6 +65,6 @@ public class HandleData extends EclipseAnnotationHandler<Data> {
new HandleSetter().generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true);
new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode);
new HandleToString().generateToStringForType(typeNode, annotationNode);
- new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), true, Collections.<Annotation>emptyList(), ast);
+ new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.<Annotation>emptyList(), ast);
}
}
diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java
index 0c82b74c..6990e609 100644
--- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java
+++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 The Project Lombok Authors.
+ * Copyright (C) 2009-2013 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
@@ -204,18 +204,27 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH
}
boolean isFinal = (typeDecl.modifiers & ClassFileConstants.AccFinal) != 0;
- boolean needsCanEqual = !isDirectDescendantOfObject || !isFinal;
- java.util.List<MemberExistsResult> existsResults = new ArrayList<MemberExistsResult>();
- existsResults.add(methodExists("equals", typeNode, 1));
- existsResults.add(methodExists("hashCode", typeNode, 0));
- existsResults.add(methodExists("canEqual", typeNode, 1));
- switch (Collections.max(existsResults)) {
+ boolean needsCanEqual = !isFinal || !isDirectDescendantOfObject;
+ MemberExistsResult equalsExists = methodExists("equals", typeNode, 1);
+ MemberExistsResult hashCodeExists = methodExists("hashCode", typeNode, 0);
+ MemberExistsResult canEqualExists = methodExists("canEqual", typeNode, 1);
+ switch (Collections.max(Arrays.asList(equalsExists, hashCodeExists, canEqualExists))) {
case EXISTS_BY_LOMBOK:
return;
case EXISTS_BY_USER:
if (whineIfExists) {
String msg = String.format("Not generating equals%s: A method with one of those names already exists. (Either all or none of these methods will be generated).", needsCanEqual ? ", hashCode and canEquals" : " and hashCode");
errorNode.addWarning(msg);
+ } else if (equalsExists == MemberExistsResult.NOT_EXISTS || hashCodeExists == MemberExistsResult.NOT_EXISTS) {
+ // This means equals OR hashCode exists and not both (or neither, but canEqual is there).
+ // Even though we should suppress the message about not generating these, this is such a weird and surprising situation we should ALWAYS generate a warning.
+ // The user code couldn't possibly (barring really weird subclassing shenanigans) be in a shippable state anyway; the implementations of these 3 methods are
+ // all inter-related and should be written by the same entity.
+ String msg = String.format("Not generating %s: One of equals, hashCode, and canEqual exists. " +
+ "You should either write all of these are none of these (in the latter case, lombok generates them).",
+ equalsExists == MemberExistsResult.NOT_EXISTS && hashCodeExists == MemberExistsResult.NOT_EXISTS ? "equals and hashCode" :
+ equalsExists == MemberExistsResult.NOT_EXISTS ? "equals" : "hashCode");
+ errorNode.addWarning(msg);
}
return;
case NOT_EXISTS:
diff --git a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java
index 0d21fc08..d6d839cc 100644
--- a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java
+++ b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java
@@ -43,7 +43,7 @@ import org.mangosdk.spi.ProviderFor;
* Handles the {@code lombok.FieldDefaults} annotation for eclipse.
*/
@ProviderFor(EclipseAnnotationHandler.class)
-@HandlerPriority(-512) //-2^9; to ensure @Setter and such pick up on messing with the fields' 'final' state, run earlier.
+@HandlerPriority(-2048) //-2^11; to ensure @Value picks up on messing with the fields' 'final' state, run earlier.
public class HandleFieldDefaults extends EclipseAnnotationHandler<FieldDefaults> {
public boolean generateFieldDefaultsForType(EclipseNode typeNode, EclipseNode pos, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) {
if (checkForTypeLevelFieldDefaults) {
@@ -112,6 +112,14 @@ public class HandleFieldDefaults extends EclipseAnnotationHandler<FieldDefaults>
return;
}
+ if (level == AccessLevel.PACKAGE) {
+ annotationNode.addError("Setting 'level' to PACKAGE does nothing. To force fields as package private, use the @PackagePrivate annotation on the field.");
+ }
+
+ if (!makeFinal && annotation.isExplicit("makeFinal")) {
+ annotationNode.addError("Setting 'makeFinal' to false does nothing. To force fields to be non-final, use the @NonFinal annotation on the field.");
+ }
+
if (node == null) return;
generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false);
diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java
index 7f788c5d..787f6f6c 100644
--- a/src/core/lombok/eclipse/handlers/HandleGetter.java
+++ b/src/core/lombok/eclipse/handlers/HandleGetter.java
@@ -49,6 +49,8 @@ import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference;
import org.eclipse.jdt.internal.compiler.ast.Assignment;
import org.eclipse.jdt.internal.compiler.ast.BinaryExpression;
import org.eclipse.jdt.internal.compiler.ast.Block;
+import org.eclipse.jdt.internal.compiler.ast.CastExpression;
+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.FieldDeclaration;
@@ -67,6 +69,7 @@ import org.eclipse.jdt.internal.compiler.ast.SynchronizedStatement;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
+import org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.mangosdk.spi.ProviderFor;
/**
@@ -184,7 +187,7 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> {
}
TypeReference fieldType = copyType(field.type, source);
- boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0;
+ boolean isBoolean = isBoolean(fieldType);
String getterName = toGetterName(fieldNode, isBoolean);
if (getterName == null) {
@@ -286,7 +289,6 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> {
}
private static final char[][] AR = fromQualifiedName("java.util.concurrent.atomic.AtomicReference");
- private static final TypeReference[][] AR_PARAMS = new TypeReference[5][];
private static final java.util.Map<String, char[][]> TYPE_MAP;
static {
@@ -305,41 +307,54 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> {
private static char[] valueName = "value".toCharArray();
private static char[] actualValueName = "actualValue".toCharArray();
+ private static final int PARENTHESIZED = (1 << ASTNode.ParenthesizedSHIFT) & ASTNode.ParenthesizedMASK;
+
private Statement[] createLazyGetterBody(ASTNode source, EclipseNode fieldNode) {
/*
- java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get();
+ java.lang.Object value = this.fieldName.get();
if (value == null) {
synchronized (this.fieldName) {
value = this.fieldName.get();
if (value == null) {
- final ValueType actualValue = new ValueType();
- value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue);
+ final RawValueType actualValue = INITIALIZER_EXPRESSION;
+ [IF PRIMITIVE]
+ value = actualValue;
+ [ELSE]
+ value = actualValue == null ? this.fieldName : actualValue;
+ [END IF]
this.fieldName.set(value);
}
}
}
- return value.get();
+ [IF PRIMITIVE]
+ return (BoxedValueType) value;
+ [ELSE]
+ return (BoxedValueType) (value == this.fieldName ? null : value);
+ [END IF]
*/
FieldDeclaration field = (FieldDeclaration) fieldNode.get();
int pS = source.sourceStart, pE = source.sourceEnd;
long p = (long)pS << 32 | pE;
- TypeReference componentType = copyType(field.type, source);
+ TypeReference rawComponentType = copyType(field.type, source);
+ TypeReference boxedComponentType = null;
+ boolean isPrimitive = false;
if (field.type instanceof SingleTypeReference && !(field.type instanceof ArrayTypeReference)) {
char[][] newType = TYPE_MAP.get(new String(((SingleTypeReference)field.type).token));
if (newType != null) {
- componentType = new QualifiedTypeReference(newType, poss(source, 3));
+ boxedComponentType = new QualifiedTypeReference(newType, poss(source, 3));
+ isPrimitive = true;
}
}
+ if (boxedComponentType == null) boxedComponentType = copyType(field.type, source);
+ boxedComponentType.sourceStart = pS; boxedComponentType.sourceEnd = boxedComponentType.statementEnd = pE;
Statement[] statements = new Statement[3];
- /* java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get(); */ {
+ /* java.lang.Object value = this.fieldName.get(); */ {
LocalDeclaration valueDecl = new LocalDeclaration(valueName, pS, pE);
- TypeReference[][] typeParams = AR_PARAMS.clone();
- typeParams[4] = new TypeReference[] {copyType(componentType, source)};
- valueDecl.type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5));
+ valueDecl.type = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(source, 3));
valueDecl.type.sourceStart = pS; valueDecl.type.sourceEnd = valueDecl.type.statementEnd = pE;
MessageSend getter = new MessageSend();
@@ -356,8 +371,12 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> {
synchronized (this.fieldName) {
value = this.fieldName.get();
if (value == null) {
- final ValueType actualValue = new ValueType();
- value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue);
+ final ValueType actualValue = INITIALIZER_EXPRESSION;
+ [IF PRIMITIVE]
+ value = actualValue;
+ [ELSE]
+ value = actualValue == null ? this.fieldName : actualValue;
+ [END IF]
this.fieldName.set(value);
}
}
@@ -383,28 +402,37 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> {
EqualExpression innerCond = new EqualExpression(
new SingleNameReference(valueName, p), new NullLiteral(pS, pE),
BinaryExpression.EQUAL_EQUAL);
+ innerCond.sourceStart = pS; innerCond.sourceEnd = innerCond.statementEnd = pE;
Block innerThen = new Block(0);
innerThen.statements = new Statement[3];
- /* final ValueType actualValue = new ValueType(); */ {
+ /* final ValueType actualValue = INITIALIZER_EXPRESSION */ {
LocalDeclaration actualValueDecl = new LocalDeclaration(actualValueName, pS, pE);
- actualValueDecl.type = copyType(field.type, source);
+ actualValueDecl.type = rawComponentType;
actualValueDecl.type.sourceStart = pS; actualValueDecl.type.sourceEnd = actualValueDecl.type.statementEnd = pE;
actualValueDecl.initialization = field.initialization;
actualValueDecl.modifiers = ClassFileConstants.AccFinal;
innerThen.statements[0] = actualValueDecl;
}
- /* value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue); */ {
- AllocationExpression create = new AllocationExpression();
- create.sourceStart = pS; create.sourceEnd = create.statementEnd = pE;
- TypeReference[][] typeParams = AR_PARAMS.clone();
- typeParams[4] = new TypeReference[] {copyType(componentType, source)};
- create.type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5));
- create.type.sourceStart = pS; create.type.sourceEnd = create.type.statementEnd = pE;
- create.arguments = new Expression[] {new SingleNameReference(actualValueName, p)};
- Assignment innerAssign = new Assignment(new SingleNameReference(valueName, p), create, pE);
- innerAssign.sourceStart = pS; innerAssign.statementEnd = innerAssign.sourceEnd = pE;
-
- innerThen.statements[1] = innerAssign;
+ /* [IF PRIMITIVE] value = actualValue; */ {
+ if (isPrimitive) {
+ Assignment innerAssign = new Assignment(new SingleNameReference(valueName, p), new SingleNameReference(actualValueName, p), pE);
+ innerAssign.sourceStart = pS; innerAssign.statementEnd = innerAssign.sourceEnd = pE;
+ innerThen.statements[1] = innerAssign;
+ }
+ }
+ /* [ELSE] value = actualValue == null ? this.fieldName : actualValue; */ {
+ if (!isPrimitive) {
+ EqualExpression avIsNull = new EqualExpression(
+ new SingleNameReference(actualValueName, p), new NullLiteral(pS, pE),
+ BinaryExpression.EQUAL_EQUAL);
+ avIsNull.sourceStart = pS; avIsNull.sourceEnd = avIsNull.statementEnd = pE;
+ Expression fieldRef = createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source);
+ ConditionalExpression ternary = new ConditionalExpression(avIsNull, fieldRef, new SingleNameReference(actualValueName, p));
+ ternary.sourceStart = pS; ternary.sourceEnd = ternary.statementEnd = pE;
+ Assignment innerAssign = new Assignment(new SingleNameReference(valueName, p), ternary, pE);
+ innerAssign.sourceStart = pS; innerAssign.statementEnd = innerAssign.sourceEnd = pE;
+ innerThen.statements[1] = innerAssign;
+ }
}
/* this.fieldName.set(value); */ {
@@ -428,26 +456,34 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> {
statements[1] = ifStatement;
}
- /* return value.get(); */ {
- MessageSend getter = new MessageSend();
- getter.sourceStart = pS; getter.sourceEnd = getter.statementEnd = pE;
- getter.selector = new char[] {'g', 'e', 't'};
- getter.receiver = new SingleNameReference(valueName, p);
-
- statements[2] = new ReturnStatement(getter, pS, pE);
+ /* [IF PRIMITIVE] return (BoxedValueType)value; */ {
+ if (isPrimitive) {
+ CastExpression cast = makeCastExpression(new SingleNameReference(valueName, p), boxedComponentType, source);
+ statements[2] = new ReturnStatement(cast, pS, pE);
+ }
+ }
+ /* [ELSE] return (BoxedValueType)(value == this.fieldName ? null : value); */ {
+ if (!isPrimitive) {
+ EqualExpression vIsThisFieldName = new EqualExpression(
+ new SingleNameReference(valueName, p), createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source),
+ BinaryExpression.EQUAL_EQUAL);
+ vIsThisFieldName.sourceStart = pS; vIsThisFieldName.sourceEnd = vIsThisFieldName.statementEnd = pE;
+ ConditionalExpression ternary = new ConditionalExpression(vIsThisFieldName, new NullLiteral(pS, pE), new SingleNameReference(valueName, p));
+ ternary.sourceStart = pS; ternary.sourceEnd = ternary.statementEnd = pE;
+ ternary.bits |= PARENTHESIZED;
+ CastExpression cast = makeCastExpression(ternary, boxedComponentType, source);
+ statements[2] = new ReturnStatement(cast, pS, pE);
+ }
}
-
// update the field type and init last
- /* private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType> fieldName = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType>>(); */ {
-
- LocalDeclaration first = (LocalDeclaration) statements[0];
- TypeReference innerType = copyType(first.type, source);
-
- TypeReference[][] typeParams = AR_PARAMS.clone();
- typeParams[4] = new TypeReference[] {copyType(innerType, source)};
+ /* private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> fieldName = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); */ {
+ TypeReference innerType = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(source, 3));
+ TypeReference[][] typeParams = new TypeReference[5][];
+ typeParams[4] = new TypeReference[] {innerType};
TypeReference type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5));
+
// Some magic here
type.sourceStart = -1; type.sourceEnd = -2;
diff --git a/src/core/lombok/eclipse/handlers/HandleLog.java b/src/core/lombok/eclipse/handlers/HandleLog.java
index bffe2d62..2e7b4475 100644
--- a/src/core/lombok/eclipse/handlers/HandleLog.java
+++ b/src/core/lombok/eclipse/handlers/HandleLog.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010-2012 The Project Lombok Authors.
+ * Copyright (C) 2010-2013 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
@@ -178,6 +178,16 @@ public class HandleLog {
}
/**
+ * Handles the {@link lombok.extern.log4j.Log4j2} annotation for Eclipse.
+ */
+ @ProviderFor(EclipseAnnotationHandler.class)
+ public static class HandleLog4j2Log extends EclipseAnnotationHandler<lombok.extern.log4j.Log4j2> {
+ @Override public void handle(AnnotationValues<lombok.extern.log4j.Log4j2> annotation, Annotation source, EclipseNode annotationNode) {
+ processAnnotation(LoggingFramework.LOG4J2, annotation, source, annotationNode);
+ }
+ }
+
+ /**
* Handles the {@link lombok.extern.slf4j.Slf4j} annotation for Eclipse.
*/
@ProviderFor(EclipseAnnotationHandler.class)
@@ -224,6 +234,9 @@ public class HandleLog {
// private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TargetType.class);
LOG4J("org.apache.log4j.Logger", "org.apache.log4j.Logger", "getLogger", "@Log4j"),
+ // private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(TargetType.class);
+ LOG4J2("org.apache.logging.log4j.Logger", "org.apache.logging.log4j.LogManager", "getLogger", "@Log4j2"),
+
// private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TargetType.class);
SLF4J("org.slf4j.Logger", "org.slf4j.LoggerFactory", "getLogger", "@Slf4j"),
diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java
index 9b46b704..3bfcc51c 100644
--- a/src/core/lombok/eclipse/handlers/HandleSetter.java
+++ b/src/core/lombok/eclipse/handlers/HandleSetter.java
@@ -159,7 +159,7 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> {
FieldDeclaration field = (FieldDeclaration) fieldNode.get();
TypeReference fieldType = copyType(field.type, source);
- boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0;
+ boolean isBoolean = isBoolean(fieldType);
String setterName = toSetterName(fieldNode, isBoolean);
boolean shouldReturnThis = shouldReturnThis(fieldNode);
@@ -192,7 +192,7 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> {
injectMethod(fieldNode.up(), method);
}
- private MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, boolean shouldReturnThis, int modifier, ASTNode source, List<Annotation> onMethod, List<Annotation> onParam) {
+ static MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, boolean shouldReturnThis, int modifier, ASTNode source, List<Annotation> onMethod, List<Annotation> onParam) {
FieldDeclaration field = (FieldDeclaration) fieldNode.get();
int pS = source.sourceStart, pE = source.sourceEnd;
long p = (long)pS << 32 | pE;
diff --git a/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java b/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java
index b7c8a5d8..aa78ca3b 100644
--- a/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java
+++ b/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java
@@ -40,6 +40,8 @@ import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
import org.eclipse.jdt.internal.compiler.ast.Block;
+import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.MemberValuePair;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
@@ -147,7 +149,21 @@ public class HandleSneakyThrows extends EclipseAnnotationHandler<SneakyThrows> {
return;
}
- if (method.statements == null) return;
+ if (method.statements == null || method.statements.length == 0) {
+ boolean hasConstructorCall = false;
+ if (method instanceof ConstructorDeclaration) {
+ ExplicitConstructorCall constructorCall = ((ConstructorDeclaration) method).constructorCall;
+ hasConstructorCall = constructorCall != null && !constructorCall.isImplicitSuper() && !constructorCall.isImplicitThis();
+ }
+
+ if (hasConstructorCall) {
+ annotation.addWarning("Calls to sibling / super constructors are always excluded from @SneakyThrows; @SneakyThrows has been ignored because there is no other code in this constructor.");
+ } else {
+ annotation.addWarning("This method or constructor is empty; @SneakyThrows has been ignored.");
+ }
+
+ return;
+ }
Statement[] contents = method.statements;
@@ -160,9 +176,9 @@ public class HandleSneakyThrows extends EclipseAnnotationHandler<SneakyThrows> {
}
private Statement buildTryCatchBlock(Statement[] contents, DeclaredException exception, ASTNode source, AbstractMethodDeclaration method) {
- int methodStart = method.bodyStart;
- int methodEnd = method.bodyEnd;
- long methodPosEnd = methodEnd << 32 | (methodEnd & 0xFFFFFFFFL);
+ int methodStart = method.bodyStart;
+ int methodEnd = method.bodyEnd;
+ long methodPosEnd = ((long) methodEnd) << 32 | (methodEnd & 0xFFFFFFFFL);
TryStatement tryStatement = new TryStatement();
setGeneratedBy(tryStatement, source);
diff --git a/src/core/lombok/eclipse/handlers/HandleToString.java b/src/core/lombok/eclipse/handlers/HandleToString.java
index 75d4acef..1193af31 100644
--- a/src/core/lombok/eclipse/handlers/HandleToString.java
+++ b/src/core/lombok/eclipse/handlers/HandleToString.java
@@ -170,7 +170,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> {
}
}
- private MethodDeclaration createToString(EclipseNode type, Collection<EclipseNode> fields,
+ static MethodDeclaration createToString(EclipseNode type, Collection<EclipseNode> fields,
boolean includeFieldNames, boolean callSuper, ASTNode source, FieldAccess fieldAccess) {
String typeName = getTypeName(type);
char[] suffix = ")".toCharArray();
@@ -209,21 +209,25 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> {
}
for (EclipseNode field : fields) {
- TypeReference fType = getFieldType(field, fieldAccess);
+ TypeReference fieldType = getFieldType(field, fieldAccess);
Expression fieldAccessor = createFieldAccessor(field, fieldAccess, source);
+ // The distinction between primitive and object will be useful if we ever add a 'hideNulls' option.
+ boolean fieldBaseTypeIsPrimitive = BUILT_IN_TYPES.contains(new String(fieldType.getLastToken()));
+ boolean fieldIsPrimitive = fieldType.dimensions() == 0 && fieldBaseTypeIsPrimitive;
+ boolean fieldIsPrimitiveArray = fieldType.dimensions() == 1 && fieldBaseTypeIsPrimitive;
+ boolean fieldIsObjectArray = fieldType.dimensions() > 0 && !fieldIsPrimitiveArray;
+ @SuppressWarnings("unused")
+ boolean fieldIsObject = !fieldIsPrimitive && !fieldIsPrimitiveArray && !fieldIsObjectArray;
+
Expression ex;
- if (fType.dimensions() > 0) {
+ if (fieldIsPrimitiveArray || fieldIsObjectArray) {
MessageSend arrayToString = new MessageSend();
arrayToString.sourceStart = pS; arrayToString.sourceEnd = pE;
arrayToString.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.UTIL, "Arrays".toCharArray());
arrayToString.arguments = new Expression[] { fieldAccessor };
setGeneratedBy(arrayToString.arguments[0], source);
- if (fType.dimensions() > 1 || !BUILT_IN_TYPES.contains(new String(fType.getLastToken()))) {
- arrayToString.selector = "deepToString".toCharArray();
- } else {
- arrayToString.selector = "toString".toCharArray();
- }
+ arrayToString.selector = (fieldIsObjectArray ? "deepToString" : "toString").toCharArray();
ex = arrayToString;
} else {
ex = fieldAccessor;
@@ -278,7 +282,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> {
return method;
}
- private String getTypeName(EclipseNode type) {
+ private static String getTypeName(EclipseNode type) {
String typeName = getSingleTypeName(type);
EclipseNode upType = type.up();
while (upType.getKind() == Kind.TYPE) {
@@ -288,7 +292,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> {
return typeName;
}
- private String getSingleTypeName(EclipseNode type) {
+ private static String getSingleTypeName(EclipseNode type) {
TypeDeclaration typeDeclaration = (TypeDeclaration)type.get();
char[] rawTypeName = typeDeclaration.name;
return rawTypeName == null ? "" : new String(rawTypeName);
@@ -297,7 +301,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> {
private static final Set<String> BUILT_IN_TYPES = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
"byte", "short", "int", "long", "char", "boolean", "double", "float")));
- private NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) {
+ private static NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) {
int pS = source.sourceStart, pE = source.sourceEnd;
long p = (long)pS << 32 | pE;
NameReference ref;
diff --git a/src/core/lombok/eclipse/handlers/HandleValue.java b/src/core/lombok/eclipse/handlers/HandleValue.java
index b69b1669..0607137b 100644
--- a/src/core/lombok/eclipse/handlers/HandleValue.java
+++ b/src/core/lombok/eclipse/handlers/HandleValue.java
@@ -30,8 +30,9 @@ import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
+import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists;
import lombok.experimental.NonFinal;
-import lombok.experimental.Value;
+import lombok.Value;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
@@ -78,6 +79,6 @@ public class HandleValue extends EclipseAnnotationHandler<Value> {
new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true);
new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode);
new HandleToString().generateToStringForType(typeNode, annotationNode);
- new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), true, Collections.<Annotation>emptyList(), ast);
+ new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.<Annotation>emptyList(), ast);
}
}
diff --git a/src/core/lombok/eclipse/handlers/HandleWither.java b/src/core/lombok/eclipse/handlers/HandleWither.java
index 9d74cbd1..27fbc635 100644
--- a/src/core/lombok/eclipse/handlers/HandleWither.java
+++ b/src/core/lombok/eclipse/handlers/HandleWither.java
@@ -160,7 +160,7 @@ public class HandleWither extends EclipseAnnotationHandler<Wither> {
FieldDeclaration field = (FieldDeclaration) fieldNode.get();
TypeReference fieldType = copyType(field.type, source);
- boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0;
+ boolean isBoolean = isBoolean(fieldType);
String witherName = toWitherName(fieldNode, isBoolean);
if (witherName == null) {
diff --git a/src/core/lombok/eclipse/handlers/NonNullHandler.java b/src/core/lombok/eclipse/handlers/NonNullHandler.java
new file mode 100644
index 00000000..5c58069c
--- /dev/null
+++ b/src/core/lombok/eclipse/handlers/NonNullHandler.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2013 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.eclipse.handlers;
+
+import java.util.Arrays;
+
+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.Annotation;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.Block;
+import org.eclipse.jdt.internal.compiler.ast.EqualExpression;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
+import org.eclipse.jdt.internal.compiler.ast.IfStatement;
+import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
+import org.eclipse.jdt.internal.compiler.ast.OperatorIds;
+import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
+import org.eclipse.jdt.internal.compiler.ast.Statement;
+import org.eclipse.jdt.internal.compiler.ast.ThrowStatement;
+import org.mangosdk.spi.ProviderFor;
+
+import lombok.NonNull;
+import lombok.core.AST.Kind;
+import lombok.core.AnnotationValues;
+import lombok.eclipse.DeferUntilPostDiet;
+import lombok.eclipse.EclipseAnnotationHandler;
+import lombok.eclipse.EclipseNode;
+
+import static lombok.eclipse.Eclipse.*;
+import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
+
+@DeferUntilPostDiet
+@ProviderFor(EclipseAnnotationHandler.class)
+public class NonNullHandler extends EclipseAnnotationHandler<NonNull> {
+ @Override public void handle(AnnotationValues<NonNull> annotation, Annotation ast, EclipseNode annotationNode) {
+ if (annotationNode.up().getKind() == Kind.FIELD) {
+ // This is meaningless unless the field is used to generate a method (@Setter, @RequiredArgsConstructor, etc),
+ // but in that case those handlers will take care of it. However, we DO check if the annotation is applied to
+ // a primitive, because those handlers trigger on any annotation named @NonNull and we only want the warning
+ // behaviour on _OUR_ 'lombok.NonNull'.
+
+ try {
+ if (isPrimitive(((AbstractVariableDeclaration) annotationNode.up().get()).type)) {
+ annotationNode.addWarning("@NonNull is meaningless on a primitive.");
+ }
+ } catch (Exception ignore) {}
+
+ return;
+ }
+
+ if (annotationNode.up().getKind() != Kind.ARGUMENT) return;
+
+ Argument arg;
+ AbstractMethodDeclaration declaration;
+
+ try {
+ arg = (Argument) annotationNode.up().get();
+ declaration = (AbstractMethodDeclaration) annotationNode.up().up().get();
+ } catch (Exception e) {
+ return;
+ }
+
+ if (isGenerated(declaration)) return;
+
+ // Possibly, if 'declaration instanceof ConstructorDeclaration', fetch declaration.constructorCall, search it for any references to our parameter,
+ // and if they exist, create a new method in the class: 'private static <T> T lombok$nullCheck(T expr, String msg) {if (expr == null) throw NPE; return expr;}' and
+ // wrap all references to it in the super/this to a call to this method.
+
+ Statement nullCheck = generateNullCheck(arg, ast);
+
+ if (nullCheck == null) {
+ // @NonNull applied to a primitive. Kinda pointless. Let's generate a warning.
+ annotationNode.addWarning("@NonNull is meaningless on a primitive.");
+ return;
+ }
+
+ if (declaration.statements == null) {
+ declaration.statements = new Statement[] {nullCheck};
+ } else {
+ char[] expectedName = arg.name;
+ for (Statement stat : declaration.statements) {
+ char[] varNameOfNullCheck = returnVarNameIfNullCheck(stat);
+ if (varNameOfNullCheck == null) break;
+ if (Arrays.equals(expectedName, varNameOfNullCheck)) return;
+ }
+
+ Statement[] newStatements = new Statement[declaration.statements.length + 1];
+ int skipOver = 0;
+ for (Statement stat : declaration.statements) {
+ if (isGenerated(stat)) skipOver++;
+ else break;
+ }
+ System.arraycopy(declaration.statements, 0, newStatements, 0, skipOver);
+ System.arraycopy(declaration.statements, skipOver, newStatements, skipOver + 1, declaration.statements.length - skipOver);
+ newStatements[skipOver] = nullCheck;
+ declaration.statements = newStatements;
+ }
+ annotationNode.up().up().rebuild();
+ }
+
+ private char[] returnVarNameIfNullCheck(Statement stat) {
+ if (!(stat instanceof IfStatement)) return null;
+
+ /* Check that the if's statement is a throw statement, possibly in a block. */ {
+ Statement then = ((IfStatement) stat).thenStatement;
+ if (then instanceof Block) {
+ Statement[] blockStatements = ((Block) then).statements;
+ if (blockStatements == null || blockStatements.length == 0) return null;
+ then = blockStatements[0];
+ }
+
+ if (!(then instanceof ThrowStatement)) return null;
+ }
+
+ /* Check that the if's conditional is like 'x == null'. Return from this method (don't generate
+ a nullcheck) if 'x' is equal to our own variable's name: There's already a nullcheck here. */ {
+ Expression cond = ((IfStatement) stat).condition;
+ if (!(cond instanceof EqualExpression)) return null;
+ EqualExpression bin = (EqualExpression) cond;
+ int operatorId = ((bin.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT);
+ if (operatorId != OperatorIds.EQUAL_EQUAL) return null;
+ if (!(bin.left instanceof SingleNameReference)) return null;
+ if (!(bin.right instanceof NullLiteral)) return null;
+ return ((SingleNameReference) bin.left).token;
+ }
+ }
+}
diff --git a/src/core/lombok/experimental/Builder.java b/src/core/lombok/experimental/Builder.java
new file mode 100644
index 00000000..1300e7d3
--- /dev/null
+++ b/src/core/lombok/experimental/Builder.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2013 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.experimental;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * The builder annotation creates a so-called 'builder' aspect to the class that is annotated or the class
+ * that contains a member which is annotated with {@code @Builder}.
+ * <p>
+ * If a member is annotated, it must be either a constructor or a static method. If a class is annotated,
+ * then a private constructor is generated with all fields as arguments
+ * (as if {@code @AllArgsConstructor(AccessLevel.PRIVATE)} is present
+ * on the class), and it is as if this constructor has been annotated with {@code @Builder} instead.
+ * <p>
+ * The effect of {@code @Builder} is that an inner class is generated named <code><strong>T</strong>Builder</code>,
+ * with a private constructor. Instances of <code><strong>T</strong>Builder</code> are made with the static
+ * method named {@code builder()} which is also generated for you in the class itself (not in the builder class).
+ * <p>
+ * The <code><strong>T</strong>Builder</code> class contains 1 method for each parameter of the annotated
+ * constructor / static method (each field, when annotating a class), which returns the builder itself.
+ * The builder also has a <code>build()</code> method which returns a completed instance of the original type,
+ * created by passing all parameters as set via the various other methods in the builder to the constructor
+ * or static method that was annotated with {@code @Builder}. The return type of this method will be the same
+ * as the relevant class, unless a static method has been annotated, in which case it'll be equal to the
+ * return type of that method.
+ * <p>
+ * Complete documentation is found at <a href="http://projectlombok.org/features/experimental/Builder.html">the project lombok features page for &#64;Builder</a>.
+ * <p>
+ * <p>
+ * Before:
+ *
+ * <pre>
+ * &#064;Builder
+ * class Example {
+ * private int foo;
+ * private final String bar;
+ * }
+ * </pre>
+ *
+ * After:
+ *
+ * <pre>
+ * class Example&lt;T&gt; {
+ * private T foo;
+ * private final String bar;
+ *
+ * private Example(T foo, String bar) {
+ * this.foo = foo;
+ * this.bar = bar;
+ * }
+ *
+ * public static &lt;T&gt; ExampleBuilder&lt;T&gt; builder() {
+ * return new ExampleBuilder&lt;T&gt;();
+ * }
+ *
+ * public static class ExampleBuilder&lt;T&gt; {
+ * private T foo;
+ * private String bar;
+ *
+ * private ExampleBuilder() {}
+ *
+ * public ExampleBuilder foo(T foo) {
+ * this.foo = foo;
+ * return this;
+ * }
+ *
+ * public ExampleBuilder bar(String bar) {
+ * this.bar = bar;
+ * return this;
+ * }
+ *
+ * &#064;java.lang.Override public String toString() {
+ * return "ExampleBuilder(foo = " + foo + ", bar = " + bar + ")";
+ * }
+ *
+ * public Example build() {
+ * return new Example(foo, bar);
+ * }
+ * }
+ * }
+ * </pre>
+ */
+@Target({TYPE, METHOD, CONSTRUCTOR})
+@Retention(SOURCE)
+public @interface Builder {
+ /** Name of the static method that creates a new builder instance. Default: {@code builder}. */
+ String builderMethodName() default "builder";
+
+ /** Name of the instance method in the builder class that creates an instance of your {@code @Builder}-annotated class. */
+ String buildMethodName() default "build";
+
+ /** Name of the builder class.
+ * Default for {@code @Builder} on types and constructors: {@code (TypeName)Builder}.
+ * Default for {@code @Builder} on static methods: {@code (ReturnTypeName)Builder}.
+ */
+ String builderClassName() default "";
+
+ /**
+ * Normally the builder's 'set' methods are fluent, meaning, they have the same name as the field. Set this
+ * to {@code false} to name the setter method for field {@code someField}: {@code setSomeField}.
+ * <p>
+ * <strong>Default: true</strong>
+ */
+ boolean fluent() default true;
+
+ /**
+ * Normally the builder's 'set' methods are chaining, meaning, they return the builder so that you can chain
+ * calls to set methods. Set this to {@code false} to have these 'set' methods return {@code void} instead.
+ * <p>
+ * <strong>Default: true</strong>
+ */
+ boolean chain() default true;
+}
diff --git a/src/core/lombok/experimental/Value.java b/src/core/lombok/experimental/Value.java
index 048066df..b7700bb5 100644
--- a/src/core/lombok/experimental/Value.java
+++ b/src/core/lombok/experimental/Value.java
@@ -39,9 +39,11 @@ import java.lang.annotation.Target;
* @see lombok.ToString
* @see lombok.EqualsAndHashCode
* @see lombok.Data
+ * @deprecated {@link lombok.Value} has been promoted to the main package, so use that one instead.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
+@Deprecated
public @interface Value {
/**
* If you specify a static constructor name, then the generated constructor will be private, and
diff --git a/src/core/lombok/extern/apachecommons/CommonsLog.java b/src/core/lombok/extern/apachecommons/CommonsLog.java
index f178ae05..024e3744 100644
--- a/src/core/lombok/extern/apachecommons/CommonsLog.java
+++ b/src/core/lombok/extern/apachecommons/CommonsLog.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010-2012 The Project Lombok Authors.
+ * Copyright (C) 2010-2013 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
@@ -52,6 +52,7 @@ import java.lang.annotation.Target;
* @see org.apache.commons.logging.LogFactory#getLog(java.lang.Class) org.apache.commons.logging.LogFactory.getLog(Class target)
* @see lombok.extern.java.Log &#64;Log
* @see lombok.extern.log4j.Log4j &#64;Log4j
+ * @see lombok.extern.log4j.Log4j2 &#64;Log4j2
* @see lombok.extern.slf4j.Slf4j &#64;Slf4j
* @see lombok.extern.slf4j.XSlf4j &#64;XSlf4j
*/
diff --git a/src/core/lombok/extern/java/Log.java b/src/core/lombok/extern/java/Log.java
index 90c62956..7ae4e07b 100644
--- a/src/core/lombok/extern/java/Log.java
+++ b/src/core/lombok/extern/java/Log.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010-2012 The Project Lombok Authors.
+ * Copyright (C) 2010-2013 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
@@ -51,6 +51,7 @@ import java.lang.annotation.Target;
* @see java.util.logging.Logger#getLogger(java.lang.String) java.util.logging.Logger.getLogger(String name)
* @see lombok.extern.apachecommons.CommonsLog &#64;CommonsLog
* @see lombok.extern.log4j.Log4j &#64;Log4j
+ * @see lombok.extern.log4j.Log4j2 &#64;Log4j2
* @see lombok.extern.slf4j.Slf4j &#64;Slf4j
* @see lombok.extern.slf4j.XSlf4j &#64;XSlf4j
*/
diff --git a/src/core/lombok/extern/log4j/Log4j.java b/src/core/lombok/extern/log4j/Log4j.java
index 9cfc5839..29e1b27c 100644
--- a/src/core/lombok/extern/log4j/Log4j.java
+++ b/src/core/lombok/extern/log4j/Log4j.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010-2012 The Project Lombok Authors.
+ * Copyright (C) 2010-2013 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
@@ -50,6 +50,7 @@ import java.lang.annotation.Target;
*
* @see org.apache.log4j.Logger org.apache.log4j.Logger
* @see org.apache.log4j.Logger#getLogger(java.lang.Class) org.apache.log4j.Logger.getLogger(Class target)
+ * @see lombok.extern.log4j.Log4j2 &#64;Log4j2
* @see lombok.extern.apachecommons.CommonsLog &#64;CommonsLog
* @see lombok.extern.java.Log &#64;Log
* @see lombok.extern.slf4j.Slf4j &#64;Slf4j
diff --git a/src/core/lombok/extern/log4j/Log4j2.java b/src/core/lombok/extern/log4j/Log4j2.java
new file mode 100644
index 00000000..2a0f09e1
--- /dev/null
+++ b/src/core/lombok/extern/log4j/Log4j2.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2013 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.extern.log4j;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Causes lombok to generate a logger field.
+ * <p>
+ * Complete documentation is found at <a href="http://projectlombok.org/features/Log.html">the project lombok features page for lombok log annotations</a>.
+ * <p>
+ * Example:
+ * <pre>
+ * &#64;Log4j2
+ * public class LogExample {
+ * }
+ * </pre>
+ *
+ * will generate:
+ *
+ * <pre>
+ * public class LogExample {
+ * private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.Logger.getLogger(LogExample.class);
+ * }
+ * </pre>
+ *
+ * This annotation is valid for classes and enumerations.<br />
+ *
+ * @see org.apache.logging.log4j.Logger org.apache.logging.log4j.Logger
+ * @see org.apache.logging.log4j.LogManager#getLogger(java.lang.Class) org.apache.logging.log4j.LogManager.getLogger(Class target)
+ * @see lombok.extern.log4j.Log4j &#64;Log4j
+ * @see lombok.extern.apachecommons.CommonsLog &#64;CommonsLog
+ * @see lombok.extern.java.Log &#64;Log
+ * @see lombok.extern.slf4j.Slf4j &#64;Slf4j
+ * @see lombok.extern.slf4j.XSlf4j &#64;XSlf4j
+ */
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+public @interface Log4j2 {
+} \ No newline at end of file
diff --git a/src/core/lombok/extern/slf4j/Slf4j.java b/src/core/lombok/extern/slf4j/Slf4j.java
index 14dbcba6..45942971 100644
--- a/src/core/lombok/extern/slf4j/Slf4j.java
+++ b/src/core/lombok/extern/slf4j/Slf4j.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010-2012 The Project Lombok Authors.
+ * Copyright (C) 2010-2013 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
@@ -51,6 +51,7 @@ import java.lang.annotation.Target;
* @see lombok.extern.apachecommons.CommonsLog &#64;CommonsLog
* @see lombok.extern.java.Log &#64;Log
* @see lombok.extern.log4j.Log4j &#64;Log4j
+ * @see lombok.extern.log4j.Log4j2 &#64;Log4j2
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
diff --git a/src/core/lombok/extern/slf4j/XSlf4j.java b/src/core/lombok/extern/slf4j/XSlf4j.java
index bdf8a62c..599c68ab 100644
--- a/src/core/lombok/extern/slf4j/XSlf4j.java
+++ b/src/core/lombok/extern/slf4j/XSlf4j.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Project Lombok Authors.
+ * Copyright (C) 2012-2013 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
@@ -51,6 +51,7 @@ import java.lang.annotation.Target;
* @see lombok.extern.apachecommons.CommonsLog &#64;CommonsLog
* @see lombok.extern.java.Log &#64;Log
* @see lombok.extern.log4j.Log4j &#64;Log4j
+ * @see lombok.extern.log4j.Log4j2 &#64;Log4j2
* @see lombok.extern.slf4j.Slf4j &#64;Slf4j
*/
@Retention(RetentionPolicy.SOURCE)
diff --git a/src/core/lombok/javac/FindTypeVarScanner.java b/src/core/lombok/javac/FindTypeVarScanner.java
index b1b8e525..7c7d9d50 100644
--- a/src/core/lombok/javac/FindTypeVarScanner.java
+++ b/src/core/lombok/javac/FindTypeVarScanner.java
@@ -88,7 +88,7 @@ public class FindTypeVarScanner extends AbstractTypeVisitor6<Void, Void> {
@Override public Void visitTypeVariable(TypeVariable t, Void p) {
Name name = null;
try {
- name = ((Type)t).tsym.name;
+ name = ((Type) t).tsym.name;
} catch (NullPointerException e) {}
if (name != null) typeVariables.add(name.toString());
subVisit(t.getLowerBound());
diff --git a/src/core/lombok/javac/HandlerLibrary.java b/src/core/lombok/javac/HandlerLibrary.java
index 2be84355..4306b5f2 100644
--- a/src/core/lombok/javac/HandlerLibrary.java
+++ b/src/core/lombok/javac/HandlerLibrary.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 The Project Lombok Authors.
+ * Copyright (C) 2009-2013 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
@@ -231,23 +231,23 @@ public class HandlerLibrary {
* @param annotation 'node.get()' - convenience parameter.
*/
public void handleAnnotation(JCCompilationUnit unit, JavacNode node, JCAnnotation annotation, long priority) {
- TypeResolver resolver = new TypeResolver(node.getPackageDeclaration(), node.getImportStatements());
+ TypeResolver resolver = new TypeResolver(node.getImportList());
String rawType = annotation.annotationType.toString();
- for (String fqn : resolver.findTypeMatches(node, typeLibrary, rawType)) {
- AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn);
- if (container == null) continue;
-
- try {
- if (container.getPriority() == priority) {
- if (checkAndSetHandled(annotation)) container.handle(node);
- }
- } catch (AnnotationValueDecodeFail fail) {
- fail.owner.setError(fail.getMessage(), fail.idx);
- } catch (Throwable t) {
- String sourceName = "(unknown).java";
- if (unit != null && unit.sourcefile != null) sourceName = unit.sourcefile.getName();
- javacError(String.format("Lombok annotation handler %s failed on " + sourceName, container.handler.getClass()), t);
+ String fqn = resolver.typeRefToFullyQualifiedName(node, typeLibrary, rawType);
+ if (fqn == null) return;
+ AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn);
+ if (container == null) return;
+
+ try {
+ if (container.getPriority() == priority) {
+ if (checkAndSetHandled(annotation)) container.handle(node);
}
+ } catch (AnnotationValueDecodeFail fail) {
+ fail.owner.setError(fail.getMessage(), fail.idx);
+ } catch (Throwable t) {
+ String sourceName = "(unknown).java";
+ if (unit != null && unit.sourcefile != null) sourceName = unit.sourcefile.getName();
+ javacError(String.format("Lombok annotation handler %s failed on " + sourceName, container.handler.getClass()), t);
}
}
diff --git a/src/core/lombok/javac/Javac6BasedLombokOptions.java b/src/core/lombok/javac/Javac6BasedLombokOptions.java
index 4bb2bdc3..871e41c4 100644
--- a/src/core/lombok/javac/Javac6BasedLombokOptions.java
+++ b/src/core/lombok/javac/Javac6BasedLombokOptions.java
@@ -26,7 +26,6 @@ import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Options;
public class Javac6BasedLombokOptions extends LombokOptions {
-
public static Javac6BasedLombokOptions replaceWithDelombokOptions(Context context) {
Options options = Options.instance(context);
context.put(optionsKey, (Options)null);
@@ -38,7 +37,7 @@ public class Javac6BasedLombokOptions extends LombokOptions {
private Javac6BasedLombokOptions(Context context) {
super(context);
}
-
+
@Override public void putJavacOption(String optionName, String value) {
put(OptionName.valueOf(optionName), value);
}
diff --git a/src/core/lombok/javac/JavacAST.java b/src/core/lombok/javac/JavacAST.java
index 71c17538..36c51210 100644
--- a/src/core/lombok/javac/JavacAST.java
+++ b/src/core/lombok/javac/JavacAST.java
@@ -24,6 +24,7 @@ package lombok.javac;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import javax.annotation.processing.Messager;
@@ -34,10 +35,12 @@ import javax.tools.JavaFileObject;
import lombok.core.AST;
import com.sun.tools.javac.code.Symtab;
+import com.sun.tools.javac.code.Source;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.model.JavacTypes;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCCatch;
+import com.sun.tools.javac.tree.JCTree.JCTry;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCBlock;
@@ -46,7 +49,6 @@ import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCIdent;
-import com.sun.tools.javac.tree.JCTree.JCImport;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
@@ -78,7 +80,7 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> {
* @param top The compilation unit, which serves as the top level node in the tree to be built.
*/
public JavacAST(Messager messager, Context context, JCCompilationUnit top) {
- super(sourceName(top), packageDeclaration(top), imports(top));
+ super(sourceName(top), packageDeclaration(top), new JavacImportList(top));
setTop(buildCompilationUnit(top));
this.context = context;
this.messager = messager;
@@ -98,16 +100,6 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> {
return (cu.pid instanceof JCFieldAccess || cu.pid instanceof JCIdent) ? cu.pid.toString() : null;
}
- private static Collection<String> imports(JCCompilationUnit cu) {
- List<String> imports = new ArrayList<String>();
- for (JCTree def : cu.defs) {
- if (def instanceof JCImport) {
- imports.add(((JCImport)def).qualid.toString());
- }
- }
- return imports;
- }
-
public Context getContext() {
return context;
}
@@ -121,9 +113,20 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> {
}
void traverseChildren(JavacASTVisitor visitor, JavacNode node) {
- for (JavacNode child : new ArrayList<JavacNode>(node.down())) {
- child.traverse(visitor);
- }
+ for (JavacNode child : node.down()) child.traverse(visitor);
+ }
+
+ @Override public int getSourceVersion() {
+ try {
+ String nm = Source.instance(context).name();
+ int underscoreIdx = nm.indexOf('_');
+ if (underscoreIdx > -1) return Integer.parseInt(nm.substring(underscoreIdx + 1));
+ } catch (Exception ignore) {}
+ return 6;
+ }
+
+ @Override public int getLatestJavaSpecSupported() {
+ return Javac.getJavaCompilerVersion();
}
/** @return A Name object generated for the proper name table belonging to this AST. */
@@ -223,6 +226,46 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> {
return putInMap(new JavacNode(this, local, childNodes, kind));
}
+ private static boolean JCTRY_RESOURCES_FIELD_INITIALIZED;
+ private static Field JCTRY_RESOURCES_FIELD;
+
+ @SuppressWarnings("unchecked")
+ private static List<JCTree> getResourcesForTryNode(JCTry tryNode) {
+ if (!JCTRY_RESOURCES_FIELD_INITIALIZED) {
+ try {
+ JCTRY_RESOURCES_FIELD = JCTry.class.getField("resources");
+ } catch (NoSuchFieldException ignore) {
+ // Java 1.6 or lower won't have this at all.
+ } catch (Exception ignore) {
+ // Shouldn't happen. Best thing we can do is just carry on and break on try/catch.
+ }
+ JCTRY_RESOURCES_FIELD_INITIALIZED = true;
+ }
+
+ if (JCTRY_RESOURCES_FIELD == null) return Collections.emptyList();
+ Object rv = null;
+ try {
+ rv = JCTRY_RESOURCES_FIELD.get(tryNode);
+ } catch (Exception ignore) {}
+
+ if (rv instanceof List) return (List<JCTree>) rv;
+ return Collections.emptyList();
+ }
+
+ private JavacNode buildTry(JCTry tryNode) {
+ if (setAndGetAsHandled(tryNode)) return null;
+ List<JavacNode> childNodes = new ArrayList<JavacNode>();
+ for (JCTree varDecl : getResourcesForTryNode(tryNode)) {
+ if (varDecl instanceof JCVariableDecl) {
+ addIfNotNull(childNodes, buildLocalVar((JCVariableDecl) varDecl, Kind.LOCAL));
+ }
+ }
+ addIfNotNull(childNodes, buildStatement(tryNode.body));
+ for (JCCatch jcc : tryNode.catchers) addIfNotNull(childNodes, buildTree(jcc, Kind.STATEMENT));
+ addIfNotNull(childNodes, buildStatement(tryNode.finalizer));
+ return putInMap(new JavacNode(this, tryNode, childNodes, Kind.STATEMENT));
+ }
+
private JavacNode buildInitializer(JCBlock initializer) {
if (setAndGetAsHandled(initializer)) return null;
List<JavacNode> childNodes = new ArrayList<JavacNode>();
@@ -264,6 +307,7 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> {
if (statement instanceof JCAnnotation) return null;
if (statement instanceof JCClassDecl) return buildType((JCClassDecl)statement);
if (statement instanceof JCVariableDecl) return buildLocalVar((JCVariableDecl)statement, Kind.LOCAL);
+ if (statement instanceof JCTry) return buildTry((JCTry) statement);
if (setAndGetAsHandled(statement)) return null;
@@ -271,9 +315,18 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> {
}
private JavacNode drill(JCTree statement) {
- List<JavacNode> childNodes = new ArrayList<JavacNode>();
- for (FieldAccess fa : fieldsOf(statement.getClass())) childNodes.addAll(buildWithField(JavacNode.class, statement, fa));
- return putInMap(new JavacNode(this, statement, childNodes, Kind.STATEMENT));
+ try {
+ List<JavacNode> childNodes = new ArrayList<JavacNode>();
+ for (FieldAccess fa : fieldsOf(statement.getClass())) childNodes.addAll(buildWithField(JavacNode.class, statement, fa));
+ return putInMap(new JavacNode(this, statement, childNodes, Kind.STATEMENT));
+ } catch (OutOfMemoryError oome) {
+ String msg = oome.getMessage();
+ if (msg == null) msg = "(no original message)";
+ OutOfMemoryError newError = new OutOfMemoryError(getFileName() + "@pos" + statement.getPreferredPosition() + ": " + msg);
+ // We could try to set the stack trace of the new exception to the same one as the old exception, but this costs memory,
+ // and we're already in an extremely fragile situation in regards to remaining heap space, so let's not do that.
+ throw newError;
+ }
}
/** For javac, both JCExpression and JCStatement are considered as valid children types. */
diff --git a/src/core/lombok/javac/JavacAnnotationHandler.java b/src/core/lombok/javac/JavacAnnotationHandler.java
index 169e2026..a86aa6c6 100644
--- a/src/core/lombok/javac/JavacAnnotationHandler.java
+++ b/src/core/lombok/javac/JavacAnnotationHandler.java
@@ -33,7 +33,7 @@ import com.sun.tools.javac.tree.JCTree.JCAnnotation;
*
* You MUST replace 'T' with a specific annotation type, such as:
*
- * {@code public class HandleGetter implements JavacAnnotationHandler<Getter>}
+ * {@code public class HandleGetter extends JavacAnnotationHandler<Getter>}
*
* Because this generics parameter is inspected to figure out which class you're interested in.
*
diff --git a/src/core/lombok/javac/JavacImportList.java b/src/core/lombok/javac/JavacImportList.java
new file mode 100644
index 00000000..d5d7460a
--- /dev/null
+++ b/src/core/lombok/javac/JavacImportList.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2013 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.javac;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
+import com.sun.tools.javac.tree.JCTree.JCImport;
+import com.sun.tools.javac.util.List;
+
+import lombok.core.ImportList;
+import lombok.core.LombokInternalAliasing;
+
+public class JavacImportList implements ImportList {
+ private final JCExpression pkg;
+ private final List<JCTree> defs;
+
+ public JavacImportList(JCCompilationUnit cud) {
+ this.pkg = cud.pid;
+ this.defs = cud.defs;
+ }
+
+ @Override public String getFullyQualifiedNameForSimpleName(String unqualified) {
+ for (JCTree def : defs) {
+ if (!(def instanceof JCImport)) continue;
+ JCTree qual = ((JCImport) def).qualid;
+ if (!(qual instanceof JCFieldAccess)) continue;
+ String simpleName = ((JCFieldAccess) qual).name.toString();
+ if (simpleName.equals(unqualified)) {
+ return LombokInternalAliasing.processAliases(qual.toString());
+ }
+ }
+
+ return null;
+ }
+
+ @Override public boolean hasStarImport(String packageName) {
+ for (Map.Entry<String, String> e : LombokInternalAliasing.IMPLIED_EXTRA_STAR_IMPORTS.entrySet()) {
+ if (e.getValue().equals(packageName) && hasStarImport(e.getKey())) return true;
+ }
+ if (pkg != null && pkg.toString().equals(packageName)) return true;
+ if ("java.lang".equals(packageName)) return true;
+
+ for (JCTree def : defs) {
+ if (!(def instanceof JCImport)) continue;
+ if (((JCImport) def).staticImport) continue;
+ JCTree qual = ((JCImport) def).qualid;
+ if (!(qual instanceof JCFieldAccess)) continue;
+ String simpleName = ((JCFieldAccess) qual).name.toString();
+ if (!"*".equals(simpleName)) continue;
+ if (packageName.equals(((JCFieldAccess) qual).selected.toString())) return true;
+ }
+
+ return false;
+ }
+
+ @Override public Collection<String> applyNameToStarImports(String startsWith, String name) {
+ ArrayList<String> out = new ArrayList<String>();
+
+ if (pkg != null && topLevelName(pkg).equals(startsWith)) out.add(pkg.toString() + "." + name);
+
+ for (JCTree def : defs) {
+ if (!(def instanceof JCImport)) continue;
+ if (((JCImport) def).staticImport) continue;
+ JCTree qual = ((JCImport) def).qualid;
+ if (!(qual instanceof JCFieldAccess)) continue;
+ String simpleName = ((JCFieldAccess) qual).name.toString();
+ if (!"*".equals(simpleName)) continue;
+
+ String topLevelName = topLevelName(qual);
+ if (topLevelName.equals(startsWith)) {
+ out.add(((JCFieldAccess) qual).selected.toString() + "." + name);
+ }
+ }
+
+ return out;
+ }
+
+ private String topLevelName(JCTree tree) {
+ while (tree instanceof JCFieldAccess) tree = ((JCFieldAccess) tree).selected;
+ return tree.toString();
+ }
+
+ @Override public String applyUnqualifiedNameToPackage(String unqualified) {
+ if (pkg == null) return unqualified;
+ return pkg.toString() + "." + unqualified;
+ }
+}
diff --git a/src/core/lombok/javac/JavacResolution.java b/src/core/lombok/javac/JavacResolution.java
index e5d8ed38..82ce0cb8 100644
--- a/src/core/lombok/javac/JavacResolution.java
+++ b/src/core/lombok/javac/JavacResolution.java
@@ -433,8 +433,8 @@ public class JavacResolution {
if (symbol.name.length() == 0) {
// Anonymous inner class
if (type instanceof ClassType) {
- List<Type> ifaces = ((ClassType)type).interfaces_field;
- Type supertype = ((ClassType)type).supertype_field;
+ List<Type> ifaces = ((ClassType) type).interfaces_field;
+ Type supertype = ((ClassType) type).supertype_field;
if (ifaces != null && ifaces.length() == 1) {
return typeToJCTree(ifaces.get(0), ast, allowCompound, allowVoid);
}
diff --git a/src/core/lombok/javac/LombokOptions.java b/src/core/lombok/javac/LombokOptions.java
index 66e42fe7..f1567e9d 100644
--- a/src/core/lombok/javac/LombokOptions.java
+++ b/src/core/lombok/javac/LombokOptions.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Project Lombok Authors.
+ * Copyright (C) 2010-2013 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
@@ -49,6 +49,6 @@ public abstract class LombokOptions extends Options {
protected LombokOptions(Context context) {
super(context);
}
-
+
public abstract void putJavacOption(String optionName, String value);
}
diff --git a/src/core/lombok/javac/apt/Processor.java b/src/core/lombok/javac/apt/Processor.java
index 96150b06..110acaad 100644
--- a/src/core/lombok/javac/apt/Processor.java
+++ b/src/core/lombok/javac/apt/Processor.java
@@ -62,10 +62,6 @@ import com.sun.tools.javac.util.Context;
/**
* This Annotation Processor is the standard injection mechanism for lombok-enabling the javac compiler.
*
- * Due to lots of changes in the core javac code, as well as lombok's heavy usage of non-public API, this
- * code only works for the javac v1.6 compiler; it definitely won't work for javac v1.5, and it probably
- * won't work for javac v1.7 without modifications.
- *
* To actually enable lombok in a javac compilation run, this class should be in the classpath when
* running javac; that's the only requirement.
*/
diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java
new file mode 100644
index 00000000..e60819da
--- /dev/null
+++ b/src/core/lombok/javac/handlers/HandleBuilder.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2013 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.javac.handlers;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+import org.mangosdk.spi.ProviderFor;
+
+import com.sun.tools.javac.code.Flags;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.tree.JCTree.JCBlock;
+import com.sun.tools.javac.tree.JCTree.JCClassDecl;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
+import com.sun.tools.javac.tree.JCTree.JCIdent;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCModifiers;
+import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree;
+import com.sun.tools.javac.tree.JCTree.JCStatement;
+import com.sun.tools.javac.tree.JCTree.JCTypeApply;
+import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.tree.TreeMaker;
+import com.sun.tools.javac.util.List;
+import com.sun.tools.javac.util.ListBuffer;
+import com.sun.tools.javac.util.Name;
+
+import lombok.AccessLevel;
+import lombok.core.AST.Kind;
+import lombok.core.AnnotationValues;
+import lombok.core.HandlerPriority;
+import lombok.core.TransformationsUtil;
+import lombok.experimental.Builder;
+import lombok.experimental.NonFinal;
+import lombok.javac.JavacAnnotationHandler;
+import lombok.javac.JavacNode;
+import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists;
+import static lombok.javac.Javac.*;
+import static lombok.core.handlers.HandlerUtil.*;
+import static lombok.javac.handlers.JavacHandlerUtil.*;
+
+@ProviderFor(JavacAnnotationHandler.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 JavacAnnotationHandler<Builder> {
+ @Override public void handle(AnnotationValues<Builder> annotation, JCAnnotation ast, JavacNode annotationNode) {
+ Builder builderInstance = annotation.getInstance();
+ String builderMethodName = builderInstance.builderMethodName();
+ String buildMethodName = builderInstance.buildMethodName();
+ String builderClassName = builderInstance.builderClassName();
+
+ if (builderMethodName == null) builderMethodName = "builder";
+ if (buildMethodName == null) buildMethodName = "build";
+ if (builderClassName == null) builderClassName = "";
+
+ if (!checkName("builderMethodName", builderMethodName, annotationNode)) return;
+ if (!checkName("buildMethodName", buildMethodName, annotationNode)) return;
+ if (!builderClassName.isEmpty()) {
+ if (!checkName("builderClassName", builderClassName, annotationNode)) return;
+ }
+
+ deleteAnnotationIfNeccessary(annotationNode, Builder.class);
+ deleteImportFromCompilationUnit(annotationNode, "lombok.experimental.Builder");
+
+ JavacNode parent = annotationNode.up();
+
+ java.util.List<JCExpression> typesOfParameters = new ArrayList<JCExpression>();
+ java.util.List<Name> namesOfParameters = new ArrayList<Name>();
+ JCExpression returnType;
+ List<JCTypeParameter> typeParams = List.nil();
+ List<JCExpression> thrownExceptions = List.nil();
+ Name nameOfStaticBuilderMethod;
+ JavacNode tdParent;
+
+ JCMethodDecl fillParametersFrom = parent.get() instanceof JCMethodDecl ? ((JCMethodDecl) parent.get()) : null;
+
+ if (parent.get() instanceof JCClassDecl) {
+ tdParent = parent;
+ JCClassDecl td = (JCClassDecl) tdParent.get();
+ ListBuffer<JavacNode> allFields = ListBuffer.lb();
+ @SuppressWarnings("deprecation")
+ boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent));
+ for (JavacNode fieldNode : HandleConstructor.findAllFields(tdParent)) {
+ JCVariableDecl fd = (JCVariableDecl) fieldNode.get();
+ // final fields with an initializer cannot be written to, so they can't be 'builderized'. Unfortunately presence of @Value makes
+ // 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.init != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue;
+ namesOfParameters.add(fd.name);
+ typesOfParameters.add(fd.vartype);
+ allFields.append(fieldNode);
+ }
+
+ new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, List.<JCAnnotation>nil(), allFields.toList(), null, SkipIfConstructorExists.I_AM_BUILDER, true, annotationNode);
+
+ returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams);
+ typeParams = td.typarams;
+ thrownExceptions = List.nil();
+ nameOfStaticBuilderMethod = null;
+ if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder";
+ } else if (fillParametersFrom != null && fillParametersFrom.getName().toString().equals("<init>")) {
+ if (!fillParametersFrom.typarams.isEmpty()) {
+ annotationNode.addError("@Builder is not supported on constructors with constructor type parameters.");
+ return;
+ }
+ tdParent = parent.up();
+ JCClassDecl td = (JCClassDecl) tdParent.get();
+ returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams);
+ typeParams = td.typarams;
+ thrownExceptions = fillParametersFrom.thrown;
+ nameOfStaticBuilderMethod = null;
+ if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder";
+ } else if (fillParametersFrom != null) {
+ tdParent = parent.up();
+ JCClassDecl td = (JCClassDecl) tdParent.get();
+ if ((fillParametersFrom.mods.flags & Flags.STATIC) == 0) {
+ annotationNode.addError("@Builder is only supported on types, constructors, and static methods.");
+ return;
+ }
+ returnType = fillParametersFrom.restype;
+ typeParams = fillParametersFrom.typarams;
+ thrownExceptions = fillParametersFrom.thrown;
+ nameOfStaticBuilderMethod = fillParametersFrom.name;
+ if (builderClassName.isEmpty()) {
+ if (returnType instanceof JCTypeApply) {
+ returnType = ((JCTypeApply) returnType).clazz;
+ }
+ if (returnType instanceof JCFieldAccess) {
+ builderClassName = ((JCFieldAccess) returnType).name.toString() + "Builder";
+ } else if (returnType instanceof JCIdent) {
+ Name n = ((JCIdent) returnType).name;
+
+ for (JCTypeParameter tp : typeParams) {
+ if (tp.name.equals(n)) {
+ annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type.");
+ return;
+ }
+ }
+ builderClassName = n.toString() + "Builder";
+ } else if (returnType instanceof JCPrimitiveTypeTree) {
+ builderClassName = returnType.toString() + "Builder";
+ if (Character.isLowerCase(builderClassName.charAt(0))) {
+ builderClassName = Character.toTitleCase(builderClassName.charAt(0)) + builderClassName.substring(1);
+ }
+
+ } else {
+ // This shouldn't happen.
+ System.err.println("Lombok bug ID#20140614-1651: javac HandleBuilder: return type to name conversion failed: " + returnType.getClass());
+ builderClassName = td.name.toString() + "Builder";
+ }
+ }
+ } else {
+ annotationNode.addError("@Builder is only supported on types, constructors, and static methods.");
+ return;
+ }
+
+ if (fillParametersFrom != null) {
+ for (JCVariableDecl param : fillParametersFrom.params) {
+ namesOfParameters.add(param.name);
+ typesOfParameters.add(param.vartype);
+ }
+ }
+
+ JavacNode builderType = findInnerClass(tdParent, builderClassName);
+ if (builderType == null) {
+ builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast);
+ } else {
+ sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode);
+ }
+ java.util.List<JavacNode> fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast);
+ java.util.List<JCMethodDecl> newMethods = new ArrayList<JCMethodDecl>();
+ for (JavacNode fieldNode : fieldNodes) {
+ JCMethodDecl newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast, builderInstance.fluent(), builderInstance.chain());
+ if (newMethod != null) newMethods.add(newMethod);
+ }
+
+ if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) {
+ JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.<JCAnnotation>nil(), builderType, List.<JavacNode>nil(), true, ast);
+ if (cd != null) injectMethod(builderType, cd);
+ }
+
+ for (JCMethodDecl newMethod : newMethods) injectMethod(builderType, newMethod);
+
+ if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) {
+ JCMethodDecl md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, thrownExceptions);
+ if (md != null) injectMethod(builderType, md);
+ }
+
+ if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) {
+ JCMethodDecl md = HandleToString.createToString(builderType, fieldNodes, true, false, FieldAccess.ALWAYS_FIELD, ast);
+ if (md != null) injectMethod(builderType, md);
+ }
+
+ if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) {
+ JCMethodDecl md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams);
+ if (md != null) injectMethod(tdParent, md);
+ }
+ }
+
+ private JCMethodDecl generateBuildMethod(String name, Name staticName, JCExpression returnType, java.util.List<Name> fieldNames, JavacNode type, List<JCExpression> thrownExceptions) {
+ TreeMaker maker = type.getTreeMaker();
+
+ JCExpression call;
+ JCStatement statement;
+
+ ListBuffer<JCExpression> args = ListBuffer.lb();
+ for (Name n : fieldNames) {
+ args.append(maker.Ident(n));
+ }
+
+ if (staticName == null) {
+ call = maker.NewClass(null, List.<JCExpression>nil(), returnType, args.toList(), null);
+ statement = maker.Return(call);
+ } else {
+ ListBuffer<JCExpression> typeParams = ListBuffer.lb();
+ for (JCTypeParameter tp : ((JCClassDecl) type.get()).typarams) {
+ typeParams.append(maker.Ident(tp.name));
+ }
+
+ JCExpression fn = maker.Select(maker.Ident(((JCClassDecl) type.up().get()).name), staticName);
+ call = maker.Apply(typeParams.toList(), fn, args.toList());
+ if (returnType instanceof JCPrimitiveTypeTree && compareCTC(getTypeTag((JCPrimitiveTypeTree) returnType), CTC_VOID)) {
+ statement = maker.Exec(call);
+ } else {
+ statement = maker.Return(call);
+ }
+ }
+
+ JCBlock body = maker.Block(0, List.<JCStatement>of(statement));
+
+ return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName(name), returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), thrownExceptions, body, null);
+ }
+
+ private JCMethodDecl generateBuilderMethod(String builderMethodName, String builderClassName, JavacNode type, List<JCTypeParameter> typeParams) {
+ TreeMaker maker = type.getTreeMaker();
+
+ ListBuffer<JCExpression> typeArgs = ListBuffer.lb();
+ for (JCTypeParameter typeParam : typeParams) {
+ typeArgs.append(maker.Ident(typeParam.name));
+ }
+
+ JCExpression call = maker.NewClass(null, List.<JCExpression>nil(), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), List.<JCExpression>nil(), null);
+ JCStatement statement = maker.Return(call);
+
+ JCBlock body = maker.Block(0, List.<JCStatement>of(statement));
+ return maker.MethodDef(maker.Modifiers(Flags.STATIC | Flags.PUBLIC), type.toName(builderMethodName), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), copyTypeParams(maker, typeParams), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null);
+ }
+
+ private java.util.List<JavacNode> addFieldsToBuilder(JavacNode builderType, java.util.List<Name> namesOfParameters, java.util.List<JCExpression> typesOfParameters, JCTree source) {
+ int len = namesOfParameters.size();
+ java.util.List<JavacNode> existing = new ArrayList<JavacNode>();
+ for (JavacNode child : builderType.down()) {
+ if (child.getKind() == Kind.FIELD) existing.add(child);
+ }
+
+ java.util.List<JavacNode>out = new ArrayList<JavacNode>();
+
+ top:
+ for (int i = len - 1; i >= 0; i--) {
+ Name name = namesOfParameters.get(i);
+ for (JavacNode exists : existing) {
+ Name n = ((JCVariableDecl) exists.get()).name;
+ if (n.equals(name)) {
+ out.add(exists);
+ continue top;
+ }
+ }
+ TreeMaker maker = builderType.getTreeMaker();
+ JCModifiers mods = maker.Modifiers(Flags.PRIVATE);
+ JCVariableDecl newField = maker.VarDef(mods, name, cloneType(maker, typesOfParameters.get(i), source), null);
+ out.add(injectField(builderType, newField));
+ }
+
+ Collections.reverse(out);
+ return out;
+ }
+
+
+ private JCMethodDecl makeSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JCTree source, boolean fluent, boolean chain) {
+ Name fieldName = ((JCVariableDecl) fieldNode.get()).name;
+
+ for (JavacNode child : builderType.down()) {
+ if (child.getKind() != Kind.METHOD) continue;
+ Name existingName = ((JCMethodDecl) child.get()).name;
+ if (existingName.equals(fieldName)) return null;
+ }
+
+ boolean isBoolean = isBoolean(fieldNode);
+ String setterName = fluent ? fieldNode.getName() : TransformationsUtil.toSetterName(null, fieldNode.getName(), isBoolean);
+
+ TreeMaker maker = builderType.getTreeMaker();
+ return HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, setterName, chain, source, List.<JCAnnotation>nil(), List.<JCAnnotation>nil());
+ }
+
+ private JavacNode findInnerClass(JavacNode parent, String name) {
+ for (JavacNode child : parent.down()) {
+ if (child.getKind() != Kind.TYPE) continue;
+ JCClassDecl td = (JCClassDecl) child.get();
+ if (td.name.contentEquals(name)) return child;
+ }
+ return null;
+ }
+
+ private JavacNode makeBuilderClass(JavacNode tdParent, String builderClassName, List<JCTypeParameter> typeParams, JCAnnotation ast) {
+ TreeMaker maker = tdParent.getTreeMaker();
+ JCModifiers mods = maker.Modifiers(Flags.PUBLIC | Flags.STATIC);
+ JCClassDecl builder = ClassDef(maker, mods, tdParent.toName(builderClassName), copyTypeParams(maker, typeParams), null, List.<JCExpression>nil(), List.<JCTree>nil());
+ return injectType(tdParent, builder);
+ }
+}
diff --git a/src/core/lombok/javac/handlers/HandleCleanup.java b/src/core/lombok/javac/handlers/HandleCleanup.java
index c75256a5..790d8964 100644
--- a/src/core/lombok/javac/handlers/HandleCleanup.java
+++ b/src/core/lombok/javac/handlers/HandleCleanup.java
@@ -32,7 +32,6 @@ import lombok.javac.JavacNode;
import org.mangosdk.spi.ProviderFor;
-import com.sun.tools.javac.code.TypeTags;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCAssign;
diff --git a/src/core/lombok/javac/handlers/HandleConstructor.java b/src/core/lombok/javac/handlers/HandleConstructor.java
index bb883ca4..ecd982e9 100644
--- a/src/core/lombok/javac/handlers/HandleConstructor.java
+++ b/src/core/lombok/javac/handlers/HandleConstructor.java
@@ -29,6 +29,7 @@ import lombok.RequiredArgsConstructor;
import lombok.core.AnnotationValues;
import lombok.core.TransformationsUtil;
import lombok.core.AST.Kind;
+import lombok.experimental.Builder;
import lombok.javac.JavacAnnotationHandler;
import lombok.javac.JavacNode;
@@ -66,7 +67,7 @@ public class HandleConstructor {
String staticName = ann.staticName();
if (level == AccessLevel.NONE) return;
List<JavacNode> fields = List.nil();
- new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, staticName, false, false, annotationNode);
+ new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, staticName, SkipIfConstructorExists.NO, false, annotationNode);
}
}
@@ -84,7 +85,7 @@ public class HandleConstructor {
@SuppressWarnings("deprecation")
boolean suppressConstructorProperties = ann.suppressConstructorProperties();
if (level == AccessLevel.NONE) return;
- new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), staticName, false, suppressConstructorProperties, annotationNode);
+ new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode);
}
}
@@ -119,11 +120,11 @@ public class HandleConstructor {
@SuppressWarnings("deprecation")
boolean suppressConstructorProperties = ann.suppressConstructorProperties();
if (level == AccessLevel.NONE) return;
- new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), staticName, false, suppressConstructorProperties, annotationNode);
+ new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode);
}
}
- private static List<JavacNode> findAllFields(JavacNode typeNode) {
+ static List<JavacNode> findAllFields(JavacNode typeNode) {
ListBuffer<JavacNode> fields = ListBuffer.lb();
for (JavacNode child : typeNode.down()) {
if (child.getKind() != Kind.FIELD) continue;
@@ -154,25 +155,34 @@ public class HandleConstructor {
return true;
}
- public void generateRequiredArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, JavacNode source) {
+ public void generateRequiredArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) {
generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findRequiredFields(typeNode), staticName, skipIfConstructorExists, false, source);
}
- public void generateAllArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, JavacNode source) {
+ public enum SkipIfConstructorExists {
+ YES, NO, I_AM_BUILDER;
+ }
+
+ public void generateAllArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) {
generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findAllFields(typeNode), staticName, skipIfConstructorExists, false, source);
}
- public void generateConstructor(JavacNode typeNode, AccessLevel level, List<JCAnnotation> onConstructor, List<JavacNode> fields, String staticName, boolean skipIfConstructorExists, boolean suppressConstructorProperties, JavacNode source) {
+ public void generateConstructor(JavacNode typeNode, AccessLevel level, List<JCAnnotation> onConstructor, List<JavacNode> fields, String staticName, SkipIfConstructorExists skipIfConstructorExists, boolean suppressConstructorProperties, JavacNode source) {
boolean staticConstrRequired = staticName != null && !staticName.equals("");
- if (skipIfConstructorExists && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return;
- if (skipIfConstructorExists) {
+ if (skipIfConstructorExists != SkipIfConstructorExists.NO && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return;
+ if (skipIfConstructorExists != SkipIfConstructorExists.NO) {
for (JavacNode child : typeNode.down()) {
if (child.getKind() == Kind.ANNOTATION) {
- if (annotationTypeMatches(NoArgsConstructor.class, child) ||
+ boolean skipGeneration = annotationTypeMatches(NoArgsConstructor.class, child) ||
annotationTypeMatches(AllArgsConstructor.class, child) ||
- annotationTypeMatches(RequiredArgsConstructor.class, child)) {
-
+ annotationTypeMatches(RequiredArgsConstructor.class, child);
+
+ if (!skipGeneration && skipIfConstructorExists == SkipIfConstructorExists.YES) {
+ skipGeneration = annotationTypeMatches(Builder.class, child);
+ }
+
+ if (skipGeneration) {
if (staticConstrRequired) {
// @Data has asked us to generate a constructor, but we're going to skip this instruction, as an explicit 'make a constructor' annotation
// will take care of it. However, @Data also wants a specific static name; this will be ignored; the appropriate way to do this is to use
@@ -207,7 +217,7 @@ public class HandleConstructor {
mods.annotations = mods.annotations.append(annotation);
}
- private JCMethodDecl createConstructor(AccessLevel level, List<JCAnnotation> onConstructor, JavacNode typeNode, List<JavacNode> fields, boolean suppressConstructorProperties, JCTree source) {
+ static JCMethodDecl createConstructor(AccessLevel level, List<JCAnnotation> onConstructor, JavacNode typeNode, List<JavacNode> fields, boolean suppressConstructorProperties, JCTree source) {
TreeMaker maker = typeNode.getTreeMaker();
boolean isEnum = (((JCClassDecl) typeNode.get()).mods.flags & Flags.ENUM) != 0;
@@ -243,7 +253,7 @@ public class HandleConstructor {
null, List.<JCTypeParameter>nil(), params.toList(), List.<JCExpression>nil(), maker.Block(0L, nullChecks.appendList(assigns).toList()), null), source);
}
- private boolean isLocalType(JavacNode type) {
+ private static boolean isLocalType(JavacNode type) {
Kind kind = type.up().getKind();
if (kind == Kind.COMPILATION_UNIT) return false;
if (kind == Kind.TYPE) return isLocalType(type.up());
diff --git a/src/core/lombok/javac/handlers/HandleData.java b/src/core/lombok/javac/handlers/HandleData.java
index 62183a15..858fb543 100644
--- a/src/core/lombok/javac/handlers/HandleData.java
+++ b/src/core/lombok/javac/handlers/HandleData.java
@@ -27,6 +27,7 @@ import lombok.Data;
import lombok.core.AnnotationValues;
import lombok.javac.JavacAnnotationHandler;
import lombok.javac.JavacNode;
+import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists;
import org.mangosdk.spi.ProviderFor;
@@ -50,7 +51,7 @@ public class HandleData extends JavacAnnotationHandler<Data> {
String staticConstructorName = annotation.getInstance().staticConstructor();
// TODO move this to the end OR move it to the top in eclipse.
- new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, true, annotationNode);
+ new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, SkipIfConstructorExists.YES, annotationNode);
new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true);
new HandleSetter().generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true);
new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode);
diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java
index 39edb143..741e7e21 100644
--- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java
+++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2012 The Project Lombok Authors.
+ * Copyright (C) 2009-2013 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
@@ -25,6 +25,7 @@ import static lombok.javac.Javac.*;
import static lombok.javac.handlers.JavacHandlerUtil.*;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import lombok.EqualsAndHashCode;
@@ -178,17 +179,26 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas
boolean isFinal = (((JCClassDecl)typeNode.get()).mods.flags & Flags.FINAL) != 0;
boolean needsCanEqual = !isFinal || !isDirectDescendantOfObject;
- java.util.List<MemberExistsResult> existsResults = new ArrayList<MemberExistsResult>();
- existsResults.add(methodExists("equals", typeNode, 1));
- existsResults.add(methodExists("hashCode", typeNode, 0));
- existsResults.add(methodExists("canEqual", typeNode, 1));
- switch (Collections.max(existsResults)) {
+ MemberExistsResult equalsExists = methodExists("equals", typeNode, 1);
+ MemberExistsResult hashCodeExists = methodExists("hashCode", typeNode, 0);
+ MemberExistsResult canEqualExists = methodExists("canEqual", typeNode, 1);
+ switch (Collections.max(Arrays.asList(equalsExists, hashCodeExists, canEqualExists))) {
case EXISTS_BY_LOMBOK:
return;
case EXISTS_BY_USER:
if (whineIfExists) {
String msg = String.format("Not generating equals%s: A method with one of those names already exists. (Either all or none of these methods will be generated).", needsCanEqual ? ", hashCode and canEquals" : " and hashCode");
source.addWarning(msg);
+ } else if (equalsExists == MemberExistsResult.NOT_EXISTS || hashCodeExists == MemberExistsResult.NOT_EXISTS) {
+ // This means equals OR hashCode exists and not both (or neither, but canEqual is there).
+ // Even though we should suppress the message about not generating these, this is such a weird and surprising situation we should ALWAYS generate a warning.
+ // The user code couldn't possibly (barring really weird subclassing shenanigans) be in a shippable state anyway; the implementations of these 3 methods are
+ // all inter-related and should be written by the same entity.
+ String msg = String.format("Not generating %s: One of equals, hashCode, and canEqual exists. " +
+ "You should either write all of these are none of these (in the latter case, lombok generates them).",
+ equalsExists == MemberExistsResult.NOT_EXISTS && hashCodeExists == MemberExistsResult.NOT_EXISTS ? "equals and hashCode" :
+ equalsExists == MemberExistsResult.NOT_EXISTS ? "equals" : "hashCode");
+ source.addWarning(msg);
}
return;
case NOT_EXISTS:
diff --git a/src/core/lombok/javac/handlers/HandleFieldDefaults.java b/src/core/lombok/javac/handlers/HandleFieldDefaults.java
index d32446c3..038f3e3f 100644
--- a/src/core/lombok/javac/handlers/HandleFieldDefaults.java
+++ b/src/core/lombok/javac/handlers/HandleFieldDefaults.java
@@ -44,7 +44,7 @@ import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
* Handles the {@code lombok.FieldDefaults} annotation for eclipse.
*/
@ProviderFor(JavacAnnotationHandler.class)
-@HandlerPriority(-512) //-2^9; to ensure @Setter and such pick up on messing with the fields' 'final' state, run earlier.
+@HandlerPriority(-2048) //-2^11; to ensure @Value picks up on messing with the fields' 'final' state, run earlier.
public class HandleFieldDefaults extends JavacAnnotationHandler<FieldDefaults> {
public boolean generateFieldDefaultsForType(JavacNode typeNode, JavacNode errorNode, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) {
if (checkForTypeLevelFieldDefaults) {
@@ -108,6 +108,14 @@ public class HandleFieldDefaults extends JavacAnnotationHandler<FieldDefaults> {
return;
}
+ if (level == AccessLevel.PACKAGE) {
+ annotationNode.addError("Setting 'level' to PACKAGE does nothing. To force fields as package private, use the @PackagePrivate annotation on the field.");
+ }
+
+ if (!makeFinal && annotation.isExplicit("makeFinal")) {
+ annotationNode.addError("Setting 'makeFinal' to false does nothing. To force fields to be non-final, use the @NonFinal annotation on the field.");
+ }
+
if (node == null) return;
generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false);
diff --git a/src/core/lombok/javac/handlers/HandleGetter.java b/src/core/lombok/javac/handlers/HandleGetter.java
index 2fa2a755..c5ec6f60 100644
--- a/src/core/lombok/javac/handlers/HandleGetter.java
+++ b/src/core/lombok/javac/handlers/HandleGetter.java
@@ -53,11 +53,9 @@ import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
import com.sun.tools.javac.tree.JCTree.JCIf;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
-import com.sun.tools.javac.tree.JCTree.JCNewClass;
import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCSynchronized;
-import com.sun.tools.javac.tree.JCTree.JCTypeApply;
import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.tree.TreeMaker;
@@ -256,6 +254,7 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> {
if (toClearOfMarkers != null) recursiveSetGeneratedBy(toClearOfMarkers, null);
decl.mods.annotations = decl.mods.annotations.appendList(delegates);
+ copyJavadoc(field, decl, CopyJavadoc.GETTER);
return decl;
}
@@ -287,6 +286,7 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> {
}
private static final String AR = "java.util.concurrent.atomic.AtomicReference";
+ private static final String JLO = "java.lang.Object";
private static final List<JCExpression> NIL_EXPRESSION = List.nil();
private static final java.util.Map<Object, String> TYPE_MAP;
@@ -305,37 +305,50 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> {
private List<JCStatement> createLazyGetterBody(TreeMaker maker, JavacNode fieldNode, JCTree source) {
/*
- java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get();
+ java.lang.Object value = this.fieldName.get();
if (value == null) {
synchronized (this.fieldName) {
value = this.fieldName.get();
- if (value == null) {
- final ValueType actualValue = new ValueType();
- value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue);
+ if (value == null) {
+ final RawValueType actualValue = INITIALIZER_EXPRESSION;
+ [IF PRIMITIVE]
+ value = actualValue;
+ [ELSE]
+ value = actualValue == null ? this.fieldName : actualValue;
+ [END IF]
this.fieldName.set(value);
}
}
}
- return value.get();
+ [IF PRIMITIVE]
+ return (BoxedValueType) value;
+ [ELSE]
+ return (BoxedValueType) (value == this.fieldName ? null : value);
+ [END IF]
*/
ListBuffer<JCStatement> statements = ListBuffer.lb();
JCVariableDecl field = (JCVariableDecl) fieldNode.get();
JCExpression copyOfRawFieldType = copyType(maker, field);
+ JCExpression copyOfBoxedFieldType = null;
field.type = null;
+ boolean isPrimitive = false;
if (field.vartype instanceof JCPrimitiveTypeTree) {
String boxed = TYPE_MAP.get(((JCPrimitiveTypeTree)field.vartype).typetag);
if (boxed != null) {
+ isPrimitive = true;
field.vartype = chainDotsString(fieldNode, boxed);
+ copyOfBoxedFieldType = chainDotsString(fieldNode, boxed);
}
}
+ if (copyOfBoxedFieldType == null) copyOfBoxedFieldType = copyType(maker, field);
Name valueName = fieldNode.toName("value");
Name actualValueName = fieldNode.toName("actualValue");
- /* java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get();*/ {
- JCTypeApply valueVarType = maker.TypeApply(chainDotsString(fieldNode, AR), List.of(copyType(maker, field)));
+ /* java.lang.Object value = this.fieldName.get();*/ {
+ JCExpression valueVarType = chainDotsString(fieldNode, JLO);
statements.append(maker.VarDef(maker.Modifiers(0), valueName, valueVarType, callGet(fieldNode, createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD))));
}
@@ -350,15 +363,23 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> {
/* if (value == null) { */ {
ListBuffer<JCStatement> innerIfStatements = ListBuffer.lb();
- /* ValueType actualValue = new ValueType(); */ {
+ /* final RawValueType actualValue = INITIALIZER_EXPRESSION; */ {
innerIfStatements.append(maker.VarDef(maker.Modifiers(Flags.FINAL), actualValueName, copyOfRawFieldType, field.init));
}
- /* value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue);*/ {
- JCTypeApply valueVarType = maker.TypeApply(chainDotsString(fieldNode, AR), List.of(copyType(maker, field)));
- JCNewClass newInstance = maker.NewClass(null, NIL_EXPRESSION, valueVarType, List.<JCExpression>of(maker.Ident(actualValueName)), null);
-
- JCStatement statement = maker.Exec(maker.Assign(maker.Ident(valueName), newInstance));
- innerIfStatements.append(statement);
+ /* [IF primitive] value = actualValue; */ {
+ if (isPrimitive) {
+ JCStatement statement = maker.Exec(maker.Assign(maker.Ident(valueName), maker.Ident(actualValueName)));
+ innerIfStatements.append(statement);
+ }
+ }
+ /* [ELSE] value = actualValue == null ? this.fieldName : actualValue; */ {
+ if (!isPrimitive) {
+ JCExpression actualValueIsNull = Javac.makeBinary(maker, CTC_EQUAL, maker.Ident(actualValueName), Javac.makeLiteral(maker, CTC_BOT, null));
+ JCExpression thisDotFieldName = createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD);
+ JCExpression ternary = maker.Conditional(actualValueIsNull, thisDotFieldName, maker.Ident(actualValueName));
+ JCStatement statement = maker.Exec(maker.Assign(maker.Ident(valueName), ternary));
+ innerIfStatements.append(statement);
+ }
}
/* this.fieldName.set(value); */ {
JCStatement statement = callSet(fieldNode, createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD), maker.Ident(valueName));
@@ -377,15 +398,25 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> {
JCIf ifStatement = maker.If(isNull, maker.Block(0, List.<JCStatement>of(synchronizedStatement)), null);
statements.append(ifStatement);
}
- /* return value.get(); */
- statements.append(maker.Return(callGet(fieldNode, maker.Ident(valueName))));
+ /* [IF PRIMITIVE] return (BoxedValueType) value; */ {
+ if (isPrimitive) {
+ statements.append(maker.Return(maker.TypeCast(copyOfBoxedFieldType, maker.Ident(valueName))));
+ }
+ }
+ /* [ELSE] return (BoxedValueType) (value == this.fieldName ? null : value); */ {
+ if (!isPrimitive) {
+ JCExpression valueEqualsSelf = Javac.makeBinary(maker, CTC_EQUAL, maker.Ident(valueName), createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD));
+ JCExpression ternary = maker.Conditional(valueEqualsSelf, Javac.makeLiteral(maker, CTC_BOT, null), maker.Ident(valueName));
+ JCExpression typeCast = maker.TypeCast(copyOfBoxedFieldType, maker.Parens(ternary));
+ statements.append(maker.Return(typeCast));
+ }
+ }
// update the field type and init last
- /* private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType> fieldName = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType>>(); */ {
+ /* private final java.util.concurrent.atomic.AtomicReference<Object> fieldName = new java.util.concurrent.atomic.AtomicReference<Object>(); */ {
field.vartype = recursiveSetGeneratedBy(
- maker.TypeApply(chainDotsString(fieldNode, AR), List.<JCExpression>of(maker.TypeApply(chainDotsString(fieldNode, AR), List.of(copyType(maker, field))))),
- source);
+ maker.TypeApply(chainDotsString(fieldNode, AR), List.<JCExpression>of(chainDotsString(fieldNode, JLO))), source);
field.init = recursiveSetGeneratedBy(maker.NewClass(null, NIL_EXPRESSION, copyType(maker, field), NIL_EXPRESSION, null), source);
}
diff --git a/src/core/lombok/javac/handlers/HandleLog.java b/src/core/lombok/javac/handlers/HandleLog.java
index 62a55c44..35a32be5 100644
--- a/src/core/lombok/javac/handlers/HandleLog.java
+++ b/src/core/lombok/javac/handlers/HandleLog.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010-2012 The Project Lombok Authors.
+ * Copyright (C) 2010-2013 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
@@ -82,7 +82,7 @@ public class HandleLog {
private static boolean createField(LoggingFramework framework, JavacNode typeNode, JCFieldAccess loggingType, JCTree source) {
TreeMaker maker = typeNode.getTreeMaker();
- // private static final <loggerType> log = <factoryMethod>(<parameter>);
+ // private static final <loggerType> log = <factoryMethod>(<parameter>);
JCExpression loggerType = chainDotsString(typeNode, framework.getLoggerTypeName());
JCExpression factoryMethod = chainDotsString(typeNode, framework.getLoggerFactoryMethodName());
@@ -128,6 +128,16 @@ public class HandleLog {
}
/**
+ * Handles the {@link lombok.extern.log4j.Log4j2} annotation for javac.
+ */
+ @ProviderFor(JavacAnnotationHandler.class)
+ public static class HandleLog4j2Log extends JavacAnnotationHandler<lombok.extern.log4j.Log4j2> {
+ @Override public void handle(AnnotationValues<lombok.extern.log4j.Log4j2> annotation, JCAnnotation ast, JavacNode annotationNode) {
+ processAnnotation(LoggingFramework.LOG4J2, annotation, annotationNode);
+ }
+ }
+
+ /**
* Handles the {@link lombok.extern.slf4j.Slf4j} annotation for javac.
*/
@ProviderFor(JavacAnnotationHandler.class)
@@ -163,6 +173,9 @@ public class HandleLog {
// private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TargetType.class);
LOG4J(lombok.extern.log4j.Log4j.class, "org.apache.log4j.Logger", "org.apache.log4j.Logger.getLogger"),
+ // private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(TargetType.class);
+ LOG4J2(lombok.extern.log4j.Log4j2.class, "org.apache.logging.log4j.Logger", "org.apache.logging.log4j.LogManager.getLogger"),
+
// private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TargetType.class);
SLF4J(lombok.extern.slf4j.Slf4j.class, "org.slf4j.Logger", "org.slf4j.LoggerFactory.getLogger"),
diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java
index 2136024e..c3ee10a3 100644
--- a/src/core/lombok/javac/handlers/HandleSetter.java
+++ b/src/core/lombok/javac/handlers/HandleSetter.java
@@ -26,10 +26,6 @@ import static lombok.javac.handlers.JavacHandlerUtil.*;
import java.util.Collection;
-import javax.lang.model.type.NoType;
-import javax.lang.model.type.TypeKind;
-import javax.lang.model.type.TypeVisitor;
-
import lombok.AccessLevel;
import lombok.Setter;
import lombok.core.AST.Kind;
@@ -43,7 +39,6 @@ import lombok.javac.handlers.JavacHandlerUtil.FieldAccess;
import org.mangosdk.spi.ProviderFor;
import com.sun.tools.javac.code.Flags;
-import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCAssign;
@@ -195,9 +190,13 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> {
injectMethod(fieldNode.up(), createdSetter);
}
- private JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, JCTree source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) {
+ static JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, JCTree source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) {
String setterName = toSetterName(field);
boolean returnThis = shouldReturnThis(field);
+ return createSetter(access, field, treeMaker, setterName, returnThis, source, onMethod, onParam);
+ }
+
+ static JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, String setterName, boolean shouldReturnThis, JCTree source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) {
if (setterName == null) return null;
JCVariableDecl fieldDecl = (JCVariableDecl) field.get();
@@ -223,17 +222,17 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> {
}
JCExpression methodType = null;
- if (returnThis) {
+ if (shouldReturnThis) {
methodType = cloneSelfType(field);
}
if (methodType == null) {
//WARNING: Do not use field.getSymbolTable().voidType - that field has gone through non-backwards compatible API changes within javac1.6.
- methodType = treeMaker.Type(new JCNoType(CTC_VOID));
- returnThis = false;
+ methodType = treeMaker.Type(Javac.createVoidType(treeMaker, CTC_VOID));
+ shouldReturnThis = false;
}
- if (returnThis) {
+ if (shouldReturnThis) {
JCReturn returnStatement = treeMaker.Return(treeMaker.Ident(field.toName("this")));
statements.append(returnStatement);
}
@@ -249,26 +248,9 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> {
annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(chainDots(field, "java", "lang", "Deprecated"), List.<JCExpression>nil()));
}
- return recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, methodType,
+ JCMethodDecl decl = recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, methodType,
methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source);
- }
-
- private static class JCNoType extends Type implements NoType {
- public JCNoType(Object tag) {
- //FIX
- super(1, null);
- }
-
- @Override
- public TypeKind getKind() {
- if (Javac.compareCTC(tag, CTC_VOID)) return TypeKind.VOID;
- if (Javac.compareCTC(tag, CTC_NONE)) return TypeKind.NONE;
- throw new AssertionError("Unexpected tag: " + tag);
- }
-
- @Override
- public <R, P> R accept(TypeVisitor<R, P> v, P p) {
- return v.visitNoType(this, p);
- }
+ copyJavadoc(field, decl, CopyJavadoc.SETTER);
+ return decl;
}
}
diff --git a/src/core/lombok/javac/handlers/HandleSneakyThrows.java b/src/core/lombok/javac/handlers/HandleSneakyThrows.java
index a5bd74e7..c818f630 100644
--- a/src/core/lombok/javac/handlers/HandleSneakyThrows.java
+++ b/src/core/lombok/javac/handlers/HandleSneakyThrows.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2009-2011 The Project Lombok Authors.
+ * Copyright (C) 2009-2013 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
@@ -36,8 +36,6 @@ import org.mangosdk.spi.ProviderFor;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.tree.JCTree;
-import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
-import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCBlock;
@@ -84,13 +82,20 @@ public class HandleSneakyThrows extends JavacAnnotationHandler<SneakyThrows> {
return;
}
- if (method.body == null) return;
- if (method.body.stats.isEmpty()) return;
+ if (method.body == null || method.body.stats.isEmpty()) {
+ generateEmptyBlockWarning(methodNode, annotation, false);
+ return;
+ }
final JCStatement constructorCall = method.body.stats.get(0);
final boolean isConstructorCall = isConstructorCall(constructorCall);
List<JCStatement> contents = isConstructorCall ? method.body.stats.tail : method.body.stats;
+ if (contents == null || contents.isEmpty()) {
+ generateEmptyBlockWarning(methodNode, annotation, true);
+ return;
+ }
+
for (String exception : exceptions) {
contents = List.of(buildTryCatchBlock(methodNode, contents, exception, annotation.get()));
}
@@ -99,14 +104,14 @@ public class HandleSneakyThrows extends JavacAnnotationHandler<SneakyThrows> {
methodNode.rebuild();
}
- private boolean isConstructorCall(final JCStatement supect) {
- if (!(supect instanceof JCExpressionStatement)) return false;
- final JCExpression supectExpression = ((JCExpressionStatement) supect).expr;
- if (!(supectExpression instanceof JCMethodInvocation)) return false;
- final String methodName = ((JCMethodInvocation) supectExpression).meth.toString();
- return "super".equals(methodName) || "this".equals(methodName);
+ private void generateEmptyBlockWarning(JavacNode methodNode, JavacNode annotation, boolean hasConstructorCall) {
+ if (hasConstructorCall) {
+ annotation.addWarning("Calls to sibling / super constructors are always excluded from @SneakyThrows; @SneakyThrows has been ignored because there is no other code in this constructor.");
+ } else {
+ annotation.addWarning("This method or constructor is empty; @SneakyThrows has been ignored.");
+ }
}
-
+
private JCStatement buildTryCatchBlock(JavacNode node, List<JCStatement> contents, String exception, JCTree source) {
TreeMaker maker = node.getTreeMaker();
diff --git a/src/core/lombok/javac/handlers/HandleToString.java b/src/core/lombok/javac/handlers/HandleToString.java
index c37fe149..8297a4db 100644
--- a/src/core/lombok/javac/handlers/HandleToString.java
+++ b/src/core/lombok/javac/handlers/HandleToString.java
@@ -24,6 +24,8 @@ package lombok.javac.handlers;
import static lombok.javac.handlers.JavacHandlerUtil.*;
import static lombok.javac.Javac.*;
+import java.util.Collection;
+
import lombok.ToString;
import lombok.core.AnnotationValues;
import lombok.core.AST.Kind;
@@ -165,7 +167,7 @@ public class HandleToString extends JavacAnnotationHandler<ToString> {
}
}
- private JCMethodDecl createToString(JavacNode typeNode, List<JavacNode> fields, boolean includeFieldNames, boolean callSuper, FieldAccess fieldAccess, JCTree source) {
+ static JCMethodDecl createToString(JavacNode typeNode, Collection<JavacNode> fields, boolean includeFieldNames, boolean callSuper, FieldAccess fieldAccess, JCTree source) {
TreeMaker maker = typeNode.getTreeMaker();
JCAnnotation overrideAnnotation = maker.Annotation(chainDots(typeNode, "java", "lang", "Override"), List.<JCExpression>nil());
@@ -199,18 +201,22 @@ public class HandleToString extends JavacAnnotationHandler<ToString> {
}
for (JavacNode fieldNode : fields) {
- JCVariableDecl field = (JCVariableDecl) fieldNode.get();
JCExpression expr;
JCExpression fieldAccessor = createFieldAccessor(maker, fieldNode, fieldAccess);
- if (getFieldType(fieldNode, fieldAccess) instanceof JCArrayTypeTree) {
- boolean multiDim = ((JCArrayTypeTree)field.vartype).elemtype instanceof JCArrayTypeTree;
- boolean primitiveArray = ((JCArrayTypeTree)field.vartype).elemtype instanceof JCPrimitiveTypeTree;
- boolean useDeepTS = multiDim || !primitiveArray;
-
- JCExpression hcMethod = chainDots(typeNode, "java", "util", "Arrays", useDeepTS ? "deepToString" : "toString");
- expr = maker.Apply(List.<JCExpression>nil(), hcMethod, List.<JCExpression>of(fieldAccessor));
+ JCExpression fieldType = getFieldType(fieldNode, fieldAccess);
+
+ // The distinction between primitive and object will be useful if we ever add a 'hideNulls' option.
+ boolean fieldIsPrimitive = fieldType instanceof JCPrimitiveTypeTree;
+ boolean fieldIsPrimitiveArray = fieldType instanceof JCArrayTypeTree && ((JCArrayTypeTree) fieldType).elemtype instanceof JCPrimitiveTypeTree;
+ boolean fieldIsObjectArray = !fieldIsPrimitiveArray && fieldType instanceof JCArrayTypeTree;
+ @SuppressWarnings("unused")
+ boolean fieldIsObject = !fieldIsPrimitive && !fieldIsPrimitiveArray && !fieldIsObjectArray;
+
+ if (fieldIsPrimitiveArray || fieldIsObjectArray) {
+ JCExpression tsMethod = chainDots(typeNode, "java", "util", "Arrays", fieldIsObjectArray ? "deepToString" : "toString");
+ expr = maker.Apply(List.<JCExpression>nil(), tsMethod, List.<JCExpression>of(fieldAccessor));
} else expr = fieldAccessor;
if (first) {
@@ -238,7 +244,7 @@ public class HandleToString extends JavacAnnotationHandler<ToString> {
List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null), source);
}
- private String getTypeName(JavacNode typeNode) {
+ private static String getTypeName(JavacNode typeNode) {
String typeName = ((JCClassDecl) typeNode.get()).name.toString();
JavacNode upType = typeNode.up();
while (upType.getKind() == Kind.TYPE) {
diff --git a/src/core/lombok/javac/handlers/HandleValue.java b/src/core/lombok/javac/handlers/HandleValue.java
index f5b10bc1..15fb4781 100644
--- a/src/core/lombok/javac/handlers/HandleValue.java
+++ b/src/core/lombok/javac/handlers/HandleValue.java
@@ -22,13 +22,17 @@
package lombok.javac.handlers;
import static lombok.javac.handlers.JavacHandlerUtil.*;
+
+import java.lang.annotation.Annotation;
+
import lombok.AccessLevel;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.experimental.NonFinal;
-import lombok.experimental.Value;
+import lombok.Value;
import lombok.javac.JavacAnnotationHandler;
import lombok.javac.JavacNode;
+import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists;
import org.mangosdk.spi.ProviderFor;
@@ -44,7 +48,9 @@ import com.sun.tools.javac.tree.JCTree.JCModifiers;
@HandlerPriority(-512) //-2^9; to ensure @EqualsAndHashCode and such pick up on this handler making the class final and messing with the fields' access levels, run earlier.
public class HandleValue extends JavacAnnotationHandler<Value> {
@Override public void handle(AnnotationValues<Value> annotation, JCAnnotation ast, JavacNode annotationNode) {
- deleteAnnotationIfNeccessary(annotationNode, Value.class);
+ @SuppressWarnings("deprecation")
+ Class<? extends Annotation> oldExperimentalValue = lombok.experimental.Value.class;
+ deleteAnnotationIfNeccessary(annotationNode, Value.class, oldExperimentalValue);
JavacNode typeNode = annotationNode.up();
boolean notAClass = !isClass(typeNode);
@@ -65,7 +71,7 @@ public class HandleValue extends JavacAnnotationHandler<Value> {
new HandleFieldDefaults().generateFieldDefaultsForType(typeNode, annotationNode, AccessLevel.PRIVATE, true, true);
// TODO move this to the end OR move it to the top in eclipse.
- new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, true, annotationNode);
+ new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, SkipIfConstructorExists.YES, annotationNode);
new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true);
new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode);
new HandleToString().generateToStringForType(typeNode, annotationNode);
diff --git a/src/core/lombok/javac/handlers/HandleWither.java b/src/core/lombok/javac/handlers/HandleWither.java
index 62ed63f1..514e27dd 100644
--- a/src/core/lombok/javac/handlers/HandleWither.java
+++ b/src/core/lombok/javac/handlers/HandleWither.java
@@ -34,6 +34,7 @@ import lombok.experimental.Wither;
import lombok.javac.Javac;
import lombok.javac.JavacAnnotationHandler;
import lombok.javac.JavacNode;
+import lombok.javac.handlers.JavacHandlerUtil.CopyJavadoc;
import lombok.javac.handlers.JavacHandlerUtil.FieldAccess;
import org.mangosdk.spi.ProviderFor;
@@ -265,7 +266,9 @@ public class HandleWither extends JavacAnnotationHandler<Wither> {
if (isFieldDeprecated(field)) {
annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(chainDots(field, "java", "lang", "Deprecated"), List.<JCExpression>nil()));
}
- return recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, returnType,
+ JCMethodDecl decl = recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, returnType,
methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source);
+ copyJavadoc(field, decl, CopyJavadoc.WITHER);
+ return decl;
}
}
diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java
index 178b82a2..211f5d36 100644
--- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java
+++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java
@@ -21,6 +21,7 @@
*/
package lombok.javac.handlers;
+import static lombok.core.TransformationsUtil.INVALID_ON_BUILDERS;
import static lombok.javac.Javac.*;
import java.lang.annotation.Annotation;
@@ -31,6 +32,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.AccessLevel;
@@ -51,9 +53,11 @@ import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree;
import com.sun.tools.javac.tree.JCTree.JCAssign;
+import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
import com.sun.tools.javac.tree.JCTree.JCIdent;
import com.sun.tools.javac.tree.JCTree.JCImport;
@@ -122,6 +126,7 @@ public class JavacHandlerUtil {
}
public static <T extends JCTree> T recursiveSetGeneratedBy(T node, JCTree source) {
+ if (node == null) return null;
setGeneratedBy(node, source);
node.accept(new MarkingScanner(source));
@@ -186,7 +191,7 @@ public class JavacHandlerUtil {
public static boolean typeMatches(Class<?> type, JavacNode node, JCTree typeNode) {
String typeName = typeNode.toString();
- TypeResolver resolver = new TypeResolver(node.getPackageDeclaration(), node.getImportStatements());
+ TypeResolver resolver = new TypeResolver(node.getImportList());
return resolver.typeMatches(node, type.getName(), typeName);
}
@@ -278,7 +283,22 @@ public class JavacHandlerUtil {
* then removes any import statement that imports this exact annotation (not star imports).
* Only does this if the DeleteLombokAnnotations class is in the context.
*/
+ @SuppressWarnings("unchecked")
public static void deleteAnnotationIfNeccessary(JavacNode annotation, Class<? extends Annotation> annotationType) {
+ deleteAnnotationIfNeccessary0(annotation, annotationType);
+ }
+
+ /**
+ * Removes the annotation from javac's AST (it remains in lombok's AST),
+ * then removes any import statement that imports this exact annotation (not star imports).
+ * Only does this if the DeleteLombokAnnotations class is in the context.
+ */
+ @SuppressWarnings("unchecked")
+ public static void deleteAnnotationIfNeccessary(JavacNode annotation, Class<? extends Annotation> annotationType1, Class<? extends Annotation> annotationType2) {
+ deleteAnnotationIfNeccessary0(annotation, annotationType1, annotationType2);
+ }
+
+ private static void deleteAnnotationIfNeccessary0(JavacNode annotation, Class<? extends Annotation>... annotationTypes) {
if (inNetbeansEditor(annotation)) return;
if (!annotation.shouldDeleteLombokAnnotations()) return;
JavacNode parentNode = annotation.directUp();
@@ -306,7 +326,10 @@ public class JavacHandlerUtil {
return;
}
- deleteImportFromCompilationUnit(annotation, annotationType.getName());
+ parentNode.getAst().setChanged();
+ for (Class<?> annotationType : annotationTypes) {
+ deleteImportFromCompilationUnit(annotation, annotationType.getName());
+ }
}
public static void deleteImportFromCompilationUnit(JavacNode node, String name) {
@@ -425,8 +448,12 @@ public class JavacHandlerUtil {
}
}
- private static boolean isBoolean(JavacNode field) {
+ public static boolean isBoolean(JavacNode field) {
JCExpression varType = ((JCVariableDecl) field.get()).vartype;
+ return isBoolean(varType);
+ }
+
+ public static boolean isBoolean(JCExpression varType) {
return varType != null && varType.toString().equals("boolean");
}
@@ -543,6 +570,23 @@ public class JavacHandlerUtil {
return MemberExistsResult.NOT_EXISTS;
}
+ public static boolean isConstructorCall(final JCStatement statement) {
+ if (!(statement instanceof JCExpressionStatement)) return false;
+ JCExpression expr = ((JCExpressionStatement) statement).expr;
+ if (!(expr instanceof JCMethodInvocation)) return false;
+ JCExpression invocation = ((JCMethodInvocation) expr).meth;
+ String name;
+ if (invocation instanceof JCFieldAccess) {
+ name = ((JCFieldAccess) invocation).name.toString();
+ } else if (invocation instanceof JCIdent) {
+ name = ((JCIdent) invocation).name.toString();
+ } else {
+ name = "";
+ }
+
+ return "super".equals(name) || "this".equals(name);
+ }
+
/**
* Turns an {@code AccessLevel} instance into the flag bit used by javac.
*/
@@ -710,11 +754,11 @@ public class JavacHandlerUtil {
*
* Also takes care of updating the JavacAST.
*/
- public static void injectField(JavacNode typeNode, JCVariableDecl field) {
- injectField(typeNode, field, false);
+ public static JavacNode injectField(JavacNode typeNode, JCVariableDecl field) {
+ return injectField(typeNode, field, false);
}
- private static void injectField(JavacNode typeNode, JCVariableDecl field, boolean addSuppressWarnings) {
+ private static JavacNode injectField(JavacNode typeNode, JCVariableDecl field, boolean addSuppressWarnings) {
JCClassDecl type = (JCClassDecl) typeNode.get();
if (addSuppressWarnings) addSuppressWarningsAll(field.mods, typeNode, field.pos, getGeneratedBy(field));
@@ -740,7 +784,7 @@ public class JavacHandlerUtil {
insertAfter.tail = fieldEntry;
}
- typeNode.add(field, Kind.FIELD);
+ return typeNode.add(field, Kind.FIELD);
}
private static boolean isEnumConstant(final JCVariableDecl field) {
@@ -781,6 +825,20 @@ public class JavacHandlerUtil {
typeNode.add(method, Kind.METHOD);
}
+ /**
+ * Adds an inner type (class, interface, enum) to the given type. Cannot inject top-level types.
+ *
+ * @param typeNode parent type to inject new type into
+ * @param type New type (class, interface, etc) to inject.
+ * @return
+ */
+ public static JavacNode injectType(final JavacNode typeNode, final JCClassDecl type) {
+ JCClassDecl typeDecl = (JCClassDecl) typeNode.get();
+ addSuppressWarningsAll(type.mods, typeNode, type.pos, getGeneratedBy(type));
+ typeDecl.defs = typeDecl.defs.append(type);
+ return typeNode.add(type, Kind.TYPE);
+ }
+
private static void addSuppressWarningsAll(JCModifiers mods, JavacNode node, int pos, JCTree source) {
TreeMaker maker = node.getTreeMaker();
JCExpression suppressWarningsType = chainDots(node, "java", "lang", "SuppressWarnings");
@@ -890,7 +948,8 @@ public class JavacHandlerUtil {
JCExpression npe = chainDots(variable, "java", "lang", "NullPointerException");
JCTree exception = maker.NewClass(null, List.<JCExpression>nil(), npe, List.<JCExpression>of(maker.Literal(fieldName.toString())), null);
JCStatement throwStatement = maker.Throw(exception);
- return maker.If(Javac.makeBinary(maker, CTC_EQUAL, maker.Ident(fieldName), Javac.makeLiteral(maker, CTC_BOT, null)), throwStatement, null);
+ JCBlock throwBlock = maker.Block(0, List.of(throwStatement));
+ return maker.If(Javac.makeBinary(maker, CTC_EQUAL, maker.Ident(fieldName), Javac.makeLiteral(maker, CTC_BOT, null)), throwBlock, null);
}
/**
@@ -991,6 +1050,49 @@ public class JavacHandlerUtil {
return result.toList();
}
+ public static List<JCTypeParameter> copyTypeParams(TreeMaker maker, List<JCTypeParameter> params) {
+ if (params == null || params.isEmpty()) return params;
+ ListBuffer<JCTypeParameter> out = ListBuffer.lb();
+ for (JCTypeParameter tp : params) out.append(maker.TypeParameter(tp.name, tp.bounds));
+ return out.toList();
+ }
+
+ public static JCExpression namePlusTypeParamsToTypeReference(TreeMaker maker, Name typeName, List<JCTypeParameter> params) {
+ ListBuffer<JCExpression> typeArgs = ListBuffer.lb();
+
+ if (!params.isEmpty()) {
+ for (JCTypeParameter param : params) {
+ typeArgs.append(maker.Ident(param.name));
+ }
+
+ return maker.TypeApply(maker.Ident(typeName), typeArgs.toList());
+ }
+
+ return maker.Ident(typeName);
+ }
+
+ public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(JavacNode typeNode, JavacNode errorNode) {
+ List<String> disallowed = List.nil();
+ for (JavacNode child : typeNode.down()) {
+ for (Class<? extends java.lang.annotation.Annotation> annType : INVALID_ON_BUILDERS) {
+ if (annotationTypeMatches(annType, child)) {
+ disallowed = disallowed.append(annType.getSimpleName());
+ }
+ }
+ }
+
+ int size = disallowed.size();
+ if (size == 0) return;
+ if (size == 1) {
+ errorNode.addError("@" + disallowed.head + " is not allowed on builder classes.");
+ return;
+ }
+ StringBuilder out = new StringBuilder();
+ for (String a : disallowed) out.append("@").append(a).append(", ");
+ out.setLength(out.length() - 2);
+ errorNode.addError(out.append(" are not allowed on builder classes.").toString());
+ }
+
static List<JCAnnotation> copyAnnotations(List<? extends JCExpression> in) {
ListBuffer<JCAnnotation> out = ListBuffer.lb();
for (JCExpression expr : in) {
@@ -1090,4 +1192,103 @@ public class JavacHandlerUtil {
// This is somewhat unsafe, but it's better than outright throwing an exception here. Returning null will just cause an exception down the pipeline.
return (JCExpression) in;
}
+
+ private static final Pattern SECTION_FINDER = Pattern.compile("^\\s*\\**\\s*[-*][-*]+\\s*([GS]ETTER|WITHER)\\s*[-*][-*]+\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
+
+ private static String stripLinesWithTagFromJavadoc(String javadoc, String regexpFragment) {
+ Pattern p = Pattern.compile("^\\s*\\**\\s*" + regexpFragment + "\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
+ Matcher m = p.matcher(javadoc);
+ return m.replaceAll("");
+ }
+
+ private static String[] splitJavadocOnSectionIfPresent(String javadoc, String sectionName) {
+ Matcher m = SECTION_FINDER.matcher(javadoc);
+ int getterSectionHeaderStart = -1;
+ int getterSectionStart = -1;
+ int getterSectionEnd = -1;
+ while (m.find()) {
+ if (m.group(1).equalsIgnoreCase(sectionName)) {
+ getterSectionStart = m.end() + 1;
+ getterSectionHeaderStart = m.start();
+ } else if (getterSectionStart != -1) {
+ getterSectionEnd = m.start();
+ }
+ }
+
+ if (getterSectionStart != -1) {
+ if (getterSectionEnd != -1) {
+ return new String[] {javadoc.substring(getterSectionStart, getterSectionEnd), javadoc.substring(0, getterSectionHeaderStart) + javadoc.substring(getterSectionEnd)};
+ } else {
+ return new String[] {javadoc.substring(getterSectionStart), javadoc.substring(0, getterSectionHeaderStart)};
+ }
+ }
+
+ return null;
+ }
+
+ public static enum CopyJavadoc {
+ VERBATIM, GETTER {
+ @Override public String[] split(String javadoc) {
+ // step 1: Check if there is a 'GETTER' section. If yes, that becomes the new method's javadoc and we strip that from the original.
+ String[] out = splitJavadocOnSectionIfPresent(javadoc, "GETTER");
+ if (out != null) return out;
+ // failing that, create a copy, but strip @return from the original and @param from the copy.
+ String copy = javadoc;
+ javadoc = stripLinesWithTagFromJavadoc(javadoc, "@returns?\\s+.*");
+ copy = stripLinesWithTagFromJavadoc(copy, "@param(?:eter)?\\s+.*");
+ return new String[] {copy, javadoc};
+ }
+ },
+ SETTER {
+ @Override public String[] split(String javadoc) {
+ return splitForSetters(javadoc, "SETTER");
+ }
+ },
+ WITHER {
+ @Override public String[] split(String javadoc) {
+ return splitForSetters(javadoc, "WITHER");
+ }
+ };
+
+ private static String[] splitForSetters(String javadoc, String sectionName) {
+ // step 1: Check if there is a 'SETTER' section. If yes, that becomes the new one and we strip that from the original.
+ String[] out = splitJavadocOnSectionIfPresent(javadoc, sectionName);
+ if (out != null) return out;
+ // failing that, create a copy, but strip @param from the original and @return from the copy.
+ String copy = javadoc;
+ javadoc = stripLinesWithTagFromJavadoc(javadoc, "@param(?:eter)?\\s+.*");
+ copy = stripLinesWithTagFromJavadoc(copy, "@returns?\\s+.*");
+ return new String[] {copy, javadoc};
+ }
+
+ /** Splits the javadoc into the section to be copied (ret[0]) and the section to replace the original with (ret[1]) */
+ public String[] split(String javadoc) {
+ return new String[] {javadoc, javadoc};
+ }
+ }
+
+ /**
+ * Copies javadoc on one node to the other.
+ *
+ * in 'GETTER' copyMode, first a 'GETTER' segment is searched for. If it exists, that will become the javadoc for the 'to' node, and this section is
+ * stripped out of the 'from' node. If no 'GETTER' segment is found, then the entire javadoc is taken minus any {@code @param} lines. any {@code @return} lines
+ * are stripped from 'from'.
+ *
+ * in 'SETTER' mode, stripping works similarly to 'GETTER' mode, except {@code param} are copied and stripped from the original and {@code @return} are skipped.
+ */
+ public static void copyJavadoc(JavacNode from, JCTree to, CopyJavadoc copyMode) {
+ if (copyMode == null) copyMode = CopyJavadoc.VERBATIM;
+ try {
+ JCCompilationUnit cu = ((JCCompilationUnit) from.top().get());
+ if (cu.docComments != null) {
+ String javadoc = cu.docComments.get(from.get());
+
+ if (javadoc != null) {
+ String[] filtered = copyMode.split(javadoc);
+ cu.docComments.put(to, filtered[0]);
+ cu.docComments.put(from.get(), filtered[1]);
+ }
+ }
+ } catch (Exception ignore) {}
+ }
}
diff --git a/src/core/lombok/javac/handlers/NonNullHandler.java b/src/core/lombok/javac/handlers/NonNullHandler.java
new file mode 100644
index 00000000..d74fb55d
--- /dev/null
+++ b/src/core/lombok/javac/handlers/NonNullHandler.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2013 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.javac.handlers;
+
+import static lombok.javac.Javac.*;
+import static lombok.javac.handlers.JavacHandlerUtil.*;
+
+import org.mangosdk.spi.ProviderFor;
+
+import com.sun.tools.javac.tree.JCTree.JCAnnotation;
+import com.sun.tools.javac.tree.JCTree.JCBinary;
+import com.sun.tools.javac.tree.JCTree.JCBlock;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCIdent;
+import com.sun.tools.javac.tree.JCTree.JCIf;
+import com.sun.tools.javac.tree.JCTree.JCLiteral;
+import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
+import com.sun.tools.javac.tree.JCTree.JCParens;
+import com.sun.tools.javac.tree.JCTree.JCStatement;
+import com.sun.tools.javac.tree.JCTree.JCThrow;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.util.List;
+
+import lombok.NonNull;
+import lombok.core.AnnotationValues;
+import lombok.core.AST.Kind;
+import lombok.javac.JavacAnnotationHandler;
+import lombok.javac.JavacNode;
+
+@ProviderFor(JavacAnnotationHandler.class)
+public class NonNullHandler extends JavacAnnotationHandler<NonNull> {
+ @Override public void handle(AnnotationValues<NonNull> annotation, JCAnnotation ast, JavacNode annotationNode) {
+ if (annotationNode.up().getKind() == Kind.FIELD) {
+ // This is meaningless unless the field is used to generate a method (@Setter, @RequiredArgsConstructor, etc),
+ // but in that case those handlers will take care of it. However, we DO check if the annotation is applied to
+ // a primitive, because those handlers trigger on any annotation named @NonNull and we only want the warning
+ // behaviour on _OUR_ 'lombok.NonNull'.
+
+ try {
+ if (isPrimitive(((JCVariableDecl) annotationNode.up().get()).vartype)) {
+ annotationNode.addWarning("@NonNull is meaningless on a primitive.");
+ }
+ } catch (Exception ignore) {}
+
+ return;
+ }
+
+ if (annotationNode.up().getKind() != Kind.ARGUMENT) return;
+
+ JCMethodDecl declaration;
+
+ try {
+ declaration = (JCMethodDecl) annotationNode.up().up().get();
+ } catch (Exception e) {
+ return;
+ }
+
+ if (JavacHandlerUtil.isGenerated(declaration)) return;
+
+ // Possibly, if 'declaration instanceof ConstructorDeclaration', fetch declaration.constructorCall, search it for any references to our parameter,
+ // and if they exist, create a new method in the class: 'private static <T> T lombok$nullCheck(T expr, String msg) {if (expr == null) throw NPE; return expr;}' and
+ // wrap all references to it in the super/this to a call to this method.
+
+ JCStatement nullCheck = recursiveSetGeneratedBy(generateNullCheck(annotationNode.getTreeMaker(), annotationNode.up()), ast);
+
+ if (nullCheck == null) {
+ // @NonNull applied to a primitive. Kinda pointless. Let's generate a warning.
+ annotationNode.addWarning("@NonNull is meaningless on a primitive.");
+ return;
+ }
+
+ List<JCStatement> statements = declaration.body.stats;
+
+ String expectedName = annotationNode.up().getName();
+ for (JCStatement stat : statements) {
+ if (JavacHandlerUtil.isConstructorCall(stat)) continue;
+ String varNameOfNullCheck = returnVarNameIfNullCheck(stat);
+ if (varNameOfNullCheck == null) break;
+ if (varNameOfNullCheck.equals(expectedName)) return;
+ }
+
+ List<JCStatement> tail = statements;
+ List<JCStatement> head = List.nil();
+ for (JCStatement stat : statements) {
+ if (JavacHandlerUtil.isConstructorCall(stat) || JavacHandlerUtil.isGenerated(stat)) {
+ tail = tail.tail;
+ head = head.prepend(stat);
+ continue;
+ }
+ break;
+ }
+
+ List<JCStatement> newList = tail.prepend(nullCheck);
+ for (JCStatement stat : head) newList = newList.prepend(stat);
+ declaration.body.stats = newList;
+ }
+
+ /**
+ * Checks if the statement is of the form 'if (x == null) {throw WHATEVER;},
+ * where the block braces are optional. If it is of this form, returns "x".
+ * If it is not of this form, returns null.
+ */
+ private String returnVarNameIfNullCheck(JCStatement stat) {
+ if (!(stat instanceof JCIf)) return null;
+
+ /* Check that the if's statement is a throw statement, possibly in a block. */ {
+ JCStatement then = ((JCIf) stat).thenpart;
+ if (then instanceof JCBlock) {
+ List<JCStatement> stats = ((JCBlock) then).stats;
+ if (stats.length() == 0) return null;
+ then = stats.get(0);
+ }
+ if (!(then instanceof JCThrow)) return null;
+ }
+
+ /* Check that the if's conditional is like 'x == null'. Return from this method (don't generate
+ a nullcheck) if 'x' is equal to our own variable's name: There's already a nullcheck here. */ {
+ JCExpression cond = ((JCIf) stat).cond;
+ while (cond instanceof JCParens) cond = ((JCParens) cond).expr;
+ if (!(cond instanceof JCBinary)) return null;
+ JCBinary bin = (JCBinary) cond;
+ if (compareCTC(getTag(bin), CTC_EQUAL)) return null;
+ if (!(bin.lhs instanceof JCIdent)) return null;
+ if (!(bin.rhs instanceof JCLiteral)) return null;
+ if (compareCTC(getTypeTag((JCLiteral) bin.rhs), CTC_BOT)) return null;
+ return ((JCIdent) bin.lhs).name.toString();
+ }
+ }
+}