Package ro.redeul.google.go.refactoring.inline

Source Code of ro.redeul.google.go.refactoring.inline.InlineLocalVariableActionHandler$InlineContext

package ro.redeul.google.go.refactoring.inline;

import com.intellij.codeInsight.highlighting.HighlightManager;
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector;
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.editor.Document;
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.colors.EditorColorsScheme;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.StatusBar;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.patterns.ElementPattern;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.psi.tree.TokenSet;
import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.refactoring.util.RefactoringMessageDialog;
import ro.redeul.google.go.GoBundle;
import ro.redeul.google.go.GoLanguage;
import ro.redeul.google.go.highlight.GoReadWriteAccessDetector;
import ro.redeul.google.go.inspection.fix.RemoveVariableFix;
import ro.redeul.google.go.lang.documentation.DocumentUtil;
import ro.redeul.google.go.lang.lexer.GoTokenTypes;
import ro.redeul.google.go.lang.psi.GoFile;
import ro.redeul.google.go.lang.psi.GoPsiElement;
import ro.redeul.google.go.lang.psi.declarations.GoVarDeclaration;
import ro.redeul.google.go.lang.psi.declarations.GoVarDeclarations;
import ro.redeul.google.go.lang.psi.expressions.GoExpr;
import ro.redeul.google.go.lang.psi.expressions.GoExpressionList;
import ro.redeul.google.go.lang.psi.expressions.GoUnaryExpression;
import ro.redeul.google.go.lang.psi.expressions.binary.*;
import ro.redeul.google.go.lang.psi.expressions.literals.GoLiteralIdentifier;
import ro.redeul.google.go.lang.psi.expressions.primary.GoCallOrConvExpression;
import ro.redeul.google.go.lang.psi.expressions.primary.GoLiteralExpression;
import ro.redeul.google.go.lang.psi.expressions.primary.GoParenthesisedExpression;
import ro.redeul.google.go.lang.psi.expressions.primary.GoSelectorExpression;
import ro.redeul.google.go.lang.psi.statements.*;
import ro.redeul.google.go.lang.psi.statements.switches.GoSwitchExpressionStatement;
import ro.redeul.google.go.lang.psi.statements.switches.GoSwitchTypeStatement;
import ro.redeul.google.go.lang.psi.visitors.GoRecursiveElementVisitor;
import ro.redeul.google.go.refactoring.GoRefactoringException;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static com.intellij.patterns.PlatformPatterns.psiElement;
import static com.intellij.patterns.StandardPatterns.or;
import static ro.redeul.google.go.lang.psi.utils.GoPsiUtils.*;
import static ro.redeul.google.go.util.EditorUtil.reformatPositions;

public class InlineLocalVariableActionHandler extends InlineActionHandler {

    @SuppressWarnings("unchecked")
    private static final ElementPattern<GoLiteralIdentifier> LOCAL_VAR_DECLARATION =
            psiElement(GoLiteralIdentifier.class)
                    .withParent(
                            or(
                                    psiElement(GoShortVarDeclaration.class),
                                    psiElement(GoVarDeclaration.class)
                                            .andNot(
                                                    psiElement().withSuperParent(2, GoFile.class)
                                            )
                            )
                    );

    @Override
    public boolean isEnabledForLanguage(Language l) {
        return l == GoLanguage.INSTANCE;
    }

    @Override
    public boolean canInlineElement(PsiElement element) {
        return LOCAL_VAR_DECLARATION.accepts(element);
    }

    @Override
    public void inlineElement(final Project project, final Editor editor, final PsiElement element) {
        ApplicationManager.getApplication().runWriteAction(new Runnable() {
            @Override
            public void run() {
                CommandProcessor.getInstance().executeCommand(project, new Runnable() {
                    public void run() {
                        doInlineElement(project, editor, element);
                    }
                }, "Inline", null);
            }
        });
    }

    private void doInlineElement(Project project, Editor editor, PsiElement element) {
        GoStatement statement = findParentOfType(element, GoStatement.class);
        if (!(element instanceof GoLiteralIdentifier) || statement == null) {
            return;
        }

        PsiElement scope = null;
        if (statement instanceof GoShortVarDeclaration) {
            scope = statement.getParent();
        } else if (statement instanceof GoVarDeclaration) {
            scope = statement.getParent().getParent();
        }

        if (!(scope instanceof GoPsiElement)) {
            String message = GoBundle.message("error.unknown.refactoring.case");
            CommonRefactoringUtil.showErrorHint(project, editor, message, "Refactoring error!", null);
            return;
        }

        GoLiteralIdentifier identifier = (GoLiteralIdentifier) element;
        GoVarDeclaration declaration = (GoVarDeclaration) statement;
        try {
            inlineElement(new InlineContext(project, editor, identifier, declaration, (GoPsiElement) scope));
        } catch (GoRefactoringException e) {
            if (ApplicationManager.getApplication().isUnitTestMode()) {
                throw new RuntimeException(e);
            }
            String message = RefactoringBundle.getCannotRefactorMessage(e.getMessage());
            CommonRefactoringUtil.showErrorHint(project, editor, message, "Refactoring error!", null);
        }
    }

