mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
StaticPseudoFunctionalStyleMethodInspection: more clear options, validation of obtained result, part of problems with type parameters are fixed
This commit is contained in:
+56
-51
@@ -25,6 +25,7 @@ import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.refactoring.util.RefactoringChangeUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class AddTypeArgumentsFix extends MethodArgumentFix {
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.AddTypeArgumentsFix");
|
||||
@@ -51,57 +52,7 @@ public class AddTypeArgumentsFix extends MethodArgumentFix {
|
||||
|
||||
@Override
|
||||
protected PsiExpression getModifiedArgument(final PsiExpression expression, final PsiType toType) throws IncorrectOperationException {
|
||||
if (!PsiUtil.isLanguageLevel5OrHigher(expression)) return null;
|
||||
|
||||
if (expression instanceof PsiMethodCallExpression) {
|
||||
final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expression;
|
||||
final PsiReferenceParameterList list = methodCall.getMethodExpression().getParameterList();
|
||||
if (list == null || list.getTypeArguments().length > 0) return null;
|
||||
final JavaResolveResult resolveResult = methodCall.resolveMethodGenerics();
|
||||
final PsiElement element = resolveResult.getElement();
|
||||
if (element instanceof PsiMethod) {
|
||||
final PsiMethod method = (PsiMethod)element;
|
||||
final PsiType returnType = method.getReturnType();
|
||||
if (returnType == null) return null;
|
||||
|
||||
final PsiTypeParameter[] typeParameters = method.getTypeParameters();
|
||||
if (typeParameters.length > 0) {
|
||||
PsiType[] mappings = PsiType.createArray(typeParameters.length);
|
||||
PsiResolveHelper helper = JavaPsiFacade.getInstance(expression.getProject()).getResolveHelper();
|
||||
LanguageLevel level = PsiUtil.getLanguageLevel(expression);
|
||||
for (int i = 0; i < typeParameters.length; i++) {
|
||||
PsiTypeParameter typeParameter = typeParameters[i];
|
||||
final PsiType substitution = helper.getSubstitutionForTypeParameter(typeParameter, returnType, toType, false, level);
|
||||
if (substitution == null || PsiType.NULL.equals(substitution)) return null;
|
||||
mappings[i] = GenericsUtil.eliminateWildcards(substitution, false);
|
||||
}
|
||||
|
||||
final PsiElementFactory factory = JavaPsiFacade.getInstance(expression.getProject()).getElementFactory();
|
||||
PsiMethodCallExpression copy = (PsiMethodCallExpression)expression.copy();
|
||||
final PsiReferenceExpression methodExpression = copy.getMethodExpression();
|
||||
final PsiReferenceParameterList parameterList = methodExpression.getParameterList();
|
||||
LOG.assertTrue(parameterList != null);
|
||||
for (PsiType mapping : mappings) {
|
||||
parameterList.add(factory.createTypeElement(mapping));
|
||||
}
|
||||
if (methodExpression.getQualifierExpression() == null) {
|
||||
final PsiExpression qualifierExpression;
|
||||
final PsiClass containingClass = method.getContainingClass();
|
||||
LOG.assertTrue(containingClass != null);
|
||||
if (method.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
qualifierExpression = factory.createReferenceExpression(containingClass);
|
||||
} else {
|
||||
qualifierExpression = RefactoringChangeUtil.createThisExpression(method.getManager(), null);
|
||||
}
|
||||
methodExpression.setQualifierExpression(qualifierExpression);
|
||||
}
|
||||
|
||||
return (PsiExpression)JavaCodeStyleManager.getInstance(copy.getProject()).shortenClassReferences(copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return addTypeArguments(expression, toType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -110,5 +61,59 @@ public class AddTypeArgumentsFix extends MethodArgumentFix {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiExpression addTypeArguments(PsiExpression expression, PsiType toType) {
|
||||
if (!PsiUtil.isLanguageLevel5OrHigher(expression)) return null;
|
||||
|
||||
if (expression instanceof PsiMethodCallExpression) {
|
||||
final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expression;
|
||||
final PsiReferenceParameterList list = methodCall.getMethodExpression().getParameterList();
|
||||
if (list == null || list.getTypeArguments().length > 0) return null;
|
||||
final JavaResolveResult resolveResult = methodCall.resolveMethodGenerics();
|
||||
final PsiElement element = resolveResult.getElement();
|
||||
if (element instanceof PsiMethod) {
|
||||
final PsiMethod method = (PsiMethod)element;
|
||||
final PsiType returnType = method.getReturnType();
|
||||
if (returnType == null) return null;
|
||||
|
||||
final PsiTypeParameter[] typeParameters = method.getTypeParameters();
|
||||
if (typeParameters.length > 0) {
|
||||
PsiType[] mappings = PsiType.createArray(typeParameters.length);
|
||||
PsiResolveHelper helper = JavaPsiFacade.getInstance(expression.getProject()).getResolveHelper();
|
||||
LanguageLevel level = PsiUtil.getLanguageLevel(expression);
|
||||
for (int i = 0; i < typeParameters.length; i++) {
|
||||
PsiTypeParameter typeParameter = typeParameters[i];
|
||||
final PsiType substitution = helper.getSubstitutionForTypeParameter(typeParameter, returnType, toType, false, level);
|
||||
if (substitution == null || PsiType.NULL.equals(substitution)) return null;
|
||||
mappings[i] = GenericsUtil.eliminateWildcards(substitution, false);
|
||||
}
|
||||
|
||||
final PsiElementFactory factory = JavaPsiFacade.getInstance(expression.getProject()).getElementFactory();
|
||||
PsiMethodCallExpression copy = (PsiMethodCallExpression)expression.copy();
|
||||
final PsiReferenceExpression methodExpression = copy.getMethodExpression();
|
||||
final PsiReferenceParameterList parameterList = methodExpression.getParameterList();
|
||||
LOG.assertTrue(parameterList != null);
|
||||
for (PsiType mapping : mappings) {
|
||||
parameterList.add(factory.createTypeElement(mapping));
|
||||
}
|
||||
if (methodExpression.getQualifierExpression() == null) {
|
||||
final PsiExpression qualifierExpression;
|
||||
final PsiClass containingClass = method.getContainingClass();
|
||||
LOG.assertTrue(containingClass != null);
|
||||
if (method.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
qualifierExpression = factory.createReferenceExpression(containingClass);
|
||||
} else {
|
||||
qualifierExpression = RefactoringChangeUtil.createThisExpression(method.getManager(), null);
|
||||
}
|
||||
methodExpression.setQualifierExpression(qualifierExpression);
|
||||
}
|
||||
|
||||
return (PsiExpression)JavaCodeStyleManager.getInstance(copy.getProject()).shortenClassReferences(copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ArgumentFixerActionFactory REGISTRAR = new AddTypeArgumentsFix.MyFixerActionFactory();
|
||||
}
|
||||
|
||||
+92
-42
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.codeInspection.java18StreamApi.AddMethodsDialog">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="500" height="400"/>
|
||||
@@ -8,53 +8,103 @@
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="5c4b9" class="com.intellij.ui.components.JBLabel">
|
||||
<grid id="8e5de" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Fully qualified class name:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<vspacer id="c8137">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
<component id="c43e4" class="com.intellij.ui.components.JBLabel">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Method name:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="f01fe" class="com.intellij.ui.components.JBLabel">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Stream API replacement:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="1f7b6" class="com.intellij.openapi.ui.ComboBox" binding="myPatternsCombo">
|
||||
<constraints>
|
||||
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="1" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="7bc3f" class="com.intellij.refactoring.ui.ClassNameReferenceEditor" binding="myClassNameEditor" custom-create="true" default-binding="true">
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="5c4b9" class="com.intellij.ui.components.JBLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Fully qualified class name:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="c43e4" class="com.intellij.ui.components.JBLabel">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Method name:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="f01fe" class="com.intellij.ui.components.JBLabel">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Stream API replacement:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="1f7b6" class="com.intellij.openapi.ui.ComboBox" binding="myTemplatesCombo">
|
||||
<constraints>
|
||||
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="f7881" class="com.intellij.openapi.ui.ComboBox" binding="myMethodNameCombo">
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="0" fill="1" indent="0" use-parent-layout="false">
|
||||
<minimum-size width="200" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="9c747" class="com.intellij.refactoring.ui.ClassNameReferenceEditor" binding="myClassNameEditor" custom-create="true">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="6dced" binding="myExamplePanel" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="7" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="f7881" class="com.intellij.openapi.ui.ComboBox" binding="myMethodNameCombo">
|
||||
<constraints>
|
||||
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="8c2dc" class="com.intellij.codeInsight.intention.impl.config.ActionUsagePanel" binding="myBeforeActionPanel">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
|
||||
<minimum-size width="-1" height="50"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="e9aff" class="com.intellij.codeInsight.intention.impl.config.ActionUsagePanel" binding="myAfterActionPanel">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false">
|
||||
<minimum-size width="-1" height="50"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="cc542" class="com.intellij.ui.TitledSeparator">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text value="Before quick fix:"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="a9a06" class="com.intellij.ui.TitledSeparator">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<opaque value="false"/>
|
||||
<text value="After quick fix:"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
|
||||
+137
-25
@@ -16,27 +16,36 @@
|
||||
package com.intellij.codeInspection.java18StreamApi;
|
||||
|
||||
|
||||
import com.intellij.codeInsight.intention.impl.config.ActionUsagePanel;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.event.DocumentAdapter;
|
||||
import com.intellij.openapi.editor.event.DocumentEvent;
|
||||
import com.intellij.openapi.fileTypes.StdFileTypes;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.ComboBox;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.psi.JavaPsiFacade;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.refactoring.ui.ClassNameReferenceEditor;
|
||||
import com.intellij.ui.CollectionComboBoxModel;
|
||||
import com.intellij.ui.ColoredListCellRenderer;
|
||||
import com.intellij.ui.ListCellRendererWrapper;
|
||||
import com.intellij.ui.SimpleTextAttributes;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Dmitry Batkovich
|
||||
@@ -46,36 +55,94 @@ public class AddMethodsDialog extends DialogWrapper {
|
||||
@NotNull private final Project myProject;
|
||||
|
||||
private JPanel myPanel;
|
||||
private ComboBox myPatternsCombo;
|
||||
private ComboBox myTemplatesCombo;
|
||||
private ClassNameReferenceEditor myClassNameEditor;
|
||||
private ComboBox myMethodNameCombo;
|
||||
private ActionUsagePanel myBeforeActionPanel;
|
||||
private ActionUsagePanel myAfterActionPanel;
|
||||
private JPanel myExamplePanel;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected AddMethodsDialog(@NotNull final Project project, @NotNull final Component parent, boolean canBeParent) {
|
||||
super(parent, canBeParent);
|
||||
myProject = project;
|
||||
final DefaultComboBoxModel model = new DefaultComboBoxModel();
|
||||
myPatternsCombo.setModel(model);
|
||||
for (String methodName : StreamApiConstants.STREAM_STREAM_API_METHODS.getValue()) {
|
||||
model.addElement(methodName);
|
||||
}
|
||||
model.addElement(StreamApiConstants.FAKE_FIND_MATCHED);
|
||||
myPatternsCombo.setRenderer(new ColoredListCellRenderer<String>() {
|
||||
myTemplatesCombo.setEnabled(false);
|
||||
myTemplatesCombo.setRenderer(new ColoredListCellRenderer<PseudoLambdaReplaceTemplate>() {
|
||||
@Override
|
||||
protected void customizeCellRenderer(JList list, String methodName, int index, boolean selected, boolean hasFocus) {
|
||||
protected void customizeCellRenderer(JList list,
|
||||
PseudoLambdaReplaceTemplate template,
|
||||
int index,
|
||||
boolean selected,
|
||||
boolean hasFocus) {
|
||||
if (template == null) {
|
||||
return;
|
||||
}
|
||||
append("stream.");
|
||||
if (StreamApiConstants.STREAM_STREAM_API_METHODS.getValue().contains(methodName)) {
|
||||
append(methodName + "()", SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
|
||||
final String streamApiMethodName = template.getStreamApiMethodName();
|
||||
if (StreamApiConstants.STREAM_STREAM_API_METHODS.getValue().contains(streamApiMethodName)) {
|
||||
append(streamApiMethodName + "()", SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
|
||||
}
|
||||
else {
|
||||
LOG.assertTrue(StreamApiConstants.FAKE_FIND_MATCHED.equals(methodName));
|
||||
LOG.assertTrue(StreamApiConstants.FAKE_FIND_MATCHED.equals(streamApiMethodName));
|
||||
append(String.format(StreamApiConstants.FAKE_FIND_MATCHED_PATTERN, "condition"), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
|
||||
append(" or ");
|
||||
append(String.format(StreamApiConstants.FAKE_FIND_MATCHED_WITH_DEFAULT_PATTERN, "condition", "defaultValue"), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
|
||||
append(".orElseGet(() -> defaultValue)", SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
|
||||
}
|
||||
}
|
||||
});
|
||||
myTemplatesCombo.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
final PseudoLambdaReplaceTemplate template = (PseudoLambdaReplaceTemplate)e.getItem();
|
||||
final Collection<PsiMethod> methods = (Collection<PsiMethod>)myMethodNameCombo.getSelectedItem();
|
||||
if (methods == null) {
|
||||
return;
|
||||
}
|
||||
for (PsiMethod method : methods) {
|
||||
if (template.validate(method) != null) {
|
||||
showTemplateExample(template, method);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
myMethodNameCombo.setModel(new DefaultComboBoxModel());
|
||||
myMethodNameCombo.addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
if (!myExamplePanel.isEnabled()) {
|
||||
myExamplePanel.setEnabled(true);
|
||||
}
|
||||
final Collection<PseudoLambdaReplaceTemplate> suitableTemplates = new LinkedHashSet<PseudoLambdaReplaceTemplate>();
|
||||
final Collection<PsiMethod> methods = (Collection<PsiMethod>) e.getItem();
|
||||
for (PseudoLambdaReplaceTemplate template : PseudoLambdaReplaceTemplate.getAllTemplates()) {
|
||||
for (PsiMethod method : methods) {
|
||||
if (template.validate(method) != null) {
|
||||
if (suitableTemplates.isEmpty()) {
|
||||
showTemplateExample(template, method);
|
||||
}
|
||||
suitableTemplates.add(template);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!myTemplatesCombo.isEnabled()) {
|
||||
myTemplatesCombo.setEnabled(true);
|
||||
}
|
||||
LOG.assertTrue(!suitableTemplates.isEmpty());
|
||||
final List<PseudoLambdaReplaceTemplate> templatesAsList = new ArrayList<PseudoLambdaReplaceTemplate>(suitableTemplates);
|
||||
myTemplatesCombo.setModel(new CollectionComboBoxModel(templatesAsList));
|
||||
myTemplatesCombo.setSelectedItem(templatesAsList.get(0));
|
||||
}
|
||||
});
|
||||
myMethodNameCombo.setRenderer(new ListCellRendererWrapper<Collection<PsiMethod>>() {
|
||||
@Override
|
||||
public void customize(JList list, Collection<PsiMethod> methods, int index, boolean selected, boolean hasFocus) {
|
||||
if (methods != null) {
|
||||
LOG.assertTrue(!methods.isEmpty());
|
||||
setText(ContainerUtil.getFirstItem(methods).getName());
|
||||
}
|
||||
}
|
||||
});
|
||||
myClassNameEditor.addDocumentListener(new DocumentAdapter() {
|
||||
@Override
|
||||
public void documentChanged(DocumentEvent e) {
|
||||
@@ -87,34 +154,79 @@ public class AddMethodsDialog extends DialogWrapper {
|
||||
myMethodNameCombo.setEnabled(false);
|
||||
}
|
||||
else {
|
||||
for (String name : ContainerUtil.newTreeSet(ContainerUtil.mapNotNull(aClass.getMethods(), new Function<PsiMethod, String>() {
|
||||
final List<PseudoLambdaReplaceTemplate> possibleTemplates = PseudoLambdaReplaceTemplate.getAllTemplates();
|
||||
final MultiMap<String, PsiMethod> nameToMethod = new MultiMap<String, PsiMethod>();
|
||||
for (PsiMethod m : ContainerUtil.filter(aClass.getMethods(), new Condition<PsiMethod>() {
|
||||
@Override
|
||||
public String fun(PsiMethod method) {
|
||||
public boolean value(PsiMethod method) {
|
||||
if (method.isConstructor() ||
|
||||
!method.hasModifierProperty(PsiModifier.STATIC) ||
|
||||
method.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
return method.getName();
|
||||
boolean templateFound = false;
|
||||
for (PseudoLambdaReplaceTemplate template : possibleTemplates) {
|
||||
if (template.validate(method) != null) {
|
||||
templateFound = true;
|
||||
}
|
||||
}
|
||||
if (!templateFound) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}))) {
|
||||
comboBoxModel.addElement(name);
|
||||
})) {
|
||||
nameToMethod.putValue(m.getName(), m);
|
||||
}
|
||||
for (Map.Entry<String, Collection<PsiMethod>> entry : nameToMethod.entrySet()) {
|
||||
comboBoxModel.addElement(entry.getValue());
|
||||
}
|
||||
myMethodNameCombo.setEnabled(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
myExamplePanel.setEnabled(false);
|
||||
init();
|
||||
}
|
||||
|
||||
private void showTemplateExample(final PseudoLambdaReplaceTemplate template, final PsiMethod method) {
|
||||
final PsiClass aClass = method.getContainingClass();
|
||||
LOG.assertTrue(aClass != null);
|
||||
final String fqn = aClass.getQualifiedName();
|
||||
LOG.assertTrue(fqn != null);
|
||||
final String parameters =
|
||||
StringUtil.join(ContainerUtil.map(method.getParameterList().getParameters(), new Function<PsiParameter, String>() {
|
||||
@Override
|
||||
public String fun(PsiParameter parameter) {
|
||||
return parameter.getName();
|
||||
}
|
||||
}), ", ");
|
||||
final String expressionText = fqn + "." + method.getName() + "(" + parameters + ")";
|
||||
final PsiExpression psiExpression = JavaPsiFacade.getElementFactory(method.getProject())
|
||||
.createExpressionFromText(expressionText, null);
|
||||
LOG.assertTrue(psiExpression instanceof PsiMethodCallExpression);
|
||||
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)psiExpression;
|
||||
template.convertToStream(methodCallExpression, method, false);
|
||||
myBeforeActionPanel.reset("void example() {\n <spot>" + methodCallExpression.getText() + "</spot>;\n}", StdFileTypes.JAVA);
|
||||
myAfterActionPanel.reset("void example() {\n <spot>" + template.convertToStream(methodCallExpression, method, true).getText() + "</spot>\n}", StdFileTypes.JAVA);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispose() {
|
||||
Disposer.dispose(myBeforeActionPanel);
|
||||
Disposer.dispose(myAfterActionPanel);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private void createUIComponents() {
|
||||
myClassNameEditor = new ClassNameReferenceEditor(myProject, null);
|
||||
}
|
||||
|
||||
public StaticPseudoFunctionalStyleMethodOptions.PipelineElement getSelectedElement() {
|
||||
return new StaticPseudoFunctionalStyleMethodOptions.PipelineElement(myClassNameEditor.getText(),
|
||||
(String)myMethodNameCombo.getSelectedItem(),
|
||||
(String)myPatternsCombo.getSelectedItem());
|
||||
ContainerUtil.getFirstItem((Collection < PsiMethod >)myMethodNameCombo.getSelectedItem()).getName(),
|
||||
(PseudoLambdaReplaceTemplate)myTemplatesCombo.getSelectedItem());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
+629
@@ -0,0 +1,629 @@
|
||||
/*
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.codeInspection.java18StreamApi;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.AddTypeArgumentsFix;
|
||||
import com.intellij.codeInspection.AnonymousCanBeLambdaInspection;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.impl.PsiDiamondTypeUtil;
|
||||
import com.intellij.psi.impl.PsiSubstitutorImpl;
|
||||
import com.intellij.psi.impl.source.PsiClassReferenceType;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.hash.HashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Dmitry Batkovich
|
||||
*/
|
||||
class PseudoLambdaReplaceTemplate {
|
||||
private final static Logger LOG = Logger.getInstance(PseudoLambdaReplaceTemplate.class);
|
||||
|
||||
public enum LambdaRole {
|
||||
PREDICATE,
|
||||
FUNCTION
|
||||
}
|
||||
|
||||
public static class ValidationInfo {
|
||||
private final int myLambdaPosition;
|
||||
private final int myIterablePosition;
|
||||
private final int myDefaultValuePosition;
|
||||
|
||||
public ValidationInfo(int lambdaPosition, int iterablePosition, int defaultValuePosition) {
|
||||
myLambdaPosition = lambdaPosition;
|
||||
myIterablePosition = iterablePosition;
|
||||
myDefaultValuePosition = defaultValuePosition;
|
||||
}
|
||||
|
||||
public int getLambdaPosition() {
|
||||
return myLambdaPosition;
|
||||
}
|
||||
|
||||
public int getIterablePosition() {
|
||||
return myIterablePosition;
|
||||
}
|
||||
|
||||
public int getDefaultValuePosition() {
|
||||
return myDefaultValuePosition;
|
||||
}
|
||||
}
|
||||
|
||||
static final PseudoLambdaReplaceTemplate MAP =
|
||||
new PseudoLambdaReplaceTemplate(StreamApiConstants.MAP, LambdaRole.FUNCTION, false);
|
||||
static final PseudoLambdaReplaceTemplate FILTER =
|
||||
new PseudoLambdaReplaceTemplate(StreamApiConstants.FILTER, LambdaRole.PREDICATE, false);
|
||||
static final PseudoLambdaReplaceTemplate FIND =
|
||||
new PseudoLambdaReplaceTemplate(StreamApiConstants.FAKE_FIND_MATCHED, LambdaRole.PREDICATE, true);
|
||||
static final PseudoLambdaReplaceTemplate ALL_MATCH =
|
||||
new PseudoLambdaReplaceTemplate(StreamApiConstants.ALL_MATCH, LambdaRole.PREDICATE, false);
|
||||
static final PseudoLambdaReplaceTemplate ANY_MATCH =
|
||||
new PseudoLambdaReplaceTemplate(StreamApiConstants.ANY_MATCH, LambdaRole.PREDICATE, false);
|
||||
|
||||
private final String myStreamApiMethodName;
|
||||
private final LambdaRole myLambdaRole;
|
||||
private final boolean myAcceptDefaultValue;
|
||||
|
||||
public PseudoLambdaReplaceTemplate(String method,
|
||||
LambdaRole type,
|
||||
boolean acceptDefaultValue) {
|
||||
myStreamApiMethodName = method;
|
||||
myLambdaRole = type;
|
||||
myAcceptDefaultValue = acceptDefaultValue;
|
||||
}
|
||||
|
||||
public static List<PseudoLambdaReplaceTemplate> getAllTemplates() {
|
||||
return ContainerUtil.newArrayList(MAP, FILTER, FIND, ALL_MATCH, ANY_MATCH);
|
||||
}
|
||||
|
||||
public ValidationInfo validate(final PsiMethod method) {
|
||||
final PsiParameter[] parameters = method.getParameterList().getParameters();
|
||||
final PsiType[] parameterTypes =
|
||||
ContainerUtil.map2Array(Arrays.asList(parameters), new PsiType[parameters.length], new Function<PsiParameter, PsiType>() {
|
||||
@Override
|
||||
public PsiType fun(PsiParameter parameter) {
|
||||
return parameter.getType();
|
||||
}
|
||||
});
|
||||
final PsiType returnType = method.getReturnType();
|
||||
|
||||
if (returnType instanceof PsiClassType) {
|
||||
final PsiClass resolvedReturnTypeClass = ((PsiClassType)returnType).resolve();
|
||||
if (!InheritanceUtil.isInheritor(resolvedReturnTypeClass, CommonClassNames.JAVA_LANG_ITERABLE)) {
|
||||
return null;
|
||||
}
|
||||
} else if (!(returnType instanceof PsiArrayType)) {
|
||||
return null;
|
||||
}
|
||||
return validate(parameterTypes, returnType, method);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ValidationInfo validate(final PsiMethodCallExpression expression) {
|
||||
final PsiType[] argumentTypes = expression.getArgumentList().getExpressionTypes();
|
||||
final PsiType methodReturnType = expression.getType();
|
||||
if (methodReturnType == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final PsiMethod method = expression.resolveMethod();
|
||||
if (method == null) {
|
||||
return null;
|
||||
}
|
||||
final PsiParameter[] expectedParameters = method.getParameterList().getParameters();
|
||||
|
||||
if (argumentTypes.length != expectedParameters.length) {
|
||||
return null;
|
||||
}
|
||||
return validate(argumentTypes, methodReturnType, expression);
|
||||
}
|
||||
|
||||
public String getStreamApiMethodName() {
|
||||
return myStreamApiMethodName;
|
||||
}
|
||||
|
||||
public LambdaRole getLambdaRole() {
|
||||
return myLambdaRole;
|
||||
}
|
||||
|
||||
public boolean isAcceptDefaultValue() {
|
||||
return myAcceptDefaultValue;
|
||||
}
|
||||
|
||||
private ValidationInfo validate(final PsiType[] arguments, final PsiType methodReturnType, final PsiElement context) {
|
||||
int lambdaPosition = -1;
|
||||
int defaultValuePosition = -1;
|
||||
int iterablePosition = -1;
|
||||
|
||||
if (!myAcceptDefaultValue) {
|
||||
if (arguments.length != 2) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
if (arguments.length != 2 && arguments.length != 3) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < arguments.length; i++) {
|
||||
PsiType type = arguments[i];
|
||||
if (isFunction(type, methodReturnType, context)) {
|
||||
if (lambdaPosition == -1) {
|
||||
lambdaPosition = i;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (isIterableOrArray(type)) {
|
||||
if (iterablePosition == -1) {
|
||||
iterablePosition = i;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (myAcceptDefaultValue && methodReturnType.isAssignableFrom(type)) {
|
||||
if (defaultValuePosition == -1) {
|
||||
defaultValuePosition = i;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lambdaPosition == -1 || iterablePosition == -1) {
|
||||
return null;
|
||||
}
|
||||
if (myAcceptDefaultValue) {
|
||||
if (defaultValuePosition == -1 && arguments.length == 3) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return new ValidationInfo(lambdaPosition, iterablePosition, defaultValuePosition);
|
||||
}
|
||||
|
||||
private boolean isFunction(PsiType type, PsiType baseMethodReturnType, PsiElement context) {
|
||||
if (type instanceof PsiMethodReferenceType) {
|
||||
final PsiMethodReferenceExpression expression = ((PsiMethodReferenceType)type).getExpression();
|
||||
final PsiMethod resolvedMethod = (PsiMethod)expression.resolve();
|
||||
if (resolvedMethod == null) {
|
||||
return false;
|
||||
}
|
||||
final PsiParameter[] parameters = resolvedMethod.getParameterList().getParameters();
|
||||
if ((parameters.length != 1 && myLambdaRole == LambdaRole.FUNCTION) || (parameters.length != 0 && myLambdaRole == LambdaRole.PREDICATE)) {
|
||||
return false;
|
||||
}
|
||||
final PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(expression);
|
||||
return isSuitableLambdaRole(returnType, baseMethodReturnType, context);
|
||||
} else if (type instanceof PsiLambdaExpressionType) {
|
||||
final PsiLambdaExpression expression = ((PsiLambdaExpressionType)type).getExpression();
|
||||
final PsiType psiType = LambdaUtil.getFunctionalInterfaceReturnType(expression.getFunctionalInterfaceType());
|
||||
return isSuitableLambdaRole(psiType, baseMethodReturnType, context);
|
||||
} else if (isSuitableFunctionalType(type, baseMethodReturnType, context)) {
|
||||
return true;
|
||||
}
|
||||
return isJavaLangClassType(type) && myLambdaRole == LambdaRole.PREDICATE;
|
||||
}
|
||||
|
||||
private boolean isSuitableLambdaRole(final PsiType lambdaReturnType,
|
||||
final PsiType baseMethodReturnType,
|
||||
final PsiElement context) {
|
||||
if (lambdaReturnType == null) {
|
||||
return false;
|
||||
}
|
||||
if (myLambdaRole == LambdaRole.PREDICATE) {
|
||||
final PsiClassType boxedBoolean = PsiType.BOOLEAN.getBoxedType(context);
|
||||
if (!(PsiType.BOOLEAN.equals(lambdaReturnType) || (boxedBoolean != null && boxedBoolean.equals(lambdaReturnType)))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOG.assertTrue(myLambdaRole == LambdaRole.FUNCTION);
|
||||
if (baseMethodReturnType instanceof PsiClassType) {
|
||||
final PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)baseMethodReturnType).resolveGenerics();
|
||||
final Map<PsiTypeParameter, PsiType> substitutionMap = resolveResult.getSubstitutor().getSubstitutionMap();
|
||||
if (substitutionMap.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
final PsiType iterableParametrizedType = ContainerUtil.getFirstItem(substitutionMap.values());
|
||||
if (!lambdaReturnType.equals(iterableParametrizedType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (baseMethodReturnType instanceof PsiArrayType) {
|
||||
if (!lambdaReturnType.equals(((PsiArrayType)baseMethodReturnType).getComponentType())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isJavaLangClassType(PsiType type) {
|
||||
if (type instanceof PsiClassType) {
|
||||
final PsiClass resolvedClass = ((PsiClassType)type).resolve();
|
||||
if (resolvedClass != null && CommonClassNames.JAVA_LANG_CLASS.equals(resolvedClass.getQualifiedName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isSuitableFunctionalType(final PsiType type, final PsiType baseMethodReturnType, final PsiElement context) {
|
||||
if (type instanceof PsiClassType) {
|
||||
PsiClass targetClass = ((PsiClassType)type).resolve();
|
||||
if (targetClass != null) {
|
||||
if (targetClass instanceof PsiAnonymousClass) {
|
||||
targetClass = ((PsiAnonymousClass)targetClass).getBaseClassType().resolve();
|
||||
if (targetClass == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!LambdaUtil.isFunctionalClass(targetClass)) {
|
||||
return false;
|
||||
}
|
||||
PsiMethod method = LambdaUtil.getFunctionalInterfaceMethod(targetClass);
|
||||
final PsiMethod[] methods = ((PsiClassType)type).resolve().findMethodsByName(method.getName(), false);
|
||||
if (methods.length != 1) {
|
||||
return false;
|
||||
}
|
||||
method = methods[0];
|
||||
if (method == null) {
|
||||
return false;
|
||||
}
|
||||
final PsiType psiType;
|
||||
if (type instanceof PsiClassReferenceType) {
|
||||
final PsiJavaCodeReferenceElement reference = ((PsiClassReferenceType)type).getReference();
|
||||
final PsiClass resolvedClass = ((PsiClassReferenceType)type).resolve();
|
||||
final PsiTypeParameter[] typeParameters = resolvedClass.getTypeParameters();
|
||||
final PsiType[] substitutedTypeParameters = reference.getTypeParameters();
|
||||
|
||||
LOG.assertTrue(typeParameters.length == substitutedTypeParameters.length);
|
||||
final Map<PsiTypeParameter, PsiType> substitutionMap = new HashMap<PsiTypeParameter, PsiType>();
|
||||
for (int i = 0; i < typeParameters.length; i++) {
|
||||
PsiTypeParameter parameter = typeParameters[i];
|
||||
PsiType t = substitutedTypeParameters[i];
|
||||
substitutionMap.put(parameter, t);
|
||||
}
|
||||
PsiSubstitutor substitutor = PsiSubstitutorImpl.createSubstitutor(substitutionMap);
|
||||
psiType = substitutor.substitute(method.getReturnType());
|
||||
} else {
|
||||
psiType = method.getReturnType();
|
||||
}
|
||||
return isSuitableLambdaRole(psiType, baseMethodReturnType, context);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PsiExpression convertToStream(final PsiMethodCallExpression expression, PsiMethod method, boolean force) {
|
||||
LOG.assertTrue(expression != null);
|
||||
if (method == null) {
|
||||
method = expression.resolveMethod();
|
||||
if (method == null) {
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
final ValidationInfo validationInfo = force ? validate(method) : validate(expression);
|
||||
if (validationInfo == null) {
|
||||
return expression;
|
||||
}
|
||||
final Project project = expression.getProject();
|
||||
int lambdaIndex = validationInfo.getLambdaPosition();
|
||||
|
||||
final PsiExpression[] expressions = expression.getArgumentList().getExpressions();
|
||||
PsiExpression lambdaExpression = expressions[lambdaIndex];
|
||||
if (!force) {
|
||||
lambdaExpression = convertClassTypeExpression(lambdaExpression);
|
||||
lambdaExpression = convertToJavaLambda(lambdaExpression, myStreamApiMethodName);
|
||||
}
|
||||
LOG.assertTrue(lambdaExpression != null);
|
||||
|
||||
final PsiExpression iterableExpression = expressions[validationInfo.getIterablePosition()];
|
||||
final String pipelineHead = createPipelineHeadText(iterableExpression, lambdaExpression, force);
|
||||
|
||||
|
||||
final String lambdaExpressionText;
|
||||
final String elementText;
|
||||
if (!StreamApiConstants.FAKE_FIND_MATCHED.equals(myStreamApiMethodName)) {
|
||||
elementText = myStreamApiMethodName;
|
||||
lambdaExpressionText = lambdaExpression.getText();
|
||||
}
|
||||
else {
|
||||
elementText = validationInfo.getDefaultValuePosition() != -1
|
||||
? String.format(StreamApiConstants.FAKE_FIND_MATCHED_WITH_DEFAULT_PATTERN, lambdaExpression.getText(),
|
||||
expressions[validationInfo.getDefaultValuePosition()].getText())
|
||||
: String.format(StreamApiConstants.FAKE_FIND_MATCHED_PATTERN, lambdaExpression.getText());
|
||||
lambdaExpressionText = null;
|
||||
}
|
||||
final String pipelineTail =
|
||||
StreamApiConstants.STREAM_STREAM_API_METHODS.getValue().contains(myStreamApiMethodName)
|
||||
? findSuitableTailMethodForCollection(method)
|
||||
: null;
|
||||
|
||||
final PsiElement replaced =
|
||||
expression.replace(createPipelineExpression(pipelineHead, elementText, lambdaExpressionText, pipelineTail, project));
|
||||
JavaCodeStyleManager.getInstance(project).shortenClassReferences(replaced.getParent());
|
||||
return (PsiExpression)replaced;
|
||||
}
|
||||
|
||||
private static PsiExpression createPipelineExpression(String pipelineHead,
|
||||
String elementText,
|
||||
String lambdaExpression,
|
||||
String pipelineTail,
|
||||
Project project) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(pipelineHead).append(".").append(elementText);
|
||||
if (lambdaExpression != null) {
|
||||
sb.append("(").append(lambdaExpression).append(")");
|
||||
}
|
||||
if (pipelineTail != null) {
|
||||
sb.append(".").append(pipelineTail);
|
||||
}
|
||||
return JavaPsiFacade.getElementFactory(project).createExpressionFromText(sb.toString(), null);
|
||||
}
|
||||
|
||||
private static void convertNewExpression(PsiMethod containingMethod, PsiNewExpression newExpression, PsiClass expectedReturnClass) {
|
||||
final String expectedReturnQName = expectedReturnClass.getQualifiedName();
|
||||
LOG.assertTrue(expectedReturnQName != null);
|
||||
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(newExpression.getProject());
|
||||
PsiAnonymousClass anonymousClass = PsiTreeUtil.findChildOfType(newExpression, PsiAnonymousClass.class);
|
||||
LOG.assertTrue(anonymousClass != null);
|
||||
PsiJavaCodeReferenceElement referenceElement = PsiTreeUtil.findChildOfType(anonymousClass, PsiJavaCodeReferenceElement.class);
|
||||
LOG.assertTrue(referenceElement != null);
|
||||
final PsiReferenceParameterList parameterList = PsiTreeUtil.findChildOfType(referenceElement, PsiReferenceParameterList.class);
|
||||
final PsiJavaCodeReferenceElement newCodeReferenceElement = factory.createReferenceFromText(expectedReturnClass.getQualifiedName()
|
||||
+
|
||||
(parameterList == null
|
||||
? ""
|
||||
: parameterList.getText()), null);
|
||||
referenceElement.replace(newCodeReferenceElement);
|
||||
final List<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) {
|
||||
final PsiType arrayComponentType = ((PsiArrayType)type).getComponentType();
|
||||
return "toArray(" + arrayComponentType.getCanonicalText() + "[]::new)";
|
||||
}
|
||||
else if (type instanceof PsiClassType) {
|
||||
final PsiClass resolvedClass = ((PsiClassType)type).resolve();
|
||||
if (resolvedClass == null) {
|
||||
return null;
|
||||
}
|
||||
final String qName = resolvedClass.getQualifiedName();
|
||||
if (qName == null) {
|
||||
return null;
|
||||
}
|
||||
if (qName.equals(CommonClassNames.JAVA_UTIL_LIST)
|
||||
|| qName.equals(CommonClassNames.JAVA_UTIL_COLLECTION)
|
||||
|| qName.equals(CommonClassNames.JAVA_LANG_ITERABLE)) {
|
||||
return "collect(" + StreamApiConstants.JAVA_UTIL_STREAM_COLLECTORS + ".toList())";
|
||||
}
|
||||
else if (qName.equals(CommonClassNames.JAVA_UTIL_SET)) {
|
||||
return "collect(" + StreamApiConstants.JAVA_UTIL_STREAM_COLLECTORS + ".toSet())";
|
||||
}
|
||||
else if (qName.equals(CommonClassNames.JAVA_UTIL_ITERATOR)) {
|
||||
return "iterator()";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static PsiExpression convertToJavaLambda(PsiExpression expression, String streamApiMethodName) {
|
||||
if (streamApiMethodName.equals(StreamApiConstants.FAKE_FIND_MATCHED)) {
|
||||
streamApiMethodName = StreamApiConstants.FILTER;
|
||||
}
|
||||
if (expression instanceof PsiMethodReferenceExpression) {
|
||||
return expression;
|
||||
}
|
||||
if (expression instanceof PsiLambdaExpression) {
|
||||
return expression;
|
||||
}
|
||||
if (expression instanceof PsiMethodCallExpression) {
|
||||
final PsiMethod method = ((PsiMethodCallExpression)expression).resolveMethod();
|
||||
if (method == null) {
|
||||
return null;
|
||||
}
|
||||
final PsiType type = method.getReturnType();
|
||||
if (!(type instanceof PsiClassType)) {
|
||||
return null;
|
||||
}
|
||||
final PsiClass lambdaClass = ((PsiClassType)type).resolve();
|
||||
if (lambdaClass == null) {
|
||||
return null;
|
||||
}
|
||||
final String methodName = lambdaClass.getMethods()[0].getName();
|
||||
if (tryConvertPseudoLambdaToStreamApi(method, resolveStreamApiLambdaClass(expression.getProject(), streamApiMethodName))) {
|
||||
return expression;
|
||||
}
|
||||
else {
|
||||
return JavaPsiFacade.getElementFactory(expression.getProject())
|
||||
.createExpressionFromText(expression.getText() + "::" + methodName, null);
|
||||
}
|
||||
}
|
||||
return AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiClass resolveStreamApiLambdaClass(Project project, String streamApiMethodName) {
|
||||
final PsiClass javaUtilStream = JavaPsiFacade.getInstance(project)
|
||||
.findClass(StreamApiConstants.JAVA_UTIL_STREAM_STREAM, GlobalSearchScope.notScope(GlobalSearchScope.projectScope(project)));
|
||||
LOG.assertTrue(javaUtilStream != null);
|
||||
final PsiMethod[] methods = javaUtilStream.findMethodsByName(streamApiMethodName, false);
|
||||
LOG.assertTrue(methods.length == 1);
|
||||
final PsiMethod method = methods[0];
|
||||
final PsiParameter[] parameters = method.getParameterList().getParameters();
|
||||
LOG.assertTrue(parameters.length == 1);
|
||||
final PsiType type = parameters[0].getType();
|
||||
LOG.assertTrue(type instanceof PsiClassType);
|
||||
final PsiClass resolved = ((PsiClassType)type).resolve();
|
||||
LOG.assertTrue(resolved != null);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private static boolean tryConvertPseudoLambdaToStreamApi(final @NotNull PsiMethod method, final @NotNull PsiClass expectedReturnClass) {
|
||||
final PsiType currentReturnType = method.getReturnType();
|
||||
if (!(currentReturnType instanceof PsiClassType)) {
|
||||
LOG.error("pseudo-lambda return type must be class " + currentReturnType);
|
||||
return true;
|
||||
}
|
||||
final PsiClass resolvedCurrentReturnType = ((PsiClassType)currentReturnType).resolve();
|
||||
if (expectedReturnClass.getManager().areElementsEquivalent(expectedReturnClass, resolvedCurrentReturnType)) {
|
||||
return true;
|
||||
}
|
||||
final PsiCodeBlock body = method.getBody();
|
||||
Collection<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;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiExpression convertClassTypeExpression(PsiExpression expression) {
|
||||
final PsiType type = expression.getType();
|
||||
if (type instanceof PsiClassType) {
|
||||
final PsiClass resolvedClass = ((PsiClassType)type).resolve();
|
||||
if (resolvedClass != null && CommonClassNames.JAVA_LANG_CLASS.equals(resolvedClass.getQualifiedName())) {
|
||||
return JavaPsiFacade.getElementFactory(expression.getProject())
|
||||
.createExpressionFromText("(" + expression.getText() + ")::isInstance", null);
|
||||
}
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
private static String createPipelineHeadText(PsiExpression collectionExpression, PsiExpression lambdaExpression, boolean force) {
|
||||
if (collectionExpression instanceof PsiNewExpression) {
|
||||
final PsiDiamondType.DiamondInferenceResult diamondResolveResult =
|
||||
PsiDiamondTypeImpl.resolveInferredTypesNoCheck((PsiNewExpression)collectionExpression, collectionExpression);
|
||||
if (!diamondResolveResult.getInferredTypes().isEmpty()) {
|
||||
collectionExpression = PsiDiamondTypeUtil.expandTopLevelDiamondsInside(collectionExpression);
|
||||
}
|
||||
}
|
||||
else if (collectionExpression instanceof PsiMethodCallExpression) {
|
||||
final PsiType currentType = collectionExpression.getType();
|
||||
if (currentType == null) {
|
||||
return null;
|
||||
}
|
||||
final PsiExpression copiedExpression = (PsiExpression) collectionExpression.copy();
|
||||
final PsiType newType = copiedExpression.getType();
|
||||
if (!currentType.equals(newType)) {
|
||||
collectionExpression = AddTypeArgumentsFix.addTypeArguments(copiedExpression, currentType);
|
||||
}
|
||||
}
|
||||
final PsiType type = collectionExpression.getType();
|
||||
if (type instanceof PsiClassType) {
|
||||
final PsiClass resolved = ((PsiClassType)type).resolve();
|
||||
LOG.assertTrue(resolved != null && resolved.getQualifiedName() != null, type);
|
||||
return collectionExpression.getText() + ".stream()";
|
||||
}
|
||||
else if (type instanceof PsiArrayType) {
|
||||
return CommonClassNames.JAVA_UTIL_ARRAYS + ".stream(" + collectionExpression.getText() + ")";
|
||||
} else if (force) {
|
||||
return collectionExpression.getText() + ".stream()";
|
||||
}
|
||||
throw new AssertionError("type: " + type + " is unexpected for expression: " + collectionExpression.getText());
|
||||
}
|
||||
|
||||
private static boolean isIterableOrArray(final PsiType type) {
|
||||
if (type instanceof PsiClassType) {
|
||||
final PsiClass resolvedClass = ((PsiClassType)type).resolve();
|
||||
if (resolvedClass != null) {
|
||||
return InheritanceUtil.isInheritor(resolvedClass, CommonClassNames.JAVA_LANG_ITERABLE);
|
||||
}
|
||||
}
|
||||
else if (type instanceof PsiArrayType) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
PseudoLambdaReplaceTemplate template = (PseudoLambdaReplaceTemplate)o;
|
||||
|
||||
if (myAcceptDefaultValue != template.myAcceptDefaultValue) return false;
|
||||
if (!myStreamApiMethodName.equals(template.myStreamApiMethodName)) return false;
|
||||
if (myLambdaRole != template.myLambdaRole) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = myStreamApiMethodName.hashCode();
|
||||
result = 31 * result + myLambdaRole.hashCode();
|
||||
result = 31 * result + (myAcceptDefaultValue ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PseudoLambdaReplaceTemplate{" +
|
||||
"myStreamApiMethodName='" + myStreamApiMethodName + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
+7
-266
@@ -18,16 +18,12 @@ package com.intellij.codeInspection.java18StreamApi;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.InvalidDataException;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -35,7 +31,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Dmitry Batkovich
|
||||
@@ -102,10 +97,9 @@ public class StaticPseudoFunctionalStyleMethodInspection extends BaseJavaBatchLo
|
||||
if (suitableHandler == null) {
|
||||
return;
|
||||
}
|
||||
final int lambdaIndex = validateMethodParameters(methodCallExpression, method, suitableHandler.getStreamApiMethodName() == StreamApiConstants.FAKE_FIND_MATCHED);
|
||||
if (lambdaIndex != -1) {
|
||||
holder.registerProblem(methodCallExpression.getMethodExpression(), "",
|
||||
new ReplacePseudoLambdaWithLambda(lambdaIndex, methodCallExpression, method, suitableHandler));
|
||||
final PseudoLambdaReplaceTemplate.ValidationInfo validationInfo = suitableHandler.getTemplate().validate(methodCallExpression);
|
||||
if (validationInfo != null) {
|
||||
holder.registerProblem(methodCallExpression.getMethodExpression(), "Pseudo functional style code", new ReplacePseudoLambdaWithLambda(suitableHandler));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,17 +107,10 @@ public class StaticPseudoFunctionalStyleMethodInspection extends BaseJavaBatchLo
|
||||
}
|
||||
|
||||
public static class ReplacePseudoLambdaWithLambda implements LocalQuickFix {
|
||||
private final int myLambdaIndex;
|
||||
private final SmartPsiElementPointer<PsiMethod> myMethodPointer;
|
||||
private final StaticPseudoFunctionalStyleMethodOptions.PipelineElement mySuitableHandler;
|
||||
private final StaticPseudoFunctionalStyleMethodOptions.PipelineElement myHandler;
|
||||
|
||||
private ReplacePseudoLambdaWithLambda(int lambdaIndex,
|
||||
@NotNull PsiMethodCallExpression expression,
|
||||
@NotNull PsiMethod method,
|
||||
@NotNull StaticPseudoFunctionalStyleMethodOptions.PipelineElement suitableHandler) {
|
||||
myLambdaIndex = lambdaIndex;
|
||||
myMethodPointer = SmartPointerManager.getInstance(expression.getProject()).createSmartPsiElementPointer(method);
|
||||
mySuitableHandler = suitableHandler;
|
||||
private ReplacePseudoLambdaWithLambda(StaticPseudoFunctionalStyleMethodOptions.PipelineElement handler) {
|
||||
myHandler = handler;
|
||||
}
|
||||
|
||||
@Nls
|
||||
@@ -143,253 +130,7 @@ public class StaticPseudoFunctionalStyleMethodInspection extends BaseJavaBatchLo
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
PsiMethodCallExpression expression = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiMethodCallExpression.class);
|
||||
LOG.assertTrue(expression != null);
|
||||
final PsiExpression[] expressions = expression.getArgumentList().getExpressions();
|
||||
PsiExpression lambdaExpression = expressions[myLambdaIndex];
|
||||
lambdaExpression = convertClassTypeExpression(lambdaExpression);
|
||||
lambdaExpression = convertToJavaLambda(lambdaExpression, mySuitableHandler.getStreamApiMethodName());
|
||||
LOG.assertTrue(lambdaExpression != null);
|
||||
|
||||
final PsiExpression collectionExpression = expressions[(1 + myLambdaIndex) % 2];
|
||||
final String pipelineHead = createPipelineHeadText(collectionExpression);
|
||||
|
||||
|
||||
final String lambdaExpressionText;
|
||||
final String elementText;
|
||||
if (!StreamApiConstants.FAKE_FIND_MATCHED.equals(mySuitableHandler.getStreamApiMethodName())) {
|
||||
elementText = mySuitableHandler.getStreamApiMethodName();
|
||||
lambdaExpressionText = lambdaExpression.getText();
|
||||
}
|
||||
else {
|
||||
elementText = expressions.length == 3
|
||||
? String.format(StreamApiConstants.FAKE_FIND_MATCHED_WITH_DEFAULT_PATTERN, lambdaExpression.getText(), expressions[2].getText())
|
||||
: String.format(StreamApiConstants.FAKE_FIND_MATCHED_PATTERN, lambdaExpression.getText());
|
||||
lambdaExpressionText = null;
|
||||
}
|
||||
final String pipelineTail =
|
||||
StreamApiConstants.STREAM_STREAM_API_METHODS.getValue().contains(mySuitableHandler.getStreamApiMethodName())
|
||||
? findSuitableTailMethodForCollection(myMethodPointer.getElement())
|
||||
: null;
|
||||
|
||||
final PsiElement replaced =
|
||||
expression.replace(createPipelineExpression(pipelineHead, elementText, lambdaExpressionText, pipelineTail, project));
|
||||
JavaCodeStyleManager.getInstance(project).shortenClassReferences(replaced.getParent());
|
||||
myHandler.getTemplate().convertToStream(expression, null, false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiExpression convertClassTypeExpression(PsiExpression expression) {
|
||||
final PsiType type = expression.getType();
|
||||
if (type instanceof PsiClassType) {
|
||||
final PsiClass resolvedClass = ((PsiClassType)type).resolve();
|
||||
if (resolvedClass != null && CommonClassNames.JAVA_LANG_CLASS.equals(resolvedClass.getQualifiedName())) {
|
||||
return JavaPsiFacade.getElementFactory(expression.getProject())
|
||||
.createExpressionFromText("(" + expression.getText() + ")::isInstance", null);
|
||||
}
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
private static String createPipelineHeadText(PsiExpression collectionExpression) {
|
||||
final PsiType type = collectionExpression.getType();
|
||||
if (type instanceof PsiClassType) {
|
||||
final PsiClass resolved = ((PsiClassType)type).resolve();
|
||||
LOG.assertTrue(resolved != null && resolved.getQualifiedName() != null, type);
|
||||
return collectionExpression.getText() + ".stream()";
|
||||
}
|
||||
else if (type instanceof PsiArrayType) {
|
||||
return CommonClassNames.JAVA_UTIL_ARRAYS + ".stream(" + collectionExpression.getText() + ")";
|
||||
}
|
||||
throw new AssertionError("type: " + type + " is unexpected");
|
||||
}
|
||||
|
||||
private static PsiExpression createPipelineExpression(String pipelineHead,
|
||||
String elementText,
|
||||
String lambdaExpression,
|
||||
String pipelineTail,
|
||||
Project project) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(pipelineHead).append(".").append(elementText);
|
||||
if (lambdaExpression != null) {
|
||||
sb.append("(").append(lambdaExpression).append(")");
|
||||
}
|
||||
if (pipelineTail != null) {
|
||||
sb.append(".").append(pipelineTail);
|
||||
}
|
||||
return JavaPsiFacade.getElementFactory(project).createExpressionFromText(sb.toString(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private static int validateMethodParameters(final PsiMethodCallExpression methodCallExpression, final PsiMethod method, boolean canThirdParameterExist) {
|
||||
final PsiType[] argumentTypes = methodCallExpression.getArgumentList().getExpressionTypes();
|
||||
final PsiParameter[] expectedParameters = method.getParameterList().getParameters();
|
||||
|
||||
if (argumentTypes.length != expectedParameters.length) {
|
||||
return -1;
|
||||
}
|
||||
if (expectedParameters.length == 2 || (canThirdParameterExist && expectedParameters.length == 3)) {
|
||||
final int collectionOrArrayIndex = findCollectionOrArrayPlacement(expectedParameters);
|
||||
if (collectionOrArrayIndex == -1) {
|
||||
return -1;
|
||||
}
|
||||
return (1 + collectionOrArrayIndex) % 2;
|
||||
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int findCollectionOrArrayPlacement(final PsiParameter[] parameters) {
|
||||
for (int i = 0, length = parameters.length; i < length; i++) {
|
||||
PsiParameter parameter = parameters[i];
|
||||
final PsiType type = parameter.getType();
|
||||
if (type instanceof PsiClassType || type instanceof PsiArrayType) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static PsiExpression convertToJavaLambda(PsiExpression expression, String streamApiMethodName) {
|
||||
if (streamApiMethodName.equals(StreamApiConstants.FAKE_FIND_MATCHED)) {
|
||||
streamApiMethodName = StreamApiConstants.FILTER;
|
||||
}
|
||||
if (expression instanceof PsiMethodReferenceExpression) {
|
||||
return expression;
|
||||
}
|
||||
if (expression instanceof PsiLambdaExpression) {
|
||||
return expression;
|
||||
}
|
||||
if (expression instanceof PsiMethodCallExpression) {
|
||||
final PsiMethod method = ((PsiMethodCallExpression)expression).resolveMethod();
|
||||
if (method == null) {
|
||||
return null;
|
||||
}
|
||||
final PsiType type = method.getReturnType();
|
||||
if (!(type instanceof PsiClassType)) {
|
||||
return null;
|
||||
}
|
||||
final PsiClass lambdaClass = ((PsiClassType)type).resolve();
|
||||
if (lambdaClass == null) {
|
||||
return null;
|
||||
}
|
||||
final String methodName = lambdaClass.getMethods()[0].getName();
|
||||
if (tryConvertPseudoLambdaToStreamApi(method, resolveStreamApiLambdaClass(expression.getProject(), streamApiMethodName))) {
|
||||
return expression;
|
||||
}
|
||||
else {
|
||||
return JavaPsiFacade.getElementFactory(expression.getProject())
|
||||
.createExpressionFromText(expression.getText() + "::" + methodName, null);
|
||||
}
|
||||
}
|
||||
return AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiClass resolveStreamApiLambdaClass(Project project, String streamApiMethodName) {
|
||||
final PsiClass javaUtilStream = JavaPsiFacade.getInstance(project)
|
||||
.findClass(StreamApiConstants.JAVA_UTIL_STREAM_STREAM, GlobalSearchScope.notScope(GlobalSearchScope.projectScope(project)));
|
||||
LOG.assertTrue(javaUtilStream != null);
|
||||
final PsiMethod[] methods = javaUtilStream.findMethodsByName(streamApiMethodName, false);
|
||||
LOG.assertTrue(methods.length == 1);
|
||||
final PsiMethod method = methods[0];
|
||||
final PsiParameter[] parameters = method.getParameterList().getParameters();
|
||||
LOG.assertTrue(parameters.length == 1);
|
||||
final PsiType type = parameters[0].getType();
|
||||
LOG.assertTrue(type instanceof PsiClassType);
|
||||
final PsiClass resolved = ((PsiClassType)type).resolve();
|
||||
LOG.assertTrue(resolved != null);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private static boolean tryConvertPseudoLambdaToStreamApi(final @NotNull PsiMethod method, final @NotNull PsiClass expectedReturnClass) {
|
||||
final PsiType currentReturnType = method.getReturnType();
|
||||
if (!(currentReturnType instanceof PsiClassType)) {
|
||||
LOG.error("pseudo-lambda return type must be class " + currentReturnType);
|
||||
return true;
|
||||
}
|
||||
final PsiClass resolvedCurrentReturnType = ((PsiClassType)currentReturnType).resolve();
|
||||
if (expectedReturnClass.getManager().areElementsEquivalent(expectedReturnClass, resolvedCurrentReturnType)) {
|
||||
return true;
|
||||
}
|
||||
final PsiCodeBlock body = method.getBody();
|
||||
Collection<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;
|
||||
}
|
||||
}
|
||||
+37
-19
@@ -16,11 +16,9 @@
|
||||
package com.intellij.codeInspection.java18StreamApi;
|
||||
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.actionSystem.LangDataKeys;
|
||||
import com.intellij.openapi.project.DumbService;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.ui.*;
|
||||
import com.intellij.ui.components.JBList;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
@@ -32,7 +30,6 @@ import javax.swing.*;
|
||||
import javax.swing.event.ListDataListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -43,6 +40,8 @@ public class StaticPseudoFunctionalStyleMethodOptions {
|
||||
private static final String FQN_ATTR = "classFqn";
|
||||
private static final String METHOD_ATTR = "method";
|
||||
private static final String STREAM_API_METHOD_ATTR = "streamApiMethod";
|
||||
private static final String LAMBDA_ROLE_ATTR = "lambdaRole";
|
||||
private static final String ACCEPTS_DEFAULT_ATTR = "acceptsDefault";
|
||||
private static final String DELETE_ATTR = "toDelete";
|
||||
private final List<PipelineElement> myElements;
|
||||
|
||||
@@ -54,11 +53,11 @@ public class StaticPseudoFunctionalStyleMethodOptions {
|
||||
private static void restoreDefault(final List<PipelineElement> elements) {
|
||||
elements.clear();
|
||||
final String guavaIterables = "com.google.common.collect.Iterables";
|
||||
elements.add(new PipelineElement(guavaIterables, "transform", StreamApiConstants.MAP));
|
||||
elements.add(new PipelineElement(guavaIterables, "filter", StreamApiConstants.FILTER));
|
||||
elements.add(new PipelineElement(guavaIterables, "find", StreamApiConstants.FAKE_FIND_MATCHED));
|
||||
elements.add(new PipelineElement(guavaIterables, "all", StreamApiConstants.ALL_MATCH));
|
||||
elements.add(new PipelineElement(guavaIterables, "any", StreamApiConstants.ANY_MATCH));
|
||||
elements.add(new PipelineElement(guavaIterables, "transform", PseudoLambdaReplaceTemplate.MAP));
|
||||
elements.add(new PipelineElement(guavaIterables, "filter", PseudoLambdaReplaceTemplate.FILTER));
|
||||
elements.add(new PipelineElement(guavaIterables, "find", PseudoLambdaReplaceTemplate.FIND));
|
||||
elements.add(new PipelineElement(guavaIterables, "all", PseudoLambdaReplaceTemplate.ALL_MATCH));
|
||||
elements.add(new PipelineElement(guavaIterables, "any", PseudoLambdaReplaceTemplate.ANY_MATCH));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -77,8 +76,11 @@ public class StaticPseudoFunctionalStyleMethodOptions {
|
||||
final String fqn = element.getAttributeValue(FQN_ATTR);
|
||||
final String method = element.getAttributeValue(METHOD_ATTR);
|
||||
final String streamApiMethod = element.getAttributeValue(STREAM_API_METHOD_ATTR);
|
||||
final PseudoLambdaReplaceTemplate.LambdaRole lambdaRole =
|
||||
PseudoLambdaReplaceTemplate.LambdaRole.valueOf(element.getAttributeValue(LAMBDA_ROLE_ATTR));
|
||||
final boolean acceptsDefault = Boolean.valueOf(element.getAttributeValue(ACCEPTS_DEFAULT_ATTR));
|
||||
final boolean toDelete = element.getAttribute(DELETE_ATTR) != null;
|
||||
final PipelineElement pipelineElement = new PipelineElement(fqn, method, streamApiMethod);
|
||||
final PipelineElement pipelineElement = new PipelineElement(fqn, method, new PseudoLambdaReplaceTemplate(streamApiMethod, lambdaRole, acceptsDefault));
|
||||
if (toDelete) {
|
||||
myElements.remove(pipelineElement);
|
||||
}
|
||||
@@ -107,10 +109,13 @@ public class StaticPseudoFunctionalStyleMethodOptions {
|
||||
}
|
||||
|
||||
public Element createXmlElement(PipelineElement element) {
|
||||
final PseudoLambdaReplaceTemplate template = element.getTemplate();
|
||||
return new Element(PIPELINE_ELEMENT_NAME)
|
||||
.setAttribute(FQN_ATTR, element.getHandlerClass())
|
||||
.setAttribute(METHOD_ATTR, element.getMethodName())
|
||||
.setAttribute(STREAM_API_METHOD_ATTR, element.getStreamApiMethodName());
|
||||
.setAttribute(STREAM_API_METHOD_ATTR, template.getStreamApiMethodName())
|
||||
.setAttribute(LAMBDA_ROLE_ATTR, template.getLambdaRole().toString())
|
||||
.setAttribute(ACCEPTS_DEFAULT_ATTR, String.valueOf(template.isAcceptDefaultValue()));
|
||||
}
|
||||
|
||||
public JComponent createPanel() {
|
||||
@@ -137,6 +142,9 @@ public class StaticPseudoFunctionalStyleMethodOptions {
|
||||
if (currentProject == null) {
|
||||
return;
|
||||
}
|
||||
if (DumbService.isDumb(currentProject)) {
|
||||
return;
|
||||
}
|
||||
final AddMethodsDialog dlg = new AddMethodsDialog(currentProject, list, false);
|
||||
if (dlg.showAndGet()) {
|
||||
final PipelineElement newElement = dlg.getSelectedElement();
|
||||
@@ -147,6 +155,7 @@ public class StaticPseudoFunctionalStyleMethodOptions {
|
||||
UIUtil.invokeLaterIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
list.revalidate();
|
||||
list.updateUI();
|
||||
}
|
||||
});
|
||||
@@ -156,19 +165,28 @@ public class StaticPseudoFunctionalStyleMethodOptions {
|
||||
@Override
|
||||
public void run(AnActionButton button) {
|
||||
myElements.remove(list.getSelectedIndex());
|
||||
UIUtil.invokeLaterIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
list.revalidate();
|
||||
list.updateUI();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).createPanel();
|
||||
}
|
||||
|
||||
public static class PipelineElement {
|
||||
public static class PipelineElement {
|
||||
private final String myHandlerClass;
|
||||
private final String myMethodName;
|
||||
private final String myStreamApiMethod;
|
||||
private final PseudoLambdaReplaceTemplate myTemplate;
|
||||
|
||||
public PipelineElement(@NotNull String handlerClass, @NotNull String methodName, @NotNull String streamApiMethod) {
|
||||
public PipelineElement(@NotNull String handlerClass,
|
||||
@NotNull String methodName,
|
||||
@NotNull PseudoLambdaReplaceTemplate template) {
|
||||
myHandlerClass = handlerClass;
|
||||
myMethodName = methodName;
|
||||
myStreamApiMethod = streamApiMethod;
|
||||
myTemplate = template;
|
||||
}
|
||||
|
||||
public String getHandlerClass() {
|
||||
@@ -179,8 +197,8 @@ public class StaticPseudoFunctionalStyleMethodOptions {
|
||||
return myMethodName;
|
||||
}
|
||||
|
||||
public String getStreamApiMethodName() {
|
||||
return myStreamApiMethod;
|
||||
public PseudoLambdaReplaceTemplate getTemplate() {
|
||||
return myTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -192,7 +210,7 @@ public class StaticPseudoFunctionalStyleMethodOptions {
|
||||
|
||||
if (!myHandlerClass.equals(element.myHandlerClass)) return false;
|
||||
if (!myMethodName.equals(element.myMethodName)) return false;
|
||||
if (!myStreamApiMethod.equals(element.myStreamApiMethod)) return false;
|
||||
if (!myTemplate.equals(element.myTemplate)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -201,7 +219,7 @@ public class StaticPseudoFunctionalStyleMethodOptions {
|
||||
public int hashCode() {
|
||||
int result = myHandlerClass.hashCode();
|
||||
result = 31 * result + myMethodName.hashCode();
|
||||
result = 31 * result + myStreamApiMethod.hashCode();
|
||||
result = 31 * result + myTemplate.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import java.lang.Iterable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.ArrayList;
|
||||
|
||||
class c {
|
||||
void m() {
|
||||
Iterable<HashMap> l = Iterables.fil<caret>ter(new ArrayList<>(), op -> false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import java.lang.Iterable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.ArrayList;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class c {
|
||||
void m() {
|
||||
Iterable<HashMap> l = new ArrayList<HashMap>().stream().filter(op -> false).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@ import java.util.Collections;
|
||||
|
||||
class c {
|
||||
void m() {
|
||||
Collections.emptyList().stream().allMatch(getPredicate(100)::apply);
|
||||
Collections.<String>emptyList().stream().allMatch(getPredicate(100)::apply);
|
||||
}
|
||||
|
||||
public Predicate<String> getPredicate(final int param) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import java.lang.Iterable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.ArrayList;
|
||||
|
||||
class c {
|
||||
void m() {
|
||||
Iterable<HashMap> l = Iterables.fil<caret>ter(Collections.emptyList(), op -> false);
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
import java.lang.Iterable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.ArrayList;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class c {
|
||||
void m() {
|
||||
Iterable<HashMap> l = Collections.<HashMap>emptyList().stream().filter(op -> false).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import java.lang.String;
|
||||
import java.util.Collections;
|
||||
|
||||
class c {
|
||||
Iterable<String> transform = Iterables.tra<caret>nsform(Collections.emptyList(), new Function<String, String>() {
|
||||
@Override
|
||||
public String apply(String input) {
|
||||
Collectors c;
|
||||
ArrayList l;
|
||||
System.out.println(input);
|
||||
//do something
|
||||
int i = 1;
|
||||
return input;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import java.lang.String;
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class c {
|
||||
Iterable<String> transform = Collections.<String>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,17 @@
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import java.lang.String;
|
||||
import java.util.Collections;
|
||||
|
||||
class c {
|
||||
void m() {
|
||||
Iterables.transf<caret>orm(new ArrayList<>(), getFunction())
|
||||
}
|
||||
|
||||
Function<String, String> getFunction() {
|
||||
return new Function<String, String>() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -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,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;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+20
@@ -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())
|
||||
}
|
||||
}
|
||||
+18
-1
@@ -28,6 +28,7 @@ import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.testFramework.IdeaTestUtil;
|
||||
import com.intellij.testFramework.builders.JavaModuleFixtureBuilder;
|
||||
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
|
||||
import com.intellij.util.PathUtil;
|
||||
import com.intellij.util.Processor;
|
||||
|
||||
import java.io.File;
|
||||
@@ -91,6 +92,22 @@ public class StaticPseudoFunctionalStyleMethodTest extends JavaCodeInsightFixtur
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testDiamondResolve() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void _testTransformLambda() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void _testTransformMethodCalling() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testTransformMethodRef() {
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void _testReplaceWithMethodReference() {
|
||||
doTest();
|
||||
}
|
||||
@@ -109,7 +126,7 @@ public class StaticPseudoFunctionalStyleMethodTest extends JavaCodeInsightFixtur
|
||||
}
|
||||
}
|
||||
}
|
||||
assertTrue(isQuickFixFound);
|
||||
assertTrue("Quick fix isn't found", isQuickFixFound);
|
||||
myFixture.checkResultByFile(getTestName(true) + "/test_after.java");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
<html>
|
||||
<body>
|
||||
Inspection detects usages of pseudo-functional code if Java Stream API is available.
|
||||
<br/>
|
||||
Consider cases when monad behaviour is emulated by static method call:
|
||||
<br/>
|
||||
<code>
|
||||
List<String> transformedIterable = Iterables.transform(someList, someTransformFunction);
|
||||
</code>
|
||||
</body>
|
||||
</html>
|
||||
@@ -727,7 +727,7 @@
|
||||
displayName="Block marker comment"/>
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="StaticPseudoFunctionalStyleMethod"
|
||||
groupBundle="messages.InspectionsBundle"
|
||||
groupName="Code style issues" enabledByDefault="false" level="WARNING"
|
||||
groupName="Java language level migration aids" enabledByDefault="false" level="WARNING"
|
||||
implementationClass="com.intellij.codeInspection.java18StreamApi.StaticPseudoFunctionalStyleMethodInspection"
|
||||
displayName="Pseudo functional expression using static class"/>
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="StringTokenizerDelimiter"
|
||||
|
||||
Reference in New Issue
Block a user