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

devonfw / IDEasy / 12084849850

29 Nov 2024 12:34PM UTC coverage: 67.031% (-0.4%) from 67.412%
12084849850

push

github

web-flow
#758: improve status commandlet (#816)

2500 of 4078 branches covered (61.3%)

Branch coverage included in aggregate %.

6515 of 9371 relevant lines covered (69.52%)

3.07 hits per line

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

59.3
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.io.FileAccess;
34
import com.devonfw.tools.ide.io.FileAccessImpl;
35
import com.devonfw.tools.ide.log.IdeLogLevel;
36
import com.devonfw.tools.ide.log.IdeLogger;
37
import com.devonfw.tools.ide.log.IdeSubLogger;
38
import com.devonfw.tools.ide.merge.DirectoryMerger;
39
import com.devonfw.tools.ide.network.NetworkProxy;
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
import com.devonfw.tools.ide.validation.ValidationResult;
55
import com.devonfw.tools.ide.validation.ValidationResultValid;
56
import com.devonfw.tools.ide.validation.ValidationState;
57
import com.devonfw.tools.ide.variable.IdeVariables;
58

59
/**
60
 * Abstract base implementation of {@link IdeContext}.
61
 */
62
public abstract class AbstractIdeContext implements IdeContext {
1✔
63

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

66
  private final IdeStartContextImpl startContext;
67

68
  private Path ideHome;
69

70
  private final Path ideRoot;
71

72
  private Path confPath;
73

74
  protected Path settingsPath;
75

76
  private Path softwarePath;
77

78
  private Path softwareExtraPath;
79

80
  private Path softwareRepositoryPath;
81

82
  protected Path pluginsPath;
83

84
  private Path workspacePath;
85

86
  private String workspaceName;
87

88
  protected Path urlsPath;
89

90
  private Path tempPath;
91

92
  private Path tempDownloadPath;
93

94
  private Path cwd;
95

96
  private Path downloadPath;
97

98
  private Path toolRepositoryPath;
99

100
  protected Path userHome;
101

102
  private Path userHomeIde;
103

104
  private SystemPath path;
105

106
  private WindowsPathSyntax pathSyntax;
107

108
  private final SystemInfo systemInfo;
109

110
  private EnvironmentVariables variables;
111

112
  private final FileAccess fileAccess;
113

114
  protected CommandletManager commandletManager;
115

116
  protected ToolRepository defaultToolRepository;
117

118
  private CustomToolRepository customToolRepository;
119

120
  private DirectoryMerger workspaceMerger;
121

122
  protected UrlMetadata urlMetadata;
123

124
  protected Path defaultExecutionDirectory;
125

126
  private StepImpl currentStep;
127

128
  protected Boolean online;
129

130
  protected IdeSystem system;
131

132
  private NetworkProxy networkProxy;
133

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

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

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

176
    setCwd(workingDirectory, workspace, currentDir);
5✔
177

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

198
    this.defaultToolRepository = new DefaultToolRepository(this);
6✔
199
  }
1✔
200

