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

devonfw / IDEasy / 10468522135

20 Aug 2024 09:00AM UTC coverage: 64.178% (+0.05%) from 64.127%
10468522135

push

github

web-flow
#531: prevent logging for processable commandlets #511: consolidate test context (#541)

2147 of 3677 branches covered (58.39%)

Branch coverage included in aggregate %.

5659 of 8486 relevant lines covered (66.69%)

2.94 hits per line

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

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

3
import java.io.BufferedReader;
4
import java.io.IOException;
5
import java.io.InputStreamReader;
6
import java.net.URL;
7
import java.net.URLConnection;
8
import java.nio.file.Files;
9
import java.nio.file.Path;
10
import java.util.HashMap;
11
import java.util.Iterator;
12
import java.util.List;
13
import java.util.Locale;
14
import java.util.Map;
15

16
import com.devonfw.tools.ide.cli.CliAbortException;
17
import com.devonfw.tools.ide.cli.CliArgument;
18
import com.devonfw.tools.ide.cli.CliArguments;
19
import com.devonfw.tools.ide.cli.CliException;
20
import com.devonfw.tools.ide.commandlet.Commandlet;
21
import com.devonfw.tools.ide.commandlet.CommandletManager;
22
import com.devonfw.tools.ide.commandlet.CommandletManagerImpl;
23
import com.devonfw.tools.ide.commandlet.ContextCommandlet;
24
import com.devonfw.tools.ide.commandlet.HelpCommandlet;
25
import com.devonfw.tools.ide.common.SystemPath;
26
import com.devonfw.tools.ide.completion.CompletionCandidate;
27
import com.devonfw.tools.ide.completion.CompletionCandidateCollector;
28
import com.devonfw.tools.ide.completion.CompletionCandidateCollectorDefault;
29
import com.devonfw.tools.ide.environment.AbstractEnvironmentVariables;
30
import com.devonfw.tools.ide.environment.EnvironmentVariables;
31
import com.devonfw.tools.ide.environment.EnvironmentVariablesType;
32
import com.devonfw.tools.ide.io.FileAccess;
33
import com.devonfw.tools.ide.io.FileAccessImpl;
34
import com.devonfw.tools.ide.log.IdeLogLevel;
35
import com.devonfw.tools.ide.log.IdeLogger;
36
import com.devonfw.tools.ide.log.IdeLoggerImpl;
37
import com.devonfw.tools.ide.log.IdeSubLogger;
38
import com.devonfw.tools.ide.merge.DirectoryMerger;
39
import com.devonfw.tools.ide.network.ProxyContext;
40
import com.devonfw.tools.ide.os.SystemInfo;
41
import com.devonfw.tools.ide.os.SystemInfoImpl;
42
import com.devonfw.tools.ide.os.WindowsPathSyntax;
43
import com.devonfw.tools.ide.process.ProcessContext;
44
import com.devonfw.tools.ide.process.ProcessContextImpl;
45
import com.devonfw.tools.ide.process.ProcessResult;
46
import com.devonfw.tools.ide.property.Property;
47
import com.devonfw.tools.ide.repo.CustomToolRepository;
48
import com.devonfw.tools.ide.repo.CustomToolRepositoryImpl;
49
import com.devonfw.tools.ide.repo.DefaultToolRepository;
50
import com.devonfw.tools.ide.repo.ToolRepository;
51
import com.devonfw.tools.ide.step.Step;
52
import com.devonfw.tools.ide.step.StepImpl;
53
import com.devonfw.tools.ide.url.model.UrlMetadata;
54

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

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

62
  private final IdeLoggerImpl logger;
63

64
  private Path ideHome;
65

66
  private final Path ideRoot;
67

68
  private Path confPath;
69

70
  private Path settingsPath;
71

72
  private Path softwarePath;
73

74
  private Path softwareExtraPath;
75

76
  private Path softwareRepositoryPath;
77

78
  private Path pluginsPath;
79

80
  private Path workspacePath;
81

82
  private String workspaceName;
83

84
  private Path urlsPath;
