source: josm/trunk/src/org/glassfish/json/JsonPatchImpl.java@ 13661

Last change on this file since 13661 was 13231, checked in by Don-vip, 6 years ago

see #15682 - upgrade to JSR 374 (JSON Processing) API 1.1.2

File size: 12.0 KB
Line 
1/*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright (c) 2015-2017 Oracle and/or its affiliates. All rights reserved.
5 *
6 * The contents of this file are subject to the terms of either the GNU
7 * General Public License Version 2 only ("GPL") or the Common Development
8 * and Distribution License("CDDL") (collectively, the "License"). You
9 * may not use this file except in compliance with the License. You can
10 * obtain a copy of the License at
11 * https://oss.oracle.com/licenses/CDDL+GPL-1.1
12 * or LICENSE.txt. See the License for the specific
13 * language governing permissions and limitations under the License.
14 *
15 * When distributing the software, include this License Header Notice in each
16 * file and include the License file at LICENSE.txt.
17 *
18 * GPL Classpath Exception:
19 * Oracle designates this particular file as subject to the "Classpath"
20 * exception as provided by Oracle in the GPL Version 2 section of the License
21 * file that accompanied this code.
22 *
23 * Modifications:
24 * If applicable, add the following below the License Header, with the fields
25 * enclosed by brackets [] replaced by your own identifying information:
26 * "Portions Copyright [year] [name of copyright owner]"
27 *
28 * Contributor(s):
29 * If you wish your version of this file to be governed by only the CDDL or
30 * only the GPL Version 2, indicate your decision by adding "[Contributor]
31 * elects to include this software in this distribution under the [CDDL or GPL
32 * Version 2] license." If you don't indicate a single choice of license, a
33 * recipient has the option to distribute your version of this file under
34 * either the CDDL, the GPL Version 2 or to extend the choice of license to
35 * its licensees as provided above. However, if you add GPL Version 2 code
36 * and therefore, elected the GPL Version 2 license, then the option applies
37 * only if the new code is made subject to such option by the copyright
38 * holder.
39 */
40
41package org.glassfish.json;
42
43import javax.json.*;
44import javax.json.JsonValue.ValueType;
45
46/**
47 * This class is an immutable representation of a JSON Patch as specified in
48 * <a href="http://tools.ietf.org/html/rfc6902">RFC 6902</a>.
49 * <p>A {@code JsonPatch} can be instantiated with {@link Json#createPatch(JsonArray)}
50 * by specifying the patch operations in a JSON Patch. Alternately, it
51 * can also be constructed with a {@link JsonPatchBuilder}.
52 * </p>
53 * The following illustrates both approaches.
54 * <p>1. Construct a JsonPatch with a JSON Patch.
55 * <pre>{@code
56 * JsonArray contacts = ... // The target to be patched
57 * JsonArray patch = ... ; // JSON Patch
58 * JsonPatch jsonpatch = Json.createPatch(patch);
59 * JsonArray result = jsonpatch.apply(contacts);
60 * } </pre>
61 * 2. Construct a JsonPatch with JsonPatchBuilder.
62 * <pre>{@code
63 * JsonPatchBuilder builder = Json.createPatchBuilder();
64 * JsonArray result = builder.add("/John/phones/office", "1234-567")
65 * .remove("/Amy/age")
66 * .build()
67 * .apply(contacts);
68 * } </pre>
69 *
70 * @since 1.1
71 */
72public class JsonPatchImpl implements JsonPatch {
73
74 private final JsonArray patch;
75
76 /**
77 * Constructs a JsonPatchImpl
78 * @param patch the JSON Patch
79 */
80 public JsonPatchImpl(JsonArray patch) {
81 this.patch = patch;
82 }
83
84 /**
85 * Compares this {@code JsonPatchImpl} with another object.
86 * @param obj the object to compare this {@code JsonPatchImpl} against
87 * @return true if the given object is a {@code JsonPatchImpl} with the same
88 * reference tokens as this one, false otherwise.
89 */
90 @Override
91 public boolean equals(Object obj) {
92 if (this == obj)
93 return true;
94 if (obj == null || obj.getClass() != JsonPatchImpl.class)
95 return false;
96 return patch.equals(((JsonPatchImpl)obj).patch);
97 }
98
99 /**
100 * Returns the hash code value for this {@code JsonPatchImpl}.
101 *
102 * @return the hash code value for this {@code JsonPatchImpl} object
103 */
104 @Override
105 public int hashCode() {
106 return patch.hashCode();
107 }
108
109 /**
110 * Returns the JSON Patch text
111 * @return the JSON Patch text
112 */
113 @Override
114 public String toString() {
115 return patch.toString();
116 }
117
118 /**
119 * Applies the patch operations to the specified {@code target}.
120 * The target is not modified by the patch.
121 *
122 * @param target the target to apply the patch operations
123 * @return the transformed target after the patch
124 * @throws JsonException if the supplied JSON Patch is malformed or if
125 * it contains references to non-existing members
126 */
127 @Override
128 public JsonStructure apply(JsonStructure target) {
129
130 JsonStructure result = target;
131
132 for (JsonValue operation: patch) {
133 if (operation.getValueType() != ValueType.OBJECT) {
134 throw new JsonException(JsonMessages.PATCH_MUST_BE_ARRAY());
135 }
136 result = apply(result, (JsonObject) operation);
137 }
138 return result;
139 }
140
141 @Override
142 public JsonArray toJsonArray() {
143 return patch;
144 }
145
146 /**
147 * Generates a JSON Patch from the source and target {@code JsonStructure}.
148 * The generated JSON Patch need not be unique.
149 * @param source the source
150 * @param target the target, must be the same type as the source
151 * @return a JSON Patch which when applied to the source, yields the target
152 */
153 public static JsonArray diff(JsonStructure source, JsonStructure target) {
154 return (new DiffGenerator()).diff(source, target);
155 }
156
157 /**
158 * Applies a JSON Patch operation to the target.
159 * @param target the target to apply the operation
160 * @param operation the JSON Patch operation
161 * @return the target after the patch
162 */
163 private JsonStructure apply(JsonStructure target, JsonObject operation) {
164
165 JsonPointer pointer = getPointer(operation, "path");
166 JsonPointer from;
167 switch (Operation.fromOperationName(operation.getString("op"))) {
168 case ADD:
169 return pointer.add(target, getValue(operation));
170 case REPLACE:
171 return pointer.replace(target, getValue(operation));
172 case REMOVE:
173 return pointer.remove(target);
174 case COPY:
175 from = getPointer(operation, "from");
176 return pointer.add(target, from.getValue(target));
177 case MOVE:
178 // Check if from is a proper prefix of path
179 String dest = operation.getString("path");
180 String src = operation.getString("from");
181 if (dest.startsWith(src) && src.length() < dest.length()) {
182 throw new JsonException(JsonMessages.PATCH_MOVE_PROPER_PREFIX(src, dest));
183 }
184 from = getPointer(operation, "from");
185 // Check if 'from' exists in target object
186 if (!from.containsValue(target)) {
187 throw new JsonException(JsonMessages.PATCH_MOVE_TARGET_NULL(src));
188 }
189 if (pointer.equals(from)) {
190 // nop
191 return target;
192 }
193 return pointer.add(from.remove(target), from.getValue(target));
194 case TEST:
195 if (! getValue(operation).equals(pointer.getValue(target))) {
196 throw new JsonException(JsonMessages.PATCH_TEST_FAILED(operation.getString("path"), getValue(operation).toString()));
197 }
198 return target;
199 default:
200 throw new JsonException(JsonMessages.PATCH_ILLEGAL_OPERATION(operation.getString("op")));
201 }
202 }
203
204 private JsonPointer getPointer(JsonObject operation, String member) {
205 JsonString pointerString = operation.getJsonString(member);
206 if (pointerString == null) {
207 missingMember(operation.getString("op"), member);
208 }
209 return Json.createPointer(pointerString.getString());
210 }
211
212 private JsonValue getValue(JsonObject operation) {
213 JsonValue value = operation.get("value");
214 if (value == null) {
215 missingMember(operation.getString("op"), "value");
216 }
217 return value;
218 }
219
220 private void missingMember(String op, String member) {
221 throw new JsonException(JsonMessages.PATCH_MEMBER_MISSING(op, member));
222 }
223
224 static class DiffGenerator {
225 private JsonPatchBuilder builder;
226
227 JsonArray diff(JsonStructure source, JsonStructure target) {
228 builder = Json.createPatchBuilder();
229 diff("", source, target);
230 return builder.build().toJsonArray();
231 }
232
233 private void diff(String path, JsonValue source, JsonValue target) {
234 if (source.equals(target)) {
235 return;
236 }
237 ValueType s = source.getValueType();
238 ValueType t = target.getValueType();
239 if (s == ValueType.OBJECT && t == ValueType.OBJECT) {
240 diffObject(path, (JsonObject) source, (JsonObject) target);
241 } else if (s == ValueType.ARRAY && t == ValueType.ARRAY) {
242 diffArray(path, (JsonArray) source, (JsonArray) target);
243 } else {
244 builder.replace(path, target);
245 }
246 }
247
248 private void diffObject(String path, JsonObject source, JsonObject target) {
249 source.forEach((key, value) -> {
250 if (target.containsKey(key)) {
251 diff(path + '/' + key, value, target.get(key));
252 } else {
253 builder.remove(path + '/' + key);
254 }
255 });
256 target.forEach((key, value) -> {
257 if (! source.containsKey(key)) {
258 builder.add(path + '/' + key, value);
259 }
260 });
261 }
262
263 /*
264 * For array element diff, find the longest common subsequence, per
265 * http://en.wikipedia.org/wiki/Longest_common_subsequence_problem .
266 * We modify the algorithm to generate a replace if possible.
267 */
268 private void diffArray(String path, JsonArray source, JsonArray target) {
269 /* The array c keeps track of length of the subsequence. To avoid
270 * computing the equality of array elements again, we
271 * left shift its value by 1, and use the low order bit to mark
272 * that two items are equal.
273 */
274 int m = source.size();
275 int n = target.size();
276 int [][] c = new int[m+1][n+1];
277 for (int i = 0; i < m+1; i++)
278 c[i][0] = 0;
279 for (int i = 0; i < n+1; i++)
280 c[0][i] = 0;
281 for (int i = 0; i < m; i++) {
282 for (int j = 0; j < n; j++) {
283 if (source.get(i).equals(target.get(j))) {
284 c[i+1][j+1] = ((c[i][j]) & ~1) + 3;
285 // 3 = (1 << 1) | 1;
286 } else {
287 c[i+1][j+1] = Math.max(c[i+1][j], c[i][j+1]) & ~1;
288 }
289 }
290 }
291
292 int i = m;
293 int j = n;
294 while (i > 0 || j > 0) {
295 if (i == 0) {
296 j--;
297 builder.add(path + '/' + j, target.get(j));
298 } else if (j == 0) {
299 i--;
300 builder.remove(path + '/' + i);
301 } else if ((c[i][j] & 1) == 1) {
302 i--; j--;
303 } else {
304 int f = c[i][j-1] >> 1;
305 int g = c[i-1][j] >> 1;
306 if (f > g) {
307 j--;
308 builder.add(path + '/' + j, target.get(j));
309 } else if (f < g) {
310 i--;
311 builder.remove(path + '/' + i);
312 } else { // f == g) {
313 i--; j--;
314 diff(path + '/' + i, source.get(i), target.get(j));
315 }
316 }
317 }
318 }
319 }
320}
321
Note: See TracBrowser for help on using the repository browser.