• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

devonfw / IDEasy / 19751480105

28 Nov 2025 01:26AM UTC coverage: 69.441% (+0.3%) from 69.136%
19751480105

push

github

web-flow
#1613: fixed duplicated CVE check and refactored installation routine (#1614)

3696 of 5851 branches covered (63.17%)

Branch coverage included in aggregate %.

9620 of 13325 relevant lines covered (72.2%)

3.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.83
cli/src/main/java/com/devonfw/tools/ide/tool/plugin/PluginBasedCommandlet.java
1
package com.devonfw.tools.ide.tool.plugin;
2

3
import java.nio.file.Files;
4
import java.nio.file.Path;
5
import java.util.Collection;
6
import java.util.List;
7
import java.util.Set;
8

9
import com.devonfw.tools.ide.cli.CliException;
10
import com.devonfw.tools.ide.common.Tag;
11
import com.devonfw.tools.ide.context.IdeContext;
12
import com.devonfw.tools.ide.io.FileAccess;
13
import com.devonfw.tools.ide.process.ProcessContext;
14
import com.devonfw.tools.ide.process.ProcessErrorHandling;
15
import com.devonfw.tools.ide.step.Step;
16
import com.devonfw.tools.ide.tool.LocalToolCommandlet;
17
import com.devonfw.tools.ide.tool.ToolInstallRequest;
18
import com.devonfw.tools.ide.tool.ide.IdeToolCommandlet;
19

20
/**
21
 * Base class for {@link LocalToolCommandlet}s that support plugins. It can automatically install configured plugins for the tool managed by this commandlet.
22
 */
