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

devonfw / IDEasy / 8144002629

04 Mar 2024 04:56PM UTC coverage: 58.928% (+0.7%) from 58.254%
8144002629

push

github

web-flow
#208: improve test infrastructure # 219: fix wrong executable (#238)

* Add first implementation

* Add javadoc

* Add javadoc

* Using target path instead of ressource path to make sure one-to-one copy of set up folders is used

* Add JavaDoc and mac mock program

* Add support for multiple dependencies while testing

* Minor changes

* Modify mock programs for testing

* Refactored example JmcTest

* Add possibility to set execution path by using the context

* Reenable test after related issue 228 has been merged

* Replace ternary with regular if

* Add missing javadoc

* Add missing javadoc

* remove unnecessary semicolon

* Fix spelling typo

* Minor test changes

* Refactoring FileExtractor class for more modularity

* using const

* Add missing extensions

* Remove unnecessary execption declaration and minor rename

* Fix spelling

* Add javadoc

* Forget dot

* minor change

* Revert "minor change"

This reverts commit ec81c3ce6.

* Revert "Merge branch 'main' into feature/208-MockOutToolRepoRefactorTestInfra"

This reverts commit d58847230, reversing
changes made to f38b3105f.

* Revert "Revert "Merge branch 'main' into feature/208-MockOutToolRepoRefactorTestInfra""

This reverts commit 3e49a0b3d.

* Revert "Revert "minor change""

This reverts commit 2f7b94624.

* fix typo

* #208: review and complete rework

* #208: improved system path

* #208: found and fixed bug on windows

* #208: improve OS mocking

* #208: improve test logging

reveal logs/errors so the developer actually sees what is happening instead of leaving them in the dark

* #208: improve OS detection

* #208: fixed

found and fixed bug in MacOS app detection for JMC, fixed copy file to folder bug, moved !extract logic back to FileAccess, f... (continued)

1580 of 2930 branches covered (53.92%)

Branch coverage included in aggregate %.

4047 of 6619 relevant lines covered (61.14%)

2.65 hits per line

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

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

36
import java.io.IOException;
37
import java.net.InetAddress;
38
import java.nio.file.Files;
39
import java.nio.file.Path;
40
import java.util.HashMap;
41
import java.util.Iterator;
42
import java.util.List;
43
import java.util.Locale;
44
import java.util.Map;
45
import java.util.Objects;
46
import java.util.function.Function;
47

48
/**
49
 * Abstract base implementation of {@link IdeContext}.
50
 */
