diff --git a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionCompletionConfidence.java b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionCompletionConfidence.java index d605842ddcfa..2f9add0138e7 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionCompletionConfidence.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionCompletionConfidence.java @@ -30,7 +30,9 @@ public class JavaReflectionCompletionConfidence extends CompletionConfidence { @Override public ThreeState shouldSkipAutopopup(@NotNull PsiElement contextElement, @NotNull PsiFile psiFile, int offset) { final PsiElement literal = contextElement.getParent(); - if (literal != null && JavaReflectionReferenceContributor.PATTERN.accepts(literal)) { + if (literal != null && + (JavaReflectionReferenceContributor.PATTERN.accepts(literal) || + JavaReflectionReferenceContributor.CLASS_PATTERN.accepts(literal))) { return ThreeState.NO; } return super.shouldSkipAutopopup(contextElement, psiFile, offset); diff --git a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceContributor.java b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceContributor.java index 8077acfde4e5..b3a0725d81bb 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceContributor.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceContributor.java @@ -16,13 +16,15 @@ package com.intellij.psi.impl.source.resolve.reference.impl; import com.intellij.patterns.PsiJavaElementPattern; -import com.intellij.psi.PsiLiteral; -import com.intellij.psi.PsiReferenceContributor; -import com.intellij.psi.PsiReferenceRegistrar; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReferenceProvider; +import com.intellij.util.ProcessingContext; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import static com.intellij.patterns.PsiJavaPatterns.psiLiteral; import static com.intellij.patterns.PsiJavaPatterns.psiMethod; +import static com.intellij.patterns.StandardPatterns.or; import static com.intellij.patterns.StandardPatterns.string; import static com.intellij.psi.CommonClassNames.JAVA_LANG_CLASS; @@ -37,8 +39,38 @@ public class JavaReflectionReferenceContributor extends PsiReferenceContributor "getDeclaredMethod")) .definedInClass(JAVA_LANG_CLASS)); + public static final PsiJavaElementPattern.Capture CLASS_PATTERN = + psiLiteral().methodCallParameter(or( + psiMethod().withName(string().equalTo("forName")).definedInClass(JAVA_LANG_CLASS), + psiMethod().withName(string().equalTo("loadClass")).definedInClass("java.lang.ClassLoader"))); + @Override public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) { - registrar.registerReferenceProvider(PATTERN, new JavaReflectionReferenceProvider()); + registrar.registerReferenceProvider(PATTERN, new JavaReflectionReferenceProvider() { + @Nullable + @Override + protected PsiReference[] getReferencesByMethod(@NotNull PsiLiteralExpression literalArgument, + @NotNull PsiReferenceExpression methodReference, + @NotNull ProcessingContext context) { + + PsiExpression qualifier = methodReference.getQualifierExpression(); + return qualifier != null ? new PsiReference[]{new JavaLangClassMemberReference(literalArgument, qualifier)} : null; + } + }); + + registrar.registerReferenceProvider(CLASS_PATTERN, new JavaReflectionReferenceProvider() { + @Nullable + @Override + protected PsiReference[] getReferencesByMethod(@NotNull PsiLiteralExpression literalArgument, + @NotNull PsiReferenceExpression methodReference, + @NotNull ProcessingContext context) { + + String referenceName = methodReference.getReferenceName(); + if ("forName".equals(referenceName) || "loadClass".equals(referenceName)) { + return new JavaClassReferenceProvider().getReferencesByElement(literalArgument, context); + } + return null; + } + }); } } diff --git a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceProvider.java b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceProvider.java index 54d353db7dbf..ec598b01111b 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceProvider.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceProvider.java @@ -18,11 +18,12 @@ package com.intellij.psi.impl.source.resolve.reference.impl; import com.intellij.psi.*; import com.intellij.util.ProcessingContext; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * @author Konstantin Bulenkov */ -public class JavaReflectionReferenceProvider extends PsiReferenceProvider { +abstract class JavaReflectionReferenceProvider extends PsiReferenceProvider { @NotNull @Override public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) { @@ -34,9 +35,9 @@ public class JavaReflectionReferenceProvider extends PsiReferenceProvider { PsiElement grandParent = parent.getParent(); if (grandParent instanceof PsiMethodCallExpression) { PsiReferenceExpression methodReference = ((PsiMethodCallExpression)grandParent).getMethodExpression(); - PsiExpression qualifier = methodReference.getQualifierExpression(); - if (qualifier != null) { - return new PsiReference[]{new JavaLangClassMemberReference(literal, qualifier)}; + PsiReference[] references = getReferencesByMethod(literal, methodReference, context); + if (references != null) { + return references; } } } @@ -44,4 +45,9 @@ public class JavaReflectionReferenceProvider extends PsiReferenceProvider { } return PsiReference.EMPTY_ARRAY; } + + @Nullable + protected abstract PsiReference[] getReferencesByMethod(@NotNull PsiLiteralExpression literalArgument, + @NotNull PsiReferenceExpression methodReference, + @NotNull ProcessingContext context); } diff --git a/java/java-tests/testData/codeInsight/completion/reflection/ClassForNameClasses.java b/java/java-tests/testData/codeInsight/completion/reflection/ClassForNameClasses.java new file mode 100644 index 000000000000..f2346319f85f --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/reflection/ClassForNameClasses.java @@ -0,0 +1,6 @@ +import foo.bar.*; +class Main { + void foo() throws ReflectiveOperationException { + Class.forName("foo.bar.P"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/reflection/ClassForNameClasses_after.java b/java/java-tests/testData/codeInsight/completion/reflection/ClassForNameClasses_after.java new file mode 100644 index 000000000000..951ec3f9492d --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/reflection/ClassForNameClasses_after.java @@ -0,0 +1,6 @@ +import foo.bar.*; +class Main { + void foo() throws ReflectiveOperationException { + Class.forName("foo.bar.PublicClass"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/reflection/ClassForNameNested.java b/java/java-tests/testData/codeInsight/completion/reflection/ClassForNameNested.java new file mode 100644 index 000000000000..ce74eb2de764 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/reflection/ClassForNameNested.java @@ -0,0 +1,6 @@ +import foo.bar.*; +class Main { + void foo() throws ReflectiveOperationException { + Class.forName("foo.bar.PublicClass."); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/reflection/ClassForNameNested_after.java b/java/java-tests/testData/codeInsight/completion/reflection/ClassForNameNested_after.java new file mode 100644 index 000000000000..faa113a76dca --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/reflection/ClassForNameNested_after.java @@ -0,0 +1,6 @@ +import foo.bar.*; +class Main { + void foo() throws ReflectiveOperationException { + Class.forName("foo.bar.PublicClass.NestedClass"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/reflection/ClassForNamePackages.java b/java/java-tests/testData/codeInsight/completion/reflection/ClassForNamePackages.java new file mode 100644 index 000000000000..d0688238fd7f --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/reflection/ClassForNamePackages.java @@ -0,0 +1,7 @@ +import foo.bar.one.*; +import foo.bar.two.*; +class Main { + void foo() throws ReflectiveOperationException { + Class.forName("foo.bar."); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/reflection/ClassForNamePackages_after.java b/java/java-tests/testData/codeInsight/completion/reflection/ClassForNamePackages_after.java new file mode 100644 index 000000000000..020592fcfa8f --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/reflection/ClassForNamePackages_after.java @@ -0,0 +1,7 @@ +import foo.bar.one.*; +import foo.bar.two.*; +class Main { + void foo() throws ReflectiveOperationException { + Class.forName("foo.bar.one"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/reflection/WithClassLoader.java b/java/java-tests/testData/codeInsight/completion/reflection/WithClassLoader.java new file mode 100644 index 000000000000..fdb73308d33a --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/reflection/WithClassLoader.java @@ -0,0 +1,6 @@ +import foo.bar.*; +class Main { + void foo() throws ReflectiveOperationException { + Thread.currentThread().getContextClassLoader().loadClass("foo.bar."); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/reflection/WithClassLoader_after.java b/java/java-tests/testData/codeInsight/completion/reflection/WithClassLoader_after.java new file mode 100644 index 000000000000..184f3c41a34b --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/reflection/WithClassLoader_after.java @@ -0,0 +1,6 @@ +import foo.bar.*; +class Main { + void foo() throws ReflectiveOperationException { + Thread.currentThread().getContextClassLoader().loadClass("foo.bar.PublicClass"); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/JavaReflectionCompletionTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/completion/JavaReflectionCompletionTest.java index d9d1fe83a6b3..c9fe7b8c6c2b 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/JavaReflectionCompletionTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/JavaReflectionCompletionTest.java @@ -142,6 +142,29 @@ public class JavaReflectionCompletionTest extends LightFixtureCompletionTestCase doTest(1, "num", "num2", "num3"); } + + public void testClassForNameClasses() { + myFixture.addClass("package foo.bar; public class PublicClass {}"); + myFixture.addClass("package foo.bar; class PackageLocalClass {}"); + doTest(1, "PackageLocalClass", "PublicClass"); + } + + public void testClassForNamePackages() { + myFixture.addClass("package foo.bar.one; public class FirstClass {}"); + myFixture.addClass("package foo.bar.two; public class SecondClass {}"); + doTest(0, "one", "two"); + } + + public void testClassForNameNested() { + myFixture.addClass("package foo.bar; public class PublicClass { public static class NestedClass {} }"); + doTest(0, "NestedClass"); + } + + public void testWithClassLoader() { + myFixture.addClass("package foo.bar; public class PublicClass {}"); + doTest(0, "PublicClass"); + } + private void doTest(int index, String... expected) { configureByFile(getTestName(false) + ".java"); assertStringItems(expected); diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/navigation/JavaReflectionClassNavigationTest.kt b/java/java-tests/testSrc/com/intellij/codeInsight/navigation/JavaReflectionClassNavigationTest.kt new file mode 100644 index 000000000000..df62a661627b --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/navigation/JavaReflectionClassNavigationTest.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.codeInsight.navigation + +import com.intellij.psi.PsiClass +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase + +/** + * @author Pavel.Dolgov + */ +class JavaReflectionClassNavigationTest : LightCodeInsightFixtureTestCase() { + + fun testPublicClass() { + myFixture.addClass("package foo.bar; public class PublicClass {}") + doTest("foo.bar.PublicClass") + } + + fun testPublicInnerClass() { + myFixture.addClass("package foo.bar; public class PublicClass { public class PublicInnerClass {} }") + doTest("foo.bar.PublicClass.PublicInnerClass") + } + + fun testPublicInnerClass2() { + myFixture.addClass("package foo.bar; public class PublicClass { public class PublicInnerClass {} }") + doTest("foo.bar.PublicClass.PublicInnerClass") + } + + fun testPackageLocalClass() { + myFixture.addClass("package foo.bar; class PackageLocalClass {}") + doTest("foo.bar.PackageLocalClass") + } + + fun testPrivateInnerClass() { + myFixture.addClass("package foo.bar; public class PublicClass { private class PrivateInnerClass {} }") + doTest("foo.bar.PublicClass.PrivateInnerClass") + } + + fun testPrivateInnerClass2() { + myFixture.addClass("package foo.bar; public class PublicClass { private class PrivateInnerClass {} }") + doTest("foo.bar.PublicClass.PrivateInnerClass") + } + + fun testWithClassLoader() { + myFixture.addClass("package foo.bar; public class PublicClass {}") + doTest("foo.bar.PublicClass.PrivateInnerClass", { "Thread.currentThread().getContextClassLoader().loadClass(\"$it\")" }) + } + + private fun doTest(className: String, usageFormatter: (String) -> String = { "Class.forName(\"$it\")" }) { + val caretPos = className.indexOf("") + val atCaret: String + var expectedName = className + if (caretPos >= 0) { + atCaret = className + val dotPos = className.indexOf(".", caretPos + 1) + if (dotPos >= 0) expectedName = className.substring(0, dotPos).replace("", "") + } + else { + atCaret = className + "" + } + myFixture.configureByText("Main.java", + """import foo.bar.*; + class Main { + void foo() throws ReflectiveOperationException { + ${usageFormatter(atCaret)}; + } + }""") + val reference = myFixture.file.findReferenceAt(myFixture.caretOffset) + assertNotNull("No reference at the caret", reference) + val resolved = reference!!.resolve() + assertTrue("Class not resolved: " + reference.canonicalText, resolved is PsiClass) + val psiClass = resolved as? PsiClass + assertEquals("Class name", expectedName, psiClass?.qualifiedName) + } +}