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

devonfw / IDEasy / 9904178931

12 Jul 2024 07:32AM UTC coverage: 61.387% (-0.5%) from 61.842%
9904178931

push

github

web-flow
#400: fix error handling for undefined IDE_ROOT and IDE_HOME (#476)

1997 of 3575 branches covered (55.86%)

Branch coverage included in aggregate %.

5297 of 8307 relevant lines covered (63.77%)

2.8 hits per line

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

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

3
import com.devonfw.tools.ide.cli.CliAbortException;
4
import com.devonfw.tools.ide.cli.CliArgument;
5
import com.devonfw.tools.ide.cli.CliArguments;
6
import com.devonfw.tools.ide.cli.CliException;
7
import com.devonfw.tools.ide.commandlet.Commandlet;
8
import com.devonfw.tools.ide.commandlet.CommandletManager;
9
import com.devonfw.tools.ide.commandlet.CommandletManagerImpl;
10
import com.devonfw.tools.ide.commandlet.ContextCommandlet;
11
import com.devonfw.tools.ide.commandlet.HelpCommandlet;
12
import com.devonfw.tools.ide.common.SystemPath;
13
import com.devonfw.tools.ide.completion.CompletionCandidate;
14
import com.devonfw.tools.ide.completion.CompletionCandidateCollector;
15
import com.devonfw.tools.ide.completion.CompletionCandidateCollectorDefault;
16
import com.devonfw.tools.ide.environment.AbstractEnvironmentVariables;
17
import com.devonfw.tools.ide.environment.EnvironmentVariables;
18
import com.devonfw.tools.ide.environment.EnvironmentVariablesType;
19
import com.devonfw.tools.ide.io.FileAccess;
20
import com.devonfw.tools.ide.io.FileAccessImpl;
21
import com.devonfw.tools.ide.log.IdeLogLevel;
22
import com.devonfw.tools.ide.log.IdeSubLogger;
23
import com.devonfw.tools.ide.log.IdeSubLoggerNone;
24
import com.devonfw.tools.ide.merge.DirectoryMerger;
25
import com.devonfw.tools.ide.network.ProxyContext;
26
import com.devonfw.tools.ide.os.SystemInfo;
27
import com.devonfw.tools.ide.os.SystemInfoImpl;
28
import com.devonfw.tools.ide.os.WindowsPathSyntax;
29
import com.devonfw.tools.ide.process.ProcessContext;
30
import com.devonfw.tools.ide.process.ProcessContextImpl;
31
import com.devonfw.tools.ide.process.ProcessResult;
32
import com.devonfw.tools.ide.property.Property;
33
import com.devonfw.tools.ide.repo.CustomToolRepository;
34
import com.devonfw.tools.ide.repo.CustomToolRepositoryImpl;
35
import com.devonfw.tools.ide.repo.DefaultToolRepository;
36
import com.devonfw.tools.ide.repo.ToolRepository;
37
import com.devonfw.tools.ide.step.Step;
38
import com.devonfw.tools.ide.step.StepImpl;
39
import com.devonfw.tools.ide.url.model.UrlMetadata;
40

41
import java.io.BufferedReader;
42
import java.io.IOException;
43
import java.io.InputStreamReader;
44
import java.net.URL;
45
import java.net.URLConnection;
46
import java.nio.file.Files;
47
import java.nio.file.Path;
48
import java.util.HashMap;
49
import java.util.Iterator;
50
import java.util.List;
51
import java.util.Locale;
52
import java.util.Map;
53
import java.util.Objects;
54
import java.util.function.Function;
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 Map<IdeLogLevel, IdeSubLogger> loggers;
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
  private 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 toolRepository;
96

97
  private 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
  private final ToolRepository defaultToolRepository;
114

115
  private CustomToolRepository customToolRepository;
116

117
  private DirectoryMerger workspaceMerger;
118

119
  private final Function<IdeLogLevel, IdeSubLogger> loggerFactory;
120

121
  private boolean offlineMode;
122

123
  private boolean forceMode;
124

125
  private boolean batchMode;
126

127
  private boolean quietMode;
128

129
  private Locale locale;
130

131
  private UrlMetadata urlMetadata;
132

133
  private Path defaultExecutionDirectory;
134

135
  private StepImpl currentStep;
136

