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

devonfw / IDEasy / 15042784573

15 May 2025 10:33AM UTC coverage: 67.693% (-0.006%) from 67.699%
15042784573

push

github

web-flow
#736: add replacement for char "+" (#1289)

Co-authored-by: Jörg Hohwiller <hohwille@users.noreply.github.com>

3103 of 4992 branches covered (62.16%)

Branch coverage included in aggregate %.

7979 of 11379 relevant lines covered (70.12%)

3.07 hits per line

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

68.82
cli/src/main/java/com/devonfw/tools/ide/tool/ide/IdeaPluginDownloader.java
1
package com.devonfw.tools.ide.tool.ide;
2

3
import java.io.IOException;
4
import java.net.HttpURLConnection;
5
import java.net.URI;
6
import java.net.URLEncoder;
7
import java.net.http.HttpClient;
8
import java.net.http.HttpClient.Redirect;
9
import java.net.http.HttpRequest;
10
import java.net.http.HttpResponse;
11
import java.nio.charset.StandardCharsets;
12
import java.nio.file.Files;
13
import java.nio.file.Path;
14
import java.time.Duration;
15
import java.util.Optional;
16

17
import com.devonfw.tools.ide.context.IdeContext;
18
import com.devonfw.tools.ide.io.FileAccess;
19
import com.devonfw.tools.ide.os.MacOsHelper;
20
import com.devonfw.tools.ide.process.ProcessContext;
21
import com.devonfw.tools.ide.step.Step;
22
import com.devonfw.tools.ide.tool.plugin.ToolPluginDescriptor;
23

24
/**
25
 * Used for a direct download and installation of idea plugins
26
 */
