blob: 19a5e846c401d65a3bf604fb7de5a2f4cec19aad [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.doctree.ReturnTree;
import static com.sun.source.doctree.DocTree.Kind.AUTHOR;
import static com.sun.source.doctree.DocTree.Kind.DEPRECATED;
import static com.sun.source.doctree.DocTree.Kind.EXCEPTION;
import static com.sun.source.doctree.DocTree.Kind.PARAM;
import static com.sun.source.doctree.DocTree.Kind.RETURN;
import static com.sun.source.doctree.DocTree.Kind.SEE;
import static com.sun.source.doctree.DocTree.Kind.SERIAL;
import static com.sun.source.doctree.DocTree.Kind.SERIAL_DATA;
import static com.sun.source.doctree.DocTree.Kind.SERIAL_FIELD;
import static com.sun.source.doctree.DocTree.Kind.SINCE;
import static com.sun.source.doctree.DocTree.Kind.THROWS;
import static com.sun.source.doctree.DocTree.Kind.UNKNOWN_BLOCK_TAG;
import static com.sun.source.doctree.DocTree.Kind.VERSION;
import com.sun.source.doctree.DocTreeVisitor;
import com.sun.source.doctree.ParamTree;
import com.sun.source.tree.Tree.Kind;
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.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.lang.model.element.*;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import org.netbeans.api.java.source.ClassIndex.NameKind;
import org.netbeans.api.java.source.*;
import org.netbeans.modules.refactoring.api.Problem;
import org.netbeans.modules.refactoring.java.RefactoringUtils;
import org.netbeans.modules.refactoring.java.api.ChangeParametersRefactoring.ParameterInfo;
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.netbeans.modules.refactoring.java.ui.ChangeParametersPanel.Javadoc;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
/**
* <b>!!! Do not use {@link Element} parameter of visitXXX methods. Use {@link #allMethods} instead!!!</b>
*
* @author Jan Becicka
*/
public class ChangeParamsTransformer extends RefactoringVisitor {
private static final Set<Modifier> ALL_ACCESS_MODIFIERS = EnumSet.of(Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC);
private static final int NOPOS = -2;
private Set<ElementHandle<ExecutableElement>> allMethods;
/** refactored element is a synthetic default constructor */
private boolean synthConstructor;
/**
* refactored element is a constructor; {@code null} if it is has not been initialized yet
* @see #init()
*/
private Boolean constructorRefactoring;
private final ParameterInfo[] paramInfos;
private Collection<? extends Modifier> newModifiers;
private String returnType;
private boolean compatible;
private final Javadoc javaDoc;
private final TreePathHandle refactoringSource;
private MethodTree origMethod;
private final String newName;
private boolean fromIntroduce = false;
public ChangeParamsTransformer(ParameterInfo[] paramInfo,
Collection<? extends Modifier> newModifiers,
String returnType,
String newName,
boolean compatible,
Javadoc javaDoc,
Set<ElementHandle<ExecutableElement>> am,
TreePathHandle refactoringSource,
boolean fromIntroduce) {
this(paramInfo, newModifiers, returnType, newName, compatible, javaDoc, am, refactoringSource);
this.fromIntroduce = fromIntroduce;
}
public ChangeParamsTransformer(ParameterInfo[] paramInfo,
Collection<? extends Modifier> newModifiers,
String returnType,
String newName,
boolean compatible,
Javadoc javaDoc,
Set<ElementHandle<ExecutableElement>> am,
TreePathHandle refactoringSource) {
super(true);
this.paramInfos = paramInfo;
this.newModifiers = newModifiers;
this.returnType = returnType;
this.newName = newName;
this.compatible = compatible;
this.javaDoc = javaDoc;
this.allMethods = am;
this.refactoringSource = refactoringSource;
}
private Problem problem;
private LinkedList<ClassTree> problemClasses = new LinkedList<ClassTree>();
public Problem getProblem() {
return problem;
}
@Override
public void setWorkingCopy(WorkingCopy workingCopy) throws ToPhaseException {
super.setWorkingCopy(workingCopy);
if(origMethod == null
&& workingCopy.getFileObject().equals(refactoringSource.getFileObject())) {
TreePath resolvedPath = refactoringSource.resolve(workingCopy);
TreePath meth = JavaPluginUtils.findMethod(resolvedPath);
origMethod = (MethodTree) meth.getLeaf();
}
}
private void checkNewModifier(TreePath tree, Element p) throws MissingResourceException {
if (newModifiers == null || newModifiers.contains(Modifier.PUBLIC)) {
return;
}
ClassTree classTree = (ClassTree) JavaRefactoringUtils.findEnclosingClass(workingCopy, tree, true, true, true, true, false).getLeaf();
if (problemClasses.contains(classTree)) {
// Only give one warning for every file
return;
}
Element el = workingCopy.getTrees().getElement(workingCopy.getTrees().getPath(workingCopy.getCompilationUnit(), classTree));
if (el == null || p == null) {
return;
}
TypeElement enclosingTypeElement1 = workingCopy.getElementUtilities().outermostTypeElement(el);
TypeElement enclosingTypeElement2 = workingCopy.getElementUtilities().outermostTypeElement(p);
if(!workingCopy.getTypes().isSameType(enclosingTypeElement1.asType(), enclosingTypeElement2.asType())) {
if(newModifiers.contains(Modifier.PRIVATE)) {
problem = MoveTransformer.createProblem(problem, false, NbBundle.getMessage(ChangeParamsTransformer.class, "ERR_StrongAccMod", Modifier.PRIVATE, enclosingTypeElement1)); //NOI18N
problemClasses.add(classTree);
} else {
PackageElement package1 = workingCopy.getElements().getPackageOf(el);
PackageElement package2 = workingCopy.getElements().getPackageOf(p);
if(!package1.getQualifiedName().equals(package2.getQualifiedName())) {
if(newModifiers.contains(Modifier.PROTECTED)) {
if(!workingCopy.getTypes().isSubtype(enclosingTypeElement1.asType(), enclosingTypeElement2.asType())) {
problem = MoveTransformer.createProblem(problem, false, NbBundle.getMessage(ChangeParamsTransformer.class, "ERR_StrongAccMod", Modifier.PROTECTED, enclosingTypeElement1)); //NOI18N
problemClasses.add(classTree);
}
} else {
problem = MoveTransformer.createProblem(problem, false, NbBundle.getMessage(ChangeParamsTransformer.class, "ERR_StrongAccMod", "<default>", enclosingTypeElement1)); //NOI18N
problemClasses.add(classTree);
}
}
}
}
}
private void init() {
if (constructorRefactoring == null) {
ElementHandle<ExecutableElement> handle = allMethods.iterator().next();
constructorRefactoring = handle.getKind() == ElementKind.CONSTRUCTOR;
Element el;
synthConstructor = constructorRefactoring
&& (el = handle.resolve(workingCopy)) != null
&& workingCopy.getElementUtilities().isSynthetic(el);
}
}
@Override
public Tree visitCompilationUnit(CompilationUnitTree node, Element p) {
init();
return super.visitCompilationUnit(node, p);
}
@Override
public Tree visitClass(ClassTree node, Element p) {
if(compatible) {
List<? extends Tree> members = node.getMembers();
for (int i = 0; i < members.size(); i++) {
Tree tree = members.get(i);
if (tree.getKind().equals(Kind.METHOD)) {
ExecutableElement element = (ExecutableElement) workingCopy.getTrees().getElement(new TreePath(getCurrentPath(), tree));
if (p.equals(element)) {
List<ExpressionTree> paramList = getNewCompatibleArguments(((MethodTree)tree).getParameters());
MethodInvocationTree methodInvocation = make.MethodInvocation(Collections.<ExpressionTree>emptyList(),
constructorRefactoring? make.Identifier("this") : make.Identifier(element),
paramList);
TypeMirror methodReturnType = element.getReturnType();
boolean hasReturn = true;
Types types = workingCopy.getTypes();
if (types.isSameType(methodReturnType, types.getNoType(TypeKind.VOID))) {
hasReturn = false;
}
StatementTree statement = null;
if (hasReturn) {
statement = make.Return(methodInvocation);
} else {
statement = make.ExpressionStatement(methodInvocation);
}
final GeneratorUtilities genutils = GeneratorUtilities.get(workingCopy);
tree = genutils.importComments(tree, workingCopy.getCompilationUnit());
BlockTree body = make.Block(Collections.singletonList(statement), false);
final BlockTree oldBody = ((MethodTree)tree).getBody();
genutils.copyComments(oldBody, body, true);
genutils.copyComments(oldBody, body, false);
MethodTree newMethod;
if (!fromIntroduce) {
newMethod = make.Method(
make.Modifiers(element.getModifiers()),
newName == null ? element.getSimpleName() : newName,
((MethodTree)tree).getReturnType(),
((MethodTree)tree).getTypeParameters(),
((MethodTree)tree).getParameters(),
((MethodTree)tree).getThrows(),
body,
null,
element.isVarArgs());
} else {
newMethod = make.Method(element, body);
if(element.getKind() == ElementKind.CONSTRUCTOR) {
newMethod = make.Method(newMethod.getModifiers(),
newMethod.getName(),
null, // workaround: make.Method(element, body) uses void as return type for contructors
newMethod.getTypeParameters(),
newMethod.getParameters(),
newMethod.getThrows(),
newMethod.getBody(),
(ExpressionTree)newMethod.getDefaultValue(),
element.isVarArgs());
}
}
genutils.copyComments(tree, newMethod, true);
genutils.copyComments(tree, newMethod, false);
ClassTree addMember = make.insertClassMember(node, i, newMethod);
rewrite(node, addMember);
}
}
}
}
return super.visitClass(node, p);
}
@Override
public Tree visitNewClass(NewClassTree tree, Element p) {
if (constructorRefactoring && !compatible && !workingCopy.getTreeUtilities().isSynthetic(getCurrentPath())) {
ExecutableElement constructor = (ExecutableElement) p;
final Trees trees = workingCopy.getTrees();
Element el = trees.getElement(getCurrentPath());
el = resolveAnonymousClassConstructor(el, tree, trees);
if (isMethodMatch(el, p)) {
List<ExpressionTree> arguments = getNewArguments(tree.getArguments(), false, constructor);
NewClassTree nju = make.NewClass(tree.getEnclosingExpression(),
(List<ExpressionTree>)tree.getTypeArguments(),
tree.getIdentifier(),
arguments,
tree.getClassBody());
rewrite(tree, nju);
}
}
return super.visitNewClass(tree, p);
}
@Override
public DocTree visitDocComment(DocCommentTree node, Element p) {
if(javaDoc != Javadoc.UPDATE) {
return node;
}
TreePath path = getCurrentDocPath().getTreePath();
Element el = workingCopy.getTrees().getElement(path);
if (isMethodMatch(el, p)) {
List<? extends DocTree> blockTags = node.getBlockTags();
List<DocTree> newTags = new LinkedList<DocTree>();
Map<String, ParamTree> oldParams = new HashMap<>();
ParamTree fake = new FakaParamTree();
int index = 0;
ReturnTree returnTree;
for (DocTree docTree : blockTags) {
if(docTree.getKind() != DocTree.Kind.PARAM || ((ParamTree) docTree).isTypeParameter()) {
if(docTree.getKind() == DocTree.Kind.RETURN) {
returnTree = (ReturnTree) docTree;
} else {
newTags.add(docTree);
}
if(TagComparator.compareTag(fake, docTree) != TagComparator.HIGHER) {
index++;
}
} else {
ParamTree paramTree = (ParamTree) docTree;
oldParams.put(paramTree.getName().getName().toString(), paramTree);
}
}
for (ParameterInfo parameterInfo : paramInfos) {
ParamTree tag;
if(parameterInfo.getOriginalIndex() == -1) {
tag = make.Param(false, make.DocIdentifier(parameterInfo.getName()), Collections.singletonList(make.Text("the value of " + parameterInfo.getName())));
} else {
String name = ((ExecutableElement)el).getParameters().get(parameterInfo.getOriginalIndex()).getSimpleName().toString();
tag = oldParams.get(name);
if(tag == null) {
tag = make.Param(false, make.DocIdentifier(parameterInfo.getName()), Collections.singletonList(make.Text("the value of " + parameterInfo.getName())));
} else if(parameterInfo.getName() != null) {
tag = make.Param(false, make.DocIdentifier(parameterInfo.getName()), tag.getDescription());
}
}
newTags.add(index++, tag);
}
// @Return
String returnTypeString;
TypeMirror returnType = ((ExecutableElement)el).getReturnType();
if (this.returnType == null) {
if (returnType != null && returnType.getKind() != TypeKind.VOID) {
returnTypeString = returnType.toString();
} else {
returnTypeString = null;
}
} else {
if(this.returnType.equals("void")) {
returnTypeString = null;
} else {
returnTypeString = this.returnType;
}
}
if(returnTypeString != null) {
newTags.add(make.DocReturn(Collections.singletonList(make.Text("the " + returnTypeString))));
}
rewrite(path.getLeaf(), node, make.DocComment(node.getFirstSentence(), node.getBody(), newTags));
}
return node;
}
/**
* special treatment for anonymous classes to resolve the proper constructor
* of extended class instead of the synthetic one.
* @see <a href="https://netbeans.org/bugzilla/show_bug.cgi?id=168775">#168775</a>
*/
private Element resolveAnonymousClassConstructor(Element el, NewClassTree tree, final Trees trees) {
if (el != null && tree.getClassBody() != null) {
Tree t = trees.getTree(el);
if (t != null && t.getKind() == Tree.Kind.METHOD) {
MethodTree constructorTree = (MethodTree) t;
Tree superCall = constructorTree.getBody().getStatements().get(0);
TreePath superCallPath = trees.getPath(
getCurrentPath().getCompilationUnit(),
((ExpressionStatementTree) superCall).getExpression());
el = trees.getElement(superCallPath);
}
}
return el;
}
private List<ExpressionTree> getNewCompatibleArguments(List<? extends VariableTree> parameters) {
List<ExpressionTree> arguments = new ArrayList();
ParameterInfo[] pi = paramInfos;
for (int i = 0; i < pi.length; i++) {
int originalIndex = pi[i].getOriginalIndex();
ExpressionTree vt;
String value;
if (originalIndex < 0) {
value = pi[i].getDefaultValue();
} else {
value = parameters.get(originalIndex).getName().toString();
}
SourcePositions pos[] = new SourcePositions[1];
vt = workingCopy.getTreeUtilities().parseExpression(value, pos);
arguments.add(vt);
}
return arguments;
}
private List<VariableTree> getNewParameters(List<? extends VariableTree> currentParameters, TreePath path) {
List<VariableTree> arguments = new ArrayList<>();
boolean skipType = currentParameters.size() > 0 && (currentParameters.get(0).getType() == null
|| workingCopy.getTreeUtilities().isSynthetic(new TreePath(path, currentParameters.get(0).getType())));
ParameterInfo[] pi = paramInfos;
for (int i = 0; i < pi.length; i++) {
int originalIndex = pi[i].getOriginalIndex();
VariableTree vt;
boolean isVarArgs = i == pi.length -1 && pi[i].getType().endsWith("..."); // NOI18N
String newType = isVarArgs? pi[i].getType().replace("...", "") : pi[i].getType(); //NOI18N
if (originalIndex < 0) {
vt = make.Variable(make.Modifiers(Collections.<Modifier>emptySet()),
pi[i].getName(),
skipType? null : make.Identifier(newType), // NOI18N
null);
} else {
vt = currentParameters.get(originalIndex);
if(!skipType) {
Tree typeTree = null;
if (origMethod != null) {
if (!pi[i].getType().equals(origMethod.getParameters().get(originalIndex).getType().toString())) {
typeTree = make.Identifier(newType);
}
} else {
typeTree = make.Identifier(newType);
}
if(typeTree != null) {
vt = make.Variable(vt.getModifiers(),
vt.getName(),
typeTree,
vt.getInitializer());
}
}
}
arguments.add(vt);
}
return arguments;
}
private List<ExpressionTree> getNewArguments(List<? extends ExpressionTree> currentArguments, boolean passThrough, ExecutableElement method) {
List<ExpressionTree> arguments = new ArrayList();
ParameterInfo[] pi = paramInfos;
for (int i = 0; i < pi.length; i++) {
int originalIndex = pi[i].getOriginalIndex();
ExpressionTree vt;
if (originalIndex < 0) {
SourcePositions pos[] = new SourcePositions[1];
if(passThrough) {
String value = pi[i].getName();
vt = workingCopy.getTreeUtilities().parseExpression(value, pos);
} else {
String value = pi[i].getDefaultValue();
if (i == pi.length - 1 && pi[i].getType().endsWith("...")) { // NOI18N
// last param is vararg, so split the default value for the remaining arguments
MethodInvocationTree parsedExpression = (MethodInvocationTree) workingCopy.getTreeUtilities().parseExpression("method("+value+")", pos); //NOI18N
for (ExpressionTree expressionTree : parsedExpression.getArguments()) {
arguments.add(translateExpression(expressionTree, currentArguments, method));
}
break;
} else {
vt = translateExpression(workingCopy.getTreeUtilities().parseExpression(value, pos), currentArguments, method);
}
}
} else {
if (i == pi.length - 1 && pi[i].getType().endsWith("...") && method.isVarArgs() && method.getParameters().size()-1 == originalIndex) { // NOI18N
// last param is vararg, so copy all remaining arguments
for (int j = originalIndex; j < currentArguments.size(); j++) {
arguments.add(currentArguments.get(j));
}
break;
} else {
vt = currentArguments.get(originalIndex);
}
}
arguments.add(vt);
}
return arguments;
}
@Override
public Tree visitMethodInvocation(MethodInvocationTree tree, Element p) {
if ((constructorRefactoring || !workingCopy.getTreeUtilities().isSynthetic(getCurrentPath())) && !compatible) {
ExecutableElement method = (ExecutableElement) p;
Element el = workingCopy.getTrees().getElement(getCurrentPath());
if (isMethodMatch(el, method)) {
checkNewModifier(getCurrentPath(), method);
boolean passThrough = false;
TreePath enclosingMethod = JavaPluginUtils.findMethod(getCurrentPath());
if(enclosingMethod != null) {
Element enclosingElement = workingCopy.getTrees().getElement(enclosingMethod);
if(isMethodMatch(enclosingElement, method)) {
passThrough = true;
}
}
List<ExpressionTree> arguments = getNewArguments(tree.getArguments(), passThrough, method);
MethodInvocationTree nju = make.MethodInvocation(
(List<ExpressionTree>)tree.getTypeArguments(),
newName != null ? make.setLabel(tree.getMethodSelect(), newName) : tree.getMethodSelect(),
arguments);
if (constructorRefactoring && workingCopy.getTreeUtilities().isSynthetic(getCurrentPath())) {
rewriteSyntheticConstructor(nju);
} else {
// rewrite existing super(); statement
rewrite(tree, nju);
}
}
}
return super.visitMethodInvocation(tree, p);
}
@Override
public Tree visitLambdaExpression(LambdaExpressionTree tree, Element p) {
TreePath path = getCurrentPath();
if (!compatible && !workingCopy.getTreeUtilities().isSynthetic(path)) {
ExecutableElement method = (ExecutableElement) p;
TypeMirror tm = workingCopy.getTrees().getTypeMirror(path);
if (tm != null && workingCopy.getTypes().isSameType(tm, method.getEnclosingElement().asType())) {
checkNewModifier(path, method);
List<VariableTree> params = getNewParameters(tree.getParameters(), path);
LambdaExpressionTree nju = make.LambdaExpression(params, tree.getBody());
rewrite(tree, nju);
}
}
return super.visitLambdaExpression(tree, p);
}
/** workaround to rewrite synthetic super(); statement */
private void rewriteSyntheticConstructor(MethodInvocationTree nju) {
TreePath constructorPath = getCurrentPath();
while (constructorPath != null && constructorPath.getLeaf().getKind() != Tree.Kind.METHOD) {
constructorPath = constructorPath.getParentPath();
}
if (constructorPath != null) {
MethodTree constrTree = (MethodTree) constructorPath.getLeaf();
BlockTree body = constrTree.getBody();
body = make.removeBlockStatement(body, 0);
body = make.insertBlockStatement(body, 0, make.ExpressionStatement(nju));
if (workingCopy.getTreeUtilities().isSynthetic(constructorPath)) {
// in case of synthetic default constructor declaration the whole constructor has to be rewritten
MethodTree njuConstructor = make.Method(
make.Modifiers(constrTree.getModifiers().getFlags(),
constrTree.getModifiers().getAnnotations()),
constrTree.getName(),
constrTree.getReturnType(),
constrTree.getTypeParameters(),
constrTree.getParameters(),
constrTree.getThrows(),
body,
(ExpressionTree) constrTree.getDefaultValue());
rewrite(constrTree, njuConstructor);
} else {
// declared default constructor => body rewrite is sufficient
rewrite(constrTree.getBody(), body);
}
}
}
@Override
public Tree visitMethod(MethodTree tree, Element p) {
if (constructorRefactoring && isSyntheticConstructorOfAnnonymousClass(workingCopy.getTrees().getElement(getCurrentPath()))) {
return tree;
}
renameDeclIfMatch(getCurrentPath(), tree, p);
return super.visitMethod(tree, p);
}
private void renameDeclIfMatch(TreePath path, Tree tree, Element elementToFind) {
if (!synthConstructor && workingCopy.getTreeUtilities().isSynthetic(path)) {
return;
}
final GeneratorUtilities genutils = GeneratorUtilities.get(workingCopy);
final MethodTree current;
if(!compatible) { // Do not import comments twice.
current = genutils.importComments((MethodTree)tree, workingCopy.getCompilationUnit());
} else {
current = (MethodTree) tree;
}
Element el = workingCopy.getTrees().getElement(path);
if (isMethodMatch(el, elementToFind)) {
List<? extends VariableTree> currentParameters = current.getParameters();
List<VariableTree> newParameters = new ArrayList<VariableTree>(paramInfos.length);
boolean renameParams = !fromIntroduce;
ExecutableElement oMethod = (ExecutableElement) el;
ExecutableElement refMethod = (ExecutableElement) elementToFind;
if(oMethod != refMethod) {
List<? extends VariableElement> oParams = oMethod.getParameters();
List<? extends VariableElement> rParams = refMethod.getParameters();
for (int i = 0; i < oParams.size(); i++) {
if(!oParams.get(i).getSimpleName().contentEquals(rParams.get(i).getSimpleName())) {
renameParams = false;
break;
}
}
}
ParameterInfo[] p = paramInfos;
for (int i=0; i<p.length; i++) {
int originalIndex = p[i].getOriginalIndex();
VariableTree vt;
if (originalIndex <0) {
boolean isVarArgs = i == p.length -1 && p[i].getType().endsWith("..."); // NOI18N
vt = make.Variable(make.Modifiers(Collections.<Modifier>emptySet()),
p[i].getName(),
make.Identifier(isVarArgs? p[i].getType().replace("...", "") : p[i].getType()), // NOI18N
null);
} else {
VariableTree originalVt = currentParameters.get(originalIndex);
boolean isVarArgs = i == p.length -1 && p[i].getType().endsWith("..."); // NOI18N
String newType = isVarArgs? p[i].getType().replace("...", "") : p[i].getType();
final Tree typeTree;
if (origMethod != null) {
if (p[i].getType().equals(origMethod.getParameters().get(originalIndex).getType().toString())) { // Type has not changed
typeTree = originalVt.getType();
} else {
typeTree = make.Identifier(newType); // NOI18N
}
} else {
typeTree = make.Identifier(newType); // NOI18N
}
vt = make.Variable(originalVt.getModifiers(),
renameParams? p[i].getName() : originalVt.getName(),
typeTree,
originalVt.getInitializer());
}
newParameters.add(vt);
}
// apply new access modifiers if necessary
Set<Modifier> modifiers = new HashSet<Modifier>(current.getModifiers().getFlags());
if (newModifiers!=null && !el.getEnclosingElement().getKind().isInterface()) {
modifiers.removeAll(ALL_ACCESS_MODIFIERS);
modifiers.addAll(newModifiers);
}
// apply new return type if necessary
boolean applyNewReturnType = false;
if(this.returnType != null) {
ExecutableElement exEl = (ExecutableElement) el;
String oldReturnType = exEl.getReturnType().toString();
if(!this.returnType.equals(oldReturnType)) {
applyNewReturnType = true;
}
}
//Compute new imports
for (VariableTree vt : newParameters) {
Set<ElementHandle<TypeElement>> declaredTypes = workingCopy.getClasspathInfo().getClassIndex().getDeclaredTypes(vt.getType().toString(), NameKind.SIMPLE_NAME, EnumSet.allOf(ClassIndex.SearchScope.class));
Set<ElementHandle<TypeElement>> declaredTypesMirr = new HashSet<ElementHandle<TypeElement>>(declaredTypes);
TypeElement type = null;
//remove private types
//TODO: and possibly package private?
for (ElementHandle<TypeElement> typeName : declaredTypes) {
TypeElement te = workingCopy.getElements().getTypeElement(typeName.getQualifiedName());
if (te == null) {
Logger.getLogger(ChangeParamsTransformer.class.getName()).log(Level.INFO, "Cannot resolve type element \"{0}\".", typeName);
continue;
}
if (te.getModifiers().contains(Modifier.PRIVATE)) {
declaredTypesMirr.remove(typeName);
}
}
if (declaredTypesMirr.size() == 1) { //creates import if there is just one proposed type
ElementHandle<TypeElement> typeName = declaredTypesMirr.iterator().next();
TypeElement te = workingCopy.getElements().getTypeElement(typeName.getQualifiedName());
if (te == null) {
Logger.getLogger(ChangeParamsTransformer.class.getName()).log(Level.INFO, "Cannot resolve type element \"{0}\".", typeName);
continue;
}
type = te;
}
if (type != null) {
PackageElement packageOf = workingCopy.getElements().getPackageOf(type);
if (packageOf.getQualifiedName().toString().equals("java.lang")) {
continue;
}
try {
SourceUtils.resolveImport(workingCopy, path, type.getQualifiedName().toString());
} catch (NullPointerException ex) {
Exceptions.printStackTrace(ex);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
}
final BlockTree body = translateBody(current.getBody(), current.getParameters(), (ExecutableElement)el, renameParams);
MethodTree nju = make.Method(
make.Modifiers(modifiers, current.getModifiers().getAnnotations()),
newName != null ? newName : current.getName(),
applyNewReturnType? make.Type(this.returnType) : current.getReturnType(),
current.getTypeParameters(),
newParameters,
current.getThrows(),
fromIntroduce? current.getBody() : body,
(ExpressionTree) current.getDefaultValue(),
p.length > 0 && p[p.length-1].getType().endsWith("...")); //NOI18N
genutils.copyComments(current, nju, true);
genutils.copyComments(current, nju, false);
if(javaDoc == Javadoc.GENERATE) {
List<DocTree> tags = new LinkedList<DocTree>();
// @TypeParam
for (TypeParameterTree typeParameterTree : current.getTypeParameters()) {
tags.add(make.Param(true, make.DocIdentifier(typeParameterTree.getName()), Collections.EMPTY_LIST));
}
// @Param
for (VariableTree variableTree : newParameters) {
tags.add(make.Param(false, make.DocIdentifier(variableTree.getName()), Collections.singletonList(make.Text("the value of " + variableTree.getName()))));
}
// @Return
String returnTypeString;
Tree returnType = nju.getReturnType();
if (this.returnType == null) {
boolean hasReturn = false;
if (returnType != null && returnType.getKind().equals(Tree.Kind.PRIMITIVE_TYPE)) {
if (!((PrimitiveTypeTree) returnType).getPrimitiveTypeKind().equals(TypeKind.VOID)) {
hasReturn = true;
}
}
if (hasReturn) {
returnTypeString = returnType.toString();
} else {
returnTypeString = null;
}
} else {
if(this.returnType.equals("void")) {
returnTypeString = null;
} else {
returnTypeString = this.returnType;
}
}
if(returnTypeString != null) {
tags.add(make.DocReturn(Collections.singletonList(make.Text("the " + returnTypeString))));
}
// @Throw
for (ExpressionTree expressionTree : current.getThrows()) {
tags.add(make.Throws(make.Reference(expressionTree, null, null), Collections.EMPTY_LIST));
}
DocCommentTree newDoc = make.DocComment(Collections.EMPTY_LIST, Collections.EMPTY_LIST, tags);
rewrite(synthConstructor? nju : tree, null, newDoc);
}
rewrite(tree, make.asReplacementOf(nju, tree));
}
}
private boolean isMethodMatch(Element method, Element p) {
if (!RefactoringUtils.isExecutableElement(method)) {
return false;
}
if(compatible) {
return method == p;
} else if (allMethods !=null) {
for (ElementHandle<ExecutableElement> mh: allMethods) {
ExecutableElement baseMethod = mh.resolve(workingCopy);
if (baseMethod==null) {
Logger.getLogger("org.netbeans.modules.refactoring.java").info("ChangeParamsTransformer cannot resolve " + mh);
continue;
}
if (baseMethod.equals(method) || workingCopy.getElements().overrides((ExecutableElement)method, baseMethod, workingCopy.getElementUtilities().enclosingTypeElement(baseMethod))) {
return true;
}
}
}
return false;
}
private boolean isSyntheticConstructorOfAnnonymousClass(Element el) {
if (el != null && el.getKind() == ElementKind.CONSTRUCTOR
&& workingCopy.getElementUtilities().isSynthetic(el)) {
Element enclosingElement = el.getEnclosingElement();
return enclosingElement != null && enclosingElement.getKind().isClass()
&& ((TypeElement) enclosingElement).getNestingKind() == NestingKind.ANONYMOUS;
}
return false;
}
private BlockTree translateBody(BlockTree blockTree, final List<? extends VariableTree> parameters, ExecutableElement p, boolean renameParams) {
final Map<ExpressionTree, ExpressionTree> original2Translated = new HashMap<ExpressionTree, ExpressionTree>();
boolean changed = false;
do {
original2Translated.clear();
if(renameParams) {
ErrorAwareTreeScanner<Void, Void> idScan = new ErrorAwareTreeScanner<Void, Void>() {
@Override
public Void visitIdentifier(IdentifierTree node, Void p) {
String name = node.getName().toString();
if (getCurrentPath().getParentPath().getLeaf().getKind() != Kind.MEMBER_SELECT){
for (int i = 0; i < paramInfos.length; i++) {
ParameterInfo parameterInfo = paramInfos[i];
if(parameterInfo.getOriginalIndex() >= 0 &&
parameters.get(parameterInfo.getOriginalIndex()).getName().contentEquals(name)) {
original2Translated.put(node, make.Identifier(parameterInfo.getName()));
}
}
}
return super.visitIdentifier(node, p);
}
};
idScan.scan(blockTree, null);
blockTree = (BlockTree) workingCopy.getTreeUtilities().translate(blockTree, original2Translated);
}
original2Translated.clear();
ErrorAwareTreeScanner<Boolean, ExecutableElement> methodScanner = new ErrorAwareTreeScanner<Boolean, ExecutableElement>() {
@Override
public Boolean visitMethodInvocation(MethodInvocationTree node, ExecutableElement p) {
boolean changed = false;
final TreePath path = workingCopy.getTrees().getPath(workingCopy.getCompilationUnit(), node);
if(path != null) {
Element el = workingCopy.getTrees().getElement(path);
if (isMethodMatch(el, p)) {
List<ExpressionTree> arguments = getNewArguments(node.getArguments(), false, p);
MethodInvocationTree nju = make.MethodInvocation(
(List<ExpressionTree>)node.getTypeArguments(),
newName != null ? make.setLabel(node.getMethodSelect(), newName) : node.getMethodSelect(),
arguments);
original2Translated.put(node, nju);
changed = true;
}
}
return super.visitMethodInvocation(node, p) || changed;
}
@Override
public Boolean reduce(Boolean r1, Boolean r2) {
return r1 == Boolean.TRUE || r2 == Boolean.TRUE;
}
};
changed = methodScanner.scan(blockTree, p) == Boolean.TRUE;
if(changed) {
blockTree = (BlockTree) workingCopy.getTreeUtilities().translate(blockTree, original2Translated);
}
} while(changed);
return blockTree;
}
private ExpressionTree translateExpression(ExpressionTree expressionTree, final List<? extends ExpressionTree> currentArguments, ExecutableElement p) {
final Map<ExpressionTree, ExpressionTree> original2Translated = new HashMap<ExpressionTree, ExpressionTree>();
boolean changed = false;
do {
original2Translated.clear();
ErrorAwareTreeScanner<Void, Void> idScan = new ErrorAwareTreeScanner<Void, Void>() {
@Override
public Void visitIdentifier(IdentifierTree node, Void p) {
String name = node.getName().toString();
if (getCurrentPath().getParentPath().getLeaf().getKind() != Kind.MEMBER_SELECT){
for (int i = 0; i < paramInfos.length; i++) {
ParameterInfo parameterInfo = paramInfos[i];
if(parameterInfo.getOriginalIndex() >= 0 && parameterInfo.getName().equals(name)) {
original2Translated.put(node, currentArguments.get(parameterInfo.getOriginalIndex()));
}
}
}
return super.visitIdentifier(node, p);
}
};
idScan.scan(expressionTree, null);
expressionTree = (ExpressionTree) workingCopy.getTreeUtilities().translate(expressionTree, original2Translated);
original2Translated.clear();
ErrorAwareTreeScanner<Boolean, ExecutableElement> methodScanner = new ErrorAwareTreeScanner<Boolean, ExecutableElement>() {
@Override
public Boolean visitMethodInvocation(MethodInvocationTree node, ExecutableElement p) {
boolean changed = false;
final TreePath path = workingCopy.getTrees().getPath(workingCopy.getCompilationUnit(), node);
if(path != null) {
Element el = workingCopy.getTrees().getElement(path);
if (isMethodMatch(el, p)) {
List<ExpressionTree> arguments = getNewArguments(node.getArguments(), false, p);
MethodInvocationTree nju = make.MethodInvocation(
(List<ExpressionTree>)node.getTypeArguments(),
newName != null ? make.setLabel(node.getMethodSelect(), newName) : node.getMethodSelect(),
arguments);
original2Translated.put(node, nju);
changed = true;
}
}
return super.visitMethodInvocation(node, p) || changed;
}
@Override
public Boolean reduce(Boolean r1, Boolean r2) {
return r1 == Boolean.TRUE || r2 == Boolean.TRUE;
}
};
changed = methodScanner.scan(expressionTree, p) == Boolean.TRUE;
if(changed) {
expressionTree = (ExpressionTree) workingCopy.getTreeUtilities().translate(expressionTree, original2Translated);
}
} while(changed);
return expressionTree;
}
/**
* Orders tags as follows
* <ul>
* <li>@author (classes and interfaces only, required)</li>
* <li>@version (classes and interfaces only, required. See footnote 1)</li>
* <li>@param (methods and constructors only)</li>
* <li>@return (methods only)</li>
* <li>@exception (</li>
* <li>@throws is a synonym added in Javadoc 1.2)</li>
* <li>@see</li>
* <li>@since</li>
* <li>@serial (or @serialField or @serialData)</li>
* <li>@deprecated (see How and When To Deprecate APIs)</li>
* </ul>
*/
private static class TagComparator implements Comparator<DocTree> {
private final static int HIGHER = -1;
private final static int EQUAL = 0;
private final static int LOWER = 1;
@Override
public int compare(DocTree t, DocTree t1) {
return compareTag(t, t1);
}
public static int compareTag(DocTree t, DocTree t1) {
if(t.getKind() == t1.getKind()) {
if(t.getKind() == DocTree.Kind.PARAM) {
ParamTree p = (ParamTree) t;
ParamTree p1 = (ParamTree) t1;
if(p.isTypeParameter() && !p1.isTypeParameter()) {
return HIGHER;
} else if(!p.isTypeParameter() && p1.isTypeParameter()) {
return LOWER;
}
}
return EQUAL;
}
switch(t.getKind()) {
case AUTHOR:
return HIGHER;
case VERSION:
if(t1.getKind() == DocTree.Kind.AUTHOR) {
return LOWER;
}
return HIGHER;
case PARAM:
if(t1.getKind() == DocTree.Kind.AUTHOR
|| t1.getKind() == DocTree.Kind.VERSION) {
return LOWER;
}
return HIGHER;
case RETURN:
if(t1.getKind() == DocTree.Kind.AUTHOR
|| t1.getKind() == DocTree.Kind.VERSION
|| t1.getKind() == DocTree.Kind.PARAM) {
return LOWER;
}
return HIGHER;
case EXCEPTION:
if(t1.getKind() == DocTree.Kind.AUTHOR
|| t1.getKind() == DocTree.Kind.VERSION
|| t1.getKind() == DocTree.Kind.PARAM
|| t1.getKind() == DocTree.Kind.RETURN) {
return LOWER;
}
return HIGHER;
case THROWS:
if(t1.getKind() == DocTree.Kind.AUTHOR
|| t1.getKind() == DocTree.Kind.VERSION
|| t1.getKind() == DocTree.Kind.PARAM
|| t1.getKind() == DocTree.Kind.RETURN
|| t1.getKind() == DocTree.Kind.EXCEPTION) {
return LOWER;
}
return HIGHER;
case SEE:
if(t1.getKind() == DocTree.Kind.AUTHOR
|| t1.getKind() == DocTree.Kind.VERSION
|| t1.getKind() == DocTree.Kind.PARAM
|| t1.getKind() == DocTree.Kind.RETURN
|| t1.getKind() == DocTree.Kind.EXCEPTION
|| t1.getKind() == DocTree.Kind.THROWS) {
return LOWER;
}
return HIGHER;
case SINCE:
if(t1.getKind() == DocTree.Kind.AUTHOR
|| t1.getKind() == DocTree.Kind.VERSION
|| t1.getKind() == DocTree.Kind.PARAM
|| t1.getKind() == DocTree.Kind.RETURN
|| t1.getKind() == DocTree.Kind.EXCEPTION
|| t1.getKind() == DocTree.Kind.THROWS
|| t1.getKind() == DocTree.Kind.SEE) {
return LOWER;
}
return HIGHER;
case SERIAL:
case SERIAL_DATA:
case SERIAL_FIELD:
if(t1.getKind() == DocTree.Kind.AUTHOR
|| t1.getKind() == DocTree.Kind.VERSION
|| t1.getKind() == DocTree.Kind.PARAM
|| t1.getKind() == DocTree.Kind.RETURN
|| t1.getKind() == DocTree.Kind.EXCEPTION
|| t1.getKind() == DocTree.Kind.THROWS
|| t1.getKind() == DocTree.Kind.SEE
|| t1.getKind() == DocTree.Kind.SINCE) {
return LOWER;
}
return HIGHER;
case DEPRECATED:
if(t1.getKind() == DocTree.Kind.UNKNOWN_BLOCK_TAG) {
return HIGHER;
}
return LOWER;
case UNKNOWN_BLOCK_TAG:
return LOWER;
}
return LOWER;
}
}
private static class FakaParamTree implements ParamTree {
public FakaParamTree() {
}
@Override
public boolean isTypeParameter() {
return false;
}
@Override
public com.sun.source.doctree.IdentifierTree getName() {
return null;
}
@Override
public List<? extends DocTree> getDescription() {
return null;
}
@Override
public String getTagName() {
return null;
}
@Override
public DocTree.Kind getKind() {
return PARAM;
}
@Override
public <R, D> R accept(DocTreeVisitor<R, D> visitor, D data) {
return null;
}
}
}