137
  /**
138
   * The constructor.
139
   *
140
   * @param minLogLevel the minimum {@link IdeLogLevel} to enable. Should be {@link IdeLogLevel#INFO} by default.
141
   * @param factory the {@link Function} to create {@link IdeSubLogger} per {@link IdeLogLevel}.
142
   * @param userDir the optional {@link Path} to current working directory.
143
   * @param toolRepository @param toolRepository the {@link ToolRepository} of the context. If it is set to {@code null} {@link DefaultToolRepository} will be
144
   * used.
145
   */
146
  public AbstractIdeContext(IdeLogLevel minLogLevel, Function<IdeLogLevel, IdeSubLogger> factory, Path userDir, ToolRepository toolRepository) {
147

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

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

184
    setCwd(userDir, workspace, currentDir);
5✔
185

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

206
    if (toolRepository == null) {
2✔
207
      this.defaultToolRepository = new DefaultToolRepository(this);
7✔
208
    } else {
209
      this.defaultToolRepository = toolRepository;
3✔
210
    }
211
  }
1✔
212

213
  private Path findIdeRoot(Path ideHomePath) {
214
    final Path ideRoot;
215
    Path ideRootPath = null;
2✔
216
    if (ideHomePath != null) {
2✔
217
      ideRootPath = ideHomePath.getParent();
3✔
218
    }
219

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

237
  @Override
238
  public void setCwd(Path userDir, String workspace, Path ideHome) {
239

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

271
    this.variables = createVariables();
4✔
272
    this.path = computeSystemPath();
4✔
273
    this.customToolRepository = CustomToolRepositoryImpl.of(this);
4✔
274
    this.workspaceMerger = new DirectoryMerger(this);
6✔
275
  }
1✔
276

277
  private String getMessageIdeHomeFound() {
278

279
    return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName;
×
280
  }
281

282
  private String getMessageIdeHomeNotFound() {
283

284
    return "You are not inside an IDE installation: " + this.cwd;
×
285
  }
286

287
  private static String getMessageIdeRootNotFound() {
288
    String root = System.getenv("IDE_ROOT");
×
289
    if (root == null) {
×
290
      return "The environment variable IDE_ROOT is undefined. Please reinstall IDEasy or manually repair IDE_ROOT variable.";
×
291
    } else {
292
      return "The environment variable IDE_ROOT is pointing to an invalid path " + root + ". Please reinstall IDEasy or manually repair IDE_ROOT variable.";
×
293
    }
294
  }
295

296
  /**
297
   * @return the status message about the {@link #getIdeHome() IDE_HOME} detection and environment variable initialization.
298
   */
299
  public String getMessageIdeHome() {
300

301
    if (this.ideHome == null) {
×
302
      return getMessageIdeHomeNotFound();
×
303
    }
304
    return getMessageIdeHomeFound();
×
305
  }
306

307
  /**
308
   * @return {@code true} if this is a test context for JUnits, {@code false} otherwise.
309
   */
310
  public boolean isTest() {
311

312
    return isMock();
3✔
313
  }
314

315
  /**
316
   * @return {@code true} if this is a mock context for JUnits, {@code false} otherwise.
317
   */
318
  public boolean isMock() {
319

320
    return false;
2✔
321
  }
322

323
  private SystemPath computeSystemPath() {
324

325
    return new SystemPath(this);
5✔
326
  }
327

328
  private boolean isIdeHome(Path dir) {
329

330
    if (!Files.isDirectory(dir.resolve("workspaces"))) {
7✔
331
      return false;
2✔
332
    } else if (!Files.isDirectory(dir.resolve("settings"))) {
7!
333
      return false;
×
334
    }
335
    return true;
2✔
336
  }
337

338
  private Path getParentPath(Path dir) {
339

340
    try {
341
      Path linkDir = dir.toRealPath();
5✔
342
      if (!dir.equals(linkDir)) {
4!
343
        return linkDir;
×
344
      } else {
345
        return dir.getParent();
3✔
346
      }
347
    } catch (IOException e) {
×
348
      throw new IllegalStateException(e);
×
349
    }
350

351
  }
352

353
  private EnvironmentVariables createVariables() {
354

355
    AbstractEnvironmentVariables system = EnvironmentVariables.ofSystem(this);
3✔
356
    AbstractEnvironmentVariables user = extendVariables(system, this.userHomeIde, EnvironmentVariablesType.USER);
7✔
357
    AbstractEnvironmentVariables settings = extendVariables(user, this.settingsPath, EnvironmentVariablesType.SETTINGS);
7✔
358
    // TODO should we keep this workspace properties? Was this feature ever used?
359
    AbstractEnvironmentVariables workspace = extendVariables(settings, this.workspacePath, EnvironmentVariablesType.WORKSPACE);
7✔
360
    AbstractEnvironmentVariables conf = extendVariables(workspace, this.confPath, EnvironmentVariablesType.CONF);
7✔
361
    return conf.resolved();
3✔
362
  }
363

364
  private AbstractEnvironmentVariables extendVariables(AbstractEnvironmentVariables envVariables, Path propertiesPath, EnvironmentVariablesType type) {
365

366
    Path propertiesFile = null;
2✔
367
    if (propertiesPath == null) {
2✔
368
      trace("Configuration directory for type {} does not exist.", type);
10✔
369
    } else if (Files.isDirectory(propertiesPath)) {
5✔
370
      propertiesFile = propertiesPath.resolve(EnvironmentVariables.DEFAULT_PROPERTIES);
4✔
371
      boolean legacySupport = (type != EnvironmentVariablesType.USER);
7✔
372
      if (legacySupport && !Files.exists(propertiesFile)) {
7✔
373
        Path legacyFile = propertiesPath.resolve(EnvironmentVariables.LEGACY_PROPERTIES);
4✔
374
        if (Files.exists(legacyFile)) {
5!
375
          propertiesFile = legacyFile;
×
376
        }
377
      }
378
    } else {
1✔
379
      debug("Configuration directory {} does not exist.", propertiesPath);
9✔
380
    }
381
    return envVariables.extend(propertiesFile, type);
5✔
382
  }
383

384
  @Override
385
  public SystemInfo getSystemInfo() {
386

387
    return this.systemInfo;
3✔
388
  }
389

390
  @Override
391
  public FileAccess getFileAccess() {
392

393
    return this.fileAccess;
3✔
394
  }
395

396
  @Override
397
  public CommandletManager getCommandletManager() {
398

399
    return this.commandletManager;
3✔
400
  }
401

402
  @Override
403
  public ToolRepository getDefaultToolRepository() {
404

405
    return this.defaultToolRepository;
3✔
406
  }
407

408
  @Override
409
  public CustomToolRepository getCustomToolRepository() {
410

411
    return this.customToolRepository;
3✔
412
  }
413

414
  @Override
415
  public Path getIdeHome() {
416

417
    return this.ideHome;
3✔
418
  }
419

420
  @Override
421
  public String getProjectName() {
422

423
    if (this.ideHome != null) {
3!
424
      return this.ideHome.getFileName().toString();
5✔
425
    }
426
    return "";
×
427
  }
428

429
  @Override
430
  public Path getIdeRoot() {
431

432
    return this.ideRoot;
3✔
433
  }
434

435
  @Override
436
  public Path getCwd() {
437

438
    return this.cwd;
3✔
439
  }
440

441
  @Override
442
  public Path getTempPath() {
443

444
    return this.tempPath;
3✔
445
  }
446

447
  @Override
448
  public Path getTempDownloadPath() {
449

450
    return this.tempDownloadPath;
3✔
451
  }
452

453
  @Override
454
  public Path getUserHome() {
455

456
    return this.userHome;
3✔
457
  }
458

459
  @Override
460
  public Path getUserHomeIde() {
461

462
    return this.userHomeIde;
3✔
463
  }
464

465
  @Override
466
  public Path getSettingsPath() {
467

468
    return this.settingsPath;
3✔
469
  }
470

471
  @Override
472
  public Path getConfPath() {
473

474
    return this.confPath;
3✔
475
  }
476

477
  @Override
478
  public Path getSoftwarePath() {
479

480
    return this.softwarePath;
3✔
481
  }
482

483
  @Override
484
  public Path getSoftwareExtraPath() {
485

486
    return this.softwareExtraPath;
3✔
487
  }
488

489
  @Override
490
  public Path getSoftwareRepositoryPath() {
491

492
    return this.softwareRepositoryPath;
3✔
493
  }
494

495
  @Override
496
  public Path getPluginsPath() {
497

498
    return this.pluginsPath;
3✔
499
  }
500

501
  @Override
502
  public String getWorkspaceName() {
503

504
    return this.workspaceName;
3✔
505
  }
506

507
  @Override
508
  public Path getWorkspacePath() {
509

510
    return this.workspacePath;
3✔
511
  }
512

513
  @Override
514
  public Path getDownloadPath() {
515

516
    return this.downloadPath;
3✔
517
  }
518

519
  @Override
520
  public Path getUrlsPath() {
521

522
    return this.urlsPath;
3✔
523
  }
524

525
  @Override
526
  public Path getToolRepositoryPath() {
527

528
    return this.toolRepository;
3✔
529
  }
530

531
  @Override
532
  public SystemPath getPath() {
533

534
    return this.path;
3✔
535
  }
536

537
  @Override
538
  public EnvironmentVariables getVariables() {
539

540
    return this.variables;
3✔
541
  }
542

543
  @Override
544
  public UrlMetadata getUrls() {
545

546
    if (this.urlMetadata == null) {
3✔
547
      if (!isTest()) {
3!
548
        getGitContext().pullOrCloneAndResetIfNeeded(IDE_URLS_GIT, this.urlsPath, null);
×
549
      }
550
      this.urlMetadata = new UrlMetadata(this);
6✔
551
    }
552
    return this.urlMetadata;
3✔
553
  }
554

555
  @Override
556
  public boolean isQuietMode() {
557

558
    return this.quietMode;
3✔
559
  }
560

561
  /**
562
   * @param quietMode new value of {@link #isQuietMode()}.
563
   */
564
  public void setQuietMode(boolean quietMode) {
565

566
    this.quietMode = quietMode;
3✔
567
  }
1✔
568

569
  @Override
570
  public boolean isBatchMode() {
571

572
    return this.batchMode;
3✔
573
  }
574

575
  /**
576
   * @param batchMode new value of {@link #isBatchMode()}.
577
   */
578
  public void setBatchMode(boolean batchMode) {
579

580
    this.batchMode = batchMode;
3✔
581
  }
1✔
582

583
  @Override
584
  public boolean isForceMode() {
585

586
    return this.forceMode;
3✔
587
  }
588

589
  /**
590
   * @param forceMode new value of {@link #isForceMode()}.
591
   */
592
  public void setForceMode(boolean forceMode) {
593

594
    this.forceMode = forceMode;
3✔
595
  }
1✔
596

597
  @Override
598
  public boolean isOfflineMode() {
599

600
    return this.offlineMode;
3✔
601
  }
602

603
  /**
604
   * @param offlineMode new value of {@link #isOfflineMode()}.
605
   */
606
  public void setOfflineMode(boolean offlineMode) {
607

608
    this.offlineMode = offlineMode;
3✔
609
  }
1✔
610

611
  @Override
612
  public boolean isOnline() {
613

614
    boolean online = false;
×
615
    try {
616
      int timeout = 1000;
×
617
      //open a connection to github.com and try to retrieve data
618
      //getContent fails if there is no connection
619
      URLConnection connection = new URL("https://www.github.com").openConnection();
×
620
      connection.setConnectTimeout(timeout);
×
621
      connection.getContent();
×
622
      online = true;
×
623

624
    } catch (Exception ignored) {
×
625

626
    }
×
627
    return online;
×
628
  }
629

630
  @Override
631
  public Locale getLocale() {
632

633
    if (this.locale == null) {
3!
634
      return Locale.getDefault();
2✔
635
    }
636
    return this.locale;
×
637
  }
638

639
  /**
640
   * @param locale new value of {@link #getLocale()}.
641
   */
642
  public void setLocale(Locale locale) {
643

644
    this.locale = locale;
3✔
645
  }
1✔
646

647
  @Override
648
  public DirectoryMerger getWorkspaceMerger() {
649

650
    return this.workspaceMerger;
3✔
651
  }
652

653
  /**
654
   * @return the {@link #getDefaultExecutionDirectory() default execution directory} in which a command process is executed.
655
   */
656
  public Path getDefaultExecutionDirectory() {
657

658
    return this.defaultExecutionDirectory;
×
659
  }
660

661
  /**
662
   * @param defaultExecutionDirectory new value of {@link #getDefaultExecutionDirectory()}.
663
   */
664
  public void setDefaultExecutionDirectory(Path defaultExecutionDirectory) {
665

666
    if (defaultExecutionDirectory != null) {
×
667
      this.defaultExecutionDirectory = defaultExecutionDirectory;
×
668
    }
669
  }
×
670

671
  @Override
672
  public ProxyContext getProxyContext() {
673

674
    return new ProxyContext(this);
5✔
675
  }
676

677
  @Override
678
  public GitContext getGitContext() {
679

680
    return new GitContextImpl(this);
×
681
  }
682

683
  @Override
684
  public ProcessContext newProcess() {
685

686
    ProcessContext processContext = createProcessContext();
3✔
687
    if (this.defaultExecutionDirectory != null) {
3!
688
      processContext.directory(this.defaultExecutionDirectory);
×
689
    }
690
    return processContext;
2✔
691
  }
692

693
  /**
694
   * @return a new instance of {@link ProcessContext}.
695
   * @see #newProcess()
696
   */
697
  protected ProcessContext createProcessContext() {
698

699
    return new ProcessContextImpl(this);
×
700
  }
701

702
  @Override
703
  public IdeSubLogger level(IdeLogLevel level) {
704

705
    IdeSubLogger logger = this.loggers.get(level);
6✔
706
    Objects.requireNonNull(logger);
3✔
707
    return logger;
2✔
708
  }
709

710
  @Override
711
  public String askForInput(String message, String defaultValue) {
712

713
    if (!message.isBlank()) {
×
714
      info(message);
×
715
    }
716
    if (isBatchMode()) {
×
717
      if (isForceMode()) {
×
718
        return defaultValue;
×
719
      } else {
720
        throw new CliAbortException();
×
721
      }
722
    }
723
    String input = readLine().trim();
×
724
    return input.isEmpty() ? defaultValue : input;
×
725
  }
726

727
  @Override
728
  public String askForInput(String message) {
729

730
    String input;
731
    do {
732
      info(message);
×
733
      input = readLine().trim();
×
734
    } while (input.isEmpty());
×
735

736
    return input;
×
737
  }
738

739
  @SuppressWarnings("unchecked")
740
  @Override
741
  public <O> O question(String question, O... options) {
742

743
    assert (options.length >= 2);
×
744
    interaction(question);
×
745
    Map<String, O> mapping = new HashMap<>(options.length);
×
746
    int i = 0;
×
747
    for (O option : options) {
×
748
      i++;
×
749
      String key = "" + option;
×
750
      addMapping(mapping, key, option);
×
751
      String numericKey = Integer.toString(i);
×
752
      if (numericKey.equals(key)) {
×
753
        trace("Options should not be numeric: " + key);
×
754
      } else {
755
        addMapping(mapping, numericKey, option);
×
756
      }
757
      interaction("Option " + numericKey + ": " + key);
×
758
    }
759
    O option = null;
×
760
    if (isBatchMode()) {
×
761
      if (isForceMode()) {
×
762
        option = options[0];
×
763
        interaction("" + option);
×
764
      }
765
    } else {
766
      while (option == null) {
×
767
        String answer = readLine();
×
768
        option = mapping.get(answer);
×
769
        if (option == null) {
×
770
          warning("Invalid answer: '" + answer + "' - please try again.");
×
771
        }
772
      }
×
773
    }
774
    return option;
×
775
  }
776

777
  /**
778
   * @return the input from the end-user (e.g. read from the console).
779
   */
780
  protected abstract String readLine();
781

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

784
    O duplicate = mapping.put(key, option);
×
785
    if (duplicate != null) {
×
786
      throw new IllegalArgumentException("Duplicated option " + key);
×
787
    }
788
  }
