Java: Fix "unresolved package" error in module-info.java by creating a class in that package (IDEA-176421)

This commit is contained in:
Pavel Dolgov
2018-01-10 18:55:26 +03:00
parent 6a40635284
commit bf89486dfa
8 changed files with 272 additions and 51 deletions

View File

@@ -301,4 +301,7 @@ public abstract class QuickFixFactory {
@NotNull
public abstract IntentionAction createWrapWithAdapterFix(@Nullable PsiType type, @NotNull PsiExpression expression);
@Nullable
public abstract IntentionAction createCreateClassInPackageInModuleFix(@NotNull Module module, @Nullable String packageName);
}

View File

@@ -308,11 +308,15 @@ public class ModuleHighlightUtil {
HighlightInfoType type = statement.getRole() == Role.OPENS ? HighlightInfoType.WARNING : HighlightInfoType.ERROR;
if (directories == null || directories.length == 0) {
String message = JavaErrorMessages.message("package.not.found", packageName);
return HighlightInfo.newHighlightInfo(type).range(refElement).descriptionAndTooltip(message).create();
HighlightInfo info = HighlightInfo.newHighlightInfo(type).range(refElement).descriptionAndTooltip(message).create();
QuickFixAction.registerQuickFixAction(info, factory().createCreateClassInPackageInModuleFix(module, packageName));
return info;
}
if (packageName != null && PsiUtil.isPackageEmpty(directories, packageName)) {
String message = JavaErrorMessages.message("package.is.empty", packageName);
return HighlightInfo.newHighlightInfo(type).range(refElement).descriptionAndTooltip(message).create();
HighlightInfo info = HighlightInfo.newHighlightInfo(type).range(refElement).descriptionAndTooltip(message).create();
QuickFixAction.registerQuickFixAction(info, factory().createCreateClassInPackageInModuleFix(module, packageName));
return info;
}
}
}

View File

@@ -707,4 +707,10 @@ public class EmptyQuickFixFactory extends QuickFixFactory {
public IntentionAction createDeleteSideEffectAwareFix(@NotNull PsiExpressionStatement statement) {
return QuickFixes.EMPTY_FIX;
}
@Nullable
@Override
public IntentionAction createCreateClassInPackageInModuleFix(@NotNull Module module, @Nullable String packageName) {
return QuickFixes.EMPTY_FIX;
}
}

View File

