inspection to highlight and convert pseudo-functional code (like guava Iterables) to java 8 api. initial version

This commit is contained in:
Dmitry Batkovich
2015-03-12 15:27:19 +03:00
parent 710104958a
commit f6ccae5153
25 changed files with 1072 additions and 92 deletions
@@ -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<PsiMethod> methods = ContainerUtil.filter(anonymousClass.getMethods(), new Condition<PsiMethod>() {
@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<PsiLocalVariable> conflictingLocals = checker.getLocals();
for (Iterator<PsiLocalVariable> 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<PsiLocalVariable> conflictingLocals = checker.getLocals();
for (Iterator<PsiLocalVariable> 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);
}
}
@@ -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<StaticPseudoFunctionalStyleMethodOptions.PipelineElement> 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<PsiMethod> 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<PsiReturnStatement> returnStatements = PsiTreeUtil.findChildrenOfType(body, PsiReturnStatement.class);
returnStatements = ContainerUtil.filter(returnStatements, new Condition<PsiReturnStatement>() {
@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<PsiMethod> methods = ContainerUtil.filter(anonymousClass.getMethods(), new Condition<PsiMethod>() {
@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;
}
}
@@ -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<String, PipelineElement> myIndex;
public StaticPseudoFunctionalStyleMethodOptions() {
myIndex = new MultiMap<String, PipelineElement>();
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<PipelineElement> 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));
}
}
@@ -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<Set<String>> STREAM_STREAM_API_METHODS = new SoftLazyValue<Set<String>>() {
@NotNull
@Override
protected Set<String> compute() {
return ContainerUtil.newHashSet(MAP, FILTER);
}
};
SoftLazyValue<Map<String, String>> FAKE_STREAM_API_METHODS_TO_PATTERN = new SoftLazyValue<Map<String, String>>() {
@NotNull
@Override
protected Map<String, String> compute() {
final HashMap<String, String> map = new HashMap<String, String>();
map.put(FAKE_FIND_MATCHED, FAKE_FIND_MATCHED_PATTERN);
return map;
}
};
}
@@ -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.al<caret>l(Collections.emptyList(), getPredicate(100));
}
public Predicate<String> getPredicate(final int param) {
return new Predicate<String>() {
@Override
public boolean apply(String input) {
System.out.println("lambda param " + param);
return false;
}
};
}
}
@@ -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<String> getPredicate(final int param) {
return new java.util.function.Predicate<String>() {
@Override
public boolean test(String input) {
System.out.println("lambda param " + param);
return false;
}
};
}
}
@@ -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.al<caret>l(Collections.emptyList(), getPredicate(100));
}
public Predicate<String> getPredicate(final int param) {
final MyComplexPredicate predicate = new MyComplexPredicate(param);
predicate.setParam2(200);
return predicate;
}
class MyComplexPredicate extends Predicate<String> {
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
}
}
}
@@ -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<String> getPredicate(final int param) {
final MyComplexPredicate predicate = new MyComplexPredicate(param);
predicate.setParam2(200);
return predicate;
}
class MyComplexPredicate extends Predicate<String> {
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
}
}
}
@@ -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.al<caret>l(Collections.<String>emptyList(), getPredicate(100));
}
public Predicate<String> getPredicate(final int param) {
return Predicates.not(new Predicate<String>() {
@Override
public boolean apply(String input) {
System.out.println("lambda param " + param);
return false;
}
});
}
}
@@ -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.<String>emptyList().stream().allMatch(getPredicate(100)::apply);
}
public Predicate<String> getPredicate(final int param) {
return Predicates.not(new Predicate<String>() {
@Override
public boolean apply(String input) {
System.out.println("lambda param " + param);
return false;
}
});
}
}
@@ -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<String> l = new ArrayList<>();
Iterable<Boolean> transform = Iterables.tr<caret>ansform(l, new Function<String, Boolean>() {
@Override
public Boolean apply(String input) {
return input.isEmpty();
}
});
}
}
@@ -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<String> l = new ArrayList<>();
Iterable<Boolean> transform = l.stream().map(String::isEmpty).collect(Collectors.toList());
}
}
@@ -0,0 +1,10 @@
import com.google.common.collect.Iterables;
import java.lang.String;
import java.util.Collections;
class c {
void m() {
Iterables.al<caret>l(Collections.emptyList(), in -> false);
}
}
@@ -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);
}
}
@@ -0,0 +1,10 @@
import com.google.common.collect.Iterables;
import java.lang.String;
import java.util.Collections;
class c {
void m() {
Iterables.an<caret>y(Collections.emptyList(), in -> false);
}
}
@@ -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);
}
}
@@ -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.fil<caret>ter(Collections.emptyList(), new Predicate<Object>() {
@Override
public boolean apply(Object input) {
return true;
}
});
}
}
@@ -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());
}
}
@@ -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.f<caret>ind(Collections.emptyList(), new Predicate<Object>() {
@Override
public boolean apply(Object input) {
return true;
}
});
}
}
@@ -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();
}
}
@@ -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.transf<caret>orm(Collections.emptyList(), new Function<String, String> () {
@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;
}
})
}
}
@@ -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())
}
}
@@ -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");
}
}
@@ -0,0 +1,5 @@
<html>
<body>
Inspection detects usages of pseudo-lambda code if Java Stream API is available (language level >= Java 1.8)
</body>
</html>
+5
View File
@@ -718,6 +718,11 @@
groupName="Code style issues" enabledByDefault="false" level="WARNING"
implementationClass="com.intellij.codeInspection.BlockMarkerCommentsInspection"
displayName="Block marker comment"/>
<localInspection groupPath="Java" language="JAVA" shortName="StaticPseudoFunctionalStyleMethod"
groupBundle="messages.InspectionsBundle"
groupName="Code style issues" enabledByDefault="false" level="WARNING"
implementationClass="com.intellij.codeInspection.java18StreamApi.StaticPseudoFunctionalStyleMethodInspection"
displayName="Pseudo functional expression using static class"/>
<intentionAction>
<className>com.intellij.codeInsight.daemon.quickFix.RedundantLambdaParameterTypeIntention</className>