×
789

790
  /**
791
   * Sets the log level.
792
   *
793
   * @param logLevel {@link IdeLogLevel}
794
   */
795
  public void setLogLevel(IdeLogLevel logLevel) {
796

797
    for (IdeLogLevel level : IdeLogLevel.values()) {
16✔
798
      IdeSubLogger logger;
799
      if (level.ordinal() < logLevel.ordinal()) {
5✔
800
        logger = new IdeSubLoggerNone(level);
6✔
801
      } else {
802
        logger = this.loggerFactory.apply(level);
6✔
803
      }
804
      this.loggers.put(level, logger);
6✔
805
    }
806
  }
1✔
807

808
  @Override
809
  public Step getCurrentStep() {
810

811
    return this.currentStep;
×
812
  }
813

814
  @Override
815
  public StepImpl newStep(boolean silent, String name, Object... parameters) {
816

817
    this.currentStep = new StepImpl(this, this.currentStep, name, silent, parameters);
11✔
818
    return this.currentStep;
3✔
819
  }
820

821
  /**
822
   * Internal method to end the running {@link Step}.
823
   *
824
   * @param step the current {@link Step} to end.
825
   */
826
  public void endStep(StepImpl step) {
827

828
    if (step == this.currentStep) {
4!
829
      this.currentStep = this.currentStep.getParent();
6✔
830
    } else {
831
      String currentStepName = "null";
×
832
      if (this.currentStep != null) {
×
833
        currentStepName = this.currentStep.getName();
×
834
      }
835
      warning("endStep called with wrong step '{}' but expected '{}'", step.getName(), currentStepName);
×
836
    }
837
  }
