diff options
-rw-r--r-- | doc/changelog.markdown | 1 | ||||
-rw-r--r-- | src/core/lombok/NoArgsConstructor.java | 8 | ||||
-rw-r--r-- | src/core/lombok/eclipse/handlers/HandleBuilder.java | 8 | ||||
-rw-r--r-- | src/core/lombok/eclipse/handlers/HandleConstructor.java | 134 | ||||
-rw-r--r-- | src/core/lombok/javac/handlers/HandleBuilder.java | 4 | ||||
-rw-r--r-- | src/core/lombok/javac/handlers/HandleConstructor.java | 91 | ||||
-rw-r--r-- | test/transform/resource/after-delombok/Constructors.java | 19 | ||||
-rw-r--r-- | test/transform/resource/after-ecj/Constructors.java | 18 | ||||
-rw-r--r-- | test/transform/resource/before/Constructors.java | 11 | ||||
-rw-r--r-- | website/features/Constructor.html | 3 |
10 files changed, 211 insertions, 86 deletions
diff --git a/doc/changelog.markdown b/doc/changelog.markdown index ae8b07ab..d1fcb777 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,6 +2,7 @@ Lombok Changelog ---------------- ### v1.16.5 "Edgy Guinea Pig" +* FEATURE: `@NoArgsConstructor(force = true)` can be used to create no args constructors even if final fields are present. * BUGFIX: Parameterized static methods with `@Builder` would produce compiler errors in javac. [Issue #828](https://github.com/rzwitserloot/lombok/issues/828). * PERFORMANCE: the config system caused significant slowdowns in eclipse if the filesystem is very slow (network file system) or has a slow authentication system. * FEATURE: the `hashCode()` method generated by lombok via `@EqualsAndHashCode`, `@Data`, and `@Value` is now smarter about nulls; they are treated as if they hash to a magic prime instead of 0, which reduces hash collisions. diff --git a/src/core/lombok/NoArgsConstructor.java b/src/core/lombok/NoArgsConstructor.java index ef347273..ff437bba 100644 --- a/src/core/lombok/NoArgsConstructor.java +++ b/src/core/lombok/NoArgsConstructor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2013 The Project Lombok Authors. + * Copyright (C) 2010-2015 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -61,6 +61,12 @@ public @interface NoArgsConstructor { AccessLevel access() default lombok.AccessLevel.PUBLIC; /** + * If {@code true}, initializes all final fields to 0 / null / false. + * Otherwise, a compile time error occurs. + */ + boolean force() default false; + + /** * Placeholder annotation to enable the placement of annotations on the generated code. * @deprecated Don't use this annotation, ever - Read the documentation. */ diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index d011e2b5..5acd9249 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -160,8 +160,8 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { allFields.add(fieldNode); } - new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, allFields, null, SkipIfConstructorExists.I_AM_BUILDER, null, - Collections.<Annotation>emptyList(), annotationNode); + new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, allFields, false, null, SkipIfConstructorExists.I_AM_BUILDER, null, + Collections.<Annotation>emptyList(), annotationNode); returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); typeParams = td.typeParameters; @@ -278,8 +278,8 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { ConstructorDeclaration cd = HandleConstructor.createConstructor( - AccessLevel.PACKAGE, builderType, Collections.<EclipseNode>emptyList(), null, - annotationNode, Collections.<Annotation>emptyList()); + AccessLevel.PACKAGE, builderType, Collections.<EclipseNode>emptyList(), false, null, + annotationNode, Collections.<Annotation>emptyList()); if (cd != null) injectMethod(builderType, cd); } diff --git a/src/core/lombok/eclipse/handlers/HandleConstructor.java b/src/core/lombok/eclipse/handlers/HandleConstructor.java index 5bcc803a..85d1d4ed 100644 --- a/src/core/lombok/eclipse/handlers/HandleConstructor.java +++ b/src/core/lombok/eclipse/handlers/HandleConstructor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014 The Project Lombok Authors. + * Copyright (C) 2010-2015 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -29,6 +29,7 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import lombok.AccessLevel; @@ -48,13 +49,20 @@ import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; import org.eclipse.jdt.internal.compiler.ast.Assignment; +import org.eclipse.jdt.internal.compiler.ast.CharLiteral; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.DoubleLiteral; import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall; import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FalseLiteral; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.FloatLiteral; +import org.eclipse.jdt.internal.compiler.ast.IntLiteral; +import org.eclipse.jdt.internal.compiler.ast.LongLiteral; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation; @@ -63,7 +71,9 @@ import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.StringLiteral; import org.eclipse.jdt.internal.compiler.ast.ThisReference; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.mangosdk.spi.ProviderFor; public class HandleConstructor { @@ -78,11 +88,13 @@ public class HandleConstructor { AccessLevel level = ann.access(); String staticName = ann.staticName(); if (level == AccessLevel.NONE) return; - List<EclipseNode> fields = new ArrayList<EclipseNode>(); + boolean force = ann.force(); + + List<EclipseNode> fields = force ? findFinalFields(typeNode) : Collections.<EclipseNode>emptyList(); List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor=", annotationNode); - new HandleConstructor().generateConstructor(typeNode, level, fields, staticName, SkipIfConstructorExists.NO, null, onConstructor, annotationNode); + new HandleConstructor().generateConstructor(typeNode, level, fields, force, staticName, SkipIfConstructorExists.NO, null, onConstructor, annotationNode); } } @@ -107,19 +119,27 @@ public class HandleConstructor { List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@RequiredArgsConstructor(onConstructor=", annotationNode); new HandleConstructor().generateConstructor( - typeNode, level, findRequiredFields(typeNode), staticName, SkipIfConstructorExists.NO, - suppressConstructorProperties, onConstructor, annotationNode); + typeNode, level, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO, + suppressConstructorProperties, onConstructor, annotationNode); } } private static List<EclipseNode> findRequiredFields(EclipseNode typeNode) { + return findFields(typeNode, true); + } + + private static List<EclipseNode> findFinalFields(EclipseNode typeNode) { + return findFields(typeNode, false); + } + + private static List<EclipseNode> findFields(EclipseNode typeNode, boolean nullMarked) { List<EclipseNode> fields = new ArrayList<EclipseNode>(); for (EclipseNode child : typeNode.down()) { if (child.getKind() != Kind.FIELD) continue; FieldDeclaration fieldDecl = (FieldDeclaration) child.get(); if (!filterField(fieldDecl)) continue; boolean isFinal = (fieldDecl.modifiers & ClassFileConstants.AccFinal) != 0; - boolean isNonNull = findAnnotations(fieldDecl, NON_NULL_PATTERN).length != 0; + boolean isNonNull = nullMarked && findAnnotations(fieldDecl, NON_NULL_PATTERN).length != 0; if ((isFinal || isNonNull) && fieldDecl.initialization == null) fields.add(child); } return fields; @@ -161,8 +181,8 @@ public class HandleConstructor { List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor=", annotationNode); new HandleConstructor().generateConstructor( - typeNode, level, findAllFields(typeNode), staticName, SkipIfConstructorExists.NO, - suppressConstructorProperties, onConstructor, annotationNode); + typeNode, level, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, + suppressConstructorProperties, onConstructor, annotationNode); } } @@ -184,14 +204,14 @@ public class HandleConstructor { EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List<Annotation> onConstructor, EclipseNode sourceNode) { - generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, skipIfConstructorExists, null, onConstructor, sourceNode); + generateConstructor(typeNode, level, findRequiredFields(typeNode), false, staticName, skipIfConstructorExists, null, onConstructor, sourceNode); } public void generateAllArgsConstructor( EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List<Annotation> onConstructor, EclipseNode sourceNode) { - generateConstructor(typeNode, level, findAllFields(typeNode), staticName, skipIfConstructorExists, null, onConstructor, sourceNode); + generateConstructor(typeNode, level, findAllFields(typeNode), false, staticName, skipIfConstructorExists, null, onConstructor, sourceNode); } public enum SkipIfConstructorExists { @@ -199,8 +219,8 @@ public class HandleConstructor { } public void generateConstructor( - EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, String staticName, SkipIfConstructorExists skipIfConstructorExists, - Boolean suppressConstructorProperties, List<Annotation> onConstructor, EclipseNode sourceNode) { + EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, boolean allToDefault, String staticName, SkipIfConstructorExists skipIfConstructorExists, + Boolean suppressConstructorProperties, List<Annotation> onConstructor, EclipseNode sourceNode) { ASTNode source = sourceNode.get(); boolean staticConstrRequired = staticName != null && !staticName.equals(""); @@ -210,8 +230,8 @@ public class HandleConstructor { for (EclipseNode child : typeNode.down()) { if (child.getKind() == Kind.ANNOTATION) { boolean skipGeneration = (annotationTypeMatches(NoArgsConstructor.class, child) || - annotationTypeMatches(AllArgsConstructor.class, child) || - annotationTypeMatches(RequiredArgsConstructor.class, child)); + annotationTypeMatches(AllArgsConstructor.class, child) || + annotationTypeMatches(RequiredArgsConstructor.class, child)); if (!skipGeneration && skipIfConstructorExists == SkipIfConstructorExists.YES) { skipGeneration = annotationTypeMatches(Builder.class, child); @@ -224,8 +244,8 @@ public class HandleConstructor { // the 'staticName' parameter of the @XArgsConstructor you've stuck on your type. // We should warn that we're ignoring @Data's 'staticConstructor' param. typeNode.addWarning( - "Ignoring static constructor name: explicit @XxxArgsConstructor annotation present; its `staticName` parameter will be used.", - source.sourceStart, source.sourceEnd); + "Ignoring static constructor name: explicit @XxxArgsConstructor annotation present; its `staticName` parameter will be used.", + source.sourceStart, source.sourceEnd); } return; } @@ -234,11 +254,11 @@ public class HandleConstructor { } ConstructorDeclaration constr = createConstructor( - staticConstrRequired ? AccessLevel.PRIVATE : level, typeNode, fields, - suppressConstructorProperties, sourceNode, onConstructor); + staticConstrRequired ? AccessLevel.PRIVATE : level, typeNode, fields, allToDefault, + suppressConstructorProperties, sourceNode, onConstructor); injectMethod(typeNode, constr); if (staticConstrRequired) { - MethodDeclaration staticConstr = createStaticConstructor(level, staticName, typeNode, fields, source); + MethodDeclaration staticConstr = createStaticConstructor(level, staticName, typeNode, allToDefault ? Collections.<EclipseNode>emptyList() : fields, source); injectMethod(typeNode, staticConstr); } } @@ -248,7 +268,7 @@ public class HandleConstructor { if (fields.isEmpty()) return null; int pS = source.sourceStart, pE = source.sourceEnd; - long p = (long)pS << 32 | pE; + long p = (long) pS << 32 | pE; long[] poss = new long[3]; Arrays.fill(poss, p); QualifiedTypeReference constructorPropertiesType = new QualifiedTypeReference(JAVA_BEANS_CONSTRUCTORPROPERTIES, poss); @@ -276,14 +296,14 @@ public class HandleConstructor { } public static ConstructorDeclaration createConstructor( - AccessLevel level, EclipseNode type, Collection<EclipseNode> fields, - Boolean suppressConstructorProperties, EclipseNode sourceNode, List<Annotation> onConstructor) { + AccessLevel level, EclipseNode type, Collection<EclipseNode> fields, boolean allToDefault, + Boolean suppressConstructorProperties, EclipseNode sourceNode, List<Annotation> onConstructor) { ASTNode source = sourceNode.get(); - TypeDeclaration typeDeclaration = ((TypeDeclaration)type.get()); - long p = (long)source.sourceStart << 32 | source.sourceEnd; + TypeDeclaration typeDeclaration = ((TypeDeclaration) type.get()); + long p = (long) source.sourceStart << 32 | source.sourceEnd; - boolean isEnum = (((TypeDeclaration)type.get()).modifiers & ClassFileConstants.AccEnum) != 0; + boolean isEnum = (((TypeDeclaration) type.get()).modifiers & ClassFileConstants.AccEnum) != 0; if (isEnum) level = AccessLevel.PRIVATE; @@ -295,8 +315,7 @@ public class HandleConstructor { } } - ConstructorDeclaration constructor = new ConstructorDeclaration( - ((CompilationUnitDeclaration) type.top().get()).compilationResult); + ConstructorDeclaration constructor = new ConstructorDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); constructor.modifiers = toEclipseModifier(level); constructor.selector = typeDeclaration.name; @@ -319,22 +338,27 @@ public class HandleConstructor { char[] rawName = field.name; char[] fieldName = removePrefixFromField(fieldNode); FieldReference thisX = new FieldReference(rawName, p); - thisX.receiver = new ThisReference((int)(p >> 32), (int)p); + int s = (int) (p >> 32); + int e = (int) p; + thisX.receiver = new ThisReference(s, e); + + Expression assignmentExpr = allToDefault ? getDefaultExpr(field.type, s, e) : new SingleNameReference(fieldName, p); - SingleNameReference assignmentNameRef = new SingleNameReference(fieldName, p); - Assignment assignment = new Assignment(thisX, assignmentNameRef, (int)p); - assignment.sourceStart = (int)(p >> 32); assignment.sourceEnd = assignment.statementEnd = (int)(p >> 32); + Assignment assignment = new Assignment(thisX, assignmentExpr, (int) p); + assignment.sourceStart = (int) (p >> 32); assignment.sourceEnd = assignment.statementEnd = (int) (p >> 32); assigns.add(assignment); - long fieldPos = (((long)field.sourceStart) << 32) | field.sourceEnd; - Argument parameter = new Argument(fieldName, fieldPos, copyType(field.type, source), Modifier.FINAL); - Annotation[] nonNulls = findAnnotations(field, NON_NULL_PATTERN); - Annotation[] nullables = findAnnotations(field, NULLABLE_PATTERN); - if (nonNulls.length != 0) { - Statement nullCheck = generateNullCheck(field, sourceNode); - if (nullCheck != null) nullChecks.add(nullCheck); + if (!allToDefault) { + long fieldPos = (((long) field.sourceStart) << 32) | field.sourceEnd; + Argument parameter = new Argument(fieldName, fieldPos, copyType(field.type, source), Modifier.FINAL); + Annotation[] nonNulls = findAnnotations(field, NON_NULL_PATTERN); + Annotation[] nullables = findAnnotations(field, NULLABLE_PATTERN); + if (nonNulls.length != 0) { + Statement nullCheck = generateNullCheck(field, sourceNode); + if (nullCheck != null) nullChecks.add(nullCheck); + } + parameter.annotations = copyAnnotations(source, nonNulls, nullables); + params.add(parameter); } - parameter.annotations = copyAnnotations(source, nonNulls, nullables); - params.add(parameter); } nullChecks.addAll(assigns); @@ -343,19 +367,32 @@ public class HandleConstructor { /* Generate annotations that must be put on the generated method, and attach them. */ { Annotation[] constructorProperties = null; - if (!suppressConstructorProperties && level != AccessLevel.PRIVATE && level != AccessLevel.PACKAGE && !isLocalType(type)) { + if (!allToDefault && !suppressConstructorProperties && level != AccessLevel.PRIVATE && level != AccessLevel.PACKAGE && !isLocalType(type)) { constructorProperties = createConstructorProperties(source, fields); } constructor.annotations = copyAnnotations(source, - onConstructor.toArray(new Annotation[0]), - constructorProperties); + onConstructor.toArray(new Annotation[0]), + constructorProperties); } constructor.traverse(new SetGeneratedByVisitor(source), typeDeclaration.scope); return constructor; } + private static Expression getDefaultExpr(TypeReference type, int s, int e) { + char[] lastToken = type.getLastToken(); + if (Arrays.equals(TypeConstants.BOOLEAN, lastToken)) return new FalseLiteral(s, e); + if (Arrays.equals(TypeConstants.CHAR, lastToken)) return new CharLiteral(new char[] {'\'', '\\', '0', '\''}, s, e); + if (Arrays.equals(TypeConstants.BYTE, lastToken) || Arrays.equals(TypeConstants.SHORT, lastToken) || + Arrays.equals(TypeConstants.INT, lastToken)) return IntLiteral.buildIntLiteral(new char[] {'0'}, s, e); + if (Arrays.equals(TypeConstants.LONG, lastToken)) return LongLiteral.buildLongLiteral(new char[] {'0', 'L'}, s, e); + if (Arrays.equals(TypeConstants.FLOAT, lastToken)) return new FloatLiteral(new char[] {'0', 'F'}, s, e); + if (Arrays.equals(TypeConstants.DOUBLE, lastToken)) return new DoubleLiteral(new char[] {'0', 'D'}, s, e); + + return new NullLiteral(s, e); + } + public static boolean isLocalType(EclipseNode type) { Kind kind = type.up().getKind(); if (kind == Kind.COMPILATION_UNIT) return false; @@ -365,10 +402,9 @@ public class HandleConstructor { public MethodDeclaration createStaticConstructor(AccessLevel level, String name, EclipseNode type, Collection<EclipseNode> fields, ASTNode source) { int pS = source.sourceStart, pE = source.sourceEnd; - long p = (long)pS << 32 | pE; + long p = (long) pS << 32 | pE; - MethodDeclaration constructor = new MethodDeclaration( - ((CompilationUnitDeclaration) type.top().get()).compilationResult); + MethodDeclaration constructor = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); constructor.modifiers = toEclipseModifier(level) | ClassFileConstants.AccStatic; TypeDeclaration typeDecl = (TypeDeclaration) type.get(); @@ -376,7 +412,7 @@ public class HandleConstructor { constructor.annotations = null; constructor.selector = name.toCharArray(); constructor.thrownExceptions = null; - constructor.typeParameters = copyTypeParams(((TypeDeclaration)type.get()).typeParameters, source); + constructor.typeParameters = copyTypeParams(((TypeDeclaration) type.get()).typeParameters, source); constructor.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; constructor.bodyStart = constructor.declarationSourceStart = constructor.sourceStart = source.sourceStart; constructor.bodyEnd = constructor.declarationSourceEnd = constructor.sourceEnd = source.sourceEnd; @@ -389,7 +425,7 @@ public class HandleConstructor { for (EclipseNode fieldNode : fields) { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); - long fieldPos = (((long)field.sourceStart) << 32) | field.sourceEnd; + long fieldPos = (((long) field.sourceStart) << 32) | field.sourceEnd; SingleNameReference nameRef = new SingleNameReference(field.name, fieldPos); assigns.add(nameRef); @@ -400,7 +436,7 @@ public class HandleConstructor { statement.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]); constructor.arguments = params.isEmpty() ? null : params.toArray(new Argument[params.size()]); - constructor.statements = new Statement[] { new ReturnStatement(statement, (int)(p >> 32), (int)p) }; + constructor.statements = new Statement[] { new ReturnStatement(statement, (int) (p >> 32), (int)p) }; constructor.traverse(new SetGeneratedByVisitor(source), typeDecl.scope); return constructor; diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index 7ef7b859..066e371e 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -142,7 +142,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { allFields.append(fieldNode); } - new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, List.<JCAnnotation>nil(), allFields.toList(), null, SkipIfConstructorExists.I_AM_BUILDER, null, annotationNode); + new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, List.<JCAnnotation>nil(), allFields.toList(), false, null, SkipIfConstructorExists.I_AM_BUILDER, null, annotationNode); returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); typeParams = td.typarams; @@ -255,7 +255,7 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { } if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.<JCAnnotation>nil(), builderType, List.<JavacNode>nil(), null, annotationNode); + JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.<JCAnnotation>nil(), builderType, List.<JavacNode>nil(), false, null, annotationNode); if (cd != null) injectMethod(builderType, cd); } diff --git a/src/core/lombok/javac/handlers/HandleConstructor.java b/src/core/lombok/javac/handlers/HandleConstructor.java index c5b309c2..4a4ec49c 100644 --- a/src/core/lombok/javac/handlers/HandleConstructor.java +++ b/src/core/lombok/javac/handlers/HandleConstructor.java @@ -23,6 +23,8 @@ package lombok.javac.handlers; import static lombok.core.handlers.HandlerUtil.*; import static lombok.javac.handlers.JavacHandlerUtil.*; +import static lombok.javac.Javac.*; + import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -41,13 +43,13 @@ 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.JCAssign; 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.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; import com.sun.tools.javac.tree.JCTree.JCReturn; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCTypeParameter; @@ -71,8 +73,9 @@ public class HandleConstructor { AccessLevel level = ann.access(); if (level == AccessLevel.NONE) return; String staticName = ann.staticName(); - List<JavacNode> fields = List.nil(); - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, staticName, SkipIfConstructorExists.NO, null, annotationNode); + boolean force = ann.force(); + List<JavacNode> fields = force ? findFinalFields(typeNode) : List.<JavacNode>nil(); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, force, staticName, SkipIfConstructorExists.NO, null, annotationNode); } } @@ -97,11 +100,19 @@ public class HandleConstructor { suppressConstructorProperties = suppress; } - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode); } } public static List<JavacNode> findRequiredFields(JavacNode typeNode) { + return findFields(typeNode, true); + } + + public static List<JavacNode> findFinalFields(JavacNode typeNode) { + return findFields(typeNode, false); + } + + public static List<JavacNode> findFields(JavacNode typeNode, boolean nullMarked) { ListBuffer<JavacNode> fields = new ListBuffer<JavacNode>(); for (JavacNode child : typeNode.down()) { if (child.getKind() != Kind.FIELD) continue; @@ -112,7 +123,7 @@ public class HandleConstructor { //Skip static fields. if ((fieldFlags & Flags.STATIC) != 0) continue; boolean isFinal = (fieldFlags & Flags.FINAL) != 0; - boolean isNonNull = !findAnnotations(child, NON_NULL_PATTERN).isEmpty(); + boolean isNonNull = nullMarked && !findAnnotations(child, NON_NULL_PATTERN).isEmpty(); if ((isFinal || isNonNull) && fieldDecl.init == null) fields.append(child); } return fields.toList(); @@ -138,7 +149,7 @@ public class HandleConstructor { boolean suppress = ann.suppressConstructorProperties(); suppressConstructorProperties = suppress; } - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode); } } @@ -174,7 +185,7 @@ public class HandleConstructor { } public void generateRequiredArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) { - generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findRequiredFields(typeNode), staticName, skipIfConstructorExists, null, source); + generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findRequiredFields(typeNode), false, staticName, skipIfConstructorExists, null, source); } public enum SkipIfConstructorExists { @@ -182,10 +193,10 @@ public class HandleConstructor { } public void generateAllArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) { - generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findAllFields(typeNode), staticName, skipIfConstructorExists, null, source); + generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findAllFields(typeNode), false, staticName, skipIfConstructorExists, null, source); } - public void generateConstructor(JavacNode typeNode, AccessLevel level, List<JCAnnotation> onConstructor, List<JavacNode> fields, String staticName, SkipIfConstructorExists skipIfConstructorExists, Boolean suppressConstructorProperties, JavacNode source) { + public void generateConstructor(JavacNode typeNode, AccessLevel level, List<JCAnnotation> onConstructor, List<JavacNode> fields, boolean allToDefault, String staticName, SkipIfConstructorExists skipIfConstructorExists, Boolean suppressConstructorProperties, JavacNode source) { boolean staticConstrRequired = staticName != null && !staticName.equals(""); if (skipIfConstructorExists != SkipIfConstructorExists.NO && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; @@ -193,8 +204,8 @@ public class HandleConstructor { for (JavacNode child : typeNode.down()) { if (child.getKind() == Kind.ANNOTATION) { boolean skipGeneration = annotationTypeMatches(NoArgsConstructor.class, child) || - annotationTypeMatches(AllArgsConstructor.class, child) || - annotationTypeMatches(RequiredArgsConstructor.class, child); + annotationTypeMatches(AllArgsConstructor.class, child) || + annotationTypeMatches(RequiredArgsConstructor.class, child); if (!skipGeneration && skipIfConstructorExists == SkipIfConstructorExists.YES) { skipGeneration = annotationTypeMatches(Builder.class, child); @@ -214,10 +225,10 @@ public class HandleConstructor { } } - JCMethodDecl constr = createConstructor(staticConstrRequired ? AccessLevel.PRIVATE : level, onConstructor, typeNode, fields, suppressConstructorProperties, source); + JCMethodDecl constr = createConstructor(staticConstrRequired ? AccessLevel.PRIVATE : level, onConstructor, typeNode, fields, allToDefault, suppressConstructorProperties, source); injectMethod(typeNode, constr); if (staticConstrRequired) { - JCMethodDecl staticConstr = createStaticConstructor(staticName, level, typeNode, fields, source.get()); + JCMethodDecl staticConstr = createStaticConstructor(staticName, level, typeNode, allToDefault ? List.<JavacNode>nil() : fields, source.get()); injectMethod(typeNode, staticConstr); } } @@ -236,7 +247,7 @@ public class HandleConstructor { mods.annotations = mods.annotations.append(annotation); } - public static JCMethodDecl createConstructor(AccessLevel level, List<JCAnnotation> onConstructor, JavacNode typeNode, List<JavacNode> fields, Boolean suppressConstructorProperties, JavacNode source) { + public static JCMethodDecl createConstructor(AccessLevel level, List<JCAnnotation> onConstructor, JavacNode typeNode, List<JavacNode> fields, boolean allToDefault, Boolean suppressConstructorProperties, JavacNode source) { JavacTreeMaker maker = typeNode.getTreeMaker(); boolean isEnum = (((JCClassDecl) typeNode.get()).mods.flags & Flags.ENUM) != 0; @@ -259,29 +270,55 @@ public class HandleConstructor { Name fieldName = removePrefixFromField(fieldNode); Name rawName = field.name; List<JCAnnotation> nonNulls = findAnnotations(fieldNode, NON_NULL_PATTERN); - List<JCAnnotation> nullables = findAnnotations(fieldNode, NULLABLE_PATTERN); - long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, typeNode.getContext()); - JCVariableDecl param = maker.VarDef(maker.Modifiers(flags, nonNulls.appendList(nullables)), fieldName, field.vartype, null); - params.append(param); + if (!allToDefault) { + List<JCAnnotation> nullables = findAnnotations(fieldNode, NULLABLE_PATTERN); + long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, typeNode.getContext()); + JCVariableDecl param = maker.VarDef(maker.Modifiers(flags, nonNulls.appendList(nullables)), fieldName, field.vartype, null); + params.append(param); + if (!nonNulls.isEmpty()) { + JCStatement nullCheck = generateNullCheck(maker, fieldNode, source); + if (nullCheck != null) nullChecks.append(nullCheck); + } + } JCFieldAccess thisX = maker.Select(maker.Ident(fieldNode.toName("this")), rawName); - JCAssign assign = maker.Assign(thisX, maker.Ident(fieldName)); + JCExpression assign = maker.Assign(thisX, allToDefault ? getDefaultExpr(maker, field.vartype) : maker.Ident(fieldName)); assigns.append(maker.Exec(assign)); - - if (!nonNulls.isEmpty()) { - JCStatement nullCheck = generateNullCheck(maker, fieldNode, source); - if (nullCheck != null) nullChecks.append(nullCheck); - } } JCModifiers mods = maker.Modifiers(toJavacModifier(level), List.<JCAnnotation>nil()); - if (!suppressConstructorProperties && level != AccessLevel.PRIVATE && level != AccessLevel.PACKAGE && !isLocalType(typeNode) && LombokOptionsFactory.getDelombokOptions(typeNode.getContext()).getFormatPreferences().generateConstructorProperties()) { + if (!allToDefault && !suppressConstructorProperties && level != AccessLevel.PRIVATE && level != AccessLevel.PACKAGE && !isLocalType(typeNode) && LombokOptionsFactory.getDelombokOptions(typeNode.getContext()).getFormatPreferences().generateConstructorProperties()) { addConstructorProperties(mods, typeNode, fields); } if (onConstructor != null) mods.annotations = mods.annotations.appendList(copyAnnotations(onConstructor)); return recursiveSetGeneratedBy(maker.MethodDef(mods, typeNode.toName("<init>"), - null, List.<JCTypeParameter>nil(), params.toList(), List.<JCExpression>nil(), - maker.Block(0L, nullChecks.appendList(assigns).toList()), null), source.get(), typeNode.getContext()); + null, List.<JCTypeParameter>nil(), params.toList(), List.<JCExpression>nil(), + maker.Block(0L, nullChecks.appendList(assigns).toList()), null), source.get(), typeNode.getContext()); + } + + private static JCExpression getDefaultExpr(JavacTreeMaker maker, JCExpression type) { + if (type instanceof JCPrimitiveTypeTree) { + switch (((JCPrimitiveTypeTree) type).getPrimitiveTypeKind()) { + case BOOLEAN: + return maker.Literal(CTC_BOOLEAN, 0); + case CHAR: + return maker.Literal(CTC_CHAR, 0); + default: + case BYTE: + case SHORT: + case INT: + return maker.Literal(CTC_INT, 0); + case LONG: + return maker.Literal(CTC_LONG, 0L); + case FLOAT: + return maker.Literal(CTC_FLOAT, 0F); + case DOUBLE: + return maker.Literal(CTC_DOUBLE, 0D); + } + } + + return maker.Literal(CTC_BOT, null); + } public static boolean isLocalType(JavacNode type) { diff --git a/test/transform/resource/after-delombok/Constructors.java b/test/transform/resource/after-delombok/Constructors.java index 466ae9ee..c138a00c 100644 --- a/test/transform/resource/after-delombok/Constructors.java +++ b/test/transform/resource/after-delombok/Constructors.java @@ -98,3 +98,22 @@ class AllArgsConstructorPackageAccess { this.x = x; } } +class NoArgsConstructor2 { + final int x; + final double y; + final char c; + final boolean b; + final float f; + final String s; + byte z; + @java.lang.SuppressWarnings("all") + @javax.annotation.Generated("lombok") + public NoArgsConstructor2() { + this.x = 0; + this.y = 0.0; + this.c = '\000'; + this.b = false; + this.f = 0.0F; + this.s = null; + } +} diff --git a/test/transform/resource/after-ecj/Constructors.java b/test/transform/resource/after-ecj/Constructors.java index 998872a2..4cc24a81 100644 --- a/test/transform/resource/after-ecj/Constructors.java +++ b/test/transform/resource/after-ecj/Constructors.java @@ -77,4 +77,22 @@ super(); this.x = x; } +} +@lombok.NoArgsConstructor(force = true) class NoArgsConstructor2 { + final int x; + final double y; + final char c; + final boolean b; + final float f; + final String s; + byte z; + public @java.lang.SuppressWarnings("all") @javax.annotation.Generated("lombok") NoArgsConstructor2() { + super(); + this.x = 0; + this.y = 0D; + this.c = '\0'; + this.b = false; + this.f = 0F; + this.s = null; + } }
\ No newline at end of file diff --git a/test/transform/resource/before/Constructors.java b/test/transform/resource/before/Constructors.java index c80acf2b..3569986f 100644 --- a/test/transform/resource/before/Constructors.java +++ b/test/transform/resource/before/Constructors.java @@ -32,4 +32,13 @@ } @lombok.AllArgsConstructor(access=lombok.AccessLevel.PACKAGE) class AllArgsConstructorPackageAccess { final String x; -}
\ No newline at end of file +} +@lombok.NoArgsConstructor(force = true) class NoArgsConstructor2 { + final int x; + final double y; + final char c; + final boolean b; + final float f; + final String s; + byte z; +} diff --git a/website/features/Constructor.html b/website/features/Constructor.html index cbb76fcf..d7168372 100644 --- a/website/features/Constructor.html +++ b/website/features/Constructor.html @@ -17,8 +17,7 @@ <p> This set of 3 annotations generate a constructor that will accept 1 parameter for certain fields, and simply assigns this parameter to the field. </p><p> - <code>@NoArgsConstructor</code> will generate a constructor with no parameters. If this is not possible (because of final fields), a compiler error will result instead. - For fields with constraints, such as <code>@NonNull</code> fields, <em>no</em> check or assignment is generated, so be aware that these constraints may then not be + <code>@NoArgsConstructor</code> will generate a constructor with no parameters. If this is not possible (because of final fields), a compiler error will result instead, unless <code>@NoArgsConstructor(force = true)</code> is used, then all final fields are initialized with 0 / false / null. For fields with constraints, such as <code>@NonNull</code> fields, <em>no</em> check or assignment is generated, so be aware that these constraints may then not be fulfilled until those fields are properly initialized later. Certain java constructs, such as hibernate and the Service Provider Interface require a no-args constructor. This annotation is useful primarily in combination with either <code>@Data</code> or one of the other constructor generating annotations. </p><p> |