23
public abstract class PluginBasedCommandlet extends LocalToolCommandlet {
24

25
  private ToolPlugins plugins;
26

27
  /**
28
   * The constructor.
29
   *
30
   * @param context the {@link IdeContext}.
31
   * @param tool the {@link #getName() tool name}.
32
   * @param tags the {@link #getTags() tags} classifying the tool. Should be created via {@link Set#of(Object) Set.of} method.
33
   */
34
  public PluginBasedCommandlet(IdeContext context, String tool, Set<Tag> tags) {
35

36
    super(context, tool, tags);
5✔
37
  }
1✔
38

39
  /**
40
   * @return the {@link ToolPlugins} of this {@link PluginBasedCommandlet}.
41
   */
42
  public ToolPlugins getPlugins() {
43

44
    if (this.plugins == null) {
3✔
45
      ToolPlugins toolPlugins = new ToolPlugins(this.context);
6✔
46

47
      // Load project-specific plugins
48
      Path pluginsPath = getPluginsConfigPath();
3✔
49
      loadPluginsFromDirectory(toolPlugins, pluginsPath);
4✔
50

51
      // Load user-specific plugins, this is done after loading the project-specific plugins so the user can potentially
52
      // override plugins (e.g. change active flag).
53
      Path userPluginsPath = getUserHomePluginsConfigPath();
3✔
54
      loadPluginsFromDirectory(toolPlugins, userPluginsPath);
4✔
55

56
      this.plugins = toolPlugins;
3✔
57
    }
58

59
    return this.plugins;
3✔
60
  }
61

62
  private void loadPluginsFromDirectory(ToolPlugins map, Path pluginsPath) {
63

64
    List<Path> children = this.context.getFileAccess()
5✔
65
        .listChildren(pluginsPath, p -> p.getFileName().toString().endsWith(IdeContext.EXT_PROPERTIES));
8✔
66
    for (Path child : children) {
10✔
67
      ToolPluginDescriptor descriptor = ToolPluginDescriptor.of(child, this.context, isPluginUrlNeeded());
7✔
68
      map.add(descriptor);
3✔
69
    }
1✔
70
  }
1✔
71

72
  /**
73
   * @return {@code true} if {@link ToolPluginDescriptor#url() plugin URL} property is needed, {@code false} otherwise.
74
   */
75
  protected boolean isPluginUrlNeeded() {
76

77
    return false;
2✔
78
  }
79

80
  /**
81
   * @return the {@link Path} to the folder with the plugin configuration files inside the settings.
82
   */
83
  protected Path getPluginsConfigPath() {
84

85
    return this.context.getSettingsPath().resolve(this.tool).resolve(IdeContext.FOLDER_PLUGINS);
9✔
86
  }
87

88
  private Path getUserHomePluginsConfigPath() {
89

90
    return this.context.getUserHomeIde().resolve(IdeContext.FOLDER_SETTINGS).resolve(this.tool).resolve(IdeContext.FOLDER_PLUGINS);
11✔
91
  }
92

93
  /**
94
   * @return the {@link Path} where the plugins of this {@link IdeToolCommandlet} shall be installed.
95
   */
96
  public Path getPluginsInstallationPath() {
97

98
    return this.context.getPluginsPath().resolve(this.tool);
7✔
99
  }
100

101
  @Override
102
  protected void postInstall(boolean newlyInstalled, ProcessContext pc) {
103

104
    super.postInstall(newlyInstalled, pc);
4✔
105
    Path pluginsInstallationPath = getPluginsInstallationPath();
3✔
106
    FileAccess fileAccess = this.context.getFileAccess();
4✔
107
    if (newlyInstalled) {
2✔
108
      fileAccess.delete(pluginsInstallationPath);
3✔
109
      List<Path> markerFiles = fileAccess.listChildren(this.context.getIdeHome().resolve(IdeContext.FOLDER_DOT_IDE), Files::isRegularFile);
14✔
110
      for (Path path : markerFiles) {
10✔
111
        if (path.getFileName().toString().startsWith("plugin." + getName())) {
8!
112
          this.context.debug("Plugin marker file {} got deleted.", path);
10✔
113
          fileAccess.delete(path);
3✔
114
        }
115
      }
1✔
116
    }
117
    fileAccess.mkdirs(pluginsInstallationPath);
3✔
118
    installPlugins(pc);
3✔
119
  }
1✔
120

121
  private void installPlugins(ProcessContext pc) {
122
    installPlugins(getPlugins().getPlugins(), pc);
6✔
123
  }
1✔
124

125
  /**
126
   * Method to install active plugins or to handle install for inactive plugins
127
   *
128
   * @param plugins as {@link Collection} of plugins to install.
129
   * @param pc the {@link ProcessContext} to use.
130
   */
131
  protected void installPlugins(Collection<ToolPluginDescriptor> plugins, ProcessContext pc) {
132
    for (ToolPluginDescriptor plugin : plugins) {
10✔
133
      Path pluginMarkerFile = retrievePluginMarkerFilePath(plugin);
4✔
134
      boolean pluginMarkerFileExists = pluginMarkerFile != null && Files.exists(pluginMarkerFile);
11!
135
      if (pluginMarkerFileExists) {
2✔
136
        this.context.debug("Markerfile for IDE {} and plugin '{}' already exists.", getName(), plugin.name());
16✔
137
      }
138
      if (plugin.active()) {
3✔
139
        if (this.context.isForcePlugins() || !pluginMarkerFileExists) {
6✔
140
          Step step = this.context.newStep("Install plugin " + plugin.name());
7✔
141
          step.run(() -> doInstallPluginStep(plugin, step, pc));
14✔
142
        } else {
1✔
143
          this.context.debug("Skipping installation of plugin '{}' due to existing marker file: {}", plugin.name(), pluginMarkerFile);
16✔
144
        }
145
      } else {
146
        if (!pluginMarkerFileExists) {
2!
147
          handleInstallForInactivePlugin(plugin);
3✔
148
        }
149
      }
150
    }
1✔
151
  }
1✔
152

153
  private void doInstallPluginStep(ToolPluginDescriptor plugin, Step step, ProcessContext pc) {
154
    boolean result = installPlugin(plugin, step, pc);
6✔
155
    if (result) {
2!
156
      createPluginMarkerFile(plugin);
3✔
157
    }
158
  }
1✔
159

160
  /**
161
   * @param plugin the {@link ToolPluginDescriptor plugin} to search for.
162
   * @return Path to the plugin marker file.
163
   */
164
  public Path retrievePluginMarkerFilePath(ToolPluginDescriptor plugin) {
165
    if (this.context.getIdeHome() != null) {
4!
166
      return this.context.getIdeHome().resolve(IdeContext.FOLDER_DOT_IDE)
7✔
167
          .resolve("plugin" + "." + getName() + "." + getInstalledEdition() + "." + plugin.name());
7✔
168
    }
169
    return null;
×
170
  }
171

172
  /**
173
   * Creates a marker file for a plugin in $IDE_HOME/.ide/plugin.«ide».«plugin-name»
174
   *
175
   * @param plugin the {@link ToolPluginDescriptor plugin} for which the marker file should be created.
176
   */
177
  public void createPluginMarkerFile(ToolPluginDescriptor plugin) {
178
    Path pluginMarkerFilePath = retrievePluginMarkerFilePath(plugin);
4✔
179
    if (pluginMarkerFilePath != null) {
2!
180
      FileAccess fileAccess = this.context.getFileAccess();
4✔
181
      fileAccess.mkdirs(pluginMarkerFilePath.getParent());
4✔
182
      fileAccess.touch(pluginMarkerFilePath);
3✔
183
    }
184
  }
1✔
185

186
  /**
187
   * @param plugin the {@link ToolPluginDescriptor} to install.
188
   * @param step the {@link Step} for the plugin installation.
189
   * @param pc the {@link ProcessContext} to use.
190
   * @return boolean true if the installation of the plugin succeeded, false if not.
191
   */
192
  public abstract boolean installPlugin(ToolPluginDescriptor plugin, Step step, ProcessContext pc);
193

194
  /**
195
   * @param plugin the {@link ToolPluginDescriptor} to install.
196
   * @param step the {@link Step} for the plugin installation.
197
   */
198
  public void installPlugin(ToolPluginDescriptor plugin, final Step step) {
199
    ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI);
6✔
200
    ToolInstallRequest request = new ToolInstallRequest(true);
5✔
201
    request.setProcessContext(pc);
3✔
202
    install(request);
4✔
203
    installPlugin(plugin, step, pc);
6✔
204
  }