1✔
838

839
  /**
840
   * Finds the matching {@link Commandlet} to run, applies {@link CliArguments} to its {@link Commandlet#getProperties() properties} and will execute it.
841
   *
842
   * @param arguments the {@link CliArgument}.
843
   * @return the return code of the execution.
844
   */
845
  public int run(CliArguments arguments) {
846

847
    CliArgument current = arguments.current();
×
848
    assert (this.currentStep == null);
×
849
    boolean supressStepSuccess = false;
×
850
    StepImpl step = newStep(true, "ide", (Object[]) current.asArray());
×
851
    Commandlet firstCandidate = null;
×
852
    try {
853
      if (!current.isEnd()) {
×
854
        String keyword = current.get();
×
855
        firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword);
×
856
        boolean matches;
857
        if (firstCandidate != null) {
×
858
          matches = applyAndRun(arguments.copy(), firstCandidate);
×
859
          if (matches) {
×
860
            supressStepSuccess = firstCandidate.isSuppressStepSuccess();
×
861
            step.success();
×
862
            return ProcessResult.SUCCESS;
×
863
          }
864
        }
865
        for (Commandlet cmd : this.commandletManager.getCommandlets()) {
×
866
          if (cmd != firstCandidate) {
×
867
            matches = applyAndRun(arguments.copy(), cmd);
×
868
            if (matches) {
×
869
              supressStepSuccess = cmd.isSuppressStepSuccess();
×
870
              step.success();
×
871
              return ProcessResult.SUCCESS;
×
872
            }
873
          }
874
        }
×
875
        step.error("Invalid arguments: {}", current.getArgs());
×
876
      }
877

878
      HelpCommandlet help = this.commandletManager.getCommandlet(HelpCommandlet.class);
×
879
      if (firstCandidate != null) {
×
880
        help.commandlet.setValue(firstCandidate);
×
881
      }
882
      help.run();
×
883
      return 1;
×
884
    } catch (Throwable t) {
×
885
      step.error(t, true);
×
886
      throw t;
×
887
    } finally {
888
      step.close();
×
889
      assert (this.currentStep == null);
×
890
      step.logSummary(supressStepSuccess);
×
891
    }
