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

devonfw / IDEasy / 12125578396

02 Dec 2024 06:25PM UTC coverage: 67.265% (+0.3%) from 67.008%
12125578396

push

github

web-flow
#824: fix git url with branch (#828)

2509 of 4080 branches covered (61.5%)

Branch coverage included in aggregate %.

6565 of 9410 relevant lines covered (69.77%)

3.08 hits per line

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

59.75
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.InputStreamReader;
5
import java.net.URL;
6
import java.net.URLConnection;
7
import java.nio.file.Files;
8
import java.nio.file.Path;
9
import java.util.HashMap;
10
import java.util.Iterator;
11
import java.util.List;
12
import java.util.Locale;
13
import java.util.Map;
14

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

62
/**
63
 * Abstract base implementation of {@link IdeContext}.
64
 */
65
public abstract class AbstractIdeContext implements IdeContext {
66

67
  private static final GitUrl IDE_URLS_GIT = new GitUrl("https://github.com/devonfw/ide-urls.git", null);
7✔
68

69
  private final IdeStartContextImpl startContext;
70

71
  private Path ideHome;
72

73
  private final Path ideRoot;
74

75
  private Path confPath;
76

77
  protected Path settingsPath;
78

79
  private Path softwarePath;
80

81
  private Path softwareExtraPath;
82

83
  private Path softwareRepositoryPath;
84

85
  protected Path pluginsPath;
86

87
  private Path workspacePath;
88

89
  private String workspaceName;
90

91
  protected Path urlsPath;
92

93
  private Path tempPath;
94

95
  private Path tempDownloadPath;
96

97
  private Path cwd;
98

99
  private Path downloadPath;
100

101
  private Path toolRepositoryPath;
102

103
  protected Path userHome;
104

105
  private Path userHomeIde;
106

107
  private SystemPath path;
108

109
  private WindowsPathSyntax pathSyntax;
110

111
  private final SystemInfo systemInfo;
112

113
  private EnvironmentVariables variables;
114

115
  private final FileAccess fileAccess;
116

117
  protected CommandletManager commandletManager;
118

119
  protected ToolRepository defaultToolRepository;
120

121
  private CustomToolRepository customToolRepository;
122

123
  private DirectoryMerger workspaceMerger;
124

125
  protected UrlMetadata urlMetadata;
126

127
  protected Path defaultExecutionDirectory;
128

129
  private StepImpl currentStep;
130

131
  protected Boolean online;
132

133
  protected IdeSystem system;
134

135
  private NetworkProxy networkProxy;
136

137
  /**
138
   * The constructor.
139
   *
140
   * @param startContext the {@link IdeLogger}.
141
   * @param workingDirectory the optional {@link Path} to current working directory.
142
   */
143
  public AbstractIdeContext(IdeStartContextImpl startContext, Path workingDirectory) {
144

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

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

179
    setCwd(workingDirectory, workspace, currentDir);
5✔
180

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

201
    this.defaultToolRepository = new DefaultToolRepository(this);
6✔
202
  }
1✔
203

204
  private Path findIdeRoot(Path ideHomePath) {
205

206
    Path ideRootPath = null;
2✔
207
    if (ideHomePath != null) {
2✔
208
      ideRootPath = ideHomePath.getParent();
4✔
209
    } else if (!isTest()) {
3!
210
      ideRootPath = getIdeRootPathFromEnv();
×
211
    }
212
    return ideRootPath;
2✔
213
  }
214

215
  private Path getIdeRootPathFromEnv() {
216
    String root = getSystem().getEnv(IdeVariables.IDE_ROOT.getName());
×
217
    if (root != null) {
×
218
      Path rootPath = Path.of(root);
×
219
      if (Files.isDirectory(rootPath)) {
×
220
        return rootPath;
×
221
      }
222
    }
223
    return null;
×
224
  }
225

226
  @Override
227
  public void setCwd(Path userDir, String workspace, Path ideHome) {
228

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

260
    this.path = computeSystemPath();
4✔
261
    this.customToolRepository = CustomToolRepositoryImpl.of(this);
4✔
262
  }
1✔
263

264
  private String getMessageIdeHomeFound() {
265

266
    return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName;
6✔
267
  }
268

269
  private String getMessageIdeHomeNotFound() {
270

271
    return "You are not inside an IDE installation: " + this.cwd;
×
272
  }
273

274
  private String getMessageIdeRootNotFound() {
275
    String root = getSystem().getEnv("IDE_ROOT");
×
276
    if (root == null) {
×
277
      return "The environment variable IDE_ROOT is undefined. Please reinstall IDEasy or manually repair IDE_ROOT variable.";
×
278
    } else {
279
      return "The environment variable IDE_ROOT is pointing to an invalid path " + root + ". Please reinstall IDEasy or manually repair IDE_ROOT variable.";
×
280
    }
281
  }
282

283
  /**
284
   * @return {@code true} if this is a test context for JUnits, {@code false} otherwise.
285
   */
286
  public boolean isTest() {
287

288
    return false;
×
289
  }
290

291
  protected SystemPath computeSystemPath() {
292

293
    return new SystemPath(this);
×
294
  }
295

296

297
  private boolean isIdeHome(Path dir) {
298

299
    if (!Files.isDirectory(dir.resolve("workspaces"))) {
7✔
300
      return false;
2✔
301
    } else if (!Files.isDirectory(dir.resolve("settings"))) {
7!
302
      return false;
×
303
    }
304
    return true;
2✔
305
  }
306

307
  private EnvironmentVariables createVariables() {
308

309
    AbstractEnvironmentVariables system = createSystemVariables();
3✔
310
    AbstractEnvironmentVariables user = extendVariables(system, this.userHomeIde, EnvironmentVariablesType.USER);
7✔
311
    AbstractEnvironmentVariables settings = extendVariables(user, this.settingsPath, EnvironmentVariablesType.SETTINGS);
7✔
312
    // TODO should we keep this workspace properties? Was this feature ever used?
313
    AbstractEnvironmentVariables workspace = extendVariables(settings, this.workspacePath, EnvironmentVariablesType.WORKSPACE);
7✔
314
    AbstractEnvironmentVariables conf = extendVariables(workspace, this.confPath, EnvironmentVariablesType.CONF);
7✔
315
    return conf.resolved();
3✔
316
  }
317

318
  protected AbstractEnvironmentVariables createSystemVariables() {
319

320
    return EnvironmentVariables.ofSystem(this);
3✔
321
  }
322

323
  protected AbstractEnvironmentVariables extendVariables(AbstractEnvironmentVariables envVariables, Path propertiesPath, EnvironmentVariablesType type) {
324

325
    Path propertiesFile = null;
2✔
326
    if (propertiesPath == null) {
2✔
327
      trace("Configuration directory for type {} does not exist.", type);
10✔
328
    } else if (Files.isDirectory(propertiesPath)) {
5✔
329
      propertiesFile = propertiesPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES);
4✔
330
      boolean legacySupport = (type != EnvironmentVariablesType.USER);
7✔
331
      if (legacySupport && !Files.exists(propertiesFile)) {
7✔
332
        Path legacyFile = propertiesPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES);
4✔
333
        if (Files.exists(legacyFile)) {
5!
334
          propertiesFile = legacyFile;
×
335
        }
336
      }
337
    } else {
1✔
338
      debug("Configuration directory {} does not exist.", propertiesPath);
9✔
339
    }