85

86
  private Path tempPath;
87

88
  private Path tempDownloadPath;
89

90
  private Path cwd;
91

92
  private Path downloadPath;
93

94
  private Path toolRepository;
95

96
  private Path userHome;
97

98
  private Path userHomeIde;
99

100
  private SystemPath path;
101

102
  private WindowsPathSyntax pathSyntax;
103

104
  private final SystemInfo systemInfo;
105

106
  private EnvironmentVariables variables;
107

108
  private final FileAccess fileAccess;
109

110
  private final CommandletManager commandletManager;
111

112
  private final ToolRepository defaultToolRepository;
113

114
  private CustomToolRepository customToolRepository;
115

116
  private DirectoryMerger workspaceMerger;
117

118
  private boolean offlineMode;
119

120
  private boolean forceMode;
121

122
  private boolean batchMode;
123

124
  private boolean quietMode;
125

126
  private Locale locale;
127

128
  private UrlMetadata urlMetadata;
129

130
  private Path defaultExecutionDirectory;
131

132
  private StepImpl currentStep;
133

134
  /**
135
   * The constructor.
136
   *
137
   * @param logger the {@link IdeLogger}.
138
   * @param userDir the optional {@link Path} to current working directory.
139
   * @param toolRepository @param toolRepository the {@link ToolRepository} of the context. If it is set to {@code null} {@link DefaultToolRepository} will
140
   *     be used.
141
   */
142
  public AbstractIdeContext(IdeLoggerImpl logger, Path userDir, ToolRepository toolRepository) {
143

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

175
    // detection completed, initializing variables
176
    this.ideRoot = findIdeRoot(currentDir);
5✔
177

178
    setCwd(userDir, workspace, currentDir);
5✔
179

180
    if (this.ideRoot == null) {
3✔
181
      this.toolRepository = null;
3✔
182
      this.urlsPath = null;
3✔
183
      this.tempPath = null;
3✔
184
      this.tempDownloadPath = null;
3✔
185
      this.softwareRepositoryPath = null;
4✔
186
    } else {
187
      Path ideBase = this.ideRoot.resolve(FOLDER_IDE);
5✔
188
      this.toolRepository = ideBase.resolve("software");
5✔
189
      this.urlsPath = ideBase.resolve("urls");
5✔
190
      this.tempPath = ideBase.resolve("tmp");
5✔
191
      this.tempDownloadPath = this.tempPath.resolve(FOLDER_DOWNLOADS);
6✔
192
      this.softwareRepositoryPath = ideBase.resolve(FOLDER_SOFTWARE);
5✔
193
      if (Files.isDirectory(this.tempPath)) {
7✔
194
        // TODO delete all files older than 1 day here...
195
      } else {
196
        this.fileAccess.mkdirs(this.tempDownloadPath);
5✔
197
      }
198
    }
199

200
    if (toolRepository == null) {
2✔
201
      this.defaultToolRepository = new DefaultToolRepository(this);
7✔
202
    } else {
203
      this.defaultToolRepository = toolRepository;
3✔
204
    }
205
  }
1✔
206

207
  private Path findIdeRoot(Path ideHomePath) {
208
    final Path ideRoot;
209
    Path ideRootPath = null;
2✔
210
    if (ideHomePath != null) {
2✔
211
      ideRootPath = ideHomePath.getParent();
3✔
212
    }
213

214
    if (!isTest()) {
3✔
215
      String root = System.getenv("IDE_ROOT");
3✔
216
      if (root != null) {
2!
217
        Path rootPath = Path.of(root);
×
218
        if ((ideRootPath == null)) {
×
219
          if (Files.isDirectory(rootPath)) {
×
220
            ideRootPath = rootPath;
×
221
          }
222
        } else if (!ideRootPath.equals(rootPath)) {
×
223
          warning("Variable IDE_ROOT is set to '{}' but for your project '{}' the path '{}' would have been expected.", rootPath,
×
224
              (this.ideHome == null) ? "undefined" : this.ideHome.getFileName(), ideRootPath);
×
225
        }
226
      }
227
    }
228
    return ideRootPath;
2✔
229
  }
