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

devonfw / IDEasy / 11970498949

22 Nov 2024 10:05AM UTC coverage: 67.435% (-0.007%) from 67.442%
11970498949

push

github

web-flow
#637: Option to disable updates (#765)

2491 of 4036 branches covered (61.72%)

Branch coverage included in aggregate %.

6467 of 9248 relevant lines covered (69.93%)

3.09 hits per line

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

60.25
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.io.FileAccess;
32
import com.devonfw.tools.ide.io.FileAccessImpl;
33
import com.devonfw.tools.ide.log.IdeLogLevel;
34
import com.devonfw.tools.ide.log.IdeLogger;
35
import com.devonfw.tools.ide.log.IdeSubLogger;
36
import com.devonfw.tools.ide.merge.DirectoryMerger;
37
import com.devonfw.tools.ide.network.ProxyContext;
38
import com.devonfw.tools.ide.os.SystemInfo;
39
import com.devonfw.tools.ide.os.SystemInfoImpl;
40
import com.devonfw.tools.ide.os.WindowsPathSyntax;
41
import com.devonfw.tools.ide.process.ProcessContext;
42
import com.devonfw.tools.ide.process.ProcessContextImpl;
43
import com.devonfw.tools.ide.process.ProcessResult;
44
import com.devonfw.tools.ide.property.Property;
45
import com.devonfw.tools.ide.repo.CustomToolRepository;
46
import com.devonfw.tools.ide.repo.CustomToolRepositoryImpl;
47
import com.devonfw.tools.ide.repo.DefaultToolRepository;
48
import com.devonfw.tools.ide.repo.ToolRepository;
49
import com.devonfw.tools.ide.step.Step;
50
import com.devonfw.tools.ide.step.StepImpl;
51
import com.devonfw.tools.ide.url.model.UrlMetadata;
52
import com.devonfw.tools.ide.validation.ValidationResult;
53
import com.devonfw.tools.ide.validation.ValidationState;
54
import com.devonfw.tools.ide.variable.IdeVariables;
55

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

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

63
  private final IdeStartContextImpl startContext;
64

65
  private Path ideHome;
66

67
  private final Path ideRoot;
68

69
  private Path confPath;
70

71
  private Path settingsPath;
72

73
  private Path softwarePath;
74

75
  private Path softwareExtraPath;
76

77
  private Path softwareRepositoryPath;
78

79
  private Path pluginsPath;
80

81
  private Path workspacePath;
82

83
  private String workspaceName;
84

85
  protected Path urlsPath;
86

87
  private Path tempPath;
88

89
  private Path tempDownloadPath;
90

91
  private Path cwd;
92

93
  private Path downloadPath;
94

95
  private Path toolRepositoryPath;
96

97
  protected Path userHome;
98

99
  private Path userHomeIde;
100

101
  private SystemPath path;
102

103
  private WindowsPathSyntax pathSyntax;
104

105
  private final SystemInfo systemInfo;
106

107
  private EnvironmentVariables variables;
108

109
  private final FileAccess fileAccess;
110

111
  private final CommandletManager commandletManager;
112

113
  protected ToolRepository defaultToolRepository;
114

115
  private CustomToolRepository customToolRepository;
116

117
  private DirectoryMerger workspaceMerger;
118

119
  protected UrlMetadata urlMetadata;
120

121
  protected Path defaultExecutionDirectory;
122

123
  private StepImpl currentStep;
124

125
  protected Boolean online;
126

127
  /**
128
   * The constructor.
129
   *
130
   * @param startContext the {@link IdeLogger}.
131
   * @param workingDirectory the optional {@link Path} to current working directory.
132
   */
133
  public AbstractIdeContext(IdeStartContextImpl startContext, Path workingDirectory) {
134

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

166
    // detection completed, initializing variables
167
    this.ideRoot = findIdeRoot(currentDir);
5✔
168

169
    setCwd(workingDirectory, workspace, currentDir);
5✔
170

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

191
    this.defaultToolRepository = new DefaultToolRepository(this);
6✔
192
  }
1✔
193

194
  private Path findIdeRoot(Path ideHomePath) {
195

196
    Path ideRootPath = null;
2✔
197
    if (ideHomePath != null) {
2✔
198
      ideRootPath = ideHomePath.getParent();
4✔
199
    } else if (!isTest()) {
3!
200
      ideRootPath = getIdeRootPathFromEnv();
×
201
    }
202
    return ideRootPath;
2✔
203
  }
204

