| /* |
| * Copyright 2000-2009 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. |
| */ |
| |
| /** |
| * @author cdr |
| */ |
| package com.intellij.codeInsight.daemon.impl.quickfix; |
| |
| import com.intellij.codeInsight.FileModificationService; |
| import com.intellij.codeInsight.daemon.QuickFixBundle; |
| import com.intellij.codeInspection.LocalQuickFixOnPsiElement; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.CodeStyleManager; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.util.Function; |
| import com.intellij.util.IncorrectOperationException; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| public class SimplifyBooleanExpressionFix extends LocalQuickFixOnPsiElement { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.SimplifyBooleanExpression"); |
| public static final String FAMILY_NAME = QuickFixBundle.message("simplify.boolean.expression.family"); |
| |
| private final Boolean mySubExpressionValue; |
| |
| // subExpressionValue == Boolean.TRUE or Boolean.FALSE if subExpression evaluates to boolean constant and needs to be replaced |
| // otherwise subExpressionValue= null and we starting to simplify expression without any further knowledge |
| public SimplifyBooleanExpressionFix(@NotNull PsiExpression subExpression, Boolean subExpressionValue) { |
| super(subExpression); |
| mySubExpressionValue = subExpressionValue; |
| } |
| |
| @Override |
| @NotNull |
| public String getText() { |
| PsiExpression expression = getSubExpression(); |
| return QuickFixBundle.message("simplify.boolean.expression.text", expression.getText(), mySubExpressionValue); |
| } |
| |
| @Override |
| @NotNull |
| public String getFamilyName() { |
| return FAMILY_NAME; |
| } |
| |
| @Override |
| public boolean isAvailable() { |
| PsiExpression expression = getSubExpression(); |
| return super.isAvailable() |
| && expression != null |
| && expression.isValid() |
| && expression.getManager().isInProject(expression) |
| && !PsiUtil.isAccessedForWriting(expression); |
| } |
| |
| @Override |
| public void invoke(@NotNull final Project project, @NotNull PsiFile file, @NotNull PsiElement startElement, @NotNull PsiElement endElement) { |
| if (!isAvailable()) return; |
| final PsiExpression expression = getSubExpression(); |
| LOG.assertTrue(expression.isValid()); |
| if (!FileModificationService.getInstance().preparePsiElementForWrite(expression)) return; |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run() { |
| simplifyExpression(project, expression, mySubExpressionValue); |
| } |
| }); |
| } |
| |
| public static void simplifyExpression(Project project, final PsiExpression subExpression, final Boolean subExpressionValue) { |
| PsiExpression expression; |
| if (subExpressionValue == null) { |
| expression = subExpression; |
| } |
| else { |
| final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); |
| final PsiExpression constExpression = factory.createExpressionFromText(Boolean.toString(subExpressionValue.booleanValue()), subExpression); |
| expression = (PsiExpression)subExpression.replace(constExpression); |
| } |
| while (expression.getParent() instanceof PsiExpression) { |
| expression = (PsiExpression)expression.getParent(); |
| } |
| simplifyExpression(expression); |
| } |
| |
| public static void simplifyIfStatement(final PsiExpression expression) throws IncorrectOperationException { |
| PsiElement parent = expression.getParent(); |
| if (!(parent instanceof PsiIfStatement) || ((PsiIfStatement)parent).getCondition() != expression) return; |
| if (!(expression instanceof PsiLiteralExpression) || expression.getType() != PsiType.BOOLEAN) return; |
| boolean condition = Boolean.parseBoolean(expression.getText()); |
| PsiIfStatement ifStatement = (PsiIfStatement)parent; |
| if (condition) { |
| replaceWithStatements(ifStatement, ifStatement.getThenBranch()); |
| } |
| else { |
| PsiStatement elseBranch = ifStatement.getElseBranch(); |
| if (elseBranch == null) { |
| ifStatement.delete(); |
| } |
| else { |
| replaceWithStatements(ifStatement, elseBranch); |
| } |
| } |
| } |
| |
| private static void replaceWithStatements(final PsiStatement orig, final PsiStatement statement) throws IncorrectOperationException { |
| if (statement == null) { |
| orig.delete(); |
| return; |
| } |
| PsiElement parent = orig.getParent(); |
| if (parent == null) return; |
| if (statement instanceof PsiBlockStatement && parent instanceof PsiCodeBlock) { |
| // See IDEADEV-24277 |
| // Code block can only be inlined into another (parent) code block. |
| // Code blocks, which are if or loop statement branches should not be inlined. |
| PsiCodeBlock codeBlock = ((PsiBlockStatement)statement).getCodeBlock(); |
| PsiJavaToken lBrace = codeBlock.getLBrace(); |
| PsiJavaToken rBrace = codeBlock.getRBrace(); |
| if (lBrace == null || rBrace == null) return; |
| |
| |
| final PsiElement[] children = codeBlock.getChildren(); |
| if (children.length > 2) { |
| final PsiElement added = |
| parent.addRangeBefore( |
| children[1], |
| children[children.length - 2], |
| orig); |
| final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(orig.getManager()); |
| codeStyleManager.reformat(added); |
| } |
| orig.delete(); |
| } |
| else { |
| orig.replace(statement); |
| } |
| } |
| |
| public static void simplifyExpression(PsiExpression expression) throws IncorrectOperationException { |
| final PsiExpression[] result = {(PsiExpression)expression.copy()}; |
| final ExpressionVisitor expressionVisitor = new ExpressionVisitor(expression.getManager(), true); |
| final IncorrectOperationException[] exception = {null}; |
| result[0].accept(new JavaRecursiveElementVisitor() { |
| @Override |
| public void visitElement(PsiElement element) { |
| // read in all children in advance since due to Igorek's exercises element replace involves its siblings invalidation |
| PsiElement[] children = element.getChildren(); |
| for (PsiElement child : children) { |
| child.accept(this); |
| } |
| } |
| |
| @Override |
| public void visitExpression(PsiExpression expression) { |
| super.visitExpression(expression); |
| expressionVisitor.clear(); |
| expression.accept(expressionVisitor); |
| if (expressionVisitor.resultExpression != null) { |
| LOG.assertTrue(expressionVisitor.resultExpression.isValid()); |
| try { |
| if (expression != result[0]) { |
| expression.replace(expressionVisitor.resultExpression); |
| } |
| else { |
| result[0] = expressionVisitor.resultExpression; |
| } |
| } |
| catch (IncorrectOperationException e) { |
| exception[0] = e; |
| } |
| } |
| } |
| }); |
| if (exception[0] != null) { |
| throw exception[0]; |
| } |
| PsiExpression newExpression = (PsiExpression)expression.replace(result[0]); |
| if (newExpression instanceof PsiLiteralExpression) { |
| final PsiElement parent = newExpression.getParent(); |
| if (parent instanceof PsiAssertStatement && ((PsiLiteralExpression)newExpression).getValue() == Boolean.TRUE) { |
| parent.delete(); |
| return; |
| } |
| } |
| simplifyIfStatement(newExpression); |
| } |
| |
| public static boolean canBeSimplified(@NotNull PsiExpression expression) { |
| if (!(expression instanceof PsiConditionalExpression) && expression.getType() != PsiType.BOOLEAN) return false; |
| |
| final ExpressionVisitor expressionVisitor = new ExpressionVisitor(expression.getManager(), false); |
| final Ref<Boolean> canBeSimplified = new Ref<Boolean>(Boolean.FALSE); |
| expression.accept(new JavaRecursiveElementWalkingVisitor() { |
| @Override |
| public void visitElement(PsiElement element) { |
| if (!canBeSimplified.get().booleanValue()) { |
| super.visitElement(element); |
| } |
| } |
| |
| @Override |
| public void visitExpression(PsiExpression expression) { |
| super.visitExpression(expression); |
| expressionVisitor.clear(); |
| expression.accept(expressionVisitor); |
| if (expressionVisitor.canBeSimplifiedFlag) { |
| canBeSimplified.set(Boolean.TRUE); |
| } |
| } |
| }); |
| return canBeSimplified.get().booleanValue(); |
| } |
| |
| private PsiExpression getSubExpression() { |
| PsiElement element = getStartElement(); |
| return element instanceof PsiExpression ? (PsiExpression)element : null; |
| } |
| |
| private static class ExpressionVisitor extends JavaElementVisitor { |
| private PsiExpression resultExpression; |
| private final PsiExpression trueExpression; |
| private final PsiExpression falseExpression; |
| private final boolean isCreateResult; |
| boolean canBeSimplifiedFlag; |
| |
| private ExpressionVisitor(PsiManager psiManager, final boolean createResult) { |
| isCreateResult = createResult; |
| trueExpression = createResult ? createExpression(psiManager, Boolean.toString(true)) : null; |
| falseExpression = createResult ? createExpression(psiManager, Boolean.toString(false)) : null; |
| } |
| |
| private static PsiExpression createExpression(final PsiManager psiManager, @NonNls String text) { |
| try { |
| return JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory().createExpressionFromText(text, null); |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| return null; |
| } |
| } |
| |
| private boolean markAndCheckCreateResult() { |
| canBeSimplifiedFlag = true; |
| return isCreateResult; |
| } |
| |
| @Override |
| public void visitPolyadicExpression(PsiPolyadicExpression expression) { |
| PsiExpression[] operands = expression.getOperands(); |
| PsiExpression lExpr = operands[0]; |
| IElementType tokenType = expression.getOperationTokenType(); |
| if (JavaTokenType.XOR == tokenType) { |
| |
| boolean negate = false; |
| List<PsiExpression> expressions = new ArrayList<PsiExpression>(); |
| for (PsiExpression operand : operands) { |
| final Boolean constBoolean = getConstBoolean(operand); |
| if (constBoolean != null) { |
| markAndCheckCreateResult(); |
| if (constBoolean == Boolean.TRUE) { |
| negate = !negate; |
| } |
| continue; |
| } |
| expressions.add(operand); |
| } |
| if (expressions.isEmpty()) { |
| resultExpression = negate ? trueExpression : falseExpression; |
| } else { |
| String simplifiedText = StringUtil.join(expressions, new Function<PsiExpression, String>() { |
| @Override |
| public String fun(PsiExpression expression) { |
| return expression.getText(); |
| } |
| }, " ^ "); |
| if (negate) { |
| if (expressions.size() > 1) { |
| simplifiedText = "!(" + simplifiedText + ")"; |
| } else { |
| simplifiedText = "!" + simplifiedText; |
| } |
| } |
| resultExpression = JavaPsiFacade.getElementFactory(expression.getProject()).createExpressionFromText(simplifiedText, expression); |
| } |
| } else { |
| for (int i = 1; i < operands.length; i++) { |
| Boolean l = getConstBoolean(lExpr); |
| PsiExpression operand = operands[i]; |
| Boolean r = getConstBoolean(operand); |
| if (l != null) { |
| simplifyBinary(tokenType, l, operand); |
| } |
| else if (r != null) { |
| simplifyBinary(tokenType, r, lExpr); |
| } |
| else { |
| final PsiJavaToken javaToken = expression.getTokenBeforeOperand(operand); |
| if (javaToken != null && !PsiTreeUtil.hasErrorElements(operand) && !PsiTreeUtil.hasErrorElements(lExpr)) { |
| try { |
| resultExpression = JavaPsiFacade.getElementFactory(expression.getProject()).createExpressionFromText(lExpr.getText() + javaToken.getText() + operand.getText(), expression); |
| } |
| catch (IncorrectOperationException e) { |
| resultExpression = null; |
| } |
| } |
| else { |
| resultExpression = null; |
| } |
| } |
| if (resultExpression != null) { |
| lExpr = resultExpression; |
| } |
| } |
| } |
| } |
| |
| private void simplifyBinary(IElementType tokenType, Boolean lConstBoolean, PsiExpression rOperand) { |
| if (!markAndCheckCreateResult()) { |
| return; |
| } |
| if (JavaTokenType.ANDAND == tokenType || JavaTokenType.AND == tokenType) { |
| resultExpression = lConstBoolean.booleanValue() ? rOperand : falseExpression; |
| } |
| else if (JavaTokenType.OROR == tokenType || JavaTokenType.OR == tokenType) { |
| resultExpression = lConstBoolean.booleanValue() ? trueExpression : rOperand; |
| } |
| else if (JavaTokenType.EQEQ == tokenType) { |
| simplifyEquation(lConstBoolean, rOperand); |
| } |
| else if (JavaTokenType.NE == tokenType) { |
| PsiPrefixExpression negatedExpression = createNegatedExpression(rOperand); |
| resultExpression = negatedExpression; |
| visitPrefixExpression(negatedExpression); |
| simplifyEquation(lConstBoolean, resultExpression); |
| } |
| } |
| |
| private void simplifyEquation(Boolean constBoolean, PsiExpression otherOperand) { |
| if (constBoolean.booleanValue()) { |
| resultExpression = otherOperand; |
| } |
| else { |
| PsiPrefixExpression negated = createNegatedExpression(otherOperand); |
| resultExpression = negated; |
| visitPrefixExpression(negated); |
| } |
| } |
| |
| @Override |
| public void visitConditionalExpression(PsiConditionalExpression expression) { |
| Boolean condition = getConstBoolean(expression.getCondition()); |
| if (condition == null) return; |
| if (!markAndCheckCreateResult()) { |
| return; |
| } |
| resultExpression = condition.booleanValue() ? expression.getThenExpression() : expression.getElseExpression(); |
| } |
| |
| private static PsiPrefixExpression createNegatedExpression(PsiExpression otherOperand) { |
| PsiPrefixExpression expression = (PsiPrefixExpression)createExpression(otherOperand.getManager(), "!(xxx)"); |
| try { |
| expression.getOperand().replace(otherOperand); |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| return expression; |
| } |
| |
| @Override |
| public void visitPrefixExpression(PsiPrefixExpression expression) { |
| PsiExpression operand = expression.getOperand(); |
| Boolean constBoolean = getConstBoolean(operand); |
| if (constBoolean == null) return; |
| IElementType tokenType = expression.getOperationTokenType(); |
| if (JavaTokenType.EXCL == tokenType) { |
| if (!markAndCheckCreateResult()) { |
| return; |
| } |
| resultExpression = constBoolean.booleanValue() ? falseExpression : trueExpression; |
| } |
| } |
| |
| |
| @Override |
| public void visitParenthesizedExpression(PsiParenthesizedExpression expression) { |
| PsiExpression subexpr = expression.getExpression(); |
| Boolean constBoolean = getConstBoolean(subexpr); |
| if (constBoolean == null) return; |
| if (!markAndCheckCreateResult()) { |
| return; |
| } |
| resultExpression = constBoolean.booleanValue() ? trueExpression : falseExpression; |
| } |
| |
| @Override |
| public void visitReferenceExpression(PsiReferenceExpression expression) { |
| visitReferenceElement(expression); |
| } |
| |
| public void clear() { |
| resultExpression = null; |
| } |
| } |
| |
| public static Boolean getConstBoolean(PsiExpression operand) { |
| if (operand == null) return null; |
| operand = PsiUtil.deparenthesizeExpression(operand); |
| if (operand == null) return null; |
| String text = operand.getText(); |
| return PsiKeyword.TRUE.equals(text) ? Boolean.TRUE : PsiKeyword.FALSE.equals(text) ? Boolean.FALSE : null; |
| } |
| } |