| /* |
| * 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.refactoring.move.moveInner; |
| |
| import com.intellij.codeInsight.ChangeContextUtil; |
| import com.intellij.codeInsight.CodeInsightUtilCore; |
| import com.intellij.lang.findUsages.DescriptiveNameUtil; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.fileEditor.FileEditorManager; |
| import com.intellij.openapi.fileEditor.OpenFileDescriptor; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.CodeStyleManager; |
| import com.intellij.psi.codeStyle.JavaCodeStyleManager; |
| import com.intellij.psi.codeStyle.VariableKind; |
| import com.intellij.psi.search.LocalSearchScope; |
| import com.intellij.psi.search.searches.ReferencesSearch; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.refactoring.BaseRefactoringProcessor; |
| import com.intellij.refactoring.RefactoringBundle; |
| import com.intellij.refactoring.listeners.RefactoringElementListener; |
| import com.intellij.refactoring.move.MoveCallback; |
| import com.intellij.refactoring.move.moveClassesOrPackages.MoveClassesOrPackagesUtil; |
| import com.intellij.refactoring.rename.RenameUtil; |
| import com.intellij.refactoring.util.*; |
| import com.intellij.usageView.UsageInfo; |
| import com.intellij.usageView.UsageViewDescriptor; |
| import com.intellij.util.Function; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.VisibilityUtil; |
| import com.intellij.util.containers.HashMap; |
| import com.intellij.util.containers.MultiMap; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| /** |
| * created at Sep 24, 2001 |
| * @author Jeka |
| */ |
| public class MoveInnerProcessor extends BaseRefactoringProcessor { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.move.moveInner.MoveInnerProcessor"); |
| |
| private MoveCallback myMoveCallback; |
| |
| private PsiClass myInnerClass; |
| private PsiClass myOuterClass; |
| private PsiElement myTargetContainer; |
| private String myParameterNameOuterClass; |
| private String myFieldNameOuterClass; |
| private String myDescriptiveName = ""; |
| private String myNewClassName; |
| private boolean mySearchInComments; |
| private boolean mySearchInNonJavaFiles; |
| private NonCodeUsageInfo[] myNonCodeUsages; |
| |
| public MoveInnerProcessor(Project project, MoveCallback moveCallback) { |
| super(project); |
| myMoveCallback = moveCallback; |
| } |
| |
| public MoveInnerProcessor(Project project, |
| PsiClass innerClass, |
| String name, |
| boolean passOuterClass, |
| String parameterName, |
| final PsiElement targetContainer) { |
| super(project); |
| setup(innerClass, name, passOuterClass, parameterName, true, true, targetContainer); |
| } |
| |
| protected String getCommandName() { |
| return RefactoringBundle.message("move.inner.class.command", myDescriptiveName); |
| } |
| |
| @NotNull |
| protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) { |
| return new MoveInnerViewDescriptor(myInnerClass); |
| } |
| |
| @NotNull |
| protected UsageInfo[] findUsages() { |
| LOG.assertTrue(myTargetContainer != null); |
| |
| Collection<PsiReference> innerClassRefs = ReferencesSearch.search(myInnerClass).findAll(); |
| ArrayList<UsageInfo> usageInfos = new ArrayList<UsageInfo>(innerClassRefs.size()); |
| for (PsiReference innerClassRef : innerClassRefs) { |
| PsiElement ref = innerClassRef.getElement(); |
| if (!PsiTreeUtil.isAncestor(myInnerClass, ref, true)) { // do not show self-references |
| usageInfos.add(new UsageInfo(ref)); |
| } |
| } |
| |
| final String newQName; |
| if (myTargetContainer instanceof PsiDirectory) { |
| final PsiDirectory targetDirectory = (PsiDirectory)myTargetContainer; |
| final PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage(targetDirectory); |
| LOG.assertTrue(aPackage != null); |
| newQName = aPackage.getQualifiedName() + "." + myNewClassName; |
| } |
| else if (myTargetContainer instanceof PsiClass) { |
| final String qName = ((PsiClass)myTargetContainer).getQualifiedName(); |
| if (qName != null) { |
| newQName = qName + "." + myNewClassName; |
| } |
| else { |
| newQName = myNewClassName; |
| } |
| } |
| else { |
| newQName = myNewClassName; |
| } |
| MoveClassesOrPackagesUtil.findNonCodeUsages(mySearchInComments, mySearchInNonJavaFiles, |
| myInnerClass, newQName, usageInfos); |
| return usageInfos.toArray(new UsageInfo[usageInfos.size()]); |
| } |
| |
| protected void refreshElements(PsiElement[] elements) { |
| boolean condition = elements.length == 1 && elements[0] instanceof PsiClass; |
| LOG.assertTrue(condition); |
| myInnerClass = (PsiClass)elements[0]; |
| } |
| |
| public boolean isSearchInComments() { |
| return mySearchInComments; |
| } |
| |
| public void setSearchInComments(boolean searchInComments) { |
| mySearchInComments = searchInComments; |
| } |
| |
| public boolean isSearchInNonJavaFiles() { |
| return mySearchInNonJavaFiles; |
| } |
| |
| public void setSearchInNonJavaFiles(boolean searchInNonJavaFiles) { |
| mySearchInNonJavaFiles = searchInNonJavaFiles; |
| } |
| |
| protected void performRefactoring(final UsageInfo[] usages) { |
| final PsiManager manager = PsiManager.getInstance(myProject); |
| final PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory(); |
| |
| final RefactoringElementListener elementListener = getTransaction().getElementListener(myInnerClass); |
| try { |
| PsiField field = null; |
| if (myParameterNameOuterClass != null) { |
| // pass outer as a parameter |
| field = factory.createField(myFieldNameOuterClass, factory.createType(myOuterClass)); |
| field = addOuterField(field); |
| myInnerClass = field.getContainingClass(); |
| addFieldInitializationToConstructors(myInnerClass, field, myParameterNameOuterClass); |
| } |
| |
| ChangeContextUtil.encodeContextInfo(myInnerClass, false); |
| |
| myInnerClass = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(myInnerClass); |
| |
| final MoveInnerOptions moveInnerOptions = new MoveInnerOptions(myInnerClass, myOuterClass, myTargetContainer, myNewClassName); |
| final MoveInnerHandler handler = MoveInnerHandler.EP_NAME.forLanguage(myInnerClass.getLanguage()); |
| final PsiClass newClass; |
| try { |
| newClass = handler.copyClass(moveInnerOptions); |
| } |
| catch (IncorrectOperationException e) { |
| RefactoringUIUtil.processIncorrectOperation(myProject, e); |
| return; |
| } |
| |
| // replace references in a new class to old inner class with references to itself |
| for (PsiReference ref : ReferencesSearch.search(myInnerClass, new LocalSearchScope(newClass), true)) { |
| PsiElement element = ref.getElement(); |
| if (element.getParent() instanceof PsiJavaCodeReferenceElement) { |
| PsiJavaCodeReferenceElement parentRef = (PsiJavaCodeReferenceElement)element.getParent(); |
| PsiElement parentRefElement = parentRef.resolve(); |
| if (parentRefElement instanceof PsiClass) { // reference to inner class inside our inner |
| parentRef.getQualifier().delete(); |
| continue; |
| } |
| } |
| ref.bindToElement(newClass); |
| } |
| |
| List<PsiReference> referencesToRebind = new ArrayList<PsiReference>(); |
| for (UsageInfo usage : usages) { |
| if (usage.isNonCodeUsage) continue; |
| PsiElement refElement = usage.getElement(); |
| PsiReference[] references = refElement.getReferences(); |
| for (PsiReference reference : references) { |
| if (reference.isReferenceTo(myInnerClass)) { |
| referencesToRebind.add(reference); |
| } |
| } |
| } |
| |
| myInnerClass.delete(); |
| |
| // correct references in usages |
| for (UsageInfo usage : usages) { |
| if (usage.isNonCodeUsage || myParameterNameOuterClass == null) continue; // should pass outer as parameter |
| |
| MoveInnerClassUsagesHandler usagesHandler = MoveInnerClassUsagesHandler.EP_NAME.forLanguage(usage.getElement().getLanguage()); |
| if (usagesHandler != null) { |
| usagesHandler.correctInnerClassUsage(usage, myOuterClass); |
| } |
| } |
| |
| for (PsiReference reference : referencesToRebind) { |
| reference.bindToElement(newClass); |
| } |
| |
| for (UsageInfo usage : usages) { |
| final PsiElement element = usage.getElement(); |
| final PsiElement parent = element != null ? element.getParent() : null; |
| if (parent instanceof PsiNewExpression) { |
| final PsiMethod resolveConstructor = ((PsiNewExpression)parent).resolveConstructor(); |
| for (PsiMethod method : newClass.getConstructors()) { |
| if (resolveConstructor == method) { |
| final PsiElement place = usage.getElement(); |
| if (place != null) { |
| VisibilityUtil.escalateVisibility(method, place); |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| if (field != null) { |
| final PsiExpression paramAccessExpression = factory.createExpressionFromText(myParameterNameOuterClass, null); |
| for (final PsiMethod constructor : newClass.getConstructors()) { |
| final PsiStatement[] statements = constructor.getBody().getStatements(); |
| if (statements.length > 0) { |
| if (statements[0] instanceof PsiExpressionStatement) { |
| PsiExpression expression = ((PsiExpressionStatement)statements[0]).getExpression(); |
| if (expression instanceof PsiMethodCallExpression) { |
| @NonNls String text = ((PsiMethodCallExpression)expression).getMethodExpression().getText(); |
| if ("this".equals(text) || "super".equals(text)) { |
| ChangeContextUtil.decodeContextInfo(expression, myOuterClass, paramAccessExpression); |
| } |
| } |
| } |
| } |
| } |
| |
| PsiExpression accessExpression = factory.createExpressionFromText(myFieldNameOuterClass, null); |
| ChangeContextUtil.decodeContextInfo(newClass, myOuterClass, accessExpression); |
| } |
| else { |
| ChangeContextUtil.decodeContextInfo(newClass, null, null); |
| } |
| |
| PsiFile targetFile = newClass.getContainingFile(); |
| OpenFileDescriptor descriptor = new OpenFileDescriptor(myProject, targetFile.getVirtualFile(), newClass.getTextOffset()); |
| FileEditorManager.getInstance(myProject).openTextEditor(descriptor, true); |
| |
| if (myMoveCallback != null) { |
| myMoveCallback.refactoringCompleted(); |
| } |
| elementListener.elementMoved(newClass); |
| |
| List<NonCodeUsageInfo> nonCodeUsages = new ArrayList<NonCodeUsageInfo>(); |
| for (UsageInfo usage : usages) { |
| if (usage instanceof NonCodeUsageInfo) { |
| nonCodeUsages.add((NonCodeUsageInfo)usage); |
| } |
| } |
| myNonCodeUsages = nonCodeUsages.toArray(new NonCodeUsageInfo[nonCodeUsages.size()]); |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| } |
| |
| private PsiField addOuterField(PsiField field) { |
| final PsiMember[] members = PsiTreeUtil.getChildrenOfType(myInnerClass, PsiMember.class); |
| if (members != null) { |
| for (PsiMember member : members) { |
| if (!member.hasModifierProperty(PsiModifier.STATIC)) { |
| return (PsiField)myInnerClass.addBefore(field, member); |
| } |
| } |
| } |
| |
| return (PsiField)myInnerClass.add(field); |
| } |
| |
| protected void performPsiSpoilingRefactoring() { |
| if (myNonCodeUsages != null) { |
| RenameUtil.renameNonCodeUsages(myProject, myNonCodeUsages); |
| } |
| } |
| |
| protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) { |
| final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>(); |
| final HashMap<PsiElement,HashSet<PsiElement>> reported = new HashMap<PsiElement, HashSet<PsiElement>>(); |
| class Visitor extends JavaRecursiveElementWalkingVisitor { |
| |
| |
| @Override public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { |
| PsiElement resolved = reference.resolve(); |
| if (resolved instanceof PsiMember && |
| PsiTreeUtil.isAncestor(myInnerClass, resolved, true) && |
| becomesInaccessible((PsiMember)resolved)) { |
| registerConflict(reference, resolved, reported, conflicts); |
| } |
| } |
| |
| |
| |
| |
| @Override public void visitClass(PsiClass aClass) { |
| if (aClass == myInnerClass) return; |
| super.visitClass(aClass); |
| } |
| } |
| |
| // if (myInnerClass.hasModifierProperty(PsiModifier.)) { |
| myOuterClass.accept(new Visitor()); |
| myInnerClass.accept(new JavaRecursiveElementWalkingVisitor() { |
| @Override |
| public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { |
| super.visitReferenceElement(reference); |
| final PsiElement resolve = reference.resolve(); |
| if (resolve instanceof PsiMember) { |
| if (PsiTreeUtil.isAncestor(myOuterClass, resolve, true) && !PsiTreeUtil.isAncestor(myInnerClass, resolve, false)) { |
| if (becomesInaccessible((PsiMember)resolve)) { |
| registerConflict(reference, resolve, reported, conflicts); |
| } |
| } |
| } |
| } |
| }); |
| |
| return showConflicts(conflicts, refUsages.get()); |
| } |
| |
| private static void registerConflict(PsiJavaCodeReferenceElement reference, |
| PsiElement resolved, |
| HashMap<PsiElement, HashSet<PsiElement>> reported, MultiMap<PsiElement, String> conflicts) { |
| final PsiElement container = ConflictsUtil.getContainer(reference); |
| HashSet<PsiElement> containerSet = reported.get(container); |
| if (containerSet == null) { |
| containerSet = new HashSet<PsiElement>(); |
| reported.put(container, containerSet); |
| } |
| if (!containerSet.contains(resolved)) { |
| containerSet.add(resolved); |
| String placesDescription; |
| if (containerSet.size() == 1) { |
| placesDescription = RefactoringUIUtil.getDescription(resolved, true); |
| } else { |
| placesDescription = "<ol><li>" + StringUtil.join(containerSet, new Function<PsiElement, String>() { |
| @Override |
| public String fun(PsiElement element) { |
| return RefactoringUIUtil.getDescription(element, true); |
| } |
| }, "</li><li>") + "</li></ol>"; |
| } |
| String message = RefactoringBundle.message("0.will.become.inaccessible.from.1", |
| placesDescription, |
| RefactoringUIUtil.getDescription(container, true)); |
| conflicts.put(container, Collections.singletonList(message)); |
| } |
| } |
| |
| private boolean becomesInaccessible(PsiMember element) { |
| final String visibilityModifier = VisibilityUtil.getVisibilityModifier(element.getModifierList()); |
| if (PsiModifier.PRIVATE.equals(visibilityModifier)) return true; |
| if (PsiModifier.PUBLIC.equals(visibilityModifier)) return false; |
| final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(myProject); |
| if (myTargetContainer instanceof PsiDirectory) { |
| final PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage((PsiDirectory)myTargetContainer); |
| assert aPackage != null : myTargetContainer; |
| return !psiFacade.isInPackage(myOuterClass, aPackage); |
| } |
| // target container is a class |
| PsiFile targetFile = myTargetContainer.getContainingFile(); |
| if (targetFile != null) { |
| final PsiDirectory containingDirectory = targetFile.getContainingDirectory(); |
| if (containingDirectory != null) { |
| final PsiPackage targetPackage = JavaDirectoryService.getInstance().getPackage(containingDirectory); |
| assert targetPackage != null : myTargetContainer; |
| return psiFacade.isInPackage(myOuterClass, targetPackage); |
| } |
| } |
| return false; |
| } |
| |
| public void setup(final PsiClass innerClass, |
| final String className, |
| final boolean passOuterClass, |
| final String parameterName, |
| boolean searchInComments, |
| boolean searchInNonJava, |
| @NotNull final PsiElement targetContainer) { |
| myNewClassName = className; |
| myInnerClass = innerClass; |
| myDescriptiveName = DescriptiveNameUtil.getDescriptiveName(myInnerClass); |
| myOuterClass = myInnerClass.getContainingClass(); |
| myTargetContainer = targetContainer; |
| JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(myProject); |
| myParameterNameOuterClass = passOuterClass ? parameterName : null; |
| if (myParameterNameOuterClass != null) { |
| myFieldNameOuterClass = |
| codeStyleManager.variableNameToPropertyName(myParameterNameOuterClass, VariableKind.PARAMETER); |
| myFieldNameOuterClass = codeStyleManager.propertyNameToVariableName(myFieldNameOuterClass, VariableKind.FIELD); |
| } |
| mySearchInComments = searchInComments; |
| mySearchInNonJavaFiles = searchInNonJava; |
| } |
| |
| private void addFieldInitializationToConstructors(PsiClass aClass, PsiField field, String parameterName) |
| throws IncorrectOperationException { |
| |
| PsiMethod[] constructors = aClass.getConstructors(); |
| PsiElementFactory factory = JavaPsiFacade.getInstance(myProject).getElementFactory(); |
| if (constructors.length > 0) { |
| for (PsiMethod constructor : constructors) { |
| if (parameterName != null) { |
| PsiParameterList parameterList = constructor.getParameterList(); |
| PsiParameter parameter = factory.createParameter(parameterName, field.getType()); |
| parameterList.addAfter(parameter, null); |
| } |
| PsiCodeBlock body = constructor.getBody(); |
| if (body == null) continue; |
| PsiStatement[] statements = body.getStatements(); |
| if (statements.length > 0) { |
| PsiStatement first = statements[0]; |
| if (first instanceof PsiExpressionStatement) { |
| PsiExpression expression = ((PsiExpressionStatement)first).getExpression(); |
| if (expression instanceof PsiMethodCallExpression) { |
| @NonNls String text = ((PsiMethodCallExpression)expression).getMethodExpression().getText(); |
| if ("this".equals(text)) { |
| continue; |
| } |
| } |
| } |
| } |
| createAssignmentStatement(constructor, field.getName(), parameterName); |
| } |
| } |
| else { |
| PsiMethod constructor = factory.createConstructor(); |
| if (parameterName != null) { |
| PsiParameterList parameterList = constructor.getParameterList(); |
| PsiParameter parameter = factory.createParameter(parameterName, field.getType()); |
| parameterList.add(parameter); |
| } |
| createAssignmentStatement(constructor, field.getName(), parameterName); |
| aClass.add(constructor); |
| } |
| } |
| |
| private PsiStatement createAssignmentStatement(PsiMethod constructor, String fieldName, String parameterName) |
| throws IncorrectOperationException { |
| |
| PsiElementFactory factory = JavaPsiFacade.getInstance(myProject).getElementFactory(); |
| @NonNls String pattern = fieldName + "=a;"; |
| if (fieldName.equals(parameterName)) { |
| pattern = "this." + pattern; |
| } |
| |
| PsiExpressionStatement statement = (PsiExpressionStatement)factory.createStatementFromText(pattern, null); |
| statement = (PsiExpressionStatement)CodeStyleManager.getInstance(myProject).reformat(statement); |
| |
| PsiCodeBlock body = constructor.getBody(); |
| assert body != null : constructor; |
| statement = (PsiExpressionStatement)body.addAfter(statement, getAnchorElement(body)); |
| |
| PsiAssignmentExpression assignment = (PsiAssignmentExpression)statement.getExpression(); |
| PsiReferenceExpression rExpr = (PsiReferenceExpression)assignment.getRExpression(); |
| assert rExpr != null : assignment; |
| PsiIdentifier identifier = (PsiIdentifier)rExpr.getReferenceNameElement(); |
| assert identifier != null : assignment; |
| identifier.replace(factory.createIdentifier(parameterName)); |
| return statement; |
| } |
| |
| @Nullable |
| private static PsiElement getAnchorElement(PsiCodeBlock body) { |
| PsiStatement[] statements = body.getStatements(); |
| if (statements.length > 0) { |
| PsiStatement first = statements[0]; |
| if (first instanceof PsiExpressionStatement) { |
| |
| PsiExpression expression = ((PsiExpressionStatement)first).getExpression(); |
| if (expression instanceof PsiMethodCallExpression) { |
| PsiReferenceExpression methodCall = ((PsiMethodCallExpression)expression).getMethodExpression(); |
| @NonNls String text = methodCall.getText(); |
| if ("super".equals(text)) { |
| return first; |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| public PsiClass getInnerClass() { |
| return myInnerClass; |
| } |
| |
| public String getNewClassName() { |
| return myNewClassName; |
| } |
| |
| public boolean shouldPassParameter() { |
| return myParameterNameOuterClass != null; |
| } |
| |
| |
| public String getParameterName() { |
| return myParameterNameOuterClass; |
| } |
| } |