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

devonfw / IDEasy / 9907372175

12 Jul 2024 11:49AM UTC coverage: 61.142% (-0.02%) from 61.162%
9907372175

push

github

hohwille
fixed tests

1997 of 3595 branches covered (55.55%)

Branch coverage included in aggregate %.

5296 of 8333 relevant lines covered (63.55%)

2.8 hits per line

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

77.33
cli/src/main/java/com/devonfw/tools/ide/merge/JsonMerger.java
1
package com.devonfw.tools.ide.merge;
2

3
import java.io.OutputStream;
4
import java.io.Reader;
5
import java.nio.file.Files;
6
import java.nio.file.Path;
7
import java.util.Collections;
8
import java.util.HashMap;
9
import java.util.Map;
10
import java.util.Set;
11
import javax.json.Json;
12
import javax.json.JsonArray;
13
import javax.json.JsonArrayBuilder;
14
import javax.json.JsonObject;
15
import javax.json.JsonObjectBuilder;
16
import javax.json.JsonReader;
17
import javax.json.JsonString;
18
import javax.json.JsonStructure;
19
import javax.json.JsonValue;
20
import javax.json.JsonWriter;
21
import javax.json.JsonWriterFactory;
22
import javax.json.stream.JsonGenerator;
23

24
import com.devonfw.tools.ide.context.IdeContext;
25
import com.devonfw.tools.ide.environment.EnvironmentVariables;
26

27
/**
28
 * Implementation of {@link FileMerger} for JSON.
29
 */