201
  private Path findIdeRoot(Path ideHomePath) {
202

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

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

223
  @Override
224
  public void setCwd(Path userDir, String workspace, Path ideHome) {
225

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

257
    this.path = computeSystemPath();
4✔
258
    this.customToolRepository = CustomToolRepositoryImpl.of(this);
4✔
259
  }
1✔
260

261
  private String getMessageIdeHomeFound() {
262

263
    return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName;
6✔
264
  }
265

266
  private String getMessageIdeHomeNotFound() {
267

268
    return "You are not inside an IDE installation: " + this.cwd;
×
269
  }
270

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

280
  /**
281
   * @return the status message about the {@link #getIdeHome() IDE_HOME} detection and environment variable initialization.
282
   */
283
  public String getMessageIdeHome() {
284

285
    if (this.ideHome == null) {
×
286
      return getMessageIdeHomeNotFound();
×
287
    }
288
    return getMessageIdeHomeFound();
×
289
  }
290

291
  /**
292
   * @return {@code true} if this is a test context for JUnits, {@code false} otherwise.
293
   */
294
  public boolean isTest() {
295

296
    return false;
×
297
  }
298

299
  protected SystemPath computeSystemPath() {
300

301
    return new SystemPath(this);
×
302
  }
303

304
  private boolean isIdeHome(Path dir) {
305

306
    if (!Files.isDirectory(dir.resolve("workspaces"))) {
7✔
307
      return false;
2✔
308
    } else if (!Files.isDirectory(dir.resolve("settings"))) {
7!
309
      return false;
×
310
    }
311
    return true;
2✔
312
  }
313

314
  private EnvironmentVariables createVariables() {
315

316
    AbstractEnvironmentVariables system = createSystemVariables();
3✔
317
    AbstractEnvironmentVariables user = extendVariables(system, this.userHomeIde, EnvironmentVariablesType.USER);
7✔
318
    AbstractEnvironmentVariables settings = extendVariables(user, this.settingsPath, EnvironmentVariablesType.SETTINGS);
7✔
319
    // TODO should we keep this workspace properties? Was this feature ever used?
320
    AbstractEnvironmentVariables workspace = extendVariables(settings, this.workspacePath, EnvironmentVariablesType.WORKSPACE);
7✔
321
    AbstractEnvironmentVariables conf = extendVariables(workspace, this.confPath, EnvironmentVariablesType.CONF);
7✔
322
    return conf.resolved();
3✔
323
  }
324

325
  protected AbstractEnvironmentVariables createSystemVariables() {
326

327
    return EnvironmentVariables.ofSystem(this);
3✔
328
  }
329

330
  protected AbstractEnvironmentVariables extendVariables(AbstractEnvironmentVariables envVariables, Path propertiesPath, EnvironmentVariablesType type) {
331

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

350
  @Override
351
  public SystemInfo getSystemInfo() {
352

353
    return this.systemInfo;
3✔
354
  }
355

356
  @Override
357
  public FileAccess getFileAccess() {
358

359
    // currently FileAccess contains download method and requires network proxy to be configured. Maybe download should be moved to its own interface/class
360
    configureNetworkProxy();
2✔
361
    return this.fileAccess;
3✔
362
  }
363

364
  @Override
365
  public CommandletManager getCommandletManager() {
366

367
    return this.commandletManager;
3✔
368
  }
369

370
  @Override
371
  public ToolRepository getDefaultToolRepository() {
372

373
    return this.defaultToolRepository;
3✔
374
  }
375

376
  @Override
377
  public CustomToolRepository getCustomToolRepository() {
378

379
    return this.customToolRepository;
3✔
380
  }
381

382
  @Override
383
  public Path getIdeHome() {
384

385
    return this.ideHome;
3✔
386
  }
387

388
  @Override
389
  public String getProjectName() {
390

391
    if (this.ideHome != null) {
3!
392
      return this.ideHome.getFileName().toString();
5✔
393
    }
394
    return "";
×
395
  }
396

397
  @Override
398
  public Path getIdeRoot() {
399

400
    return this.ideRoot;
3✔
401
  }
402

403
  @Override
404
  public Path getCwd() {
405

406
    return this.cwd;
3✔
407
  }
408

409
  @Override
410
  public Path getTempPath() {
411

412
    return this.tempPath;
3✔
413
  }
414

415
  @Override
416
  public Path getTempDownloadPath() {
417

418
    return this.tempDownloadPath;
3✔
419
  }
420

421
  @Override
422
  public Path getUserHome() {
423

424
    return this.userHome;
3✔
425
  }
426

427
  @Override
428
  public Path getUserHomeIde() {
429

430
    return this.userHomeIde;
3✔
431
  }
432

433
  @Override
434
  public Path getSettingsPath() {
435

436
    return this.settingsPath;
3✔
437
  }
438

439
  @Override
440
  public Path getConfPath() {
441

442
    return this.confPath;
3✔
443
  }
444

445
  @Override
446
  public Path getSoftwarePath() {
447

448
    return this.softwarePath;
3✔
449
  }
450

451
  @Override
452
  public Path getSoftwareExtraPath() {
453

454
    return this.softwareExtraPath;
3✔
455
  }
456

457
  @Override
458
  public Path getSoftwareRepositoryPath() {
459

460
    return this.softwareRepositoryPath;
3✔
461
  }
462

463
  @Override
464
  public Path getPluginsPath() {
465

466
    return this.pluginsPath;
3✔
467
  }
468

469
  @Override
470
  public String getWorkspaceName() {
471

472
    return this.workspaceName;
3✔
473
  }
474

475
  @Override
476
  public Path getWorkspacePath() {
477

478
    return this.workspacePath;
3✔
479
  }
480

481
  @Override
482
  public Path getDownloadPath() {
483

484
    return this.downloadPath;
3✔
485
  }
486

487
  @Override
488
  public Path getUrlsPath() {
489

490
    return this.urlsPath;
3✔
491
  }
492

493
  @Override
494
  public Path getToolRepositoryPath() {
495

496
    return this.toolRepositoryPath;
3✔
497
  }
498

499
  @Override
500
  public SystemPath getPath() {
501

502
    return this.path;
3✔
503
  }
504

505
  @Override
506
  public EnvironmentVariables getVariables() {
507

508
    if (this.variables == null) {
3✔
509
      this.variables = createVariables();
4✔
510
    }
511
    return this.variables;
3✔
512
  }
513

514
  @Override
515
  public UrlMetadata getUrls() {
516

517
    if (this.urlMetadata == null) {
3✔
518
      if (!isTest()) {
3!
519
        getGitContext().pullOrCloneAndResetIfNeeded(IDE_URLS_GIT, this.urlsPath, null);
×
520
      }
521
      this.urlMetadata = new UrlMetadata(this);
6✔
522
    }
523
    return this.urlMetadata;
3✔
524
  }
525

526
  @Override
527
  public boolean isQuietMode() {
528

529
    return this.startContext.isQuietMode();
4✔
530
  }
531

532
  @Override
533
  public boolean isBatchMode() {
534

535
    return this.startContext.isBatchMode();
×
536
  }
537

538
  @Override
539
  public boolean isForceMode() {
540

541
    return this.startContext.isForceMode();
4✔
542
  }
543

544
  @Override
545
  public boolean isOfflineMode() {
546

547
    return this.startContext.isOfflineMode();
4✔
548
  }
549

550
  @Override
551
  public boolean isSkipUpdatesMode() {
552
    return this.startContext.isSkipUpdatesMode();
×
553
  }
554

555
  @Override
556
  public boolean isOnline() {
557

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

577
  private void configureNetworkProxy() {
578
    if (this.networkProxy == null) {
3✔
579
      this.networkProxy = new NetworkProxy(this);
6✔
580
      this.networkProxy.configure();
3✔
581
    }
582
  }
1✔
583

584
  @Override
585
  public Locale getLocale() {
586

587
    Locale locale = this.startContext.getLocale();
4✔
588
    if (locale == null) {
2!
589
      locale = Locale.getDefault();
×
590
    }
591
    return locale;
2✔
592
  }
593

594
  @Override
595
  public DirectoryMerger getWorkspaceMerger() {
596

597
    if (this.workspaceMerger == null) {
3✔
598
      this.workspaceMerger = new DirectoryMerger(this);
6✔
599
    }
600
    return this.workspaceMerger;
3✔
601
  }
602

603
  /**
604
   * @return the {@link #getDefaultExecutionDirectory() default execution directory} in which a command process is executed.
605
   */
606
  @Override
607
  public Path getDefaultExecutionDirectory() {
608

609
    return this.defaultExecutionDirectory;
×
610
  }
611

612
  /**
613
   * @param defaultExecutionDirectory new value of {@link #getDefaultExecutionDirectory()}.
614
   */
615
  public void setDefaultExecutionDirectory(Path defaultExecutionDirectory) {
616

617
    if (defaultExecutionDirectory != null) {
×
618
      this.defaultExecutionDirectory = defaultExecutionDirectory;
×
619
    }
620
  }
×
621

622
  @Override
623
  public GitContext getGitContext() {
624

625
    return new GitContextImpl(this);
×
626
  }
627

628
  @Override
629
  public ProcessContext newProcess() {
630

631
    ProcessContext processContext = createProcessContext();
3✔
632
    if (this.defaultExecutionDirectory != null) {
3!
633
      processContext.directory(this.defaultExecutionDirectory);
×
634
    }
635
    return processContext;
2✔
636
  }
637

638
  @Override
639
  public IdeSystem getSystem() {
640

641
    if (this.system == null) {
×
642
      this.system = new IdeSystemImpl(this);
×
643
    }
644
    return this.system;
×
645
  }
646

647
  /**
648
   * @return a new instance of {@link ProcessContext}.
649
   * @see #newProcess()
650
   */
651
  protected ProcessContext createProcessContext() {
652

653
    return new ProcessContextImpl(this);
×
654
  }
655

656
  @Override
657
  public IdeSubLogger level(IdeLogLevel level) {
658

659
    return this.startContext.level(level);
5✔
660
  }
661

662
  @Override
663
  public void logIdeHomeAndRootStatus() {
664

665
    if (this.ideRoot != null) {
×
666
      success("IDE_ROOT is set to {}", this.ideRoot);
×
667
    }
668
    if (this.ideHome == null) {
×
669
      warning(getMessageIdeHomeNotFound());
×
670
    } else {
671
      success("IDE_HOME is set to {}", this.ideHome);
×
672
    }
673
  }
×
674

675
  @Override
676
  public String askForInput(String message, String defaultValue) {
677

678
    if (!message.isBlank()) {
×
679
      info(message);
×
680
    }
681
    if (isBatchMode()) {
×
682
      if (isForceMode()) {
×
683
        return defaultValue;
×
684
      } else {
685
        throw new CliAbortException();
×
686
      }
687
    }
688
    String input = readLine().trim();
×
689
    return input.isEmpty() ? defaultValue : input;
×
690
  }
691

692
  @Override
693
  public String askForInput(String message) {
694

695
    String input;
696
    do {
697
      info(message);
3✔
698
      input = readLine().trim();
4✔
699
    } while (input.isEmpty());
3!
700

701
    return input;
2✔
702
  }
703

704
  @SuppressWarnings("unchecked")
705
  @Override
706
  public <O> O question(String question, O... options) {
707

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

742
  /**
743
   * @return the input from the end-user (e.g. read from the console).
744
   */
745
  protected abstract String readLine();
746

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

749
    O duplicate = mapping.put(key, option);
×
750
    if (duplicate != null) {
×
751
      throw new IllegalArgumentException("Duplicated option " + key);
×
752
    }
753
  }
×
754

755
  @Override
756
  public Step getCurrentStep() {
757

758
    return this.currentStep;
×
759
  }
760

761
  @Override
762
  public StepImpl newStep(boolean silent, String name, Object... parameters) {
763

764
    this.currentStep = new StepImpl(this, this.currentStep, name, silent, parameters);
11✔
765
    return this.currentStep;
3✔
766
  }
767

768
  /**
769
   * Internal method to end the running {@link Step}.
770
   *
771
   * @param step the current {@link Step} to end.
772
   */
773
  public void endStep(StepImpl step) {
774

775
    if (step == this.currentStep) {
4!
776
      this.currentStep = this.currentStep.getParent();
6✔
777
    } else {
778
      String currentStepName = "null";
×
779
      if (this.currentStep != null) {
×
780
        currentStepName = this.currentStep.getName();
×
781
      }
782
      warning("endStep called with wrong step '{}' but expected '{}'", step.getName(), currentStepName);
×
783
    }
784
  }
1✔
785

786
  /**
787
   * Finds the matching {@link Commandlet} to run, applies {@link CliArguments} to its {@link Commandlet#getProperties() properties} and will execute it.
788
   *
789
   * @param arguments the {@link CliArgument}.
790
   * @return the return code of the execution.
791
   */
792
  public int run(CliArguments arguments) {
793

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

828
      HelpCommandlet help = this.commandletManager.getCommandlet(HelpCommandlet.class);
×
829
      if (firstCandidate != null) {
×
830
        help.commandlet.setValue(firstCandidate);
×
831
      }
832
      help.run();
×
833
      return 1;
×
834
    } catch (Throwable t) {
×
835
      step.error(t, true);
×
836
      throw t;
×
837
    } finally {
838
      step.close();
2✔
839
      assert (this.currentStep == null);
4!
840
      step.logSummary(supressStepSuccess);
3✔
841
    }
842
  }
843

844
  /**
845
   * @param cmd the potential {@link Commandlet} to {@link #apply(CliArguments, Commandlet, CompletionCandidateCollector) apply} and
846
   *     {@link Commandlet#run() run}.
847
   * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully, {@code false} otherwise (the
848
   *     {@link Commandlet} did not match and we have to try a different candidate).
849
   */
850
  private ValidationResult applyAndRun(CliArguments arguments, Commandlet cmd) {
851

852
    cmd.clearProperties();
2✔
853

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

944
  private boolean completeCommandlet(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) {
945
    if (cmd.isIdeHomeRequired() && (this.ideHome == null)) {
6!
946
      return false;
×
947
    } else {
948
      return apply(arguments.copy(), cmd, collector).isValid();
8✔
949
    }
950
  }
951

952

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

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

1014
  @Override
1015
  public String findBash() {
1016

1017
    String bash = "bash";
2✔
1018
    if (SystemInfoImpl.INSTANCE.isWindows()) {
3!
1019
      bash = findBashOnWindows();
×
1020
    }
1021

1022
    return bash;
2✔
1023
  }
1024

1025
  private String findBashOnWindows() {
1026

1027
    // Check if Git Bash exists in the default location
1028
    Path defaultPath = Path.of("C:\\Program Files\\Git\\bin\\bash.exe");
×
1029
    if (Files.exists(defaultPath)) {
×
1030
      return defaultPath.toString();
×
1031
    }
1032

1033
    // If not found in the default location, try the registry query
1034
    String[] bashVariants = { "GitForWindows", "Cygwin\\setup" };
×
1035
    String[] registryKeys = { "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER" };
×
1036
    String regQueryResult;
1037
    for (String bashVariant : bashVariants) {
×
1038
      for (String registryKey : registryKeys) {
×
1039
        String toolValueName = ("GitForWindows".equals(bashVariant)) ? "InstallPath" : "rootdir";
×
1040
        String command = "reg query " + registryKey + "\\Software\\" + bashVariant + "  /v " + toolValueName + " 2>nul";
×
1041

1042
        try {
1043
          Process process = new ProcessBuilder("cmd.exe", "/c", command).start();
×
1044
          try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
×
1045
            StringBuilder output = new StringBuilder();
×
1046
            String line;
1047

1048
            while ((line = reader.readLine()) != null) {
×
1049
              output.append(line);
×
1050
            }
1051

1052
            int exitCode = process.waitFor();
×
1053
            if (exitCode != 0) {
×
1054
              return null;
×
1055
            }
1056

1057
            regQueryResult = output.toString();
×
1058
            if (regQueryResult != null) {
×
1059
              int index = regQueryResult.indexOf("REG_SZ");
×
1060
              if (index != -1) {
×
1061
                String path = regQueryResult.substring(index + "REG_SZ".length()).trim();
×
1062
                return path + "\\bin\\bash.exe";
×
1063
              }
1064
            }
1065

1066
          }
×
1067
        } catch (Exception e) {
×
1068
          return null;
×
1069
        }
×
1070
      }
1071
    }
1072
    // no bash found
1073
    return null;
×
1074
  }
1075

1076
  @Override
1077
  public WindowsPathSyntax getPathSyntax() {
1078
    return this.pathSyntax;
3✔
1079
  }
1080

1081
  /**
1082
   * @param pathSyntax new value of {@link #getPathSyntax()}.
1083
   */
1084
  public void setPathSyntax(WindowsPathSyntax pathSyntax) {
1085

1086
    this.pathSyntax = pathSyntax;
3✔
1087
  }
1✔
1088

1089
  /**
1090
   * @return the {@link IdeStartContextImpl}.
1091
   */
1092
  public IdeStartContextImpl getStartContext() {
1093

1094
    return startContext;
3✔
1095
  }
1096

1097
  /**
1098
   * Reloads this context and re-initializes the {@link #getVariables() variables}.
1099
   */
1100
  public void reload() {
1101
    this.variables = null;
3✔
1102
  }
1✔
1103
}
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