340
    return envVariables.extend(propertiesFile, type);
5✔
341
  }
342

343
  @Override
344
  public SystemInfo getSystemInfo() {
345

346
    return this.systemInfo;
3✔
347
  }
348

349
  @Override
350
  public FileAccess getFileAccess() {
351

352
    // currently FileAccess contains download method and requires network proxy to be configured. Maybe download should be moved to its own interface/class
353
    configureNetworkProxy();
2✔
354
    return this.fileAccess;
3✔
355
  }
356

357
  @Override
358
  public CommandletManager getCommandletManager() {
359

360
    return this.commandletManager;
3✔
361
  }
362

363
  @Override
364
  public ToolRepository getDefaultToolRepository() {
365

366
    return this.defaultToolRepository;
3✔
367
  }
368

369
  @Override
370
  public CustomToolRepository getCustomToolRepository() {
371

372
    return this.customToolRepository;
3✔
373
  }
374

375
  @Override
376
  public Path getIdeHome() {
377

378
    return this.ideHome;
3✔
379
  }
380

381
  @Override
382
  public String getProjectName() {
383

384
    if (this.ideHome != null) {
3!
385
      return this.ideHome.getFileName().toString();
5✔
386
    }
387
    return "";
×
388
  }
389

390
  @Override
391
  public Path getIdeRoot() {
392

393
    return this.ideRoot;
3✔
394
  }