30
public class JsonMerger extends FileMerger {
31

32
  /**
33
   * The constructor.
34
   *
35
   * @param context the {@link #context}.
36
   */
37
  public JsonMerger(IdeContext context) {
38

39
    super(context);
3✔
40
  }
1✔
41

42
  @Override
43
  public void merge(Path setup, Path update, EnvironmentVariables variables, Path workspace) {
44

45
    JsonStructure json = null;
2✔
46
    Path template = setup;
2✔
47
    boolean updateFileExists = Files.exists(update);
5✔
48
    if (Files.exists(workspace)) {
5✔
49
      if (!updateFileExists) {
2!
50
        return; // nothing to do ...
×
51
      }
52
      json = load(workspace);
4✔
53
    } else if (Files.exists(setup)) {
5✔
54
      json = load(setup);
3✔
55
    }
56
    JsonStructure mergeJson = null;
2✔
57
    if (updateFileExists) {
2!
58
      if (json == null) {
2✔
59
        json = load(update);
4✔
60
      } else {
61
        mergeJson = load(update);
3✔
62
      }
63
      template = update;
2✔
64
    }
65
    Status status = new Status();
4✔
66
    JsonStructure result = (JsonStructure) mergeAndResolve(json, mergeJson, variables, status, template.toString());
10✔
67
    if (status.updated) {
3✔
68
      save(result, workspace);
3✔
69
      this.context.debug("Saved created/updated file {}", workspace);
11✔
70
    } else {
71
      this.context.trace("No changes for file {}", workspace);
10✔
72
    }
73
  }
1✔
74

75
  private static JsonStructure load(Path file) {
76

77
    try (Reader reader = Files.newBufferedReader(file)) {
3✔
78
      JsonReader jsonReader = Json.createReader(reader);
3✔
79
      return jsonReader.read();
5✔
80
    } catch (Exception e) {
×
81
      throw new IllegalStateException("Failed to read JSON from " + file, e);
×
82
    }
83
  }
84

85
  private static void save(JsonStructure json, Path file) {
86

87
    ensureParentDirectoryExists(file);
2✔
88
    try (OutputStream out = Files.newOutputStream(file)) {
5✔
89

90
      Map<String, Object> config = new HashMap<>();
4✔
91
      config.put(JsonGenerator.PRETTY_PRINTING, Boolean.TRUE);
5✔
92
      // JSON-P API sucks: no way to set the indentation string
93
      // preferred would be two spaces, implementation has four whitespaces hardcoded
94
      // See org.glassfish.json.JsonPrettyGeneratorImpl
95
      // when will they ever learn...?
96
      JsonWriterFactory jsonWriterFactory = Json.createWriterFactory(config);
3✔
97
      JsonWriter jsonWriter = jsonWriterFactory.createWriter(out);
4✔
98
      jsonWriter.write(json);
3✔
99
      jsonWriter.close();
2✔
100
    } catch (Exception e) {
×
101
      throw new IllegalStateException("Failed to save JSON to " + file, e);
×
102
    }
1✔
103
  }
1✔
104

105
  @Override
106
  public void inverseMerge(Path workspace, EnvironmentVariables variables, boolean addNewProperties, Path updateFile) {
107

108
    if (!Files.exists(workspace) || !Files.exists(updateFile)) {
×
109
      return;
×
110
    }
111
    JsonStructure updateDocument = load(updateFile);
×
112
    JsonStructure workspaceDocument = load(workspace);
×
113
    Status status = new Status(addNewProperties);
×
114
    JsonStructure result = (JsonStructure) mergeAndResolve(workspaceDocument, updateDocument, variables, status,
×
115
        workspace.getFileName());
×
116
    if (status.updated) {
×
117
      save(result, updateFile);
×
118
      this.context.debug("Saved changes from {} to {}", workspace.getFileName(), updateFile);
×
119
    } else {
120
      this.context.trace("No changes for {}", updateFile);
×
121
    }
122
  }
×
123

124
  private JsonValue mergeAndResolve(JsonValue json, JsonValue mergeJson, EnvironmentVariables variables, Status status,
125
      Object src) {
126

127
    if (json == null) {
2✔
128
      if (mergeJson == null) {
2!
129
        return null;
×
130
      } else {
131
        return mergeAndResolve(mergeJson, null, variables, status, src);
8✔
132
      }
133
    } else {
134
      if (mergeJson == null) {
2✔
135
        status.updated = true; // JSON to merge does not exist and needs to be created
3✔
136
      }
137
      return switch (json.getValueType()) {
7!
138
        case OBJECT -> mergeAndResolveObject((JsonObject) json, (JsonObject) mergeJson, variables, status, src);
10✔
139
        case ARRAY -> mergeAndResolveArray((JsonArray) json, (JsonArray) mergeJson, variables, status, src);
10✔
140
        case STRING -> mergeAndResolveString((JsonString) json, (JsonString) mergeJson, variables, status, src);
10✔
141
        case NUMBER, FALSE, TRUE, NULL -> mergeAndResolveNativeType(json, mergeJson, variables, status);
7✔
142
        default -> {
143
          this.context.error("Undefined JSON type {}", json.getClass());
×
144
          yield null;
×
145
        }
146
      };
147
    }
148
  }
149

150
  private JsonObject mergeAndResolveObject(JsonObject json, JsonObject mergeJson, EnvironmentVariables variables,
151
      Status status, Object src) {
152

153
    // json = workspace/setup
154
    // mergeJson = update
155
    JsonObjectBuilder builder = Json.createObjectBuilder();
2✔
156
    Set<String> mergeKeySet = Collections.emptySet();
2✔
157
    if (mergeJson != null) {
2✔
158
      mergeKeySet = mergeJson.keySet();
3✔
159
      for (String key : mergeKeySet) {
10✔
160
        JsonValue mergeValue = mergeJson.get(key);
5✔
161
        JsonValue value = json.get(key);
5✔
162
        value = mergeAndResolve(value, mergeValue, variables, status, src);
8✔
163
        builder.add(key, value);
5✔
164
      }
1✔
165
    }
166
    if (status.addNewProperties || !status.inverse) {
6!
167
      for (String key : json.keySet()) {
11✔
168
        if (!mergeKeySet.contains(key)) {
4✔
169
          JsonValue value = json.get(key);
5✔
170
          value = mergeAndResolve(value, null, variables, status, src);
8✔
171
          builder.add(key, value);
5✔
172
          if (status.inverse) {
3!
173
            // added new property on inverse merge...
174
            status.updated = true;
×
175
          }
176
        }
177
      }
1✔
178
    }
179
    return builder.build();
3✔
180
  }
181

182
  private JsonArray mergeAndResolveArray(JsonArray json, JsonArray mergeJson, EnvironmentVariables variables,
183
      Status status, Object src) {
184

185
    JsonArrayBuilder builder = Json.createArrayBuilder();
2✔
186
    // KISS: Merging JSON arrays could be very complex. We simply let mergeJson override json...
187
    JsonArray source = json;
2✔
188
    if (mergeJson != null) {
2!
189
      source = mergeJson;
2✔
190
    }
191
    for (JsonValue value : source) {
10✔
192
      JsonValue resolvedValue = mergeAndResolve(value, null, variables, status, src);
8✔
193
      builder.add(resolvedValue);
4✔
194
    }
1✔
195
    return builder.build();
3✔
196
  }
197

198
  private JsonString mergeAndResolveString(JsonString json, JsonString mergeJson, EnvironmentVariables variables,
199
      Status status, Object src) {
200

201
    JsonString jsonString = json;
2✔
202
    if (mergeJson != null) {
2✔
203
      jsonString = mergeJson;
2✔
204
    }
205
    String string = jsonString.getString();
3✔
206
    String resolvedString;
207
    if (status.inverse) {
3!
208
      resolvedString = variables.inverseResolve(string, src);
×
209
    } else {
210
      resolvedString = variables.resolve(string, src, this.legacySupport);
7✔
211
    }
212
    if (!resolvedString.equals(string)) {
4✔
213
      status.updated = true;
3✔
214
    }
215
    return Json.createValue(resolvedString);
3✔
216
  }
217

218
  private JsonValue mergeAndResolveNativeType(JsonValue json, JsonValue mergeJson, EnvironmentVariables variables,
219
      Status status) {
220

221
    if (mergeJson == null) {
2✔
222
      return json;
2✔
223
    } else {
224
      return mergeJson;
2✔
225
    }
226
  }
227

228
  private static class Status {
229

230
    /**
231
     * {@code true} for inverse merge, {@code false} otherwise (for regular forward merge).
232
     */
233
    private final boolean inverse;
234

235
    private final boolean addNewProperties;
236

237
    private boolean updated;
238

239
    /**
240
     * The constructor.
241
     */
242
    public Status() {
243

244
      this(false, false);
4✔
245
    }
1✔
246

247
    /**
248
     * The constructor.
249
     *
250
     * @param addNewProperties - {@code true} to add new properties from workspace on reverse merge, {@code false} otherwise.
251
     */
252
    public Status(boolean addNewProperties) {
253

254
      this(true, addNewProperties);
×
255
    }
×
256

257
    private Status(boolean inverse, boolean addNewProperties) {
258

259
      super();
2✔
260
      this.inverse = inverse;
3✔
261
      this.addNewProperties = addNewProperties;
3✔
262
      this.updated = false;
3✔
263
    }
1✔
264

265
  }
266

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