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

devonfw / IDEasy / 9598832091

20 Jun 2024 01:59PM UTC coverage: 60.002%. Remained the same
9598832091

push

github

web-flow
#323: fix removal of PATH entires from previous IDEasy projects (#401)

1813 of 3331 branches covered (54.43%)

Branch coverage included in aggregate %.

4759 of 7622 relevant lines covered (62.44%)

2.73 hits per line

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

56.81
cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java
1
package com.devonfw.tools.ide.context;
2

3
import com.devonfw.tools.ide.cli.CliAbortException;
4
import com.devonfw.tools.ide.cli.CliArgument;
5
import com.devonfw.tools.ide.cli.CliArguments;
6
import com.devonfw.tools.ide.cli.CliException;
7
import com.devonfw.tools.ide.commandlet.Commandlet;
8
import com.devonfw.tools.ide.commandlet.CommandletManager;
9
import com.devonfw.tools.ide.commandlet.CommandletManagerImpl;
10
import com.devonfw.tools.ide.commandlet.ContextCommandlet;
11
import com.devonfw.tools.ide.commandlet.HelpCommandlet;
12
import com.devonfw.tools.ide.common.SystemPath;
13
import com.devonfw.tools.ide.completion.CompletionCandidate;
14
import com.devonfw.tools.ide.completion.CompletionCandidateCollector;
15
import com.devonfw.tools.ide.completion.CompletionCandidateCollectorDefault;
16
import com.devonfw.tools.ide.environment.AbstractEnvironmentVariables;
17
import com.devonfw.tools.ide.environment.EnvironmentVariables;
18
import com.devonfw.tools.ide.environment.EnvironmentVariablesType;
19
import com.devonfw.tools.ide.io.FileAccess;
20
import com.devonfw.tools.ide.io.FileAccessImpl;
21
import com.devonfw.tools.ide.log.IdeLogLevel;
22
import com.devonfw.tools.ide.log.IdeSubLogger;
23
import com.devonfw.tools.ide.log.IdeSubLoggerNone;
24
import com.devonfw.tools.ide.merge.DirectoryMerger;
25
import com.devonfw.tools.ide.network.ProxyContext;
26
import com.devonfw.tools.ide.os.SystemInfo;
27
import com.devonfw.tools.ide.os.SystemInfoImpl;
28
import com.devonfw.tools.ide.os.WindowsPathSyntax;
29
import com.devonfw.tools.ide.process.ProcessContext;
30
import com.devonfw.tools.ide.process.ProcessContextImpl;
31
import com.devonfw.tools.ide.process.ProcessResult;
32
import com.devonfw.tools.ide.property.Property;
33
import com.devonfw.tools.ide.repo.CustomToolRepository;
34
import com.devonfw.tools.ide.repo.CustomToolRepositoryImpl;
35
import com.devonfw.tools.ide.repo.DefaultToolRepository;
36
import com.devonfw.tools.ide.repo.ToolRepository;
37
import com.devonfw.tools.ide.step.Step;
38
import com.devonfw.tools.ide.step.StepImpl;
39
import com.devonfw.tools.ide.url.model.UrlMetadata;
40

41
import java.io.BufferedReader;
42
import java.io.IOException;
43
import java.io.InputStreamReader;
44
import java.net.URL;
45
import java.net.URLConnection;
46
import java.nio.file.Files;
47
import java.nio.file.Path;
48
import java.util.HashMap;
49
import java.util.Iterator;
50
import java.util.List;
51
import java.util.Locale;
52
import java.util.Map;
53
import java.util.Objects;
54
import java.util.function.Function;
55

56
/**
57
 * Abstract base implementation of {@link IdeContext}.
58
 */