@@ -7,6 +7,7 @@ import com.intellij.codeInsight.CodeInsightUtil;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.roots.JavaProjectRootsUtil;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
@@ -14,11 +15,16 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiUtil;
import com.intellij.ui.ListCellRendererWrapper;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import static com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageUtils.scheduleFileOrPackageCreationFailedMessageBox;
@@ -77,14 +83,13 @@ public abstract class CreateServiceClassFixBase implements IntentionAction {
}
@Nullable
protected static PsiClass createClassInRoot(@NotNull String classFQN,
boolean isClass,
@NotNull PsiDirectory rootDir,
@Nullable String superClassName) {
public static PsiDirectory getOrCreatePackageDirInRoot(@NotNull String packageName, @NotNull PsiDirectory rootDir) {
if (packageName.isEmpty()) {
return rootDir;
}
PsiDirectory directory = rootDir;
String lastName;
StringTokenizer st = new StringTokenizer(classFQN, ".");
for (lastName = st.nextToken(); st.hasMoreTokens(); lastName = st.nextToken()) {
String[] shortNames = packageName.split("\\.");
for (String lastName : shortNames) {
PsiDirectory subdirectory = directory.findSubdirectory(lastName);
if (subdirectory != null) {
directory = subdirectory;
@@ -99,29 +104,24 @@ public abstract class CreateServiceClassFixBase implements IntentionAction {
}
}
}
PsiClass psiClass = createClass(lastName, isClass, directory);
if (psiClass != null) {
PsiUtil.setModifierProperty(psiClass, PsiModifier.PUBLIC, true);
if (superClassName != null) {
CreateFromUsageUtils.setupSuperClassReference(psiClass, superClassName);
}
}
return psiClass;
return directory;
}
@Nullable
private static PsiClass createClass(String name, boolean isClass, PsiDirectory directory) {
try {
if (isClass) {
return JavaDirectoryService.getInstance().createClass(directory, name);
}
return JavaDirectoryService.getInstance().createInterface(directory, name);
}
catch (final IncorrectOperationException e) {
scheduleFileOrPackageCreationFailedMessageBox(e, name, directory, false);
}
return null;
protected static PsiClass createClassInRoot(@NotNull String classFQN,
boolean isClass,
@NotNull PsiDirectory rootDir,
@NotNull PsiElement contextElement,
@Nullable String superClassName) {
String packageName = StringUtil.getPackageName(classFQN);
int lastDot = classFQN.lastIndexOf('.');
String className = lastDot >= 0 ? classFQN.substring(lastDot + 1) : classFQN;
PsiDirectory packageDir = getOrCreatePackageDirInRoot(packageName, rootDir);
if (packageDir == null) return null;
return CreateFromUsageUtils.createClass(isClass ? CreateClassKind.CLASS : CreateClassKind.INTERFACE,
packageDir, className, contextElement.getManager(),
contextElement, null, superClassName);
}
protected static PsiDirectory[] getModuleRootDirs(Module module) {
@@ -136,9 +136,19 @@ public abstract class CreateServiceClassFixBase implements IntentionAction {
.toArray(PsiDirectory[]::new);
}
protected static void positionCursor(@Nullable PsiClass psiClass) {
public static void positionCursor(@Nullable PsiClass psiClass) {
if (psiClass != null) {
CodeInsightUtil.positionCursor(psiClass.getProject(), psiClass.getContainingFile(), psiClass);
}
}
public static class PsiDirectoryListCellRenderer extends ListCellRendererWrapper<PsiDirectory> {
@Override
public void customize(JList list, PsiDirectory psiDir, int index, boolean selected, boolean hasFocus) {
if (psiDir != null) {
String text = ProjectUtil.calcRelativeToProjectPath(psiDir.getVirtualFile(), psiDir.getProject(), true, false, true);
setText(text);
}
}
}
}

View File

@@ -19,7 +19,6 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.ui.ListCellRendererWrapper;
import com.intellij.ui.components.JBRadioButton;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
@@ -114,7 +113,7 @@ public class CreateServiceImplementationClassFix extends CreateServiceClassFixBa
PsiDirectory rootDir = file.getUserData(SERVICE_ROOT_DIR);
Boolean isSubclass = file.getUserData(SERVICE_IS_SUBCLASS);
if (rootDir != null && isSubclass != null) {
WriteAction.run(() -> createClassInRoot(rootDir, isSubclass));
WriteAction.run(() -> createClassInRoot(rootDir, isSubclass, file));
}
return;
}
@@ -125,17 +124,17 @@ public class CreateServiceImplementationClassFix extends CreateServiceClassFixBa
PsiDirectory psiRootDir = dialog.getRootDir();
if (psiRootDir != null) {
boolean isSubclass = dialog.isSubclass();
PsiClass psiClass = WriteAction.compute(() -> createClassInRoot(psiRootDir, isSubclass));
PsiClass psiClass = WriteAction.compute(() -> createClassInRoot(psiRootDir, isSubclass, file));
positionCursor(psiClass);
}
}
}
}
private PsiClass createClassInRoot(@NotNull PsiDirectory psiRootDir, boolean isSubclass) {
private PsiClass createClassInRoot(@NotNull PsiDirectory psiRootDir, boolean isSubclass, @NotNull PsiElement contextElement) {
Project project = psiRootDir.getProject();
PsiClass psiImplClass = createClassInRoot(myImplementationClassName, true,
psiRootDir, isSubclass ? mySuperClassName : null);
psiRootDir, contextElement, isSubclass ? mySuperClassName : null);
if (psiImplClass != null && !isSubclass) {
String text = "public static " + mySuperClassName + " provider() { return null;}";
PsiMethod method = JavaPsiFacade.getElementFactory(project).createMethodFromText(text, psiImplClass.getLBrace());
@@ -177,12 +176,7 @@ public class CreateServiceImplementationClassFix extends CreateServiceClassFixBa
group.add(mySubclassButton);
group.add(myProviderButton);
myRootDirCombo.setRenderer(new ListCellRendererWrapper<PsiDirectory>() {
@Override
public void customize(JList list, PsiDirectory psiDir, int index, boolean selected, boolean hasFocus) {
setText(psiDir != null ? psiDir.getVirtualFile().getPresentableUrl() : "");
}
});
myRootDirCombo.setRenderer(new PsiDirectoryListCellRenderer());
myRootDirCombo.setModel(new DefaultComboBoxModel<>(psiRootDirs));
init();

View File

@@ -102,7 +102,7 @@ public class CreateServiceInterfaceOrClassFix extends CreateServiceClassFixBase
PsiDirectory rootDir = file.getUserData(SERVICE_ROOT_DIR);
Boolean isClass = file.getUserData(SERVICE_IS_CLASS);
if (rootDir != null && isClass != null) {
WriteAction.run(() -> createClassInRoot(myInterfaceName, isClass, rootDir, null));
WriteAction.run(() -> createClassInRoot(myInterfaceName, isClass, rootDir, file, null));
}
return;
}
@@ -111,7 +111,7 @@ public class CreateServiceInterfaceOrClassFix extends CreateServiceClassFixBase
PsiDirectory rootDir = dialog.getRootDir();
boolean isClass = dialog.isClass();
if (rootDir != null) {
PsiClass psiClass = WriteAction.compute(() -> createClassInRoot(myInterfaceName, isClass, rootDir, null));
PsiClass psiClass = WriteAction.compute(() -> createClassInRoot(myInterfaceName, isClass, rootDir, file, null));
positionCursor(psiClass);
}
}
@@ -140,7 +140,7 @@ public class CreateServiceInterfaceOrClassFix extends CreateServiceClassFixBase
positionCursor(psiClass);
}
private static class CreateServiceInterfaceDialog extends DialogWrapper {
private class CreateServiceInterfaceDialog extends DialogWrapper {
private final ComboBoxWithWidePopup<Module> myModuleCombo = new ComboBoxWithWidePopup<>();
private final ComboBoxWithWidePopup<PsiDirectory> myRootDirCombo = new ComboBoxWithWidePopup<>();
private final TemplateKindCombo myKindCombo = new TemplateKindCombo();
@@ -156,12 +156,7 @@ public class CreateServiceInterfaceOrClassFix extends CreateServiceClassFixBase
}
});
myRootDirCombo.setRenderer(new ListCellRendererWrapper<PsiDirectory>() {
@Override
public void customize(JList list, PsiDirectory psiDir, int index, boolean selected, boolean hasFocus) {
setText(psiDir != null ? psiDir.getVirtualFile().getPresentableUrl() : "");
}
});
myRootDirCombo.setRenderer(new PsiDirectoryListCellRenderer());
myModuleCombo.addActionListener(e -> updateRootDirsCombo(psiRootDirs));
Module[] modules = psiRootDirs.keySet().toArray(Module.EMPTY_ARRAY);
Arrays.sort(modules, Comparator.comparing(Module::getName));
@@ -197,7 +192,10 @@ public class CreateServiceInterfaceOrClassFix extends CreateServiceClassFixBase
@Nullable
@Override
protected JComponent createNorthPanel() {
JTextField nameTextField = new JTextField(myInterfaceName);
nameTextField.setEditable(false);
return JBPanelFactory.grid()
.add(JBPanelFactory.panel(nameTextField).withLabel("Name:"))
.add(JBPanelFactory.panel(myModuleCombo).withLabel("Module:"))
.add(JBPanelFactory.panel(myRootDirCombo).withLabel("Source root:"))
.add(JBPanelFactory.panel(myKindCombo).withLabel("Kind:"))

View File

@@ -0,0 +1,199 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
package com.intellij.codeInsight.intention.impl;
import com.intellij.codeInsight.daemon.impl.quickfix.CreateClassKind;
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageUtils;
import com.intellij.codeInsight.daemon.impl.quickfix.CreateServiceClassFixBase;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.ide.actions.TemplateKindCombo;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.JavaProjectRootsUtil;
import com.intellij.openapi.ui.ComboBoxWithWidePopup;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.openapi.ui.panel.JBPanelFactory;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.ui.components.JBTextField;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PlatformIcons;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
/**
* @author Pavel.Dolgov
*/
public class CreateClassInPackageInModuleFix implements IntentionAction {
private String myModuleName;
private String myPackageName;
public CreateClassInPackageInModuleFix(String moduleName, String packageName) {
myModuleName = moduleName;
myPackageName = packageName;
}
@Nls
@NotNull
@Override
public String getText() {
return "Create a class in '" + myPackageName + "'";
}
@Nls
@NotNull
@Override
public String getFamilyName() {
return "Create a class in package";
}
@Override
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
return ModuleManager.getInstance(project).findModuleByName(myModuleName) != null;
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
Module module = ModuleManager.getInstance(project).findModuleByName(myModuleName);
if (module != null) {
List<VirtualFile> roots = new ArrayList<>();
JavaProjectRootsUtil.collectSuitableDestinationSourceRoots(module, roots);
PsiManager psiManager = PsiManager.getInstance(project);
PsiDirectory[] rootDirs = roots.stream()
.sorted(Comparator.comparing(VirtualFile::getPresentableUrl))
.map(psiManager::findDirectory)
.filter(Objects::nonNull)
.toArray(PsiDirectory[]::new);
if (rootDirs.length != 0) {
CreateClassInPackageDialog dialog = new CreateClassInPackageDialog(project, rootDirs);
if (dialog.showAndGet()) {
PsiClass psiClass = WriteAction.compute(() -> {
PsiDirectory rootDir = dialog.getRootDir();
if (rootDir != null) {
PsiDirectory psiPackageDir = CreateServiceClassFixBase.getOrCreatePackageDirInRoot(myPackageName, rootDir);
if (psiPackageDir != null) {
return CreateFromUsageUtils.createClass(dialog.getKind(), psiPackageDir, dialog.getName(), psiManager, file, null, null);
}
}
return null;
});
CreateServiceClassFixBase.positionCursor(psiClass);
}
}
}
}
@Override
public boolean startInWriteAction() {
return false;
}
@Nullable
public static IntentionAction createFix(@NotNull Module module, @Nullable String packageName) {
return StringUtil.isNotEmpty(packageName) ? new CreateClassInPackageInModuleFix(module.getName(), packageName) : null;
}
private class CreateClassInPackageDialog extends DialogWrapper {
private final JBTextField myNameTextField = new JBTextField();
private final ComboBoxWithWidePopup<PsiDirectory> myRootDirCombo = new ComboBoxWithWidePopup<>();
private final TemplateKindCombo myKindCombo = new TemplateKindCombo();
@Nullable private Project myProject;
protected CreateClassInPackageDialog(@Nullable Project project, @NotNull PsiDirectory[] rootDirs) {
super(project);
myProject = project;
setTitle("Create Class in Package");
myRootDirCombo.setRenderer(new CreateServiceClassFixBase.PsiDirectoryListCellRenderer());
myRootDirCombo.setModel(new DefaultComboBoxModel<>(rootDirs));
for (CreateClassKind kind : CreateClassKind.values()) {
myKindCombo.addItem(CommonRefactoringUtil.capitalize(kind.getDescription()), getKindIcon(kind), kind.name());
}
init();
}
@NotNull
@Override
protected Action[] createActions() {
return new Action[]{getOKAction(), getCancelAction()};
}
@Override
protected JComponent createCenterPanel() {
return null;
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return myNameTextField;
}
@Nullable
@Override
protected JComponent createNorthPanel() {
return JBPanelFactory.grid()
.add(JBPanelFactory.panel(myNameTextField).withLabel("Name:")
.withComment("The class will be created in the package '" + myPackageName + "'"))
.add(JBPanelFactory.panel(myRootDirCombo).withLabel("Source root:"))
.add(JBPanelFactory.panel(myKindCombo).withLabel("Kind:"))
.createPanel();
}
@Nullable
@Override
protected ValidationInfo doValidate() {
String name = getName();
PsiDirectory rootDir = getRootDir();
LanguageLevel level = rootDir != null ? PsiUtil.getLanguageLevel(rootDir) : LanguageLevel.HIGHEST;
if (PsiNameHelper.getInstance(myProject).isIdentifier(name, level)) {
return null;
}
return new ValidationInfo("This is not a valid Java class name", myNameTextField);
}
@NotNull
public String getName() {
return myNameTextField.getText().trim();
}
@Nullable
public PsiDirectory getRootDir() {
return (PsiDirectory)myRootDirCombo.getSelectedItem();
}
public CreateClassKind getKind() {
return CreateClassKind.valueOf(myKindCombo.getSelectedName());
}
private Icon getKindIcon(@NotNull CreateClassKind kind) {
switch (kind) {
case CLASS: return PlatformIcons.CLASS_ICON;
case INTERFACE: return PlatformIcons.INTERFACE_ICON;
case ENUM: return PlatformIcons.ENUM_ICON;
case ANNOTATION: return PlatformIcons.ANNOTATION_TYPE_ICON;
}
return null;
}
}
}

View File

@@ -16,6 +16,7 @@ import com.intellij.codeInsight.daemon.quickFix.CreateFieldOrPropertyFix;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.IntentionManager;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.codeInsight.intention.impl.CreateClassInPackageInModuleFix;
import com.intellij.codeInsight.intention.impl.ReplaceAssignmentWithComparisonFix;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspectionBase;
@@ -896,4 +897,10 @@ public class QuickFixFactoryImpl extends QuickFixFactory {
public IntentionAction createDeleteSideEffectAwareFix(@NotNull PsiExpressionStatement statement) {
return new DeleteSideEffectsAwareFix(statement, statement.getExpression());
}
@Nullable
@Override
public IntentionAction createCreateClassInPackageInModuleFix(@NotNull Module module, @Nullable String packageName) {
return CreateClassInPackageInModuleFix.createFix(module, packageName);
}
}