395

396
  @Override
397
  public Path getCwd() {
398

399
    return this.cwd;
3✔
400
  }
401

402
  @Override
403
  public Path getTempPath() {
404

405
    return this.tempPath;
3✔
406
  }
407

408
  @Override
409
  public Path getTempDownloadPath() {
410

411
    return this.tempDownloadPath;
3✔
412
  }
413

414
  @Override
415
  public Path getUserHome() {
416

417
    return this.userHome;
3✔
418
  }
419

420
  @Override
421
  public Path getUserHomeIde() {
422

423
    return this.userHomeIde;
3✔
424
  }
425

426
  @Override
427
  public Path getSettingsPath() {
428

429
    return this.settingsPath;
3✔
430
  }
431

432
  @Override
433
  public Path getConfPath() {
434

435
    return this.confPath;
3✔
436
  }
437

438
  @Override
439
  public Path getSoftwarePath() {
440

441
    return this.softwarePath;
3✔
442
  }
443

444
  @Override
445
  public Path getSoftwareExtraPath() {
446

447
    return this.softwareExtraPath;
3✔
448
  }
449

450
  @Override
451
  public Path getSoftwareRepositoryPath() {
452

453
    return this.softwareRepositoryPath;
3✔
454
  }
455

456
  @Override
457
  public Path getPluginsPath() {
458

459
    return this.pluginsPath;
3✔
460
  }
461

462
  @Override
463
  public String getWorkspaceName() {
464

465
    return this.workspaceName;
3✔
466
  }
467

468
  @Override
469
  public Path getWorkspacePath() {
470

471
    return this.workspacePath;
3✔
472
  }
473

474
  @Override
475
  public Path getDownloadPath() {
476

477
    return this.downloadPath;
3✔
478
  }
479

480
  @Override
481
  public Path getUrlsPath() {
482

483
    return this.urlsPath;
3✔
484
  }
485

486
  @Override
487
  public Path getToolRepositoryPath() {
488

489
    return this.toolRepositoryPath;
3✔
490
  }
491

492
  @Override
493
  public SystemPath getPath() {
494

495
    return this.path;
3✔
496
  }
497

498
  @Override
499
  public EnvironmentVariables getVariables() {
500

501
    if (this.variables == null) {
3✔
502
      this.variables = createVariables();
4✔
503
    }
504
    return this.variables;
3✔
505
  }
506

507
  @Override
508
  public UrlMetadata getUrls() {
509

510
    if (this.urlMetadata == null) {
3✔
511
      if (!isTest()) {
3!
512
        getGitContext().pullOrCloneAndResetIfNeeded(IDE_URLS_GIT, this.urlsPath, null);
×
513
      }
514
      this.urlMetadata = new UrlMetadata(this);
6✔
515
    }
516
    return this.urlMetadata;
3✔
517
  }
518

519
  @Override
520
  public boolean isQuietMode() {
521

522
    return this.startContext.isQuietMode();
4✔
523
  }
524

525
  @Override
526
  public boolean isBatchMode() {
527

528
    return this.startContext.isBatchMode();
×
529
  }
530

531
  @Override
532
  public boolean isForceMode() {
533

534
    return this.startContext.isForceMode();
4✔
535
  }
536

537
  @Override
538
  public boolean isOfflineMode() {
539

540
    return this.startContext.isOfflineMode();
4✔
541
  }
542

543
  @Override
