From 07903627379052dff1215ed35a4e06cd44528478 Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Tue, 22 Mar 2016 14:45:41 +0300 Subject: [PATCH] guava type migration: convert anonymous Function-s with runtime retention policy annotations to anonymous classes except @javax.annotations.Nullable (can be configured in settings) --- .../AnonymousCanBeLambdaInspection.java | 18 ++++-- ...onymousCanBeMethodReferenceInspection.java | 3 +- .../IntroduceParameterDialog.java | 3 +- .../typeMigration/TypeEvaluator.java | 5 ++ .../typeMigration/TypeMigrationLabeler.java | 14 ++--- .../typeMigration/TypeMigrationProcessor.java | 18 +++--- .../typeMigration/TypeMigrationRules.java | 20 +++++- .../typeMigration/ui/MigrationPanel.java | 2 +- .../inspections/GuavaConversionSettings.java | 39 ++++++++++++ .../inspections/GuavaInspection.java | 11 ++-- .../guava/BaseGuavaTypeConversionRule.java | 25 +++++--- .../guava/FluentIterableConversionUtil.java | 5 +- .../rules/guava/GuavaConversionUtil.java | 63 +++++++++++++++++-- .../rules/guava/GuavaLambda.java | 14 +++-- .../guava/GuavaLambdaConversionRule.java | 29 ++++++++- .../inspections/GuavaInspectionTest.java | 27 +++++++- ...unctionAnnotatedWithRuntimeAnnotation.java | 29 +++++++++ ...nctionAnnotatedWithRuntimeAnnotation2.java | 29 +++++++++ ...AnnotatedWithRuntimeAnnotation2_after.java | 12 ++++ ...nAnnotatedWithRuntimeAnnotation_after.java | 12 ++++ ...alFunctionalExpressionUsageInspection.java | 3 +- 21 files changed, 323 insertions(+), 58 deletions(-) create mode 100644 java/typeMigration/src/com/intellij/refactoring/typeMigration/inspections/GuavaConversionSettings.java create mode 100644 java/typeMigration/testData/inspections/guava/functionAnnotatedWithRuntimeAnnotation.java create mode 100644 java/typeMigration/testData/inspections/guava/functionAnnotatedWithRuntimeAnnotation2.java create mode 100644 java/typeMigration/testData/inspections/guava/functionAnnotatedWithRuntimeAnnotation2_after.java create mode 100644 java/typeMigration/testData/inspections/guava/functionAnnotatedWithRuntimeAnnotation_after.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/AnonymousCanBeLambdaInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/AnonymousCanBeLambdaInspection.java index 1db51e9b22cd..57b3466fb292 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/AnonymousCanBeLambdaInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/AnonymousCanBeLambdaInspection.java @@ -100,7 +100,7 @@ public class AnonymousCanBeLambdaInspection extends BaseJavaBatchLocalInspection final PsiElement lambdaContext = parent != null ? parent.getParent() : null; if (lambdaContext != null && (LambdaUtil.isValidLambdaContext(lambdaContext) || !(lambdaContext instanceof PsiExpressionStatement)) && - canBeConvertedToLambda(aClass, false, reportNotAnnotatedInterfaces)) { + canBeConvertedToLambda(aClass, false, reportNotAnnotatedInterfaces, Collections.emptySet())) { final PsiElement lBrace = aClass.getLBrace(); LOG.assertTrue(lBrace != null); 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 runtimeAnnotationsToIgnore) { PsiAnnotation[] annotations = method.getModifierList().getAnnotations(); for (PsiAnnotation annotation : annotations) { PsiJavaCodeReferenceElement ref = annotation.getNameReferenceElement(); PsiElement target = ref != null ? ref.resolve() : null; if (target instanceof PsiClass) { + if (runtimeAnnotationsToIgnore.contains(((PsiClass)target).getQualifiedName())) { + continue; + } final PsiAnnotation retentionAnno = AnnotationUtil.findAnnotation((PsiClass)target, Retention.class.getName()); if (retentionAnno != null) { PsiAnnotationMemberValue value = retentionAnno.findAttributeValue("value"); @@ -178,13 +181,16 @@ public class AnonymousCanBeLambdaInspection extends BaseJavaBatchLocalInspection return null; } - public static boolean canBeConvertedToLambda(PsiAnonymousClass aClass, boolean acceptParameterizedFunctionTypes) { - return canBeConvertedToLambda(aClass, acceptParameterizedFunctionTypes, true); + public static boolean canBeConvertedToLambda(PsiAnonymousClass aClass, + boolean acceptParameterizedFunctionTypes, + @NotNull Set ignoredRuntimeAnnotations) { + return canBeConvertedToLambda(aClass, acceptParameterizedFunctionTypes, true, ignoredRuntimeAnnotations); } public static boolean canBeConvertedToLambda(PsiAnonymousClass aClass, boolean acceptParameterizedFunctionTypes, - boolean reportNotAnnotatedInterfaces) { + boolean reportNotAnnotatedInterfaces, + @NotNull Set ignoredRuntimeAnnotations) { if (PsiUtil.getLanguageLevel(aClass).isAtLeast(LanguageLevel.JDK_1_8)) { final PsiClassType baseClassType = aClass.getBaseClassType(); final PsiClassType.ClassResolveResult resolveResult = baseClassType.resolveGenerics(); @@ -203,7 +209,7 @@ public class AnonymousCanBeLambdaInspection extends BaseJavaBatchLocalInspection final PsiMethod method = methods[0]; return method.getBody() != null && !hasForbiddenRefsInsideBody(method, aClass) && - !hasRuntimeAnnotations(method) && + !hasRuntimeAnnotations(method, ignoredRuntimeAnnotations) && !method.hasModifierProperty(PsiModifier.SYNCHRONIZED); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/AnonymousCanBeMethodReferenceInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/AnonymousCanBeMethodReferenceInspection.java index e5a4047c4446..f5b95d7b41d7 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/AnonymousCanBeMethodReferenceInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/AnonymousCanBeMethodReferenceInspection.java @@ -29,6 +29,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.util.Collections; /** * User: anna @@ -76,7 +77,7 @@ public class AnonymousCanBeMethodReferenceInspection extends BaseJavaBatchLocalI @Override public void visitAnonymousClass(PsiAnonymousClass 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 PsiCodeBlock body = method.getBody(); final PsiCallExpression callExpression = diff --git a/java/java-impl/src/com/intellij/refactoring/introduceParameter/IntroduceParameterDialog.java b/java/java-impl/src/com/intellij/refactoring/introduceParameter/IntroduceParameterDialog.java index 194f0163d193..51af3a7b7cf3 100644 --- a/java/java-impl/src/com/intellij/refactoring/introduceParameter/IntroduceParameterDialog.java +++ b/java/java-impl/src/com/intellij/refactoring/introduceParameter/IntroduceParameterDialog.java @@ -44,6 +44,7 @@ import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; +import java.util.Collections; import java.util.List; public class IntroduceParameterDialog extends RefactoringDialog { @@ -234,7 +235,7 @@ public class IntroduceParameterDialog extends RefactoringDialog { myCbCollapseToLambda = new NonFocusableCheckBox(RefactoringBundle.message("introduce.parameter.convert.lambda")); final PsiAnonymousClass anonymClass = myExpression instanceof PsiNewExpression ? ((PsiNewExpression)myExpression).getAnonymousClass() : 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)); gbConstraints.gridy++; panel.add(myCbCollapseToLambda, gbConstraints); diff --git a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeEvaluator.java b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeEvaluator.java index 997d4d96cba8..8e5f0c430e99 100644 --- a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeEvaluator.java +++ b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeEvaluator.java @@ -447,6 +447,11 @@ public class TypeEvaluator { return migrationTtype; } + @Nullable + public T getSettings(Class aClass) { + return myRules.getConversionSettings(aClass); + } + private class SubstitutorBuilder { private final Map myMapping; private final PsiMethod myMethod; diff --git a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationLabeler.java b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationLabeler.java index 9fa7a70af45e..bc301fb4e721 100644 --- a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationLabeler.java +++ b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationLabeler.java @@ -28,7 +28,6 @@ import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.impl.PsiImplUtil; -import com.intellij.psi.impl.source.PsiClassReferenceType; import com.intellij.psi.impl.source.PsiImmediateClassType; import com.intellij.psi.javadoc.PsiDocTagValue; 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.util.*; 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.GraphGenerator; import org.jetbrains.annotations.NotNull; @@ -55,8 +54,6 @@ import org.jetbrains.annotations.TestOnly; import javax.swing.*; import java.util.*; -import java.util.HashMap; -import java.util.HashSet; /** * @author db @@ -296,6 +293,11 @@ public class TypeMigrationLabeler { return new MigrationProducer(conversions); } + @Nullable + public T getSettings(Class aClass) { + return myRules.getConversionSettings(aClass); + } + class MigrationProducer { private final Map myRemainConversions; @@ -353,10 +355,6 @@ public class TypeMigrationLabeler { Object getConversion(UsageInfo info) { return myRemainConversions.remove(info); } - - boolean allOfConversionsUsed() { - return myRemainConversions.isEmpty(); - } } void postProcessNewExpression(@NotNull PsiNewExpression expression) { diff --git a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationProcessor.java b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationProcessor.java index 335f8bcf389c..ab6fd927c2e6 100644 --- a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationProcessor.java @@ -35,10 +35,8 @@ import com.intellij.ui.content.Content; import com.intellij.usageView.UsageInfo; import com.intellij.usageView.UsageViewDescriptor; import com.intellij.usageView.UsageViewManager; -import com.intellij.usages.Usage; import com.intellij.util.*; import com.intellij.util.containers.*; -import com.intellij.util.containers.HashMap; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; @@ -242,13 +240,14 @@ public class TypeMigrationProcessor extends BaseRefactoringProcessor { ((PsiVariable)element).normalizeDeclaration(); } } - change(myLabeler, usages); + change(usages, myLabeler, myProject); } - public static void change(TypeMigrationLabeler labeler, UsageInfo[] usages) { - final List newExpressionsToCheckDiamonds = new SmartList(); + public static void change(UsageInfo[] usages, TypeMigrationLabeler labeler, Project project) { + final List> newExpressionsToCheckDiamonds = new SmartList<>(); final TypeMigrationLabeler.MigrationProducer producer = labeler.createMigratorFor(usages); + final SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(project); List nonCodeUsages = new ArrayList(); for (UsageInfo usage : usages) { if (((TypeMigrationUsageInfo)usage).isExcluded()) continue; @@ -260,7 +259,7 @@ public class TypeMigrationProcessor extends BaseRefactoringProcessor { producer.change((TypeMigrationUsageInfo)usage, new Consumer() { @Override 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) { - labeler.postProcessNewExpression(newExpression); + for (SmartPsiElementPointer newExpressionPointer : newExpressionsToCheckDiamonds) { + final PsiNewExpression newExpression = newExpressionPointer.getElement(); + if (newExpression != null) { + labeler.postProcessNewExpression(newExpression); + } } for (UsageInfo usageInfo : nonCodeUsages) { diff --git a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationRules.java b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationRules.java index bc0e1aed4082..94b4bd996230 100644 --- a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationRules.java +++ b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationRules.java @@ -27,28 +27,42 @@ import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NonNls; 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 * Date: Oct 2, 2004 */ public class TypeMigrationRules { - private final LinkedList myConversionRules = new LinkedList(); + private final List myConversionRules; + private final Map myConversionCustomSettings = new HashMap<>(); private SearchScope mySearchScope; private final MigrateGetterNameSetting myMigrateGetterNameSetting = new MigrateGetterNameSetting(); public TypeMigrationRules() { + final TypeConversionRule[] extensions = Extensions.getExtensions(TypeConversionRule.EP_NAME); + myConversionRules = new ArrayList<>(extensions.length + 2); myConversionRules.add(new RootTypeConversionRule()); myConversionRules.add(new DisjunctionTypeConversionRule()); - ContainerUtil.addAll(myConversionRules, Extensions.getExtensions(TypeConversionRule.EP_NAME)); + ContainerUtil.addAll(myConversionRules, extensions); } public void addConversionDescriptor(TypeConversionRule rule) { myConversionRules.add(rule); } + public void addConversionRuleSettings(Object settings) { + myConversionCustomSettings.put(settings.getClass(), settings); + } + + public T getConversionSettings(Class aClass) { + return (T)myConversionCustomSettings.get(aClass); + } + @NonNls @Nullable public TypeConversionDescriptorBase findConversion(final PsiType from, final PsiType to, final PsiMember member, final PsiExpression context, diff --git a/java/java-impl/src/com/intellij/refactoring/typeMigration/ui/MigrationPanel.java b/java/java-impl/src/com/intellij/refactoring/typeMigration/ui/MigrationPanel.java index a9c6f81f4e17..c350f5c74361 100644 --- a/java/java-impl/src/com/intellij/refactoring/typeMigration/ui/MigrationPanel.java +++ b/java/java-impl/src/com/intellij/refactoring/typeMigration/ui/MigrationPanel.java @@ -229,7 +229,7 @@ public class MigrationPanel extends JPanel implements Disposable { } new WriteCommandAction(myProject) { protected void run(@NotNull Result result) throws Throwable { - TypeMigrationProcessor.change(myLabeler, usages); + TypeMigrationProcessor.change(usages, myLabeler, myProject); } }.execute(); } diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/inspections/GuavaConversionSettings.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/inspections/GuavaConversionSettings.java new file mode 100644 index 000000000000..9a5e6938eb95 --- /dev/null +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/inspections/GuavaConversionSettings.java @@ -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 myIgnoredAnnotations; + + public GuavaConversionSettings(boolean ignoreJavaxNullable) { + Set ignoredAnnotations = new HashSet<>(); + if (ignoreJavaxNullable) { + ignoredAnnotations.add("java.annotations.Nullable"); + } + myIgnoredAnnotations = Collections.unmodifiableSet(ignoredAnnotations); + } + + public Set getIgnoredAnnotations() { + return myIgnoredAnnotations; + } +} diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/inspections/GuavaInspection.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/inspections/GuavaInspection.java index 8d9d4661f229..a5015e8c87e1 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/inspections/GuavaInspection.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/inspections/GuavaInspection.java @@ -66,6 +66,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool { public boolean checkVariables = true; public boolean checkChains = true; public boolean checkReturnTypes = true; + public boolean ignoreJavaxNullable = true; @SuppressWarnings("Duplicates") @Override @@ -74,6 +75,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool { panel.addCheckbox("Report variables", "checkVariables"); panel.addCheckbox("Report method chains", "checkChains"); panel.addCheckbox("Report return types", "checkReturnTypes"); + panel.addCheckbox("Erase @javax.annotations.Nullable from converted functions", "ignoreJavaxNullable"); return panel; } @@ -278,7 +280,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool { }; } - public static class MigrateGuavaTypeFix extends LocalQuickFixAndIntentionActionOnPsiElement implements BatchQuickFix { + public class MigrateGuavaTypeFix extends LocalQuickFixAndIntentionActionOnPsiElement implements BatchQuickFix { private final PsiType myTargetType; private MigrateGuavaTypeFix(@NotNull PsiElement element, PsiType targetType) { @@ -346,7 +348,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool { if (!elementsToFix.isEmpty()) performTypeMigration(elementsToFix, migrationTypes); } - private static MigrateGuavaTypeFix getFix(ProblemDescriptor descriptor) { + private MigrateGuavaTypeFix getFix(ProblemDescriptor descriptor) { final QuickFix[] fixes = descriptor.getFixes(); LOG.assertTrue(fixes != null); for (QuickFix fix : fixes) { @@ -357,7 +359,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool { throw new AssertionError(); } - private static boolean performTypeMigration(List elements, List types) { + private boolean performTypeMigration(List elements, List types) { PsiFile containingFile = null; for (PsiElement element : elements) { final PsiFile currentContainingFile = element.getContainingFile(); @@ -374,6 +376,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool { final TypeMigrationRules rules = new TypeMigrationRules(); rules.setBoundScope(GlobalSearchScopesCore.projectProductionScope(containingFile.getProject()) .union(GlobalSearchScopesCore.projectTestScope(containingFile.getProject()))); + rules.addConversionRuleSettings(new GuavaConversionSettings(ignoreJavaxNullable)); TypeMigrationProcessor.runHighlightingTypeMigration(containingFile.getProject(), null, rules, @@ -388,7 +391,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool { return true; } - private static Function createMigrationTypeFunction(@NotNull final List elements, + private Function createMigrationTypeFunction(@NotNull final List elements, @NotNull final List types) { LOG.assertTrue(elements.size() == types.size()); final Map mappings = new HashMap(); diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/BaseGuavaTypeConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/BaseGuavaTypeConversionRule.java index dea4602686d4..2bc7fb0f99e7 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/BaseGuavaTypeConversionRule.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/BaseGuavaTypeConversionRule.java @@ -20,6 +20,7 @@ import com.intellij.psi.*; import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase; import com.intellij.refactoring.typeMigration.TypeEvaluator; import com.intellij.refactoring.typeMigration.TypeMigrationLabeler; +import com.intellij.refactoring.typeMigration.inspections.GuavaConversionSettings; import com.intellij.refactoring.typeMigration.rules.TypeConversionRule; import com.intellij.reference.SoftLazyValue; import com.intellij.util.IncorrectOperationException; @@ -98,14 +99,7 @@ public abstract class BaseGuavaTypeConversionRule extends TypeConversionRule { return findConversionForMethod(from, to, method, methodName, context, labeler); } else if (context instanceof PsiNewExpression) { final PsiAnonymousClass anonymousClass = ((PsiNewExpression)context).getAnonymousClass(); - if (anonymousClass != null && AnonymousCanBeLambdaInspection.canBeConvertedToLambda(anonymousClass, false)) { - return new TypeConversionDescriptorBase() { - @Override - public PsiExpression replace(PsiExpression expression, TypeEvaluator evaluator) throws IncorrectOperationException { - return AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, false, true); - }; - }; - } + return anonymousClass == null ? null : findConversionForAnonymous(anonymousClass, labeler.getSettings(GuavaConversionSettings.class)); } else if (context instanceof PsiReferenceExpression) { final PsiElement resolvedElement = ((PsiReferenceExpression)context).resolve(); @@ -116,6 +110,21 @@ public abstract class BaseGuavaTypeConversionRule extends TypeConversionRule { return null; } + @Nullable + protected TypeConversionDescriptorBase findConversionForAnonymous(@NotNull PsiAnonymousClass anonymousClass, + @Nullable GuavaConversionSettings settings) { + final Set 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) { return aClass != null && (ruleFromClass().equals(aClass.getQualifiedName()) || getAdditionalUtilityClasses().contains(aClass.getQualifiedName())); } diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FluentIterableConversionUtil.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FluentIterableConversionUtil.java index 3987df0e2f26..7ae87b26e85c 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FluentIterableConversionUtil.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FluentIterableConversionUtil.java @@ -16,7 +16,6 @@ package com.intellij.refactoring.typeMigration.rules.guava; import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil; -import com.intellij.codeInspection.AnonymousCanBeLambdaInspection; import com.intellij.codeInspection.java18StreamApi.StreamApiConstants; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Pair; @@ -144,9 +143,7 @@ public class FluentIterableConversionUtil { PsiAnonymousClass anonymousClass; if (argument instanceof PsiNewExpression && (anonymousClass = ((PsiNewExpression)argument).getAnonymousClass()) != null) { - if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(anonymousClass, true)) { - argument = AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(argument, true, true); - }; + argument = GuavaConversionUtil.convertAnonymousClass((PsiNewExpression)argument, anonymousClass, typeEvaluator); } final JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(expression.getProject()); if (argument != null && !(argument instanceof PsiFunctionalExpression)) { diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaConversionUtil.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaConversionUtil.java index 608ba2b4b46f..b609c0adce54 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaConversionUtil.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaConversionUtil.java @@ -16,18 +16,25 @@ package com.intellij.refactoring.typeMigration.rules.guava; import com.intellij.codeInspection.AnonymousCanBeLambdaInspection; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.psi.*; import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiTypesUtil; import com.intellij.refactoring.typeMigration.TypeEvaluator; +import com.intellij.refactoring.typeMigration.inspections.GuavaConversionSettings; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collections; +import java.util.Set; + /** * @author Dmitry Batkovich */ public class GuavaConversionUtil { + private final static Logger LOG = Logger.getInstance(GuavaConversionUtil.class); + @Nullable public static PsiType getFunctionReturnType(PsiExpression functionExpression) { if (functionExpression instanceof PsiFunctionalExpression) { @@ -88,13 +95,14 @@ public class GuavaConversionUtil { 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) { final PsiAnonymousClass anonymousClass = ((PsiNewExpression)expression).getAnonymousClass(); if (anonymousClass != null) { - if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(anonymousClass, true)) { - return AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, true, true); - } + return convertAnonymousClass((PsiNewExpression)expression, anonymousClass, evaluator); } else { final GuavaLambda lambda = GuavaLambda.findFor(evaluator.evaluateType(expression)); @@ -134,6 +142,53 @@ public class GuavaConversionUtil { return expression; } + public static PsiExpression convertAnonymousClass(@NotNull PsiNewExpression expression, + @NotNull PsiAnonymousClass anonymousClass, + @NotNull TypeEvaluator typeEvaluator) { + final GuavaConversionSettings settings = typeEvaluator.getSettings(GuavaConversionSettings.class); + final Set 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) { return (PsiExpression)expression.replace(JavaPsiFacade.getElementFactory(expression.getProject()) .createExpressionFromText(expression.getText() + "::" + lambda.getSamName(), expression)); diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaLambda.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaLambda.java index 7e61849e21e3..2bac1de41f23 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaLambda.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaLambda.java @@ -25,20 +25,26 @@ import org.jetbrains.annotations.Nullable; * @author Dmitry Batkovich */ public enum GuavaLambda { - PREDICATE("com.google.common.base.Predicate", "java.util.function.Predicate", "apply", "test"), - FUNCTION("com.google.common.base.Function", "java.util.function.Function", "apply", "apply"), - SUPPLIER("com.google.common.base.Supplier", "java.util.function.Supplier", "get", "get"); + 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", 1), + SUPPLIER("com.google.common.base.Supplier", "java.util.function.Supplier", "get", "get", 0); private final String myClassQName; private final String myJavaAnalogueClassQName; private final String mySamName; 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; myJavaAnalogueClassQName = javaAnalogueClassQName; mySamName = samName; myJavaAnalogueSamName = javaAnalogueSamName; + myParametersCount = count; + } + + public int getParametersCount() { + return myParametersCount; } public String getClassQName() { diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaLambdaConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaLambdaConversionRule.java index eaa1d5448c16..366acf0d9820 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaLambdaConversionRule.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaLambdaConversionRule.java @@ -15,10 +15,12 @@ */ package com.intellij.refactoring.typeMigration.rules.guava; -import com.intellij.psi.PsiExpression; -import com.intellij.psi.PsiReferenceExpression; -import com.intellij.psi.PsiVariable; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.psi.*; 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.Nullable; @@ -28,6 +30,7 @@ import java.util.Map; * @author Dmitry Batkovich */ public class GuavaLambdaConversionRule extends BaseGuavaTypeConversionRule { + private final static Logger LOG = Logger.getInstance(GuavaLambdaConversionRule.class); private final GuavaLambda myLambda; protected GuavaLambdaConversionRule(GuavaLambda lambda) { @@ -58,6 +61,20 @@ public class GuavaLambdaConversionRule extends BaseGuavaTypeConversionRule { 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 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); + } + } } diff --git a/java/typeMigration/test/com/intellij/codeInsight/inspections/GuavaInspectionTest.java b/java/typeMigration/test/com/intellij/codeInsight/inspections/GuavaInspectionTest.java index 480a17c124be..df07a2ea5b09 100644 --- a/java/typeMigration/test/com/intellij/codeInsight/inspections/GuavaInspectionTest.java +++ b/java/typeMigration/test/com/intellij/codeInsight/inspections/GuavaInspectionTest.java @@ -39,6 +39,14 @@ import java.util.List; * @author Dmitry Batkovich */ public class GuavaInspectionTest extends JavaCodeInsightFixtureTestCase { + private GuavaInspection myInspection; + + @Override + public void setUp() throws Exception { + super.setUp(); + myInspection = new GuavaInspection(); + myFixture.enableInspections(myInspection); + } @Override protected String getTestDataPath() { @@ -49,6 +57,7 @@ public class GuavaInspectionTest extends JavaCodeInsightFixtureTestCase { protected void tuneFixture(JavaModuleFixtureBuilder moduleBuilder) throws Exception { moduleBuilder.setLanguageLevel(LanguageLevel.JDK_1_8); 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()); } @@ -73,7 +82,7 @@ public class GuavaInspectionTest extends JavaCodeInsightFixtureTestCase { } public void testFluentIterableChainWithoutVariable() { - doTestAllFile();; + doTestAllFile(); } public void testChainedFluentIterableWithChainedInitializer() { @@ -259,9 +268,23 @@ public class GuavaInspectionTest extends JavaCodeInsightFixtureTestCase { 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... highlightedElements) { myFixture.configureByFile(getTestName(true) + ".java"); - myFixture.enableInspections(new GuavaInspection()); + myFixture.doHighlighting(); for (IntentionAction action : myFixture.getAvailableIntentions()) { if (action instanceof GuavaInspection.MigrateGuavaTypeFix) { diff --git a/java/typeMigration/testData/inspections/guava/functionAnnotatedWithRuntimeAnnotation.java b/java/typeMigration/testData/inspections/guava/functionAnnotatedWithRuntimeAnnotation.java new file mode 100644 index 000000000000..76142e510712 --- /dev/null +++ b/java/typeMigration/testData/inspections/guava/functionAnnotatedWithRuntimeAnnotation.java @@ -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 l) { + Function function = new Function() { + @Nullable + @Override + public String apply(@Nullable String x) { + return x; + } + }; + + boolean strings = FluentIterable.from(l).transform(new Function() { + @Nullable + @Override + public String apply(@Nullable String x) { + return x; + } + }).first().isPresent(); + } + +} \ No newline at end of file diff --git a/java/typeMigration/testData/inspections/guava/functionAnnotatedWithRuntimeAnnotation2.java b/java/typeMigration/testData/inspections/guava/functionAnnotatedWithRuntimeAnnotation2.java new file mode 100644 index 000000000000..76142e510712 --- /dev/null +++ b/java/typeMigration/testData/inspections/guava/functionAnnotatedWithRuntimeAnnotation2.java @@ -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 l) { + Function function = new Function() { + @Nullable + @Override + public String apply(@Nullable String x) { + return x; + } + }; + + boolean strings = FluentIterable.from(l).transform(new Function() { + @Nullable + @Override + public String apply(@Nullable String x) { + return x; + } + }).first().isPresent(); + } + +} \ No newline at end of file diff --git a/java/typeMigration/testData/inspections/guava/functionAnnotatedWithRuntimeAnnotation2_after.java b/java/typeMigration/testData/inspections/guava/functionAnnotatedWithRuntimeAnnotation2_after.java new file mode 100644 index 000000000000..1de193064e40 --- /dev/null +++ b/java/typeMigration/testData/inspections/guava/functionAnnotatedWithRuntimeAnnotation2_after.java @@ -0,0 +1,12 @@ +import java.util.List; +import java.util.function.Function; + +class A { + + void m(List l) { + Function function = x -> x; + + boolean strings = l.stream().map(x -> x).findFirst().isPresent(); + } + +} \ No newline at end of file diff --git a/java/typeMigration/testData/inspections/guava/functionAnnotatedWithRuntimeAnnotation_after.java b/java/typeMigration/testData/inspections/guava/functionAnnotatedWithRuntimeAnnotation_after.java new file mode 100644 index 000000000000..1de193064e40 --- /dev/null +++ b/java/typeMigration/testData/inspections/guava/functionAnnotatedWithRuntimeAnnotation_after.java @@ -0,0 +1,12 @@ +import java.util.List; +import java.util.function.Function; + +class A { + + void m(List l) { + Function function = x -> x; + + boolean strings = l.stream().map(x -> x).findFirst().isPresent(); + } + +} \ No newline at end of file diff --git a/plugins/InspectionGadgets/src/com/intellij/codeInspection/TrivialFunctionalExpressionUsageInspection.java b/plugins/InspectionGadgets/src/com/intellij/codeInspection/TrivialFunctionalExpressionUsageInspection.java index 9ff98fee97ca..2e3814f14768 100644 --- a/plugins/InspectionGadgets/src/com/intellij/codeInspection/TrivialFunctionalExpressionUsageInspection.java +++ b/plugins/InspectionGadgets/src/com/intellij/codeInspection/TrivialFunctionalExpressionUsageInspection.java @@ -28,6 +28,7 @@ import com.intellij.refactoring.util.LambdaRefactoringUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; +import java.util.Collections; import java.util.List; public class TrivialFunctionalExpressionUsageInspection extends BaseJavaBatchLocalInspectionTool { @@ -73,7 +74,7 @@ public class TrivialFunctionalExpressionUsageInspection extends BaseJavaBatchLoc @Override public void visitAnonymousClass(final PsiAnonymousClass aClass) { - if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(aClass, false)) { + if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(aClass, false, Collections.emptySet())) { final PsiElement newExpression = aClass.getParent(); doCheckMethodCallOnFunctionalExpression(new Condition() { @Override