diff --git a/.idea/.gitignore b/.idea/.gitignore
index 13566b8..2a1e71a 100644
--- a/.idea/.gitignore
+++ b/.idea/.gitignore
@@ -6,3 +6,6 @@
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+
+/checkstyle-idea.xml
+/git_toolbox_blame.xml
diff --git a/metadata/changelog.html b/metadata/changelog.html
index a795038..c8a957a 100644
--- a/metadata/changelog.html
+++ b/metadata/changelog.html
@@ -5,6 +5,10 @@
Changelog
+1.0.3
+
+ - support setting of module SDK
+
1.0.2
- support intellij 2024.3
diff --git a/src/main/java/de/gebit/plugins/autoconfig/AutoconfigStartup.java b/src/main/java/de/gebit/plugins/autoconfig/AutoconfigStartup.java
index 769ab4f..f676f37 100644
--- a/src/main/java/de/gebit/plugins/autoconfig/AutoconfigStartup.java
+++ b/src/main/java/de/gebit/plugins/autoconfig/AutoconfigStartup.java
@@ -8,72 +8,24 @@
package de.gebit.plugins.autoconfig;
-import com.intellij.ide.impl.TrustedProjects;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.ProjectActivity;
+import de.gebit.plugins.autoconfig.service.ConfigurationUpdaterService;
import kotlin.Unit;
import kotlin.coroutines.Continuation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-
-import static de.gebit.plugins.autoconfig.util.Notifications.showInfo;
-
/**
- * Entry point for the opening of a project. The yaml configuration file is read here, the resulting configuration options object is passed to the
- * CommonConfigurationHandler. At the end, a message is composed, displaying a list of all updated configuration options.
+ * Entry point for the opening of a project. The yaml configuration file is read here, the resulting configuration
+ * options object is passed to the CommonConfigurationHandler. At the end, a message is composed, displaying a list of
+ * all updated configuration options.
*/
public class AutoconfigStartup implements ProjectActivity {
- private static final com.intellij.openapi.diagnostic.Logger LOG = Logger.getInstance(AutoconfigStartup.class);
-
- public static final ExtensionPointName>
- EP_NAME = ExtensionPointName.create("de.gebit.plugins.autoconfig.configurationUpdater");
@Nullable
@Override
public Object execute(@NotNull Project project, @NotNull Continuation super Unit> continuation) {
- return runAutoconfig(project);
- }
-
- @SuppressWarnings("UnstableApiUsage")
- public @Nullable List runAutoconfig(@NotNull Project project) {
- ConfigurationLoaderService projectService = project.getService(ConfigurationLoaderService.class);
- if (projectService == null || !projectService.hasAutoconfigDir()) {
- return null;
- }
-
- if (!TrustedProjects.isTrusted(project)) {
- showInfo("Project configuration has not been updated, because project was opened in safe mode.", project);
- return null;
- }
-
- List changedConfigs = new ArrayList<>();
-
- for (UpdateHandler> updateHandler : EP_NAME.getExtensionList()) {
- changedConfigs.addAll(processUpdateHandler(project, updateHandler, projectService));
- }
-
- if (!changedConfigs.isEmpty()) {
- String notification = String.join(", ", changedConfigs);
- showInfo("New project configurations applied: " + notification, project);
- }
-
- return changedConfigs;
- }
-
- private List processUpdateHandler(@NotNull Project project, UpdateHandler updateHandler, ConfigurationLoaderService projectService) {
- Optional extensionConfiguration = projectService.getConfiguration(updateHandler.getConfigurationClass(), updateHandler.getFileName());
- if (extensionConfiguration.isPresent()) {
- return updateHandler.updateConfiguration(extensionConfiguration.get(), project);
- } else {
- LOG.info("No configuration for " + updateHandler.getUpdaterName() + " found.");
- return Collections.emptyList();
- }
+ return project.getService(ConfigurationUpdaterService.class).runAutoconfig();
}
}
diff --git a/src/main/java/de/gebit/plugins/autoconfig/ConfigurationLoaderService.java b/src/main/java/de/gebit/plugins/autoconfig/ConfigurationLoaderService.java
deleted file mode 100644
index fbb1de4..0000000
--- a/src/main/java/de/gebit/plugins/autoconfig/ConfigurationLoaderService.java
+++ /dev/null
@@ -1,96 +0,0 @@
-//
-// ConfigurationLoaderService.java
-//
-// Copyright (C) 2024
-// GEBIT Solutions GmbH,
-// Berlin, Duesseldorf, Stuttgart, Leipzig (Germany)
-// All rights reserved.
-
-package de.gebit.plugins.autoconfig;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
-import com.intellij.openapi.application.ReadAction;
-import com.intellij.openapi.components.Service;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.openapi.vfs.VirtualFileManager;
-import com.intellij.project.ProjectKt;
-import de.gebit.plugins.autoconfig.util.Notifications;
-
-import java.io.IOException;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-
-/**
- * Service used to load yaml files from project directory.
- */
-@Service(Service.Level.PROJECT)
-public final class ConfigurationLoaderService {
- /**
- * Configuration directory containing yaml sources.
- */
- public static final String AUTOCONFIG_DIRECTORY = "autoconfig";
-
- private final Project project;
-
- private final Map configurationOptions = new HashMap<>();
-
- ConfigurationLoaderService(Project project) {
- this.project = project;
- }
-
- public Optional getConfiguration(Class objectClass, String configFileName) {
- T configObject = null;
- Object o = configurationOptions.get(objectClass.getCanonicalName());
- if (o != null && o.getClass().isAssignableFrom(objectClass)) {
- //noinspection unchecked
- configObject = (T) o;
- }
- if (configObject != null) {
- return Optional.of(configObject);
- }
- Optional autoconfigDirectory = getConfigDirectory();
- if (autoconfigDirectory.isEmpty()) {
- return Optional.empty();
- }
- var configYaml = autoconfigDirectory.get().findChild(configFileName);
- if (configYaml == null) {
- return Optional.empty();
- }
- try {
- configObject = ReadAction.compute(() -> {
- final ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
- return objectMapper.readValue(configYaml.getInputStream(), objectClass);
- });
- configurationOptions.put(objectClass.getCanonicalName(), configObject);
- } catch (IOException e) {
- Notifications.showInfo("Unable to parse configuration yaml: " + configFileName, project);
- }
- return Optional.ofNullable(configObject);
- }
-
- public boolean hasAutoconfigDir() {
- return getConfigDirectory().isPresent();
- }
-
- private Optional getConfigDirectory() {
- var projectFile = project.getProjectFile();
- if (projectFile == null) {
- // Fallback to find autoconfig directory. May happen when project is first opened and no misc.xml can be found
- Path directoryStorePath = ProjectKt.getStateStore(project).getDirectoryStorePath();
- if (directoryStorePath != null) {
- return Optional.ofNullable(VirtualFileManager.getInstance().findFileByNioPath(directoryStorePath))
- .map(m -> m.findChild(AUTOCONFIG_DIRECTORY));
- }
- return Optional.empty();
- }
- return Optional.ofNullable(projectFile.getParent().findChild(AUTOCONFIG_DIRECTORY));
- }
-
- public void resetConfigurationCache() {
- configurationOptions.clear();
- }
-}
diff --git a/src/main/java/de/gebit/plugins/autoconfig/UpdateHandler.java b/src/main/java/de/gebit/plugins/autoconfig/UpdateHandler.java
index 6bb9971..3726579 100644
--- a/src/main/java/de/gebit/plugins/autoconfig/UpdateHandler.java
+++ b/src/main/java/de/gebit/plugins/autoconfig/UpdateHandler.java
@@ -15,43 +15,20 @@
/**
* Extension point for used to supply configuration update handlers.
*/
-public interface UpdateHandler {
+public interface UpdateHandler extends de.gebit.plugins.autoconfig.UpdateSettings {
/**
- * Provide the file name that is expected to contain the extensions' configuration.
- *
- * @return the file name that is expected to contain the extensions' configuration
- */
- String getFileName();
-
- /**
- * The json schema containing/providing the information on how to write the configuration file.
- *
- * @return json schema containing/providing the information on how to write the configuration file
- */
- String getJsonSchema();
-
- /**
- * The updater name is used for logging purposes and as a name for the json schema displayed in IntelliJ status bar.
- *
- * @return the name of this updater
- */
- String getUpdaterName();
-
- /**
- * The configuration object class used to read/deserialize the configuration file.
- *
- * @return configuration object class used to read/deserialize the configuration file
- */
- Class getConfigurationClass();
-
- /**
- * The implementation of the configuration updates. A configuration update object is supplied containing the information gathered from the yaml
- * file.
+ * The implementation of the configuration updates. A configuration update object is supplied containing the
+ * information gathered from the yaml file.
*
* @param configuration the configuration object used for this update handler
* @param project the project that will receive the configuration updates in case they can be applied
* @return list of configuration parts that have been updated
*/
List updateConfiguration(T configuration, Project project);
+
+ @Override
+ default UpdateTarget getUpdateTarget() {
+ return UpdateTarget.PROJECT;
+ }
}
diff --git a/src/main/java/de/gebit/plugins/autoconfig/UpdateModuleHandler.java b/src/main/java/de/gebit/plugins/autoconfig/UpdateModuleHandler.java
new file mode 100644
index 0000000..544d4fb
--- /dev/null
+++ b/src/main/java/de/gebit/plugins/autoconfig/UpdateModuleHandler.java
@@ -0,0 +1,39 @@
+package de.gebit.plugins.autoconfig;
+
+
+import com.intellij.openapi.module.Module;
+
+import java.util.List;
+
+/**
+ * Extension point for used to supply module configuration update handlers.
+ */
+public interface UpdateModuleHandler extends UpdateSettings {
+
+ /**
+ * Implementing handlers should carefully select the modules in which to apply any settings changes.
+ *
+ * @param configuration the configuration object used for this update handler
+ * @param module module to accept or deny
+ * @return whether the given module should be accepted for this module handler, default is {@code false} (no changes
+ * will be applied)
+ */
+ default boolean acceptModule(T configuration, Module module) {
+ return false;
+ }
+
+ /**
+ * The implementation of the configuration updates. A configuration update object is supplied containing the
+ * information gathered from the yaml file.
+ *
+ * @param configuration the configuration object used for this update handler
+ * @param module the module that will receive the configuration updates in case they can be applied
+ * @return list of configuration parts that have been updated
+ */
+ List updateConfiguration(T configuration, Module module);
+
+ @Override
+ default UpdateTarget getUpdateTarget() {
+ return UpdateTarget.MODULE;
+ }
+}
diff --git a/src/main/java/de/gebit/plugins/autoconfig/UpdateSettings.java b/src/main/java/de/gebit/plugins/autoconfig/UpdateSettings.java
new file mode 100644
index 0000000..84b772b
--- /dev/null
+++ b/src/main/java/de/gebit/plugins/autoconfig/UpdateSettings.java
@@ -0,0 +1,43 @@
+package de.gebit.plugins.autoconfig;
+
+
+/**
+ * Common interface for update handlers. Meta information about the handler is returned from the given methods.
+ */
+public interface UpdateSettings {
+ /**
+ * Provide the file name that is expected to contain the extensions' configuration.
+ *
+ * @return the file name that is expected to contain the extensions' configuration
+ */
+ String getFileName();
+
+ /**
+ * The json schema containing/providing the information on how to write the configuration file.
+ *
+ * @return json schema containing/providing the information on how to write the configuration file
+ */
+ String getJsonSchema();
+
+ /**
+ * The updater name is used for logging purposes and as a name for the json schema displayed in IntelliJ status
+ * bar.
+ *
+ * @return the name of this updater
+ */
+ String getUpdaterName();
+
+ /**
+ * The configuration object class used to read/deserialize the configuration file.
+ *
+ * @return configuration object class used to read/deserialize the configuration file
+ */
+ Class getConfigurationClass();
+
+ /**
+ * Specifies, whether modules or projects are target for this handler object.
+ *
+ * @return whether modules or projects are target for this handler object
+ */
+ UpdateTarget getUpdateTarget();
+}
diff --git a/src/main/java/de/gebit/plugins/autoconfig/UpdateTarget.java b/src/main/java/de/gebit/plugins/autoconfig/UpdateTarget.java
new file mode 100644
index 0000000..da63546
--- /dev/null
+++ b/src/main/java/de/gebit/plugins/autoconfig/UpdateTarget.java
@@ -0,0 +1,9 @@
+package de.gebit.plugins.autoconfig;
+
+
+/**
+ * Specifies, whether projects or modules are target of configuration updates.
+ */
+public enum UpdateTarget {
+ PROJECT, MODULE
+}
diff --git a/src/main/java/de/gebit/plugins/autoconfig/actions/CreateAutoconfigFileAction.java b/src/main/java/de/gebit/plugins/autoconfig/actions/CreateAutoconfigFileAction.java
index 2ee1a05..d3ff28c 100644
--- a/src/main/java/de/gebit/plugins/autoconfig/actions/CreateAutoconfigFileAction.java
+++ b/src/main/java/de/gebit/plugins/autoconfig/actions/CreateAutoconfigFileAction.java
@@ -6,20 +6,25 @@
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.actionSystem.LangDataKeys;
-import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
-import de.gebit.plugins.autoconfig.UpdateHandler;
+import de.gebit.plugins.autoconfig.UpdateSettings;
+import de.gebit.plugins.autoconfig.UpdateTarget;
import de.gebit.plugins.autoconfig.create.CreateAutoconfigFileDialog;
+import de.gebit.plugins.autoconfig.service.ConfigurationDirectoryService;
import org.jetbrains.annotations.NotNull;
-import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
import java.util.Optional;
/**
@@ -32,53 +37,55 @@ public class CreateAutoconfigFileAction extends CreateInDirectoryActionBase {
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = e.getProject();
if (project != null) {
- CreateAutoconfigFileDialog autoconfigFileDialog = new CreateAutoconfigFileDialog();
+ Module[] modules = ModuleManager.getInstance(project).getModules();
+ List moduleList = Arrays.stream(modules).toList();
+ CreateAutoconfigFileDialog autoconfigFileDialog = new CreateAutoconfigFileDialog(moduleList);
if (!autoconfigFileDialog.showAndGet()) {
// the dialog has been canceled
return;
}
// the update handler selected in the dialogs form
- Optional> optionalUpdateHandler = autoconfigFileDialog.getSelectedHandler();
- if (optionalUpdateHandler.isEmpty()) {
+ Optional> optionalUpdateSettings = autoconfigFileDialog.getSelectedSettings();
+ if (optionalUpdateSettings.isEmpty()) {
return;
}
- UpdateHandler> updateHandler = optionalUpdateHandler.get();
- VirtualFile projectFile = project.getProjectFile();
- if (projectFile == null) {
- LOGGER.warn("No project file? This is unusual. We can't automatically create a new Autoconfig file then. Please create it yourself.");
- return;
+
+ UpdateSettings> updateSettings = optionalUpdateSettings.get();
+ ConfigurationDirectoryService configurationDirectoryService = project.getService(
+ ConfigurationDirectoryService.class);
+ Optional updateSettingsFile;
+
+ // try to find a file for either a project...
+ if (updateSettings.getUpdateTarget().equals(UpdateTarget.PROJECT)) {
+ updateSettingsFile = configurationDirectoryService.findOrCreateProjectAutoconfigFile(
+ updateSettings.getFileName());
+ } else {
+ // ... or a module
+ Optional selectedModule = autoconfigFileDialog.getSelectedModule();
+ updateSettingsFile = selectedModule.flatMap(
+ module -> configurationDirectoryService.findOrCreateModuleAutoconfigFile(module,
+ updateSettings.getFileName()));
}
- try {
- VirtualFile updateHandlerFile = WriteAction.compute(() -> {
- VirtualFile autoconfigDirectory = projectFile.getParent().findChild("autoconfig");
- if (autoconfigDirectory == null) {
- // no ".idea/autoconfig" directory found. We're going to create it.
- autoconfigDirectory = projectFile.getParent().createChildDirectory(this, "autoconfig");
- }
- return autoconfigDirectory.findOrCreateChildData(this, updateHandler.getFileName());
- });
- if (updateHandlerFile != null) {
- // we open the file
- OpenFileAction.openFile(updateHandlerFile, project);
- Document document = FileDocumentManager.getInstance().getDocument(updateHandlerFile);
- if (document != null) {
- PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document);
- IdeView ideView = e.getData(LangDataKeys.IDE_VIEW);
- if (psiFile != null && ideView != null) {
- // and then focus the project/file view on the file
- ideView.selectElement(psiFile);
- }
+
+ updateSettingsFile.ifPresentOrElse(file -> {
+ // we open the file
+ OpenFileAction.openFile(file, project);
+ Document document = FileDocumentManager.getInstance().getDocument(file);
+ if (document != null) {
+ PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document);
+ IdeView ideView = e.getData(LangDataKeys.IDE_VIEW);
+ if (psiFile != null && ideView != null) {
+ // and then focus the project/file view on the file
+ ideView.selectElement(psiFile);
}
- ActionManager actionManager = ActionManager.getInstance();
- AnAction codeCompletion = actionManager.getAction("CodeCompletion");
- // and then we invoke code-completion on the possibly newly created opened file.
- // now=false because otherwise it might get executed before other IDE actions are done,
- // which would result in it losing its focus right away
- actionManager.tryToExecute(codeCompletion, null, null, null, false);
}
- } catch (IOException ex) {
- LOGGER.error("Couldn't create Autoconfig file!", ex);
- }
+ ActionManager actionManager = ActionManager.getInstance();
+ AnAction codeCompletion = actionManager.getAction(IdeActions.ACTION_CODE_COMPLETION);
+ // and then we invoke code-completion on the possibly newly created opened file.
+ // now=false because otherwise it might get executed before other IDE actions are done,
+ // which would result in it losing its focus right away
+ actionManager.tryToExecute(codeCompletion, null, null, null, false);
+ }, () -> LOGGER.error("Unable to create autoconfig file: ", updateSettings.getFileName()));
}
}
}
diff --git a/src/main/java/de/gebit/plugins/autoconfig/actions/RunAutoconfigAction.java b/src/main/java/de/gebit/plugins/autoconfig/actions/RunAutoconfigAction.java
index 36c1261..db3b73a 100644
--- a/src/main/java/de/gebit/plugins/autoconfig/actions/RunAutoconfigAction.java
+++ b/src/main/java/de/gebit/plugins/autoconfig/actions/RunAutoconfigAction.java
@@ -5,8 +5,7 @@
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
-import de.gebit.plugins.autoconfig.AutoconfigStartup;
-import de.gebit.plugins.autoconfig.ConfigurationLoaderService;
+import de.gebit.plugins.autoconfig.service.ConfigurationUpdaterService;
import de.gebit.plugins.autoconfig.util.Notifications;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -25,16 +24,11 @@ public void actionPerformed(@NotNull AnActionEvent e) {
return;
}
- ConfigurationLoaderService configurationLoaderService = project.getService(ConfigurationLoaderService.class);
- if (configurationLoaderService != null) {
- configurationLoaderService.resetConfigurationCache();
- }
-
// save currently open and unsaved config files
saveUnsavedAutoconfigs();
// perform changes
- List changedSettings = new AutoconfigStartup().runAutoconfig(project);
+ List changedSettings = project.getService(ConfigurationUpdaterService.class).runAutoconfig();
// notify user if no changes were necessary
if (changedSettings == null || changedSettings.isEmpty()) {
diff --git a/src/main/java/de/gebit/plugins/autoconfig/create/CreateAutoconfigFileDialog.java b/src/main/java/de/gebit/plugins/autoconfig/create/CreateAutoconfigFileDialog.java
index ca456db..998a4c1 100644
--- a/src/main/java/de/gebit/plugins/autoconfig/create/CreateAutoconfigFileDialog.java
+++ b/src/main/java/de/gebit/plugins/autoconfig/create/CreateAutoconfigFileDialog.java
@@ -1,11 +1,15 @@
package de.gebit.plugins.autoconfig.create;
+import com.intellij.openapi.module.Module;
import com.intellij.openapi.ui.DialogWrapper;
-import de.gebit.plugins.autoconfig.AutoconfigStartup;
-import de.gebit.plugins.autoconfig.UpdateHandler;
+import de.gebit.plugins.autoconfig.UpdateSettings;
+import de.gebit.plugins.autoconfig.messages.AutoconfigBundle;
+import de.gebit.plugins.autoconfig.service.ConfigurationUpdaterService;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Optional;
/**
@@ -13,21 +17,30 @@
*/
public class CreateAutoconfigFileDialog extends DialogWrapper {
+ private final List moduleList;
private CreateAutoconfigFileForm autoconfigFileForm;
- public CreateAutoconfigFileDialog() {
+ public CreateAutoconfigFileDialog(List moduleList) {
super(false);
+ this.moduleList = moduleList;
init();
- setTitle("Create Autoconfig File");
+ setTitle(AutoconfigBundle.message("createautoconfigfile.title"));
}
@Override
protected @Nullable JComponent createCenterPanel() {
- autoconfigFileForm = new CreateAutoconfigFileForm(AutoconfigStartup.EP_NAME.getExtensionList());
+ List> settings = new ArrayList<>(
+ ConfigurationUpdaterService.PROJECT_EP_NAME.getExtensionList());
+ settings.addAll(ConfigurationUpdaterService.MODULE_EP_NAME.getExtensionList());
+ autoconfigFileForm = new CreateAutoconfigFileForm(settings, moduleList);
return autoconfigFileForm.getForm();
}
- public Optional> getSelectedHandler() {
- return autoconfigFileForm.getSelectedHandler();
+ public Optional> getSelectedSettings() {
+ return autoconfigFileForm.getSelectedSettings();
+ }
+
+ public Optional getSelectedModule() {
+ return autoconfigFileForm.getSelectedModule();
}
}
diff --git a/src/main/java/de/gebit/plugins/autoconfig/create/CreateAutoconfigFileForm.form b/src/main/java/de/gebit/plugins/autoconfig/create/CreateAutoconfigFileForm.form
index ea4c9dc..e2e21d6 100644
--- a/src/main/java/de/gebit/plugins/autoconfig/create/CreateAutoconfigFileForm.form
+++ b/src/main/java/de/gebit/plugins/autoconfig/create/CreateAutoconfigFileForm.form
@@ -1,32 +1,59 @@
diff --git a/src/main/java/de/gebit/plugins/autoconfig/create/CreateAutoconfigFileForm.java b/src/main/java/de/gebit/plugins/autoconfig/create/CreateAutoconfigFileForm.java
index 92fe00d..7881136 100644
--- a/src/main/java/de/gebit/plugins/autoconfig/create/CreateAutoconfigFileForm.java
+++ b/src/main/java/de/gebit/plugins/autoconfig/create/CreateAutoconfigFileForm.java
@@ -1,28 +1,56 @@
package de.gebit.plugins.autoconfig.create;
+import com.intellij.openapi.module.Module;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import com.intellij.uiDesigner.core.Spacer;
import de.gebit.plugins.autoconfig.UpdateHandler;
+import de.gebit.plugins.autoconfig.UpdateSettings;
+import de.gebit.plugins.autoconfig.UpdateTarget;
+import de.gebit.plugins.autoconfig.messages.AutoconfigBundle;
import lombok.Getter;
import javax.swing.*;
import java.awt.*;
+import java.lang.reflect.Method;
+import java.util.Comparator;
import java.util.List;
import java.util.Optional;
+import java.util.ResourceBundle;
/**
* Form displaying a dropdown with {@link UpdateHandler}s to choose from
*/
public class CreateAutoconfigFileForm {
- private JComboBox handlerSelection;
+ private JComboBox settingsSelection;
@Getter
private JPanel form;
+ private JComboBox moduleSelection;
- public CreateAutoconfigFileForm(List> handlers) {
- ComboBoxModel comboBoxModel = new DefaultComboBoxModel<>(handlers.stream().map(HandlerComboBoxWrapper::new).toArray(HandlerComboBoxWrapper[]::new));
- handlerSelection.setModel(comboBoxModel);
+ public CreateAutoconfigFileForm(List> handlers, List moduleList) {
+ SettingsComboBoxWrapper[] comboBoxWrappers = handlers.stream()
+ .sorted(Comparator.comparing(UpdateSettings::getUpdaterName))
+ .map(SettingsComboBoxWrapper::new)
+ .toArray(SettingsComboBoxWrapper[]::new);
+ ModuleComboBoxWrapper[] modules = moduleList.stream()
+ .sorted(Comparator.comparing(Module::getName))
+ .map(ModuleComboBoxWrapper::new)
+ .toArray(ModuleComboBoxWrapper[]::new);
+ moduleSelection.setModel(new DefaultComboBoxModel<>(modules));
+ settingsSelection.setModel(new DefaultComboBoxModel<>(comboBoxWrappers));
+ settingsSelection.addActionListener(event -> {
+ if (event.getSource() instanceof JComboBox> comboBox && comboBox.getSelectedItem() instanceof SettingsComboBoxWrapper selectedItem) {
+ updateModuleSelectionModel(selectedItem);
+ }
+ });
+ if (comboBoxWrappers.length > 0) {
+ updateModuleSelectionModel(comboBoxWrappers[0]);
+ }
+ }
+
+ private void updateModuleSelectionModel(SettingsComboBoxWrapper selectedSetting) {
+ moduleSelection.setEnabled(selectedSetting.handler().getUpdateTarget().equals(UpdateTarget.MODULE));
}
{
@@ -33,20 +61,95 @@ public CreateAutoconfigFileForm(List> handlers) {
}
/**
- * Method generated by IntelliJ IDEA GUI Designer >>> IMPORTANT!! <<< DO NOT edit this method OR call it in your code!
+ * Method generated by IntelliJ IDEA GUI Designer >>> IMPORTANT!! <<< DO NOT edit this method OR call it in your
+ * code!
*
* @noinspection ALL
*/
private void $$$setupUI$$$() {
form = new JPanel();
- form.setLayout(new GridLayoutManager(3, 1, new Insets(0, 0, 0, 0), -1, -1));
- handlerSelection = new JComboBox();
- form.add(handlerSelection, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+ form.setLayout(new GridLayoutManager(4, 2, new Insets(0, 0, 0, 0), -1, -1));
+ settingsSelection = new JComboBox();
+ form.add(settingsSelection,
+ new GridConstraints(1, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,
+ GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0,
+ false));
final JLabel label1 = new JLabel();
- label1.setText("Select which Plugin to create the Autoconfig File for");
- form.add(label1, new GridConstraints(0, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+ this.$$$loadLabelText$$$(label1,
+ this.$$$getMessageFromBundle$$$("de/gebit/plugins/autoconfig/messages/AutoconfigBundle",
+ "createautoconfigfile.selectplugin"));
+ form.add(label1, new GridConstraints(0, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,
+ GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
final Spacer spacer1 = new Spacer();
- form.add(spacer1, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1, GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
+ form.add(spacer1,
+ new GridConstraints(3, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_VERTICAL, 1,
+ GridConstraints.SIZEPOLICY_WANT_GROW, null, null, null, 0, false));
+ moduleSelection = new JComboBox();
+ moduleSelection.setEnabled(false);
+ form.add(moduleSelection,
+ new GridConstraints(2, 1, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_HORIZONTAL,
+ GridConstraints.SIZEPOLICY_CAN_GROW, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0,
+ false));
+ final JLabel label2 = new JLabel();
+ this.$$$loadLabelText$$$(label2,
+ this.$$$getMessageFromBundle$$$("de/gebit/plugins/autoconfig/messages/AutoconfigBundle",
+ "createautoconfigfile.selectfile"));
+ form.add(label2, new GridConstraints(1, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,
+ GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+ final JLabel label3 = new JLabel();
+ this.$$$loadLabelText$$$(label3,
+ this.$$$getMessageFromBundle$$$("de/gebit/plugins/autoconfig/messages/AutoconfigBundle",
+ "createautoconfigfile.selectmodule"));
+ form.add(label3, new GridConstraints(2, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE,
+ GridConstraints.SIZEPOLICY_FIXED, GridConstraints.SIZEPOLICY_FIXED, null, null, null, 0, false));
+ label2.setLabelFor(settingsSelection);
+ label3.setLabelFor(moduleSelection);
+ }
+
+ private static Method $$$cachedGetBundleMethod$$$ = null;
+
+ private String $$$getMessageFromBundle$$$(String path, String key) {
+ ResourceBundle bundle;
+ try {
+ Class> thisClass = this.getClass();
+ if ($$$cachedGetBundleMethod$$$ == null) {
+ Class> dynamicBundleClass = thisClass.getClassLoader().loadClass("com.intellij.DynamicBundle");
+ $$$cachedGetBundleMethod$$$ = dynamicBundleClass.getMethod("getBundle", String.class, Class.class);
+ }
+ bundle = (ResourceBundle) $$$cachedGetBundleMethod$$$.invoke(null, path, thisClass);
+ } catch (Exception e) {
+ bundle = ResourceBundle.getBundle(path);
+ }
+ return bundle.getString(key);
+ }
+
+ /**
+ * @noinspection ALL
+ */
+ private void $$$loadLabelText$$$(JLabel component, String text) {
+ StringBuffer result = new StringBuffer();
+ boolean haveMnemonic = false;
+ char mnemonic = '\0';
+ int mnemonicIndex = -1;
+ for (int i = 0; i < text.length(); i++) {
+ if (text.charAt(i) == '&') {
+ i++;
+ if (i == text.length()) {
+ break;
+ }
+ if (!haveMnemonic && text.charAt(i) != '&') {
+ haveMnemonic = true;
+ mnemonic = text.charAt(i);
+ mnemonicIndex = result.length();
+ }
+ }
+ result.append(text.charAt(i));
+ }
+ component.setText(result.toString());
+ if (haveMnemonic) {
+ component.setDisplayedMnemonic(mnemonic);
+ component.setDisplayedMnemonicIndex(mnemonicIndex);
+ }
}
/**
@@ -56,19 +159,34 @@ public CreateAutoconfigFileForm(List> handlers) {
return form;
}
- private record HandlerComboBoxWrapper(UpdateHandler> handler) {
+ private record SettingsComboBoxWrapper(UpdateSettings> handler) {
+ @Override
+ public String toString() {
+ return handler.getUpdaterName() + (handler.getUpdateTarget()
+ .equals(UpdateTarget.MODULE) ? " (" + AutoconfigBundle.message(
+ "createautoconfigfile.moduleaddition") + ")" : "");
+ }
+ }
+
+ private record ModuleComboBoxWrapper(Module module) {
@Override
public String toString() {
- return handler.getUpdaterName();
+ return module.getName();
}
}
- public Optional> getSelectedHandler() {
- if (handlerSelection.getSelectedItem() instanceof HandlerComboBoxWrapper handlerComboBoxWrapper) {
- return Optional.of(handlerComboBoxWrapper.handler());
+ public Optional> getSelectedSettings() {
+ if (settingsSelection.getSelectedItem() instanceof SettingsComboBoxWrapper settingsComboBoxWrapper) {
+ return Optional.of(settingsComboBoxWrapper.handler());
} else {
return Optional.empty();
}
}
+ public Optional getSelectedModule() {
+ if (moduleSelection.getSelectedItem() instanceof ModuleComboBoxWrapper moduleComboBoxWrapper) {
+ return Optional.of(moduleComboBoxWrapper.module());
+ }
+ return Optional.empty();
+ }
}
diff --git a/src/main/java/de/gebit/plugins/autoconfig/handlers/CommonConfigurationHandler.java b/src/main/java/de/gebit/plugins/autoconfig/handlers/common/CommonConfigurationHandler.java
similarity index 94%
rename from src/main/java/de/gebit/plugins/autoconfig/handlers/CommonConfigurationHandler.java
rename to src/main/java/de/gebit/plugins/autoconfig/handlers/common/CommonConfigurationHandler.java
index 31222eb..59f6394 100644
--- a/src/main/java/de/gebit/plugins/autoconfig/handlers/CommonConfigurationHandler.java
+++ b/src/main/java/de/gebit/plugins/autoconfig/handlers/common/CommonConfigurationHandler.java
@@ -6,7 +6,7 @@
// Berlin, Duesseldorf, Stuttgart, Leipzig (Germany)
// All rights reserved.
-package de.gebit.plugins.autoconfig.handlers;
+package de.gebit.plugins.autoconfig.handlers.common;
import com.intellij.codeInsight.actions.onSave.FormatOnSaveOptions;
import com.intellij.codeInsight.actions.onSave.FormatOnSaveOptionsBase;
@@ -16,12 +16,14 @@
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.externalSystem.autoimport.ExternalSystemProjectTrackerSettings;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkType;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.updateSettings.impl.UpdateSettings;
import com.intellij.openapi.vcs.IssueNavigationConfiguration;
import com.intellij.openapi.vcs.IssueNavigationLink;
import de.gebit.plugins.autoconfig.UpdateHandler;
+import de.gebit.plugins.autoconfig.handlers.AbstractHandler;
import de.gebit.plugins.autoconfig.model.Formatting;
import de.gebit.plugins.autoconfig.model.GeneralConfiguration;
import de.gebit.plugins.autoconfig.model.IssueNavigation;
@@ -129,14 +131,14 @@ private void applyOnSaveOptions(OnSave options, Project project, List up
private void applyProjectSDKOptions(ProjectSDK sdkOptions, Project project, List updatedConfigs) {
if (sdkOptions != null) {
- SdkType sdk = SdkType.findByName(sdkOptions.getType());
- if (sdk != null) {
- String projectSdk = JDKResolver.findProjectSdk(sdkOptions.getName(), project);
+ SdkType sdkType = SdkType.findByName(sdkOptions.getType());
+ if (sdkType != null) {
+ Sdk projectSdk = JDKResolver.findSdk(sdkOptions.getName(), project);
if (projectSdk != null) {
ProjectRootManager projectRootManager = ProjectRootManager.getInstance(project);
- applySetting(projectSdk, projectRootManager.getProjectSdkName(), sdkName -> WriteAction.runAndWait(
- () -> projectRootManager.setProjectSdkName(sdkName, sdkOptions.getType())), updatedConfigs,
- "Project SDK");
+ applySetting(projectSdk, projectRootManager.getProjectSdkName(),
+ sdkName -> WriteAction.runAndWait(() -> projectRootManager.setProjectSdk(projectSdk)),
+ updatedConfigs, "Project SDK");
}
} else {
Notifications.showWarning(
diff --git a/src/main/java/de/gebit/plugins/autoconfig/handlers/common/CommonModuleConfigurationHandler.java b/src/main/java/de/gebit/plugins/autoconfig/handlers/common/CommonModuleConfigurationHandler.java
new file mode 100644
index 0000000..910c030
--- /dev/null
+++ b/src/main/java/de/gebit/plugins/autoconfig/handlers/common/CommonModuleConfigurationHandler.java
@@ -0,0 +1,101 @@
+package de.gebit.plugins.autoconfig.handlers.common;
+
+
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.projectRoots.SdkType;
+import com.intellij.openapi.roots.ModifiableRootModel;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.roots.ProjectRootManager;
+import de.gebit.plugins.autoconfig.UpdateModuleHandler;
+import de.gebit.plugins.autoconfig.handlers.AbstractHandler;
+import de.gebit.plugins.autoconfig.model.GeneralModuleConfiguration;
+import de.gebit.plugins.autoconfig.model.ModuleSDK;
+import de.gebit.plugins.autoconfig.sdk.JDKResolver;
+import de.gebit.plugins.autoconfig.util.Notifications;
+import org.jetbrains.annotations.NonNls;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Allow setting module SDK for a limited number of matching modules.
+ */
+public class CommonModuleConfigurationHandler extends AbstractHandler implements UpdateModuleHandler {
+ private static final @NonNls String CONFIG_SCHEMA_JSON = "/schema/configModule.schema.json";
+
+ private static final @NonNls String CONFIG_FILE_NAME = "autoconfigModule.yaml";
+
+ private static final @NonNls Class CONFIGURATION_CLASS = GeneralModuleConfiguration.class;
+
+ @Override
+ public String getFileName() {
+ return CONFIG_FILE_NAME;
+ }
+
+ @Override
+ public String getJsonSchema() {
+ return CONFIG_SCHEMA_JSON;
+ }
+
+ @Override
+ public String getUpdaterName() {
+ return "Common module configuration updater";
+ }
+
+ @Override
+ public Class getConfigurationClass() {
+ return CONFIGURATION_CLASS;
+ }
+
+ @Override
+ public boolean acceptModule(GeneralModuleConfiguration configuration, Module module) {
+ return matchesAnyName(module, configuration.getModuleFilter());
+ }
+
+ private static boolean matchesAnyName(Module module, List patterns) {
+ return patterns.stream().anyMatch(p -> module.getName().matches(p));
+ }
+
+ @Override
+ public List updateConfiguration(GeneralModuleConfiguration configuration, Module module) {
+ List updatedConfigs = new ArrayList<>();
+ applyModuleSDKOptions(configuration.getModuleSDK(), module, updatedConfigs);
+ return updatedConfigs;
+ }
+
+ private void applyModuleSDKOptions(ModuleSDK sdkOptions, Module module, List updatedConfigs) {
+ if (sdkOptions != null) {
+ SdkType sdk = SdkType.findByName(sdkOptions.getType());
+ if (sdk != null) {
+ Sdk moduleSdk = JDKResolver.findSdk(sdkOptions.getName(), module.getProject());
+ if (moduleSdk != null) {
+ ProjectRootManager projectRootManager = ProjectRootManager.getInstance(module.getProject());
+ ModifiableRootModel modifiableModel = ModuleRootManager.getInstance(module).getModifiableModel();
+ Sdk currentModuleSdk = modifiableModel.getSdk();
+ Sdk projectSdk = projectRootManager.getProjectSdk();
+ if (projectSdk != null && projectSdk.equals(moduleSdk)) {
+ // reset module SDK in case project SDK and designated module SDK are identical
+ if (!modifiableModel.isSdkInherited()) {
+ WriteAction.runAndWait(() -> {
+ modifiableModel.inheritSdk();
+ modifiableModel.commit();
+ });
+ updatedConfigs.add("Module SDK");
+ }
+ } else {
+ applySetting(moduleSdk, currentModuleSdk, s -> WriteAction.runAndWait(() -> {
+ modifiableModel.setSdk(s);
+ modifiableModel.commit();
+ }), updatedConfigs, "Module SDK");
+ }
+ }
+ } else {
+ Notifications.showWarning(
+ "SDK type \"" + sdkOptions.getType() + "\" has not been found for auto configuration",
+ module.getProject());
+ }
+ }
+ }
+}
diff --git a/src/main/java/de/gebit/plugins/autoconfig/handlers/maven/MavenHandler.java b/src/main/java/de/gebit/plugins/autoconfig/handlers/maven/MavenHandler.java
index d3224c6..52b5d6f 100644
--- a/src/main/java/de/gebit/plugins/autoconfig/handlers/maven/MavenHandler.java
+++ b/src/main/java/de/gebit/plugins/autoconfig/handlers/maven/MavenHandler.java
@@ -10,6 +10,7 @@
import com.intellij.conversion.impl.ConversionContextImpl;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
import de.gebit.plugins.autoconfig.UpdateHandler;
import de.gebit.plugins.autoconfig.handlers.AbstractHandler;
import de.gebit.plugins.autoconfig.model.MavenConfiguration;
@@ -21,7 +22,7 @@
import java.util.ArrayList;
import java.util.List;
-import static de.gebit.plugins.autoconfig.sdk.JDKResolver.findProjectSdk;
+import static de.gebit.plugins.autoconfig.sdk.JDKResolver.findSdk;
/**
* Maven configuration update handler.
@@ -85,9 +86,11 @@ public List updateConfiguration(MavenConfiguration maven, Project projec
applySetting(mavenImportingConfig.getVmOptions(), mavenImportingProjectSettings.getVmOptionsForImporter(),
mavenImportingProjectSettings::setVmOptionsForImporter, changedConfigs,
"VM options for maven importer");
- applySetting(findProjectSdk(mavenImportingConfig.getJdk(), project),
- mavenImportingProjectSettings.getJdkForImporter(), mavenImportingProjectSettings::setJdkForImporter,
- changedConfigs, "JDK for importer");
+ Sdk sdk = findSdk(mavenImportingConfig.getJdk(), project);
+ if (sdk != null) {
+ applySetting(sdk.getName(), mavenImportingProjectSettings.getJdkForImporter(),
+ mavenImportingProjectSettings::setJdkForImporter, changedConfigs, "JDK for importer");
+ }
}
var mavenRunnerConfig = maven.getRunner();
@@ -95,8 +98,11 @@ public List updateConfiguration(MavenConfiguration maven, Project projec
var mavenRunnerProjectSettings = MavenRunner.getInstance(project).getState();
applySetting(mavenRunnerConfig.getVmOptions(), mavenRunnerProjectSettings.getVmOptions(),
mavenRunnerProjectSettings::setVmOptions, changedConfigs, "VM options for maven runner");
- applySetting(findProjectSdk(mavenRunnerConfig.getJre(), project), mavenRunnerProjectSettings.getJreName(),
- mavenRunnerProjectSettings::setJreName, changedConfigs, "JRE for runner");
+ Sdk sdk = findSdk(mavenRunnerConfig.getJre(), project);
+ if (sdk != null) {
+ applySetting(sdk.getName(), mavenRunnerProjectSettings.getJreName(),
+ mavenRunnerProjectSettings::setJreName, changedConfigs, "JRE for runner");
+ }
}
return changedConfigs;
diff --git a/src/main/java/de/gebit/plugins/autoconfig/json/ConfigurationJsonSchemeProviderFactory.java b/src/main/java/de/gebit/plugins/autoconfig/json/ConfigurationJsonSchemeProviderFactory.java
index 2cca74e..29c3a8b 100644
--- a/src/main/java/de/gebit/plugins/autoconfig/json/ConfigurationJsonSchemeProviderFactory.java
+++ b/src/main/java/de/gebit/plugins/autoconfig/json/ConfigurationJsonSchemeProviderFactory.java
@@ -11,15 +11,18 @@
import com.intellij.openapi.project.Project;
import com.jetbrains.jsonSchema.extension.JsonSchemaFileProvider;
import com.jetbrains.jsonSchema.extension.JsonSchemaProviderFactory;
-import de.gebit.plugins.autoconfig.AutoconfigStartup;
import de.gebit.plugins.autoconfig.UpdateHandler;
+import de.gebit.plugins.autoconfig.UpdateModuleHandler;
+import de.gebit.plugins.autoconfig.UpdateSettings;
+import de.gebit.plugins.autoconfig.service.ConfigurationUpdaterService;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
- * This class is invoked, when the configuration plugin starts up and will provide a json schema for YML and json files.
+ * This class is invoked, when the configuration plugin starts up and will provide a json schema for YML and json
+ * files.
*/
public class ConfigurationJsonSchemeProviderFactory implements JsonSchemaProviderFactory {
@@ -27,9 +30,17 @@ public class ConfigurationJsonSchemeProviderFactory implements JsonSchemaProvide
@Override
public List getProviders(@NotNull Project project) {
List schemaFileProviders = new ArrayList<>();
- for (UpdateHandler> updateHandler : AutoconfigStartup.EP_NAME.getExtensionList()) {
- schemaFileProviders.add(new SimpleJsonSchemeFileProvider(updateHandler.getUpdaterName(), updateHandler.getFileName(), updateHandler.getJsonSchema(), updateHandler.getConfigurationClass()));
+ for (UpdateHandler> updateHandler : ConfigurationUpdaterService.PROJECT_EP_NAME.getExtensionList()) {
+ schemaFileProviders.add(toSchemeFileProvider(updateHandler));
+ }
+ for (UpdateModuleHandler> updateHandler : ConfigurationUpdaterService.MODULE_EP_NAME.getExtensionList()) {
+ schemaFileProviders.add(toSchemeFileProvider(updateHandler));
}
return schemaFileProviders;
}
+
+ private static SimpleJsonSchemeFileProvider toSchemeFileProvider(UpdateSettings updateSettings) {
+ return new SimpleJsonSchemeFileProvider(updateSettings.getUpdaterName(), updateSettings.getFileName(),
+ updateSettings.getJsonSchema(), updateSettings.getConfigurationClass());
+ }
}
diff --git a/src/main/java/de/gebit/plugins/autoconfig/messages/AutoconfigBundle.java b/src/main/java/de/gebit/plugins/autoconfig/messages/AutoconfigBundle.java
new file mode 100644
index 0000000..1a7ca6e
--- /dev/null
+++ b/src/main/java/de/gebit/plugins/autoconfig/messages/AutoconfigBundle.java
@@ -0,0 +1,25 @@
+package de.gebit.plugins.autoconfig.messages;
+
+
+import com.intellij.DynamicBundle;
+import org.jetbrains.annotations.Nls;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.PropertyKey;
+
+/**
+ * Message bundle for autoconfig plugin.
+ */
+public class AutoconfigBundle {
+ public static final @NonNls String IDEA_ACTIONS_BUNDLE = "de.gebit.plugins.autoconfig.messages.AutoconfigBundle";
+
+ public static final DynamicBundle INSTANCE = new DynamicBundle(AutoconfigBundle.class, IDEA_ACTIONS_BUNDLE);
+
+ public AutoconfigBundle() {
+ // no initialisation necessary
+ }
+
+ public static @NotNull @Nls String message(@NotNull @PropertyKey(resourceBundle = IDEA_ACTIONS_BUNDLE) String key, Object @NotNull ... params) {
+ return INSTANCE.getMessage(key, params);
+ }
+}
diff --git a/src/main/java/de/gebit/plugins/autoconfig/plugins/AutoconfigUpdateSettingsProvider.java b/src/main/java/de/gebit/plugins/autoconfig/plugins/AutoconfigUpdateSettingsProvider.java
index 32f0d0c..a027fab 100644
--- a/src/main/java/de/gebit/plugins/autoconfig/plugins/AutoconfigUpdateSettingsProvider.java
+++ b/src/main/java/de/gebit/plugins/autoconfig/plugins/AutoconfigUpdateSettingsProvider.java
@@ -2,21 +2,25 @@
import com.intellij.ide.impl.ProjectUtilCore;
import com.intellij.ide.impl.TrustedProjects;
+import com.intellij.openapi.project.Project;
import com.intellij.openapi.updateSettings.impl.UpdateSettingsProvider;
-import de.gebit.plugins.autoconfig.ConfigurationLoaderService;
-import de.gebit.plugins.autoconfig.handlers.CommonConfigurationHandler;
+import com.intellij.openapi.vfs.VirtualFile;
+import de.gebit.plugins.autoconfig.handlers.common.CommonConfigurationHandler;
import de.gebit.plugins.autoconfig.model.GeneralConfiguration;
+import de.gebit.plugins.autoconfig.service.ConfigurationDirectoryService;
+import de.gebit.plugins.autoconfig.service.ConfigurationLoaderService;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
/**
- * "Safer" Plugin-Repository Provider which does not persist the configured plugin repository. This provider is used when checking for required
- * plugins and when checking for plugin updates.
+ * "Safer" Plugin-Repository Provider which does not persist the configured plugin repository. This provider is used
+ * when checking for required plugins and when checking for plugin updates.
*/
public class AutoconfigUpdateSettingsProvider implements UpdateSettingsProvider {
@@ -26,10 +30,20 @@ public class AutoconfigUpdateSettingsProvider implements UpdateSettingsProvider
public List getPluginRepositories() {
List pluginRepositories = new ArrayList<>();
Arrays.stream(ProjectUtilCore.getOpenProjects()).filter(TrustedProjects::isTrusted).forEach(project -> {
- ConfigurationLoaderService projectService = project.getService(ConfigurationLoaderService.class);
- final Optional configuration = projectService.getConfiguration(CommonConfigurationHandler.CONFIGURATION_CLASS, CommonConfigurationHandler.CONFIG_FILE_NAME);
- configuration.map(GeneralConfiguration::getPluginRepositories).ifPresent(pluginRepositories::addAll);
+ ConfigurationDirectoryService configDirectoryService = project.getService(
+ ConfigurationDirectoryService.class);
+ pluginRepositories.addAll(configDirectoryService.getProjectAutoconfigDirectory()
+ .map(dir -> setupPluginRepositories(project, dir))
+ .orElse(Collections.emptyList()));
});
return pluginRepositories;
}
+
+ private List setupPluginRepositories(Project project, VirtualFile configDirectory) {
+ ConfigurationLoaderService projectService = project.getService(ConfigurationLoaderService.class);
+ final Optional configuration = projectService.getConfiguration(
+ CommonConfigurationHandler.CONFIGURATION_CLASS, CommonConfigurationHandler.CONFIG_FILE_NAME,
+ configDirectory);
+ return configuration.map(GeneralConfiguration::getPluginRepositories).orElse(Collections.emptyList());
+ }
}
diff --git a/src/main/java/de/gebit/plugins/autoconfig/sdk/JDKResolver.java b/src/main/java/de/gebit/plugins/autoconfig/sdk/JDKResolver.java
index 5eb5b14..2ad25b6 100644
--- a/src/main/java/de/gebit/plugins/autoconfig/sdk/JDKResolver.java
+++ b/src/main/java/de/gebit/plugins/autoconfig/sdk/JDKResolver.java
@@ -35,19 +35,19 @@ private JDKResolver() {
// nothing
}
- public static String findProjectSdk(String sdkName, Project project) {
+ public static Sdk findSdk(String sdkName, Project project) {
if (sdkName == null) {
return null;
}
for (Sdk jdk : ProjectJdkTable.getInstance().getAllJdks()) {
if (jdk.getName().equals(sdkName)) {
- return jdk.getName();
+ return jdk;
}
}
resolveMissingJDK(sdkName, project);
- return sdkName;
+ return null;
}
/**
diff --git a/src/main/java/de/gebit/plugins/autoconfig/service/ConfigurationDirectoryService.java b/src/main/java/de/gebit/plugins/autoconfig/service/ConfigurationDirectoryService.java
new file mode 100644
index 0000000..75761e2
--- /dev/null
+++ b/src/main/java/de/gebit/plugins/autoconfig/service/ConfigurationDirectoryService.java
@@ -0,0 +1,139 @@
+package de.gebit.plugins.autoconfig.service;
+
+
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.components.Service;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.project.ProjectKt;
+import lombok.RequiredArgsConstructor;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
+
+/**
+ * Service used to determine directories for project and modules.
+ */
+@RequiredArgsConstructor
+@Service(Service.Level.PROJECT)
+public final class ConfigurationDirectoryService {
+ private static final @NotNull Logger LOGGER = Logger.getInstance(ConfigurationDirectoryService.class);
+
+ /**
+ * Configuration directory containing yaml sources.
+ */
+ public static final String AUTOCONFIG_DIRECTORY = "autoconfig";
+
+ private final Project project;
+
+ /**
+ * Get project configuration directory ".idea/autoconfig".
+ *
+ * @return project configuration directory ".idea/autoconfig", or else an empty Optional.
+ */
+ public Optional getProjectAutoconfigDirectory() {
+ return getProjectDirectory().map(f -> f.findChild(AUTOCONFIG_DIRECTORY));
+ }
+
+ /**
+ * Get module configuration directory ".idea/autoconfig". Whether ".idea" is used depends on the project.
+ *
+ * @return module configuration directory ".idea/autoconfig", or else an empty Optional.
+ */
+ public Optional getModuleAutoconfigDirectory(Module module) {
+ VirtualFile moduleDir = ProjectUtil.guessModuleDir(module);
+ if (moduleDir == null) {
+ return Optional.empty();
+ }
+ String projectIdeaDirectoryName = getIdeaDirectoryName();
+ VirtualFile moduleIdeaDirectory = moduleDir.findChild(projectIdeaDirectoryName);
+ if (moduleIdeaDirectory == null) {
+ return Optional.empty();
+ }
+ return Optional.ofNullable(moduleIdeaDirectory.findChild(AUTOCONFIG_DIRECTORY));
+ }
+
+ private @NotNull String getIdeaDirectoryName() {
+ Optional projectDirectory = getProjectDirectory();
+ return projectDirectory.map(VirtualFile::getName).orElse(".idea");
+ }
+
+ /**
+ * Determine the location of the ".idea/autoconfig" directory (and create it if necessary). A new configuration file
+ * with the given name is created in the directory.
+ *
+ * @param fileName the configuration file name to be created
+ * @return the file handle for the newly created file, or an empty Optional
+ */
+ public Optional findOrCreateProjectAutoconfigFile(String fileName) {
+ Optional projectDirectory = getProjectDirectory();
+
+ if (projectDirectory.isEmpty()) {
+ return Optional.empty();
+ }
+ return findOrCreateAutoconfigFile(projectDirectory.get(), fileName, AUTOCONFIG_DIRECTORY);
+ }
+
+ /**
+ * Determine the location of the ".idea/autoconfig" directory (and create it if necessary). A new configuration file
+ * with the given name is created in the directory.
+ *
+ * @param module the module to create the file for
+ * @param fileName the configuration file name to be created
+ * @return the file handle for the newly created file, or an empty Optional
+ */
+ public Optional findOrCreateModuleAutoconfigFile(Module module, String fileName) {
+ VirtualFile moduleDir = ProjectUtil.guessModuleDir(module);
+ if (moduleDir == null) {
+ return Optional.empty();
+ }
+ String projectIdeaDirectoryName = getIdeaDirectoryName();
+ return findOrCreateAutoconfigFile(moduleDir, fileName, projectIdeaDirectoryName, AUTOCONFIG_DIRECTORY);
+ }
+
+ private Optional findOrCreateAutoconfigFile(VirtualFile baseDirectory, String fileName, String... dirList) {
+ try {
+ return Optional.of(WriteAction.compute(() -> {
+ VirtualFile autoconfigDirectory = baseDirectory;
+ for (String subDirectory : dirList) {
+ VirtualFile subDirectoryFile = autoconfigDirectory.findChild(subDirectory);
+ if (subDirectoryFile == null) {
+ // no ".idea" or ".idea/autoconfig" directory found. We're going to create it.
+ autoconfigDirectory = autoconfigDirectory.createChildDirectory(this, subDirectory);
+ } else {
+ autoconfigDirectory = subDirectoryFile;
+ }
+ }
+
+ return autoconfigDirectory.findOrCreateChildData(this, fileName);
+ }));
+ } catch (IOException ex) {
+ LOGGER.error("Couldn't create Autoconfig file!", ex);
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * Determine the location of the .idea directory.
+ *
+ * @return the location of the .idea directory
+ */
+ public Optional getProjectDirectory() {
+ var projectFile = project.getProjectFile();
+ if (projectFile == null) {
+ // Fallback to find .idea directory. May happen when project is first opened and no misc.xml can be found
+ Path directoryStorePath = ProjectKt.getStateStore(project).getDirectoryStorePath();
+ if (directoryStorePath != null) {
+ return Optional.ofNullable(VirtualFileManager.getInstance().findFileByNioPath(directoryStorePath));
+ }
+ return Optional.empty();
+ }
+ return Optional.ofNullable(projectFile.getParent());
+ }
+}
diff --git a/src/main/java/de/gebit/plugins/autoconfig/service/ConfigurationLoaderService.java b/src/main/java/de/gebit/plugins/autoconfig/service/ConfigurationLoaderService.java
new file mode 100644
index 0000000..d5474d9
--- /dev/null
+++ b/src/main/java/de/gebit/plugins/autoconfig/service/ConfigurationLoaderService.java
@@ -0,0 +1,48 @@
+//
+// ConfigurationLoaderService.java
+//
+// Copyright (C) 2024
+// GEBIT Solutions GmbH,
+// Berlin, Duesseldorf, Stuttgart, Leipzig (Germany)
+// All rights reserved.
+
+package de.gebit.plugins.autoconfig.service;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.components.Service;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import de.gebit.plugins.autoconfig.util.Notifications;
+import lombok.RequiredArgsConstructor;
+
+import java.io.IOException;
+import java.util.Optional;
+
+/**
+ * Service used to load yaml files from project directory.
+ */
+@RequiredArgsConstructor
+@Service(Service.Level.PROJECT)
+public final class ConfigurationLoaderService {
+
+ private final Project project;
+
+ public Optional getConfiguration(Class objectClass, String configFileName, VirtualFile autoconfigDirectory) {
+ T configObject = null;
+ var configYaml = autoconfigDirectory.findChild(configFileName);
+ if (configYaml == null) {
+ return Optional.empty();
+ }
+ try {
+ configObject = ReadAction.compute(() -> {
+ final ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
+ return objectMapper.readValue(configYaml.getInputStream(), objectClass);
+ });
+ } catch (IOException e) {
+ Notifications.showInfo("Unable to parse configuration yaml: " + configFileName, project);
+ }
+ return Optional.ofNullable(configObject);
+ }
+}
diff --git a/src/main/java/de/gebit/plugins/autoconfig/service/ConfigurationUpdaterService.java b/src/main/java/de/gebit/plugins/autoconfig/service/ConfigurationUpdaterService.java
new file mode 100644
index 0000000..18a642b
--- /dev/null
+++ b/src/main/java/de/gebit/plugins/autoconfig/service/ConfigurationUpdaterService.java
@@ -0,0 +1,113 @@
+package de.gebit.plugins.autoconfig.service;
+
+
+import com.intellij.ide.impl.TrustedProjects;
+import com.intellij.openapi.components.Service;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import de.gebit.plugins.autoconfig.AutoconfigStartup;
+import de.gebit.plugins.autoconfig.UpdateHandler;
+import de.gebit.plugins.autoconfig.UpdateModuleHandler;
+import lombok.RequiredArgsConstructor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import static de.gebit.plugins.autoconfig.util.Notifications.showInfo;
+
+/**
+ * Service used to update project/module configurations.
+ */
+@RequiredArgsConstructor
+@Service(Service.Level.PROJECT)
+public final class ConfigurationUpdaterService {
+ private static final com.intellij.openapi.diagnostic.Logger LOG = Logger.getInstance(AutoconfigStartup.class);
+
+ public static final ExtensionPointName> PROJECT_EP_NAME = ExtensionPointName.create(
+ "de.gebit.plugins.autoconfig.configurationUpdater");
+
+ public static final ExtensionPointName> MODULE_EP_NAME = ExtensionPointName.create(
+ "de.gebit.plugins.autoconfig.moduleConfigurationUpdater");
+
+ private final Project project;
+
+ @SuppressWarnings("UnstableApiUsage")
+ public @Nullable List runAutoconfig() {
+ ConfigurationLoaderService projectService = project.getService(ConfigurationLoaderService.class);
+ if (projectService == null) {
+ return null;
+ }
+
+ if (!TrustedProjects.isTrusted(project)) {
+ showInfo("Project configuration has not been updated, because project was opened in safe mode.", project);
+ return null;
+ }
+
+ List changedConfigs = new ArrayList<>();
+
+ ConfigurationDirectoryService configurationDirectoryService = project.getService(
+ ConfigurationDirectoryService.class);
+ configurationDirectoryService.getProjectAutoconfigDirectory().ifPresent(dir -> {
+ for (UpdateHandler> updateHandler : PROJECT_EP_NAME.getExtensionList()) {
+ changedConfigs.addAll(processUpdateHandler(project, updateHandler, projectService, dir));
+ }
+ });
+
+ List moduleConfigs = new ArrayList<>();
+ List modules = new ArrayList<>();
+ for (Module module : ModuleManager.getInstance(project).getModules()) {
+ Optional moduleAutoconfigDirectory = configurationDirectoryService.getModuleAutoconfigDirectory(
+ module);
+ moduleAutoconfigDirectory.ifPresent(d -> moduleConfigs.add(new ModuleDirectory(module, d)));
+ modules.add(module);
+ }
+
+
+ for (UpdateModuleHandler> updateHandler : MODULE_EP_NAME.getExtensionList()) {
+ changedConfigs.addAll(processModuleUpdateHandler(moduleConfigs, modules, updateHandler, projectService));
+ }
+
+ if (!changedConfigs.isEmpty()) {
+ String notification = String.join(", ", changedConfigs);
+ showInfo("New project configurations applied: " + notification, project);
+ }
+
+ return changedConfigs;
+ }
+
+ private List processModuleUpdateHandler(@NotNull List moduleConfigs, List modules, UpdateModuleHandler updateHandler, ConfigurationLoaderService projectService) {
+ Set modulesUpdates = new HashSet<>();
+ for (ModuleDirectory moduleDirectory : moduleConfigs) {
+ Optional extensionConfiguration = projectService.getConfiguration(updateHandler.getConfigurationClass(),
+ updateHandler.getFileName(), moduleDirectory.directory());
+ extensionConfiguration.ifPresent(config -> modules.stream()
+ .filter(module -> updateHandler.acceptModule(config, module))
+ .forEach(module -> modulesUpdates.addAll(updateHandler.updateConfiguration(config, module))));
+ }
+
+ return modulesUpdates.stream().toList();
+ }
+
+ private List processUpdateHandler(@NotNull Project project, UpdateHandler updateHandler, ConfigurationLoaderService projectService, VirtualFile configDirectory) {
+ Optional extensionConfiguration = projectService.getConfiguration(updateHandler.getConfigurationClass(),
+ updateHandler.getFileName(), configDirectory);
+ return extensionConfiguration.map(c -> updateHandler.updateConfiguration(c, project)).orElseGet(() -> {
+ LOG.info("No configuration for " + updateHandler.getUpdaterName() + " found.");
+ return Collections.emptyList();
+ });
+ }
+
+ private record ModuleDirectory(Module module, VirtualFile directory) {
+
+ }
+}
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 4b1b957..7c52721 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -2,7 +2,7 @@
de.gebit.plugins.autoconfig
Autoconfig
GEBIT Solutions GmbH
-
+
com.intellij.modules.json
@@ -13,6 +13,7 @@
+
@@ -27,7 +28,8 @@
-
+
+
diff --git a/src/main/resources/de/gebit/plugins/autoconfig/messages/AutoconfigBundle.properties b/src/main/resources/de/gebit/plugins/autoconfig/messages/AutoconfigBundle.properties
new file mode 100644
index 0000000..063e2c2
--- /dev/null
+++ b/src/main/resources/de/gebit/plugins/autoconfig/messages/AutoconfigBundle.properties
@@ -0,0 +1,5 @@
+createautoconfigfile.title=Create Autoconfig File
+createautoconfigfile.selectplugin=Select which plugin to create the Autoconfig file for
+createautoconfigfile.selectmodule=&Module (if applicable):
+createautoconfigfile.selectfile=&Settings file:
+createautoconfigfile.moduleaddition=module
\ No newline at end of file
diff --git a/src/main/resources/schema/configModule.schema.json b/src/main/resources/schema/configModule.schema.json
new file mode 100644
index 0000000..f892904
--- /dev/null
+++ b/src/main/resources/schema/configModule.schema.json
@@ -0,0 +1,37 @@
+{
+ "$id": "https://www.gebit.de/autoconfig-intellij-plugin/config.schema.json",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "General module configuration",
+ "type": "object",
+ "required": [
+ "moduleFilter"
+ ],
+ "properties": {
+ "moduleFilter": {
+ "type": "array",
+ "description": "List of modules/module regex patterns. Modules that match any of the names/patterns will be configured by this configuration.",
+ "items": {
+ "type": "string",
+ "description": "Module name/pattern"
+ }
+ },
+ "moduleSDK": {
+ "type": "object",
+ "description": "The module SDK to be used.",
+ "required": [
+ "name",
+ "type"
+ ],
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "The SDK name, e.g. a version or a distributor"
+ },
+ "type": {
+ "type": "string",
+ "description": "The SDK type, e.g. \"JavaSDK\", \"IDEA JDK\", \"KotlinSDK\" or \"Android SDK\"."
+ }
+ }
+ }
+ }
+}