    private void inlineElement(InlineContext ctx) throws GoRefactoringException {
        SmartPsiElementPointer<GoPsiElement> scopePointer = createSmartElementPointer(ctx.scope);

        Usage usage = findUsage(ctx.identifierToInline, ctx.scope);
        PsiElement initializer = getIdentifierInitializer(ctx, usage);
        if (!promptToInline(ctx, usage)) {
            return;
        }

        expandAllUsage(ctx, usage, initializer);

        removeIdentifierDeclaration(ctx);

        GoPsiElement scope = scopePointer.getElement();
        if (scope != null) {
            reformatPositions(scope);
        }
    }

    private void removeIdentifierDeclaration(InlineContext ctx) throws GoRefactoringException {
        PsiElement parent = ctx.statement.getParent();
        if (ctx.statement.getIdentifiers().length > 1 ||
                parent instanceof GoVarDeclarations ||
                parent instanceof GoBlockStatement) {
            applyRemoveVariableFix(ctx);
        } else if (parent instanceof GoIfStatement) {
            deleteIfSimpleStatement(ctx.editor.getDocument(), (GoIfStatement) parent);
        } else if (parent instanceof GoSwitchExpressionStatement ||
                parent instanceof GoSwitchTypeStatement) {
            deleteSwitchSimpleStatement(ctx.editor.getDocument(), (GoStatement) parent);
        } else {
            applyRemoveVariableFix(ctx);
        }
    }

    private void expandAllUsage(InlineContext ctx, Usage usage, PsiElement initializer) throws GoRefactoringException {
        Document document = ctx.editor.getDocument();
        for (int i = usage.readUsages.length - 1; i >= 0; i--) {
            PsiElement readUsage = usage.readUsages[i];
            expandUsage(document, readUsage, initializer);
        }

        PsiDocumentManager.getInstance(ctx.project).commitDocument(document);
    }

    private PsiElement getIdentifierInitializer(InlineContext ctx, Usage usage) throws GoRefactoringException {
        String name = ctx.identifierToInline.getName();
        PsiElement initializer = getInitializer(ctx.identifierToInline);
        boolean shouldHaveInitializer = identifierShouldHaveInitializer(ctx.identifierToInline);
        if (shouldHaveInitializer && initializer == null ||
                usage.writeUsages.length == 0 && !shouldHaveInitializer) {
            throw new GoRefactoringException(RefactoringBundle.message("variable.has.no.initializer", ctx.identifierToInline.getText()));
        }

        if (usage.readUsages.length == 0) {
            String messsage = GoBundle.message("error.variable.is.never.used", name);
            throw new GoRefactoringException(messsage);
        }

        if (usage.writeUsages.length == 1 && !shouldHaveInitializer) {
            if (usage.readUsages[0].getTextOffset() > usage.writeUsages[0].getTextOffset()) {
                String messsage = GoBundle.message("error.variable.is.used.before.modification", name);
                throw new GoRefactoringException(messsage);
            }

            initializer = getInitializer((GoLiteralIdentifier) usage.writeUsages[0]);
            if (initializer == null) {
                throw new GoRefactoringException(RefactoringBundle.message("variable.has.no.initializer", ctx.identifierToInline.getText()));
            }
        }

        if (usage.writeUsages.length >= 2 ||
                usage.writeUsages.length == 1 && shouldHaveInitializer) {
            throw new GoRefactoringException(RefactoringBundle.message("variable.is.accessed.for.writing", name));
        }

        while (initializer instanceof GoParenthesisedExpression) {
            initializer = ((GoParenthesisedExpression) initializer).getInnerExpression();
        }

        return initializer;
    }

    private void applyRemoveVariableFix(InlineContext ctx) {
        new RemoveVariableFix().applyFix(ctx.identifierToInline);
    }

