UseBulkOperationInspection: register BulkMethodInfo via extension points; JPA classes extracted

This commit is contained in:
Tagir Valeev
2016-12-30 17:42:52 +07:00
parent aced865af2
commit 38db92f734
20 changed files with 309 additions and 196 deletions

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2000-2016 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.bulkOperation;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiUtil;
/**
* @author Tagir Valeev
*/
public class BulkMethodInfo {
private final String myClassName;
private final String mySimpleName;
private final String myBulkName;
public BulkMethodInfo(String className, String simpleName, String bulkName) {
myClassName = className;
mySimpleName = simpleName;
myBulkName = bulkName;
}
public boolean isMyMethod(PsiReferenceExpression ref) {
if (!mySimpleName.equals(ref.getReferenceName())) return false;
PsiElement element = ref.resolve();
if (!(element instanceof PsiMethod)) return false;
PsiMethod method = (PsiMethod)element;
PsiParameterList parameters = method.getParameterList();
if (parameters.getParametersCount() != 1) return false;
PsiParameter parameter = parameters.getParameters()[0];
PsiClass parameterClass = PsiUtil.resolveClassInClassTypeOnly(parameter.getType());
if (parameterClass == null ||
CommonClassNames.JAVA_LANG_ITERABLE.equals(parameterClass.getQualifiedName()) ||
CommonClassNames.JAVA_UTIL_COLLECTION.equals(parameterClass.getQualifiedName())) {
return false;
}
PsiClass methodClass = method.getContainingClass();
return methodClass != null && InheritanceUtil.isInheritor(methodClass, myClassName);
}
public boolean isSupportedIterable(PsiExpression qualifier, PsiExpression iterable, boolean useArraysAsList) {
PsiType qualifierType = qualifier.getType();
if (!(qualifierType instanceof PsiClassType)) return false;
PsiType type = iterable.getType();
PsiElementFactory factory = JavaPsiFacade.getElementFactory(iterable.getProject());
String text = iterable.getText();
if (type instanceof PsiArrayType) {
PsiType componentType = ((PsiArrayType)type).getComponentType();
if (!useArraysAsList || componentType instanceof PsiPrimitiveType) return false;
PsiClass listClass =
JavaPsiFacade.getInstance(iterable.getProject()).findClass(CommonClassNames.JAVA_UTIL_LIST, iterable.getResolveScope());
if (listClass == null) return false;
type = factory.createType(listClass, componentType);
text = CommonClassNames.JAVA_UTIL_ARRAYS + ".asList(" + text + ")";
}
if (!InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_LANG_ITERABLE)) return false;
PsiExpression expression = factory.createExpressionFromText(qualifier.getText() + "." + myBulkName + "(" + text + ")", iterable);
if (!(expression instanceof PsiMethodCallExpression)) return false;
PsiMethodCallExpression call = (PsiMethodCallExpression)expression;
PsiMethod bulkMethod = call.resolveMethod();
if (bulkMethod == null) return false;
PsiParameterList parameters = bulkMethod.getParameterList();
if (parameters.getParametersCount() != 1) return false;
PsiType parameterType = parameters.getParameters()[0].getType();
parameterType = call.resolveMethodGenerics().getSubstitutor().substitute(parameterType);
PsiClass parameterClass = PsiUtil.resolveClassInClassTypeOnly(parameterType);
return parameterClass != null &&
(CommonClassNames.JAVA_LANG_ITERABLE.equals(parameterClass.getQualifiedName()) ||
CommonClassNames.JAVA_UTIL_COLLECTION.equals(parameterClass.getQualifiedName())) &&
parameterType.isAssignableFrom(type);
}
public String getClassName() {
return myClassName;
}
public String getSimpleName() {
return mySimpleName;
}
public String getBulkName() {
return myBulkName;
}
public String getReplacementName() {
return StringUtil.getShortName(myClassName) + "." + myBulkName;
}
@Override
public String toString() {
return myClassName + "::" + mySimpleName + " => " + myBulkName;
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2000-2016 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.bulkOperation;
import com.intellij.openapi.extensions.ExtensionPointName;
import org.jetbrains.annotations.NotNull;
import java.util.stream.Stream;
/**
* @author Tagir Valeev
*/
public interface BulkMethodInfoProvider {
public static final ExtensionPointName<BulkMethodInfoProvider> KEY =
ExtensionPointName.create("com.intellij.java.inspection.bulkMethodInfo");
/**
* @return stream of BulkMethodInfo structures which represent the consumers
* Simple method should accept an element parameter and bulk method should accept Iterable or Collection of given elements
*/
@NotNull
default Stream<BulkMethodInfo> consumers() {
return Stream.empty();
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2000-2016 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.bulkOperation;
import com.intellij.psi.CommonClassNames;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.stream.Stream;
/**
* @author Tagir Valeev
*/
public class JdkBulkMethodInfoProvider implements BulkMethodInfoProvider {
private static BulkMethodInfo[] INFOS = {
new BulkMethodInfo(CommonClassNames.JAVA_UTIL_COLLECTION, "add", "addAll"),
new BulkMethodInfo(CommonClassNames.JAVA_UTIL_LIST, "remove", "removeAll")
};
@NotNull
@Override
public Stream<BulkMethodInfo> consumers() {
return Arrays.stream(INFOS);
}
}

View File

@@ -13,17 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInspection;
package com.intellij.codeInspection.bulkOperation;
import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel;
import com.intellij.codeInspection.util.IteratorDeclaration;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.util.RefactoringUtil;
@@ -45,14 +44,8 @@ import java.util.regex.Pattern;
* @author Tagir Valeev
*/
public class UseBulkOperationInspection extends BaseJavaBatchLocalInspectionTool {
private static BulkMethodInfo[] INFOS = {
new BulkMethodInfo(CommonClassNames.JAVA_UTIL_COLLECTION, "add", "addAll"),
new BulkMethodInfo(CommonClassNames.JAVA_UTIL_LIST, "remove", "removeAll"),
new BulkMethodInfo("org.springframework.data.repository.CrudRepository", "save", "save"),
new BulkMethodInfo("org.springframework.data.repository.CrudRepository", "delete", "delete"),
};
private List<BulkMethodInfo> myInfos = StreamEx.of(INFOS).toList();
private List<BulkMethodInfo> myInfos = StreamEx.of(BulkMethodInfoProvider.KEY.getExtensions())
.flatMap(BulkMethodInfoProvider::consumers).toList();
public boolean USE_ARRAYS_AS_LIST = true;
@@ -275,7 +268,7 @@ public class UseBulkOperationInspection extends BaseJavaBatchLocalInspectionTool
if (qualifier instanceof PsiThisExpression) {
PsiMethod method = PsiTreeUtil.getParentOfType(iterable, PsiMethod.class);
// Likely we are inside of the bulk method implementation
if (method != null && method.getName().equals(info.myBulkName)) return;
if (method != null && method.getName().equals(info.getBulkName())) return;
}
if (isSupportedQualifier(qualifier) && info.isSupportedIterable(qualifier, iterable, USE_ARRAYS_AS_LIST)) {
holder.registerProblem(methodExpression,
@@ -347,81 +340,10 @@ public class UseBulkOperationInspection extends BaseJavaBatchLocalInspectionTool
ct.delete(loop);
}
}
PsiElement result = ct.replaceAndRestoreComments(parent, ct.text(qualifier) + "." + myInfo.myBulkName + "(" + iterableText + ")"
PsiElement result = ct.replaceAndRestoreComments(parent, ct.text(qualifier) + "." + myInfo.getBulkName() + "(" + iterableText + ")"
+ (parent instanceof PsiStatement ? ";" : ""));
result = JavaCodeStyleManager.getInstance(project).shortenClassReferences(result);
CodeStyleManager.getInstance(project).reformat(result);
}
}
private static class BulkMethodInfo {
private final String myClassName;
private final String mySimpleName;
private final String myBulkName;
private BulkMethodInfo(String className, String simpleName, String bulkName) {
myClassName = className;
mySimpleName = simpleName;
myBulkName = bulkName;
}
public boolean isMyMethod(PsiReferenceExpression ref) {
if (!mySimpleName.equals(ref.getReferenceName())) return false;
PsiElement element = ref.resolve();
if (!(element instanceof PsiMethod)) return false;
PsiMethod method = (PsiMethod)element;
PsiParameterList parameters = method.getParameterList();
if (parameters.getParametersCount() != 1) return false;
PsiParameter parameter = parameters.getParameters()[0];
PsiClass parameterClass = PsiUtil.resolveClassInClassTypeOnly(parameter.getType());
if (parameterClass == null ||
CommonClassNames.JAVA_LANG_ITERABLE.equals(parameterClass.getQualifiedName()) ||
CommonClassNames.JAVA_UTIL_COLLECTION.equals(parameterClass.getQualifiedName())) {
return false;
}
PsiClass methodClass = method.getContainingClass();
return methodClass != null && InheritanceUtil.isInheritor(methodClass, myClassName);
}
public boolean isSupportedIterable(PsiExpression qualifier, PsiExpression iterable, boolean useArraysAsList) {
PsiType qualifierType = qualifier.getType();
if (!(qualifierType instanceof PsiClassType)) return false;
PsiType type = iterable.getType();
PsiElementFactory factory = JavaPsiFacade.getElementFactory(iterable.getProject());
String text = iterable.getText();
if (type instanceof PsiArrayType) {
PsiType componentType = ((PsiArrayType)type).getComponentType();
if (!useArraysAsList || componentType instanceof PsiPrimitiveType) return false;
PsiClass listClass =
JavaPsiFacade.getInstance(iterable.getProject()).findClass(CommonClassNames.JAVA_UTIL_LIST, iterable.getResolveScope());
if (listClass == null) return false;
type = factory.createType(listClass, componentType);
text = CommonClassNames.JAVA_UTIL_ARRAYS + ".asList(" + text + ")";
}
if (!InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_LANG_ITERABLE)) return false;
PsiExpression expression = factory.createExpressionFromText(qualifier.getText() + "." + myBulkName + "(" + text + ")", iterable);
if (!(expression instanceof PsiMethodCallExpression)) return false;
PsiMethodCallExpression call = (PsiMethodCallExpression)expression;
PsiMethod bulkMethod = call.resolveMethod();
if (bulkMethod == null) return false;
PsiParameterList parameters = bulkMethod.getParameterList();
if (parameters.getParametersCount() != 1) return false;
PsiType parameterType = parameters.getParameters()[0].getType();
parameterType = call.resolveMethodGenerics().getSubstitutor().substitute(parameterType);
PsiClass parameterClass = PsiUtil.resolveClassInClassTypeOnly(parameterType);
return parameterClass != null &&
(CommonClassNames.JAVA_LANG_ITERABLE.equals(parameterClass.getQualifiedName()) ||
CommonClassNames.JAVA_UTIL_COLLECTION.equals(parameterClass.getQualifiedName())) &&
parameterType.isAssignableFrom(type);
}
public String getReplacementName() {
return StringUtil.getShortName(myClassName) + "." + myBulkName;
}
@Override
public String toString() {
return myClassName + "::" + mySimpleName + " => " + myBulkName;
}
}
}

View File

@@ -1,15 +0,0 @@
// "Replace iteration with bulk 'CrudRepository.save' call" "true"
package org.springframework.data.repository;
import java.io.Serializable;
interface CrudRepository<T,ID extends Serializable> {
<S extends T> Iterable<S> save(Iterable<S> entities);
<S extends T> S save(S entity);
}
public class Main {
public void test(CrudRepository<CharSequence, Integer> repo, Iterable<String> stringsToSave) {
repo.save(stringsToSave);
}
}

View File

@@ -1,16 +0,0 @@
// "Replace iteration with bulk 'CrudRepository.save' call" "true"
package org.springframework.data.repository;
import java.io.Serializable;
import java.util.Arrays;
interface CrudRepository<T,ID extends Serializable> {
<S extends T> Iterable<S> save(Iterable<S> entities);
<S extends T> S save(S entity);
}
public class Main {
public void test(CrudRepository<CharSequence, Integer> repo, String[] stringsToSave) {
repo.save(Arrays.asList(stringsToSave));
}
}

View File

@@ -1,16 +0,0 @@
// "Replace iteration with bulk 'CrudRepository.save' call" "true"
package org.springframework.data.repository;
import java.io.Serializable;
import java.util.List;
interface CrudRepository<T,ID extends Serializable> {
<S extends T> Iterable<S> save(Iterable<S> entities);
<S extends T> S save(S entity);
}
public class Main {
public void test(CrudRepository<Iterable<String>, Integer> repo, Iterable<List<String>> stringsToSave) {
repo.save(stringsToSave);
}
}

View File

@@ -0,0 +1,13 @@
// "Replace iteration with bulk 'TestClass.test' call" "true"
package testpackage;
interface TestClass<T> {
<S extends T> Iterable<S> test(Iterable<S> entities);
<S extends T> S test(S entity);
}
public class Main {
public void test(TestClass<CharSequence> repo, Iterable<String> stringsToSave) {
repo.test(stringsToSave);
}
}

View File

@@ -0,0 +1,15 @@
// "Replace iteration with bulk 'TestClass.test' call" "true"
package testpackage;
import java.util.*;
interface TestClass<T> {
<S extends T> Iterable<S> test(Iterable<S> entities);
<S extends T> S test(S entity);
}
public class Main {
public void test(TestClass<CharSequence> repo, String[] stringsToSave) {
repo.test(Arrays.asList(stringsToSave));
}
}

View File

@@ -0,0 +1,15 @@
// "Replace iteration with bulk 'TestClass.test' call" "true"
package testpackage;
import java.util.*;
interface TestClass<T> {
<S extends T> Iterable<S> test(Iterable<S> entities);
<S extends T> S test(S entity);
}
public class Main {
public void test(TestClass<Iterable<String>> repo, Iterable<List<String>> stringsToSave) {
repo.test(stringsToSave);
}
}

View File

@@ -1,15 +0,0 @@
// "Replace iteration with bulk 'CrudRepository.save' call" "true"
package org.springframework.data.repository;
import java.io.Serializable;
interface CrudRepository<T,ID extends Serializable> {
<S extends T> Iterable<S> save(Iterable<S> entities);
<S extends T> S save(S entity);
}
public class Main {
public void test(CrudRepository<CharSequence, Integer> repo, Iterable<String> stringsToSave) {
stringsToSave.forEach(<caret>repo::save);
}
}

View File

@@ -1,16 +0,0 @@
// "Replace iteration with bulk 'CrudRepository.save' call" "true"
package org.springframework.data.repository;
import java.io.Serializable;
import java.util.Arrays;
interface CrudRepository<T,ID extends Serializable> {
<S extends T> Iterable<S> save(Iterable<S> entities);
<S extends T> S save(S entity);
}
public class Main {
public void test(CrudRepository<CharSequence, Integer> repo, String[] stringsToSave) {
Arrays.stream(stringsToSave).forEach(<caret>repo::save);
}
}

View File

@@ -1,16 +0,0 @@
// "Replace iteration with bulk 'CrudRepository.save' call" "false"
package org.springframework.data.repository;
import java.io.Serializable;
import java.util.List;
interface CrudRepository<T,ID extends Serializable> {
<S extends T> Iterable<S> save(Iterable<S> entities);
<S extends T> S save(S entity);
}
public class Main {
public void test(CrudRepository<CharSequence, Integer> repo, Iterable<List<String>> stringsToSave) {
stringsToSave.forEach(<caret>repo::save);
}
}

View File

@@ -1,16 +0,0 @@
// "Replace iteration with bulk 'CrudRepository.save' call" "true"
package org.springframework.data.repository;
import java.io.Serializable;
import java.util.List;
interface CrudRepository<T,ID extends Serializable> {
<S extends T> Iterable<S> save(Iterable<S> entities);
<S extends T> S save(S entity);
}
public class Main {
public void test(CrudRepository<Iterable<String>, Integer> repo, Iterable<List<String>> stringsToSave) {
stringsToSave.forEach(<caret>repo::save);
}
}

View File

@@ -0,0 +1,13 @@
// "Replace iteration with bulk 'TestClass.test' call" "true"
package testpackage;
interface TestClass<T> {
<S extends T> Iterable<S> test(Iterable<S> entities);
<S extends T> S test(S entity);
}
public class Main {
public void test(TestClass<CharSequence> repo, Iterable<String> stringsToSave) {
stringsToSave.forEach(<caret>repo::test);
}
}

View File

@@ -0,0 +1,15 @@
// "Replace iteration with bulk 'TestClass.test' call" "true"
package testpackage;
import java.util.*;
interface TestClass<T> {
<S extends T> Iterable<S> test(Iterable<S> entities);
<S extends T> S test(S entity);
}
public class Main {
public void test(TestClass<CharSequence> repo, String[] stringsToSave) {
Arrays.stream(stringsToSave).forEach(<caret>repo::test);
}
}

View File

@@ -0,0 +1,15 @@
// "Replace iteration with bulk 'TestClass.test' call" "false"
package testpackage;
import java.util.*;
interface TestClass<T> {
<S extends T> Iterable<S> test(Iterable<S> entities);
<S extends T> S test(S entity);
}
public class Main {
public void test(TestClass<CharSequence> repo, Iterable<List<String>> stringsToSave) {
stringsToSave.forEach(<caret>repo::test);
}
}

View File

@@ -0,0 +1,15 @@
// "Replace iteration with bulk 'TestClass.test' call" "true"
package testpackage;
import java.util.*;
interface TestClass<T> {
<S extends T> Iterable<S> test(Iterable<S> entities);
<S extends T> S test(S entity);
}
public class Main {
public void test(TestClass<Iterable<String>> repo, Iterable<List<String>> stringsToSave) {
stringsToSave.forEach(<caret>repo::test);
}
}

View File

@@ -16,14 +16,27 @@
package com.intellij.codeInsight.daemon.quickFix;
import com.intellij.codeInspection.LocalInspectionTool;
import com.intellij.codeInspection.UseBulkOperationInspection;
import com.intellij.codeInspection.bulkOperation.BulkMethodInfo;
import com.intellij.codeInspection.bulkOperation.BulkMethodInfoProvider;
import com.intellij.codeInspection.bulkOperation.UseBulkOperationInspection;
import com.intellij.openapi.extensions.Extensions;
import org.jetbrains.annotations.NotNull;
import java.util.stream.Stream;
public class UseBulkOperationInspectionTest extends LightQuickFixParameterizedTestCase {
@NotNull
@Override
protected LocalInspectionTool[] configureLocalInspectionTools() {
Extensions.getArea(null).getExtensionPoint(BulkMethodInfoProvider.KEY.getName())
.registerExtension(new BulkMethodInfoProvider() {
@NotNull
@Override
public Stream<BulkMethodInfo> consumers() {
return Stream.of(new BulkMethodInfo("testpackage.TestClass", "test", "test"));
}
});
UseBulkOperationInspection inspection = new UseBulkOperationInspection();
inspection.USE_ARRAYS_AS_LIST = true;
return new LocalInspectionTool[]{

View File

@@ -331,6 +331,8 @@
<extensionPoint name="library.javaSourceRootDetector" interface="com.intellij.openapi.roots.libraries.ui.RootDetector"/>
<extensionPoint name="documentationDelegateProvider" interface="com.intellij.codeInsight.javadoc.DocumentationDelegateProvider"/>
<extensionPoint name="java.inspection.bulkMethodInfo" interface="com.intellij.codeInspection.bulkOperation.BulkMethodInfoProvider"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
@@ -861,7 +863,7 @@
<localInspection groupPath="Java" language="JAVA" shortName="UseBulkOperation"
groupBundle="messages.InspectionsBundle"
groupKey="group.names.performance.issues" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.UseBulkOperationInspection"
implementationClass="com.intellij.codeInspection.bulkOperation.UseBulkOperationInspection"
displayName="Use bulk operation instead of iteration"/>
<localInspection groupPath="Java" language="JAVA" shortName="ComparatorCombinators"
groupBundle="messages.InspectionsBundle"
@@ -1855,6 +1857,8 @@
<codeUsageScopeOptimizer implementation="com.intellij.compiler.JavaCompilerReferencesCodeUsageScopeOptimizer"/>
<importTestOutput implementation="com.intellij.execution.AntTestContentHandler$AntTestOutputExtension"/>
<java.inspection.bulkMethodInfo implementation="com.intellij.codeInspection.bulkOperation.JdkBulkMethodInfoProvider"/>
</extensions>
<actions>