27
public class IdeaPluginDownloader {
28

29
  private static final String BUILD_FILE = "build.txt";
30
  private final IdeContext context;
31
  private final IdeaBasedIdeToolCommandlet commandlet;
32

33
  /**
34
   * the constructor
35
   *
36
   * @param context the {@link IdeContext}.
37
   * @param commandlet the {@link IdeaBasedIdeToolCommandlet} to use.
38
   */
39
  public IdeaPluginDownloader(IdeContext context, IdeaBasedIdeToolCommandlet commandlet) {
2✔
40
    this.context = context;
3✔
41
    this.commandlet = commandlet;
3✔
42
  }
1✔
43

44
  /**
45
   * @param plugin the {@link ToolPluginDescriptor} to install.
46
   * @param step the {@link Step} for the plugin installation.
47
   * @param pc the {@link ProcessContext} to use.
48
   * @return boolean {@code true} if successful installed, {@code false} if not.
49
   */
50
  public boolean installPlugin(ToolPluginDescriptor plugin, Step step, ProcessContext pc) {
51
    String downloadUrl = getDownloadUrl(plugin);
4✔
52

53
    String pluginId = plugin.id();
3✔
54

55
    Path tmpDir = null;
2✔
56

57
    try {
58
      Path installationPath = this.commandlet.getPluginsInstallationPath();
4✔
59
      ensureInstallationPathExists(installationPath);
3✔
60

61
      FileAccess fileAccess = context.getFileAccess();
4✔
62
      tmpDir = fileAccess.createTempDir(pluginId);
4✔
63

64
      Path downloadedFile = downloadPlugin(fileAccess, downloadUrl, tmpDir, pluginId);
7✔
65
      extractDownloadedPlugin(fileAccess, downloadedFile, pluginId);
5✔
66

67
      step.success();
2✔
68
      return true;
4✔
69
    } catch (IOException e) {
×
70
      step.error(e);
×
71
      throw new IllegalStateException("Failed to process installation of plugin: " + pluginId, e);
×
72
    } finally {
73
      if (tmpDir != null) {
2!
74
        context.getFileAccess().delete(tmpDir);
5✔
75
      }
76
    }
77
  }
78

79
  /**
80
   * @param plugin the {@link ToolPluginDescriptor} to be installer
81
   * @return a {@link String} representing the download URL.
82
   */
83
  private String getDownloadUrl(ToolPluginDescriptor plugin) {
84
    String downloadUrl = plugin.url();
3✔
85
    String pluginId = URLEncoder.encode(plugin.id(), StandardCharsets.UTF_8).replaceAll("\\+", "%20");
8✔
86

87
    String buildVersion = readBuildVersion();
3✔
88

89
    if (downloadUrl == null || downloadUrl.isEmpty()) {
5!
90
      downloadUrl = String.format("https://plugins.jetbrains.com/pluginManager?action=download&id=%s&build=%s", pluginId, buildVersion);
×
91
    }
92
    return downloadUrl;
2✔
93
  }
94

95
  private String readBuildVersion() {
96
    Path buildFile = this.commandlet.getToolPath().resolve(BUILD_FILE);
6✔
97
    if (context.getSystemInfo().isMac()) {
5✔
98
      MacOsHelper macOsHelper = new MacOsHelper(context);
6✔
99
      Path appPath = macOsHelper.findAppDir(macOsHelper.findRootToolPath(this.commandlet, context));
9✔
100
      buildFile = appPath.resolve("Contents/Resources").resolve(BUILD_FILE);
6✔
101
    }
102
    try {
103
      return Files.readString(buildFile);
3✔
104
    } catch (IOException e) {
×
105
      throw new IllegalStateException("Failed to read " + this.commandlet.getName() + " build version: " + buildFile, e);
×
106
    }
107
  }
108

109
  private void ensureInstallationPathExists(Path installationPath) throws IOException {
110
    if (!Files.exists(installationPath)) {
5!
111
      try {
112
        Files.createDirectories(installationPath);
×
113
      } catch (IOException e) {
×
114
        throw new IllegalStateException("Failed to create directory " + installationPath, e);
×
115
      }
×
116
    }
117
  }
1✔
118

119
  private Path downloadPlugin(FileAccess fileAccess, String downloadUrl, Path tmpDir, String pluginId) throws IOException {
120
    String extension = getFileExtensionFromUrl(downloadUrl);
4✔
121
    if (extension.isEmpty()) {
3!
122
      throw new IllegalStateException("Unknown file type for URL: " + downloadUrl);
×
123
    }
124
    String fileName = String.format("%s-plugin-%s%s", this.commandlet.getName(), pluginId, extension);
19✔
125
    Path downloadedFile = tmpDir.resolve(fileName);
4✔
126
    fileAccess.download(downloadUrl, downloadedFile);
4✔
127
    return downloadedFile;
2✔
128
  }
129

130
  private void extractDownloadedPlugin(FileAccess fileAccess, Path downloadedFile, String pluginId) throws IOException {
131
    Path targetDir = this.commandlet.getPluginsInstallationPath().resolve(pluginId);
6✔
132
    if (Files.exists(targetDir)) {
5!
133
      context.info("Plugin already installed, target directory already existing: {}", targetDir);
×
134
    } else {
135
      fileAccess.extract(downloadedFile, targetDir);
4✔
136
    }
137
  }
1✔
138

139
  private String getFileExtensionFromUrl(String urlString) throws RuntimeException {
140

141
    URI uri = null;
2✔
142
    HttpRequest request;
143
    try (HttpClient client = HttpClient.newBuilder().followRedirects(Redirect.ALWAYS).build()) {
5✔
144
      uri = URI.create(urlString);
3✔
145
      request = HttpRequest.newBuilder().uri(uri)
4✔
146
          .method("HEAD", HttpRequest.BodyPublishers.noBody()).timeout(Duration.ofSeconds(5)).build();
7✔
147

148
      HttpResponse<?> res = client.send(request, HttpResponse.BodyHandlers.ofString());
5✔
149

150
      int responseCode = res.statusCode();
3✔
151
      if (responseCode != HttpURLConnection.HTTP_OK) {
3!
152
        throw new RuntimeException("Failed to fetch file headers: HTTP " + responseCode);
×
153
      }
154

155
      Optional<String> contentType = res.headers().firstValue("content-type");
5✔
156
      if (contentType.isEmpty()) {
3!
157
        return "";
×
158
      }
159
      return switch (contentType.get()) {
12!
160
        case "application/zip" -> ".zip";
×
161
        case "application/java-archive" -> ".jar";
2✔
162
        default -> "";
1✔
163
      };
164
    } catch (Exception e) {
×
165
      throw new RuntimeException("Failed to perform HEAD request of URL " + uri, e);
×
166
    }
167
  }
168
}
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