892
  }
893

894
  /**
895
   * @param cmd the potential {@link Commandlet} to
896
   *     {@link #apply(CliArguments, Commandlet, CompletionCandidateCollector) apply} and {@link Commandlet#run() run}.
897
   * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully,
898
   *     {@code false} otherwise (the {@link Commandlet} did not match and we have to try a different candidate).
899
   */
900
  private boolean applyAndRun(CliArguments arguments, Commandlet cmd) {
901

902
    cmd.clearProperties();
×
903

904
    boolean matches = apply(arguments, cmd, null);
×
905
    if (matches) {
×
906
      matches = cmd.validate();
×
907
    }
908
    if (matches) {
×
909
      debug("Running commandlet {}", cmd);
×
910
      if (cmd.isIdeHomeRequired() && (this.ideHome == null)) {
×
911
        throw new CliException(getMessageIdeHomeNotFound(), ProcessResult.NO_IDE_HOME);
×
912
      } else if (cmd.isIdeRootRequired() && (this.ideRoot == null)) {
×
913
        throw new CliException(getMessageIdeRootNotFound(), ProcessResult.NO_IDE_ROOT);
×
914
      }
915
      if (!cmd.isProcessableOutput()) {
×
916
        if (cmd.isIdeHomeRequired()) {
×
917
          debug(getMessageIdeHomeFound());
×
918
        }
919
      }
920
      cmd.run();
×
921
    } else {
922
      trace("Commandlet did not match");
×
923
    }
924
    return matches;
×
925
  }
