lombok.eclipse.handlers.HandleTuple Maven / Gradle / Ivy
Show all versions of lombok-pg Show documentation
/*
* Copyright © 2011 Philipp Eichhorn
*
* 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.ast.AST.*;
import static lombok.core.util.Arrays.*;
import static lombok.core.util.ErrorMessages.*;
import static lombok.eclipse.handlers.Eclipse.*;
import java.util.*;
import lombok.*;
import lombok.core.util.Each;
import lombok.core.util.Is;
import lombok.eclipse.EclipseASTAdapter;
import lombok.eclipse.EclipseASTVisitor;
import lombok.eclipse.EclipseNode;
import lombok.eclipse.handlers.ast.EclipseASTMaker;
import lombok.eclipse.handlers.ast.EclipseMethod;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
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.Argument;
import org.eclipse.jdt.internal.compiler.ast.Assignment;
import org.eclipse.jdt.internal.compiler.ast.Block;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
import org.eclipse.jdt.internal.compiler.ast.SingleNameReference;
import org.eclipse.jdt.internal.compiler.ast.Statement;
import org.eclipse.jdt.internal.compiler.ast.TypeReference;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
import org.mangosdk.spi.ProviderFor;
/**
* Handles the {@code lombok.Tuple.tuple} method call for eclipse.
*/
@ProviderFor(EclipseASTVisitor.class)
public class HandleTuple extends EclipseASTAdapter {
private final Set methodNames = new HashSet();
private int withVarCounter;
@Override
public void visitCompilationUnit(final EclipseNode top, final CompilationUnitDeclaration unit) {
methodNames.clear();
withVarCounter = 0;
}
@Override
public void visitLocal(final EclipseNode localNode, final LocalDeclaration local) {
MessageSend initTupleCall = getTupelCall(localNode, local.initialization);
if (initTupleCall != null) {
final EclipseMethod method = EclipseMethod.methodOf(localNode, local);
if (method == null) {
localNode.addError(canBeUsedInBodyOfMethodsOnly("tuple"));
} else if (handle(localNode, initTupleCall)) {
methodNames.add(getMethodName(initTupleCall));
}
}
}
@Override
public void visitStatement(final EclipseNode statementNode, final Statement statement) {
if (statement instanceof Assignment) {
final Assignment assignment = (Assignment) statement;
final MessageSend leftTupleCall = getTupelCall(statementNode, assignment.lhs);
final MessageSend rightTupleCall = getTupelCall(statementNode, assignment.expression);
if ((leftTupleCall != null) && (rightTupleCall != null)) {
final EclipseMethod method = EclipseMethod.methodOf(statementNode, statement);
if (method == null) {
statementNode.addError(canBeUsedInBodyOfMethodsOnly("tuple"));
} else if (handle(statementNode, leftTupleCall, rightTupleCall)) {
methodNames.add(getMethodName(leftTupleCall));
methodNames.add(getMethodName(rightTupleCall));
}
}
}
}
private MessageSend getTupelCall(final EclipseNode node, final Expression expression) {
if (expression instanceof MessageSend) {
final MessageSend tupleCall = (MessageSend) expression;
final String methodName = getMethodName(tupleCall);
if (isMethodCallValid(node, methodName, Tuple.class, "tuple")) {
return tupleCall;
}
}
return null;
}
@Override
public void endVisitCompilationUnit(final EclipseNode top, final CompilationUnitDeclaration unit) {
for (String methodName : methodNames) {
deleteMethodCallImports(top, methodName, Tuple.class, "tuple");
}
}
public boolean handle(final EclipseNode tupleInitNode, final MessageSend initTupleCall) {
if (Is.empty(initTupleCall.arguments)) {
return true;
}
int numberOfArguments = initTupleCall.arguments.length;
List localDecls = new ArrayList();
String type = ((LocalDeclaration) tupleInitNode.get()).type.toString();
for (EclipseNode node : tupleInitNode.directUp().down()) {
if (!(node.get() instanceof LocalDeclaration)) continue;
LocalDeclaration localDecl = (LocalDeclaration) node.get();
if (!type.equals(localDecl.type.toString())) continue;
localDecls.add(localDecl);
if (localDecls.size() > numberOfArguments) {
localDecls.remove(0);
}
if (node.equals(tupleInitNode)) {
break;
}
}
if (numberOfArguments != localDecls.size()) {
tupleInitNode.addError(String.format("Argument mismatch on the right side. (required: %s found: %s)", localDecls.size(), numberOfArguments));
return false;
}
int index = 0;
for (LocalDeclaration localDecl : localDecls) {
localDecl.initialization = initTupleCall.arguments[index++];
}
return true;
}
public boolean handle(final EclipseNode tupleAssignNode, final MessageSend leftTupleCall, final MessageSend rightTupleCall) {
if (!validateTupel(tupleAssignNode, leftTupleCall, rightTupleCall)) return false;
List tempVarAssignments = new ArrayList();
List assignments = new ArrayList();
List varnames = collectVarnames(leftTupleCall.arguments);
EclipseASTMaker builder = new EclipseASTMaker(tupleAssignNode, leftTupleCall);
if (sameSize(leftTupleCall.arguments, rightTupleCall.arguments)) {
Iterator varnameIter = varnames.listIterator();
final Set blacklistedNames = new HashSet();
for (Expression arg : Each.elementIn(rightTupleCall.arguments)) {
String varname = varnameIter.next();
final boolean canUseSimpleAssignment = new SimpleAssignmentAnalyser(blacklistedNames).scan(arg);
blacklistedNames.add(varname);
if (!canUseSimpleAssignment) {
final TypeReference vartype = new VarTypeFinder(varname, tupleAssignNode.get()).scan(tupleAssignNode.top().get());
if (vartype != null) {
String tempVarname = "$tuple" + withVarCounter++;
tempVarAssignments.add(builder.build(LocalDecl(Type(vartype), tempVarname).makeFinal().withInitialization(Expr(arg)), Statement.class));
assignments.add(builder.build(Assign(Name(varname), Name(tempVarname)), Statement.class));
} else {
tupleAssignNode.addError("Lombok-pg Bug. Unable to find vartype.");
return false;
}
} else {
assignments.add(builder.build(Assign(Name(varname), Expr(arg)), Statement.class));
}
}
} else {
final TypeReference vartype = new VarTypeFinder(varnames.get(0), tupleAssignNode.get()).scan(tupleAssignNode.top().get());
if (vartype != null) {
String tempVarname = "$tuple" + withVarCounter++;
tempVarAssignments.add(builder.build(LocalDecl(Type(vartype).withDimensions(1), tempVarname).makeFinal().withInitialization(Expr(rightTupleCall.arguments[0])), Statement.class));
int arrayIndex = 0;
for (String varname : varnames) {
assignments.add(builder.build(Assign(Name(varname), ArrayRef(Name(tempVarname), Number(arrayIndex++))), Statement.class));
}
}
}
tempVarAssignments.addAll(assignments);
tryToInjectStatements(tupleAssignNode, tupleAssignNode.get(), tempVarAssignments);
return true;
}
private boolean validateTupel(final EclipseNode tupleAssignNode, final MessageSend leftTupleCall, final MessageSend rightTupleCall) {
if (!sameSize(leftTupleCall.arguments, rightTupleCall.arguments) && (rightTupleCall.arguments.length != 1)) {
tupleAssignNode.addError("The left and right hand side of the assignment must have the same amount of arguments or"
+ " must have one array-type argument for the tuple assignment to work.");
return false;
}
if (!containsOnlyNames(leftTupleCall.arguments)) {
tupleAssignNode.addError("Only variable names are allowed as arguments of the left hand side in a tuple assignment.");
return false;
}
return true;
}
private void tryToInjectStatements(final EclipseNode node, final ASTNode nodeThatUsesTupel, final List statementsToInject) {
EclipseNode parent = node;
ASTNode statementThatUsesTupel = nodeThatUsesTupel;
while ((!(parent.directUp().get() instanceof AbstractMethodDeclaration)) && (!(parent.directUp().get() instanceof Block))) {
parent = parent.directUp();
statementThatUsesTupel = parent.get();
}
Statement statement = (Statement) statementThatUsesTupel;
EclipseNode grandParent = parent.directUp();
ASTNode block = grandParent.get();
if (block instanceof Block) {
((Block) block).statements = injectStatements(((Block) block).statements, statement, statementsToInject);
} else if (block instanceof AbstractMethodDeclaration) {
((AbstractMethodDeclaration) block).statements = injectStatements(((AbstractMethodDeclaration) block).statements, statement, statementsToInject);
} else {
// this would be odd but what the hell
return;
}
grandParent.rebuild();
}
private static Statement[] injectStatements(final Statement[] statements, final Statement statement, final List withCallStatements) {
final List newStatements = new ArrayList();
for (Statement stat : statements) {
if (stat == statement) {
newStatements.addAll(withCallStatements);
} else newStatements.add(stat);
}
return newStatements.toArray(new Statement[newStatements.size()]);
}
private List collectVarnames(final Expression[] expressions) {
List varnames = new ArrayList();
if (expressions != null) for (Expression expression : expressions) {
varnames.add(new String(((SingleNameReference) expression).token));
}
return varnames;
}
private boolean containsOnlyNames(final Expression[] expressions) {
if (expressions != null) for (Expression expression : expressions) {
if (!(expression instanceof SingleNameReference)) {
return false;
}
}
return true;
}
/**
* Look for the type of a variable in the scope of the given expression.
*
* {@link VarTypeFinder#scan(com.sun.source.tree.Tree, Void) VarTypeFinder.scan(Tree, Void)} will return the type of
* a variable in the scope of the given expression.
*/
@RequiredArgsConstructor
private static class VarTypeFinder extends ASTVisitor {
private final String varname;
private final ASTNode expr;
private boolean lockVarname;
private TypeReference vartype;
public TypeReference scan(final ASTNode astNode) {
if (astNode instanceof CompilationUnitDeclaration) {
((CompilationUnitDeclaration) astNode).traverse(this, (CompilationUnitScope) null);
} else if (astNode instanceof MethodDeclaration) {
((MethodDeclaration) astNode).traverse(this, (ClassScope) null);
} else {
astNode.traverse(this, null);
}
return vartype;
}
@Override
public boolean visit(final LocalDeclaration localDeclaration, final BlockScope scope) {
return visit(localDeclaration);
}
@Override
public boolean visit(final FieldDeclaration fieldDeclaration, final MethodScope scope) {
return visit(fieldDeclaration);
}
@Override
public boolean visit(final Argument argument, final BlockScope scope) {
return visit(argument);
}
@Override
public boolean visit(final Argument argument, final ClassScope scope) {
return visit(argument);
}
@Override
public boolean visit(final Assignment assignment, final BlockScope scope) {
if ((expr != null) && (expr.equals(assignment))) {
lockVarname = true;
}
return true;
}
public boolean visit(final AbstractVariableDeclaration variableDeclaration) {
if (!lockVarname && varname.equals(new String(variableDeclaration.name))) {
vartype = variableDeclaration.type;
}
return true;
}
}
/**
* Look for variable names that would break a simple assignment after transforming the tuple.
*
* If {@link SimpleAssignmentAnalyser#scan(com.sun.source.tree.Tree, Void) AssignmentAnalyser.scan(Tree, Void)}
* return {@code null} or {@code true} everything is fine, otherwise a temporary assignment is needed.
*/
@RequiredArgsConstructor
private static class SimpleAssignmentAnalyser extends ASTVisitor {
private final Set blacklistedVarnames;
private boolean canUseSimpleAssignment;
public boolean scan(final ASTNode astNode) {
canUseSimpleAssignment = true;
if (astNode instanceof CompilationUnitDeclaration) {
((CompilationUnitDeclaration) astNode).traverse(this, (CompilationUnitScope) null);
} else if (astNode instanceof MethodDeclaration) {
((MethodDeclaration) astNode).traverse(this, (ClassScope) null);
} else {
astNode.traverse(this, null);
}
return canUseSimpleAssignment;
}
@Override
public boolean visit(final SingleNameReference singleNameReference, final BlockScope scope) {
if (blacklistedVarnames.contains(new String(singleNameReference.token))) {
canUseSimpleAssignment = false;
return false;
}
return true;
}
}
}