205
  private static Path getIdeRootPathFromEnv() {
206
    String root = System.getenv(IdeVariables.IDE_ROOT.getName());
×
207
    if (root != null) {
×
208
      Path rootPath = Path.of(root);
×
209
      if (Files.isDirectory(rootPath)) {
×
210
        return rootPath;
×
211
      }
212
    }
213
    return null;
×
214
  }
215

216
  @Override
217
  public void setCwd(Path userDir, String workspace, Path ideHome) {
218

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

250
    this.path = computeSystemPath();
4✔
251
    this.customToolRepository = CustomToolRepositoryImpl.of(this);
4✔
252
  }
1✔
253

254
  private String getMessageIdeHomeFound() {
255

256
    return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName;
6✔
257
  }
258

259
  private String getMessageIdeHomeNotFound() {
260

261
    return "You are not inside an IDE installation: " + this.cwd;
×
262
  }
263

264
  private static String getMessageIdeRootNotFound() {
265
    String root = System.getenv("IDE_ROOT");
×
266
    if (root == null) {
×
267
      return "The environment variable IDE_ROOT is undefined. Please reinstall IDEasy or manually repair IDE_ROOT variable.";
×
268
    } else {
269
      return "The environment variable IDE_ROOT is pointing to an invalid path " + root + ". Please reinstall IDEasy or manually repair IDE_ROOT variable.";
×
270
    }
271
  }
272

273
  /**
274
   * @return the status message about the {@link #getIdeHome() IDE_HOME} detection and environment variable initialization.
275
   */
276
  public String getMessageIdeHome() {
277

278
    if (this.ideHome == null) {
×
279
      return getMessageIdeHomeNotFound();
×
280
    }
281
    return getMessageIdeHomeFound();
×
282
  }
283

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

289
    return false;
×
290
  }
291

292
  protected SystemPath computeSystemPath() {
293

294
    return new SystemPath(this);
×
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
    return this.fileAccess;
3✔
353
  }
354

355
  @Override
356
  public CommandletManager getCommandletManager() {
357

358
    return this.commandletManager;
3✔
359
  }
360

361
  @Override
362
  public ToolRepository getDefaultToolRepository() {
363

364
    return this.defaultToolRepository;
3✔
365
  }
366

367
  @Override
368
  public CustomToolRepository getCustomToolRepository() {
369

370
    return this.customToolRepository;
3✔
371
  }
372

373
  @Override
374
  public Path getIdeHome() {
375

376
    return this.ideHome;
3✔
377
  }
378

379
  @Override
