blob: b93057fd060bf1ee00e2bc62153423dd4eb0e85d [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed 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 com.intellij.codeInsight;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Key;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.util.RefactoringChangeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ChangeContextUtil {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.ChangeContextUtil");
public static final Key<String> ENCODED_KEY = Key.create("ENCODED_KEY");
public static final Key<PsiClass> THIS_QUALIFIER_CLASS_KEY = Key.create("THIS_QUALIFIER_CLASS_KEY");
public static final Key<PsiMember> REF_MEMBER_KEY = Key.create("REF_MEMBER_KEY");
public static final Key<Boolean> CAN_REMOVE_QUALIFIER_KEY = Key.create("CAN_REMOVE_QUALIFIER_KEY");
public static final Key<PsiClass> REF_CLASS_KEY = Key.create("REF_CLASS_KEY");
public static final Key<PsiClass> REF_MEMBER_THIS_CLASS_KEY = Key.create("REF_MEMBER_THIS_CLASS_KEY");
private ChangeContextUtil() {}
public static void encodeContextInfo(PsiElement scope, boolean includeRefClasses) {
encodeContextInfo(scope, scope, includeRefClasses, true);
}
public static void encodeContextInfo(PsiElement scope, boolean includeRefClasses, boolean canChangeQualifier) {
encodeContextInfo(scope, scope, includeRefClasses, canChangeQualifier);
}
private static void encodeContextInfo(PsiElement scope,
PsiElement topLevelScope,
boolean includeRefClasses,
boolean canChangeQualifier) {
if (scope instanceof PsiThisExpression){
scope.putCopyableUserData(ENCODED_KEY, "");
PsiThisExpression thisExpr = (PsiThisExpression)scope;
final PsiJavaCodeReferenceElement qualifier = thisExpr.getQualifier();
if (qualifier == null){
PsiClass thisClass = RefactoringChangeUtil.getThisClass(thisExpr);
if (thisClass != null && !(thisClass instanceof PsiAnonymousClass)){
thisExpr.putCopyableUserData(THIS_QUALIFIER_CLASS_KEY, thisClass);
}
}
else {
final PsiElement resolved = qualifier.resolve();
if (resolved instanceof PsiClass && resolved == topLevelScope) {
thisExpr.putCopyableUserData(THIS_QUALIFIER_CLASS_KEY, (PsiClass)topLevelScope);
}
}
}
else if (scope instanceof PsiReferenceExpression){
scope.putCopyableUserData(ENCODED_KEY, "");
PsiReferenceExpression refExpr = (PsiReferenceExpression)scope;
PsiExpression qualifier = refExpr.getQualifierExpression();
if (qualifier == null){
final JavaResolveResult resolveResult = refExpr.advancedResolve(false);
final PsiElement refElement = resolveResult.getElement();
if (refElement != null && !PsiTreeUtil.isAncestor(topLevelScope, refElement, false)){
if (refElement instanceof PsiClass){
if (includeRefClasses){
refExpr.putCopyableUserData(REF_CLASS_KEY, (PsiClass)refElement);
}
}
else if (refElement instanceof PsiMember){
refExpr.putCopyableUserData(REF_MEMBER_KEY, ( (PsiMember)refElement));
final PsiElement resolveScope = resolveResult.getCurrentFileResolveScope();
if (resolveScope instanceof PsiClass && !PsiTreeUtil.isAncestor(topLevelScope, resolveScope, false)) {
refExpr.putCopyableUserData(REF_MEMBER_THIS_CLASS_KEY, (PsiClass)resolveScope);
}
}
}
}
else if (canChangeQualifier) {
refExpr.putCopyableUserData(CAN_REMOVE_QUALIFIER_KEY, canRemoveQualifier(refExpr) ? Boolean.TRUE : Boolean.FALSE);
}
}
else if (includeRefClasses) {
PsiReference ref = scope.getReference();
if (ref != null){
scope.putCopyableUserData(ENCODED_KEY, "");
PsiElement refElement = ref.resolve();
if (refElement instanceof PsiClass && !PsiTreeUtil.isAncestor(topLevelScope, refElement, false)){
scope.putCopyableUserData(REF_CLASS_KEY, (PsiClass)refElement);
}
}
}
for(PsiElement child = scope.getFirstChild(); child != null; child = child.getNextSibling()){
encodeContextInfo(child, topLevelScope, includeRefClasses, canChangeQualifier);
}
}
public static PsiElement decodeContextInfo(@NotNull PsiElement scope,
@Nullable PsiClass thisClass,
@Nullable PsiExpression thisAccessExpr) throws IncorrectOperationException {
if (scope.getCopyableUserData(ENCODED_KEY) != null) {
scope.putCopyableUserData(ENCODED_KEY, null);
if (scope instanceof PsiThisExpression) {
PsiThisExpression thisExpr = (PsiThisExpression)scope;
scope = decodeThisExpression(thisExpr, thisClass, thisAccessExpr);
}
else if (scope instanceof PsiReferenceExpression) {
scope = decodeReferenceExpression((PsiReferenceExpression)scope, thisAccessExpr, thisClass);
}
else {
PsiClass refClass = scope.getCopyableUserData(REF_CLASS_KEY);
scope.putCopyableUserData(REF_CLASS_KEY, null);
if (refClass != null && refClass.isValid()) {
PsiReference ref = scope.getReference();
if (ref != null) {
final String qualifiedName = refClass.getQualifiedName();
if (qualifiedName != null) {
if (JavaPsiFacade.getInstance(refClass.getProject()).findClass(qualifiedName, scope.getResolveScope()) != null) {
scope = ref.bindToElement(refClass);
}
}
}
}
}
}
if (scope instanceof PsiClass) {
if (thisAccessExpr != null) {
thisAccessExpr = (PsiExpression)qualifyThis(thisAccessExpr, thisClass);
}
}
PsiElement child = scope.getFirstChild();
while (child != null) {
child = decodeContextInfo(child, thisClass, thisAccessExpr).getNextSibling();
}
return scope;
}
private static PsiElement decodeThisExpression(PsiThisExpression thisExpr,
PsiClass thisClass,
PsiExpression thisAccessExpr) throws IncorrectOperationException {
final PsiJavaCodeReferenceElement qualifier = thisExpr.getQualifier();
PsiClass encodedQualifierClass = thisExpr.getCopyableUserData(THIS_QUALIFIER_CLASS_KEY);
thisExpr.putCopyableUserData(THIS_QUALIFIER_CLASS_KEY, null);
if (qualifier == null){
if (encodedQualifierClass != null && encodedQualifierClass.isValid()){
if (encodedQualifierClass.equals(thisClass) && thisAccessExpr != null && thisAccessExpr.isValid()){
return thisExpr.replace(thisAccessExpr);
}
}
}
else {
PsiClass qualifierClass = (PsiClass)qualifier.resolve();
if (encodedQualifierClass == qualifierClass && thisClass != null) {
qualifier.bindToElement(thisClass);
}
else {
if (qualifierClass != null) {
if (qualifierClass.equals(thisClass) && thisAccessExpr != null && thisAccessExpr.isValid()) {
return thisExpr.replace(thisAccessExpr);
}
}
}
}
return thisExpr;
}
private static PsiReferenceExpression decodeReferenceExpression(@NotNull PsiReferenceExpression refExpr,
PsiExpression thisAccessExpr,
PsiClass thisClass) throws IncorrectOperationException {
PsiManager manager = refExpr.getManager();
PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory();
PsiExpression qualifier = refExpr.getQualifierExpression();
if (qualifier == null){
PsiMember refMember = refExpr.getCopyableUserData(REF_MEMBER_KEY);
refExpr.putCopyableUserData(REF_MEMBER_KEY, null);
if (refMember != null && refMember.isValid()){
PsiClass containingClass = refMember.getContainingClass();
if (refMember.hasModifierProperty(PsiModifier.STATIC)){
PsiElement refElement = refExpr.resolve();
if (!manager.areElementsEquivalent(refMember, refElement)){
refExpr.setQualifierExpression(factory.createReferenceExpression(containingClass));
}
}
else {
final PsiClass realParentClass = refExpr.getCopyableUserData(REF_MEMBER_THIS_CLASS_KEY);
refExpr.putCopyableUserData(REF_MEMBER_THIS_CLASS_KEY, null);
if (thisAccessExpr != null && thisClass != null && realParentClass != null &&
InheritanceUtil.isInheritorOrSelf(thisClass, realParentClass, true)) {
boolean needQualifier = true;
PsiElement refElement = refExpr.resolve();
if (refMember.equals(refElement) ||
(refElement instanceof PsiMethod && refMember instanceof PsiMethod && ArrayUtil.find(((PsiMethod)refElement).findSuperMethods(), refMember) > -1)){
if (thisAccessExpr instanceof PsiThisExpression && ((PsiThisExpression)thisAccessExpr).getQualifier() == null) {
//Trivial qualifier
needQualifier = false;
}
else {
final PsiClass currentClass = findThisClass(refExpr, refMember);
if (thisAccessExpr instanceof PsiThisExpression){
PsiJavaCodeReferenceElement thisQualifier = ((PsiThisExpression)thisAccessExpr).getQualifier();
PsiClass thisExprClass = thisQualifier != null
? (PsiClass)thisQualifier.resolve()
: RefactoringChangeUtil.getThisClass(refExpr);
if (currentClass.equals(thisExprClass) || thisExprClass.isInheritor(realParentClass, true)){ // qualifier is not necessary
needQualifier = false;
}
}
}
}
if (needQualifier){
refExpr.setQualifierExpression(thisAccessExpr);
}
}
else if (thisClass != null && realParentClass != null && PsiTreeUtil.isAncestor(realParentClass, thisClass, true)) {
PsiElement refElement = refExpr.resolve();
if (refElement != null && !manager.areElementsEquivalent(refMember, refElement)) {
refExpr = RefactoringChangeUtil.qualifyReference(refExpr, refMember, null);
}
}
}
}
else {
PsiClass refClass = refExpr.getCopyableUserData(REF_CLASS_KEY);
refExpr.putCopyableUserData(REF_CLASS_KEY, null);
if (refClass != null && refClass.isValid()){
refExpr = (PsiReferenceExpression)refExpr.bindToElement(refClass);
}
}
}
else{
Boolean couldRemove = refExpr.getCopyableUserData(CAN_REMOVE_QUALIFIER_KEY);
refExpr.putCopyableUserData(CAN_REMOVE_QUALIFIER_KEY, null);
if (couldRemove == Boolean.FALSE && canRemoveQualifier(refExpr)){
PsiReferenceExpression newRefExpr = (PsiReferenceExpression)factory.createExpressionFromText(
refExpr.getReferenceName(), null);
refExpr = (PsiReferenceExpression)refExpr.replace(newRefExpr);
}
}
return refExpr;
}
private static PsiClass findThisClass(PsiReferenceExpression refExpr, PsiMember refMember) {
LOG.assertTrue(refExpr.getQualifierExpression() == null);
final PsiClass refMemberClass = refMember.getContainingClass();
if (refMemberClass == null) return null;
PsiElement parent = refExpr.getContext();
while(parent != null){
if (parent instanceof PsiClass){
if (parent.equals(refMemberClass) || ((PsiClass)parent).isInheritor(refMemberClass, true)){
return (PsiClass)parent;
}
}
parent = parent.getContext();
}
return refMemberClass;
}
public static boolean canRemoveQualifier(PsiReferenceExpression refExpr) {
try{
PsiExpression qualifier = refExpr.getQualifierExpression();
if (!(qualifier instanceof PsiReferenceExpression)) return false;
if (refExpr.getTypeParameters().length > 0) return false;
PsiElement qualifierRefElement = ((PsiReferenceExpression)qualifier).resolve();
if (!(qualifierRefElement instanceof PsiClass)) return false;
PsiElement refElement = refExpr.resolve();
if (refElement == null) return false;
PsiElementFactory factory = JavaPsiFacade.getInstance(refExpr.getProject()).getElementFactory();
if (refExpr.getParent() instanceof PsiMethodCallExpression){
PsiMethodCallExpression methodCall = (PsiMethodCallExpression)refExpr.getParent();
PsiMethodCallExpression newMethodCall = (PsiMethodCallExpression)factory.createExpressionFromText(
refExpr.getReferenceName() + "()", refExpr);
newMethodCall.getArgumentList().replace(methodCall.getArgumentList());
PsiElement newRefElement = newMethodCall.getMethodExpression().resolve();
return refElement.equals(newRefElement);
}
else if (refExpr instanceof PsiMethodReferenceExpression) {
return false;
}
else {
PsiReferenceExpression newRefExpr = (PsiReferenceExpression)factory.createExpressionFromText(
refExpr.getReferenceName(), refExpr);
PsiElement newRefElement = newRefExpr.resolve();
return refElement.equals(newRefElement);
}
}
catch(IncorrectOperationException e){
LOG.error(e);
return false;
}
}
private static PsiElement qualifyThis(PsiElement scope, PsiClass thisClass) throws IncorrectOperationException {
if (scope instanceof PsiThisExpression){
PsiThisExpression thisExpr = (PsiThisExpression)scope;
if (thisExpr.getQualifier() == null){
if (thisClass instanceof PsiAnonymousClass) return null;
PsiThisExpression qualifiedThis = RefactoringChangeUtil.createThisExpression(thisClass.getManager(), thisClass);
if (thisExpr.getParent() != null) {
return thisExpr.replace(qualifiedThis);
} else {
return qualifiedThis;
}
}
}
else if (!(scope instanceof PsiClass)){
for(PsiElement child = scope.getFirstChild(); child != null; child = child.getNextSibling()){
if (qualifyThis(child, thisClass) == null) return null;
}
}
return scope;
}
public static PsiClass getThisClass(PsiElement element) {
return PsiTreeUtil.getParentOfType(element, PsiClass.class);
}
public static void clearContextInfo(PsiElement scope) {
scope.putCopyableUserData(THIS_QUALIFIER_CLASS_KEY, null);
scope.putCopyableUserData(REF_MEMBER_KEY, null);
scope.putCopyableUserData(CAN_REMOVE_QUALIFIER_KEY, null);
for(PsiElement child = scope.getFirstChild(); child != null; child = child.getNextSibling()){
clearContextInfo(child);
}
}
}