1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 package com.linecorp.centraldogma.internal.jsonpatch;
36
37 import static com.linecorp.centraldogma.internal.jsonpatch.JsonPatchUtil.encodeSegment;
38 import static java.util.Objects.requireNonNull;
39
40 import java.io.IOException;
41 import java.util.HashMap;
42 import java.util.Iterator;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Set;
46 import java.util.TreeSet;
47
48 import com.fasterxml.jackson.annotation.JsonCreator;
49 import com.fasterxml.jackson.core.JsonGenerator;
50 import com.fasterxml.jackson.core.JsonPointer;
51 import com.fasterxml.jackson.databind.JsonMappingException;
52 import com.fasterxml.jackson.databind.JsonNode;
53 import com.fasterxml.jackson.databind.JsonSerializable;
54 import com.fasterxml.jackson.databind.SerializerProvider;
55 import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
56 import com.fasterxml.jackson.databind.node.ArrayNode;
57 import com.fasterxml.jackson.databind.node.JsonNodeType;
58 import com.fasterxml.jackson.databind.node.ObjectNode;
59 import com.google.common.annotations.VisibleForTesting;
60 import com.google.common.base.Equivalence;
61 import com.google.common.collect.ImmutableList;
62 import com.google.common.collect.Iterators;
63 import com.google.common.collect.Sets;
64
65 import com.linecorp.centraldogma.common.jsonpatch.JsonPatchConflictException;
66 import com.linecorp.centraldogma.common.jsonpatch.JsonPatchOperation;
67 import com.linecorp.centraldogma.internal.Jackson;
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125 public final class JsonPatch implements JsonSerializable {
126
127 private static final Equivalence<JsonNode> EQUIVALENCE = JsonNumEquals.getInstance();
128 private static final JsonPointer EMPTY_JSON_POINTER = JsonPointer.compile("");
129 private static final JsonPointer END_OF_ARRAY_POINTER = JsonPointer.compile("/-");
130
131
132
133
134
135
136
137
138
139 public static JsonPatch fromJson(final JsonNode node) throws IOException {
140 requireNonNull(node, "node");
141 try {
142 return Jackson.treeToValue(node, JsonPatch.class);
143 } catch (JsonMappingException e) {
144 throw new JsonPatchConflictException("invalid JSON patch", e);
145 }
146 }
147
148
149
150
151
152
153
154
155
156 public static JsonPatch generate(final JsonNode source, final JsonNode target, ReplaceMode replaceMode) {
157 requireNonNull(source, "source");
158 requireNonNull(target, "target");
159 final DiffProcessor processor = new DiffProcessor(replaceMode, () -> unchangedValues(source, target));
160 generateDiffs(processor, EMPTY_JSON_POINTER, source, target);
161 return processor.getPatch();
162 }
163
164 private static void generateDiffs(final DiffProcessor processor, final JsonPointer pointer,
165 final JsonNode source, final JsonNode target) {
166
167 if (EQUIVALENCE.equivalent(source, target)) {
168 return;
169 }
170
171 final JsonNodeType sourceType = source.getNodeType();
172 final JsonNodeType targetType = target.getNodeType();
173
174
175
176
177 if (sourceType != targetType) {
178 processor.valueReplaced(pointer, source, target);
179 return;
180 }
181
182
183
184
185
186
187
188 if (!source.isContainerNode()) {
189 processor.valueReplaced(pointer, source, target);
190 return;
191 }
192
193
194
195
196
197 if (sourceType == JsonNodeType.OBJECT) {
198 generateObjectDiffs(processor, pointer, (ObjectNode) source, (ObjectNode) target);
199 } else {
200
201 generateArrayDiffs(processor, pointer, (ArrayNode) source, (ArrayNode) target);
202 }
203 }
204
205 private static void generateObjectDiffs(final DiffProcessor processor, final JsonPointer pointer,
206 final ObjectNode source, final ObjectNode target) {
207
208 final Set<String> sourceFields = new TreeSet<>();
209 Iterators.addAll(sourceFields, source.fieldNames());
210 final Set<String> targetFields = new TreeSet<>();
211 Iterators.addAll(targetFields, target.fieldNames());
212
213 for (final String field : Sets.difference(sourceFields, targetFields)) {
214 processor.valueRemoved(pointer.append(JsonPointer.valueOf(encodeSegment(field))));
215 }
216
217 for (final String field : Sets.difference(targetFields, sourceFields)) {
218 processor.valueAdded(pointer.append(JsonPointer.valueOf(encodeSegment(field))), target.get(field));
219 }
220
221 for (final String field : Sets.intersection(sourceFields, targetFields)) {
222 generateDiffs(processor, pointer.append(JsonPointer.valueOf(encodeSegment(field))),
223 source.get(field), target.get(field));
224 }
225 }
226
227 private static void generateArrayDiffs(final DiffProcessor processor, final JsonPointer pointer,
228 final ArrayNode source, final ArrayNode target) {
229 final int sourceSize = source.size();
230 final int targetSize = target.size();
231 final int size = Math.min(sourceSize, targetSize);
232
233
234
235
236
237 for (int index = size; index < sourceSize; index++) {
238 processor.valueRemoved(pointer.append(JsonPointer.valueOf("/" + size)));
239 }
240
241 for (int index = 0; index < size; index++) {
242 generateDiffs(processor, pointer.append(JsonPointer.valueOf("/" + index)),
243 source.get(index), target.get(index));
244 }
245
246
247 for (int index = size; index < targetSize; index++) {
248 processor.valueAdded(pointer.append(END_OF_ARRAY_POINTER), target.get(index));
249 }
250 }
251
252 @VisibleForTesting
253 static Map<JsonPointer, JsonNode> unchangedValues(final JsonNode source, final JsonNode target) {
254 final Map<JsonPointer, JsonNode> ret = new HashMap<>();
255 computeUnchanged(ret, EMPTY_JSON_POINTER, source, target);
256 return ret;
257 }
258
259 private static void computeUnchanged(final Map<JsonPointer, JsonNode> ret, final JsonPointer pointer,
260 final JsonNode source, final JsonNode target) {
261 if (EQUIVALENCE.equivalent(source, target)) {
262 ret.put(pointer, target);
263 return;
264 }
265
266 final JsonNodeType sourceType = source.getNodeType();
267 final JsonNodeType targetType = target.getNodeType();
268
269 if (sourceType != targetType) {
270 return;
271 }
272
273
274
275 switch (sourceType) {
276 case OBJECT:
277 computeUnchangedObject(ret, pointer, source, target);
278 break;
279 case ARRAY:
280 computeUnchangedArray(ret, pointer, source, target);
281 break;
282 default:
283
284 }
285 }
286
287 private static void computeUnchangedObject(final Map<JsonPointer, JsonNode> ret, final JsonPointer pointer,
288 final JsonNode source, final JsonNode target) {
289 final Iterator<String> sourceFields = source.fieldNames();
290 while (sourceFields.hasNext()) {
291 final String name = sourceFields.next();
292 if (!target.has(name)) {
293 continue;
294 }
295 computeUnchanged(ret, pointer.append(JsonPointer.valueOf(encodeSegment(name))),
296 source.get(name), target.get(name));
297 }
298 }
299
300 private static void computeUnchangedArray(final Map<JsonPointer, JsonNode> ret, final JsonPointer pointer,
301 final JsonNode source, final JsonNode target) {
302 final int size = Math.min(source.size(), target.size());
303 for (int i = 0; i < size; i++) {
304 computeUnchanged(ret, pointer.append(JsonPointer.valueOf("/" + i)),
305 source.get(i), target.get(i));
306 }
307 }
308
309
310
311
312 private final List<JsonPatchOperation> operations;
313
314
315
316
317
318
319
320 @JsonCreator
321 JsonPatch(final List<JsonPatchOperation> operations) {
322 this.operations = ImmutableList.copyOf(operations);
323 }
324
325
326
327
328 public boolean isEmpty() {
329 return operations.isEmpty();
330 }
331
332
333
334
335 public List<JsonPatchOperation> operations() {
336 return operations;
337 }
338
339
340
341
342
343
344
345
346
347 public JsonNode apply(final JsonNode node) {
348 requireNonNull(node, "node");
349 JsonNode ret = node.deepCopy();
350 for (final JsonPatchOperation operation : operations) {
351 ret = operation.apply(ret);
352 }
353
354 return ret;
355 }
356
357
358
359
360 public ArrayNode toJson() {
361 return (ArrayNode) Jackson.valueToTree(this);
362 }
363
364 @Override
365 public String toString() {
366 return operations.toString();
367 }
368
369 @Override
370 public void serialize(final JsonGenerator jgen, final SerializerProvider provider) throws IOException {
371 jgen.writeStartArray();
372 for (final JsonPatchOperation op : operations) {
373 op.serialize(jgen, provider);
374 }
375 jgen.writeEndArray();
376 }
377
378 @Override
379 public void serializeWithType(final JsonGenerator jgen,
380 final SerializerProvider provider, final TypeSerializer typeSer)
381 throws IOException {
382 serialize(jgen, provider);
383 }
384 }