/* * Copyright 2000-2013 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.daemon.impl.quickfix; import com.intellij.codeInsight.daemon.QuickFixBundle; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.UnfairTextRange; import com.intellij.psi.*; import com.intellij.psi.impl.PsiDiamondTypeUtil; import com.intellij.psi.util.PsiExpressionTrimRenderer; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class ChangeNewOperatorTypeFix implements IntentionAction { private final PsiType myType; private final PsiNewExpression myExpression; private ChangeNewOperatorTypeFix(PsiType type, PsiNewExpression expression) { myType = type; myExpression = expression; } @Override @NotNull public String getText() { return QuickFixBundle.message("change.new.operator.type.text", new PsiExpressionTrimRenderer.RenderFunction().fun(myExpression), myType.getPresentableText(), myType instanceof PsiArrayType ? "" : "()"); } @Override @NotNull public String getFamilyName() { return QuickFixBundle.message("change.new.operator.type.family"); } @Override public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { return myType.isValid() && myExpression.isValid() && myExpression.getManager().isInProject(myExpression) && !TypeConversionUtil.isPrimitiveAndNotNull(myType) && (myType instanceof PsiArrayType || myExpression.getArgumentList() != null) ; } @Override public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { changeNewOperatorType(myExpression, myType, editor); } private static void changeNewOperatorType(PsiNewExpression originalExpression, PsiType toType, final Editor editor) throws IncorrectOperationException { PsiNewExpression newExpression; PsiElementFactory factory = JavaPsiFacade.getInstance(originalExpression.getProject()).getElementFactory(); int caretOffset; TextRange selection; if (toType instanceof PsiArrayType) { final PsiExpression[] originalExpressionArrayDimensions = originalExpression.getArrayDimensions(); caretOffset = 0; @NonNls String text = "new " + toType.getDeepComponentType().getCanonicalText() + "["; if (originalExpressionArrayDimensions.length > 0) { text += originalExpressionArrayDimensions[0].getText(); } else { text += "0"; caretOffset = -2; } text += "]"; for (int i = 1; i < toType.getArrayDimensions(); i++) { text += "["; String arrayDimension = ""; if (originalExpressionArrayDimensions.length > i) { arrayDimension = originalExpressionArrayDimensions[i].getText(); text += arrayDimension; } text += "]"; if (caretOffset < 0) { caretOffset -= arrayDimension.length() + 2; } } newExpression = (PsiNewExpression)factory.createExpressionFromText(text, originalExpression); if (caretOffset < 0) { selection = new UnfairTextRange(caretOffset, caretOffset+1); } else { selection = null; } } else { final PsiAnonymousClass anonymousClass = originalExpression.getAnonymousClass(); newExpression = (PsiNewExpression)factory.createExpressionFromText("new " + toType.getCanonicalText() + "()" + (anonymousClass != null ? "{}" : ""), originalExpression); PsiExpressionList argumentList = originalExpression.getArgumentList(); if (argumentList == null) return; newExpression.getArgumentList().replace(argumentList); if (anonymousClass == null) { //just to prevent useless inference if (PsiDiamondTypeUtil.canCollapseToDiamond(newExpression, originalExpression, toType)) { final PsiElement paramList = PsiDiamondTypeUtil.replaceExplicitWithDiamond(newExpression.getClassOrAnonymousClassReference().getParameterList()); newExpression = PsiTreeUtil.getParentOfType(paramList, PsiNewExpression.class); } } if (anonymousClass != null) { PsiAnonymousClass newAnonymousClass = newExpression.getAnonymousClass(); final PsiElement childInside = anonymousClass.getLBrace().getNextSibling(); if (childInside != null) { newAnonymousClass.addRange(childInside, anonymousClass.getRBrace().getPrevSibling()); } } selection = null; caretOffset = -1; } PsiElement element = originalExpression.replace(newExpression); editor.getCaretModel().moveToOffset(element.getTextRange().getEndOffset() + caretOffset); editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); if (selection != null) { selection = selection.shiftRight(element.getTextRange().getEndOffset()); editor.getSelectionModel().setSelection(selection.getStartOffset(), selection.getEndOffset()); } } @Override public boolean startInWriteAction() { return true; } public static void register(final HighlightInfo highlightInfo, PsiExpression expression, final PsiType lType) { expression = PsiUtil.deparenthesizeExpression(expression); if (!(expression instanceof PsiNewExpression)) return; final PsiType rType = expression.getType(); PsiType newType = lType; if (rType instanceof PsiClassType && newType instanceof PsiClassType) { final PsiClassType.ClassResolveResult rResolveResult = ((PsiClassType)rType).resolveGenerics(); PsiClass rClass = rResolveResult.getElement(); if (rClass instanceof PsiAnonymousClass) { rClass = ((PsiAnonymousClass)rClass).getBaseClassType().resolve(); } if (rClass != null) { final PsiClassType.ClassResolveResult lResolveResult = ((PsiClassType)newType).resolveGenerics(); final PsiClass lClass = lResolveResult.getElement(); if (lClass != null) { PsiSubstitutor substitutor = getInheritorSubstitutorForNewExpression(lClass, rClass, lResolveResult.getSubstitutor(), expression); if (substitutor != null) { newType = JavaPsiFacade.getInstance(lClass.getProject()).getElementFactory().createType(rClass, substitutor); } } } } PsiNewExpression newExpression = (PsiNewExpression)expression; QuickFixAction.registerQuickFixAction(highlightInfo, new ChangeNewOperatorTypeFix(newType, newExpression)); } /* Guesswork */ @Nullable private static PsiSubstitutor getInheritorSubstitutorForNewExpression(final PsiClass baseClass, final PsiClass inheritor, final PsiSubstitutor baseSubstitutor, final PsiElement context) { final Project project = baseClass.getProject(); JavaPsiFacade facade = JavaPsiFacade.getInstance(project); final PsiResolveHelper resolveHelper = facade.getResolveHelper(); PsiSubstitutor superSubstitutor = TypeConversionUtil.getClassSubstitutor(baseClass, inheritor, PsiSubstitutor.EMPTY); if (superSubstitutor == null) return null; PsiSubstitutor inheritorSubstitutor = PsiSubstitutor.EMPTY; for (PsiTypeParameter inheritorParameter : PsiUtil.typeParametersIterable(inheritor)) { for (PsiTypeParameter baseParameter : PsiUtil.typeParametersIterable(baseClass)) { final PsiType substituted = superSubstitutor.substitute(baseParameter); PsiType arg = baseSubstitutor.substitute(baseParameter); if (arg instanceof PsiWildcardType) arg = ((PsiWildcardType)arg).getBound(); PsiType substitution = resolveHelper.getSubstitutionForTypeParameter(inheritorParameter, substituted, arg, true, PsiUtil.getLanguageLevel(context)); if (PsiType.NULL.equals(substitution)) continue; if (substitution == null) { return facade.getElementFactory().createRawSubstitutor(inheritor); } inheritorSubstitutor = inheritorSubstitutor.put(inheritorParameter, substitution); break; } } return inheritorSubstitutor; } }