544
  public boolean isSkipUpdatesMode() {
545
    return this.startContext.isSkipUpdatesMode();
×
546
  }
547

548
  @Override
549
  public boolean isOnline() {
550

551
    if (this.online == null) {
3✔
552
      configureNetworkProxy();
2✔
553
      // we currently assume we have only a CLI process that runs shortly
554
      // therefore we run this check only once to save resources when this method is called many times
555
      try {
556
        int timeout = 1000;
2✔
557
        //open a connection to github.com and try to retrieve data
558
        //getContent fails if there is no connection
559
        URLConnection connection = new URL("https://www.github.com").openConnection();
6✔
560
        connection.setConnectTimeout(timeout);
3✔
561
        connection.getContent();
3✔
562
        this.online = Boolean.TRUE;
3✔
563
      } catch (Exception ignored) {
×
564
        this.online = Boolean.FALSE;
×
565
      }
1✔
566
    }
567
    return this.online.booleanValue();
4✔
568
  }
569

570
  private void configureNetworkProxy() {
571
    if (this.networkProxy == null) {
3✔
572
      this.networkProxy = new NetworkProxy(this);
6✔
573
      this.networkProxy.configure();
3✔
574
    }
575
  }
1✔
576

577
  @Override
578
  public Locale getLocale() {
579

580
    Locale locale = this.startContext.getLocale();
4✔
581
    if (locale == null) {
2!
582
      locale = Locale.getDefault();
×
583
    }
584
    return locale;
2✔
585
  }
586

587
  @Override
588
  public DirectoryMerger getWorkspaceMerger() {
589

590
    if (this.workspaceMerger == null) {
3✔
591
      this.workspaceMerger = new DirectoryMerger(this);
6✔
592
    }
593
    return this.workspaceMerger;
3✔
594
  }
595

596
  /**
597
   * @return the {@link #getDefaultExecutionDirectory() default execution directory} in which a command process is executed.
598
   */
599
  @Override
600
  public Path getDefaultExecutionDirectory() {
601

602
    return this.defaultExecutionDirectory;
×
603
  }
604

605
  /**
606
   * @param defaultExecutionDirectory new value of {@link #getDefaultExecutionDirectory()}.
607
   */
608
  public void setDefaultExecutionDirectory(Path defaultExecutionDirectory) {
609

610
    if (defaultExecutionDirectory != null) {
×
611
      this.defaultExecutionDirectory = defaultExecutionDirectory;
×
612
    }
613
  }
×
614

615
  @Override
616
  public GitContext getGitContext() {
617

618
    return new GitContextImpl(this);
×
619
  }
620

621
  @Override
622
  public ProcessContext newProcess() {
623

624
    ProcessContext processContext = createProcessContext();
3✔
625
    if (this.defaultExecutionDirectory != null) {
3!
626
      processContext.directory(this.defaultExecutionDirectory);
×
627
    }
628
    return processContext;
2✔
629
  }
630

631
  @Override
632
  public IdeSystem getSystem() {
633

634
    if (this.system == null) {
×
635
      this.system = new IdeSystemImpl(this);
×
636
    }
637
    return this.system;
×
638
  }
639

640
  /**
641
   * @return a new instance of {@link ProcessContext}.
642
   * @see #newProcess()
643
   */
644
  protected ProcessContext createProcessContext() {
645

646
    return new ProcessContextImpl(this);
×
647
  }
648

649
  @Override
650
  public IdeSubLogger level(IdeLogLevel level) {
651

652
    return this.startContext.level(level);
5✔
653
  }
654

655
  @Override
656
  public void logIdeHomeAndRootStatus() {
657

658
    if (this.ideRoot != null) {
×
659
      success("IDE_ROOT is set to {}", this.ideRoot);
×
660
    }
661
    if (this.ideHome == null) {
×
662
      warning(getMessageIdeHomeNotFound());
×
663
    } else {
664
      success("IDE_HOME is set to {}", this.ideHome);
×
665
    }
666
  }
×
667

668
  @Override
