aboutsummaryrefslogtreecommitdiff
path: root/src/core/lombok/javac
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/lombok/javac
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/lombok/javac')
-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
24 files changed, 1069 insertions, 156 deletions
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();
+ }
+ }
+}