From ea72e1d05a1bf55c7cb997a5e4bdc07781ec649f Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Wed, 18 Apr 2018 12:45:28 +0700 Subject: [PATCH] JavaPsiMathUtil#add created & used; JavaPsiMathUtil#simplifyComparison --- .../StreamApiMigrationInspection.java | 15 +- .../ManualArrayCopyInspection.java | 20 +- .../siyeh/ig/psiutils/JavaPsiMathUtil.java | 222 ++++++++++++++++++ .../ReverseForLoopDirectionIntention.java | 55 +---- 4 files changed, 229 insertions(+), 83 deletions(-) create mode 100644 plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/JavaPsiMathUtil.java diff --git a/java/java-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java b/java/java-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java index 6c236f82b7c6..bf9b53725b24 100644 --- a/java/java-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/streamMigration/StreamApiMigrationInspection.java @@ -853,7 +853,7 @@ public class StreamApiMigrationInspection extends AbstractBaseJavaLocalInspectio @Override String createReplacement(CommentTracker ct) { - return ".limit(" + getLimitExpression(ct) + ")"; + return ".limit(" + JavaPsiMathUtil.add(myExpression, myDelta, ct) + ")"; } PsiLocalVariable getCounterVariable() { @@ -875,19 +875,6 @@ public class StreamApiMigrationInspection extends AbstractBaseJavaLocalInspectio boolean isWriteAllowed(PsiVariable variable, PsiExpression reference) { return variable == myCounterVariable && PsiTreeUtil.isAncestor(myCounter, reference, false); } - - private String getLimitExpression(CommentTracker ct) { - if (myDelta == 0) { - return ct.text(myExpression); - } - if (myExpression instanceof PsiLiteralExpression) { - Object value = ((PsiLiteralExpression)myExpression).getValue(); - if (value instanceof Integer || value instanceof Long) { - return String.valueOf(((Number)value).longValue() + myDelta); - } - } - return ct.text(myExpression, ParenthesesUtils.ADDITIVE_PRECEDENCE) + "+" + myDelta; - } } static class DistinctOp extends Operation { diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/performance/ManualArrayCopyInspection.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/performance/ManualArrayCopyInspection.java index c748fa678aef..7912cf8117b0 100644 --- a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/performance/ManualArrayCopyInspection.java +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/performance/ManualArrayCopyInspection.java @@ -311,25 +311,7 @@ public class ManualArrayCopyInspection extends BaseInspection { if (!plusOne) { return commentTracker.text(expression, ParenthesesUtils.ADDITIVE_PRECEDENCE); } - if (expression instanceof PsiBinaryExpression) { - final PsiBinaryExpression binaryExpression = (PsiBinaryExpression)expression; - final IElementType tokenType = binaryExpression.getOperationTokenType(); - if (tokenType == JavaTokenType.MINUS) { - final PsiExpression rhs = binaryExpression.getROperand(); - if (ExpressionUtils.isOne(rhs)) { - return commentTracker.text(binaryExpression.getLOperand()); - } - } - } - else if (expression instanceof PsiLiteralExpression) { - final PsiLiteralExpression literalExpression = (PsiLiteralExpression)expression; - final Object value = literalExpression.getValue(); - if (value instanceof Integer) { - final Integer integer = (Integer)value; - return String.valueOf(integer.intValue() + 1); - } - } - return commentTracker.text(expression, ParenthesesUtils.ADDITIVE_PRECEDENCE) + "+1"; + return JavaPsiMathUtil.add(expression, 1, commentTracker); } @NonNls diff --git a/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/JavaPsiMathUtil.java b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/JavaPsiMathUtil.java new file mode 100644 index 000000000000..b312258ae21d --- /dev/null +++ b/plugins/InspectionGadgets/InspectionGadgetsAnalysis/src/com/siyeh/ig/psiutils/JavaPsiMathUtil.java @@ -0,0 +1,222 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.siyeh.ig.psiutils; + +import com.intellij.codeInspection.dataFlow.value.DfaRelationValue; +import com.intellij.psi.*; +import com.intellij.psi.tree.IElementType; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.ArrayUtil; +import one.util.streamex.StreamEx; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +import static com.intellij.util.ObjectUtils.tryCast; + +/** + * Math conversions on PSI expressions + */ +public class JavaPsiMathUtil { + private JavaPsiMathUtil() { + throw new UnsupportedOperationException(); + } + + /** + * Returns an expression text which evaluates to the supplied expression plus given addend. May perform some simplifications. + * E.g. if supplied expression is "x-1" and addend is 1, then "x" will be returned. + * + * @param expression expression to add to + * @param addend an addend value. Can be negative or zero. + * @param ct comment tracker to track used comments + * @return an expression text representing an expression with added value. + */ + @NotNull + public static String add(@NotNull PsiExpression expression, int addend, @NotNull CommentTracker ct) { + if (addend == 0) return ct.text(expression); + PsiExpression stripped = PsiUtil.skipParenthesizedExprDown(expression); + Number value = getNumberFromLiteral(stripped); + if (value instanceof Integer) { + return String.valueOf(value.intValue() + addend); + } + else if (value instanceof Long) { + return String.valueOf(value.longValue() + addend); + } + if (stripped instanceof PsiPolyadicExpression) { + PsiPolyadicExpression polyadicExpression = (PsiPolyadicExpression)stripped; + int multiplier = getMultiplier(polyadicExpression); + if (multiplier != 0) { + value = getNumberFromLiteral(ArrayUtil.getLastElement(polyadicExpression.getOperands())); + String updatedAddend = null; + if (value instanceof Integer) { + updatedAddend = String.valueOf(value.intValue() * multiplier + addend); + } + else if (value instanceof Long) { + updatedAddend = String.valueOf(value.longValue() * multiplier + addend); + } + if (updatedAddend != null) { + if (updatedAddend.equals("0")) { + updatedAddend = ""; + } + else if (!updatedAddend.startsWith("-")) { + updatedAddend = "+" + updatedAddend; + } + return updateLastAddend(polyadicExpression, updatedAddend, ct); + } + } + } + return ct.text(expression, ParenthesesUtils.ADDITIVE_PRECEDENCE) + (addend > 0 ? "+" : "") + addend; + } + + /** + * Tries to simplify the comparison performing algebraic conversions (e.g. "i+1 > j" -> "i >= j" or "i+3 > j+5" -> "i > j+2"). + * Note that this method does not take into account possible integer overflow, so the semantic change is possible if + * integer overflow is considered to be a valid situation. + * + * @param comparison comparison to simplify + * @param ct comment tracker to track used comments + * @return a simplified comparison text or null if no simplification can be performed (if null is returned, + * then comment tracker is unchanged). + */ + @Contract("null, _ -> null") + @Nullable + public static String simplifyComparison(PsiExpression comparison, @NotNull CommentTracker ct) { + if (!(comparison instanceof PsiBinaryExpression)) return null; + PsiBinaryExpression binOp = (PsiBinaryExpression)comparison; + DfaRelationValue.RelationType relationType = DfaRelationValue.RelationType.fromElementType(binOp.getOperationTokenType()); + if (relationType == null) return null; + String operator = binOp.getOperationSign().getText(); + PsiExpression left = PsiUtil.skipParenthesizedExprDown(binOp.getLOperand()); + PsiExpression right = PsiUtil.skipParenthesizedExprDown(binOp.getROperand()); + if (left == null || right == null) return null; + Integer leftValue = tryCast(getNumberFromLiteral(left), Integer.class); + Integer rightValue = tryCast(getNumberFromLiteral(right), Integer.class); + PsiPolyadicExpression leftPolyadic = tryCast(left, PsiPolyadicExpression.class); + Integer leftAddend = tryCast(getLastAddend(leftPolyadic), Integer.class); + PsiPolyadicExpression rightPolyadic = tryCast(right, PsiPolyadicExpression.class); + Integer rightAddend = tryCast(getLastAddend(rightPolyadic), Integer.class); + if (leftAddend != null) { + if (leftAddend == 1 && operator.equals(">")) { + return updateLastAddend(leftPolyadic, "", ct) + ">=" + ct.text(right); + } + if (leftAddend == 1 && operator.equals("<=")) { + return updateLastAddend(leftPolyadic, "", ct) + "<" + ct.text(right); + } + if (leftAddend == -1 && operator.equals(">=")) { + return updateLastAddend(leftPolyadic, "", ct) + ">" + ct.text(right); + } + if (leftAddend == -1 && operator.equals("<")) { + return updateLastAddend(leftPolyadic, "", ct) + "<=" + ct.text(right); + } + if (rightValue != null) { + rightValue -= leftAddend; + return updateLastAddend(leftPolyadic, "", ct) + operator + rightValue; + } + } + if (rightAddend != null) { + if (rightAddend == 1 && operator.equals("<")) { + return ct.text(left) + "<=" + updateLastAddend(rightPolyadic, "", ct); + } + if (rightAddend == 1 && operator.equals(">=")) { + return ct.text(left) + ">" + updateLastAddend(rightPolyadic, "", ct); + } + if (rightAddend == -1 && operator.equals("<=")) { + return ct.text(left) + "<" + updateLastAddend(rightPolyadic, "", ct); + } + if (rightAddend == -1 && operator.equals(">")) { + return ct.text(left) + ">=" + updateLastAddend(rightPolyadic, "", ct); + } + if (leftValue != null) { + leftValue -= rightAddend; + return leftValue + operator + updateLastAddend(rightPolyadic, "", ct); + } + } + if (leftAddend != null && rightAddend != null) { + int diff = leftAddend - rightAddend; + if (diff > 0) { + return updateLastAddend(leftPolyadic, "+" + diff, ct) + operator + updateLastAddend(rightPolyadic, "", ct); + } else if (diff < 0) { + return updateLastAddend(leftPolyadic, "", ct) + operator + updateLastAddend(rightPolyadic, "+"+(-diff), ct); + } else { + return updateLastAddend(leftPolyadic, "", ct) + operator + updateLastAddend(rightPolyadic, "", ct); + } + } + return null; + } + + private static String updateLastAddend(PsiPolyadicExpression polyadicExpression, String updatedAddend, CommentTracker ct) { + PsiExpression lastElement = Objects.requireNonNull(ArrayUtil.getLastElement(polyadicExpression.getOperands())); + PsiJavaToken lastToken = Objects.requireNonNull(polyadicExpression.getTokenBeforeOperand(lastElement)); + PsiElement from = polyadicExpression.getFirstChild(); + PsiElement to = lastToken.getPrevSibling(); + return StreamEx.iterate(from, PsiElement::getNextSibling).takeWhileInclusive(e -> !to.equals(e)).map(ct::text).joining() + updatedAddend; + } + + @Nullable + @Contract("null->null") + private static Number getNumberFromLiteral(@Nullable PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (expression instanceof PsiLiteralExpression) { + return tryCast(((PsiLiteralExpression)expression).getValue(), Number.class); + } + if (expression instanceof PsiUnaryExpression && ((PsiUnaryExpression)expression).getOperationTokenType().equals(JavaTokenType.MINUS)) { + PsiExpression operand = ((PsiUnaryExpression)expression).getOperand(); + if (operand instanceof PsiLiteralExpression) { + Object value = ((PsiLiteralExpression)operand).getValue(); + return negate(value); + } + } + return null; + } + + @Nullable + private static Number negate(Object value) { + if (value instanceof Integer) { + return -((Integer)value).intValue(); + } + if (value instanceof Long) { + return -((Long)value).longValue(); + } + if (value instanceof Double) { + return -((Double)value).doubleValue(); + } + if (value instanceof Float) { + return -((Float)value).floatValue(); + } + return null; + } + + private static int getMultiplier(PsiPolyadicExpression polyadicExpression) { + int multiplier; + IElementType type = polyadicExpression.getOperationTokenType(); + if (type.equals(JavaTokenType.PLUS)) { + multiplier = 1; + } + else if (type.equals(JavaTokenType.MINUS)) { + multiplier = -1; + } + else { + multiplier = 0; + } + return multiplier; + } + + @Nullable + private static Number getLastAddend(PsiPolyadicExpression polyadic) { + if (polyadic != null) { + int multiplier = getMultiplier(polyadic); + if (multiplier != 0) { + PsiExpression lastOp = ArrayUtil.getLastElement(polyadic.getOperands()); + Number lastAddend = getNumberFromLiteral(lastOp); + if (multiplier == 1) { + return lastAddend; + } + else if (multiplier == -1) { + return negate(lastAddend); + } + } + } + return null; + } +} diff --git a/plugins/IntentionPowerPak/src/com/siyeh/ipp/forloop/ReverseForLoopDirectionIntention.java b/plugins/IntentionPowerPak/src/com/siyeh/ipp/forloop/ReverseForLoopDirectionIntention.java index 0e08932e09e0..221832cf5688 100644 --- a/plugins/IntentionPowerPak/src/com/siyeh/ipp/forloop/ReverseForLoopDirectionIntention.java +++ b/plugins/IntentionPowerPak/src/com/siyeh/ipp/forloop/ReverseForLoopDirectionIntention.java @@ -18,9 +18,9 @@ package com.siyeh.ipp.forloop; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.tree.IElementType; +import com.siyeh.ig.psiutils.CommentTracker; import com.siyeh.ig.psiutils.ComparisonUtils; -import com.siyeh.ig.psiutils.ExpressionUtils; -import com.siyeh.ig.psiutils.ParenthesesUtils; +import com.siyeh.ig.psiutils.JavaPsiMathUtil; import com.siyeh.ig.psiutils.VariableAccessUtils; import com.siyeh.ipp.base.Intention; import com.siyeh.ipp.base.PsiElementPredicate; @@ -163,53 +163,8 @@ public class ReverseForLoopDirectionIntention extends Intention { updateExpression.replace(newUpdate); } - private static String incrementExpression(PsiExpression expression, - boolean positive) { - if (expression instanceof PsiLiteralExpression) { - final PsiLiteralExpression literalExpression = - (PsiLiteralExpression)expression; - final Number value = (Number)literalExpression.getValue(); - if (value == null) { - return null; - } - if (positive) { - return String.valueOf(value.longValue() + 1L); - } - else { - return String.valueOf(value.longValue() - 1L); - } - } - else { - if (expression instanceof PsiBinaryExpression) { - // see if we can remove a -1 instead of adding a +1 - final PsiBinaryExpression binaryExpression = - (PsiBinaryExpression)expression; - final PsiExpression rhs = binaryExpression.getROperand(); - if (ExpressionUtils.isOne(rhs)) { - final IElementType tokenType = - binaryExpression.getOperationTokenType(); - if (tokenType == JavaTokenType.MINUS && positive) { - return binaryExpression.getLOperand().getText(); - } - else if (tokenType == JavaTokenType.PLUS && !positive) { - return binaryExpression.getLOperand().getText(); - } - } - } - final String expressionText; - if (ParenthesesUtils.getPrecedence(expression) > - ParenthesesUtils.ADDITIVE_PRECEDENCE) { - expressionText = '(' + expression.getText() + ')'; - } - else { - expressionText = expression.getText(); - } - if (positive) { - return expressionText + "+1"; - } - else { - return expressionText + "-1"; - } - } + private static String incrementExpression(PsiExpression expression, boolean positive) { + // TODO: properly support comment tracking + return JavaPsiMathUtil.add(expression, positive ? 1 : -1, new CommentTracker()); } } \ No newline at end of file