|
49 | 49 | * </ul>
|
50 | 50 | *
|
51 | 51 | * Array values generate operations in the order of elements. Factorizing is
|
52 |
| - * done to merge add and remove into move operations if values are equivalent. |
53 |
| - * Copy and test operations are not generated.</p> |
| 52 | + * done to merge add and remove into move operations and convert duplicate |
| 53 | + * add to copy operations if values are equivalent. Test operations are not |
| 54 | + * generated.</p> |
54 | 55 | *
|
55 | 56 | * <p>Note that due to the way {@link JsonNode} is implemented, this class is
|
56 | 57 | * inherently <b>not</b> thread safe (since {@code JsonNode} is mutable). It is
|
@@ -327,9 +328,11 @@ else if (lengths[x][y] == lengths[x][y - 1])
|
327 | 328 |
|
328 | 329 | /**
|
329 | 330 | * Factorize list of ordered differences. Where removed values are
|
330 |
| - * equivalent to added values, merge add and remove to move operation |
331 |
| - * differences. Because remove operation differences are relocated in |
332 |
| - * the process of merging, other differences can be side effected. |
| 331 | + * equivalent to added values, merge add and remove to move |
| 332 | + * differences. Because remove differences are relocated in the |
| 333 | + * process of merging, other differences can be side effected. |
| 334 | + * Add differences with equivalent values to previous add |
| 335 | + * differences are converted to copy differences. |
333 | 336 | *
|
334 | 337 | * @param diffs list of ordered differences.
|
335 | 338 | */
|
@@ -364,7 +367,8 @@ private static void factorizeDiffs(final List<Diff> diffs)
|
364 | 367 | // are performed out of the original diff ordering just before the
|
365 | 368 | // paired add when converted to a move. consequently, moves that are
|
366 | 369 | // deferred or advanced must be tracked to allow proper diff array
|
367 |
| - // index adjustments for diffs operating on the same arrays. |
| 370 | + // index adjustments for diffs operating on the same arrays. diff |
| 371 | + // order is assumed to be acyclic and linearly processing arrays. |
368 | 372 | final List<Diff> deferredArrayRemoves = Lists.newArrayList();
|
369 | 373 | final List<Diff> advancedArrayRemoves = Lists.newArrayList();
|
370 | 374 | for (final Iterator<Diff> diffIter = diffs.iterator();
|
@@ -445,6 +449,37 @@ private static void factorizeDiffs(final List<Diff> diffs)
|
445 | 449 | diff.secondArrayIndex = adjustSecondArrayIndex(deferredArrayRemoves,
|
446 | 450 | diff.arrayPath, diff.secondArrayIndex);
|
447 | 451 | }
|
| 452 | + |
| 453 | + // Factorize add diffs with equivalent non-empty object or array |
| 454 | + // values into copy diffs; from paths for copy diffs can be set using |
| 455 | + // previous add diff paths and/or array paths because diff order is |
| 456 | + // acyclic and immutable for this factorization. The only exception |
| 457 | + // to this rule are adds that append to arrays: these have no concrete |
| 458 | + // path that can serve as a copy diff from path. |
| 459 | + final List<Diff> addDiffs = Lists.newArrayList(); |
| 460 | + for (final Diff diff: diffs) |
| 461 | + if (diff.operation == DiffOperation.ADD) |
| 462 | + if (diff.value.size() > 0) { |
| 463 | + // check add diff value against list of previous add diffs |
| 464 | + Diff addDiff = null; |
| 465 | + for (final Diff testAddDiff: addDiffs) |
| 466 | + if (EQUIVALENCE.equivalent(diff.value, testAddDiff.value)) { |
| 467 | + addDiff = testAddDiff; |
| 468 | + break; |
| 469 | + } |
| 470 | + // if not found previously, save add diff, (if not appending |
| 471 | + // to an array which can have no concrete from path), and continue |
| 472 | + if (addDiff == null) { |
| 473 | + if (diff.arrayPath == null || diff.secondArrayIndex != -1) |
| 474 | + addDiffs.add(diff); |
| 475 | + continue; |
| 476 | + } |
| 477 | + // previous add diff found by value: convert add diff to copy |
| 478 | + // diff with from path set to concrete add diff path |
| 479 | + diff.operation = DiffOperation.COPY; |
| 480 | + diff.fromPath = addDiff.arrayPath != null ? addDiff.getSecondArrayPath() |
| 481 | + : addDiff.path; |
| 482 | + } |
448 | 483 | }
|
449 | 484 |
|
450 | 485 | /**
|
@@ -512,6 +547,7 @@ private enum DiffOperation
|
512 | 547 | REMOVE("remove"),
|
513 | 548 | REPLACE("replace"),
|
514 | 549 | MOVE("move"),
|
| 550 | + COPY("copy"), |
515 | 551 | ;
|
516 | 552 |
|
517 | 553 | private final String opName;
|
@@ -577,15 +613,16 @@ private JsonNode asJsonPatch()
|
577 | 613 | : path;
|
578 | 614 | final ObjectNode patch = operation.newOp(ptr);
|
579 | 615 | /*
|
580 |
| - * A remomve only has a path |
| 616 | + * A remove only has a path |
581 | 617 | */
|
582 | 618 | if (operation == DiffOperation.REMOVE)
|
583 | 619 | return patch;
|
584 | 620 | /*
|
585 | 621 | * A move has a "source path" (the "from" member), other defined
|
586 | 622 | * operations (add and replace) have a value instead.
|
587 | 623 | */
|
588 |
| - if (operation == DiffOperation.MOVE) |
| 624 | + if (operation == DiffOperation.MOVE |
| 625 | + || operation == DiffOperation.COPY) |
589 | 626 | patch.put("from", fromPath.toString());
|
590 | 627 | else
|
591 | 628 | patch.put("value", value);
|
|
0 commit comments