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

devonfw / IDEasy / 12764639324

14 Jan 2025 09:22AM UTC coverage: 68.082% (+0.5%) from 67.541%
12764639324

Pull #820

github

web-flow
Merge 0ebdb6283 into 875fbff84
Pull Request #820: #759: upgrade settings commandlet

2689 of 4311 branches covered (62.38%)

Branch coverage included in aggregate %.

6946 of 9841 relevant lines covered (70.58%)

3.1 hits per line

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

93.44
cli/src/main/java/com/devonfw/tools/ide/environment/EnvironmentVariablesPropertiesFile.java
1
package com.devonfw.tools.ide.environment;
2

3
import java.io.BufferedReader;
4
import java.io.BufferedWriter;
5
import java.io.IOException;
6
import java.nio.file.Files;
7
import java.nio.file.Path;
8
import java.util.ArrayList;
9
import java.util.HashMap;
10
import java.util.HashSet;
11
import java.util.List;
12
import java.util.Map;
13
import java.util.Objects;
14
import java.util.Set;
15

16
import com.devonfw.tools.ide.context.IdeContext;
17
import com.devonfw.tools.ide.variable.IdeVariables;
18
import com.devonfw.tools.ide.variable.VariableDefinition;
19

20
/**
21
 * Implementation of {@link EnvironmentVariables}.
22
 */
