diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/PluginBasedCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/PluginBasedCommandlet.java new file mode 100644 index 000000000..2d92a6cec --- /dev/null +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/PluginBasedCommandlet.java @@ -0,0 +1,198 @@ +package com.devonfw.tools.ide.tool; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import com.devonfw.tools.ide.cli.CliException; +import com.devonfw.tools.ide.common.Tag; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.io.FileAccess; +import com.devonfw.tools.ide.tool.ide.IdeToolCommandlet; +import com.devonfw.tools.ide.tool.ide.PluginDescriptor; +import com.devonfw.tools.ide.tool.ide.PluginDescriptorImpl; + +public abstract class PluginBasedCommandlet extends LocalToolCommandlet { + + private Map pluginsMap; + + private Collection configuredPlugins; + + /** + * The constructor. + * + * @param context the {@link IdeContext}. + * @param tool the {@link #getName() tool name}. + * @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} + * method. + */ + public PluginBasedCommandlet(IdeContext context, String tool, Set tags) { + + super(context, tool, tags); + } + + protected Map getPluginsMap() { + + if (this.pluginsMap == null) { + Map map = new HashMap<>(); + + // Load project-specific plugins + Path pluginsPath = getPluginsConfigPath(); + loadPluginsFromDirectory(map, pluginsPath); + + // Load user-specific plugins + Path userPluginsPath = getUserHomePluginsConfigPath(); + loadPluginsFromDirectory(map, userPluginsPath); + + this.pluginsMap = map; + } + + return this.pluginsMap; + } + + private void loadPluginsFromDirectory(Map map, Path pluginsPath) { + if (Files.isDirectory(pluginsPath)) { + try (Stream childStream = Files.list(pluginsPath)) { + Iterator iterator = childStream.iterator(); + while (iterator.hasNext()) { + Path child = iterator.next(); + String filename = child.getFileName().toString(); + if (filename.endsWith(IdeContext.EXT_PROPERTIES) && Files.exists(child)) { + PluginDescriptor descriptor = PluginDescriptorImpl.of(child, this.context, isPluginUrlNeeded()); + + // Priority to user-specific plugins + map.put(descriptor.getName(), descriptor); + } + } + } catch (IOException e) { + throw new IllegalStateException("Failed to list children of directory " + pluginsPath, e); + } + } + } + + /** + * @return {@code true} if {@link PluginDescriptor#getUrl() plugin URL} property is needed, {@code false} otherwise. + */ + protected boolean isPluginUrlNeeded() { + + return false; + } + + protected Path getPluginsConfigPath() { + + return this.context.getSettingsPath().resolve(this.tool).resolve(IdeContext.FOLDER_PLUGINS); + } + + private Path getUserHomePluginsConfigPath() { + + return context.getUserHome().resolve(Paths.get(".ide", "settings", this.tool, IdeContext.FOLDER_PLUGINS)); + } + + + /** + * @return the immutable {@link Collection} of {@link PluginDescriptor}s configured for this IDE tool. + */ + public Collection getConfiguredPlugins() { + + if (this.configuredPlugins == null) { + this.configuredPlugins = Collections.unmodifiableCollection(getPluginsMap().values()); + } + return this.configuredPlugins; + } + + /** + * @return the {@link Path} where the plugins of this {@link IdeToolCommandlet} shall be installed. + */ + public Path getPluginsInstallationPath() { + + return this.context.getPluginsPath().resolve(this.tool); + } + + /** + * @param plugin the {@link PluginDescriptor} to install. + */ + public abstract void installPlugin(PluginDescriptor plugin); + + /** + * @param plugin the {@link PluginDescriptor} to uninstall. + */ + public void uninstallPlugin(PluginDescriptor plugin) { + + Path pluginsPath = getPluginsInstallationPath(); + if (!Files.isDirectory(pluginsPath)) { + this.context.debug("Omitting to uninstall plugin {} ({}) as plugins folder does not exist at {}", + plugin.getName(), plugin.getId(), pluginsPath); + return; + } + FileAccess fileAccess = this.context.getFileAccess(); + Path match = fileAccess.findFirst(pluginsPath, p -> p.getFileName().toString().startsWith(plugin.getId()), false); + if (match == null) { + this.context.debug("Omitting to uninstall plugin {} ({}) as plugins folder does not contain a match at {}", + plugin.getName(), plugin.getId(), pluginsPath); + return; + } + fileAccess.delete(match); + } + + /** + * @param key the filename of the properties file configuring the requested plugin (typically excluding the + * ".properties" extension). + * @return the {@link PluginDescriptor} for the given {@code key}. + */ + public PluginDescriptor getPlugin(String key) { + + if (key == null) { + return null; + } + if (key.endsWith(IdeContext.EXT_PROPERTIES)) { + key = key.substring(0, key.length() - IdeContext.EXT_PROPERTIES.length()); + } + PluginDescriptor pluginDescriptor = getPluginsMap().get(key); + if (pluginDescriptor == null) { + throw new CliException( + "Could not find plugin " + key + " at " + getPluginsConfigPath().resolve(key) + ".properties"); + } + return pluginDescriptor; + } + + @Override + protected boolean doInstall(boolean silent) { + + boolean newlyInstalled = super.doInstall(silent); + // post installation... + boolean installPlugins = newlyInstalled; + Path pluginsInstallationPath = getPluginsInstallationPath(); + if (newlyInstalled) { + this.context.getFileAccess().delete(pluginsInstallationPath); + } else if (!Files.isDirectory(pluginsInstallationPath)) { + installPlugins = true; + } + if (installPlugins) { + for (PluginDescriptor plugin : getPluginsMap().values()) { + if (plugin.isActive()) { + installPlugin(plugin); + } else { + handleInstall4InactivePlugin(plugin); + } + } + } + return newlyInstalled; + } + + /** + * @param plugin the in{@link PluginDescriptor#isActive() active} {@link PluginDescriptor} that is skipped for regular + * plugin installation. + */ + protected void handleInstall4InactivePlugin(PluginDescriptor plugin) { + + this.context.debug("Omitting installation of inactive plugin {} ({}).", plugin.getName(), plugin.getId()); + } +} diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java index d99aa227c..3f1b32299 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/mvn/Mvn.java @@ -1,17 +1,20 @@ package com.devonfw.tools.ide.tool.mvn; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Set; import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; -import com.devonfw.tools.ide.tool.LocalToolCommandlet; +import com.devonfw.tools.ide.tool.PluginBasedCommandlet; import com.devonfw.tools.ide.tool.ToolCommandlet; +import com.devonfw.tools.ide.tool.ide.PluginDescriptor; import com.devonfw.tools.ide.tool.java.Java; /** * {@link ToolCommandlet} for maven. */ -public class Mvn extends LocalToolCommandlet { +public class Mvn extends PluginBasedCommandlet { /** * The constructor. @@ -30,4 +33,16 @@ public boolean install(boolean silent) { return super.install(silent); } + @Override + public void installPlugin(PluginDescriptor plugin) { + + Path mavenPlugin = this.context.getSoftwarePath().resolve(this.tool).resolve("lib/ext/" + plugin.getName() + ".jar"); + this.context.getFileAccess().download(plugin.getUrl(), mavenPlugin); + if (Files.exists(mavenPlugin)) { + this.context.success("Successfully added {} to {}", plugin.getName(), mavenPlugin.toString()); + } else { + this.context.warning("Plugin {} has wrong properties\n" // + + "Please check the plugin properties file in {}", mavenPlugin.getFileName(), mavenPlugin.toAbsolutePath()); + } + } } diff --git a/cli/src/test/java/com/devonfw/tools/ide/commandlet/EnvironmentCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EnvironmentCommandletTest.java index 4d7a931ad..f52639c8c 100644 --- a/cli/src/test/java/com/devonfw/tools/ide/commandlet/EnvironmentCommandletTest.java +++ b/cli/src/test/java/com/devonfw/tools/ide/commandlet/EnvironmentCommandletTest.java @@ -1,6 +1,5 @@ package com.devonfw.tools.ide.commandlet; -import com.devonfw.tools.ide.context.IdeContext; import org.junit.jupiter.api.Test; import com.devonfw.tools.ide.context.AbstractIdeContextTest; diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/ExamplePluginBasedCommandlet.java b/cli/src/test/java/com/devonfw/tools/ide/tool/ExamplePluginBasedCommandlet.java new file mode 100644 index 000000000..6570ce6a7 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/ExamplePluginBasedCommandlet.java @@ -0,0 +1,27 @@ +package com.devonfw.tools.ide.tool; + +import java.util.Set; + +import com.devonfw.tools.ide.common.Tag; +import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.tool.ide.PluginDescriptor; + +public class ExamplePluginBasedCommandlet extends PluginBasedCommandlet { + /** + * The constructor. + * + * @param context the {@link IdeContext}. + * @param tool the {@link #getName() tool name}. + * @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} + * method. + */ + public ExamplePluginBasedCommandlet(IdeContext context, String tool, Set tags) { + + super(context, tool, tags); + } + + @Override + public void installPlugin(PluginDescriptor plugin) { + + } +} diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/PluginBasedCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/PluginBasedCommandletTest.java new file mode 100644 index 000000000..f72ee5a35 --- /dev/null +++ b/cli/src/test/java/com/devonfw/tools/ide/tool/PluginBasedCommandletTest.java @@ -0,0 +1,41 @@ +package com.devonfw.tools.ide.tool; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Map; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import com.devonfw.tools.ide.common.Tag; +import com.devonfw.tools.ide.context.AbstractIdeContextTest; +import com.devonfw.tools.ide.context.IdeTestContext; +import com.devonfw.tools.ide.tool.ide.PluginDescriptor; + +public class PluginBasedCommandletTest extends AbstractIdeContextTest { + + @Test + void testGetPluginsMap() { + IdeTestContext context = newContext(PROJECT_BASIC, "", true); + String tool = "eclipse"; + Set tags = null; + ExamplePluginBasedCommandlet pluginBasedCommandlet = new ExamplePluginBasedCommandlet(context, tool, tags); + + Map pluginsMap = pluginBasedCommandlet.getPluginsMap(); + assertNotNull(pluginsMap); + + assertTrue(pluginsMap.containsKey("checkstyle")); + assertTrue(pluginsMap.containsKey("anyedit")); + + PluginDescriptor plugin1 = pluginsMap.get("checkstyle"); + assertNotNull(plugin1); + assertEquals("checkstyle", plugin1.getName()); + + PluginDescriptor plugin2 = pluginsMap.get("anyedit"); + assertNotNull(plugin2); + assertEquals("anyedit", plugin2.getName()); + + // Check if anyedit plugin has value "false" --> value from user directory + assertEquals(false, plugin2.isActive()); + } +} diff --git a/cli/src/test/resources/ide-projects/basic/home/.ide/settings/eclipse/plugins/anyedit.properties b/cli/src/test/resources/ide-projects/basic/home/.ide/settings/eclipse/plugins/anyedit.properties new file mode 100644 index 000000000..eb298f3bb --- /dev/null +++ b/cli/src/test/resources/ide-projects/basic/home/.ide/settings/eclipse/plugins/anyedit.properties @@ -0,0 +1,3 @@ +plugin_url=https://raw.githubusercontent.com/iloveeclipse/plugins/latest/ +plugin_id=AnyEditTools.feature.group +plugin_active=false diff --git a/cli/src/test/resources/ide-projects/basic/settings/eclipse/plugins/anyedit.properties b/cli/src/test/resources/ide-projects/basic/settings/eclipse/plugins/anyedit.properties new file mode 100644 index 000000000..09a94b116 --- /dev/null +++ b/cli/src/test/resources/ide-projects/basic/settings/eclipse/plugins/anyedit.properties @@ -0,0 +1,3 @@ +plugin_url=https://raw.githubusercontent.com/iloveeclipse/plugins/latest/ +plugin_id=AnyEditTools.feature.group +plugin_active=true diff --git a/cli/src/test/resources/ide-projects/basic/settings/eclipse/plugins/checkstyle.properties b/cli/src/test/resources/ide-projects/basic/settings/eclipse/plugins/checkstyle.properties new file mode 100644 index 000000000..6e402c7c5 --- /dev/null +++ b/cli/src/test/resources/ide-projects/basic/settings/eclipse/plugins/checkstyle.properties @@ -0,0 +1,3 @@ +plugin_url=https://checkstyle.org/eclipse-cs-update-site +plugin_id=net.sf.eclipsecs.feature.group +plugin_active=true