@@ -52,40 +52,43 @@ public class MatchOperation {
52
52
final Match .Value actual ;
53
53
final Match .Value expected ;
54
54
final List <MatchOperation > failures ;
55
+ // TODO merge this with Match.Type which should be a complex object not an enum
56
+ final boolean matchEachEmptyAllowed ;
55
57
56
58
boolean pass = true ;
57
59
private String failReason ;
58
60
59
- MatchOperation (Match .Type type , Match .Value actual , Match .Value expected ) {
60
- this (JsEngine .global (), null , type , actual , expected );
61
+ MatchOperation (Match .Type type , Match .Value actual , Match .Value expected , boolean matchEachEmptyAllowed ) {
62
+ this (JsEngine .global (), null , type , actual , expected , matchEachEmptyAllowed );
61
63
}
62
64
63
- MatchOperation (JsEngine js , Match .Type type , Match .Value actual , Match .Value expected ) {
64
- this (js , null , type , actual , expected );
65
+ MatchOperation (JsEngine js , Match .Type type , Match .Value actual , Match .Value expected , boolean matchEachEmptyAllowed ) {
66
+ this (js , null , type , actual , expected , matchEachEmptyAllowed );
65
67
}
66
68
67
- MatchOperation (Match .Context context , Match .Type type , Match .Value actual , Match .Value expected ) {
68
- this (null , context , type , actual , expected );
69
+ MatchOperation (Match .Context context , Match .Type type , Match .Value actual , Match .Value expected , boolean matchEachEmptyAllowed ) {
70
+ this (null , context , type , actual , expected , matchEachEmptyAllowed );
69
71
}
70
72
71
- private MatchOperation (JsEngine js , Match .Context context , Match .Type type , Match .Value actual , Match .Value expected ) {
73
+ private MatchOperation (JsEngine js , Match .Context context , Match .Type type , Match .Value actual , Match .Value expected , boolean matchEachEmptyAllowed ) {
72
74
this .type = type ;
73
75
this .actual = actual ;
74
76
this .expected = expected ;
77
+ this .matchEachEmptyAllowed = matchEachEmptyAllowed ;
75
78
if (context == null ) {
76
79
if (js == null ) {
77
80
js = JsEngine .global ();
78
81
}
79
82
this .failures = new ArrayList ();
80
83
if (actual .isXml ()) {
81
- this .context = new Match .Context (js , this , true , 0 , "/" , "" , -1 );
84
+ this .context = new Match .Context (js , this , true , 0 , "/" , "" , -1 , matchEachEmptyAllowed );
82
85
} else {
83
- this .context = new Match .Context (js , this , false , 0 , "$" , "" , -1 );
86
+ this .context = new Match .Context (js , this , false , 0 , "$" , "" , -1 , matchEachEmptyAllowed );
84
87
}
85
88
} else {
86
89
this .context = context ;
87
90
this .failures = context .root .failures ;
88
- }
91
+ }
89
92
}
90
93
91
94
private Match .Type fromMatchEach () {
@@ -159,15 +162,15 @@ boolean execute() {
159
162
case EACH_CONTAINS_DEEP :
160
163
if (actual .isList ()) {
161
164
List list = actual .getValue ();
162
- if (list .isEmpty ()) {
165
+ if (list .isEmpty () && ! matchEachEmptyAllowed ) {
163
166
return fail ("match each failed, empty array / list" );
164
167
}
165
168
Match .Type nestedMatchType = fromMatchEach ();
166
169
int count = list .size ();
167
170
for (int i = 0 ; i < count ; i ++) {
168
171
Object o = list .get (i );
169
172
context .JS .put ("_$" , o );
170
- MatchOperation mo = new MatchOperation (context .descend (i ), nestedMatchType , new Match .Value (o ), expected );
173
+ MatchOperation mo = new MatchOperation (context .descend (i ), nestedMatchType , new Match .Value (o ), expected , matchEachEmptyAllowed );
171
174
mo .execute ();
172
175
context .JS .bindings .removeMember ("_$" );
173
176
if (!mo .pass ) {
@@ -198,7 +201,7 @@ boolean execute() {
198
201
case CONTAINS_ANY_DEEP :
199
202
// don't tamper with strings on the RHS that represent arrays or objects
200
203
if (!expected .isList () && !(expected .isString () && expected .isArrayObjectOrReference ())) {
201
- MatchOperation mo = new MatchOperation (context , type , actual , new Match .Value (Collections .singletonList (expected .getValue ())));
204
+ MatchOperation mo = new MatchOperation (context , type , actual , new Match .Value (Collections .singletonList (expected .getValue ())), matchEachEmptyAllowed );
202
205
mo .execute ();
203
206
return mo .pass ? pass () : fail (mo .failReason );
204
207
}
@@ -208,7 +211,7 @@ boolean execute() {
208
211
}
209
212
if (expected .isXml () && actual .isMap ()) {
210
213
// special case, auto-convert rhs
211
- MatchOperation mo = new MatchOperation (context , type , actual , new Match .Value (XmlUtils .toObject (expected .getValue (), true )));
214
+ MatchOperation mo = new MatchOperation (context , type , actual , new Match .Value (XmlUtils .toObject (expected .getValue (), true )), matchEachEmptyAllowed );
212
215
mo .execute ();
213
216
return mo .pass ? pass () : fail (mo .failReason );
214
217
}
@@ -284,7 +287,7 @@ private boolean macroEqualsExpected(String expStr) {
284
287
JsValue jv = context .JS .eval (macro );
285
288
context .JS .bindings .removeMember ("$" );
286
289
context .JS .bindings .removeMember ("_" );
287
- MatchOperation mo = new MatchOperation (context , nestedType , actual , new Match .Value (jv .getValue ()));
290
+ MatchOperation mo = new MatchOperation (context , nestedType , actual , new Match .Value (jv .getValue ()), matchEachEmptyAllowed );
288
291
return mo .execute ();
289
292
} else if (macro .startsWith ("[" )) {
290
293
int closeBracketPos = macro .indexOf (']' );
@@ -321,15 +324,15 @@ private boolean macroEqualsExpected(String expStr) {
321
324
macro = "#" + macro ;
322
325
}
323
326
if (macro .startsWith ("#" )) {
324
- MatchOperation mo = new MatchOperation (context , Match .Type .EACH_EQUALS , actual , new Match .Value (macro ));
327
+ MatchOperation mo = new MatchOperation (context , Match .Type .EACH_EQUALS , actual , new Match .Value (macro ), matchEachEmptyAllowed );
325
328
mo .execute ();
326
329
return mo .pass ? pass () : fail ("all array elements matched" );
327
330
} else { // schema reference
328
331
Match .Type nestedType = macroToMatchType (true , macro ); // match each
329
332
int startPos = matchTypeToStartPos (nestedType );
330
333
macro = macro .substring (startPos );
331
334
JsValue jv = context .JS .eval (macro );
332
- MatchOperation mo = new MatchOperation (context , nestedType , actual , new Match .Value (jv .getValue ()));
335
+ MatchOperation mo = new MatchOperation (context , nestedType , actual , new Match .Value (jv .getValue ()), matchEachEmptyAllowed );
333
336
return mo .execute ();
334
337
}
335
338
}
@@ -445,7 +448,7 @@ private boolean actualEqualsExpected() {
445
448
for (int i = 0 ; i < actListCount ; i ++) {
446
449
Match .Value actListValue = new Match .Value (actList .get (i ));
447
450
Match .Value expListValue = new Match .Value (expList .get (i ));
448
- MatchOperation mo = new MatchOperation (context .descend (i ), Match .Type .EQUALS , actListValue , expListValue );
451
+ MatchOperation mo = new MatchOperation (context .descend (i ), Match .Type .EQUALS , actListValue , expListValue , matchEachEmptyAllowed );
449
452
mo .execute ();
450
453
if (!mo .pass ) {
451
454
return fail ("array match failed at index " + i );
@@ -510,7 +513,7 @@ private boolean matchMapValues(Map<String, Object> actMap, Map<String, Object> e
510
513
} else {
511
514
childMatchType = Match .Type .EQUALS ;
512
515
}
513
- MatchOperation mo = new MatchOperation (context .descend (key ), childMatchType , childActValue , new Match .Value (childExp ));
516
+ MatchOperation mo = new MatchOperation (context .descend (key ), childMatchType , childActValue , new Match .Value (childExp ), matchEachEmptyAllowed );
514
517
mo .execute ();
515
518
if (mo .pass ) {
516
519
if (type == Match .Type .CONTAINS_ANY || type == Match .Type .CONTAINS_ANY_DEEP ) {
@@ -585,7 +588,7 @@ private boolean actualContainsExpected() {
585
588
default :
586
589
childMatchType = Match .Type .EQUALS ;
587
590
}
588
- MatchOperation mo = new MatchOperation (context .descend (i ), childMatchType , actListValue , expListValue );
591
+ MatchOperation mo = new MatchOperation (context .descend (i ), childMatchType , actListValue , expListValue , matchEachEmptyAllowed );
589
592
mo .execute ();
590
593
if (mo .pass ) {
591
594
if (type == Match .Type .CONTAINS_ANY || type == Match .Type .CONTAINS_ANY_DEEP ) {
0 commit comments