blob: 21f09477c816348760a4137f6fb1122be850c999 [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.doctree.DocCommentTree;
import com.sun.source.doctree.DocTree;
import com.sun.source.tree.*;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import org.netbeans.api.java.source.support.ErrorAwareTreeScanner;
import com.sun.source.util.Trees;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.lang.model.element.*;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import org.netbeans.api.java.source.*;
import org.netbeans.modules.refactoring.api.MoveRefactoring;
import org.netbeans.modules.refactoring.api.Problem;
import org.netbeans.modules.refactoring.java.RefactoringUtils;
import org.netbeans.modules.refactoring.java.api.JavaMoveMembersProperties;
import org.netbeans.modules.refactoring.java.api.JavaMoveMembersProperties.Visibility;
import org.netbeans.modules.refactoring.java.api.JavaRefactoringUtils;
import org.netbeans.modules.refactoring.java.spi.RefactoringVisitor;
import org.netbeans.modules.refactoring.java.spi.ToPhaseException;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.NbBundle;
import org.openide.util.Pair;
/**
*
* @author Ralph Ruijs
*/
public class MoveMembersTransformer extends RefactoringVisitor {
private static final int NOPOS = -2;
private static final Set<Modifier> ALL_ACCESS_MODIFIERS = EnumSet.of(Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC);
private Problem problem;
private Collection<? extends TreePathHandle> allElements;
private final Visibility visibility;
private final HashMap<TreePathHandle, Boolean> usageOutsideOfPackage;
private final HashMap<TreePathHandle, Boolean> usageOutsideOfType;
private final TreePathHandle targetHandle;
private final boolean delegate;
private final boolean deprecate;
private final boolean updateJavadoc;
public MoveMembersTransformer(MoveRefactoring refactoring) {
allElements = refactoring.getRefactoringSource().lookupAll(TreePathHandle.class);
JavaMoveMembersProperties properties = refactoring.getContext().lookup(JavaMoveMembersProperties.class);
properties = properties == null ? new JavaMoveMembersProperties(allElements.toArray(new TreePathHandle[allElements.size()])) : properties;
visibility = properties.getVisibility();
usageOutsideOfPackage = new HashMap<>();
usageOutsideOfType = new HashMap<>();
for (TreePathHandle treePathHandle : allElements) {
usageOutsideOfPackage.put(treePathHandle, Boolean.FALSE);
}
targetHandle = refactoring.getTarget().lookup(TreePathHandle.class);
delegate = properties.isDelegate();
deprecate = properties.isAddDeprecated();
updateJavadoc = properties.isUpdateJavaDoc();
}
@Override
public void setWorkingCopy(WorkingCopy workingCopy) throws ToPhaseException {
for (TreePathHandle element : allElements) {
SourceUtils.forceSource(workingCopy, element.getFileObject());
}
super.setWorkingCopy(workingCopy);
}
public Problem getProblem() {
return problem;
}
@Override
public Tree visitMemberSelect(MemberSelectTree node, Element target) {
if (changeIfMatch(getCurrentPath(), node, target)) {
return node;
}
return super.visitMemberSelect(node, target);
}
@Override
public Tree visitIdentifier(IdentifierTree node, Element target) {
if (changeIfMatch(getCurrentPath(), node, target)) {
return node;
}
return super.visitIdentifier(node, target);
}
@Override
public Tree visitMethodInvocation(MethodInvocationTree node, Element target) {
if (changeIfMatch(getCurrentPath(), node, target)) {
return node;
}
return super.visitMethodInvocation(node, target);
}
@Override
public Tree visitMemberReference(MemberReferenceTree node, Element target) {
if (changeIfMatch(getCurrentPath(), node, target)) {
return node;
}
return super.visitMemberReference(node, target);
}
private boolean changeIfMatch(TreePath currentPath, Tree node, final Element target) throws IllegalArgumentException {
Element el = workingCopy.getTrees().getElement(currentPath);
if (el == null) {
return false;
}
TreePathHandle elementBeingMoved = isElementBeingMoved(el);
if (elementBeingMoved != null) {
final FileObject folder = targetHandle.getFileObject().getParent();
final CompilationUnitTree compilationUnit = currentPath.getCompilationUnit();
checkForUsagesOutsideOfPackage(folder, compilationUnit, elementBeingMoved);
checkForUsagesOutsideOfType(target, currentPath, elementBeingMoved);
if (node instanceof MethodInvocationTree) {
if (!delegate) {
changeMethodInvocation((ExecutableElement) el, (MethodInvocationTree) node, currentPath, target);
}
} else if (node instanceof IdentifierTree) {
changeIdentifier(el, (IdentifierTree) node, currentPath, target);
} else if (node instanceof MemberSelectTree) {
changeMemberSelect(el, (MemberSelectTree) node, currentPath, target);
} else if (node.getKind() == Tree.Kind.MEMBER_REFERENCE) {
changeMemberRefer(el, (MemberReferenceTree) node, currentPath, target);
}
return true;
} else {
return false;
}
}
@Override
public Tree visitClass(ClassTree node, Element target) {
insertIfMatch(getCurrentPath(), node, target);
return super.visitClass(node, target);
}
@Override
public Tree visitVariable(VariableTree node, Element target) {
if (removeIfMatch(getCurrentPath(), target)) {
return node;
} else {
return super.visitVariable(node, target);
}
}
@Override
public Tree visitMethod(MethodTree node, Element target) {
if (removeIfMatch(getCurrentPath(), target)) {
return node;
} else {
return super.visitMethod(node, target);
}
}
private void changeMemberRefer(Element el, final MemberReferenceTree node, TreePath currentPath, final Element target) {
if (el.getModifiers().contains(Modifier.STATIC)) {
Tree oldT = node.getQualifierExpression();
Tree newT = make.QualIdent(make.setLabel(make.QualIdent(target), target.getSimpleName()).toString());
rewrite(oldT, newT);
} 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(false, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "WRN_NoAccessor", source))); //NOI18N
}
}
private void changeMemberSelect(Element el, final MemberSelectTree node, TreePath currentPath, final Element target) {
if (el.getModifiers().contains(Modifier.STATIC)) {
ExpressionTree expression = node.getExpression();
TreePath enclosingClassPath = JavaRefactoringUtils.findEnclosingClass(workingCopy, currentPath, true, true, true, true, true);
Element enclosingElement = workingCopy.getTrees().getElement(enclosingClassPath);
if (target.equals(enclosingElement)) {
IdentifierTree newIdt = make.Identifier(node.getIdentifier());
rewrite(node, newIdt);
} else {
ExpressionTree newIdent = make.QualIdent(target);
rewrite(expression, newIdent);
}
} else {
TreePath enclosingClassPath = JavaRefactoringUtils.findEnclosingClass(workingCopy, currentPath, true, true, true, true, false);
Scope scope = workingCopy.getTrees().getScope(currentPath);
Element enclosingElement = workingCopy.getTrees().getElement(enclosingClassPath);
if (target.equals(enclosingElement)
&& node.getKind() == Tree.Kind.MEMBER_SELECT
&& !scope.getEnclosingMethod().getModifiers().contains(Modifier.STATIC)) {
IdentifierTree newIdt = make.Identifier(((MemberSelectTree) node).getIdentifier());
rewrite(node, newIdt);
} else {
Iterable<? extends Element> vars = workingCopy.getElementUtilities().getLocalMembersAndVars(scope, new ElementUtilities.ElementAcceptor() {
@Override
public boolean accept(Element e, TypeMirror type) { // Type will always be null
return workingCopy.getTypes().isSameType(e.asType(), target.asType()) && isElementBeingMoved(e) == null;
}
});
if (!vars.iterator().hasNext()) {
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, NbBundle.getMessage(MoveMembersRefactoringPlugin.class, "WRN_NoAccessor", source))); //NOI18N
} else {
Element localVar = vars.iterator().next();
MemberSelectTree selectTree = (MemberSelectTree) node;
Tree it = selectTree.getExpression();
Tree newIt = make.Identifier(localVar);
if (it != null && newIt != null) {
rewrite(it, newIt);
}
}
}
}
}
private void changeIdentifier(Element el, final IdentifierTree node, TreePath currentPath, final Element target) {
if (el.getModifiers().contains(Modifier.STATIC)) {
IdentifierTree it = (IdentifierTree) node;
TreePath enclosingClassPath = JavaRefactoringUtils.findEnclosingClass(workingCopy, currentPath, true, true, true, true, true);
Element enclosingElement = workingCopy.getTrees().getElement(enclosingClassPath);
if (enclosingElement == null || !enclosingElement.equals(target)) {
ExpressionTree qualIdent = make.QualIdent(target);
MemberSelectTree memberSelect = make.MemberSelect(qualIdent, it.getName().toString());
rewrite(it, memberSelect);
}
} else {
Scope scope = workingCopy.getTrees().getScope(currentPath);
// TODO Maybe move to configuration
Iterable<? extends Element> vars = workingCopy.getElementUtilities().getLocalMembersAndVars(scope, new ElementUtilities.ElementAcceptor() {
@Override
public boolean accept(Element e, TypeMirror type) { // Type will always be null
return workingCopy.getTypes().isSameType(e.asType(), target.asType());
}
});
if (!vars.iterator().hasNext()) {
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, NbBundle.getMessage(MoveMembersTransformer.class, "WRN_NoAccessor", source))); //NOI18N
} else {
Tree it;
Tree newIt;
Element localVar = vars.iterator().next();
IdentifierTree variableTree = (IdentifierTree) node;
it = node;
newIt = make.setLabel(node, localVar.getSimpleName().toString() + "." + variableTree.getName().toString()); //NOI18N
if (it != null && newIt != null) {
rewrite(it, newIt);
}
}
}
}
/**
* Changing a method invocation to refer to the new location.
*
* Steps: 1. Check if we need to remove a parameter from the invocation. 2.
* Check if it is a Static method. 2.1 Change methodSelect 2.2 Translate
* method arguments
*
* 3. Find Parameter or local var to use 3.1 Create problem if no accessor
* 4. Check if it needs an argument for local accessors 4.1 Check if it van
* be the memberselect
*
* 5. Create a new method invocation
*
* @param el
* @param node
* @param currentPath
* @param target
*/
private void changeMethodInvocation(final ExecutableElement el, final MethodInvocationTree node, final TreePath currentPath, final Element target) {
rewrite(node, createMethodInvocationTree(el, node, currentPath, target, false));
}
private MethodInvocationTree createMethodInvocationTree(final ExecutableElement el, final MethodInvocationTree node, final TreePath currentPath, final Element target, boolean delegate) {
TreePath enclosingClassPath = JavaRefactoringUtils.findEnclosingClass(workingCopy, currentPath, true, true, true, true, true);
Element enclosingElement = workingCopy.getTrees().getElement(enclosingClassPath);
final LinkedList<ExpressionTree> arguments = new LinkedList(node.getArguments());
ExpressionTree newMethodSelect;
if (el.getModifiers().contains(Modifier.STATIC)) {
if (node.getMethodSelect().getKind() == Tree.Kind.MEMBER_SELECT) {
if (enclosingElement != null && enclosingElement.equals(target)) {
newMethodSelect = make.Identifier(((MemberSelectTree) node.getMethodSelect()).getIdentifier());
} else {
newMethodSelect = make.MemberSelect(make.QualIdent(target), ((MemberSelectTree) node.getMethodSelect()).getIdentifier().toString());
}
} else { // if (methodSelect.getKind() == Tree.Kind.IDENTIFIER) {
if (enclosingElement == null || !enclosingElement.equals(target)) {
newMethodSelect = make.MemberSelect(make.QualIdent(target), el);
} else {
newMethodSelect = node.getMethodSelect();
}
}
} else {
final ExpressionTree selectExpression;
int removedIndex = -1;
List<? extends VariableElement> parameters = el.getParameters();
for (int i = 0; i < parameters.size(); i++) {
VariableElement variableElement = parameters.get(i);
if (workingCopy.getTypes().isSameType(variableElement.asType(), target.asType())) {
removedIndex = i;
break;
}
}
if (removedIndex != -1) {
selectExpression = node.getArguments().get(removedIndex);
} else {
Scope scope = workingCopy.getTrees().getScope(currentPath);
Iterable<? extends Element> vars = workingCopy.getElementUtilities().getLocalMembersAndVars(scope, new ElementUtilities.ElementAcceptor() {
@Override
public boolean accept(Element e, TypeMirror type) { // Type will always be null
return workingCopy.getTypes().isSameType(e.asType(), target.asType());
}
});
if (!vars.iterator().hasNext()) {
if(delegate) {
problem = JavaPluginUtils.chainProblems(problem, new Problem(false, NbBundle.getMessage(MoveMembersTransformer.class, "WRN_NoAccessor", NbBundle.getMessage(MoveMembersTransformer.class, "TXT_DelegatingMethod"))));
} 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(false, NbBundle.getMessage(MoveMembersTransformer.class, "WRN_NoAccessor", source))); //NOI18N
}
selectExpression = null;
} else {
Element localVar = vars.iterator().next();
selectExpression = make.Identifier(localVar);
}
}
if (selectExpression == null) {
newMethodSelect = node.getMethodSelect();
} else {
if (node.getMethodSelect().getKind() == Tree.Kind.MEMBER_SELECT) {
MemberSelectTree selectTree = (MemberSelectTree) node.getMethodSelect();
boolean inStatic = false;
TreePath blockPath = currentPath;
while(blockPath != null) {
if(blockPath.getLeaf().getKind() == Tree.Kind.BLOCK) {
TreePath parentPath = blockPath.getParentPath();
if(parentPath != null && parentPath.getLeaf().getKind() == Tree.Kind.METHOD) {
MethodTree enclosingMethod = (MethodTree) parentPath.getLeaf();
if(enclosingMethod.getModifiers().getFlags().contains(Modifier.STATIC)) {
inStatic = true;
}
} else if(parentPath != null && parentPath.getLeaf().getKind() == Tree.Kind.CLASS) {
inStatic = true;
}
}
blockPath = blockPath.getParentPath();
}
if (enclosingElement.equals(target)) {
if(inStatic) {
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, NbBundle.getMessage(MoveMembersTransformer.class, "WRN_NoAccessor", source))); //NOI18N
newMethodSelect = node.getMethodSelect();
} else {
newMethodSelect = make.Identifier(((MemberSelectTree) node.getMethodSelect()).getIdentifier());
}
} else {
newMethodSelect = make.MemberSelect(selectExpression, selectTree.getIdentifier());
}
} else { // if (node.getMethodSelect().getKind() == Tree.Kind.IDENTIFIER) {
IdentifierTree variableTree = (IdentifierTree) node.getMethodSelect();
newMethodSelect = make.MemberSelect(selectExpression, variableTree.getName().toString());
}
}
if (removedIndex != -1) {
arguments.remove(removedIndex);
}
TypeMirror sourceType = workingCopy.getTrees().getTypeMirror(enclosingClassPath);
ErrorAwareTreeScanner<Boolean, TypeMirror> needsArgumentScanner = new ErrorAwareTreeScanner<Boolean, TypeMirror>() {
// Logic is a copy from #insertIfMatch
@Override
public Boolean visitMemberSelect(MemberSelectTree node, TypeMirror source) {
String isThis = node.getExpression().toString();
if (isThis.equals("this") || isThis.endsWith(".this")) { //NOI18N
TreePath thisPath = new TreePath(currentPath, node);
Element el = workingCopy.getTrees().getElement(thisPath);
if (el != null && isElementBeingMoved(el) != null) {
return false;
}
}
return super.visitMemberSelect(node, source);
}
@Override
public Boolean visitIdentifier(IdentifierTree node, TypeMirror source) {
TreePath thisPath = new TreePath(currentPath, node);
Element el = workingCopy.getTrees().getElement(thisPath);
if (el != null && isElementBeingMoved(el) == null) {
String isThis = node.toString();
// TODO: Check for super keyword. if super is used, but it is not overloaded, there is no problem. else warning.
if (isThis.equals("this") || isThis.endsWith(".this")) { //NOI18N
if (!el.getModifiers().contains(Modifier.STATIC)) {
return true;
}
} else {
if (el.getKind() == ElementKind.METHOD || el.getKind() == ElementKind.FIELD || el.getKind() == ElementKind.ENUM) {
TypeElement elType = workingCopy.getElementUtilities().enclosingTypeElement(el);
if (elType != null && workingCopy.getTypes().isSubtype(source, elType.asType())) {
if (!el.getModifiers().contains(Modifier.STATIC)) {
return true;
}
}
}
}
}
return super.visitIdentifier(node, source);
}
@Override
public Boolean reduce(Boolean r1, Boolean r2) {
return (r1 == Boolean.TRUE || r2 == Boolean.TRUE);
}
};
if (workingCopy.getTrees().getTree(el) == null) {
System.err.println("!!!");
}
Boolean needsArgument = needsArgumentScanner.scan(workingCopy.getTrees().getTree(el).getBody(), sourceType);
if (needsArgument == Boolean.TRUE) {
ExpressionTree newArgument;
if (enclosingElement.equals(target) && node.getMethodSelect().getKind() == Tree.Kind.MEMBER_SELECT) {
newArgument = ((MemberSelectTree) node.getMethodSelect()).getExpression();
} else {
newArgument = workingCopy.getTreeUtilities().parseExpression("this", new SourcePositions[1]); //NOI18N
}
if (el.isVarArgs()) {
arguments.add(arguments.size() - 1, newArgument);
} else {
arguments.add(newArgument);
}
}
}
List<ExpressionTree> typeArguments = new LinkedList<ExpressionTree>((List<? extends ExpressionTree>)node.getTypeArguments());
Element returnType = workingCopy.getTypes().asElement(el.getReturnType());
if(returnType != null && returnType.getKind() == ElementKind.TYPE_PARAMETER) {
TypeParameterElement typeParameterElement = (TypeParameterElement) returnType;
if(typeParameterElement.getGenericElement().getKind() != ElementKind.METHOD) {
ExpressionTree methodSelect = node.getMethodSelect();
if(methodSelect.getKind() == Tree.Kind.MEMBER_SELECT) {
VariableElement element = (VariableElement) workingCopy.getTrees().getElement(new TreePath(currentPath, ((MemberSelectTree)methodSelect).getExpression()));
if (element != null) {
TypeMirror asType = element.asType();
if(asType.getKind() == TypeKind.DECLARED) {
List<? extends TypeMirror> typeArguments1 = ((DeclaredType)asType).getTypeArguments();
for (TypeMirror typeMirror : typeArguments1) {
typeArguments.add((ExpressionTree)make.Type(typeMirror));
}
}
}
} else {
ClassTree classTree = workingCopy.getTrees().getTree((TypeElement)enclosingElement);
for (TypeParameterTree typeParameterTree : classTree.getTypeParameters()) {
if(typeParameterTree.getName().contentEquals(el.getReturnType().toString())) {
typeArguments.add((ExpressionTree)make.Type(typeParameterTree.getName().toString()));
}
}
}
}
}
if(!typeArguments.isEmpty() &&
newMethodSelect.getKind() == Tree.Kind.IDENTIFIER) {
newMethodSelect = make.MemberSelect(make.Identifier("this"), ((IdentifierTree)newMethodSelect).getName());
}
return make.MethodInvocation(typeArguments, newMethodSelect, arguments);
}
private void checkForUsagesOutsideOfPackage(final FileObject folder, final CompilationUnitTree compilationUnit, TreePathHandle elementBeingMoved) {
if (!RefactoringUtils.getPackageName(folder).equals(
RefactoringUtils.getPackageName(compilationUnit))) {
usageOutsideOfPackage.put(elementBeingMoved, Boolean.TRUE);
}
}
private void checkForUsagesOutsideOfType(final Element target, TreePath currentPath, TreePathHandle elementBeingMoved) {
final Types types = workingCopy.getTypes();
TypeMirror targetType = target.asType();
TreePath enclosingPath = JavaRefactoringUtils.findEnclosingClass(workingCopy, currentPath, true, true, true, true, false);
Element enclosingEl = null;
if(enclosingPath != null) {
enclosingEl = workingCopy.getTrees().getElement(enclosingPath);
}
if(enclosingEl != null) {
TypeMirror enclosingType = enclosingEl.asType();
if(!(enclosedBy(targetType, enclosingType) || enclosedBy(enclosingType, targetType)) && !types.isSameType(enclosingType, targetType)) {
usageOutsideOfType.put(elementBeingMoved, Boolean.TRUE);
}
} else {
usageOutsideOfType.put(elementBeingMoved, Boolean.TRUE);
}
}
private void insertIfMatch(TreePath currentPath, ClassTree node, final Element target) throws IllegalArgumentException {
Element el = workingCopy.getTrees().getElement(currentPath);
if (el == null) {
return;
}
if (el.equals(target)) {
ClassTree newClassTree = node;
for (TreePathHandle tph : allElements) {
final TreePath resolvedPath = tph.resolve(workingCopy);
if (resolvedPath == null) {
// XXX - should report a problem ?
continue;
}
Tree member = resolvedPath.getLeaf();
Tree newMember = null;
Element resolvedElement = workingCopy.getTrees().getElement(resolvedPath);
if (resolvedElement == null) {
continue;
}
final GeneratorUtilities genUtils = GeneratorUtilities.get(workingCopy);
genUtils.importComments(member, resolvedPath.getCompilationUnit());
// Make a new Method tree
if (member.getKind() == Tree.Kind.METHOD) {
// Change Modifiers
final MethodTree methodTree = (MethodTree) member;
ExecutableElement method = (ExecutableElement) resolvedElement;
ModifiersTree modifiers = changeModifiers(genUtils.importFQNs(methodTree.getModifiers()), usageOutsideOfPackage.get(tph) == Boolean.TRUE, usageOutsideOfType.get(tph) == Boolean.TRUE);
// Find and remove a usable parameter
final List<? extends VariableTree> parameters = methodTree.getParameters();
LinkedList<VariableTree> newParameters;
VariableTree removedParameter = null;
boolean isStatic = method.getModifiers().contains(Modifier.STATIC);
newParameters = new LinkedList<VariableTree>();
for (int i = 0; i < parameters.size(); i++) {
VariableTree variableTree = parameters.get(i);
TypeMirror type = workingCopy.getTrees().getTypeMirror(TreePath.getPath(resolvedPath, variableTree));
if (!isStatic && removedParameter == null && type != null && workingCopy.getTypes().isSameType(type, target.asType())) {
removedParameter = variableTree;
} else {
newParameters.add(genUtils.importFQNs(variableTree));
}
}
// Scan the body and fix references
BlockTree body = methodTree.getBody();
final TreePath bodyPath = new TreePath(resolvedPath, body);
final Trees trees = workingCopy.getTrees();
final Map<ExpressionTree, ExpressionTree> fqns = new HashMap<ExpressionTree, ExpressionTree>();
ErrorAwareTreeScanner<Void, Void> fqnScan = new ErrorAwareTreeScanner<Void, Void>() {
@Override
public Void visitIdentifier(IdentifierTree node, Void p) {
TreePath treePath = trees.getPath(bodyPath.getCompilationUnit(), node);
if(!JavaPluginUtils.isSyntheticPath(workingCopy, treePath)) {
// FIXME: path may skip some intermediate types which bring the identifier.
Element el = trees.getElement(treePath);
if (el != null) {
fqns.put(node, make.Identifier(el));
}
}
return super.visitIdentifier(node, p);
}
};
fqnScan.scan(body, null);
body = (BlockTree) workingCopy.getTreeUtilities().translate(body, fqns);
// Remove the parameter and change it to the keyword this
final Map<ExpressionTree, ExpressionTree> original2Translated = new HashMap<ExpressionTree, ExpressionTree>();
// Add parameter and change local accessors
TreePath sourceClass = JavaRefactoringUtils.findEnclosingClass(workingCopy, resolvedPath, true, true, true, true, true);
TypeMirror sourceType = workingCopy.getTrees().getTypeMirror(sourceClass);
final String parameterName = getParameterName(sourceType, workingCopy.getTrees().getScope(bodyPath), workingCopy);
ErrorAwareTreeScanner<Boolean, TypeMirror> idScan = new ErrorAwareTreeScanner<Boolean, TypeMirror>() {
@Override
public Boolean visitMemberSelect(MemberSelectTree node, TypeMirror source) {
String isThis = node.getExpression().toString();
if (isThis.equals("this") || isThis.endsWith(".this")) { //NOI18N
TreePath currentPath = new TreePath(resolvedPath, node);
Element el = trees.getElement(currentPath);
if (el != null && isElementBeingMoved(el) != null) {
return false;
}
} else {
TreePath currentPath = new TreePath(resolvedPath, node);
Element el = trees.getElement(currentPath);
if (el != null && isElementBeingMoved(el) != null &&
el.getKind() != ElementKind.PACKAGE &&
el.getModifiers().contains(Modifier.STATIC)) {
ExpressionTree ident = make.Identifier(target);
MemberSelectTree memberSelect = make.MemberSelect(ident, el);
original2Translated.put(node, memberSelect);
}
}
return super.visitMemberSelect(node, source);
}
@Override
public Boolean visitIdentifier(IdentifierTree node, TypeMirror source) {
TreePath currentPath = new TreePath(resolvedPath, node);
Element el = trees.getElement(currentPath);
boolean result = false;
if (el != null && isElementBeingMoved(el) == null && el.getKind() != ElementKind.PACKAGE) {
TypeElement elType = workingCopy.getElementUtilities().enclosingTypeElement(el);
// TODO: Check for super keyword. if super is used, but it is not overloaded, there is no problem. else warning.
String isThis = node.toString();
if (isThis.equals("this") || isThis.endsWith(".this")) { //NOI18N
// Check for static
if (!el.getModifiers().contains(Modifier.STATIC)) {
ExpressionTree newLabel = make.setLabel(node, parameterName);
original2Translated.put(node, newLabel);
result = true;
} else {
ExpressionTree ident = make.Identifier(elType);
original2Translated.put(node, ident);
}
} else {
if (el.getKind() == ElementKind.METHOD || el.getKind() == ElementKind.FIELD || el.getKind() == ElementKind.ENUM) {
if (elType != null && workingCopy.getTypes().isSubtype(source, elType.asType())) {
if (!el.getModifiers().contains(Modifier.STATIC)) {
MemberSelectTree memberSelect = make.MemberSelect(workingCopy.getTreeUtilities().parseExpression(parameterName, new SourcePositions[1]), el);
original2Translated.put(node, memberSelect);
result = true;
} else {
ExpressionTree ident = make.Identifier(elType);
MemberSelectTree memberSelect = make.MemberSelect(ident, el);
original2Translated.put(node, memberSelect);
}
}
}
}
}
return super.visitIdentifier(node, source) == Boolean.TRUE || result;
}
@Override
public Boolean reduce(Boolean r1, Boolean r2) {
return (r1 == Boolean.TRUE || r2 == Boolean.TRUE);
}
};
boolean addParameter = idScan.scan(body, sourceType) == Boolean.TRUE;
if (removedParameter != null) {
ErrorAwareTreeScanner<Void, Pair<Element, ExpressionTree>> idScan2 = new ErrorAwareTreeScanner<Void, Pair<Element, ExpressionTree>>() {
@Override
public Void visitIdentifier(IdentifierTree node, Pair<Element, ExpressionTree> p) {
TreePath currentPath = new TreePath(resolvedPath, node);
Element el = trees.getElement(currentPath);
if (p.first().equals(el)) {
original2Translated.put(node, p.second());
}
return super.visitIdentifier(node, p);
}
};
TreePath path = new TreePath(resolvedPath, removedParameter);
Element element = trees.getElement(path);
if (element != null) {
final Pair<Element, ExpressionTree> pair = Pair.of(element, workingCopy.getTreeUtilities().parseExpression("this", new SourcePositions[1])); // NOI18N
idScan2.scan(body, pair);
}
}
body = (BlockTree) workingCopy.getTreeUtilities().translate(body, original2Translated);
if (addParameter) {
VariableTree vt = make.Variable(make.Modifiers(Collections.<Modifier>emptySet()), parameterName, make.QualIdent(sourceType.toString()), null);
if (method.isVarArgs()) {
newParameters.add(newParameters.size() - 1, vt);
} else {
newParameters.add(vt);
}
}
// Addimports
body = genUtils.importFQNs(body);
List<TypeParameterTree> typeParameters = new LinkedList<TypeParameterTree>(methodTree.getTypeParameters());
if(method.getReturnType().getKind() == TypeKind.TYPEVAR) {
Element element = workingCopy.getTypes().asElement(method.getReturnType());
if(element.getKind() == ElementKind.TYPE_PARAMETER) {
TypeParameterElement typeParameterElement = (TypeParameterElement) element;
if(typeParameterElement.getGenericElement().getKind() != ElementKind.METHOD) {
List<ExpressionTree> bounds = new LinkedList<ExpressionTree>();
for (TypeMirror typeMirror : typeParameterElement.getBounds()) {
if(!typeMirror.toString().equals("java.lang.Object")) { //NOI18N
bounds.add((ExpressionTree)make.Type(typeMirror));
}
}
typeParameters.add(make.TypeParameter(typeParameterElement.getSimpleName(), bounds));
}
}
}
Tree returnType = methodTree.getReturnType();
if(returnType != null) {
final TreePath returnPath = new TreePath(resolvedPath, returnType);
Element returnTypeEl = trees.getElement(returnPath);
if(returnTypeEl != null && returnTypeEl.getKind() != ElementKind.TYPE_PARAMETER && isElementBeingMoved(returnTypeEl) == null) {
returnType = genUtils.importFQNs(returnType);
}
}
newMember = make.Method(modifiers, methodTree.getName(), returnType, typeParameters, newParameters, methodTree.getThrows(), body, (ExpressionTree) methodTree.getDefaultValue());
// Make a new Variable (Field) tree
} else if (member.getKind() == Tree.Kind.VARIABLE) {
VariableTree field = (VariableTree) member;
ModifiersTree modifiers = changeModifiers(genUtils.importFQNs(field.getModifiers()), usageOutsideOfPackage.get(tph) == Boolean.TRUE, usageOutsideOfType.get(tph) == Boolean.TRUE);
// Scan the initializer and fix references
ExpressionTree initializer = field.getInitializer();
initializer = fixReferences(initializer, target, resolvedPath);
VariableTree importFQNs = genUtils.importFQNs(field);
newMember = make.Variable(modifiers, field.getName(), importFQNs.getType(), initializer);
}
// Insert the member and copy its comments
if (newMember != null) {
genUtils.copyComments(member, newMember, true);
genUtils.copyComments(member, newMember, false);
if(newMember.getKind() == Tree.Kind.METHOD) {
if(updateJavadoc) {
MethodTree method = (MethodTree) newMember;
List<Comment> comments = workingCopy.getTreeUtilities().getComments(method, true);
Comment comment;
if(comments.isEmpty()) {
comment = generateJavadoc(method, target, false);
} else {
if(comments.get(0).isDocComment()) {
make.removeComment(method, 0, true);
comment = updateJavadoc(resolvedElement, target, false);
} else {
comment = generateJavadoc(method, target, false);
}
}
make.addComment(newMember, comment, true);
}
}
newClassTree = genUtils.insertClassMember(newClassTree, newMember);
}
}
rewrite(node, newClassTree);
}
}
private boolean removeIfMatch(TreePath currentPath, Element target) throws IllegalArgumentException {
Element el = workingCopy.getTrees().getElement(currentPath);
if (el == null) {
return false;
}
if (isElementBeingMoved(el) != null) {
TreePath enclosingClassPath = JavaRefactoringUtils.findEnclosingClass(workingCopy, currentPath, true, true, true, true, true);
ClassTree classTree = (ClassTree) enclosingClassPath.getLeaf();
ClassTree newClassTree = classTree;
for (TreePathHandle tph : allElements) {
TreePath resolvedPath = tph.resolve(workingCopy);
if (resolvedPath == null) {
// XXX: report missing target ?
continue;
}
Tree member = resolvedPath.getLeaf();
if (delegate && member.getKind() == Tree.Kind.METHOD) {
MethodTree methodTree = (MethodTree) member;
int index = newClassTree.getMembers().indexOf(methodTree);
newClassTree = make.removeClassMember(newClassTree, methodTree);
ExecutableElement element = (ExecutableElement) workingCopy.getTrees().getElement(resolvedPath);
if (element == null) {
continue;
}
List<ExpressionTree> paramList = new ArrayList<ExpressionTree>();
for (VariableElement variableElement : element.getParameters()) {
IdentifierTree vt = make.Identifier(variableElement.getSimpleName().toString());
paramList.add(vt);
}
MethodInvocationTree methodInvocation = make.MethodInvocation(Collections.<ExpressionTree>emptyList(),
make.Identifier(element),
paramList);
methodInvocation = createMethodInvocationTree(element, methodInvocation, currentPath, target, true);
TypeMirror methodReturnType = element.getReturnType();
final StatementTree statement;
final Types types = workingCopy.getTypes();
if (!types.isSameType(methodReturnType, types.getNoType(TypeKind.VOID))) {
statement = make.Return(methodInvocation);
} else {
statement = make.ExpressionStatement(methodInvocation);
}
ModifiersTree modifiers = methodTree.getModifiers();
if(deprecate) {
AnnotationTree annotation = make.Annotation(make.Identifier("Deprecated"), Collections.EMPTY_LIST); //NOI18N
modifiers = make.addModifiersAnnotation(modifiers, annotation);
}
MethodTree method = make.Method(modifiers, methodTree.getName(), methodTree.getReturnType(), methodTree.getTypeParameters(), methodTree.getParameters(), methodTree.getThrows(), make.Block(Collections.singletonList(statement), false), (ExpressionTree) methodTree.getDefaultValue());
GeneratorUtilities.get(workingCopy).importComments(member, resolvedPath.getCompilationUnit());
GeneratorUtilities.get(workingCopy).copyComments(member, method, true);
GeneratorUtilities.get(workingCopy).copyComments(member, method, false);
if(updateJavadoc) {
List<Comment> comments = workingCopy.getTreeUtilities().getComments(method, true);
Comment comment;
if(comments.isEmpty()) {
comment = generateJavadoc(method, target, deprecate);
} else {
if(comments.get(0).isDocComment()) {
make.removeComment(method, 0, true);
comment = updateJavadoc(element, target, deprecate);
} else {
comment = generateJavadoc(method, target, deprecate);
}
}
make.addComment(method, comment, true);
}
newClassTree = make.insertClassMember(newClassTree, index, method);
} else {
newClassTree = make.removeClassMember(newClassTree, member);
}
}
rewrite(classTree, newClassTree);
return true;
}
return false;
}
private Comment updateJavadoc(Element method, Element targetElement, boolean addDeprecated) {
DocCommentTree javadoc = workingCopy.getDocTrees().getDocCommentTree(method);
List<DocTree> otherTags = new LinkedList<>();
List<DocTree> returnTags = new LinkedList<>();
List<DocTree> throwsTags = new LinkedList<>();
List<DocTree> paramTags = new LinkedList<>();
for (DocTree tag : javadoc.getBlockTags()) {
switch (tag.getKind()) {
case RETURN: returnTags.add(tag); break;
case THROWS: throwsTags.add(tag); break;
case PARAM: paramTags.add(tag); break;
default: otherTags.add(tag);
}
}
StringBuilder text = new StringBuilder(javadoc.getBody().stream().map(t -> t.toString()).collect(Collectors.joining(""))).append("\n\n"); // NOI18N
text.append(tagsToString(paramTags));
text.append(tagsToString(returnTags));
text.append(tagsToString(throwsTags));
text.append(tagsToString(otherTags));
if(addDeprecated) {
String target = targetElement.asType().toString() + "#" + method.getSimpleName(); // NOI18N
text.append(org.openide.util.NbBundle.getMessage(MoveMembersTransformer.class, "TAG_Deprecated", target));
}
Comment comment = Comment.create(Comment.Style.JAVADOC, NOPOS, NOPOS, NOPOS, text.toString());
return comment;
}
private String tagsToString(List<DocTree> tags) {
StringBuilder sb = new StringBuilder();
for (DocTree tag : tags) {
sb.append(tag.toString()).append("\n"); // NOI18N
}
return sb.toString();
}
private Comment generateJavadoc(MethodTree current, Element targetElement, boolean addDeprecated) {
Tree returnType = current.getReturnType();
StringBuilder builder = new StringBuilder("\n"); // NOI18N
for (VariableTree variableTree : current.getParameters()) {
builder.append(String.format("@param %s the value of %s", variableTree.getName(), variableTree.getName())); // NOI18N
builder.append("\n"); // NOI18N
}
boolean hasReturn = false;
if (returnType != null && returnType.getKind().equals(Tree.Kind.PRIMITIVE_TYPE)) {
if (!((PrimitiveTypeTree) returnType).getPrimitiveTypeKind().equals(TypeKind.VOID)) {
hasReturn = true;
}
}
if(hasReturn) {
builder.append("@return the ").append(returnType).append("\n"); // NOI18N
}
for (ExpressionTree expressionTree : current.getThrows()) {
builder.append("@throws ").append(expressionTree).append("\n"); // NOI18N
}
if(addDeprecated) {
String target = targetElement.asType().toString() + "#" + current.getName(); // NOI18N
builder.append(org.openide.util.NbBundle.getMessage(MoveMembersTransformer.class, "TAG_Deprecated", target));
}
Comment comment = Comment.create(
Comment.Style.JAVADOC, NOPOS, NOPOS, NOPOS,
builder.toString());
return comment;
}
private TreePathHandle isElementBeingMoved(Element el) {
for (TreePathHandle mh : allElements) {
Element element = mh.resolveElement(workingCopy);
if (element == null) {
Logger.getLogger("org.netbeans.modules.refactoring.java").log(Level.INFO, "MoveMembersTransformer cannot resolve {0}", mh); //NOI18N
continue;
}
if (element.equals(el)) {
return mh;
}
}
return null;
}
private ModifiersTree changeModifiers(ModifiersTree modifiersTree, boolean usageOutsideOfPackage, boolean usageOutsideOfType) {
final Set<Modifier> flags = modifiersTree.getFlags();
Set<Modifier> newModifiers = flags.isEmpty() ? EnumSet.noneOf(Modifier.class) : EnumSet.copyOf(flags);
switch (visibility) {
case ESCALATE:
if (usageOutsideOfPackage) {
if (!flags.contains(Modifier.PUBLIC)) { // TODO: if only subtype, change protected
newModifiers.removeAll(ALL_ACCESS_MODIFIERS);
newModifiers.add(Modifier.PUBLIC);
}
} else {
if(usageOutsideOfType) {
if (flags.contains(Modifier.PRIVATE)) {
newModifiers.removeAll(ALL_ACCESS_MODIFIERS);
}
}
}
break;
case ASIS:
default:
break;
case PUBLIC:
newModifiers.removeAll(ALL_ACCESS_MODIFIERS);
newModifiers.add(Modifier.PUBLIC);
break;
case PROTECTED:
newModifiers.removeAll(ALL_ACCESS_MODIFIERS);
newModifiers.add(Modifier.PROTECTED);
break;
case DEFAULT:
newModifiers.removeAll(ALL_ACCESS_MODIFIERS);
break;
case PRIVATE:
newModifiers.removeAll(ALL_ACCESS_MODIFIERS);
newModifiers.add(Modifier.PRIVATE);
break;
}
ModifiersTree modifiers = make.Modifiers(newModifiers, modifiersTree.getAnnotations());
return modifiers;
}
private <T extends Tree> T fixReferences(T body, Element target, final TreePath resolvedPath) {
TreePath enclosingClassPath = JavaRefactoringUtils.findEnclosingClass(workingCopy, resolvedPath, true, true, true, true, true);
final TypeElement enclosingClass = (TypeElement) workingCopy.getTrees().getElement(enclosingClassPath);
final Map<Tree, Tree> original2Translated = new HashMap<Tree, Tree>();
// TODO Change this to something like that is used for method body; importFqns
ErrorAwareTreeScanner<Void, Void> idScan = new ErrorAwareTreeScanner<Void, Void>() {
@Override
public Void visitIdentifier(IdentifierTree node, Void p) {
TreePath currentPath = new TreePath(resolvedPath, node);
if (currentPath.getParentPath().getLeaf().getKind() == Tree.Kind.MEMBER_SELECT) {
return super.visitIdentifier(node, p); // Already checked by visitMemberSelect
}
Element element = workingCopy.getTrees().getElement(currentPath);
if (element != null && isElementBeingMoved(element) == null && element.getModifiers().contains(Modifier.STATIC)) {
Tree newTree = make.QualIdent(element);
original2Translated.put(node, newTree);
}
return super.visitIdentifier(node, p);
}
@Override
public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
TreePath currentPath = new TreePath(resolvedPath, node);
if (currentPath.getParentPath().getLeaf().getKind() == Tree.Kind.MEMBER_SELECT) {
return super.visitMethodInvocation(node, p); // Already checked by visitMemberSelect
}
Element element = workingCopy.getTrees().getElement(currentPath);
ExpressionTree methodSelect = node.getMethodSelect();
if (element != null && isElementBeingMoved(element) == null) {
if (element.getModifiers().contains(Modifier.STATIC)) {
Tree newTree = make.QualIdent(element);
original2Translated.put(methodSelect, newTree);
} else {
problem = JavaPluginUtils.chainProblems(problem, new Problem(false, NbBundle.getMessage(MoveMembersTransformer.class, "WRN_InitNoAccess")));
}
}
return super.visitMethodInvocation(node, p);
}
@Override
public Void visitMemberSelect(MemberSelectTree node, Void p) {
Element element = workingCopy.getTrees().getElement(new TreePath(resolvedPath, node));
if (element != null && isElementBeingMoved(element) == null && element.getModifiers().contains(Modifier.STATIC)) {
Tree newTree = make.QualIdent(element);
original2Translated.put(node, newTree);
}
return super.visitMemberSelect(node, p);
}
};
idScan.scan(body, null);
return (T) workingCopy.getTreeUtilities().translate(body, original2Translated);
}
private static String getParameterName(TypeMirror type, Scope scope, CompilationController info) {
String name = JavaPluginUtils.getName(type);
if (name == null) {
name = JavaPluginUtils.DEFAULT_NAME;
}
return JavaPluginUtils.makeNameUnique(info, scope, name);
}
private boolean enclosedBy(TypeMirror t1, TypeMirror t2) {
if(t1.getKind() == TypeKind.DECLARED) {
if(workingCopy.getTypes().isSameType(t1, t2)) {
return true;
}
DeclaredType dt = (DeclaredType) t1;
TypeMirror enclosingType = dt.getEnclosingType();
if(enclosingType.getKind() == TypeKind.NONE) {
return false;
} else {
return enclosedBy(enclosingType, t2);
}
} else {
return false;
}
}
}