51
public abstract class AbstractIdeContext implements IdeContext {
1✔
52

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

55
  private final Map<IdeLogLevel, IdeSubLogger> loggers;
56

57
  private final Path ideHome;
58

59
  private final Path ideRoot;
60

61
  private final Path confPath;
62

63
  private final Path settingsPath;
64

65
  private final Path softwarePath;
66

67
  private final Path softwareRepositoryPath;
68

69
  private final Path pluginsPath;
70

71
  private final Path workspacePath;
72

73
  private final String workspaceName;
74

75
  private final Path urlsPath;
76

77
  private final Path tempPath;
78

79
  private final Path tempDownloadPath;
80

81
  private final Path cwd;
82

83
  private final Path downloadPath;
84

85
  private final Path toolRepository;
86

87
  private final Path userHome;
88

89
  private final Path userHomeIde;
90

91
  private final SystemPath path;
92

93
  private final SystemInfo systemInfo;
94

95
  private final EnvironmentVariables variables;
96

97
  private final FileAccess fileAccess;
98

99
  private final CommandletManager commandletManager;
100

101
  private final ToolRepository defaultToolRepository;
102

103
  private final CustomToolRepository customToolRepository;
104

105
  private final DirectoryMerger workspaceMerger;
106

107
  private final Function<IdeLogLevel, IdeSubLogger> loggerFactory;
108

109
  private boolean offlineMode;
110

111
  private boolean forceMode;
112

113
  private boolean batchMode;
114

115
  private boolean quietMode;
116

117
  private Locale locale;
118

119
  private UrlMetadata urlMetadata;
120

121
  private Path defaultExecutionDirectory;
122

123
  /**
124
   * The constructor.
125
   *
126
   * @param minLogLevel the minimum {@link IdeLogLevel} to enable. Should be {@link IdeLogLevel#INFO} by default.
127
   * @param factory the {@link Function} to create {@link IdeSubLogger} per {@link IdeLogLevel}.
128
   * @param userDir the optional {@link Path} to current working directory.
129
   * @param toolRepository @param toolRepository the {@link ToolRepository} of the context. If it is set to {@code null}
130
   * {@link DefaultToolRepository} will be used.
131
   */
132
  public AbstractIdeContext(IdeLogLevel minLogLevel, Function<IdeLogLevel, IdeSubLogger> factory, Path userDir,
133
      ToolRepository toolRepository) {
134

135
    super();
2✔
136
    this.loggerFactory = factory;
3✔
137
    this.loggers = new HashMap<>();
5✔
138
    setLogLevel(minLogLevel);
3✔
139
    this.systemInfo = SystemInfoImpl.INSTANCE;
3✔
140
    this.commandletManager = new CommandletManagerImpl(this);
6✔
141
    this.fileAccess = new FileAccessImpl(this);
6✔
142
    String workspace = WORKSPACE_MAIN;
2✔
143
    if (userDir == null) {
2✔
144
      this.cwd = Path.of(System.getProperty("user.dir"));
8✔
145
    } else {
146
      this.cwd = userDir.toAbsolutePath();
4✔
147
    }
148
    // detect IDE_HOME and WORKSPACE
149
    Path currentDir = this.cwd;
3✔
150
    String name1 = "";
2✔
151
    String name2 = "";
2✔
152
    while (currentDir != null) {
2✔
153
      trace("Looking for IDE_HOME in {}", currentDir);
9✔
154
      if (isIdeHome(currentDir)) {
4✔
155
        if (FOLDER_WORKSPACES.equals(name1)) {
4✔
156
          workspace = name2;
3✔
157
        }
158
        break;
159
      }
160
      name2 = name1;
2✔
161
      int nameCount = currentDir.getNameCount();
3✔
162
      if (nameCount >= 1) {
3✔
163
        name1 = currentDir.getName(nameCount - 1).toString();
7✔
164
      }
165
      currentDir = getParentPath(currentDir);
4✔
166
    }
1✔
167
    // detection completed, initializing variables
168
    this.ideHome = currentDir;
3✔
169
    this.workspaceName = workspace;
3✔
170
    if (this.ideHome == null) {
3✔
171
      info(getMessageIdeHomeNotFound());
4✔
172
      this.workspacePath = null;
3✔
173
      this.ideRoot = null;
3✔
174
      this.confPath = null;
3✔
175
      this.settingsPath = null;
3✔
176
      this.softwarePath = null;
3✔
177
      this.pluginsPath = null;
4✔
178
    } else {
179
      debug(getMessageIdeHomeFound());
4✔
180
      this.workspacePath = this.ideHome.resolve(FOLDER_WORKSPACES).resolve(this.workspaceName);
9✔
181
      Path ideRootPath = this.ideHome.getParent();
4✔
182
      String root = null;
2✔
183
      if (!isTest()) {
3!
184
        root = System.getenv("IDE_ROOT");
×
185
      }
186
      if (root != null) {
2!
187
        Path rootPath = Path.of(root);
×
188
        if (Files.isDirectory(rootPath)) {
×
189
          if (!ideRootPath.equals(rootPath)) {
×
190
            warning(
×
191
                "Variable IDE_ROOT is set to '{}' but for your project '{}' the path '{}' would have been expected.",
192
                root, this.ideHome.getFileName(), ideRootPath);
×
193
          }
194
          ideRootPath = rootPath;
×
195
        } else {
196
          warning("Variable IDE_ROOT is not set to a valid directory '{}'." + root);
×
197
          ideRootPath = null;
×
198
        }
199
      }
200
      this.ideRoot = ideRootPath;
3✔
201
      this.confPath = this.ideHome.resolve(FOLDER_CONF);
6✔
202
      this.settingsPath = this.ideHome.resolve(FOLDER_SETTINGS);
6✔
203
      this.softwarePath = this.ideHome.resolve(FOLDER_SOFTWARE);
6✔
204
      this.pluginsPath = this.ideHome.resolve(FOLDER_PLUGINS);
6✔
205
    }
206
    if (this.ideRoot == null) {
3✔
207
      this.toolRepository = null;
3✔
208
      this.urlsPath = null;
3✔
209
      this.tempPath = null;
3✔
210
      this.tempDownloadPath = null;
3✔
211
      this.softwareRepositoryPath = null;
4✔
212
    } else {
213
      Path ideBase = this.ideRoot.resolve(FOLDER_IDE);
5✔
214
      this.toolRepository = ideBase.resolve("software");
5✔
215
      this.urlsPath = ideBase.resolve("urls");
5✔
216
      this.tempPath = ideBase.resolve("tmp");
5✔
217
      this.tempDownloadPath = this.tempPath.resolve(FOLDER_DOWNLOADS);
6✔
218
      this.softwareRepositoryPath = ideBase.resolve(FOLDER_SOFTWARE);
5✔
219
      if (Files.isDirectory(this.tempPath)) {
7✔
220
        // TODO delete all files older than 1 day here...
221
      } else {
222
        this.fileAccess.mkdirs(this.tempDownloadPath);
5✔
223
      }
224
    }
225
    if (isTest()) {
3✔
226
      // only for testing...
227
      if (this.ideHome == null) {
3✔
228
        this.userHome = Path.of("/non-existing-user-home-for-testing");
7✔
229
      } else {
230
        this.userHome = this.ideHome.resolve("home");
7✔
231
      }
232
    } else {
233
      this.userHome = Path.of(System.getProperty("user.home"));
7✔
234
    }
235
    this.userHomeIde = this.userHome.resolve(".ide");
6✔
236
    this.downloadPath = this.userHome.resolve("Downloads/ide");
6✔
237
    this.variables = createVariables();
4✔
238
    this.path = computeSystemPath();
4✔
239

240
    if (toolRepository == null) {
2✔
241
      this.defaultToolRepository = new DefaultToolRepository(this);
7✔
242
    } else {
243
      this.defaultToolRepository = toolRepository;
3✔
244
    }
245

246
    this.customToolRepository = CustomToolRepositoryImpl.of(this);
4✔
247
    this.workspaceMerger = new DirectoryMerger(this);
6✔
248
  }
1✔
249

250
  private String getMessageIdeHomeFound() {
251

252
    return "IDE environment variables have been set for " + this.ideHome + " in workspace " + this.workspaceName;
6✔
253
  }
254

255
  private String getMessageIdeHomeNotFound() {
256

257
    return "You are not inside an IDE installation: " + this.cwd;
4✔
258
  }
259

260
  /**
261
   * @return the status message about the {@link #getIdeHome() IDE_HOME} detection and environment variable
262
   * initialization.
263
   */
264
  public String getMessageIdeHome() {
265

266
    if (this.ideHome == null) {
×
267
      return getMessageIdeHomeNotFound();
×
268
    }
269
    return getMessageIdeHomeFound();
×
270
  }
271

272
  /**
273
   * @return {@code true} if this is a test context for JUnits, {@code false} otherwise.
274
   */
275
  public boolean isTest() {
276

277
    return isMock();
3✔
278
  }
279

280
  /**
281
   * @return {@code true} if this is a mock context for JUnits, {@code false} otherwise.
282
   */
283
  public boolean isMock() {
284

285
    return false;
2✔
286
  }
287

288
  private SystemPath computeSystemPath() {
289

290
    return new SystemPath(this);
5✔
291
  }
292

293
  private boolean isIdeHome(Path dir) {
294

295
    if (!Files.isDirectory(dir.resolve("workspaces"))) {
7✔
296
      return false;
2✔
297
    } else if (!Files.isDirectory(dir.resolve("settings"))) {
7!
298
      return false;
×
299
    }
300
    return true;
2✔
301
  }
302

303
  private Path getParentPath(Path dir) {
304

305
    try {
306
      Path linkDir = dir.toRealPath();
5✔
307
      if (!dir.equals(linkDir)) {
4!
308
        return linkDir;
×
309
      } else {
310
        return dir.getParent();
3✔
311
      }
312
    } catch (IOException e) {
×
313
      throw new IllegalStateException(e);
×
314
    }
315

316
  }
317

318
  private EnvironmentVariables createVariables() {
319

320
    AbstractEnvironmentVariables system = EnvironmentVariables.ofSystem(this);
3✔
321
    AbstractEnvironmentVariables user = extendVariables(system, this.userHomeIde, EnvironmentVariablesType.USER);
7✔
322
    AbstractEnvironmentVariables settings = extendVariables(user, this.settingsPath, EnvironmentVariablesType.SETTINGS);
7✔
323
    // TODO should we keep this workspace properties? Was this feature ever used?
324
    AbstractEnvironmentVariables workspace = extendVariables(settings, this.workspacePath,
7✔
325
        EnvironmentVariablesType.WORKSPACE);
326
    AbstractEnvironmentVariables conf = extendVariables(workspace, this.confPath, EnvironmentVariablesType.CONF);
7✔
327
    return conf.resolved();
3✔
328
  }
329

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

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

351
  @Override
352
  public SystemInfo getSystemInfo() {
353

354
    return this.systemInfo;
3✔
355
  }
356

357
  @Override
358
  public FileAccess getFileAccess() {
359

360
    return this.fileAccess;
3✔
361
  }
362

363
  @Override
364
  public CommandletManager getCommandletManager() {
365

366
    return this.commandletManager;
3✔
367
  }
368

369
  @Override
370
  public ToolRepository getDefaultToolRepository() {
371

372
    return this.defaultToolRepository;
3✔
373
  }
374

375
  @Override
376
  public CustomToolRepository getCustomToolRepository() {
377

378
    return this.customToolRepository;
×
379
  }
380

381
  @Override
382
  public Path getIdeHome() {
383

384
    return this.ideHome;
3✔
385
  }
386

387
  @Override
388
  public Path getIdeRoot() {
389

390
    return this.ideRoot;
3✔
391
  }
392

393
  @Override
394
  public Path getCwd() {
395

396
    return this.cwd;
×
397
  }
398

399
  @Override
400
  public Path getTempPath() {
401

402
    return this.tempPath;
3✔
403
  }
404

405
  @Override
406
  public Path getTempDownloadPath() {
407

408
    return this.tempDownloadPath;
3✔
409
  }
410

411
  @Override
412
  public Path getUserHome() {
413

414
    return this.userHome;
3✔
415
  }
416

417
  @Override
418
  public Path getUserHomeIde() {
419

420
    return this.userHomeIde;
×
421
  }
422

423
  @Override
424
  public Path getSettingsPath() {
425

426
    return this.settingsPath;
3✔
427
  }
428

429
  @Override
430
  public Path getConfPath() {
431

432
    return this.confPath;
×
433
  }
434

435
  @Override
436
  public Path getSoftwarePath() {
437

438
    return this.softwarePath;
3✔
439
  }
440

441
  @Override
442
  public Path getSoftwareRepositoryPath() {
443

444
    return this.softwareRepositoryPath;
3✔
445
  }
446

447
  @Override
448
  public Path getPluginsPath() {
449

450
    return this.pluginsPath;
×
451
  }
452

453
  @Override
454
  public String getWorkspaceName() {
455

456
    return this.workspaceName;
3✔
457
  }
458

459
  @Override
460
  public Path getWorkspacePath() {
461

462
    return this.workspacePath;
3✔
463
  }
464

465
  @Override
466
  public Path getDownloadPath() {
467

468
    return this.downloadPath;
3✔
469
  }
470

471
  @Override
472
  public Path getUrlsPath() {
473

474
    return this.urlsPath;
3✔
475
  }
476

477
  @Override
478
  public Path getToolRepositoryPath() {
479

480
    return this.toolRepository;
3✔
481
  }
482

483
  @Override
484
  public SystemPath getPath() {
485

486
    return this.path;
3✔
487
  }
488

489
  @Override
490
  public EnvironmentVariables getVariables() {
491

492
    return this.variables;
3✔
493
  }
494

495
  @Override
496
  public UrlMetadata getUrls() {
497

498
    if (this.urlMetadata == null) {
3✔
499
      if (!isTest()) {
3!
500
        this.getGitContext().pullOrFetchAndResetIfNeeded(IDE_URLS_GIT, this.urlsPath, "origin", "master");
×
501
      }
502
      this.urlMetadata = new UrlMetadata(this);
6✔
503
    }
504
    return this.urlMetadata;
3✔
505
  }
506

507
  @Override
508
  public boolean isQuietMode() {
509

510
    return this.quietMode;
3✔
511
  }
512

513
  /**
514
   * @param quietMode new value of {@link #isQuietMode()}.
515
   */
516
  public void setQuietMode(boolean quietMode) {
517

518
    this.quietMode = quietMode;
3✔
519
  }
1✔
520

521
  @Override
522
  public boolean isBatchMode() {
523

524
    return this.batchMode;
3✔
525
  }
526

527
  /**
528
   * @param batchMode new value of {@link #isBatchMode()}.
529
   */
530
  public void setBatchMode(boolean batchMode) {
531

532
    this.batchMode = batchMode;
3✔
533
  }
1✔
534

535
  @Override
536
  public boolean isForceMode() {
537

538
    return this.forceMode;
3✔
539
  }
540

541
  /**
542
   * @param forceMode new value of {@link #isForceMode()}.
543
   */
544
  public void setForceMode(boolean forceMode) {
545

546
    this.forceMode = forceMode;
3✔
547
  }
1✔
548

549
  @Override
550
  public boolean isOfflineMode() {
551

552
    return this.offlineMode;
3✔
553
  }
554

555
  /**
556
   * @param offlineMode new value of {@link #isOfflineMode()}.
557
   */
558
  public void setOfflineMode(boolean offlineMode) {
559

560
    this.offlineMode = offlineMode;
3✔
561
  }
1✔
562

563
  @Override
564
  public boolean isOnline() {
565

566
    boolean online = false;
×
567
    try {
568
      int timeout = 1000;
×
569
      online = InetAddress.getByName("github.com").isReachable(timeout);
×
570
    } catch (Exception ignored) {
×
571

572
    }
×
573
    return online;
×
574
  }
575

576
  @Override
577
  public Locale getLocale() {
578

579
    if (this.locale == null) {
3!
580
      return Locale.getDefault();
2✔
581
    }
582
    return this.locale;
×
583
  }
584

585
  /**
586
   * @param locale new value of {@link #getLocale()}.
587
   */
588
  public void setLocale(Locale locale) {
589

590
    this.locale = locale;
3✔
591
  }
1✔
592

593
  @Override
594
  public DirectoryMerger getWorkspaceMerger() {
595

596
    return this.workspaceMerger;
3✔
597
  }
598

599
  /**
600
   * @return the {@link #defaultExecutionDirectory} the directory in which a command process is executed.
601
   */
602
  public Path getDefaultExecutionDirectory() {
603

604
    return this.defaultExecutionDirectory;
×
605
  }
606

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

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

617
  @Override
618
  public GitContext getGitContext() {
619

620
    return new GitContextImpl(this);
×
621
  }
622

623
  @Override
624
  public ProcessContext newProcess() {
625

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

633
  /**
634
   * @return a new instance of {@link ProcessContext}.
635
   * @see #newProcess()
636
   */
637
  protected ProcessContext createProcessContext() {
638

639
    return new ProcessContextImpl(this);
×
640
  }
641

642
  @Override
643
  public IdeSubLogger level(IdeLogLevel level) {
644

645
    IdeSubLogger logger = this.loggers.get(level);
6✔
646
    Objects.requireNonNull(logger);
3✔
647
    return logger;
2✔
648
  }
649

650
  @SuppressWarnings("unchecked")
651
  @Override
652
  public <O> O question(String question, O... options) {
653

654
    assert (options.length >= 2);
×
655
    interaction(question);
×
656
    Map<String, O> mapping = new HashMap<>(options.length);
×
657
    int i = 0;
×
658
    for (O option : options) {
×
659
      i++;
×
660
      String key = "" + option;
×
661
      addMapping(mapping, key, option);
×
662
      String numericKey = Integer.toString(i);
×
663
      if (numericKey.equals(key)) {
×
664
        trace("Options should not be numeric: " + key);
×
665
      } else {
666
        addMapping(mapping, numericKey, option);
×
667
      }
668
      interaction("Option " + numericKey + ": " + key);
×
669
    }
670
    O option = null;
×
671
    if (isBatchMode()) {
×
672
      if (isForceMode()) {
×
673
        option = options[0];
×
674
        interaction("" + option);
×
675
      }
676
    } else {
677
      while (option == null) {
×
678
        String answer = readLine();
×
679
        option = mapping.get(answer);
×
680
        if (option == null) {
×
681
          warning("Invalid answer: '" + answer + "' - please try again.");
×
682
        }
683
      }
×
684
    }
685
    return option;
×
686
  }
687

688
  /**
689
   * @return the input from the end-user (e.g. read from the console).
690
   */
691
  protected abstract String readLine();
692

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

695
    O duplicate = mapping.put(key, option);
×
696
    if (duplicate != null) {
×
697
      throw new IllegalArgumentException("Duplicated option " + key);
×
698
    }
699
  }
×
700

701
  /**
702
   * Sets the log level.
703
   *
704
   * @param logLevel {@link IdeLogLevel}
705
   */
706
  public void setLogLevel(IdeLogLevel logLevel) {
707

708
    for (IdeLogLevel level : IdeLogLevel.values()) {
16✔
709
      IdeSubLogger logger;
710
      if (level.ordinal() < logLevel.ordinal()) {
5✔
711
        logger = new IdeSubLoggerNone(level);
6✔
712
      } else {
713
        logger = this.loggerFactory.apply(level);
6✔
714
      }
715
      this.loggers.put(level, logger);
6✔
716
    }
717
  }
1✔
718

719
  /**
720
   * Finds the matching {@link Commandlet} to run, applies {@link CliArguments} to its
721
   * {@link Commandlet#getProperties() properties} and will execute it.
722
   *
723
   * @param arguments the {@link CliArgument}.
724
   * @return the return code of the execution.
725
   */
726
  public int run(CliArguments arguments) {
727

728
    CliArgument current = arguments.current();
×
729
    if (!current.isEnd()) {
×
730
      String keyword = current.get();
×
731
      Commandlet firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword);
×
732
      boolean matches;
733
      if (firstCandidate != null) {
×
734
        matches = applyAndRun(arguments.copy(), firstCandidate);
×
735
        if (matches) {
×
736
          return ProcessResult.SUCCESS;
×
737
        }
738
      }
739
      for (Commandlet cmd : this.commandletManager.getCommandlets()) {
×
740
        if (cmd != firstCandidate) {
×
741
          matches = applyAndRun(arguments.copy(), cmd);
×
742
          if (matches) {
×
743
            return ProcessResult.SUCCESS;
×
744
          }
745
        }
746
      }
×
747
      error("Invalid arguments: {}", current.getArgs());
×
748
    }
749
    this.commandletManager.getCommandlet(HelpCommandlet.class).run();
×
750
    return 1;
×
751
  }
