blob: ff48db4cb773a85c93b12d7ec49c4425ca518113 [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 com.sun.source.util.TreePath;
import com.sun.source.util.Trees;
import java.io.IOException;
import java.util.Collection;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.*;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.netbeans.api.java.source.ModificationResult.Difference;
import org.netbeans.api.java.source.*;
import org.netbeans.modules.refactoring.api.AbstractRefactoring;
import org.netbeans.modules.refactoring.api.Problem;
import org.netbeans.modules.refactoring.java.RefactoringUtils;
import org.netbeans.modules.refactoring.java.api.JavaRefactoringUtils;
import org.netbeans.modules.refactoring.java.api.UseSuperTypeRefactoring;
import org.netbeans.modules.refactoring.java.spi.DiffElement;
import org.netbeans.modules.refactoring.java.spi.JavaRefactoringPlugin;
import org.netbeans.modules.refactoring.java.spi.RefactoringVisitor;
import org.netbeans.modules.refactoring.java.spi.ToPhaseException;
import org.netbeans.modules.refactoring.spi.RefactoringElementsBag;
import org.openide.ErrorManager;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
/*
* UseSuperTypeRefactoringPlugin.java
*
* Created on June 22, 2005
*
* @author Bharath Ravi Kumar
*/
/**
* The plugin that performs the actual work on
* behalf of the use super type refactoring
*/
public class UseSuperTypeRefactoringPlugin extends JavaRefactoringPlugin {
private final UseSuperTypeRefactoring refactoring;
/**
* Creates a new instance of UseSuperTypeRefactoringPlugin
* @param refactoring The refactoring to be used by this plugin
*/
public UseSuperTypeRefactoringPlugin(UseSuperTypeRefactoring refactoring) {
this.refactoring = refactoring;
}
/**
* Prepares the underlying where used query & checks
* for the visibility of the target type.
*/
@Override
public Problem prepare(RefactoringElementsBag refactoringElements) {
TreePathHandle subClassHandle = refactoring.getTypeElement();
replaceSubtypeUsages(subClassHandle, refactoringElements);
return null;
}
@Override
protected JavaSource getJavaSource(Phase p) {
switch (p) {
default:
return JavaSource.forFileObject(refactoring.getTypeElement().getFileObject());
}
}
/**
*Checks whether the candidate element is a valid Type.
*@return Problem The problem instance indicating that an invalid element was selected.
*/
@Override
public Problem preCheck() {
cancelRequest = false;
cancelRequested.set(false);
// Element subType = refactoring.getTypeElement();
// if(!(subType instanceof JavaClass)){
// String errMsg = NbBundle.getMessage(UseSuperTypeRefactoringPlugin.class,
// "ERR_UseSuperType_InvalidElement"); // NOI18N
// return new Problem(true, errMsg);
// }
return null;
}
/**
* @return A problem indicating that no super type was selected.
*/
@Override
public Problem fastCheckParameters() {
if (refactoring.getTargetSuperType() == null) {
return new Problem(true, NbBundle.getMessage(UseSuperTypeRefactoringPlugin.class, "ERR_UseSuperTypeNoSuperType"));
}
return null;
}
/**
* A no op. Returns null
*/
@Override
public Problem checkParameters() {
return null;
}
//---------private methods follow--------
private void replaceSubtypeUsages(final TreePathHandle subClassHandle, final RefactoringElementsBag elemsBag) {
JavaSource javaSrc = JavaSource.forFileObject(subClassHandle.getFileObject());
try {
javaSrc.runUserActionTask(new CancellableTask<CompilationController>() {
@Override
public void cancel() { }
@Override
public void run(CompilationController complController) throws IOException {
complController.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
FileObject fo = subClassHandle.getFileObject();
ClasspathInfo classpathInfo = RefactoringUtils.getClasspathInfoFor(true, true, fo) ;
ClassIndex clsIndx = classpathInfo.getClassIndex();
TypeElement javaClassElement = (TypeElement) subClassHandle.
resolveElement(complController);
EnumSet<ClassIndex.SearchKind> typeRefSearch = EnumSet.of(ClassIndex.SearchKind.TYPE_REFERENCES);
Set<FileObject> refFileObjSet = clsIndx.getResources(ElementHandle.create(javaClassElement), typeRefSearch, EnumSet.of(ClassIndex.SearchScope.SOURCE));
if (!refFileObjSet.isEmpty()) {
fireProgressListenerStart(AbstractRefactoring.PREPARE, refFileObjSet.size());
try{
Collection<ModificationResult> results = processFiles(refFileObjSet, new FindRefTask(subClassHandle, refactoring.getTargetSuperType()));
elemsBag.registerTransaction(createTransaction(results));
for (ModificationResult result : results) {
for (FileObject fileObj : result.getModifiedFileObjects()) {
for (Difference diff : result.getDifferences(fileObj)) {
String old = diff.getOldText();
if (old != null) {
elemsBag.add(refactoring, DiffElement.create(diff, fileObj, result));
}
}
}
}
}finally{
fireProgressListenerStop();
}
}
}
}, false);
} catch (IOException ioex) {
ioex.printStackTrace();
}
}
private final class FindRefTask implements CancellableTask<WorkingCopy> {
private final TreePathHandle subClassHandle;
private final ElementHandle superClassHandle;
private FindRefTask(TreePathHandle subClassHandle, ElementHandle superClassHandle) {
this.subClassHandle = subClassHandle;
this.superClassHandle = superClassHandle;
}
@Override
public void cancel() {
}
@Override
public void run(WorkingCopy compiler) throws Exception {
try {
if (compiler.toPhase(JavaSource.Phase.RESOLVED) != JavaSource.Phase.RESOLVED) {
return;
}
CompilationUnitTree cu = compiler.getCompilationUnit();
if (cu == null) {
ErrorManager.getDefault().log(ErrorManager.ERROR, "compiler.getCompilationUnit() is null " + compiler); // NOI18N
return;
}
Element subClassElement = subClassHandle.resolveElement(compiler);
Element superClassElement = superClassHandle.resolve(compiler);
if (superClassElement == null) {
//superClassElement is not resolvable in this project
//do not replace
//#202030
return;
}
assert subClassElement != null;
ReferencesVisitor findRefVisitor = new ReferencesVisitor(compiler, subClassElement, superClassElement);
findRefVisitor.scan(compiler.getCompilationUnit(), subClassElement);
} finally {
fireProgressListenerStep();
}
}
}
private static class ReferencesVisitor extends RefactoringVisitor {
private final TypeElement superTypeElement;
private final TypeElement subTypeElement;
private ReferencesVisitor(WorkingCopy workingCopy, Element subClassElement, Element superClassElement) {
try {
setWorkingCopy(workingCopy);
} catch (ToPhaseException phase) {
//should never be thrown;
Exceptions.printStackTrace(phase);
}
this.superTypeElement = (TypeElement) superClassElement;
this.subTypeElement = (TypeElement) subClassElement;
}
@Override
public Tree visitMemberSelect(MemberSelectTree memSelTree, Element elemToFind) {
Element elem = asElement(memSelTree);
if ((elem != null) && isStatic(elem)) {
Element expreElem = asElement(memSelTree.getExpression());
//If a static member was referenced using the object instead
//of the class, don't handle it here.
if(expreElem == null || ! (ElementKind.CLASS.equals(expreElem.getKind()) ||
ElementKind.INTERFACE.equals(expreElem.getKind()))){
return super.visitMemberSelect(memSelTree, elemToFind);
}
TypeElement type = (TypeElement) expreElem;
if (!subTypeElement.equals(type)) {
return super.visitMemberSelect(memSelTree, elemToFind);
}
if (hidesSupTypeMember(elem, superTypeElement)) {
replaceType(memSelTree, superTypeElement);
}
}
return super.visitMemberSelect(memSelTree, elemToFind);
}
@Override
public Tree visitVariable(VariableTree varTree, Element elementToMatch) {
TreePath treePath = getCurrentPath();
VariableElement varElement = (VariableElement) workingCopy.
getTrees().getElement(treePath);
//This check shouldn't be needed (ideally).
if (varElement == null) {
return super.visitVariable(varTree, elementToMatch);
}
TreePath parentPath = treePath.getParentPath();
if(parentPath != null && parentPath.getLeaf().getKind() == Tree.Kind.CATCH) {
// Do not change in catch statement
return super.visitVariable(varTree, elementToMatch);
}
Types types = workingCopy.getTypes();
TypeMirror varTypeErasure = types.erasure(varElement.asType());
TypeMirror elToMatchErasure = types.erasure(subTypeElement.asType());
if (types.isSameType(varTypeErasure, elToMatchErasure)) {
//Check for overloaded methods
boolean clashWithOverload = false;
if(parentPath != null && parentPath.getLeaf().getKind() == Tree.Kind.METHOD) {
Trees trees = workingCopy.getTrees();
ExecutableElement parent = (ExecutableElement) trees.getElement(parentPath);
TreePath enclosing = JavaRefactoringUtils.findEnclosingClass(workingCopy, parentPath, true, true, true, true, true);
TypeElement typeEl = (TypeElement) (enclosing == null? null : trees.getElement(enclosing));
if(parent != null && typeEl != null) {
Name simpleName = parent.getSimpleName();
int size = parent.getParameters().size();
OUTER: for (ExecutableElement method : ElementFilter.methodsIn(workingCopy.getElements().getAllMembers(typeEl))) {
if (method != parent &&
method.getKind() == parent.getKind() &&
size == method.getParameters().size() &&
simpleName.contentEquals(method.getSimpleName())) {
for (int i = 0; i < parent.getParameters().size(); i++) {
VariableElement par = parent.getParameters().get(i);
TypeMirror parErasure = types.erasure(par.asType());
TypeMirror par2Erasure = types.erasure(method.getParameters().get(i).asType());
if(!types.isSameType(parErasure, par2Erasure)) {
if(types.isAssignable(types.erasure(superTypeElement.asType()), par2Erasure)) {
clashWithOverload = true;
break OUTER;
}
if(types.isSubtype(parErasure, par2Erasure)) {
clashWithOverload = true;
break OUTER;
}
}
}
}
}
}
}
if (!clashWithOverload && isReplaceCandidate(varElement)) {
replaceWithSuperType(treePath, varTree, varElement, superTypeElement);
}
}
return super.visitVariable(varTree, elementToMatch);
}
// handles only simple situations like
// ArrayList list = (ArrayList) getList();
// where ArrayList is being substituted by a supertype
@Override
public Tree visitTypeCast(TypeCastTree castTree, Element elementToMatch) {
TreePath path = getCurrentPath();
Types types = workingCopy.getTypes();
TypeMirror castTypeErasure = types.erasure(workingCopy.getTrees().getTypeMirror(path));
TypeMirror elToMatchErasure = types.erasure(subTypeElement.asType());
path = path.getParentPath();
Element element = workingCopy.getTrees().getElement(path);
if (element instanceof VariableElement && types.isSameType(castTypeErasure, elToMatchErasure)) {
VariableElement varElement = (VariableElement)element;
TypeMirror varTypeErasure = types.erasure(varElement.asType());
if (types.isSameType(varTypeErasure, elToMatchErasure) && isReplaceCandidate(varElement)) {
TypeCastTree newTree = make.TypeCast(
make.Identifier(superTypeElement), castTree.getExpression());
rewrite(castTree, newTree);
}
}
return super.visitTypeCast(castTree, elementToMatch);
}
private boolean hidesSupTypeMember(Element methElement, TypeElement superTypeElement) {
Elements elements = workingCopy.getElements();
List<? extends Element> containedElements = elements.getAllMembers(superTypeElement);
for (Element elem : containedElements) {
boolean isPresentInSuperType = methElement.equals(elem) ||
elements.hides(methElement, elem);
if ((elem != null) && isStatic(elem) && isPresentInSuperType) {
return true;
}
}
return false;
}
private boolean isReplaceCandidate(VariableElement varElement) {
VarUsageVisitor varUsagesVisitor = new VarUsageVisitor(subTypeElement,
workingCopy, superTypeElement);
varUsagesVisitor.scan(workingCopy.getCompilationUnit(), varElement);
return varUsagesVisitor.isReplaceCandidate();
}
private boolean isStatic(Element element) {
Set<Modifier> modifiers = element.getModifiers();
return modifiers.contains(Modifier.STATIC);
}
private void replaceType(MemberSelectTree memSelTree, Element superTypeElement) {
MemberSelectTree newTree = make.MemberSelect(
make.Identifier(superTypeElement), memSelTree.getIdentifier());
rewrite(memSelTree, newTree);
}
private void replaceWithSuperType(TreePath path, VariableTree oldVarTree, VariableElement varElement, Element superTypeElement) {
Types types = workingCopy.getTypes();
TypeMirror supTypeErasure = types.erasure(superTypeElement.asType());
DeclaredType varType = (DeclaredType) varElement.asType();
TypeMirror theType = null;
List<TypeMirror> supertypes = new LinkedList(types.directSupertypes(varType));
while(!supertypes.isEmpty()) {
TypeMirror supertype = supertypes.remove(0);
if(types.isSameType(types.erasure(supertype), supTypeErasure)) {
theType = supertype;
break;
}
supertypes.addAll(types.directSupertypes(supertype));
}
if(theType == null) {
theType = supTypeErasure;
}
Tree superTypeTree = make.Type(theType);
ExpressionTree oldInitTree = oldVarTree.getInitializer();
ModifiersTree oldModifiers = oldVarTree.getModifiers();
Tree newTree = make.Variable(oldModifiers, oldVarTree.getName(),
superTypeTree, oldInitTree);
rewrite(oldVarTree, newTree);
}
private Element asElement(Tree tree) {
Trees treeUtil = workingCopy.getTrees();
TreePath treePath = treeUtil.getPath(workingCopy.getCompilationUnit(), tree);
Element element = treeUtil.getElement(treePath);
return element;
}
}
}