59
public abstract class AbstractIdeContext implements IdeContext {
1✔
60

61
  private static final String IDE_URLS_GIT = "https://github.com/devonfw/ide-urls.git";
62

63
  private final Map<IdeLogLevel, IdeSubLogger> loggers;
64

65
  private Path ideHome;
66

67
  private final Path ideRoot;
68

69
  private Path confPath;
70

71
  private Path settingsPath;
72

73
  private Path softwarePath;
74

75
  private Path softwareExtraPath;
76

77
  private Path softwareRepositoryPath;
78

79
  private Path pluginsPath;
80

81
  private Path workspacePath;
82

83
  private String workspaceName;
84

85
  private Path urlsPath;
86

87
  private Path tempPath;
88

89
  private Path tempDownloadPath;
90

91
  private Path cwd;
92

93
  private Path downloadPath;
94

95
  private Path toolRepository;
96

97
  private Path userHome;
98

99
  private Path userHomeIde;
100

101
  private SystemPath path;
102

103
  private WindowsPathSyntax pathSyntax;
104

105
  private final SystemInfo systemInfo;
106

107
  private EnvironmentVariables variables;
108

109
  private final FileAccess fileAccess;
110

111
  private final CommandletManager commandletManager;
112

113
  private final ToolRepository defaultToolRepository;
114

115
  private CustomToolRepository customToolRepository;
116

117
  private DirectoryMerger workspaceMerger;
118

119
  private final Function<IdeLogLevel, IdeSubLogger> loggerFactory;
120

121
  private boolean offlineMode;
122

123
  private boolean forceMode;
124

125
  private boolean batchMode;
126

127
  private boolean quietMode;
128

129
  private Locale locale;
130

131
  private UrlMetadata urlMetadata;
132

133
  private Path defaultExecutionDirectory;
134

135
  private StepImpl currentStep;
136

137
  /**
138
   * The constructor.
139
   *
140
   * @param minLogLevel the minimum {@link IdeLogLevel} to enable. Should be {@link IdeLogLevel#INFO} by default.
141
   * @param factory the {@link Function} to create {@link IdeSubLogger} per {@link IdeLogLevel}.
142
   * @param userDir the optional {@link Path} to current working directory.
143
   * @param toolRepository @param toolRepository the {@link ToolRepository} of the context. If it is set to {@code null} {@link DefaultToolRepository} will be
144
   * used.
145
   */
146
  public AbstractIdeContext(IdeLogLevel minLogLevel, Function<IdeLogLevel, IdeSubLogger> factory, Path userDir, ToolRepository toolRepository) {
147

148
    super();
2✔
149
    this.loggerFactory = factory;
3✔
150
    this.loggers = new HashMap<>();
5✔
151
    setLogLevel(minLogLevel);
3✔
152
    this.systemInfo = SystemInfoImpl.INSTANCE;
3✔
153
    this.commandletManager = new CommandletManagerImpl(this);
6✔
154
    this.fileAccess = new FileAccessImpl(this);
6✔
155
    String workspace = WORKSPACE_MAIN;
2✔
156
    if (userDir == null) {
2✔
157
      userDir = Path.of(System.getProperty("user.dir"));
7✔
158
    } else {
159
      userDir = userDir.toAbsolutePath();
3✔
160
    }
161
    // detect IDE_HOME and WORKSPACE
162
    Path currentDir = userDir;
2✔
163
    String name1 = "";
2✔
164
    String name2 = "";
2✔
165
    while (currentDir != null) {
2✔
166
      trace("Looking for IDE_HOME in {}", currentDir);
9✔
167
      if (isIdeHome(currentDir)) {
4✔
168
        if (FOLDER_WORKSPACES.equals(name1)) {
4✔
169
          workspace = name2;
3✔
170
        }
171
        break;
172
      }
173
      name2 = name1;
2✔
174
      int nameCount = currentDir.getNameCount();
3✔
175
      if (nameCount >= 1) {
3✔
176
        name1 = currentDir.getName(nameCount - 1).toString();
7✔
177
      }
178
      currentDir = getParentPath(currentDir);
4✔
179
    }
1✔
180

181
    // detection completed, initializing variables
182
    Path ideRootPath = null;
2✔
183
    if (currentDir == null) {
2✔
184
      info(getMessageIdeHomeNotFound());
5✔
185
    } else {
186
      debug(getMessageIdeHomeFound());
4✔
187
      ideRootPath = currentDir.getParent();
3✔
188
    }
189

190
    if (!isTest()) {
3✔
191
      String root = System.getenv("IDE_ROOT");
3✔
192
      if (root != null) {
2!
193
        Path rootPath = Path.of(root);
×
194
        if (ideRootPath == null) {
×
195
          ideRootPath = rootPath;
×
196
        } else if (!ideRootPath.equals(rootPath)) {
×
197
          warning("Variable IDE_ROOT is set to '{}' but for your project '{}' the path '{}' would have been expected.", rootPath, this.ideHome.getFileName(),
×
198
              ideRootPath);
199
        }
200
      }
201
    }
202
    if (ideRootPath == null || !Files.isDirectory(ideRootPath)) {
7!
203
      error("IDE_ROOT is not set or not a valid directory.");
3✔
204
    }
205
    this.ideRoot = ideRootPath;
3✔
206

207
    setCwd(userDir, workspace, currentDir);
5✔
208

209
    if (this.ideRoot == null) {
3✔
210
      this.toolRepository = null;
3✔
211
      this.urlsPath = null;
3✔
212
      this.tempPath = null;
3✔
213
      this.tempDownloadPath = null;
3✔
214
      this.softwareRepositoryPath = null;
4✔
215
    } else {
216
      Path ideBase = this.ideRoot.resolve(FOLDER_IDE);
5✔
217
      this.toolRepository = ideBase.resolve("software");
5✔
218
      this.urlsPath = ideBase.resolve("urls");
5✔
219
      this.tempPath = ideBase.resolve("tmp");
5✔
220
      this.tempDownloadPath = this.tempPath.resolve(FOLDER_DOWNLOADS);
6✔
221
      this.softwareRepositoryPath = ideBase.resolve(FOLDER_SOFTWARE);
5✔
222
      if (Files.isDirectory(this.tempPath)) {
7✔
223
        // TODO delete all files older than 1 day here...
224
      } else {
225
        this.fileAccess.mkdirs(this.tempDownloadPath);
5✔
226
      }
227
    }
228

229
    if (toolRepository == null) {
2✔
230
      this.defaultToolRepository = new DefaultToolRepository(this);
7✔
231
    } else {
232
      this.defaultToolRepository = toolRepository;
3✔
233
    }
234
  }
1✔
235

236
  @Override
237
  public void setCwd(Path userDir, String workspace, Path ideHome) {
238

239
    this.cwd = userDir;
3✔
240
    this.workspaceName = workspace;
3✔
241
    this.ideHome = ideHome;
3✔
242
    if (ideHome == null) {
2✔
243
      this.workspacePath = null;
3✔
244
      this.confPath = null;
3✔
245
      this.settingsPath = null;
3✔
246
      this.softwarePath = null;
3✔
247
      this.softwareExtraPath = null;
3✔
248
      this.pluginsPath = null;
4✔
249
    } else {
250
      this.workspacePath = this.ideHome.resolve(FOLDER_WORKSPACES).resolve(this.workspaceName);
9✔
251
      this.confPath = this.ideHome.resolve(FOLDER_CONF);
6✔
252
      this.settingsPath = this.ideHome.resolve(FOLDER_SETTINGS);
6✔
253
      this.softwarePath = this.ideHome.resolve(FOLDER_SOFTWARE);
6✔
254
      this.softwareExtraPath = this.softwarePath.resolve(FOLDER_EXTRA);
6✔
255
      this.pluginsPath = this.ideHome.resolve(FOLDER_PLUGINS);
6✔
256
    }
257
    if (isTest()) {
3✔
258
      // only for testing...
259
      if (this.ideHome == null) {
3✔
260
        this.userHome = Path.of("/non-existing-user-home-for-testing");
7✔
261
      } else {
262
        this.userHome = this.ideHome.resolve("home");
7✔
263
      }
264
    } else {
265
      this.userHome = Path.of(System.getProperty("user.home"));
7✔
266
    }
267
    this.userHomeIde = this.userHome.resolve(".ide");
6✔
268
    this.downloadPath = this.userHome.resolve("Downloads/ide");
6✔
269

270
    this.variables = createVariables();
4✔
271
    this.path = computeSystemPath();
4✔
272
    this.customToolRepository = CustomToolRepositoryImpl.of(this);
4✔
273
    this.workspaceMerger = new DirectoryMerger(this);
6✔
274
  }
1✔
275

276
  private String getMessageIdeHomeFound() {
277

278
    return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName;
6✔
279
  }
280

281
  private String getMessageIdeHomeNotFound() {
282

283
    return "You are not inside an IDE installation: " + this.cwd;
4✔
284
  }
285

286
  /**
287
   * @return the status message about the {@link #getIdeHome() IDE_HOME} detection and environment variable initialization.
288
   */
289
  public String getMessageIdeHome() {
290

291
    if (this.ideHome == null) {
×
292
      return getMessageIdeHomeNotFound();
×
293
    }
294
    return getMessageIdeHomeFound();
×
295
  }
296

297
  /**
298
   * @return {@code true} if this is a test context for JUnits, {@code false} otherwise.
299
   */
300
  public boolean isTest() {
301

302
    return isMock();
3✔
303
  }
304

305
  /**
306
   * @return {@code true} if this is a mock context for JUnits, {@code false} otherwise.
307
   */
308
  public boolean isMock() {
309

310
    return false;
2✔
311
  }
312

313
  private SystemPath computeSystemPath() {
314

315
    return new SystemPath(this);
5✔
316
  }
317

318
  private boolean isIdeHome(Path dir) {
319

320
    if (!Files.isDirectory(dir.resolve("workspaces"))) {
7✔
321
      return false;
2✔
322
    } else if (!Files.isDirectory(dir.resolve("settings"))) {
7!
323
      return false;
×
324
    }
325
    return true;
2✔
326
  }
327

328
  private Path getParentPath(Path dir) {
329

330
    try {
331
      Path linkDir = dir.toRealPath();
5✔
332
      if (!dir.equals(linkDir)) {
4!
333
        return linkDir;
×
334
      } else {
335
        return dir.getParent();
3✔
336
      }
337
    } catch (IOException e) {
×
338
      throw new IllegalStateException(e);
×
339
    }
340

341
  }
342

343
  private EnvironmentVariables createVariables() {
344

345
    AbstractEnvironmentVariables system = EnvironmentVariables.ofSystem(this);
3✔
346
    AbstractEnvironmentVariables user = extendVariables(system, this.userHomeIde, EnvironmentVariablesType.USER);
7✔
347
    AbstractEnvironmentVariables settings = extendVariables(user, this.settingsPath, EnvironmentVariablesType.SETTINGS);
7✔
348
    // TODO should we keep this workspace properties? Was this feature ever used?
349
    AbstractEnvironmentVariables workspace = extendVariables(settings, this.workspacePath, EnvironmentVariablesType.WORKSPACE);
7✔
350
    AbstractEnvironmentVariables conf = extendVariables(workspace, this.confPath, EnvironmentVariablesType.CONF);
7✔
351
    return conf.resolved();
3✔
352
  }
353

354
  private AbstractEnvironmentVariables extendVariables(AbstractEnvironmentVariables envVariables, Path propertiesPath, EnvironmentVariablesType type) {
355

356
    Path propertiesFile = null;
2✔
357
    if (propertiesPath == null) {
2✔
358
      trace("Configuration directory for type {} does not exist.", type);
10✔
359
    } else if (Files.isDirectory(propertiesPath)) {
5✔
360
      propertiesFile = propertiesPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES);
4✔
361
      boolean legacySupport = (type != EnvironmentVariablesType.USER);
7✔
362
      if (legacySupport && !Files.exists(propertiesFile)) {
7✔
363
        Path legacyFile = propertiesPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES);
4✔
364
        if (Files.exists(legacyFile)) {
5!
365
          propertiesFile = legacyFile;
×
366
        }
367
      }
368
    } else {
1✔
369
      debug("Configuration directory {} does not exist.", propertiesPath);
9✔
370
    }