230

231
  @Override
232
  public void setCwd(Path userDir, String workspace, Path ideHome) {
233

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

265
    this.variables = createVariables();
4✔
266
    this.path = computeSystemPath();
4✔
267
    this.customToolRepository = CustomToolRepositoryImpl.of(this);
4✔
268
    this.workspaceMerger = new DirectoryMerger(this);
6✔
269
  }
1✔
270

271
  private String getMessageIdeHomeFound() {
272

273
    return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName;
6✔
274
  }
275

276
  private String getMessageIdeHomeNotFound() {
277

278
    return "You are not inside an IDE installation: " + this.cwd;
×
279
  }
280

281
  private static String getMessageIdeRootNotFound() {
282
    String root = System.getenv("IDE_ROOT");
×
283
    if (root == null) {
×
284
      return "The environment variable IDE_ROOT is undefined. Please reinstall IDEasy or manually repair IDE_ROOT variable.";
×
285
    } else {
286
      return "The environment variable IDE_ROOT is pointing to an invalid path " + root + ". Please reinstall IDEasy or manually repair IDE_ROOT variable.";
×
287
    }
288
  }
289

290
  /**
291
   * @return the status message about the {@link #getIdeHome() IDE_HOME} detection and environment variable initialization.
292
   */
293
  public String getMessageIdeHome() {
294

295
    if (this.ideHome == null) {
×
296
      return getMessageIdeHomeNotFound();
×
297
    }
298
    return getMessageIdeHomeFound();
×
299
  }
300

301
  /**
302
   * @return {@code true} if this is a test context for JUnits, {@code false} otherwise.
303
   */
304
  public boolean isTest() {
305

306
    return false;
2✔
307
  }
308

309
  protected SystemPath computeSystemPath() {
310

311
    return new SystemPath(this);
5✔
312
  }
313

314
  private boolean isIdeHome(Path dir) {
315

316
    if (!Files.isDirectory(dir.resolve("workspaces"))) {
7✔
317
      return false;
2✔
318
    } else if (!Files.isDirectory(dir.resolve("settings"))) {
7!
319
      return false;
×
320
    }
321
    return true;
2✔
322
  }
323

324
  private Path getParentPath(Path dir) {
325

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

337
  }
338

