From 507388b7a501a81435b4e8f0bf1fd91c3d23a5f1 Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Wed, 1 Jul 2015 14:51:30 +0300 Subject: [PATCH] StaticPseudoFunctionalStyleMethodInspection: more clear options, validation of obtained result, part of problems with type parameters are fixed --- .../impl/quickfix/AddTypeArgumentsFix.java | 107 +-- .../java18StreamApi/AddMethodsDialog.form | 134 ++-- .../java18StreamApi/AddMethodsDialog.java | 162 ++++- .../PseudoLambdaReplaceTemplate.java | 629 ++++++++++++++++++ ...PseudoFunctionalStyleMethodInspection.java | 273 +------- ...ticPseudoFunctionalStyleMethodOptions.java | 56 +- .../lambdaLibsStatic/diamondResolve/test.java | 13 + .../diamondResolve/test_after.java | 14 + .../lambdaIsntAnonymous2/test_after.java | 2 +- .../missingTypeParameters/test.java | 13 + .../missingTypeParameters/test_after.java | 14 + .../transformLambda/test.java | 19 + .../transformLambda/test_after.java | 17 + .../transformMethodCalling/test.java | 17 + .../transformMethodCalling/test_after.java | 20 + .../transformMethodRef/test.java | 21 + .../transformMethodRef/test_after.java | 20 + ...StaticPseudoFunctionalStyleMethodTest.java | 19 +- .../StaticPseudoFunctionalStyleMethod.html | 6 + resources/src/META-INF/IdeaPlugin.xml | 2 +- 20 files changed, 1152 insertions(+), 406 deletions(-) create mode 100644 java/java-impl/src/com/intellij/codeInspection/java18StreamApi/PseudoLambdaReplaceTemplate.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/diamondResolve/test.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/diamondResolve/test_after.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/missingTypeParameters/test.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/missingTypeParameters/test_after.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/transformLambda/test.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/transformLambda/test_after.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/transformMethodCalling/test.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/transformMethodCalling/test_after.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/transformMethodRef/test.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/transformMethodRef/test_after.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddTypeArgumentsFix.java b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddTypeArgumentsFix.java index b0a80a0a7fa9..7ee7ea1c245d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddTypeArgumentsFix.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddTypeArgumentsFix.java @@ -25,6 +25,7 @@ import com.intellij.psi.util.TypeConversionUtil; import com.intellij.refactoring.util.RefactoringChangeUtil; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class AddTypeArgumentsFix extends MethodArgumentFix { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.AddTypeArgumentsFix"); @@ -51,57 +52,7 @@ public class AddTypeArgumentsFix extends MethodArgumentFix { @Override protected PsiExpression getModifiedArgument(final PsiExpression expression, final PsiType toType) throws IncorrectOperationException { - if (!PsiUtil.isLanguageLevel5OrHigher(expression)) return null; - - if (expression instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expression; - final PsiReferenceParameterList list = methodCall.getMethodExpression().getParameterList(); - if (list == null || list.getTypeArguments().length > 0) return null; - final JavaResolveResult resolveResult = methodCall.resolveMethodGenerics(); - final PsiElement element = resolveResult.getElement(); - if (element instanceof PsiMethod) { - final PsiMethod method = (PsiMethod)element; - final PsiType returnType = method.getReturnType(); - if (returnType == null) return null; - - final PsiTypeParameter[] typeParameters = method.getTypeParameters(); - if (typeParameters.length > 0) { - PsiType[] mappings = PsiType.createArray(typeParameters.length); - PsiResolveHelper helper = JavaPsiFacade.getInstance(expression.getProject()).getResolveHelper(); - LanguageLevel level = PsiUtil.getLanguageLevel(expression); - for (int i = 0; i < typeParameters.length; i++) { - PsiTypeParameter typeParameter = typeParameters[i]; - final PsiType substitution = helper.getSubstitutionForTypeParameter(typeParameter, returnType, toType, false, level); - if (substitution == null || PsiType.NULL.equals(substitution)) return null; - mappings[i] = GenericsUtil.eliminateWildcards(substitution, false); - } - - final PsiElementFactory factory = JavaPsiFacade.getInstance(expression.getProject()).getElementFactory(); - PsiMethodCallExpression copy = (PsiMethodCallExpression)expression.copy(); - final PsiReferenceExpression methodExpression = copy.getMethodExpression(); - final PsiReferenceParameterList parameterList = methodExpression.getParameterList(); - LOG.assertTrue(parameterList != null); - for (PsiType mapping : mappings) { - parameterList.add(factory.createTypeElement(mapping)); - } - if (methodExpression.getQualifierExpression() == null) { - final PsiExpression qualifierExpression; - final PsiClass containingClass = method.getContainingClass(); - LOG.assertTrue(containingClass != null); - if (method.hasModifierProperty(PsiModifier.STATIC)) { - qualifierExpression = factory.createReferenceExpression(containingClass); - } else { - qualifierExpression = RefactoringChangeUtil.createThisExpression(method.getManager(), null); - } - methodExpression.setQualifierExpression(qualifierExpression); - } - - return (PsiExpression)JavaCodeStyleManager.getInstance(copy.getProject()).shortenClassReferences(copy); - } - } - } - - return null; + return addTypeArguments(expression, toType); } @Override @@ -110,5 +61,59 @@ public class AddTypeArgumentsFix extends MethodArgumentFix { } } + @Nullable + public static PsiExpression addTypeArguments(PsiExpression expression, PsiType toType) { + if (!PsiUtil.isLanguageLevel5OrHigher(expression)) return null; + + if (expression instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expression; + final PsiReferenceParameterList list = methodCall.getMethodExpression().getParameterList(); + if (list == null || list.getTypeArguments().length > 0) return null; + final JavaResolveResult resolveResult = methodCall.resolveMethodGenerics(); + final PsiElement element = resolveResult.getElement(); + if (element instanceof PsiMethod) { + final PsiMethod method = (PsiMethod)element; + final PsiType returnType = method.getReturnType(); + if (returnType == null) return null; + + final PsiTypeParameter[] typeParameters = method.getTypeParameters(); + if (typeParameters.length > 0) { + PsiType[] mappings = PsiType.createArray(typeParameters.length); + PsiResolveHelper helper = JavaPsiFacade.getInstance(expression.getProject()).getResolveHelper(); + LanguageLevel level = PsiUtil.getLanguageLevel(expression); + for (int i = 0; i < typeParameters.length; i++) { + PsiTypeParameter typeParameter = typeParameters[i]; + final PsiType substitution = helper.getSubstitutionForTypeParameter(typeParameter, returnType, toType, false, level); + if (substitution == null || PsiType.NULL.equals(substitution)) return null; + mappings[i] = GenericsUtil.eliminateWildcards(substitution, false); + } + + final PsiElementFactory factory = JavaPsiFacade.getInstance(expression.getProject()).getElementFactory(); + PsiMethodCallExpression copy = (PsiMethodCallExpression)expression.copy(); + final PsiReferenceExpression methodExpression = copy.getMethodExpression(); + final PsiReferenceParameterList parameterList = methodExpression.getParameterList(); + LOG.assertTrue(parameterList != null); + for (PsiType mapping : mappings) { + parameterList.add(factory.createTypeElement(mapping)); + } + if (methodExpression.getQualifierExpression() == null) { + final PsiExpression qualifierExpression; + final PsiClass containingClass = method.getContainingClass(); + LOG.assertTrue(containingClass != null); + if (method.hasModifierProperty(PsiModifier.STATIC)) { + qualifierExpression = factory.createReferenceExpression(containingClass); + } else { + qualifierExpression = RefactoringChangeUtil.createThisExpression(method.getManager(), null); + } + methodExpression.setQualifierExpression(qualifierExpression); + } + + return (PsiExpression)JavaCodeStyleManager.getInstance(copy.getProject()).shortenClassReferences(copy); + } + } + } + return null; + } + public static ArgumentFixerActionFactory REGISTRAR = new AddTypeArgumentsFix.MyFixerActionFactory(); } diff --git a/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/AddMethodsDialog.form b/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/AddMethodsDialog.form index 6c448f4a4e8d..fdd485c53036 100644 --- a/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/AddMethodsDialog.form +++ b/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/AddMethodsDialog.form @@ -1,6 +1,6 @@
- + @@ -8,53 +8,103 @@ - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/AddMethodsDialog.java b/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/AddMethodsDialog.java index 8a8461045f36..d84d67e879f4 100644 --- a/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/AddMethodsDialog.java +++ b/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/AddMethodsDialog.java @@ -16,27 +16,36 @@ package com.intellij.codeInspection.java18StreamApi; +import com.intellij.codeInsight.intention.impl.config.ActionUsagePanel; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.event.DocumentAdapter; import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.psi.JavaPsiFacade; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiMethod; -import com.intellij.psi.PsiModifier; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.refactoring.ui.ClassNameReferenceEditor; +import com.intellij.ui.CollectionComboBoxModel; import com.intellij.ui.ColoredListCellRenderer; +import com.intellij.ui.ListCellRendererWrapper; import com.intellij.ui.SimpleTextAttributes; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.util.*; +import java.util.List; /** * @author Dmitry Batkovich @@ -46,36 +55,94 @@ public class AddMethodsDialog extends DialogWrapper { @NotNull private final Project myProject; private JPanel myPanel; - private ComboBox myPatternsCombo; + private ComboBox myTemplatesCombo; private ClassNameReferenceEditor myClassNameEditor; private ComboBox myMethodNameCombo; + private ActionUsagePanel myBeforeActionPanel; + private ActionUsagePanel myAfterActionPanel; + private JPanel myExamplePanel; @SuppressWarnings("unchecked") protected AddMethodsDialog(@NotNull final Project project, @NotNull final Component parent, boolean canBeParent) { super(parent, canBeParent); myProject = project; - final DefaultComboBoxModel model = new DefaultComboBoxModel(); - myPatternsCombo.setModel(model); - for (String methodName : StreamApiConstants.STREAM_STREAM_API_METHODS.getValue()) { - model.addElement(methodName); - } - model.addElement(StreamApiConstants.FAKE_FIND_MATCHED); - myPatternsCombo.setRenderer(new ColoredListCellRenderer() { + myTemplatesCombo.setEnabled(false); + myTemplatesCombo.setRenderer(new ColoredListCellRenderer() { @Override - protected void customizeCellRenderer(JList list, String methodName, int index, boolean selected, boolean hasFocus) { + protected void customizeCellRenderer(JList list, + PseudoLambdaReplaceTemplate template, + int index, + boolean selected, + boolean hasFocus) { + if (template == null) { + return; + } append("stream."); - if (StreamApiConstants.STREAM_STREAM_API_METHODS.getValue().contains(methodName)) { - append(methodName + "()", SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); + final String streamApiMethodName = template.getStreamApiMethodName(); + if (StreamApiConstants.STREAM_STREAM_API_METHODS.getValue().contains(streamApiMethodName)) { + append(streamApiMethodName + "()", SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); } else { - LOG.assertTrue(StreamApiConstants.FAKE_FIND_MATCHED.equals(methodName)); + LOG.assertTrue(StreamApiConstants.FAKE_FIND_MATCHED.equals(streamApiMethodName)); append(String.format(StreamApiConstants.FAKE_FIND_MATCHED_PATTERN, "condition"), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); append(" or "); - append(String.format(StreamApiConstants.FAKE_FIND_MATCHED_WITH_DEFAULT_PATTERN, "condition", "defaultValue"), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); + append(".orElseGet(() -> defaultValue)", SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); + } + } + }); + myTemplatesCombo.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + final PseudoLambdaReplaceTemplate template = (PseudoLambdaReplaceTemplate)e.getItem(); + final Collection methods = (Collection)myMethodNameCombo.getSelectedItem(); + if (methods == null) { + return; + } + for (PsiMethod method : methods) { + if (template.validate(method) != null) { + showTemplateExample(template, method); + break; + } } } }); myMethodNameCombo.setModel(new DefaultComboBoxModel()); + myMethodNameCombo.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (!myExamplePanel.isEnabled()) { + myExamplePanel.setEnabled(true); + } + final Collection suitableTemplates = new LinkedHashSet(); + final Collection methods = (Collection) e.getItem(); + for (PseudoLambdaReplaceTemplate template : PseudoLambdaReplaceTemplate.getAllTemplates()) { + for (PsiMethod method : methods) { + if (template.validate(method) != null) { + if (suitableTemplates.isEmpty()) { + showTemplateExample(template, method); + } + suitableTemplates.add(template); + } + } + } + if (!myTemplatesCombo.isEnabled()) { + myTemplatesCombo.setEnabled(true); + } + LOG.assertTrue(!suitableTemplates.isEmpty()); + final List templatesAsList = new ArrayList(suitableTemplates); + myTemplatesCombo.setModel(new CollectionComboBoxModel(templatesAsList)); + myTemplatesCombo.setSelectedItem(templatesAsList.get(0)); + } + }); + myMethodNameCombo.setRenderer(new ListCellRendererWrapper>() { + @Override + public void customize(JList list, Collection methods, int index, boolean selected, boolean hasFocus) { + if (methods != null) { + LOG.assertTrue(!methods.isEmpty()); + setText(ContainerUtil.getFirstItem(methods).getName()); + } + } + }); myClassNameEditor.addDocumentListener(new DocumentAdapter() { @Override public void documentChanged(DocumentEvent e) { @@ -87,34 +154,79 @@ public class AddMethodsDialog extends DialogWrapper { myMethodNameCombo.setEnabled(false); } else { - for (String name : ContainerUtil.newTreeSet(ContainerUtil.mapNotNull(aClass.getMethods(), new Function() { + final List possibleTemplates = PseudoLambdaReplaceTemplate.getAllTemplates(); + final MultiMap nameToMethod = new MultiMap(); + for (PsiMethod m : ContainerUtil.filter(aClass.getMethods(), new Condition() { @Override - public String fun(PsiMethod method) { + public boolean value(PsiMethod method) { if (method.isConstructor() || !method.hasModifierProperty(PsiModifier.STATIC) || method.hasModifierProperty(PsiModifier.PRIVATE)) { - return null; + return false; } - return method.getName(); + boolean templateFound = false; + for (PseudoLambdaReplaceTemplate template : possibleTemplates) { + if (template.validate(method) != null) { + templateFound = true; + } + } + if (!templateFound) { + return false; + } + return true; } - }))) { - comboBoxModel.addElement(name); + })) { + nameToMethod.putValue(m.getName(), m); + } + for (Map.Entry> entry : nameToMethod.entrySet()) { + comboBoxModel.addElement(entry.getValue()); } myMethodNameCombo.setEnabled(true); } } }); + + myExamplePanel.setEnabled(false); init(); } + private void showTemplateExample(final PseudoLambdaReplaceTemplate template, final PsiMethod method) { + final PsiClass aClass = method.getContainingClass(); + LOG.assertTrue(aClass != null); + final String fqn = aClass.getQualifiedName(); + LOG.assertTrue(fqn != null); + final String parameters = + StringUtil.join(ContainerUtil.map(method.getParameterList().getParameters(), new Function() { + @Override + public String fun(PsiParameter parameter) { + return parameter.getName(); + } + }), ", "); + final String expressionText = fqn + "." + method.getName() + "(" + parameters + ")"; + final PsiExpression psiExpression = JavaPsiFacade.getElementFactory(method.getProject()) + .createExpressionFromText(expressionText, null); + LOG.assertTrue(psiExpression instanceof PsiMethodCallExpression); + final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)psiExpression; + template.convertToStream(methodCallExpression, method, false); + myBeforeActionPanel.reset("void example() {\n " + methodCallExpression.getText() + ";\n}", StdFileTypes.JAVA); + myAfterActionPanel.reset("void example() {\n " + template.convertToStream(methodCallExpression, method, true).getText() + "\n}", StdFileTypes.JAVA); + } + + @Override + protected void dispose() { + Disposer.dispose(myBeforeActionPanel); + Disposer.dispose(myAfterActionPanel); + super.dispose(); + } + private void createUIComponents() { myClassNameEditor = new ClassNameReferenceEditor(myProject, null); } public StaticPseudoFunctionalStyleMethodOptions.PipelineElement getSelectedElement() { return new StaticPseudoFunctionalStyleMethodOptions.PipelineElement(myClassNameEditor.getText(), - (String)myMethodNameCombo.getSelectedItem(), - (String)myPatternsCombo.getSelectedItem()); + ContainerUtil.getFirstItem((Collection < PsiMethod >)myMethodNameCombo.getSelectedItem()).getName(), + (PseudoLambdaReplaceTemplate)myTemplatesCombo.getSelectedItem()); } @Nullable diff --git a/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/PseudoLambdaReplaceTemplate.java b/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/PseudoLambdaReplaceTemplate.java new file mode 100644 index 000000000000..4c425ed5d9d9 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/PseudoLambdaReplaceTemplate.java @@ -0,0 +1,629 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInspection.java18StreamApi; + +import com.intellij.codeInsight.daemon.impl.quickfix.AddTypeArgumentsFix; +import com.intellij.codeInspection.AnonymousCanBeLambdaInspection; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Condition; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.impl.PsiDiamondTypeUtil; +import com.intellij.psi.impl.PsiSubstitutorImpl; +import com.intellij.psi.impl.source.PsiClassReferenceType; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.util.*; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.hash.HashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * @author Dmitry Batkovich + */ +class PseudoLambdaReplaceTemplate { + private final static Logger LOG = Logger.getInstance(PseudoLambdaReplaceTemplate.class); + + public enum LambdaRole { + PREDICATE, + FUNCTION + } + + public static class ValidationInfo { + private final int myLambdaPosition; + private final int myIterablePosition; + private final int myDefaultValuePosition; + + public ValidationInfo(int lambdaPosition, int iterablePosition, int defaultValuePosition) { + myLambdaPosition = lambdaPosition; + myIterablePosition = iterablePosition; + myDefaultValuePosition = defaultValuePosition; + } + + public int getLambdaPosition() { + return myLambdaPosition; + } + + public int getIterablePosition() { + return myIterablePosition; + } + + public int getDefaultValuePosition() { + return myDefaultValuePosition; + } + } + + static final PseudoLambdaReplaceTemplate MAP = + new PseudoLambdaReplaceTemplate(StreamApiConstants.MAP, LambdaRole.FUNCTION, false); + static final PseudoLambdaReplaceTemplate FILTER = + new PseudoLambdaReplaceTemplate(StreamApiConstants.FILTER, LambdaRole.PREDICATE, false); + static final PseudoLambdaReplaceTemplate FIND = + new PseudoLambdaReplaceTemplate(StreamApiConstants.FAKE_FIND_MATCHED, LambdaRole.PREDICATE, true); + static final PseudoLambdaReplaceTemplate ALL_MATCH = + new PseudoLambdaReplaceTemplate(StreamApiConstants.ALL_MATCH, LambdaRole.PREDICATE, false); + static final PseudoLambdaReplaceTemplate ANY_MATCH = + new PseudoLambdaReplaceTemplate(StreamApiConstants.ANY_MATCH, LambdaRole.PREDICATE, false); + + private final String myStreamApiMethodName; + private final LambdaRole myLambdaRole; + private final boolean myAcceptDefaultValue; + + public PseudoLambdaReplaceTemplate(String method, + LambdaRole type, + boolean acceptDefaultValue) { + myStreamApiMethodName = method; + myLambdaRole = type; + myAcceptDefaultValue = acceptDefaultValue; + } + + public static List getAllTemplates() { + return ContainerUtil.newArrayList(MAP, FILTER, FIND, ALL_MATCH, ANY_MATCH); + } + + public ValidationInfo validate(final PsiMethod method) { + final PsiParameter[] parameters = method.getParameterList().getParameters(); + final PsiType[] parameterTypes = + ContainerUtil.map2Array(Arrays.asList(parameters), new PsiType[parameters.length], new Function() { + @Override + public PsiType fun(PsiParameter parameter) { + return parameter.getType(); + } + }); + final PsiType returnType = method.getReturnType(); + + if (returnType instanceof PsiClassType) { + final PsiClass resolvedReturnTypeClass = ((PsiClassType)returnType).resolve(); + if (!InheritanceUtil.isInheritor(resolvedReturnTypeClass, CommonClassNames.JAVA_LANG_ITERABLE)) { + return null; + } + } else if (!(returnType instanceof PsiArrayType)) { + return null; + } + return validate(parameterTypes, returnType, method); + } + + @Nullable + public ValidationInfo validate(final PsiMethodCallExpression expression) { + final PsiType[] argumentTypes = expression.getArgumentList().getExpressionTypes(); + final PsiType methodReturnType = expression.getType(); + if (methodReturnType == null) { + return null; + } + + final PsiMethod method = expression.resolveMethod(); + if (method == null) { + return null; + } + final PsiParameter[] expectedParameters = method.getParameterList().getParameters(); + + if (argumentTypes.length != expectedParameters.length) { + return null; + } + return validate(argumentTypes, methodReturnType, expression); + } + + public String getStreamApiMethodName() { + return myStreamApiMethodName; + } + + public LambdaRole getLambdaRole() { + return myLambdaRole; + } + + public boolean isAcceptDefaultValue() { + return myAcceptDefaultValue; + } + + private ValidationInfo validate(final PsiType[] arguments, final PsiType methodReturnType, final PsiElement context) { + int lambdaPosition = -1; + int defaultValuePosition = -1; + int iterablePosition = -1; + + if (!myAcceptDefaultValue) { + if (arguments.length != 2) { + return null; + } + } else { + if (arguments.length != 2 && arguments.length != 3) { + return null; + } + } + + for (int i = 0; i < arguments.length; i++) { + PsiType type = arguments[i]; + if (isFunction(type, methodReturnType, context)) { + if (lambdaPosition == -1) { + lambdaPosition = i; + continue; + } + else { + return null; + } + } + if (isIterableOrArray(type)) { + if (iterablePosition == -1) { + iterablePosition = i; + continue; + } + else { + return null; + } + } + if (myAcceptDefaultValue && methodReturnType.isAssignableFrom(type)) { + if (defaultValuePosition == -1) { + defaultValuePosition = i; + } + else { + return null; + } + } + } + + if (lambdaPosition == -1 || iterablePosition == -1) { + return null; + } + if (myAcceptDefaultValue) { + if (defaultValuePosition == -1 && arguments.length == 3) { + return null; + } + } + return new ValidationInfo(lambdaPosition, iterablePosition, defaultValuePosition); + } + + private boolean isFunction(PsiType type, PsiType baseMethodReturnType, PsiElement context) { + if (type instanceof PsiMethodReferenceType) { + final PsiMethodReferenceExpression expression = ((PsiMethodReferenceType)type).getExpression(); + final PsiMethod resolvedMethod = (PsiMethod)expression.resolve(); + if (resolvedMethod == null) { + return false; + } + final PsiParameter[] parameters = resolvedMethod.getParameterList().getParameters(); + if ((parameters.length != 1 && myLambdaRole == LambdaRole.FUNCTION) || (parameters.length != 0 && myLambdaRole == LambdaRole.PREDICATE)) { + return false; + } + final PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(expression); + return isSuitableLambdaRole(returnType, baseMethodReturnType, context); + } else if (type instanceof PsiLambdaExpressionType) { + final PsiLambdaExpression expression = ((PsiLambdaExpressionType)type).getExpression(); + final PsiType psiType = LambdaUtil.getFunctionalInterfaceReturnType(expression.getFunctionalInterfaceType()); + return isSuitableLambdaRole(psiType, baseMethodReturnType, context); + } else if (isSuitableFunctionalType(type, baseMethodReturnType, context)) { + return true; + } + return isJavaLangClassType(type) && myLambdaRole == LambdaRole.PREDICATE; + } + + private boolean isSuitableLambdaRole(final PsiType lambdaReturnType, + final PsiType baseMethodReturnType, + final PsiElement context) { + if (lambdaReturnType == null) { + return false; + } + if (myLambdaRole == LambdaRole.PREDICATE) { + final PsiClassType boxedBoolean = PsiType.BOOLEAN.getBoxedType(context); + if (!(PsiType.BOOLEAN.equals(lambdaReturnType) || (boxedBoolean != null && boxedBoolean.equals(lambdaReturnType)))) { + return false; + } + } + else { + LOG.assertTrue(myLambdaRole == LambdaRole.FUNCTION); + if (baseMethodReturnType instanceof PsiClassType) { + final PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)baseMethodReturnType).resolveGenerics(); + final Map substitutionMap = resolveResult.getSubstitutor().getSubstitutionMap(); + if (substitutionMap.size() != 1) { + return false; + } + final PsiType iterableParametrizedType = ContainerUtil.getFirstItem(substitutionMap.values()); + if (!lambdaReturnType.equals(iterableParametrizedType)) { + return false; + } + } + else if (baseMethodReturnType instanceof PsiArrayType) { + if (!lambdaReturnType.equals(((PsiArrayType)baseMethodReturnType).getComponentType())) { + return false; + } + } + } + return true; + } + + private static boolean isJavaLangClassType(PsiType type) { + if (type instanceof PsiClassType) { + final PsiClass resolvedClass = ((PsiClassType)type).resolve(); + if (resolvedClass != null && CommonClassNames.JAVA_LANG_CLASS.equals(resolvedClass.getQualifiedName())) { + return true; + } + } + return false; + } + + private boolean isSuitableFunctionalType(final PsiType type, final PsiType baseMethodReturnType, final PsiElement context) { + if (type instanceof PsiClassType) { + PsiClass targetClass = ((PsiClassType)type).resolve(); + if (targetClass != null) { + if (targetClass instanceof PsiAnonymousClass) { + targetClass = ((PsiAnonymousClass)targetClass).getBaseClassType().resolve(); + if (targetClass == null) { + return false; + } + } + if (!LambdaUtil.isFunctionalClass(targetClass)) { + return false; + } + PsiMethod method = LambdaUtil.getFunctionalInterfaceMethod(targetClass); + final PsiMethod[] methods = ((PsiClassType)type).resolve().findMethodsByName(method.getName(), false); + if (methods.length != 1) { + return false; + } + method = methods[0]; + if (method == null) { + return false; + } + final PsiType psiType; + if (type instanceof PsiClassReferenceType) { + final PsiJavaCodeReferenceElement reference = ((PsiClassReferenceType)type).getReference(); + final PsiClass resolvedClass = ((PsiClassReferenceType)type).resolve(); + final PsiTypeParameter[] typeParameters = resolvedClass.getTypeParameters(); + final PsiType[] substitutedTypeParameters = reference.getTypeParameters(); + + LOG.assertTrue(typeParameters.length == substitutedTypeParameters.length); + final Map substitutionMap = new HashMap(); + for (int i = 0; i < typeParameters.length; i++) { + PsiTypeParameter parameter = typeParameters[i]; + PsiType t = substitutedTypeParameters[i]; + substitutionMap.put(parameter, t); + } + PsiSubstitutor substitutor = PsiSubstitutorImpl.createSubstitutor(substitutionMap); + psiType = substitutor.substitute(method.getReturnType()); + } else { + psiType = method.getReturnType(); + } + return isSuitableLambdaRole(psiType, baseMethodReturnType, context); + } + return false; + } else { + return false; + } + } + + @NotNull + public PsiExpression convertToStream(final PsiMethodCallExpression expression, PsiMethod method, boolean force) { + LOG.assertTrue(expression != null); + if (method == null) { + method = expression.resolveMethod(); + if (method == null) { + return expression; + } + } + final ValidationInfo validationInfo = force ? validate(method) : validate(expression); + if (validationInfo == null) { + return expression; + } + final Project project = expression.getProject(); + int lambdaIndex = validationInfo.getLambdaPosition(); + + final PsiExpression[] expressions = expression.getArgumentList().getExpressions(); + PsiExpression lambdaExpression = expressions[lambdaIndex]; + if (!force) { + lambdaExpression = convertClassTypeExpression(lambdaExpression); + lambdaExpression = convertToJavaLambda(lambdaExpression, myStreamApiMethodName); + } + LOG.assertTrue(lambdaExpression != null); + + final PsiExpression iterableExpression = expressions[validationInfo.getIterablePosition()]; + final String pipelineHead = createPipelineHeadText(iterableExpression, lambdaExpression, force); + + + final String lambdaExpressionText; + final String elementText; + if (!StreamApiConstants.FAKE_FIND_MATCHED.equals(myStreamApiMethodName)) { + elementText = myStreamApiMethodName; + lambdaExpressionText = lambdaExpression.getText(); + } + else { + elementText = validationInfo.getDefaultValuePosition() != -1 + ? String.format(StreamApiConstants.FAKE_FIND_MATCHED_WITH_DEFAULT_PATTERN, lambdaExpression.getText(), + expressions[validationInfo.getDefaultValuePosition()].getText()) + : String.format(StreamApiConstants.FAKE_FIND_MATCHED_PATTERN, lambdaExpression.getText()); + lambdaExpressionText = null; + } + final String pipelineTail = + StreamApiConstants.STREAM_STREAM_API_METHODS.getValue().contains(myStreamApiMethodName) + ? findSuitableTailMethodForCollection(method) + : null; + + final PsiElement replaced = + expression.replace(createPipelineExpression(pipelineHead, elementText, lambdaExpressionText, pipelineTail, project)); + JavaCodeStyleManager.getInstance(project).shortenClassReferences(replaced.getParent()); + return (PsiExpression)replaced; + } + + private static PsiExpression createPipelineExpression(String pipelineHead, + String elementText, + String lambdaExpression, + String pipelineTail, + Project project) { + final StringBuilder sb = new StringBuilder(); + sb.append(pipelineHead).append(".").append(elementText); + if (lambdaExpression != null) { + sb.append("(").append(lambdaExpression).append(")"); + } + if (pipelineTail != null) { + sb.append(".").append(pipelineTail); + } + return JavaPsiFacade.getElementFactory(project).createExpressionFromText(sb.toString(), null); + } + + private static void convertNewExpression(PsiMethod containingMethod, PsiNewExpression newExpression, PsiClass expectedReturnClass) { + final String expectedReturnQName = expectedReturnClass.getQualifiedName(); + LOG.assertTrue(expectedReturnQName != null); + final PsiElementFactory factory = JavaPsiFacade.getElementFactory(newExpression.getProject()); + PsiAnonymousClass anonymousClass = PsiTreeUtil.findChildOfType(newExpression, PsiAnonymousClass.class); + LOG.assertTrue(anonymousClass != null); + PsiJavaCodeReferenceElement referenceElement = PsiTreeUtil.findChildOfType(anonymousClass, PsiJavaCodeReferenceElement.class); + LOG.assertTrue(referenceElement != null); + final PsiReferenceParameterList parameterList = PsiTreeUtil.findChildOfType(referenceElement, PsiReferenceParameterList.class); + final PsiJavaCodeReferenceElement newCodeReferenceElement = factory.createReferenceFromText(expectedReturnClass.getQualifiedName() + + + (parameterList == null + ? "" + : parameterList.getText()), null); + referenceElement.replace(newCodeReferenceElement); + final List methods = ContainerUtil.filter(anonymousClass.getMethods(), new Condition() { + @Override + public boolean value(PsiMethod method) { + return !"equals".equals(method.getName()); + } + }); + LOG.assertTrue(methods.size() == 1, methods); + final PsiMethod method = methods.get(0); + method.setName(expectedReturnClass.getMethods()[0].getName()); + final PsiTypeElement element = containingMethod.getReturnTypeElement(); + if (element != null) { + final PsiReferenceParameterList genericParameter = PsiTreeUtil.findChildOfType(element, PsiReferenceParameterList.class); + element.replace( + factory.createTypeElementFromText(expectedReturnQName + (genericParameter == null ? "" : genericParameter.getText()), null)); + } + } + + @Nullable + private static String findSuitableTailMethodForCollection(PsiMethod lambdaHandler) { + final PsiType type = lambdaHandler.getReturnType(); + if (type instanceof PsiArrayType) { + final PsiType arrayComponentType = ((PsiArrayType)type).getComponentType(); + return "toArray(" + arrayComponentType.getCanonicalText() + "[]::new)"; + } + else if (type instanceof PsiClassType) { + final PsiClass resolvedClass = ((PsiClassType)type).resolve(); + if (resolvedClass == null) { + return null; + } + final String qName = resolvedClass.getQualifiedName(); + if (qName == null) { + return null; + } + if (qName.equals(CommonClassNames.JAVA_UTIL_LIST) + || qName.equals(CommonClassNames.JAVA_UTIL_COLLECTION) + || qName.equals(CommonClassNames.JAVA_LANG_ITERABLE)) { + return "collect(" + StreamApiConstants.JAVA_UTIL_STREAM_COLLECTORS + ".toList())"; + } + else if (qName.equals(CommonClassNames.JAVA_UTIL_SET)) { + return "collect(" + StreamApiConstants.JAVA_UTIL_STREAM_COLLECTORS + ".toSet())"; + } + else if (qName.equals(CommonClassNames.JAVA_UTIL_ITERATOR)) { + return "iterator()"; + } + } + return null; + } + + private static PsiExpression convertToJavaLambda(PsiExpression expression, String streamApiMethodName) { + if (streamApiMethodName.equals(StreamApiConstants.FAKE_FIND_MATCHED)) { + streamApiMethodName = StreamApiConstants.FILTER; + } + if (expression instanceof PsiMethodReferenceExpression) { + return expression; + } + if (expression instanceof PsiLambdaExpression) { + return expression; + } + if (expression instanceof PsiMethodCallExpression) { + final PsiMethod method = ((PsiMethodCallExpression)expression).resolveMethod(); + if (method == null) { + return null; + } + final PsiType type = method.getReturnType(); + if (!(type instanceof PsiClassType)) { + return null; + } + final PsiClass lambdaClass = ((PsiClassType)type).resolve(); + if (lambdaClass == null) { + return null; + } + final String methodName = lambdaClass.getMethods()[0].getName(); + if (tryConvertPseudoLambdaToStreamApi(method, resolveStreamApiLambdaClass(expression.getProject(), streamApiMethodName))) { + return expression; + } + else { + return JavaPsiFacade.getElementFactory(expression.getProject()) + .createExpressionFromText(expression.getText() + "::" + methodName, null); + } + } + return AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, true); + } + + @NotNull + private static PsiClass resolveStreamApiLambdaClass(Project project, String streamApiMethodName) { + final PsiClass javaUtilStream = JavaPsiFacade.getInstance(project) + .findClass(StreamApiConstants.JAVA_UTIL_STREAM_STREAM, GlobalSearchScope.notScope(GlobalSearchScope.projectScope(project))); + LOG.assertTrue(javaUtilStream != null); + final PsiMethod[] methods = javaUtilStream.findMethodsByName(streamApiMethodName, false); + LOG.assertTrue(methods.length == 1); + final PsiMethod method = methods[0]; + final PsiParameter[] parameters = method.getParameterList().getParameters(); + LOG.assertTrue(parameters.length == 1); + final PsiType type = parameters[0].getType(); + LOG.assertTrue(type instanceof PsiClassType); + final PsiClass resolved = ((PsiClassType)type).resolve(); + LOG.assertTrue(resolved != null); + return resolved; + } + + private static boolean tryConvertPseudoLambdaToStreamApi(final @NotNull PsiMethod method, final @NotNull PsiClass expectedReturnClass) { + final PsiType currentReturnType = method.getReturnType(); + if (!(currentReturnType instanceof PsiClassType)) { + LOG.error("pseudo-lambda return type must be class " + currentReturnType); + return true; + } + final PsiClass resolvedCurrentReturnType = ((PsiClassType)currentReturnType).resolve(); + if (expectedReturnClass.getManager().areElementsEquivalent(expectedReturnClass, resolvedCurrentReturnType)) { + return true; + } + final PsiCodeBlock body = method.getBody(); + Collection returnStatements = PsiTreeUtil.findChildrenOfType(body, PsiReturnStatement.class); + returnStatements = ContainerUtil.filter(returnStatements, new Condition() { + @Override + public boolean value(PsiReturnStatement statement) { + return PsiTreeUtil.getParentOfType(statement, PsiMethod.class) == method; + } + }); + if (returnStatements.size() != 1) { + return false; + } + final PsiReturnStatement returnStatement = ContainerUtil.getFirstItem(returnStatements); + assert returnStatement != null; + final PsiExpression returnValue = returnStatement.getReturnValue(); + if (returnValue instanceof PsiNewExpression) { + convertNewExpression(method, (PsiNewExpression)returnValue, expectedReturnClass); + return true; + } + else { + return false; + } + } + + @NotNull + private static PsiExpression convertClassTypeExpression(PsiExpression expression) { + final PsiType type = expression.getType(); + if (type instanceof PsiClassType) { + final PsiClass resolvedClass = ((PsiClassType)type).resolve(); + if (resolvedClass != null && CommonClassNames.JAVA_LANG_CLASS.equals(resolvedClass.getQualifiedName())) { + return JavaPsiFacade.getElementFactory(expression.getProject()) + .createExpressionFromText("(" + expression.getText() + ")::isInstance", null); + } + } + return expression; + } + + private static String createPipelineHeadText(PsiExpression collectionExpression, PsiExpression lambdaExpression, boolean force) { + if (collectionExpression instanceof PsiNewExpression) { + final PsiDiamondType.DiamondInferenceResult diamondResolveResult = + PsiDiamondTypeImpl.resolveInferredTypesNoCheck((PsiNewExpression)collectionExpression, collectionExpression); + if (!diamondResolveResult.getInferredTypes().isEmpty()) { + collectionExpression = PsiDiamondTypeUtil.expandTopLevelDiamondsInside(collectionExpression); + } + } + else if (collectionExpression instanceof PsiMethodCallExpression) { + final PsiType currentType = collectionExpression.getType(); + if (currentType == null) { + return null; + } + final PsiExpression copiedExpression = (PsiExpression) collectionExpression.copy(); + final PsiType newType = copiedExpression.getType(); + if (!currentType.equals(newType)) { + collectionExpression = AddTypeArgumentsFix.addTypeArguments(copiedExpression, currentType); + } + } + final PsiType type = collectionExpression.getType(); + if (type instanceof PsiClassType) { + final PsiClass resolved = ((PsiClassType)type).resolve(); + LOG.assertTrue(resolved != null && resolved.getQualifiedName() != null, type); + return collectionExpression.getText() + ".stream()"; + } + else if (type instanceof PsiArrayType) { + return CommonClassNames.JAVA_UTIL_ARRAYS + ".stream(" + collectionExpression.getText() + ")"; + } else if (force) { + return collectionExpression.getText() + ".stream()"; + } + throw new AssertionError("type: " + type + " is unexpected for expression: " + collectionExpression.getText()); + } + + private static boolean isIterableOrArray(final PsiType type) { + if (type instanceof PsiClassType) { + final PsiClass resolvedClass = ((PsiClassType)type).resolve(); + if (resolvedClass != null) { + return InheritanceUtil.isInheritor(resolvedClass, CommonClassNames.JAVA_LANG_ITERABLE); + } + } + else if (type instanceof PsiArrayType) { + return true; + } + return false; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + PseudoLambdaReplaceTemplate template = (PseudoLambdaReplaceTemplate)o; + + if (myAcceptDefaultValue != template.myAcceptDefaultValue) return false; + if (!myStreamApiMethodName.equals(template.myStreamApiMethodName)) return false; + if (myLambdaRole != template.myLambdaRole) return false; + + return true; + } + + @Override + public int hashCode() { + int result = myStreamApiMethodName.hashCode(); + result = 31 * result + myLambdaRole.hashCode(); + result = 31 * result + (myAcceptDefaultValue ? 1 : 0); + return result; + } + + @Override + public String toString() { + return "PseudoLambdaReplaceTemplate{" + + "myStreamApiMethodName='" + myStreamApiMethodName + '\'' + + '}'; + } +} diff --git a/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/StaticPseudoFunctionalStyleMethodInspection.java b/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/StaticPseudoFunctionalStyleMethodInspection.java index 47ff9eb5e6fa..398aabc0ff20 100644 --- a/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/StaticPseudoFunctionalStyleMethodInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/StaticPseudoFunctionalStyleMethodInspection.java @@ -18,16 +18,12 @@ package com.intellij.codeInspection.java18StreamApi; import com.intellij.codeInspection.*; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; -import com.intellij.psi.codeStyle.JavaCodeStyleManager; -import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; -import com.intellij.util.containers.ContainerUtil; import org.jdom.Element; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -35,7 +31,6 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.Collection; -import java.util.List; /** * @author Dmitry Batkovich @@ -102,10 +97,9 @@ public class StaticPseudoFunctionalStyleMethodInspection extends BaseJavaBatchLo if (suitableHandler == null) { return; } - final int lambdaIndex = validateMethodParameters(methodCallExpression, method, suitableHandler.getStreamApiMethodName() == StreamApiConstants.FAKE_FIND_MATCHED); - if (lambdaIndex != -1) { - holder.registerProblem(methodCallExpression.getMethodExpression(), "", - new ReplacePseudoLambdaWithLambda(lambdaIndex, methodCallExpression, method, suitableHandler)); + final PseudoLambdaReplaceTemplate.ValidationInfo validationInfo = suitableHandler.getTemplate().validate(methodCallExpression); + if (validationInfo != null) { + holder.registerProblem(methodCallExpression.getMethodExpression(), "Pseudo functional style code", new ReplacePseudoLambdaWithLambda(suitableHandler)); } } } @@ -113,17 +107,10 @@ public class StaticPseudoFunctionalStyleMethodInspection extends BaseJavaBatchLo } public static class ReplacePseudoLambdaWithLambda implements LocalQuickFix { - private final int myLambdaIndex; - private final SmartPsiElementPointer myMethodPointer; - private final StaticPseudoFunctionalStyleMethodOptions.PipelineElement mySuitableHandler; + private final StaticPseudoFunctionalStyleMethodOptions.PipelineElement myHandler; - private ReplacePseudoLambdaWithLambda(int lambdaIndex, - @NotNull PsiMethodCallExpression expression, - @NotNull PsiMethod method, - @NotNull StaticPseudoFunctionalStyleMethodOptions.PipelineElement suitableHandler) { - myLambdaIndex = lambdaIndex; - myMethodPointer = SmartPointerManager.getInstance(expression.getProject()).createSmartPsiElementPointer(method); - mySuitableHandler = suitableHandler; + private ReplacePseudoLambdaWithLambda(StaticPseudoFunctionalStyleMethodOptions.PipelineElement handler) { + myHandler = handler; } @Nls @@ -143,253 +130,7 @@ public class StaticPseudoFunctionalStyleMethodInspection extends BaseJavaBatchLo public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { PsiMethodCallExpression expression = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiMethodCallExpression.class); LOG.assertTrue(expression != null); - final PsiExpression[] expressions = expression.getArgumentList().getExpressions(); - PsiExpression lambdaExpression = expressions[myLambdaIndex]; - lambdaExpression = convertClassTypeExpression(lambdaExpression); - lambdaExpression = convertToJavaLambda(lambdaExpression, mySuitableHandler.getStreamApiMethodName()); - LOG.assertTrue(lambdaExpression != null); - - final PsiExpression collectionExpression = expressions[(1 + myLambdaIndex) % 2]; - final String pipelineHead = createPipelineHeadText(collectionExpression); - - - final String lambdaExpressionText; - final String elementText; - if (!StreamApiConstants.FAKE_FIND_MATCHED.equals(mySuitableHandler.getStreamApiMethodName())) { - elementText = mySuitableHandler.getStreamApiMethodName(); - lambdaExpressionText = lambdaExpression.getText(); - } - else { - elementText = expressions.length == 3 - ? String.format(StreamApiConstants.FAKE_FIND_MATCHED_WITH_DEFAULT_PATTERN, lambdaExpression.getText(), expressions[2].getText()) - : String.format(StreamApiConstants.FAKE_FIND_MATCHED_PATTERN, lambdaExpression.getText()); - lambdaExpressionText = null; - } - final String pipelineTail = - StreamApiConstants.STREAM_STREAM_API_METHODS.getValue().contains(mySuitableHandler.getStreamApiMethodName()) - ? findSuitableTailMethodForCollection(myMethodPointer.getElement()) - : null; - - final PsiElement replaced = - expression.replace(createPipelineExpression(pipelineHead, elementText, lambdaExpressionText, pipelineTail, project)); - JavaCodeStyleManager.getInstance(project).shortenClassReferences(replaced.getParent()); + myHandler.getTemplate().convertToStream(expression, null, false); } - - @NotNull - private static PsiExpression convertClassTypeExpression(PsiExpression expression) { - final PsiType type = expression.getType(); - if (type instanceof PsiClassType) { - final PsiClass resolvedClass = ((PsiClassType)type).resolve(); - if (resolvedClass != null && CommonClassNames.JAVA_LANG_CLASS.equals(resolvedClass.getQualifiedName())) { - return JavaPsiFacade.getElementFactory(expression.getProject()) - .createExpressionFromText("(" + expression.getText() + ")::isInstance", null); - } - } - return expression; - } - - private static String createPipelineHeadText(PsiExpression collectionExpression) { - final PsiType type = collectionExpression.getType(); - if (type instanceof PsiClassType) { - final PsiClass resolved = ((PsiClassType)type).resolve(); - LOG.assertTrue(resolved != null && resolved.getQualifiedName() != null, type); - return collectionExpression.getText() + ".stream()"; - } - else if (type instanceof PsiArrayType) { - return CommonClassNames.JAVA_UTIL_ARRAYS + ".stream(" + collectionExpression.getText() + ")"; - } - throw new AssertionError("type: " + type + " is unexpected"); - } - - private static PsiExpression createPipelineExpression(String pipelineHead, - String elementText, - String lambdaExpression, - String pipelineTail, - Project project) { - final StringBuilder sb = new StringBuilder(); - sb.append(pipelineHead).append(".").append(elementText); - if (lambdaExpression != null) { - sb.append("(").append(lambdaExpression).append(")"); - } - if (pipelineTail != null) { - sb.append(".").append(pipelineTail); - } - return JavaPsiFacade.getElementFactory(project).createExpressionFromText(sb.toString(), null); - } - } - - private static int validateMethodParameters(final PsiMethodCallExpression methodCallExpression, final PsiMethod method, boolean canThirdParameterExist) { - final PsiType[] argumentTypes = methodCallExpression.getArgumentList().getExpressionTypes(); - final PsiParameter[] expectedParameters = method.getParameterList().getParameters(); - - if (argumentTypes.length != expectedParameters.length) { - return -1; - } - if (expectedParameters.length == 2 || (canThirdParameterExist && expectedParameters.length == 3)) { - final int collectionOrArrayIndex = findCollectionOrArrayPlacement(expectedParameters); - if (collectionOrArrayIndex == -1) { - return -1; - } - return (1 + collectionOrArrayIndex) % 2; - - } - return -1; - } - - private static int findCollectionOrArrayPlacement(final PsiParameter[] parameters) { - for (int i = 0, length = parameters.length; i < length; i++) { - PsiParameter parameter = parameters[i]; - final PsiType type = parameter.getType(); - if (type instanceof PsiClassType || type instanceof PsiArrayType) { - return i; - } - } - return -1; - } - - private static PsiExpression convertToJavaLambda(PsiExpression expression, String streamApiMethodName) { - if (streamApiMethodName.equals(StreamApiConstants.FAKE_FIND_MATCHED)) { - streamApiMethodName = StreamApiConstants.FILTER; - } - if (expression instanceof PsiMethodReferenceExpression) { - return expression; - } - if (expression instanceof PsiLambdaExpression) { - return expression; - } - if (expression instanceof PsiMethodCallExpression) { - final PsiMethod method = ((PsiMethodCallExpression)expression).resolveMethod(); - if (method == null) { - return null; - } - final PsiType type = method.getReturnType(); - if (!(type instanceof PsiClassType)) { - return null; - } - final PsiClass lambdaClass = ((PsiClassType)type).resolve(); - if (lambdaClass == null) { - return null; - } - final String methodName = lambdaClass.getMethods()[0].getName(); - if (tryConvertPseudoLambdaToStreamApi(method, resolveStreamApiLambdaClass(expression.getProject(), streamApiMethodName))) { - return expression; - } - else { - return JavaPsiFacade.getElementFactory(expression.getProject()) - .createExpressionFromText(expression.getText() + "::" + methodName, null); - } - } - return AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, true); - } - - @NotNull - private static PsiClass resolveStreamApiLambdaClass(Project project, String streamApiMethodName) { - final PsiClass javaUtilStream = JavaPsiFacade.getInstance(project) - .findClass(StreamApiConstants.JAVA_UTIL_STREAM_STREAM, GlobalSearchScope.notScope(GlobalSearchScope.projectScope(project))); - LOG.assertTrue(javaUtilStream != null); - final PsiMethod[] methods = javaUtilStream.findMethodsByName(streamApiMethodName, false); - LOG.assertTrue(methods.length == 1); - final PsiMethod method = methods[0]; - final PsiParameter[] parameters = method.getParameterList().getParameters(); - LOG.assertTrue(parameters.length == 1); - final PsiType type = parameters[0].getType(); - LOG.assertTrue(type instanceof PsiClassType); - final PsiClass resolved = ((PsiClassType)type).resolve(); - LOG.assertTrue(resolved != null); - return resolved; - } - - private static boolean tryConvertPseudoLambdaToStreamApi(final @NotNull PsiMethod method, final @NotNull PsiClass expectedReturnClass) { - final PsiType currentReturnType = method.getReturnType(); - if (!(currentReturnType instanceof PsiClassType)) { - LOG.error("pseudo-lambda return type must be class " + currentReturnType); - return true; - } - final PsiClass resolvedCurrentReturnType = ((PsiClassType)currentReturnType).resolve(); - if (expectedReturnClass.getManager().areElementsEquivalent(expectedReturnClass, resolvedCurrentReturnType)) { - return true; - } - final PsiCodeBlock body = method.getBody(); - Collection returnStatements = PsiTreeUtil.findChildrenOfType(body, PsiReturnStatement.class); - returnStatements = ContainerUtil.filter(returnStatements, new Condition() { - @Override - public boolean value(PsiReturnStatement statement) { - return PsiTreeUtil.getParentOfType(statement, PsiMethod.class) == method; - } - }); - if (returnStatements.size() != 1) { - return false; - } - final PsiReturnStatement returnStatement = ContainerUtil.getFirstItem(returnStatements); - assert returnStatement != null; - final PsiExpression returnValue = returnStatement.getReturnValue(); - if (returnValue instanceof PsiNewExpression) { - convertNewExpression(method, (PsiNewExpression)returnValue, expectedReturnClass); - return true; - } - else { - return false; - } - } - - private static void convertNewExpression(PsiMethod containingMethod, PsiNewExpression newExpression, PsiClass expectedReturnClass) { - final String expectedReturnQName = expectedReturnClass.getQualifiedName(); - LOG.assertTrue(expectedReturnQName != null); - final PsiElementFactory factory = JavaPsiFacade.getElementFactory(newExpression.getProject()); - PsiAnonymousClass anonymousClass = PsiTreeUtil.findChildOfType(newExpression, PsiAnonymousClass.class); - LOG.assertTrue(anonymousClass != null); - PsiJavaCodeReferenceElement referenceElement = PsiTreeUtil.findChildOfType(anonymousClass, PsiJavaCodeReferenceElement.class); - LOG.assertTrue(referenceElement != null); - final PsiReferenceParameterList parameterList = PsiTreeUtil.findChildOfType(referenceElement, PsiReferenceParameterList.class); - final PsiJavaCodeReferenceElement newCodeReferenceElement = factory.createReferenceFromText(expectedReturnClass.getQualifiedName() - + - (parameterList == null - ? "" - : parameterList.getText()), null); - referenceElement.replace(newCodeReferenceElement); - final List methods = ContainerUtil.filter(anonymousClass.getMethods(), new Condition() { - @Override - public boolean value(PsiMethod method) { - return !"equals".equals(method.getName()); - } - }); - LOG.assertTrue(methods.size() == 1, methods); - final PsiMethod method = methods.get(0); - method.setName(expectedReturnClass.getMethods()[0].getName()); - final PsiTypeElement element = containingMethod.getReturnTypeElement(); - if (element != null) { - final PsiReferenceParameterList genericParameter = PsiTreeUtil.findChildOfType(element, PsiReferenceParameterList.class); - element.replace(factory.createTypeElementFromText(expectedReturnQName + (genericParameter == null ? "" : genericParameter.getText()), null)); - } - } - - @Nullable - private static String findSuitableTailMethodForCollection(PsiMethod lambdaHandler) { - final PsiType type = lambdaHandler.getReturnType(); - if (type instanceof PsiArrayType) { - return "toArray(String[]::new)"; - } - else if (type instanceof PsiClassType) { - final PsiClass resolvedClass = ((PsiClassType)type).resolve(); - if (resolvedClass == null) { - return null; - } - final String qName = resolvedClass.getQualifiedName(); - if (qName == null) { - return null; - } - if (qName.equals(CommonClassNames.JAVA_UTIL_LIST) - || qName.equals(CommonClassNames.JAVA_UTIL_COLLECTION) - || qName.equals(CommonClassNames.JAVA_LANG_ITERABLE)) { - return "collect(" + StreamApiConstants.JAVA_UTIL_STREAM_COLLECTORS + ".toList())"; - } - else if (qName.equals(CommonClassNames.JAVA_UTIL_SET)) { - return "collect(" + StreamApiConstants.JAVA_UTIL_STREAM_COLLECTORS + ".toSet())"; - } - else if (qName.equals(CommonClassNames.JAVA_UTIL_ITERATOR)) { - return "iterator()"; - } - } - return null; } } \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/StaticPseudoFunctionalStyleMethodOptions.java b/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/StaticPseudoFunctionalStyleMethodOptions.java index 0fa7399ca3c8..9787c394b997 100644 --- a/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/StaticPseudoFunctionalStyleMethodOptions.java +++ b/java/java-impl/src/com/intellij/codeInspection/java18StreamApi/StaticPseudoFunctionalStyleMethodOptions.java @@ -16,11 +16,9 @@ package com.intellij.codeInspection.java18StreamApi; import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.actionSystem.LangDataKeys; +import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; -import com.intellij.psi.*; -import com.intellij.psi.search.GlobalSearchScope; import com.intellij.ui.*; import com.intellij.ui.components.JBList; import com.intellij.util.containers.ContainerUtil; @@ -32,7 +30,6 @@ import javax.swing.*; import javax.swing.event.ListDataListener; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; /** @@ -43,6 +40,8 @@ public class StaticPseudoFunctionalStyleMethodOptions { private static final String FQN_ATTR = "classFqn"; private static final String METHOD_ATTR = "method"; private static final String STREAM_API_METHOD_ATTR = "streamApiMethod"; + private static final String LAMBDA_ROLE_ATTR = "lambdaRole"; + private static final String ACCEPTS_DEFAULT_ATTR = "acceptsDefault"; private static final String DELETE_ATTR = "toDelete"; private final List myElements; @@ -54,11 +53,11 @@ public class StaticPseudoFunctionalStyleMethodOptions { private static void restoreDefault(final List elements) { elements.clear(); final String guavaIterables = "com.google.common.collect.Iterables"; - elements.add(new PipelineElement(guavaIterables, "transform", StreamApiConstants.MAP)); - elements.add(new PipelineElement(guavaIterables, "filter", StreamApiConstants.FILTER)); - elements.add(new PipelineElement(guavaIterables, "find", StreamApiConstants.FAKE_FIND_MATCHED)); - elements.add(new PipelineElement(guavaIterables, "all", StreamApiConstants.ALL_MATCH)); - elements.add(new PipelineElement(guavaIterables, "any", StreamApiConstants.ANY_MATCH)); + elements.add(new PipelineElement(guavaIterables, "transform", PseudoLambdaReplaceTemplate.MAP)); + elements.add(new PipelineElement(guavaIterables, "filter", PseudoLambdaReplaceTemplate.FILTER)); + elements.add(new PipelineElement(guavaIterables, "find", PseudoLambdaReplaceTemplate.FIND)); + elements.add(new PipelineElement(guavaIterables, "all", PseudoLambdaReplaceTemplate.ALL_MATCH)); + elements.add(new PipelineElement(guavaIterables, "any", PseudoLambdaReplaceTemplate.ANY_MATCH)); } @NotNull @@ -77,8 +76,11 @@ public class StaticPseudoFunctionalStyleMethodOptions { final String fqn = element.getAttributeValue(FQN_ATTR); final String method = element.getAttributeValue(METHOD_ATTR); final String streamApiMethod = element.getAttributeValue(STREAM_API_METHOD_ATTR); + final PseudoLambdaReplaceTemplate.LambdaRole lambdaRole = + PseudoLambdaReplaceTemplate.LambdaRole.valueOf(element.getAttributeValue(LAMBDA_ROLE_ATTR)); + final boolean acceptsDefault = Boolean.valueOf(element.getAttributeValue(ACCEPTS_DEFAULT_ATTR)); final boolean toDelete = element.getAttribute(DELETE_ATTR) != null; - final PipelineElement pipelineElement = new PipelineElement(fqn, method, streamApiMethod); + final PipelineElement pipelineElement = new PipelineElement(fqn, method, new PseudoLambdaReplaceTemplate(streamApiMethod, lambdaRole, acceptsDefault)); if (toDelete) { myElements.remove(pipelineElement); } @@ -107,10 +109,13 @@ public class StaticPseudoFunctionalStyleMethodOptions { } public Element createXmlElement(PipelineElement element) { + final PseudoLambdaReplaceTemplate template = element.getTemplate(); return new Element(PIPELINE_ELEMENT_NAME) .setAttribute(FQN_ATTR, element.getHandlerClass()) .setAttribute(METHOD_ATTR, element.getMethodName()) - .setAttribute(STREAM_API_METHOD_ATTR, element.getStreamApiMethodName()); + .setAttribute(STREAM_API_METHOD_ATTR, template.getStreamApiMethodName()) + .setAttribute(LAMBDA_ROLE_ATTR, template.getLambdaRole().toString()) + .setAttribute(ACCEPTS_DEFAULT_ATTR, String.valueOf(template.isAcceptDefaultValue())); } public JComponent createPanel() { @@ -137,6 +142,9 @@ public class StaticPseudoFunctionalStyleMethodOptions { if (currentProject == null) { return; } + if (DumbService.isDumb(currentProject)) { + return; + } final AddMethodsDialog dlg = new AddMethodsDialog(currentProject, list, false); if (dlg.showAndGet()) { final PipelineElement newElement = dlg.getSelectedElement(); @@ -147,6 +155,7 @@ public class StaticPseudoFunctionalStyleMethodOptions { UIUtil.invokeLaterIfNeeded(new Runnable() { @Override public void run() { + list.revalidate(); list.updateUI(); } }); @@ -156,19 +165,28 @@ public class StaticPseudoFunctionalStyleMethodOptions { @Override public void run(AnActionButton button) { myElements.remove(list.getSelectedIndex()); + UIUtil.invokeLaterIfNeeded(new Runnable() { + @Override + public void run() { + list.revalidate(); + list.updateUI(); + } + }); } }).createPanel(); } - public static class PipelineElement { + public static class PipelineElement { private final String myHandlerClass; private final String myMethodName; - private final String myStreamApiMethod; + private final PseudoLambdaReplaceTemplate myTemplate; - public PipelineElement(@NotNull String handlerClass, @NotNull String methodName, @NotNull String streamApiMethod) { + public PipelineElement(@NotNull String handlerClass, + @NotNull String methodName, + @NotNull PseudoLambdaReplaceTemplate template) { myHandlerClass = handlerClass; myMethodName = methodName; - myStreamApiMethod = streamApiMethod; + myTemplate = template; } public String getHandlerClass() { @@ -179,8 +197,8 @@ public class StaticPseudoFunctionalStyleMethodOptions { return myMethodName; } - public String getStreamApiMethodName() { - return myStreamApiMethod; + public PseudoLambdaReplaceTemplate getTemplate() { + return myTemplate; } @Override @@ -192,7 +210,7 @@ public class StaticPseudoFunctionalStyleMethodOptions { if (!myHandlerClass.equals(element.myHandlerClass)) return false; if (!myMethodName.equals(element.myMethodName)) return false; - if (!myStreamApiMethod.equals(element.myStreamApiMethod)) return false; + if (!myTemplate.equals(element.myTemplate)) return false; return true; } @@ -201,7 +219,7 @@ public class StaticPseudoFunctionalStyleMethodOptions { public int hashCode() { int result = myHandlerClass.hashCode(); result = 31 * result + myMethodName.hashCode(); - result = 31 * result + myStreamApiMethod.hashCode(); + result = 31 * result + myTemplate.hashCode(); return result; } } diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/diamondResolve/test.java b/java/java-tests/testData/inspection/lambdaLibsStatic/diamondResolve/test.java new file mode 100644 index 000000000000..2f9ec5b1b413 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/diamondResolve/test.java @@ -0,0 +1,13 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Predicate; + +import java.lang.Iterable; +import java.util.HashMap; +import java.util.Collections; +import java.util.ArrayList; + +class c { + void m() { + Iterable l = Iterables.filter(new ArrayList<>(), op -> false); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/diamondResolve/test_after.java b/java/java-tests/testData/inspection/lambdaLibsStatic/diamondResolve/test_after.java new file mode 100644 index 000000000000..32789bc26034 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/diamondResolve/test_after.java @@ -0,0 +1,14 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Predicate; + +import java.lang.Iterable; +import java.util.HashMap; +import java.util.Collections; +import java.util.ArrayList; +import java.util.stream.Collectors; + +class c { + void m() { + Iterable l = new ArrayList().stream().filter(op -> false).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous2/test_after.java b/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous2/test_after.java index 65eb750815ea..e1c89f69e2a5 100644 --- a/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous2/test_after.java +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous2/test_after.java @@ -6,7 +6,7 @@ import java.util.Collections; class c { void m() { - Collections.emptyList().stream().allMatch(getPredicate(100)::apply); + Collections.emptyList().stream().allMatch(getPredicate(100)::apply); } public Predicate getPredicate(final int param) { diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/missingTypeParameters/test.java b/java/java-tests/testData/inspection/lambdaLibsStatic/missingTypeParameters/test.java new file mode 100644 index 000000000000..8c911af98bd0 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/missingTypeParameters/test.java @@ -0,0 +1,13 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Predicate; + +import java.lang.Iterable; +import java.util.HashMap; +import java.util.Collections; +import java.util.ArrayList; + +class c { + void m() { + Iterable l = Iterables.filter(Collections.emptyList(), op -> false); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/missingTypeParameters/test_after.java b/java/java-tests/testData/inspection/lambdaLibsStatic/missingTypeParameters/test_after.java new file mode 100644 index 000000000000..c5273bd3b0fc --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/missingTypeParameters/test_after.java @@ -0,0 +1,14 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Predicate; + +import java.lang.Iterable; +import java.util.HashMap; +import java.util.Collections; +import java.util.ArrayList; +import java.util.stream.Collectors; + +class c { + void m() { + Iterable l = Collections.emptyList().stream().filter(op -> false).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/transformLambda/test.java b/java/java-tests/testData/inspection/lambdaLibsStatic/transformLambda/test.java new file mode 100644 index 000000000000..cc8ef8ab6ddb --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/transformLambda/test.java @@ -0,0 +1,19 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Function; + +import java.lang.String; +import java.util.Collections; + +class c { + Iterable transform = Iterables.transform(Collections.emptyList(), new Function() { + @Override + public String apply(String input) { + Collectors c; + ArrayList l; + System.out.println(input); + //do something + int i = 1; + return input; + } + }); +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/transformLambda/test_after.java b/java/java-tests/testData/inspection/lambdaLibsStatic/transformLambda/test_after.java new file mode 100644 index 000000000000..bf6d86ece3a8 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/transformLambda/test_after.java @@ -0,0 +1,17 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Function; + +import java.lang.String; +import java.util.Collections; +import java.util.stream.Collectors; + +class c { + Iterable transform = Collections.emptyList().stream().map(input -> { + Collectors c; + ArrayList l; + System.out.println(input); + //do something + int i = 1; + return input; + }).collect(Collectors.toList()); +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/transformMethodCalling/test.java b/java/java-tests/testData/inspection/lambdaLibsStatic/transformMethodCalling/test.java new file mode 100644 index 000000000000..addd4facbcf6 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/transformMethodCalling/test.java @@ -0,0 +1,17 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Function; + +import java.lang.String; +import java.util.Collections; + +class c { + void m() { + Iterables.transform(new ArrayList<>(), getFunction()) + } + + Function getFunction() { + return new Function() { + + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/transformMethodCalling/test_after.java b/java/java-tests/testData/inspection/lambdaLibsStatic/transformMethodCalling/test_after.java new file mode 100644 index 000000000000..452f94d0f295 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/transformMethodCalling/test_after.java @@ -0,0 +1,20 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Function; + +import java.lang.String; +import java.util.ArrayList; +import java.util.Collections; +import java.util.stream.Collectors; + +class c { + void m() { + Collections.emptyList().stream().map(input -> { + Collectors c; + ArrayList l; + System.out.println(input); + //do something + int i = 1; + return input; + }).collect(Collectors.toList()) + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/transformMethodRef/test.java b/java/java-tests/testData/inspection/lambdaLibsStatic/transformMethodRef/test.java new file mode 100644 index 000000000000..73dc592b128d --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/transformMethodRef/test.java @@ -0,0 +1,21 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Function; + +import java.lang.String; +import java.util.Collections; + +class c { + void m() { + Iterables.transform(Collections.emptyList(), new Function () { + @Override + public String apply(String input) { + java.util.stream.Collectors c; + java.util.ArrayList l; + System.out.println(input); + //do something + int i = 1; + return input; + } + }) + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/transformMethodRef/test_after.java b/java/java-tests/testData/inspection/lambdaLibsStatic/transformMethodRef/test_after.java new file mode 100644 index 000000000000..452f94d0f295 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/transformMethodRef/test_after.java @@ -0,0 +1,20 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Function; + +import java.lang.String; +import java.util.ArrayList; +import java.util.Collections; +import java.util.stream.Collectors; + +class c { + void m() { + Collections.emptyList().stream().map(input -> { + Collectors c; + ArrayList l; + System.out.println(input); + //do something + int i = 1; + return input; + }).collect(Collectors.toList()) + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/StaticPseudoFunctionalStyleMethodTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/StaticPseudoFunctionalStyleMethodTest.java index 1acaf1586e15..098f85b1561f 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/StaticPseudoFunctionalStyleMethodTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInspection/StaticPseudoFunctionalStyleMethodTest.java @@ -28,6 +28,7 @@ import com.intellij.psi.search.GlobalSearchScope; import com.intellij.testFramework.IdeaTestUtil; import com.intellij.testFramework.builders.JavaModuleFixtureBuilder; import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase; +import com.intellij.util.PathUtil; import com.intellij.util.Processor; import java.io.File; @@ -91,6 +92,22 @@ public class StaticPseudoFunctionalStyleMethodTest extends JavaCodeInsightFixtur doTest(); } + public void testDiamondResolve() { + doTest(); + } + + public void _testTransformLambda() { + doTest(); + } + + public void _testTransformMethodCalling() { + doTest(); + } + + public void testTransformMethodRef() { + doTest(); + } + public void _testReplaceWithMethodReference() { doTest(); } @@ -109,7 +126,7 @@ public class StaticPseudoFunctionalStyleMethodTest extends JavaCodeInsightFixtur } } } - assertTrue(isQuickFixFound); + assertTrue("Quick fix isn't found", isQuickFixFound); myFixture.checkResultByFile(getTestName(true) + "/test_after.java"); } } diff --git a/resources-en/src/inspectionDescriptions/StaticPseudoFunctionalStyleMethod.html b/resources-en/src/inspectionDescriptions/StaticPseudoFunctionalStyleMethod.html index 598ae3052713..10c3f90c8cc6 100644 --- a/resources-en/src/inspectionDescriptions/StaticPseudoFunctionalStyleMethod.html +++ b/resources-en/src/inspectionDescriptions/StaticPseudoFunctionalStyleMethod.html @@ -1,5 +1,11 @@ Inspection detects usages of pseudo-functional code if Java Stream API is available. +
+Consider cases when monad behaviour is emulated by static method call: +
+ +List<String> transformedIterable = Iterables.transform(someList, someTransformFunction); + \ No newline at end of file diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 26bc49d9633c..06d676173454 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -727,7 +727,7 @@ displayName="Block marker comment"/>