Insoection to convert guava's fluent iterables to java 8 streams

This commit is contained in:
Dmitry Batkovich
2015-07-15 18:12:26 +03:00
parent b2dec6a348
commit 67ceee54df
7 changed files with 1032 additions and 1 deletions
@@ -0,0 +1,138 @@
/*
* Copyright 2000-2015 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.intellij.codeInspection.java18StreamApi;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import org.jetbrains.annotations.Nullable;
/**
* @author Dmitry Batkovich
*/
abstract class FluentIterableMethodTransformer {
private final static Logger LOG = Logger.getInstance(FluentIterableMethodTransformer.class);
protected abstract String formatMethod(PsiExpression[] initialMethodParameters);
protected boolean negate() {
return false;
}
@Nullable
public final PsiMethodCallExpression transform(PsiMethodCallExpression expression, PsiElementFactory elementFactory) {
final String formatted = formatMethod(expression.getArgumentList().getExpressions());
final String negation = negate() ? "!" : "";
final PsiExpression qualifierExpression = expression.getMethodExpression().getQualifierExpression();
LOG.assertTrue(qualifierExpression != null);
final String oldQualifierText = qualifierExpression.getText();
final String expressionText = negation + oldQualifierText + "." + formatted;
final PsiElement replaced = expression.replace(elementFactory.createExpressionFromText(expressionText, null));
if (replaced instanceof PsiMethodCallExpression) {
PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)replaced;
while (true) {
final PsiExpression qualifier = methodCallExpression.getMethodExpression().getQualifierExpression();
LOG.assertTrue(qualifier != null);
if (oldQualifierText.equals(qualifier.getText())) {
return methodCallExpression;
}
methodCallExpression = (PsiMethodCallExpression)qualifier;
}
}
else {
return null;
}
}
static class OneParameterMethodTransformer extends FluentIterableMethodTransformer {
private final String myTemplate;
private final boolean myParameterIsFunctionOrPredicate;
OneParameterMethodTransformer(String template, boolean parameterIsFunctionOrPredicate) {
myTemplate = template;
myParameterIsFunctionOrPredicate = parameterIsFunctionOrPredicate;
}
OneParameterMethodTransformer(String parameterIsFunctionOrPredicate) {
this(parameterIsFunctionOrPredicate, false);
}
protected String formatMethod(PsiExpression[] initialMethodParameters) {
final String matchedElementText;
if (myParameterIsFunctionOrPredicate) {
if (initialMethodParameters.length == 1) {
PsiExpression parameter = initialMethodParameters[0];
final PsiType type = parameter.getType();
Boolean role;
if (!(type instanceof PsiMethodReferenceType) && !(type instanceof PsiLambdaExpressionType)
&& (role = GuavaFunctionAndPredicateConverter.isClassConditionPredicate(parameter)) != null) {
matchedElementText = GuavaFunctionAndPredicateConverter.convertFunctionOrPredicateParameter(parameter, role);
} else {
matchedElementText = parameter.getText();
}
} else {
matchedElementText = "";
}
} else {
matchedElementText = initialMethodParameters.length > 0 ? initialMethodParameters[0].getText() : "";
}
return String.format(myTemplate, matchedElementText);
}
}
static class ToArrayMethodTransformer extends FluentIterableMethodTransformer {
@Override
protected String formatMethod(PsiExpression[] initialMethodParameters) {
final PsiExpression parameter = initialMethodParameters[0];
if (parameter instanceof PsiClassObjectAccessExpression) {
final PsiType type = ((PsiClassObjectAccessExpression)parameter).getOperand().getType();
if (type instanceof PsiClassType) {
final PsiClass resolvedClass = ((PsiClassType)type).resolve();
if (resolvedClass != null) {
final String qName = resolvedClass.getQualifiedName();
if (qName != null) {
return String.format("toArray(%s[]::new)", qName);
}
}
}
}
return "toArray()";
}
}
static class ParameterlessMethodTransformer extends FluentIterableMethodTransformer {
private final String myTemplate;
private final boolean myNegation;
ParameterlessMethodTransformer(String template) {
this(template.endsWith(")") ? template : (template + "()"), false);
}
ParameterlessMethodTransformer(String template, boolean negation) {
myTemplate = template;
myNegation = negation;
}
@Override
protected boolean negate() {
return myNegation;
}
@Override
protected String formatMethod(PsiExpression[] initialMethodParameters) {
return myTemplate;
}
}
}
@@ -0,0 +1,476 @@
/*
* Copyright 2000-2015 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.intellij.codeInspection.java18StreamApi;
import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.impl.scopes.JdkScope;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import gnu.trove.THashSet;
import gnu.trove.TObjectIdentityHashingStrategy;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Dmitry Batkovich
*/
@SuppressWarnings("DialogTitleCapitalization")
public class GuavaFluentIterableInspection extends BaseJavaBatchLocalInspectionTool {
private final static Logger LOG = Logger.getInstance(GuavaFluentIterableInspection.class);
private final static String PROBLEM_DESCRIPTION = "FluentIterable is used while Stream API is accessible";
public final static String GUAVA_FLUENT_ITERABLE = "com.google.common.collect.FluentIterable";
public final static String GUAVA_OPTIONAL = "com.google.common.base.Optional";
public final static String GUAVA_IMMUTABLE_MAP = "com.google.common.collect.ImmutableMap";
public final static String FLUENT_ITERABLE_FROM = "from";
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
final PsiClass fluentIterable = JavaPsiFacade.getInstance(holder.getProject())
.findClass(GUAVA_FLUENT_ITERABLE, GlobalSearchScope.allScope(holder.getProject()));
if (fluentIterable == null) {
return PsiElementVisitor.EMPTY_VISITOR;
}
return new JavaRecursiveElementVisitor() {
private final SmartPointerManager mySmartPointerManager = SmartPointerManager.getInstance(holder.getProject());
private final Set<PsiMethodCallExpression> myMethodCallsToIgnore =
new THashSet<PsiMethodCallExpression>(new TObjectIdentityHashingStrategy<PsiMethodCallExpression>());
private final MultiMap<PsiLocalVariable, PsiExpression> myLocalVariablesUsages = new MultiMap<PsiLocalVariable, PsiExpression>();
private final Set<PsiLocalVariable> myUnconvertibleVariables = new THashSet<PsiLocalVariable>();
@Override
public void visitLocalVariable(final PsiLocalVariable localVariable) {
final PsiType type = localVariable.getType();
if (!(type instanceof PsiClassType) || myUnconvertibleVariables.contains(localVariable)) {
return;
}
final PsiClass variableClass = ((PsiClassType)type).resolve();
if (variableClass == null) {
return;
}
final String qualifiedName = variableClass.getQualifiedName();
if (!GUAVA_FLUENT_ITERABLE.equals(qualifiedName)) {
return;
}
final PsiCodeBlock context = PsiTreeUtil.getParentOfType(localVariable, PsiCodeBlock.class);
if (context == null || !checkDeclaration(localVariable.getInitializer())) {
myUnconvertibleVariables.add(localVariable);
}
}
private boolean checkDeclaration(PsiExpression declaration) {
if (declaration == null) {
return true;
}
if (!(declaration instanceof PsiMethodCallExpression)) {
return false;
}
PsiMethodCallExpression currentCallExpression = (PsiMethodCallExpression)declaration;
while (true) {
final PsiExpression qualifier = currentCallExpression.getMethodExpression().getQualifierExpression();
if (qualifier instanceof PsiMethodCallExpression) {
final PsiMethod method = currentCallExpression.resolveMethod();
if (method == null || GuavaFluentIterableMethodConverters.isStopMethod(method.getName())) {
return false;
}
final PsiClass aClass = method.getContainingClass();
if (aClass == null || !aClass.isEquivalentTo(fluentIterable)) {
return false;
}
currentCallExpression = (PsiMethodCallExpression)qualifier;
} else {
if (qualifier instanceof PsiReferenceExpression) {
final PsiMethod method = currentCallExpression.resolveMethod();
if (method == null || !FLUENT_ITERABLE_FROM.equals(method.getName())) {
return false;
}
final PsiClass aClass = method.getContainingClass();
if (aClass == null || !GUAVA_FLUENT_ITERABLE.equals(aClass.getQualifiedName())) {
return false;
}
break;
} else {
return false;
}
}
}
return true;
}
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
super.visitReferenceExpression(expression);
final PsiElement resolvedElement = expression.resolve();
if (resolvedElement instanceof PsiLocalVariable) {
PsiLocalVariable fluentIterableVariable = (PsiLocalVariable)resolvedElement;
if (!fluentIterable.isEquivalentTo(PsiTypesUtil.getPsiClass(fluentIterableVariable.getType())) ||
myUnconvertibleVariables.contains(fluentIterableVariable)) {
return;
}
analyzeExpression(expression, fluentIterableVariable);
}
}
private void addToUnconvertible(PsiLocalVariable variable) {
myUnconvertibleVariables.add(variable);
myLocalVariablesUsages.remove(variable);
}
@Override
public void visitJavaFile(PsiJavaFile file) {
super.visitJavaFile(file);
for (Map.Entry<PsiLocalVariable, Collection<PsiExpression>> e : myLocalVariablesUsages.entrySet()) {
final PsiLocalVariable localVariable = e.getKey();
final Collection<PsiExpression> foundUsages = e.getValue();
final SmartPsiElementPointer<PsiLocalVariable> variablePointer = mySmartPointerManager.createSmartPsiElementPointer(localVariable);
final ConvertGuavaFluentIterableQuickFix quickFix = new ConvertGuavaFluentIterableQuickFix(variablePointer, ContainerUtil.map(
new THashSet<PsiExpression>(foundUsages), new Function<PsiExpression, SmartPsiElementPointer<PsiExpression>>() {
@Override
public SmartPsiElementPointer<PsiExpression> fun(PsiExpression expression) {
return mySmartPointerManager.createSmartPsiElementPointer(expression);
}
}));
holder.registerProblem(localVariable, PROBLEM_DESCRIPTION, quickFix);
for (PsiExpression usage : foundUsages) {
holder.registerProblem(usage, PROBLEM_DESCRIPTION, quickFix);
}
}
myLocalVariablesUsages.clear();
myUnconvertibleVariables.clear();
}
@Override
public void visitMethodCallExpression(PsiMethodCallExpression expression) {
super.visitMethodCallExpression(expression);
if (!myMethodCallsToIgnore.add(expression)) {
return;
}
final PsiReferenceExpression methodExpression = expression.getMethodExpression();
final String methodName = methodExpression.getReferenceName();
if (FLUENT_ITERABLE_FROM.equals(methodName)) {
final PsiMethod method = expression.resolveMethod();
if (method == null || !method.hasModifierProperty(PsiModifier.STATIC) || !fluentIterable.isEquivalentTo(method.getContainingClass())) {
return;
}
PsiMethodCallExpression currentExpression = expression;
while (true) {
myMethodCallsToIgnore.add(currentExpression);
PsiMethodCallExpression parentMethodCall = PsiTreeUtil.getParentOfType(currentExpression, PsiMethodCallExpression.class);
if (parentMethodCall != null) {
if (parentMethodCall.getMethodExpression().getQualifierExpression() == currentExpression) {
final PsiMethod parentCallMethod = parentMethodCall.resolveMethod();
if (parentCallMethod != null && fluentIterable.isEquivalentTo(parentCallMethod.getContainingClass())) {
if (GuavaFluentIterableMethodConverters.isStopMethod(parentCallMethod.getName())) {
return;
}
currentExpression = parentMethodCall;
continue;
}
}
}
final PsiElement expressionParent = currentExpression.getParent();
if (expressionParent instanceof PsiReturnStatement) {
final PsiType containingMethodReturnType = findContainingMethodReturnType(currentExpression);
if (containingMethodReturnType instanceof PsiClassType) {
final PsiClass resolvedClass = ((PsiClassType)containingMethodReturnType).resolve();
if (resolvedClass == null || !(resolvedClass.getResolveScope() instanceof JdkScope)) {
return;
}
}
} else if (expressionParent instanceof PsiLocalVariable) {
final PsiType type = ((PsiLocalVariable)expressionParent).getType();
if (type instanceof PsiClassType) {
final PsiClass resolvedClass = ((PsiClassType)type).resolve();
if (resolvedClass == null || !(resolvedClass.getResolveScope() instanceof JdkScope)) {
return;
}
}
} else if (expressionParent instanceof PsiExpressionList) {
if (expressionParent.getParent() instanceof PsiMethodCallExpression
&& !isMethodWithParamAcceptsConversion((PsiMethodCallExpression)expressionParent.getParent(),
currentExpression,
fluentIterable)) {
return;
}
}
final List<SmartPsiElementPointer<PsiExpression>> exprAsList =
ContainerUtil.list(mySmartPointerManager.createSmartPsiElementPointer((PsiExpression)currentExpression));
holder.registerProblem(currentExpression, PROBLEM_DESCRIPTION, new ConvertGuavaFluentIterableQuickFix(null, exprAsList));
return;
}
} else {
final PsiExpression qualifierExpression = methodExpression.getQualifierExpression();
if (GuavaFluentIterableMethodConverters.isFluentIterableMethod(methodName) &&
qualifierExpression instanceof PsiReferenceExpression) {
final PsiElement resolvedElement = ((PsiReferenceExpression)qualifierExpression).resolve();
if (resolvedElement instanceof PsiLocalVariable) {
PsiLocalVariable fluentIterableLocalVariable = (PsiLocalVariable)resolvedElement;
if (!fluentIterable.isEquivalentTo(PsiTypesUtil.getPsiClass(fluentIterableLocalVariable.getType())) ||
myUnconvertibleVariables.contains(fluentIterableLocalVariable)) {
return;
}
analyzeExpression(expression, fluentIterableLocalVariable);
}
}
}
}
private void analyzeExpression(PsiExpression expression, PsiLocalVariable fluentIterableLocalVariable) {
PsiExpression baseExpression = expression;
while (true) {
final PsiMethodCallExpression
methodCallExpression = PsiTreeUtil.getParentOfType(baseExpression, PsiMethodCallExpression.class);
if (methodCallExpression != null && methodCallExpression.getMethodExpression().getQualifierExpression() == baseExpression) {
final String currentMethodName = methodCallExpression.getMethodExpression().getReferenceName();
if (GuavaFluentIterableMethodConverters.isFluentIterableMethod(currentMethodName)) {
if (GuavaFluentIterableMethodConverters.isStopMethod((currentMethodName))) {
addToUnconvertible(fluentIterableLocalVariable);
return;
}
else {
final PsiMethod method = methodCallExpression.resolveMethod();
if (method != null && method.getContainingClass() != null && method.getContainingClass().isEquivalentTo(fluentIterable)) {
baseExpression = methodCallExpression;
myMethodCallsToIgnore.add(methodCallExpression);
continue;
}
}
}
}
break;
}
final PsiElement parent = baseExpression.getParent();
if (parent instanceof PsiExpressionList) {
myLocalVariablesUsages.putValue(fluentIterableLocalVariable, baseExpression);
final boolean suitable = parent.getParent() instanceof PsiMethodCallExpression &&
isMethodWithParamAcceptsConversion((PsiMethodCallExpression)parent.getParent(), baseExpression,
fluentIterable);
if (!suitable) {
addToUnconvertible(fluentIterableLocalVariable);
}
} else if (parent instanceof PsiReferenceExpression) {
final PsiMethodCallExpression parentMethodCall = PsiTreeUtil.getParentOfType(baseExpression, PsiMethodCallExpression.class);
if (parentMethodCall != null && parentMethodCall.getMethodExpression().getQualifier() == baseExpression) {
if (GuavaOptionalConverter.isConvertibleIfOption(parentMethodCall)) {
myLocalVariablesUsages.putValue(fluentIterableLocalVariable, baseExpression);
} else {
addToUnconvertible(fluentIterableLocalVariable);
}
}
}
else if (parent instanceof PsiLocalVariable) {
myLocalVariablesUsages.putValue(fluentIterableLocalVariable, baseExpression);
} else if (parent instanceof PsiAssignmentExpression) {
final PsiAssignmentExpression assignment = (PsiAssignmentExpression)parent;
final PsiExpression lExpression = assignment.getLExpression();
if (lExpression instanceof PsiReferenceExpression) {
if (((PsiReferenceExpression)lExpression).isReferenceTo(fluentIterableLocalVariable)) {
if (isSelfAssignment(assignment, fluentIterableLocalVariable)) {
myLocalVariablesUsages.putValue(fluentIterableLocalVariable, baseExpression);
return;
}
if (checkDeclaration(assignment.getRExpression())) {
myLocalVariablesUsages.putValue(fluentIterableLocalVariable, assignment.getRExpression());
return;
}
addToUnconvertible(fluentIterableLocalVariable);
} else {
myLocalVariablesUsages.putValue(fluentIterableLocalVariable, baseExpression);
}
}
else {
myLocalVariablesUsages.putValue(fluentIterableLocalVariable, baseExpression);
}
} else if (parent instanceof PsiReturnStatement) {
final PsiType containingMethodReturnType = findContainingMethodReturnType(baseExpression);
if (baseExpression == expression) {
if (!(containingMethodReturnType instanceof PsiClassType)) {
addToUnconvertible(fluentIterableLocalVariable);
return;
}
final PsiClass resolvedClass = ((PsiClassType)containingMethodReturnType).resolve();
if (resolvedClass == null || (!CommonClassNames.JAVA_LANG_ITERABLE.equals(resolvedClass.getQualifiedName()) &&
!CommonClassNames.JAVA_LANG_OBJECT.equals(resolvedClass.getQualifiedName()))) {
addToUnconvertible(fluentIterableLocalVariable);
} else {
myLocalVariablesUsages.putValue(fluentIterableLocalVariable, baseExpression);
}
}
else {
if (containingMethodReturnType instanceof PsiClassType) {
final PsiClass resolvedClass = ((PsiClassType)containingMethodReturnType).resolve();
if (resolvedClass == null || !(resolvedClass.getResolveScope() instanceof JdkScope)) {
addToUnconvertible(fluentIterableLocalVariable);
}
}
}
} else if (parent instanceof PsiExpressionStatement) {
myLocalVariablesUsages.putValue(fluentIterableLocalVariable, baseExpression);
}
}
};
}
public static boolean isMethodWithParamAcceptsConversion(PsiMethodCallExpression methodCallExpression,
PsiExpression baseExpression,
PsiClass fluentIterable) {
final PsiExpressionList argList = methodCallExpression.getArgumentList();
final PsiMethod method = methodCallExpression.resolveMethod();
if (method == null) {
return true;
}
final PsiParameterList paramList = method.getParameterList();
if (paramList.getParametersCount() != argList.getExpressions().length &&
!(paramList.getParameters()[paramList.getParametersCount() - 1].getType() instanceof PsiEllipsisType)) {
return false;
}
int index = -1;
PsiExpression[] expressions = argList.getExpressions();
for (int i = 0, length = expressions.length; i < length; i++) {
if (expressions[i] == baseExpression) {
index = i;
break;
}
}
LOG.assertTrue(index >= 0);
PsiType parameterType;
if (index > paramList.getParametersCount() - 1) {
parameterType = paramList.getParameters()[paramList.getParametersCount() - 1].getType();
} else {
parameterType = paramList.getParameters()[index].getType();
}
if (parameterType instanceof PsiEllipsisType) {
parameterType = ((PsiEllipsisType)parameterType).getComponentType();
}
if (parameterType instanceof PsiClassType) {
final PsiClass resolvedParameterClass = ((PsiClassType)parameterType).resolve();
final JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(methodCallExpression.getProject());
final GlobalSearchScope scope = GlobalSearchScope.allScope(methodCallExpression.getProject());
final PsiClass optional = javaPsiFacade.findClass(GUAVA_OPTIONAL, scope);
final PsiClass immutableMap = javaPsiFacade.findClass(GUAVA_IMMUTABLE_MAP, scope);
if (resolvedParameterClass != null &&
(InheritanceUtil.isInheritorOrSelf(resolvedParameterClass, fluentIterable, true) ||
InheritanceUtil.isInheritorOrSelf(resolvedParameterClass, optional, true) ||
InheritanceUtil.isInheritorOrSelf(resolvedParameterClass, immutableMap, true))){
return false;
}
}
return true;
}
@Nullable
static PsiType findContainingMethodReturnType(PsiElement methodElement) {
final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(methodElement, PsiMethod.class);
if (containingMethod == null) {
return null;
}
final PsiType containingMethodReturnType = containingMethod.getReturnType();
if (containingMethodReturnType == null) {
return null;
}
return containingMethodReturnType;
}
private static boolean isSelfAssignment(PsiAssignmentExpression expression, PsiLocalVariable localVariable) {
final PsiExpression rExpression = expression.getRExpression();
if (!(rExpression instanceof PsiMethodCallExpression)) {
return false;
}
PsiMethodCallExpression methodCall = (PsiMethodCallExpression)rExpression;
while (true) {
final PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression();
if (qualifier instanceof PsiMethodCallExpression) {
methodCall = (PsiMethodCallExpression)qualifier;
} else {
break;
}
}
final PsiExpression qualifierExpression = methodCall.getMethodExpression().getQualifierExpression();
if (qualifierExpression instanceof PsiReferenceExpression &&
!((PsiReferenceExpression)qualifierExpression).isReferenceTo(localVariable)) {
return false;
}
return true;
}
public static class ConvertGuavaFluentIterableQuickFix implements LocalQuickFix {
@Nullable private final SmartPsiElementPointer<PsiLocalVariable> myVariable;
@NotNull private final List<SmartPsiElementPointer<PsiExpression>> myFoundUsages;
protected ConvertGuavaFluentIterableQuickFix(@Nullable SmartPsiElementPointer<PsiLocalVariable> variable,
@NotNull List<SmartPsiElementPointer<PsiExpression>> foundUsages) {
myVariable = variable;
myFoundUsages = foundUsages;
}
@Nls
@NotNull
@Override
public String getName() {
return getFamilyName();
}
@NotNull
@Override
public String getFamilyName() {
return "Convert Guava's FluentIterable to java.util.stream.Stream";
}
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(project);
final PsiElementFactory elementFactory = javaPsiFacade.getElementFactory();
final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project);
for (SmartPsiElementPointer<PsiExpression> usage : myFoundUsages) {
final PsiExpression element = usage.getElement();
if (element != null) {
GuavaFluentIterableMethodConverters.convert(element, elementFactory, codeStyleManager);
}
}
if (myVariable != null) {
final PsiLocalVariable element = myVariable.getElement();
if (element != null) {
GuavaFluentIterableMethodConverters.convert(element, elementFactory, codeStyleManager);
}
}
}
}
}
@@ -0,0 +1,254 @@
/*
* Copyright 2000-2015 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.intellij.codeInspection.java18StreamApi;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author Dmitry Batkovich
*/
public class GuavaFluentIterableMethodConverters {
private static final Logger LOG = Logger.getInstance(GuavaFluentIterableMethodConverters.class);
private static final Map<String, FluentIterableMethodTransformer> METHOD_INDEX = new HashMap<String, FluentIterableMethodTransformer>();
private static final Map<String, String> TO_OTHER_COLLECTION_METHODS = new HashMap<String, String>();
private static final Set<String> STOP_METHODS = new HashSet<String>();
static {
METHOD_INDEX.put("allMatch", new FluentIterableMethodTransformer.OneParameterMethodTransformer(StreamApiConstants.ALL_MATCH + "(%s)", true));
METHOD_INDEX.put("anyMatch", new FluentIterableMethodTransformer.OneParameterMethodTransformer(StreamApiConstants.ANY_MATCH + "(%s)", true));
METHOD_INDEX.put("contains", new FluentIterableMethodTransformer.OneParameterMethodTransformer("anyMatch(e -> e != null && e.equals(%s))"));
METHOD_INDEX.put("copyInto", new FluentIterableMethodTransformer.OneParameterMethodTransformer("forEach(%s::add)"));
METHOD_INDEX.put("filter", new FluentIterableMethodTransformer.OneParameterMethodTransformer(StreamApiConstants.FILTER + "(%s)", true));
METHOD_INDEX.put("first", new FluentIterableMethodTransformer.ParameterlessMethodTransformer(StreamApiConstants.FIND_FIRST));
METHOD_INDEX.put("firstMatch", new FluentIterableMethodTransformer.OneParameterMethodTransformer("filter(%s).findFirst()", true));
METHOD_INDEX.put("get", new FluentIterableMethodTransformer.OneParameterMethodTransformer("collect(java.util.stream.Collectors.toList()).get(%s)"));
METHOD_INDEX
.put("index", new FluentIterableMethodTransformer.OneParameterMethodTransformer("collect(java.util.stream.Collectors.toList()).indexOf(%s)"));
METHOD_INDEX.put("isEmpty", new FluentIterableMethodTransformer.ParameterlessMethodTransformer("findAny().isPresent()", true));
METHOD_INDEX.put("last", new FluentIterableMethodTransformer.ParameterlessMethodTransformer("reduce((previous, current) -> current)"));
METHOD_INDEX.put("limit", new FluentIterableMethodTransformer.OneParameterMethodTransformer(StreamApiConstants.LIMIT + "(%s)"));
METHOD_INDEX.put("size", new FluentIterableMethodTransformer.ParameterlessMethodTransformer("collect(java.util.stream.Collectors.toList()).size()"));
METHOD_INDEX.put("skip", new FluentIterableMethodTransformer.OneParameterMethodTransformer(StreamApiConstants.SKIP + "(%s)"));
METHOD_INDEX.put("toArray", new FluentIterableMethodTransformer.ToArrayMethodTransformer());
METHOD_INDEX.put("transform", new FluentIterableMethodTransformer.OneParameterMethodTransformer(StreamApiConstants.MAP + "(%s)", true));
METHOD_INDEX.put("transformAndConcat", new FluentIterableMethodTransformer.OneParameterMethodTransformer(StreamApiConstants.FLAT_MAP + "(%s)", true));
METHOD_INDEX.put("uniqueIndex", new FluentIterableMethodTransformer.OneParameterMethodTransformer(
"collect(java.util.stream.Collectors.toMap(%s, java.util.function.Function.identity()))"));
TO_OTHER_COLLECTION_METHODS.put("toMap", "collect(java.util.stream.Collectors.toMap(java.util.function.Function.identity(), %s))");
TO_OTHER_COLLECTION_METHODS.put("toList", "collect(java.util.stream.Collectors.toList())");
TO_OTHER_COLLECTION_METHODS.put("toSet", "collect(java.util.stream.Collectors.toSet())");
TO_OTHER_COLLECTION_METHODS.put("toSortedList", "sorted(%s).collect(java.util.stream.Collectors.toList())");
TO_OTHER_COLLECTION_METHODS.put("toSortedSet", "sorted(%s).collect(java.util.stream.Collectors.toSet())");
STOP_METHODS.add("append");
STOP_METHODS.add("cycle");
}
public static boolean isFluentIterableMethod(final String methodName) {
return STOP_METHODS.contains(methodName) ||
TO_OTHER_COLLECTION_METHODS.containsKey(methodName) ||
METHOD_INDEX.containsKey(methodName);
}
public static boolean isStopMethod(final String methodName) {
return STOP_METHODS.contains(methodName);
}
public static void convert(final PsiLocalVariable localVariable,
final PsiElementFactory elementFactory,
final JavaCodeStyleManager codeStyleManager) {
final PsiTypeElement typeElement = localVariable.getTypeElement();
final PsiReferenceParameterList generics = PsiTreeUtil.findChildOfType(typeElement, PsiReferenceParameterList.class);
typeElement.replace(elementFactory.createTypeElementFromText(
StreamApiConstants.JAVA_UTIL_STREAM_STREAM + (generics == null ? "" : generics.getText()), null));
final PsiExpression initializer = localVariable.getInitializer();
if (initializer != null) {
PsiMethodCallExpression initializerMethodCall = (PsiMethodCallExpression)initializer;
convertMethodCallDeep(elementFactory, initializerMethodCall);
}
codeStyleManager.shortenClassReferences(localVariable);
}
public static void convert(PsiExpression expression,
final PsiElementFactory elementFactory,
final JavaCodeStyleManager codeStyleManager) {
if (expression instanceof PsiReferenceExpression) {
final PsiElement expressionParent = expression.getParent();
if (expressionParent instanceof PsiReturnStatement || isIterableMethodParameter(expressionParent, expression)) {
expression = (PsiExpression)expression.replace(
elementFactory.createExpressionFromText(expression.getText() + ".collect(java.util.stream.Collectors.toList())", null));
codeStyleManager.shortenClassReferences(expression);
}
return;
}
final PsiMethodCallExpression parentMethodCall = PsiTreeUtil.getParentOfType(expression, PsiMethodCallExpression.class);
if (parentMethodCall != null && parentMethodCall.getMethodExpression().getQualifierExpression() == expression) {
final PsiMethod seqTailMethod = parentMethodCall.resolveMethod();
if (seqTailMethod == null) {
return;
}
final PsiClass seqTailMethodClass = seqTailMethod.getContainingClass();
if (seqTailMethodClass != null && GuavaFluentIterableInspection.GUAVA_OPTIONAL.equals(seqTailMethodClass.getQualifiedName())) {
final PsiMethodCallExpression newParentMethodCall =
GuavaOptionalConverter.convertGuavaOptionalToJava(parentMethodCall, elementFactory);
expression = newParentMethodCall.getMethodExpression().getQualifierExpression();
}
}
if (expression instanceof PsiMethodCallExpression) {
expression = convertMethodCallDeep(elementFactory, (PsiMethodCallExpression)expression);
}
if (expression == null) {
return;
}
final PsiElement parent = expression.getParent();
if (parent instanceof PsiExpressionList) {
final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)parent.getParent();
PsiExpression[] expressions = methodCall.getArgumentList().getExpressions();
int index = ArrayUtil.indexOf(expressions, expression);
LOG.assertTrue(index >= 0);
final PsiMethod method = methodCall.resolveMethod();
LOG.assertTrue(method != null);
final PsiType parameterType = method.getParameterList().getParameters()[index].getType();
expression = addCollectToListIfNeed(expression, parameterType, elementFactory);
} else if (parent instanceof PsiReturnStatement) {
final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(parent, PsiMethod.class);
LOG.assertTrue(containingMethod != null);
final PsiType returnType = containingMethod.getReturnType();
expression = addCollectToListIfNeed(expression, returnType, elementFactory);
}
codeStyleManager.shortenClassReferences(expression);
}
private static PsiExpression addCollectToListIfNeed(PsiExpression expression, PsiType type, PsiElementFactory elementFactory) {
if (type instanceof PsiClassType) {
PsiClass resolvedParamClass = ((PsiClassType)type).resolve();
if (resolvedParamClass != null && CommonClassNames.JAVA_LANG_ITERABLE.equals(resolvedParamClass.getQualifiedName())) {
final PsiExpression newExpression =
elementFactory.createExpressionFromText(expression.getText() + ".collect(java.util.stream.Collectors.toList())", null);
return (PsiExpression) expression.replace(newExpression);
}
}
return expression;
}
private static boolean isIterableMethodParameter(PsiElement listExpression, PsiExpression parameterExpression) {
if (!(listExpression instanceof PsiExpressionList)) {
return false;
}
if (!(listExpression.getParent() instanceof PsiMethodCallExpression)) {
return false;
}
final Project project = parameterExpression.getProject();
final PsiClass fluentIterable = JavaPsiFacade.getInstance(project)
.findClass(GuavaFluentIterableInspection.GUAVA_FLUENT_ITERABLE, GlobalSearchScope.allScope(project));
return GuavaFluentIterableInspection.isMethodWithParamAcceptsConversion((PsiMethodCallExpression)listExpression.getParent(), parameterExpression, fluentIterable);
}
@Nullable
private static PsiMethodCallExpression convertMethodCallDeep(PsiElementFactory elementFactory,
@NotNull PsiMethodCallExpression methodCall) {
PsiMethodCallExpression newMethodCall = methodCall;
PsiMethodCallExpression returnCall = null;
while (true) {
final Pair<PsiMethodCallExpression, Boolean> converted = convertMethodCall(elementFactory, newMethodCall);
if (converted.getSecond()) {
return returnCall;
}
if (returnCall == null) {
returnCall = converted.getFirst();
}
if (converted.getFirst() == null) {
return returnCall;
}
newMethodCall = converted.getFirst();
final PsiExpression expression = newMethodCall.getMethodExpression().getQualifierExpression();
if (expression instanceof PsiMethodCallExpression) {
newMethodCall = (PsiMethodCallExpression)expression;
}
else {
return returnCall;
}
}
}
public static Pair<PsiMethodCallExpression, Boolean> convertMethodCall(PsiElementFactory elementFactory, PsiMethodCallExpression methodCall) {
final PsiReferenceExpression methodExpression = methodCall.getMethodExpression();
final String name = methodExpression.getReferenceName();
if (TO_OTHER_COLLECTION_METHODS.containsKey(name)) {
return Pair.create(convertToCollection(methodCall, name, elementFactory), false);
}
else if (GuavaFluentIterableInspection.FLUENT_ITERABLE_FROM.equals(name)) {
final PsiExpression[] argumentList = methodCall.getArgumentList().getExpressions();
LOG.assertTrue(argumentList.length == 1);
final PsiExpression expression = argumentList[0];
final PsiType type = expression.getType();
LOG.assertTrue(type instanceof PsiClassType);
final PsiClass resolvedClass = ((PsiClassType)type).resolve();
final String newExpressionText;
if (InheritanceUtil.isInheritor(resolvedClass, CommonClassNames.JAVA_UTIL_COLLECTION)) {
newExpressionText = expression.getText() + ".stream()";
} else {
newExpressionText = "java.util.stream.StreamSupport.stream(" + expression.getText() + ".spliterator(), false)";
}
return Pair.create((PsiMethodCallExpression)methodCall.replace(elementFactory.createExpressionFromText(newExpressionText, null)), true);
}
else {
final FluentIterableMethodTransformer transformer = METHOD_INDEX.get(name);
LOG.assertTrue(transformer != null, name);
final PsiMethodCallExpression transformedExpression = transformer.transform(methodCall, elementFactory);
return Pair.create(transformedExpression, false);
}
}
private static PsiMethodCallExpression convertToCollection(final PsiMethodCallExpression methodCall,
final String methodName,
final PsiElementFactory elementFactory) {
final PsiExpression[] expressions = methodCall.getArgumentList().getExpressions();
assert expressions.length < 2;
String template = TO_OTHER_COLLECTION_METHODS.get(methodName);
if (expressions.length == 1) {
template = String.format(template, expressions[0].getText());
}
final PsiExpression qualifier = methodCall.getMethodExpression().getQualifierExpression();
if (qualifier == null) {
return null;
}
final String text = qualifier.getText() + "." + template;
final PsiExpression expression = elementFactory.createExpressionFromText(text, null);
return (PsiMethodCallExpression)methodCall.replace(expression);
}
}
@@ -0,0 +1,70 @@
/*
* Copyright 2000-2015 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.intellij.codeInspection.java18StreamApi;
import com.intellij.codeInspection.AnonymousCanBeLambdaInspection;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Dmitry Batkovich
*/
public class GuavaFunctionAndPredicateConverter {
private final static Logger LOG = Logger.getInstance(GuavaFunctionAndPredicateConverter.class);
@Nullable
public static Boolean isClassConditionPredicate(final PsiExpression expression) {
final PsiType type = expression.getType();
if (type instanceof PsiClassType) {
final PsiClass resolvedClass = ((PsiClassType)type).resolve();
if (InheritanceUtil.isInheritor(resolvedClass, "com.google.common.base.Function") ||
InheritanceUtil.isInheritor(resolvedClass, "com.google.common.base.Predicate")) {
return Boolean.FALSE;
}
else if (InheritanceUtil.isInheritor(resolvedClass, CommonClassNames.JAVA_LANG_CLASS)) {
return Boolean.TRUE;
}
}
return null;
}
@NotNull
public static String convertFunctionOrPredicateParameter(final @NotNull PsiExpression expression,
final boolean role) {
if (role) {
final String pattern = expression instanceof PsiMethodCallExpression || expression instanceof PsiReferenceExpression
? "%s::isInstance"
: "(%s)::isInstance";
return String.format(pattern, expression.getText());
}
if (expression instanceof PsiNewExpression) {
final PsiAnonymousClass anonymousClass = ((PsiNewExpression)expression).getAnonymousClass();
if (anonymousClass != null && AnonymousCanBeLambdaInspection.canBeConvertedToLambda(anonymousClass, true)) {
final PsiLambdaExpression lambdaExpression = AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, true);
LOG.assertTrue(lambdaExpression != null);
return lambdaExpression.getText();
}
}
String qualifierText = expression.getText();
if (!(expression instanceof PsiMethodCallExpression) && !(expression instanceof PsiReferenceExpression)) {
qualifierText = "(" + qualifierText + ")";
}
return qualifierText + "::apply";
}
}
@@ -0,0 +1,84 @@
/*
* Copyright 2000-2015 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.intellij.codeInspection.java18StreamApi;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*;
import com.intellij.util.containers.hash.HashMap;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
/**
* @author Dmitry Batkovich
*/
public class GuavaOptionalConverter {
private static final Logger LOG = Logger.getInstance(GuavaOptionalConverter.class);
private static final Map<String, FluentIterableMethodTransformer> METHODS_CONVERSION = new HashMap<String, FluentIterableMethodTransformer>();
private static final String OR_METHOD = "or";
static {
METHODS_CONVERSION.put("isPresent", new FluentIterableMethodTransformer.ParameterlessMethodTransformer("isPresent"));
METHODS_CONVERSION.put("get", new FluentIterableMethodTransformer.ParameterlessMethodTransformer("get"));
METHODS_CONVERSION.put(OR_METHOD, new FluentIterableMethodTransformer.OneParameterMethodTransformer("orElse(%s)"));
METHODS_CONVERSION.put("orNull", new FluentIterableMethodTransformer.ParameterlessMethodTransformer("orElse(null)"));
}
public static boolean isConvertibleIfOption(PsiMethodCallExpression methodCallExpression) {
final PsiMethod method = methodCallExpression.resolveMethod();
if (method == null) {
return false;
}
final PsiClass aClass = method.getContainingClass();
if (aClass == null) {
return false;
}
if (!GuavaFluentIterableInspection.GUAVA_OPTIONAL.equals(aClass.getQualifiedName())) {
return true;
}
return METHODS_CONVERSION.containsKey(method.getName());
}
@NotNull
public static PsiMethodCallExpression convertGuavaOptionalToJava(final PsiMethodCallExpression methodCall,
final PsiElementFactory elementFactory) {
final String methodName = methodCall.getMethodExpression().getReferenceName();
if (methodName == OR_METHOD) {
final PsiExpression[] arguments = methodCall.getArgumentList().getExpressions();
if (arguments.length != 1) {
return methodCall;
}
final PsiExpression argument = arguments[0];
final PsiType type = argument.getType();
if (type instanceof PsiClassType) {
final PsiClass resolvedClass = ((PsiClassType)type).resolve();
if (resolvedClass != null) {
final String qName = resolvedClass.getQualifiedName();
if (GuavaFluentIterableInspection.GUAVA_OPTIONAL.equals(qName) || "com.google.common.base.Supplier".equals(qName)) {
return methodCall;
}
}
}
}
final FluentIterableMethodTransformer conversion = METHODS_CONVERSION.get(methodName);
if (conversion == null) {
return methodCall;
}
final PsiMethodCallExpression transformed = conversion.transform(methodCall, elementFactory);
LOG.assertTrue(transformed != null);
return transformed;
}
}
@@ -0,0 +1,5 @@
<html>
<body>
Inspection detects most of cases when Guava's Fluent Iterable can be replaced by Java Stream API.
</body>
</html>
+5 -1
View File
@@ -740,7 +740,11 @@
groupName="Performance issues" enabledByDefault="false" level="WARNING"
implementationClass="com.intellij.codeInspection.CollectionAddAllCanBeReplacedWithConstructorInspection"
displayName="Collection.addAll() can be replaced with parametrized constructor"/>
<localInspection groupPath="Java" language="JAVA" shortName="GuavaFluentIterable"
groupBundle="messages.InspectionsBundle"
groupName="Java language level migration aids" enabledByDefault="false" level="WARNING"
implementationClass="com.intellij.codeInspection.java18StreamApi.GuavaFluentIterableInspection"
displayName="Guava's FluentIterable can be replaced by java.util.stream.Stream"/>
<intentionAction>
<className>com.intellij.codeInsight.daemon.quickFix.RedundantLambdaParameterTypeIntention</className>
<category>Java/Declaration</category>