[java, completion] refactoring: simplify code, add javadoc, add localization IJ-CR-127501

GitOrigin-RevId: e5adf605b24e8f74932f98c805d926ea4ef5d200
This commit is contained in:
Aleksey Dobrynin
2024-03-01 22:56:33 +00:00
committed by intellij-monorepo-bot
parent 0141f07298
commit d9e11e9153
10 changed files with 80 additions and 60 deletions
@@ -3,9 +3,10 @@ package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.codeInspection.jigsaw.JigsawApiConstants;
import com.intellij.codeInspection.jigsaw.JigsawUtil;
import com.intellij.java.JavaBundle;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiJavaModule;
import org.jetbrains.annotations.NotNull;
public class JigsawProviderLookupElement extends LookupElement {
@@ -15,20 +16,18 @@ public class JigsawProviderLookupElement extends LookupElement {
@Override
public @NotNull String getLookupString() {
return JigsawApiConstants.PROVIDER;
return PsiJavaModule.PROVIDER_METHOD;
}
@Override
public void renderElement(@NotNull LookupElementPresentation presentation) {
super.renderElement(presentation);
presentation.setTypeText("provider() method declaration");
presentation.setTypeText(JavaBundle.message("completion.provider.method.declaration.type"));
}
@Override
public void handleInsert(@NotNull InsertionContext context) {
int selectionLength = context.getSelectionEndOffset() - context.getStartOffset();
JigsawUtil.addProviderMethod(myPsiClass, context.getEditor(), context.getStartOffset(),
(offset, content) -> context.getDocument().replaceString(offset, offset + selectionLength,
content));
context.getDocument().replaceString(context.getStartOffset(), context.getSelectionEndOffset(), "");
JigsawUtil.addProviderMethod(myPsiClass, context.getEditor(), context.getStartOffset());
}
}
@@ -30,8 +30,7 @@ public class GenerateProviderMethodHandler implements CodeInsightActionHandler {
if (!FileModificationService.getInstance().preparePsiElementsForWrite(targetClass)) return;
WriteCommandAction.writeCommandAction(project, file)
.run(() -> JigsawUtil.addProviderMethod(targetClass, editor, getOffset(context, offset),
(currentOffset, content) -> editor.getDocument().insertString(currentOffset, content)));
.run(() -> JigsawUtil.addProviderMethod(targetClass, editor, getOffset(context, offset)));
}
/**
@@ -1,6 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.jigsaw;
public interface JigsawApiConstants {
String PROVIDER = "provider";
}
@@ -12,30 +12,32 @@ import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.BiConsumer;
public final class JigsawUtil {
private JigsawUtil() { }
public static void addProviderMethod(@NotNull PsiClass targetClass,
@NotNull Editor editor,
int startOffset,
@NotNull BiConsumer<Integer, String> setMethod) {
/**
* Adds a provider static method to a target class (java service) at the specified start offset in the editor.
* The provider method returns the target class instance.
*
* @param targetClass The class to which the provider method will be added.
* @param editor The editor in which the changes will be made.
* @param startOffset The start offset in the editor where the provider method will be inserted.
*/
public static void addProviderMethod(@NotNull PsiClass targetClass, @NotNull Editor editor, int startOffset) {
String className = targetClass.getName();
if (className == null) return;
String methodStringBeforeCursor = "public static " + className + " " + JigsawApiConstants.PROVIDER + "() {" +
String methodStringBeforeCursor = "public static " + className + " " + PsiJavaModule.PROVIDER_METHOD + "() {" +
"return new " + className + "(";
String methodStringAfterCursor = ");}";
Document document = editor.getDocument();
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(targetClass.getProject());
setMethod.accept(startOffset, methodStringBeforeCursor + methodStringAfterCursor);
editor.getDocument().insertString(startOffset, methodStringBeforeCursor + methodStringAfterCursor);
editor.getCaretModel().moveToOffset(startOffset + methodStringBeforeCursor.length());
documentManager.commitDocument(document);
documentManager.doPostponedOperationsAndUnblockDocument(document);
PsiFile psiFile = documentManager.getPsiFile(document);
if (psiFile != null) {
@@ -44,10 +46,19 @@ public final class JigsawUtil {
}
}
/**
* Verifies the accessibility of the provider method for a specified class. The target class must meet the following criteria:
* - It implements an interface that is listed in the module-info provider section.
* - It is declared in the module-info provider section.
* - It does not have a provider method.
*
* @param targetClass The class to be checked for provider method accessibility.
* @return true if the provider method is accessible, false otherwise.
*/
@Contract("null -> false")
public static boolean checkProviderMethodAccessible(@Nullable PsiClass targetClass) {
if (targetClass == null || targetClass.getName() == null) return false;
JvmMethod[] methods = targetClass.findMethodsByName(JigsawApiConstants.PROVIDER);
JvmMethod[] methods = targetClass.findMethodsByName(PsiJavaModule.PROVIDER_METHOD);
for (JvmMethod method : methods) {
if (!method.hasParameters()) return false;
}
@@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.psi;
import com.intellij.openapi.util.NlsSafe;
@@ -16,9 +16,27 @@ public interface PsiJavaModule extends NavigatablePsiElement, PsiNameIdentifierO
String MODULE_INFO_CLASS = "module-info";
@NlsSafe String MODULE_INFO_FILE = MODULE_INFO_CLASS + ".java";
String MODULE_INFO_CLS_FILE = MODULE_INFO_CLASS + ".class";
/**
* "java.base" module is the the core module of the Java SE Platform.
* As such, it does not require explicit declaration in the module's {@code requires} directive.
*/
String JAVA_BASE = "java.base";
/**
* Represents the manifest attribute name used to specify an automatic module name.
* When a JAR file does not contain a module-info.java file,
* it can still be used as an automatic module by providing this attribute in its manifest.
* The value of this attribute defines the module's name, allowing it to be required by other modules.
*/
String AUTO_MODULE_NAME = "Automatic-Module-Name";
/**
* When a service provider specifies a "provider" method, the service loader uses this method to create a service provider instance.
* This method must be public, static, have no parameters, and return a type compatible with the service's interface or class.
*/
String PROVIDER_METHOD = "provider";
/* See http://openjdk.org/jeps/261#Class-loaders, "Class loaders" */
Set<String> UPGRADEABLE = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
"java.activation", "java.compiler", "java.corba", "java.transaction", "java.xml.bind", "java.xml.ws", "java.xml.ws.annotation",
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="17" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
@@ -39,10 +39,6 @@ public class GenerateProviderMethodTest extends LightFixtureCompletionTestCase {
doTest("src/org/jetbrains/providers/MyProviderImpl.java");
}
public void testWithRecord() {
doTest("src/org/jetbrains/providers/MyRecord.java");
}
public void testWithProvider() {
doTest("src/org/jetbrains/providers/WithProvider.java");
}
@@ -5,15 +5,18 @@ import com.intellij.JavaTestUtil;
import com.intellij.codeInsight.completion.LightFixtureCompletionTestCase;
import com.intellij.codeInsight.lookup.Lookup;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.impl.LookupImpl;
import com.intellij.java.testFramework.fixtures.MultiModuleProjectDescriptor;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.NeedsIndex;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@NeedsIndex.SmartMode(reason = "Provider method completion is not supported in the dumb mode")
public class ProviderMethodCompletionTest extends LightFixtureCompletionTestCase {
@@ -33,60 +36,59 @@ public class ProviderMethodCompletionTest extends LightFixtureCompletionTestCase
}
public void testNonModuleClass() {
doTest("src/org/jetbrains/providers/SimpleClass.java", "private",
"protected");
doTest("src/org/jetbrains/providers/SimpleClass.java", Set.of("private",
"protected"));
}
public void testSimpleProvider() {
doTest("src/org/jetbrains/providers/MyProviderImpl.java", "provider",
"private",
"protected");
}
public void testWithRecord() {
doTest("src/org/jetbrains/providers/MyRecord.java", "provider",
"private",
"protected");
doTest("src/org/jetbrains/providers/MyProviderImpl.java", Set.of("provider",
"private",
"protected"));
}
public void testWithProvider() {
doTest("src/org/jetbrains/providers/WithProvider.java", "private",
"protected");
doTest("src/org/jetbrains/providers/WithProvider.java", Set.of("private",
"protected"));
}
public void testSubClass() {
doTest("src/org/jetbrains/providers/MySuperClass.java", "provider",
"private",
"protected");
doTest("src/org/jetbrains/providers/MySuperClass.java", Set.of("provider",
"private",
"protected"));
}
public void testWrongPlace() {
doTest("src/org/jetbrains/providers/WrongPlace.java", "process", "program");
doTest("src/org/jetbrains/providers/WrongPlace.java", Set.of("process", "program"));
}
public void testWrongPlace2() {
doTest("src/org/jetbrains/providers/WrongPlace2.java");
doTest("src/org/jetbrains/providers/WrongPlace2.java", Set.of());
}
private void doTest(String path, String... names) {
private void doTest(String path, Set<String> names) {
VirtualFile file = getModule().getModuleFile().getParent().findFileByRelativePath(path);
myFixture.configureFromExistingVirtualFile(file);
myFixture.completeBasic();
List<LookupElement> items = Arrays.asList(myFixture.completeBasic());
if (names.length != 0) {
assertStringItems(names);
if (!names.isEmpty()) {
// contains
List<String> currentNames = ContainerUtil.map(items, LookupElement::getLookupString);
assertTrue(currentNames + " should contains all " + names, currentNames.containsAll(names));
// not contains
if (!names.contains("provider")) {
assertTrue("lookup items shouldn't contain a 'provider' element",
items.stream().map(LookupElement::getLookupString).noneMatch(name -> "provider".equals(name)));
}
Optional<LookupElement> item = getLookup().getItems().stream().filter(le -> "provider".equals(le.getLookupString())).findFirst();
Optional<LookupElement> item = items.stream().filter(le -> "provider".equals(le.getLookupString())).findFirst();
if (item.isPresent()) {
myFixture.getLookup().setCurrentItem(item.get());
myFixture.finishLookup(Lookup.NORMAL_SELECT_CHAR);
}
}
else {
LookupImpl lookup = getLookup();
if (lookup != null) {
assertStringItems(names);
}
assertTrue("lookup items shouldn't contain a 'provider' element",
items.stream().map(LookupElement::getLookupString).noneMatch(name -> "provider".equals(name)));
}
checkResultByFile("after/" + path);
}
@@ -151,6 +151,7 @@ completion.smart.hint=Press {0} to show only variants that are suitable by type
completion.smart.toar.hint=Press {0} once more to find collections of {1}
completion.smart.type.generate.anonymous.body=generate anonymous body
completion.unknown.type=Unknown type {0}
completion.provider.method.declaration.type=provider() method declaration
configurable.EqualsHashCodeTemplatesPanel.display.name=Templates
configurable.GenerateToStringConfigurable.display.name=Settings
configurable.TemplatesPanel.display.name=Templates