Skip to content

Commit 6c00a7c

Browse files
Duncan MackinderDuncan Mackinder
authored andcommitted
Add ArrayValueMatcher, improve diagnostics
Add ArrayValueMatcher to simplify verification of range of array elements Improve diagnostics from RegularExpressionValueMatcher Remove RegularExpressionCustomization as it did not prove useful
1 parent 3851ea5 commit 6c00a7c

13 files changed

+471
-213
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package org.skyscreamer.jsonassert;
2+
3+
import java.text.MessageFormat;
4+
5+
import org.json.JSONArray;
6+
import org.json.JSONException;
7+
import org.skyscreamer.jsonassert.comparator.JSONComparator;
8+
9+
/**
10+
* A value matcher for arrays. This operates like STRICT_ORDER array match,
11+
* however if expected array has less elements than actual array the matching
12+
* process loops through the expected array to get expected elements for the
13+
* additional actual elements. In general the expected array will contain a
14+
* single element which is matched against each actual array element in turn.
15+
* This allows simple verification of constant array element components and
16+
* coupled with RegularExpressionValueMatcher can be used to match specific
17+
* array element components against a regular expression pattern. If the
18+
* expected object is not an array, a one element expected array is created
19+
* containing whatever is provided as the expected value.
20+
*
21+
* @author Duncan Mackinder
22+
*
23+
*/
24+
public class ArrayValueMatcher<T> implements LocationAwareValueMatcher<T> {
25+
private final JSONComparator comparator;
26+
private final int from;
27+
private final int to;
28+
29+
/**
30+
* Create ArrayValueMatcher to match every element in actual array against
31+
* elements taken in sequence from expected array, repeating from start of
32+
* expected array if necessary.
33+
*
34+
* @param comparator
35+
* comparator to use to compare elements
36+
*/
37+
public ArrayValueMatcher(JSONComparator comparator) {
38+
this(comparator, 0, Integer.MAX_VALUE);
39+
}
40+
41+
/**
42+
* Create ArrayValueMatcher to match specified element in actual array
43+
* against first element of expected array.
44+
*
45+
* @param comparator
46+
* comparator to use to compare elements
47+
*/
48+
public ArrayValueMatcher(JSONComparator comparator, int i) {
49+
this(comparator, i, i);
50+
}
51+
52+
/**
53+
* Create ArrayValueMatcher to match every element in specified range
54+
* (inclusive) from actual array against elements taken in sequence from
55+
* expected array, repeating from start of expected array if necessary.
56+
*
57+
* @param comparator
58+
* comparator to use to compare elements
59+
* @from first element in actual array to compare
60+
* @to last element in actual array to compare
61+
*/
62+
public ArrayValueMatcher(JSONComparator comparator, int from, int to) {
63+
assert comparator != null : "comparator null";
64+
assert from >= 0 : MessageFormat.format("from({0}) < 0", from);
65+
assert to >= from : MessageFormat.format("to({0}) < from({1})", to,
66+
from);
67+
this.comparator = comparator;
68+
this.from = from;
69+
this.to = to;
70+
}
71+
72+
@Override
73+
/*
74+
* NOTE: method defined as required by ValueMatcher interface but will never
75+
* be called so defined simply to indicate match failure
76+
*/
77+
public boolean equal(T o1, T o2) {
78+
return false;
79+
}
80+
81+
@Override
82+
public boolean equal(String prefix, T actual, T expected, JSONCompareResult result) {
83+
if (!(actual instanceof JSONArray)) {
84+
throw new IllegalArgumentException("ArrayValueMatcher applied to non-array actual value");
85+
}
86+
try {
87+
JSONArray actualArray = (JSONArray) actual;
88+
JSONArray expectedArray = expected instanceof JSONArray ? (JSONArray) expected: new JSONArray(new Object[] { expected });
89+
int first = Math.max(0, from);
90+
int last = Math.min(actualArray.length() - 1, to);
91+
int expectedLen = expectedArray.length();
92+
for (int i = first; i <= last; i++) {
93+
String elementPrefix = MessageFormat.format("{0}[{1}]", prefix, i);
94+
Object actualElement = actualArray.get(i);
95+
Object expectedElement = expectedArray.get((i - first) % expectedLen);
96+
comparator.compareValues(elementPrefix, expectedElement, actualElement, result);
97+
}
98+
// values must match since no exceptions thrown
99+
return true;
100+
}
101+
catch (JSONException e) {
102+
return false;
103+
}
104+
}
105+
106+
}

