guava type migration: convert anonymous Function-s with runtime retention policy annotations to anonymous classes except @javax.annotations.Nullable (can be configured in settings)

This commit is contained in:
Dmitry Batkovich
2016-03-22 14:46:28 +03:00
parent b19935c978
commit 0790362737
21 changed files with 323 additions and 58 deletions
@@ -100,7 +100,7 @@ public class AnonymousCanBeLambdaInspection extends BaseJavaBatchLocalInspection
final PsiElement lambdaContext = parent != null ? parent.getParent() : null; final PsiElement lambdaContext = parent != null ? parent.getParent() : null;
if (lambdaContext != null && if (lambdaContext != null &&
(LambdaUtil.isValidLambdaContext(lambdaContext) || !(lambdaContext instanceof PsiExpressionStatement)) && (LambdaUtil.isValidLambdaContext(lambdaContext) || !(lambdaContext instanceof PsiExpressionStatement)) &&
canBeConvertedToLambda(aClass, false, reportNotAnnotatedInterfaces)) { canBeConvertedToLambda(aClass, false, reportNotAnnotatedInterfaces, Collections.emptySet())) {
final PsiElement lBrace = aClass.getLBrace(); final PsiElement lBrace = aClass.getLBrace();
LOG.assertTrue(lBrace != null); LOG.assertTrue(lBrace != null);
final TextRange rangeInElement = new TextRange(0, aClass.getStartOffsetInParent() + lBrace.getStartOffsetInParent()); final TextRange rangeInElement = new TextRange(0, aClass.getStartOffsetInParent() + lBrace.getStartOffsetInParent());
@@ -111,12 +111,15 @@ public class AnonymousCanBeLambdaInspection extends BaseJavaBatchLocalInspection
}; };
} }
private static boolean hasRuntimeAnnotations(PsiMethod method) { private static boolean hasRuntimeAnnotations(PsiMethod method, @NotNull Set<String> runtimeAnnotationsToIgnore) {
PsiAnnotation[] annotations = method.getModifierList().getAnnotations(); PsiAnnotation[] annotations = method.getModifierList().getAnnotations();
for (PsiAnnotation annotation : annotations) { for (PsiAnnotation annotation : annotations) {
PsiJavaCodeReferenceElement ref = annotation.getNameReferenceElement(); PsiJavaCodeReferenceElement ref = annotation.getNameReferenceElement();
PsiElement target = ref != null ? ref.resolve() : null; PsiElement target = ref != null ? ref.resolve() : null;
if (target instanceof PsiClass) { if (target instanceof PsiClass) {
if (runtimeAnnotationsToIgnore.contains(((PsiClass)target).getQualifiedName())) {
continue;
}
final PsiAnnotation retentionAnno = AnnotationUtil.findAnnotation((PsiClass)target, Retention.class.getName()); final PsiAnnotation retentionAnno = AnnotationUtil.findAnnotation((PsiClass)target, Retention.class.getName());
if (retentionAnno != null) { if (retentionAnno != null) {
PsiAnnotationMemberValue value = retentionAnno.findAttributeValue("value"); PsiAnnotationMemberValue value = retentionAnno.findAttributeValue("value");
@@ -178,13 +181,16 @@ public class AnonymousCanBeLambdaInspection extends BaseJavaBatchLocalInspection
return null; return null;
} }
public static boolean canBeConvertedToLambda(PsiAnonymousClass aClass, boolean acceptParameterizedFunctionTypes) { public static boolean canBeConvertedToLambda(PsiAnonymousClass aClass,
return canBeConvertedToLambda(aClass, acceptParameterizedFunctionTypes, true); boolean acceptParameterizedFunctionTypes,
@NotNull Set<String> ignoredRuntimeAnnotations) {
return canBeConvertedToLambda(aClass, acceptParameterizedFunctionTypes, true, ignoredRuntimeAnnotations);
} }
public static boolean canBeConvertedToLambda(PsiAnonymousClass aClass, public static boolean canBeConvertedToLambda(PsiAnonymousClass aClass,
boolean acceptParameterizedFunctionTypes, boolean acceptParameterizedFunctionTypes,
boolean reportNotAnnotatedInterfaces) { boolean reportNotAnnotatedInterfaces,
@NotNull Set<String> ignoredRuntimeAnnotations) {
if (PsiUtil.getLanguageLevel(aClass).isAtLeast(LanguageLevel.JDK_1_8)) { if (PsiUtil.getLanguageLevel(aClass).isAtLeast(LanguageLevel.JDK_1_8)) {
final PsiClassType baseClassType = aClass.getBaseClassType(); final PsiClassType baseClassType = aClass.getBaseClassType();
final PsiClassType.ClassResolveResult resolveResult = baseClassType.resolveGenerics(); final PsiClassType.ClassResolveResult resolveResult = baseClassType.resolveGenerics();
@@ -203,7 +209,7 @@ public class AnonymousCanBeLambdaInspection extends BaseJavaBatchLocalInspection
final PsiMethod method = methods[0]; final PsiMethod method = methods[0];
return method.getBody() != null && return method.getBody() != null &&
!hasForbiddenRefsInsideBody(method, aClass) && !hasForbiddenRefsInsideBody(method, aClass) &&
!hasRuntimeAnnotations(method) && !hasRuntimeAnnotations(method, ignoredRuntimeAnnotations) &&
!method.hasModifierProperty(PsiModifier.SYNCHRONIZED); !method.hasModifierProperty(PsiModifier.SYNCHRONIZED);
} }
} }
@@ -29,6 +29,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import javax.swing.*; import javax.swing.*;
import java.util.Collections;
/** /**
* User: anna * User: anna
@@ -76,7 +77,7 @@ public class AnonymousCanBeMethodReferenceInspection extends BaseJavaBatchLocalI
@Override @Override
public void visitAnonymousClass(PsiAnonymousClass aClass) { public void visitAnonymousClass(PsiAnonymousClass aClass) {
super.visitAnonymousClass(aClass); super.visitAnonymousClass(aClass);
if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(aClass, true, reportNotAnnotatedInterfaces)) { if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(aClass, true, reportNotAnnotatedInterfaces, Collections.emptySet())) {
final PsiMethod method = aClass.getMethods()[0]; final PsiMethod method = aClass.getMethods()[0];
final PsiCodeBlock body = method.getBody(); final PsiCodeBlock body = method.getBody();
final PsiCallExpression callExpression = final PsiCallExpression callExpression =
@@ -44,6 +44,7 @@ import org.jetbrains.annotations.NotNull;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.util.Collections;
import java.util.List; import java.util.List;
public class IntroduceParameterDialog extends RefactoringDialog { public class IntroduceParameterDialog extends RefactoringDialog {
@@ -234,7 +235,7 @@ public class IntroduceParameterDialog extends RefactoringDialog {
myCbCollapseToLambda = new NonFocusableCheckBox(RefactoringBundle.message("introduce.parameter.convert.lambda")); myCbCollapseToLambda = new NonFocusableCheckBox(RefactoringBundle.message("introduce.parameter.convert.lambda"));
final PsiAnonymousClass anonymClass = myExpression instanceof PsiNewExpression ? ((PsiNewExpression)myExpression).getAnonymousClass() final PsiAnonymousClass anonymClass = myExpression instanceof PsiNewExpression ? ((PsiNewExpression)myExpression).getAnonymousClass()
: null; : null;
myCbCollapseToLambda.setVisible(anonymClass != null && AnonymousCanBeLambdaInspection.canBeConvertedToLambda(anonymClass, false)); myCbCollapseToLambda.setVisible(anonymClass != null && AnonymousCanBeLambdaInspection.canBeConvertedToLambda(anonymClass, false, Collections.emptySet()));
myCbCollapseToLambda.setSelected(PropertiesComponent.getInstance(myProject).getBoolean(INTRODUCE_PARAMETER_LAMBDA)); myCbCollapseToLambda.setSelected(PropertiesComponent.getInstance(myProject).getBoolean(INTRODUCE_PARAMETER_LAMBDA));
gbConstraints.gridy++; gbConstraints.gridy++;
panel.add(myCbCollapseToLambda, gbConstraints); panel.add(myCbCollapseToLambda, gbConstraints);
@@ -447,6 +447,11 @@ public class TypeEvaluator {
return migrationTtype; return migrationTtype;
} }
@Nullable
public <T> T getSettings(Class<T> aClass) {
return myRules.getConversionSettings(aClass);
}
private class SubstitutorBuilder { private class SubstitutorBuilder {
private final Map<PsiTypeParameter, PsiType> myMapping; private final Map<PsiTypeParameter, PsiType> myMapping;
private final PsiMethod myMethod; private final PsiMethod myMethod;
@@ -28,7 +28,6 @@ import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*; import com.intellij.psi.*;
import com.intellij.psi.impl.PsiImplUtil; import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.impl.source.PsiImmediateClassType; import com.intellij.psi.impl.source.PsiImmediateClassType;
import com.intellij.psi.javadoc.PsiDocTagValue; import com.intellij.psi.javadoc.PsiDocTagValue;
import com.intellij.psi.search.PsiSearchScopeUtil; import com.intellij.psi.search.PsiSearchScopeUtil;
@@ -46,7 +45,7 @@ import com.intellij.refactoring.typeMigration.usageInfo.TypeMigrationUsageInfo;
import com.intellij.usageView.UsageInfo; import com.intellij.usageView.UsageInfo;
import com.intellij.util.*; import com.intellij.util.*;
import com.intellij.util.concurrency.Semaphore; import com.intellij.util.concurrency.Semaphore;
import com.intellij.util.containers.*; import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.graph.DFSTBuilder; import com.intellij.util.graph.DFSTBuilder;
import com.intellij.util.graph.GraphGenerator; import com.intellij.util.graph.GraphGenerator;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -55,8 +54,6 @@ import org.jetbrains.annotations.TestOnly;
import javax.swing.*; import javax.swing.*;
import java.util.*; import java.util.*;
import java.util.HashMap;
import java.util.HashSet;
/** /**
* @author db * @author db
@@ -296,6 +293,11 @@ public class TypeMigrationLabeler {
return new MigrationProducer(conversions); return new MigrationProducer(conversions);
} }
@Nullable
public <T> T getSettings(Class<T> aClass) {
return myRules.getConversionSettings(aClass);
}
class MigrationProducer { class MigrationProducer {
private final Map<UsageInfo, Object> myRemainConversions; private final Map<UsageInfo, Object> myRemainConversions;
@@ -353,10 +355,6 @@ public class TypeMigrationLabeler {
Object getConversion(UsageInfo info) { Object getConversion(UsageInfo info) {
return myRemainConversions.remove(info); return myRemainConversions.remove(info);
} }
boolean allOfConversionsUsed() {
return myRemainConversions.isEmpty();
}
} }
void postProcessNewExpression(@NotNull PsiNewExpression expression) { void postProcessNewExpression(@NotNull PsiNewExpression expression) {
@@ -35,10 +35,8 @@ import com.intellij.ui.content.Content;
import com.intellij.usageView.UsageInfo; import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor; import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.usageView.UsageViewManager; import com.intellij.usageView.UsageViewManager;
import com.intellij.usages.Usage;
import com.intellij.util.*; import com.intellij.util.*;
import com.intellij.util.containers.*; import com.intellij.util.containers.*;
import com.intellij.util.containers.HashMap;
import gnu.trove.THashSet; import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -242,13 +240,14 @@ public class TypeMigrationProcessor extends BaseRefactoringProcessor {
((PsiVariable)element).normalizeDeclaration(); ((PsiVariable)element).normalizeDeclaration();
} }
} }
change(myLabeler, usages); change(usages, myLabeler, myProject);
} }
public static void change(TypeMigrationLabeler labeler, UsageInfo[] usages) { public static void change(UsageInfo[] usages, TypeMigrationLabeler labeler, Project project) {
final List<PsiNewExpression> newExpressionsToCheckDiamonds = new SmartList<PsiNewExpression>(); final List<SmartPsiElementPointer<PsiNewExpression>> newExpressionsToCheckDiamonds = new SmartList<>();
final TypeMigrationLabeler.MigrationProducer producer = labeler.createMigratorFor(usages); final TypeMigrationLabeler.MigrationProducer producer = labeler.createMigratorFor(usages);
final SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(project);
List<UsageInfo> nonCodeUsages = new ArrayList<UsageInfo>(); List<UsageInfo> nonCodeUsages = new ArrayList<UsageInfo>();
for (UsageInfo usage : usages) { for (UsageInfo usage : usages) {
if (((TypeMigrationUsageInfo)usage).isExcluded()) continue; if (((TypeMigrationUsageInfo)usage).isExcluded()) continue;
@@ -260,7 +259,7 @@ public class TypeMigrationProcessor extends BaseRefactoringProcessor {
producer.change((TypeMigrationUsageInfo)usage, new Consumer<PsiNewExpression>() { producer.change((TypeMigrationUsageInfo)usage, new Consumer<PsiNewExpression>() {
@Override @Override
public void consume(@NotNull PsiNewExpression expression) { public void consume(@NotNull PsiNewExpression expression) {
newExpressionsToCheckDiamonds.add(expression); newExpressionsToCheckDiamonds.add(smartPointerManager.createSmartPsiElementPointer(expression));
} }
}); });
} }
@@ -269,8 +268,11 @@ public class TypeMigrationProcessor extends BaseRefactoringProcessor {
} }
} }
for (PsiNewExpression newExpression : newExpressionsToCheckDiamonds) { for (SmartPsiElementPointer<PsiNewExpression> newExpressionPointer : newExpressionsToCheckDiamonds) {
labeler.postProcessNewExpression(newExpression); final PsiNewExpression newExpression = newExpressionPointer.getElement();
if (newExpression != null) {
labeler.postProcessNewExpression(newExpression);
}
} }
for (UsageInfo usageInfo : nonCodeUsages) { for (UsageInfo usageInfo : nonCodeUsages) {
@@ -27,28 +27,42 @@ import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.LinkedList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** /**
* @author db * @author db
* Date: Oct 2, 2004 * Date: Oct 2, 2004
*/ */
public class TypeMigrationRules { public class TypeMigrationRules {
private final LinkedList<TypeConversionRule> myConversionRules = new LinkedList<TypeConversionRule>(); private final List<TypeConversionRule> myConversionRules;
private final Map<Class, Object> myConversionCustomSettings = new HashMap<>();
private SearchScope mySearchScope; private SearchScope mySearchScope;
private final MigrateGetterNameSetting myMigrateGetterNameSetting = new MigrateGetterNameSetting(); private final MigrateGetterNameSetting myMigrateGetterNameSetting = new MigrateGetterNameSetting();
public TypeMigrationRules() { public TypeMigrationRules() {
final TypeConversionRule[] extensions = Extensions.getExtensions(TypeConversionRule.EP_NAME);
myConversionRules = new ArrayList<>(extensions.length + 2);
myConversionRules.add(new RootTypeConversionRule()); myConversionRules.add(new RootTypeConversionRule());
myConversionRules.add(new DisjunctionTypeConversionRule()); myConversionRules.add(new DisjunctionTypeConversionRule());
ContainerUtil.addAll(myConversionRules, Extensions.getExtensions(TypeConversionRule.EP_NAME)); ContainerUtil.addAll(myConversionRules, extensions);
} }
public void addConversionDescriptor(TypeConversionRule rule) { public void addConversionDescriptor(TypeConversionRule rule) {
myConversionRules.add(rule); myConversionRules.add(rule);
} }
public void addConversionRuleSettings(Object settings) {
myConversionCustomSettings.put(settings.getClass(), settings);
}
public <T> T getConversionSettings(Class<T> aClass) {
return (T)myConversionCustomSettings.get(aClass);
}
@NonNls @NonNls
@Nullable @Nullable
public TypeConversionDescriptorBase findConversion(final PsiType from, final PsiType to, final PsiMember member, final PsiExpression context, public TypeConversionDescriptorBase findConversion(final PsiType from, final PsiType to, final PsiMember member, final PsiExpression context,
@@ -229,7 +229,7 @@ public class MigrationPanel extends JPanel implements Disposable {
} }
new WriteCommandAction(myProject) { new WriteCommandAction(myProject) {
protected void run(@NotNull Result result) throws Throwable { protected void run(@NotNull Result result) throws Throwable {
TypeMigrationProcessor.change(myLabeler, usages); TypeMigrationProcessor.change(usages, myLabeler, myProject);
} }
}.execute(); }.execute();
} }
@@ -0,0 +1,39 @@
/*
* Copyright 2000-2016 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.refactoring.typeMigration.inspections;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* @author Dmitry Batkovich
*/
public class GuavaConversionSettings {
private final Set<String> myIgnoredAnnotations;
public GuavaConversionSettings(boolean ignoreJavaxNullable) {
Set<String> ignoredAnnotations = new HashSet<>();
if (ignoreJavaxNullable) {
ignoredAnnotations.add("java.annotations.Nullable");
}
myIgnoredAnnotations = Collections.unmodifiableSet(ignoredAnnotations);
}
public Set<String> getIgnoredAnnotations() {
return myIgnoredAnnotations;
}
}
@@ -66,6 +66,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool {
public boolean checkVariables = true; public boolean checkVariables = true;
public boolean checkChains = true; public boolean checkChains = true;
public boolean checkReturnTypes = true; public boolean checkReturnTypes = true;
public boolean ignoreJavaxNullable = true;
@SuppressWarnings("Duplicates") @SuppressWarnings("Duplicates")
@Override @Override
@@ -74,6 +75,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool {
panel.addCheckbox("Report variables", "checkVariables"); panel.addCheckbox("Report variables", "checkVariables");
panel.addCheckbox("Report method chains", "checkChains"); panel.addCheckbox("Report method chains", "checkChains");
panel.addCheckbox("Report return types", "checkReturnTypes"); panel.addCheckbox("Report return types", "checkReturnTypes");
panel.addCheckbox("Erase @javax.annotations.Nullable from converted functions", "ignoreJavaxNullable");
return panel; return panel;
} }
@@ -278,7 +280,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool {
}; };
} }
public static class MigrateGuavaTypeFix extends LocalQuickFixAndIntentionActionOnPsiElement implements BatchQuickFix<ProblemDescriptor> { public class MigrateGuavaTypeFix extends LocalQuickFixAndIntentionActionOnPsiElement implements BatchQuickFix<ProblemDescriptor> {
private final PsiType myTargetType; private final PsiType myTargetType;
private MigrateGuavaTypeFix(@NotNull PsiElement element, PsiType targetType) { private MigrateGuavaTypeFix(@NotNull PsiElement element, PsiType targetType) {
@@ -346,7 +348,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool {
if (!elementsToFix.isEmpty()) performTypeMigration(elementsToFix, migrationTypes); if (!elementsToFix.isEmpty()) performTypeMigration(elementsToFix, migrationTypes);
} }
private static MigrateGuavaTypeFix getFix(ProblemDescriptor descriptor) { private MigrateGuavaTypeFix getFix(ProblemDescriptor descriptor) {
final QuickFix[] fixes = descriptor.getFixes(); final QuickFix[] fixes = descriptor.getFixes();
LOG.assertTrue(fixes != null); LOG.assertTrue(fixes != null);
for (QuickFix fix : fixes) { for (QuickFix fix : fixes) {
@@ -357,7 +359,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool {
throw new AssertionError(); throw new AssertionError();
} }
private static boolean performTypeMigration(List<PsiElement> elements, List<PsiType> types) { private boolean performTypeMigration(List<PsiElement> elements, List<PsiType> types) {
PsiFile containingFile = null; PsiFile containingFile = null;
for (PsiElement element : elements) { for (PsiElement element : elements) {
final PsiFile currentContainingFile = element.getContainingFile(); final PsiFile currentContainingFile = element.getContainingFile();
@@ -374,6 +376,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool {
final TypeMigrationRules rules = new TypeMigrationRules(); final TypeMigrationRules rules = new TypeMigrationRules();
rules.setBoundScope(GlobalSearchScopesCore.projectProductionScope(containingFile.getProject()) rules.setBoundScope(GlobalSearchScopesCore.projectProductionScope(containingFile.getProject())
.union(GlobalSearchScopesCore.projectTestScope(containingFile.getProject()))); .union(GlobalSearchScopesCore.projectTestScope(containingFile.getProject())));
rules.addConversionRuleSettings(new GuavaConversionSettings(ignoreJavaxNullable));
TypeMigrationProcessor.runHighlightingTypeMigration(containingFile.getProject(), TypeMigrationProcessor.runHighlightingTypeMigration(containingFile.getProject(),
null, null,
rules, rules,
@@ -388,7 +391,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool {
return true; return true;
} }
private static Function<PsiElement, PsiType> createMigrationTypeFunction(@NotNull final List<PsiElement> elements, private Function<PsiElement, PsiType> createMigrationTypeFunction(@NotNull final List<PsiElement> elements,
@NotNull final List<PsiType> types) { @NotNull final List<PsiType> types) {
LOG.assertTrue(elements.size() == types.size()); LOG.assertTrue(elements.size() == types.size());
final Map<PsiElement, PsiType> mappings = new HashMap<PsiElement, PsiType>(); final Map<PsiElement, PsiType> mappings = new HashMap<PsiElement, PsiType>();
@@ -20,6 +20,7 @@ import com.intellij.psi.*;
import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase; import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase;
import com.intellij.refactoring.typeMigration.TypeEvaluator; import com.intellij.refactoring.typeMigration.TypeEvaluator;
import com.intellij.refactoring.typeMigration.TypeMigrationLabeler; import com.intellij.refactoring.typeMigration.TypeMigrationLabeler;
import com.intellij.refactoring.typeMigration.inspections.GuavaConversionSettings;
import com.intellij.refactoring.typeMigration.rules.TypeConversionRule; import com.intellij.refactoring.typeMigration.rules.TypeConversionRule;
import com.intellij.reference.SoftLazyValue; import com.intellij.reference.SoftLazyValue;
import com.intellij.util.IncorrectOperationException; import com.intellij.util.IncorrectOperationException;
@@ -98,14 +99,7 @@ public abstract class BaseGuavaTypeConversionRule extends TypeConversionRule {
return findConversionForMethod(from, to, method, methodName, context, labeler); return findConversionForMethod(from, to, method, methodName, context, labeler);
} else if (context instanceof PsiNewExpression) { } else if (context instanceof PsiNewExpression) {
final PsiAnonymousClass anonymousClass = ((PsiNewExpression)context).getAnonymousClass(); final PsiAnonymousClass anonymousClass = ((PsiNewExpression)context).getAnonymousClass();
if (anonymousClass != null && AnonymousCanBeLambdaInspection.canBeConvertedToLambda(anonymousClass, false)) { return anonymousClass == null ? null : findConversionForAnonymous(anonymousClass, labeler.getSettings(GuavaConversionSettings.class));
return new TypeConversionDescriptorBase() {
@Override
public PsiExpression replace(PsiExpression expression, TypeEvaluator evaluator) throws IncorrectOperationException {
return AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, false, true);
};
};
}
} }
else if (context instanceof PsiReferenceExpression) { else if (context instanceof PsiReferenceExpression) {
final PsiElement resolvedElement = ((PsiReferenceExpression)context).resolve(); final PsiElement resolvedElement = ((PsiReferenceExpression)context).resolve();
@@ -116,6 +110,21 @@ public abstract class BaseGuavaTypeConversionRule extends TypeConversionRule {
return null; return null;
} }
@Nullable
protected TypeConversionDescriptorBase findConversionForAnonymous(@NotNull PsiAnonymousClass anonymousClass,
@Nullable GuavaConversionSettings settings) {
final Set<String> ignoredAnnotations = settings != null ? settings.getIgnoredAnnotations() : Collections.emptySet();
if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(anonymousClass, false, ignoredAnnotations)) {
return new TypeConversionDescriptorBase() {
@Override
public PsiExpression replace(PsiExpression expression, @NotNull TypeEvaluator evaluator) throws IncorrectOperationException {
return AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, false, true);
}
};
}
return null;
}
protected boolean isValidMethodQualifierToConvert(PsiClass aClass) { protected boolean isValidMethodQualifierToConvert(PsiClass aClass) {
return aClass != null && (ruleFromClass().equals(aClass.getQualifiedName()) || getAdditionalUtilityClasses().contains(aClass.getQualifiedName())); return aClass != null && (ruleFromClass().equals(aClass.getQualifiedName()) || getAdditionalUtilityClasses().contains(aClass.getQualifiedName()));
} }
@@ -16,7 +16,6 @@
package com.intellij.refactoring.typeMigration.rules.guava; package com.intellij.refactoring.typeMigration.rules.guava;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil; import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.codeInspection.AnonymousCanBeLambdaInspection;
import com.intellij.codeInspection.java18StreamApi.StreamApiConstants; import com.intellij.codeInspection.java18StreamApi.StreamApiConstants;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Pair;
@@ -144,9 +143,7 @@ public class FluentIterableConversionUtil {
PsiAnonymousClass anonymousClass; PsiAnonymousClass anonymousClass;
if (argument instanceof PsiNewExpression && if (argument instanceof PsiNewExpression &&
(anonymousClass = ((PsiNewExpression)argument).getAnonymousClass()) != null) { (anonymousClass = ((PsiNewExpression)argument).getAnonymousClass()) != null) {
if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(anonymousClass, true)) { argument = GuavaConversionUtil.convertAnonymousClass((PsiNewExpression)argument, anonymousClass, typeEvaluator);
argument = AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(argument, true, true);
};
} }
final JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(expression.getProject()); final JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(expression.getProject());
if (argument != null && !(argument instanceof PsiFunctionalExpression)) { if (argument != null && !(argument instanceof PsiFunctionalExpression)) {
@@ -16,18 +16,25 @@
package com.intellij.refactoring.typeMigration.rules.guava; package com.intellij.refactoring.typeMigration.rules.guava;
import com.intellij.codeInspection.AnonymousCanBeLambdaInspection; import com.intellij.codeInspection.AnonymousCanBeLambdaInspection;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.*; import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil; import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.refactoring.typeMigration.TypeEvaluator; import com.intellij.refactoring.typeMigration.TypeEvaluator;
import com.intellij.refactoring.typeMigration.inspections.GuavaConversionSettings;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.Set;
/** /**
* @author Dmitry Batkovich * @author Dmitry Batkovich
*/ */
public class GuavaConversionUtil { public class GuavaConversionUtil {
private final static Logger LOG = Logger.getInstance(GuavaConversionUtil.class);
@Nullable @Nullable
public static PsiType getFunctionReturnType(PsiExpression functionExpression) { public static PsiType getFunctionReturnType(PsiExpression functionExpression) {
if (functionExpression instanceof PsiFunctionalExpression) { if (functionExpression instanceof PsiFunctionalExpression) {
@@ -88,13 +95,14 @@ public class GuavaConversionUtil {
return false; return false;
} }
public static PsiExpression adjustLambdaContainingExpression(PsiExpression expression, boolean insertTypeCase, PsiType targetType, TypeEvaluator evaluator) { public static PsiExpression adjustLambdaContainingExpression(PsiExpression expression,
boolean insertTypeCase,
PsiType targetType,
@NotNull TypeEvaluator evaluator) {
if (expression instanceof PsiNewExpression) { if (expression instanceof PsiNewExpression) {
final PsiAnonymousClass anonymousClass = ((PsiNewExpression)expression).getAnonymousClass(); final PsiAnonymousClass anonymousClass = ((PsiNewExpression)expression).getAnonymousClass();
if (anonymousClass != null) { if (anonymousClass != null) {
if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(anonymousClass, true)) { return convertAnonymousClass((PsiNewExpression)expression, anonymousClass, evaluator);
return AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, true, true);
}
} }
else { else {
final GuavaLambda lambda = GuavaLambda.findFor(evaluator.evaluateType(expression)); final GuavaLambda lambda = GuavaLambda.findFor(evaluator.evaluateType(expression));
@@ -134,6 +142,53 @@ public class GuavaConversionUtil {
return expression; return expression;
} }
public static PsiExpression convertAnonymousClass(@NotNull PsiNewExpression expression,
@NotNull PsiAnonymousClass anonymousClass,
@NotNull TypeEvaluator typeEvaluator) {
final GuavaConversionSettings settings = typeEvaluator.getSettings(GuavaConversionSettings.class);
final Set<String> ignoredAnnotations = settings != null ? settings.getIgnoredAnnotations() : Collections.emptySet();
if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(anonymousClass, true, ignoredAnnotations)) {
return AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, true, true);
} else {
return tryConvertClassAndSamNameToJava(expression);
}
}
public static PsiExpression tryConvertClassAndSamNameToJava(PsiNewExpression expression) {
final GuavaLambda lambda = GuavaLambda.findFor(expression.getType());
if (lambda == null) return expression;
final PsiAnonymousClass aClass = expression.getAnonymousClass();
LOG.assertTrue(aClass != null);
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(expression.getProject());
if (!lambda.getSamName().equals(lambda.getJavaAnalogueSamName())) {
boolean isFound = false;
for (PsiMethod method : aClass.findMethodsByName(lambda.getSamName(), false)) {
if (method.getParameterList().getParametersCount() == lambda.getParametersCount()) {
for (PsiMethod psiMethod : method.findSuperMethods()) {
final PsiClass superMethodContainingClass = psiMethod.getContainingClass();
if (superMethodContainingClass != null && lambda.getClassQName().equals(superMethodContainingClass.getQualifiedName())) {
final PsiIdentifier methodNameIdentifier = method.getNameIdentifier();
LOG.assertTrue(methodNameIdentifier != null);
methodNameIdentifier.replace(factory.createIdentifier(lambda.getJavaAnalogueSamName()));
isFound = true;
break;
}
}
}
if (isFound) break;
}
}
final PsiElement currentClassName = aClass.getBaseClassReference().getReferenceNameElement();
if (currentClassName != null) {
final PsiElement newNameElement = factory.createReferenceFromText(lambda.getJavaAnalogueClassQName(), null);
currentClassName.replace(newNameElement);
}
return (PsiExpression)expression.replace(factory.createExpressionFromText(expression.getText(), null));
}
private static PsiExpression addMethodReference(@NotNull PsiExpression expression, @NotNull GuavaLambda lambda) { private static PsiExpression addMethodReference(@NotNull PsiExpression expression, @NotNull GuavaLambda lambda) {
return (PsiExpression)expression.replace(JavaPsiFacade.getElementFactory(expression.getProject()) return (PsiExpression)expression.replace(JavaPsiFacade.getElementFactory(expression.getProject())
.createExpressionFromText(expression.getText() + "::" + lambda.getSamName(), expression)); .createExpressionFromText(expression.getText() + "::" + lambda.getSamName(), expression));
@@ -25,20 +25,26 @@ import org.jetbrains.annotations.Nullable;
* @author Dmitry Batkovich * @author Dmitry Batkovich
*/ */
public enum GuavaLambda { public enum GuavaLambda {
PREDICATE("com.google.common.base.Predicate", "java.util.function.Predicate", "apply", "test"), PREDICATE("com.google.common.base.Predicate", "java.util.function.Predicate", "apply", "test", 1),
FUNCTION("com.google.common.base.Function", "java.util.function.Function", "apply", "apply"), FUNCTION("com.google.common.base.Function", "java.util.function.Function", "apply", "apply", 1),
SUPPLIER("com.google.common.base.Supplier", "java.util.function.Supplier", "get", "get"); SUPPLIER("com.google.common.base.Supplier", "java.util.function.Supplier", "get", "get", 0);
private final String myClassQName; private final String myClassQName;
private final String myJavaAnalogueClassQName; private final String myJavaAnalogueClassQName;
private final String mySamName; private final String mySamName;
private final String myJavaAnalogueSamName; private final String myJavaAnalogueSamName;
private final int myParametersCount;
GuavaLambda(String classQName, String javaAnalogueClassQName, String samName, String javaAnalogueSamName) { GuavaLambda(String classQName, String javaAnalogueClassQName, String samName, String javaAnalogueSamName, int count) {
myClassQName = classQName; myClassQName = classQName;
myJavaAnalogueClassQName = javaAnalogueClassQName; myJavaAnalogueClassQName = javaAnalogueClassQName;
mySamName = samName; mySamName = samName;
myJavaAnalogueSamName = javaAnalogueSamName; myJavaAnalogueSamName = javaAnalogueSamName;
myParametersCount = count;
}
public int getParametersCount() {
return myParametersCount;
} }
public String getClassQName() { public String getClassQName() {
@@ -15,10 +15,12 @@
*/ */
package com.intellij.refactoring.typeMigration.rules.guava; package com.intellij.refactoring.typeMigration.rules.guava;
import com.intellij.psi.PsiExpression; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.PsiReferenceExpression; import com.intellij.psi.*;
import com.intellij.psi.PsiVariable;
import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase; import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase;
import com.intellij.refactoring.typeMigration.TypeEvaluator;
import com.intellij.refactoring.typeMigration.inspections.GuavaConversionSettings;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -28,6 +30,7 @@ import java.util.Map;
* @author Dmitry Batkovich * @author Dmitry Batkovich
*/ */
public class GuavaLambdaConversionRule extends BaseGuavaTypeConversionRule { public class GuavaLambdaConversionRule extends BaseGuavaTypeConversionRule {
private final static Logger LOG = Logger.getInstance(GuavaLambdaConversionRule.class);
private final GuavaLambda myLambda; private final GuavaLambda myLambda;
protected GuavaLambdaConversionRule(GuavaLambda lambda) { protected GuavaLambdaConversionRule(GuavaLambda lambda) {
@@ -58,6 +61,20 @@ public class GuavaLambdaConversionRule extends BaseGuavaTypeConversionRule {
return myLambda.getJavaAnalogueClassQName(); return myLambda.getJavaAnalogueClassQName();
} }
@Nullable
@Override
protected TypeConversionDescriptorBase findConversionForAnonymous(@NotNull PsiAnonymousClass anonymousClass,
GuavaConversionSettings settings) {
final TypeConversionDescriptorBase conversion = super.findConversionForAnonymous(anonymousClass, settings);
if (conversion != null) {
return conversion;
}
final PsiClass baseClass = anonymousClass.getBaseClassType().resolve();
return baseClass != null && myLambda.getClassQName().equals(baseClass.getQualifiedName())
? new ConvertLambdaClassToJavaClassDescriptor()
: null;
}
public static class Function extends GuavaLambdaConversionRule { public static class Function extends GuavaLambdaConversionRule {
public Function() { public Function() {
super(GuavaLambda.FUNCTION); super(GuavaLambda.FUNCTION);
@@ -70,5 +87,11 @@ public class GuavaLambdaConversionRule extends BaseGuavaTypeConversionRule {
} }
} }
private static class ConvertLambdaClassToJavaClassDescriptor extends TypeConversionDescriptorBase {
@Override
public PsiExpression replace(PsiExpression expression, @NotNull TypeEvaluator evaluator) throws IncorrectOperationException {
return GuavaConversionUtil.tryConvertClassAndSamNameToJava((PsiNewExpression)expression);
}
}
} }
@@ -39,6 +39,14 @@ import java.util.List;
* @author Dmitry Batkovich * @author Dmitry Batkovich
*/ */
public class GuavaInspectionTest extends JavaCodeInsightFixtureTestCase { public class GuavaInspectionTest extends JavaCodeInsightFixtureTestCase {
private GuavaInspection myInspection;
@Override
public void setUp() throws Exception {
super.setUp();
myInspection = new GuavaInspection();
myFixture.enableInspections(myInspection);
}
@Override @Override
protected String getTestDataPath() { protected String getTestDataPath() {
@@ -49,6 +57,7 @@ public class GuavaInspectionTest extends JavaCodeInsightFixtureTestCase {
protected void tuneFixture(JavaModuleFixtureBuilder moduleBuilder) throws Exception { protected void tuneFixture(JavaModuleFixtureBuilder moduleBuilder) throws Exception {
moduleBuilder.setLanguageLevel(LanguageLevel.JDK_1_8); moduleBuilder.setLanguageLevel(LanguageLevel.JDK_1_8);
moduleBuilder.addLibraryJars("guava", PathManager.getHomePathFor(Assert.class) + "/lib/", "guava-17.0.jar"); moduleBuilder.addLibraryJars("guava", PathManager.getHomePathFor(Assert.class) + "/lib/", "guava-17.0.jar");
moduleBuilder.addLibraryJars("jsr305", PathManager.getHomePathFor(Assert.class) + "/lib/", "jsr305.jar");
moduleBuilder.addJdk(IdeaTestUtil.getMockJdk18Path().getPath()); moduleBuilder.addJdk(IdeaTestUtil.getMockJdk18Path().getPath());
} }
@@ -73,7 +82,7 @@ public class GuavaInspectionTest extends JavaCodeInsightFixtureTestCase {
} }
public void testFluentIterableChainWithoutVariable() { public void testFluentIterableChainWithoutVariable() {
doTestAllFile();; doTestAllFile();
} }
public void testChainedFluentIterableWithChainedInitializer() { public void testChainedFluentIterableWithChainedInitializer() {
@@ -259,9 +268,23 @@ public class GuavaInspectionTest extends JavaCodeInsightFixtureTestCase {
doTest(); doTest();
} }
// for ex: javax.annotations.Nullable is runtime annotation
public void testFunctionAnnotatedWithRuntimeAnnotation() {
doTestAllFile();
}
public void testFunctionAnnotatedWithRuntimeAnnotation2() {
try {
myInspection.ignoreJavaxNullable = false;
doTestAllFile();
} finally {
myInspection.ignoreJavaxNullable = true;
}
}
private void doTestNoQuickFixes(Class<? extends PsiElement>... highlightedElements) { private void doTestNoQuickFixes(Class<? extends PsiElement>... highlightedElements) {
myFixture.configureByFile(getTestName(true) + ".java"); myFixture.configureByFile(getTestName(true) + ".java");
myFixture.enableInspections(new GuavaInspection());
myFixture.doHighlighting(); myFixture.doHighlighting();
for (IntentionAction action : myFixture.getAvailableIntentions()) { for (IntentionAction action : myFixture.getAvailableIntentions()) {
if (action instanceof GuavaInspection.MigrateGuavaTypeFix) { if (action instanceof GuavaInspection.MigrateGuavaTypeFix) {
@@ -0,0 +1,29 @@
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import javax.annotations.Nullable;
import static com.google.common.collect.FluentIterable.from
import java.util.ArrayList;
import java.util.List;
class A {
void m(List<String> l) {
Function<String, String> function = new Function<String, String>() {
@Nullable
@Override
public String apply(@Nullable String x) {
return x;
}
};
boolean strings = FluentIterable.from(l).transform(new Function<String, String>() {
@Nullable
@Override
public String apply(@Nullable String x) {
return x;
}
}).first().isPresent();
}
}
@@ -0,0 +1,29 @@
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import javax.annotations.Nullable;
import static com.google.common.collect.FluentIterable.from
import java.util.ArrayList;
import java.util.List;
class A {
void m(List<String> l) {
Function<String, String> function = new Function<String, String>() {
@Nullable
@Override
public String apply(@Nullable String x) {
return x;
}
};
boolean strings = FluentIterable.from(l).transform(new Function<String, String>() {
@Nullable
@Override
public String apply(@Nullable String x) {
return x;
}
}).first().isPresent();
}
}
@@ -0,0 +1,12 @@
import java.util.List;
import java.util.function.Function;
class A {
void m(List<String> l) {
Function<String, String> function = x -> x;
boolean strings = l.stream().map(x -> x).findFirst().isPresent();
}
}
@@ -0,0 +1,12 @@
import java.util.List;
import java.util.function.Function;
class A {
void m(List<String> l) {
Function<String, String> function = x -> x;
boolean strings = l.stream().map(x -> x).findFirst().isPresent();
}
}
@@ -28,6 +28,7 @@ import com.intellij.refactoring.util.LambdaRefactoringUtil;
import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.List; import java.util.List;
public class TrivialFunctionalExpressionUsageInspection extends BaseJavaBatchLocalInspectionTool { public class TrivialFunctionalExpressionUsageInspection extends BaseJavaBatchLocalInspectionTool {
@@ -73,7 +74,7 @@ public class TrivialFunctionalExpressionUsageInspection extends BaseJavaBatchLoc
@Override @Override
public void visitAnonymousClass(final PsiAnonymousClass aClass) { public void visitAnonymousClass(final PsiAnonymousClass aClass) {
if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(aClass, false)) { if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(aClass, false, Collections.emptySet())) {
final PsiElement newExpression = aClass.getParent(); final PsiElement newExpression = aClass.getParent();
doCheckMethodCallOnFunctionalExpression(new Condition<PsiElement>() { doCheckMethodCallOnFunctionalExpression(new Condition<PsiElement>() {
@Override @Override