diff options
author | Reinier Zwitserloot <reinier@tipit.to> | 2009-08-27 23:16:47 +0200 |
---|---|---|
committer | Reinier Zwitserloot <reinier@tipit.to> | 2009-08-27 23:16:47 +0200 |
commit | 4e152f2f1485f904feb45ae614236d4aa4b6edc9 (patch) | |
tree | aa7de35a215a5d7077e27e0ef4c1699e9c56779b | |
parent | 0221e460b2e648b142284c6c462d5798f33a3ff7 (diff) | |
parent | fe0da3f53f1e88b704e21463cc5fea3d998e394a (diff) | |
download | lombok-4e152f2f1485f904feb45ae614236d4aa4b6edc9.tar.gz lombok-4e152f2f1485f904feb45ae614236d4aa4b6edc9.tar.bz2 lombok-4e152f2f1485f904feb45ae614236d4aa4b6edc9.zip |
Merge branch 'nonnull'
Conflicts:
src/lombok/eclipse/handlers/HandleData.java
src/lombok/eclipse/handlers/HandleEqualsAndHashCode.java
src/lombok/eclipse/handlers/HandleSetter.java
src/lombok/javac/handlers/HandleData.java
src/lombok/javac/handlers/HandleEqualsAndHashCode.java
src/lombok/javac/handlers/HandleSetter.java
-rw-r--r-- | src/lombok/NonNull.java | 39 | ||||
-rw-r--r-- | src/lombok/eclipse/Eclipse.java | 52 | ||||
-rw-r--r-- | src/lombok/eclipse/handlers/HandleData.java | 21 | ||||
-rw-r--r-- | src/lombok/eclipse/handlers/HandleEqualsAndHashCode.java | 8 | ||||
-rw-r--r-- | src/lombok/eclipse/handlers/HandleGetter.java | 15 | ||||
-rw-r--r-- | src/lombok/eclipse/handlers/HandleSetter.java | 20 | ||||
-rw-r--r-- | src/lombok/eclipse/handlers/PKG.java | 47 | ||||
-rw-r--r-- | src/lombok/javac/handlers/HandleData.java | 18 | ||||
-rw-r--r-- | src/lombok/javac/handlers/HandleEqualsAndHashCode.java | 8 | ||||
-rw-r--r-- | src/lombok/javac/handlers/HandleGetter.java | 4 | ||||
-rw-r--r-- | src/lombok/javac/handlers/HandleSetter.java | 14 | ||||
-rw-r--r-- | src/lombok/javac/handlers/PKG.java | 43 |
12 files changed, 255 insertions, 34 deletions
diff --git a/src/lombok/NonNull.java b/src/lombok/NonNull.java new file mode 100644 index 00000000..46afc5da --- /dev/null +++ b/src/lombok/NonNull.java @@ -0,0 +1,39 @@ +/* + * Copyright © 2009 Reinier Zwitserloot and Roel Spilker. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Lombok is smart enough to translate any annotation named <code>@NonNull</code> or <code>@NotNull</code> in any casing and + * with any package name to the return type of generated getters and the parameter of generated setters and constructors, + * as well as generate the appropriate null checks in the setter and constructor. + * + * You can use this annotation for the purpose, though you can also use JSR305's annotation, findbugs's, pmd's, or IDEA's, or just + * about anyone elses. As long as it is named <code>@NonNull</code> or <code>@NotNull</code>. + */ +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE}) +@Retention(RetentionPolicy.CLASS) +public @interface NonNull {} diff --git a/src/lombok/eclipse/Eclipse.java b/src/lombok/eclipse/Eclipse.java index cba2bd05..017affa1 100644 --- a/src/lombok/eclipse/Eclipse.java +++ b/src/lombok/eclipse/Eclipse.java @@ -49,11 +49,14 @@ import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.Literal; +import org.eclipse.jdt.internal.compiler.ast.MarkerAnnotation; import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; +import org.eclipse.jdt.internal.compiler.ast.NormalAnnotation; import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.TypeParameter; @@ -117,7 +120,7 @@ public class Eclipse { * For 'speed' reasons, Eclipse works a lot with char arrays. I have my doubts this was a fruitful exercise, * but we need to deal with it. This turns [[java][lang][String]] into "java.lang.String". */ - static String toQualifiedName(char[][] typeName) { + public static String toQualifiedName(char[][] typeName) { StringBuilder sb = new StringBuilder(); boolean first = true; for ( char[] c : typeName ) { @@ -127,6 +130,16 @@ public class Eclipse { return sb.toString(); } + public static char[][] fromQualifiedName(String typeName) { + String[] split = typeName.split("\\."); + char[][] result = new char[split.length][]; + for (int i = 0; i < split.length; i++) { + result[i] = split[i].toCharArray(); + } + return result; + } + + /** * You can't share TypeParameter objects or bad things happen; for example, one 'T' resolves differently * from another 'T', even for the same T in a single class file. Unfortunately the TypeParameter type hierarchy @@ -241,6 +254,43 @@ public class Eclipse { return ref; } + public static Annotation[] copyAnnotations(Annotation[] annotations) { + return copyAnnotations(annotations, null); + } + + public static Annotation[] copyAnnotations(Annotation[] annotations1, Annotation[] annotations2) { + if (annotations1 == null && annotations2 == null) return null; + if (annotations1 == null) annotations1 = new Annotation[0]; + if (annotations2 == null) annotations2 = new Annotation[0]; + Annotation[] outs = new Annotation[annotations1.length + annotations2.length]; + int idx = 0; + for ( Annotation annotation : annotations1 ) { + outs[idx++] = copyAnnotation(annotation); + } + for ( Annotation annotation : annotations2 ) { + outs[idx++] = copyAnnotation(annotation); + } + return outs; + } + + public static Annotation copyAnnotation(Annotation annotation) { + if (annotation instanceof MarkerAnnotation) { + return new MarkerAnnotation(copyType(annotation.type), annotation.sourceStart); + } + + if (annotation instanceof SingleMemberAnnotation) { + SingleMemberAnnotation result = new SingleMemberAnnotation(copyType(annotation.type), annotation.sourceStart); + result.memberValue = ((SingleMemberAnnotation)annotation).memberValue; + } + + if (annotation instanceof NormalAnnotation) { + NormalAnnotation result = new NormalAnnotation(copyType(annotation.type), annotation.sourceStart); + result.memberValuePairs = ((NormalAnnotation)annotation).memberValuePairs; + } + + return annotation; + } + /** * Checks if the provided annotation type is likely to be the intended type for the given annotation node. * diff --git a/src/lombok/eclipse/handlers/HandleData.java b/src/lombok/eclipse/handlers/HandleData.java index b598313b..f072ea64 100644 --- a/src/lombok/eclipse/handlers/HandleData.java +++ b/src/lombok/eclipse/handlers/HandleData.java @@ -89,7 +89,8 @@ public class HandleData implements EclipseAnnotationHandler<Data> { //Skip static fields. if ( (fieldDecl.modifiers & ClassFileConstants.AccStatic) != 0 ) continue; boolean isFinal = (fieldDecl.modifiers & ClassFileConstants.AccFinal) != 0; - if ( isFinal && fieldDecl.initialization == null ) nodesForConstructor.add(child); + boolean isNonNull = findAnnotations(fieldDecl, NON_NULL_PATTERN).length != 0; + if ( (isFinal || isNonNull) && fieldDecl.initialization == null ) nodesForConstructor.add(child); new HandleGetter().generateGetterForField(child, annotationNode.get()); if ( !isFinal ) new HandleSetter().generateSetterForField(child, annotationNode.get()); } @@ -139,18 +140,27 @@ public class HandleData implements EclipseAnnotationHandler<Data> { List<Argument> args = new ArrayList<Argument>(); List<Statement> assigns = new ArrayList<Statement>(); + List<Statement> nullChecks = new ArrayList<Statement>(); for ( Node fieldNode : fields ) { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); FieldReference thisX = new FieldReference(("this." + new String(field.name)).toCharArray(), p); thisX.receiver = new ThisReference((int)(p >> 32), (int)p); thisX.token = field.name; + assigns.add(new Assignment(thisX, new SingleNameReference(field.name, p), (int)p)); long fieldPos = (((long)field.sourceStart) << 32) | field.sourceEnd; - args.add(new Argument(field.name, fieldPos, copyType(field.type), Modifier.FINAL)); + Argument argument = new Argument(field.name, fieldPos, copyType(field.type), Modifier.FINAL); + Annotation[] nonNulls = findAnnotations(field, NON_NULL_PATTERN); + Annotation[] nullables = findAnnotations(field, NULLABLE_PATTERN); + if (nonNulls.length != 0) nullChecks.add(generateNullCheck(field)); + Annotation[] copiedAnnotations = copyAnnotations(nonNulls, nullables); + if (copiedAnnotations.length != 0) argument.annotations = copiedAnnotations; + args.add(argument); } - constructor.statements = assigns.isEmpty() ? null : assigns.toArray(new Statement[assigns.size()]); + nullChecks.addAll(assigns); + constructor.statements = nullChecks.isEmpty() ? null : nullChecks.toArray(new Statement[nullChecks.size()]); constructor.arguments = args.isEmpty() ? null : args.toArray(new Argument[args.size()]); return constructor; } @@ -188,6 +198,11 @@ public class HandleData implements EclipseAnnotationHandler<Data> { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); long fieldPos = (((long)field.sourceStart) << 32) | field.sourceEnd; assigns.add(new SingleNameReference(field.name, fieldPos)); + + Argument argument = new Argument(field.name, fieldPos, copyType(field.type), 0); + Annotation[] copiedAnnotations = copyAnnotations( + findAnnotations(field, NON_NULL_PATTERN), findAnnotations(field, NULLABLE_PATTERN)); + if (copiedAnnotations.length != 0) argument.annotations = copiedAnnotations; args.add(new Argument(field.name, fieldPos, copyType(field.type), Modifier.FINAL)); } diff --git a/src/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/lombok/eclipse/handlers/HandleEqualsAndHashCode.java index b8916d46..1adbe781 100644 --- a/src/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ b/src/lombok/eclipse/handlers/HandleEqualsAndHashCode.java @@ -154,19 +154,19 @@ public class HandleEqualsAndHashCode implements EclipseAnnotationHandler<EqualsA return false; } - boolean isDirectDescendentOfObject = true; + boolean isDirectDescendantOfObject = true; if ( typeDecl.superclass != null ) { String p = typeDecl.superclass.toString(); - isDirectDescendentOfObject = p.equals("Object") || p.equals("java.lang.Object"); + isDirectDescendantOfObject = p.equals("Object") || p.equals("java.lang.Object"); } - if ( isDirectDescendentOfObject && callSuper ) { + if ( isDirectDescendantOfObject && callSuper ) { errorNode.addError("Generating equals/hashCode with a supercall to java.lang.Object is pointless."); return true; } - if ( !isDirectDescendentOfObject && !callSuper && implicit ) { + if ( !isDirectDescendantOfObject && !callSuper && implicit ) { errorNode.addWarning("Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type."); } diff --git a/src/lombok/eclipse/handlers/HandleGetter.java b/src/lombok/eclipse/handlers/HandleGetter.java index 982ccc97..5fb7876a 100644 --- a/src/lombok/eclipse/handlers/HandleGetter.java +++ b/src/lombok/eclipse/handlers/HandleGetter.java @@ -27,7 +27,7 @@ import lombok.Getter; import lombok.core.AnnotationValues; import lombok.core.TransformationsUtil; import lombok.core.AST.Kind; -import lombok.eclipse.Eclipse; +import static lombok.eclipse.Eclipse.*; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseAST.Node; @@ -64,7 +64,7 @@ public class HandleGetter implements EclipseAnnotationHandler<Getter> { public void generateGetterForField(Node fieldNode, ASTNode pos) { for ( Node child : fieldNode.down() ) { if ( child.getKind() == Kind.ANNOTATION ) { - if ( Eclipse.annotationTypeMatches(Getter.class, child) ) { + if ( annotationTypeMatches(Getter.class, child) ) { //The annotation will make it happen, so we can skip it. return; } @@ -87,7 +87,7 @@ public class HandleGetter implements EclipseAnnotationHandler<Getter> { } FieldDeclaration field = (FieldDeclaration) fieldNode.get(); - TypeReference fieldType = Eclipse.copyType(field.type); + TypeReference fieldType = copyType(field.type); String fieldName = new String(field.name); boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0; String getterName = TransformationsUtil.toGetterName(fieldName, isBoolean); @@ -113,6 +113,11 @@ public class HandleGetter implements EclipseAnnotationHandler<Getter> { } MethodDeclaration method = generateGetter((TypeDeclaration) fieldNode.up().get(), field, getterName, modifier, pos); + Annotation[] copiedAnnotations = copyAnnotations( + findAnnotations(field, NON_NULL_PATTERN), findAnnotations(field, NULLABLE_PATTERN)); + if (copiedAnnotations.length != 0) { + method.annotations = copiedAnnotations; + } injectMethod(fieldNode.up(), method); @@ -123,14 +128,14 @@ public class HandleGetter implements EclipseAnnotationHandler<Getter> { int modifier, ASTNode pos) { MethodDeclaration method = new MethodDeclaration(parent.compilationResult); method.modifiers = modifier; - method.returnType = Eclipse.copyType(field.type); + method.returnType = copyType(field.type); method.annotations = null; method.arguments = null; method.selector = name.toCharArray(); method.binding = null; method.thrownExceptions = null; method.typeParameters = null; - method.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + method.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; Expression fieldExpression = new SingleNameReference(field.name, (field.declarationSourceStart << 32) | field.declarationSourceEnd); Statement returnStatement = new ReturnStatement(fieldExpression, field.sourceStart, field.sourceEnd); method.bodyStart = method.declarationSourceStart = method.sourceStart = pos.sourceStart; diff --git a/src/lombok/eclipse/handlers/HandleSetter.java b/src/lombok/eclipse/handlers/HandleSetter.java index 1e301c29..5ad9b193 100644 --- a/src/lombok/eclipse/handlers/HandleSetter.java +++ b/src/lombok/eclipse/handlers/HandleSetter.java @@ -21,6 +21,7 @@ */ package lombok.eclipse.handlers; +import static lombok.eclipse.Eclipse.*; import static lombok.eclipse.handlers.PKG.*; import java.lang.reflect.Modifier; @@ -30,7 +31,6 @@ import lombok.Setter; import lombok.core.AnnotationValues; import lombok.core.TransformationsUtil; import lombok.core.AST.Kind; -import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseAST.Node; @@ -71,7 +71,7 @@ public class HandleSetter implements EclipseAnnotationHandler<Setter> { public void generateSetterForField(Node fieldNode, ASTNode pos) { for ( Node child : fieldNode.down() ) { if ( child.getKind() == Kind.ANNOTATION ) { - if ( Eclipse.annotationTypeMatches(Setter.class, child) ) { + if ( annotationTypeMatches(Setter.class, child) ) { //The annotation will make it happen, so we can skip it. return; } @@ -122,25 +122,35 @@ public class HandleSetter implements EclipseAnnotationHandler<Setter> { private MethodDeclaration generateSetter(TypeDeclaration parent, FieldDeclaration field, String name, int modifier, ASTNode ast) { + long pos = (((long)ast.sourceStart) << 32) | ast.sourceEnd; MethodDeclaration method = new MethodDeclaration(parent.compilationResult); method.modifiers = modifier; method.returnType = TypeReference.baseTypeReference(TypeIds.T_void, 0); method.annotations = null; - Argument param = new Argument(field.name, pos, Eclipse.copyType(field.type), Modifier.FINAL); + Argument param = new Argument(field.name, pos, copyType(field.type), Modifier.FINAL); method.arguments = new Argument[] { param }; method.selector = name.toCharArray(); method.binding = null; method.thrownExceptions = null; method.typeParameters = null; method.scope = parent.scope == null ? null : new MethodScope(parent.scope, method, false); - method.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + method.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; FieldReference thisX = new FieldReference(field.name, pos); thisX.receiver = new ThisReference(ast.sourceStart, ast.sourceEnd); Assignment assignment = new Assignment(thisX, new SingleNameReference(field.name, pos), (int)pos); method.bodyStart = method.declarationSourceStart = method.sourceStart = ast.sourceStart; method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = ast.sourceEnd; - method.statements = new Statement[] { assignment }; + + Annotation[] nonNulls = findAnnotations(field, NON_NULL_PATTERN); + Annotation[] nullables = findAnnotations(field, NULLABLE_PATTERN); + if (nonNulls.length == 0) { + method.statements = new Statement[] { assignment }; + } else { + method.statements = new Statement[] { generateNullCheck(field), assignment }; + } + Annotation[] copiedAnnotations = copyAnnotations(nonNulls, nullables); + if (copiedAnnotations.length != 0) param.annotations = copiedAnnotations; return method; } } diff --git a/src/lombok/eclipse/handlers/PKG.java b/src/lombok/eclipse/handlers/PKG.java index 7fdf7afd..17096b70 100644 --- a/src/lombok/eclipse/handlers/PKG.java +++ b/src/lombok/eclipse/handlers/PKG.java @@ -21,7 +21,12 @@ */ package lombok.eclipse.handlers; +import static lombok.eclipse.Eclipse.fromQualifiedName; + import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; import lombok.AccessLevel; import lombok.core.AST.Kind; @@ -29,9 +34,23 @@ import lombok.eclipse.EclipseAST; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration; +import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; +import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.EqualExpression; +import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.StringLiteral; +import org.eclipse.jdt.internal.compiler.ast.ThrowStatement; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; class PKG { private PKG() {} @@ -212,4 +231,32 @@ class PKG { type.add(method, Kind.METHOD).recursiveSetHandled(); } + + static final Pattern NON_NULL_PATTERN = Pattern.compile("^no[tn]null$", Pattern.CASE_INSENSITIVE); + static final Pattern NULLABLE_PATTERN = Pattern.compile("^nullable$", Pattern.CASE_INSENSITIVE); + + static Annotation[] findAnnotations(FieldDeclaration field, Pattern namePattern) { + List<Annotation> result = new ArrayList<Annotation>(); + for (Annotation annotation : field.annotations) { + TypeReference typeRef = annotation.type; + if ( typeRef != null && typeRef.getTypeName()!= null ) { + char[][] typeName = typeRef.getTypeName(); + String suspect = new String(typeName[typeName.length - 1]); + if ( namePattern.matcher(suspect).matches() ) { + result.add(annotation); + } + } + } + return result.toArray(new Annotation[0]); + } + + static Statement generateNullCheck(AbstractVariableDeclaration variable) { + AllocationExpression exception = new AllocationExpression(); + exception.type = new QualifiedTypeReference(fromQualifiedName("java.lang.NullPointerException"), new long[]{0, 0, 0}); + exception.arguments = new Expression[] { new StringLiteral(variable.name, 0, variable.name.length - 1, 0)}; + ThrowStatement throwStatement = new ThrowStatement(exception, 0, 0); + + return new IfStatement(new EqualExpression(new SingleNameReference(variable.name, 0), + new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL), throwStatement, 0, 0); + } } diff --git a/src/lombok/javac/handlers/HandleData.java b/src/lombok/javac/handlers/HandleData.java index 7b094106..3b4f8951 100644 --- a/src/lombok/javac/handlers/HandleData.java +++ b/src/lombok/javac/handlers/HandleData.java @@ -79,7 +79,8 @@ public class HandleData implements JavacAnnotationHandler<Data> { if ( (fieldFlags & Flags.STATIC) != 0 ) continue; if ( (fieldFlags & Flags.TRANSIENT) == 0 ) nodesForEquality = nodesForEquality.append(child); boolean isFinal = (fieldFlags & Flags.FINAL) != 0; - if ( isFinal && fieldDecl.init == null ) nodesForConstructor = nodesForConstructor.append(child); + boolean isNonNull = !findAnnotations(child, NON_NULL_PATTERN).isEmpty(); + if ( (isFinal || isNonNull) && fieldDecl.init == null ) nodesForConstructor = nodesForConstructor.append(child); new HandleGetter().generateGetterForField(child, annotationNode.get()); if ( !isFinal ) new HandleSetter().generateSetterForField(child, annotationNode.get()); } @@ -106,21 +107,28 @@ public class HandleData implements JavacAnnotationHandler<Data> { TreeMaker maker = typeNode.getTreeMaker(); JCClassDecl type = (JCClassDecl) typeNode.get(); + List<JCStatement> nullChecks = List.nil(); List<JCStatement> assigns = List.nil(); List<JCVariableDecl> params = List.nil(); for ( Node fieldNode : fields ) { JCVariableDecl field = (JCVariableDecl) fieldNode.get(); - JCVariableDecl param = maker.VarDef(maker.Modifiers(Flags.FINAL), field.name, field.vartype, null); + List<JCAnnotation> nonNulls = findAnnotations(fieldNode, NON_NULL_PATTERN); + List<JCAnnotation> nullables = findAnnotations(fieldNode, NULLABLE_PATTERN); + JCVariableDecl param = maker.VarDef(maker.Modifiers(Flags.FINAL, nonNulls.appendList(nullables)), field.name, field.vartype, null); params = params.append(param); JCFieldAccess thisX = maker.Select(maker.Ident(fieldNode.toName("this")), field.name); JCAssign assign = maker.Assign(thisX, maker.Ident(field.name)); assigns = assigns.append(maker.Exec(assign)); + + if (!nonNulls.isEmpty()) { + nullChecks = nullChecks.append(generateNullCheck(maker, fieldNode)); + } } JCModifiers mods = maker.Modifiers(isPublic ? Modifier.PUBLIC : Modifier.PRIVATE); return maker.MethodDef(mods, typeNode.toName("<init>"), - null, type.typarams, params, List.<JCExpression>nil(), maker.Block(0L, assigns), null); + null, type.typarams, params, List.<JCExpression>nil(), maker.Block(0L, nullChecks.appendList(assigns)), null); } private JCMethodDecl createStaticConstructor(String name, Node typeNode, List<Node> fields) { @@ -160,7 +168,9 @@ public class HandleData implements JavacAnnotationHandler<Data> { for ( JCExpression arg : typeApply.arguments ) tArgs = tArgs.append(arg); pType = maker.TypeApply(typeApply.clazz, tArgs); } else pType = field.vartype; - JCVariableDecl param = maker.VarDef(maker.Modifiers(Flags.FINAL), field.name, pType, null); + List<JCAnnotation> nonNulls = findAnnotations(fieldNode, NON_NULL_PATTERN); + List<JCAnnotation> nullables = findAnnotations(fieldNode, NULLABLE_PATTERN); + JCVariableDecl param = maker.VarDef(maker.Modifiers(Flags.FINAL, nonNulls.appendList(nullables)), field.name, pType, null); params = params.append(param); args = args.append(maker.Ident(field.name)); } diff --git a/src/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/lombok/javac/handlers/HandleEqualsAndHashCode.java index efc154f6..8c3124c9 100644 --- a/src/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/lombok/javac/handlers/HandleEqualsAndHashCode.java @@ -120,20 +120,20 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd return false; } - boolean isDirectDescendentOfObject = true; + boolean isDirectDescendantOfObject = true; JCTree extending = ((JCClassDecl)typeNode.get()).extending; if ( extending != null ) { String p = extending.toString(); - isDirectDescendentOfObject = p.equals("Object") || p.equals("java.lang.Object"); + isDirectDescendantOfObject = p.equals("Object") || p.equals("java.lang.Object"); } - if ( isDirectDescendentOfObject && callSuper ) { + if ( isDirectDescendantOfObject && callSuper ) { errorNode.addError("Generating equals/hashCode with a supercall to java.lang.Object is pointless."); return true; } - if ( !isDirectDescendentOfObject && !callSuper && implicit ) { + if ( !isDirectDescendantOfObject && !callSuper && implicit ) { errorNode.addWarning("Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type."); } diff --git a/src/lombok/javac/handlers/HandleGetter.java b/src/lombok/javac/handlers/HandleGetter.java index 3d3227e4..774a9955 100644 --- a/src/lombok/javac/handlers/HandleGetter.java +++ b/src/lombok/javac/handlers/HandleGetter.java @@ -128,7 +128,9 @@ public class HandleGetter implements JavacAnnotationHandler<Getter> { List<JCExpression> throwsClauses = List.nil(); JCExpression annotationMethodDefaultValue = null; - return treeMaker.MethodDef(treeMaker.Modifiers(access, List.<JCAnnotation>nil()), methodName, methodType, + List<JCAnnotation> nonNulls = findAnnotations(field, NON_NULL_PATTERN); + List<JCAnnotation> nullables = findAnnotations(field, NULLABLE_PATTERN); + return treeMaker.MethodDef(treeMaker.Modifiers(access, nonNulls.appendList(nullables)), methodName, methodType, methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue); } } diff --git a/src/lombok/javac/handlers/HandleSetter.java b/src/lombok/javac/handlers/HandleSetter.java index 2e25ee57..412b1c43 100644 --- a/src/lombok/javac/handlers/HandleSetter.java +++ b/src/lombok/javac/handlers/HandleSetter.java @@ -119,9 +119,19 @@ public class HandleSetter implements JavacAnnotationHandler<Setter> { JCFieldAccess thisX = treeMaker.Select(treeMaker.Ident(field.toName("this")), fieldDecl.name); JCAssign assign = treeMaker.Assign(thisX, treeMaker.Ident(fieldDecl.name)); - JCBlock methodBody = treeMaker.Block(0, List.<JCStatement>of(treeMaker.Exec(assign))); + List<JCStatement> statements; + List<JCAnnotation> nonNulls = findAnnotations(field, NON_NULL_PATTERN); + List<JCAnnotation> nullables = findAnnotations(field, NULLABLE_PATTERN); + if (nonNulls.isEmpty()) { + statements = List.<JCStatement>of(treeMaker.Exec(assign)); + } + else { + statements = List.<JCStatement>of(generateNullCheck(treeMaker, field), treeMaker.Exec(assign)); + } + + JCBlock methodBody = treeMaker.Block(0, statements); Name methodName = field.toName(toSetterName(fieldDecl)); - JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.FINAL), fieldDecl.name, fieldDecl.vartype, null); + JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.FINAL, nonNulls.appendList(nullables)), fieldDecl.name, fieldDecl.vartype, null); JCExpression methodType = treeMaker.Type(field.getSymbolTable().voidType); List<JCTypeParameter> methodGenericParams = List.nil(); diff --git a/src/lombok/javac/handlers/PKG.java b/src/lombok/javac/handlers/PKG.java index 205a2b6e..42cfed13 100644 --- a/src/lombok/javac/handlers/PKG.java +++ b/src/lombok/javac/handlers/PKG.java @@ -22,20 +22,26 @@ package lombok.javac.handlers; import java.lang.reflect.Modifier; +import java.util.regex.Pattern; + +import lombok.AccessLevel; +import lombok.core.TransformationsUtil; +import lombok.core.AST.Kind; +import lombok.javac.JavacAST; +import lombok.javac.JavacAST.Node; import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.TypeTags; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.util.List; - -import lombok.AccessLevel; -import lombok.core.TransformationsUtil; -import lombok.core.AST.Kind; -import lombok.javac.JavacAST; +import com.sun.tools.javac.util.Name; /** * Container for static utility methods relevant to this package. @@ -251,4 +257,31 @@ class PKG { return e; } + + static final Pattern NON_NULL_PATTERN = Pattern.compile("^no[tn]null$", Pattern.CASE_INSENSITIVE); + static final Pattern NULLABLE_PATTERN = Pattern.compile("^nullable$", Pattern.CASE_INSENSITIVE); + + static List<JCAnnotation> findAnnotations(Node fieldNode, Pattern namePattern) { + List<JCAnnotation> result = List.nil(); + for ( Node child : fieldNode.down() ) { + if ( child.getKind() == Kind.ANNOTATION ) { + JCAnnotation annotation = (JCAnnotation) child.get(); + String name = annotation.annotationType.toString(); + int idx = name.lastIndexOf("."); + String suspect = idx == -1 ? name : name.substring(idx + 1); + if (namePattern.matcher(suspect).matches()) { + result = result.append(annotation); + } + } + } + return result; + } + + static JCStatement generateNullCheck(TreeMaker treeMaker, JavacAST.Node variable) { + Name fieldName = ((JCVariableDecl) variable.get()).name; + JCExpression npe = chainDots(treeMaker, variable, "java", "lang", "NullPointerException"); + JCTree exception = treeMaker.NewClass(null, List.<JCExpression>nil(), npe, List.<JCExpression>of(treeMaker.Literal(fieldName.toString())), null); + JCStatement throwStatement = treeMaker.Throw(exception); + return treeMaker.If(treeMaker.Binary(JCTree.EQ, treeMaker.Ident(fieldName), treeMaker.Literal(TypeTags.BOT, null)), throwStatement, null); + } } |