blob: 48a53691911d15e1a6b27982cc2a4d2183b43b7a [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.util.List;
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.type.TypeVariable;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import org.netbeans.api.java.source.WorkingCopy;
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.util.Exceptions;
/**
* @author Jan Becicka, Bharath Ravi Kumar
*/
class VarUsageVisitor extends RefactoringVisitor {
private final TypeElement superTypeElement;
private final TypeElement subTypeElement;
private boolean isReplCandidate = true;
VarUsageVisitor(TypeElement subTypeElement, WorkingCopy workingCopy,
TypeElement superTypeElem) {
try {
setWorkingCopy(workingCopy);
} catch (ToPhaseException ex) {
Exceptions.printStackTrace(ex);
}
this.superTypeElement = superTypeElem;
this.subTypeElement = subTypeElement;
}
@Override
public Tree visitReturn(ReturnTree node, Element p) {
ExpressionTree expression = node.getExpression();
checkReturnType(expression, p);
return super.visitReturn(node, p);
}
private void checkReturnType(ExpressionTree expression, Element p) {
if(expression == null || p == null) { return; }
if(expression.getKind() == Tree.Kind.IDENTIFIER) {
IdentifierTree ident = (IdentifierTree) expression;
checkReturnType(ident, p);
} else if(expression.getKind() == Tree.Kind.CONDITIONAL_EXPRESSION) {
ConditionalExpressionTree cet = (ConditionalExpressionTree) expression;
checkReturnType(cet.getFalseExpression(), p);
checkReturnType(cet.getTrueExpression(), p);
}
}
private void checkReturnType(IdentifierTree ident, Element p) {
Element el = asElement(ident);
if(el.equals(p)) {
MethodTree method = (MethodTree) JavaPluginUtils.findMethod(getCurrentPath()).getLeaf();
TypeMirror returnType = workingCopy.getTrees().getTypeMirror(workingCopy.getTrees().getPath(workingCopy.getCompilationUnit(), method.getReturnType()));
if(!workingCopy.getTypes().isSubtype(superTypeElement.asType(), returnType)) {
isReplCandidate = false;
}
}
}
@Override
public Tree visitMemberSelect(MemberSelectTree memSelectTree, Element refVarElem) {
Element methodElement = this.asElement(memSelectTree);
Element varElement = asElement(memSelectTree.getExpression());
if (!refVarElem.equals(varElement)) {
return super.visitMemberSelect(memSelectTree, refVarElem);
}
boolean isAssgCmptble = isMemberAvailable(subTypeElement, methodElement,
superTypeElement);
if (!isAssgCmptble) {
isReplCandidate = false;
}
return super.visitMemberSelect(memSelectTree, refVarElem);
}
@Override
public Tree visitMethodInvocation(MethodInvocationTree node, Element p) {
List<? extends ExpressionTree> arguments = node.getArguments();
for (int i = 0; i < arguments.size(); i++) {
ExpressionTree argument = arguments.get(i);
Element argElement = asElement(argument); // TODO: Slow and misses ternary expressions
if(p.equals(argElement)) {
Element element = asElement(node);
if (element.getKind() == ElementKind.METHOD) {
ExecutableElement method = (ExecutableElement) element;
VariableElement parameter = method.getParameters().get(i);
Types types = workingCopy.getTypes();
TypeMirror parameterType = parameter.asType();
if(parameterType.getKind().equals(TypeKind.TYPEVAR)) {
TypeVariable typeVariable = (TypeVariable) parameterType;
TypeMirror upperBound = typeVariable.getUpperBound();
TypeMirror lowerBound = typeVariable.getLowerBound();
if(upperBound != null && !types.isSubtype(superTypeElement.asType(), upperBound)) {
isReplCandidate = false;
}
if(lowerBound != null && !types.isSubtype(lowerBound, superTypeElement.asType())) {
isReplCandidate = false;
}
} else if(!types.isAssignable(superTypeElement.asType(), parameterType)) {
isReplCandidate = false;
}
}
}
}
return super.visitMethodInvocation(node, p);
}
@Override
public Tree visitAssignment(AssignmentTree assgnTree, Element refVarElem) {
ExpressionTree exprnTree = assgnTree.getExpression();
Element exprElement = asElement(exprnTree);
if (!refVarElem.equals(exprElement)) {
return super.visitAssignment(assgnTree, refVarElem);
}
ExpressionTree varExprTree = assgnTree.getVariable();
VariableElement varElement = (VariableElement) asElement(varExprTree);
isReplCandidate = isReplacableAssgnmt(varElement) && isReplCandidate;
return super.visitAssignment(assgnTree, refVarElem);
}
@Override
public Tree visitVariable(VariableTree varTree, Element refVarElem) {
ExpressionTree initTree = varTree.getInitializer();
if (null == initTree) {
return super.visitVariable(varTree, refVarElem);
}
Element exprElement = asElement(initTree);
if (!refVarElem.equals(exprElement)) {
return super.visitVariable(varTree, refVarElem);
}
VariableElement varElement = (VariableElement) asElement(varTree);
isReplCandidate = isReplacableAssgnmt(varElement) && isReplCandidate;
return super.visitVariable(varTree, refVarElem);
}
@Override
public Tree visitMethod(MethodTree node, Element p) {
List<? extends VariableTree> parameters = node.getParameters();
for (VariableTree variableTree : parameters) {
Element var = workingCopy.getTrees().getElement(new TreePath(getCurrentPath(), variableTree));
ExecutableElement element = (ExecutableElement) workingCopy.getTrees().getElement(getCurrentPath());
if(p.equals(var) && element != null) {
// if var is != null then the enclosing method and class must exist as well.
TypeElement classElement = (TypeElement) workingCopy.getTrees().getElement(JavaRefactoringUtils.findEnclosingClass(workingCopy, getCurrentPath(), true, true, true, true, false));
List<ExecutableElement> methods = ElementFilter.methodsIn(workingCopy.getElements().getAllMembers(classElement));
String methodName = node.getName().toString();
for (ExecutableElement method : methods) {
if(methodName.equals(method.getSimpleName().toString())
&& node.getParameters().size() == method.getParameters().size()) {
boolean sameParameters = true;
for (int j = 0; j < node.getParameters().size(); j++) {
TypeMirror exType = ((VariableElement)method.getParameters().get(j)).asType();
TypeMirror type;
VariableElement vari = (VariableElement)element.getParameters().get(j);
if(p.equals(vari)) {
type = superTypeElement.asType();
} else {
type = vari.asType();
}
if(!workingCopy.getTypes().isSameType(exType, type)) {
sameParameters = false;
}
}
if(sameParameters) {
isReplCandidate = false;
}
}
}
}
}
return super.visitMethod(node, p);
}
private boolean isMemberAvailable(TypeElement subTypeElement, Element methodElement,
TypeElement superTypeElement) {
ElementKind memberKind = methodElement.getKind();
if(ElementKind.METHOD.equals(memberKind)){
return isMethodAvailable(subTypeElement, (ExecutableElement)methodElement,
superTypeElement);
}else{
return isHidingMember(subTypeElement, methodElement, superTypeElement);
}
}
private boolean isMethodAvailable(TypeElement subTypeElement,
ExecutableElement execElem, TypeElement superTypeElement) {
Elements elements = workingCopy.getElements();
List<? extends Element> memberElements = elements.getAllMembers(superTypeElement);
for (Element elem : memberElements) {
if(ElementKind.METHOD.equals(elem.getKind())){
if(execElem.getModifiers().contains(Modifier.STATIC) && elements.hides(execElem, elem)){
return true;
}else{
if(execElem.equals(elem) || elements.overrides(execElem, (ExecutableElement)elem,
subTypeElement)){
return true;
}
}
}
}
return false;
}
private boolean isHidingMember(TypeElement subTypeElement, Element variableElement,
TypeElement superTypeElement) {
//TODO: We do not handle nested types yet (includes enums)
Elements elements = workingCopy.getElements();
List<? extends Element> memberElements = elements.getAllMembers(superTypeElement);
for (Element elem : memberElements) {
if(variableElement.equals(elem) || elements.hides(variableElement, elem)){
return true;
}
}
return false;
}
private boolean isReplacableAssgnmt(VariableElement varElement) {
if (isDeclaredType(varElement.asType())) {
DeclaredType declType = (DeclaredType) varElement.asType();
TypeElement varType = (TypeElement) declType.asElement();
if (isAssignable(superTypeElement, varType)) {
return true;
}
}
return false;
}
boolean isReplaceCandidate() {
return isReplCandidate;
}
private boolean isAssignable(TypeElement typeFrom, TypeElement typeTo) {
Types types = workingCopy.getTypes();
return types.isAssignable(typeFrom.asType(), typeTo.asType());
}
private Element asElement(Tree tree) {
Trees treeUtil = workingCopy.getTrees();
TreePath treePath = treeUtil.getPath(workingCopy.getCompilationUnit(), tree);
Element element = treeUtil.getElement(treePath);
return element;
}
private boolean isDeclaredType(TypeMirror type) {
return TypeKind.DECLARED.equals(type.getKind());
}
}