    private static final TokenSet MUL_TOKENS = TokenSet.create(
            GoTokenTypes.oMUL,
            GoTokenTypes.oQUOTIENT,
            GoTokenTypes.oREMAINDER,
            GoTokenTypes.oSHIFT_LEFT,
            GoTokenTypes.oSHIFT_RIGHT,
            GoTokenTypes.oBIT_AND,
            GoTokenTypes.oBIT_CLEAR
    );

    private static final TokenSet ADD_TOKENS = TokenSet.create(
            GoTokenTypes.oPLUS,
            GoTokenTypes.oMINUS,
            GoTokenTypes.oBIT_OR,
            GoTokenTypes.oBIT_XOR
    );

    private static final TokenSet REL_TOKENS = TokenSet.create(
            GoTokenTypes.oEQ,
            GoTokenTypes.oNOT_EQ,
            GoTokenTypes.oLESS,
            GoTokenTypes.oLESS_OR_EQUAL,
            GoTokenTypes.oGREATER,
            GoTokenTypes.oGREATER_OR_EQUAL
    );

    private static int getExpressionPrecedence(PsiElement element) {
        if (element instanceof GoMultiplicativeExpression)
            return 5;

        if ( element instanceof GoAdditiveExpression)
            return 4;

        if ( element instanceof GoRelationalExpression)
            return 3;

        if ( element instanceof GoLogicalAndExpression)
            return 2;

        if ( element instanceof GoLogicalOrExpression)
            return 1;

        if (element instanceof GoUnaryExpression)
            return 10;

        if (element instanceof GoSelectorExpression)
            return 15;

        if (element instanceof GoLiteralIdentifier || element instanceof GoLiteralExpression) {
            return 20;
        }

        return -1;
    }

    private void expandUsage(Document document, PsiElement usage, PsiElement initializer) throws GoRefactoringException {
        PsiElement usageParent = usage.getParent();
        if (usageParent instanceof GoLiteralExpression) {
            usageParent = usageParent.getParent();
        }
        int usagePrecedence = getExpressionPrecedence(usageParent);
        int initializerPrecedence = getExpressionPrecedence(initializer);
        String text = initializer.getText();

        if (initializerPrecedence == -1) {
            if (!(usageParent instanceof GoCallOrConvExpression) &&
                    !(usageParent instanceof GoParenthesisedExpression))
                unknownCase();
        } else if (initializerPrecedence < usagePrecedence) {
            text = "(" + text + ")";
        }
        DocumentUtil.replaceElementWithText(document, usage, text);
    }

    private void deleteIfSimpleStatement(Document document, GoIfStatement ifStatement) throws GoRefactoringException {
        GoSimpleStatement simpleStatement = ifStatement.getSimpleStatement();
        if (simpleStatement == null) {
            unknownCase();
            return;
        }
        int start = simpleStatement.getTextOffset();
        int end = ifStatement.getExpression().getTextOffset();
        document.deleteString(start, end);
    }

    private void deleteSwitchSimpleStatement(Document document, GoStatement statement)
            throws GoRefactoringException {
        GoSimpleStatement simpleStatement;
        if (statement instanceof GoSwitchExpressionStatement) {
            simpleStatement = ((GoSwitchExpressionStatement) statement).getSimpleStatement();
        } else {
            simpleStatement = ((GoSwitchTypeStatement) statement).getSimpleStatement();
        }

        if (simpleStatement == null) {
            unknownCase();
            return;
        }

        PsiElement endElement;
        if (statement instanceof GoSwitchExpressionStatement) {
            endElement = ((GoSwitchExpressionStatement) statement).getExpression();
        } else {
            endElement = ((GoSwitchTypeStatement) statement).getTypeGuard();
        }

        if (endElement == null) {
            endElement = findChildOfType(statement, GoTokenTypes.pLCURLY);
        }

        if (endElement == null) {
            unknownCase();
            return;
        }

        int start = simpleStatement.getTextOffset();
        document.deleteString(start, endElement.getTextOffset());
    }

    private void unknownCase() throws GoRefactoringException {
        throw new GoRefactoringException(GoBundle.message("error.unknown.refactoring.case"));
    }