669
  public String askForInput(String message, String defaultValue) {
670

671
    if (!message.isBlank()) {
×
672
      info(message);
×
673
    }
674
    if (isBatchMode()) {
×
675
      if (isForceMode()) {
×
676
        return defaultValue;
×
677
      } else {
678
        throw new CliAbortException();
×
679
      }
680
    }
681
    String input = readLine().trim();
×
682
    return input.isEmpty() ? defaultValue : input;
×
683
  }
684

685
  @Override
686
  public String askForInput(String message) {
687

688
    String input;
689
    do {
690
      info(message);
3✔
691
      input = readLine().trim();
4✔
692
    } while (input.isEmpty());
3!
693

694
    return input;
2✔
695
  }
696

697
  @SuppressWarnings("unchecked")
698
  @Override
699
  public <O> O question(String question, O... options) {
700

701
    assert (options.length >= 2);
×
702
    interaction(question);
×
703
    Map<String, O> mapping = new HashMap<>(options.length);
×
704
    int i = 0;
×
705
    for (O option : options) {
×
706
      i++;
×
707
      String key = "" + option;
×
708
      addMapping(mapping, key, option);
×
709
      String numericKey = Integer.toString(i);
×
710
      if (numericKey.equals(key)) {
×
711
        trace("Options should not be numeric: " + key);
×
712
      } else {
713
        addMapping(mapping, numericKey, option);
×
714
      }
715
      interaction("Option " + numericKey + ": " + key);
×
716
    }
717
    O option = null;
×
718
    if (isBatchMode()) {
×
719
      if (isForceMode()) {
×
720
        option = options[0];
×
721
        interaction("" + option);
×
722
      }
723
    } else {
724
      while (option == null) {
×
725
        String answer = readLine();
×
726
        option = mapping.get(answer);
×
727
        if (option == null) {
×
728
          warning("Invalid answer: '" + answer + "' - please try again.");
×
729
        }
730
      }
×
731
    }
732
    return option;
×
733
  }
734

735
  /**
736
   * @return the input from the end-user (e.g. read from the console).
737
   */
738
  protected abstract String readLine();
739

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

742
    O duplicate = mapping.put(key, option);
×
743
    if (duplicate != null) {
×
744
      throw new IllegalArgumentException("Duplicated option " + key);
×
745
    }
746
  }
×
747

748
  @Override
749
  public Step getCurrentStep() {
750

751
    return this.currentStep;
×
752
  }
753

754
  @Override
755
  public StepImpl newStep(boolean silent, String name, Object... parameters) {
756

757
    this.currentStep = new StepImpl(this, this.currentStep, name, silent, parameters);
11✔
758
    return this.currentStep;
3✔
759
  }
760

761
  /**
762
   * Internal method to end the running {@link Step}.
763
   *
764
   * @param step the current {@link Step} to end.
765
   */
766
  public void endStep(StepImpl step) {
767

768
    if (step == this.currentStep) {
4!
769
      this.currentStep = this.currentStep.getParent();
6✔
770
    } else {
771
      String currentStepName = "null";
×
772
      if (this.currentStep != null) {
×
773
        currentStepName = this.currentStep.getName();
×
774
      }
775
      warning("endStep called with wrong step '{}' but expected '{}'", step.getName(), currentStepName);
×
776
    }
777
  }
1✔
778

779
  /**
780
   * Finds the matching {@link Commandlet} to run, applies {@link CliArguments} to its {@link Commandlet#getProperties() properties} and will execute it.
781
   *
782
   * @param arguments the {@link CliArgument}.
783
   * @return the return code of the execution.
784
   */
