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

devonfw / IDEasy / 16593268168

29 Jul 2025 10:15AM UTC coverage: 68.618% (-0.07%) from 68.686%
16593268168

push

github

web-flow
#1389: Add podman support (#1431)

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

3302 of 5220 branches covered (63.26%)

Branch coverage included in aggregate %.

8422 of 11866 relevant lines covered (70.98%)

3.14 hits per line

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

85.29
cli/src/main/java/com/devonfw/tools/ide/common/SystemPath.java
1
package com.devonfw.tools.ide.common;
2

3
import java.io.File;
4
import java.io.IOException;
5
import java.nio.file.Files;
6
import java.nio.file.Path;
7
import java.util.ArrayList;
8
import java.util.Collections;
9
import java.util.HashMap;
10
import java.util.Iterator;
11
import java.util.List;
12
import java.util.Map;
13
import java.util.regex.Pattern;
14
import java.util.stream.Stream;
15

16
import com.devonfw.tools.ide.context.IdeContext;
17
import com.devonfw.tools.ide.os.SystemInfoImpl;
18
import com.devonfw.tools.ide.os.WindowsPathSyntax;
19
import com.devonfw.tools.ide.variable.IdeVariables;
20

21
/**
22
 * Represents the PATH variable in a structured way. The PATH contains the system path entries together with the entries for the IDEasy tools. The generic
23
 * system path entries are stored in a {@link List} ({@code paths}) and the tool entries are stored in a {@link Map} ({@code tool2pathMap}) as they can change
24
 * dynamically at runtime (e.g. if a new tool is installed). As the tools must have priority the actual PATH is build by first the entries for the tools and
25
 * then the generic entries from the system PATH. Such tool entries are ignored from the actual PATH of the {@link System#getenv(String) environment} at
26
 * construction time and are recomputed from the "software" folder. This is important as the initial {@link System#getenv(String) environment} PATH entries can
27
 * come from a different IDEasy project and the use may have changed projects before calling us again. Recomputing the PATH ensures side-effects from other
28
 * projects. However, it also will ensure all the entries to IDEasy locations are automatically managed and therefore cannot be managed manually be the
29
 * end-user.
30
 */
