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

devonfw / IDEasy / 12084849850

29 Nov 2024 12:34PM UTC coverage: 67.031% (-0.4%) from 67.412%
12084849850

push

github

web-flow
#758: improve status commandlet (#816)

2500 of 4078 branches covered (61.3%)

Branch coverage included in aggregate %.

6515 of 9371 relevant lines covered (69.52%)

3.07 hits per line

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

77.72
cli/src/main/java/com/devonfw/tools/ide/environment/AbstractEnvironmentVariables.java
1
package com.devonfw.tools.ide.environment;
2

3
import java.nio.file.Path;
4
import java.util.ArrayList;
5
import java.util.HashMap;
6
import java.util.List;
7
import java.util.Map;
8
import java.util.regex.Matcher;
9

10
import com.devonfw.tools.ide.context.IdeContext;
11
import com.devonfw.tools.ide.log.IdeLogLevel;
12
import com.devonfw.tools.ide.variable.IdeVariables;
13
import com.devonfw.tools.ide.variable.VariableDefinition;
14
import com.devonfw.tools.ide.variable.VariableSyntax;
15
import com.devonfw.tools.ide.version.VersionIdentifier;
16

17
/**
18
 * Abstract base implementation of {@link EnvironmentVariables}.
19
 */
20
public abstract class AbstractEnvironmentVariables implements EnvironmentVariables {
21

22
  /**
23
   * When we replace variable expressions with their value the resulting {@link String} can change in size (shrink or grow). By adding a bit of extra capacity
24
   * we reduce the chance that the capacity is too small and a new buffer array has to be allocated and array-copy has to be performed.
25
   */
26
  private static final int EXTRA_CAPACITY = 8;
27

28
  private static final String SELF_REFERENCING_NOT_FOUND = "";
29

30
  private static final int MAX_RECURSION = 9;
31

32
  /**
33
   * @see #getParent()
34
   */
35
  protected final AbstractEnvironmentVariables parent;
36

37
  /**
38
   * The {@link IdeContext} instance.
39
   */
40
  protected final IdeContext context;
41

42
  private VariableSource source;
43

44
  /**
45
   * The constructor.
46
   *
47
   * @param parent the parent {@link EnvironmentVariables} to inherit from.
48
   * @param context the {@link IdeContext}.
49
   */
50
  public AbstractEnvironmentVariables(AbstractEnvironmentVariables parent, IdeContext context) {
51

52
    super();
2✔
53
    this.parent = parent;
3✔
54
    if (context == null) {
2!
55
      if (parent == null) {
×
56
        throw new IllegalArgumentException("parent and logger must not both be null!");
×
57
      }
58
      this.context = parent.context;
×
59
    } else {
60
      this.context = context;
3✔
61
    }
62
  }
1✔
63

64
  @Override
65
  public EnvironmentVariables getParent() {
66

67
    return this.parent;
3✔
68
  }
69

70
  @Override
71
  public Path getPropertiesFilePath() {
72

73
    return null;
2✔
74
  }
75

76
  @Override
77
  public Path getLegacyPropertiesFilePath() {
78

79
    return null;
×
80
  }
81

82
  @Override
83
  public VariableSource getSource() {
84

85
    if (this.source == null) {
3✔
86
      this.source = new VariableSource(getType(), getPropertiesFilePath());
9✔
87
    }
88
    return this.source;
3✔
89
  }
90

91
  /**
92
   * @param name the name of the variable to check.
93
   * @return {@code true} if the variable shall be exported, {@code false} otherwise.
94
   */
95
  protected boolean isExported(String name) {
96

97
    if (this.parent != null) {
3✔
98
      if (this.parent.isExported(name)) {
5!
99
        return true;
×
100
      }
101
    }
102
    return false;
2✔
103
  }
104

105
  @Override
106
  public final List<VariableLine> collectVariables() {
107

108
    return collectVariables(false);
4✔
109
  }
110

111
  @Override
112
  public final List<VariableLine> collectExportedVariables() {
113

114
    return collectVariables(true);
4✔
115
  }
116

117
  private final List<VariableLine> collectVariables(boolean onlyExported) {
118

119
    Map<String, VariableLine> variables = new HashMap<>();
4✔
120
    collectVariables(variables, onlyExported, this);
5✔
121
    return new ArrayList<>(variables.values());
6✔
122
  }
123

124
  /**
125
   * @param variables the {@link Map} where to add the names of the variables defined here as keys, and their corresponding source as value.
126
   */
127
  protected void collectVariables(Map<String, VariableLine> variables, boolean onlyExported, AbstractEnvironmentVariables resolver) {
128

129
    if (this.parent != null) {
3✔
130
      this.parent.collectVariables(variables, onlyExported, resolver);
6✔
131
    }
132
  }
1✔
133

134
  protected VariableLine createVariableLine(String name, boolean onlyExported, AbstractEnvironmentVariables resolver) {
135

136
    boolean export = resolver.isExported(name);
4✔
137
    if (!onlyExported || export) {
4✔
138
      String value = resolver.get(name, false);
5✔
139
      if (value != null) {
2✔
140
        return VariableLine.of(export, name, value, getSource());
7✔
141
      }
142
    }
143
    return null;
2✔
144
  }
145

146
  /**
147
   * @param propertiesFilePath the {@link #getPropertiesFilePath() propertiesFilePath} of the child {@link EnvironmentVariables}.
148
   * @param type the {@link #getType() type}.
149
   * @return the new {@link EnvironmentVariables}.
150
   */
151
  public AbstractEnvironmentVariables extend(Path propertiesFilePath, EnvironmentVariablesType type) {
152

153
    return new EnvironmentVariablesPropertiesFile(this, type, propertiesFilePath, this.context);
9✔
154
  }
155

156
  /**
157
   * @return a new child {@link EnvironmentVariables} that will resolve variables recursively or this instance itself if already satisfied.
158
   */
159
  public EnvironmentVariables resolved() {
160

161
    return new EnvironmentVariablesResolved(this);
5✔
162
  }
163

164
  @Override
165
  public String resolve(String string, Object source) {
166
    return resolveRecursive(string, source, 0, this, new ResolveContext(source, string, false, VariableSyntax.CURLY));
14✔
167
  }
168

169
  @Override
170
  public String resolve(String string, Object source, boolean legacySupport) {
171

172
    return resolveRecursive(string, source, 0, this, new ResolveContext(source, string, legacySupport, null));
14✔
173
  }
174

175
  /**
176
   * This method is called recursively. This allows you to resolve variables that are defined by other variables.
177
   *
178
   * @param value the {@link String} that potentially contains variables in the syntax "${«variable«}". Those will be resolved by this method and replaced
179
   *     with their {@link #get(String) value}.
180
   * @param source the source where the {@link String} to resolve originates from. Should have a reasonable {@link Object#toString() string representation}
181
   *     that will be used in error or log messages if a variable could not be resolved.
182
   * @param recursion the current recursion level. This is used to interrupt endless recursion.
183
   * @param resolvedVars this is a reference to an object of {@link EnvironmentVariablesResolved} being the lowest level in the
184
   *     {@link EnvironmentVariablesType hierarchy} of variables. In case of a self-referencing variable {@code x} the resolving has to continue one level
185
   *     higher in the {@link EnvironmentVariablesType hierarchy} to avoid endless recursion. The {@link EnvironmentVariablesResolved} is then used if another
186
   *     variable {@code y} must be resolved, since resolving this variable has to again start at the lowest level. For example: For levels {@code l1, l2} with
187
   *     {@code l1 < l2} and {@code x=${x} foo} and {@code y=bar} defined at level {@code l1} and {@code x=test ${y}} defined at level {@code l2}, {@code x} is
188
   *     first resolved at level {@code l1} and then up the {@link EnvironmentVariablesType hierarchy} at {@code l2} to avoid endless recursion. However,
189
   *     {@code y} must be resolved starting from the lowest level in the {@link EnvironmentVariablesType hierarchy} and therefore
190
   *     {@link EnvironmentVariablesResolved} is used.
191
   * @param context the {@link ResolveContext}.
192
   * @return the given {@link String} with the variables resolved.
193
   */
194
  private String resolveRecursive(String value, Object source, int recursion, AbstractEnvironmentVariables resolvedVars, ResolveContext context) {
195

196
    if (value == null) {
2!
197
      return null;
×
198
    }
199
    if (recursion > MAX_RECURSION) {
3!
200
      throw new IllegalStateException(
×
201
          "Reached maximum recursion resolving " + value + " for root variable " + context.rootSrc + " with value '" + context.rootValue + "'.");
202
    }
203
    recursion++;
1✔
204

205
    String resolved;
206
    if (context.syntax == null) {
3✔
207
      resolved = resolveWithSyntax(value, source, recursion, resolvedVars, context, VariableSyntax.SQUARE);
9✔
208
      if (context.legacySupport) {
3!
209
        resolved = resolveWithSyntax(value, source, recursion, resolvedVars, context, VariableSyntax.CURLY);
10✔
210
      }
211
    } else {
212
      resolved = resolveWithSyntax(value, source, recursion, resolvedVars, context, context.syntax);
10✔
213
    }
214
    return resolved;
2✔
215
  }
216

217
  private String resolveWithSyntax(final String value, final Object src, final int recursion, final AbstractEnvironmentVariables resolvedVars,
218
      final ResolveContext context, final VariableSyntax syntax) {
219

220
    Matcher matcher = syntax.getPattern().matcher(value);
5✔
221
    if (!matcher.find()) {
3✔
222
      return value;
2✔
223
    }
224
    StringBuilder sb = new StringBuilder(value.length() + EXTRA_CAPACITY);
8✔
225
    do {
226
      String variableName = syntax.getVariable(matcher);
4✔
227
      String variableValue = resolvedVars.getValue(variableName, false);
5✔
228
      if (variableValue == null) {
2✔
229
        IdeLogLevel logLevel = IdeLogLevel.WARNING;
2✔
230
        if (context.legacySupport && (syntax == VariableSyntax.CURLY)) {
3!
231
          logLevel = IdeLogLevel.INFO;
×
232
        }
233
        String var = matcher.group();
3✔
234
        if (recursion > 1) {
3✔
235
          this.context.level(logLevel).log("Undefined variable {} in '{}' at '{}={}'", var, context.rootSrc, src, value);
27✔
236
        } else {
237
          this.context.level(logLevel).log("Undefined variable {} in '{}'", var, src);
17✔
238
        }
239
        continue;
1✔
240
      }
241
      EnvironmentVariables lowestFound = findVariable(variableName);
4✔
242
      if ((lowestFound == null) || !lowestFound.getFlat(variableName).equals(value)) {
8✔
243
        // looking for "variableName" starting from resolved upwards the hierarchy
244
        String replacement = resolvedVars.resolveRecursive(variableValue, variableName, recursion, resolvedVars, context);
8✔
245
        matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
6✔
246
      } else { // is self referencing
1✔
247
        // finding next occurrence of "variableName" up the hierarchy of EnvironmentVariablesType
248
        EnvironmentVariables next = lowestFound.getParent();
3✔
249
        while (next != null) {
2✔
250
          if (next.getFlat(variableName) != null) {
4✔
251
            break;
1✔
252
          }
253
          next = next.getParent();
4✔
254
        }
255
        if (next == null) {
2✔
256
          matcher.appendReplacement(sb, Matcher.quoteReplacement(SELF_REFERENCING_NOT_FOUND));
6✔
257
          continue;
1✔
258
        }
259
        // resolving a self referencing variable one level up the hierarchy of EnvironmentVariablesType, i.e. at "next",
260
        // to avoid endless recursion
261
        String replacement = ((AbstractEnvironmentVariables) next).resolveRecursive(next.getFlat(variableName), variableName, recursion, resolvedVars, context
11✔
262
        );
263
        matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
6✔
264

265
      }
266
    } while (matcher.find());
3✔
267
    matcher.appendTail(sb);
4✔
268

269
    String resolved = sb.toString();
3✔
270
    return resolved;
2✔
271
  }
272

273
  /**
274
   * Like {@link #get(String)} but with higher-level features including to resolve {@link IdeVariables} with their default values.
275
   *
276
   * @param name the name of the variable to get.
277
   * @param ignoreDefaultValue - {@code true} if the {@link VariableDefinition#getDefaultValue(IdeContext) default value} of a potential
278
   *     {@link VariableDefinition} shall be ignored, {@code false} to return default instead of {@code null}.
279
   * @return the value of the variable.
280
   */
281
  protected String getValue(String name, boolean ignoreDefaultValue) {
282

283
    VariableDefinition<?> var = IdeVariables.get(name);
3✔
284
    String value;
285
    if ((var != null) && var.isForceDefaultValue()) {
5✔
286
      value = var.getDefaultValueAsString(this.context);
6✔
287
    } else {
288
      value = this.parent.get(name, false);
6✔
289
    }
290
    if ((value == null) && (var != null)) {
4✔
291
      String key = var.getName();
3✔
292
      if (!name.equals(key)) {
4✔
293
        // try new name (e.g. IDE_TOOLS or IDE_HOME) if no value could be found by given legacy name (e.g.
294
        // DEVON_IDE_TOOLS or DEVON_IDE_HOME)
295
        value = this.parent.get(key, false);
6✔
296
      }
297
      if ((value == null) && !ignoreDefaultValue) {
4!
298
        value = var.getDefaultValueAsString(this.context);
5✔
299
      }
300
    }
301
    if ((value != null) && (value.startsWith("~/"))) {
6✔
302
      value = this.context.getUserHome() + value.substring(1);
8✔
303
    }
304
    return value;
2✔
305
  }
306

307
  @Override
308
  public String inverseResolve(String string, Object src, VariableSyntax syntax) {
309

310
    String result = string;
×
311
    // TODO add more variables to IdeVariables like JAVA_HOME
312
    for (VariableDefinition<?> variable : IdeVariables.VARIABLES) {
×
313
      if (variable != IdeVariables.PATH) {
×
314
        String name = variable.getName();
×
315
        String value = get(name);
×
316
        if (value == null) {
×
317
          value = variable.getDefaultValueAsString(this.context);
×
318
        }
319
        if (value != null) {
×
320
          result = result.replace(value, syntax.create(name));
×
321
        }
322
      }
323
    }
×
324
    if (!result.equals(string)) {
×
325
      this.context.trace("Inverse resolved '{}' to '{}' from {}.", string, result, src);
×
326
    }
327
    return result;
×
328
  }
329

330
  @Override
331
  public VersionIdentifier getToolVersion(String tool) {
332

333
    String variable = EnvironmentVariables.getToolVersionVariable(tool);
3✔
334
    String value = get(variable);
4✔
335
    if (value == null) {
2✔
336
      return VersionIdentifier.LATEST;
2✔
337
    } else if (value.isEmpty()) {
3✔
338
      this.context.warning("Variable {} is configured with empty value, please fix your configuration.", variable);
10✔
339
      return VersionIdentifier.LATEST;
2✔
340
    }
341
    VersionIdentifier version = VersionIdentifier.of(value);
3✔
342
    if (version == null) {
2!
343
      // can actually never happen, but for robustness
344
      version = VersionIdentifier.LATEST;
×
345
    }
346
    return version;
2✔
347
  }
348

349
  @Override
350
  public String toString() {
351

352
    return getSource().toString();
×
353
  }
354

355
  /**
356
   * Simple record for the immutable arguments of recursive resolve methods.
357
   *
358
   * @param rootSrc the root source where the {@link String} to resolve originates from.
359
   * @param rootValue the root value to resolve.
360
   * @param legacySupport flag for legacy support (see {@link #resolve(String, Object, boolean)}). Only considered if {@link #syntax()} is {@code null}.
361
   * @param syntax the explicit {@link VariableSyntax} to use.
362
   */
363
  private static record ResolveContext(Object rootSrc, String rootValue, boolean legacySupport, VariableSyntax syntax) {
15✔
364

365
  }
366

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