/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.refactor.inline; import com.intellij.codeInsight.TargetElementUtilBase; import com.intellij.codeInsight.highlighting.HighlightManager; import com.intellij.codeInsight.intention.QuickFixFactory; import com.intellij.lang.Language; import com.intellij.lang.refactoring.InlineActionHandler; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.WindowManager; import com.intellij.psi.JavaTokenType; import com.intellij.psi.PsiArrayAccessExpression; import com.intellij.psi.PsiAssignmentExpression; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiCodeBlock; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiJavaCodeReferenceElement; import com.intellij.psi.PsiLocalVariable; import com.intellij.psi.PsiPostfixExpression; import com.intellij.psi.PsiPrefixExpression; import com.intellij.psi.PsiReference; import com.intellij.psi.PsiReferenceExpression; import com.intellij.psi.PsiVariable; import com.intellij.psi.controlFlow.DefUseUtil; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.PsiUtilBase; import com.intellij.refactoring.HelpID; import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.util.CommonRefactoringUtil; import com.intellij.refactoring.util.InlineUtil; import com.intellij.refactoring.util.RefactoringMessageDialog; import com.intellij.util.ArrayUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.Processor; import com.intellij.util.Query; import gw.plugin.ij.lang.GosuLanguage; import gw.plugin.ij.lang.psi.api.expressions.IGosuReferenceExpression; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class GosuInlineLocalHandler extends InlineActionHandler { private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.inline.InlineLocalHandler"); private static final String REFACTORING_NAME = RefactoringBundle.message("inline.variable.title"); @Override public boolean isEnabledForLanguage(Language l) { return l instanceof GosuLanguage; } public boolean canInlineElement(PsiElement element) { return element != null && element.getLanguage() instanceof GosuLanguage && element instanceof PsiLocalVariable; } public void inlineElement(Project project, Editor editor, PsiElement element) { final PsiReference psiReference = TargetElementUtilBase.findReference(editor); final IGosuReferenceExpression refExpr = psiReference instanceof IGosuReferenceExpression ? ((IGosuReferenceExpression) psiReference) : null; invoke(project, editor, (PsiLocalVariable) element, refExpr); } /** * should be called in AtomicAction */ public static void invoke(@NotNull final Project project, final Editor editor, final PsiLocalVariable local, IGosuReferenceExpression refExpr) { if (!CommonRefactoringUtil.checkReadOnlyStatus(project, local)) { return; } final HighlightManager highlightManager = HighlightManager.getInstance(project); final String localName = local.getName(); final Query<PsiReference> query = ReferencesSearch.search(local, GlobalSearchScope.allScope(project), false); if (query.findFirst() == null) { LOG.assertTrue(refExpr == null); String message = RefactoringBundle.message("variable.is.never.used", localName); CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_VARIABLE); return; } final PsiClass containingClass = PsiTreeUtil.getParentOfType(local, PsiClass.class); final List<PsiClass> innerClassesWithUsages = Collections.synchronizedList(new ArrayList<PsiClass>()); final List<PsiElement> innerClassUsages = Collections.synchronizedList(new ArrayList<PsiElement>()); query.forEach(new Processor<PsiReference>() { public boolean process(final PsiReference psiReference) { final PsiElement element = psiReference.getElement(); PsiClass psiClass = PsiTreeUtil.getParentOfType(element, PsiClass.class); while (psiClass != containingClass && psiClass != null) { final PsiClass parentPsiClass = PsiTreeUtil.getParentOfType(psiClass, PsiClass.class, true); if (parentPsiClass == containingClass) { innerClassesWithUsages.add(psiClass); innerClassUsages.add(element); } psiClass = parentPsiClass; } return true; } }); final PsiCodeBlock containerBlock = PsiTreeUtil.getParentOfType(local, PsiCodeBlock.class); LOG.assertTrue(containerBlock != null, local); final PsiExpression defToInline = innerClassesWithUsages.isEmpty() ? getDefToInline(local, refExpr, containerBlock) : getDefToInline(local, innerClassesWithUsages.get(0), containerBlock); if (defToInline == null) { final String key = refExpr == null ? "variable.has.no.initializer" : "variable.has.no.dominating.definition"; String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message(key, localName)); CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_VARIABLE); return; } final List<PsiElement> refsToInlineList = new ArrayList<>(); Collections.addAll(refsToInlineList, DefUseUtil.getRefs(containerBlock, local, defToInline)); for (PsiElement innerClassUsage : innerClassUsages) { if (!refsToInlineList.contains(innerClassUsage)) { refsToInlineList.add(innerClassUsage); } } if (refsToInlineList.size() == 0) { String message = RefactoringBundle.message("variable.is.never.used.before.modification", localName); CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_VARIABLE); return; } final PsiElement[] refsToInline = PsiUtilBase.toPsiElementArray(refsToInlineList); EditorColorsManager manager = EditorColorsManager.getInstance(); final TextAttributes attributes = manager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); final TextAttributes writeAttributes = manager.getGlobalScheme().getAttributes(EditorColors.WRITE_SEARCH_RESULT_ATTRIBUTES); if (refExpr != null && PsiUtil.isAccessedForReading(refExpr) && ArrayUtil.find(refsToInline, refExpr) < 0) { final PsiElement[] defs = DefUseUtil.getDefs(containerBlock, local, refExpr); LOG.assertTrue(defs.length > 0); highlightManager.addOccurrenceHighlights(editor, defs, attributes, true, null); String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("variable.is.accessed.for.writing", localName)); CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_VARIABLE); WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting")); return; } PsiFile workingFile = local.getContainingFile(); for (PsiElement ref : refsToInline) { final PsiFile otherFile = ref.getContainingFile(); if (!otherFile.equals(workingFile)) { String message = RefactoringBundle.message("variable.is.referenced.in.multiple.files", localName); CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_VARIABLE); return; } } for (final PsiElement ref : refsToInline) { final PsiElement[] defs = DefUseUtil.getDefs(containerBlock, local, ref); boolean isSameDefinition = true; for (PsiElement def : defs) { isSameDefinition &= isSameDefinition(def, defToInline); } if (!isSameDefinition) { highlightManager.addOccurrenceHighlights(editor, defs, writeAttributes, true, null); highlightManager.addOccurrenceHighlights(editor, new PsiElement[]{ref}, attributes, true, null); String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("variable.is.accessed.for.writing.and.used.with.inlined", localName)); CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_VARIABLE); WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting")); return; } } final PsiElement writeAccess = checkRefsInAugmentedAssignmentOrUnaryModified(refsToInline); if (writeAccess != null) { HighlightManager.getInstance(project).addOccurrenceHighlights(editor, new PsiElement[]{writeAccess}, writeAttributes, true, null); String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("variable.is.accessed.for.writing", localName)); CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.INLINE_VARIABLE); WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting")); return; } if (editor != null && !ApplicationManager.getApplication().isUnitTestMode()) { // TODO : check if initializer uses fieldNames that possibly will be hidden by other // locals with the same names after inlining highlightManager.addOccurrenceHighlights( editor, refsToInline, attributes, true, null ); int occurrencesCount = refsToInline.length; String occurencesString = RefactoringBundle.message("occurences.string", occurrencesCount); final String promptKey = isInliningVariableInitializer(defToInline) ? "inline.local.variable.prompt" : "inline.local.variable.definition.prompt"; final String question = RefactoringBundle.message(promptKey, localName) + " " + occurencesString; RefactoringMessageDialog dialog = new RefactoringMessageDialog( REFACTORING_NAME, question, HelpID.INLINE_VARIABLE, "OptionPane.questionIcon", true, project); dialog.show(); if (!dialog.isOK()) { WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting")); return; } } final Runnable runnable = new Runnable() { public void run() { try { PsiExpression[] exprs = new PsiExpression[refsToInline.length]; for (int idx = 0; idx < refsToInline.length; idx++) { PsiJavaCodeReferenceElement refElement = (PsiJavaCodeReferenceElement) refsToInline[idx]; exprs[idx] = InlineUtil.inlineVariable(local, defToInline, refElement); } if (!isInliningVariableInitializer(defToInline)) { defToInline.getParent().delete(); } else { defToInline.delete(); } if (ReferencesSearch.search(local).findFirst() == null) { QuickFixFactory.getInstance().createRemoveUnusedVariableFix(local).invoke(project, editor, local.getContainingFile()); } if (editor != null && !ApplicationManager.getApplication().isUnitTestMode()) { highlightManager.addOccurrenceHighlights(editor, exprs, attributes, true, null); WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting")); } for (final PsiExpression expr : exprs) { InlineUtil.tryToInlineArrayCreationForVarargs(expr); } } catch (IncorrectOperationException e) { LOG.error(e); } } }; CommandProcessor.getInstance().executeCommand(project, new Runnable() { public void run() { ApplicationManager.getApplication().runWriteAction(runnable); } }, RefactoringBundle.message("inline.command", localName), null); } @Nullable public static PsiElement checkRefsInAugmentedAssignmentOrUnaryModified(final PsiElement[] refsToInline) { for (PsiElement element : refsToInline) { PsiElement parent = element.getParent(); if (parent instanceof PsiArrayAccessExpression) { element = parent; parent = parent.getParent(); } if (parent instanceof PsiAssignmentExpression && element == ((PsiAssignmentExpression) parent).getLExpression() || isUnaryWriteExpression(parent)) { return element; } } return null; } private static boolean isUnaryWriteExpression(PsiElement parent) { IElementType tokenType = null; if (parent instanceof PsiPrefixExpression) { tokenType = ((PsiPrefixExpression) parent).getOperationTokenType(); } if (parent instanceof PsiPostfixExpression) { tokenType = ((PsiPostfixExpression) parent).getOperationTokenType(); } return tokenType == JavaTokenType.PLUSPLUS || tokenType == JavaTokenType.MINUSMINUS; } private static boolean isSameDefinition(final PsiElement def, final PsiExpression defToInline) { if (def instanceof PsiLocalVariable) { return defToInline.equals(((PsiLocalVariable) def).getInitializer()); } final PsiElement parent = def.getParent(); return parent instanceof PsiAssignmentExpression && defToInline.equals(((PsiAssignmentExpression) parent).getRExpression()); } private static boolean isInliningVariableInitializer(final PsiExpression defToInline) { return defToInline.getParent() instanceof PsiVariable; } @Nullable private static PsiExpression getDefToInline(final PsiLocalVariable local, final PsiElement refExpr, final PsiCodeBlock block) { if (refExpr != null) { PsiElement def; if (refExpr instanceof PsiReferenceExpression && PsiUtil.isAccessedForWriting((PsiExpression) refExpr)) { def = refExpr; } else { final PsiElement[] defs = DefUseUtil.getDefs(block, local, refExpr); if (defs.length == 1) { def = defs[0]; } else { return null; } } if (def instanceof PsiReferenceExpression && def.getParent() instanceof PsiAssignmentExpression) { final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression) def.getParent(); if (assignmentExpression.getOperationTokenType() != JavaTokenType.EQ) { return null; } final PsiExpression rExpr = assignmentExpression.getRExpression(); if (rExpr != null) { return rExpr; } } } return local.getInitializer(); } }