371
    return envVariables.extend(propertiesFile, type);
5✔
372
  }
373

374
  @Override
375
  public SystemInfo getSystemInfo() {
376

377
    return this.systemInfo;
3✔
378
  }
379

380
  @Override
381
  public FileAccess getFileAccess() {
382

383
    return this.fileAccess;
3✔
384
  }
385

386
  @Override
387
  public CommandletManager getCommandletManager() {
388

389
    return this.commandletManager;
3✔
390
  }
391

392
  @Override
393
  public ToolRepository getDefaultToolRepository() {
394

395
    return this.defaultToolRepository;
3✔
396
  }
397

398
  @Override
399
  public CustomToolRepository getCustomToolRepository() {
400

401
    return this.customToolRepository;
3✔
402
  }
403

404
  @Override
405
  public Path getIdeHome() {
406

407
    return this.ideHome;
3✔
408
  }
409

410
  @Override
411
  public String getProjectName() {
412

413
    if (this.ideHome != null) {
3!
414
      return this.ideHome.getFileName().toString();
5✔
415
    }
416
    return "";
×
417
  }
418

419
  @Override
420
  public Path getIdeRoot() {
421

422
    return this.ideRoot;
3✔
423
  }
424

425
  @Override
426
  public Path getCwd() {
427

428
    return this.cwd;
3✔
429
  }
