From f6ccae51530c8bfc6c0edaa06785a9708b5638bb Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Thu, 12 Mar 2015 15:26:38 +0300 Subject: [PATCH] inspection to highlight and convert pseudo-functional code (like guava Iterables) to java 8 api. initial version --- .../AnonymousCanBeLambdaInspection.java | 210 +++++----- ...PseudoFunctionalStyleMethodInspection.java | 359 ++++++++++++++++++ ...ticPseudoFunctionalStyleMethodOptions.java | 87 +++++ .../java18StreamApi/StreamApiConstants.java | 59 +++ .../lambdaIsntAnonymous/test.java | 21 + .../lambdaIsntAnonymous/test_after.java | 21 + .../lambdaIsntAnonymous2/test.java | 41 ++ .../lambdaIsntAnonymous2/test_after.java | 41 ++ .../lambdaIsntAnonymous3/test.java | 22 ++ .../lambdaIsntAnonymous3/test_after.java | 22 ++ .../replaceWithMethodReference/test.java | 17 + .../test_after.java | 13 + .../lambdaLibsStatic/simpleAll/test.java | 10 + .../simpleAll/test_after.java | 10 + .../lambdaLibsStatic/simpleAny/test.java | 10 + .../simpleAny/test_after.java | 10 + .../lambdaLibsStatic/simpleFilter/test.java | 16 + .../simpleFilter/test_after.java | 12 + .../lambdaLibsStatic/simpleFind/test.java | 16 + .../simpleFind/test_after.java | 11 + .../simpleTransform/test.java | 21 + .../simpleTransform/test_after.java | 20 + ...StaticPseudoFunctionalStyleMethodTest.java | 105 +++++ .../StaticPseudoFunctionalStyleMethod.html | 5 + resources/src/META-INF/IdeaPlugin.xml | 5 + 25 files changed, 1072 insertions(+), 92 deletions(-) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/java18StreamApi/StaticPseudoFunctionalStyleMethodInspection.java create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/java18StreamApi/StaticPseudoFunctionalStyleMethodOptions.java create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/java18StreamApi/StreamApiConstants.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous/test.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous/test_after.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous2/test.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous2/test_after.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous3/test.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous3/test_after.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/replaceWithMethodReference/test.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/replaceWithMethodReference/test_after.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/simpleAll/test.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/simpleAll/test_after.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/simpleAny/test.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/simpleAny/test_after.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/simpleFilter/test.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/simpleFilter/test_after.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/simpleFind/test.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/simpleFind/test_after.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/simpleTransform/test.java create mode 100644 java/java-tests/testData/inspection/lambdaLibsStatic/simpleTransform/test_after.java create mode 100644 java/java-tests/testSrc/com/intellij/codeInspection/StaticPseudoFunctionalStyleMethodTest.java create mode 100644 resources-en/src/inspectionDescriptions/StaticPseudoFunctionalStyleMethod.html diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/AnonymousCanBeLambdaInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/AnonymousCanBeLambdaInspection.java index a50eaeed5409..5d314a88b103 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/AnonymousCanBeLambdaInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/AnonymousCanBeLambdaInspection.java @@ -24,6 +24,7 @@ import com.intellij.codeInsight.intention.HighPriorityAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.pom.java.LanguageLevel; @@ -41,6 +42,7 @@ import com.intellij.psi.util.PsiTypesUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.util.ArrayUtilRt; import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtilRt; import com.intellij.util.containers.hash.LinkedHashMap; import org.jetbrains.annotations.Nls; @@ -207,6 +209,120 @@ public class AnonymousCanBeLambdaInspection extends BaseJavaBatchLocalInspection return false; } + public static PsiLambdaExpression replacePsiElementWithLambda(@NotNull PsiElement element, final boolean ignoreEqualsMethod) { + if (element instanceof PsiNewExpression) { + if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return null; + final PsiAnonymousClass anonymousClass = ((PsiNewExpression)element).getAnonymousClass(); + + LOG.assertTrue(anonymousClass != null); + + ChangeContextUtil.encodeContextInfo(anonymousClass, true); + final PsiElement lambdaContext = anonymousClass.getParent().getParent(); + boolean validContext = LambdaUtil.isValidLambdaContext(lambdaContext); + final String canonicalText = anonymousClass.getBaseClassType().getCanonicalText(); + + final PsiMethod method; + if (ignoreEqualsMethod) { + final List methods = ContainerUtil.filter(anonymousClass.getMethods(), new Condition() { + @Override + public boolean value(PsiMethod method) { + return !"equals".equals(method.getName()); + } + }); + method = methods.get(0); + } else { + method = anonymousClass.getMethods()[0]; + } + LOG.assertTrue(method != null); + + final PsiCodeBlock body = method.getBody(); + LOG.assertTrue(body != null); + + final ForbiddenRefsChecker checker = new ForbiddenRefsChecker(method, anonymousClass); + body.accept(checker); + + PsiResolveHelper helper = PsiResolveHelper.SERVICE.getInstance(body.getProject()); + final Set conflictingLocals = checker.getLocals(); + for (Iterator iterator = conflictingLocals.iterator(); iterator.hasNext(); ) { + PsiLocalVariable local = iterator.next(); + final String localName = local.getName(); + if (localName == null || helper.resolveReferencedVariable(localName, anonymousClass) == null) { + iterator.remove(); + } + } + + final Project project = element.getProject(); + final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); + + ReplaceWithLambdaFix + .giveUniqueNames(project, lambdaContext, elementFactory, body, conflictingLocals.toArray(new PsiVariable[conflictingLocals.size()])); + + final String lambdaWithTypesDeclared = ReplaceWithLambdaFix.composeLambdaText(method, true); + final String withoutTypesDeclared = ReplaceWithLambdaFix.composeLambdaText(method, false); + + PsiLambdaExpression lambdaExpression = + (PsiLambdaExpression)elementFactory.createExpressionFromText(withoutTypesDeclared, anonymousClass); + + PsiElement lambdaBody = lambdaExpression.getBody(); + LOG.assertTrue(lambdaBody != null); + lambdaBody.replace(body); + + ReplaceWithLambdaFix + .giveUniqueNames(project, lambdaContext, elementFactory, lambdaExpression, lambdaExpression.getParameterList().getParameters()); + + final PsiNewExpression newExpression = (PsiNewExpression)anonymousClass.getParent(); + lambdaExpression = (PsiLambdaExpression)newExpression.replace(lambdaExpression); + final PsiExpression singleExpr = RedundantLambdaCodeBlockInspection.isCodeBlockRedundant(lambdaExpression, + lambdaExpression.getBody()); + if (singleExpr != null) { + lambdaExpression.getBody().replace(singleExpr); + } + ChangeContextUtil.decodeContextInfo(lambdaExpression, null, null); + if (!validContext) { + final PsiParenthesizedExpression typeCast = + (PsiParenthesizedExpression)elementFactory.createExpressionFromText("((" + canonicalText + ")" + withoutTypesDeclared + ")", lambdaExpression); + final PsiExpression typeCastExpr = typeCast.getExpression(); + LOG.assertTrue(typeCastExpr != null); + final PsiExpression typeCastOperand = ((PsiTypeCastExpression)typeCastExpr).getOperand(); + LOG.assertTrue(typeCastOperand != null); + final PsiElement fromText = ((PsiLambdaExpression)typeCastOperand).getBody(); + LOG.assertTrue(fromText != null); + lambdaBody = lambdaExpression.getBody(); + LOG.assertTrue(lambdaBody != null); + fromText.replace(lambdaBody); + lambdaExpression.replace(typeCast); + return lambdaExpression; + } + + PsiType interfaceType = lambdaExpression.getFunctionalInterfaceType(); + if (ReplaceWithLambdaFix.isInferred(lambdaExpression, interfaceType)) { + final PsiLambdaExpression withTypes = + (PsiLambdaExpression)elementFactory.createExpressionFromText(lambdaWithTypesDeclared, lambdaExpression); + final PsiElement withTypesBody = withTypes.getBody(); + LOG.assertTrue(withTypesBody != null); + lambdaBody = lambdaExpression.getBody(); + LOG.assertTrue(lambdaBody != null); + withTypesBody.replace(lambdaBody); + lambdaExpression = (PsiLambdaExpression)lambdaExpression.replace(withTypes); + + interfaceType = lambdaExpression.getFunctionalInterfaceType(); + if (ReplaceWithLambdaFix.isInferred(lambdaExpression, interfaceType)) { + final PsiTypeCastExpression typeCast = (PsiTypeCastExpression)elementFactory.createExpressionFromText("(" + canonicalText + ")" + withoutTypesDeclared, lambdaExpression); + final PsiExpression typeCastOperand = typeCast.getOperand(); + LOG.assertTrue(typeCastOperand instanceof PsiLambdaExpression); + final PsiElement fromText = ((PsiLambdaExpression)typeCastOperand).getBody(); + LOG.assertTrue(fromText != null); + lambdaBody = lambdaExpression.getBody(); + LOG.assertTrue(lambdaBody != null); + fromText.replace(lambdaBody); + lambdaExpression.replace(typeCast); + } + } + return lambdaExpression; + } + return null; + } + private static class ReplaceWithLambdaFix implements LocalQuickFix, HighPriorityAction { @NotNull @Override @@ -223,98 +339,8 @@ public class AnonymousCanBeLambdaInspection extends BaseJavaBatchLocalInspection @Override public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { final PsiElement element = descriptor.getPsiElement(); - if (element instanceof PsiNewExpression) { - if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return; - final PsiAnonymousClass anonymousClass = ((PsiNewExpression)element).getAnonymousClass(); - - LOG.assertTrue(anonymousClass != null); - - ChangeContextUtil.encodeContextInfo(anonymousClass, true); - final PsiElement lambdaContext = anonymousClass.getParent().getParent(); - boolean validContext = LambdaUtil.isValidLambdaContext(lambdaContext); - final String canonicalText = anonymousClass.getBaseClassType().getCanonicalText(); - final PsiMethod method = anonymousClass.getMethods()[0]; - LOG.assertTrue(method != null); - - final PsiCodeBlock body = method.getBody(); - LOG.assertTrue(body != null); - - final ForbiddenRefsChecker checker = new ForbiddenRefsChecker(method, anonymousClass); - body.accept(checker); - - PsiResolveHelper helper = PsiResolveHelper.SERVICE.getInstance(body.getProject()); - final Set conflictingLocals = checker.getLocals(); - for (Iterator iterator = conflictingLocals.iterator(); iterator.hasNext(); ) { - PsiLocalVariable local = iterator.next(); - final String localName = local.getName(); - if (localName == null || helper.resolveReferencedVariable(localName, anonymousClass) == null) { - iterator.remove(); - } - } - - final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); - - giveUniqueNames(project, lambdaContext, elementFactory, body, conflictingLocals.toArray(new PsiVariable[conflictingLocals.size()])); - - final String lambdaWithTypesDeclared = composeLambdaText(method, true); - final String withoutTypesDeclared = composeLambdaText(method, false); - - PsiLambdaExpression lambdaExpression = - (PsiLambdaExpression)elementFactory.createExpressionFromText(withoutTypesDeclared, anonymousClass); - - PsiElement lambdaBody = lambdaExpression.getBody(); - LOG.assertTrue(lambdaBody != null); - lambdaBody.replace(body); - - giveUniqueNames(project, lambdaContext, elementFactory, lambdaExpression, lambdaExpression.getParameterList().getParameters()); - - final PsiNewExpression newExpression = (PsiNewExpression)anonymousClass.getParent(); - lambdaExpression = (PsiLambdaExpression)newExpression.replace(lambdaExpression); - final PsiExpression singleExpr = RedundantLambdaCodeBlockInspection.isCodeBlockRedundant(lambdaExpression, lambdaExpression.getBody()); - if (singleExpr != null) { - lambdaExpression.getBody().replace(singleExpr); - } - ChangeContextUtil.decodeContextInfo(lambdaExpression, null, null); - if (!validContext) { - final PsiParenthesizedExpression typeCast = - (PsiParenthesizedExpression)elementFactory.createExpressionFromText("((" + canonicalText + ")" + withoutTypesDeclared + ")", lambdaExpression); - final PsiExpression typeCastExpr = typeCast.getExpression(); - LOG.assertTrue(typeCastExpr != null); - final PsiExpression typeCastOperand = ((PsiTypeCastExpression)typeCastExpr).getOperand(); - LOG.assertTrue(typeCastOperand != null); - final PsiElement fromText = ((PsiLambdaExpression)typeCastOperand).getBody(); - LOG.assertTrue(fromText != null); - lambdaBody = lambdaExpression.getBody(); - LOG.assertTrue(lambdaBody != null); - fromText.replace(lambdaBody); - lambdaExpression.replace(typeCast); - return; - } - - PsiType interfaceType = lambdaExpression.getFunctionalInterfaceType(); - if (isInferred(lambdaExpression, interfaceType)) { - final PsiLambdaExpression withTypes = - (PsiLambdaExpression)elementFactory.createExpressionFromText(lambdaWithTypesDeclared, lambdaExpression); - final PsiElement withTypesBody = withTypes.getBody(); - LOG.assertTrue(withTypesBody != null); - lambdaBody = lambdaExpression.getBody(); - LOG.assertTrue(lambdaBody != null); - withTypesBody.replace(lambdaBody); - lambdaExpression = (PsiLambdaExpression)lambdaExpression.replace(withTypes); - - interfaceType = lambdaExpression.getFunctionalInterfaceType(); - if (isInferred(lambdaExpression, interfaceType)) { - final PsiTypeCastExpression typeCast = (PsiTypeCastExpression)elementFactory.createExpressionFromText("(" + canonicalText + ")" + withoutTypesDeclared, lambdaExpression); - final PsiExpression typeCastOperand = typeCast.getOperand(); - LOG.assertTrue(typeCastOperand instanceof PsiLambdaExpression); - final PsiElement fromText = ((PsiLambdaExpression)typeCastOperand).getBody(); - LOG.assertTrue(fromText != null); - lambdaBody = lambdaExpression.getBody(); - LOG.assertTrue(lambdaBody != null); - fromText.replace(lambdaBody); - lambdaExpression.replace(typeCast); - } - } + if (element != null) { + replacePsiElementWithLambda(element, false); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/java18StreamApi/StaticPseudoFunctionalStyleMethodInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/java18StreamApi/StaticPseudoFunctionalStyleMethodInspection.java new file mode 100644 index 000000000000..3a620b700bf1 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/java18StreamApi/StaticPseudoFunctionalStyleMethodInspection.java @@ -0,0 +1,359 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInspection.java18StreamApi; + +import com.intellij.codeInspection.*; +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.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; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.List; + +/** + * @author Dmitry Batkovich + */ +public class StaticPseudoFunctionalStyleMethodInspection extends BaseJavaBatchLocalInspectionTool { + private final static Logger LOG = Logger.getInstance(StaticPseudoFunctionalStyleMethodInspection.class); + + private StaticPseudoFunctionalStyleMethodOptions myOptions = new StaticPseudoFunctionalStyleMethodOptions(); + + + @Override + public void readSettings(@NotNull Element node) throws InvalidDataException { + super.readSettings(node); + } + + @Override + public void writeSettings(@NotNull Element node) throws WriteExternalException { + super.writeSettings(node); + } + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { + if (!PsiUtil.isLanguageLevel8OrHigher(holder.getFile())) { + return PsiElementVisitor.EMPTY_VISITOR; + } + return new PsiElementVisitor() { + @Override + public void visitElement(PsiElement element) { + if (element instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)element; + String qName = methodCallExpression.getMethodExpression().getQualifiedName(); + if (qName == null) { + return; + } + final int dotIndex = qName.lastIndexOf('.'); + if (dotIndex >= 0) { + qName = qName.substring(dotIndex + 1); + } + final Collection handlerInfos = myOptions.findElementsByMethodName(qName); + if (handlerInfos.isEmpty()) { + return; + } + final PsiMethod method = methodCallExpression.resolveMethod(); + if (method == null) { + return; + } + final PsiClass aClass = method.getContainingClass(); + if (aClass == null) { + return; + } + final String classQualifiedName = aClass.getQualifiedName(); + if (classQualifiedName == null) { + return; + } + StaticPseudoFunctionalStyleMethodOptions.PipelineElement suitableHandler = null; + for (StaticPseudoFunctionalStyleMethodOptions.PipelineElement h : handlerInfos) { + if (h.getHandlerClass().equals(classQualifiedName)) { + suitableHandler = h; + break; + } + } + if (suitableHandler == null) { + return; + } + final int lambdaIndex = validateMethodParameters(methodCallExpression, method); + if (lambdaIndex != -1) { + holder.registerProblem(methodCallExpression.getMethodExpression(), "", + new ReplacePseudoLambdaWithLambda(lambdaIndex, methodCallExpression, method, suitableHandler)); + } + } + } + }; + } + + public static class ReplacePseudoLambdaWithLambda implements LocalQuickFix { + private final int myLambdaIndex; + private final SmartPsiElementPointer myMethodPointer; + private final StaticPseudoFunctionalStyleMethodOptions.PipelineElement mySuitableHandler; + + 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; + } + + @Nls + @NotNull + @Override + public String getName() { + return getFamilyName(); + } + + @NotNull + @Override + public String getFamilyName() { + return "Replace with Java Stream API pipeline"; + } + + @Override + 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 = convertToJavaLambda(lambdaExpression, mySuitableHandler.getStreamApiMethodName()); + LOG.assertTrue(lambdaExpression != null); + + final PsiExpression collectionExpression = expressions[(1 + myLambdaIndex) % 2]; + final String pipelineHead = createPipelineHeadText(collectionExpression); + + final String patternForFake = + StreamApiConstants.FAKE_STREAM_API_METHODS_TO_PATTERN.getValue().get(mySuitableHandler.getStreamApiMethodName()); + + final String lambdaExpressionText; + final String elementText; + if (patternForFake == null) { + elementText = mySuitableHandler.getStreamApiMethodName(); + lambdaExpressionText = lambdaExpression.getText(); + } + else { + elementText = String.format(patternForFake, 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()); + } + + 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); + 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) { + final PsiType[] argumentTypes = methodCallExpression.getArgumentList().getExpressionTypes(); + final PsiParameter[] expectedParameters = method.getParameterList().getParameters(); + if (argumentTypes.length != expectedParameters.length || expectedParameters.length != 2) { + return -1; + } + final int collectionOrArrayIndex = findCollectionOrArrayPlacement(expectedParameters); + if (collectionOrArrayIndex == -1) { + return -1; + } + return (1 + collectionOrArrayIndex) % 2; + } + + 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(final PsiExpression expression, String streamApiMethodName) { + 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 (tryConvertLambdaToStreamApi(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 tryConvertLambdaToStreamApi(final PsiMethod method, final PsiClass expectedReturnClass) { + 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-analysis-impl/src/com/intellij/codeInspection/java18StreamApi/StaticPseudoFunctionalStyleMethodOptions.java b/java/java-analysis-impl/src/com/intellij/codeInspection/java18StreamApi/StaticPseudoFunctionalStyleMethodOptions.java new file mode 100644 index 000000000000..e0f3ff49a9ff --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/java18StreamApi/StaticPseudoFunctionalStyleMethodOptions.java @@ -0,0 +1,87 @@ +/* + * 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.util.containers.MultiMap; +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +/** + * @author Dmitry Batkovich + */ +public class StaticPseudoFunctionalStyleMethodOptions { + private final MultiMap myIndex; + + public StaticPseudoFunctionalStyleMethodOptions() { + myIndex = new MultiMap(); + restoreDefault(); + } + + public static class PipelineElement { + private final String myHandlerClass; + private final String myMethodName; + private final String myStreamApiMethod; + + public PipelineElement(String handlerClass, String methodName, @Nullable String streamApiMethod) { + myHandlerClass = handlerClass; + myMethodName = methodName; + myStreamApiMethod = streamApiMethod; + } + + public String getHandlerClass() { + return myHandlerClass; + } + + public String getMethodName() { + return myMethodName; + } + + public String getStreamApiMethodName() { + return myStreamApiMethod; + } + } + + @NotNull + public Collection findElementsByMethodName(final String methodName) { + return myIndex.get(methodName); + } + + public void addElement(PipelineElement element) { + myIndex.putValue(element.getMethodName(), element); + } + + public void readExternal(final @NotNull Element element) { + + } + + + public void writeExternal(final @NotNull Element element) { + + } + + private void restoreDefault() { + myIndex.clear(); + final String guavaIterables = "com.google.common.collect.Iterables"; + addElement(new PipelineElement(guavaIterables, "transform", StreamApiConstants.MAP)); + addElement(new PipelineElement(guavaIterables, "filter", StreamApiConstants.FILTER)); + addElement(new PipelineElement(guavaIterables, "find", StreamApiConstants.FAKE_FIND_MATCHED)); + addElement(new PipelineElement(guavaIterables, "all", StreamApiConstants.ALL_MATCH)); + addElement(new PipelineElement(guavaIterables, "any", StreamApiConstants.ANY_MATCH)); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/java18StreamApi/StreamApiConstants.java b/java/java-analysis-impl/src/com/intellij/codeInspection/java18StreamApi/StreamApiConstants.java new file mode 100644 index 000000000000..7621d08f568b --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/java18StreamApi/StreamApiConstants.java @@ -0,0 +1,59 @@ +/* + * 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.reference.SoftLazyValue; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.hash.HashMap; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.Set; + +/** + * @author Dmitry Batkovich + */ +public interface StreamApiConstants { + String JAVA_UTIL_STREAM_STREAM = "java.util.stream.Stream"; + + String ANY_MATCH = "anyMatch"; + String ALL_MATCH = "allMatch"; + String MAP = "map"; + String FILTER = "filter"; + + String FAKE_FIND_MATCHED = "#findMatched"; + String FAKE_FIND_MATCHED_PATTERN = "filter(%s).findFirst().get()"; + + String JAVA_UTIL_STREAM_COLLECTORS = "java.util.stream.Collectors"; + + SoftLazyValue> STREAM_STREAM_API_METHODS = new SoftLazyValue>() { + @NotNull + @Override + protected Set compute() { + return ContainerUtil.newHashSet(MAP, FILTER); + } + }; + + SoftLazyValue> FAKE_STREAM_API_METHODS_TO_PATTERN = new SoftLazyValue>() { + @NotNull + @Override + protected Map compute() { + final HashMap map = new HashMap(); + map.put(FAKE_FIND_MATCHED, FAKE_FIND_MATCHED_PATTERN); + return map; + } + }; +} diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous/test.java b/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous/test.java new file mode 100644 index 000000000000..fb6019507166 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous/test.java @@ -0,0 +1,21 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Predicate; + +import java.lang.String; +import java.util.Collections; + +class c { + void m() { + Iterables.all(Collections.emptyList(), getPredicate(100)); + } + + public Predicate getPredicate(final int param) { + return new Predicate() { + @Override + public boolean apply(String input) { + System.out.println("lambda param " + param); + return false; + } + }; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous/test_after.java b/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous/test_after.java new file mode 100644 index 000000000000..82652edb89d4 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous/test_after.java @@ -0,0 +1,21 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Predicate; + +import java.lang.String; +import java.util.Collections; + +class c { + void m() { + Collections.emptyList().stream().allMatch(getPredicate(100)); + } + + public java.util.function.Predicate getPredicate(final int param) { + return new java.util.function.Predicate() { + @Override + public boolean test(String input) { + System.out.println("lambda param " + param); + return false; + } + }; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous2/test.java b/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous2/test.java new file mode 100644 index 000000000000..2bb2e0c9a7b4 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous2/test.java @@ -0,0 +1,41 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Predicate; + +import java.lang.String; +import java.util.Collections; + +class c { + void m() { + Iterables.all(Collections.emptyList(), getPredicate(100)); + } + + public Predicate getPredicate(final int param) { + final MyComplexPredicate predicate = new MyComplexPredicate(param); + predicate.setParam2(200); + return predicate; + } + + class MyComplexPredicate extends Predicate { + int param; + int param2; + + public MyComplexPredicate(int param) { + this.param = param; + } + + public void setParam2(int param2) { + this.param2 = param2; + } + + @Override + public boolean apply(String input) { + System.out.println("lambda param " + param); + doMagic(); + return false; + } + + private void doMagic() { + //do something + } + } +} \ 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 new file mode 100644 index 000000000000..65eb750815ea --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous2/test_after.java @@ -0,0 +1,41 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Predicate; + +import java.lang.String; +import java.util.Collections; + +class c { + void m() { + Collections.emptyList().stream().allMatch(getPredicate(100)::apply); + } + + public Predicate getPredicate(final int param) { + final MyComplexPredicate predicate = new MyComplexPredicate(param); + predicate.setParam2(200); + return predicate; + } + + class MyComplexPredicate extends Predicate { + int param; + int param2; + + public MyComplexPredicate(int param) { + this.param = param; + } + + public void setParam2(int param2) { + this.param2 = param2; + } + + @Override + public boolean apply(String input) { + System.out.println("lambda param " + param); + doMagic(); + return false; + } + + private void doMagic() { + //do something + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous3/test.java b/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous3/test.java new file mode 100644 index 000000000000..370ff08d0c10 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous3/test.java @@ -0,0 +1,22 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; + +import java.lang.String; +import java.util.Collections; + +class c { + void m() { + Iterables.all(Collections.emptyList(), getPredicate(100)); + } + + public Predicate getPredicate(final int param) { + return Predicates.not(new Predicate() { + @Override + public boolean apply(String input) { + System.out.println("lambda param " + param); + return false; + } + }); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous3/test_after.java b/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous3/test_after.java new file mode 100644 index 000000000000..5316c33a2253 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/lambdaIsntAnonymous3/test_after.java @@ -0,0 +1,22 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; + +import java.lang.String; +import java.util.Collections; + +class c { + void m() { + Collections.emptyList().stream().allMatch(getPredicate(100)::apply); + } + + public Predicate getPredicate(final int param) { + return Predicates.not(new Predicate() { + @Override + public boolean apply(String input) { + System.out.println("lambda param " + param); + return false; + } + }); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/replaceWithMethodReference/test.java b/java/java-tests/testData/inspection/lambdaLibsStatic/replaceWithMethodReference/test.java new file mode 100644 index 000000000000..865fe827ffcf --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/replaceWithMethodReference/test.java @@ -0,0 +1,17 @@ +import com.google.common.collect.Iterables; + +import java.lang.String; +import java.util.List; +import java.util.ArrayList; + +class c { + void m() { + List l = new ArrayList<>(); + Iterable transform = Iterables.transform(l, new Function() { + @Override + public Boolean apply(String input) { + return input.isEmpty(); + } + }); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/replaceWithMethodReference/test_after.java b/java/java-tests/testData/inspection/lambdaLibsStatic/replaceWithMethodReference/test_after.java new file mode 100644 index 000000000000..bc0fda301a1b --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/replaceWithMethodReference/test_after.java @@ -0,0 +1,13 @@ +import com.google.common.collect.Iterables; + +import java.lang.String; +import java.util.List; +import java.util.ArrayList; +import java.util.stream.Collectors; + +class c { + void m() { + List l = new ArrayList<>(); + Iterable transform = l.stream().map(String::isEmpty).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/simpleAll/test.java b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleAll/test.java new file mode 100644 index 000000000000..c93fb0141686 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleAll/test.java @@ -0,0 +1,10 @@ +import com.google.common.collect.Iterables; + +import java.lang.String; +import java.util.Collections; + +class c { + void m() { + Iterables.all(Collections.emptyList(), in -> false); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/simpleAll/test_after.java b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleAll/test_after.java new file mode 100644 index 000000000000..3aee102d6b5c --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleAll/test_after.java @@ -0,0 +1,10 @@ +import com.google.common.collect.Iterables; + +import java.lang.String; +import java.util.Collections; + +class c { + void m() { + Collections.emptyList().stream().allMatch(in -> false); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/simpleAny/test.java b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleAny/test.java new file mode 100644 index 000000000000..aa8c8c15f9d7 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleAny/test.java @@ -0,0 +1,10 @@ +import com.google.common.collect.Iterables; + +import java.lang.String; +import java.util.Collections; + +class c { + void m() { + Iterables.any(Collections.emptyList(), in -> false); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/simpleAny/test_after.java b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleAny/test_after.java new file mode 100644 index 000000000000..b6cc0770bd12 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleAny/test_after.java @@ -0,0 +1,10 @@ +import com.google.common.collect.Iterables; + +import java.lang.String; +import java.util.Collections; + +class c { + void m() { + Collections.emptyList().stream().anyMatch(in -> false); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/simpleFilter/test.java b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleFilter/test.java new file mode 100644 index 000000000000..781eb3c021a7 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleFilter/test.java @@ -0,0 +1,16 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Predicate; + +import java.lang.String; +import java.util.Collections; + +class c { + void m() { + Iterables.filter(Collections.emptyList(), new Predicate() { + @Override + public boolean apply(Object input) { + return true; + } + }); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/simpleFilter/test_after.java b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleFilter/test_after.java new file mode 100644 index 000000000000..5438316b7122 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleFilter/test_after.java @@ -0,0 +1,12 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Predicate; + +import java.lang.String; +import java.util.Collections; +import java.util.stream.Collectors; + +class c { + void m() { + Collections.emptyList().stream().filter(input -> true).collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/simpleFind/test.java b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleFind/test.java new file mode 100644 index 000000000000..659f81f7be23 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleFind/test.java @@ -0,0 +1,16 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Predicate; + +import java.lang.String; +import java.util.Collections; + +class c { + void m() { + Iterables.find(Collections.emptyList(), new Predicate() { + @Override + public boolean apply(Object input) { + return true; + } + }); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/simpleFind/test_after.java b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleFind/test_after.java new file mode 100644 index 000000000000..db6dff003227 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleFind/test_after.java @@ -0,0 +1,11 @@ +import com.google.common.collect.Iterables; +import com.google.common.base.Predicate; + +import java.lang.String; +import java.util.Collections; + +class c { + void m() { + Collections.emptyList().stream().filter(input -> true).findFirst().get(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/lambdaLibsStatic/simpleTransform/test.java b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleTransform/test.java new file mode 100644 index 000000000000..73dc592b128d --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleTransform/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/simpleTransform/test_after.java b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleTransform/test_after.java new file mode 100644 index 000000000000..452f94d0f295 --- /dev/null +++ b/java/java-tests/testData/inspection/lambdaLibsStatic/simpleTransform/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 new file mode 100644 index 000000000000..74bed574b116 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInspection/StaticPseudoFunctionalStyleMethodTest.java @@ -0,0 +1,105 @@ +/* + * 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; + +import com.intellij.JavaTestUtil; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInspection.ex.QuickFixWrapper; +import com.intellij.codeInspection.java18StreamApi.StaticPseudoFunctionalStyleMethodInspection; +import com.intellij.openapi.application.PathManager; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.testFramework.IdeaTestUtil; +import com.intellij.testFramework.builders.JavaModuleFixtureBuilder; +import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase; + +import java.io.File; + +/** + * @author Dmitry Batkovich + */ +public class StaticPseudoFunctionalStyleMethodTest extends JavaCodeInsightFixtureTestCase { + @Override + protected String getTestDataPath() { + return JavaTestUtil.getJavaTestDataPath() + "/inspection/lambdaLibsStatic"; + } + + @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.addJdk(IdeaTestUtil.getMockJdk18Path().getPath()); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + } + + public void testSimpleTransform() { + doTest(); + } + + public void testSimpleFilter() { + doTest(); + } + + public void testSimpleFind() { + doTest(); + } + + public void testSimpleAll() { + doTest(); + } + + public void testSimpleAny() { + doTest(); + } + + public void testLambdaIsntAnonymous() { + doTest(); + } + + public void testLambdaIsntAnonymous2() { + doTest(); + } + + public void testLambdaIsntAnonymous3() { + doTest(); + } + + public void _testReplaceWithMethodReference() { + doTest(); + } + + private void doTest() { + myFixture.configureByFile(getTestName(true) + "/test.java"); + myFixture.enableInspections(new StaticPseudoFunctionalStyleMethodInspection()); + boolean isQuickFixFound = false; + for (IntentionAction action : myFixture.getAvailableIntentions()) { + if (action instanceof QuickFixWrapper) { + final LocalQuickFix fix = ((QuickFixWrapper)action).getFix(); + if (fix instanceof StaticPseudoFunctionalStyleMethodInspection.ReplacePseudoLambdaWithLambda) { + myFixture.launchAction(action); + isQuickFixFound = true; + break; + } + } + } + assertTrue(isQuickFixFound); + myFixture.checkResultByFile(getTestName(true) + "/test_after.java"); + } +} diff --git a/resources-en/src/inspectionDescriptions/StaticPseudoFunctionalStyleMethod.html b/resources-en/src/inspectionDescriptions/StaticPseudoFunctionalStyleMethod.html new file mode 100644 index 000000000000..f76b2ec9850a --- /dev/null +++ b/resources-en/src/inspectionDescriptions/StaticPseudoFunctionalStyleMethod.html @@ -0,0 +1,5 @@ + + +Inspection detects usages of pseudo-lambda code if Java Stream API is available (language level >= Java 1.8) + + \ No newline at end of file diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 42045ae3a923..d2111515f42a 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -718,6 +718,11 @@ groupName="Code style issues" enabledByDefault="false" level="WARNING" implementationClass="com.intellij.codeInspection.BlockMarkerCommentsInspection" displayName="Block marker comment"/> + com.intellij.codeInsight.daemon.quickFix.RedundantLambdaParameterTypeIntention