926

927
  /**
928
   * @param arguments the {@link CliArguments#ofCompletion(String...) completion arguments}.
929
   * @param includeContextOptions to include the options of {@link ContextCommandlet}.
930
   * @return the {@link List} of {@link CompletionCandidate}s to suggest.
931
   */
932
  public List<CompletionCandidate> complete(CliArguments arguments, boolean includeContextOptions) {
933

934
    CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault(this);
5✔
935
    if (arguments.current().isStart()) {
4✔
936
      arguments.next();
3✔
937
    }
938
    if (includeContextOptions) {
2✔
939
      ContextCommandlet cc = new ContextCommandlet();
4✔
940
      for (Property<?> property : cc.getProperties()) {
11✔
941
        assert (property.isOption());
4!
942
        property.apply(arguments, this, cc, collector);
7✔
943
      }
1✔
944
    }
945
    CliArgument current = arguments.current();
3✔
946
    if (!current.isEnd()) {
3!
947
      String keyword = current.get();
3✔
948
      Commandlet firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword);
5✔
949
      boolean matches = false;
2✔
950
      if (firstCandidate != null) {
2✔
951
        matches = apply(arguments.copy(), firstCandidate, collector);
8✔
952
      } else if (current.isCombinedShortOption()) {
3✔
953
        collector.add(keyword, null, null, null);
6✔
954
      }
955
      if (!matches) {
2!
956
        for (Commandlet cmd : this.commandletManager.getCommandlets()) {
12✔
957
          if (cmd != firstCandidate) {
3✔
958
            apply(arguments.copy(), cmd, collector);
7✔
959
          }
960
        }
1✔
961
      }
962
    }
