diff --git a/java/idea-ui/src/com/intellij/ide/util/importProject/ProjectLayoutPanel.java b/java/idea-ui/src/com/intellij/ide/util/importProject/ProjectLayoutPanel.java index 34cd2e3b6f8a..01edd36d772b 100644 --- a/java/idea-ui/src/com/intellij/ide/util/importProject/ProjectLayoutPanel.java +++ b/java/idea-ui/src/com/intellij/ide/util/importProject/ProjectLayoutPanel.java @@ -459,9 +459,6 @@ abstract class ProjectLayoutPanel extends JPanel { public Icon getIcon() { return getElementIcon(file); } - public Color getColor() { - return null; - } }); } myChooser.selectElements(ContainerUtil.createMaybeSingletonList(ContainerUtil.getFirstItem(files))); @@ -527,9 +524,5 @@ abstract class ProjectLayoutPanel extends JPanel { public Icon getIcon() { return getElementIcon(myEntry); } - - public Color getColor() { - return null; - } } } diff --git a/java/java-impl/src/com/intellij/ide/actions/JavaCreateTemplateInPackageAction.java b/java/java-impl/src/com/intellij/ide/actions/JavaCreateTemplateInPackageAction.java index 1859532ea8d7..acfd3507dafa 100644 --- a/java/java-impl/src/com/intellij/ide/actions/JavaCreateTemplateInPackageAction.java +++ b/java/java-impl/src/com/intellij/ide/actions/JavaCreateTemplateInPackageAction.java @@ -38,6 +38,10 @@ public abstract class JavaCreateTemplateInPackageAction ex @Override protected boolean checkPackageExists(PsiDirectory directory) { + return doCheckPackageExists(directory); + } + + public static boolean doCheckPackageExists(PsiDirectory directory) { PsiPackage pkg = JavaDirectoryService.getInstance().getPackage(directory); if (pkg == null) { return false; diff --git a/platform/configuration-store-impl/src/ChooseComponentsToExportDialog.java b/platform/configuration-store-impl/src/ChooseComponentsToExportDialog.java index 221ee9942929..49d9d2c0e29c 100644 --- a/platform/configuration-store-impl/src/ChooseComponentsToExportDialog.java +++ b/platform/configuration-store-impl/src/ChooseComponentsToExportDialog.java @@ -237,18 +237,6 @@ public class ChooseComponentsToExportDialog extends DialogWrapper { private static class ComponentElementProperties implements ElementsChooser.ElementProperties { private final Set items = new THashSet<>(); - @Override - @Nullable - public Icon getIcon() { - return null; - } - - @Override - @Nullable - public Color getColor() { - return null; - } - public String toString() { Set names = new LinkedHashSet<>(); for (ExportableItem component : items) { diff --git a/platform/lang-api/src/com/intellij/ide/actions/ElementCreator.java b/platform/lang-api/src/com/intellij/ide/actions/ElementCreator.java index 5d1ac159d889..995dcbf0b838 100644 --- a/platform/lang-api/src/com/intellij/ide/actions/ElementCreator.java +++ b/platform/lang-api/src/com/intellij/ide/actions/ElementCreator.java @@ -99,10 +99,15 @@ public abstract class ElementCreator implements WriteActionAware { private void handleException(Exception t) { LOG.info(t); + String errorMessage = getErrorMessage(t); + Messages.showMessageDialog(myProject, errorMessage, myErrorTitle, Messages.getErrorIcon()); + } + + public static String getErrorMessage(Throwable t) { String errorMessage = CreateElementActionBase.filterMessage(t.getMessage()); if (errorMessage == null || errorMessage.length() == 0) { errorMessage = t.toString(); } - Messages.showMessageDialog(myProject, errorMessage, myErrorTitle, Messages.getErrorIcon()); + return errorMessage; } } diff --git a/platform/lang-impl/src/com/intellij/ide/actions/CreateTemplateInPackageAction.java b/platform/lang-impl/src/com/intellij/ide/actions/CreateTemplateInPackageAction.java index 2d32ca5521ec..32b68252ceec 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/CreateTemplateInPackageAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/CreateTemplateInPackageAction.java @@ -34,6 +34,7 @@ import org.jetbrains.jps.model.module.JpsModuleSourceRootType; import javax.swing.*; import java.util.Set; +import java.util.function.Function; /** * @author peter @@ -58,19 +59,24 @@ public abstract class CreateTemplateInPackageAction extend @Override protected boolean isAvailable(final DataContext dataContext) { + return isAvailable(dataContext, mySourceRootTypes, this::checkPackageExists); + } + + public static boolean isAvailable(DataContext dataContext, Set> sourceRootTypes, + Function checkPackageExists) { final Project project = CommonDataKeys.PROJECT.getData(dataContext); final IdeView view = LangDataKeys.IDE_VIEW.getData(dataContext); if (project == null || view == null || view.getDirectories().length == 0) { return false; } - if (mySourceRootTypes == null) { + if (sourceRootTypes == null) { return true; } ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex(); for (PsiDirectory dir : view.getDirectories()) { - if (projectFileIndex.isUnderSourceRootOfType(dir.getVirtualFile(), mySourceRootTypes) && checkPackageExists(dir)) { + if (projectFileIndex.isUnderSourceRootOfType(dir.getVirtualFile(), sourceRootTypes) && checkPackageExists.apply(dir)) { return true; } } diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/ChooseModulesDialog.java b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/ChooseModulesDialog.java index dbb09dbc643b..fd1945f97732 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/ChooseModulesDialog.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/ChooseModulesDialog.java @@ -36,7 +36,7 @@ public class ChooseModulesDialog extends ChooseElementsDialog { super(parent, items, title, description, true); } - public ChooseModulesDialog(final Project project, final List items, final String title, final String description) { + public ChooseModulesDialog(Project project, List items, String title, @Nullable String description) { super(project, items, title, description, true); } diff --git a/platform/platform-impl/src/com/intellij/ide/util/ChooseElementsDialog.java b/platform/platform-impl/src/com/intellij/ide/util/ChooseElementsDialog.java index 3e00db61cd28..8b798f7f6801 100644 --- a/platform/platform-impl/src/com/intellij/ide/util/ChooseElementsDialog.java +++ b/platform/platform-impl/src/com/intellij/ide/util/ChooseElementsDialog.java @@ -100,6 +100,14 @@ public abstract class ChooseElementsDialog extends DialogWrapper { @Nullable protected abstract Icon getItemIcon(T item); + /** + * Override this method and return non-null value to specify location of {@code item}. + * It will be shown as grayed text next to the {@link #getItemText(T) item text}. + */ + protected String getItemLocation(T item) { + return null; // default implementation + } + @NotNull public List getChosenElements() { return isOK() ? myChooser.getSelectedElements() : Collections.emptyList(); @@ -132,12 +140,16 @@ public abstract class ChooseElementsDialog extends DialogWrapper { private ElementsChooser.ElementProperties createElementProperties(final T item) { return new ElementsChooser.ElementProperties() { + @Override + @Nullable public Icon getIcon() { return getItemIcon(item); } - public Color getColor() { - return null; + @Override + @Nullable + public String getLocation() { + return getItemLocation(item); } }; } diff --git a/platform/platform-impl/src/com/intellij/ide/util/MultiStateElementsChooser.java b/platform/platform-impl/src/com/intellij/ide/util/MultiStateElementsChooser.java index 0d8de2e29548..83d8885c9b47 100644 --- a/platform/platform-impl/src/com/intellij/ide/util/MultiStateElementsChooser.java +++ b/platform/platform-impl/src/com/intellij/ide/util/MultiStateElementsChooser.java @@ -15,6 +15,7 @@ */ package com.intellij.ide.util; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.ui.*; import com.intellij.ui.table.JBTable; @@ -317,9 +318,17 @@ public class MultiStateElementsChooser extends JPanel implements Component public interface ElementProperties { @Nullable - Icon getIcon(); + default Icon getIcon() { + return null; + } @Nullable - Color getColor(); + default Color getColor() { + return null; + } + @Nullable + default String getLocation() { + return null; + } } public void addElement(T element, final S markState, ElementProperties elementProperties) { @@ -643,37 +652,36 @@ public class MultiStateElementsChooser extends JPanel implements Component return null; } - private class MyElementColumnCellRenderer extends DefaultTableCellRenderer { + + private class MyElementColumnCellRenderer extends ColoredTableCellRenderer { @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - final Color color = UIUtil.getTableFocusCellBackground(); - Component component; - T t = (T)value; - try { - UIManager.put(UIUtil.TABLE_FOCUS_CELL_BACKGROUND_PROPERTY, table.getSelectionBackground()); - component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - setText(t != null ? getItemText(t) : ""); - if (component instanceof JLabel) { - ((JLabel)component).setBorder(noFocusBorder); + protected void customizeCellRenderer(JTable table, @Nullable Object value, boolean selected, boolean hasFocus, int row, int column) { + @SuppressWarnings("unchecked") T item = (T)value; + String text = item == null ? "" : getItemText(item); + append(text); + + ElementProperties properties = myElementToPropertiesMap.get(item); + + if (properties != null) { + String location = properties.getLocation(); + if (StringUtil.isNotEmpty(location)) { + append(" (" + location + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES); } } - finally { - UIManager.put(UIUtil.TABLE_FOCUS_CELL_BACKGROUND_PROPERTY, color); + + setTransparentIconBackground(true); + Icon icon = properties != null ? properties.getIcon() : item != null ? getItemIcon(item) : null; + if (icon != null) { + setIcon(icon); } - final MyTableModel model = (MyTableModel)table.getModel(); - component.setEnabled(isSelected || (MultiStateElementsChooser.this.isEnabled() && - (!myColorUnmarkedElements || myMarkStateDescriptor.isMarked(model.getElementMarkState(row))))); - final ElementProperties properties = myElementToPropertiesMap.get(t); - if (component instanceof JLabel) { - final Icon icon = properties != null ? properties.getIcon() : t != null ? getItemIcon(t) : null; - JLabel label = (JLabel)component; - label.setIcon(icon); - label.setDisabledIcon(icon); - } - component.setForeground(properties != null && properties.getColor() != null ? - properties.getColor() : - isSelected ? table.getSelectionForeground() : table.getForeground()); - return component; + + setForeground(properties != null && properties.getColor() != null ? + properties.getColor() : + selected ? table.getSelectionForeground() : table.getForeground()); + + @SuppressWarnings("unchecked") MyTableModel model = (MyTableModel)table.getModel(); + setEnabled(selected || (MultiStateElementsChooser.this.isEnabled() && + (!myColorUnmarkedElements || myMarkStateDescriptor.isMarked(model.getElementMarkState(row))))); } } diff --git a/plugins/devkit/resources/META-INF/plugin.xml b/plugins/devkit/resources/META-INF/plugin.xml index 051b4be3f2dd..4c4d00c1c798 100644 --- a/plugins/devkit/resources/META-INF/plugin.xml +++ b/plugins/devkit/resources/META-INF/plugin.xml @@ -228,13 +228,12 @@ class="com.intellij.ide.actions.NonTrivialActionGroup"> - - - - + + + diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ApplicationComponent.java.ft b/plugins/devkit/resources/fileTemplates/j2ee/ApplicationComponent.java.ft deleted file mode 100644 index 55c8d555e965..000000000000 --- a/plugins/devkit/resources/fileTemplates/j2ee/ApplicationComponent.java.ft +++ /dev/null @@ -1,26 +0,0 @@ -package ${PACKAGE_NAME}; - -import com.intellij.openapi.components.ApplicationComponent; -import org.jetbrains.annotations.NotNull; - -#parse("File Header.java") -public class ${NAME} implements ApplicationComponent { - public ${NAME}() { - } - - @Override - public void initComponent() { - // TODO: insert component initialization logic here - } - - @Override - public void disposeComponent() { - // TODO: insert component disposal logic here - } - - @Override - @NotNull - public String getComponentName() { - return "${NAME}"; - } -} diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceClass.java.ft b/plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceClass.java.ft new file mode 100644 index 000000000000..072ea24ba8d6 --- /dev/null +++ b/plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceClass.java.ft @@ -0,0 +1,10 @@ +package ${PACKAGE_NAME}; + +import com.intellij.openapi.components.ServiceManager; + +#parse("File Header.java") +public class ${NAME} { + public static ${NAME} getInstance() { + return ServiceManager.getService(${NAME}.class); + } +} diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceClass.java.html b/plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceClass.java.html new file mode 100644 index 000000000000..ffad651eb417 --- /dev/null +++ b/plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceClass.java.html @@ -0,0 +1,11 @@ + + + + + + +
This is a built-in template used each time you create + a new IntelliJ Platform application-level service class (without a separated service interface). +
+ + \ No newline at end of file diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceImplementation.java.ft b/plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceImplementation.java.ft new file mode 100644 index 000000000000..1f643380176d --- /dev/null +++ b/plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceImplementation.java.ft @@ -0,0 +1,8 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end +#if(${INTERFACE_PACKAGE_NAME} && ${INTERFACE_PACKAGE_NAME} != "") +import ${INTERFACE_PACKAGE_NAME}.${INTERFACE_NAME};#end + +#parse("File Header.java") +public class ${NAME} implements ${INTERFACE_NAME} { + +} diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceImplementation.java.html b/plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceImplementation.java.html new file mode 100644 index 000000000000..9619304526c7 --- /dev/null +++ b/plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceImplementation.java.html @@ -0,0 +1,26 @@ + + + + + + +
This is a built-in template used each time you create + a new IntelliJ Platform application-level service interface implementation. +
+ + + + + + + + + + + + + + +
Predefined variables will take the following values:
${INTERFACE_NAME} service interface short name.
${INTERFACE_PACKAGE_NAME} service interface package or an empty string if interface is placed in the same package as implementation.
+ + \ No newline at end of file diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceInterface.java.ft b/plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceInterface.java.ft new file mode 100644 index 000000000000..1f215b44299b --- /dev/null +++ b/plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceInterface.java.ft @@ -0,0 +1,10 @@ +package ${PACKAGE_NAME}; + +import com.intellij.openapi.components.ServiceManager; + +#parse("File Header.java") +public interface ${NAME} { + static ${NAME} getInstance() { + return ServiceManager.getService(${NAME}.class); + } +} diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ModuleComponent.java.html b/plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceInterface.java.html similarity index 78% rename from plugins/devkit/resources/fileTemplates/j2ee/ModuleComponent.java.html rename to plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceInterface.java.html index 851b80a35df1..99788adf1c40 100644 --- a/plugins/devkit/resources/fileTemplates/j2ee/ModuleComponent.java.html +++ b/plugins/devkit/resources/fileTemplates/j2ee/ApplicationServiceInterface.java.html @@ -3,7 +3,7 @@
This is a built-in template used each time you create - a new IntelliJ Platform module component. + a new IntelliJ Platform application-level service interface.
diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ModuleComponent.java.ft b/plugins/devkit/resources/fileTemplates/j2ee/ModuleComponent.java.ft deleted file mode 100644 index c82902992e11..000000000000 --- a/plugins/devkit/resources/fileTemplates/j2ee/ModuleComponent.java.ft +++ /dev/null @@ -1,33 +0,0 @@ -package ${PACKAGE_NAME}; - -import com.intellij.openapi.module.ModuleComponent; -import com.intellij.openapi.module.Module; -import org.jetbrains.annotations.NotNull; - -#parse("File Header.java") -public class ${NAME} implements ModuleComponent { - public ${NAME}(Module module) { - } - - @Override - public void initComponent() { - // TODO: insert component initialization logic here - } - - @Override - public void disposeComponent() { - // TODO: insert component disposal logic here - } - - @Override - @NotNull - public String getComponentName() { - return "${NAME}"; - } - - @Override - public void moduleAdded() { - // Invoked when the module corresponding to this component instance has been completely - // loaded and added to the project. - } -} diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceClass.java.ft b/plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceClass.java.ft new file mode 100644 index 000000000000..57a642afe7ff --- /dev/null +++ b/plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceClass.java.ft @@ -0,0 +1,15 @@ +package ${PACKAGE_NAME}; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleServiceManager; +import org.jetbrains.annotations.NotNull; + +#parse("File Header.java") +public class ${NAME} { + public ${NAME}(Module module) { + } + + public static ${NAME} getInstance(@NotNull Module module) { + return ModuleServiceManager.getService(module, ${NAME}.class); + } +} diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceClass.java.html b/plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceClass.java.html new file mode 100644 index 000000000000..b57d160f2c42 --- /dev/null +++ b/plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceClass.java.html @@ -0,0 +1,11 @@ + + + + + + +
This is a built-in template used each time you create + a new IntelliJ Platform module-level service class (without a separated service interface). +
+ + \ No newline at end of file diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceImplementation.java.ft b/plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceImplementation.java.ft new file mode 100644 index 000000000000..bbae6db44471 --- /dev/null +++ b/plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceImplementation.java.ft @@ -0,0 +1,10 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end +#if(${INTERFACE_PACKAGE_NAME} && ${INTERFACE_PACKAGE_NAME} != "") +import ${INTERFACE_PACKAGE_NAME}.${INTERFACE_NAME};#end +import com.intellij.openapi.module.Module; + +#parse("File Header.java") +public class ${NAME} implements ${INTERFACE_NAME} { + public ${NAME}(Module project) { + } +} diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceImplementation.java.html b/plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceImplementation.java.html new file mode 100644 index 000000000000..3c01a48f4f36 --- /dev/null +++ b/plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceImplementation.java.html @@ -0,0 +1,26 @@ + + + + + + +
This is a built-in template used each time you create + a new IntelliJ Platform module-level service interface implementation. +
+ + + + + + + + + + + + + + +
Predefined variables will take the following values:
${INTERFACE_NAME} service interface short name.
${INTERFACE_PACKAGE_NAME} service interface package or an empty string if interface is placed in the same package as implementation.
+ + \ No newline at end of file diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceInterface.java.ft b/plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceInterface.java.ft new file mode 100644 index 000000000000..2b702ec47610 --- /dev/null +++ b/plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceInterface.java.ft @@ -0,0 +1,12 @@ +package ${PACKAGE_NAME}; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleServiceManager; +import org.jetbrains.annotations.NotNull; + +#parse("File Header.java") +public interface ${NAME} { + static ${NAME} getInstance(@NotNull Module module) { + return ModuleServiceManager.getService(module, ${NAME}.class); + } +} diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ProjectComponent.java.html b/plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceInterface.java.html similarity index 79% rename from plugins/devkit/resources/fileTemplates/j2ee/ProjectComponent.java.html rename to plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceInterface.java.html index 4b1452f22fa9..8e656c2a8a8d 100644 --- a/plugins/devkit/resources/fileTemplates/j2ee/ProjectComponent.java.html +++ b/plugins/devkit/resources/fileTemplates/j2ee/ModuleServiceInterface.java.html @@ -3,7 +3,7 @@
This is a built-in template used each time you create - a new IntelliJ Platform project component. + a new IntelliJ Platform module-level service interface.
diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ProjectComponent.java.ft b/plugins/devkit/resources/fileTemplates/j2ee/ProjectComponent.java.ft deleted file mode 100644 index c894cbae60a7..000000000000 --- a/plugins/devkit/resources/fileTemplates/j2ee/ProjectComponent.java.ft +++ /dev/null @@ -1,37 +0,0 @@ -package ${PACKAGE_NAME}; - -import com.intellij.openapi.components.ProjectComponent; -import com.intellij.openapi.project.Project; -import org.jetbrains.annotations.NotNull; - -#parse("File Header.java") -public class ${NAME} implements ProjectComponent { - public ${NAME}(Project project) { - } - - @Override - public void initComponent() { - // TODO: insert component initialization logic here - } - - @Override - public void disposeComponent() { - // TODO: insert component disposal logic here - } - - @Override - @NotNull - public String getComponentName() { - return "${NAME}"; - } - - @Override - public void projectOpened() { - // called when project is opened - } - - @Override - public void projectClosed() { - // called when project is being closed - } -} diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceClass.java.ft b/plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceClass.java.ft new file mode 100644 index 000000000000..147e64e3ad62 --- /dev/null +++ b/plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceClass.java.ft @@ -0,0 +1,15 @@ +package ${PACKAGE_NAME}; + +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; + +#parse("File Header.java") +public class ${NAME} { + public ${NAME}(Project project) { + } + + public static ${NAME} getInstance(@NotNull Project project) { + return ServiceManager.getService(project, ${NAME}.class); + } +} diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceClass.java.html b/plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceClass.java.html new file mode 100644 index 000000000000..38247e5efaa1 --- /dev/null +++ b/plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceClass.java.html @@ -0,0 +1,11 @@ + + + + + + +
This is a built-in template used each time you create + a new IntelliJ Platform project-level service class (without a separated service interface). +
+ + \ No newline at end of file diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceImplementation.java.ft b/plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceImplementation.java.ft new file mode 100644 index 000000000000..29230c8fb895 --- /dev/null +++ b/plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceImplementation.java.ft @@ -0,0 +1,10 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end +#if(${INTERFACE_PACKAGE_NAME} && ${INTERFACE_PACKAGE_NAME} != "") +import ${INTERFACE_PACKAGE_NAME}.${INTERFACE_NAME};#end +import com.intellij.openapi.project.Project; + +#parse("File Header.java") +public class ${NAME} implements ${INTERFACE_NAME} { + public ${NAME}(Project project) { + } +} diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceImplementation.java.html b/plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceImplementation.java.html new file mode 100644 index 000000000000..afb242e07ed3 --- /dev/null +++ b/plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceImplementation.java.html @@ -0,0 +1,26 @@ + + + + + + +
This is a built-in template used each time you create + a new IntelliJ Platform project-level service interface implementation. +
+ + + + + + + + + + + + + + +
Predefined variables will take the following values:
${INTERFACE_NAME} service interface short name.
${INTERFACE_PACKAGE_NAME} service interface package or an empty string if interface is placed in the same package as implementation.
+ + \ No newline at end of file diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceInterface.java.ft b/plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceInterface.java.ft new file mode 100644 index 000000000000..9d945b83275d --- /dev/null +++ b/plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceInterface.java.ft @@ -0,0 +1,12 @@ +package ${PACKAGE_NAME}; + +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.project.Project; +import org.jetbrains.annotations.NotNull; + +#parse("File Header.java") +public interface ${NAME} { + static ${NAME} getInstance(@NotNull Project project) { + return ServiceManager.getService(project, ${NAME}.class); + } +} diff --git a/plugins/devkit/resources/fileTemplates/j2ee/ApplicationComponent.java.html b/plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceInterface.java.html similarity index 78% rename from plugins/devkit/resources/fileTemplates/j2ee/ApplicationComponent.java.html rename to plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceInterface.java.html index bdb4e6f18604..e9f5e2ac5f6f 100644 --- a/plugins/devkit/resources/fileTemplates/j2ee/ApplicationComponent.java.html +++ b/plugins/devkit/resources/fileTemplates/j2ee/ProjectServiceInterface.java.html @@ -3,7 +3,7 @@
This is a built-in template used each time you create - a new IntelliJ Platform application component. + a new IntelliJ Platform project-level service interface.
diff --git a/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties b/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties index 5ff700a51792..db4a9f666f5d 100644 --- a/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties +++ b/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties @@ -4,6 +4,9 @@ module.description=Plugin modules are used for developing plugins for Intelli They provide IntelliJ Platform Plugin SDK and Run Configuration for running and debugging plugins inside an IDE. plugin.descriptor=IntelliJ Platform Plugin Descriptor +# suppress inspection "UnusedProperty" +group.PluginDeployActions.text=Plugin Deployment Actions + #Module Editor - Deployment deployment.title=Plugin Deployment deployment.cleanup=Clean Up {0} Directory @@ -19,6 +22,10 @@ vm.parameters=&VM Options program.parameters=&Program Arguments #Prepare for deployment action +# suppress inspection "UnusedProperty" +action.MakeJarAction.text=Prepare To Deploy +# suppress inspection "UnusedProperty" +action.MakeAllJarsAction.text=Prepare All Plugins To Deploy select.plugin.modules.title=Select modules select.plugin.modules.description=Select modules to be prepared for deployment prepare.for.deployment.common=Preparing For Deployment @@ -42,30 +49,20 @@ error.occurred=Error Occurred sdk.no.specified=No SDK specified for plugin module ''{0}'' sdk.type.incorrect=Wrong SDK type for plugin module ''{0}''. sdk.type.incorrect.common=Wrong SDK type for plugin module -incorrect.dependency.non-plugin-module=The non-plugin module ''{0}'' cannot depend on plugin module ''{1}''. -incorrect.dependency.not-declared=The plugin module ''{0}'' doesn''t declare the dependency on ''{1}'' in its plugin.xml. error.file.not.found=File not Found error.file.not.found.message=File ''{0}'' not found error.no.plugin.xml=No plugin.xml file found -error.plugin.xml.readonly=The plugin.xml file is read-only +error.plugin.xml.readonly=Read-only plugin.xml file cannot be processed: {0} +error.cannot.process.plugin.xml=Cannot process plugin descriptor file: {0} +error.cannot.create.service.class=Cannot Create Service Class #run configurations -run.configuration.classpath.from.module.choose=Use Classpath and JDK from Module: run.configuration.no.module.specified=No plugin module specified for configuration run.configuration.title=Plugin run.configuration.type.description=Plugin Sandbox Environment -idea.log.tab.title=IDEA LOG #Misc info.message=Info -create.smth=Create {0} -show.smth=&Show {0} -presentable.plugin.module.name=Plugin Module ''{0}'' - -action.MakeJarAction.text=Prepare To Deploy -action.MakeAllJarsAction.text=Prepare All Plugins To Deploy - -dont.add.idea.libs.to.classpath=IDE-related libraries ({0}) must not be added to the module classpath. Please add them to the IntelliJ Platform Plugin SDK instead. new.action.id=&Action ID: new.action.description=&Description: new.action.class.name=&Class Name: @@ -84,35 +81,35 @@ new.action.keyboard.second=Second: new.action.keyboard.clear=X new.action.keyboard.clear.tooltip=Clear shortcut command.implement.externalizable=Implement Externalizable + new.menu.action.text=Action new.menu.action.description=Create New Action new.action.error=Cannot create action new.action.command=Create Action new.action.action.name=Creating new action: {0}.{1} new.action.dialog.title=New Action -new.menu.application.component.text=Application Component -new.menu.application.component.description=Create New Application Component -new.application.component.error=Cannot create application component -new.application.component.command=Create Application Component -new.application.component.prompt.title=New Application Component -new.application.component.prompt=Enter new application component name: -new.application.component.action.name=Creating new application component: {0}.{1} -new.menu.module.component.text=Module Component -new.menu.module.component.description=Create New Module Component -new.module.component.error=Cannot create module component -new.module.component.command=Create Module Component -new.module.component.prompt.title=New Module Component -new.module.component.prompt=Enter new module component name: -new.module.component.action.name=Creating new module component: {0}.{1} -new.menu.project.component.text=Project Component -new.menu.project.component.description=Create New Project Component -new.project.component.error=Cannot create project component -new.project.component.command=Create Project Component -new.project.component.prompt.title=New Project Component -new.project.component.prompt=Enter new project component name: -new.project.component.action.name=Creating new project component: {0}.{1} -select.plugin.modules.to.patch=Select Plugin Modules to Patch +new.menu.application.component.text=Application Component +new.menu.module.component.text=Module Component +new.menu.project.component.text=Project Component + +new.service.class.action.name=Creating new service +new.service.dialog.interface=Service &interface: +new.service.dialog.implementation=Service i&mplementation: +new.service.dialog.class=Service &class: +new.service.dialog.separate=&Separate interface from implementation + +new.menu.application.service.text=Application Service +new.menu.application.service.description=Create New Application Service +new.application.service.dialog.title=Create Application Service +new.menu.project.service.text=Project Service +new.menu.project.service.description=Create New Project Service +new.project.service.dialog.title=Create Project Service +new.menu.module.service.text=Module Service +new.menu.module.service.description=Create New Module Service +new.module.service.dialog.title=Create New Module Service + +select.plugin.module.to.patch=Select Plugin Module to Patch keyword.extend=extend keyword.implement=implement @@ -126,7 +123,6 @@ inspections.registration.problems.option.check.plugin.xml=Check Plugin Descripto inspections.registration.problems.option.check.java.actions=Check Java Actions inspections.registration.problems.option.check.java.code=Check Java Code inspections.registration.problems.quickfix.read-only=Class ''{0}'' is read-only -inspections.registration.problems.quickfix.make.public=Make {0} public inspections.registration.problems.quickfix.create.constructor=Create no-argument constructor inspections.registration.problems.incompatible.message=According to its registration in plugin.xml, the class should {0} ''{1}'' @@ -135,7 +131,6 @@ inspections.registration.problems.missing.noarg.ctor=Action class must have a no inspections.registration.problems.missing.implementation.class=Missing implementation-class inspections.registration.problems.cannot.resolve.class=Cannot resolve {0} class -inspections.registration.problems.component.should.implement=Component class must implement ''{0}'' inspections.registration.problems.component.incompatible.interface=Component class is not assignable to its interface-class ''{0}'' inspections.registration.problems.component.duplicate.interface=Multiple components with the same interface-class are not allowed inspections.registration.problems.action.incompatible.class=Action class must extend ''{0}'' @@ -150,18 +145,13 @@ inspections.component.not.registered.quickfix.error=Cannot Register {0} inspections.component.postfix.template.not.found.description.name=Postfix template Description Checker -InspectionUseGrayColor=Use Gray ant.build.jar.comment=Build archive for plugin ''{0}'' ant.build.jar.description=Build plugin archive for module ''{0}'' -project.title=Plugin Project no.java.sdk.for.idea.sdk.found=No Java SDK of appropriate version found. In addition to the IntelliJ Platform Plugin SDK, you need to define a JDK with the same Java version ({0}). no.idea.sdk.version.found=Failed to detect JDK version required for IntelliJ Platform Plugin SDK. -group.PluginDeployActions.text=Plugin Deployment Actions - error.cannot.resolve.plugin=Cannot resolve plugin {0} create.description.file=Create description file {0} select.target.location.of.description=Select target location of {0} -serialization.only.member.used.explicitly=Serialization-only member used explicitly implemented.at.runtime.dom=DOM Element implemented at runtime implemented.at.runtime.jamElement=JAM Element implemented at runtime diff --git a/plugins/devkit/src/DevKitFileTemplatesFactory.java b/plugins/devkit/src/DevKitFileTemplatesFactory.java index b31e5432bd90..a8bf2da2c093 100644 --- a/plugins/devkit/src/DevKitFileTemplatesFactory.java +++ b/plugins/devkit/src/DevKitFileTemplatesFactory.java @@ -27,9 +27,15 @@ public class DevKitFileTemplatesFactory implements FileTemplateGroupDescriptorFa FileTemplateGroupDescriptor descriptor = new FileTemplateGroupDescriptor(DevKitBundle.message("plugin.descriptor"), AllIcons.Nodes.Plugin); descriptor.addTemplate(new FileTemplateDescriptor("plugin.xml", StdFileTypes.XML.getIcon())); - descriptor.addTemplate(new FileTemplateDescriptor("ProjectComponent.java", StdFileTypes.JAVA.getIcon())); - descriptor.addTemplate(new FileTemplateDescriptor("ApplicationComponent.java", StdFileTypes.JAVA.getIcon())); - descriptor.addTemplate(new FileTemplateDescriptor("ModuleComponent.java", StdFileTypes.JAVA.getIcon())); + descriptor.addTemplate(new FileTemplateDescriptor("ProjectServiceClass.java", StdFileTypes.JAVA.getIcon())); + descriptor.addTemplate(new FileTemplateDescriptor("ProjectServiceInterface.java", StdFileTypes.JAVA.getIcon())); + descriptor.addTemplate(new FileTemplateDescriptor("ProjectServiceImplementation.java", StdFileTypes.JAVA.getIcon())); + descriptor.addTemplate(new FileTemplateDescriptor("ApplicationServiceClass.java", StdFileTypes.JAVA.getIcon())); + descriptor.addTemplate(new FileTemplateDescriptor("ApplicationServiceInterface.java", StdFileTypes.JAVA.getIcon())); + descriptor.addTemplate(new FileTemplateDescriptor("ApplicationServiceImplementation.java", StdFileTypes.JAVA.getIcon())); + descriptor.addTemplate(new FileTemplateDescriptor("ModuleServiceClass.java", StdFileTypes.JAVA.getIcon())); + descriptor.addTemplate(new FileTemplateDescriptor("ModuleServiceInterface.java", StdFileTypes.JAVA.getIcon())); + descriptor.addTemplate(new FileTemplateDescriptor("ModuleServiceImplementation.java", StdFileTypes.JAVA.getIcon())); descriptor.addTemplate(new FileTemplateDescriptor("Action.java", StdFileTypes.JAVA.getIcon())); descriptor.addTemplate(new FileTemplateDescriptor("InspectionDescription.html", StdFileTypes.HTML.getIcon())); return descriptor; diff --git a/plugins/devkit/src/actions/DevkitActionsUtil.java b/plugins/devkit/src/actions/DevkitActionsUtil.java new file mode 100644 index 000000000000..d469344f68d7 --- /dev/null +++ b/plugins/devkit/src/actions/DevkitActionsUtil.java @@ -0,0 +1,172 @@ +/* + * 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 org.jetbrains.idea.devkit.actions; + +import com.intellij.CommonBundle; +import com.intellij.ide.actions.CreateFileAction; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.OrderEntry; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.roots.ui.configuration.ChooseModulesDialog; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.JavaDirectoryService; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.xml.XmlFile; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.devkit.DevKitBundle; +import org.jetbrains.idea.devkit.module.PluginModuleType; + +import java.io.File; +import java.util.*; + +public final class DevkitActionsUtil { + private static final Logger LOG = Logger.getInstance(DevkitActionsUtil.class); + + private DevkitActionsUtil() { + } + + + /** + * @return plugin descriptor for current module (if it's a plugin module) or plugin descriptor selected in dialog or null if cancelled. + * @throws IncorrectOperationException if no plugin descriptors found. + */ + @Nullable + public static XmlFile choosePluginModuleDescriptor(PsiDirectory directory) { + Project project = directory.getProject(); + Module module = getModule(directory); + + XmlFile currentModulePluginXml = PluginModuleType.getPluginXml(module); + if (currentModulePluginXml != null) { + return currentModulePluginXml; + } + + if (module != null) { + List candidateModules = PluginModuleType.getCandidateModules(module); + Iterator it = candidateModules.iterator(); + while (it.hasNext()) { + Module m = it.next(); + if (PluginModuleType.getPluginXml(m) == null) it.remove(); + } + + if (candidateModules.size() == 1) { + return PluginModuleType.getPluginXml(candidateModules.get(0)); + } + + ChoosePluginModuleDialog chooseModulesDialog = new ChoosePluginModuleDialog(project, candidateModules, + DevKitBundle.message("select.plugin.module.to.patch"), null); + chooseModulesDialog.setSingleSelectionMode(); + chooseModulesDialog.show(); + + List selectedModules = chooseModulesDialog.getChosenElements(); + if (selectedModules.isEmpty()) { + return null; // cancelled + } + + assert selectedModules.size() == 1; + XmlFile pluginXml = PluginModuleType.getPluginXml(selectedModules.get(0)); + if (pluginXml != null) { + return pluginXml; + } + } + + Messages.showMessageDialog(project, DevKitBundle.message("error.no.plugin.xml"), + CommonBundle.getErrorTitle(), Messages.getErrorIcon()); + return null; + } + + public static PsiClass createSingleClass(String name, String classTemplateName, PsiDirectory directory) { + return createSingleClass(name, classTemplateName, directory, Collections.emptyMap()); + } + + public static PsiClass createSingleClass(String name, String classTemplateName, PsiDirectory directory, + @NotNull Map properties) { + if (name.contains(".")) { + String[] names = name.split("\\."); + for (int i = 0; i < names.length - 1; i++) { + directory = CreateFileAction.findOrCreateSubdirectory(directory, names[i]); + } + name = names[names.length - 1]; + } + + return JavaDirectoryService.getInstance().createClass(directory, name, classTemplateName, false, properties); + } + + @Nullable + private static Module getModule(PsiDirectory dir) { + Project project = dir.getProject(); + ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + + VirtualFile vFile = dir.getVirtualFile(); + if (fileIndex.isInLibrarySource(vFile) || fileIndex.isInLibraryClasses(vFile)) { + List orderEntries = fileIndex.getOrderEntriesForFile(vFile); + if (orderEntries.isEmpty()) { + return null; + } + Set modules = new HashSet<>(); + for (OrderEntry orderEntry : orderEntries) { + modules.add(orderEntry.getOwnerModule()); + } + Module[] candidates = modules.toArray(new Module[modules.size()]); + Arrays.sort(candidates, ModuleManager.getInstance(project).moduleDependencyComparator()); + return candidates[0]; + } + return fileIndex.getModuleForFile(vFile); + } + + + private static class ChoosePluginModuleDialog extends ChooseModulesDialog { + public ChoosePluginModuleDialog(Project project, List items, String title, @Nullable String description) { + super(project, items, title, description); + } + + @Override + protected String getItemLocation(Module item) { + XmlFile pluginXml = PluginModuleType.getPluginXml(item); + if (pluginXml == null) { + return null; + } + + VirtualFile virtualFile = pluginXml.getVirtualFile(); + VirtualFile projectPath = item.getProject().getBaseDir(); + + boolean shouldReturnNull = false; + if (virtualFile == null) { + LOG.warn("Unexpected null plugin.xml VirtualFile for module: " + item); + shouldReturnNull = true; + } + if (projectPath == null) { + LOG.warn("Unexpected null project basedir VirtualFile for module: " + item); + shouldReturnNull = true; + } + if (shouldReturnNull) return null; + + + if (VfsUtilCore.isAncestor(projectPath, virtualFile, false)) { + return VfsUtilCore.getRelativePath(virtualFile, projectPath, File.separatorChar); + } + return virtualFile.getPresentableUrl(); + } + } +} diff --git a/plugins/devkit/src/actions/GenerateClassAndPatchPluginXmlActionBase.java b/plugins/devkit/src/actions/GenerateClassAndPatchPluginXmlActionBase.java deleted file mode 100644 index 322316ab8851..000000000000 --- a/plugins/devkit/src/actions/GenerateClassAndPatchPluginXmlActionBase.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2000-2012 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 org.jetbrains.idea.devkit.actions; - -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.xml.XmlFile; -import com.intellij.util.IncorrectOperationException; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.idea.devkit.util.ComponentType; - -import javax.swing.*; - -/** - * @author max - */ -public abstract class GenerateClassAndPatchPluginXmlActionBase extends GeneratePluginClassAction { - public GenerateClassAndPatchPluginXmlActionBase(String text, String description, @Nullable Icon icon) { - super(text, description, icon); - } - - protected abstract String getClassNamePrompt(); - protected abstract String getClassNamePromptTitle(); - - protected PsiElement[] invokeDialogImpl(Project project, PsiDirectory directory) { - MyInputValidator validator = new MyInputValidator(project, directory); - Messages.showInputDialog(project, getClassNamePrompt(), getClassNamePromptTitle(), Messages.getQuestionIcon(), "", validator); - return validator.getCreatedElements(); - } - - protected abstract ComponentType getComponentType(); - - public void patchPluginXml(XmlFile pluginXml, PsiClass klass) throws IncorrectOperationException { - getComponentType().patchPluginXml(pluginXml, klass); - } -} diff --git a/plugins/devkit/src/actions/GenerateComponentExternalizationAction.java b/plugins/devkit/src/actions/GenerateComponentExternalizationAction.java index 481a3559cc17..9c69bcf134dd 100644 --- a/plugins/devkit/src/actions/GenerateComponentExternalizationAction.java +++ b/plugins/devkit/src/actions/GenerateComponentExternalizationAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * 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. @@ -33,11 +33,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.devkit.DevKitBundle; -/** - * @author max - */ public class GenerateComponentExternalizationAction extends AnAction { - private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.devkit.actions.GenerateComponentExternalizationAction"); + private static final Logger LOG = Logger.getInstance(GenerateComponentExternalizationAction.class); @NonNls private final static String BASE_COMPONENT = "com.intellij.openapi.components.BaseComponent"; @NonNls private final static String PERSISTENCE_STATE_COMPONENT = "com.intellij.openapi.components.PersistentStateComponent"; @@ -50,6 +47,7 @@ public class GenerateComponentExternalizationAction extends AnAction { super.beforeActionPerformedUpdate(e); } + @Override public void actionPerformed(AnActionEvent e) { final PsiClass target = getComponentInContext(e.getDataContext()); assert target != null; @@ -126,6 +124,7 @@ public class GenerateComponentExternalizationAction extends AnAction { return contextClass; } + @Override public void update(AnActionEvent e) { super.update(e); final PsiClass target = getComponentInContext(e.getDataContext()); diff --git a/plugins/devkit/src/actions/GeneratePluginClassAction.java b/plugins/devkit/src/actions/GeneratePluginClassAction.java deleted file mode 100644 index 94686bd7e91f..000000000000 --- a/plugins/devkit/src/actions/GeneratePluginClassAction.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * 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 org.jetbrains.idea.devkit.actions; - -import com.intellij.ide.IdeView; -import com.intellij.ide.actions.CreateElementActionBase; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.LangDataKeys; -import com.intellij.openapi.actionSystem.Presentation; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.OrderEntry; -import com.intellij.openapi.roots.ProjectFileIndex; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.JavaDirectoryService; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; -import com.intellij.psi.xml.XmlFile; -import com.intellij.util.IncorrectOperationException; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.idea.devkit.DevKitBundle; -import org.jetbrains.idea.devkit.module.PluginModuleType; -import org.jetbrains.idea.devkit.util.ChooseModulesDialog; -import org.jetbrains.idea.devkit.util.DescriptorUtil; -import org.jetbrains.idea.devkit.util.PsiUtil; -import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes; - -import javax.swing.*; -import java.util.*; - -/** - * @author yole - */ -public abstract class GeneratePluginClassAction extends CreateElementActionBase implements DescriptorUtil.Patcher { - protected final Set myFilesToPatch = new HashSet<>(); - - // length == 1 is important to make MyInputValidator close the dialog when - // module selection is canceled. That's some weird interface actually... - private static final PsiElement[] CANCELED = new PsiElement[1]; - - public GeneratePluginClassAction(String text, String description, @Nullable Icon icon) { - super(text, description, icon); - } - - @NotNull - protected final PsiElement[] invokeDialog(Project project, PsiDirectory directory) { - try { - final PsiElement[] psiElements = invokeDialogImpl(project, directory); - return psiElements == CANCELED ? PsiElement.EMPTY_ARRAY : psiElements; - } - finally { - myFilesToPatch.clear(); - } - } - - protected abstract PsiElement[] invokeDialogImpl(Project project, PsiDirectory directory); - - private void addPluginModule(Module module) { - final XmlFile pluginXml = PluginModuleType.getPluginXml(module); - if (pluginXml != null) myFilesToPatch.add(pluginXml); - } - - public void update(final AnActionEvent e) { - super.update(e); - - final Presentation presentation = e.getPresentation(); - if (presentation.isEnabled()) { - final Project project = e.getProject(); - final Module module = e.getData(LangDataKeys.MODULE); - if (project != null && module != null && - PsiUtil.isPluginModule(module)) { - final IdeView view = e.getData(LangDataKeys.IDE_VIEW); - if (view != null) { - // from com.intellij.ide.actions.CreateClassAction.update() - ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex(); - PsiDirectory[] dirs = view.getDirectories(); - for (PsiDirectory dir : dirs) { - if (projectFileIndex.isUnderSourceRootOfType(dir.getVirtualFile(), JavaModuleSourceRootTypes.SOURCES) && - JavaDirectoryService.getInstance().getPackage(dir) != null) { - return; - } - } - } - } - - presentation.setEnabledAndVisible(false); - } - } - - @Nullable - protected static Module getModule(PsiDirectory dir) { - Project project = dir.getProject(); - final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); - - final VirtualFile vFile = dir.getVirtualFile(); - if (fileIndex.isInLibrarySource(vFile) || fileIndex.isInLibraryClasses(vFile)) { - final List orderEntries = fileIndex.getOrderEntriesForFile(vFile); - if (orderEntries.isEmpty()) { - return null; - } - Set modules = new HashSet<>(); - for (OrderEntry orderEntry : orderEntries) { - modules.add(orderEntry.getOwnerModule()); - } - final Module[] candidates = modules.toArray(new Module[modules.size()]); - Arrays.sort(candidates, ModuleManager.getInstance(project).moduleDependencyComparator()); - return candidates[0]; - } - return fileIndex.getModuleForFile(vFile); - } - - @Override - public boolean startInWriteAction() { - return false; - } - - @NotNull - protected PsiElement[] create(String newName, PsiDirectory directory) throws Exception { - final Project project = directory.getProject(); - final Module module = getModule(directory); - - if (module != null) { - addPluginModule(module); - - if (myFilesToPatch.isEmpty()) { - final List candidateModules = PluginModuleType.getCandidateModules(module); - final Iterator it = candidateModules.iterator(); - while (it.hasNext()) { - Module m = it.next(); - if (PluginModuleType.getPluginXml(m) == null) it.remove(); - } - - if (candidateModules.size() == 1) { - addPluginModule(candidateModules.get(0)); - } - else { - final ChooseModulesDialog dialog = new ChooseModulesDialog(project, candidateModules, getTemplatePresentation().getDescription()); - if (!dialog.showAndGet()) { - // create() should return CANCELED now - return CANCELED; - } - else { - final List modules = dialog.getSelectedModules(); - for (Module m : modules) { - addPluginModule(m); - } - } - } - } - } - - if (myFilesToPatch.size() == 0) { - throw new IncorrectOperationException(DevKitBundle.message("error.no.plugin.xml")); - } - if (myFilesToPatch.size() == 0) { - // user canceled module selection - return CANCELED; - } - - final PsiClass klass = JavaDirectoryService.getInstance().createClass(directory, newName, getClassTemplateName()); - - DescriptorUtil.patchPluginXml(this, klass, myFilesToPatch.toArray(new XmlFile[myFilesToPatch.size()])); - - return new PsiElement[]{klass}; - } - - @NonNls - protected abstract String getClassTemplateName(); -} diff --git a/plugins/devkit/src/actions/NewActionAction.java b/plugins/devkit/src/actions/NewActionAction.java index 3beb14958419..62fbb7b130cd 100644 --- a/plugins/devkit/src/actions/NewActionAction.java +++ b/plugins/devkit/src/actions/NewActionAction.java @@ -15,6 +15,12 @@ */ package org.jetbrains.idea.devkit.actions; +import com.intellij.ide.actions.CreateElementActionBase; +import com.intellij.ide.actions.CreateTemplateInPackageAction; +import com.intellij.ide.actions.JavaCreateTemplateInPackageAction; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.actionSystem.LangDataKeys; +import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.psi.PsiClass; @@ -22,49 +28,97 @@ import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; import com.intellij.psi.xml.XmlFile; import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.devkit.DevKitBundle; import org.jetbrains.idea.devkit.util.ActionType; +import org.jetbrains.idea.devkit.util.DescriptorUtil; +import org.jetbrains.idea.devkit.util.PsiUtil; +import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes; + +public class NewActionAction extends CreateElementActionBase implements DescriptorUtil.Patcher { + // length == 1 is important to make MyInputValidator close the dialog when + // module selection is canceled. That's some weird interface actually... + private static final PsiClass[] CANCELED = new PsiClass[1]; -/** - * @author yole - */ -public class NewActionAction extends GeneratePluginClassAction { private NewActionDialog myDialog; + private XmlFile pluginDescriptorToPatch; public NewActionAction() { super(DevKitBundle.message("new.menu.action.text"), DevKitBundle.message("new.menu.action.description"), null); } - protected PsiElement[] invokeDialogImpl(Project project, PsiDirectory directory) { + @NotNull + @Override + protected final PsiElement[] invokeDialog(Project project, PsiDirectory directory) { + PsiElement[] psiElements = doInvokeDialog(project, directory); + return psiElements == CANCELED ? PsiElement.EMPTY_ARRAY : psiElements; + } + + private PsiElement[] doInvokeDialog(Project project, PsiDirectory directory) { myDialog = new NewActionDialog(project); - myDialog.show(); - if (myDialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) { - final MyInputValidator validator = new MyInputValidator(project, directory); - // this actually runs the action to create the class from template - validator.canClose(myDialog.getActionName()); + try { + myDialog.show(); + if (myDialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) { + pluginDescriptorToPatch = DevkitActionsUtil.choosePluginModuleDescriptor(directory); + if (pluginDescriptorToPatch != null) { + MyInputValidator validator = new MyInputValidator(project, directory); + // this actually runs the action to create the class from template + validator.canClose(myDialog.getActionName()); + return validator.getCreatedElements(); + } + } + return PsiElement.EMPTY_ARRAY; + } finally { myDialog = null; - return validator.getCreatedElements(); + pluginDescriptorToPatch = null; } - myDialog = null; - return PsiElement.EMPTY_ARRAY; } - protected String getClassTemplateName() { - return "Action.java"; + @Override + protected boolean isAvailable(DataContext dataContext) { + if (!super.isAvailable(dataContext)) { + return false; + } + + Module module = dataContext.getData(LangDataKeys.MODULE); + if (module == null || !PsiUtil.isPluginModule(module)) { + return false; + } + + return CreateTemplateInPackageAction.isAvailable(dataContext, JavaModuleSourceRootTypes.SOURCES, + JavaCreateTemplateInPackageAction::doCheckPackageExists); } - public void patchPluginXml(final XmlFile pluginXml, final PsiClass klass) throws IncorrectOperationException { + @Override + public boolean startInWriteAction() { + return false; + } + + @NotNull + @Override + protected PsiElement[] create(String newName, PsiDirectory directory) throws Exception { + PsiClass createdClass = DevkitActionsUtil.createSingleClass(newName, "Action.java", directory); + DescriptorUtil.patchPluginXml(this, createdClass, pluginDescriptorToPatch); + return new PsiElement[]{createdClass}; + } + + + @Override + public void patchPluginXml(XmlFile pluginXml, PsiClass klass) throws IncorrectOperationException { ActionType.ACTION.patchPluginXml(pluginXml, klass, myDialog); } + @Override protected String getErrorTitle() { return DevKitBundle.message("new.action.error"); } + @Override protected String getCommandName() { return DevKitBundle.message("new.action.command"); } + @Override protected String getActionName(PsiDirectory directory, String newName) { return DevKitBundle.message("new.action.action.name", directory, newName); } diff --git a/plugins/devkit/src/actions/NewApplicationComponentAction.java b/plugins/devkit/src/actions/NewApplicationComponentAction.java deleted file mode 100644 index 8cc6988c3def..000000000000 --- a/plugins/devkit/src/actions/NewApplicationComponentAction.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2000-2009 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 org.jetbrains.idea.devkit.actions; - -import com.intellij.psi.PsiDirectory; -import org.jetbrains.idea.devkit.util.ComponentType; -import org.jetbrains.idea.devkit.DevKitBundle; - -/** - * @author max - */ -public class NewApplicationComponentAction extends GenerateClassAndPatchPluginXmlActionBase { - /** - *. - */ - public NewApplicationComponentAction() { - super(DevKitBundle.message("new.menu.application.component.text"), - DevKitBundle.message("new.menu.application.component.description"), null); - } - - protected ComponentType getComponentType() { - return ComponentType.APPLICATION; - } - - protected String getErrorTitle() { - return DevKitBundle.message("new.application.component.error"); - } - - protected String getCommandName() { - return DevKitBundle.message("new.application.component.command"); - } - - protected String getClassNamePromptTitle() { - return DevKitBundle.message("new.application.component.prompt.title"); - } - - protected String getClassTemplateName() { - return "ApplicationComponent.java"; - } - - protected String getClassNamePrompt() { - return DevKitBundle.message("new.application.component.prompt"); - } - - protected String getActionName(PsiDirectory directory, String newName) { - return DevKitBundle.message("new.application.component.action.name", directory, newName); - } -} diff --git a/plugins/devkit/src/actions/NewModuleComponentAction.java b/plugins/devkit/src/actions/NewModuleComponentAction.java deleted file mode 100644 index 7a999c39e36d..000000000000 --- a/plugins/devkit/src/actions/NewModuleComponentAction.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2000-2009 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 org.jetbrains.idea.devkit.actions; - -import com.intellij.psi.PsiDirectory; -import org.jetbrains.idea.devkit.util.ComponentType; -import org.jetbrains.idea.devkit.DevKitBundle; - -/** - * @author max - */ -public class NewModuleComponentAction extends GenerateClassAndPatchPluginXmlActionBase { - /** - *. - */ - public NewModuleComponentAction() { - super(DevKitBundle.message("new.menu.module.component.text"), - DevKitBundle.message("new.menu.module.component.description"), null); - } - - protected ComponentType getComponentType() { - return ComponentType.MODULE; - } - - protected String getErrorTitle() { - return DevKitBundle.message("new.module.component.error"); - } - - protected String getCommandName() { - return DevKitBundle.message("new.module.component.command"); - } - - protected String getClassNamePromptTitle() { - return DevKitBundle.message("new.module.component.prompt.title"); - } - - protected String getClassTemplateName() { - return "ModuleComponent.java"; - } - - protected String getClassNamePrompt() { - return DevKitBundle.message("new.module.component.prompt"); - } - - protected String getActionName(PsiDirectory directory, String newName) { - return DevKitBundle.message("new.module.component.action.name", directory, newName); - } -} diff --git a/plugins/devkit/src/actions/NewProjectComponentAction.java b/plugins/devkit/src/actions/NewProjectComponentAction.java deleted file mode 100644 index d97b4fc554f7..000000000000 --- a/plugins/devkit/src/actions/NewProjectComponentAction.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2000-2009 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 org.jetbrains.idea.devkit.actions; - -import com.intellij.psi.PsiDirectory; -import org.jetbrains.idea.devkit.util.ComponentType; -import org.jetbrains.idea.devkit.DevKitBundle; - -/** - * @author max - */ -public class NewProjectComponentAction extends GenerateClassAndPatchPluginXmlActionBase { - /** - *. - */ - public NewProjectComponentAction() { - super(DevKitBundle.message("new.menu.project.component.text"), - DevKitBundle.message("new.menu.project.component.description"), null); - } - - protected ComponentType getComponentType() { - return ComponentType.PROJECT; - } - - protected String getErrorTitle() { - return DevKitBundle.message("new.project.component.error"); - } - - protected String getCommandName() { - return DevKitBundle.message("new.project.component.command"); - } - - protected String getClassNamePromptTitle() { - return DevKitBundle.message("new.project.component.prompt.title"); - } - - protected String getClassTemplateName() { - return "ProjectComponent.java"; - } - - protected String getClassNamePrompt() { - return DevKitBundle.message("new.project.component.prompt"); - } - - protected String getActionName(PsiDirectory directory, String newName) { - return DevKitBundle.message("new.project.component.action.name", directory, newName); - } -} diff --git a/plugins/devkit/src/actions/service/NewApplicationServiceAction.java b/plugins/devkit/src/actions/service/NewApplicationServiceAction.java new file mode 100644 index 000000000000..8bbeb5815c3f --- /dev/null +++ b/plugins/devkit/src/actions/service/NewApplicationServiceAction.java @@ -0,0 +1,50 @@ +/* + * 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 org.jetbrains.idea.devkit.actions.service; + +import org.jetbrains.idea.devkit.DevKitBundle; + +public class NewApplicationServiceAction extends NewServiceActionBase { + public NewApplicationServiceAction() { + super(DevKitBundle.message("new.menu.application.service.text"), + DevKitBundle.message("new.menu.application.service.description")); + } + + @Override + protected String getTagName() { + return "applicationService"; + } + + @Override + protected String getOnlyImplementationTemplateName() { + return "ApplicationServiceClass.java"; + } + + @Override + protected String getInterfaceTemplateName() { + return "ApplicationServiceInterface.java"; + } + + @Override + protected String getImplementationTemplateName() { + return "ApplicationServiceImplementation.java"; + } + + @Override + protected String getDialogTitle() { + return DevKitBundle.message("new.application.service.dialog.title"); + } +} diff --git a/plugins/devkit/src/actions/service/NewModuleServiceAction.java b/plugins/devkit/src/actions/service/NewModuleServiceAction.java new file mode 100644 index 000000000000..5f25fd643ff3 --- /dev/null +++ b/plugins/devkit/src/actions/service/NewModuleServiceAction.java @@ -0,0 +1,50 @@ +/* + * 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 org.jetbrains.idea.devkit.actions.service; + +import org.jetbrains.idea.devkit.DevKitBundle; + +public class NewModuleServiceAction extends NewServiceActionBase { + public NewModuleServiceAction() { + super(DevKitBundle.message("new.menu.module.service.text"), + DevKitBundle.message("new.menu.module.service.description")); + } + + @Override + protected String getTagName() { + return "moduleService"; + } + + @Override + protected String getOnlyImplementationTemplateName() { + return "ModuleServiceClass.java"; + } + + @Override + protected String getInterfaceTemplateName() { + return "ModuleServiceInterface.java"; + } + + @Override + protected String getImplementationTemplateName() { + return "ModuleServiceImplementation.java"; + } + + @Override + protected String getDialogTitle() { + return DevKitBundle.message("new.module.service.dialog.title"); + } +} diff --git a/plugins/devkit/src/actions/service/NewProjectServiceAction.java b/plugins/devkit/src/actions/service/NewProjectServiceAction.java new file mode 100644 index 000000000000..268a72994931 --- /dev/null +++ b/plugins/devkit/src/actions/service/NewProjectServiceAction.java @@ -0,0 +1,50 @@ +/* + * 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 org.jetbrains.idea.devkit.actions.service; + +import org.jetbrains.idea.devkit.DevKitBundle; + +public class NewProjectServiceAction extends NewServiceActionBase { + public NewProjectServiceAction() { + super(DevKitBundle.message("new.menu.project.service.text"), + DevKitBundle.message("new.menu.project.service.description")); + } + + @Override + protected String getTagName() { + return "projectService"; + } + + @Override + protected String getOnlyImplementationTemplateName() { + return "ProjectServiceClass.java"; + } + + @Override + protected String getInterfaceTemplateName() { + return "ProjectServiceInterface.java"; + } + + @Override + protected String getImplementationTemplateName() { + return "ProjectServiceImplementation.java"; + } + + @Override + protected String getDialogTitle() { + return DevKitBundle.message("new.project.service.dialog.title"); + } +} diff --git a/plugins/devkit/src/actions/service/NewServiceActionBase.java b/plugins/devkit/src/actions/service/NewServiceActionBase.java new file mode 100644 index 000000000000..c5c3feb90f5b --- /dev/null +++ b/plugins/devkit/src/actions/service/NewServiceActionBase.java @@ -0,0 +1,351 @@ +/* + * 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 org.jetbrains.idea.devkit.actions.service; + +import com.intellij.CommonBundle; +import com.intellij.ide.IdeBundle; +import com.intellij.ide.IdeView; +import com.intellij.ide.actions.CreateInDirectoryActionBase; +import com.intellij.ide.actions.ElementCreator; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.LangDataKeys; +import com.intellij.openapi.application.Result; +import com.intellij.openapi.application.RunResult; +import com.intellij.openapi.application.WriteActionAware; +import com.intellij.openapi.command.UndoConfirmationPolicy; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; +import com.intellij.ui.DocumentAdapter; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.xml.DomFileElement; +import com.intellij.util.xml.DomManager; +import com.intellij.xml.util.IncludedXmlTag; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.devkit.DevKitBundle; +import org.jetbrains.idea.devkit.actions.DevkitActionsUtil; +import org.jetbrains.idea.devkit.dom.Extensions; +import org.jetbrains.idea.devkit.dom.IdeaPlugin; +import org.jetbrains.idea.devkit.util.DescriptorUtil; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import java.util.concurrent.Callable; + +/** + * An base class for actions generating service classes (implementation and optionally interface) and registering new service in plugin.xml. + */ +abstract class NewServiceActionBase extends CreateInDirectoryActionBase implements WriteActionAware { + NewServiceActionBase(String text, String description) { + super(text, description, null); + } + + @Override + public boolean startInWriteAction() { + return false; + } + + @Override + public final void actionPerformed(AnActionEvent e) { + IdeView view = e.getData(LangDataKeys.IDE_VIEW); + if (view == null) { + return; + } + + Project project = e.getProject(); + + PsiDirectory dir = view.getOrChooseDirectory(); + if (dir == null) return; + + ServiceCreator serviceCreator = new ServiceCreator(dir, getInterfaceTemplateName(), getImplementationTemplateName(), + getOnlyImplementationTemplateName(), getTagName()); + PsiClass[] createdClasses = invokeDialog(project, serviceCreator, dir); + if (createdClasses == null) { + return; + } + + for (PsiClass createdClass : createdClasses) { + view.selectElement(createdClass); + } + } + + @Nullable + private PsiClass[] invokeDialog(Project project, ServiceCreator serviceCreator, PsiDirectory dir) { + DialogWrapper dialog = new NewServiceDialog(project, serviceCreator, dir); + dialog.show(); + return serviceCreator.getCreatedClasses(); + } + + protected abstract String getTagName(); + + protected abstract String getOnlyImplementationTemplateName(); + protected abstract String getInterfaceTemplateName(); + protected abstract String getImplementationTemplateName(); + + protected abstract String getDialogTitle(); + + + private class NewServiceDialog extends DialogWrapper { + private final ServiceCreator myServiceCreator; + private final PsiDirectory myDirectory; + + private JPanel myTopPanel; + + private JTextField myServiceNameTextField; + private JCheckBox mySeparateServiceInterfaceCheckbox; + private JTextField myServiceImplementationTextField; + private JLabel myServiceNameLabel; + + private boolean myAdjusting = false; + private boolean myNeedAdjust = true; + + NewServiceDialog(@Nullable Project project, ServiceCreator serviceCreator, PsiDirectory directory) { + super(project); + + setOKActionEnabled(false); + setTitle(getDialogTitle()); + + myServiceCreator = serviceCreator; + myDirectory = directory; + + mySeparateServiceInterfaceCheckbox.addActionListener(e -> { + if (mySeparateServiceInterfaceCheckbox.isSelected()) { + myServiceImplementationTextField.setEnabled(true); + myServiceNameLabel.setText(DevKitBundle.message("new.service.dialog.interface")); + } else { + myServiceImplementationTextField.setEnabled(false); + myServiceNameLabel.setText(DevKitBundle.message("new.service.dialog.class")); + } + adjustServiceImplementationTextField(); + }); + + myServiceNameTextField.getDocument().addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(DocumentEvent e) { + setOKActionEnabled(myServiceNameTextField.getText().length() > 0); + adjustServiceImplementationTextField(); + } + }); + myServiceImplementationTextField.getDocument().addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(DocumentEvent e) { + if (!myAdjusting) { + myNeedAdjust = false; + } + } + }); + + init(); + } + + private void adjustServiceImplementationTextField() { + if (!mySeparateServiceInterfaceCheckbox.isSelected()) { + myAdjusting = true; + myServiceImplementationTextField.setText(""); + myAdjusting = false; + } else if (myNeedAdjust) { + myAdjusting = true; + myServiceImplementationTextField.setText("impl." + myServiceNameTextField.getText() + "Impl"); + myAdjusting = false; + } + } + + @Nullable + @Override + public JComponent getPreferredFocusedComponent() { + return myServiceNameTextField; + } + + @Override + protected void doOKAction() { + XmlFile pluginDescriptorToPatch = DevkitActionsUtil.choosePluginModuleDescriptor(myDirectory); + if (pluginDescriptorToPatch == null) { + return; // canceled + } + + if (mySeparateServiceInterfaceCheckbox.isSelected()) { + // separated interface and implementation + String serviceInterface = myServiceNameTextField.getText().trim(); + String serviceImplementation = myServiceImplementationTextField.getText().trim(); + + if (checkInput(serviceInterface) && checkInput(serviceImplementation) && + myServiceCreator.createInterfaceAndImplementation(serviceInterface, serviceImplementation, pluginDescriptorToPatch)) { + close(OK_EXIT_CODE); + } + } else { + // only implementation + String serviceOnlyImplementation = myServiceNameTextField.getText().trim(); + + if (checkInput(serviceOnlyImplementation) && + myServiceCreator.createOnlyImplementation(serviceOnlyImplementation, pluginDescriptorToPatch)) { + close(OK_EXIT_CODE); + } + } + } + + private boolean checkInput(String input) { + if (StringUtil.isEmpty(input)) { + Messages.showMessageDialog(getContentPane(), IdeBundle.message("error.name.should.be.specified"), + CommonBundle.getErrorTitle(), Messages.getErrorIcon()); + return false; + } + return true; + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + return myTopPanel; + } + } + + + static class ServiceCreator { // not private for testing purpose only + private static final Logger LOG = Logger.getInstance(ServiceCreator.class); + private static final String INTERFACE_NAME_PROPERTY = "INTERFACE_NAME"; + private static final String INTERFACE_PACKAGE_PROPERTY = "INTERFACE_PACKAGE_NAME"; + + private final PsiDirectory myDirectory; + private final String myServiceInterfaceTemplateName; + private final String myServiceImplementationTemplateName; + private final String myServiceOnlyImplementationTemplateName; + private final String myTagName; + + private PsiClass[] createdClasses = null; + + ServiceCreator(PsiDirectory directory, + String serviceInterfaceTemplateName, + String serviceImplementationTemplateName, + String serviceOnlyImplementationTemplateName, + String tagName) { + myDirectory = directory; + myServiceInterfaceTemplateName = serviceInterfaceTemplateName; + myServiceImplementationTemplateName = serviceImplementationTemplateName; + myServiceOnlyImplementationTemplateName = serviceOnlyImplementationTemplateName; + myTagName = tagName; + } + + PsiClass[] getCreatedClasses() { + return createdClasses; + } + + /** + * @return whether the service was created (which indicates whether the create service dialog can be closed). + */ + boolean createInterfaceAndImplementation(String interfaceName, String implementationName, XmlFile pluginXml) { + return doCreateService(() -> { + PsiClass createdInterface = DevkitActionsUtil.createSingleClass(interfaceName, myServiceInterfaceTemplateName, myDirectory); + + String interfaceShortName = createdInterface.getName(); + String implementationDirRelativePackage = StringUtil.getPackageName(implementationName); + String interfacePackage; + if (implementationDirRelativePackage.isEmpty()) { + interfacePackage = ""; // interface and implementation are placed in the same package; there shouldn't be an import statement + } else { + //noinspection ConstantConditions + interfacePackage = StringUtil.getPackageName(createdInterface.getQualifiedName()); + } + + PsiClass createdImplementation = DevkitActionsUtil.createSingleClass( + implementationName, myServiceImplementationTemplateName, myDirectory, + ContainerUtil.stringMap(INTERFACE_NAME_PROPERTY, interfaceShortName, INTERFACE_PACKAGE_PROPERTY, interfacePackage)); + + patchPluginXml(createdInterface, createdImplementation, pluginXml); + + createdClasses = new PsiClass[]{createdInterface, createdImplementation}; + return true; + }); + } + + /** + * @return whether the service was created (which indicates whether the create service dialog can be closed). + */ + boolean createOnlyImplementation(String onlyImplementationName, XmlFile pluginXml) { + return doCreateService(() -> { + PsiClass createdOnlyImplementation = DevkitActionsUtil.createSingleClass( + onlyImplementationName, myServiceOnlyImplementationTemplateName, myDirectory); + + patchPluginXml(null, createdOnlyImplementation, pluginXml); + + createdClasses = new PsiClass[]{createdOnlyImplementation}; + return true; + }); + } + + private boolean doCreateService(Callable action) { + RunResult result = new WriteCommandAction(getProject(), DevKitBundle.message("new.service.class.action.name")) { + @Override + protected void run(@NotNull Result result) throws Throwable { + result.setResult(action.call()); + } + + @Override + protected UndoConfirmationPolicy getUndoConfirmationPolicy() { + return UndoConfirmationPolicy.REQUEST_CONFIRMATION; + } + }.execute(); + + if (result.hasException()) { + handleException(result.getThrowable()); + return false; + } + + return result.getResultObject(); + } + + private void patchPluginXml(@Nullable PsiClass createdInterface, @NotNull PsiClass createdImplementation, XmlFile pluginXml) { + DescriptorUtil.checkPluginXmlsWritable(getProject(), pluginXml); + + DomFileElement fileElement = DomManager.getDomManager(getProject()).getFileElement(pluginXml, IdeaPlugin.class); + if (fileElement == null) { + throw new IncorrectOperationException(DevKitBundle.message("error.cannot.process.plugin.xml", pluginXml)); + } + + IdeaPlugin ideaPlugin = fileElement.getRootElement(); + Extensions targetExtensions = ideaPlugin.getExtensions().stream() + .filter(extensions -> !(extensions instanceof IncludedXmlTag)) + .filter(extensions -> Extensions.DEFAULT_PREFIX.equals(extensions.getDefaultExtensionNs().getStringValue())) + .findAny() + .orElseGet(() -> ideaPlugin.addExtensions()); + + XmlTag serviceTag = targetExtensions.addExtension(Extensions.DEFAULT_PREFIX + "." + myTagName).getXmlTag(); + if (createdInterface != null) { + serviceTag.setAttribute("serviceInterface", createdInterface.getQualifiedName()); + } + serviceTag.setAttribute("serviceImplementation", createdImplementation.getQualifiedName()); + } + + private void handleException(Throwable t) { + LOG.info(t); + String errorMessage = ElementCreator.getErrorMessage(t); + Messages.showMessageDialog( + getProject(), errorMessage, DevKitBundle.message("error.cannot.create.service.class"), Messages.getErrorIcon()); + } + + private Project getProject() { + return myDirectory.getProject(); + } + } +} diff --git a/plugins/devkit/src/actions/service/NewServiceDialog.form b/plugins/devkit/src/actions/service/NewServiceDialog.form new file mode 100644 index 000000000000..1eabd371b6cf --- /dev/null +++ b/plugins/devkit/src/actions/service/NewServiceDialog.form @@ -0,0 +1,58 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/devkit/src/inspections/quickfix/AbstractRegisterFix.java b/plugins/devkit/src/inspections/quickfix/AbstractRegisterFix.java index daaf9c66f00b..99110d1addb1 100644 --- a/plugins/devkit/src/inspections/quickfix/AbstractRegisterFix.java +++ b/plugins/devkit/src/inspections/quickfix/AbstractRegisterFix.java @@ -33,12 +33,10 @@ import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.idea.devkit.DevKitBundle; +import org.jetbrains.idea.devkit.actions.DevkitActionsUtil; import org.jetbrains.idea.devkit.module.PluginModuleType; -import org.jetbrains.idea.devkit.util.ChooseModulesDialog; import org.jetbrains.idea.devkit.util.DescriptorUtil; -import java.util.List; - abstract class AbstractRegisterFix implements LocalQuickFix, DescriptorUtil.Patcher { protected final SmartPsiElementPointer myPointer; protected static final Logger LOG = Logger.getInstance(AbstractRegisterFix.class); @@ -87,33 +85,21 @@ abstract class AbstractRegisterFix implements LocalQuickFix, DescriptorUtil.Patc Runnable command = () -> { try { XmlFile pluginXml = PluginModuleType.getPluginXml(module); + if (pluginXml == null) { + pluginXml = DevkitActionsUtil.choosePluginModuleDescriptor(psiFile.getContainingDirectory()); + } + if (pluginXml != null) { DescriptorUtil.patchPluginXml(this, element, pluginXml); } - else { - List modules = PluginModuleType.getCandidateModules(module); - if (modules.size() > 1) { - ChooseModulesDialog dialog = new ChooseModulesDialog(project, modules, getName()); - if (!dialog.showAndGet()) { - return; - } - modules = dialog.getSelectedModules(); - } - XmlFile[] pluginXmls = new XmlFile[modules.size()]; - for (int i = 0; i < pluginXmls.length; i++) { - pluginXmls[i] = PluginModuleType.getPluginXml(modules.get(i)); - } - - DescriptorUtil.patchPluginXml(this, element, pluginXmls); - } CommandProcessor.getInstance().markCurrentCommandAsGlobal(project); - } - catch (IncorrectOperationException e) { + } catch (IncorrectOperationException e) { Messages.showMessageDialog(project, filterMessage(e.getMessage()), DevKitBundle.message("inspections.component.not.registered.quickfix.error", getType()), Messages.getErrorIcon()); } }; + CommandProcessor.getInstance().executeCommand(project, command, getName(), null); } } diff --git a/plugins/devkit/src/util/ChooseModulesDialog.java b/plugins/devkit/src/util/ChooseModulesDialog.java deleted file mode 100644 index 5d6dfbbe8ca5..000000000000 --- a/plugins/devkit/src/util/ChooseModulesDialog.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2000-2015 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 org.jetbrains.idea.devkit.util; - -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleType; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.ui.MultiLineLabelUI; -import com.intellij.openapi.vfs.VfsUtilCore; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.xml.XmlFile; -import com.intellij.ui.ColoredListCellRenderer; -import com.intellij.ui.ScrollPaneFactory; -import com.intellij.ui.SimpleTextAttributes; -import com.intellij.ui.TableUtil; -import com.intellij.ui.components.JBList; -import com.intellij.ui.table.JBTable; -import com.intellij.util.ui.JBUI; -import com.intellij.util.ui.components.BorderLayoutPanel; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.idea.devkit.DevKitBundle; -import org.jetbrains.idea.devkit.module.PluginModuleType; - -import javax.swing.*; -import javax.swing.event.TableModelEvent; -import javax.swing.event.TableModelListener; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.TableCellRenderer; -import java.awt.*; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; - -/** - * @author swr - */ -public class ChooseModulesDialog extends DialogWrapper { - private final Icon myIcon; - private final String myMessage; - private final JTable myView; - private final List myCandidateModules; - private final boolean[] myStates; - - public ChooseModulesDialog(final Project project, List candidateModules, @NonNls String title) { - this ( project, candidateModules, title, DevKitBundle.message("select.plugin.modules.to.patch")); - } - - public ChooseModulesDialog(final Project project, List candidateModules, @NonNls String title, final String message) { - super(project, false); - setTitle(title); - - myCandidateModules = candidateModules; - myIcon = Messages.getQuestionIcon(); - myMessage = message; - myView = new JBTable(new AbstractTableModel() { - public int getRowCount() { - return myCandidateModules.size(); - } - - public int getColumnCount() { - return 2; - } - - public boolean isCellEditable(int rowIndex, int columnIndex) { - return columnIndex == 0; - } - - public void setValueAt(Object aValue, int rowIndex, int columnIndex) { - myStates[rowIndex] = (Boolean)aValue; - fireTableCellUpdated(rowIndex, columnIndex); - } - - public Class getColumnClass(int columnIndex) { - return columnIndex == 0 ? Boolean.class : Module.class; - } - - public Object getValueAt(int rowIndex, int columnIndex) { - return columnIndex == 0 ? myStates[rowIndex] : myCandidateModules.get(rowIndex); - } - }); - - myView.setShowGrid(false); - myView.setTableHeader(null); - myView.setIntercellSpacing(JBUI.emptySize()); - TableUtil.setupCheckboxColumn(myView, 0); - myView.getModel().addTableModelListener(new TableModelListener() { - public void tableChanged(TableModelEvent e) { - getOKAction().setEnabled(getSelectedModules().size() > 0); - } - }); - myView.addKeyListener(new KeyAdapter() { - public void keyTyped(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ENTER - || e.getKeyChar() == '\n') { - doOKAction(); - } - } - }); - myView.setDefaultRenderer(Module.class, new MyTableCellRenderer(project)); - - myStates = new boolean[candidateModules.size()]; - Arrays.fill(myStates, true); - - init(); - } - - protected JComponent createNorthPanel() { - BorderLayoutPanel panel = JBUI.Panels.simplePanel(15, 10); - if (myIcon != null) { - JLabel iconLabel = new JLabel(myIcon); - panel.addToLeft(JBUI.Panels.simplePanel().addToTop(iconLabel)); - } - - BorderLayoutPanel messagePanel = JBUI.Panels.simplePanel(); - if (myMessage != null) { - JLabel textLabel = new JLabel(myMessage); - textLabel.setBorder(JBUI.Borders.emptyBottom(5)); - textLabel.setUI(new MultiLineLabelUI()); - messagePanel.addToTop(textLabel); - } - panel.add(messagePanel, BorderLayout.CENTER); - - final JScrollPane jScrollPane = ScrollPaneFactory.createScrollPane(); - jScrollPane.setViewportView(myView); - jScrollPane.setPreferredSize(JBUI.size(300, 80)); - panel.addToBottom(jScrollPane); - return panel; - } - - public JComponent getPreferredFocusedComponent() { - return myView; - } - - - protected JComponent createCenterPanel() { - return null; - } - - public List getSelectedModules() { - final ArrayList list = new ArrayList<>(myCandidateModules); - final Iterator modules = list.iterator(); - for (boolean b : myStates) { - modules.next(); - if (!b) { - modules.remove(); - } - } - return list; - } - - private static class MyTableCellRenderer implements TableCellRenderer { - private final JList myList; - private final Project myProject; - private final ColoredListCellRenderer myCellRenderer; - - public MyTableCellRenderer(Project project) { - myProject = project; - myList = new JBList(); - myCellRenderer = new ColoredListCellRenderer() { - protected void customizeCellRenderer(@NotNull JList list, Object value, int index, boolean selected, boolean hasFocus) { - final Module module = ((Module)value); - setIcon(ModuleType.get(module).getIcon()); - append(module.getName(), SimpleTextAttributes.REGULAR_ATTRIBUTES); - - final XmlFile pluginXml = PluginModuleType.getPluginXml(module); - assert pluginXml != null; - - final VirtualFile virtualFile = pluginXml.getVirtualFile(); - assert virtualFile != null; - final VirtualFile projectPath = myProject.getBaseDir(); - assert projectPath != null; - if (VfsUtilCore.isAncestor(projectPath, virtualFile, false)) { - append(" (" + VfsUtilCore.getRelativePath(virtualFile, projectPath, File.separatorChar) + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES); - } else { - append(" (" + virtualFile.getPresentableUrl() + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES); - } - } - }; - } - - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - return myCellRenderer.getListCellRendererComponent(myList, value, row, isSelected, hasFocus); - } - } -} diff --git a/plugins/devkit/src/util/DescriptorUtil.java b/plugins/devkit/src/util/DescriptorUtil.java index 57b8b2e876ea..b915bfb4d64f 100644 --- a/plugins/devkit/src/util/DescriptorUtil.java +++ b/plugins/devkit/src/util/DescriptorUtil.java @@ -17,6 +17,7 @@ package org.jetbrains.idea.devkit.util; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.ReadonlyStatusHandler; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiClass; @@ -33,10 +34,14 @@ import org.jetbrains.idea.devkit.DevKitBundle; import org.jetbrains.idea.devkit.dom.IdeaPlugin; import org.jetbrains.idea.devkit.module.PluginModuleType; -/** - * @author swr - */ -public class DescriptorUtil { +public final class DescriptorUtil { + private DescriptorUtil() { + } + + public interface Patcher { + void patchPluginXml(XmlFile pluginXml, PsiClass klass) throws IncorrectOperationException; + } + public static void processComponents(XmlTag root, ComponentType.Processor processor) { final ComponentType[] types = ComponentType.values(); @@ -52,28 +57,19 @@ public class DescriptorUtil { } } - public interface Patcher { - void patchPluginXml(XmlFile pluginXml, PsiClass klass) throws IncorrectOperationException; + public static void patchPluginXml(Patcher patcher, PsiClass klass, XmlFile pluginXml) throws IncorrectOperationException { + checkPluginXmlsWritable(klass.getProject(), pluginXml); + WriteAction.run((ThrowableRunnable)() -> patcher.patchPluginXml(pluginXml, klass)); } - public static void patchPluginXml(Patcher patcher, PsiClass klass, XmlFile... pluginXmls) throws IncorrectOperationException { - final VirtualFile[] files = new VirtualFile[pluginXmls.length]; - int i = 0; - for (XmlFile pluginXml : pluginXmls) { - files[i++] = pluginXml.getVirtualFile(); - } + public static void checkPluginXmlsWritable(Project project, XmlFile pluginXml) { + VirtualFile file = pluginXml.getVirtualFile(); - final ReadonlyStatusHandler readonlyStatusHandler = ReadonlyStatusHandler.getInstance(klass.getProject()); - final ReadonlyStatusHandler.OperationStatus status = readonlyStatusHandler.ensureFilesWritable(files); + final ReadonlyStatusHandler readonlyStatusHandler = ReadonlyStatusHandler.getInstance(project); + final ReadonlyStatusHandler.OperationStatus status = readonlyStatusHandler.ensureFilesWritable(file); if (status.hasReadonlyFiles()) { - throw new IncorrectOperationException(DevKitBundle.message("error.plugin.xml.readonly")); + throw new IncorrectOperationException(DevKitBundle.message("error.plugin.xml.readonly", status.getReadonlyFiles()[0])); } - - WriteAction.run((ThrowableRunnable)() -> { - for (XmlFile pluginXml : pluginXmls) { - patcher.patchPluginXml(pluginXml, klass); - } - }); } @Nullable diff --git a/plugins/devkit/testData/actions/newService/META-INF/plugin.xml b/plugins/devkit/testData/actions/newService/META-INF/plugin.xml new file mode 100644 index 000000000000..1da555c15e7b --- /dev/null +++ b/plugins/devkit/testData/actions/newService/META-INF/plugin.xml @@ -0,0 +1,7 @@ + + test.plugin + Test Plugin + 1.0 + + + \ No newline at end of file diff --git a/plugins/devkit/testSources/actions/service/ServiceCreatorTest.java b/plugins/devkit/testSources/actions/service/ServiceCreatorTest.java new file mode 100644 index 000000000000..f21c1f8d1bb1 --- /dev/null +++ b/plugins/devkit/testSources/actions/service/ServiceCreatorTest.java @@ -0,0 +1,154 @@ +/* + * 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 org.jetbrains.idea.devkit.actions.service; + +import com.intellij.openapi.application.PluginPathManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiJavaCodeReferenceElement; +import com.intellij.psi.PsiReferenceList; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; +import com.intellij.testFramework.TestDataPath; +import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase; +import com.intellij.util.xml.DomFileElement; +import com.intellij.util.xml.DomManager; +import org.jetbrains.idea.devkit.dom.Extensions; +import org.jetbrains.idea.devkit.dom.IdeaPlugin; +import org.jetbrains.idea.devkit.module.PluginModuleType; + +import java.util.List; + +@TestDataPath("$CONTENT_ROOT/testData/actions/newService/") +public class ServiceCreatorTest extends JavaCodeInsightFixtureTestCase { + @Override + protected String getBasePath() { + return PluginPathManager.getPluginHomePathRelative("devkit") + "/testData/actions/newService"; + } + + + public void testCreateApplicationServiceInterfaceAndImplementation() { + doTestCreateInterfaceAndImplementation("my.plugin.ApplicationServiceInterface", "my.plugin.impl.ApplicationServiceImpl", + "ApplicationServiceInterface.java", "ApplicationServiceImplementation.java", + "applicationService"); + } + + public void testCreateProjectServiceInterfaceAndImplementation() { + doTestCreateInterfaceAndImplementation("my.plugin.ProjectServiceInterface", "my.plugin.impl.ProjectServiceImpl", + "ProjectServiceInterface.java", "ProjectServiceImplementation.java", "projectService"); + } + + public void testCreateModuleServiceInterfaceAndImplementation() { + doTestCreateInterfaceAndImplementation("my.plugin.ModuleServiceInterface", "my.plugin.impl.ModuleServiceImpl", + "ModuleServiceInterface.java", "ModuleServiceImplementation.java", "moduleService"); + } + + public void testCreateApplicationServiceOnlyImplementation() { + doTestCreateOnlyImplementation("my.plugin.ApplicationServiceClass", "ApplicationServiceClass.java", "applicationService"); + } + + public void testCreateProjectServiceOnlyImplementation() { + doTestCreateOnlyImplementation("my.plugin.ProjectServiceClass", "ProjectServiceClass.java", "projectService"); + } + + public void testCreateModuleServiceOnlyImplementation() { + doTestCreateOnlyImplementation("my.plugin.ModuleServiceClass", "ModuleServiceClass.java", "moduleService"); + } + + + private void doTestCreateInterfaceAndImplementation(String interfaceFqName, String implementationFqName, + String interfaceTemplate, String implementationTemplate, String tagName) { + VirtualFile copied = myFixture.copyDirectoryToProject("", ""); + PsiDirectory dir = myFixture.getPsiManager().findDirectory(copied); + XmlFile pluginXml = PluginModuleType.getPluginXml(myFixture.getModule()); + + NewServiceActionBase.ServiceCreator creator = new NewServiceActionBase.ServiceCreator( + dir, interfaceTemplate, implementationTemplate, null, tagName); + boolean created = creator.createInterfaceAndImplementation(interfaceFqName, implementationFqName, pluginXml); + assertTrue(created); + + PsiClass[] createdClasses = creator.getCreatedClasses(); + assertNotNull(createdClasses); + assertSize(2, createdClasses); + + PsiClass createdInterface = createdClasses[0]; + PsiClass createdImplementation = createdClasses[1]; + + assertEquals(interfaceFqName.substring(interfaceFqName.lastIndexOf(".") + 1), createdInterface.getName()); + assertEquals(implementationFqName.substring(implementationFqName.lastIndexOf(".") + 1), createdImplementation.getName()); + + PsiReferenceList implementsList = createdImplementation.getImplementsList(); + assertNotNull(implementsList); + PsiJavaCodeReferenceElement[] elements = implementsList.getReferenceElements(); + assertNotNull(elements); + PsiJavaCodeReferenceElement element = elements[0]; + assertEquals(interfaceFqName, element.getQualifiedName()); + + DomFileElement fileElement = DomManager.getDomManager(getProject()).getFileElement(pluginXml, IdeaPlugin.class); + assertNotNull(fileElement); + IdeaPlugin ideaPlugin = fileElement.getRootElement(); + List extensionsList = ideaPlugin.getExtensions(); + assertNotNull(extensionsList); + assertEquals(1, extensionsList.size()); + + XmlTag extensions = extensionsList.get(0).getXmlTag(); + assertNotNull(extensions); + XmlTag[] extensionTags = extensions.getSubTags(); + assertNotNull(extensionTags); + assertSize(1, extensionTags); + + XmlTag serviceTag = extensionTags[0]; + assertEquals(tagName, serviceTag.getName()); + assertEquals(interfaceFqName, serviceTag.getAttributeValue("serviceInterface")); + assertEquals(implementationFqName, serviceTag.getAttributeValue("serviceImplementation")); + } + + private void doTestCreateOnlyImplementation(String implementationFqName, String classTemplate, String tagName) { + VirtualFile copied = myFixture.copyDirectoryToProject("", ""); + PsiDirectory dir = myFixture.getPsiManager().findDirectory(copied); + XmlFile pluginXml = PluginModuleType.getPluginXml(myFixture.getModule()); + + NewServiceActionBase.ServiceCreator creator = new NewServiceActionBase.ServiceCreator(dir, null, null, classTemplate, tagName); + boolean created = creator.createOnlyImplementation(implementationFqName, pluginXml); + assertTrue(created); + + PsiClass[] createdClasses = creator.getCreatedClasses(); + assertNotNull(createdClasses); + assertSize(1, createdClasses); + + PsiClass createdImplementation = createdClasses[0]; + assertEquals(implementationFqName.substring(implementationFqName.lastIndexOf(".") + 1), createdImplementation.getName()); + + DomFileElement fileElement = DomManager.getDomManager(getProject()).getFileElement(pluginXml, IdeaPlugin.class); + assertNotNull(fileElement); + IdeaPlugin ideaPlugin = fileElement.getRootElement(); + List extensionsList = ideaPlugin.getExtensions(); + assertNotNull(extensionsList); + assertEquals(1, extensionsList.size()); + + XmlTag extensions = extensionsList.get(0).getXmlTag(); + assertNotNull(extensions); + XmlTag[] extensionTags = extensions.getSubTags(); + assertNotNull(extensionTags); + assertSize(1, extensionTags); + + XmlTag serviceTag = extensionTags[0]; + assertEquals(tagName, serviceTag.getName()); + assertNull(serviceTag.getAttributeValue("serviceInterface")); + assertEquals(implementationFqName, serviceTag.getAttributeValue("serviceImplementation")); + } +}