430

431
  @Override
432
  public Path getTempPath() {
433

434
    return this.tempPath;
3✔
435
  }
436

437
  @Override
438
  public Path getTempDownloadPath() {
439

440
    return this.tempDownloadPath;
3✔
441
  }
442

443
  @Override
444
  public Path getUserHome() {
445

446
    return this.userHome;
3✔
447
  }
448

449
  @Override
450
  public Path getUserHomeIde() {
451

452
    return this.userHomeIde;
3✔
453
  }
454

455
  @Override
456
  public Path getSettingsPath() {
457

458
    return this.settingsPath;
3✔
459
  }
460

461
  @Override
462
  public Path getConfPath() {
463

464
    return this.confPath;
3✔
465
  }
466

467
  @Override
468
  public Path getSoftwarePath() {
469

470
    return this.softwarePath;
3✔
471
  }
472

473
  @Override
474
  public Path getSoftwareExtraPath() {
475

476
    return this.softwareExtraPath;
3✔
477
  }
478

479
  @Override
480
  public Path getSoftwareRepositoryPath() {
481

482
    return this.softwareRepositoryPath;
3✔
483
  }
484

485
  @Override
486
  public Path getPluginsPath() {
487

488
    return this.pluginsPath;
3✔
489
  }
490

491
  @Override
492
  public String getWorkspaceName() {
493

494
    return this.workspaceName;
3✔
495
  }
496

497
  @Override
498
  public Path getWorkspacePath() {
499

500
    return this.workspacePath;
3✔
501
  }