380
  public String getProjectName() {
381

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

388
  @Override
389
  public Path getIdeRoot() {
390

391
    return this.ideRoot;
3✔
392
  }
393

394
  @Override
395
  public Path getCwd() {
396

397
    return this.cwd;
3✔
398
  }
399

400
  @Override
401
  public Path getTempPath() {
402

403
    return this.tempPath;
3✔
404
  }
405

406
  @Override
407
  public Path getTempDownloadPath() {
408

409
    return this.tempDownloadPath;
3✔
410
  }
411

412
  @Override
413
  public Path getUserHome() {
414

415
    return this.userHome;
3✔
416
  }
417

418
  @Override
419
  public Path getUserHomeIde() {
420

421
    return this.userHomeIde;
3✔
422
  }
423

424
  @Override
425
  public Path getSettingsPath() {
426

427
    return this.settingsPath;
3✔
428
  }
429

430
  @Override
431
  public Path getConfPath() {
432

433
    return this.confPath;
3✔
434
  }
435

436
  @Override
437
  public Path getSoftwarePath() {
438

439
    return this.softwarePath;
3✔
440
  }
441

442
  @Override
443
  public Path getSoftwareExtraPath() {
444

445
    return this.softwareExtraPath;
3✔
446
  }
447

448
  @Override
449
  public Path getSoftwareRepositoryPath() {
450

451
    return this.softwareRepositoryPath;
3✔
452
  }
453

454
  @Override
455
  public Path getPluginsPath() {
456

457
    return this.pluginsPath;
3✔
458
  }
459

460
  @Override
461
  public String getWorkspaceName() {
462

463
    return this.workspaceName;
3✔
464
  }
465

466
  @Override
467
  public Path getWorkspacePath() {
468

469
    return this.workspacePath;
3✔
470
  }
471

472
  @Override
473
  public Path getDownloadPath() {
474

475
    return this.downloadPath;
3✔
476
  }
477

478
  @Override
479
  public Path getUrlsPath() {
480

481
    return this.urlsPath;
3✔
482
  }
483

484
  @Override
485
  public Path getToolRepositoryPath() {
486

487
    return this.toolRepositoryPath;
3✔
488
  }
489

490
  @Override
491
  public SystemPath getPath() {
492

493
    return this.path;
3✔
494
  }
495

496
  @Override
497
  public EnvironmentVariables getVariables() {
498

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

505
  @Override
506
  public UrlMetadata getUrls() {
507

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

517
  @Override
518
  public boolean isQuietMode() {
519

520
    return this.startContext.isQuietMode();
4✔
521
  }
522

523
  @Override
524
  public boolean isBatchMode() {
525

526
    return this.startContext.isBatchMode();
×
527
  }
528

529
  @Override
530
  public boolean isForceMode() {
531

532
    return this.startContext.isForceMode();
4✔
533
  }
534

535
  @Override
536
  public boolean isOfflineMode() {
537

538
    return this.startContext.isOfflineMode();
4✔
539
  }
540

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

546
  @Override
547
  public boolean isOnline() {
548

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

567
  @Override
568
  public Locale getLocale() {
569

570
    Locale locale = this.startContext.getLocale();
4✔
571
    if (locale == null) {
2!
572
      locale = Locale.getDefault();
×
573
    }
574
    return locale;
2✔
575
  }
576

577
  @Override
578
  public DirectoryMerger getWorkspaceMerger() {
579

580
    if (this.workspaceMerger == null) {
3✔
581
      this.workspaceMerger = new DirectoryMerger(this);
6✔
582
    }
583
    return this.workspaceMerger;
3✔
584
  }
585

586
  /**
587
   * @return the {@link #getDefaultExecutionDirectory() default execution directory} in which a command process is executed.
588
   */
589
  @Override
590
  public Path getDefaultExecutionDirectory() {
591

592
    return this.defaultExecutionDirectory;
×
593
  }
594

595
  /**
596
   * @param defaultExecutionDirectory new value of {@link #getDefaultExecutionDirectory()}.
597
   */
598
  public void setDefaultExecutionDirectory(Path defaultExecutionDirectory) {
599

600
    if (defaultExecutionDirectory != null) {
×
601
      this.defaultExecutionDirectory = defaultExecutionDirectory;
×
602
    }
603
  }
×
604

605
  @Override
606
  public ProxyContext getProxyContext() {
607

608
    return new ProxyContext(this);
5✔
609
  }
610

611
  @Override
612
  public GitContext getGitContext() {
613

614
    return new GitContextImpl(this);
×
615
  }
616

617
  @Override
618
  public ProcessContext newProcess() {
619

620
    ProcessContext processContext = createProcessContext();
3✔
621
    if (this.defaultExecutionDirectory != null) {
3!
622
      processContext.directory(this.defaultExecutionDirectory);
×
623
    }
624
    return processContext;
2✔
625
  }
626

627
  /**
628
   * @return a new instance of {@link ProcessContext}.
629
   * @see #newProcess()
630
   */
631
  protected ProcessContext createProcessContext() {
632

633
    return new ProcessContextImpl(this);
×
634
  }
635

636
  @Override
637
  public IdeSubLogger level(IdeLogLevel level) {
638

639
    return this.startContext.level(level);
5✔
640
  }
641

642
  @Override
643
  public String askForInput(String message, String defaultValue) {
644

645
    if (!message.isBlank()) {
×
646
      info(message);
×
647
    }
648
    if (isBatchMode()) {
×
649
      if (isForceMode()) {
×
650
        return defaultValue;
×
651
      } else {
652
        throw new CliAbortException();
×
653
      }
654
    }
655
    String input = readLine().trim();
×
656
    return input.isEmpty() ? defaultValue : input;
×
657
  }
658

659
  @Override
660
  public String askForInput(String message) {
661

662
    String input;
663
    do {
664
      info(message);
3✔
665
      input = readLine().trim();
4✔
666
    } while (input.isEmpty());
3!
667

668
    return input;
2✔
669
  }
670

671
  @SuppressWarnings("unchecked")
672
  @Override
673
  public <O> O question(String question, O... options) {
674

675
    assert (options.length >= 2);
×
676
    interaction(question);
×
677
    Map<String, O> mapping = new HashMap<>(options.length);
×
678
    int i = 0;
×
679
    for (O option : options) {
×
680
      i++;
×
681
      String key = "" + option;
×
682
      addMapping(mapping, key, option);
×
683
      String numericKey = Integer.toString(i);
×
684
      if (numericKey.equals(key)) {
×
685
        trace("Options should not be numeric: " + key);
×
686
      } else {
687
        addMapping(mapping, numericKey, option);
×
688
      }
689
      interaction("Option " + numericKey + ": " + key);
×
690
    }
691
    O option = null;
×
692
    if (isBatchMode()) {
×
693
      if (isForceMode()) {
×
694
        option = options[0];
×
695
        interaction("" + option);
×
696
      }
697
    } else {
698
      while (option == null) {
×
699
        String answer = readLine();
×
700
        option = mapping.get(answer);
×
701
        if (option == null) {
×
702
          warning("Invalid answer: '" + answer + "' - please try again.");
×
703
        }
704
      }
×
705
    }
706
    return option;
×
707
  }
708

709
  /**
710
   * @return the input from the end-user (e.g. read from the console).
711
   */
712
  protected abstract String readLine();
713

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

716
    O duplicate = mapping.put(key, option);
×
717
    if (duplicate != null) {
×
718
      throw new IllegalArgumentException("Duplicated option " + key);
×
719
    }
720
  }
×
721

722
  @Override
723
  public Step getCurrentStep() {
724

725
    return this.currentStep;
×
726
  }
727

728
  @Override
729
  public StepImpl newStep(boolean silent, String name, Object... parameters) {
730

731
    this.currentStep = new StepImpl(this, this.currentStep, name, silent, parameters);
11✔
732
    return this.currentStep;
3✔
733
  }
734

735
  /**
736
   * Internal method to end the running {@link Step}.
737
   *
738
   * @param step the current {@link Step} to end.
739
   */
740
  public void endStep(StepImpl step) {
741

742
    if (step == this.currentStep) {
4!
743
      this.currentStep = this.currentStep.getParent();
6✔
744
    } else {
745
      String currentStepName = "null";
×
746
      if (this.currentStep != null) {
×
747
        currentStepName = this.currentStep.getName();
×
748
      }
749
      warning("endStep called with wrong step '{}' but expected '{}'", step.getName(), currentStepName);
×
750
    }
751
  }
1✔
752

753
  /**
754
   * Finds the matching {@link Commandlet} to run, applies {@link CliArguments} to its {@link Commandlet#getProperties() properties} and will execute it.
755
   *
756
   * @param arguments the {@link CliArgument}.
757
   * @return the return code of the execution.
758
   */
759
  public int run(CliArguments arguments) {
760

761
    CliArgument current = arguments.current();
3✔
762
    assert (this.currentStep == null);
4!
763
    boolean supressStepSuccess = false;
2✔
764
    StepImpl step = newStep(true, "ide", (Object[]) current.asArray());
8✔
765
    Commandlet firstCandidate = null;
2✔
766
    try {
767
      if (!current.isEnd()) {
3!
768
        String keyword = current.get();
3✔
769
        firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword);
5✔
770
        ValidationResult firstResult = null;
2✔
771
        if (firstCandidate != null) {
2!
772
          firstResult = applyAndRun(arguments.copy(), firstCandidate);
6✔
773
          if (firstResult.isValid()) {
3!
774
            supressStepSuccess = firstCandidate.isSuppressStepSuccess();
3✔
775
            step.success();
2✔
776
            return ProcessResult.SUCCESS;
4✔
777
          }
778
        }
779
        for (Commandlet cmd : this.commandletManager.getCommandlets()) {
×
780
          if (cmd != firstCandidate) {
×
781
            ValidationResult result = applyAndRun(arguments.copy(), cmd);
×
782
            if (result.isValid()) {
×
783
              supressStepSuccess = cmd.isSuppressStepSuccess();
×
784
              step.success();
×
785
              return ProcessResult.SUCCESS;
×
786
            }
787
          }
788
        }
×
789
        if (firstResult != null) {
×
790
          throw new CliException(firstResult.getErrorMessage());
×
791
        }
792
        step.error("Invalid arguments: {}", current.getArgs());
×
793
      }
794

795
      HelpCommandlet help = this.commandletManager.getCommandlet(HelpCommandlet.class);
×
796
      if (firstCandidate != null) {
×
797
        help.commandlet.setValue(firstCandidate);
×
798
      }
799
      help.run();
×
800
      return 1;
×
801
    } catch (Throwable t) {
×
802
      step.error(t, true);
×
803
      throw t;
×
804
    } finally {
805
      step.close();
2✔
806
      assert (this.currentStep == null);
4!
807
      step.logSummary(supressStepSuccess);
3✔
808
    }
809
  }
810

811
  /**
812
   * @param cmd the potential {@link Commandlet} to {@link #apply(CliArguments, Commandlet, CompletionCandidateCollector) apply} and
813
   *     {@link Commandlet#run() run}.
814
   * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully, {@code false} otherwise (the
815
   *     {@link Commandlet} did not match and we have to try a different candidate).
816
   */
817
  private ValidationResult applyAndRun(CliArguments arguments, Commandlet cmd) {
818

819
    cmd.clearProperties();
2✔
820

821
    ValidationResult result = apply(arguments, cmd, null);
6✔
822
    if (result.isValid()) {
3!
823
      result = cmd.validate();
3✔
824
    }
825
    if (result.isValid()) {
3!
826
      debug("Running commandlet {}", cmd);
9✔
827
      if (cmd.isIdeHomeRequired() && (this.ideHome == null)) {
6!
828
        throw new CliException(getMessageIdeHomeNotFound(), ProcessResult.NO_IDE_HOME);
×
829
      } else if (cmd.isIdeRootRequired() && (this.ideRoot == null)) {
6!
830
        throw new CliException(getMessageIdeRootNotFound(), ProcessResult.NO_IDE_ROOT);
×
831
      }
832
      if (cmd.isProcessableOutput()) {
3!
833
        if (!debug().isEnabled()) {
×
834
          // unless --debug or --trace was supplied, processable output commandlets will disable all log-levels except INFO to prevent other logs interfere
835
          for (IdeLogLevel level : IdeLogLevel.values()) {
×
836
            if (level != IdeLogLevel.PROCESSABLE) {
×
837
              this.startContext.setLogLevel(level, false);
×
838
            }
839
          }
840
        }
841
        this.startContext.activateLogging();
×
842
      } else {
843
        this.startContext.activateLogging();
3✔
844
        if (!isTest()) {
3!
845
          if (this.ideRoot == null) {
×
846
            warning("Variable IDE_ROOT is undefined. Please check your installation or run setup script again.");
×
847
          } else if (this.ideHome != null) {
×
848
            Path ideRootPath = getIdeRootPathFromEnv();
×
849
            if (!this.ideRoot.equals(ideRootPath)) {
×
850
              warning("Variable IDE_ROOT is set to '{}' but for your project '{}' the path '{}' would have been expected.", ideRootPath,
×
851
                  this.ideHome.getFileName(), this.ideRoot);
×
852
            }
853
          }
854
        }
855
        if (cmd.isIdeHomeRequired()) {
3!
856
          debug(getMessageIdeHomeFound());
4✔
857
        }
858
      }
859
      if (this.settingsPath != null) {
3!
860
        if (getGitContext().isRepositoryUpdateAvailable(this.settingsPath) ||
7!
861
            (getGitContext().fetchIfNeeded(this.settingsPath) && getGitContext().isRepositoryUpdateAvailable(this.settingsPath))) {
5!
862
          interaction("Updates are available for the settings repository. If you want to pull the latest changes, call ide update.");
×
863
        }
864
      }
865
      cmd.run();
3✔
866
    } else {
867
      trace("Commandlet did not match");
×
868
    }
869
    return result;
2✔
870
  }
871

872
  /**
873
   * @param arguments the {@link CliArguments#ofCompletion(String...) completion arguments}.
874
   * @param includeContextOptions to include the options of {@link ContextCommandlet}.
875
   * @return the {@link List} of {@link CompletionCandidate}s to suggest.
876
   */
877
  public List<CompletionCandidate> complete(CliArguments arguments, boolean includeContextOptions) {
878

879
    CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault(this);
5✔
880
    if (arguments.current().isStart()) {
4✔
881
      arguments.next();
3✔
882
    }
883
    if (includeContextOptions) {
2✔
884
      ContextCommandlet cc = new ContextCommandlet();
4✔
885
      for (Property<?> property : cc.getProperties()) {
11✔
886
        assert (property.isOption());
4!
887
        property.apply(arguments, this, cc, collector);
7✔
888
      }
1✔
889
    }
890
    CliArgument current = arguments.current();
3✔
891
    if (!current.isEnd()) {
3!
892
      String keyword = current.get();
3✔
893
      Commandlet firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword);
5✔
894
      boolean matches = false;
2✔
895
      if (firstCandidate != null) {
2✔
896
        matches = completeCommandlet(arguments, firstCandidate, collector);
7✔
897
      } else if (current.isCombinedShortOption()) {
3✔
898
        collector.add(keyword, null, null, null);
6✔
899
      }
900
      if (!matches) {
2!
901
        for (Commandlet cmd : this.commandletManager.getCommandlets()) {
12✔
902
          if (cmd != firstCandidate) {
3✔
903
            completeCommandlet(arguments, cmd, collector);
6✔
904
          }
905
        }
1✔
906
      }
907
    }
908
    return collector.getSortedCandidates();
3✔
909
  }
910

911
  private boolean completeCommandlet(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) {
912
    if (cmd.isIdeHomeRequired() && (this.ideHome == null)) {
6!
913
      return false;
×
914
    } else {
915
      return apply(arguments.copy(), cmd, collector).isValid();
8✔
916
    }
917
  }
918

919

920
  /**
921
   * @param arguments the {@link CliArguments} to apply. Will be {@link CliArguments#next() consumed} as they are matched. Consider passing a
922
   *     {@link CliArguments#copy() copy} as needed.
923
   * @param cmd the potential {@link Commandlet} to match.
924
   * @param collector the {@link CompletionCandidateCollector}.
925
   * @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}
926
   *     and {@link Commandlet#validate() validated}), {@code false} otherwise (the {@link Commandlet} did not match and we have to try a different candidate).
927
   */
928
  public ValidationResult apply(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) {
929

930
    trace("Trying to match arguments to commandlet {}", cmd.getName());
10✔
931
    CliArgument currentArgument = arguments.current();
3✔
932
    Iterator<Property<?>> propertyIterator;
933
    if (currentArgument.isCompletion()) {
3✔
934
      propertyIterator = cmd.getProperties().iterator();
5✔
935
    } else {
936
      propertyIterator = cmd.getValues().iterator();
4✔
937
    }
938
    Property<?> property = null;
2✔
939
    if (propertyIterator.hasNext()) {
3✔
940
      property = propertyIterator.next();
4✔
941
    }
942
    while (!currentArgument.isEnd()) {
3✔
943
      trace("Trying to match argument '{}'", currentArgument);
9✔
944
      Property<?> currentProperty = property;
2✔
945
      if (!arguments.isEndOptions()) {
3!
946
        Property<?> option = cmd.getOption(currentArgument.getKey());
5✔
947
        if (option != null) {
2✔
948
          currentProperty = option;
2✔
949
        }
950
      }
951
      if (currentProperty == null) {
2✔
952
        trace("No option or next value found");
3✔
953
        ValidationState state = new ValidationState(null);
5✔
954
        state.addErrorMessage("No matching property found");
3✔
955
        return state;
2✔
956
      }
957
      trace("Next property candidate to match argument is {}", currentProperty);
9✔
958
      if (currentProperty == property) {
3✔
959
        if (!property.isMultiValued()) {
3✔
960
          if (propertyIterator.hasNext()) {
3✔
961
            property = propertyIterator.next();
5✔
962
          } else {
963
            property = null;
2✔
964
          }
965
        }
966
        if ((property != null) && property.isValue() && property.isMultiValued()) {
8✔
967
          arguments.stopSplitShortOptions();
2✔
968
        }
969
      }
970
      boolean matches = currentProperty.apply(arguments, this, cmd, collector);
7✔
971
      if (!matches || currentArgument.isCompletion()) {
5✔
972
        ValidationState state = new ValidationState(null);
5✔
973
        state.addErrorMessage("No matching property found");
3✔
974
        return state;
2✔
975
      }
976
      currentArgument = arguments.current();
3✔
977
    }
1✔
978
    return new ValidationState(null);
5✔
979
  }
980

981
  @Override
982
  public String findBash() {
983

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

989
    return bash;
2✔
990
  }
991

992
  private String findBashOnWindows() {
993

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

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

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

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

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

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

1033
          }
×
1034
        } catch (Exception e) {
×
1035
          return null;
×
1036
        }
×
1037
      }
1038
    }
1039
    // no bash found
1040
    return null;
×
1041
  }
1042

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

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

1053
    this.pathSyntax = pathSyntax;
3✔
1054
  }
1✔
1055

1056
  /**
1057
   * @return the {@link IdeStartContextImpl}.
1058
   */
1059
  public IdeStartContextImpl getStartContext() {
1060

1061
    return startContext;
3✔
1062
  }
1063

1064
  /**
1065
   * Reloads this context and re-initializes the {@link #getVariables() variables}.
1066
   */
1067
  public void reload() {
1068
    this.variables = null;
3✔
1069
  }
1✔
1070
}
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