lombok.eclipse.handlers.HandleNonNull Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of develhacked-lombok Show documentation
Show all versions of develhacked-lombok Show documentation
The library enhancing Java classes at compilation by annotations that contained in the Develhack Core Library.
/*
* Copyright (C) 2013-2014 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package lombok.eclipse.handlers;
import static lombok.core.handlers.HandlerUtil.*;
import static lombok.eclipse.Eclipse.isPrimitive;
import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
import java.util.Arrays;
import lombok.ConfigurationKeys;
import lombok.NonNull;
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.eclipse.DeferUntilPostDiet;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.Argument;
import org.eclipse.jdt.internal.compiler.ast.Block;
import org.eclipse.jdt.internal.compiler.ast.EqualExpression;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.IfStatement;
import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
import org.eclipse.jdt.internal.compiler.ast.OperatorIds;
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.SynchronizedStatement;
import org.eclipse.jdt.internal.compiler.ast.ThrowStatement;
import org.eclipse.jdt.internal.compiler.ast.TryStatement;
import org.mangosdk.spi.ProviderFor;
@DeferUntilPostDiet
@ProviderFor(EclipseAnnotationHandler.class)
@HandlerPriority(value = 512) // 2^9; onParameter=@__(@NonNull) has to run first.
public class HandleNonNull extends EclipseAnnotationHandler {
@Override public void handle(AnnotationValues annotation, Annotation ast, EclipseNode annotationNode) {
handleFlagUsage(annotationNode, ConfigurationKeys.NON_NULL_FLAG_USAGE, "@NonNull");
if (annotationNode.up().getKind() == Kind.FIELD) {
// This is meaningless unless the field is used to generate a method (@Setter, @RequiredArgsConstructor, etc),
// but in that case those handlers will take care of it. However, we DO check if the annotation is applied to
// a primitive, because those handlers trigger on any annotation named @NonNull and we only want the warning
// behaviour on _OUR_ 'lombok.NonNull'.
try {
if (isPrimitive(((AbstractVariableDeclaration) annotationNode.up().get()).type)) {
annotationNode.addWarning("@NonNull is meaningless on a primitive.");
}
} catch (Exception ignore) {}
return;
}
if (annotationNode.up().getKind() != Kind.ARGUMENT) return;
Argument arg;
AbstractMethodDeclaration declaration;
try {
arg = (Argument) annotationNode.up().get();
declaration = (AbstractMethodDeclaration) annotationNode.up().up().get();
} catch (Exception e) {
return;
}
if (isGenerated(declaration)) return;
if (declaration.isAbstract()) {
annotationNode.addWarning("@NonNull is meaningless on a parameter of an abstract method.");
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 lombok$nullCheck(T expr, String msg) {if (expr == null) throw NPE; return expr;}' and
// wrap all references to it in the super/this to a call to this method.
Statement nullCheck = generateNullCheck(arg, annotationNode);
if (nullCheck == null) {
// @NonNull applied to a primitive. Kinda pointless. Let's generate a warning.
annotationNode.addWarning("@NonNull is meaningless on a primitive.");
return;
}
if (declaration.statements == null) {
declaration.statements = new Statement[] {nullCheck};
} else {
char[] expectedName = arg.name;
/* Abort if the null check is already there, delving into try and synchronized statements */ {
Statement[] stats = declaration.statements;
int idx = 0;
while (stats != null && stats.length > idx) {
Statement stat = stats[idx++];
if (stat instanceof TryStatement) {
stats = ((TryStatement) stat).tryBlock.statements;
idx = 0;
continue;
}
if (stat instanceof SynchronizedStatement) {
stats = ((SynchronizedStatement) stat).block.statements;
idx = 0;
continue;
}
char[] varNameOfNullCheck = returnVarNameIfNullCheck(stat);
if (varNameOfNullCheck == null) break;
if (Arrays.equals(varNameOfNullCheck, expectedName)) return;
}
}
Statement[] newStatements = new Statement[declaration.statements.length + 1];
int skipOver = 0;
for (Statement stat : declaration.statements) {
if (isGenerated(stat) && isNullCheck(stat)) skipOver++;
else break;
}
System.arraycopy(declaration.statements, 0, newStatements, 0, skipOver);
System.arraycopy(declaration.statements, skipOver, newStatements, skipOver + 1, declaration.statements.length - skipOver);
newStatements[skipOver] = nullCheck;
declaration.statements = newStatements;
}
annotationNode.up().up().rebuild();
}
public boolean isNullCheck(Statement stat) {
return returnVarNameIfNullCheck(stat) != null;
}
public char[] returnVarNameIfNullCheck(Statement stat) {
if (!(stat instanceof IfStatement)) return null;
/* Check that the if's statement is a throw statement, possibly in a block. */ {
Statement then = ((IfStatement) stat).thenStatement;
if (then instanceof Block) {
Statement[] blockStatements = ((Block) then).statements;
if (blockStatements == null || blockStatements.length == 0) return null;
then = blockStatements[0];
}
if (!(then instanceof ThrowStatement)) return null;
}
/* Check that the if's conditional is like 'x == null'. Return from this method (don't generate
a nullcheck) if 'x' is equal to our own variable's name: There's already a nullcheck here. */ {
Expression cond = ((IfStatement) stat).condition;
if (!(cond instanceof EqualExpression)) return null;
EqualExpression bin = (EqualExpression) cond;
int operatorId = ((bin.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT);
if (operatorId != OperatorIds.EQUAL_EQUAL) return null;
if (!(bin.left instanceof SingleNameReference)) return null;
if (!(bin.right instanceof NullLiteral)) return null;
return ((SingleNameReference) bin.left).token;
}
}
}