Java: Provide completion inside literal argument of Class.forName() and ClassLoader.loadClass() (IDEA-167267)

This commit is contained in:
Pavel Dolgov
2017-02-06 17:24:18 +03:00
parent 6b4f77f3a2
commit bcb8f9b8e8
13 changed files with 209 additions and 9 deletions

View File

@@ -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);

View File

@@ -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<PsiLiteral> 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;
}
});
}
}

View File

@@ -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);
}

View File

@@ -0,0 +1,6 @@
import foo.bar.*;
class Main {
void foo() throws ReflectiveOperationException {
Class.forName("foo.bar.P<caret>");
}
}

View File

@@ -0,0 +1,6 @@
import foo.bar.*;
class Main {
void foo() throws ReflectiveOperationException {
Class.forName("foo.bar.PublicClass");
}
}

View File

@@ -0,0 +1,6 @@
import foo.bar.*;
class Main {
void foo() throws ReflectiveOperationException {
Class.forName("foo.bar.PublicClass.<caret>");
}
}

View File

@@ -0,0 +1,6 @@
import foo.bar.*;
class Main {
void foo() throws ReflectiveOperationException {
Class.forName("foo.bar.PublicClass.NestedClass");
}
}

View File

@@ -0,0 +1,7 @@
import foo.bar.one.*;
import foo.bar.two.*;
class Main {
void foo() throws ReflectiveOperationException {
Class.forName("foo.bar.<caret>");
}
}

View File

@@ -0,0 +1,7 @@
import foo.bar.one.*;
import foo.bar.two.*;
class Main {
void foo() throws ReflectiveOperationException {
Class.forName("foo.bar.one");
}
}

View File

@@ -0,0 +1,6 @@
import foo.bar.*;
class Main {
void foo() throws ReflectiveOperationException {
Thread.currentThread().getContextClassLoader().loadClass("foo.bar.<caret>");
}
}

View File

@@ -0,0 +1,6 @@
import foo.bar.*;
class Main {
void foo() throws ReflectiveOperationException {
Thread.currentThread().getContextClassLoader().loadClass("foo.bar.PublicClass");
}
}

View File

@@ -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);

View File

@@ -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.<caret>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.<caret>PublicClass.PrivateInnerClass")
}
fun testWithClassLoader() {
myFixture.addClass("package foo.bar; public class PublicClass {}")
doTest("foo.bar.<caret>PublicClass.PrivateInnerClass", { "Thread.currentThread().getContextClassLoader().loadClass(\"$it\")" })
}
private fun doTest(className: String, usageFormatter: (String) -> String = { "Class.forName(\"$it\")" }) {
val caretPos = className.indexOf("<caret>")
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("<caret>", "")
}
else {
atCaret = className + "<caret>"
}
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)
}
}