502

503
  @Override
504
  public Path getDownloadPath() {
505

506
    return this.downloadPath;
3✔
507
  }
508

509
  @Override
510
  public Path getUrlsPath() {
511

512
    return this.urlsPath;
3✔
513
  }
514

515
  @Override
516
  public Path getToolRepositoryPath() {
517

518
    return this.toolRepository;
3✔
519
  }
520

521
  @Override
522
  public SystemPath getPath() {
523

524
    return this.path;
3✔
525
  }
526

527
  @Override
528
  public EnvironmentVariables getVariables() {
529

530
    return this.variables;
3✔
531
  }
532

533
  @Override
534
  public UrlMetadata getUrls() {
535

536
    if (this.urlMetadata == null) {
3✔
537
      if (!isTest()) {
3!
538
        getGitContext().pullOrCloneAndResetIfNeeded(IDE_URLS_GIT, this.urlsPath, null);
×
539
      }
540
      this.urlMetadata = new UrlMetadata(this);
6✔
541
    }
542
    return this.urlMetadata;
3✔
543
  }
544

545
  @Override
546
  public boolean isQuietMode() {
547

548
    return this.quietMode;
3✔
549
  }
550

551
  /**
552
   * @param quietMode new value of {@link #isQuietMode()}.
553
   */
554
  public void setQuietMode(boolean quietMode) {
555

556
    this.quietMode = quietMode;
3✔
557
  }
1✔
558

559
  @Override
560
  public boolean isBatchMode() {
561

562
    return this.batchMode;
3✔
563
  }
564

565
  /**
566
   * @param batchMode new value of {@link #isBatchMode()}.
567
   */
568
  public void setBatchMode(boolean batchMode) {
569

570
    this.batchMode = batchMode;
3✔
571
  }
1✔
572

573
  @Override
574
  public boolean isForceMode() {
575

576
    return this.forceMode;
3✔
577
  }
578

579
  /**
580
   * @param forceMode new value of {@link #isForceMode()}.
581
   */
582
  public void setForceMode(boolean forceMode) {
583

584
    this.forceMode = forceMode;
3✔
585
  }
1✔
586

587
  @Override
588
  public boolean isOfflineMode() {
589

590
    return this.offlineMode;
3✔
591
  }
592

593
  /**
594
   * @param offlineMode new value of {@link #isOfflineMode()}.
595
   */
596
  public void setOfflineMode(boolean offlineMode) {
597

598
    this.offlineMode = offlineMode;
3✔
599
  }
1✔
600

601
  @Override
602
  public boolean isOnline() {
603

604
    boolean online = false;
×
605
    try {
606
      int timeout = 1000;
×
607
      //open a connection to github.com and try to retrieve data
608
      //getContent fails if there is no connection
609
      URLConnection connection = new URL("https://www.github.com").openConnection();
×
610
      connection.setConnectTimeout(timeout);
×
611
      connection.getContent();
×
612
      online = true;
×
613

614
    } catch (Exception ignored) {
×
615

616
    }
×
617
    return online;
×
618
  }
619

620
  @Override
621
  public Locale getLocale() {
622

623
    if (this.locale == null) {
3!
624
      return Locale.getDefault();
2✔
625
    }
626
    return this.locale;
×
627
  }
628

629
  /**
630
   * @param locale new value of {@link #getLocale()}.
631
   */
632
  public void setLocale(Locale locale) {
633

634
    this.locale = locale;
3✔
635
  }
1✔
636

637
  @Override
638
  public DirectoryMerger getWorkspaceMerger() {
639

640
    return this.workspaceMerger;
3✔
641
  }
642

643
  /**
644
   * @return the {@link #getDefaultExecutionDirectory() default execution directory} in which a command process is executed.
645
   */
646
  public Path getDefaultExecutionDirectory() {
647

648
    return this.defaultExecutionDirectory;
×
649
  }
650

651
  /**
652
   * @param defaultExecutionDirectory new value of {@link #getDefaultExecutionDirectory()}.
653
   */
654
  public void setDefaultExecutionDirectory(Path defaultExecutionDirectory) {
655

656
    if (defaultExecutionDirectory != null) {
×
657
      this.defaultExecutionDirectory = defaultExecutionDirectory;
×
658
    }
659
  }
×
660

661
  @Override
662
  public ProxyContext getProxyContext() {
663

664
    return new ProxyContext(this);
5✔
665
  }
666

667
  @Override
668
  public GitContext getGitContext() {
669

670
    return new GitContextImpl(this);
×
671
  }
672

673
  @Override
674
  public ProcessContext newProcess() {
675

676
    ProcessContext processContext = createProcessContext();
3✔
677
    if (this.defaultExecutionDirectory != null) {
3!
678
      processContext.directory(this.defaultExecutionDirectory);
×
679
    }
680
    return processContext;
2✔
681
  }
