lombok.javac.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.javac.handlers;
import static lombok.ast.AST.*;
import static lombok.core.util.ErrorMessages.*;
import static lombok.javac.handlers.Javac.*;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import lombok.*;
import lombok.javac.JavacASTAdapter;
import lombok.javac.JavacASTVisitor;
import lombok.javac.JavacNode;
import lombok.javac.handlers.ast.JavacASTMaker;
import lombok.javac.handlers.ast.JavacMethod;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAssign;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCCase;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCIdent;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import org.mangosdk.spi.ProviderFor;
@ProviderFor(JavacASTVisitor.class)
public class HandleTuple extends JavacASTAdapter {
private final Set methodNames = new HashSet();
private int withVarCounter;
@Override
public void visitCompilationUnit(final JavacNode top, final JCCompilationUnit unit) {
methodNames.clear();
withVarCounter = 0;
}
@Override
public void visitLocal(final JavacNode localNode, final JCVariableDecl local) {
JCMethodInvocation initTupleCall = getTupelCall(localNode, local.init);
if (initTupleCall != null) {
final JavacMethod method = JavacMethod.methodOf(localNode, local);
if (method == null) {
localNode.addError(canBeUsedInBodyOfMethodsOnly("tuple"));
} else if (handle(localNode, initTupleCall)) {
methodNames.add(initTupleCall.meth.toString());
}
}
}
@Override
public void visitStatement(final JavacNode statementNode, final JCTree statement) {
if (statement instanceof JCAssign) {
final JCAssign assignment = (JCAssign) statement;
final JCMethodInvocation leftTupleCall = getTupelCall(statementNode, assignment.lhs);
final JCMethodInvocation rightTupleCall = getTupelCall(statementNode, assignment.rhs);
if ((leftTupleCall != null) && (rightTupleCall != null)) {
final JavacMethod method = JavacMethod.methodOf(statementNode, statement);
if (method == null) {
statementNode.addError(canBeUsedInBodyOfMethodsOnly("tuple"));
} else if (handle(statementNode, leftTupleCall, rightTupleCall)) {
methodNames.add(leftTupleCall.meth.toString());
methodNames.add(rightTupleCall.meth.toString());
}
}
}
}
private JCMethodInvocation getTupelCall(final JavacNode node, final JCExpression expression) {
if (expression instanceof JCMethodInvocation) {
final JCMethodInvocation tupleCall = (JCMethodInvocation) expression;
final String methodName = tupleCall.meth.toString();
if (isMethodCallValid(node, methodName, Tuple.class, "tuple")) {
return tupleCall;
}
}
return null;
}
@Override
public void endVisitCompilationUnit(final JavacNode top, final JCCompilationUnit unit) {
for (String methodName : methodNames) {
deleteMethodCallImports(top, methodName, Tuple.class, "tuple");
}
}
public boolean handle(final JavacNode tupleInitNode, final JCMethodInvocation initTupleCall) {
if (initTupleCall.args.isEmpty()) {
return true;
}
int numberOfArguments = initTupleCall.args.size();
List localDecls = List. nil();
String type = ((JCVariableDecl) tupleInitNode.get()).vartype.toString();
for (JavacNode node : tupleInitNode.directUp().down()) {
if (!(node.get() instanceof JCVariableDecl)) continue;
JCVariableDecl localDecl = (JCVariableDecl) node.get();
if (!type.equals(localDecl.vartype.toString())) continue;
localDecls = localDecls.append(localDecl);
if (localDecls.size() > numberOfArguments) {
localDecls.head = localDecls.tail.head;
}
if (node.equals(tupleInitNode)) {
break;
}
}
if (numberOfArguments != localDecls.length()) {
tupleInitNode.addError(String.format("Argument mismatch on the right side. (required: %s found: %s)", localDecls.length(), numberOfArguments));
return false;
}
int index = 0;
for (JCVariableDecl localDecl : localDecls) {
localDecl.init = initTupleCall.args.get(index++);
}
return true;
}
public boolean handle(final JavacNode tupleAssignNode, final JCMethodInvocation leftTupleCall, final JCMethodInvocation rightTupleCall) {
if (!validateTupel(tupleAssignNode, leftTupleCall, rightTupleCall)) return false;
ListBuffer tempVarAssignments = ListBuffer.lb();
ListBuffer assignments = ListBuffer.lb();
List varnames = collectVarnames(leftTupleCall.args);
JavacASTMaker builder = new JavacASTMaker(tupleAssignNode, leftTupleCall);
if (leftTupleCall.args.length() == rightTupleCall.args.length()) {
Iterator varnameIter = varnames.listIterator();
final Set blacklistedNames = new HashSet();
for (JCExpression arg : rightTupleCall.args) {
String varname = varnameIter.next();
final Boolean canUseSimpleAssignment = new SimpleAssignmentAnalyser(blacklistedNames).scan(arg, null);
blacklistedNames.add(varname);
if ((canUseSimpleAssignment != null) && !canUseSimpleAssignment) {
final JCExpression vartype = new VarTypeFinder(varname, tupleAssignNode.get()).scan(tupleAssignNode.top().get(), null);
if (vartype != null) {
String tempVarname = "$tuple" + withVarCounter++;
tempVarAssignments.append(builder.build(LocalDecl(Type(vartype), tempVarname).makeFinal().withInitialization(Expr(arg)), JCStatement.class));
assignments.append(builder.build(Assign(Name(varname), Name(tempVarname)), JCStatement.class));
} else {
tupleAssignNode.addError("Lombok-pg Bug. Unable to find vartype.");
return false;
}
} else {
assignments.append(builder.build(Assign(Name(varname), Expr(arg)), JCStatement.class));
}
}
} else {
final JCExpression vartype = new VarTypeFinder(varnames.get(0), tupleAssignNode.get()).scan(tupleAssignNode.top().get(), null);
if (vartype != null) {
String tempVarname = "$tuple" + withVarCounter++;
tempVarAssignments.append(builder.build(LocalDecl(Type(vartype).withDimensions(1), tempVarname).makeFinal().withInitialization(Expr(rightTupleCall.args.head)), JCStatement.class));
int arrayIndex = 0;
for (String varname : varnames) {
assignments.append(builder.build(Assign(Name(varname), ArrayRef(Name(tempVarname), Number(arrayIndex++))), JCStatement.class));
}
}
}
tempVarAssignments.appendList(assignments);
tryToInjectStatements(tupleAssignNode, tupleAssignNode.get(), tempVarAssignments.toList());
return true;
}
private boolean validateTupel(final JavacNode tupleAssignNode, final JCMethodInvocation leftTupleCall, final JCMethodInvocation rightTupleCall) {
if ((leftTupleCall.args.length() != rightTupleCall.args.length()) && (rightTupleCall.args.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.args)) {
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 JavacNode node, final JCTree nodeThatUsesTupel, final List statementsToInject) {
JavacNode parent = node;
JCTree statementThatUsesTupel = nodeThatUsesTupel;
while (!(statementThatUsesTupel instanceof JCStatement)) {
parent = parent.directUp();
statementThatUsesTupel = parent.get();
}
JCStatement statement = (JCStatement) statementThatUsesTupel;
JavacNode grandParent = parent.directUp();
JCTree block = grandParent.get();
if (block instanceof JCBlock) {
((JCBlock) block).stats = injectStatements(((JCBlock) block).stats, statement, statementsToInject);
} else if (block instanceof JCCase) {
((JCCase) block).stats = injectStatements(((JCCase) block).stats, statement, statementsToInject);
} else if (block instanceof JCMethodDecl) {
((JCMethodDecl) block).body.stats = injectStatements(((JCMethodDecl) block).body.stats, statement, statementsToInject);
} else {
// this would be odd odd but what the hell
return;
}
grandParent.rebuild();
}
private List injectStatements(final List statements, final JCStatement statement, final List statementsToInject) {
final ListBuffer newStatements = ListBuffer.lb();
for (JCStatement stat : statements) {
if (stat == statement) {
newStatements.appendList(statementsToInject);
} else newStatements.append(stat);
}
return newStatements.toList();
}
private List collectVarnames(final List expressions) {
ListBuffer varnames = ListBuffer.lb();
for (JCExpression expression : expressions) {
varnames.append(expression.toString());
}
return varnames.toList();
}
private boolean containsOnlyNames(final List expressions) {
for (JCExpression expression : expressions) {
if (!(expression instanceof JCIdent)) {
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 TreeScanner {
private final String varname;
private final JCTree expr;
private boolean lockVarname;
@Override
public JCExpression visitVariable(final VariableTree node, final Void p) {
if (!lockVarname && varname.equals(node.getName().toString())) {
return (JCExpression) node.getType();
}
return null;
}
@Override
public JCExpression visitAssignment(final AssignmentTree node, final Void p) {
if ((expr != null) && (expr.equals(node))) {
lockVarname = true;
}
return super.visitAssignment(node, p);
}
@Override
public JCExpression reduce(final JCExpression r1, final JCExpression r2) {
return (r1 != null) ? r1 : r2;
}
}
/**
* 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 TreeScanner {
private final Set blacklistedVarnames;
@Override
public Boolean visitMemberSelect(final MemberSelectTree node, final Void p) {
return true;
}
@Override
public Boolean visitIdentifier(final IdentifierTree node, final Void p) {
return !blacklistedVarnames.contains(node.getName().toString());
}
@Override
public Boolean reduce(final Boolean r1, final Boolean r2) {
return !r1 && r2;
}
}
}