diff options
author | Reinier Zwitserloot <reinier@zwitserloot.com> | 2013-07-22 23:23:46 +0200 |
---|---|---|
committer | Reinier Zwitserloot <reinier@zwitserloot.com> | 2013-07-22 23:23:46 +0200 |
commit | 45697b50816df79475a8bb69dc89ff68747fbfe6 (patch) | |
tree | 25cb023eec1f74baf5063cc5a58a5351ee43d6f0 /src/core/lombok/javac | |
parent | 4c03e3d220900431085897878d4888bf530b31ec (diff) | |
parent | deed98be16e5099af52d951fc611f86a82a42858 (diff) | |
download | lombok-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')
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(); + } + } +} |