682

683
  /**
684
   * @return a new instance of {@link ProcessContext}.
685
   * @see #newProcess()
686
   */
687
  protected ProcessContext createProcessContext() {
688

689
    return new ProcessContextImpl(this);
×
690
  }
691

692
  @Override
693
  public IdeSubLogger level(IdeLogLevel level) {
694

695
    IdeSubLogger logger = this.loggers.get(level);
6✔
696
    Objects.requireNonNull(logger);
3✔
697
    return logger;
2✔
698
  }
699

700
  @Override
701
  public String askForInput(String message, String defaultValue) {
702

703
    if (!message.isBlank()) {
×
704
      info(message);
×
705
    }
706
    if (isBatchMode()) {
×
707
      if (isForceMode()) {
×
708
        return defaultValue;
×
709
      } else {
710
        throw new CliAbortException();
×
711
      }
712
    }
713
    String input = readLine().trim();
×
714
    return input.isEmpty() ? defaultValue : input;
×
715
  }
716

717
  @Override
718
  public String askForInput(String message) {
719

720
    String input;
721
    do {
722
      info(message);
×
723
      input = readLine().trim();
×
724
    } while (input.isEmpty());
×
725

726
    return input;
×
727
  }
728

729
  @SuppressWarnings("unchecked")
730
  @Override
731
  public <O> O question(String question, O... options) {
732

733
    assert (options.length >= 2);
×
734
    interaction(question);
×
735
    Map<String, O> mapping = new HashMap<>(options.length);
×
736
    int i = 0;
×
737
    for (O option : options) {
×
738
      i++;
×
739
      String key = "" + option;
×
740
      addMapping(mapping, key, option);
×
741
      String numericKey = Integer.toString(i);
×
742
      if (numericKey.equals(key)) {
×
743
        trace("Options should not be numeric: " + key);
×
744
      } else {
745
        addMapping(mapping, numericKey, option);
×
746
      }
747
      interaction("Option " + numericKey + ": " + key);
×
748
    }
749
    O option = null;
×
750
    if (isBatchMode()) {
×
751
      if (isForceMode()) {
×
752
        option = options[0];
×
753
        interaction("" + option);
×
754
      }
755
    } else {
756
      while (option == null) {
×
757
        String answer = readLine();
×
758
        option = mapping.get(answer);
×
759
        if (option == null) {
×
760
          warning("Invalid answer: '" + answer + "' - please try again.");
×
761
        }
762
      }
×
763
    }
764
    return option;
×
765
  }
766

767
  /**
768
   * @return the input from the end-user (e.g. read from the console).
769
   */
770
  protected abstract String readLine();
771

772
  private static <O> void addMapping(Map<String, O> mapping, String key, O option) {
773

774
    O duplicate = mapping.put(key, option);
×
775
    if (duplicate != null) {
×
776
      throw new IllegalArgumentException("Duplicated option " + key);
×
777
    }
778
  }
×
779

780
  /**
781
   * Sets the log level.
782
   *
783
   * @param logLevel {@link IdeLogLevel}
784
   */
785
  public void setLogLevel(IdeLogLevel logLevel) {
786

787
    for (IdeLogLevel level : IdeLogLevel.values()) {
16✔
788
      IdeSubLogger logger;
789
      if (level.ordinal() < logLevel.ordinal()) {
5✔
790
        logger = new IdeSubLoggerNone(level);
6✔
791
      } else {
792
        logger = this.loggerFactory.apply(level);
6✔
793
      }
794
      this.loggers.put(level, logger);
6✔
795
    }
796
  }
1✔
797

798
  @Override
799
  public Step getCurrentStep() {
800

801
    return this.currentStep;
×
802
  }
803

804
  @Override
805
  public StepImpl newStep(boolean silent, String name, Object... parameters) {
806

807
    this.currentStep = new StepImpl(this, this.currentStep, name, silent, parameters);
11✔
808
    return this.currentStep;
3✔
809
  }
810

811
  /**
812
   * Internal method to end the running {@link Step}.
813
   *
814
   * @param step the current {@link Step} to end.
815
   */
816
  public void endStep(StepImpl step) {
817

818
    if (step == this.currentStep) {
4!
819
      this.currentStep = this.currentStep.getParent();
6✔
820
    } else {
821
      String currentStepName = "null";
×
822
      if (this.currentStep != null) {
×
823
        currentStepName = this.currentStep.getName();
×
824
      }
825
      warning("endStep called with wrong step '{}' but expected '{}'", step.getName(), currentStepName);
×
826
    }
827
  }
1✔
828

