/* * Copyright 2000-2016 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 org.jetbrains.plugins.groovy.refactoring.introduce.variable; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.util.Pass; import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiModifier; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.refactoring.HelpID; import com.intellij.refactoring.introduce.inplace.OccurrencesChooser; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils; import org.jetbrains.plugins.groovy.lang.psi.GrControlFlowOwner; import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; import org.jetbrains.plugins.groovy.lang.psi.api.formatter.GrControlStatement; import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField; import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement; import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter; import org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner; import org.jetbrains.plugins.groovy.refactoring.GrRefactoringError; import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringBundle; import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringUtil; import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceContext; import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceHandlerBase; import org.jetbrains.plugins.groovy.refactoring.introduce.StringPartInfo; public class GrIntroduceVariableHandler extends GrIntroduceHandlerBase<GroovyIntroduceVariableSettings, GrControlFlowOwner> { public static final String DUMMY_NAME = "________________xxx_________________"; protected static final String REFACTORING_NAME = GroovyRefactoringBundle.message("introduce.variable.title"); private RangeMarker myPosition = null; @NotNull @Override protected GrControlFlowOwner[] findPossibleScopes(GrExpression selectedExpr, GrVariable variable, StringPartInfo stringPartInfo, Editor editor) { // Get container element final GrControlFlowOwner scope = ControlFlowUtils.findControlFlowOwner(stringPartInfo != null ? stringPartInfo.getLiteral() : selectedExpr); if (scope == null) { throw new GrRefactoringError( GroovyRefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", REFACTORING_NAME)); } if (!GroovyRefactoringUtil.isAppropriateContainerForIntroduceVariable(scope)) { throw new GrRefactoringError( GroovyRefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", REFACTORING_NAME)); } return new GrControlFlowOwner[]{scope}; } @Override protected void checkExpression(@NotNull GrExpression selectedExpr) { // Cannot perform refactoring in parameter default values PsiElement parent = selectedExpr.getParent(); while (!(parent == null || parent instanceof GroovyFileBase || parent instanceof GrParameter)) { parent = parent.getParent(); } if (checkInFieldInitializer(selectedExpr)) { throw new GrRefactoringError(GroovyRefactoringBundle.message("refactoring.is.not.supported.in.the.current.context")); } if (parent instanceof GrParameter) { throw new GrRefactoringError(GroovyRefactoringBundle.message("refactoring.is.not.supported.in.method.parameters")); } } @Override protected void checkVariable(@NotNull GrVariable variable) throws GrRefactoringError { throw new GrRefactoringError(null); } @Override protected void checkStringLiteral(@NotNull StringPartInfo info) throws GrRefactoringError { //todo } @Override protected void checkOccurrences(@NotNull PsiElement[] occurrences) { //nothing to do } private static boolean checkInFieldInitializer(@NotNull GrExpression expr) { PsiElement parent = expr.getParent(); if (parent instanceof GrClosableBlock) { return false; } if (parent instanceof GrField && expr == ((GrField)parent).getInitializerGroovy()) { return true; } if (parent instanceof GrExpression) { return checkInFieldInitializer(((GrExpression)parent)); } return false; } /** * Inserts new variable declarations and replaces occurrences */ @Override public GrVariable runRefactoring(@NotNull final GrIntroduceContext context, @NotNull final GroovyIntroduceVariableSettings settings) { // Generating variable declaration GrVariable insertedVar = processExpression(context, settings); moveOffsetToPositionMarker(context.getEditor()); return insertedVar; } private void moveOffsetToPositionMarker(Editor editor) { if (editor != null && getPositionMarker() != null) { editor.getSelectionModel().removeSelection(); editor.getCaretModel().moveToOffset(getPositionMarker().getEndOffset()); } } @Override protected GrInplaceVariableIntroducer getIntroducer(@NotNull GrIntroduceContext context, @NotNull OccurrencesChooser.ReplaceChoice choice) { final Ref<GrIntroduceContext> contextRef = Ref.create(context); if (context.getStringPart() != null) { extractStringPart(contextRef); } context = contextRef.get(); final GrStatement anchor = findAnchor(context, choice == OccurrencesChooser.ReplaceChoice.ALL); if (anchor.getParent() instanceof GrControlStatement) { addBraces(anchor, contextRef); } return new GrInplaceVariableIntroducer(getRefactoringName(), choice, contextRef.get()) { @Override protected GrVariable runRefactoring(GrIntroduceContext context, GroovyIntroduceVariableSettings settings, boolean processUsages) { return refactorInWriteAction(() -> processUsages ? processExpression(context, settings) : addVariable(context, settings)); } @Override protected void performPostIntroduceTasks() { super.performPostIntroduceTasks(); moveOffsetToPositionMarker(contextRef.get().getEditor()); } }; } private static GrVariable addVariable(@NotNull GrIntroduceContext context, @NotNull GroovyIntroduceVariableSettings settings) { GrStatement anchor = findAnchor(context, settings.replaceAllOccurrences()); PsiElement parent = anchor.getParent(); assert parent instanceof GrStatementOwner; GrVariableDeclaration generated = generateDeclaration(context, settings); GrStatement declaration = ((GrStatementOwner)parent).addStatementBefore(generated, anchor); declaration = (GrStatement)JavaCodeStyleManager.getInstance(context.getProject()).shortenClassReferences(declaration); return ((GrVariableDeclaration)declaration).getVariables()[0]; } @Override protected void showScopeChooser(GrControlFlowOwner[] scopes, Pass<GrControlFlowOwner> callback, Editor editor) { //todo do nothing right now } @NotNull private static GrVariableDeclaration generateDeclaration(@NotNull GrIntroduceContext context, @NotNull GroovyIntroduceVariableSettings settings) { final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(context.getProject()); final String[] modifiers = settings.isDeclareFinal() ? new String[]{PsiModifier.FINAL} : null; final GrVariableDeclaration declaration = factory.createVariableDeclaration(modifiers, "foo", settings.getSelectedType(), settings.getName()); generateInitializer(context, declaration.getVariables()[0]); return declaration; } @NotNull private GrVariable processExpression(@NotNull GrIntroduceContext context, @NotNull GroovyIntroduceVariableSettings settings) { GrVariableDeclaration varDecl = generateDeclaration(context, settings); if (context.getStringPart() != null) { final GrExpression ref = context.getStringPart().replaceLiteralWithConcatenation(DUMMY_NAME); return doProcessExpression(context, settings, varDecl, new PsiElement[]{ref}, ref, true); } else { final GrExpression expression = context.getExpression(); assert expression != null; return doProcessExpression(context, settings, varDecl, context.getOccurrences(), expression, true); } } private GrVariable doProcessExpression(@NotNull final GrIntroduceContext context, @NotNull GroovyIntroduceVariableSettings settings, @NotNull GrVariableDeclaration varDecl, @NotNull PsiElement[] elements, @NotNull GrExpression expression, boolean processUsages) { return new GrIntroduceLocalVariableProcessor(context, settings, elements, expression, processUsages) { @Override protected void refreshPositionMarker(PsiElement e) { GrIntroduceVariableHandler.this.refreshPositionMarker(context.getEditor().getDocument().createRangeMarker(e.getTextRange())); } }.processExpression(varDecl); } @NotNull private static GrExpression generateInitializer(@NotNull GrIntroduceContext context, @NotNull GrVariable variable) { final GrExpression initializer = context.getStringPart() != null ? context.getStringPart().createLiteralFromSelected() : context.getExpression(); final GrExpression dummyInitializer = variable.getInitializerGroovy(); assert dummyInitializer != null; return dummyInitializer.replaceWithExpression(initializer, true); } void refreshPositionMarker(RangeMarker marker) { myPosition = marker; } private RangeMarker getPositionMarker() { return myPosition; } @NotNull @Override protected String getRefactoringName() { return REFACTORING_NAME; } @NotNull @Override protected String getHelpID() { return HelpID.INTRODUCE_VARIABLE; } @Override @NotNull protected GroovyIntroduceVariableDialog getDialog(@NotNull GrIntroduceContext context) { final GroovyVariableValidator validator = new GroovyVariableValidator(context); return new GroovyIntroduceVariableDialog(context, validator); } }