diff --git a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeConversionDescriptorBase.java b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeConversionDescriptorBase.java index 34b87a6d6747..b2eb1d9df9bf 100644 --- a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeConversionDescriptorBase.java +++ b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeConversionDescriptorBase.java @@ -16,7 +16,11 @@ package com.intellij.refactoring.typeMigration; import com.intellij.psi.PsiExpression; +import com.intellij.psi.PsiSubstitutor; +import com.intellij.psi.PsiType; import com.intellij.refactoring.typeMigration.usageInfo.TypeMigrationUsageInfo; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.Nullable; public class TypeConversionDescriptorBase { @@ -33,7 +37,26 @@ public class TypeConversionDescriptorBase { myRoot = root; } - public void replace(PsiExpression expression){} + /** + * @return converted expression type or null if not known + */ + @Nullable + public PsiType conversionType() { + return null; + } + + /** + * @return substitutor of converted method parameters + * or null if expression is not method call expression + */ + @Nullable + public PsiSubstitutor getConvertedMethodParameters() { + return null; + } + + public PsiExpression replace(PsiExpression expression) throws IncorrectOperationException { + return expression; + } @Override public String toString() { 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 10adac418525..eaa2123506b7 100644 --- a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationLabeler.java +++ b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationLabeler.java @@ -331,6 +331,10 @@ public class TypeMigrationLabeler { } } + else if (expr instanceof PsiLambdaExpression) { + //TODO conversion of lambda expression now works incorrectly [Dmitry Batkovich] + return; + } convertExpression(expr, migrationType, originalType, isCovariant); } diff --git a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationReplacementUtil.java b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationReplacementUtil.java index 0004ff456cb6..873f6230f09b 100644 --- a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationReplacementUtil.java +++ b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationReplacementUtil.java @@ -40,7 +40,11 @@ public class TypeMigrationReplacementUtil { public static void replaceExpression(PsiExpression expression, final Project project, Object conversion) { if (conversion instanceof TypeConversionDescriptorBase) { - ((TypeConversionDescriptorBase)conversion).replace(expression); + try { + ((TypeConversionDescriptorBase)conversion).replace(expression); + } catch (IncorrectOperationException e) { + LOG.error(e); + } } else if (conversion instanceof String) { String replacement = (String)conversion; try { diff --git a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationStatementProcessor.java b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationStatementProcessor.java index 505fc9f35696..3431b403a8b5 100644 --- a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationStatementProcessor.java +++ b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationStatementProcessor.java @@ -219,12 +219,13 @@ class TypeMigrationStatementProcessor extends JavaRecursiveElementVisitor { myLabeler.markFailedConversion(typePair, qualifierExpression); } else { final PsiElement parent = Util.getEssentialParent(expression); + final PsiType type = conversion.conversionType(); if (parent instanceof PsiMethodCallExpression) { myLabeler.setConversionMapping((PsiMethodCallExpression)parent, conversion); - myTypeEvaluator.setType(new TypeMigrationUsageInfo(parent), myTypeEvaluator.evaluateType((PsiExpression)parent)); + myTypeEvaluator.setType(new TypeMigrationUsageInfo(parent), type != null ? type: myTypeEvaluator.evaluateType((PsiExpression)parent)); } else { myLabeler.setConversionMapping(expression, conversion); - myTypeEvaluator.setType(new TypeMigrationUsageInfo(expression), myTypeEvaluator.evaluateType(expression)); + myTypeEvaluator.setType(new TypeMigrationUsageInfo(expression), type != null ? type: myTypeEvaluator.evaluateType(expression)); } } } diff --git a/java/typeMigration/src/META-INF/TypeMigration.xml b/java/typeMigration/src/META-INF/TypeMigration.xml index 8ab2163d80d3..9a0c0e187dfa 100644 --- a/java/typeMigration/src/META-INF/TypeMigration.xml +++ b/java/typeMigration/src/META-INF/TypeMigration.xml @@ -7,6 +7,11 @@ + + + + + com.intellij.refactoring.typeMigration.intentions.ConvertFieldToAtomicIntention Java/Concurrency @@ -19,5 +24,11 @@ com.intellij.refactoring.typeMigration.intentions.ChangeClassParametersIntention Java/Declaration + + diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/TypeConversionDescriptor.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/TypeConversionDescriptor.java index 0861f1ec9e5c..e1a2bd8b5ff3 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/TypeConversionDescriptor.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/TypeConversionDescriptor.java @@ -5,12 +5,14 @@ import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.project.Project; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.PsiExpression; +import com.intellij.psi.PsiType; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.structuralsearch.MatchOptions; import com.intellij.structuralsearch.plugin.replace.ReplaceOptions; import com.intellij.structuralsearch.plugin.replace.impl.Replacer; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; /** * Created by IntelliJ IDEA. @@ -25,10 +27,10 @@ public class TypeConversionDescriptor extends TypeConversionDescriptorBase { private String myStringToReplace = null; private String myReplaceByString = "$"; private PsiExpression myExpression; + private PsiType myConversionType; public TypeConversionDescriptor(@NonNls final String stringToReplace, @NonNls final String replaceByString) { - myStringToReplace = stringToReplace; - myReplaceByString = replaceByString; + this(stringToReplace, replaceByString, (PsiExpression)null); } public TypeConversionDescriptor(@NonNls final String stringToReplace, @NonNls final String replaceByString, final PsiExpression expression) { @@ -37,6 +39,16 @@ public class TypeConversionDescriptor extends TypeConversionDescriptorBase { myExpression = expression; } + public TypeConversionDescriptor(String stringToReplace, String replaceByString, PsiType conversionType) { + this(stringToReplace, replaceByString); + myConversionType = conversionType; + } + + public TypeConversionDescriptor withConversionType(PsiType conversionType) { + myConversionType = conversionType; + return this; + } + public void setStringToReplace(String stringToReplace) { myStringToReplace = stringToReplace; } @@ -61,8 +73,14 @@ public class TypeConversionDescriptor extends TypeConversionDescriptorBase { myExpression = expression; } + @Nullable @Override - public void replace(PsiExpression expression) { + public PsiType conversionType() { + return myConversionType; + } + + @Override + public PsiExpression replace(PsiExpression expression) { if (getExpression() != null) expression = getExpression(); final Project project = expression.getProject(); final ReplaceOptions options = new ReplaceOptions(); @@ -70,19 +88,9 @@ public class TypeConversionDescriptor extends TypeConversionDescriptorBase { matchOptions.setFileType(StdFileTypes.JAVA); options.setMatchOptions(matchOptions); final Replacer replacer = new Replacer(project, null); - try { - final String replacement = replacer.testReplace(expression.getText(), getStringToReplace(), getReplaceByString(), options); - try { - JavaCodeStyleManager.getInstance(project).shortenClassReferences(expression.replace( + final String replacement = replacer.testReplace(expression.getText(), getStringToReplace(), getReplaceByString(), options); + return (PsiExpression)JavaCodeStyleManager.getInstance(project).shortenClassReferences(expression.replace( JavaPsiFacade.getInstance(project).getElementFactory().createExpressionFromText(replacement, expression))); - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - } - catch (IncorrectOperationException e) { - LOG.error(e); - } } @Override diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/TypeMigrationVariableTypeFixProvider.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/TypeMigrationVariableTypeFixProvider.java index fe8872b35840..bcebf17cef20 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/TypeMigrationVariableTypeFixProvider.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/TypeMigrationVariableTypeFixProvider.java @@ -27,7 +27,12 @@ public class TypeMigrationVariableTypeFixProvider implements ChangeVariableTypeQ @NotNull public IntentionAction[] getFixes(@NotNull PsiVariable variable, @NotNull PsiType toReturn) { - return new IntentionAction[]{new VariableTypeFix(variable, toReturn) { + return new IntentionAction[]{createTypeMigrationFix(variable, toReturn)}; + } + + @NotNull + public static VariableTypeFix createTypeMigrationFix(@NotNull final PsiVariable variable, @NotNull final PsiType toReturn) { + return new VariableTypeFix(variable, toReturn) { @NotNull @Override public String getText() { @@ -61,6 +66,6 @@ public class TypeMigrationVariableTypeFixProvider implements ChangeVariableTypeQ public boolean startInWriteAction() { return true; } - }}; + }; } } diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/inspections/GuavaInspection.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/inspections/GuavaInspection.java new file mode 100644 index 000000000000..60be8f9f462c --- /dev/null +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/inspections/GuavaInspection.java @@ -0,0 +1,90 @@ +/* + * 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.refactoring.typeMigration.inspections; + +import com.intellij.codeInsight.daemon.impl.quickfix.VariableTypeFix; +import com.intellij.codeInspection.*; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.AtomicNotNullLazyValue; +import com.intellij.psi.*; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.util.PsiUtil; +import com.intellij.refactoring.typeMigration.TypeMigrationVariableTypeFixProvider; +import com.intellij.refactoring.typeMigration.rules.TypeConversionRule; +import com.intellij.refactoring.typeMigration.rules.guava.BaseGuavaTypeConversionRule; +import com.intellij.util.containers.hash.HashMap; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/** + * @author Dmitry Batkovich + */ +@SuppressWarnings("DialogTitleCapitalization") +public class GuavaInspection extends BaseJavaLocalInspectionTool { + private final static String PROBLEM_DESCRIPTION = "Guava's functional primitives can be replaced by Java API"; + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { + if (!PsiUtil.isLanguageLevel8OrHigher(holder.getFile())) { + return PsiElementVisitor.EMPTY_VISITOR; + } + return new JavaElementVisitor() { + private final AtomicNotNullLazyValue> myGuavaClassConversions = new AtomicNotNullLazyValue>() { + @NotNull + @Override + protected Map compute() { + Map map = new HashMap(); + for (TypeConversionRule rule : TypeConversionRule.EP_NAME.getExtensions()) { + if (rule instanceof BaseGuavaTypeConversionRule) { + final String fromClass = ((BaseGuavaTypeConversionRule)rule).ruleFromClass(); + final String toClass = ((BaseGuavaTypeConversionRule)rule).ruleToClass(); + map.put(fromClass, toClass); + } + } + return map; + } + }; + + @Override + public void visitVariable(PsiVariable variable) { + final PsiType type = variable.getType(); + if (type instanceof PsiClassType) { + final PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)type).resolveGenerics(); + final PsiClass psiClass = resolveResult.getElement(); + if (psiClass != null) { + final String qName = psiClass.getQualifiedName(); + final String toQName = myGuavaClassConversions.getValue().get(qName); + if (toQName != null) { + + final Project project = holder.getProject(); + final JavaPsiFacade javaPsiFacade = JavaPsiFacade.getInstance(project); + final PsiClass targetClass = javaPsiFacade.findClass(toQName, GlobalSearchScope.allScope(project)); + if (targetClass != null) { + final Collection typeParameters = resolveResult.getSubstitutor().getSubstitutionMap().values(); + final PsiClassType targetType = + javaPsiFacade.getElementFactory().createType(targetClass, typeParameters.toArray(new PsiType[typeParameters.size()])); + final VariableTypeFix fix = TypeMigrationVariableTypeFixProvider.createTypeMigrationFix(variable, targetType); + holder.registerProblem(variable, PROBLEM_DESCRIPTION, fix); + } + } + } + } + } + }; + } +} \ No newline at end of file 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 new file mode 100644 index 000000000000..9c2662bc1729 --- /dev/null +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/BaseGuavaTypeConversionRule.java @@ -0,0 +1,128 @@ +/* + * 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.refactoring.typeMigration.rules.guava; + +import com.intellij.codeInspection.AnonymousCanBeLambdaInspection; +import com.intellij.psi.*; +import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase; +import com.intellij.refactoring.typeMigration.TypeMigrationLabeler; +import com.intellij.refactoring.typeMigration.rules.TypeConversionRule; +import com.intellij.reference.SoftLazyValue; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.hash.HashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +/** + * @author Dmitry Batkovich + */ +public abstract class BaseGuavaTypeConversionRule extends TypeConversionRule { + private final SoftLazyValue> mySimpleDescriptors = new SoftLazyValue>() { + @NotNull + @Override + protected Map compute() { + Map map = new HashMap(); + fillSimpleDescriptors(map); + return map; + } + }; + + protected abstract void fillSimpleDescriptors(Map descriptorsMap); + + @Nullable + protected TypeConversionDescriptorBase findConversionForMethod(@NotNull PsiType from, + @NotNull PsiType to, + @NotNull PsiMethod method, + String methodName, + PsiExpression context, + TypeMigrationLabeler labeler) { + return null; + }; + + @NotNull + public abstract String ruleFromClass(); + + @NotNull + public abstract String ruleToClass(); + + @Nullable + @Override + public final TypeConversionDescriptorBase findConversion(PsiType from, + PsiType to, + PsiMember member, + PsiExpression context, + TypeMigrationLabeler labeler) { + if (!canConvert(from, to)) { + return null; + } + if (member instanceof PsiMethod) { + PsiMethod method = (PsiMethod)member; + final String methodName = method.getName(); + final TypeConversionDescriptorBase descriptor = mySimpleDescriptors.getValue().get(methodName); + if (descriptor != null) { + return descriptor; + } + return findConversionForMethod(from, to, method, methodName, context, labeler); + } else if (context instanceof PsiNewExpression) { + final PsiAnonymousClass anonymousClass = ((PsiNewExpression)context).getAnonymousClass(); + if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(anonymousClass, false)) { + return new TypeConversionDescriptorBase() { + @Override + public PsiExpression replace(PsiExpression expression) throws IncorrectOperationException { + return AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, false); + }; + }; + } + } + return null; + } + + private boolean canConvert(PsiType from, PsiType to) { + if (from instanceof PsiEllipsisType) { + from = ((PsiEllipsisType)from).getComponentType(); + } + if (to instanceof PsiEllipsisType) { + to = ((PsiEllipsisType)to).getComponentType(); + } + if (!(from instanceof PsiClassType)) { + return false; + } + if (!(to instanceof PsiClassType)) { + return false; + } + + final PsiClassType.ClassResolveResult fromResolveResult = ((PsiClassType)from).resolveGenerics(); + PsiClass fromClass = fromResolveResult.getElement(); + if (fromClass instanceof PsiAnonymousClass) { + fromClass = ((PsiAnonymousClass)fromClass).getBaseClassType().resolve(); + } + if (fromClass == null || !ruleFromClass().equals(fromClass.getQualifiedName())) { + return false; + } + + final PsiClassType.ClassResolveResult toResolveResult = ((PsiClassType)to).resolveGenerics(); + final PsiClass toClass = toResolveResult.getElement(); + if (toClass == null || !ruleToClass().equals(toClass.getQualifiedName())) { + return false; + } + + //final Collection fromTypeParameters = fromResolveResult.getSubstitutor().getSubstitutionMap().values(); + //final Collection toTypeParameters = toResolveResult.getSubstitutor().getSubstitutionMap().values(); + return true;//!fromTypeParameters.equals(toTypeParameters); + } +} diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFluentIterableConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFluentIterableConversionRule.java new file mode 100644 index 000000000000..701f210c575b --- /dev/null +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFluentIterableConversionRule.java @@ -0,0 +1,72 @@ +/* + * 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.refactoring.typeMigration.rules.guava; + +import com.intellij.codeInspection.java18StreamApi.StreamApiConstants; +import com.intellij.refactoring.typeMigration.TypeConversionDescriptor; +import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +/** + * @author Dmitry Batkovich + */ +public class GuavaFluentIterableConversionRule extends BaseGuavaTypeConversionRule { + + @Override + protected void fillSimpleDescriptors(Map descriptorsMap) { + descriptorsMap.put("contains", + new TypeConversionDescriptor("$it$.contains($o$)", "$it$.anyMatch(e -> e != null && e.equals(%s))")); + descriptorsMap.put("from", new TypeConversionDescriptor("FluentIterable.from($it$)", "$it$.stream()")); + descriptorsMap.put("isEmpty", new TypeConversionDescriptor("$q$.isEmpty()", "$q$.findAny().isPresent()")); + descriptorsMap.put("skip", new TypeConversionDescriptorBase()); + descriptorsMap.put("limit", new TypeConversionDescriptorBase()); + descriptorsMap.put("transform", new LambdaParametersTypeConversionDescription("$q$.transform($params$)", "$q$.map($params$)")); + descriptorsMap.put("transformAndConcat", + new LambdaParametersTypeConversionDescription("$q$.transformAndConcat($params$)", "$q$.flatMap($params$)")); + descriptorsMap.put("first", new TypeConversionDescriptor("$q$.first()", "$q$.findFirst()")); + + descriptorsMap.put("allMatch", new TypeConversionDescriptor("$it$.allMatch($c$)", "$it$." + StreamApiConstants.ALL_MATCH + "($c$)")); + descriptorsMap.put("anyMatch", new TypeConversionDescriptor("$it$.anyMatch($c$)", "$it$." + StreamApiConstants.ANY_MATCH + "($c$)")); + + descriptorsMap.put("filter", new TypeConversionDescriptor("$it$.filter($p$)", "$it$." + StreamApiConstants.FILTER + "($p$)")); + descriptorsMap.put("first", new TypeConversionDescriptor("$it$.first()", "$it$." + StreamApiConstants.FIND_FIRST + "()")); + descriptorsMap.put("firstMatch", new TypeConversionDescriptor("$it$.firstMatch($p$)", "$it$.filter($p$).findFirst()")); + descriptorsMap.put("get", new TypeConversionDescriptor("$it$.get($p$)", "$it$.collect(java.util.stream.Collectors.toList()).get($p$)")); + descriptorsMap.put("size", new TypeConversionDescriptor("$it$.size()", "$it$.collect(java.util.stream.Collectors.toList()).size()")); + + descriptorsMap.put("toMap", new TypeConversionDescriptor("$it$.toMap($f$)", + "$it$.collect(java.util.stream.Collectors.toMap(java.util.function.Function.identity(), $f$))")); + descriptorsMap.put("toList", new TypeConversionDescriptor("$it$.toList()", "$it$.collect(java.util.stream.Collectors.toList())")); + descriptorsMap.put("toSet", new TypeConversionDescriptor("$it$.toSet()", "$it$.collect(java.util.stream.Collectors.toSet())")); + descriptorsMap.put("toSortedList", new TypeConversionDescriptor("$it$.toSortedList($c$)", "$it$.sorted($c$).collect(java.util.stream.Collectors.toList())")); + descriptorsMap.put("toSortedSet", new TypeConversionDescriptor("$it$.toSortedSet($c$)", "$it$.sorted($c$).collect(java.util.stream.Collectors.toSet())")); + + } + + @NotNull + @Override + public String ruleFromClass() { + return "com.google.common.collect.FluentIterable"; + } + + @NotNull + @Override + public String ruleToClass() { + return StreamApiConstants.JAVA_UTIL_STREAM_STREAM; + } +} diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFunctionConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFunctionConversionRule.java new file mode 100644 index 000000000000..bf2e79c1cb04 --- /dev/null +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFunctionConversionRule.java @@ -0,0 +1,62 @@ +/* + * 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.refactoring.typeMigration.rules.guava; + +import com.intellij.psi.PsiExpression; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiType; +import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase; +import com.intellij.refactoring.typeMigration.TypeMigrationLabeler; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +/** + * @author Dmitry Batkovich + */ +public class GuavaFunctionConversionRule extends BaseGuavaTypeConversionRule { + @Override + protected void fillSimpleDescriptors(Map descriptorsMap) { + descriptorsMap.put("apply", new TypeConversionDescriptorBase()); + } + + @Nullable + @Override + protected TypeConversionDescriptorBase findConversionForMethod(@NotNull PsiType from, + @NotNull PsiType to, + @NotNull PsiMethod method, + String methodName, + PsiExpression context, + TypeMigrationLabeler labeler) { + if (method.isConstructor()) { + System.out.println("123"); + } + return null; + } + + @NotNull + @Override + public String ruleFromClass() { + return "com.google.common.base.Function"; + } + + @NotNull + @Override + public String ruleToClass() { + return "java.util.function.Function"; + } +} diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaOptionalConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaOptionalConversionRule.java new file mode 100644 index 000000000000..8d0da65ec343 --- /dev/null +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaOptionalConversionRule.java @@ -0,0 +1,134 @@ +/* + * 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.refactoring.typeMigration.rules.guava; + +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTypesUtil; +import com.intellij.refactoring.typeMigration.TypeConversionDescriptor; +import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase; +import com.intellij.refactoring.typeMigration.TypeMigrationLabeler; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +/** + * @author Dmitry Batkovich + */ +public class GuavaOptionalConversionRule extends BaseGuavaTypeConversionRule { + public final static String GUAVA_OPTIONAL = "com.google.common.base.Optional"; + + @Nullable + @Override + protected TypeConversionDescriptorBase findConversionForMethod(@NotNull PsiType from, + @NotNull PsiType to, + @NotNull PsiMethod method, + String methodName, + PsiExpression context, + TypeMigrationLabeler labeler) { + if ("or".equals(methodName)) { + PsiMethodCallExpression methodCallExpression = null; + if (context instanceof PsiMethodCallExpression) { + final PsiParameter[] parameters = method.getParameterList().getParameters(); + if (parameters.length != 1) { + return null; + } + final PsiClass aClass = PsiTypesUtil.getPsiClass(parameters[0].getType()); + if (aClass != null) { + final String qName = aClass.getQualifiedName(); + if (GUAVA_OPTIONAL.equals(qName)) { + return new TypeConversionDescriptor("$expr$", "java.util.Optional.ofNullable($expr$.get())"); + } + else if ("com.google.common.bas.Supplier".equals(qName)) { + return new TypeConversionDescriptor("$expr$", "java.util.Optional.ofNullable($expr$)"); + } + else { + return new TypeConversionDescriptor("$expr$", "java.util.Optional.ofNullable($expr$)"); + } + } + return null; + } else if (context.getParent() instanceof PsiMethodCallExpression) { + methodCallExpression = (PsiMethodCallExpression)context.getParent(); + } + if (methodCallExpression == null) { + return null; + } + final PsiParameter[] parameters = method.getParameterList().getParameters(); + if (parameters.length != 1) { + return null; + } + final PsiClass aClass = PsiTypesUtil.getPsiClass(parameters[0].getType()); + if (aClass != null) { + final String qName = aClass.getQualifiedName(); + if (GUAVA_OPTIONAL.equals(qName)) { + return new TypeConversionDescriptor("$val$.or($other$)", "java.util.Optional.ofNullable($val$.orElseGet($other$::get))", to); + } + else if ("com.google.common.bas.Supplier".equals(qName)) { + return new TypeConversionDescriptor("$val$.or($other$)", "$val$.orElseGet($other$::get)"); + } + else { + return new TypeConversionDescriptor("$val$.or($other$)", "$val$.orElse($other$)"); + } + } + return null; + } + return null; + } + + @Override + protected void fillSimpleDescriptors(Map descriptorsMap) { + descriptorsMap.put("absent", new TypeConversionDescriptor("Optional.absent()", "java.util.Optional.empty()") { + @Override + public PsiExpression replace(PsiExpression expression) { + assert expression instanceof PsiMethodCallExpression; + + final PsiReferenceParameterList typeArguments = ((PsiMethodCallExpression)expression).getTypeArgumentList(); + PsiReferenceParameterList typeArgumentsCopy = + typeArguments.getTypeArguments().length == 0 ? null : (PsiReferenceParameterList)typeArguments.copy(); + final PsiMethodCallExpression replacedExpression = (PsiMethodCallExpression)super.replace(expression); + if (typeArgumentsCopy != null) { + replacedExpression.getTypeArgumentList().replace(typeArgumentsCopy); + } + return replacedExpression; + } + + }); + + descriptorsMap.put("of", new TypeConversionDescriptor("Optional.of($ref$)", "java.util.Optional.of($ref$)")); + descriptorsMap.put("fromNullable", new TypeConversionDescriptor("Optional.fromNullable($ref$)", "java.util.Optional.ofNullable($ref$)")); + descriptorsMap.put("presentInstances", new TypeConversionDescriptor("Optional.presentInstances($it$)", "java.util.stream.StreamSupport.stream($it$.spliterator(), false).map(java.util.Optional::get).collect(java.util.Collectors.toList())")); + + final TypeConversionDescriptorBase identity = new TypeConversionDescriptorBase(); + descriptorsMap.put("get", identity); + descriptorsMap.put("isPresent", identity); + descriptorsMap.put("orNull", new TypeConversionDescriptor("$val$.orNull()", "$val$.orElse(null)")); + descriptorsMap.put("asSet", new TypeConversionDescriptor("$val$.asSet()", + "$val$.isPresent() ? java.util.Collections.singleton($val$.get()) : java.util.Collections.emptySet()")); + descriptorsMap.put("transform", new TypeConversionDescriptor("$val$.transform($fun$)", "$val$.map($fun$)")); + } + + @NotNull + @Override + public String ruleFromClass() { + return "com.google.common.base.Optional"; + } + + @NotNull + @Override + public String ruleToClass() { + return "java.util.Optional"; + } +} diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaPredicateConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaPredicateConversionRule.java new file mode 100644 index 000000000000..2b1c4a4032ca --- /dev/null +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaPredicateConversionRule.java @@ -0,0 +1,52 @@ +/* + * 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.refactoring.typeMigration.rules.guava; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.psi.*; +import com.intellij.refactoring.typeMigration.TypeConversionDescriptor; +import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase; +import com.intellij.refactoring.typeMigration.TypeMigrationLabeler; +import com.intellij.util.Function; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Map; + +/** + * @author Dmitry Batkovich + */ +public class GuavaPredicateConversionRule extends BaseGuavaTypeConversionRule { + @Override + protected void fillSimpleDescriptors(Map descriptorsMap) { + descriptorsMap.put("apply", new TypeConversionDescriptor("$q$.apply($o$)", "$q$.test($o$)")); + } + + @NotNull + @Override + public String ruleFromClass() { + return "com.google.common.base.Predicate"; + } + + @NotNull + @Override + public String ruleToClass() { + return "java.util.function.Predicate"; + } +} diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/LambdaParametersTypeConversionDescription.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/LambdaParametersTypeConversionDescription.java new file mode 100644 index 000000000000..7bcb9f3e8e4c --- /dev/null +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/LambdaParametersTypeConversionDescription.java @@ -0,0 +1,67 @@ +/* + * 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.refactoring.typeMigration.rules.guava; + +import com.intellij.codeInspection.AnonymousCanBeLambdaInspection; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.psi.*; +import com.intellij.refactoring.typeMigration.TypeConversionDescriptor; +import org.jetbrains.annotations.NonNls; + +/** + * @author Dmitry Batkovich + */ +public class LambdaParametersTypeConversionDescription extends TypeConversionDescriptor { + private static final Logger LOG = Logger.getInstance(LambdaParametersTypeConversionDescription.class); + + public LambdaParametersTypeConversionDescription(@NonNls String stringToReplace, @NonNls String replaceByString) { + super(stringToReplace, replaceByString); + } + + @Override + public PsiExpression replace(PsiExpression expression) { + LOG.assertTrue(expression instanceof PsiMethodCallExpression); + PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expression; + final PsiExpression[] arguments = methodCall.getArgumentList().getExpressions(); + if (arguments.length == 1) { + final PsiExpression functionArg = arguments[0]; + convertParameter(functionArg); + } + return super.replace(expression); + } + + private static PsiExpression addApplyReference(final PsiExpression expression) { + return (PsiExpression)expression.replace(JavaPsiFacade.getElementFactory(expression.getProject()).createExpressionFromText(expression.getText() + "::apply", null)); + } + + public static PsiExpression convertParameter(PsiExpression expression) { + if (expression instanceof PsiNewExpression) { + final PsiAnonymousClass anonymousClass = ((PsiNewExpression)expression).getAnonymousClass(); + if (anonymousClass != null) { + if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(anonymousClass, true)) { + AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, true); + } + } + else { + return addApplyReference(expression); + } + } + else if (!(expression instanceof PsiFunctionalExpression)) { + return addApplyReference(expression); + } + return expression; + } +} diff --git a/java/typeMigration/test/com/intellij/codeInsight/inspections/GuavaInspectionTest.java b/java/typeMigration/test/com/intellij/codeInsight/inspections/GuavaInspectionTest.java new file mode 100644 index 000000000000..70643e91f4e4 --- /dev/null +++ b/java/typeMigration/test/com/intellij/codeInsight/inspections/GuavaInspectionTest.java @@ -0,0 +1,77 @@ +/* + * 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.codeInsight.inspections; + +import com.intellij.codeInsight.daemon.impl.quickfix.VariableTypeFix; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.openapi.application.PathManager; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.refactoring.typeMigration.inspections.GuavaInspection; +import com.intellij.testFramework.IdeaTestUtil; +import com.intellij.testFramework.PlatformTestUtil; +import com.intellij.testFramework.builders.JavaModuleFixtureBuilder; +import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase; + +import java.io.File; + +/** + * @author Dmitry Batkovich + */ +public class GuavaInspectionTest extends JavaCodeInsightFixtureTestCase { + + @Override + protected String getTestDataPath() { + return PlatformTestUtil.getCommunityPath() + "/java/typeMigration/testData/inspections/guava"; + } + + @Override + protected void tuneFixture(JavaModuleFixtureBuilder moduleBuilder) throws Exception { + moduleBuilder.setLanguageLevel(LanguageLevel.JDK_1_8); + moduleBuilder.addLibraryJars("guava-17.0.jar", PathManager.getHomePath().replace(File.separatorChar, '/') + "/community/lib/", + "guava-17.0.jar"); + moduleBuilder.addLibraryJars("guava-17.0.jar-2", PathManager.getHomePath().replace(File.separatorChar, '/') + "/lib/", + "guava-17.0.jar"); + moduleBuilder.addJdk(IdeaTestUtil.getMockJdk18Path().getPath()); + } + + public void _testOptional() { + doTest(); + } + + public void _testOptional2() { + doTest(); + } + + public void _testSimpleFluentIterable() { + doTest(); + } + + private void doTest() { + myFixture.configureByFile(getTestName(true) + ".java"); + myFixture.enableInspections(new GuavaInspection()); + boolean actionFound = false; + myFixture.doHighlighting(); + for (IntentionAction action : myFixture.getAvailableIntentions()) { + if (action instanceof VariableTypeFix) { + myFixture.launchAction(action); + actionFound = true; + break; + } + } + assertTrue(actionFound); + myFixture.checkResultByFile(getTestName(true) + "_after.java"); + } +} diff --git a/java/typeMigration/testData/inspections/guava/optional.java b/java/typeMigration/testData/inspections/guava/optional.java new file mode 100644 index 000000000000..a19588bdef78 --- /dev/null +++ b/java/typeMigration/testData/inspections/guava/optional.java @@ -0,0 +1,17 @@ +import com.google.common.base.Optional; + +class A { + + void m() { + Optional o = Optional.absent(); + String s = o.get(); + Optional yetAnotherOptional = Optional.fromNullable("bla-bla"); + System.out.println(o.or(yetAnotherOptional)); + if (o.isPresent()) { + System.out.println(o.get()); + } + System.out.println(o.orNull()); + Set set = o.asSet(); + } + +} \ No newline at end of file diff --git a/java/typeMigration/testData/inspections/guava/optional2.java b/java/typeMigration/testData/inspections/guava/optional2.java new file mode 100644 index 000000000000..4fffaebceba7 --- /dev/null +++ b/java/typeMigration/testData/inspections/guava/optional2.java @@ -0,0 +1,17 @@ +import com.google.common.base.Optional; + +class A { + + Optional m1() { + Optional o = Optional.absent(); + String s = o.get(); + return o; + } + + void w1() { + Optional someVar = m1(); + System.out.println(someVar.get()); + System.out.println(someVar.isPresent() ? Collections.singleton(someVar.get()) : Collections.emptySet()); + } + +} \ No newline at end of file diff --git a/java/typeMigration/testData/inspections/guava/optional2_after.java b/java/typeMigration/testData/inspections/guava/optional2_after.java new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/java/typeMigration/testData/inspections/guava/optional_after.java b/java/typeMigration/testData/inspections/guava/optional_after.java new file mode 100644 index 000000000000..e957e38b1690 --- /dev/null +++ b/java/typeMigration/testData/inspections/guava/optional_after.java @@ -0,0 +1,19 @@ +import com.google.common.base.Optional; + +import java.util.Collections; + +class A { + + void m() { + java.util.Optional o = java.util.Optional.empty(); + String s = o.get(); + Optional yetAnotherOptional = Optional.fromNullable("bla-bla"); + System.out.println(java.util.Optional.ofNullable(o.orElseGet(yetAnotherOptional::get))); + if (o.isPresent()) { + System.out.println(o.get()); + } + System.out.println(o.orElse(null)); + Set set = o.isPresent() ? Collections.singleton(o.get()) : Collections.emptySet(); + } + +} \ No newline at end of file diff --git a/java/typeMigration/testData/inspections/guava/simpleFluentIterable.java b/java/typeMigration/testData/inspections/guava/simpleFluentIterable.java new file mode 100644 index 000000000000..4d8950f335c5 --- /dev/null +++ b/java/typeMigration/testData/inspections/guava/simpleFluentIterable.java @@ -0,0 +1,32 @@ +import com.google.common.base.Function; +import com.google.common.collect.FluentIterable; + +import java.util.ArrayList; +import java.util.List; + +class A { + + Function myFunction = new Function() { + @Override + public String apply(String input) { + return input.trim(); + } + }; + + List main(String[] args) { + ArrayList strings = new ArrayList(); + FluentIterable it = FluentIterable.from(strings); + it = it.transform(input -> input.intern()); + it = it.transform(String::trim); + it = it.transform(new Function() { + @Override + public String apply(String input) { + System.out.println("do some action on " + input); + return input.substring(0, 10); + } + }); + it = it.transform(myFunction); + return it.toList(); + } + +} \ No newline at end of file diff --git a/java/typeMigration/testData/inspections/guava/simpleFluentIterable_after.java b/java/typeMigration/testData/inspections/guava/simpleFluentIterable_after.java new file mode 100644 index 000000000000..4d16b0e25980 --- /dev/null +++ b/java/typeMigration/testData/inspections/guava/simpleFluentIterable_after.java @@ -0,0 +1,30 @@ +import com.google.common.base.Function; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +class A { + + Function myFunction = new Function() { + @Override + public String apply(String input) { + return input.trim(); + } + }; + + List main(String[] args) { + ArrayList strings = new ArrayList(); + Stream it = strings.stream(); + it = it.map(input -> input.intern()); + it = it.map(String::trim); + it = it.map(input -> { + System.out.println("do some action on " + input); + return input.substring(0, 10); + }); + it = it.map(myFunction::apply); + return it.collect(Collectors.toList()); + } + +} \ No newline at end of file diff --git a/resources-en/src/inspectionDescriptions/Guava.html b/resources-en/src/inspectionDescriptions/Guava.html new file mode 100644 index 000000000000..2e21dbe99177 --- /dev/null +++ b/resources-en/src/inspectionDescriptions/Guava.html @@ -0,0 +1,6 @@ + + +Inspection detects usages of Guava's functional primitives like +c.g.c.c.FluentIterable, c.g.c.b.Optional, c.g.c.b.Function and c.g.c.b.Supplier. + + \ No newline at end of file