src/main/java/org/skyscreamer/jsonassert/Customizable.java

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/main/java/org/skyscreamer/jsonassert/Customization.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/**
44
* Associates a custom matcher to a specific jsonpath.
55
*/
6-
public final class Customization implements Customizable {
6+
public final class Customization {
77
private final String path;
88
private final ValueMatcher<Object> comparator;
99

@@ -14,23 +14,19 @@ public Customization(String path, ValueMatcher<Object> comparator) {
1414
this.comparator = comparator;
1515
}
1616

17-
public static Customizable customization(String path, ValueMatcher<Object> comparator) {
17+
public static Customization customization(String path, ValueMatcher<Object> comparator) {
1818
return new Customization(path, comparator);
1919
}
2020

21-
/* (non-Javadoc)
22-
* @see org.skyscreamer.jsonassert.Customizable#appliesToPath(java.lang.String)
23-
*/
24-
@Override
2521
public boolean appliesToPath(String path) {
2622
return this.path.equals(path);
2723
}
2824

29-
/* (non-Javadoc)
30-
* @see org.skyscreamer.jsonassert.Customizable#matches(java.lang.Object, java.lang.Object)
31-
*/
32-
@Override
33-
public boolean matches(Object actual, Object expected) {
34-
return comparator.equal(actual, expected);
35-
}
25+
public boolean matches(String prefix, Object actual, Object expected,
26+
JSONCompareResult result) {
27+
if (comparator instanceof LocationAwareValueMatcher) {
28+
return ((LocationAwareValueMatcher<Object>)comparator).equal(prefix, actual, expected, result);
29+
}
30+
return comparator.equal(actual, expected);
31+
}
3632
}

src/main/java/org/skyscreamer/jsonassert/JSONCompareResult.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,16 @@ public JSONCompareResult fail(String field, Object expected, Object actual) {
127127
return this;
128128
}
129129

130+
/**
131+
* Identify that the comparison failed
132+
* @param field Which field failed
133+
* @param exception exception containing details of match failure
134+
*/
135+
public JSONCompareResult fail(String field, ValueMatcherException exception) {
136+
fail(field + ": " + exception.getMessage(), exception.getExpected(), exception.getActual());
137+
return this;
138+
}
139+
130140
private String formatFailureMessage(String field, Object expected, Object actual) {
131141
return field
132142
+ "\nExpected: "
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
*
3+
*/
4+
package org.skyscreamer.jsonassert;
5+
6+
/**
7+
* A ValueMatcher extension that provides location in form of prefix to the equals method.
8+
*
9+
* @author Duncan Mackinder
10+
*
11+
*/
12+
public interface LocationAwareValueMatcher<T> extends ValueMatcher<T> {
13+
14+
boolean equal(String prefix, T actual, T expected, JSONCompareResult result);
15+
}

src/main/java/org/skyscreamer/jsonassert/RegularExpressionCustomization.java

Lines changed: 0 additions & 40 deletions
This file was deleted.
Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
package org.skyscreamer.jsonassert;
22

33
import java.util.regex.Pattern;
4+
import java.util.regex.PatternSyntaxException;
45

56
import org.skyscreamer.jsonassert.ValueMatcher;
67

78
/**
89
* A JSONassert value matcher that matches actual value to regular expression.
910
* If non-null regular expression passed to constructor, then all actual values
10-
* will be compared against this pattern, ignoring any expected value passed to
11-
* equal method. If null regular expression passed to constructor, then expected
12-
* value passed to equals method will be treated as a regular expression.
11+
* will be compared against this constant pattern, ignoring any expected value
12+
* passed to equal method. If null regular expression passed to constructor,
13+
* then expected value passed to equals method will be used to dynamically
14+
* specify regular expression pattern that actual value must match.
1315
*
1416
* @author Duncan Mackinder
1517
*
@@ -18,20 +20,63 @@ public class RegularExpressionValueMatcher<T> implements ValueMatcher<T> {
1820

1921
private final Pattern expectedPattern;
2022

23+
/**
24+
* Create RegularExpressionValueMatcher in which the pattern the actual
25+
* value must match with be specified dynamically from the expected string
26+
* passed to this matcher in the equals method.
27+
*/
2128
public RegularExpressionValueMatcher() {
2229
this(null);
2330
}
2431

25-
public RegularExpressionValueMatcher(String expected) {
26-
expectedPattern = expected == null ? null : Pattern.compile(expected);
32+
/**
33+
* Create RegularExpressionValueMatcher with specified pattern. If pattern
34+
* is not null, it must be a valid regular expression that defines a
35+
* constant expected pattern that every actual value must match (in this
36+
* case the expected value passed to equal method will be ignored). If
37+
* pattern is null, the pattern the actual value must match with be
38+
* specified dynamically from the expected string passed to this matcher in
39+
* the equals method.
40+
*
41+
* @param pattern
42+
* if non null, regular expression pattern which all actual
43+
* values this matcher is applied to must match. If null, this
44+
* matcher will apply pattern specified dynamically via the
45+
* expected parameter to the equal method.
46+
* @throws IllegalArgumentException
47+
* if pattern is non-null and not a valid regular expression.
48+
*/
49+
public RegularExpressionValueMatcher(String pattern) throws IllegalArgumentException {
50+
try {
51+
expectedPattern = pattern == null ? null : Pattern.compile(pattern);
52+
}
53+
catch (PatternSyntaxException e) {
54+
throw new IllegalArgumentException("Constant expected pattern invalid: " + e.getMessage(), e);
55+
}
2756
}
2857

2958
@Override
3059
public boolean equal(T actual, T expected) {
3160
String actualString = actual.toString();
32-
Pattern pattern = expectedPattern != null ? expectedPattern : Pattern
33-
.compile(expected.toString());
34-
return pattern.matcher(actualString).matches();
61+
String expectedString = expected.toString();
62+
try {
63+
Pattern pattern = isStaticPattern() ? expectedPattern : Pattern
64+
.compile(expectedString);
65+
if (!pattern.matcher(actualString).matches()) {
66+
throw new ValueMatcherException(getPatternType() + " expected pattern did not match value", pattern.toString(), actualString);
67+
}
68+
}
69+
catch (PatternSyntaxException e) {
70+
throw new ValueMatcherException(getPatternType() + " expected pattern invalid: " + e.getMessage(), e, expectedString, actualString);
71+
}
72+
return true;
3573
}
3674

75+
private boolean isStaticPattern() {
76+
return expectedPattern != null;
77+
}
78+
79+
private String getPatternType() {
80+
return isStaticPattern()? "Constant": "Dynamic";
81+
}
3782
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package org.skyscreamer.jsonassert;
2+
3+
/**
4+
* Exception that may be thrown by ValueMatcher subclasses to provide more detail on why matches method failed.
5+
*
6+
* @author Duncan Mackinder
7+
*
8+
*/
9+
public class ValueMatcherException extends RuntimeException {
10+
private static final long serialVersionUID = 1L;
11+
12+
private final String expected;
13+
14+
private final String actual;
15+
16+
public ValueMatcherException(String message, String expected, String actual) {
17+
super(message);
18+
this.expected = expected;
19+
this.actual = actual;
20+
}
21+
22+
public ValueMatcherException(Throwable cause, String expected, String actual) {
23+
super(cause);
24+
this.expected = expected;
25+
this.actual = actual;
26+
}
27+
28+
public ValueMatcherException(String message, Throwable cause, String expected, String actual) {
29+
super(message, cause);
30+
this.expected = expected;
31+
this.actual = actual;
32+
}
33+
34+
/**
35+
* @return the expected value
36+
*/
37+
public String getExpected() {
38+
return expected;
39+
}
40+
41+
/**
42+
* @return the actual value
43+
*/
44+
public String getActual() {
45+
return actual;
46+
}
47+
48+
}

0 commit comments

Comments
 (0)