/* * Copyright 2011 Jon S Akhtar (Sylvanaar) * * 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.sylvanaar.idea.Lua.refactoring.introduce; import com.intellij.openapi.diagnostic.Logger; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; import com.sylvanaar.idea.Lua.lang.psi.LuaPsiElementFactory; import com.sylvanaar.idea.Lua.lang.psi.LuaPsiFileBase; import com.sylvanaar.idea.Lua.lang.psi.LuaReferenceElement; import com.sylvanaar.idea.Lua.lang.psi.expressions.LuaExpression; import com.sylvanaar.idea.Lua.lang.psi.expressions.LuaExpressionList; import com.sylvanaar.idea.Lua.lang.psi.statements.LuaAssignmentStatement; import com.sylvanaar.idea.Lua.lang.psi.statements.LuaConditionalLoop; import com.sylvanaar.idea.Lua.lang.psi.statements.LuaDeclarationStatement; import com.sylvanaar.idea.Lua.lang.psi.statements.LuaStatementElement; import com.sylvanaar.idea.Lua.lang.psi.symbols.LuaCompoundIdentifier; import com.sylvanaar.idea.Lua.lang.psi.symbols.LuaParameter; import com.sylvanaar.idea.Lua.lang.psi.symbols.LuaSymbol; import com.sylvanaar.idea.Lua.lang.psi.util.LuaStatementOwner; import com.sylvanaar.idea.Lua.refactoring.LuaRefactoringUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; /** * @author ilyas */ public abstract class LuaIntroduceVariableBase extends LuaIntroduceHandlerBase<LuaIntroduceVariableSettings> { private static final Logger log = Logger.getInstance("Lua.IntroduceVariableBase"); protected static String REFACTORING_NAME = "Introduce Variable"; public static final String IDLUAREFACTORTMP = "_______IDLUAREFACTORTMP"; private PsiElement positionElement = null; @NotNull @Override protected PsiElement findScope(LuaExpression selectedExpr, LuaSymbol variable) { // Get container element final PsiElement scope = LuaRefactoringUtil.getEnclosingContainer(selectedExpr); // if (scope == null || !(scope instanceof :PsiElement)) { // throw new GrIntroduceRefactoringError( // GroovyRefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", REFACTORING_NAME)); // } // if (!GroovyRefactoringUtil.isAppropriateContainerForIntroduceVariable(scope)) { // throw new GrIntroduceRefactoringError( // GroovyRefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", REFACTORING_NAME)); // } return scope; } protected void checkExpression(LuaExpression selectedExpr) { // Cannot perform refactoring in parameter default values PsiElement parent = selectedExpr.getParent(); while (!(parent == null || parent instanceof LuaPsiFileBase || parent instanceof LuaParameter)) { parent = parent.getParent(); } // if (checkInFieldInitializer(selectedExpr)) { // throw new GrIntroduceRefactoringError(GroovyRefactoringBundle.message("refactoring.is.not.supported.in.the.current.context")); // } if (parent instanceof LuaParameter) { throw new LuaIntroduceRefactoringError("Refactoring is not supported in parameters"); } } @Override protected void checkVariable(LuaSymbol variable) throws LuaIntroduceRefactoringError { if (variable instanceof LuaCompoundIdentifier && !((LuaCompoundIdentifier) variable).isCompoundDeclaration()) return; throw new LuaIntroduceRefactoringError(null); } @Override protected void checkOccurrences(PsiElement[] occurrences) { //nothing to do } // private static boolean checkInFieldInitializer(LuaExpression expr) { // PsiElement parent = expr.getParent(); // if (parent instanceof GrClosableBlock) { // return false; // } // if (parent instanceof GrField && expr == ((GrField)parent).getInitializerGroovy()) { // return true; // } // if (parent instanceof LuaExpression) { // return checkInFieldInitializer(((LuaExpression)parent)); // } // return false; // } /** * Inserts new variable declaratrions and replaces occurrences */ public LuaSymbol runRefactoring(final LuaIntroduceContext context, final LuaIntroduceVariableSettings settings) { // Generating varibable declaration final LuaPsiElementFactory factory = LuaPsiElementFactory.getInstance(context.project); LuaDeclarationStatement varDecl = null; if (context.var == null) { if (settings.isLocal()) varDecl = (LuaDeclarationStatement) factory.createStatementFromText( "local " + settings.getName() + " = " + context.expression.getText()); else varDecl = (LuaDeclarationStatement) factory.createStatementFromText( settings.getName() + " = " + context.expression.getText()); } else { varDecl = (LuaDeclarationStatement) factory.createStatementFromText((settings.isLocal() ? "local " : "") + settings.getName() + " = " + IDLUAREFACTORTMP); } // = factory.createVariableDeclaration(settings.isDeclareFinal() ? new String[]{PsiModifier.FINAL} : null, // (LuaExpression)PsiUtil.skipParentheses(context.expression, false), settings.getSelectedType(), // settings.getName()); // Marker for caret posiotion try { LuaExpression firstOccurrence; if (context.var == null) { /* insert new variable */ LuaRefactoringUtil.sortOccurrences(context.occurrences); if (context.occurrences.length == 0 || !(context.occurrences[0] instanceof LuaExpression)) { throw new IncorrectOperationException("Wrong expression occurrence"); } } if (settings.replaceAllOccurrences()) { firstOccurrence = ((LuaExpression)context.occurrences[0]); } else { firstOccurrence = context.expression; } //resolveLocalConflicts(context.scope, varDecl.getVariables()[0].getName()); // Replace at the place of first occurrence LuaSymbol insertedVar = replaceFirstAssignmentStatement(firstOccurrence, context, varDecl); boolean alreadyDefined = insertedVar != null; if (insertedVar == null) { // Insert before first occurrence if (context.var != null) substituteInitializerExpression((LuaExpression) context.var.copy(), varDecl); assert varDecl.getDefinedSymbols().length > 0; insertedVar = insertVariableDefinition(context, settings, varDecl); } // insertedVar.setType(settings.getSelectedType()); //Replace other occurrences LuaSymbol refExpr = createReferenceSymbol(settings, factory); if (settings.replaceAllOccurrences()) { ArrayList<PsiElement> replaced = new ArrayList<PsiElement>(); for (PsiElement occurrence : context.occurrences) { if (!(alreadyDefined && firstOccurrence.equals(occurrence))) { if (occurrence instanceof LuaExpression) { LuaExpression element = (LuaExpression)occurrence; if (element.getParent() instanceof LuaReferenceElement) element = (LuaExpression) element.getParent(); replaced.add(element.replaceWithExpression(refExpr, true)); // For caret position if (occurrence.equals(context.expression)) { refreshPositionMarker(replaced.get(replaced.size() - 1)); } refExpr = createReferenceSymbol(settings, factory); } else { throw new IncorrectOperationException("Expression occurrence to be replaced is not instance of GroovyPsiElement"); } } } if (context.editor != null) { // todo implement it... // final PsiElement[] replacedOccurrences = replaced.toArray(new PsiElement[replaced.size()]); // highlightReplacedOccurrences(myProject, editor, replacedOccurrences); } } else { if (!alreadyDefined) { refreshPositionMarker(context.expression.replaceWithExpression(refExpr, true)); } } // Setting caret to logical position if (context.editor != null && getPositionMarker() != null) { context.editor.getCaretModel().moveToOffset(getPositionMarker().getTextRange().getEndOffset()); context.editor.getSelectionModel().removeSelection(); } return insertedVar; } catch (IncorrectOperationException e) { log.error(e); } return null; } private void substituteInitializerExpression(LuaExpression expression, LuaDeclarationStatement varDecl) { int markerPos = varDecl.getText().indexOf(IDLUAREFACTORTMP); LuaExpression fakeInitializer = PsiTreeUtil .findElementOfClassAtOffset(varDecl.getContainingFile(), markerPos, LuaExpression.class, false); assert fakeInitializer.getText().equals(IDLUAREFACTORTMP); if (fakeInitializer instanceof LuaExpressionList) fakeInitializer = ((LuaExpressionList) fakeInitializer).getLuaExpressions().get(0); if (fakeInitializer.getParent() instanceof LuaReferenceElement) fakeInitializer = (LuaExpression) fakeInitializer.getParent(); fakeInitializer.replace(expression); } private LuaSymbol createReferenceSymbol(LuaIntroduceVariableSettings settings, LuaPsiElementFactory factory) { LuaSymbol symbol = settings.isLocal() ? factory.createLocalNameIdentifier(settings.getName()) : factory.createGlobalNameIdentifier(settings.getName()); if (! (symbol instanceof LuaReferenceElement)) symbol = (LuaSymbol) symbol.getParent(); return symbol; } private static void resolveLocalConflicts(PsiElement tempContainer, String varName) { for (PsiElement child : tempContainer.getChildren()) { // if (child instanceof LuaReferenceElement && !child.getText().contains(".")) { // PsiReference psiReference = child.getReference(); // if (psiReference != null) { // final PsiElement resolved = psiReference.resolve(); // if (resolved != null) { // String fieldName = getFieldName(resolved); // if (fieldName != null && varName.equals(fieldName)) { // LuaPsiElementFactory factory = LuaPsiElementFactory.getInstance(tempContainer.getProject()); // ((LuaReferenceElement)child).replaceWithExpression(factory.createExpressionFromText("this." + child.getText()), true); // } // } // } // } // else { resolveLocalConflicts(child, varName); // } } } // @Nullable // private static String getFieldName(PsiElement element) { // if (element instanceof GrAccessorMethod) element = ((GrAccessorMethod)element).getProperty(); // return element instanceof GrField ? ((GrField)element).getName() : null; // } private void refreshPositionMarker(PsiElement position) { if (positionElement == null && position != null) { positionElement = position; } } private PsiElement getPositionMarker() { return positionElement; } private LuaSymbol insertVariableDefinition(LuaIntroduceContext context, LuaIntroduceVariableSettings settings, LuaDeclarationStatement varDecl) throws IncorrectOperationException { log.assertTrue(context.occurrences.length > 0); LuaStatementElement anchorElement = (LuaStatementElement)findAnchor(context, settings, context.occurrences, context.scope); log.assertTrue(anchorElement != null); PsiElement realContainer = anchorElement.getParent(); assert LuaRefactoringUtil.isAppropriateContainerForIntroduceVariable(realContainer); if (!(realContainer instanceof LuaConditionalLoop)) { if (realContainer instanceof LuaStatementOwner) { LuaStatementOwner block = (LuaStatementOwner)realContainer; varDecl = (LuaDeclarationStatement)block.addStatementBefore(varDecl, anchorElement); block.addAfter(LuaPsiElementFactory.getInstance(context.project).createWhiteSpaceFromText("\n"), varDecl); } } // else { // // To replace branch body correctly // String refId = varDecl.getVariables()[0].getName(); // GrBlockStatement newBody; // final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(context.project); // if (anchorElement.equals(context.expression)) { // newBody = factory.createBlockStatement(varDecl); // } // else { // replaceExpressionOccurrencesInStatement(anchorElement, context.expression, refId, settings.replaceAllOccurrences()); // newBody = factory.createBlockStatement(varDecl, anchorElement); // } // // varDecl = (LuaSymbolDeclaration)newBody.getBlock().getStatements()[0]; // // GrCodeBlock tempBlock = ((GrBlockStatement)((GrLoopStatement)realContainer).replaceBody(newBody)).getBlock(); // refreshPositionMarker(tempBlock.getStatements()[tempBlock.getStatements().length - 1]); // } return varDecl.getDefinedSymbols()[0]; } private static void replaceExpressionOccurrencesInStatement(LuaStatementElement stmt, LuaExpression expr, String refText, boolean replaceAllOccurrences) throws IncorrectOperationException { LuaPsiElementFactory factory = LuaPsiElementFactory.getInstance(stmt.getProject()); LuaExpression refExpr = factory.createExpressionFromText(refText); if (!replaceAllOccurrences) { expr.replaceWithExpression(refExpr, true); } else { PsiElement[] occurrences = LuaRefactoringUtil.getExpressionOccurrences(expr, stmt); for (PsiElement occurrence : occurrences) { if (occurrence instanceof LuaExpression) { LuaExpression LuaExpression = (LuaExpression)occurrence; LuaExpression.replaceWithExpression(refExpr, true); refExpr = factory.createExpressionFromText(refText); } else { throw new IncorrectOperationException(); } } } } /** * Replaces an expression occurrence by appropriate variable declaration */ @Nullable private LuaSymbol replaceFirstAssignmentStatement(@NotNull LuaExpression expr, LuaIntroduceContext context, @NotNull LuaDeclarationStatement definition) throws IncorrectOperationException { LuaAssignmentStatement assign = PsiTreeUtil.getParentOfType(expr, LuaAssignmentStatement.class, true, LuaStatementElement.class); if (assign != null) { LuaSymbol symbol = assign.getLeftExprs().getSymbols()[0]; if (symbol instanceof LuaReferenceElement) symbol = (LuaSymbol) ((LuaReferenceElement) symbol).getElement(); boolean isSymbolAssinedto = false; for (PsiElement element : context.occurrences) if (element.equals(symbol)) isSymbolAssinedto = true; if (isSymbolAssinedto) { // for now we only support single assignments when we are replacing an assignment with a variable // definition if (assign.getLeftExprs().count() != 1 && assign.getRightExprs().count() != 1) return null; substituteInitializerExpression( (LuaExpression) assign.getRightExprs().getLuaExpressions().get(0).copy(), definition); definition = (LuaDeclarationStatement) assign.replaceWithStatement(definition); if (expr.equals(context.expression)) { refreshPositionMarker(definition); } return definition.getDefinedSymbols()[0]; } } return null; } }