/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.refactor.introduceVariable; import com.google.common.collect.Maps; import com.intellij.codeInsight.completion.JavaCompletionUtil; import com.intellij.codeInsight.highlighting.HighlightManager; import com.intellij.codeInsight.lookup.LookupManager; import com.intellij.featureStatistics.FeatureUsageTracker; import com.intellij.featureStatistics.ProductivityFeatureNames; import com.intellij.ide.util.PropertiesComponent; import com.intellij.lang.LanguageRefactoringSupport; import com.intellij.lang.refactoring.RefactoringSupportProvider; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.editor.SelectionModel; 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.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pass; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.WindowManager; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.JavaRecursiveElementWalkingVisitor; import com.intellij.psi.JavaTokenType; import com.intellij.psi.PsiArrayInitializerExpression; import com.intellij.psi.PsiArrayType; import com.intellij.psi.PsiAssignmentExpression; import com.intellij.psi.PsiBlockStatement; import com.intellij.psi.PsiCallExpression; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiCodeBlock; import com.intellij.psi.PsiCodeFragment; import com.intellij.psi.PsiComment; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementFactory; import com.intellij.psi.PsiEllipsisType; import com.intellij.psi.PsiErrorElement; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiExpressionList; import com.intellij.psi.PsiExpressionStatement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiIfStatement; import com.intellij.psi.PsiJavaToken; import com.intellij.psi.PsiLiteralExpression; import com.intellij.psi.PsiLocalVariable; import com.intellij.psi.PsiLoopStatement; import com.intellij.psi.PsiManager; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiMethodCallExpression; import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiNewExpression; import com.intellij.psi.PsiParameter; import com.intellij.psi.PsiParenthesizedExpression; import com.intellij.psi.PsiReference; import com.intellij.psi.PsiReferenceExpression; import com.intellij.psi.PsiReturnStatement; import com.intellij.psi.PsiStatement; import com.intellij.psi.PsiSubstitutor; import com.intellij.psi.PsiSuperExpression; import com.intellij.psi.PsiType; import com.intellij.psi.PsiVariable; import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.SmartPointerManager; import com.intellij.psi.SmartPsiElementPointer; import com.intellij.psi.SmartTypePointer; import com.intellij.psi.SmartTypePointerManager; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.codeStyle.SuggestedNameInfo; import com.intellij.psi.codeStyle.VariableKind; import com.intellij.psi.impl.PsiDiamondTypeUtil; import com.intellij.psi.impl.source.jsp.jspJava.JspCodeBlock; import com.intellij.psi.impl.source.jsp.jspJava.JspHolderMethod; import com.intellij.psi.impl.source.resolve.DefaultParameterTypeInferencePolicy; import com.intellij.psi.impl.source.tree.java.ReplaceExpressionUtil; 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.IntroduceHandlerBase; import com.intellij.refactoring.IntroduceTargetChooser; import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.introduce.inplace.AbstractInplaceIntroducer; import com.intellij.refactoring.introduce.inplace.OccurrencesChooser; import com.intellij.refactoring.introduceVariable.IntroduceVariableSettings; import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer; import com.intellij.refactoring.ui.TypeSelectorManagerImpl; import com.intellij.refactoring.util.CommonRefactoringUtil; import com.intellij.refactoring.util.FieldConflictsResolver; import com.intellij.refactoring.util.RefactoringUIUtil; import com.intellij.refactoring.util.occurrences.ExpressionOccurrenceManager; import com.intellij.refactoring.util.occurrences.NotInSuperCallOccurrenceFilter; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.MultiMap; import gw.plugin.ij.lang.psi.api.statements.IGosuVariable; import gw.plugin.ij.lang.psi.util.GosuPsiParseUtil; import gw.plugin.ij.refactor.GosuCodeInsightUtil; import gw.plugin.ij.refactor.GosuRefactoringUtil; import gw.plugin.ij.refactor.GosuRenderFuction; import gw.plugin.ij.refactor.intoduceField.GosuElementToWorkOn; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; /** * copy of Intellij IntroduceVariableBase */ public abstract class GosuIntroduceVariableBase extends IntroduceHandlerBase { private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.introduceVariable.IntroduceVariableBase"); @NonNls private static final String PREFER_STATEMENTS_OPTION = "introduce.variable.prefer.statements"; protected static final String REFACTORING_NAME = RefactoringBundle.message("introduce.variable.title"); public static final Key<Boolean> NEED_PARENTHESIS = Key.create("NEED_PARENTHESIS"); public static SuggestedNameInfo getSuggestedName(@Nullable PsiType type, @NotNull final PsiExpression expression) { return getSuggestedName(type, expression, expression); } public static SuggestedNameInfo getSuggestedName(@Nullable PsiType type, @NotNull final PsiExpression expression, final PsiElement anchor) { final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(expression.getProject()); final SuggestedNameInfo nameInfo = codeStyleManager.suggestVariableName(VariableKind.LOCAL_VARIABLE, null, expression, type); final String[] strings = JavaCompletionUtil .completeVariableNameForRefactoring(codeStyleManager, type, VariableKind.LOCAL_VARIABLE, nameInfo); final SuggestedNameInfo.Delegate delegate = new SuggestedNameInfo.Delegate(strings, nameInfo); return codeStyleManager.suggestUniqueVariableName(delegate, anchor, true); } public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file, DataContext dataContext) { final SelectionModel selectionModel = editor.getSelectionModel(); if (!selectionModel.hasSelection()) { final int offset = editor.getCaretModel().getOffset(); final PsiElement[] statementsInRange = findStatementsAtOffset(editor, file, offset); //try line selection if (statementsInRange.length == 1 && (!GosuRefactoringUtil.isStatementOrExpressionstatement(statementsInRange[0]) || statementsInRange[0].getTextRange().getStartOffset() > offset || statementsInRange[0].getTextRange().getEndOffset() < offset || isPreferStatements())) { selectionModel.selectLineAtCaret(); final PsiExpression expressionInRange = findExpressionInRange(project, file, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd()); if (expressionInRange == null || getErrorMessage(expressionInRange) != null) { selectionModel.removeSelection(); } } if (!selectionModel.hasSelection()) { final List<PsiExpression> expressions = collectExpressions(file, editor, offset, statementsInRange); if (expressions.isEmpty()) { selectionModel.selectLineAtCaret(); } else if (expressions.size() == 1) { final TextRange textRange = expressions.get(0).getTextRange(); selectionModel.setSelection(textRange.getStartOffset(), textRange.getEndOffset()); } else { IntroduceTargetChooser.showChooser(editor, expressions, new Pass<PsiExpression>() { public void pass(final PsiExpression selectedValue) { invoke(project, editor, file, selectedValue.getTextRange().getStartOffset(), selectedValue.getTextRange().getEndOffset()); } }, new GosuRenderFuction()); return; } } } if (invoke(project, editor, file, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd()) && LookupManager.getActiveLookup(editor) == null) { selectionModel.removeSelection(); } } public static boolean isPreferStatements() { return Boolean.valueOf(PropertiesComponent.getInstance().getOrInit(PREFER_STATEMENTS_OPTION, "false")).booleanValue() || Registry.is(PREFER_STATEMENTS_OPTION, false); } public static List<PsiExpression> collectExpressions(final PsiFile file, final Editor editor, final int offset, final PsiElement... statementsInRange) { Document document = editor.getDocument(); CharSequence text = document.getCharsSequence(); int correctedOffset = offset; int textLength = document.getTextLength(); if (offset >= textLength) { correctedOffset = textLength - 1; } else if (!Character.isJavaIdentifierPart(text.charAt(offset))) { correctedOffset--; } if (correctedOffset < 0) { correctedOffset = offset; } else if (!Character.isJavaIdentifierPart(text.charAt(correctedOffset))) { if (text.charAt(correctedOffset) == ';') {//initially caret on the end of line correctedOffset--; } if (correctedOffset < 0 || text.charAt(correctedOffset) != ')') { correctedOffset = offset; } } final PsiElement elementAtCaret = file.findElementAt(correctedOffset); final List<PsiExpression> expressions = new ArrayList<>(); /*for (PsiElement element : statementsInRange) { if (element instanceof PsiExpressionStatement) { final PsiExpression expression = ((PsiExpressionStatement)element).getExpression(); if (expression.getType() != PsiType.VOID) { expressions.add(expression); } } }*/ PsiExpression expression = PsiTreeUtil.getParentOfType(elementAtCaret, PsiExpression.class); while (expression != null) { if (!expressions.contains(expression) && !(expression instanceof PsiParenthesizedExpression) && !(expression instanceof PsiSuperExpression) && expression.getType() != PsiType.VOID) { if (!(GosuRefactoringUtil.isPsiReferenceExpression(expression) && (expression.getParent() instanceof PsiMethodCallExpression || ((PsiReference) expression).resolve() instanceof PsiClass)) && !(expression instanceof PsiAssignmentExpression)) { expressions.add(expression); } } expression = PsiTreeUtil.getParentOfType(expression, PsiExpression.class); } return expressions; } public static PsiElement[] findStatementsAtOffset(final Editor editor, final PsiFile file, final int offset) { final Document document = editor.getDocument(); final int lineNumber = document.getLineNumber(offset); final int lineStart = document.getLineStartOffset(lineNumber); final int lineEnd = document.getLineEndOffset(lineNumber); return GosuCodeInsightUtil.findStatementsInRange(file, lineStart, lineEnd); } private boolean invoke(final Project project, final Editor editor, PsiFile file, int startOffset, int endOffset) { FeatureUsageTracker.getInstance().triggerFeatureUsed(ProductivityFeatureNames.REFACTORING_INTRODUCE_VARIABLE); PsiDocumentManager.getInstance(project).commitAllDocuments(); return invokeImpl(project, findExpressionInRange(project, file, startOffset, endOffset), editor); } private static PsiExpression findExpressionInRange(Project project, PsiFile file, int startOffset, int endOffset) { PsiExpression tempExpr = GosuCodeInsightUtil.findExpressionInRange(file, startOffset, endOffset); if (tempExpr == null) { PsiElement[] statements = GosuCodeInsightUtil.findStatementsInRange(file, startOffset, endOffset); if (statements.length == 1) { if (statements[0] instanceof PsiExpressionStatement) { tempExpr = ((PsiExpressionStatement) statements[0]).getExpression(); } else if (statements[0] instanceof PsiReturnStatement) { tempExpr = ((PsiReturnStatement) statements[0]).getReturnValue(); } } } if (tempExpr == null) { tempExpr = getSelectedExpression(project, file, startOffset, endOffset); } return tempExpr; } /** * @return can return NotNull value although extraction will fail: reason could be retrieved from {@link #getErrorMessage(PsiExpression)} */ public static PsiExpression getSelectedExpression(final Project project, final PsiFile file, int startOffset, int endOffset) { PsiElement elementAtStart = file.findElementAt(startOffset); if (elementAtStart == null || elementAtStart instanceof PsiWhiteSpace || elementAtStart instanceof PsiComment) { elementAtStart = PsiTreeUtil.skipSiblingsForward(elementAtStart, PsiWhiteSpace.class, PsiComment.class); if (elementAtStart == null) { return null; } startOffset = elementAtStart.getTextOffset(); } PsiElement elementAtEnd = file.findElementAt(endOffset - 1); if (elementAtEnd == null || elementAtEnd instanceof PsiWhiteSpace || elementAtEnd instanceof PsiComment) { elementAtEnd = PsiTreeUtil.skipSiblingsBackward(elementAtEnd, PsiWhiteSpace.class, PsiComment.class); if (elementAtEnd == null) { return null; } endOffset = elementAtEnd.getTextRange().getEndOffset(); } if (endOffset <= startOffset) { return null; } PsiElement elementAt = PsiTreeUtil.findCommonParent(elementAtStart, elementAtEnd); if (PsiTreeUtil.getParentOfType(elementAt, PsiExpression.class, false) == null) { elementAt = null; } final PsiLiteralExpression literalExpression = PsiTreeUtil.getParentOfType(elementAt, PsiLiteralExpression.class); final PsiLiteralExpression startLiteralExpression = PsiTreeUtil.getParentOfType(elementAtStart, PsiLiteralExpression.class); final PsiLiteralExpression endLiteralExpression = PsiTreeUtil.getParentOfType(file.findElementAt(endOffset), PsiLiteralExpression.class); final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory(); String text = null; PsiExpression tempExpr; try { text = file.getText().subSequence(startOffset, endOffset).toString(); String prefix = null; String stripped = text; if (startLiteralExpression != null) { final int startExpressionOffset = startLiteralExpression.getTextOffset(); if (startOffset == startExpressionOffset) { if (StringUtil.startsWithChar(text, '\"') || StringUtil.startsWithChar(text, '\'')) { stripped = text.substring(1); } } else if (startOffset == startExpressionOffset + 1) { text = "\"" + text; } else if (startOffset > startExpressionOffset + 1) { prefix = "\" + "; text = "\"" + text; } } String suffix = null; if (endLiteralExpression != null) { final int endExpressionOffset = endLiteralExpression.getTextOffset() + endLiteralExpression.getTextLength(); if (endOffset == endExpressionOffset) { if (StringUtil.endsWithChar(stripped, '\"') || StringUtil.endsWithChar(stripped, '\'')) { stripped = stripped.substring(0, stripped.length() - 1); } } else if (endOffset == endExpressionOffset - 1) { text += "\""; } else if (endOffset < endExpressionOffset - 1) { suffix = " + \""; text += "\""; } } boolean primitive = false; if (stripped.equals("true") || stripped.equals("false")) { primitive = true; } else { try { Integer.parseInt(stripped); primitive = true; } catch (NumberFormatException e1) { //then not primitive } } if (primitive) { text = stripped; } if (literalExpression != null && text.equals(literalExpression.getText())) { return literalExpression; } final PsiElement parent = literalExpression != null ? literalExpression : elementAt; // tempExpr = (PsiExpression) GosuPsiParseUtil.parseExpression(text, PsiManager.getInstance(project)); tempExpr = elementFactory.createExpressionFromText(text, parent); final boolean[] hasErrors = new boolean[1]; final JavaRecursiveElementWalkingVisitor errorsVisitor = new JavaRecursiveElementWalkingVisitor() { @Override public void visitElement(final PsiElement element) { if (hasErrors[0]) { return; } super.visitElement(element); } @Override public void visitErrorElement(final PsiErrorElement element) { hasErrors[0] = true; } }; tempExpr.accept(errorsVisitor); if (hasErrors[0]) { return null; } tempExpr.putUserData(GosuElementToWorkOn.PREFIX, prefix); tempExpr.putUserData(GosuElementToWorkOn.SUFFIX, suffix); final RangeMarker rangeMarker = FileDocumentManager.getInstance().getDocument(file.getVirtualFile()).createRangeMarker(startOffset, endOffset); tempExpr.putUserData(GosuElementToWorkOn.TEXT_RANGE, rangeMarker); if (parent != null) { tempExpr.putUserData(GosuElementToWorkOn.PARENT, parent); } else { PsiErrorElement errorElement = elementAtStart instanceof PsiErrorElement ? (PsiErrorElement) elementAtStart : PsiTreeUtil.getNextSiblingOfType(elementAtStart, PsiErrorElement.class); if (errorElement == null) { errorElement = PsiTreeUtil.getParentOfType(elementAtStart, PsiErrorElement.class); } if (errorElement == null) { return null; } if (!(errorElement.getParent() instanceof PsiClass)) { return null; } tempExpr.putUserData(GosuElementToWorkOn.PARENT, errorElement); tempExpr.putUserData(GosuElementToWorkOn.OUT_OF_CODE_BLOCK, Boolean.TRUE); } final String fakeInitializer = "intellijidearulezzz"; final int[] refIdx = new int[1]; final PsiExpression toBeExpression = createReplacement(fakeInitializer, project, prefix, suffix, parent, rangeMarker, refIdx); toBeExpression.accept(errorsVisitor); if (hasErrors[0]) { return null; } final PsiReferenceExpression refExpr = PsiTreeUtil.getParentOfType(toBeExpression.findElementAt(refIdx[0]), PsiReferenceExpression.class); assert refExpr != null; if (ReplaceExpressionUtil.isNeedParenthesis(refExpr.getNode(), tempExpr.getNode())) { tempExpr.putCopyableUserData(NEED_PARENTHESIS, Boolean.TRUE); return tempExpr; } } catch (IncorrectOperationException e) { if (elementAt instanceof PsiExpressionList) { final PsiElement parent = elementAt.getParent(); return parent instanceof PsiCallExpression ? createArrayCreationExpression(text, startOffset, endOffset, (PsiCallExpression) parent) : null; } return null; } return tempExpr; } @Nullable public static String getErrorMessage(PsiExpression expr) { final Boolean needParenthesis = expr.getCopyableUserData(NEED_PARENTHESIS); if (needParenthesis != null && needParenthesis.booleanValue()) { return "Extracting selected expression would change the semantic of the whole expression."; } return null; } private static PsiExpression createArrayCreationExpression(String text, int startOffset, int endOffset, PsiCallExpression parent) { if (text == null || parent == null) { return null; } final String[] varargsExpressions = text.split("s*,s*"); if (varargsExpressions.length > 1) { final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(parent.getProject()); final PsiMethod psiMethod = parent.resolveMethod(); if (psiMethod == null || !psiMethod.isVarArgs()) { return null; } final PsiParameter[] parameters = psiMethod.getParameterList().getParameters(); final PsiParameter varargParameter = parameters[parameters.length - 1]; final PsiType type = varargParameter.getType(); LOG.assertTrue(type instanceof PsiEllipsisType); final PsiArrayType psiType = (PsiArrayType) ((PsiEllipsisType) type).toArrayType(); final PsiExpression[] args = parent.getArgumentList().getExpressions(); final PsiSubstitutor psiSubstitutor = JavaPsiFacade.getInstance(parent.getProject()).getResolveHelper().inferTypeArguments(psiMethod.getTypeParameters(), parameters, args, PsiSubstitutor.EMPTY, parent, DefaultParameterTypeInferencePolicy.INSTANCE); if (startOffset < args[parameters.length - 1].getTextRange().getStartOffset()) { return null; } final PsiFile containingFile = parent.getContainingFile(); PsiElement startElement = containingFile.findElementAt(startOffset); while (startElement != null && startElement.getParent() != parent.getArgumentList()) { startElement = startElement.getParent(); } if (startElement == null || startOffset > startElement.getTextOffset()) { return null; } PsiElement endElement = containingFile.findElementAt(endOffset - 1); while (endElement != null && endElement.getParent() != parent.getArgumentList()) { endElement = endElement.getParent(); } if (endElement == null || endOffset < endElement.getTextRange().getEndOffset()) { return null; } final PsiType componentType = psiSubstitutor.substitute(psiType.getComponentType()); try { final PsiExpression expressionFromText = elementFactory.createExpressionFromText("new " + componentType.getCanonicalText() + "[]{" + text + "}", parent); final RangeMarker rangeMarker = FileDocumentManager.getInstance().getDocument(containingFile.getVirtualFile()).createRangeMarker(startOffset, endOffset); expressionFromText.putUserData(GosuElementToWorkOn.TEXT_RANGE, rangeMarker); expressionFromText.putUserData(GosuElementToWorkOn.PARENT, parent); return expressionFromText; } catch (IncorrectOperationException e) { return null; } } return null; } protected boolean invokeImpl(final Project project, final PsiExpression expr, final Editor editor) { if (expr != null) { final String errorMessage = getErrorMessage(expr); if (errorMessage != null) { showErrorMessage(project, editor, RefactoringBundle.getCannotRefactorMessage(errorMessage)); return false; } } if (expr != null && expr.getParent() instanceof PsiExpressionStatement) { FeatureUsageTracker.getInstance().triggerFeatureUsed("refactoring.introduceVariable.incompleteStatement"); } if (LOG.isDebugEnabled()) { LOG.debug("expression:" + expr); } if (expr == null || !expr.isPhysical()) { if (GosuReassignVariableUtil.reassign(editor)) { return false; } if (expr == null) { String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("selected.block.should.represent.an.expression")); showErrorMessage(project, editor, message); return false; } } final PsiType originalType = GosuRefactoringUtil.getTypeByExpressionWithExpectedType(expr); if (originalType == null) { String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("unknown.expression.type")); showErrorMessage(project, editor, message); return false; } if (PsiType.VOID.equals(originalType)) { String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("selected.expression.has.void.type")); showErrorMessage(project, editor, message); return false; } final PsiElement physicalElement = expr.getUserData(GosuElementToWorkOn.PARENT); final PsiElement anchorStatement = GosuRefactoringUtil.getParentStatement(physicalElement != null ? physicalElement : expr, false); if (anchorStatement == null) { return parentStatementNotFound(project, editor); } if (checkAnchorBeforeThisOrSuper(project, editor, anchorStatement, REFACTORING_NAME, HelpID.INTRODUCE_VARIABLE)) { return false; } final PsiElement tempContainer = anchorStatement.getParent(); if (!(tempContainer instanceof PsiCodeBlock) && !isLoopOrIf(tempContainer)) { String message = RefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", REFACTORING_NAME); showErrorMessage(project, editor, message); return false; } if (!NotInSuperCallOccurrenceFilter.INSTANCE.isOK(expr)) { String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("cannot.introduce.variable.in.super.constructor.call")); showErrorMessage(project, editor, message); return false; } final PsiFile file = anchorStatement.getContainingFile(); LOG.assertTrue(file != null, "expr.getContainingFile() == null"); if (!CommonRefactoringUtil.checkReadOnlyStatus(project, file)) { return false; } PsiElement containerParent = tempContainer; PsiElement lastScope = tempContainer; while (true) { if (containerParent instanceof PsiFile) { break; } if (containerParent instanceof PsiMethod) { break; } containerParent = containerParent.getParent(); if (containerParent instanceof PsiCodeBlock) { lastScope = containerParent; } } final ExpressionOccurrenceManager occurenceManager = new ExpressionOccurrenceManager(expr, lastScope, NotInSuperCallOccurrenceFilter.INSTANCE) { @Override public PsiElement getAnchorStatementForAllInScope(PsiElement scope) { return GosuRefactoringUtil.getAnchorElementForMultipleExpressions(getOccurrences(), scope); } }; final PsiExpression[] occurrences = occurenceManager.getOccurrences(); final PsiElement anchorStatementIfAll = occurenceManager.getAnchorStatementForAll(); final LinkedHashMap<OccurrencesChooser.ReplaceChoice, List<PsiExpression>> occurrencesMap = Maps.newLinkedHashMap(); final boolean hasWriteAccess = fillChoices(expr, occurrences, occurrencesMap); final PsiElement nameSuggestionContext = editor != null ? file.findElementAt(editor.getCaretModel().getOffset()) : null; final RefactoringSupportProvider supportProvider = LanguageRefactoringSupport.INSTANCE.forLanguage(expr.getLanguage()); final boolean isInplaceAvailableOnDataContext = supportProvider != null && editor.getSettings().isVariableInplaceRenameEnabled() && supportProvider.isInplaceIntroduceAvailable(expr, nameSuggestionContext) && !ApplicationManager.getApplication().isUnitTestMode() && !isInJspHolderMethod(expr); final boolean inFinalContext = occurenceManager.isInFinalContext(); final GosuInputValidator validator = new GosuInputValidator(this, project, anchorStatementIfAll, anchorStatement, occurenceManager); final TypeSelectorManagerImpl typeSelectorManager = new TypeSelectorManagerImpl(project, originalType, expr, occurrences); final boolean[] wasSucceed = new boolean[]{true}; final Pass<OccurrencesChooser.ReplaceChoice> callback = new Pass<OccurrencesChooser.ReplaceChoice>() { @Override public void pass(final OccurrencesChooser.ReplaceChoice choice) { final boolean allOccurences = choice != OccurrencesChooser.ReplaceChoice.NO; final PsiElement chosenAnchor = allOccurences ? anchorStatementIfAll : anchorStatement; final Ref<SmartPsiElementPointer<PsiVariable>> variable = new Ref<>(); final IntroduceVariableSettings settings = getSettings(project, editor, expr, occurrences, typeSelectorManager, inFinalContext, hasWriteAccess, validator, chosenAnchor, choice); if (!settings.isOK()) { wasSucceed[0] = false; return; } typeSelectorManager.setAllOccurrences(allOccurences); final List<RangeMarker> occurrenceMarkers = new ArrayList<>(); final boolean noWrite = choice == OccurrencesChooser.ReplaceChoice.NO_WRITE; for (PsiExpression occurrence : occurrences) { if (allOccurences || (noWrite && !PsiUtil.isAccessedForWriting(occurrence))) { occurrenceMarkers.add(editor.getDocument().createRangeMarker(occurrence.getTextRange())); } } final Runnable runnable = introduce(project, expr, editor, anchorStatement, tempContainer, occurrences, anchorStatementIfAll, settings, variable); CommandProcessor.getInstance().executeCommand( project, new Runnable() { public void run() { ApplicationManager.getApplication().runWriteAction(runnable); if (isInplaceAvailableOnDataContext) { final PsiVariable elementToRename = variable.get().getElement(); if (elementToRename != null) { editor.getCaretModel().moveToOffset(elementToRename.getTextOffset()); //TODO replace VariableInplaceRenamer with GosuVariableInplaceIntroducer(see JavaVariableInplaceIntroducer) VariableInplaceRenamer renamer = new VariableInplaceRenamer(elementToRename, editor, project); PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument()); renamer.performInplaceRename(); } } } }, REFACTORING_NAME, null); } }; if (!isInplaceAvailableOnDataContext) { callback.pass(null); } else { OccurrencesChooser.<PsiExpression>simpleChooser(editor).showChooser(callback, occurrencesMap); } return wasSucceed[0]; } private static boolean isInJspHolderMethod(PsiExpression expr) { final PsiElement parent1 = expr.getParent(); if (parent1 == null) { return false; } final PsiElement parent2 = parent1.getParent(); if (!(parent2 instanceof JspCodeBlock)) { return false; } final PsiElement parent3 = parent2.getParent(); return parent3 instanceof JspHolderMethod; } /** * @return true if write usages found */ private static boolean fillChoices(final PsiExpression expr, final PsiExpression[] occurrences, final LinkedHashMap<OccurrencesChooser.ReplaceChoice, List<PsiExpression>> occurrencesMap) { occurrencesMap.put(OccurrencesChooser.ReplaceChoice.NO, Collections.singletonList(expr)); final List<PsiExpression> nonWrite = new ArrayList<>(); boolean cantReplaceAll = false; for (PsiExpression occurrence : occurrences) { if (!GosuRefactoringUtil.isAssignmentLHS(occurrence)) { nonWrite.add(occurrence); } else if (isFinalVariableOnLHS(occurrence)) { cantReplaceAll = true; } } final boolean hasWriteAccess = occurrences.length > nonWrite.size() && occurrences.length > 1; if (hasWriteAccess) { occurrencesMap.put(OccurrencesChooser.ReplaceChoice.NO_WRITE, nonWrite); } if (occurrences.length > 1 && !cantReplaceAll) { occurrencesMap.put(OccurrencesChooser.ReplaceChoice.ALL, Arrays.asList(occurrences)); } return hasWriteAccess; } private static Runnable introduce(final Project project, final PsiExpression expr, final Editor editor, PsiElement anchorStatement, PsiElement tempContainer, final PsiExpression[] occurrences, PsiElement anchorStatementIfAll, final IntroduceVariableSettings settings, final Ref<SmartPsiElementPointer<PsiVariable>> variable) { if (settings.isReplaceAllOccurrences()) { anchorStatement = anchorStatementIfAll; tempContainer = anchorStatement.getParent(); } final PsiElement container = tempContainer; PsiElement child = anchorStatement; if (!isLoopOrIf(container)) { child = locateAnchor(child); if (isFinalVariableOnLHS(expr)) { child = child.getNextSibling(); } } final PsiElement anchor = child == null ? anchorStatement : child; boolean tempDeleteSelf = false; final boolean replaceSelf = settings.isReplaceLValues() || !GosuRefactoringUtil.isAssignmentLHS(expr); if (!isLoopOrIf(container)) { if (expr.getParent() instanceof PsiExpressionStatement && anchor.equals(anchorStatement)) { PsiStatement statement = (PsiStatement) expr.getParent(); PsiElement parent = statement.getParent(); if (parent instanceof PsiCodeBlock || //fabrique parent instanceof PsiCodeFragment) { tempDeleteSelf = true; } } tempDeleteSelf &= replaceSelf; } final boolean deleteSelf = tempDeleteSelf; final int col = editor != null ? editor.getCaretModel().getLogicalPosition().column : 0; final int line = editor != null ? editor.getCaretModel().getLogicalPosition().line : 0; if (deleteSelf) { if (editor != null) { LogicalPosition pos = new LogicalPosition(line, col); editor.getCaretModel().moveToLogicalPosition(pos); } } final PsiCodeBlock newDeclarationScope = PsiTreeUtil.getParentOfType(container, PsiCodeBlock.class, false); final FieldConflictsResolver fieldConflictsResolver = new FieldConflictsResolver(settings.getEnteredName(), newDeclarationScope); final PsiElement finalAnchorStatement = anchorStatement; return new Runnable() { public void run() { try { PsiStatement statement = null; final boolean isInsideLoop = isLoopOrIf(container); if (!isInsideLoop && deleteSelf) { statement = (PsiStatement) expr.getParent(); } final PsiExpression expr1 = fieldConflictsResolver.fixInitializer(expr); PsiExpression initializer = GosuRefactoringUtil.unparenthesizeExpression(expr1); final SmartTypePointer selectedType = SmartTypePointerManager.getInstance(project).createSmartTypePointer( settings.getSelectedType()); if (expr1 instanceof PsiNewExpression) { final PsiNewExpression newExpression = (PsiNewExpression) expr1; if (newExpression.getArrayInitializer() != null) { initializer = newExpression.getArrayInitializer(); } initializer = replaceExplicitWithDiamondWhenApplicable(initializer, selectedType.getType()); } IGosuVariable declaration = GosuPsiParseUtil .createLocalVariableDeclarationStatement(project, settings.getEnteredName(), settings.isDeclareFinal(), initializer); if (!isInsideLoop) { declaration = (IGosuVariable) container.addBefore(declaration, anchor); LOG.assertTrue(expr1.isValid()); if (deleteSelf) { // never true final PsiElement lastChild = statement.getLastChild(); if (lastChild instanceof PsiComment) { // keep trailing comment declaration.addBefore(lastChild, null); } statement.delete(); if (editor != null) { LogicalPosition pos = new LogicalPosition(line, col); editor.getCaretModel().moveToLogicalPosition(pos); editor.getCaretModel().moveToOffset(declaration.getTextRange().getEndOffset()); editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); editor.getSelectionModel().removeSelection(); } } } PsiExpression ref = (PsiExpression) GosuPsiParseUtil.parseExpression(settings.getEnteredName(), PsiManager.getInstance(project)); if (settings.isReplaceAllOccurrences()) { ArrayList<PsiElement> array = new ArrayList<>(); for (PsiExpression occurrence : occurrences) { if (deleteSelf && occurrence.equals(expr)) { continue; } if (occurrence.equals(expr)) { occurrence = expr1; } if (occurrence != null) { occurrence = GosuRefactoringUtil.outermostParenthesizedExpression(occurrence); } if (settings.isReplaceLValues() || !GosuRefactoringUtil.isAssignmentLHS(occurrence)) { array.add(replace(occurrence, ref, project)); } } if (editor != null) { final PsiElement[] replacedOccurences = PsiUtilBase.toPsiElementArray(array); highlightReplacedOccurences(project, editor, replacedOccurences); } } else { if (!deleteSelf && replaceSelf) { replace(expr1, ref, project); } } declaration = (IGosuVariable) putStatementInLoopBody(declaration, container, finalAnchorStatement); // declaration = (IGosuVariable) JavaCodeStyleManager.getInstance(project).shortenClassReferences(declaration); // PsiVariable var = (PsiVariable) declaration.getDeclaredElements()[0]; // PsiUtil.setModifierProperty(var, PsiModifier.FINAL, settings.isDeclareFinal()); variable.set(SmartPointerManager.getInstance(project).createLazyPointer((PsiVariable) declaration)); fieldConflictsResolver.fix(); } catch (IncorrectOperationException e) { LOG.error(e); } } }; } private static boolean isFinalVariableOnLHS(PsiExpression expr) { if (expr instanceof PsiReferenceExpression && GosuRefactoringUtil.isAssignmentLHS(expr)) { final PsiElement resolve = ((PsiReferenceExpression) expr).resolve(); if (resolve instanceof PsiVariable && ((PsiVariable) resolve).hasModifierProperty(PsiModifier.FINAL)) { //should be inserted after assignment return true; } } return false; } public static PsiExpression replaceExplicitWithDiamondWhenApplicable(final PsiExpression initializer, final PsiType expectedType) { if (initializer instanceof PsiNewExpression) { final PsiNewExpression newExpression = (PsiNewExpression) initializer; final PsiExpression tryToDetectDiamondNewExpr = ((PsiVariable) JavaPsiFacade.getElementFactory(initializer.getProject()) .createVariableDeclarationStatement("x", expectedType, initializer).getDeclaredElements()[0]) .getInitializer(); if (tryToDetectDiamondNewExpr instanceof PsiNewExpression && PsiDiamondTypeUtil.canCollapseToDiamond((PsiNewExpression) tryToDetectDiamondNewExpr, (PsiNewExpression) tryToDetectDiamondNewExpr, expectedType)) { final PsiElement paramList = PsiDiamondTypeUtil .replaceExplicitWithDiamond(newExpression.getClassOrAnonymousClassReference().getParameterList()); return PsiTreeUtil.getParentOfType(paramList, PsiNewExpression.class); } } return initializer; } public static PsiElement replace(final PsiExpression expr1, final PsiExpression ref, final Project project) throws IncorrectOperationException { final PsiExpression expr2; if (expr1 instanceof PsiArrayInitializerExpression && expr1.getParent() instanceof PsiNewExpression) { expr2 = (PsiNewExpression) expr1.getParent(); } else { expr2 = GosuRefactoringUtil.outermostParenthesizedExpression(expr1); } if (expr2.isPhysical()) { return expr2.replace(ref); } else { final String prefix = expr1.getUserData(GosuElementToWorkOn.PREFIX); final String suffix = expr1.getUserData(GosuElementToWorkOn.SUFFIX); final PsiElement parent = expr1.getUserData(GosuElementToWorkOn.PARENT); final RangeMarker rangeMarker = expr1.getUserData(GosuElementToWorkOn.TEXT_RANGE); return parent.replace(createReplacement(ref.getText(), project, prefix, suffix, parent, rangeMarker, new int[1])); } } private static PsiExpression createReplacement(final String refText, final Project project, final String prefix, final String suffix, final PsiElement parent, final RangeMarker rangeMarker, int[] refIdx) { String text = refText; if (parent != null) { final String allText = parent.getContainingFile().getText(); final TextRange parentRange = parent.getTextRange(); LOG.assertTrue(parentRange.getStartOffset() <= rangeMarker.getStartOffset(), parent + "; prefix:" + prefix + "; suffix:" + suffix); String beg = allText.substring(parentRange.getStartOffset(), rangeMarker.getStartOffset()); if (StringUtil.stripQuotesAroundValue(beg).trim().length() == 0 && prefix == null) { beg = ""; } LOG.assertTrue(rangeMarker.getEndOffset() <= parentRange.getEndOffset(), parent + "; prefix:" + prefix + "; suffix:" + suffix); String end = allText.substring(rangeMarker.getEndOffset(), parentRange.getEndOffset()); if (StringUtil.stripQuotesAroundValue(end).trim().length() == 0 && suffix == null) { end = ""; } final String start = beg + (prefix != null ? prefix : ""); refIdx[0] = start.length(); text = start + refText + (suffix != null ? suffix : "") + end; } return JavaPsiFacade.getInstance(project).getElementFactory().createExpressionFromText(text, parent); } public static PsiStatement putStatementInLoopBody(PsiStatement declaration, PsiElement container, PsiElement finalAnchorStatement) throws IncorrectOperationException { if (isLoopOrIf(container)) { PsiStatement loopBody = getLoopBody(container, finalAnchorStatement); PsiStatement loopBodyCopy = loopBody != null ? (PsiStatement) loopBody.copy() : null; PsiBlockStatement blockStatement = (PsiBlockStatement) JavaPsiFacade.getInstance(container.getProject()).getElementFactory() .createStatementFromText("{}", null); blockStatement = (PsiBlockStatement) CodeStyleManager.getInstance(container.getProject()).reformat(blockStatement); final PsiElement prevSibling = loopBody.getPrevSibling(); if (prevSibling instanceof PsiWhiteSpace) { final PsiElement pprev = prevSibling.getPrevSibling(); if (!(pprev instanceof PsiComment) || !((PsiComment) pprev).getTokenType().equals(JavaTokenType.END_OF_LINE_COMMENT)) { prevSibling.delete(); } } blockStatement = (PsiBlockStatement) loopBody.replace(blockStatement); final PsiCodeBlock codeBlock = blockStatement.getCodeBlock(); declaration = (PsiStatement) codeBlock.add(declaration); JavaCodeStyleManager.getInstance(declaration.getProject()).shortenClassReferences(declaration); if (loopBodyCopy != null) { codeBlock.add(loopBodyCopy); } } return declaration; } private boolean parentStatementNotFound(final Project project, Editor editor) { String message = RefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", REFACTORING_NAME); showErrorMessage(project, editor, message); return false; } protected boolean invokeImpl(Project project, PsiLocalVariable localVariable, Editor editor) { throw new UnsupportedOperationException(); } private static PsiElement locateAnchor(PsiElement child) { while (child != null) { PsiElement prev = child.getPrevSibling(); if (GosuRefactoringUtil.isStatement(prev)) { break; } if (prev instanceof PsiJavaToken && ((PsiJavaToken) prev).getTokenType() == JavaTokenType.LBRACE) { break; } child = prev; } while (child instanceof PsiWhiteSpace || child instanceof PsiComment) { child = child.getNextSibling(); } return child; } protected static void highlightReplacedOccurences(Project project, Editor editor, PsiElement[] replacedOccurences) { if (editor == null) { return; } if (ApplicationManager.getApplication().isUnitTestMode()) { return; } HighlightManager highlightManager = HighlightManager.getInstance(project); EditorColorsManager colorsManager = EditorColorsManager.getInstance(); TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); highlightManager.addOccurrenceHighlights(editor, replacedOccurences, attributes, true, null); WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting")); } protected abstract void showErrorMessage(Project project, Editor editor, String message); @Nullable private static PsiStatement getLoopBody(PsiElement container, PsiElement anchorStatement) { if (container instanceof PsiLoopStatement) { return ((PsiLoopStatement) container).getBody(); } else if (container instanceof PsiIfStatement) { final PsiStatement thenBranch = ((PsiIfStatement) container).getThenBranch(); if (thenBranch != null && PsiTreeUtil.isAncestor(thenBranch, anchorStatement, false)) { return thenBranch; } final PsiStatement elseBranch = ((PsiIfStatement) container).getElseBranch(); if (elseBranch != null && PsiTreeUtil.isAncestor(elseBranch, anchorStatement, false)) { return elseBranch; } LOG.assertTrue(false); } LOG.assertTrue(false); return null; } public static boolean isLoopOrIf(PsiElement element) { return element instanceof PsiLoopStatement || element instanceof PsiIfStatement; } protected boolean reportConflicts(MultiMap<PsiElement, String> conflicts, Project project, IntroduceVariableSettings settings) { return false; } public IntroduceVariableSettings getSettings(Project project, Editor editor, PsiExpression expr, PsiExpression[] occurrences, final TypeSelectorManagerImpl typeSelectorManager, boolean declareFinalIfAll, boolean anyAssignmentLHS, final GosuInputValidator validator, PsiElement anchor, final OccurrencesChooser.ReplaceChoice replaceChoice) { final boolean replaceAll = replaceChoice == OccurrencesChooser.ReplaceChoice.ALL || replaceChoice == OccurrencesChooser.ReplaceChoice.NO_WRITE; final SuggestedNameInfo suggestedName = getSuggestedName(typeSelectorManager.getDefaultType(), expr, anchor); final String variableName = suggestedName.names[0]; final boolean declareFinal = replaceAll && declareFinalIfAll || !anyAssignmentLHS && createFinals(project); final boolean replaceWrite = anyAssignmentLHS && replaceChoice == OccurrencesChooser.ReplaceChoice.ALL; return new IntroduceVariableSettings() { @Override public String getEnteredName() { return variableName; } @Override public boolean isReplaceAllOccurrences() { return replaceAll; } @Override public boolean isDeclareFinal() { return declareFinal; } @Override public boolean isReplaceLValues() { return replaceWrite; } @Override public PsiType getSelectedType() { final PsiType selectedType = typeSelectorManager.getTypeSelector().getSelectedType(); return selectedType != null ? selectedType : typeSelectorManager.getDefaultType(); } @Override public boolean isOK() { return true; } }; } public static boolean createFinals(Project project) { final Boolean createFinals = null;//JavaRefactoringSettings.getInstance().INTRODUCE_LOCAL_CREATE_FINALS; return createFinals == null ? CodeStyleSettingsManager.getSettings(project).GENERATE_FINAL_LOCALS : createFinals.booleanValue(); } public static boolean checkAnchorBeforeThisOrSuper(final Project project, final Editor editor, final PsiElement tempAnchorElement, final String refactoringName, final String helpID) { if (tempAnchorElement instanceof PsiExpressionStatement) { PsiExpression enclosingExpr = ((PsiExpressionStatement) tempAnchorElement).getExpression(); if (enclosingExpr instanceof PsiMethodCallExpression) { PsiMethod method = ((PsiMethodCallExpression) enclosingExpr).resolveMethod(); if (method != null && method.isConstructor()) { //This is either 'this' or 'super', both must be the first in the respective contructor String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("invalid.expression.context")); CommonRefactoringUtil.showErrorHint(project, editor, message, refactoringName, helpID); return true; } } } return false; } public interface Validator { boolean isOK(IntroduceVariableSettings dialog); } public static void checkInLoopCondition(PsiExpression occurence, MultiMap<PsiElement, String> conflicts) { final PsiElement loopForLoopCondition = GosuRefactoringUtil.getLoopForLoopCondition(occurence); if (loopForLoopCondition == null) { return; } final List<PsiVariable> referencedVariables = GosuRefactoringUtil.collectReferencedVariables(occurence); final List<PsiVariable> modifiedInBody = new ArrayList<>(); for (PsiVariable psiVariable : referencedVariables) { if (GosuRefactoringUtil.isModifiedInScope(psiVariable, loopForLoopCondition)) { modifiedInBody.add(psiVariable); } } if (!modifiedInBody.isEmpty()) { for (PsiVariable variable : modifiedInBody) { final String message = RefactoringBundle.message("is.modified.in.loop.body", RefactoringUIUtil.getDescription(variable, false)); conflicts.putValue(variable, CommonRefactoringUtil.capitalize(message)); } conflicts.putValue(occurence, RefactoringBundle.message("introducing.variable.may.break.code.logic")); } } @Override public AbstractInplaceIntroducer getInplaceIntroducer() { return null; } }