963
    return collector.getSortedCandidates();
3✔
964
  }
965

966
  /**
967
   * @param arguments the {@link CliArguments} to apply. Will be {@link CliArguments#next() consumed} as they are matched. Consider passing a
968
   * {@link CliArguments#copy() copy} as needed.
969
   * @param cmd the potential {@link Commandlet} to match.
970
   * @param collector the {@link CompletionCandidateCollector}.
971
   * @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}
972
   * and {@link Commandlet#validate() validated}), {@code false} otherwise (the {@link Commandlet} did not match and we have to try a different candidate).
973
   */
974
  public boolean apply(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) {
975

976
    trace("Trying to match arguments to commandlet {}", cmd.getName());
10✔
977
    CliArgument currentArgument = arguments.current();
3✔
978
    Iterator<Property<?>> propertyIterator;
979
    if (currentArgument.isCompletion()) {
3✔
980
      propertyIterator = cmd.getProperties().iterator();
5✔
981
    } else {
982
      propertyIterator = cmd.getValues().iterator();
4✔
983
    }
984
    while (!currentArgument.isEnd()) {
3!
985
      trace("Trying to match argument '{}'", currentArgument);
9✔
986
      Property<?> property = null;
2✔
987
      if (!arguments.isEndOptions()) {
3!
988
        property = cmd.getOption(currentArgument.getKey());
5✔
989
      }
990
      if (property == null) {
2✔
991
        if (!propertyIterator.hasNext()) {
3✔
992
          trace("No option or next value found");
3✔
993
          return false;
2✔
994
        }
995
        property = propertyIterator.next();
4✔
996
      }
997
      trace("Next property candidate to match argument is {}", property);
9✔
998
      boolean matches = property.apply(arguments, this, cmd, collector);
7✔
999
      if (!matches || currentArgument.isCompletion()) {
5✔
1000
        return false;
2✔
1001
      }
1002
      currentArgument = arguments.current();
3✔
1003
    }
1✔
1004
    return true;
×
1005
  }
1006

1007
  public String findBash() {
1008

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

1014
    return bash;
2✔
1015
  }
1016

1017
  private String findBashOnWindows() {
1018

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

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

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

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

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

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

1058
          }
×
1059
        } catch (Exception e) {
×
1060
          return null;
×
1061
        }
×
1062
      }
1063
    }
1064
    // no bash found
1065
    throw new IllegalStateException("Could not find Bash. Please install Git for Windows and rerun.");
×
1066
  }
1067

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

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

1078
    this.pathSyntax = pathSyntax;
3✔
1079
  }
1✔
1080
}
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