| /* |
| * 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.changeClassSignature; |
| |
| import com.intellij.history.LocalHistory; |
| import com.intellij.history.LocalHistoryAction; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.JavaCodeStyleManager; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.search.searches.ReferencesSearch; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.refactoring.BaseRefactoringProcessor; |
| import com.intellij.refactoring.changeSignature.ChangeSignatureUtil; |
| import com.intellij.refactoring.listeners.RefactoringEventData; |
| import com.intellij.refactoring.util.RefactoringUIUtil; |
| import com.intellij.usageView.UsageInfo; |
| import com.intellij.usageView.UsageViewDescriptor; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.containers.MultiMap; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| /** |
| * @author dsl |
| */ |
| public class ChangeClassSignatureProcessor extends BaseRefactoringProcessor { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.changeClassSignature.ChangeClassSignatureProcessor"); |
| private PsiClass myClass; |
| private final TypeParameterInfo[] myNewSignature; |
| |
| public ChangeClassSignatureProcessor(Project project, PsiClass aClass, TypeParameterInfo[] newSignature) { |
| super(project); |
| myClass = aClass; |
| myNewSignature = newSignature; |
| } |
| |
| protected void refreshElements(PsiElement[] elements) { |
| LOG.assertTrue(elements.length == 1); |
| LOG.assertTrue(elements[0] instanceof PsiClass); |
| myClass = (PsiClass)elements[0]; |
| } |
| |
| protected String getCommandName() { |
| return ChangeClassSignatureDialog.REFACTORING_NAME; |
| } |
| |
| @NotNull |
| protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) { |
| return new ChangeClassSigntaureViewDescriptor(myClass); |
| } |
| |
| @Override |
| protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) { |
| final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>(); |
| |
| final PsiTypeParameter[] parameters = myClass.getTypeParameters(); |
| final Map<String, TypeParameterInfo> infos = new HashMap<String, TypeParameterInfo>(); |
| for (TypeParameterInfo info : myNewSignature) { |
| final String newName = info.isForExistingParameter() ? parameters[info.getOldParameterIndex()].getName() : info.getNewName(); |
| TypeParameterInfo existing = infos.get(newName); |
| if (existing != null) { |
| conflicts.putValue(myClass, RefactoringUIUtil.getDescription(myClass, false) + " already contains type parameter " + newName); |
| } |
| infos.put(newName, info); |
| } |
| return showConflicts(conflicts, refUsages.get()); |
| } |
| |
| @NotNull |
| protected UsageInfo[] findUsages() { |
| GlobalSearchScope projectScope = GlobalSearchScope.projectScope(myProject); |
| List<UsageInfo> result = new ArrayList<UsageInfo>(); |
| |
| boolean hadTypeParameters = myClass.hasTypeParameters(); |
| for (final PsiReference reference : ReferencesSearch.search(myClass, projectScope, false)) { |
| if (reference.getElement() instanceof PsiJavaCodeReferenceElement) { |
| PsiJavaCodeReferenceElement referenceElement = (PsiJavaCodeReferenceElement)reference.getElement(); |
| PsiElement parent = referenceElement.getParent(); |
| if (parent instanceof PsiTypeElement && parent.getParent() instanceof PsiInstanceOfExpression) continue; |
| if (parent instanceof PsiNewExpression && PsiUtil.isLanguageLevel7OrHigher(parent)) { |
| final PsiReferenceParameterList parameterList = referenceElement.getParameterList(); |
| if (parameterList != null) { |
| final PsiTypeElement[] parameterElements = parameterList.getTypeParameterElements(); |
| if (parameterElements.length == 1 && parameterElements[0].getType() instanceof PsiDiamondType) continue; |
| } |
| } |
| if (parent instanceof PsiTypeElement || parent instanceof PsiNewExpression || parent instanceof PsiAnonymousClass || |
| parent instanceof PsiReferenceList) { |
| if (!hadTypeParameters || referenceElement.getTypeParameters().length > 0) { |
| result.add(new UsageInfo(referenceElement)); |
| } |
| } |
| } |
| } |
| return result.toArray(new UsageInfo[result.size()]); |
| } |
| |
| protected void performRefactoring(UsageInfo[] usages) { |
| LocalHistoryAction a = LocalHistory.getInstance().startAction(getCommandName()); |
| try { |
| doRefactoring(usages); |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| finally { |
| a.finish(); |
| } |
| } |
| |
| @Nullable |
| @Override |
| protected String getRefactoringId() { |
| return "refactoring.changeClassSignature"; |
| } |
| |
| @Nullable |
| @Override |
| protected RefactoringEventData getBeforeData() { |
| RefactoringEventData data = new RefactoringEventData(); |
| data.addElement(myClass); |
| return data; |
| } |
| |
| @Nullable |
| @Override |
| protected RefactoringEventData getAfterData(UsageInfo[] usages) { |
| RefactoringEventData data = new RefactoringEventData(); |
| data.addElement(myClass); |
| return data; |
| } |
| |
| private void doRefactoring(UsageInfo[] usages) throws IncorrectOperationException { |
| final PsiTypeParameter[] typeParameters = myClass.getTypeParameters(); |
| final boolean[] toRemoveParms = detectRemovedParameters(typeParameters); |
| |
| for (final UsageInfo usage : usages) { |
| LOG.assertTrue(usage.getElement() instanceof PsiJavaCodeReferenceElement); |
| processUsage(usage, typeParameters, toRemoveParms); |
| } |
| final Map<PsiTypeElement, PsiClass> supersMap = new HashMap<PsiTypeElement, PsiClass>(); |
| myClass.accept(new JavaRecursiveElementWalkingVisitor() { |
| @Override |
| public void visitTypeElement(PsiTypeElement typeElement) { |
| super.visitTypeElement(typeElement); |
| final PsiType type = typeElement.getType(); |
| final PsiClass psiClass = PsiUtil.resolveClassInType(type); |
| if (psiClass instanceof PsiTypeParameter) { |
| final int i = ArrayUtil.find(typeParameters, psiClass); |
| if ( i >= 0 && i < toRemoveParms.length && toRemoveParms[i]) { |
| supersMap.put(typeElement, psiClass.getSuperClass()); |
| } |
| } |
| } |
| }); |
| final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(myProject); |
| for (Map.Entry<PsiTypeElement, PsiClass> classEntry : supersMap.entrySet()) { |
| classEntry.getKey().replace(elementFactory.createTypeElement(elementFactory.createType(classEntry.getValue()))); |
| } |
| changeClassSignature(typeParameters, toRemoveParms); |
| } |
| |
| private void changeClassSignature(final PsiTypeParameter[] originalTypeParameters, boolean[] toRemoveParms) |
| throws IncorrectOperationException { |
| PsiElementFactory factory = JavaPsiFacade.getInstance(myClass.getProject()).getElementFactory(); |
| List<PsiTypeParameter> newTypeParameters = new ArrayList<PsiTypeParameter>(); |
| for (final TypeParameterInfo info : myNewSignature) { |
| int oldIndex = info.getOldParameterIndex(); |
| if (oldIndex >= 0) { |
| newTypeParameters.add(originalTypeParameters[oldIndex]); |
| } |
| else { |
| newTypeParameters.add(factory.createTypeParameterFromText(info.getNewName(), null)); |
| } |
| } |
| ChangeSignatureUtil.synchronizeList(myClass.getTypeParameterList(), newTypeParameters, TypeParameterList.INSTANCE, toRemoveParms); |
| } |
| |
| private boolean[] detectRemovedParameters(final PsiTypeParameter[] original) { |
| final boolean[] toRemove = new boolean[original.length]; |
| Arrays.fill(toRemove, true); |
| for (final TypeParameterInfo info : myNewSignature) { |
| int oldParameterIndex = info.getOldParameterIndex(); |
| if (oldParameterIndex >= 0) { |
| toRemove[oldParameterIndex] = false; |
| } |
| } |
| return toRemove; |
| } |
| |
| private void processUsage(UsageInfo usage, PsiTypeParameter[] original, boolean[] toRemove) throws IncorrectOperationException { |
| PsiElementFactory factory = JavaPsiFacade.getInstance(myClass.getProject()).getElementFactory(); |
| PsiJavaCodeReferenceElement referenceElement = (PsiJavaCodeReferenceElement)usage.getElement(); |
| assert referenceElement != null : usage; |
| PsiSubstitutor usageSubstitutor = determineUsageSubstitutor(referenceElement); |
| |
| PsiReferenceParameterList referenceParameterList = referenceElement.getParameterList(); |
| assert referenceParameterList != null : referenceElement; |
| PsiTypeElement[] oldValues = referenceParameterList.getTypeParameterElements(); |
| if (oldValues.length != original.length) return; |
| List<PsiTypeElement> newValues = new ArrayList<PsiTypeElement>(); |
| for (final TypeParameterInfo info : myNewSignature) { |
| int oldIndex = info.getOldParameterIndex(); |
| if (oldIndex >= 0) { |
| newValues.add(oldValues[oldIndex]); |
| } |
| else { |
| PsiType type = info.getDefaultValue().getType(myClass.getLBrace(), PsiManager.getInstance(myProject)); |
| |
| PsiTypeElement newValue = factory.createTypeElement(usageSubstitutor.substitute(type)); |
| newValues.add(newValue); |
| } |
| } |
| |
| ChangeSignatureUtil.synchronizeList(referenceParameterList, newValues, ReferenceParameterList.INSTANCE, toRemove); |
| JavaCodeStyleManager.getInstance(myProject).shortenClassReferences(referenceParameterList); |
| } |
| |
| private PsiSubstitutor determineUsageSubstitutor(PsiJavaCodeReferenceElement referenceElement) { |
| PsiType[] typeArguments = referenceElement.getTypeParameters(); |
| PsiSubstitutor usageSubstitutor = PsiSubstitutor.EMPTY; |
| PsiTypeParameter[] typeParameters = myClass.getTypeParameters(); |
| if (typeParameters.length == typeArguments.length) { |
| for (int i = 0; i < typeParameters.length; i++) { |
| usageSubstitutor = usageSubstitutor.put(typeParameters[i], typeArguments[i]); |
| } |
| } |
| return usageSubstitutor; |
| } |
| |
| private static class ReferenceParameterList implements ChangeSignatureUtil.ChildrenGenerator<PsiReferenceParameterList, PsiTypeElement> { |
| private static final ReferenceParameterList INSTANCE = new ReferenceParameterList(); |
| |
| public List<PsiTypeElement> getChildren(PsiReferenceParameterList list) { |
| return Arrays.asList(list.getTypeParameterElements()); |
| } |
| } |
| |
| private static class TypeParameterList implements ChangeSignatureUtil.ChildrenGenerator<PsiTypeParameterList, PsiTypeParameter> { |
| private static final TypeParameterList INSTANCE = new TypeParameterList(); |
| |
| public List<PsiTypeParameter> getChildren(PsiTypeParameterList psiTypeParameterList) { |
| return Arrays.asList(psiTypeParameterList.getTypeParameters()); |
| } |
| } |
| |
| } |