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

devonfw / IDEasy / 13696929899

06 Mar 2025 10:48AM UTC coverage: 68.467% (+0.2%) from 68.253%
13696929899

Pull #1085

github

web-flow
Merge fe2061e2b into cb3091c59
Pull Request #1085: #654: improved plugin suppport

3065 of 4919 branches covered (62.31%)

Branch coverage included in aggregate %.

7924 of 11131 relevant lines covered (71.19%)

3.11 hits per line

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

85.62
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.ide.IdeToolCommandlet;
18

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

24
  private ToolPlugins plugins;
25

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

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

38
  public ToolPlugins getPlugins() {
39

40
    if (this.plugins == null) {
3✔
41
      ToolPlugins toolPlugins = new ToolPlugins(this.context);
6✔
42

43
      // Load project-specific plugins
44
      Path pluginsPath = getPluginsConfigPath();
3✔
45
      loadPluginsFromDirectory(toolPlugins, pluginsPath);
4✔
46

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

52
      this.plugins = toolPlugins;
3✔
53
    }
54

55
    return this.plugins;
3✔
56
  }
57

58
  private void loadPluginsFromDirectory(ToolPlugins map, Path pluginsPath) {
59

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

68
  /**
69
   * @return {@code true} if {@link ToolPluginDescriptor#url() plugin URL} property is needed, {@code false} otherwise.
70
   */
71
  protected boolean isPluginUrlNeeded() {
72

73
    return false;
2✔
74
  }
75

76
  /**
77
   * @return the {@link Path} to the folder with the plugin configuration files inside the settings.
78
   */
79
  protected Path getPluginsConfigPath() {
80

81
    return this.context.getSettingsPath().resolve(this.tool).resolve(IdeContext.FOLDER_PLUGINS);
9✔
82
  }
83

84
  private Path getUserHomePluginsConfigPath() {
85

86
    return this.context.getUserHomeIde().resolve("settings").resolve(this.tool).resolve(IdeContext.FOLDER_PLUGINS);
11✔
87
  }
88

89
  /**
90
   * @return the {@link Path} where the plugins of this {@link IdeToolCommandlet} shall be installed.
91
   */
92
  public Path getPluginsInstallationPath() {
93

94
    return this.context.getPluginsPath().resolve(this.tool);
7✔
95
  }
96

97
  @Override
98
  protected void postInstall(boolean newlyInstalled, ProcessContext pc) {
99

100
    super.postInstall(newlyInstalled, pc);
4✔
101
    Path pluginsInstallationPath = getPluginsInstallationPath();
3✔
102
    FileAccess fileAccess = this.context.getFileAccess();
4✔
103
    if (!Files.exists(retrieveEditionMarkerFilePath(getName()))) {
8✔
104
      this.context.debug("Matching edition marker file for {} was not found, re-installing plugins", getInstalledEdition());
11✔
105
      fileAccess.delete(pluginsInstallationPath);
3✔
106
      if (this.context.getIdeHome() != null) {
4✔
107
        List<Path> markerFiles = fileAccess.listChildren(this.context.getIdeHome().resolve(".ide"), Files::isRegularFile);
14✔
108
        for (Path path : markerFiles) {
10✔
109
          if (path.getFileName().toString().startsWith("plugin." + getName())) {
8!
110
            this.context.debug("Plugin marker file {} got deleted.", path);
10✔
111
            fileAccess.delete(path);
3✔
112
          }
113
        }
1✔
114
      }
115

116
      createEditionMarkerFile();
2✔
117
    }
118
    fileAccess.mkdirs(pluginsInstallationPath);
3✔
119
    installPlugins(pc);
3✔
120
  }
1✔
121

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

126
  /**
127
   * Creates a marker file for an installed edition.
128
   */
129
  public void createEditionMarkerFile() {
130
    if (this.context.getPluginsPath() != null) {
4!
131
      Path pluginsPath = retrieveEditionMarkerFilePath(getName());
5✔
132
      FileAccess fileAccess = this.context.getFileAccess();
4✔
133
      fileAccess.mkdirs(this.context.getPluginsPath().resolve(getName()));
8✔
134
      fileAccess.touch(pluginsPath);
3✔
135
    }
136
  }
1✔
137

138
  /**
139
   * @param toolName the tool name to search for.
140
   * @return Path to the edition marker file.
141
   */
142
  public Path retrieveEditionMarkerFilePath(String toolName) {
143
    if (this.context.getPluginsPath() != null) {
4!
144
      return this.context.getPluginsPath().resolve(toolName).resolve("." + getInstalledEdition());
10✔
145
    }
146
    return null;
×
147
  }
148

149
  /**
150
   * Method to install active plugins or to handle install for inactive plugins
151
   *
152
   * @param plugins as {@link Collection} of plugins to install.
153
   * @param pc the {@link ProcessContext} to use.
154
   */
155
  protected void installPlugins(Collection<ToolPluginDescriptor> plugins, ProcessContext pc) {
156
    for (ToolPluginDescriptor plugin : plugins) {
10✔
157
      if (plugin.active()) {
3✔
158
        if (retrievePluginMarkerFilePath(plugin) != null && Files.exists(retrievePluginMarkerFilePath(plugin))) {
11✔
159
          this.context.debug("Markerfile for IDE: {} and active plugin: {} already exists.", getName(), plugin.name());
17✔
160
        } else {
161
          try (Step step = this.context.newStep("Install plugin " + plugin.name())) {
8✔
162
            installPlugin(plugin, step, pc);
5✔
163
            createPluginMarkerFile(plugin);
3✔
164
          }
165
        }
166
      } else {
167
        if (retrievePluginMarkerFilePath(plugin) != null && Files.exists(retrievePluginMarkerFilePath(plugin))) {
11!
168
          this.context.debug("Markerfile for IDE: {} and inactive plugin: {} already exists.", getName(), plugin.name());
×
169
        } else {
170
          handleInstall4InactivePlugin(plugin);
3✔
171
        }
172
      }
173
    }
1✔
174
  }
1✔
175

176
  /**
177
   * @param plugin the {@link ToolPluginDescriptor plugin} to search for.
178
   * @return Path to the plugin marker file.
179
   */
180
  public Path retrievePluginMarkerFilePath(ToolPluginDescriptor plugin) {
181
    if (this.context.getIdeHome() != null) {
4✔
182
      return this.context.getIdeHome().resolve(IdeContext.FOLDER_DOT_IDE).resolve("plugin" + "." + getName() + "." + getInstalledEdition() + "." + plugin.name());
14✔
183
    }
184
    return null;
2✔
185
  }
186

187
  /**
188
   * Creates a marker file for a plugin in $IDE_HOME/.ide/plugin.«ide».«plugin-name»
189
   *
190
   * @param plugin the {@link ToolPluginDescriptor plugin} for which the marker file should be created.
191
   */
192
  public void createPluginMarkerFile(ToolPluginDescriptor plugin) {
193
    if (this.context.getIdeHome() != null) {
4✔
194
      Path hiddenIdePath = this.context.getIdeHome().resolve(IdeContext.FOLDER_DOT_IDE);
6✔
195
      this.context.getFileAccess().mkdirs(hiddenIdePath);
5✔
196
      this.context.getFileAccess().touch(hiddenIdePath.resolve("plugin" + "." + getName() + "." + getInstalledEdition() + "." + plugin.name()));
13✔
197
    }
198
  }
1✔
199

200
  /**
201
   * @param plugin the {@link ToolPluginDescriptor} to install.
202
   * @param step the {@link Step} for the plugin installation.
203
   * @param pc the {@link ProcessContext} to use.
204
   */
205
  public abstract void installPlugin(ToolPluginDescriptor plugin, Step step, ProcessContext pc);
206

207
  /**
208
   * @param plugin the {@link ToolPluginDescriptor} to install.
209
   * @param step the {@link Step} for the plugin installation.
210
   */
211
  public void installPlugin(ToolPluginDescriptor plugin, final Step step) {
212
    ProcessContext pc = this.context.newProcess().errorHandling(ProcessErrorHandling.THROW_CLI);
6✔
213
    install(true, pc);
5✔
214
    installPlugin(plugin, step, pc);
5✔
215
  }
1✔
216

217
  /**
218
   * @param plugin the {@link ToolPluginDescriptor} to uninstall.
219
   */
220
  public void uninstallPlugin(ToolPluginDescriptor plugin) {
221

222
    boolean error = false;
2✔
223
    Path pluginsPath = getPluginsInstallationPath();
3✔
224
    if (!Files.isDirectory(pluginsPath)) {
5!
225
      this.context.debug("Omitting to uninstall plugin {} ({}) as plugins folder does not exist at {}",
×
226
          plugin.name(), plugin.id(), pluginsPath);
×
227
      error = true;
×
228
    }
229
    FileAccess fileAccess = this.context.getFileAccess();
4✔
230
    Path match = fileAccess.findFirst(pluginsPath, p -> p.getFileName().toString().startsWith(plugin.id()), false);
14✔
231
    if (match == null) {
2!
232
      this.context.debug("Omitting to uninstall plugin {} ({}) as plugins folder does not contain a match at {}",
9✔
233
          plugin.name(), plugin.id(), pluginsPath);
11✔
234
      error = true;
2✔
235
    }
236
    if (error) {
2!
237
      context.error("Could not uninstall plugin " + plugin + " because we could not find an installation");
7✔
238
    } else {
239
      fileAccess.delete(match);
×
240
      context.info("Successfully uninstalled plugin " + plugin);
×
241
    }
242
  }
1✔
243

244
  /**
245
   * @param key the filename of the properties file configuring the requested plugin (typically excluding the ".properties" extension).
246
   * @return the {@link ToolPluginDescriptor} for the given {@code key}.
247
   */
248
  public ToolPluginDescriptor getPlugin(String key) {
249

250
    if (key == null) {
2!
251
      return null;
×
252
    }
253
    if (key.endsWith(IdeContext.EXT_PROPERTIES)) {
4!
254
      key = key.substring(0, key.length() - IdeContext.EXT_PROPERTIES.length());
×
255
    }
256

257
    ToolPlugins toolPlugins = getPlugins();
3✔
258
    ToolPluginDescriptor pluginDescriptor = toolPlugins.getByName(key);
4✔
259
    if (pluginDescriptor == null) {
2!
260
      throw new CliException(
×
261
          "Could not find plugin " + key + " at " + getPluginsConfigPath().resolve(key) + ".properties");
×
262
    }
263
    return pluginDescriptor;
2✔
264
  }
265

266
  /**
267
   * @param plugin the in{@link ToolPluginDescriptor#active() active} {@link ToolPluginDescriptor} that is skipped for regular plugin installation.
268
   */
269
  protected void handleInstall4InactivePlugin(ToolPluginDescriptor plugin) {
270

271
    this.context.debug("Omitting installation of inactive plugin {} ({}).", plugin.name(), plugin.id());
16✔
272
  }
1✔
273
}
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