Commit b2a7e273 authored by Pavel Dolgov's avatar Pavel Dolgov
Browse files

Java: UI for choosing source root when creating unresolved class for Java 9 service (IDEA-183452)

parent 7958911f
Showing with 272 additions and 56 deletions
+272 -56
......@@ -5,8 +5,12 @@ package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.CodeInsightUtil;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.JavaProjectRootsUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiUtil;
......@@ -14,12 +18,15 @@ import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.StringTokenizer;
import java.util.*;
import static com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageUtils.scheduleFileOrPackageCreationFailedMessageBox;
/**
* @author Pavel.Dolgov
*/
public abstract class CreateServiceClassFixBase implements IntentionAction {
private static final Logger LOG = Logger.getInstance(CreateServiceClassFixBase.class);
@Override
public boolean startInWriteAction() {
......@@ -68,7 +75,10 @@ public abstract class CreateServiceClassFixBase implements IntentionAction {
}
@Nullable
protected static PsiClass createClassInRootImpl(@NotNull String classFQN, @NotNull PsiDirectory rootDir, @Nullable String superClassName) {
protected static PsiClass createClassInRoot(@NotNull CreateClassKind classKind,
@NotNull String classFQN,
@NotNull PsiDirectory rootDir,
@Nullable String superClassName) {
PsiDirectory directory = rootDir;
String lastName;
StringTokenizer st = new StringTokenizer(classFQN, ".");
......@@ -82,20 +92,52 @@ public abstract class CreateServiceClassFixBase implements IntentionAction {
directory = directory.createSubdirectory(lastName);
}
catch (IncorrectOperationException e) {
CreateFromUsageUtils.scheduleFileOrPackageCreationFailedMessageBox(e, lastName, directory, true);
scheduleFileOrPackageCreationFailedMessageBox(e, lastName, directory, true);
return null;
}
}
}
PsiClass psiClass = JavaDirectoryService.getInstance().createClass(directory, lastName);
PsiUtil.setModifierProperty(psiClass, PsiModifier.PUBLIC, true);
if (superClassName != null) {
CreateFromUsageUtils.setupSuperClassReference(psiClass, superClassName);
PsiClass psiClass = createClass(classKind, lastName, directory);
if (psiClass != null) {
PsiUtil.setModifierProperty(psiClass, PsiModifier.PUBLIC, true);
if (superClassName != null) {
CreateFromUsageUtils.setupSuperClassReference(psiClass, superClassName);
}
}
return psiClass;
}
@Nullable
private static PsiClass createClass(@NotNull CreateClassKind classKind, String name, PsiDirectory directory) {
try {
switch (classKind) {
case CLASS:
return JavaDirectoryService.getInstance().createClass(directory, name);
case INTERFACE:
return JavaDirectoryService.getInstance().createInterface(directory, name);
default:
LOG.error("Unsupported kind of service class: " + classKind);
}
}
catch (final IncorrectOperationException e) {
scheduleFileOrPackageCreationFailedMessageBox(e, name, directory, false);
}
return null;
}
protected static PsiDirectory[] getModuleRootDirs(Module module) {
List<VirtualFile> roots = new ArrayList<>();
JavaProjectRootsUtil.collectSuitableDestinationSourceRoots(module, roots);
PsiManager psiManager = PsiManager.getInstance(module.getProject());
return roots.stream()
.map(psiManager::findDirectory)
.filter(Objects::nonNull)
.sorted(Comparator.comparing(psiDir -> psiDir.getVirtualFile().getPresentableUrl()))
.toArray(PsiDirectory[]::new);
}
protected static void positionCursor(@Nullable PsiClass psiClass) {
if (psiClass != null) {
CodeInsightUtil.positionCursor(psiClass.getProject(), psiClass.getContainingFile(), psiClass);
......
......@@ -11,21 +11,23 @@ import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.module.impl.scopes.ModulesScope;
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.panel.JBPanelFactory;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
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;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import javax.swing.*;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* @author Pavel.Dolgov
......@@ -48,31 +50,25 @@ public class CreateServiceImplementationClassFix extends CreateServiceClassFixBa
if (providesStatement != null && providesStatement.getImplementationList() == parent) {
myImplementationClassName = referenceElement.getQualifiedName();
if (myImplementationClassName != null) {
mySuperClassName = getSuperClassName(providesStatement);
if (mySuperClassName != null) {
Module module = ModuleUtilCore.findModuleForFile(referenceElement.getContainingFile());
myModuleName = module != null ? module.getName() : null;
PsiJavaCodeReferenceElement interfaceReference = providesStatement.getInterfaceReference();
if (interfaceReference != null) {
PsiClass superClass = ObjectUtils.tryCast(interfaceReference.resolve(), PsiClass.class);
if (superClass != null) {
mySuperClassName = superClass.getQualifiedName();
if (mySuperClassName != null) {
myModuleName = Optional.of(referenceElement)
.map(PsiElement::getContainingFile)
.map(ModuleUtilCore::findModuleForFile)
.map(Module::getName)
.orElse(null);
}
}
}
}
}
}
}
@Nullable
private static String getSuperClassName(@NotNull PsiProvidesStatement providesStatement) {
PsiJavaCodeReferenceElement interfaceReference = providesStatement.getInterfaceReference();
if (interfaceReference != null) {
if (interfaceReference.isQualified()) {
return interfaceReference.getQualifiedName();
}
PsiClass superClass = ObjectUtils.tryCast(interfaceReference.resolve(), PsiClass.class);
if (superClass != null) {
return superClass.getQualifiedName();
}
}
return null;
}
@Nls
@NotNull
@Override
......@@ -112,15 +108,30 @@ public class CreateServiceImplementationClassFix extends CreateServiceClassFixBa
}
}
List<VirtualFile> roots = new ArrayList<>();
JavaProjectRootsUtil.collectSuitableDestinationSourceRoots(module, roots);
PsiManager psiManager = file.getManager();
roots.stream() // todo UI for choosing source root similar to AddModuleDependencyFix
.map(psiManager::findDirectory)
.filter(Objects::nonNull)
.findAny()
.ifPresent(this::createClassInRoot);
PsiDirectory[] psiRootDirs = getModuleRootDirs(module);
CreateServiceImplementationDialog dialog = new CreateServiceImplementationDialog(project, psiRootDirs, mySuperClassName);
if (dialog.showAndGet()) {
PsiDirectory psiRootDir = dialog.getRootDir();
if (psiRootDir != null) {
boolean isSubclass = dialog.isSubclass();
PsiClass psiClass = WriteAction.compute(() -> createClassInRoot(psiRootDir, isSubclass));
positionCursor(psiClass);
}
}
}
}
private PsiClass createClassInRoot(@NotNull PsiDirectory psiRootDir, boolean isSubclass) {
Project project = psiRootDir.getProject();
PsiClass psiImplClass = createClassInRoot(CreateClassKind.CLASS, myImplementationClassName,
psiRootDir, isSubclass ? mySuperClassName : null);
if (psiImplClass != null && !isSubclass) {
String text = "public static " + mySuperClassName + " provider() { return null;}";
PsiMethod method = JavaPsiFacade.getElementFactory(project).createMethodFromText(text, psiImplClass.getLBrace());
psiImplClass.addAfter(method, psiImplClass.getLBrace());
}
return psiImplClass;
}
@Nullable
......@@ -136,8 +147,68 @@ public class CreateServiceImplementationClassFix extends CreateServiceClassFixBa
positionCursor(psiClass);
}
private void createClassInRoot(PsiDirectory rootDir) {
PsiClass psiClass = WriteAction.compute(() -> createClassInRootImpl(myImplementationClassName, rootDir, mySuperClassName));
positionCursor(psiClass);
private static class CreateServiceImplementationDialog extends DialogWrapper {
private final ComboBoxWithWidePopup<PsiDirectory> myRootDirCombo = new ComboBoxWithWidePopup<>();
private final JRadioButton mySubclassButton = new JBRadioButton();
private final JRadioButton myProviderButton = new JBRadioButton();
protected CreateServiceImplementationDialog(@Nullable Project project,
@NotNull PsiDirectory[] psiRootDirs,
@NotNull String superClassName) {
super(project);
setTitle("Create Service Implementation");
mySubclassButton.setText("Subclass of '" + superClassName + "'");
mySubclassButton.setSelected(true);
myProviderButton.setText("With 'provider()' method");
ButtonGroup group = new ButtonGroup();
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.setModel(new DefaultComboBoxModel<>(psiRootDirs));
init();
}
@NotNull
@Override
protected Action[] createActions() {
return new Action[]{getOKAction(), getCancelAction()};
}
@Override
protected JComponent createCenterPanel() {
return null;
}
@Nullable
@Override
protected JComponent createNorthPanel() {
JPanel radioButtons = JBPanelFactory.grid()
.add(JBPanelFactory.panel(mySubclassButton))
.add(JBPanelFactory.panel(myProviderButton))
.createPanel();
return JBPanelFactory.grid()
.add(JBPanelFactory.panel(radioButtons).withLabel("Implementation:"))
.add(JBPanelFactory.panel(myRootDirCombo).withLabel("Source root:"))
.createPanel();
}
@Nullable
public PsiDirectory getRootDir() {
return (PsiDirectory)myRootDirCombo.getSelectedItem();
}
public boolean isSubclass() {
return mySubclassButton.isSelected();
}
}
}
......@@ -4,19 +4,32 @@
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.QuickFixBundle;
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.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.ui.ComboBoxWithWidePopup;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.panel.JBPanelFactory;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.ui.ListCellRendererWrapper;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.PlatformIcons;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Arrays;
import java.util.Objects;
import java.util.Comparator;
import java.util.Map;
/**
* @author Pavel.Dolgov
......@@ -82,27 +95,117 @@ public class CreateServiceInterfaceOrClassFix extends CreateServiceClassFixBase
while (psiPackage == null && !StringUtil.isEmpty(qualifierText));
if (psiPackage != null) {
ProjectFileIndex index = ProjectFileIndex.SERVICE.getInstance(project);
PsiManager psiManager = PsiManager.getInstance(project);
PsiDirectory[] directories = psiPackage.getDirectories();
Arrays.stream(directories) // todo UI for choosing source root similar to AddModuleDependencyFix
.map(directory -> index.getSourceRootForFile(directory.getVirtualFile()))
.filter(Objects::nonNull)
.map(psiManager::findDirectory)
.filter(Objects::nonNull)
.findAny()
.ifPresent(this::createClassInRoot);
Map<Module, PsiDirectory[]> psiRootDirs = getModuleRootDirs(psiPackage);
if (!psiRootDirs.isEmpty()) {
CreateServiceInterfaceDialog dialog = new CreateServiceInterfaceDialog(project, psiRootDirs);
if (dialog.showAndGet()) {
PsiClass psiClass =
WriteAction.compute(() -> {
PsiDirectory rootDir = dialog.getRootDir();
if (rootDir != null) {
return createClassInRoot(dialog.getClassKind(), myInterfaceName, rootDir, null);
}
return null;
});
positionCursor(psiClass);
}
}
}
}
@NotNull
private static Map<Module, PsiDirectory[]> getModuleRootDirs(@NotNull PsiPackage psiPackage) {
ProjectFileIndex index = ProjectFileIndex.SERVICE.getInstance(psiPackage.getProject());
return StreamEx.of(psiPackage.getDirectories())
.map(PsiDirectory::getVirtualFile)
.map(index::getSourceRootForFile)
.nonNull()
.map(index::getModuleForFile)
.nonNull()
.distinct()
.mapToEntry(CreateServiceClassFixBase::getModuleRootDirs)
.filterValues(dirs -> !ArrayUtil.isEmpty(dirs))
.toMap();
}
private void createClassInOuter(@NotNull String qualifierText, @NotNull PsiClass outerClass) {
String name = myInterfaceName.substring(qualifierText.length() + 1);
PsiClass psiClass = WriteAction.compute(() -> createClassInOuterImpl(name, outerClass, null));
positionCursor(psiClass);
}
private void createClassInRoot(@NotNull PsiDirectory rootDir) {
PsiClass psiClass = WriteAction.compute(() -> createClassInRootImpl(myInterfaceName, rootDir, null));
positionCursor(psiClass);
private static class CreateServiceInterfaceDialog extends DialogWrapper {
private final ComboBoxWithWidePopup<Module> myModuleCombo = new ComboBoxWithWidePopup<>();
private final ComboBoxWithWidePopup<PsiDirectory> myRootDirCombo = new ComboBoxWithWidePopup<>();
private final TemplateKindCombo myKindCombo = new TemplateKindCombo();
protected CreateServiceInterfaceDialog(@Nullable Project project, @NotNull Map<Module, PsiDirectory[]> psiRootDirs) {
super(project);
setTitle("Create Service Interface or Class");
myModuleCombo.setRenderer(new ListCellRendererWrapper<Module>() {
@Override
public void customize(JList list, Module module, int index, boolean selected, boolean hasFocus) {
setText(module.getName());
}
});
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() : "");
}
});
myModuleCombo.addActionListener(e -> updateRootDirsCombo(psiRootDirs));
Module[] modules = psiRootDirs.keySet().toArray(Module.EMPTY_ARRAY);
Arrays.sort(modules, Comparator.comparing(Module::getName));
myModuleCombo.setModel(new DefaultComboBoxModel<>(modules));
updateRootDirsCombo(psiRootDirs);
myKindCombo.addItem(CommonRefactoringUtil.capitalize(CreateClassKind.CLASS.getDescription()), PlatformIcons.CLASS_ICON,
CreateClassKind.CLASS.name());
myKindCombo.addItem(CommonRefactoringUtil.capitalize(CreateClassKind.INTERFACE.getDescription()), PlatformIcons.INTERFACE_ICON,
CreateClassKind.INTERFACE.name());
init();
}
private void updateRootDirsCombo(@NotNull Map<Module, PsiDirectory[]> psiRootDirs) {
Module module = (Module)myModuleCombo.getSelectedItem();
PsiDirectory[] moduleRootDirs = psiRootDirs.getOrDefault(module, PsiDirectory.EMPTY_ARRAY);
myRootDirCombo.setModel(new DefaultComboBoxModel<>(moduleRootDirs));
}
@NotNull
@Override
protected Action[] createActions() {
return new Action[]{getOKAction(), getCancelAction()};
}
@Override
protected JComponent createCenterPanel() {
return null;
}
@Nullable
@Override
protected JComponent createNorthPanel() {
return JBPanelFactory.grid()
.add(JBPanelFactory.panel(myModuleCombo).withLabel("Module:"))
.add(JBPanelFactory.panel(myRootDirCombo).withLabel("Source root:"))
.add(JBPanelFactory.panel(myKindCombo).withLabel("Kind:"))
.createPanel();
}
@Nullable
public PsiDirectory getRootDir() {
return (PsiDirectory)myRootDirCombo.getSelectedItem();
}
@NotNull
public CreateClassKind getClassKind() {
return CreateClassKind.valueOf(myKindCombo.getSelectedName());
}
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment