All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lombok.eclipse.handlers.HandleTuple Maven / Gradle / Ivy

There is a newer version: 0.11.3
Show newest version
/*
 * 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; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy