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

devonfw / IDEasy / 12357095455

16 Dec 2024 04:35PM UTC coverage: 67.215% (-0.2%) from 67.459%
12357095455

Pull #850

github

web-flow
Merge e465baecc into 52afdd7c7
Pull Request #850: #757: Settings in code repository

2574 of 4176 branches covered (61.64%)

Branch coverage included in aggregate %.

6668 of 9574 relevant lines covered (69.65%)

3.06 hits per line

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

63.17
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.ArrayList;
10
import java.util.HashMap;
11
import java.util.Iterator;
12
import java.util.List;
13
import java.util.Locale;
14
import java.util.Map;
15

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

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

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

70
  private final IdeStartContextImpl startContext;
71

72
  private Path ideHome;
73

74
  private final Path ideRoot;
75

76
  private Path confPath;
77

78
  protected Path settingsPath;
79

80
  private Path settingsCommitIdPath;
81

82
  private Path softwarePath;
83

84
  private Path softwareExtraPath;
85

86
  private Path softwareRepositoryPath;
87

88
  protected Path pluginsPath;
89

90
  private Path workspacePath;
91

92
  private String workspaceName;
93

94
  protected Path urlsPath;
95

96
  private Path tempPath;
97

98
  private Path tempDownloadPath;
99

100
  private Path cwd;
101

102
  private Path downloadPath;
103

104
  private Path toolRepositoryPath;
105

106
  protected Path userHome;
107

108
  private Path userHomeIde;
109

110
  private SystemPath path;
111

112
  private WindowsPathSyntax pathSyntax;
113

114
  private final SystemInfo systemInfo;
115

116
  private EnvironmentVariables variables;
117

118
  private final FileAccess fileAccess;
119

120
  protected CommandletManager commandletManager;
121

122
  protected ToolRepository defaultToolRepository;
123

124
  private CustomToolRepository customToolRepository;
125

126
  private DirectoryMerger workspaceMerger;
127

128
  protected UrlMetadata urlMetadata;
129

130
  protected Path defaultExecutionDirectory;
131

132
  private StepImpl currentStep;
133

134
  protected Boolean online;
135

136
  protected IdeSystem system;
137

138
  private NetworkProxy networkProxy;
139

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

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

179
    // detection completed, initializing variables
180
    this.ideRoot = findIdeRoot(currentDir);
5✔
181

182
    setCwd(workingDirectory, workspace, currentDir);
5✔
183

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

204
    this.defaultToolRepository = new DefaultToolRepository(this);
6✔
205
  }
1✔
206

207
  private Path findIdeRoot(Path ideHomePath) {
208

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

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

229
  @Override
230
  public void setCwd(Path userDir, String workspace, Path ideHome) {
231

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

264
    this.path = computeSystemPath();
4✔
265
    this.customToolRepository = CustomToolRepositoryImpl.of(this);
4✔
266
  }
1✔
267

268
  private String getMessageIdeHomeFound() {
269

270
    return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName;
6✔
271
  }
272

273
  private String getMessageIdeHomeNotFound() {
274

275
    return "You are not inside an IDE installation: " + this.cwd;
4✔
276
  }
277

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

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

292
    return false;
×
293
  }
294

295
  protected SystemPath computeSystemPath() {
296

297
    return new SystemPath(this);
×
298
  }
299

300

