| /* |
| * Copyright 2000-2014 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.refactoring.rename; |
| |
| import com.intellij.codeInsight.ChangeContextUtil; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.light.LightElement; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.search.LocalSearchScope; |
| import com.intellij.psi.search.SearchScope; |
| import com.intellij.psi.search.searches.ClassInheritorsSearch; |
| import com.intellij.psi.search.searches.ReferencesSearch; |
| import com.intellij.psi.util.ClassUtil; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtilCore; |
| import com.intellij.refactoring.HelpID; |
| import com.intellij.refactoring.JavaRefactoringSettings; |
| import com.intellij.refactoring.RefactoringBundle; |
| import com.intellij.refactoring.listeners.RefactoringElementListener; |
| import com.intellij.refactoring.util.MoveRenameUsageInfo; |
| import com.intellij.refactoring.util.RefactoringUIUtil; |
| import com.intellij.refactoring.util.RefactoringUtil; |
| import com.intellij.usageView.UsageInfo; |
| import com.intellij.util.ArrayUtilRt; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.Processor; |
| import com.intellij.util.containers.MultiMap; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| import java.util.regex.Pattern; |
| |
| /** |
| * @author yole |
| */ |
| public class RenameJavaClassProcessor extends RenamePsiElementProcessor { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.rename.RenameJavaClassProcessor"); |
| |
| public boolean canProcessElement(@NotNull final PsiElement element) { |
| return element instanceof PsiClass; |
| } |
| |
| public void renameElement(final PsiElement element, |
| final String newName, |
| final UsageInfo[] usages, |
| @Nullable RefactoringElementListener listener) throws IncorrectOperationException { |
| PsiClass aClass = (PsiClass) element; |
| ArrayList<UsageInfo> postponedCollisions = new ArrayList<UsageInfo>(); |
| List<MemberHidesOuterMemberUsageInfo> hidesOut = new ArrayList<MemberHidesOuterMemberUsageInfo>(); |
| // rename all references |
| for (final UsageInfo usage : usages) { |
| if (usage instanceof ResolvableCollisionUsageInfo) { |
| if (usage instanceof CollidingClassImportUsageInfo) { |
| ((CollidingClassImportUsageInfo)usage).getImportStatement().delete(); |
| } else if (usage instanceof MemberHidesOuterMemberUsageInfo) { |
| final PsiElement usageElement = usage.getElement(); |
| final PsiJavaCodeReferenceElement collidingRef = (PsiJavaCodeReferenceElement)usageElement; |
| if (collidingRef != null) { |
| hidesOut.add(new MemberHidesOuterMemberUsageInfo(usageElement, (PsiClass)collidingRef.resolve())); |
| } |
| } |
| else { |
| postponedCollisions.add(usage); |
| } |
| } |
| } |
| |
| // do actual rename |
| ChangeContextUtil.encodeContextInfo(aClass.getContainingFile(), true, false); |
| aClass.setName(newName); |
| |
| for (UsageInfo usage : usages) { |
| if (!(usage instanceof ResolvableCollisionUsageInfo)) { |
| final PsiReference ref = usage.getReference(); |
| if (ref == null) continue; |
| try { |
| ref.bindToElement(aClass); |
| } |
| catch (IncorrectOperationException e) {//fall back to old scheme |
| ref.handleElementRename(newName); |
| } |
| } |
| } |
| |
| ChangeContextUtil.decodeContextInfo(aClass.getContainingFile(), null, null); //to make refs to other classes from this one resolve to their old referent |
| |
| // resolve collisions |
| for (UsageInfo postponedCollision : postponedCollisions) { |
| ClassHidesImportedClassUsageInfo collision = (ClassHidesImportedClassUsageInfo) postponedCollision; |
| collision.resolveCollision(); |
| } |
| |
| for (MemberHidesOuterMemberUsageInfo usage : hidesOut) { |
| PsiJavaCodeReferenceElement collidingRef = (PsiJavaCodeReferenceElement)usage.getElement(); |
| PsiMember member = (PsiMember)usage.getReferencedElement(); |
| if (collidingRef != null && collidingRef.isValid() && member != null && member.isValid()) { |
| final PsiManager manager = member.getManager(); |
| final PsiElementFactory factory = JavaPsiFacade.getElementFactory(member.getProject()); |
| final String name = member.getName(); |
| final PsiClass containingClass = member.getContainingClass(); |
| if (name != null && containingClass != null) { |
| if (manager.areElementsEquivalent(factory.createReferenceFromText(name, collidingRef).resolve(), member)) continue; |
| final PsiJavaCodeReferenceElement ref = factory.createReferenceFromText("A." + name, collidingRef); |
| final PsiJavaCodeReferenceElement qualifier = (PsiJavaCodeReferenceElement)ref.getQualifier(); |
| LOG.assertTrue(qualifier != null); |
| final PsiJavaCodeReferenceElement classReference = factory.createClassReferenceElement(containingClass); |
| qualifier.replace(classReference); |
| collidingRef.replace(ref); |
| } |
| } |
| } |
| if (listener != null) { |
| listener.elementRenamed(aClass); |
| } |
| } |
| |
| @Nullable |
| public Pair<String, String> getTextOccurrenceSearchStrings(@NotNull final PsiElement element, @NotNull final String newName) { |
| if (element instanceof PsiClass) { |
| final PsiClass aClass = (PsiClass)element; |
| if (aClass.getParent() instanceof PsiClass) { |
| final String dollaredStringToSearch = ClassUtil.getJVMClassName(aClass); |
| final String dollaredStringToReplace = dollaredStringToSearch == null ? null : RefactoringUtil.getNewInnerClassName(aClass, dollaredStringToSearch, newName); |
| if (dollaredStringToReplace != null) { |
| return Pair.create(dollaredStringToSearch, dollaredStringToReplace); |
| } |
| } |
| } |
| return null; |
| } |
| |
| public String getQualifiedNameAfterRename(final PsiElement element, final String newName, final boolean nonJava) { |
| if (nonJava) { |
| final PsiClass aClass = (PsiClass)element; |
| return PsiUtilCore.getQualifiedNameAfterRename(aClass.getQualifiedName(), newName); |
| } |
| else { |
| return newName; |
| } |
| } |
| |
| @Override |
| public void prepareRenaming(PsiElement element, String newName, Map<PsiElement, String> allRenames, SearchScope scope) { |
| final PsiMethod[] constructors = ((PsiClass) element).getConstructors(); |
| for (PsiMethod constructor : constructors) { |
| if (constructor instanceof PsiMirrorElement) { |
| final PsiElement prototype = ((PsiMirrorElement)constructor).getPrototype(); |
| if (prototype instanceof PsiNamedElement) { |
| allRenames.put(prototype, newName); |
| } |
| } |
| else if (!(constructor instanceof LightElement)) { |
| allRenames.put(constructor, newName); |
| } |
| } |
| } |
| |
| public void findCollisions(final PsiElement element, final String newName, final Map<? extends PsiElement, String> allRenames, final List<UsageInfo> result) { |
| final PsiClass aClass = (PsiClass)element; |
| final ClassCollisionsDetector classCollisionsDetector = new ClassCollisionsDetector(aClass); |
| Collection<UsageInfo> initialResults = new ArrayList<UsageInfo>(result); |
| for(UsageInfo usageInfo: initialResults) { |
| if (usageInfo instanceof MoveRenameUsageInfo) { |
| classCollisionsDetector.addClassCollisions(usageInfo.getElement(), newName, result); |
| } |
| } |
| findSubmemberHidesMemberCollisions(aClass, newName, result); |
| |
| if (aClass instanceof PsiTypeParameter) { |
| final PsiTypeParameterListOwner owner = ((PsiTypeParameter)aClass).getOwner(); |
| if (owner != null) { |
| for (PsiTypeParameter typeParameter : owner.getTypeParameters()) { |
| if (Comparing.equal(newName, typeParameter.getName())) { |
| result.add(new UnresolvableCollisionUsageInfo(aClass, typeParameter) { |
| @Override |
| public String getDescription() { |
| return "There is already type parameter in " + RefactoringUIUtil.getDescription(aClass, false) + " with name " + newName; |
| } |
| }); |
| } |
| } |
| } |
| } |
| } |
| |
| public static void findSubmemberHidesMemberCollisions(final PsiClass aClass, final String newName, final List<UsageInfo> result) { |
| if (aClass.getParent() instanceof PsiClass) { |
| PsiClass parent = (PsiClass)aClass.getParent(); |
| Collection<PsiClass> inheritors = ClassInheritorsSearch.search(parent, true).findAll(); |
| for (PsiClass inheritor : inheritors) { |
| if (newName.equals(inheritor.getName())) { |
| final ClassCollisionsDetector classCollisionsDetector = new ClassCollisionsDetector(aClass); |
| for (PsiReference reference : ReferencesSearch.search(inheritor, new LocalSearchScope(inheritor))) { |
| classCollisionsDetector.addClassCollisions(reference.getElement(), newName, result); |
| } |
| } |
| PsiClass[] inners = inheritor.getInnerClasses(); |
| for (PsiClass inner : inners) { |
| if (newName.equals(inner.getName())) { |
| result.add(new SubmemberHidesMemberUsageInfo(inner, aClass)); |
| } |
| } |
| } |
| } else if (aClass instanceof PsiTypeParameter) { |
| final PsiTypeParameterListOwner owner = ((PsiTypeParameter)aClass).getOwner(); |
| if (owner instanceof PsiClass) { |
| final PsiClass[] supers = ((PsiClass)owner).getSupers(); |
| for (PsiClass superClass : supers) { |
| if (newName.equals(superClass.getName())) { |
| final ClassCollisionsDetector classCollisionsDetector = new ClassCollisionsDetector(aClass); |
| for (PsiReference reference : ReferencesSearch.search(superClass, new LocalSearchScope(superClass))) { |
| classCollisionsDetector.addClassCollisions(reference.getElement(), newName, result); |
| } |
| } |
| PsiClass[] inners = superClass.getInnerClasses(); |
| for (final PsiClass inner : inners) { |
| if (newName.equals(inner.getName())) { |
| ReferencesSearch.search(inner).forEach(new Processor<PsiReference>() { |
| public boolean process(final PsiReference reference) { |
| PsiElement refElement = reference.getElement(); |
| if (refElement instanceof PsiReferenceExpression && ((PsiReferenceExpression)refElement).isQualified()) return true; |
| MemberHidesOuterMemberUsageInfo info = new MemberHidesOuterMemberUsageInfo(refElement, aClass); |
| result.add(info); |
| return true; |
| } |
| }); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private static class ClassCollisionsDetector { |
| final HashSet<PsiFile> myProcessedFiles = new HashSet<PsiFile>(); |
| final PsiClass myRenamedClass; |
| private final String myRenamedClassQualifiedName; |
| |
| public ClassCollisionsDetector(PsiClass renamedClass) { |
| myRenamedClass = renamedClass; |
| myRenamedClassQualifiedName = myRenamedClass.getQualifiedName(); |
| } |
| |
| public void addClassCollisions(PsiElement referenceElement, String newName, List<UsageInfo> results) { |
| final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(referenceElement.getProject()).getResolveHelper(); |
| final PsiClass aClass = resolveHelper.resolveReferencedClass(newName, referenceElement); |
| if (aClass == null) return; |
| if (aClass instanceof PsiTypeParameter && myRenamedClass instanceof PsiTypeParameter) { |
| final PsiTypeParameterListOwner member = PsiTreeUtil.getParentOfType(referenceElement, PsiTypeParameterListOwner.class); |
| if (member != null) { |
| final PsiTypeParameterList typeParameterList = member.getTypeParameterList(); |
| if (typeParameterList != null && ArrayUtilRt.find(typeParameterList.getTypeParameters(), myRenamedClass) > -1) { |
| if (member.hasModifierProperty(PsiModifier.STATIC)) return; |
| } |
| } |
| } |
| final PsiFile containingFile = referenceElement.getContainingFile(); |
| final String text = referenceElement.getText(); |
| if (Comparing.equal(myRenamedClassQualifiedName, removeSpaces(text))) return; |
| if (myProcessedFiles.contains(containingFile)) return; |
| for (PsiReference reference : ReferencesSearch.search(aClass, new LocalSearchScope(containingFile))) { |
| final PsiElement collisionReferenceElement = reference.getElement(); |
| if (collisionReferenceElement instanceof PsiJavaCodeReferenceElement) { |
| final PsiElement parent = collisionReferenceElement.getParent(); |
| if (parent instanceof PsiImportStatement) { |
| results.add(new CollidingClassImportUsageInfo((PsiImportStatement)parent, myRenamedClass)); |
| } |
| else { |
| if (aClass.getQualifiedName() != null) { |
| results.add(new ClassHidesImportedClassUsageInfo((PsiJavaCodeReferenceElement)collisionReferenceElement, |
| myRenamedClass, aClass)); |
| } |
| else { |
| results.add(new ClassHidesUnqualifiableClassUsageInfo((PsiJavaCodeReferenceElement)collisionReferenceElement, |
| myRenamedClass, aClass)); |
| } |
| } |
| } |
| } |
| myProcessedFiles.add(containingFile); |
| } |
| } |
| |
| @NonNls private static final Pattern WHITE_SPACE_PATTERN = Pattern.compile("\\s"); |
| |
| private static String removeSpaces(String s) { |
| return WHITE_SPACE_PATTERN.matcher(s).replaceAll(""); |
| } |
| |
| public void findExistingNameConflicts(final PsiElement element, final String newName, final MultiMap<PsiElement,String> conflicts) { |
| if (element instanceof PsiCompiledElement) return; |
| final PsiClass aClass = (PsiClass)element; |
| if (newName.equals(aClass.getName())) return; |
| final PsiClass containingClass = aClass.getContainingClass(); |
| if (containingClass != null) { // innerClass |
| PsiClass[] innerClasses = containingClass.getInnerClasses(); |
| for (PsiClass innerClass : innerClasses) { |
| if (newName.equals(innerClass.getName())) { |
| conflicts.putValue(innerClass, RefactoringBundle.message("inner.class.0.is.already.defined.in.class.1", newName, containingClass.getQualifiedName())); |
| break; |
| } |
| } |
| } |
| else if (!(aClass instanceof PsiTypeParameter)) { |
| final String qualifiedNameAfterRename = PsiUtilCore.getQualifiedNameAfterRename(aClass.getQualifiedName(), newName); |
| Project project = element.getProject(); |
| final PsiClass conflictingClass = |
| JavaPsiFacade.getInstance(project).findClass(qualifiedNameAfterRename, GlobalSearchScope.allScope(project)); |
| if (conflictingClass != null) { |
| conflicts.putValue(conflictingClass, RefactoringBundle.message("class.0.already.exists", qualifiedNameAfterRename)); |
| } |
| } |
| } |
| |
| @Nullable |
| @NonNls |
| public String getHelpID(final PsiElement element) { |
| return HelpID.RENAME_CLASS; |
| } |
| |
| public boolean isToSearchInComments(final PsiElement psiElement) { |
| return JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_CLASS; |
| } |
| |
| public void setToSearchInComments(final PsiElement element, final boolean enabled) { |
| JavaRefactoringSettings.getInstance().RENAME_SEARCH_IN_COMMENTS_FOR_CLASS = enabled; |
| } |
| |
| public boolean isToSearchForTextOccurrences(final PsiElement element) { |
| return JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_CLASS; |
| } |
| |
| public void setToSearchForTextOccurrences(final PsiElement element, final boolean enabled) { |
| JavaRefactoringSettings.getInstance().RENAME_SEARCH_FOR_TEXT_FOR_CLASS = enabled; |
| } |
| } |