785
  public int run(CliArguments arguments) {
786

787
    CliArgument current = arguments.current();
3✔
788
    assert (this.currentStep == null);
4!
789
    boolean supressStepSuccess = false;
2✔
790
    StepImpl step = newStep(true, "ide", (Object[]) current.asArray());
8✔
791
    Commandlet firstCandidate = null;
2✔
792
    try {
793
      if (!current.isEnd()) {
3!
794
        String keyword = current.get();
3✔
795
        firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword);
5✔
796
        ValidationResult firstResult = null;
2✔
797
        if (firstCandidate != null) {
2!
798
          firstResult = applyAndRun(arguments.copy(), firstCandidate);
6✔
799
          if (firstResult.isValid()) {
3!
800
            supressStepSuccess = firstCandidate.isSuppressStepSuccess();
3✔
801
            step.success();
2✔
802
            return ProcessResult.SUCCESS;
4✔
803
          }
804
        }
805
        for (Commandlet cmd : this.commandletManager.getCommandlets()) {
×
806
          if (cmd != firstCandidate) {
×
807
            ValidationResult result = applyAndRun(arguments.copy(), cmd);
×
808
            if (result.isValid()) {
×
809
              supressStepSuccess = cmd.isSuppressStepSuccess();
×
810
              step.success();
×
811
              return ProcessResult.SUCCESS;
×
812
            }
813
          }
814
        }
×
815
        if (firstResult != null) {
×
816
          throw new CliException(firstResult.getErrorMessage());
×
817
        }
818
        step.error("Invalid arguments: {}", current.getArgs());
×
819
      }
820

821
      HelpCommandlet help = this.commandletManager.getCommandlet(HelpCommandlet.class);
×
822
      if (firstCandidate != null) {
×
823
        help.commandlet.setValue(firstCandidate);
×
824
      }
825
      help.run();
×
826
      return 1;
×
827
    } catch (Throwable t) {
×
828
      step.error(t, true);
×
829
      throw t;
×
830
    } finally {
831
      step.close();
2✔
832
      assert (this.currentStep == null);
4!
833
      step.logSummary(supressStepSuccess);
3✔
834
    }
835
  }
836

837
  /**
838
   * @param cmd the potential {@link Commandlet} to {@link #apply(CliArguments, Commandlet, CompletionCandidateCollector) apply} and
839
   *     {@link Commandlet#run() run}.
840
   * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully, {@code false} otherwise (the
841
   *     {@link Commandlet} did not match and we have to try a different candidate).
842
   */
843
  private ValidationResult applyAndRun(CliArguments arguments, Commandlet cmd) {
844

845
    cmd.clearProperties();
2✔
846

847
    ValidationResult result = apply(arguments, cmd, null);
6✔
848
    if (result.isValid()) {
3!
849
      result = cmd.validate();
3✔
850
    }
851
    if (result.isValid()) {
3!
852
      debug("Running commandlet {}", cmd);
9✔
853
      if (cmd.isIdeHomeRequired() && (this.ideHome == null)) {
6!
854
        throw new CliException(getMessageIdeHomeNotFound(), ProcessResult.NO_IDE_HOME);
×
855
      } else if (cmd.isIdeRootRequired() && (this.ideRoot == null)) {
6!
856
        throw new CliException(getMessageIdeRootNotFound(), ProcessResult.NO_IDE_ROOT);
×
857
      }
858
      if (cmd.isProcessableOutput()) {
3!
859
        if (!debug().isEnabled()) {
×
860
          // unless --debug or --trace was supplied, processable output commandlets will disable all log-levels except INFO to prevent other logs interfere
861
          for (IdeLogLevel level : IdeLogLevel.values()) {
×
862
            if (level != IdeLogLevel.PROCESSABLE) {
×
863
              this.startContext.setLogLevel(level, false);
×
864
            }
865
          }
866
        }
867
        this.startContext.activateLogging();
×
868
      } else {
869
        this.startContext.activateLogging();
3✔
870
        if (!isTest()) {
3!
871
          if (this.ideRoot == null) {
×
872
            warning("Variable IDE_ROOT is undefined. Please check your installation or run setup script again.");
×
873
          } else if (this.ideHome != null) {
×
874
            Path ideRootPath = getIdeRootPathFromEnv();
×
875
            if (!this.ideRoot.equals(ideRootPath)) {
×
876
              warning("Variable IDE_ROOT is set to '{}' but for your project '{}' the path '{}' would have been expected.", ideRootPath,
×
877
                  this.ideHome.getFileName(), this.ideRoot);
×
878
            }
879
          }
880
        }
881
        if (cmd.isIdeHomeRequired()) {
3!
882
          debug(getMessageIdeHomeFound());
4✔
883
        }
884
        if (this.settingsPath != null) {
3!
885
          if (getGitContext().isRepositoryUpdateAvailable(this.settingsPath) ||
7!
886
              (getGitContext().fetchIfNeeded(this.settingsPath) && getGitContext().isRepositoryUpdateAvailable(this.settingsPath))) {
5!
887
            interaction("Updates are available for the settings repository. If you want to pull the latest changes, call ide update.");
×
888
          }
889
        }
890
      }
891
      cmd.run();
3✔
892
    } else {
893
      trace("Commandlet did not match");
×
894
    }
895
    return result;
2✔
896
  }
