CountingLoop extracted and used; IndexedContainer simplified

This commit is contained in:
Tagir Valeev
2017-01-17 11:34:30 +07:00
parent 2a4829a345
commit 8b4f950405
5 changed files with 161 additions and 129 deletions
@@ -25,7 +25,6 @@ import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.psiutils.*;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Contract;
@@ -138,31 +137,13 @@ public class UseBulkOperationInspection extends BaseJavaBatchLocalInspectionTool
@Nullable
private static PsiExpression findIterableForIndexedLoop(PsiForStatement loop, PsiExpression getElementExpression) {
IndexedContainer container = IndexedContainer.fromGetExpression(getElementExpression);
if(container == null) return null;
PsiExpression indexExpression = container.extractIndexFromGetExpression(getElementExpression);
// Check that loop initialization is like `int idx = 0` and loop update is like `idx++`
PsiStatement initialization = loop.getInitialization();
if (!(initialization instanceof PsiDeclarationStatement)) return null;
PsiElement[] declaredElements = ((PsiDeclarationStatement)initialization).getDeclaredElements();
if (declaredElements.length != 1 || !(declaredElements[0] instanceof PsiLocalVariable)) return null;
PsiLocalVariable indexVariable = (PsiLocalVariable)declaredElements[0];
if (!ExpressionUtils.isReferenceTo(indexExpression, indexVariable) ||
!ExpressionUtils.isZero(indexVariable.getInitializer()) ||
!VariableAccessUtils.variableIsIncremented(indexVariable, loop.getUpdate())) {
return null;
}
// Check that loop condition is like `idx < arr.length` or `idx < list.size()`
PsiBinaryExpression condition = ObjectUtils.tryCast(loop.getCondition(), PsiBinaryExpression.class);
if (condition == null ||
!condition.getOperationTokenType().equals(JavaTokenType.LT) ||
!ExpressionUtils.isReferenceTo(condition.getLOperand(), indexVariable)) {
return null;
}
PsiExpression bound = condition.getROperand();
return container.extractQualifierFromLengthExpression(bound);
CountingLoop countingLoop = CountingLoop.from(loop);
if (countingLoop == null || countingLoop.isIncluding() || !ExpressionUtils.isZero(countingLoop.getInitializer())) return null;
IndexedContainer container = IndexedContainer.fromLengthExpression(countingLoop.getBound());
if (container == null) return null;
PsiExpression index = container.extractIndexFromGetExpression(getElementExpression);
if (!ExpressionUtils.isReferenceTo(index, countingLoop.getCounter())) return null;
return container.getQualifier();
}
@Nullable
@@ -32,7 +32,6 @@ import com.intellij.psi.controlFlow.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
@@ -688,7 +687,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
@Nullable
static PsiLocalVariable extractArray(TerminalBlock tb) {
CountingLoop loop = tb.getLastOperation(CountingLoop.class);
CountingLoopSource loop = tb.getLastOperation(CountingLoopSource.class);
if(loop == null || loop.myIncluding) return null;
PsiAssignmentExpression assignment = tb.getSingleExpression(PsiAssignmentExpression.class);
if(assignment == null || !assignment.getOperationTokenType().equals(JavaTokenType.EQ)) return null;
@@ -945,7 +944,7 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
@Contract("null -> null")
static StreamSource tryCreate(PsiLoopStatement statement) {
if(statement instanceof PsiForStatement) {
return CountingLoop.from((PsiForStatement)statement);
return CountingLoopSource.from((PsiForStatement)statement);
}
if(statement instanceof PsiForeachStatement) {
ArrayStream source = ArrayStream.from((PsiForeachStatement)statement);
@@ -1073,15 +1072,15 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
}
}
static class CountingLoop extends StreamSource {
static class CountingLoopSource extends StreamSource {
final PsiExpression myBound;
final boolean myIncluding;
private CountingLoop(PsiLoopStatement loop,
PsiVariable counter,
PsiExpression initializer,
PsiExpression bound,
boolean including) {
private CountingLoopSource(PsiLoopStatement loop,
PsiVariable counter,
PsiExpression initializer,
PsiExpression bound,
boolean including) {
super(loop, counter, initializer);
myBound = bound;
myIncluding = including;
@@ -1099,8 +1098,8 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
return className+"."+methodName+"("+myExpression.getText()+", "+myBound.getText()+")";
}
CountingLoop withBound(PsiExpression bound) {
return new CountingLoop(getLoop(), getVariable(), getExpression(), bound, myIncluding);
CountingLoopSource withBound(PsiExpression bound) {
return new CountingLoopSource(getLoop(), getVariable(), getExpression(), bound, myIncluding);
}
@Override
@@ -1115,45 +1114,10 @@ public class StreamApiMigrationInspection extends BaseJavaBatchLocalInspectionTo
}
@Nullable
public static CountingLoop from(PsiForStatement forStatement) {
// check that initialization is for(int/long i = <initial_value>;...;...)
PsiDeclarationStatement initialization = tryCast(forStatement.getInitialization(), PsiDeclarationStatement.class);
if (initialization == null || initialization.getDeclaredElements().length != 1) return null;
PsiLocalVariable counter = tryCast(initialization.getDeclaredElements()[0], PsiLocalVariable.class);
if(counter == null) return null;
if(!counter.getType().equals(PsiType.INT) && !counter.getType().equals(PsiType.LONG)) return null;
PsiExpression initializer = counter.getInitializer();
if(initializer == null) return null;
// check that increment is like for(...;...;i++)
if(!VariableAccessUtils.variableIsIncremented(counter, forStatement.getUpdate())) return null;
// check that condition is like for(...;i<bound;...) or for(...;i<=bound;...)
PsiBinaryExpression condition = tryCast(forStatement.getCondition(), PsiBinaryExpression.class);
if(condition == null) return null;
IElementType type = condition.getOperationTokenType();
boolean closed = false;
PsiExpression bound;
PsiExpression ref;
if(type.equals(JavaTokenType.LE)) {
bound = condition.getROperand();
ref = condition.getLOperand();
closed = true;
} else if(type.equals(JavaTokenType.LT)) {
bound = condition.getROperand();
ref = condition.getLOperand();
} else if(type.equals(JavaTokenType.GE)) {
bound = condition.getLOperand();
ref = condition.getROperand();
closed = true;
} else if(type.equals(JavaTokenType.GT)) {
bound = condition.getLOperand();
ref = condition.getROperand();
} else return null;
if(bound == null || !ExpressionUtils.isReferenceTo(ref, counter)) return null;
if(!TypeConversionUtil.areTypesAssignmentCompatible(counter.getType(), bound)) return null;
return new CountingLoop(forStatement, counter, initializer, bound, closed);
public static CountingLoopSource from(PsiForStatement forStatement) {
CountingLoop loop = CountingLoop.from(forStatement);
if (loop == null) return null;
return new CountingLoopSource(forStatement, loop.getCounter(), loop.getInitializer(), loop.getBound(), loop.isIncluding());
}
}
}
@@ -15,7 +15,7 @@
*/
package com.intellij.codeInspection.streamMigration;
import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.CountingLoop;
import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.CountingLoopSource;
import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.InitializerUsageStatus;
import com.intellij.codeInspection.streamMigration.StreamApiMigrationInspection.MapOp;
import com.intellij.openapi.project.Project;
@@ -45,7 +45,7 @@ public class ToArrayMigration extends BaseStreamApiMigration {
if(initializer == null) return null;
PsiExpression dimension = ArrayUtil.getFirstElement(initializer.getArrayDimensions());
if(dimension == null) return null;
CountingLoop loop = tb.getLastOperation(CountingLoop.class);
CountingLoopSource loop = tb.getLastOperation(CountingLoopSource.class);
if(loop == null) return null;
PsiArrayType arrayType = tryCast(initializer.getType(), PsiArrayType.class);
if(arrayType == null) return null;
@@ -0,0 +1,131 @@
/*
* Copyright 2000-2017 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 com.siyeh.ig.psiutils;
import com.intellij.psi.*;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.intellij.util.ObjectUtils.tryCast;
/**
* Represents a loop of form {@code for(int/long counter = initializer; counter </<= bound; counter++)}
*
* @author Tagir Valeev
*/
public class CountingLoop {
final @NotNull PsiLocalVariable myCounter;
final @NotNull PsiLoopStatement myLoop;
final @NotNull PsiExpression myInitializer;
final @NotNull PsiExpression myBound;
final boolean myIncluding;
private CountingLoop(@NotNull PsiLoopStatement loop,
@NotNull PsiLocalVariable counter,
@NotNull PsiExpression initializer,
@NotNull PsiExpression bound,
boolean including) {
myInitializer = initializer;
myCounter = counter;
myLoop = loop;
myBound = bound;
myIncluding = including;
}
/**
* @return loop counter variable
*/
@NotNull
public PsiLocalVariable getCounter() {
return myCounter;
}
/**
* @return loop statement
*/
@NotNull
public PsiLoopStatement getLoop() {
return myLoop;
}
/**
* @return counter variable initial value
*/
@NotNull
public PsiExpression getInitializer() {
return myInitializer;
}
/**
* @return loop bound
*/
@NotNull
public PsiExpression getBound() {
return myBound;
}
/**
* @return true if bound is including
*/
public boolean isIncluding() {
return myIncluding;
}
@Nullable
public static CountingLoop from(PsiForStatement forStatement) {
// check that initialization is for(int/long i = <initial_value>;...;...)
PsiDeclarationStatement initialization = tryCast(forStatement.getInitialization(), PsiDeclarationStatement.class);
if (initialization == null || initialization.getDeclaredElements().length != 1) return null;
PsiLocalVariable counter = tryCast(initialization.getDeclaredElements()[0], PsiLocalVariable.class);
if(counter == null) return null;
if(!counter.getType().equals(PsiType.INT) && !counter.getType().equals(PsiType.LONG)) return null;
PsiExpression initializer = PsiUtil.skipParenthesizedExprDown(counter.getInitializer());
if(initializer == null) return null;
// check that increment is like for(...;...;i++)
if(!VariableAccessUtils.variableIsIncremented(counter, forStatement.getUpdate())) return null;
// check that condition is like for(...;i<bound;...) or for(...;i<=bound;...)
PsiBinaryExpression condition = tryCast(forStatement.getCondition(), PsiBinaryExpression.class);
if(condition == null) return null;
IElementType type = condition.getOperationTokenType();
boolean closed = false;
PsiExpression bound;
PsiExpression ref;
if(type.equals(JavaTokenType.LE)) {
bound = condition.getROperand();
ref = condition.getLOperand();
closed = true;
} else if(type.equals(JavaTokenType.LT)) {
bound = condition.getROperand();
ref = condition.getLOperand();
} else if(type.equals(JavaTokenType.GE)) {
bound = condition.getLOperand();
ref = condition.getROperand();
closed = true;
} else if(type.equals(JavaTokenType.GT)) {
bound = condition.getLOperand();
ref = condition.getROperand();
} else return null;
if(bound == null || !ExpressionUtils.isReferenceTo(ref, counter)) return null;
if(!TypeConversionUtil.areTypesAssignmentCompatible(counter.getType(), bound)) return null;
return new CountingLoop(forStatement, counter, initializer, bound, closed);
}
}
@@ -42,15 +42,6 @@ public abstract class IndexedContainer {
*/
public abstract boolean isGetMethodReference(PsiMethodReferenceExpression methodReference);
/**
* Extracts the qualifier if the supplied expression obtains the container length (either array.length or list.size())
*
* @param expression expression to extract the qualifier from
* @return the extracted qualifier or null if the supplied expression is not a length expression. The extracted qualifier might be
* non-physical if it was implicit in the original code (e.g. "this" could be returned if original call was simply "size()")
*/
public abstract PsiExpression extractQualifierFromLengthExpression(@Nullable PsiExpression expression);
/**
* Returns an ancestor element retrieval expression if the supplied expression is the index used in it
* (e.g. index in arr[index] or in list.get(index))
@@ -76,6 +67,10 @@ public abstract class IndexedContainer {
return myQualifier;
}
public boolean isQualifierEquivalent(@Nullable PsiExpression candidate) {
return candidate != null && PsiEquivalenceUtil.areElementsEquivalent(myQualifier, candidate);
}
/**
* @return type of the elements in the container or null if cannot be determined
*/
@@ -103,28 +98,6 @@ public abstract class IndexedContainer {
return null;
}
/**
* Creates an IndexedContainer from element retrieval expression (like array[idx] or list.get(idx))
*
* @param expression expression to create an IndexedContainer from
* @return newly created IndexedContainer or null if the supplied expression is not element retrieval expression
*/
@Nullable
public static IndexedContainer fromGetExpression(PsiExpression expression) {
expression = PsiUtil.skipParenthesizedExprDown(expression);
if (expression instanceof PsiArrayAccessExpression) {
PsiArrayAccessExpression arrayAccess = (PsiArrayAccessExpression)expression;
return new ArrayIndexedContainer(arrayAccess.getArrayExpression());
}
if (expression instanceof PsiMethodCallExpression) {
PsiMethodCallExpression call = (PsiMethodCallExpression)expression;
PsiExpression[] args = call.getArgumentList().getExpressions();
if (args.length != 1 || !ListIndexedContainer.isGetCall(call)) return null;
return new ListIndexedContainer(ExpressionUtils.getQualifierOrThis(call.getMethodExpression()));
}
return null;
}
static class ArrayIndexedContainer extends IndexedContainer {
ArrayIndexedContainer(PsiExpression qualifier) {
super(qualifier);
@@ -135,13 +108,6 @@ public abstract class IndexedContainer {
return false;
}
@Override
public PsiExpression extractQualifierFromLengthExpression(@Nullable PsiExpression expression) {
expression = PsiUtil.skipParenthesizedExprDown(expression);
PsiExpression lengthQualifier = ExpressionUtils.getArrayFromLengthExpression(expression);
return lengthQualifier != null && PsiEquivalenceUtil.areElementsEquivalent(getQualifier(), lengthQualifier) ? lengthQualifier : null;
}
@Override
public PsiExpression extractGetExpressionFromIndex(@Nullable PsiExpression indexExpression) {
if (indexExpression != null) {
@@ -159,7 +125,7 @@ public abstract class IndexedContainer {
expression = PsiUtil.skipParenthesizedExprDown(expression);
if (expression instanceof PsiArrayAccessExpression) {
PsiArrayAccessExpression arrayAccess = (PsiArrayAccessExpression)expression;
if (PsiEquivalenceUtil.areElementsEquivalent(getQualifier(), arrayAccess.getArrayExpression())) {
if (isQualifierEquivalent(arrayAccess.getArrayExpression())) {
return arrayAccess.getIndexExpression();
}
}
@@ -181,20 +147,11 @@ public abstract class IndexedContainer {
@Override
public boolean isGetMethodReference(PsiMethodReferenceExpression methodReference) {
if (!"get".equals(methodReference.getReferenceName())) return false;
PsiExpression qualifier = methodReference.getQualifierExpression();
if (qualifier == null || !PsiEquivalenceUtil.areElementsEquivalent(getQualifier(), qualifier)) return false;
if (!isQualifierEquivalent(ExpressionUtils.getQualifierOrThis(methodReference))) return false;
PsiMethod method = ObjectUtils.tryCast(methodReference.resolve(), PsiMethod.class);
return method != null && MethodUtils.methodMatches(method, CommonClassNames.JAVA_UTIL_LIST, null, "get", PsiType.INT);
}
@Override
public PsiExpression extractQualifierFromLengthExpression(@Nullable PsiExpression expression) {
PsiMethodCallExpression call = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(expression), PsiMethodCallExpression.class);
if (call == null || !isSizeCall(call)) return null;
PsiExpression lengthQualifier = ExpressionUtils.getQualifierOrThis(call.getMethodExpression());
return PsiEquivalenceUtil.areElementsEquivalent(getQualifier(), lengthQualifier) ? lengthQualifier : null;
}
@Override
public PsiExpression extractGetExpressionFromIndex(@Nullable PsiExpression indexExpression) {
if (indexExpression != null) {
@@ -215,8 +172,7 @@ public abstract class IndexedContainer {
PsiMethodCallExpression call = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(expression), PsiMethodCallExpression.class);
if (call == null) return null;
PsiExpression[] args = call.getArgumentList().getExpressions();
if (args.length == 1 && isGetCall(call) &&
PsiEquivalenceUtil.areElementsEquivalent(getQualifier(), ExpressionUtils.getQualifierOrThis(call.getMethodExpression()))) {
if (args.length == 1 && isGetCall(call) && isQualifierEquivalent(ExpressionUtils.getQualifierOrThis(call.getMethodExpression()))) {
return args[0];
}
return null;