829
  /**
830
   * Finds the matching {@link Commandlet} to run, applies {@link CliArguments} to its {@link Commandlet#getProperties() properties} and will execute it.
831
   *
832
   * @param arguments the {@link CliArgument}.
833
   * @return the return code of the execution.
834
   */
835
  public int run(CliArguments arguments) {
836

837
    CliArgument current = arguments.current();
×
838
    assert (this.currentStep == null);
×
839
    boolean supressStepSuccess = false;
×
840
    StepImpl step = newStep(true, "ide", (Object[]) current.asArray());
×
841
    try {
842
      if (!current.isEnd()) {
×
843
        String keyword = current.get();
×
844
        Commandlet firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword);
×
845
        boolean matches;
846
        if (firstCandidate != null) {
×
847
          matches = applyAndRun(arguments.copy(), firstCandidate);
×
848
          if (matches) {
×
849
            supressStepSuccess = firstCandidate.isSuppressStepSuccess();
×
850
            step.success();
×
851
            return ProcessResult.SUCCESS;
×
852
          }
853
        }
854
        for (Commandlet cmd : this.commandletManager.getCommandlets()) {
×
855
          if (cmd != firstCandidate) {
×
856
            matches = applyAndRun(arguments.copy(), cmd);
×
857
            if (matches) {
×
858
              supressStepSuccess = cmd.isSuppressStepSuccess();
×
859
              step.success();
×
860
              return ProcessResult.SUCCESS;
×
861
            }
862
          }
863
        }
×
864
        step.error("Invalid arguments: {}", current.getArgs());
×
865
      }
866
      this.commandletManager.getCommandlet(HelpCommandlet.class).run();
×
867
      return 1;
×
868
    } catch (Throwable t) {
×
869
      step.error(t, true);
×
870
      throw t;
×
871
    } finally {
872
      step.end();
×
873
      assert (this.currentStep == null);
×
874
      step.logSummary(supressStepSuccess);
×
875
    }
876
  }
877

878
  /**
879
   * @param cmd the potential {@link Commandlet} to
880
   *     {@link #apply(CliArguments, Commandlet, CompletionCandidateCollector) apply} and {@link Commandlet#run() run}.
881
   * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully,
882
   *     {@code false} otherwise (the {@link Commandlet} did not match and we have to try a different candidate).
883
   */
884
  private boolean applyAndRun(CliArguments arguments, Commandlet cmd) {
885

886
    cmd.clearProperties();
×
887

888
    boolean matches = apply(arguments, cmd, null);
×
889
    if (matches) {
×
890
      matches = cmd.validate();
×
891
    }
892
    if (matches) {
×
893
      debug("Running commandlet {}", cmd);
×
894
      if (cmd.isIdeHomeRequired() && (this.ideHome == null)) {
×
895
        throw new CliException(getMessageIdeHomeNotFound());
×
896
      }
897
      cmd.run();
×
898
    } else {
899
      trace("Commandlet did not match");
×
900
    }
901
    return matches;
×
902
  }
903

904
  /**
905
   * @param arguments the {@link CliArguments#ofCompletion(String...) completion arguments}.
906
   * @param includeContextOptions to include the options of {@link ContextCommandlet}.
907
   * @return the {@link List} of {@link CompletionCandidate}s to suggest.
908
   */
909
  public List<CompletionCandidate> complete(CliArguments arguments, boolean includeContextOptions) {
910

911
    CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault(this);
5✔
912
    if (arguments.current().isStart()) {
4✔
913
      arguments.next();
3✔
914
    }
915
    if (includeContextOptions) {
2✔
916
      ContextCommandlet cc = new ContextCommandlet();
4✔
917
      for (Property<?> property : cc.getProperties()) {
11✔
918
        assert (property.isOption());
4!
919
        property.apply(arguments, this, cc, collector);
7✔
920
      }
1✔
921
    }
922
    CliArgument current = arguments.current();
3✔
923
    if (!current.isEnd()) {
3!
924
      String keyword = current.get();
3✔
925
      Commandlet firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword);
5✔
926
      boolean matches = false;
2✔
927
      if (firstCandidate != null) {
2✔
928
        matches = apply(arguments.copy(), firstCandidate, collector);
8✔
929
      } else if (current.isCombinedShortOption()) {
3✔
930
        collector.add(keyword, null, null, null);
6✔
931
      }
932
      if (!matches) {
2!
933
        for (Commandlet cmd : this.commandletManager.getCommandlets()) {
12✔
934
          if (cmd != firstCandidate) {
3✔
935
            apply(arguments.copy(), cmd, collector);
7✔
936
          }
937
        }
1✔
938
      }
939
    }
940
    return collector.getSortedCandidates();
3✔
941
  }
942