897

898
  /**
899
   * @param arguments the {@link CliArguments#ofCompletion(String...) completion arguments}.
900
   * @param includeContextOptions to include the options of {@link ContextCommandlet}.
901
   * @return the {@link List} of {@link CompletionCandidate}s to suggest.
902
   */
903
  public List<CompletionCandidate> complete(CliArguments arguments, boolean includeContextOptions) {
904

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

937
  private boolean completeCommandlet(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) {
938
    if (cmd.isIdeHomeRequired() && (this.ideHome == null)) {
6!
939
      return false;
×
940
    } else {
941
      return apply(arguments.copy(), cmd, collector).isValid();
8✔
942
    }
943
  }
944

945

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

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

1007
  @Override
1008
  public String findBash() {
1009

1010
    String bash = "bash";
2✔
1011
    if (SystemInfoImpl.INSTANCE.isWindows()) {
3!
1012
      bash = findBashOnWindows();
×
1013
    }
1014

1015
    return bash;
2✔
1016
  }
1017

1018
  private String findBashOnWindows() {
1019

1020
    // Check if Git Bash exists in the default location
1021
    Path defaultPath = Path.of("C:\\Program Files\\Git\\bin\\bash.exe");
×
1022
    if (Files.exists(defaultPath)) {
×
1023
      return defaultPath.toString();
×
1024
    }
1025

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

1035
        try {
1036
          Process process = new ProcessBuilder("cmd.exe", "/c", command).start();
×
1037
          try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
×
1038
            StringBuilder output = new StringBuilder();
×
1039
            String line;
1040

1041
            while ((line = reader.readLine()) != null) {
×
1042
              output.append(line);
×
1043
            }
1044

1045
            int exitCode = process.waitFor();
×
1046
            if (exitCode != 0) {
×
1047
              return null;
×
1048
            }
1049

1050
            regQueryResult = output.toString();
×
1051
            if (regQueryResult != null) {
×
1052
              int index = regQueryResult.indexOf("REG_SZ");
×
1053
              if (index != -1) {
×
1054
                String path = regQueryResult.substring(index + "REG_SZ".length()).trim();
×
1055
                return path + "\\bin\\bash.exe";
×
1056
              }
1057
            }
1058

1059
          }
×
1060
        } catch (Exception e) {
×
1061
          return null;
×
1062
        }
×
1063
      }
1064
    }
1065
    // no bash found
1066
    return null;
×
1067
  }
1068

1069
  @Override
1070
  public WindowsPathSyntax getPathSyntax() {
1071
    return this.pathSyntax;
3✔
1072
  }
1073

1074
  /**
1075
   * @param pathSyntax new value of {@link #getPathSyntax()}.
1076
   */
1077
  public void setPathSyntax(WindowsPathSyntax pathSyntax) {
1078

1079
    this.pathSyntax = pathSyntax;
3✔
1080
  }
1✔
1081

1082
  /**
1083
   * @return the {@link IdeStartContextImpl}.
1084
   */
1085
  public IdeStartContextImpl getStartContext() {
1086

1087
    return startContext;
3✔
1088
  }
1089

1090
  /**
1091
   * Reloads this context and re-initializes the {@link #getVariables() variables}.
1092
   */
1093
  public void reload() {
1094
    this.variables = null;
3✔
1095
  }
1✔
1096
}
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

© 2025 Coveralls, Inc