752

753
  /**
754
   * @param cmd the potential {@link Commandlet} to
755
   * {@link #apply(CliArguments, Commandlet, CompletionCandidateCollector) apply} and {@link Commandlet#run() run}.
756
   * @return {@code true} if the given {@link Commandlet} matched and did {@link Commandlet#run() run} successfully,
757
   * {@code false} otherwise (the {@link Commandlet} did not match and we have to try a different candidate).
758
   */
759
  private boolean applyAndRun(CliArguments arguments, Commandlet cmd) {
760

761
    boolean matches = apply(arguments, cmd, null);
×
762
    if (matches) {
×
763
      matches = cmd.validate();
×
764
    }
765
    if (matches) {
×
766
      debug("Running commandlet {}", cmd);
×
767
      if (cmd.isIdeHomeRequired() && (this.ideHome == null)) {
×
768
        throw new CliException(getMessageIdeHomeNotFound());
×
769
      }
770
      cmd.run();
×
771
    } else {
772
      trace("Commandlet did not match");
×
773
    }
774
    return matches;
×
775
  }
776

777
  /**
778
   * @param arguments the {@link CliArguments#ofCompletion(String...) completion arguments}.
779
   * @param includeContextOptions to include the options of {@link ContextCommandlet}.
780
   * @return the {@link List} of {@link CompletionCandidate}s to suggest.
781
   */
782
  public List<CompletionCandidate> complete(CliArguments arguments, boolean includeContextOptions) {
783

784
    CompletionCandidateCollector collector = new CompletionCandidateCollectorDefault(this);
5✔
785
    if (arguments.current().isStart()) {
4✔
786
      arguments.next();
3✔
787
    }
788
    if (includeContextOptions) {
2✔
789
      ContextCommandlet cc = new ContextCommandlet();
4✔
790
      for (Property<?> property : cc.getProperties()) {
11✔
791
        assert (property.isOption());
4!
792
        property.apply(arguments, this, cc, collector);
7✔
793
      }
1✔
794
    }
795
    CliArgument current = arguments.current();
3✔
796
    if (!current.isEnd()) {
3!
797
      String keyword = current.get();
3✔
798
      Commandlet firstCandidate = this.commandletManager.getCommandletByFirstKeyword(keyword);
5✔
799
      boolean matches = false;
2✔
800
      if (firstCandidate != null) {
2✔
801
        matches = apply(arguments.copy(), firstCandidate, collector);
8✔
802
      } else if (current.isCombinedShortOption()) {
3✔
803
        collector.add(keyword, null, null, null);
6✔
804
      }
805
      if (!matches) {
2!
806
        for (Commandlet cmd : this.commandletManager.getCommandlets()) {
12✔
807
          if (cmd != firstCandidate) {
3✔
808
            apply(arguments.copy(), cmd, collector);
7✔
809
          }
810
        }
1✔
811
      }
812
    }
813
    return collector.getSortedCandidates();
3✔
814
  }
