blob: 698c1f1192629121527e439840a1ada08707e949 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.refactoring.java.plugins;
import com.sun.source.tree.*;
import static com.sun.source.tree.Tree.Kind.*;
import com.sun.source.util.*;
import com.sun.tools.javac.tree.JCTree;
import java.util.*;
import javax.lang.model.element.*;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.netbeans.api.java.source.Comment;
import org.netbeans.api.java.source.ElementUtilities;
import org.netbeans.api.java.source.GeneratorUtilities;
import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.api.java.source.TreePathHandle;
import org.netbeans.api.java.source.TreeUtilities;
import org.netbeans.api.java.source.WorkingCopy;
import org.netbeans.api.java.source.support.ErrorAwareTreePathScanner;
import org.netbeans.api.java.source.support.ErrorAwareTreeScanner;
import org.netbeans.modules.refactoring.api.Problem;
import org.netbeans.modules.refactoring.java.api.JavaRefactoringUtils;
import org.netbeans.modules.refactoring.java.spi.RefactoringVisitor;
import org.openide.filesystems.FileUtil;
import org.openide.util.NbBundle;
import org.openide.util.Pair;
import static org.netbeans.modules.refactoring.java.plugins.Bundle.*;
import org.netbeans.modules.refactoring.java.spi.ToPhaseException;
/**
*
* @author Ralph Ruijs
*/
public class InlineMethodTransformer extends RefactoringVisitor {
private static final EnumSet<Tree.Kind> LITERALS = EnumSet.of(LAMBDA_EXPRESSION, PRIMITIVE_TYPE, PREFIX_INCREMENT, PREFIX_DECREMENT, BITWISE_COMPLEMENT, LEFT_SHIFT, RIGHT_SHIFT, UNSIGNED_RIGHT_SHIFT, MULTIPLY_ASSIGNMENT, DIVIDE_ASSIGNMENT, REMAINDER_ASSIGNMENT, PLUS_ASSIGNMENT, MINUS_ASSIGNMENT, LEFT_SHIFT_ASSIGNMENT, RIGHT_SHIFT_ASSIGNMENT, UNSIGNED_RIGHT_SHIFT_ASSIGNMENT, AND_ASSIGNMENT, XOR_ASSIGNMENT, OR_ASSIGNMENT, INT_LITERAL, LONG_LITERAL, FLOAT_LITERAL, DOUBLE_LITERAL, BOOLEAN_LITERAL, CHAR_LITERAL, STRING_LITERAL, NULL_LITERAL, ERRONEOUS);
private Trees trees;
private MethodTree methodTree;
private boolean hasParameters;
private Problem problem;
private HashMap<Tree, Tree> original2Translated;
private final Deque<Map<Tree, List<StatementTree>>> newStatsQueue;
private final Deque<Map<Tree, Tree>> translateQueue;
private final LinkedList<String> definedIds;
private final HashSet<Element> changedParamters;
private final TreePathHandle tph;
public InlineMethodTransformer(TreePathHandle tph) {
this.tph = tph;
newStatsQueue = new LinkedList<>();
translateQueue = new LinkedList<>();
definedIds = new LinkedList<>();
changedParamters = new HashSet<>();
}
@Override
public void setWorkingCopy(WorkingCopy workingCopy) throws ToPhaseException {
SourceUtils.forceSource(workingCopy, tph.getFileObject());
super.setWorkingCopy(workingCopy);
}
public Problem getProblem() {
return problem;
}
@Override
public Tree visitCompilationUnit(CompilationUnitTree node, Element p) {
try {
GeneratorUtilities genUtils = GeneratorUtilities.get(workingCopy);
genUtils.importComments(node, node);
trees = workingCopy.getTrees();
methodTree = (MethodTree) trees.getTree(p);
hasParameters = methodTree.getParameters().size() > 0;
return super.visitCompilationUnit(node, p);
} finally {
trees = null;
methodTree = null;
hasParameters = false;
}
}
@Override
public Tree visitClass(ClassTree node, Element p) {
final TreePath classPath = getCurrentPath();
ClassTree classTree = node;
for (Tree member : classTree.getMembers()) {
TreePath memberPath = new TreePath(classPath, member);
Element element = workingCopy.getTrees().getElement(memberPath);
if (p.equals(element)) {
classTree = make.removeClassMember(classTree, member);
break;
}
}
if (classPath.getParentPath().getLeaf().getKind() != Tree.Kind.COMPILATION_UNIT) {
original2Translated.put(node, classTree);
return super.visitClass(node, p);
}
original2Translated = new HashMap<>();
Tree value = super.visitClass(node, p);
classTree = (ClassTree) workingCopy.getTreeUtilities().translate(classTree, original2Translated);
rewrite(node, classTree);
return value;
}
@Override
public Tree visitBlock(BlockTree node, Element p) {
newStatsQueue.add(new HashMap<Tree, List<StatementTree>>());
translateQueue.add(new HashMap<Tree, Tree>());
int nrOfIds = definedIds.size();
Tree value = super.visitBlock(node, p);
while(definedIds.size() > nrOfIds) {
definedIds.pop();
}
Map<Tree, List<StatementTree>> newStatementsForBlock = newStatsQueue.pollLast();
Map<Tree, Tree> original2TranslatedForBlock = translateQueue.pollLast();
if (!newStatementsForBlock.isEmpty()) {
List<StatementTree> newStatementList = new LinkedList<>();
for (StatementTree statementTree : node.getStatements()) {
List<StatementTree> stats = newStatementsForBlock.get(statementTree);
if (stats != null) {
newStatementList.addAll(stats);
}
Tree tree = original2TranslatedForBlock.get(statementTree);
if(tree == null || tree.getKind() != EMPTY_STATEMENT ||
((JCTree)tree).pos >= 0) {
newStatementList.add(statementTree);
}
}
BlockTree newBlock = make.Block(newStatementList, node.isStatic());
if(!original2TranslatedForBlock.isEmpty()) {
newBlock = (BlockTree) workingCopy.getTreeUtilities().translate(newBlock, original2TranslatedForBlock);
}
original2Translated.put(node, newBlock);
}
return value;
}
@Override
public Tree visitMethod(MethodTree node, Element p) {
if (workingCopy.getTreeUtilities().isSynthetic(getCurrentPath())) {
return node;
}
return super.visitMethod(node, p);
}
@Override
public Tree visitMemberReference(MemberReferenceTree node, Element methodElement) {
final TreePath methodInvocationPath = getCurrentPath();
Element el = trees.getElement(methodInvocationPath);
// TypeMirror tm = workingCopy.getTrees().getTypeMirror(getCurrentPath());
if (el != null && el.getKind() == ElementKind.METHOD && methodElement.equals(el)) {
List<? extends VariableTree> parameters = methodTree.getParameters();
BlockTree body = methodTree.getBody();
final HashMap<Tree, Tree> original2TranslatedBody = new HashMap<>();
scanForNameClash(methodInvocationPath, body, methodElement, original2TranslatedBody);
if (problem != null && problem.isFatal()) {
return node;
}
body = (BlockTree) workingCopy.getTreeUtilities().translate(body, original2TranslatedBody);
LambdaExpressionTree lambda = make.LambdaExpression(parameters, body);
rewrite(node, lambda);
}
return super.visitMemberReference(node, methodElement);
}
@Override
@NbBundle.Messages({
"ERR_InlineMethodMultipleReturn=Cannot inline method with multiple return statements at {0}.",
"WRN_InlineMethodMultipleLines=The method body has multiple statements, will not update reference at {0}. The sources will not compile after refactoring!",
"ERR_InlineMethodNoLastReturn=The last statement is not a return statement, cannot inline at {0}."})
public Tree visitMethodInvocation(MethodInvocationTree node, Element methodElement) {
Tree value = super.visitMethodInvocation(node, methodElement);
final TreePath methodInvocationPath = getCurrentPath();
Element el = trees.getElement(methodInvocationPath);
if (el != null && el.getKind() == ElementKind.METHOD && methodElement.equals(el)) {
GeneratorUtilities genUtils = GeneratorUtilities.get(workingCopy);
ExecutableElement method = (ExecutableElement) el;
List<StatementTree> newStatementList = new LinkedList<>();
final TypeElement bodyEnclosingTypeElement = workingCopy.getElementUtilities().enclosingTypeElement(methodElement);
TreePath findEnclosingClass = JavaRefactoringUtils.findEnclosingClass(workingCopy, methodInvocationPath, true, true, true, true, true);
final Element invocationEnclosingTypeElement = workingCopy.getTrees().getElement(findEnclosingClass);
boolean inSameClass = bodyEnclosingTypeElement.equals(invocationEnclosingTypeElement);
boolean inStatic = !methodElement.getModifiers().contains(Modifier.STATIC) && isInStaticContext(methodInvocationPath);
boolean invokeOnInstance = false;
MethodInvocationTree invTree = (MethodInvocationTree)methodInvocationPath.getLeaf();
if (!methodElement.getModifiers().contains(Modifier.STATIC) && invTree.getMethodSelect().getKind() == Tree.Kind.MEMBER_SELECT) {
invokeOnInstance = true;
MemberSelectTree mst = (MemberSelectTree)invTree.getMethodSelect();
if (mst.getExpression().getKind() == Tree.Kind.IDENTIFIER) {
// check for this.method(). super. qualifier should be omitted if and only if the symbols is not shadowed in subclass.
Name n = ((IdentifierTree)mst.getExpression()).getName();
invokeOnInstance = !(n.contentEquals("this")); // NOI18N
}
}
TreePath statementPath = findCorrespondingStatement(methodInvocationPath);
StatementTree statementTree = (StatementTree) statementPath.getLeaf();
BlockTree body = methodTree.getBody();
final HashMap<Tree, Tree> original2TranslatedBody = new HashMap<>();
scanForNameClash(methodInvocationPath, body, methodElement, original2TranslatedBody);
if (problem != null && problem.isFatal()) {
return node;
}
TreeUtilities treeUtilities = workingCopy.getTreeUtilities();
body = (BlockTree) treeUtilities.translate(body, original2TranslatedBody);
final Boolean[] multiplereturn = {null};
new ErrorAwareTreeScanner<Void, Void>() {
@Override
public Void scan(Tree node, Void p) {
if(Boolean.TRUE == multiplereturn[0]) {
return null;
}
return super.scan(node, p);
}
@Override
public Void visitNewClass(NewClassTree node, Void p) {
return null; //not interested in return from innerclass
}
@Override
public Void visitReturn(ReturnTree node, Void p) {
multiplereturn[0] = multiplereturn[0] == null ? Boolean.FALSE : Boolean.TRUE;
return super.visitReturn(node, p);
}
}.scan(body, null);
if (hasParameters) {
final HashMap<Tree, Tree> original2TranslatedBody2 = new HashMap<>();
replaceParametersWithArguments(original2TranslatedBody2, method, node, methodInvocationPath, body, newStatementList);
body = (BlockTree) treeUtilities.translate(body, original2TranslatedBody2);
}
TreePath methodPath = trees.getPath(methodElement);
TreePath bodyPath = new TreePath(methodPath, methodTree.getBody());
Scope scope = workingCopy.getTrees().getScope(methodInvocationPath);
ExpressionTree methodSelect = node.getMethodSelect();
if (methodSelect.getKind() == Tree.Kind.MEMBER_SELECT) {
methodSelect = ((MemberSelectTree) methodSelect).getExpression();
} else {
methodSelect = null;
}
// Add statements up to the last statement (return)
for (int i = 0; i < body.getStatements().size() - 1; i++) {
StatementTree statement = body.getStatements().get(i);
if ((!inSameClass || invokeOnInstance) || inStatic) {
statement = (StatementTree) fixReferences(statement, new TreePath(bodyPath, statement), method, scope, methodSelect);
if (!inSameClass) {
statement = genUtils.importFQNs(statement);
}
}
newStatementList.add(statement);
}
Tree parent = methodInvocationPath.getParentPath().getLeaf();
Tree grandparent = null;
Tree methodInvocation;
if (parent.getKind() != Tree.Kind.EXPRESSION_STATEMENT) {
methodInvocation = node;
} else {
grandparent = methodInvocationPath.getParentPath().getParentPath().getLeaf();
switch (grandparent.getKind()) {
case FOR_LOOP:
case ENHANCED_FOR_LOOP:
case WHILE_LOOP:
case DO_WHILE_LOOP:
case IF:
methodInvocation = grandparent;
break;
default:
methodInvocation = parent;
}
}
Tree lastStatement;
if (body.getStatements().size() > 0) {
lastStatement = body.getStatements().get(body.getStatements().size() - 1);
} else {
lastStatement = null;
}
if (lastStatement != null && ((!inSameClass || invokeOnInstance) || inStatic)) {
lastStatement = fixReferences(lastStatement, new TreePath(bodyPath, lastStatement), method, scope, methodSelect);
if (!inSameClass) {
lastStatement = genUtils.importFQNs(lastStatement);
}
}
if(Boolean.TRUE == multiplereturn[0]) {
if(parent.getKind() == RETURN) {
Map<Tree, List<StatementTree>> addedStatementsForBlock = newStatsQueue.getLast();
Map<Tree, Tree> translateMap = translateQueue.getLast();
List<StatementTree> stats = addedStatementsForBlock.get(statementTree);
if(stats == null) {
addedStatementsForBlock.put(statementTree, stats = new LinkedList<>());
}
stats.addAll(newStatementList);
genUtils.copyComments(methodInvocation, lastStatement, true);
List<Comment> comments = workingCopy.getTreeUtilities().getComments(methodInvocation, false);
for (Comment comment : comments) {
make.addComment(lastStatement, Comment.create(comment.style(), comment.getText()), false);
}
translateMap.put(parent, lastStatement);
return value;
} else {
SourcePositions positions = workingCopy.getTrees().getSourcePositions();
long startPosition = positions.getStartPosition(workingCopy.getCompilationUnit(), node);
long lineNumber = workingCopy.getCompilationUnit().getLineMap().getLineNumber(startPosition);
String source = FileUtil.getFileDisplayName(workingCopy.getFileObject()) + ':' + lineNumber;
problem = JavaPluginUtils.chainProblems(problem,
new Problem(true, ERR_InlineMethodMultipleReturn(source)));
return value;
}
}
Map<Tree, Tree> translateMap = translateQueue.size() > 0 ? translateQueue.getLast() : original2Translated;
lastStatement = translateLastStatement(genUtils, parent, grandparent, newStatementList, lastStatement, node, methodInvocationPath, method, translateMap);
Element element = workingCopy.getTrees().getElement(statementPath);
if (element != null && element.getKind() == ElementKind.FIELD) {
if (!newStatementList.isEmpty()) {
SourcePositions positions = workingCopy.getTrees().getSourcePositions();
long startPosition = positions.getStartPosition(workingCopy.getCompilationUnit(), node);
long lineNumber = workingCopy.getCompilationUnit().getLineMap().getLineNumber(startPosition);
String source = FileUtil.getFileDisplayName(workingCopy.getFileObject()) + ':' + lineNumber;
problem = JavaPluginUtils.chainProblems(problem,
new Problem(false, WRN_InlineMethodMultipleLines(source)));
} else if (lastStatement instanceof StatementTree) {
SourcePositions positions = workingCopy.getTrees().getSourcePositions();
long startPosition = positions.getStartPosition(workingCopy.getCompilationUnit(), node);
long lineNumber = workingCopy.getCompilationUnit().getLineMap().getLineNumber(startPosition);
String source = FileUtil.getFileDisplayName(workingCopy.getFileObject()) + ':' + lineNumber;
problem = JavaPluginUtils.chainProblems(problem,
new Problem(true, ERR_InlineMethodNoLastReturn(source)));
return value;
} else {
genUtils.copyComments(methodInvocation, lastStatement, true);
List<Comment> comments = workingCopy.getTreeUtilities().getComments(methodInvocation, false);
for (Comment comment : comments) {
make.addComment(lastStatement, Comment.create(comment.style(), comment.getText()), false);
}
rewrite(methodInvocation, lastStatement);
}
} else {
if (lastStatement instanceof StatementTree) {
if(parent.getKind() == EXPRESSION_STATEMENT) {
} else {
SourcePositions positions = workingCopy.getTrees().getSourcePositions();
long startPosition = positions.getStartPosition(workingCopy.getCompilationUnit(), node);
long lineNumber = workingCopy.getCompilationUnit().getLineMap().getLineNumber(startPosition);
String source = FileUtil.getFileDisplayName(workingCopy.getFileObject()) + ':' + lineNumber;
problem = JavaPluginUtils.chainProblems(problem,
new Problem(true, ERR_InlineMethodNoLastReturn(source)));
return value;
}
}
Map<Tree, List<StatementTree>> addedStatementsForBlock = newStatsQueue.getLast();
List<StatementTree> stats = addedStatementsForBlock.get(statementTree);
if(stats == null) {
addedStatementsForBlock.put(statementTree, stats = new LinkedList<>());
}
stats.addAll(newStatementList);
genUtils.copyComments(methodInvocation, lastStatement, true);
List<Comment> comments = workingCopy.getTreeUtilities().getComments(methodInvocation, false);
for (Comment comment : comments) {
make.addComment(lastStatement, Comment.create(comment.style(), comment.getText()), false);
}
translateMap.put(methodInvocation, lastStatement == null? make.EmptyStatement(): lastStatement);
}
}
return value;
}
private boolean isInStaticContext(TreePath methodInvocationPath) {
TreePath parent = methodInvocationPath.getParentPath();
while (parent != null) {
if (parent.getLeaf().getKind() == Tree.Kind.METHOD) {
break;
}
parent = parent.getParentPath();
}
return parent != null && ((MethodTree) parent.getLeaf()).getModifiers().getFlags().contains(Modifier.STATIC);
}
@NbBundle.Messages({
"WRN_InlineNotAccessible={0} is not accessible from {1}."})
private Tree fixReferences(Tree tree, TreePath treePath, final ExecutableElement method, final Scope scope, final ExpressionTree methodSelect) {
final HashMap<Tree, Tree> orig2trans = new HashMap<>();
final ElementUtilities elementUtilities = workingCopy.getElementUtilities();
final TypeElement bodyEnclosingTypeElement = elementUtilities.enclosingTypeElement(method);
ErrorAwareTreePathScanner<Void, ExecutableElement> idScan = new ErrorAwareTreePathScanner<Void, ExecutableElement>() {
@Override
public Void visitIdentifier(IdentifierTree node, ExecutableElement p) {
TreePath currentPath = getCurrentPath();
if (currentPath.getParentPath().getLeaf().getKind() == Tree.Kind.MEMBER_SELECT) {
return super.visitIdentifier(node, p); // Already checked by visitMemberSelect
}
Element el = trees.getElement(currentPath);
if (el != null) {
TypeMirror targetType = el.getEnclosingElement().asType();
if (methodSelect != null
&& targetType != null && targetType.getKind() == TypeKind.DECLARED
&& el.getEnclosingElement() != method
&& !workingCopy.getTrees().isAccessible(scope, el, (DeclaredType)targetType)) {
problem = JavaPluginUtils.chainProblems(problem, new Problem(false, WRN_InlineNotAccessible(el, targetType)));
}
TypeElement invocationEnclosingTypeElement = elementUtilities.enclosingTypeElement(el);
if (el.getKind() != ElementKind.LOCAL_VARIABLE && bodyEnclosingTypeElement.equals(invocationEnclosingTypeElement)) {
if (el.getModifiers().contains(Modifier.STATIC)) {
Tree newTree = make.QualIdent(el);
orig2trans.put(node, newTree);
} else if(methodSelect != null) {
Tree newTree = make.MemberSelect(methodSelect, el);
orig2trans.put(node, newTree);
}
}
}
return super.visitIdentifier(node, p);
}
@Override
public Void visitMethodInvocation(MethodInvocationTree node, ExecutableElement p) {
TreePath currentPath = getCurrentPath();
if (currentPath.getParentPath().getLeaf().getKind() == Tree.Kind.MEMBER_SELECT) {
return super.visitMethodInvocation(node, p); // Already checked by visitMemberSelect
}
Element el = trees.getElement(currentPath);
if (el != null && el != p) {
DeclaredType declaredType = workingCopy.getTypes().getDeclaredType(scope.getEnclosingClass());
if (methodSelect != null
&& el.getEnclosingElement() != method
&& !workingCopy.getTrees().isAccessible(scope, el, declaredType)) {
problem = JavaPluginUtils.chainProblems(problem, new Problem(false, WRN_InlineNotAccessible(el, declaredType)));
}
TypeElement invocationEnclosingTypeElement = elementUtilities.enclosingTypeElement(el);
if (bodyEnclosingTypeElement.equals(invocationEnclosingTypeElement)) {
if (el.getModifiers().contains(Modifier.STATIC)) {
Tree newTree = make.QualIdent(el);
orig2trans.put(node.getMethodSelect(), newTree);
} else {
ExpressionTree methodInvocationSelect = node.getMethodSelect();
if (methodInvocationSelect.getKind() == Tree.Kind.MEMBER_SELECT) {
ExpressionTree expression = ((MemberSelectTree) methodInvocationSelect).getExpression();
String isThis = expression.toString();
if (isThis.equals("this") || isThis.endsWith(".this")) { //NOI18N
if (methodSelect == null) { // We must be in Inner class.
MemberSelectTree memberSelect = make.MemberSelect(workingCopy.getTreeUtilities().parseExpression(bodyEnclosingTypeElement.getSimpleName() + ".this", new SourcePositions[1]), el);
orig2trans.put(node, memberSelect);
} else {
orig2trans.put(expression, methodSelect);
}
} else if(methodSelect != null) {
Tree newTree = make.MemberSelect(methodSelect, el);
orig2trans.put(node, newTree);
}
} else if(methodSelect != null) {
Tree newTree = make.MemberSelect(methodSelect, el);
orig2trans.put(methodInvocationSelect, newTree);
}
}
}
}
if(el != null && el == p) {
return null;
} else {
return super.visitMethodInvocation(node, p);
}
}
@Override
public Void visitMemberSelect(MemberSelectTree node, ExecutableElement p) {
TreePath currentPath = getCurrentPath();
Element el = trees.getElement(currentPath);
if (el != null && el.getKind() != ElementKind.PACKAGE) {
DeclaredType declaredType = workingCopy.getTypes().getDeclaredType(scope.getEnclosingClass());
if (methodSelect != null
&& el.getEnclosingElement() != method
&& !workingCopy.getTrees().isAccessible(scope, el, declaredType)) {
problem = JavaPluginUtils.chainProblems(problem, new Problem(false, WRN_InlineNotAccessible(el, declaredType)));
}
TypeElement invocationEnclosingTypeElement = elementUtilities.enclosingTypeElement(el);
if (bodyEnclosingTypeElement.equals(invocationEnclosingTypeElement)) {
if (el.getModifiers().contains(Modifier.STATIC)) {
Tree newTree = make.QualIdent(el);
orig2trans.put(node, newTree);
} else {
ExpressionTree expression = node.getExpression();
String isThis = expression.toString();
if (isThis.equals("this") || isThis.endsWith(".this")) { //NOI18N
if (methodSelect == null) { // We must be in Inner class.
MemberSelectTree memberSelect = make.MemberSelect(workingCopy.getTreeUtilities().parseExpression(bodyEnclosingTypeElement.getSimpleName() + ".this", new SourcePositions[1]), el);
orig2trans.put(node, memberSelect);
} else {
orig2trans.put(expression, methodSelect);
}
} else {
if (methodSelect != null) {
Tree newTree = make.MemberSelect(methodSelect, el);
orig2trans.put(node, newTree);
}
}
}
}
}
return super.visitMemberSelect(node, p);
}
};
idScan.scan(treePath, method);
Tree result = workingCopy.getTreeUtilities().translate(tree, orig2trans);
return result;
}
private void scanForNameClash(final TreePath methodInvocationPath, final Tree body, Element p, final HashMap<Tree, Tree> original2TranslatedBody) {
Element resolved = tph.getElementHandle().resolve(workingCopy);
final CompilationUnitTree compilationUnitTree = workingCopy.getTrees().getPath(resolved).getCompilationUnit();
final LinkedList<Pair<Element, String>> renames = new LinkedList<>();
// Scan the body and look for name clashes
ErrorAwareTreeScanner<Void, ExecutableElement> nameClashScanner = new ErrorAwareTreeScanner<Void, ExecutableElement>() {
@Override
public Void visitVariable(VariableTree node, ExecutableElement p) {
TreePath path = trees.getPath(compilationUnitTree, node);
if (path != null) {
Element variable = trees.getElement(path);
if (variable != null && !(variable.getKind() == ElementKind.PARAMETER && p.getParameters().contains((VariableElement) variable))) {
String varName = node.getName().toString();
String uniqueName = JavaPluginUtils.makeNameUnique(workingCopy,
workingCopy.getTrees().getScope(methodInvocationPath), varName, definedIds);
if(uniqueName == null ? varName != null : !uniqueName.equals(varName)) {
original2TranslatedBody.put(node, make.setLabel(node, uniqueName));
renames.add(Pair.of(variable, uniqueName));
definedIds.add(uniqueName);
} else {
definedIds.add(varName);
}
}
}
return super.visitVariable(node, p);
}
@Override
public Void visitAssignment(AssignmentTree node, ExecutableElement p) {
checkParameter(node.getVariable());
return super.visitAssignment(node, p);
}
@Override
public Void visitCompoundAssignment(CompoundAssignmentTree node, ExecutableElement p) {
checkParameter(node.getVariable());
return super.visitCompoundAssignment(node, p);
}
@Override
public Void visitUnary(UnaryTree node, ExecutableElement p) {
checkParameter(node.getExpression());
return super.visitUnary(node, p);
}
private void checkParameter(Tree node) {
TreePath path = trees.getPath(compilationUnitTree, node);
if (path != null) {
Element variable = trees.getElement(path);
if(variable != null && variable.getKind() == ElementKind.PARAMETER) {
changedParamters.add(variable);
}
}
}
};
nameClashScanner.scan(body, (ExecutableElement) p);
ErrorAwareTreeScanner<Void, Pair<Element, String>> idScan = new ErrorAwareTreeScanner<Void, Pair<Element, String>>() {
@Override
public Void visitIdentifier(IdentifierTree node, Pair<Element, String> p) {
TreePath path = trees.getPath(compilationUnitTree, node);
Element el = null;
if(path != null) {
el = trees.getElement(path);
}
if (p.first().equals(el)) {
original2TranslatedBody.put(node, make.setLabel(node, p.second()));
}
return super.visitIdentifier(node, p);
}
};
for (Pair<Element, String> pair : renames) {
idScan.scan(body, pair);
}
}
private TreePath findCorrespondingStatement(TreePath methodInvocationPath) {
TreePath statementPath = methodInvocationPath;
WHILE: while (statementPath != null) {
if (statementPath.getParentPath() != null) {
switch (statementPath.getParentPath().getLeaf().getKind()) {
case BLOCK:
case CLASS:
break WHILE;
}
}
statementPath = statementPath.getParentPath();
}
return statementPath;
}
@SuppressWarnings("string-comparison-by-operator")
private void replaceParametersWithArguments(final HashMap<Tree, Tree> original2TranslatedBody, ExecutableElement el, MethodInvocationTree node, final TreePath methodInvocationPath, BlockTree body, List<StatementTree> newVars) {
Element resolved = tph.getElementHandle().resolve(workingCopy);
final CompilationUnitTree compilationUnitTree = workingCopy.getTrees().getPath(resolved).getCompilationUnit();
final LinkedList<Pair<Element, String>> renames = new LinkedList<>();
ErrorAwareTreeScanner<Void, Pair<VariableElement, ExpressionTree>> idScan = new ErrorAwareTreeScanner<Void, Pair<VariableElement, ExpressionTree>>() {
@Override
public Void visitIdentifier(IdentifierTree node, Pair<VariableElement, ExpressionTree> p) {
TreePath currentPath = trees.getPath(compilationUnitTree, node);
Element el = null;
if(currentPath != null) {
el = trees.getElement(currentPath);
}
if (p.first().equals(el)) {
original2TranslatedBody.put(node, p.second());
}
return super.visitIdentifier(node, p);
}
};
for (int i = 0; i < el.getParameters().size(); i++) {
VariableElement element = el.getParameters().get(i);
if(el.isVarArgs() && el.getParameters().size() == i+1) {
ArrayType asType = (ArrayType) element.asType();
Tree type = make.Type(asType.getComponentType());
Tree arraytype = make.Type(asType);
List<ExpressionTree> arguments = new LinkedList<>();
if(node.getArguments().size() > i) {
arguments.addAll(node.getArguments().subList(i, node.getArguments().size()));
}
String varName = element.getSimpleName().toString();
String uniqueName = JavaPluginUtils.makeNameUnique(workingCopy,
workingCopy.getTrees().getScope(methodInvocationPath), varName, definedIds);
if(uniqueName == null ? varName != null : !uniqueName.equals(varName)) {
renames.add(Pair.of((Element)element, uniqueName));
definedIds.add(uniqueName);
} else {
definedIds.add(varName);
}
newVars.add(make.Variable(make.Modifiers(element.getModifiers()), (uniqueName == null ? varName != null : !uniqueName.equals(varName))? uniqueName : varName, arraytype, make.NewArray(type, Collections.EMPTY_LIST, arguments)));
} else {
ExpressionTree argument = node.getArguments().get(i);
if(!translateQueue.isEmpty() && translateQueue.getLast().containsKey(argument)) {
argument = (ExpressionTree) translateQueue.getLast().get(argument);
}
if(LITERALS.contains(argument.getKind()) && changedParamters.contains(element)) {
Tree arraytype = make.Type(element.asType());
String varName = element.getSimpleName().toString();
String uniqueName = JavaPluginUtils.makeNameUnique(workingCopy,
workingCopy.getTrees().getScope(methodInvocationPath), varName, definedIds);
if(uniqueName == null ? varName != null : !uniqueName.equals(varName)) {
renames.add(Pair.of((Element)element, uniqueName));
definedIds.add(uniqueName);
} else {
definedIds.add(varName);
}
newVars.add(make.Variable(make.Modifiers(element.getModifiers()), (uniqueName == null ? varName != null : !uniqueName.equals(varName))? uniqueName : varName, arraytype, argument));
} else {
final Pair<VariableElement, ExpressionTree> pair = Pair.of(element, argument);
idScan.scan(body, pair);
}
}
}
ErrorAwareTreeScanner<Void, Pair<Element, String>> renameScan = new ErrorAwareTreeScanner<Void, Pair<Element, String>>() {
@Override
public Void visitIdentifier(IdentifierTree node, Pair<Element, String> p) {
TreePath path = trees.getPath(compilationUnitTree, node);
Element el = null;
if(path != null) {
el = trees.getElement(path);
}
if (p.first().equals(el)) {
original2TranslatedBody.put(node, make.setLabel(node, p.second()));
}
return super.visitIdentifier(node, p);
}
};
for (Pair<Element, String> pair : renames) {
renameScan.scan(body, pair);
}
}
@NbBundle.Messages(
"WRN_InlineChangeReturn=Unsafe -- the return expression is not used in {0}.")
private Tree translateLastStatement(GeneratorUtilities genUtils, Tree parent, Tree grandparent, List<StatementTree> newStatementList, Tree lastStatement, Tree node, TreePath location, Element method, Map<Tree, Tree> translateMap) {
Tree result = lastStatement;
TreeDuplicator duplicator = new TreeDuplicator(make, genUtils);
if (parent.getKind() != Tree.Kind.EXPRESSION_STATEMENT) {
if (result != null) {
switch (result.getKind()) {
case EXPRESSION_STATEMENT:
result = ((ExpressionStatementTree) result).getExpression();
break;
case RETURN:
result = ((ReturnTree) result).getExpression();
break;
default:
// TODO: Problem, need an expression, but last statement is not an expression.
}
if(result instanceof ExpressionTree) {
boolean needsParentheses = OperatorPrecedence.needsParentheses(location, method, (ExpressionTree) result, workingCopy);
if(needsParentheses) {
result = workingCopy.getTreeMaker().Parenthesized((ExpressionTree) result);
}
}
}
} else {
if (result != null) {
if (result.getKind() == Tree.Kind.RETURN) {
ExpressionTree returnExpression = ((ReturnTree) result).getExpression();
/*
* Allowed expressions in expressionstatement:
* Assignment
* PreIncrementExpression
* PreDecrementExpression
* PostIncrementExpression
* PostDecrementExpression
* MethodInvocation
* ClassInstanceCreationExpression
*/
switch (returnExpression.getKind()) {
case ASSIGNMENT:
case PREFIX_INCREMENT:
case PREFIX_DECREMENT:
case POSTFIX_DECREMENT:
case POSTFIX_INCREMENT:
case METHOD_INVOCATION:
case NEW_CLASS:
case NEW_ARRAY:
result = make.ExpressionStatement(returnExpression);
break;
default:
result = make.EmptyStatement();
SourcePositions positions = workingCopy.getTrees().getSourcePositions();
long startPosition = positions.getStartPosition(workingCopy.getCompilationUnit(), node);
long lineNumber = workingCopy.getCompilationUnit().getLineMap().getLineNumber(startPosition);
String source = FileUtil.getFileDisplayName(workingCopy.getFileObject()) + ':' + lineNumber;
problem = JavaPluginUtils.chainProblems(problem,
new Problem(false, WRN_InlineChangeReturn(source)));
break;
}
}
}
switch (grandparent.getKind()) {
case FOR_LOOP: {
ForLoopTree forLoopTree = (ForLoopTree) grandparent;
if(translateMap.containsKey(grandparent)) {
Tree newTree = translateMap.get(grandparent);
if(newTree.getKind() == FOR_LOOP) {
forLoopTree = (ForLoopTree) newTree;
}
}
StatementTree statement = forLoopTree.getStatement();
if (statement == parent) {
addResultToStatementList(result, newStatementList);
statement = make.Block(newStatementList, false);
newStatementList.clear();
}
List<? extends ExpressionStatementTree> updates = forLoopTree.getUpdate();
List<ExpressionStatementTree> newUpdates = new LinkedList<ExpressionStatementTree>();
for (ExpressionStatementTree update : updates) {
if (update == parent) {
addResultToStatementList(result, newStatementList);
for (StatementTree statementTree1 : newStatementList) {
if (statementTree1.getKind() == Tree.Kind.EXPRESSION_STATEMENT) {
newUpdates.add((ExpressionStatementTree) statementTree1);
} else {
// TODO: Problem forloop list only accepts expression statements.
break;
}
}
newStatementList.clear();
} else {
newUpdates.add(update);
}
}
List<? extends StatementTree> initializers = forLoopTree.getInitializer();
List<StatementTree> newInitializers = new LinkedList<StatementTree>();
for (StatementTree initiazer : initializers) {
if (initiazer == parent) {
addResultToStatementList(result, newStatementList);
for (StatementTree statementTree1 : newStatementList) {
if (statementTree1.getKind() == Tree.Kind.EXPRESSION_STATEMENT) {
newInitializers.add((ExpressionStatementTree) statementTree1);
} else if (statementTree1.getKind() == Tree.Kind.VARIABLE) {
newInitializers.add((VariableTree) statementTree1);
} else {
// TODO: Problem forloop list only accepts expression statements.
break;
}
}
newStatementList.clear();
} else {
newInitializers.add(initiazer);
}
}
result = make.ForLoop(newInitializers, forLoopTree.getCondition(), newUpdates, statement);
}
break;
case ENHANCED_FOR_LOOP: {
EnhancedForLoopTree enhancedForLoopTree = (EnhancedForLoopTree) grandparent;
StatementTree statement = enhancedForLoopTree.getStatement();
if (statement == parent) {
addResultToStatementList(result, newStatementList);
statement = make.Block(newStatementList, false);
newStatementList.clear();
}
result = make.EnhancedForLoop(enhancedForLoopTree.getVariable(), enhancedForLoopTree.getExpression(), statement);
}
break;
case WHILE_LOOP: {
WhileLoopTree whileLoopTree = (WhileLoopTree) grandparent;
StatementTree statement = whileLoopTree.getStatement();
if (statement == parent) {
addResultToStatementList(result, newStatementList);
statement = make.Block(newStatementList, false);
newStatementList.clear();
}
result = make.WhileLoop(whileLoopTree.getCondition(), statement);
}
break;
case DO_WHILE_LOOP: {
DoWhileLoopTree doWhileLoopTree = (DoWhileLoopTree) grandparent;
StatementTree statement = doWhileLoopTree.getStatement();
if (statement == parent) {
addResultToStatementList(result, newStatementList);
statement = make.Block(newStatementList, false);
newStatementList.clear();
}
result = make.DoWhileLoop(doWhileLoopTree.getCondition(), statement);
}
break;
case IF: {
IfTree ifTree = (IfTree) grandparent;
StatementTree thenStatement = ifTree.getThenStatement();
if (thenStatement == parent) {
addResultToStatementList(result, newStatementList);
thenStatement = make.Block(newStatementList, false);
newStatementList.clear();
}
StatementTree elseStatement = ifTree.getElseStatement();
if (elseStatement == parent) {
addResultToStatementList(result, newStatementList);
elseStatement = make.Block(newStatementList, false);
newStatementList.clear();
}
result = make.If(ifTree.getCondition(), thenStatement, elseStatement);
}
break;
}
}
if(result != null) {
result = duplicator.duplicate(result);
genUtils.copyComments(lastStatement, result, true);
genUtils.copyComments(lastStatement, result, false);
}
return result;
}
private void addResultToStatementList(Tree result, List<StatementTree> newStatementList) {
if (result != null) {
newStatementList.add((StatementTree) result);
}
}
}