Skip to content

Commit 56961a3

Browse files
committed
refactor: patch matching management
1 parent c31db00 commit 56961a3

8 files changed

Lines changed: 560 additions & 173 deletions

File tree

src/main/java/com/deblock/jsondiff/matcher/IgnoringFieldMatcher.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,18 @@
66

77
import java.util.Arrays;
88
import java.util.List;
9-
import java.util.regex.Pattern;
109

1110
public class IgnoringFieldMatcher implements PartialJsonMatcher {
12-
private final List<Pattern> fieldsToIgnore;
11+
private final List<PathMatcher> fieldsToIgnore;
1312

14-
public IgnoringFieldMatcher(List<Pattern> patterns) {
15-
this.fieldsToIgnore = patterns;
13+
public IgnoringFieldMatcher(List<String> paths) {
14+
this.fieldsToIgnore = paths.stream()
15+
.map(PathMatcher::from)
16+
.toList();
1617
}
1718

18-
public IgnoringFieldMatcher(Pattern... patterns) {
19-
this.fieldsToIgnore = Arrays.stream(patterns).toList();
19+
public IgnoringFieldMatcher(String ...paths) {
20+
this(Arrays.stream(paths).toList());
2021
}
2122

2223
@Override
@@ -26,7 +27,6 @@ public JsonDiff jsonDiff(Path path, JsonNode expectedJson, JsonNode receivedJson
2627

2728
@Override
2829
public boolean manage(Path path, JsonNode expected, JsonNode received) {
29-
String stringPath = path.toString();
30-
return fieldsToIgnore.stream().anyMatch(pattern -> pattern.matcher(stringPath).matches());
30+
return fieldsToIgnore.stream().anyMatch(pattern -> pattern.match(path));
3131
}
3232
}

src/main/java/com/deblock/jsondiff/matcher/Path.java

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,88 @@
22

33
import java.util.Objects;
44

5+
/**
6+
* Represents a JSON path (e.g., $.property.0.subproperty).
7+
* Stored in reverse order (last element at head) for O(1) add operations
8+
* and efficient end-matching in PathMatcher.
9+
*/
510
public class Path {
611
public static final Path ROOT = new Path();
712

8-
public final PathItem property;
9-
public final Path next;
13+
private final PathItem last;
14+
private final Path previous;
1015

1116
public Path() {
1217
this(null, null);
1318
}
1419

15-
private Path(PathItem property, Path next) {
16-
this.property = property;
17-
this.next = next;
20+
private Path(PathItem last, Path previous) {
21+
this.last = last;
22+
this.previous = previous;
1823
}
1924

20-
private Path(PathItem property) {
21-
this.property = property;
22-
this.next = null;
25+
public Path add(PathItem item) {
26+
if (this.last == null) {
27+
return new Path(item, null);
28+
}
29+
return new Path(item, this);
2330
}
2431

25-
public Path add(PathItem item) {
26-
if (this.next == null) {
27-
return new Path(this.property, new Path(item));
28-
} else {
29-
return new Path(this.property, this.next.add(item));
32+
public PathItem item() {
33+
return last;
34+
}
35+
36+
/**
37+
* Returns the path without its last element.
38+
*/
39+
public Path previous() {
40+
return previous == null ? ROOT : previous;
41+
}
42+
43+
/**
44+
* Returns the path items in natural order (from root to leaf).
45+
* This is useful for traversing the path from start to end.
46+
*/
47+
public java.util.List<PathItem> toList() {
48+
java.util.List<PathItem> result = new java.util.ArrayList<>();
49+
collectItems(result);
50+
return result;
51+
}
52+
53+
private void collectItems(java.util.List<PathItem> result) {
54+
if (last == null) return;
55+
if (previous != null) {
56+
previous.collectItems(result);
3057
}
58+
result.add(last);
3159
}
3260

61+
@Override
3362
public String toString() {
34-
return ((this.property == null) ? "$" : this.property) +
35-
((this.next == null) ? "" : "." + this.next);
63+
StringBuilder sb = new StringBuilder("$");
64+
appendReversed(sb);
65+
return sb.toString();
66+
}
67+
68+
private void appendReversed(StringBuilder sb) {
69+
if (last == null) return;
70+
if (previous != null) {
71+
previous.appendReversed(sb);
72+
}
73+
sb.append(".").append(last);
3674
}
3775

3876
@Override
3977
public boolean equals(Object o) {
4078
if (this == o) return true;
4179
if (o == null || getClass() != o.getClass()) return false;
4280
Path path = (Path) o;
43-
return Objects.equals(property, path.property) && Objects.equals(next, path.next);
81+
return Objects.equals(last, path.last) && Objects.equals(previous, path.previous);
4482
}
4583

4684
@Override
4785
public int hashCode() {
48-
return Objects.hash(property, next);
86+
return Objects.hash(last, previous);
4987
}
5088

5189
public interface PathItem {

src/main/java/com/deblock/jsondiff/matcher/PathMatcher.java

Lines changed: 51 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,102 @@
11
package com.deblock.jsondiff.matcher;
22

3-
43
public class PathMatcher {
5-
public final PathMatcher.PathMatcherItem property;
6-
public final PathMatcher next;
4+
public final PathMatcherItem last;
5+
public final PathMatcher previous;
76

87
public static PathMatcher from(String path) {
98
PathMatcher matcher = new PathMatcher();
10-
for (String part: path.split("\\.")) {
9+
for (String part : path.split("\\.")) {
10+
if (part.isEmpty()) {
11+
throw new IllegalArgumentException("path matcher part can not be empty");
12+
}
1113
if (part.endsWith("]")) {
1214
String index = part.substring(part.lastIndexOf("[") + 1, part.length() - 1);
13-
matcher = matcher.add(new PathMatcherItem.ObjectProperty(part.substring(0, part.lastIndexOf("["))));
14-
if ("*".equals(index)) {
15-
matcher = matcher.add(new PathMatcherItem.WilcardMatcherItem(Path.PathItem.ArrayIndex.class));
16-
} else {
17-
matcher = matcher.add(new PathMatcherItem.ArrayIndex(Integer.parseInt(index)));
18-
}
19-
} else if ("*".equals(part)) {
20-
matcher = matcher.add(new PathMatcherItem.WilcardMatcherItem(Path.PathItem.ObjectProperty.class));
15+
matcher = matcher.add(PathMatcherItem.ofProperty(part.substring(0, part.lastIndexOf("["))));
16+
matcher = matcher.add(PathMatcherItem.ofArrayIndex(index));
2117
} else {
22-
matcher = matcher.add(new PathMatcher.PathMatcherItem.ObjectProperty(part));
18+
matcher = matcher.add(PathMatcherItem.ofProperty(part));
2319
}
2420
}
2521
return matcher;
2622
}
2723

28-
public PathMatcher() {
24+
private PathMatcher() {
2925
this(null, null);
3026
}
3127

32-
private PathMatcher(PathMatcher.PathMatcherItem property, PathMatcher next) {
33-
this.property = property;
34-
this.next = next;
28+
private PathMatcher(PathMatcherItem last, PathMatcher previous) {
29+
this.last = last;
30+
this.previous = previous;
3531
}
3632

37-
private PathMatcher(PathMatcher.PathMatcherItem property) {
38-
this.property = property;
39-
this.next = null;
40-
}
41-
42-
public PathMatcher add(PathMatcher.PathMatcherItem item) {
43-
if (this.next == null) {
44-
return new PathMatcher(this.property, new PathMatcher(item));
45-
} else {
46-
return new PathMatcher(this.property, this.next.add(item));
33+
private PathMatcher add(PathMatcherItem item) {
34+
if (this.last == null) {
35+
return new PathMatcher(item, null);
4736
}
37+
return new PathMatcher(item, this);
4838
}
4939

5040
public boolean match(Path path) {
51-
int pathLength = length(path);
52-
int matcherLength = length();
41+
if (this.last == null) {
42+
return true;
43+
}
5344

54-
if (matcherLength > pathLength) {
45+
if (path == null || path.item() == null) {
5546
return false;
5647
}
5748

58-
// Align path to match from the end
59-
Path alignedPath = path;
60-
for (int i = 0; i < pathLength - matcherLength; i++) {
61-
alignedPath = alignedPath.next;
49+
if (!this.last.match(path.item())) {
50+
return false;
6251
}
6352

64-
return matchFromHere(alignedPath);
65-
}
66-
67-
private int length() {
68-
int count = 0;
69-
PathMatcher current = this;
70-
while (current != null && current.property != null) {
71-
count++;
72-
current = current.next;
53+
if (this.previous == null) {
54+
return true;
7355
}
74-
return count;
75-
}
7656

77-
private int length(Path path) {
78-
int count = 0;
79-
Path current = path;
80-
while (current != null && current.property != null) {
81-
count++;
82-
current = current.next;
83-
}
84-
return count;
57+
return this.previous.match(path.previous());
8558
}
8659

87-
private boolean matchFromHere(Path path) {
88-
if (this.property == null) {
89-
return next == null || next.matchFromHere(path);
90-
}
91-
if (path == null || path.property == null) {
92-
return false;
93-
}
94-
if (this.property.match(path.property)) {
95-
if (next == null) {
96-
return path.next == null || path.next.property == null;
97-
}
98-
return path.next != null && next.matchFromHere(path.next);
99-
}
100-
return false;
60+
@Override
61+
public String toString() {
62+
StringBuilder sb = new StringBuilder("$");
63+
appendReversed(sb);
64+
return sb.toString();
10165
}
10266

103-
public String toString() {
104-
return ((this.property == null) ? "$" : this.property) +
105-
((this.next == null) ? "" : "." + this.next);
67+
private void appendReversed(StringBuilder sb) {
68+
if (last == null) return;
69+
if (previous != null) {
70+
previous.appendReversed(sb);
71+
}
72+
sb.append(".").append(last);
10673
}
10774

108-
private interface PathMatcherItem {
109-
static PathMatcherItem of(String property) {
110-
return new PathMatcher.PathMatcherItem.ObjectProperty(property);
75+
public interface PathMatcherItem {
76+
static PathMatcherItem ofProperty(String property) {
77+
if ("*".equals(property)) {
78+
return new WildcardMatcherItem(Path.PathItem.ObjectProperty.class);
79+
}
80+
return new ObjectProperty(property);
11181
}
11282

113-
static PathMatcherItem of(Integer index) {
114-
return new PathMatcher.PathMatcherItem.ArrayIndex(index);
83+
static PathMatcherItem ofArrayIndex(String index) {
84+
if ("*".equals(index)) {
85+
return new WildcardMatcherItem(Path.PathItem.ArrayIndex.class);
86+
}
87+
return new ArrayIndex(Integer.parseInt(index));
11588
}
11689

11790
boolean match(Path.PathItem pathItem);
11891

119-
class WilcardMatcherItem implements PathMatcherItem {
92+
class WildcardMatcherItem implements PathMatcherItem {
12093
private final Class<? extends Path.PathItem> type;
12194

122-
public WilcardMatcherItem(Class<? extends Path.PathItem> type) {
95+
public WildcardMatcherItem(Class<? extends Path.PathItem> type) {
12396
this.type = type;
12497
}
12598

99+
@Override
126100
public String toString() {
127101
return "*";
128102
}

0 commit comments

Comments
 (0)