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