    private PsiElement getInitializer(GoLiteralIdentifier identifier) {
        PsiElement parent = identifier.getParent();
        if (parent instanceof GoVarDeclaration) {
            GoVarDeclaration declaration = (GoVarDeclaration) parent;
            GoLiteralIdentifier[] identifiers = declaration.getIdentifiers();
            GoExpr[] expressions = declaration.getExpressions();
            if (expressions.length != identifiers.length) {
                return null;
            }

            for (int i = 0; i < expressions.length; i++) {
                if (identifiers[i].getTextRange().equals(identifier.getTextRange())) {
                    return expressions[i];
                }
            }
        } else if (parent instanceof GoExpressionList) {
            PsiElement grandpa = parent.getParent();
            if (grandpa instanceof GoAssignmentStatement) {
                GoAssignmentStatement assignment = (GoAssignmentStatement) grandpa;
                GoExpr[] identifiers = assignment.getLeftSideExpressions().getExpressions();
                GoExpr[] expressions = assignment.getRightSideExpressions().getExpressions();
                if (identifiers.length != expressions.length) {
                    return null;
                }

                for (int i = 0; i < expressions.length; i++) {
                    if (identifiers[i].getTextRange().equals(identifier.getTextRange())) {
                        return expressions[i];
                    }
                }
            }
        }
        return null;
    }

    private boolean identifierShouldHaveInitializer(GoLiteralIdentifier identifier) {
        PsiElement parent = identifier.getParent();

        return !(parent instanceof GoVarDeclaration && ((GoVarDeclaration) parent).getExpressions().length == 0);
    }

    private boolean promptToInline(InlineContext ctx, Usage usage) {
        if (ApplicationManager.getApplication().isUnitTestMode()) {
            return true;
        }

        usage.highlight(ctx.project, ctx.editor, ctx.identifierToInline);

        int occurrencesCount = usage.writeUsages.length + usage.readUsages.length;
        String occurrencesString = RefactoringBundle.message("occurences.string", occurrencesCount);
        String name = ctx.identifierToInline.getText();
        String question = RefactoringBundle.message("inline.local.variable.prompt", name) + " " + occurrencesString;
        RefactoringMessageDialog dialog = new RefactoringMessageDialog(
                RefactoringBundle.message("inline.variable.title"),
                question,
                HelpID.INLINE_VARIABLE,
                "OptionPane.questionIcon",
                true,
                ctx.project);
        dialog.show();
        if (!dialog.isOK()) {
            StatusBar statusBar = WindowManager.getInstance().getStatusBar(ctx.project);
            if (statusBar != null) {
                statusBar.setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting"));
            }
            return false;
        }
        return true;
    }

    private static Usage findUsage(final GoLiteralIdentifier identifier, GoPsiElement scope) {
        final List<PsiElement> writeUsages = new ArrayList<PsiElement>();
        final List<PsiElement> readUsages = new ArrayList<PsiElement>();

        final GoReadWriteAccessDetector detector = new GoReadWriteAccessDetector();
        new GoRecursiveElementVisitor() {
            @Override
            public void visitLiteralIdentifier(GoLiteralIdentifier id) {
                if (identifier.equals(resolveSafely(id, PsiElement.class))) {
                    if (detector.getExpressionAccess(id) == ReadWriteAccessDetector.Access.Read) {
                        readUsages.add(id);
                    } else {
                        writeUsages.add(id);
                    }
                }
            }
        }.visitElement(scope);
        return new Usage(writeUsages, readUsages);
    }

    private static class InlineContext {
        final Project project;
        final Editor editor;
        final GoLiteralIdentifier identifierToInline;
        final GoVarDeclaration statement;
        final GoPsiElement scope;

        private InlineContext(Project project, Editor editor, GoLiteralIdentifier identifierToInline,
                              GoVarDeclaration statement, GoPsiElement scope) {
            this.project = project;
            this.editor = editor;
            this.identifierToInline = identifierToInline;
            this.statement = statement;
            this.scope = scope;
        }
    }

    private static class Usage {
        final PsiElement[] writeUsages;
        final PsiElement[] readUsages;

        private Usage(Collection<PsiElement> writeUsages, Collection<PsiElement> readUsages) {
            this.writeUsages = writeUsages.toArray(new PsiElement[writeUsages.size()]);
            this.readUsages = readUsages.toArray(new PsiElement[readUsages.size()]);
        }

        private void highlight(Project project, Editor editor, GoLiteralIdentifier identifierToInline) {
            HighlightManager manager = HighlightManager.getInstance(project);
            EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
            TextAttributes attributes = scheme.getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
            TextAttributes writeAttributes = scheme.getAttributes(EditorColors.WRITE_SEARCH_RESULT_ATTRIBUTES);
            manager.addOccurrenceHighlights(editor, readUsages, attributes, true, null);
            manager.addOccurrenceHighlights(editor, writeUsages, writeAttributes, true, null);
            manager.addOccurrenceHighlights(editor, new PsiElement[]{identifierToInline}, writeAttributes, true, null);
        }
    }
}
TOP

Related Classes of ro.redeul.google.go.refactoring.inline.InlineLocalVariableActionHandler$InlineContext

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.