1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.forgerock.json;
18
19 import static org.forgerock.json.JsonValueFunctions.pointer;
20
21 import java.util.ArrayList;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.List;
25
26 import org.forgerock.util.Reject;
27
28
29
30
31 public final class JsonPatch {
32
33
34
35
36 public static final String MEDIA_TYPE = "application/json-patch";
37
38
39
40
41 public static final JsonPointer OP_PTR = new JsonPointer("/op");
42
43
44
45
46 public static final JsonPointer PATH_PTR = new JsonPointer("/path");
47
48
49
50
51
52 public static final JsonPointer FROM_PTR = new JsonPointer("/from");
53
54
55
56
57
58
59
60 public static final JsonPointer VALUE_PTR = new JsonPointer("/value");
61
62
63
64
65 private static final JsonPatchValueTransformer DEFAULT_TRANSFORM =
66 new JsonPatchValueTransformer() {
67 public Object getTransformedValue(JsonValue target, JsonValue op) {
68 if (op.get(JsonPatch.VALUE_PTR) != null) {
69 return op.get(JsonPatch.VALUE_PTR).getObject();
70 }
71 throw new JsonValueException(op, "expecting a value member");
72 }
73 };
74
75
76
77
78
79
80
81
82
83
84
85 public static JsonValue diff(JsonValue original, JsonValue target) {
86 final List<Object> result = new ArrayList<>();
87 if (differentTypes(original, target)) {
88 result.add(op("replace", original.getPointer(), target));
89 } else if (original.isMap()) {
90 for (String key : original.keys()) {
91 if (target.isDefined(key)) {
92 JsonValue diff = diff(original.get(key), target.get(key));
93 if (diff.size() > 0) {
94 result.addAll(diff.asList());
95 }
96 } else {
97 result.add(op("remove", original.getPointer().child(key), null));
98 }
99 }
100 for (String key : target.keys()) {
101 if (!original.isDefined(key)) {
102 result.add(op("add", original.getPointer().child(key), target.get(key)));
103 }
104 }
105 } else if (original.isList()) {
106 boolean replace = false;
107 if (original.size() != target.size()) {
108 replace = true;
109 } else {
110 Iterator<JsonValue> i1 = original.iterator();
111 Iterator<JsonValue> i2 = target.iterator();
112 while (i1.hasNext() && i2.hasNext()) {
113 if (diff(i1.next(), i2.next()).size() > 0) {
114 replace = true;
115 break;
116 }
117 }
118 }
119 if (replace) {
120 result.add(op("replace", original.getPointer(), target));
121 }
122 } else if (!original.isNull() && !original.getObject().equals(target.getObject())) {
123 result.add(op("replace", original.getPointer(), target));
124 }
125 return new JsonValue(result);
126 }
127
128
129
130
131
132
133
134
135
136
137
138 public static boolean isEqual(JsonValue value, JsonValue other) {
139 Reject.ifFalse(isJsonPrimitive(value) && isJsonPrimitive(other),
140 "JsonPatch#isEqual only supports recognizable JSON primitives");
141 if (differentTypes(value, other)) {
142 return false;
143 }
144 if (value.size() != other.size()) {
145 return false;
146 }
147 if (value.isMap()) {
148
149 for (String key : value.keys()) {
150 if (!other.isDefined(key)
151 || !isEqual(value.get(key), other.get(key))) {
152 return false;
153 }
154 }
155 } else if (value.isList()) {
156 Iterator<JsonValue> i1 = value.iterator();
157 Iterator<JsonValue> i2 = other.iterator();
158 while (i1.hasNext() && i2.hasNext()) {
159 if (!isEqual(i1.next(), i2.next())) {
160 return false;
161 }
162 }
163 } else if (!value.isNull() && !value.getObject().equals(other.getObject())) {
164 return false;
165 }
166 return true;
167 }
168
169 private static boolean isJsonPrimitive(JsonValue value) {
170 return value.isNull() || value.isBoolean() || value.isMap() || value.isList() || value.isNumber()
171 || value.isString();
172 }
173
174
175
176
177
178
179
180
181 private static boolean differentTypes(JsonValue v1, JsonValue v2) {
182 return !(v1.isNull() && v2.isNull())
183 && !(v1.isMap() && v2.isMap())
184 && !(v1.isList() && v2.isList())
185 && !(v1.isString() && v2.isString())
186 && !(v1.isNumber() && v2.isNumber())
187 && !(v1.isBoolean() && v2.isBoolean());
188 }
189
190 private static HashMap<String, Object> op(String op, JsonPointer pointer, JsonValue value) {
191 HashMap<String, Object> result = new HashMap<String, Object>();
192 result.put(OP_PTR.leaf(), op);
193 result.put(PATH_PTR.leaf(), pointer.toString());
194 if (value != null) {
195 result.put(VALUE_PTR.leaf(), value.copy().getObject());
196 }
197 return result;
198 }
199
200
201
202
203
204
205
206
207
208
209 public static void patch(JsonValue original, JsonValue patch) {
210 patch(original, patch, DEFAULT_TRANSFORM);
211 }
212
213
214
215
216
217
218
219
220
221
222
223 public static void patch(JsonValue original, JsonValue patch, JsonPatchValueTransformer transform) {
224 for (JsonValue operation : patch.required().expect(List.class)) {
225 if (!operation.isDefined("op")) {
226 throw new JsonValueException(operation, "op not specified");
227 }
228 PatchOperation op = PatchOperation.valueOf(operation.get(OP_PTR));
229 if (op == null) {
230 throw new JsonValueException(operation, "invalid op specified");
231 }
232 op.execute(original, operation, transform);
233 }
234 }
235
236 private enum PatchOperation {
237 ADD {
238
239 @Override
240 void execute(JsonValue original, JsonValue operation, JsonPatchValueTransformer transform) {
241 JsonPointer modifyPath = operation.get(PATH_PTR).expect(String.class).as(pointer());
242 JsonValue parent = parentValue(modifyPath, original);
243 if (parent == null) {
244
245 if (original.getObject() != null) {
246 throw new JsonValueException(operation, "root value already exists");
247 }
248 original.setObject(transform.getTransformedValue(original, operation));
249 } else {
250 try {
251 if (parent.isList()) {
252 try {
253
254 Integer index = Integer.valueOf(modifyPath.leaf());
255 parent.add(index, transform.getTransformedValue(original, operation));
256 } catch (Exception e) {
257
258 parent.add(modifyPath.leaf(), transform.getTransformedValue(original, operation));
259 }
260 } else if (original.get(modifyPath) != null && original.get(modifyPath).isList()) {
261
262 JsonValue target = original.get(modifyPath);
263 target.asList().add(transform.getTransformedValue(original, operation));
264 } else {
265
266 parent.add(modifyPath.leaf(), transform.getTransformedValue(original, operation));
267 }
268 } catch (JsonException je) {
269 throw new JsonValueException(operation, je);
270 }
271 }
272 }
273 },
274 REMOVE {
275
276 @Override
277 void execute(JsonValue original, JsonValue operation, JsonPatchValueTransformer transform) {
278 JsonPointer modifyPath = operation.get(PATH_PTR).expect(String.class).as(pointer());
279 JsonValue parent = parentValue(modifyPath, original);
280 String leaf = modifyPath.leaf();
281 if (parent == null) {
282
283 original.setObject(null);
284 } else {
285 if (!parent.isDefined(leaf)) {
286 throw new JsonValueException(operation, "value to remove not found");
287 }
288 try {
289 parent.remove(leaf);
290 } catch (JsonException je) {
291 throw new JsonValueException(operation, je);
292 }
293 }
294 }
295 },
296 REPLACE {
297
298 @Override
299 void execute(JsonValue original, JsonValue operation, JsonPatchValueTransformer transform) {
300 JsonPointer modifyPath = operation.get(PATH_PTR).expect(String.class).as(pointer());
301 JsonValue parent = parentValue(modifyPath, original);
302 if (parent != null) {
303
304 String leaf = modifyPath.leaf();
305 if (!parent.isDefined(leaf)) {
306 throw new JsonValueException(operation, "value to replace not found");
307 }
308 parent.put(leaf, transform.getTransformedValue(original, operation));
309 } else {
310
311 original.setObject(transform.getTransformedValue(original, operation));
312 }
313 }
314 },
315 MOVE {
316
317 @Override
318 void execute(JsonValue original, JsonValue operation, JsonPatchValueTransformer transform) {
319 JsonPointer sourcePath = operation.get(FROM_PTR).expect(String.class).as(pointer());
320 JsonPointer destPath = operation.get(PATH_PTR).expect(String.class).as(pointer());
321 JsonValue sourceParent = parentValue(sourcePath, original);
322 if (sourceParent == null) {
323 throw new JsonValueException(operation, "cannot move root object");
324 }
325 JsonValue object = sourceParent.get(sourcePath.leaf());
326 JsonValue destParent = parentValue(destPath, original);
327 if (destParent == null) {
328
329 original.setObject(object);
330 } else {
331 sourceParent.remove(sourcePath.leaf());
332 destParent.put(destPath.leaf(), object);
333 }
334 }
335 },
336 COPY {
337
338 @Override
339 void execute(JsonValue original, JsonValue operation, JsonPatchValueTransformer transform) {
340 JsonPointer sourcePath = operation.get(FROM_PTR).expect(String.class).as(pointer());
341 JsonPointer destPath = operation.get(PATH_PTR).expect(String.class).as(pointer());
342 JsonValue sourceParent = parentValue(sourcePath, original);
343 JsonValue object = sourceParent.get(sourcePath.leaf());
344 JsonValue destParent = parentValue(destPath, original);
345 if (destParent == null) {
346
347 original.setObject(object);
348 } else {
349 destParent.put(destPath.leaf(), object);
350 }
351 }
352 },
353 TEST {
354
355 @Override
356 void execute(JsonValue original, JsonValue operation, JsonPatchValueTransformer transform) {
357 JsonPointer testPath = operation.get(PATH_PTR).expect(String.class).as(pointer());
358 JsonValue testTarget = parentValue(testPath, original).get(testPath.leaf());
359 JsonValue testValue = new JsonValue(transform.getTransformedValue(original, operation));
360
361 if (diff(testTarget, testValue).asList().size() > 0) {
362 throw new JsonValueException(operation, "test failed");
363 }
364 }
365 };
366
367 void execute(JsonValue original, JsonValue operation, JsonPatchValueTransformer transform) {
368 throw new JsonValueException(original, "unsupported operation");
369 }
370
371 static PatchOperation valueOf(JsonValue op) {
372 return valueOf(op.expect(String.class).asString().toUpperCase());
373 }
374 }
375
376
377
378
379
380
381
382
383
384 private static JsonValue parentValue(JsonPointer pointer, JsonValue target) {
385 JsonValue result = null;
386 JsonPointer parent = pointer.parent();
387 if (parent != null) {
388 result = target.get(parent);
389 if (result == null) {
390 throw new JsonException("parent value not found");
391 }
392 }
393 return result;
394 }
395
396
397 private JsonPatch() {
398 }
399 }