301
  private boolean isIdeHome(Path dir) {
302

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

311
  private EnvironmentVariables createVariables() {
312

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

322
  protected AbstractEnvironmentVariables createSystemVariables() {
323

324
    return EnvironmentVariables.ofSystem(this);
3✔
325
  }
326

327
  protected AbstractEnvironmentVariables extendVariables(AbstractEnvironmentVariables envVariables, Path propertiesPath, EnvironmentVariablesType type) {
328

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

347
  @Override
348
  public SystemInfo getSystemInfo() {
349

350
    return this.systemInfo;
3✔
351
  }
352

353
  @Override
354
  public FileAccess getFileAccess() {
355

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

361
  @Override
362
  public CommandletManager getCommandletManager() {
363

364
    return this.commandletManager;
3✔
365
  }
366

367
  @Override
368
  public ToolRepository getDefaultToolRepository() {
369

370
    return this.defaultToolRepository;
3✔
371
  }
372

373
  @Override
374
  public CustomToolRepository getCustomToolRepository() {
375

376
    return this.customToolRepository;
3✔
377
  }
378

379
  @Override
380
  public Path getIdeHome() {
381

382
    return this.ideHome;
3✔
383
  }
384

385
  @Override
386
  public String getProjectName() {
387

388
    if (this.ideHome != null) {
3!
389
      return this.ideHome.getFileName().toString();
5✔
390
    }
391
    return "";
×
392
  }
393

394
  @Override
395
  public Path getIdeRoot() {
396

397
    return this.ideRoot;
3✔
398
  }
399

400
  @Override
401
  public Path getCwd() {
402

403
    return this.cwd;
3✔
404
  }
405

406
  @Override
407
  public Path getTempPath() {
408

409
    return this.tempPath;
3✔
410
  }
411

412
  @Override
413
  public Path getTempDownloadPath() {
414

415
    return this.tempDownloadPath;
3✔
416
  }
417

418
  @Override
419
  public Path getUserHome() {
420

421
    return this.userHome;
3✔
422
  }
423

424
  @Override
425
  public Path getUserHomeIde() {
426

427
    return this.userHomeIde;
3✔
428
  }
429

430
  @Override
431
  public Path getSettingsPath() {
432

433
    return this.settingsPath;
3✔
434
  }
435

436
  @Override
437
  public Path getSettingsGitRepository() {
438

439
    Path settingsPath = getSettingsPath();
3✔
440

441
    if (settingsPath == null) {
2✔
442
      error("No settings repository was found.");
3✔
443
      return null;
2✔
444
    }
445

446
    // check whether the settings path has a .git folder only if its not a symbolic link
447
    if (!Files.exists(settingsPath.resolve(".git")) && !Files.isSymbolicLink(settingsPath)) {
10!
448
      error("Settings repository exists but is not a git repository.");
3✔
449
      return null;
2✔
450
    }
451

452
    return settingsPath;
×
453
  }
454

455
  @Override
456
  public Path getSettingsCommitIdPath() {
457

458
    return this.settingsCommitIdPath;
3✔
459
  }
460

461
  @Override
462
  public Path getConfPath() {
463

464
    return this.confPath;
3✔
465
  }
466

467
  @Override
468
  public Path getSoftwarePath() {
469

470
    return this.softwarePath;
3✔
471
  }
472

473
  @Override
474
  public Path getSoftwareExtraPath() {
475

476
    return this.softwareExtraPath;
3✔
477
  }
478

479
  @Override
480
  public Path getSoftwareRepositoryPath() {
481

482
    return this.softwareRepositoryPath;
3✔
483
  }
484

485
  @Override
486
  public Path getPluginsPath() {
487

488
    return this.pluginsPath;
3✔
489
  }
490

491
  @Override
492
  public String getWorkspaceName() {
493

494
    return this.workspaceName;
3✔
495
  }
496

497
  @Override
498
  public Path getWorkspacePath() {
499

500
    return this.workspacePath;
3✔
501
  }
502

503
  @Override
504
  public Path getDownloadPath() {
505

506
    return this.downloadPath;
3✔
507
  }
508

509
  @Override
510
  public Path getUrlsPath() {
511

512
    return this.urlsPath;
3✔
513
  }
514

515
  @Override
516
  public Path getToolRepositoryPath() {
517

518
    return this.toolRepositoryPath;
3✔
519
  }
520

521
  @Override
522
  public SystemPath getPath() {
523

524
    return this.path;
3✔
525
  }
526

527
  @Override
528
  public EnvironmentVariables getVariables() {
529

530
    if (this.variables == null) {
3✔
531
      this.variables = createVariables();
4✔
532
    }
533
    return this.variables;
3✔
534
  }
535

536
  @Override
537
  public UrlMetadata getUrls() {
538

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

548
  @Override
549
  public boolean isQuietMode() {
550

551
    return this.startContext.isQuietMode();
4✔
552
  }
553

554
  @Override
555
  public boolean isBatchMode() {
556

557
    return this.startContext.isBatchMode();
×
558
  }
559

560
  @Override
561
  public boolean isForceMode() {
562

563
    return this.startContext.isForceMode();
4✔
564
  }
565

566
  @Override
567
  public boolean isOfflineMode() {
568

569
    return this.startContext.isOfflineMode();
4✔
570
  }
571

572
  @Override
573
  public boolean isSkipUpdatesMode() {
574
    return this.startContext.isSkipUpdatesMode();
×
575
  }
576

577
  @Override
578
  public boolean isOnline() {
579

580
    if (this.online == null) {
3✔
581
      configureNetworkProxy();
2✔
582
      // we currently assume we have only a CLI process that runs shortly
583
      // therefore we run this check only once to save resources when this method is called many times
584
      try {
585
        int timeout = 1000;
2✔
586
        //open a connection to github.com and try to retrieve data
587
        //getContent fails if there is no connection
588
        URLConnection connection = new URL("https://www.github.com").openConnection();
6✔
589
        connection.setConnectTimeout(timeout);
3✔
590
        connection.getContent();
3✔
591
        this.online = Boolean.TRUE;
3✔
592
      } catch (Exception ignored) {
×
593
        this.online = Boolean.FALSE;
×
594
      }
1✔
595
    }
596
    return this.online.booleanValue();
4✔
597
  }
598

599
  private void configureNetworkProxy() {
600
    if (this.networkProxy == null) {
3✔
601
      this.networkProxy = new NetworkProxy(this);
6✔
602
      this.networkProxy.configure();
3✔
603
    }
604
  }
1✔
605

606
  @Override
607
  public Locale getLocale() {
608

609
    Locale locale = this.startContext.getLocale();
4✔
610
    if (locale == null) {
2!
611
      locale = Locale.getDefault();
×
612
    }
613
    return locale;
2✔
614
  }
615

616
  @Override
617
  public DirectoryMerger getWorkspaceMerger() {
618

619
    if (this.workspaceMerger == null) {
3✔
620
      this.workspaceMerger = new DirectoryMerger(this);
6✔
621
    }
622
    return this.workspaceMerger;
3✔
623
  }
624

625
  /**
626
   * @return the {@link #getDefaultExecutionDirectory() default execution directory} in which a command process is executed.
627
   */
628
  @Override
629
  public Path getDefaultExecutionDirectory() {
630

631
    return this.defaultExecutionDirectory;
×
632
  }
633

634
  /**
635
   * @param defaultExecutionDirectory new value of {@link #getDefaultExecutionDirectory()}.
636
   */
637
  public void setDefaultExecutionDirectory(Path defaultExecutionDirectory) {
638

639
    if (defaultExecutionDirectory != null) {
×
640
      this.defaultExecutionDirectory = defaultExecutionDirectory;
×
641
    }
642
  }
×
643

644
  @Override
645
  public GitContext getGitContext() {
646

647
    return new GitContextImpl(this);
×
648
  }
649

650
  @Override
651
  public ProcessContext newProcess() {
652

653
    ProcessContext processContext = createProcessContext();
3✔
654
    if (this.defaultExecutionDirectory != null) {
3!
655
      processContext.directory(this.defaultExecutionDirectory);
×
656
    }
657
    return processContext;
2✔
658
  }
659

660
  @Override
661
  public IdeSystem getSystem() {
662

663
    if (this.system == null) {
×
664
      this.system = new IdeSystemImpl(this);
×
665
    }
666
    return this.system;
×
667
  }
668

669
  /**
670
   * @return a new instance of {@link ProcessContext}.
671
   * @see #newProcess()
672
   */
673
  protected ProcessContext createProcessContext() {
674

675
    return new ProcessContextImpl(this);
×
676
  }
677

678
  @Override
679
  public IdeSubLogger level(IdeLogLevel level) {
680

681
    return this.startContext.level(level);
5✔
682
  }
683

684
  @Override
685
  public void logIdeHomeAndRootStatus() {
686

687
    if (this.ideRoot != null) {
3!
688
      success("IDE_ROOT is set to {}", this.ideRoot);
×
689
    }
690
    if (this.ideHome == null) {
3!
691
      warning(getMessageIdeHomeNotFound());
5✔
692
    } else {
693
      success("IDE_HOME is set to {}", this.ideHome);
×
694
    }
695
  }
1✔
696

697
  @Override
698
  public String askForInput(String message, String defaultValue) {
699

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

714
  @Override
715
  public String askForInput(String message) {
716

717
    String input;
718
    do {
719
      info(message);
3✔
720
      input = readLine().trim();
4✔
721
    } while (input.isEmpty());
3!
722

723
    return input;
2✔
724
  }
725

726
  @SuppressWarnings("unchecked")
727
  @Override
728
  public <O> O question(String question, O... options) {
729

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

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

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

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

777
  @Override
778
  public Step getCurrentStep() {
779

780
    return this.currentStep;
×
781
  }
782

783
  @Override
784
  public StepImpl newStep(boolean silent, String name, Object... parameters) {
785

786
    this.currentStep = new StepImpl(this, this.currentStep, name, silent, parameters);
11✔
787
    return this.currentStep;
3✔
788
  }
789

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

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

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

816
    CliArgument current = arguments.current();
3✔
817
    assert (this.currentStep == null);
4!
818
    boolean supressStepSuccess = false;
2✔
819
    StepImpl step = newStep(true, "ide", (Object[]) current.asArray());
8✔
820
    Iterator<Commandlet> commandletIterator = this.commandletManager.findCommandlet(arguments, null);
6✔
821
    Commandlet cmd = null;
2✔
822
    ValidationResult result = null;
2✔
823
    try {
824
      while (commandletIterator.hasNext()) {
3!
825
        cmd = commandletIterator.next();
4✔
826
        result = applyAndRun(arguments.copy(), cmd);
6✔
827
        if (result.isValid()) {
3!
828
          supressStepSuccess = cmd.isSuppressStepSuccess();
3✔
829
          step.success();
2✔
830
          return ProcessResult.SUCCESS;
4✔
831
        }
832
      }
833
      this.startContext.activateLogging();
×
834
      if (result != null) {
×
835
        error(result.getErrorMessage());
×
836
      }
837
      step.error("Invalid arguments: {}", current.getArgs());
×
838
      HelpCommandlet help = this.commandletManager.getCommandlet(HelpCommandlet.class);
×
839
      if (cmd != null) {
×
840
        help.commandlet.setValue(cmd);
×
841
      }
842
      help.run();
×
843
      return 1;
×
844
    } catch (Throwable t) {
×
845
      this.startContext.activateLogging();
×
846
      step.error(t, true);
×
847
      throw t;
×
848
    } finally {
849
      step.close();
2✔
850
      assert (this.currentStep == null);
4!
851
      step.logSummary(supressStepSuccess);
3✔
852
    }
853
  }
854

855
  /**
856
   * @param cmd the potential {@link Commandlet} to {@link #apply(CliArguments, Commandlet) apply} and {@link Commandlet#run() run}.
857
   * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully, {@code false} otherwise (the
858
   *     {@link Commandlet} did not match and we have to try a different candidate).
859
   */
860
  private ValidationResult applyAndRun(CliArguments arguments, Commandlet cmd) {
861

862
    IdeLogLevel previousLogLevel = null;
2✔
863
    cmd.reset();
2✔
864
    ValidationResult result = apply(arguments, cmd);
5✔
865
    if (result.isValid()) {
3!
866
      result = cmd.validate();
3✔
867
    }
868
    if (result.isValid()) {
3!
869
      debug("Running commandlet {}", cmd);
9✔
870
      if (cmd.isIdeHomeRequired() && (this.ideHome == null)) {
6!
871
        throw new CliException(getMessageIdeHomeNotFound(), ProcessResult.NO_IDE_HOME);
×
872
      } else if (cmd.isIdeRootRequired() && (this.ideRoot == null)) {
6!
873
        throw new CliException(getMessageIdeRootNotFound(), ProcessResult.NO_IDE_ROOT);
×
874
      }
875
      try {
876
        if (cmd.isProcessableOutput()) {
3!
877
          if (!debug().isEnabled()) {
×
878
            // unless --debug or --trace was supplied, processable output commandlets will disable all log-levels except INFO to prevent other logs interfere
879
            previousLogLevel = this.startContext.setLogLevel(IdeLogLevel.PROCESSABLE);
×
880
          }
881
          this.startContext.activateLogging();
×
882
        } else {
883
          this.startContext.activateLogging();
3✔
884
          verifyIdeRoot();
2✔
885
          if (cmd.isIdeHomeRequired()) {
3✔
886
            debug(getMessageIdeHomeFound());
4✔
887
          }
888
          Path settingsRepository = getSettingsGitRepository();
3✔
889
          if (settingsRepository != null) {
2!
890
            if (getGitContext().isRepositoryUpdateAvailable(settingsRepository, getSettingsCommitIdPath()) ||
×
891
                (getGitContext().fetchIfNeeded(settingsRepository) && getGitContext().isRepositoryUpdateAvailable(settingsRepository, getSettingsCommitIdPath()))) {
×
892
              interaction("Updates are available for the settings repository. If you want to apply the latest changes, call \"ide update\"");
×
893
            }
894
          }
895
        }
896
        cmd.run();
2✔
897
      } finally {
898
        if (previousLogLevel != null) {
2!
899
          this.startContext.setLogLevel(previousLogLevel);
×
900
        }
901
      }
1✔
902
    } else {
903
      trace("Commandlet did not match");
×
904
    }
905
    return result;
2✔
906
  }
907

908
  private void verifyIdeRoot() {
909
    if (!isTest()) {
3!
910
      if (this.ideRoot == null) {
×
911
        warning("Variable IDE_ROOT is undefined. Please check your installation or run setup script again.");
×
912
      } else if (this.ideHome != null) {
×
913
        Path ideRootPath = getIdeRootPathFromEnv();
×
914
        if (!this.ideRoot.equals(ideRootPath)) {
×
915
          warning("Variable IDE_ROOT is set to '{}' but for your project '{}' the path '{}' would have been expected.", ideRootPath,
×
916
              this.ideHome.getFileName(), this.ideRoot);
×
917
        }
918
      }
919
    }
920
  }
1✔
921

922
  /**
923
   * @param arguments the {@link CliArguments#ofCompletion(String...) completion arguments}.
924
   * @param includeContextOptions to include the options of {@link ContextCommandlet}.
925
   * @return the {@link List} of {@link CompletionCandidate}s to suggest.
926
   */
927
  public List<CompletionCandidate> complete(CliArguments arguments, boolean includeContextOptions) {
928
    CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault(this);
5✔
929
    if (arguments.current().isStart()) {
4✔
930
      arguments.next();
3✔
931
    }
932
    if (includeContextOptions) {
2✔
933
      ContextCommandlet cc = new ContextCommandlet();
4✔
934
      for (Property<?> property : cc.getProperties()) {
11✔
935
        assert (property.isOption());
4!
936
        property.apply(arguments, this, cc, collector);
7✔
937
      }
1✔
938
    }
939
    Iterator<Commandlet> commandletIterator = this.commandletManager.findCommandlet(arguments, collector);
6✔
940
    CliArgument current = arguments.current();
3✔
941
    if (current.isCompletion() && current.isCombinedShortOption()) {
6✔
942
      collector.add(current.get(), null, null, null);
7✔
943
    }
944
    arguments.next();
3✔
945
    while (commandletIterator.hasNext()) {
3✔
946
      Commandlet cmd = commandletIterator.next();
4✔
947
      if (!arguments.current().isEnd()) {
4✔
948
        completeCommandlet(arguments.copy(), cmd, collector);
6✔
949
      }
950
    }
1✔
951
    return collector.getSortedCandidates();
3✔
952
  }
953

954
  private void completeCommandlet(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) {
955
    trace("Trying to match arguments for auto-completion for commandlet {}", cmd.getName());
10✔
956
    Iterator<Property<?>> valueIterator = cmd.getValues().iterator();
4✔
957
    valueIterator.next(); // skip first property since this is the keyword property that already matched to find the commandlet
3✔
958
    List<Property<?>> properties = cmd.getProperties();
3✔
959
    // we are creating our own list of options and remove them when matched to avoid duplicate suggestions
960
    List<Property<?>> optionProperties = new ArrayList<>(properties.size());
6✔
961
    for (Property<?> property : properties) {
10✔
962
      if (property.isOption()) {
3✔
963
        optionProperties.add(property);
4✔
964
      }
965
    }
1✔
966
    CliArgument currentArgument = arguments.current();
3✔
967
    while (!currentArgument.isEnd()) {
3✔
968
      trace("Trying to match argument '{}'", currentArgument);
9✔
969
      if (currentArgument.isOption() && !arguments.isEndOptions()) {
6!
970
        if (currentArgument.isCompletion()) {
3✔
971
          Iterator<Property<?>> optionIterator = optionProperties.iterator();
3✔
972
          while (optionIterator.hasNext()) {
3✔
973
            Property<?> option = optionIterator.next();
4✔
974
            boolean success = option.apply(arguments, this, cmd, collector);
7✔
975
            if (success) {
2✔
976
              optionIterator.remove();
2✔
977
              arguments.next();
3✔
978
            }
979
          }
1✔
980
        } else {
1✔
981
          Property<?> option = cmd.getOption(currentArgument.get());
5✔
982
          if (option != null) {
2✔
983
            arguments.next();
3✔
984
            boolean removed = optionProperties.remove(option);
4✔
985
            if (!removed) {
2!
986
              option = null;
×
987
            }
988
          }
989
          if (option == null) {
2✔
990
            trace("No such option was found.");
3✔
991
            return;
1✔
992
          }
993
        }
1✔
994
      } else {
995
        if (valueIterator.hasNext()) {
3✔
996
          Property<?> valueProperty = valueIterator.next();
4✔
997
          boolean success = valueProperty.apply(arguments, this, cmd, collector);
7✔
998
          if (!success) {
2!
999
            trace("Completion cannot match any further.");
×
1000
            return;
×
1001
          }
1002
        } else {
1✔
1003
          trace("No value left for completion.");
3✔
1004
          return;
1✔
1005
        }
1006
      }
1007
      currentArgument = arguments.current();
4✔
1008
    }
1009
  }
1✔
1010

1011

1012
  /**
1013
   * @param arguments the {@link CliArguments} to apply. Will be {@link CliArguments#next() consumed} as they are matched. Consider passing a
1014
   *     {@link CliArguments#copy() copy} as needed.
1015
   * @param cmd the potential {@link Commandlet} to match.
1016
   * @return the {@link ValidationResult} telling if the {@link CliArguments} can be applied successfully or if validation errors ocurred.
1017
   */
1018
  public ValidationResult apply(CliArguments arguments, Commandlet cmd) {
1019

1020
    trace("Trying to match arguments to commandlet {}", cmd.getName());
10✔
1021
    CliArgument currentArgument = arguments.current();
3✔
1022
    Iterator<Property<?>> propertyIterator = cmd.getValues().iterator();
4✔
1023
    Property<?> property = null;
2✔
1024
    if (propertyIterator.hasNext()) {
3!
1025
      property = propertyIterator.next();
4✔
1026
    }
1027
    while (!currentArgument.isEnd()) {
3✔
1028
      trace("Trying to match argument '{}'", currentArgument);
9✔
1029
      Property<?> currentProperty = property;
2✔
1030
      if (!arguments.isEndOptions()) {
3!
1031
        Property<?> option = cmd.getOption(currentArgument.getKey());
5✔
1032
        if (option != null) {
2!
1033
          currentProperty = option;
×
1034
        }
1035
      }
1036
      if (currentProperty == null) {
2!
1037
        trace("No option or next value found");
×
1038
        ValidationState state = new ValidationState(null);
×
1039
        state.addErrorMessage("No matching property found");
×
1040
        return state;
×
1041
      }
1042
      trace("Next property candidate to match argument is {}", currentProperty);
9✔
1043
      if (currentProperty == property) {
3!
1044
        if (!property.isMultiValued()) {
3✔
1045
          if (propertyIterator.hasNext()) {
3✔
1046
            property = propertyIterator.next();
5✔
1047
          } else {
1048
            property = null;
2✔
1049
          }
1050
        }
1051
        if ((property != null) && property.isValue() && property.isMultiValued()) {
8!
1052
          arguments.stopSplitShortOptions();
2✔
1053
        }
1054
      }
1055
      boolean matches = currentProperty.apply(arguments, this, cmd, null);
7✔
1056
      if (!matches && currentArgument.isCompletion()) {
2!
1057
        ValidationState state = new ValidationState(null);
×
1058
        state.addErrorMessage("No matching property found");
×
1059
        return state;
×
1060
      }
1061
      currentArgument = arguments.current();
3✔
1062
    }
1✔
1063
    return ValidationResultValid.get();
2✔
1064
  }
1065

1066
  @Override
1067
  public String findBash() {
1068

1069
    String bash = "bash";
2✔
1070
    if (SystemInfoImpl.INSTANCE.isWindows()) {
3!
1071
      bash = findBashOnWindows();
×
1072
    }
1073

1074
    return bash;
2✔
1075
  }
1076

1077
  private String findBashOnWindows() {
1078

1079
    // Check if Git Bash exists in the default location
1080
    Path defaultPath = Path.of("C:\\Program Files\\Git\\bin\\bash.exe");
×
1081
    if (Files.exists(defaultPath)) {
×
1082
      return defaultPath.toString();
×
1083
    }
1084

1085
    // If not found in the default location, try the registry query
1086
    String[] bashVariants = { "GitForWindows", "Cygwin\\setup" };
×
1087
    String[] registryKeys = { "HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER" };
×
1088
    String regQueryResult;
1089
    for (String bashVariant : bashVariants) {
×
1090
      for (String registryKey : registryKeys) {
×
1091
        String toolValueName = ("GitForWindows".equals(bashVariant)) ? "InstallPath" : "rootdir";
×
1092
        String command = "reg query " + registryKey + "\\Software\\" + bashVariant + "  /v " + toolValueName + " 2>nul";
×
1093

1094
        try {
1095
          Process process = new ProcessBuilder("cmd.exe", "/c", command).start();
×
1096
          try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
×
1097
            StringBuilder output = new StringBuilder();
×
1098
            String line;
1099

1100
            while ((line = reader.readLine()) != null) {
×
1101
              output.append(line);
×
1102
            }
1103

1104
            int exitCode = process.waitFor();
×
1105
            if (exitCode != 0) {
×
1106
              return null;
×
1107
            }
1108

1109
            regQueryResult = output.toString();
×
1110
            if (regQueryResult != null) {
×
1111
              int index = regQueryResult.indexOf("REG_SZ");
×
1112
              if (index != -1) {
×
1113
                String path = regQueryResult.substring(index + "REG_SZ".length()).trim();
×
1114
                return path + "\\bin\\bash.exe";
×
1115
              }
1116
            }
1117

1118
          }
×
1119
        } catch (Exception e) {
×
1120
          return null;
×
1121
        }
×
1122
      }
1123
    }
1124
    // no bash found
1125
    return null;
×
1126
  }
1127

1128
  @Override
1129
  public WindowsPathSyntax getPathSyntax() {
1130
    return this.pathSyntax;
3✔
1131
  }
1132

1133
  /**
1134
   * @param pathSyntax new value of {@link #getPathSyntax()}.
1135
   */
1136
  public void setPathSyntax(WindowsPathSyntax pathSyntax) {
1137

1138
    this.pathSyntax = pathSyntax;
3✔
1139
  }
1✔
1140

1141
  /**
1142
   * @return the {@link IdeStartContextImpl}.
1143
   */
1144
  public IdeStartContextImpl getStartContext() {
1145

1146
    return startContext;
3✔
1147
  }
1148

1149
  /**
1150
   * Reloads this context and re-initializes the {@link #getVariables() variables}.
1151
   */
1152
  public void reload() {
1153
    this.variables = null;
3✔
1154
  }
1✔
1155
}
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