943
  /**
944
   * @param arguments the {@link CliArguments} to apply. Will be {@link CliArguments#next() consumed} as they are matched. Consider passing a
945
   * {@link CliArguments#copy() copy} as needed.
946
   * @param cmd the potential {@link Commandlet} to match.
947
   * @param collector the {@link CompletionCandidateCollector}.
948
   * @return {@code true} if the given {@link Commandlet} matches to the given {@link CliArgument}(s) and those have been applied (set in the {@link Commandlet}
949
   * and {@link Commandlet#validate() validated}), {@code false} otherwise (the {@link Commandlet} did not match and we have to try a different candidate).
950
   */
951
  public boolean apply(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) {
952

953
    trace("Trying to match arguments to commandlet {}", cmd.getName());
10✔
954
    CliArgument currentArgument = arguments.current();
3✔
955
    Iterator<Property<?>> propertyIterator;
956
    if (currentArgument.isCompletion()) {
3✔
957
      propertyIterator = cmd.getProperties().iterator();
5✔
958
    } else {
959
      propertyIterator = cmd.getValues().iterator();
4✔
960
    }
961
    while (!currentArgument.isEnd()) {
3!
962
      trace("Trying to match argument '{}'", currentArgument);
9✔
963
      Property<?> property = null;
2✔
964
      if (!arguments.isEndOptions()) {
3!
965
        property = cmd.getOption(currentArgument.getKey());
5✔
966
      }
967
      if (property == null) {
2✔
968
        if (!propertyIterator.hasNext()) {
3✔
969
          trace("No option or next value found");
3✔
970
          return false;
2✔
971
        }
972
        property = propertyIterator.next();
4✔
973
      }
974
      trace("Next property candidate to match argument is {}", property);
9✔
975
      boolean matches = property.apply(arguments, this, cmd, collector);
7✔
976
      if (!matches || currentArgument.isCompletion()) {
5✔
977
        return false;
2✔
978
      }
979
      currentArgument = arguments.current();
3✔
980
    }
1✔
981
    return true;
×
982
  }
983

984
  public String findBash() {
985

986
    String bash = "bash";
2✔
987
    if (SystemInfoImpl.INSTANCE.isWindows()) {
3!
988
      bash = findBashOnWindows();
×
989
    }
990

991
    return bash;
2✔
992
  }
993

994
  private String findBashOnWindows() {
995

996
    // Check if Git Bash exists in the default location
997
    Path defaultPath = Path.of("C:\\Program Files\\Git\\bin\\bash.exe");
×
998
    if (Files.exists(defaultPath)) {
×
999
      return defaultPath.toString();
×
1000
    }
1001

1002
    // If not found in the default location, try the registry query
1003
    String[] bashVariants = { "GitForWindows", "Cygwin\\setup" };
×
1004
    String[] registryKeys = { "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER" };
×
1005
    String regQueryResult;
1006
    for (String bashVariant : bashVariants) {
×
1007
      for (String registryKey : registryKeys) {
×
1008
        String toolValueName = ("GitForWindows".equals(bashVariant)) ? "InstallPath" : "rootdir";
×
1009
        String command = "reg query " + registryKey + "\\Software\\" + bashVariant + "  /v " + toolValueName + " 2>nul";
×
1010

1011
        try {
1012
          Process process = new ProcessBuilder("cmd.exe", "/c", command).start();
×
1013
          try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
×
1014
            StringBuilder output = new StringBuilder();
×
1015
            String line;
1016

1017
            while ((line = reader.readLine()) != null) {
×
1018
              output.append(line);
×
1019
            }
1020

1021
            int exitCode = process.waitFor();
×
1022
            if (exitCode != 0) {
×
1023
              return null;
×
1024
            }
1025

1026
            regQueryResult = output.toString();
×
1027
            if (regQueryResult != null) {
×
1028
              int index = regQueryResult.indexOf("REG_SZ");
×
1029
              if (index != -1) {
×
1030
                String path = regQueryResult.substring(index + "REG_SZ".length()).trim();
×
1031
                return path + "\\bin\\bash.exe";
×
1032
              }
1033
            }
1034

1035
          }
×
1036
        } catch (Exception e) {
×
1037
          return null;
×
1038
        }
×
1039
      }
1040
    }
1041
    // no bash found
1042
    throw new IllegalStateException("Could not find Bash. Please install Git for Windows and rerun.");
×
1043
  }
1044

1045
  @Override
1046
  public WindowsPathSyntax getPathSyntax() {
1047
    return this.pathSyntax;
3✔
1048
  }
1049

1050
  /**
1051
   * @param pathSyntax new value of {@link #getPathSyntax()}.
1052
   */
1053
  public void setPathSyntax(WindowsPathSyntax pathSyntax) {
1054

1055
    this.pathSyntax = pathSyntax;
3✔
1056
  }
1✔
1057
}
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