1✔
205

206
  /**
207
   * @param plugin the {@link ToolPluginDescriptor} to uninstall.
208
   */
209
  public void uninstallPlugin(ToolPluginDescriptor plugin) {
210

211
    boolean error = false;
2✔
212
    Path pluginsPath = getPluginsInstallationPath();
3✔
213
    if (!Files.isDirectory(pluginsPath)) {
5!
214
      this.context.debug("Omitting to uninstall plugin {} ({}) as plugins folder does not exist at {}",
×
215
          plugin.name(), plugin.id(), pluginsPath);
×
216
      error = true;
×
217
    }
218
    FileAccess fileAccess = this.context.getFileAccess();
4✔
219
    Path match = fileAccess.findFirst(pluginsPath, p -> p.getFileName().toString().startsWith(plugin.id()), false);
7✔
220
    if (match == null) {
2!
221
      this.context.debug("Omitting to uninstall plugin {} ({}) as plugins folder does not contain a match at {}",
9✔
222
          plugin.name(), plugin.id(), pluginsPath);
11✔
223
      error = true;
2✔
224
    }
225
    if (error) {
2!
226
      context.error("Could not uninstall plugin " + plugin + " because we could not find an installation");
7✔
227
    } else {
228
      fileAccess.delete(match);
×
229
      context.info("Successfully uninstalled plugin " + plugin);
×
230
    }
231
  }
1✔
232

233
  /**
234
   * @param key the filename of the properties file configuring the requested plugin (typically excluding the ".properties" extension).
235
   * @return the {@link ToolPluginDescriptor} for the given {@code key}.
236
   */
237
  public ToolPluginDescriptor getPlugin(String key) {
238

239
    if (key == null) {
2!
240
      return null;
×
241
    }
242
    if (key.endsWith(IdeContext.EXT_PROPERTIES)) {
4!
243
      key = key.substring(0, key.length() - IdeContext.EXT_PROPERTIES.length());
×
244
    }
245

246
    ToolPlugins toolPlugins = getPlugins();
3✔
247
    ToolPluginDescriptor pluginDescriptor = toolPlugins.getByName(key);
4✔
248
    if (pluginDescriptor == null) {
2!
249
      throw new CliException(
×
250
          "Could not find plugin " + key + " at " + getPluginsConfigPath().resolve(key) + ".properties");
×
251
    }
252
    return pluginDescriptor;
2✔
253
  }
254

255
  /**
256
   * @param plugin the in{@link ToolPluginDescriptor#active() active} {@link ToolPluginDescriptor} that is skipped for regular plugin installation.
257
   */
258
  protected void handleInstallForInactivePlugin(ToolPluginDescriptor plugin) {
259

260
    this.context.debug("Omitting installation of inactive plugin {} ({}).", plugin.name(), plugin.id());
16✔
261
  }
1✔
262
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc