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

devonfw / IDEasy / 26101908636

19 May 2026 01:55PM UTC coverage: 70.982% (+0.003%) from 70.979%
26101908636

Pull #1859

github

web-flow
Merge cf4a7f717 into b4eeee25f
Pull Request #1859: #1392: Smart completions

4472 of 6964 branches covered (64.22%)

Branch coverage included in aggregate %.

11521 of 15567 relevant lines covered (74.01%)

3.14 hits per line

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

85.71
cli/src/main/java/com/devonfw/tools/ide/commandlet/Commandlet.java
1
package com.devonfw.tools.ide.commandlet;
2

3
import java.util.ArrayList;
4
import java.util.Collections;
5
import java.util.HashMap;
6
import java.util.List;
7
import java.util.Map;
8
import java.util.Objects;
9

10
import com.devonfw.tools.ide.context.AbstractIdeContext;
11
import com.devonfw.tools.ide.context.IdeContext;
12
import com.devonfw.tools.ide.nls.NlsBundle;
13
import com.devonfw.tools.ide.property.KeywordProperty;
14
import com.devonfw.tools.ide.property.Property;
15
import com.devonfw.tools.ide.tool.ToolCommandlet;
16
import com.devonfw.tools.ide.validation.ValidationResult;
17
import com.devonfw.tools.ide.validation.ValidationState;
18
import com.devonfw.tools.ide.version.VersionIdentifier;
19

20
/**
21
 * A {@link Commandlet} is a sub-command of the IDE CLI.
22
 */
23
public abstract class Commandlet {
1✔
24

25
  /** The {@link IdeContext} instance. */
26
  protected final IdeContext context;
27

28
  private final List<Property<?>> propertiesList;
29

30
  private final List<Property<?>> properties;
31

32
  private final List<Property<?>> valuesList;
33

34
  private final List<Property<?>> values;
35

36
  private final Map<String, Property<?>> optionMap;
37

38
  private Property<?> multiValued;
39

40
  private KeywordProperty firstKeyword;
41

42
  /**
43
   * The constructor.
44
   *
45
   * @param context the {@link IdeContext}.
46
   */
47
  public Commandlet(IdeContext context) {
48

49
    super();
2✔
50
    this.context = context;
3✔
51
    this.propertiesList = new ArrayList<>();
5✔
52
    this.properties = Collections.unmodifiableList(this.propertiesList);
5✔
53
    this.valuesList = new ArrayList<>();
5✔
54
    this.values = Collections.unmodifiableList(this.valuesList);
5✔
55
    this.optionMap = new HashMap<>();
5✔
56
  }
1✔
57

58
  /**
59
   * @return the {@link List} with all {@link Property properties} of this {@link Commandlet}.
60
   */
61
  public List<Property<?>> getProperties() {
62

63
    return this.properties;
3✔
64
  }
65

66
  /**
67
   * @return the {@link List} of {@link Property properties} that are {@link Property#isValue() values}.
68
   */
69
  public List<Property<?>> getValues() {
70

71
    return this.values;
3✔
72
  }
73

74
  /**
75
   * Clear the set values on all properties of the {@link Commandlet#propertiesList}
76
   */
77
  public void reset() {
78

79
    for (Property<?> property : this.propertiesList) {
11✔
80
      property.clearValue();
2✔
81
    }
1✔
82
  }
1✔
83

84
  /**
85
   * @param nameOrAlias the potential {@link Property#getName() name} or {@link Property#getAlias() alias} of the requested {@link Property}.
86
   * @return the requested {@link Property property} or {@code null} if not found.
87
   */
88
  public Property<?> getOption(String nameOrAlias) {
89

90
    return this.optionMap.get(nameOrAlias);
6✔
91
  }
92

93
  /**
94
   * @param keyword the {@link KeywordProperty keyword} to {@link #add(Property) add}.
95
   */
96
  protected void addKeyword(String keyword) {
97

98
    addKeyword(keyword, null);
4✔
99
  }
1✔
100

101
  /**
102
   * Create a new keyword property and set it as the first keyword property.
103
   *
104
   * @param keyword the string to create the property from
105
   * @param alias the alias for the given keyword
106
   */
107
  protected void setFirstKeyword(String keyword, String alias) {
108
    KeywordProperty property = new KeywordProperty(keyword, true, alias);
7✔
109

110
    this.firstKeyword = property;
3✔
111
    this.add(property);
4✔
112
  }
1✔
113

114
  /**
115
   * Create a new keyword property without an alias and set it as the first keyword property.
116
   *
117
   * @param keyword the string to create the property from
118
   */
119
  protected void setFirstKeyword(String keyword) {
120
    this.setFirstKeyword(keyword, null);
4✔
121
  }
1✔
122

123
  /**
124
   * @param keyword the {@link KeywordProperty keyword} to {@link #add(Property) add}.
125
   * @param alias the optional {@link KeywordProperty#getAlias() alias}.
126
   */
127
  protected void addKeyword(String keyword, String alias) {
128

129
    KeywordProperty property = new KeywordProperty(keyword, true, alias);
7✔
130
    if (this.firstKeyword == null) {
3✔
131
      if (!this.properties.isEmpty()) {
4!
132
        throw new IllegalStateException(property + " must be first property in " + getClass().getSimpleName());
×
133
      }
134
      this.firstKeyword = property;
3✔
135
    }
136
    add(property);
4✔
137
  }
1✔
138

139
  /**
140
   * @param <P> type of the {@link Property}.
141
   * @param property the {@link Property} to register.
142
   * @return the given {@link Property}.
143
   */
144
  protected <P extends Property<?>> P add(P property) {
145

146
    if (this.multiValued != null) {
3!
147
      throw new IllegalStateException("The multi-valued property " + this.multiValued + " can not be followed by " + property);
×
148
    }
149
    this.propertiesList.add(property);
5✔
150
    if (property.isOption()) {
3✔
151
      add(property.getName(), property, false);
6✔
152
      add(property.getAlias(), property, true);
6✔
153
    }
154
    if (property.isValue()) {
3✔
155
      this.valuesList.add(property);
5✔
156
    }
157
    if (property.isMultiValued()) {
3✔
158
      this.multiValued = property;
3✔
159
    }
160
    return property;
2✔
161
  }
162

163
  private void add(String name, Property<?> property, boolean alias) {
164

165
    if (alias && (name == null)) {
4✔
166
      return;
1✔
167
    }
168
    Objects.requireNonNull(name);
3✔
169
    assert (name.equals(name.trim()));
6!
170
    if (name.isEmpty() && !alias) {
3!
171
      return;
×
172
    }
173
    Property<?> duplicate = this.optionMap.put(name, property);
7✔
174
    if (duplicate != null) {
2!
175
      throw new IllegalStateException("Duplicate name or alias " + name + " for " + property + " and " + duplicate);
×
176
    }
177
  }
1✔
178

179
  /**
180
   * @return the name of this {@link Commandlet} (e.g. "help").
181
   */
182
  public abstract String getName();
183

184
  /**
185
   * @return the first keyword of this {@link Commandlet}. Typically the same as {@link #getName() name} but may also differ (e.g. "set" vs. "set-version").
186
   */
187
  public KeywordProperty getFirstKeyword() {
188

189
    return this.firstKeyword;
3✔
190
  }
191

192
  /**
193
   * @param <C> type of the {@link Commandlet}.
194
   * @param commandletType the {@link Class} reflecting the requested {@link Commandlet}.
195
   * @return the requested {@link Commandlet}.
196
   * @see CommandletManager#getCommandlet(Class)
197
   */
198
  protected <C extends Commandlet> C getCommandlet(Class<C> commandletType) {
199

200
    return this.context.getCommandletManager().getCommandlet(commandletType);
6✔
201
  }
202

203
  /**
204
   * @return {@code true} if {@link IdeContext#getIdeHome() IDE_HOME} is required for this commandlet, {@code false} otherwise.
205
   */
206
  public boolean isIdeHomeRequired() {
207

208
    return isIdeRootRequired();
3✔
209
  }
210

211
  /**
212
   * @return {@code true} if {@link IdeContext#getIdeRoot() IDE_ROOT} is required for this commandlet, {@code false} otherwise.
213
   */
214
  public boolean isIdeRootRequired() {
215

216
    return !this.context.isTest();
6!
217
  }
218

219
  /**
220
   * @return {@code true} to suppress the {@link com.devonfw.tools.ide.step.StepImpl#logSummary(boolean) step summary success message}.
221
   */
222
  public boolean isSuppressStepSuccess() {
223

224
    return false;
2✔
225
  }
226

227
  /**
228
   * @return {@code true} if the output of this commandlet is (potentially) processed automatically from outside, {@code false} otherwise. For example
229
   *     {@link CompleteCommandlet} logs the suggestions for auto-completion to a bash script. Also the {@link EnvironmentCommandlet} logs the environment
230
   *     variables for the {@code ide} wrapper script. In such scenarios these logs shall not be spammed with warnings like "IDE_ROOT is not set" that would
231
   *     break the processing of the output.
232
   */
233
  public boolean isProcessableOutput() {
234

235
    return false;
2✔
236
  }
237

238
  /**
239
   * @return {@code true} to write a logfile (unless disabled via {@link com.devonfw.tools.ide.variable.IdeVariables#IDE_WRITE_LOGFILE}), {@code false}
240
   *     otherwise.
241
   */
242
  public boolean isWriteLogFile() {
243
    return !isProcessableOutput();
×
244
  }
245

246
  /**
247
   * Runs this {@link Commandlet}.
248
   */
249
  public final void run() {
250

251
    if (this.context != null) { // for ContextCommandlet we do not have a context yet
3✔
252
      ((AbstractIdeContext) this.context).configureJavaUtilLogging(this);
5✔
253
    }
254
    doRun();
2✔
255
  }
1✔
256

257
  protected abstract void doRun();
258

259
  /**
260
   * @return {@code true} if this {@link Commandlet} is the valid candidate to be {@link #run()}, {@code false} otherwise.
261
   * @see Property#validate()
262
   */
263
  public ValidationResult validate() {
264
    ValidationState state = new ValidationState(null);
5✔
265
    // avoid validation exception if not a candidate to be run.
266
    for (Property<?> property : this.propertiesList) {
11✔
267
      state.add(property.validate());
4✔
268
    }
1✔
269
    return state;
2✔
270
  }
271

272
  /**
273
   * Provide additional usage help of this {@link Commandlet} to the user.
274
   *
275
   * @param bundle the {@link NlsBundle} to get I18N messages from.
276
   */
277
  public void printHelp(NlsBundle bundle) {
278

279
  }
1✔
280

281
  @Override
282
  public String toString() {
283

284
    return getClass().getSimpleName() + "[" + getName() + "]";
7✔
285
  }
286

287
  /**
288
   * @return the {@link ToolCommandlet} set in a {@link Property} of this commandlet used for auto-completion of a {@link VersionIdentifier} or
289
   *     {@link com.devonfw.tools.ide.tool.plugin.ToolPluginDescriptor}, otherwise {@code null} if not exists or not configured.
290
   */
291
  public ToolCommandlet getToolForCompletion() {
292
    return null;
2✔
293
  }
294
}
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