package lombok.eclipse.handlers;
import static lombok.core.handlers.HandlerUtil.handleFlagUsage;
import static lombok.eclipse.Eclipse.isPrimitive;
import static lombok.eclipse.handlers.EclipseHandlerUtil.*;
import java.util.Arrays;
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.AssertStatement;
import org.eclipse.jdt.internal.compiler.ast.Assignment;
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.MessageSend;
import org.eclipse.jdt.internal.compiler.ast.NullLiteral;
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.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.mangosdk.spi.ProviderFor;
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.EclipseAST;
import lombok.eclipse.EclipseAnnotationHandler;
import lombok.eclipse.EclipseNode;
@DeferUntilPostDiet
@ProviderFor(EclipseAnnotationHandler.class)
@HandlerPriority(value = 512)
public class HandleNonNull extends EclipseAnnotationHandler<NonNull> {
private static final char[] REQUIRE_NON_NULL = "requireNonNull".toCharArray();
private static final char[] CHECK_NOT_NULL = "checkNotNull".toCharArray();
public static final HandleNonNull INSTANCE = new HandleNonNull();
public void fix(EclipseNode method) {
for (EclipseNode m : method.down()) {
if (m.getKind() != Kind.ARGUMENT) continue;
for (EclipseNode c : m.down()) {
if (c.getKind() == Kind.ANNOTATION) {
if (annotationTypeMatches(NonNull.class, c)) {
handle0((Annotation) c.get(), c, true);
}
}
}
}
}
@Override public void handle(AnnotationValues<NonNull> annotation, Annotation ast, EclipseNode annotationNode) {
handle0(ast, annotationNode, false);
}
private void handle0(Annotation ast, EclipseNode annotationNode, boolean force) {
handleFlagUsage(annotationNode, ConfigurationKeys.NON_NULL_FLAG_USAGE, "@NonNull");
if (annotationNode.up().getKind() == Kind.FIELD) {
try {
if (isPrimitive(((AbstractVariableDeclaration) annotationNode.up().get()).type)) {
annotationNode.addWarning("@NonNull is meaningless on a primitive.");
}
} catch (Exception ignore) {}
return;
}
Argument param;
EclipseNode paramNode;
AbstractMethodDeclaration declaration;
switch (annotationNode.up().getKind()) {
case ARGUMENT:
paramNode = annotationNode.up();
break;
case TYPE_USE:
EclipseNode typeNode = annotationNode.directUp();
boolean ok = false;
ASTNode astNode = typeNode.get();
if (astNode instanceof TypeReference) {
Annotation[] anns = EclipseAST.getTopLevelTypeReferenceAnnotations((TypeReference) astNode);
if (anns == null) return;
for (Annotation ann : anns) if (ast == ann) ok = true;
}
if (!ok) return;
paramNode = typeNode.directUp();
break;
default:
return;
}
try {
param = (Argument) paramNode.get();
declaration = (AbstractMethodDeclaration) paramNode.up().get();
} catch (Exception e) {
return;
}
if (!force && isGenerated(declaration)) return;
if (declaration.isAbstract()) {
return;
}
Statement nullCheck = generateNullCheck(param, annotationNode, null);
if (nullCheck == null) {
annotationNode.addWarning("@NonNull is meaningless on a primitive.");
return;
}
if (declaration.statements == null) {
declaration.statements = new Statement[] {nullCheck};
} else {
char[] expectedName = param.name;
{
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;
}
paramNode.up().rebuild();
}
public boolean isNullCheck(Statement stat) {
return returnVarNameIfNullCheck(stat) != null;
}
public char[] returnVarNameIfNullCheck(Statement stat) {
boolean isIf = stat instanceof IfStatement;
boolean isExpression = stat instanceof Expression;
if (!isIf && !(stat instanceof AssertStatement) && !isExpression) return null;
if (isExpression) {
Expression expression = (Expression) stat;
if (expression instanceof Assignment) expression = ((Assignment) expression).expression;
if (!(expression instanceof MessageSend)) return null;
MessageSend invocation = (MessageSend) expression;
if (!Arrays.equals(invocation.selector, CHECK_NOT_NULL) && !Arrays.equals(invocation.selector, REQUIRE_NON_NULL)) return null;
if (invocation.arguments == null || invocation.arguments.length == 0) return null;
Expression firstArgument = invocation.arguments[0];
if (!(firstArgument instanceof SingleNameReference)) return null;
return ((SingleNameReference) firstArgument).token;
}
if (isIf) {
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;
}
{
Expression cond = isIf ? ((IfStatement) stat).condition : ((AssertStatement) stat).assertExpression;
if (!(cond instanceof EqualExpression)) return null;
EqualExpression bin = (EqualExpression) cond;
String op = bin.operatorToString();
if (isIf) {
if (!"==".equals(op)) return null;
} else {
if (!"!=".equals(op)) return null;
}
if (!(bin.left instanceof SingleNameReference)) return null;
if (!(bin.right instanceof NullLiteral)) return null;
return ((SingleNameReference) bin.left).token;
}
}
}