Skip to content

Commit 0049416

Browse files
committed
1 parent dba15de commit 0049416

19 files changed

+362
-197
lines changed

NOTICE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
bsonpatch library
2-
Copyright 2017 eBay, Inc.
2+
Copyright 2017,2018 eBay, Inc.
33

44
This product includes software developed at
55
eBay, Inc. (https://www.ebay.com/).

README.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ The code here was ported (copied, renamed, repackaged, modified) from the [zjson
2020

2121
### How to use:
2222

23-
### Current Version : 0.3.6
23+
### Current Version : 0.4.1
2424

2525
Add following to `<dependencies/>` section of your pom.xml -
2626

2727
```xml
2828
<dependency>
2929
<groupId>com.ebay.bsonpatch</groupId>
3030
<artifactId>bsonpatch</artifactId>
31-
<version>0.3.6</version>
31+
<version>0.4.1</version>
3232
</dependency>
3333
```
3434

@@ -50,18 +50,18 @@ The algorithm which computes this JsonPatch currently generates following operat
5050
- COPY
5151
- TEST
5252

53-
## To turn off MOVE & COPY Operations
54-
```xml
55-
EnumSet<DiffFlags> flags = DiffFlags.dontNormalizeOpIntoMoveAndCopy().clone()
56-
BsonArray patch = BsonDiff.asJson(BsonValue source, BsonValue target, flags)
57-
```
58-
5953
### Apply Json Patch
6054
```xml
6155
BsonValue target = BsonPatch.apply(BsonArray patch, BsonValue source);
6256
```
6357
Given a Patch, it apply it to source Bson and return a target Bson which can be ( Bson object or array or value ). This operation performed on a clone of source Bson ( thus, source Bson is untouched and can be used further).
6458

59+
## To turn off MOVE & COPY Operations
60+
```xml
61+
EnumSet<DiffFlags> flags = DiffFlags.dontNormalizeOpIntoMoveAndCopy().clone()
62+
BsonArray patch = BsonDiff.asJson(BsonValue source, BsonValue target, flags)
63+
```
64+
6565
### Example
6666
First Json
6767
```json
@@ -89,6 +89,8 @@ a new instance with the patch applied, leaving the `source` unchanged.
8989
1. 100+ selective hardcoded different input jsons , with their driver test classes present under /test directory.
9090
2. Apart from selective input, a deterministic random json generator is present under ( TestDataGenerator.java ), and its driver test class method is JsonDiffTest.testGeneratedJsonDiff().
9191

92+
#### *** Tests can only show presence of bugs and not their absence ***
93+
9294
## Get Involved
9395

9496
* **Contributing**: Pull requests are welcome!

pom.xml

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.ebay.bsonpatch</groupId>
88
<artifactId>bsonpatch</artifactId>
9-
<version>0.3.6-SNAPSHOT</version>
9+
<version>0.4.1</version>
1010
<packaging>jar</packaging>
1111

1212
<name>${project.groupId}:${project.artifactId}</name>
@@ -129,19 +129,7 @@
129129
<dependency>
130130
<groupId>org.mongodb</groupId>
131131
<artifactId>mongo-java-driver</artifactId>
132-
<version>3.5.0</version>
133-
</dependency>
134-
<!-- For functional operators used in computing diffs -->
135-
<dependency>
136-
<groupId>com.google.guava</groupId>
137-
<artifactId>guava</artifactId>
138-
<version>20.0</version>
139-
</dependency>
140-
<!-- included to compute longest common subsequence of arrays in diff calculation -->
141-
<dependency>
142-
<groupId>org.apache.commons</groupId>
143-
<artifactId>commons-collections4</artifactId>
144-
<version>4.1</version>
132+
<version>3.6.1</version>
145133
</dependency>
146134
<!-- For IOUtils.toString(inputStream, charset) and StringBuilderWriter -->
147135
<dependency>

src/main/java/com/ebay/bsonpatch/BsonDiff.java

Lines changed: 29 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -23,60 +23,38 @@
2323
import java.util.EnumSet;
2424
import java.util.HashMap;
2525
import java.util.Iterator;
26-
import java.util.LinkedList;
2726
import java.util.List;
2827
import java.util.Map;
2928

30-
import org.apache.commons.collections4.ListUtils;
3129
import org.bson.BsonArray;
3230
import org.bson.BsonDocument;
3331
import org.bson.BsonString;
3432
import org.bson.BsonValue;
3533

36-
import com.google.common.base.Function;
37-
import com.google.common.base.Joiner;
38-
import com.google.common.base.Preconditions;
39-
import com.google.common.collect.Iterables;
40-
import com.google.common.collect.Lists;
4134

4235
public final class BsonDiff {
4336

44-
private static final EncodePathFunction ENCODE_PATH_FUNCTION = new EncodePathFunction();
45-
4637
private BsonDiff() {
4738
}
4839

49-
private final static class EncodePathFunction implements Function<Object, String> {
50-
@Override
51-
public String apply(Object object) {
52-
String path = object.toString(); // see http://tools.ietf.org/html/rfc6901#section-4
53-
return path.replaceAll("~", "~0").replaceAll("/", "~1");
54-
}
55-
}
56-
5740
public static BsonArray asBson(final BsonValue source, final BsonValue target) {
5841
return asBson(source, target, DiffFlags.defaults());
5942
}
6043

6144
public static BsonArray asBson(final BsonValue source, final BsonValue target, EnumSet<DiffFlags> flags) {
6245
final List<Diff> diffs = new ArrayList<Diff>();
63-
List<Object> path = new LinkedList<Object>();
64-
/*
65-
* generating diffs in the order of their occurrence
66-
*/
46+
List<Object> path = new ArrayList<Object>(0);
47+
48+
// generating diffs in the order of their occurrence
6749
generateDiffs(diffs, path, source, target);
6850

6951
if (!flags.contains(DiffFlags.OMIT_MOVE_OPERATION)) {
70-
/*
71-
* Merging remove & add to move operation
72-
*/
52+
// Merging remove & add to move operation
7353
compactDiffs(diffs);
7454
}
7555

7656
if (!flags.contains(DiffFlags.OMIT_COPY_OPERATION)) {
77-
/*
78-
* Introduce copy operation
79-
*/
57+
// Introduce copy operation
8058
introduceCopyOperation(source, target, diffs);
8159
}
8260

@@ -91,7 +69,7 @@ private static void introduceCopyOperation(BsonValue source, BsonValue target, L
9169
Map<BsonValue, List<Object>> unchangedValues = getUnchangedPart(source, target);
9270
for (int i = 0; i < diffs.size(); i++) {
9371
Diff diff = diffs.get(i);
94-
if (Operation.ADD.equals(diff.getOperation())) {
72+
if (Operation.ADD == diff.getOperation()) {
9573
List<Object> matchingValuePath = getMatchingValuePath(unchangedValues, diff.getValue());
9674
if (matchingValuePath != null && isAllowed(matchingValuePath, diff.getPath())) {
9775
diffs.set(i, new Diff(Operation.COPY, matchingValuePath, diff.getPath()));
@@ -138,7 +116,7 @@ private static boolean isAllowed(List<Object> source, List<Object> destination)
138116

139117
private static Map<BsonValue, List<Object>> getUnchangedPart(BsonValue source, BsonValue target) {
140118
Map<BsonValue, List<Object>> unchangedValues = new HashMap<BsonValue, List<Object>>();
141-
computeUnchangedValues(unchangedValues, Lists.newArrayList(), source, target);
119+
computeUnchangedValues(unchangedValues, new ArrayList<Object>(), source, target);
142120
return unchangedValues;
143121
}
144122

@@ -158,7 +136,7 @@ private static void computeUnchangedValues(Map<BsonValue, List<Object>> unchange
158136
case ARRAY:
159137
computeArray(unchangedValues, path, source, target);
160138
default:
161-
/* nothing */
139+
/* nothing */
162140
}
163141
}
164142
}
@@ -192,8 +170,8 @@ private static void compactDiffs(List<Diff> diffs) {
192170
Diff diff1 = diffs.get(i);
193171

194172
// if not remove OR add, move to next diff
195-
if (!(Operation.REMOVE.equals(diff1.getOperation()) ||
196-
Operation.ADD.equals(diff1.getOperation()))) {
173+
if (!(Operation.REMOVE == diff1.getOperation() ||
174+
Operation.ADD == diff1.getOperation())) {
197175
continue;
198176
}
199177

@@ -204,13 +182,13 @@ private static void compactDiffs(List<Diff> diffs) {
204182
}
205183

206184
Diff moveDiff = null;
207-
if (Operation.REMOVE.equals(diff1.getOperation()) &&
208-
Operation.ADD.equals(diff2.getOperation())) {
185+
if (Operation.REMOVE == diff1.getOperation() &&
186+
Operation.ADD == diff2.getOperation()) {
209187
computeRelativePath(diff2.getPath(), i + 1, j - 1, diffs);
210188
moveDiff = new Diff(Operation.MOVE, diff1.getPath(), diff2.getPath());
211189

212-
} else if (Operation.ADD.equals(diff1.getOperation()) &&
213-
Operation.REMOVE.equals(diff2.getOperation())) {
190+
} else if (Operation.ADD == diff1.getOperation() &&
191+
Operation.REMOVE == diff2.getOperation()) {
214192
computeRelativePath(diff2.getPath(), i, j - 1, diffs); // diff1's add should also be considered
215193
moveDiff = new Diff(Operation.MOVE, diff2.getPath(), diff1.getPath());
216194
}
@@ -226,14 +204,14 @@ private static void compactDiffs(List<Diff> diffs) {
226204
//Note : only to be used for arrays
227205
//Finds the longest common Ancestor ending at Array
228206
private static void computeRelativePath(List<Object> path, int startIdx, int endIdx, List<Diff> diffs) {
229-
List<Integer> counters = new ArrayList<Integer>();
207+
List<Integer> counters = new ArrayList<Integer>(path.size());
230208

231209
resetCounters(counters, path.size());
232210

233211
for (int i = startIdx; i <= endIdx; i++) {
234212
Diff diff = diffs.get(i);
235213
//Adjust relative path according to #ADD and #Remove
236-
if (Operation.ADD.equals(diff.getOperation()) || Operation.REMOVE.equals(diff.getOperation())) {
214+
if (Operation.ADD == diff.getOperation() || Operation.REMOVE == diff.getOperation()) {
237215
updatePath(path, diff, counters);
238216
}
239217
}
@@ -277,10 +255,10 @@ private static void updatePath(List<Object> path, Diff pseudo, List<Integer> cou
277255
}
278256

279257
private static void updateCounters(Diff pseudo, int idx, List<Integer> counters) {
280-
if (Operation.ADD.equals(pseudo.getOperation())) {
258+
if (Operation.ADD == pseudo.getOperation()) {
281259
counters.set(idx, counters.get(idx) - 1);
282260
} else {
283-
if (Operation.REMOVE.equals(pseudo.getOperation())) {
261+
if (Operation.REMOVE == pseudo.getOperation()) {
284262
counters.set(idx, counters.get(idx) + 1);
285263
}
286264
}
@@ -302,20 +280,22 @@ private static BsonDocument getBsonNode(Diff diff, EnumSet<DiffFlags> flags) {
302280
switch (diff.getOperation()) {
303281
case MOVE:
304282
case COPY:
305-
bsonNode.put(Constants.FROM, new BsonString(getArrayNodeRepresentation(diff.getPath()))); // required {from} only in case of Move Operation
306-
bsonNode.put(Constants.PATH, new BsonString(getArrayNodeRepresentation(diff.getToPath()))); // destination Path
283+
bsonNode.put(Constants.FROM, new BsonString(PathUtils.getPathRepresentation(diff.getPath()))); // required {from} only in case of Move Operation
284+
bsonNode.put(Constants.PATH, new BsonString(PathUtils.getPathRepresentation(diff.getToPath()))); // destination Path
307285
break;
308286

309287
case REMOVE:
310-
bsonNode.put(Constants.PATH, new BsonString(getArrayNodeRepresentation(diff.getPath())));
288+
bsonNode.put(Constants.PATH, new BsonString(PathUtils.getPathRepresentation(diff.getPath())));
311289
if (!flags.contains(DiffFlags.OMIT_VALUE_ON_REMOVE))
312290
bsonNode.put(Constants.VALUE, diff.getValue());
313291
break;
314-
315-
case ADD:
316292
case REPLACE:
293+
if (flags.contains(DiffFlags.ADD_ORIGINAL_VALUE_ON_REPLACE)) {
294+
bsonNode.put(Constants.FROM_VALUE, diff.getSrcValue());
295+
}
296+
case ADD:
317297
case TEST:
318-
bsonNode.put(Constants.PATH, new BsonString(getArrayNodeRepresentation(diff.getPath())));
298+
bsonNode.put(Constants.PATH, new BsonString(PathUtils.getPathRepresentation(diff.getPath())));
319299
bsonNode.put(Constants.VALUE, diff.getValue());
320300
break;
321301

@@ -327,12 +307,6 @@ private static BsonDocument getBsonNode(Diff diff, EnumSet<DiffFlags> flags) {
327307
return bsonNode;
328308
}
329309

330-
private static String getArrayNodeRepresentation(List<Object> path) {
331-
return Joiner.on('/').appendTo(new StringBuilder().append('/'),
332-
Iterables.transform(path, ENCODE_PATH_FUNCTION)).toString();
333-
}
334-
335-
336310
private static void generateDiffs(List<Diff> diffs, List<Object> path, BsonValue source, BsonValue target) {
337311
if (!source.equals(target)) {
338312
if (source.isArray() && target.isArray()) {
@@ -344,7 +318,7 @@ private static void generateDiffs(List<Diff> diffs, List<Object> path, BsonValue
344318
} else {
345319
//can be replaced
346320

347-
diffs.add(Diff.generateDiff(Operation.REPLACE, path, target));
321+
diffs.add(Diff.generateDiff(Operation.REPLACE, path, source, target));
348322
}
349323
}
350324
}
@@ -452,17 +426,13 @@ private static void compareDocuments(List<Diff> diffs, List<Object> path, BsonVa
452426
}
453427

454428
private static List<Object> getPath(List<Object> path, Object key) {
455-
List<Object> toReturn = new ArrayList<Object>();
429+
List<Object> toReturn = new ArrayList<Object>(path.size() + 1);
456430
toReturn.addAll(path);
457431
toReturn.add(key);
458432
return toReturn;
459433
}
460434

461435
private static List<BsonValue> getLCS(final BsonValue first, final BsonValue second) {
462-
463-
Preconditions.checkArgument(first.isArray(), "LCS can only work on BSON arrays");
464-
Preconditions.checkArgument(second.isArray(), "LCS can only work on BSON arrays");
465-
466-
return ListUtils.longestCommonSubsequence(Lists.newArrayList(first.asArray()), Lists.newArrayList(second.asArray()));
436+
return InternalUtils.longestCommonSubsequence(InternalUtils.toList(first.asArray()), InternalUtils.toList(second.asArray()));
467437
}
468438
}

src/main/java/com/ebay/bsonpatch/BsonPatch.java

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,10 @@
2727
import org.bson.BsonNull;
2828
import org.bson.BsonValue;
2929

30-
import com.google.common.base.Function;
31-
import com.google.common.base.Splitter;
32-
import com.google.common.collect.Iterables;
33-
import com.google.common.collect.Lists;
34-
3530
public final class BsonPatch {
3631

37-
private static final DecodePathFunction DECODE_PATH_FUNCTION = new DecodePathFunction();
38-
3932
private BsonPatch() {}
4033

41-
private final static class DecodePathFunction implements Function<String, String> {
42-
@Override
43-
public String apply(String path) {
44-
return path.replaceAll("~1", "/").replaceAll("~0", "~"); // see http://tools.ietf.org/html/rfc6901#section-4
45-
}
46-
}
47-
4834
private static BsonValue getPatchAttr(BsonValue bsonNode, String attr) {
4935
BsonValue child = bsonNode.asDocument().get(attr);
5036
if (child == null)
@@ -68,7 +54,7 @@ private static void process(BsonArray patch, BsonPatchProcessor processor, EnumS
6854
BsonValue bsonNode = operations.next();
6955
if (!bsonNode.isDocument()) throw new InvalidBsonPatchException("Invalid BSON Patch payload (not an object)");
7056
Operation operation = Operation.fromRfcName(getPatchAttr(bsonNode, Constants.OP).asString().getValue().replaceAll("\"", ""));
71-
List<String> path = getPath(getPatchAttr(bsonNode, Constants.PATH));
57+
List<String> path = PathUtils.getPath(getPatchAttr(bsonNode, Constants.PATH));
7258

7359
switch (operation) {
7460
case REMOVE: {
@@ -97,13 +83,13 @@ private static void process(BsonArray patch, BsonPatchProcessor processor, EnumS
9783
}
9884

9985
case MOVE: {
100-
List<String> fromPath = getPath(getPatchAttr(bsonNode, Constants.FROM));
86+
List<String> fromPath = PathUtils.getPath(getPatchAttr(bsonNode, Constants.FROM));
10187
processor.move(fromPath, path);
10288
break;
10389
}
10490

10591
case COPY: {
106-
List<String> fromPath = getPath(getPatchAttr(bsonNode, Constants.FROM));
92+
List<String> fromPath = PathUtils.getPath(getPatchAttr(bsonNode, Constants.FROM));
10793
processor.copy(fromPath, path);
10894
break;
10995
}
@@ -130,7 +116,7 @@ public static void validate(BsonArray patch) throws InvalidBsonPatchException {
130116
}
131117

132118
public static BsonValue apply(BsonArray patch, BsonValue source, EnumSet<CompatibilityFlags> flags) throws BsonPatchApplicationException {
133-
CopyingApplyProcessor processor = new CopyingApplyProcessor(source);
119+
CopyingApplyProcessor processor = new CopyingApplyProcessor(source, flags);
134120
process(patch, processor, flags);
135121
return processor.result();
136122
}
@@ -139,17 +125,13 @@ public static BsonValue apply(BsonArray patch, BsonValue source) throws BsonPatc
139125
return apply(patch, source, CompatibilityFlags.defaults());
140126
}
141127

142-
public static void applyInPlace(BsonArray patch, BsonValue source){
128+
public static void applyInPlace(BsonArray patch, BsonValue source) {
143129
applyInPlace(patch, source, CompatibilityFlags.defaults());
144130
}
145131

146-
public static void applyInPlace(BsonArray patch, BsonValue source, EnumSet<CompatibilityFlags> flags){
147-
InPlaceApplyProcessor processor = new InPlaceApplyProcessor(source);
132+
public static void applyInPlace(BsonArray patch, BsonValue source, EnumSet<CompatibilityFlags> flags) {
133+
InPlaceApplyProcessor processor = new InPlaceApplyProcessor(source, flags);
148134
process(patch, processor, flags);
149135
}
150136

151-
private static List<String> getPath(BsonValue path) {
152-
List<String> paths = Splitter.on('/').splitToList(path.asString().getValue().replaceAll("\"", ""));
153-
return Lists.newArrayList(Iterables.transform(paths, DECODE_PATH_FUNCTION));
154-
}
155137
}

0 commit comments

Comments
 (0)