diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/reference/RefJavaModuleImpl.java b/java/java-analysis-impl/src/com/intellij/codeInspection/reference/RefJavaModuleImpl.java index 69d8ff16e6ca..e3273c4b553d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/reference/RefJavaModuleImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/reference/RefJavaModuleImpl.java @@ -18,6 +18,7 @@ package com.intellij.codeInspection.reference; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.psi.*; +import com.intellij.util.containers.ContainerUtil; import gnu.trove.THashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -43,7 +44,7 @@ public class RefJavaModuleImpl extends RefElementImpl implements RefJavaModule { @Override protected void initialize() { - + ((RefModuleImpl)myRefModule).add(this); } @Override @@ -115,7 +116,35 @@ public class RefJavaModuleImpl extends RefElementImpl implements RefJavaModule { } } } - ((RefModuleImpl)myRefModule).add(this); + for (PsiProvidesStatement statement : javaModule.getProvides()) { + final PsiJavaCodeReferenceElement interfaceReference = statement.getInterfaceReference(); + final PsiReferenceList implementationList = statement.getImplementationList(); + if (interfaceReference != null && implementationList != null) { + final PsiElement providerInterface = interfaceReference.resolve(); + if (providerInterface instanceof PsiClass) { + final RefElement refInterface = getRefManager().getReference(providerInterface); + if (refInterface instanceof RefJavaElementImpl) { + for (PsiJavaCodeReferenceElement implementationReference : implementationList.getReferenceElements()) { + final PsiElement implementationClass = implementationReference.resolve(); + if (implementationClass instanceof PsiClass) { + PsiElement targetElement = getProviderMethod((PsiClass)implementationClass); + if (targetElement == null) { + targetElement = getDefaultConstructor((PsiClass)implementationClass); + if (targetElement == null) { + targetElement = implementationClass; + } + } + final RefElement refTargetElement = getRefManager().getReference(targetElement); + if (refTargetElement != null) { + ((RefJavaElementImpl)refInterface) + .addReference(refTargetElement, targetElement, providerInterface, false, true, null); + } + } + } + } + } + } + } getRefManager().fireBuildReferences(this); } } @@ -150,4 +179,19 @@ public class RefJavaModuleImpl extends RefElementImpl implements RefJavaModule { } return !exportedPackages.isEmpty() ? exportedPackages : Collections.emptyMap(); } + + @Nullable + private static PsiMethod getProviderMethod(@NotNull PsiClass psiClass) { + final PsiMethod[] methods = psiClass.findMethodsByName("provider", false); + return ContainerUtil.find(methods, m -> m.hasModifierProperty(PsiModifier.PUBLIC) && + m.hasModifierProperty(PsiModifier.STATIC) && + m.getParameterList().getParametersCount() == 0); + } + + @Nullable + private static PsiMethod getDefaultConstructor(@NotNull PsiClass psiClass) { + final PsiMethod[] constructors = psiClass.getConstructors(); + return ContainerUtil.find(constructors, m -> m.hasModifierProperty(PsiModifier.PUBLIC) && + m.getParameterList().getParametersCount() == 0); + } } diff --git a/java/java-tests/testData/inspection/unusedServiceImplementations/constructor/MyServiceImpl.java b/java/java-tests/testData/inspection/unusedServiceImplementations/constructor/MyServiceImpl.java new file mode 100644 index 000000000000..2a507686b86f --- /dev/null +++ b/java/java-tests/testData/inspection/unusedServiceImplementations/constructor/MyServiceImpl.java @@ -0,0 +1,8 @@ +package my.impl; +import my.api.MyService; + +public class MyServiceImpl implements MyService { + public MyServiceImpl() {} + @Override + public void foo() {} +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/unusedServiceImplementations/constructor/expected.xml b/java/java-tests/testData/inspection/unusedServiceImplementations/constructor/expected.xml new file mode 100644 index 000000000000..5e933496b9cf --- /dev/null +++ b/java/java-tests/testData/inspection/unusedServiceImplementations/constructor/expected.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/unusedServiceImplementations/implementation/MyServiceImpl.java b/java/java-tests/testData/inspection/unusedServiceImplementations/implementation/MyServiceImpl.java new file mode 100644 index 000000000000..0a3d1e4a0b63 --- /dev/null +++ b/java/java-tests/testData/inspection/unusedServiceImplementations/implementation/MyServiceImpl.java @@ -0,0 +1,7 @@ +package my.impl; +import my.api.MyService; + +public class MyServiceImpl implements MyService { + @Override + public void foo() {} +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/unusedServiceImplementations/implementation/expected.xml b/java/java-tests/testData/inspection/unusedServiceImplementations/implementation/expected.xml new file mode 100644 index 000000000000..5e933496b9cf --- /dev/null +++ b/java/java-tests/testData/inspection/unusedServiceImplementations/implementation/expected.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/unusedServiceImplementations/provider/MyServiceImpl.java b/java/java-tests/testData/inspection/unusedServiceImplementations/provider/MyServiceImpl.java new file mode 100644 index 000000000000..0dcb7e99ce79 --- /dev/null +++ b/java/java-tests/testData/inspection/unusedServiceImplementations/provider/MyServiceImpl.java @@ -0,0 +1,12 @@ +package my.impl; +import my.api.MyService; + +public class MyServiceImpl { + public static MyService provider() { + return new MyService() { + @Override + public void foo() { + } + }; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/unusedServiceImplementations/provider/expected.xml b/java/java-tests/testData/inspection/unusedServiceImplementations/provider/expected.xml new file mode 100644 index 000000000000..5e933496b9cf --- /dev/null +++ b/java/java-tests/testData/inspection/unusedServiceImplementations/provider/expected.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/unusedServiceImplementations/unusedConstructor/MyServiceImpl.java b/java/java-tests/testData/inspection/unusedServiceImplementations/unusedConstructor/MyServiceImpl.java new file mode 100644 index 000000000000..2a507686b86f --- /dev/null +++ b/java/java-tests/testData/inspection/unusedServiceImplementations/unusedConstructor/MyServiceImpl.java @@ -0,0 +1,8 @@ +package my.impl; +import my.api.MyService; + +public class MyServiceImpl implements MyService { + public MyServiceImpl() {} + @Override + public void foo() {} +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/unusedServiceImplementations/unusedConstructor/expected.xml b/java/java-tests/testData/inspection/unusedServiceImplementations/unusedConstructor/expected.xml new file mode 100644 index 000000000000..ae969d8f95d0 --- /dev/null +++ b/java/java-tests/testData/inspection/unusedServiceImplementations/unusedConstructor/expected.xml @@ -0,0 +1,10 @@ + + + + MyServiceImpl.java + 5 + + unused + Unused declaration + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/unusedServiceImplementations/unusedImplementation/MyServiceImpl.java b/java/java-tests/testData/inspection/unusedServiceImplementations/unusedImplementation/MyServiceImpl.java new file mode 100644 index 000000000000..0a3d1e4a0b63 --- /dev/null +++ b/java/java-tests/testData/inspection/unusedServiceImplementations/unusedImplementation/MyServiceImpl.java @@ -0,0 +1,7 @@ +package my.impl; +import my.api.MyService; + +public class MyServiceImpl implements MyService { + @Override + public void foo() {} +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/unusedServiceImplementations/unusedImplementation/expected.xml b/java/java-tests/testData/inspection/unusedServiceImplementations/unusedImplementation/expected.xml new file mode 100644 index 000000000000..6a061d5f437d --- /dev/null +++ b/java/java-tests/testData/inspection/unusedServiceImplementations/unusedImplementation/expected.xml @@ -0,0 +1,10 @@ + + + + MyServiceImpl.java + 4 + + unused + Unused declaration + + \ No newline at end of file diff --git a/java/java-tests/testData/inspection/unusedServiceImplementations/unusedProvider/MyServiceImpl.java b/java/java-tests/testData/inspection/unusedServiceImplementations/unusedProvider/MyServiceImpl.java new file mode 100644 index 000000000000..0dcb7e99ce79 --- /dev/null +++ b/java/java-tests/testData/inspection/unusedServiceImplementations/unusedProvider/MyServiceImpl.java @@ -0,0 +1,12 @@ +package my.impl; +import my.api.MyService; + +public class MyServiceImpl { + public static MyService provider() { + return new MyService() { + @Override + public void foo() { + } + }; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/unusedServiceImplementations/unusedProvider/expected.xml b/java/java-tests/testData/inspection/unusedServiceImplementations/unusedProvider/expected.xml new file mode 100644 index 000000000000..ae969d8f95d0 --- /dev/null +++ b/java/java-tests/testData/inspection/unusedServiceImplementations/unusedProvider/expected.xml @@ -0,0 +1,10 @@ + + + + MyServiceImpl.java + 5 + + unused + Unused declaration + + \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/Java9UnusedServiceImplementationsTest.kt b/java/java-tests/testSrc/com/intellij/codeInspection/Java9UnusedServiceImplementationsTest.kt new file mode 100644 index 000000000000..1976ef5ebe94 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInspection/Java9UnusedServiceImplementationsTest.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInspection + +import com.intellij.analysis.AnalysisScope +import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection +import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper +import com.intellij.idea.Bombed +import com.intellij.openapi.application.ex.PathManagerEx +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.io.FileUtil.loadFileText +import com.intellij.testFramework.InspectionTestUtil +import com.intellij.testFramework.createGlobalContextForTool +import com.intellij.testFramework.fixtures.LightJava9ModulesCodeInsightFixtureTestCase +import com.intellij.testFramework.fixtures.MultiModuleJava9ProjectDescriptor.ModuleDescriptor.* +import org.intellij.lang.annotations.Language +import org.jetbrains.annotations.NonNls + +/** + * @author Pavel.Dolgov + */ +class Java9UnusedServiceImplementationsTest : LightJava9ModulesCodeInsightFixtureTestCase() { + override fun getTestDataPath() = PathManagerEx.getTestDataPath() + "/inspection/unusedServiceImplementations/" + + override fun setUp() { + super.setUp() + + addFile("module-info.java", "module MAIN { requires M2; }", MAIN) + addFile("module-info.java", "module M2 { exports my.api; provides my.api.MyService with my.impl.MyServiceImpl; }", M2) + + addFile("my/api/MyService.java", "package my.api; public interface MyService { void foo(); }", M2) + } + + fun testImplementation() = doTest() + + fun testConstructor() = doTest() + + fun testProvider() = doTest() + + @Bombed(user ="Pavel Dolgov", year = 2017, month = 8, day = 1, description = "Disabled, until the test infrastructure supports it") + fun testUnusedImplementation() = doTest(false) + + @Bombed(user ="Pavel Dolgov", year = 2017, month = 8, day = 1, description = "Disabled, until the test infrastructure supports it") + fun testUnusedConstructor() = doTest(false) + + @Bombed(user ="Pavel Dolgov", year = 2017, month = 8, day = 1, description = "Disabled, until the test infrastructure supports it") + fun testUnusedProvider() = doTest(false) + + private fun doTest(withUsage: Boolean = true) { + @Language("JAVA") @NonNls + val usageText = """ + import my.api.MyService; + public class MyApp { + public static void main(String[] args) { + for (MyService service : ServiceLoader.load(MyService.class)) { + service.foo(); + } + } + }""" + if (withUsage) addFile("my/app/MyApp.java", usageText, MAIN) + + val testPath = testDataPath + "/" + getTestName(true) + val sourceFile = FileUtil.findFirstThatExist(testPath + "/MyServiceImpl.java") + assertNotNull("Test data: $testPath", sourceFile) + val implText = String(loadFileText(sourceFile!!)) + addFile("my/impl/MyServiceImpl.java", implText, M2) + + val toolWrapper = GlobalInspectionToolWrapper(UnusedDeclarationInspection()) + val scope = AnalysisScope(project) + val globalContext = createGlobalContextForTool(scope, project, listOf(toolWrapper)) + InspectionTestUtil.runTool(toolWrapper, scope, globalContext) + InspectionTestUtil.compareToolResults(globalContext, toolWrapper, true, testPath) + } +} \ No newline at end of file