815

816
  /**
817
   * @param arguments the {@link CliArguments} to apply. Will be {@link CliArguments#next() consumed} as they are
818
   * matched. Consider passing a {@link CliArguments#copy() copy} as needed.
819
   * @param cmd the potential {@link Commandlet} to match.
820
   * @param collector the {@link CompletionCandidateCollector}.
821
   * @return {@code true} if the given {@link Commandlet} matches to the given {@link CliArgument}(s) and those have
822
   * been applied (set in the {@link Commandlet} and {@link Commandlet#validate() validated}), {@code false} otherwise
823
   * (the {@link Commandlet} did not match and we have to try a different candidate).
824
   */
825
  public boolean apply(CliArguments arguments, Commandlet cmd, CompletionCandidateCollector collector) {
826

827
    trace("Trying to match arguments to commandlet {}", cmd.getName());
10✔
828
    CliArgument currentArgument = arguments.current();
3✔
829
    Iterator<Property<?>> propertyIterator;
830
    if (currentArgument.isCompletion()) {
3✔
831
      propertyIterator = cmd.getProperties().iterator();
5✔
832
    } else {
833
      propertyIterator = cmd.getValues().iterator();
4✔
834
    }
835
    while (!currentArgument.isEnd()) {
3!
836
      trace("Trying to match argument '{}'", currentArgument);
9✔
837
      Property<?> property = null;
2✔
838
      if (!arguments.isEndOptions()) {
3!
839
        property = cmd.getOption(currentArgument.getKey());
5✔
840
      }
841
      if (property == null) {
2✔
842
        if (!propertyIterator.hasNext()) {
3✔
843
          trace("No option or next value found");
3✔
844
          return false;
2✔
845
        }
846
        property = propertyIterator.next();
4✔
847
      }
848
      trace("Next property candidate to match argument is {}", property);
9✔
849
      boolean matches = property.apply(arguments, this, cmd, collector);
7✔
850
      if (!matches || currentArgument.isCompletion()) {
5✔
851
        return false;
2✔
852
      }
853
      currentArgument = arguments.current();
3✔
854
    }
1✔
855
    return true;
×
856
  }
857

858
}
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

© 2026 Coveralls, Inc