339
  private EnvironmentVariables createVariables() {
340

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

350
  protected AbstractEnvironmentVariables createSystemVariables() {
351

352
    return EnvironmentVariables.ofSystem(this);
3✔
353
  }
354

355
  protected AbstractEnvironmentVariables extendVariables(AbstractEnvironmentVariables envVariables, Path propertiesPath, EnvironmentVariablesType type) {
356

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

375
  @Override
376
  public SystemInfo getSystemInfo() {
377

378
    return this.systemInfo;
3✔
379
  }
380

381
  @Override
382
  public FileAccess getFileAccess() {
383

384
    return this.fileAccess;
3✔
385
  }
386

387
  @Override
388
  public CommandletManager getCommandletManager() {
389

390
    return this.commandletManager;
3✔
391
  }
392

393
  @Override
394
  public ToolRepository getDefaultToolRepository() {
395

396
    return this.defaultToolRepository;
3✔
397
  }
398

399
  @Override
400
  public CustomToolRepository getCustomToolRepository() {
401

402
    return this.customToolRepository;
3✔
403
  }
404

405
  @Override
406
  public Path getIdeHome() {
407

408
    return this.ideHome;
3✔
409
  }
410

411
  @Override
412
  public String getProjectName() {
413

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

420
  @Override
421
  public Path getIdeRoot() {
422

423
    return this.ideRoot;
3✔
424
  }
425

426
  @Override
427
  public Path getCwd() {
428

429
    return this.cwd;
3✔
430
  }
431

432
  @Override
433
  public Path getTempPath() {
434

435
    return this.tempPath;
3✔
436
  }
437

438
  @Override
439
  public Path getTempDownloadPath() {
440

441
    return this.tempDownloadPath;
3✔
442
  }
443

444
  @Override
445
  public Path getUserHome() {
446

447
    return this.userHome;
3✔
448
  }
449

450
  @Override
451
  public Path getUserHomeIde() {
452

453
    return this.userHomeIde;
3✔
454
  }
455

456
  @Override
457
  public Path getSettingsPath() {
458

459
    return this.settingsPath;
3✔
460
  }
461

462
  @Override
463
  public Path getConfPath() {
464

465
    return this.confPath;
3✔
466
  }
467

468
  @Override
469
  public Path getSoftwarePath() {
470

471
    return this.softwarePath;
3✔
472
  }
473

474
  @Override
475
  public Path getSoftwareExtraPath() {
476

477
    return this.softwareExtraPath;
3✔
478
  }
479

480
  @Override
481
  public Path getSoftwareRepositoryPath() {
482

483
    return this.softwareRepositoryPath;
3✔
484
  }
485

486
  @Override
487
  public Path getPluginsPath() {
488

489
    return this.pluginsPath;
3✔
490
  }
491

492
  @Override
493
  public String getWorkspaceName() {
494

495
    return this.workspaceName;
3✔
496
  }
497

498
  @Override
499
  public Path getWorkspacePath() {
500

501
    return this.workspacePath;
3✔
502
  }
503

504
  @Override
505
  public Path getDownloadPath() {
506

507
    return this.downloadPath;
3✔
508
  }
509

510
  @Override
511
  public Path getUrlsPath() {
512

513
    return this.urlsPath;
3✔
514
  }
515

516
  @Override
517
  public Path getToolRepositoryPath() {
518

519
    return this.toolRepository;
3✔
520
  }
521

522
  @Override
523
  public SystemPath getPath() {
524

525
    return this.path;
3✔
526
  }
527

528
  @Override
529
  public EnvironmentVariables getVariables() {
530

531
    return this.variables;
3✔
532
  }
533

534
  @Override
535
  public UrlMetadata getUrls() {
536

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

546
  @Override
547
  public boolean isQuietMode() {
548

549
    return this.quietMode;
3✔
550
  }
551

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

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

560
  @Override
561
  public boolean isBatchMode() {
562

563
    return this.batchMode;
3✔
564
  }
565

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

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

574
  @Override
575
  public boolean isForceMode() {
576

577
    return this.forceMode;
3✔
578
  }
579

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

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

588
  @Override
589
  public boolean isOfflineMode() {
590

591
    return this.offlineMode;
3✔
592
  }
593

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

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

602
  @Override
603
  public boolean isOnline() {
604

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

615
    } catch (Exception ignored) {
×
616

617
    }
×
618
    return online;
×
619
  }
620

621
  @Override
622
  public Locale getLocale() {
623

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

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

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

638
  @Override
639
  public DirectoryMerger getWorkspaceMerger() {
640

641
    return this.workspaceMerger;
3✔
642
  }
643

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

650
    return this.defaultExecutionDirectory;
×
651
  }
652

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

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

663
  @Override
664
  public ProxyContext getProxyContext() {
665

666
    return new ProxyContext(this);
5✔
667
  }
668

669
  @Override
670
  public GitContext getGitContext() {
671

672
    return new GitContextImpl(this);
×
673
  }
674

675
  @Override
676
  public ProcessContext newProcess() {
677

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

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

691
    return new ProcessContextImpl(this);
×
692
  }
693

694
  @Override
695
  public IdeSubLogger level(IdeLogLevel level) {
696

697
    return this.logger.level(level);
5✔
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);
3✔
723
      input = readLine().trim();
4✔
724
    } while (input.isEmpty());
3!
725

726
    return input;
2✔
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
  @Override
781
  public Step getCurrentStep() {
782

783
    return this.currentStep;
×
784
  }
785

786
  @Override
787
  public StepImpl newStep(boolean silent, String name, Object... parameters) {
788

789
    this.currentStep = new StepImpl(this, this.currentStep, name, silent, parameters);
11✔
790
    return this.currentStep;
3✔
791
  }
792

793
  /**
794
   * Internal method to end the running {@link Step}.
795
   *
796
   * @param step the current {@link Step} to end.
797
   */
798
  public void endStep(StepImpl step) {
799

800
    if (step == this.currentStep) {
4!
801
      this.currentStep = this.currentStep.getParent();
6✔
802
    } else {
803
      String currentStepName = "null";
×
804
      if (this.currentStep != null) {
×
805
        currentStepName = this.currentStep.getName();
×
806
      }
807
      warning("endStep called with wrong step '{}' but expected '{}'", step.getName(), currentStepName);
×
808
    }
809
  }
1✔
810

811
  /**
812
   * Finds the matching {@link Commandlet} to run, applies {@link CliArguments} to its {@link Commandlet#getProperties() properties} and will execute it.
813
   *
814
   * @param arguments the {@link CliArgument}.
815
   * @return the return code of the execution.
816
   */
817
  public int run(CliArguments arguments) {
818

819
    CliArgument current = arguments.current();
3✔
820
    assert (this.currentStep == null);
4!
821
    boolean supressStepSuccess = false;
2✔
822
    StepImpl step = newStep(true, "ide", (Object[]) current.asArray());
8✔
823
    Commandlet firstCandidate = null;
2✔
824
    try {
825
      if (!current.isEnd()) {
3!
826
        String keyword = current.get();
3✔
827
        firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword);
5✔
828
        boolean matches;
829
        if (firstCandidate != null) {
2!
830
          matches = applyAndRun(arguments.copy(), firstCandidate);
6✔
831
          if (matches) {
2!
832
            supressStepSuccess = firstCandidate.isSuppressStepSuccess();
3✔
833
            step.success();
2✔
834
            return ProcessResult.SUCCESS;
4✔
835
          }
836
        }
837
        for (Commandlet cmd : this.commandletManager.getCommandlets()) {
×
838
          if (cmd != firstCandidate) {
×
839
            matches = applyAndRun(arguments.copy(), cmd);
×
840
            if (matches) {
×
841
              supressStepSuccess = cmd.isSuppressStepSuccess();
×
842
              step.success();
×
843
              return ProcessResult.SUCCESS;
×
844
            }
845
          }
846
        }
×
847
        step.error("Invalid arguments: {}", current.getArgs());
×
848
      }
849

850
      HelpCommandlet help = this.commandletManager.getCommandlet(HelpCommandlet.class);
×
851
      if (firstCandidate != null) {
×
852
        help.commandlet.setValue(firstCandidate);
×
853
      }
854
      help.run();
×
855
      return 1;
×
856
    } catch (Throwable t) {
×
857
      step.error(t, true);
×
858
      throw t;
×
859
    } finally {
860
      step.close();
2✔
861
      assert (this.currentStep == null);
4!
862
      step.logSummary(supressStepSuccess);
3✔
863
    }
864
  }
865

866
  /**
867
   * @param cmd the potential {@link Commandlet} to {@link #apply(CliArguments, Commandlet, CompletionCandidateCollector) apply} and
868
   *     {@link Commandlet#run() run}.
869
   * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully, {@code false} otherwise (the
870
   *     {@link Commandlet} did not match and we have to try a different candidate).
871
   */
872
  private boolean applyAndRun(CliArguments arguments, Commandlet cmd) {
873

874
    cmd.clearProperties();
2✔
875

876
    boolean matches = apply(arguments, cmd, null);
6✔
877
    if (matches) {
2!
878
      matches = cmd.validate();
3✔
879
    }
880
    if (matches) {
2!
881
      debug("Running commandlet {}", cmd);
9✔
882
      if (cmd.isIdeHomeRequired() && (this.ideHome == null)) {
6!
883
        throw new CliException(getMessageIdeHomeNotFound(), ProcessResult.NO_IDE_HOME);
×
884
      } else if (cmd.isIdeRootRequired() && (this.ideRoot == null)) {
6!
885
        throw new CliException(getMessageIdeRootNotFound(), ProcessResult.NO_IDE_ROOT);
×
886
      }
887
      if (cmd.isProcessableOutput()) {
3!
888
        for (IdeLogLevel level : IdeLogLevel.values()) {
×
889
          if (level != IdeLogLevel.INFO) {
×
890
            this.logger.setLogLevel(level, false);
×
891
          }
892
        }
893
      } else {
894
        if (cmd.isIdeHomeRequired()) {
3!
895
          debug(getMessageIdeHomeFound());
4✔
896
        }
897
      }
898
      cmd.run();
3✔
899
    } else {
900
      trace("Commandlet did not match");
×
901
    }
902
    return matches;
2✔
903
  }
904

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

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

944
  /**
945
   * @param arguments the {@link CliArguments} to apply. Will be {@link CliArguments#next() consumed} as they are matched. Consider passing a
946
   *     {@link CliArguments#copy() copy} as needed.
947
   * @param cmd the potential {@link Commandlet} to match.
948
   * @param collector the {@link CompletionCandidateCollector}.
949
   * @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}
950
   *     and {@link Commandlet#validate() validated}), {@code false} otherwise (the {@link Commandlet} did not match and we have to try a different candidate).
951
   */
952
  public boolean apply(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) {
953

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

1001
  @Override
1002
  public String findBash() {
1003

1004
    String bash = "bash";
2✔
1005
    if (SystemInfoImpl.INSTANCE.isWindows()) {
3!
1006
      bash = findBashOnWindows();
×
1007
    }
1008

1009
    return bash;
2✔
1010
  }
1011

1012
  private String findBashOnWindows() {
1013

1014
    // Check if Git Bash exists in the default location
1015
    Path defaultPath = Path.of("C:\\Program Files\\Git\\bin\\bash.exe");
×
1016
    if (Files.exists(defaultPath)) {
×
1017
      return defaultPath.toString();
×
1018
    }
1019

1020
    // If not found in the default location, try the registry query
1021
    String[] bashVariants = { "GitForWindows", "Cygwin\\setup" };
×
1022
    String[] registryKeys = { "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER" };
×
1023
    String regQueryResult;
1024
    for (String bashVariant : bashVariants) {
×
1025
      for (String registryKey : registryKeys) {
×
1026
        String toolValueName = ("GitForWindows".equals(bashVariant)) ? "InstallPath" : "rootdir";
×
1027
        String command = "reg query " + registryKey + "\\Software\\" + bashVariant + "  /v " + toolValueName + " 2>nul";
×
1028

1029
        try {
1030
          Process process = new ProcessBuilder("cmd.exe", "/c", command).start();
×
1031
          try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
×
1032
            StringBuilder output = new StringBuilder();
×
1033
            String line;
1034

1035
            while ((line = reader.readLine()) != null) {
×
1036
              output.append(line);
×
1037
            }
1038

1039
            int exitCode = process.waitFor();
×
1040
            if (exitCode != 0) {
×
1041
              return null;
×
1042
            }
1043

1044
            regQueryResult = output.toString();
×
1045
            if (regQueryResult != null) {
×
1046
              int index = regQueryResult.indexOf("REG_SZ");
×
1047
              if (index != -1) {
×
1048
                String path = regQueryResult.substring(index + "REG_SZ".length()).trim();
×
1049
                return path + "\\bin\\bash.exe";
×
1050
              }
1051
            }
1052

1053
          }
×
1054
        } catch (Exception e) {
×
1055
          return null;
×
1056
        }
×
1057
      }
1058
    }
1059
    // no bash found
1060
    return null;
×
1061
  }
1062

1063
  @Override
1064
  public WindowsPathSyntax getPathSyntax() {
1065
    return this.pathSyntax;
3✔
1066
  }
1067

1068
  /**
1069
   * @param pathSyntax new value of {@link #getPathSyntax()}.
1070
   */
1071
  public void setPathSyntax(WindowsPathSyntax pathSyntax) {
1072

1073
    this.pathSyntax = pathSyntax;
3✔
1074
  }
1✔
1075
}
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