31
public class SystemPath {
32

33
  private static final Pattern REGEX_WINDOWS_PATH = Pattern.compile("([a-zA-Z]:)?(\\\\[a-zA-Z0-9\\s_.-]+)+\\\\?");
3✔
34

35
  private final String envPath;
36

37
  private final char pathSeparator;
38

39
  private final Map<String, Path> tool2pathMap;
40

41
  private final List<Path> paths;
42

43
  private final List<Path> extraPathEntries;
44

45
  private final IdeContext context;
46

47
  private static final List<String> EXTENSION_PRIORITY = List.of(".exe", ".cmd", ".bat", ".msi", ".ps1", "");
9✔
48

49
  /**
50
   * The constructor.
51
   *
52
   * @param context {@link IdeContext}.
53
   */
54
  public SystemPath(IdeContext context) {
55

56
    this(context, System.getenv(IdeVariables.PATH.getName()));
×
57
  }
×
58

59
  /**
60
   * The constructor.
61
   *
62
   * @param context {@link IdeContext}.
63
   * @param envPath the value of the PATH variable.
64
   */
65
  public SystemPath(IdeContext context, String envPath) {
66

67
    this(context, envPath, context.getIdeRoot(), context.getSoftwarePath());
8✔
68
  }
1✔
69

70
  /**
71
   * The constructor.
72
   *
73
   * @param context {@link IdeContext} for the output of information.
74
   * @param envPath the value of the PATH variable.
75
   * @param ideRoot the {@link IdeContext#getIdeRoot() IDE_ROOT}.
76
   * @param softwarePath the {@link IdeContext#getSoftwarePath() software path}.
77
   */
78
  public SystemPath(IdeContext context, String envPath, Path ideRoot, Path softwarePath) {
79

80
    this(context, envPath, ideRoot, softwarePath, File.pathSeparatorChar, Collections.emptyList());
8✔
81
  }
1✔
82

83
  /**
84
   * The constructor.
85
   *
86
   * @param context {@link IdeContext} for the output of information.
87
   * @param envPath the value of the PATH variable.
88
   * @param ideRoot the {@link IdeContext#getIdeRoot() IDE_ROOT}.
89
   * @param softwarePath the {@link IdeContext#getSoftwarePath() software path}.
90
   * @param pathSeparator the path separator char (';' for Windows and ':' otherwise).
91
   * @param extraPathEntries the {@link List} of additional {@link Path}s to prepend.
92
   */
93
  public SystemPath(IdeContext context, String envPath, Path ideRoot, Path softwarePath, char pathSeparator, List<Path> extraPathEntries) {
94

95
    this(context, envPath, pathSeparator, extraPathEntries, new HashMap<>(), new ArrayList<>());
12✔
96
    String[] envPaths = envPath.split(Character.toString(pathSeparator));
5✔
97
    for (String segment : envPaths) {
16✔
98
      Path path = Path.of(segment);
5✔
99
      String tool = getTool(path, ideRoot);
4✔
100
      if (tool == null) {
2!
101
        this.paths.add(path);
5✔
102
      }
103
    }
104
    collectToolPath(softwarePath);
3✔
105
  }
1✔
106

107
  private SystemPath(IdeContext context, String envPath, char pathSeparator, List<Path> extraPathEntries, Map<String, Path> tool2PathMap, List<Path> paths) {
108

109
    super();
2✔
110
    this.context = context;
3✔
111
    this.envPath = envPath;
3✔
112
    this.pathSeparator = pathSeparator;
3✔
113
    this.extraPathEntries = extraPathEntries;
3✔
114
    this.tool2pathMap = tool2PathMap;
3✔
115
    this.paths = paths;
3✔
116
  }
1✔
117

118
  private void collectToolPath(Path softwarePath) {
119

120
    if (softwarePath == null) {
2✔
121
      return;
1✔
122
    }
123
    if (Files.isDirectory(softwarePath)) {
5✔
124
      try (Stream<Path> children = Files.list(softwarePath)) {
3✔
125
        Iterator<Path> iterator = children.iterator();
3✔
126
        while (iterator.hasNext()) {
3✔
127
          Path child = iterator.next();
4✔
128
          String tool = child.getFileName().toString();
4✔
129
          if (!"extra".equals(tool) && Files.isDirectory(child)) {
9!
130
            Path toolPath = child;
2✔
131
            Path bin = child.resolve("bin");
4✔
132
            if (Files.isDirectory(bin)) {
5✔
133
              toolPath = bin;
2✔
134
            }
135
            this.tool2pathMap.put(tool, toolPath);
6✔
136
          }
137
        }
1✔
138
      } catch (IOException e) {
×
139
        throw new IllegalStateException("Failed to list children of " + softwarePath, e);
×
140
      }
1✔
141
    }
142
  }
1✔
143

144
  private static String getTool(Path path, Path ideRoot) {
145

146
    if (ideRoot == null) {
2✔
147
      return null;
2✔
148
    }
149
    if (path.startsWith(ideRoot)) {
4✔
150
      Path relativized = ideRoot.relativize(path);
4✔
151
      int count = relativized.getNameCount();
3✔
152
      if (count >= 3) {
3!
153
        if (relativized.getName(1).toString().equals("software")) {
×
154
          return relativized.getName(2).toString();
×
155
        }
156
      }
157
    }
158
    return null;
2✔
159
  }
160

161
  private Path findBinaryInOrder(Path path, String tool) {
162

163
    List<String> extensionPriority = List.of("");
3✔
164
    if (SystemInfoImpl.INSTANCE.isWindows()) {
3!
165
      extensionPriority = EXTENSION_PRIORITY;
×
166
    }
167
    for (String extension : extensionPriority) {
10✔
168

169
      Path fileToExecute = path.resolve(tool + extension);
6✔
170

171
      if (Files.exists(fileToExecute)) {
5✔
172
        return fileToExecute;
2✔
173
      }
174
    }
1✔
175

176
    return null;
2✔
177
  }
178

179
  /**
180
   * @param binaryName the name of the tool.
181
   * @return {@code true} if the given {@code tool} is a binary that can be found on the PATH, {@code false} otherwise.
182
   */
183
  public boolean hasBinaryOnPath(String binaryName) {
184
    Path binary = Path.of(binaryName);
×
185
    Path resolvedBinary = findBinary(binary);
×
186
    return (resolvedBinary != binary);
×
187
  }
188

189
  /**
190
   * @param binaryName the name of the tool.
191
   * @return the {@link Path} to the binary executable of the tool. E.g. if "mvn" is given then ".../software/mvn/bin/mvn" could be returned. If the executable
192
   *     was not found on PATH, the same {@link Path} instance is returned that was given as argument.
193
   */
194
  public Path findBinaryPathByName(String binaryName) {
195
    return findBinary(Path.of(binaryName));
×
196
  }
197

198
  /**
199
   * @param toolPath the {@link Path} to the tool installation.
200
   * @return the {@link Path} to the binary executable of the tool. E.g. if "mvn" is given then ".../software/mvn/bin/mvn" could be returned. If the executable
201
   *     was not found on PATH, the same {@link Path} instance is returned that was given as argument.
202
   */
203
  public Path findBinary(Path toolPath) {
204

205
    Path parent = toolPath.getParent();
3✔
206
    String fileName = toolPath.getFileName().toString();
4✔
207

208
    if (parent == null) {
2✔
209

210
      for (Path path : this.tool2pathMap.values()) {
12✔
211
        Path binaryPath = findBinaryInOrder(path, fileName);
5✔
212
        if (binaryPath != null) {
2✔
213
          return binaryPath;
2✔
214
        }
215
      }
1✔
216

217
      for (Path path : this.paths) {
11!
218
        Path binaryPath = findBinaryInOrder(path, fileName);
5✔
219
        if (binaryPath != null) {
2✔
220
          return binaryPath;
2✔
221
        }
222
      }
1✔
223
    } else {
224
      Path binaryPath = findBinaryInOrder(parent, fileName);
5✔
225
      if (binaryPath != null) {
2✔
226
        return binaryPath;
2✔
227
      }
228
    }
229

230
    return toolPath;
2✔
231
  }
232

233
  /**
234
   * @param tool the name of the tool.
235
   * @return the {@link Path} to the directory of the tool where the binaries can be found or {@code null} if the tool is not installed.
236
   */
237
  public Path getPath(String tool) {
238

239
    return this.tool2pathMap.get(tool);
6✔
240
  }
241

242
  /**
243
   * @param tool the name of the tool.
244
   * @param path the new {@link #getPath(String) tool bin path}.
245
   */
246
  public void setPath(String tool, Path path) {
247

248
    this.tool2pathMap.put(tool, path);
6✔
249
  }
1✔
250

251
  @Override
252
  public String toString() {
253

254
    return toString(null);
4✔
255
  }
256

257
  /**
258
   * @param pathSyntax the {@link WindowsPathSyntax} to convert to.
259
   * @return this {@link SystemPath} as {@link String} for the PATH environment variable.
260
   */
261
  public String toString(WindowsPathSyntax pathSyntax) {
262

263
    char separator;
264
    if (pathSyntax == WindowsPathSyntax.MSYS) {
3!
265
      separator = ':';
×
266
    } else {
267
      separator = this.pathSeparator;
3✔
268
    }
269
    StringBuilder sb = new StringBuilder(this.envPath.length() + 128);
9✔
270
    for (Path path : this.extraPathEntries) {
11✔
271
      appendPath(path, sb, separator, pathSyntax);
5✔
272
    }
1✔
273
    for (Path path : this.tool2pathMap.values()) {
12✔
274
      appendPath(path, sb, separator, pathSyntax);
5✔
275
    }
1✔
276
    for (Path path : this.paths) {
11✔
277
      appendPath(path, sb, separator, pathSyntax);
5✔
278
    }
1✔
279
    return sb.toString();
3✔
280
  }
281

282
  /**
283
   * Derive a new {@link SystemPath} from this instance with the given parameters.
284
   *
285
   * @param overriddenPath the entire PATH to override and replace the current one from this {@link SystemPath} or {@code null} to keep the current PATH.
286
   * @param extraPathEntries the {@link List} of additional PATH entries to add to the beginning of the PATH. May be empty to add nothing.
287
   * @return the new {@link SystemPath} derived from this instance with the given parameters applied.
288
   */
289
  public SystemPath withPath(String overriddenPath, List<Path> extraPathEntries) {
290

291
    if (overriddenPath == null) {
2!
292
      return new SystemPath(this.context, this.envPath, this.pathSeparator, extraPathEntries, this.tool2pathMap, this.paths);
15✔
293
    } else {
294
      return new SystemPath(this.context, overriddenPath, null, null, this.pathSeparator, extraPathEntries);
×
295
    }
296
  }
297

298
  private static void appendPath(Path path, StringBuilder sb, char separator, WindowsPathSyntax pathSyntax) {
299

300
    if (sb.length() > 0) {
3✔
301
      sb.append(separator);
4✔
302
    }
303
    String pathString;
304
    if (pathSyntax == null) {
2✔
305
      pathString = path.toString();
4✔
306
    } else {
307
      pathString = pathSyntax.format(path);
4✔
308
    }
309
    sb.append(pathString);
4✔
310
  }
1✔
311

312
  /**
313
   * Method to validate if a given path string is a Windows path or not
314
   *
315
   * @param pathString The string to check if it is a Windows path string.
316
   * @return {@code true} if it is a valid windows path string, else {@code false}.
317
   */
318
  public static boolean isValidWindowsPath(String pathString) {
319

320
    return REGEX_WINDOWS_PATH.matcher(pathString).matches();
5✔
321
  }
322
}
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