23
public final class EnvironmentVariablesPropertiesFile extends EnvironmentVariablesMap {
1✔
24

25
  private static final String NEWLINE = "\n";
26

27
  private final EnvironmentVariablesType type;
28

29
  private final Path propertiesFilePath;
30

31
  private final Path legacyPropertiesFilePath;
32

33
  private final Map<String, String> variables;
34

35
  private final Set<String> exportedVariables;
36

37
  private final Set<String> modifiedVariables;
38

39
  private Boolean legacyConfiguration;
40

41
  /**
42
   * The constructor.
43
   *
44
   * @param parent the parent {@link EnvironmentVariables} to inherit from.
45
   * @param type the {@link #getType() type}.
46
   * @param propertiesFilePath the {@link #getSource() source}.
47
   * @param context the {@link IdeContext}.
48
   */
49
  public EnvironmentVariablesPropertiesFile(AbstractEnvironmentVariables parent, EnvironmentVariablesType type,
50
      Path propertiesFilePath, IdeContext context) {
51

52
    this(parent, type, getParent(propertiesFilePath), propertiesFilePath, context);
8✔
53
  }
1✔
54

55
  /**
56
   * The constructor.
57
   *
58
   * @param parent the parent {@link EnvironmentVariables} to inherit from.
59
   * @param type the {@link #getType() type}.
60
   * @param propertiesFolderPath the {@link Path} to the folder where the properties file is expected.
61
   * @param propertiesFilePath the {@link #getSource() source}.
62
   * @param context the {@link IdeContext}.
63
   */
64
  public EnvironmentVariablesPropertiesFile(AbstractEnvironmentVariables parent, EnvironmentVariablesType type, Path propertiesFolderPath,
65
      Path propertiesFilePath, IdeContext context) {
66

67
    super(parent, context);
4✔
68
    Objects.requireNonNull(type);
3✔
69
    assert (type != EnvironmentVariablesType.RESOLVED);
4!
70
    this.type = type;
3✔
71
    if (propertiesFolderPath == null) {
2✔
72
      this.propertiesFilePath = null;
3✔
73
      this.legacyPropertiesFilePath = null;
4✔
74
    } else {
75
      if (propertiesFilePath == null) {
2✔
76
        this.propertiesFilePath = propertiesFolderPath.resolve(DEFAULT_PROPERTIES);
6✔
77
      } else {
78
        this.propertiesFilePath = propertiesFilePath;
3✔
79
        assert (propertiesFilePath.getParent().equals(propertiesFolderPath));
6!
80
      }
81
      Path legacyPropertiesFolderPath = propertiesFolderPath;
2✔
82
      if (type == EnvironmentVariablesType.USER) {
3✔
83
        // ~/devon.properties vs. ~/.ide/ide.properties
84
        legacyPropertiesFolderPath = propertiesFolderPath.getParent();
3✔
85
      }
86
      this.legacyPropertiesFilePath = legacyPropertiesFolderPath.resolve(LEGACY_PROPERTIES);
5✔
87
    }
88
    this.variables = new HashMap<>();
5✔
89
    this.exportedVariables = new HashSet<>();
5✔
90
    this.modifiedVariables = new HashSet<>();
5✔
91
    load();
2✔
92
  }
1✔
93

94
  private static Path getParent(Path path) {
95

96
    if (path == null) {
2!
97
      return null;
×
98
    }
99
    return path.getParent();
3✔
100
  }
101

102
  private void load() {
103

104
    boolean success = load(this.propertiesFilePath);
5✔
105
    if (success) {
2✔
106
      this.legacyConfiguration = Boolean.FALSE;
4✔
107
    } else {
108
      success = load(this.legacyPropertiesFilePath);
5✔
109
      if (success) {
2✔
110
        this.legacyConfiguration = Boolean.TRUE;
3✔
111
      }
112
    }
113
  }
1✔
114

115
  private boolean load(Path file) {
116
    if (file == null) {
2✔
117
      return false;
2✔
118
    }
119
    if (!Files.exists(file)) {
5✔
120
      this.context.trace("Properties not found at {}", file);
10✔
121
      return false;
2✔
122
    }
123
    this.context.trace("Loading properties from {}", file);
10✔
124
    boolean legacyProperties = file.getFileName().toString().equals(LEGACY_PROPERTIES);
6✔
125
    try (BufferedReader reader = Files.newBufferedReader(file)) {
3✔
126
      String line;
127
      do {
128
        line = reader.readLine();
3✔
129
        if (line != null) {
2✔
130
          VariableLine variableLine = VariableLine.of(line, this.context, getSource());
7✔
131
          String name = variableLine.getName();
3✔
132
          if (name != null) {
2✔
133
            VariableLine migratedVariableLine = migrateLine(variableLine, false);
5✔
134
            if (migratedVariableLine == null) {
2!
135
              this.context.warning("Illegal variable definition: {}", variableLine);
×
136
              continue;
×
137
            }
138
            String migratedName = migratedVariableLine.getName();
3✔
139
            String migratedValue = migratedVariableLine.getValue();
3✔
140
            boolean legacyVariable = IdeVariables.isLegacyVariable(name);
3✔
141
            if (legacyVariable && !legacyProperties) {
4✔
142
              this.context.warning("Legacy variable name is used to define variable {} in {} - please cleanup your configuration.", variableLine,
14✔
143
                  file);
144
            }
145
            String oldValue = this.variables.get(migratedName);
6✔
146
            if (oldValue != null) {
2✔
147
              VariableDefinition<?> variableDefinition = IdeVariables.get(name);
3✔
148
              if (legacyVariable) {
2✔
149
                // if the legacy name was configured we do not want to override the official variable!
150
                this.context.warning("Both legacy variable {} and official variable {} are configured in {} - ignoring legacy variable declaration!",
10✔
151
                    variableDefinition.getLegacyName(), variableDefinition.getName(), file);
11✔
152
              } else {
153
                this.context.warning("Duplicate variable definition {} with old value '{}' and new value '{}' in {}", name, oldValue, migratedValue,
22✔
154
                    file);
155
                this.variables.put(migratedName, migratedValue);
6✔
156
              }
157
            } else {
1✔
158
              this.variables.put(migratedName, migratedValue);
6✔
159
            }
160
            if (variableLine.isExport()) {
3✔
161
              this.exportedVariables.add(migratedName);
5✔
162
            }
163
          }
164
        }
165
      } while (line != null);
2✔
166
      return true;
4✔
167
    } catch (IOException e) {
×
168
      throw new IllegalStateException("Failed to load properties from " + file, e);
×
169
    }
170
  }
171

172
  @Override
173
  public void save() {
174

175
    boolean isLegacy = Boolean.TRUE.equals(this.legacyConfiguration);
5✔
176
    if (this.modifiedVariables.isEmpty() && !isLegacy) {
6✔
177
      this.context.trace("No changes to save in properties file {}", this.propertiesFilePath);
11✔
178
      return;
1✔
179
    }
180

181
    Path file = this.propertiesFilePath;
3✔
182
    if (isLegacy) {
2✔
183
      this.context.info("Converting legacy properties to {}", this.propertiesFilePath);
11✔
184
      file = this.legacyPropertiesFilePath;
3✔
185
    }
186

187
    List<VariableLine> lines = loadVariableLines(file);
4✔
188

189
    this.context.getFileAccess().mkdirs(this.propertiesFilePath.getParent());
7✔
190
    try (BufferedWriter writer = Files.newBufferedWriter(this.propertiesFilePath)) {
6✔
191
      // copy and modify original lines from properties file
192
      for (VariableLine line : lines) {
10✔
193
        VariableLine newLine = migrateLine(line, true);
5✔
194
        if (newLine == null) {
2✔
195
          this.context.debug("Removed variable line '{}' from {}", line, this.propertiesFilePath);
16✔
196
        } else {
197
          if (newLine != line) {
3✔
198
            this.context.debug("Changed variable line from '{}' to '{}' in {}", line, newLine, this.propertiesFilePath);
19✔
199
          }
200
          writer.append(newLine.toString());
5✔
201
          writer.append(NEWLINE);
4✔
202
          String name = line.getName();
3✔
203
          if (name != null) {
2✔
204
            this.modifiedVariables.remove(name);
5✔
205
          }
206
        }
207
      }
1✔
208
      // append variables that have been newly added
209
      for (String name : this.modifiedVariables) {
11✔
210
        String value = this.variables.get(name);
6✔
211
        if (value == null) {
2✔
212
          this.context.trace("Internal error: removed variable {} was not found in {}", name, this.propertiesFilePath);
16✔
213
        } else {
214
          boolean export = this.exportedVariables.contains(name);
5✔
215
          VariableLine line = VariableLine.of(export, name, value);
5✔
216
          writer.append(line.toString());
5✔
217
          writer.append(NEWLINE);
4✔
218
        }
219
      }
1✔
220
      this.modifiedVariables.clear();
3✔
221
    } catch (IOException e) {
×
222
      throw new IllegalStateException("Failed to save properties to " + this.propertiesFilePath, e);
×
223
    }
1✔
224
    this.legacyConfiguration = Boolean.FALSE;
3✔
225
  }
1✔
226

227
  private List<VariableLine> loadVariableLines(Path file) {
228
    List<VariableLine> lines = new ArrayList<>();
4✔
229
    if (!Files.exists(file)) {
5✔
230
      // Skip reading if the file does not exist
231
      this.context.debug("Properties file {} does not exist, skipping read.", file);
10✔
232
      return lines;
2✔
233
    }
234
    try (BufferedReader reader = Files.newBufferedReader(file)) {
3✔
235
      String line;
236
      do {
237
        line = reader.readLine();
3✔
238
        if (line != null) {
2✔
239
          VariableLine variableLine = VariableLine.of(line, this.context, getSource());
7✔
240
          lines.add(variableLine);
4✔
241
        }
242
      } while (line != null);
2✔
243
    } catch (IOException e) {
×
244
      throw new IllegalStateException("Failed to load existing properties from " + file, e);
×
245
    }
1✔
246
    return lines;
2✔
247
  }
248

249
  private VariableLine migrateLine(VariableLine line, boolean saveNotLoad) {
250

251
    String name = line.getName();
3✔
252
    if (name != null) {
2✔
253
      VariableDefinition<?> variableDefinition = IdeVariables.get(name);
3✔
254
      if (variableDefinition != null) {
2✔
255
        line = variableDefinition.migrateLine(line);
4✔
256
      }
257
      if (saveNotLoad) {
2✔
258
        name = line.getName();
3✔
259
        if (this.modifiedVariables.contains(name)) {
5✔
260
          String value = this.variables.get(name);
6✔
261
          if (value == null) {
2✔
262
            return null;
2✔
263
          } else {
264
            line = line.withValue(value);
4✔
265
          }
266
        }
267
        boolean newExport = this.exportedVariables.contains(name);
5✔
268
        if (line.isExport() != newExport) {
4✔
269
          line = line.withExport(newExport);
4✔
270
        }
271
      }
272
    }
273
    return line;
2✔
274
  }
275

276
  @Override
277
  protected Map<String, String> getVariables() {
278

279
    return this.variables;
3✔
280
  }
281

282
  @Override
283
  protected void collectVariables(Map<String, VariableLine> variables, boolean onlyExported, AbstractEnvironmentVariables resolver) {
284

285
    for (String key : this.variables.keySet()) {
12✔
286
      variables.computeIfAbsent(key, k -> createVariableLine(key, onlyExported, resolver));
15✔
287
    }
1✔
288
    super.collectVariables(variables, onlyExported, resolver);
5✔
289
  }
1✔
290

291
  @Override
292
  public boolean isExported(String name) {
293

294
    if (this.exportedVariables.contains(name)) {
5!
295
      return true;
×
296
    }
297
    return super.isExported(name);
4✔
298
  }
299

300
  @Override
301
  public EnvironmentVariablesType getType() {
302

303
    return this.type;
3✔
304
  }
305

306
  @Override
307
  public Path getPropertiesFilePath() {
308

309
    return this.propertiesFilePath;
3✔
310
  }
311

312
  @Override
313
  public Path getLegacyPropertiesFilePath() {
314

315
    return this.legacyPropertiesFilePath;
3✔
316
  }
317

318
  /**
319
   * @return {@code Boolean#TRUE} if the current variable state comes from {@link #getLegacyPropertiesFilePath()}, {@code Boolean#FALSE} if state comes from
320
   *     {@link #getPropertiesFilePath()}), and {@code null} if neither of these files existed (nothing was loaded).
321
   */
322
  public Boolean getLegacyConfiguration() {
323

324
    return this.legacyConfiguration;
3✔
325
  }
326

327
  @Override
328
  public String set(String name, String value) {
329

330
    return set(name, value, this.exportedVariables.contains(name));
×
331
  }
332

333
  @Override
334
  public String set(String name, String value, boolean export) {
335

336
    String oldValue = this.variables.put(name, value);
7✔
337
    boolean flagChanged = export != this.exportedVariables.contains(name);
10✔
338
    if (Objects.equals(value, oldValue) && !flagChanged) {
6✔
339
      this.context.trace("Set variable '{}={}' caused no change in {}", name, value, this.propertiesFilePath);
20✔
340
    } else {
341
      this.context.debug("Set variable '{}={}' in {}", name, value, this.propertiesFilePath);
19✔
342
      this.modifiedVariables.add(name);
5✔
343
      if (export && (value != null)) {
4!
344
        this.exportedVariables.add(name);
6✔
345
      } else {
346
        this.exportedVariables.remove(name);
5✔
347
      }
348
    }
349
    return oldValue;
2✔
350
  }
351

352
  /**
353
   * Removes a property.
354
   *
355
   * @param name name of the property to remove.
356
   */
357
  public void remove(String name) {
358
    String oldValue = this.variables.remove(name);
6✔
359
    if (oldValue != null) {
2✔
360
      this.modifiedVariables.add(name);
5✔
361
      this.exportedVariables.remove(name);
5✔
362
      this.context.debug("Removed variable name of '{}' in {}", name, this.propertiesFilePath);
15✔
363
    }
364
  }
1✔
365

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