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

devonfw / IDEasy / 13631164443

03 Mar 2025 12:42PM UTC coverage: 68.505% (+0.3%) from 68.251%
13631164443

Pull #1085

github

web-flow
Merge 9829c710e into 991139853
Pull Request #1085: #654: improved plugin suppport

3066 of 4919 branches covered (62.33%)

Branch coverage included in aggregate %.

7927 of 11128 relevant lines covered (71.23%)

3.11 hits per line

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

85.71
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) {
99

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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