• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

xmlunit / xmlunit / cd160610-9b67-4752-a2c4-e3a0d82d991e

21 Apr 2025 11:55AM UTC coverage: 91.756% (-0.02%) from 91.78%
cd160610-9b67-4752-a2c4-e3a0d82d991e

push

circleci

web-flow
Merge pull request #289 from xmlunit/circleci-project-setup

CircleCI project setup

3996 of 4698 branches covered (85.06%)

11754 of 12810 relevant lines covered (91.76%)

2.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.0
/xmlunit-legacy/src/main/java/org/custommonkey/xmlunit/NewDifferenceEngine.java
1
/*
2
******************************************************************
3
Copyright (c) 2001-2010,2013,2015-2016,2018,2022 Jeff Martin, Tim Bacon
4
All rights reserved.
5

6
Redistribution and use in source and binary forms, with or without
7
modification, are permitted provided that the following conditions
8
are met:
9

10
    * Redistributions of source code must retain the above copyright
11
      notice, this list of conditions and the following disclaimer.
12
    * Redistributions in binary form must reproduce the above
13
      copyright notice, this list of conditions and the following
14
      disclaimer in the documentation and/or other materials provided
15
      with the distribution.
16
    * Neither the name of the XMLUnit nor the names
17
      of its contributors may be used to endorse or promote products
18
      derived from this software without specific prior written
19
      permission.
20

21
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32
POSSIBILITY OF SUCH DAMAGE.
33

34
******************************************************************
35
*/
36

37
package org.custommonkey.xmlunit;
38

39
import java.util.Collections;
40
import java.util.HashMap;
41
import java.util.Iterator;
42
import java.util.LinkedList;
43
import java.util.List;
44
import java.util.Map;
45
import javax.xml.transform.Source;
46

47
import org.xmlunit.builder.Input;
48
import org.xmlunit.diff.ByNameAndTextRecSelector;
49
import org.xmlunit.diff.Comparison;
50
import org.xmlunit.diff.ComparisonListener;
51
import org.xmlunit.diff.ComparisonResult;
52
import org.xmlunit.diff.ComparisonType;
53
import org.xmlunit.diff.DOMDifferenceEngine;
54
import org.xmlunit.diff.DefaultNodeMatcher;
55
import org.xmlunit.diff.DifferenceEvaluator;
56
import org.xmlunit.diff.DifferenceEvaluators;
57
import org.xmlunit.diff.ElementSelector;
58
import org.xmlunit.diff.ElementSelectors;
59
import org.xmlunit.diff.NodeFilters;
60
import org.xmlunit.diff.NodeMatcher;
61
import org.xmlunit.input.CommentLessSource;
62
import org.xmlunit.input.WhitespaceNormalizedSource;
63
import org.xmlunit.input.WhitespaceStrippedSource;
64
import org.xmlunit.util.IterableNodeList;
65
import org.xmlunit.util.Linqy;
66
import org.xmlunit.util.Predicate;
67
import org.custommonkey.xmlunit.examples.RecursiveElementNameAndTextQualifier;
68

69
import org.w3c.dom.CDATASection;
70
import org.w3c.dom.Comment;
71
import org.w3c.dom.Document;
72
import org.w3c.dom.DocumentType;
73
import org.w3c.dom.Element;
74
import org.w3c.dom.Node;
75

76
/**
77
 * Class that has responsibility for comparing Nodes and notifying a
78
 * DifferenceListener of any differences or dissimilarities that are found.
79
 * Knows how to compare namespaces and nested child nodes, but currently
80
 * only compares nodes of type ELEMENT_NODE, CDATA_SECTION_NODE,
81
 * COMMENT_NODE, DOCUMENT_TYPE_NODE, PROCESSING_INSTRUCTION_NODE and TEXT_NODE.
82
 * Nodes of other types (eg ENTITY_NODE) will be skipped.
83
 * @see DifferenceListener#differenceFound(Difference)
84
 */
85
public class NewDifferenceEngine
86
    implements DifferenceConstants, DifferenceEngineContract {
87

88
    private static final Integer ZERO = Integer.valueOf(0);
1✔
89
    private static final Map<Class<? extends ElementQualifier>, ElementSelector>
90
        KNOWN_SELECTORS;
91
    static {
92
        Map<Class<? extends ElementQualifier>, ElementSelector> m =
1✔
93
            new HashMap<Class<? extends ElementQualifier>, ElementSelector>();
94
        m.put(ElementNameAndTextQualifier.class,
1✔
95
              ElementSelectors.byNameAndText);
96
        m.put(ElementNameQualifier.class, ElementSelectors.byName);
1✔
97
        m.put(RecursiveElementNameAndTextQualifier.class,
1✔
98
              new ByNameAndTextRecSelector());
99
        KNOWN_SELECTORS = Collections.unmodifiableMap(m);
1✔
100
    }
1✔
101

102
    private final ComparisonController controller;
103
    private MatchTracker matchTracker;
104

105
    /**
106
     * Simple constructor that uses no MatchTracker at all.
107
     * @param controller the instance used to determine whether a Difference
108
     * detected by this class should halt further comparison or not
109
     * @see ComparisonController#haltComparison(Difference)
110
     */
111
    public NewDifferenceEngine(ComparisonController controller) {
112
        this(controller, null);
1✔
113
    }
1✔
114

115
    /**
116
     * Simple constructor
117
     * @param controller the instance used to determine whether a Difference
118
     * detected by this class should halt further comparison or not
119
     * @param matchTracker the instance that is notified on each
120
     * successful match.  May be null.
121
     * @see ComparisonController#haltComparison(Difference)
122
     * @see MatchTracker#matchFound(Difference)
123
     */
124
    public NewDifferenceEngine(ComparisonController controller,
125
                               MatchTracker matchTracker) {
1✔
126
        this.controller = controller;
1✔
127
        this.matchTracker = matchTracker;
1✔
128
    }
1✔
129

130
    /**
131
     * @param matchTracker the instance that is notified on each
132
     * successful match.  May be null.
133
     */
134
    @Override
135
    public void setMatchTracker(MatchTracker matchTracker) {
136
        this.matchTracker = matchTracker;
1✔
137
    }
1✔
138

139
    /**
140
     * Entry point for Node comparison testing.
141
     * @param control Control XML to compare
142
     * @param test Test XML to compare
143
     * @param listener Notified of any {@link Difference differences} detected
144
     * during node comparison testing
145
     * @param elementQualifier Used to determine which elements qualify for
146
     * comparison e.g. when a node has repeated child elements that may occur
147
     * in any sequence and that sequence is not considered important.
148
     */
149
    @Override
150
    public void compare(Node control, Node test, DifferenceListener listener,
151
                        ElementQualifier elementQualifier) {
152
        DOMDifferenceEngine engine = new DOMDifferenceEngine();
1✔
153
        engine.setNodeFilter(NodeFilters.AcceptAll);
1✔
154

155
        final IsBetweenDocumentNodeAndRootElement checkPrelude =
1✔
156
            new IsBetweenDocumentNodeAndRootElement();
157
        engine.addComparisonListener(checkPrelude);
1✔
158

159
        if (matchTracker != null) {
1✔
160
            engine
1✔
161
                .addMatchListener(new MatchTracker2ComparisonListener(matchTracker));
1✔
162
        }
163

164
        org.xmlunit.diff.ComparisonController mappedController =
1✔
165
            new ComparisonController2ComparisonController(controller);
166
        engine.setComparisonController(mappedController);
1✔
167
        if (listener != null) {
1!
168
            final DifferenceEvaluator evaluator =
1✔
169
                DifferenceEvaluators.chain(DifferenceEvaluators.Default,
1✔
170
                                           DifferenceEvaluators.ignorePrologDifferencesExceptDoctype(),
1✔
171
                                           new IgnoreDoctypeNotPresentDifferences(),
172
                                           new DifferenceListener2DifferenceEvaluator(listener));
173
            engine.setDifferenceEvaluator(evaluator);
1✔
174
        }
175

176
        NodeMatcher m = new DefaultNodeMatcher();
1✔
177
        if (elementQualifier != null) {
1✔
178
            Class<?> c = elementQualifier.getClass();
1✔
179
            if (KNOWN_SELECTORS.containsKey(c)) {
1✔
180
                m = new DefaultNodeMatcher(KNOWN_SELECTORS.get(c));
1✔
181
            } else {
182
                m = new DefaultNodeMatcher(new ElementQualifier2ElementSelector(elementQualifier));
1✔
183
            }
184
        }
185
        if (!XMLUnit.getCompareUnmatched()) {
1✔
186
            engine.setNodeMatcher(m);
1✔
187
        } else {
188
            engine.setNodeMatcher(new CompareUnmatchedNodeMatcher(m));
1✔
189
        }
190

191
        Input.Builder ctrlBuilder = Input.fromNode(control);
1✔
192
        Input.Builder tstBuilder = Input.fromNode(test);
1✔
193

194
        Source ctrlSource = ctrlBuilder.build();
1✔
195
        Source tstSource = tstBuilder.build();
1✔
196
        if (XMLUnit.getIgnoreComments()) {
1✔
197
            ctrlSource = new CommentLessSource(ctrlSource);
1✔
198
            tstSource = new CommentLessSource(tstSource);
1✔
199
        }
200
        if (XMLUnit.getNormalizeWhitespace()) {
1✔
201
            ctrlSource = new WhitespaceNormalizedSource(ctrlSource);
1✔
202
            tstSource = new WhitespaceNormalizedSource(tstSource);
1✔
203
        } else if (XMLUnit.getIgnoreWhitespace()) {
1✔
204
            ctrlSource = new WhitespaceStrippedSource(ctrlSource);
1✔
205
            tstSource = new WhitespaceStrippedSource(tstSource);
1✔
206
        }
207

208
        engine.compare(ctrlSource, tstSource);
1✔
209
    }
1✔
210

211
    private static Iterable<Difference> toDifference(org.xmlunit.diff.Difference d) {
212
        return toDifference(d.getComparison());
1✔
213
    }
214

215
    /**
216
     * Maps a Comparison to Differences.
217
     * @param comp comparison to map
218
     * @return Differences
219
     */
220
    public static Iterable<Difference> toDifference(Comparison comp) {
221
        List<Difference> diffs = new LinkedList<Difference>();
1✔
222
        Difference proto = null;
1✔
223
        switch (comp.getType()) {
1!
224
        case ATTR_VALUE_EXPLICITLY_SPECIFIED:
225
            proto = ATTR_VALUE_EXPLICITLY_SPECIFIED;
×
226
            break;
×
227
        case HAS_DOCTYPE_DECLARATION:
228
            proto = HAS_DOCTYPE_DECLARATION;
1✔
229
            break;
1✔
230
        case DOCTYPE_NAME:
231
            proto = DOCTYPE_NAME;
×
232
            break;
×
233
        case DOCTYPE_PUBLIC_ID:
234
            proto = DOCTYPE_PUBLIC_ID;
1✔
235
            break;
1✔
236
        case DOCTYPE_SYSTEM_ID:
237
            proto = DOCTYPE_SYSTEM_ID;
1✔
238
            break;
1✔
239
        case SCHEMA_LOCATION:
240
            proto = SCHEMA_LOCATION;
1✔
241
            break;
1✔
242
        case NO_NAMESPACE_SCHEMA_LOCATION:
243
            proto = NO_NAMESPACE_SCHEMA_LOCATION;
1✔
244
            break;
1✔
245
        case NODE_TYPE:
246
            proto = NODE_TYPE;
1✔
247
            break;
1✔
248
        case NAMESPACE_PREFIX:
249
            proto = NAMESPACE_PREFIX;
1✔
250
            break;
1✔
251
        case NAMESPACE_URI:
252
            proto = NAMESPACE_URI;
1✔
253
            break;
1✔
254
        case TEXT_VALUE:
255
            if (comp.getControlDetails().getTarget() instanceof CDATASection) {
1✔
256
                proto = CDATA_VALUE;
1✔
257
            } else if (comp.getControlDetails().getTarget()
1✔
258
                       instanceof Comment) {
259
                proto = COMMENT_VALUE;
1✔
260
            } else {
261
                proto = TEXT_VALUE;
1✔
262
            }
263
            break;
1✔
264
        case PROCESSING_INSTRUCTION_TARGET:
265
            proto = PROCESSING_INSTRUCTION_TARGET;
1✔
266
            break;
1✔
267
        case PROCESSING_INSTRUCTION_DATA:
268
            proto = PROCESSING_INSTRUCTION_DATA;
×
269
            break;
×
270
        case ELEMENT_TAG_NAME:
271
            proto = ELEMENT_TAG_NAME;
1✔
272
            break;
1✔
273
        case ELEMENT_NUM_ATTRIBUTES:
274
            proto = ELEMENT_NUM_ATTRIBUTES;
1✔
275
            break;
1✔
276
        case ATTR_VALUE:
277
            proto = ATTR_VALUE;
1✔
278
            break;
1✔
279
        case CHILD_NODELIST_LENGTH:
280
            Comparison.Detail cd = comp.getControlDetails();
1✔
281
            Comparison.Detail td = comp.getTestDetails();
1✔
282
            if (ZERO.equals(cd.getValue())
1✔
283
                || ZERO.equals(td.getValue())) {
1✔
284
                diffs.add(new Difference(HAS_CHILD_NODES,
1✔
285
                                         new NodeDetail(String
286
                                                        .valueOf(!ZERO
1✔
287
                                                                 .equals(cd
1✔
288
                                                                         .getValue())),
1✔
289
                                                        cd.getTarget(),
1✔
290
                                                        cd.getXPath()),
1✔
291
                                         new NodeDetail(String
292
                                                        .valueOf(!ZERO
1✔
293
                                                                 .equals(td
1✔
294
                                                                         .getValue())),
1✔
295
                                                        td.getTarget(),
1✔
296
                                                        td.getXPath())));
1✔
297
            }
298
            proto = CHILD_NODELIST_LENGTH;
1✔
299
            break;
1✔
300
        case CHILD_NODELIST_SEQUENCE:
301
            proto = CHILD_NODELIST_SEQUENCE;
1✔
302
            break;
1✔
303
        case CHILD_LOOKUP:
304
            proto = CHILD_NODE_NOT_FOUND;
1✔
305
            break;
1✔
306
        case ATTR_NAME_LOOKUP:
307
            proto = ATTR_NAME_NOT_FOUND;
1✔
308
            break;
1✔
309
        default:
310
            /* comparison doesn't match one of legacy's built-in differences */
311
            break;
312
        }
313
        if (proto != null) {
1✔
314
            diffs.add(new Difference(proto, toNodeDetail(comp.getControlDetails()),
1✔
315
                                     toNodeDetail(comp.getTestDetails())));
1✔
316
        }
317
        return diffs;
1✔
318
    }
319

320
    /**
321
     * Maps node details.
322
     * @param detail XMLUnit 2.x detail
323
     * @return XMLUnit 1.x detail
324
     */
325
    public static NodeDetail toNodeDetail(Comparison.Detail detail) {
326
        String value = String.valueOf(detail.getValue());
1✔
327
        if (detail.getValue() instanceof Node) {
1!
328
            value = ((Node) detail.getValue()).getNodeName();
×
329
        }
330
        return new NodeDetail(value, detail.getTarget(),
1✔
331
                              detail.getXPath());
1✔
332
    }
333

334
    /**
335
     * Adapts XMLUnit 1.x MatchTracker to XMLUnit 2.x ComparisonListener.
336
     */
337
    public static class MatchTracker2ComparisonListener
338
        implements ComparisonListener {
339
        private final MatchTracker mt;
340

341
        /**
342
         * @param m MatchTracker to adapt
343
         */
344
        public MatchTracker2ComparisonListener(MatchTracker m) {
1✔
345
            mt = m;
1✔
346
        }
1✔
347

348
        @Override
349
        public void comparisonPerformed(Comparison comparison,
350
                                        ComparisonResult outcome) {
351
            for (Difference diff : toDifference(comparison)) {
1✔
352
                mt.matchFound(diff);
1✔
353
            }
1✔
354
        }
1✔
355
    }
356

357
    /**
358
     * Adapts XMLUnit 1.x ComparisonController to XMLUnit 2.x ComparisonController.
359
     */
360
    public static class ComparisonController2ComparisonController
361
        implements org.xmlunit.diff.ComparisonController {
362
        private final ComparisonController cc;
363
        /**
364
         * @param c ComparisonController to adapt.
365
         */
366
        public ComparisonController2ComparisonController(ComparisonController c) {
1✔
367
            cc = c;
1✔
368
        }
1✔
369

370
        @Override
371
        public boolean stopDiffing(org.xmlunit.diff.Difference difference) {
372
            for (Difference diff : toDifference(difference)) {
1✔
373
                if (cc.haltComparison(diff)) {
1✔
374
                    return true;
1✔
375
                }
376
            }
1✔
377
            return false;
1✔
378
        }
379
    }
380

381
    /**
382
     * Adapts XMLUnit 1.x ComparisonQualifider to XMLUnit 2.x ElementSelector.
383
     */
384
    public static class ElementQualifier2ElementSelector
385
        implements ElementSelector {
386
        private final ElementQualifier eq;
387

388
        /**
389
         * @param eq ElementQualifier to adapt
390
         */
391
        public ElementQualifier2ElementSelector(ElementQualifier eq) {
1✔
392
            this.eq = eq;
1✔
393
        }
1✔
394

395
        @Override
396
        public boolean canBeCompared(Element controlElement,
397
                                     Element testElement) {
398
            return eq.qualifyForComparison(controlElement, testElement);
1✔
399
        }
400

401
    }
402

403
    private static final class IgnoreDoctypeNotPresentDifferences implements DifferenceEvaluator {
404
        public ComparisonResult evaluate(Comparison comparison,
405
                                         ComparisonResult outcome) {
406
            if (outcome == ComparisonResult.EQUAL) {
1✔
407
                return outcome;
1✔
408
            }
409
            if (comparison.getType() == ComparisonType.CHILD_LOOKUP
1✔
410
                && (isDoctype(comparison.getControlDetails())
1✔
411
                    || isDoctype(comparison.getTestDetails()))) {
1!
412
                return ComparisonResult.EQUAL;
1✔
413
            }
414
            if (comparison.getType() == ComparisonType.CHILD_NODELIST_LENGTH
1✔
415
                && isDocumentWithDocTypeDifference(comparison)
1!
416
                && Math.abs((Integer) comparison.getControlDetails().getValue()
×
417
                            - (Integer) comparison.getTestDetails().getValue())
×
418
                   == 1) {
419
                return ComparisonResult.EQUAL;
×
420
            }
421
            if (comparison.getType() == ComparisonType.CHILD_NODELIST_SEQUENCE
1✔
422
                && isDocumentWithDocTypeDifference(comparison)) {
1!
423
                return ComparisonResult.EQUAL;
×
424
            }
425
            return outcome;
1✔
426
        }
427

428
        private boolean isDoctype(Comparison.Detail detail) {
429
            return detail != null && detail.getTarget() instanceof DocumentType;
1!
430
        }
431

432
        private boolean isDocumentWithDocTypeDifference(Comparison comparison) {
433
            if (!(comparison.getControlDetails().getTarget() instanceof Document)) {
1!
434
                return false;
1✔
435
            }
436
            return hasDoctypeChild(comparison.getControlDetails().getTarget())
×
437
                || hasDoctypeChild(comparison.getTestDetails().getTarget());
×
438
        }
439

440
        private boolean hasDoctypeChild(Node n) {
441
            return Linqy.any(new IterableNodeList(n.getChildNodes()),
×
442
                             new Predicate<Node>() {
×
443
                                 public boolean test(Node n) {
444
                                     return n instanceof DocumentType;
×
445
                                 }
446
                             });
447
        }
448
    }
449

450
    /**
451
     * Adapts XMLUnit 1.x DifferenceListener to XMLUnit 2.x DifferenceEvaluator.
452
     */
453
    public static class DifferenceListener2DifferenceEvaluator
454
        implements DifferenceEvaluator {
455
        private final DifferenceListener dl;
456

457
        /**
458
         * @param dl DifferenceListener to adapt
459
         */
460
        public DifferenceListener2DifferenceEvaluator(DifferenceListener dl) {
1✔
461
            this.dl = dl;
1✔
462
        }
1✔
463

464
        @Override
465
        public ComparisonResult evaluate(Comparison comparison,
466
                                         ComparisonResult outcome) {
467
            if (outcome == ComparisonResult.EQUAL) {
1✔
468
                return outcome;
1✔
469
            }
470
            ComparisonResult max = outcome;
1✔
471
            for (Difference diff : toDifference(comparison)) {
1✔
472
                ComparisonResult curr = null;
1✔
473
                switch (dl.differenceFound(diff)) {
1✔
474
                case DifferenceListener
475
                    .RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL:
476
                    curr = ComparisonResult.EQUAL;
1✔
477
                    break;
1✔
478
                case DifferenceListener
479
                    .RETURN_IGNORE_DIFFERENCE_NODES_SIMILAR:
480
                    curr = ComparisonResult.SIMILAR;
1✔
481
                    break;
1✔
482
                case DifferenceListener
483
                    .RETURN_UPGRADE_DIFFERENCE_NODES_DIFFERENT:
484
                    curr = ComparisonResult.DIFFERENT;
1✔
485
                    break;
1✔
486
                default:
487
                    // unknown result, ignore it
488
                    break;
489
                }
490
                if (curr != null && curr.compareTo(max) > 0) {
1✔
491
                    max = curr;
1✔
492
                }
493
            }
1✔
494
            return max;
1✔
495
        }
496
    }
497

498
    /**
499
     * Tests whether the DifferenceEngine is currently processing
500
     * comparisons of "things" between the document node and the
501
     * document's root element (comments or PIs, mostly) since these
502
     * must be ignored for backwards compatibility reasons.
503
     *
504
     * <p>Relies on the following assumptions:
505
     * <ul>
506

507
     *   <li>the last comparison DOMDifferenceEngine performs on the
508
     *     document node is an XML_ENCODING comparison.</li>
509
     *   <li>the first comparison DOMDifferenceEngine performs on matching
510
     *     root elements is a NODE_TYPE comparison.  The control Node
511
     *     is an Element Node.</li>
512
     *   <li>the first comparison DOMDifferenceEngine performs if the
513
     *     root elements don't match is a CHILD_LOOKUP comparison.
514
     *     The control Node is an Element Node.</li>
515
     * </ul>
516
     * </p>
517
     */
518
    private static class IsBetweenDocumentNodeAndRootElement
1✔
519
        implements ComparisonListener {
520

521
        private boolean haveSeenXmlEncoding = false;
1✔
522
        private boolean haveSeenElementNodeComparison = false;
1✔
523

524
        public void comparisonPerformed(Comparison comparison,
525
                                        ComparisonResult outcome) {
526
            if (comparison.getType() == ComparisonType.XML_ENCODING) {
1✔
527
                haveSeenXmlEncoding = true;
1✔
528
            } else if (comparison.getControlDetails().getTarget()
1✔
529
                          instanceof Element
530
                       &&
531
                       (comparison.getType() == ComparisonType.NODE_TYPE
1✔
532
                        || comparison.getType() == ComparisonType.CHILD_LOOKUP)
1✔
533
                       ) {
534
                haveSeenElementNodeComparison = true;
1✔
535
            }
536
        }
1✔
537

538
        private boolean shouldSkip() {
539
            return haveSeenXmlEncoding && !haveSeenElementNodeComparison;
×
540
        }
541
    }
542

543
    private static class CompareUnmatchedNodeMatcher
544
        implements NodeMatcher {
545
        private final NodeMatcher nestedMatcher;
546
        private CompareUnmatchedNodeMatcher(NodeMatcher nested) {
1✔
547
            nestedMatcher = nested;
1✔
548
        }
1✔
549

550
        public Iterable<Map.Entry<Node, Node>>
551
            match(Iterable<Node> controlNodes,
552
                  Iterable<Node> testNodes) {
553
            final Map<Node, Node> map = new HashMap<Node, Node>();
1✔
554
            for (Map.Entry<Node, Node> e
555
                     : nestedMatcher.match(controlNodes, testNodes)) {
1✔
556
                map.put(e.getKey(), e.getValue());
1✔
557
            }
1✔
558

559
            final LinkedList<Map.Entry<Node, Node>> result =
1✔
560
                new LinkedList<Map.Entry<Node, Node>>();
561

562
            for (Node n : controlNodes) {
1✔
563
                if (map.containsKey(n)) {
1✔
564
                    result.add(new Entry(n, map.get(n)));
1✔
565
                } else {
566
                    Iterable<Node> unmatchedTestElements =
1✔
567
                        Linqy.filter(testNodes, new Predicate<Node>() {
1✔
568
                                @Override
569
                                public boolean test(Node t) {
570
                                    return !map.containsValue(t);
1✔
571
                                }
572
                            });
573
                    Iterator<Node> it = unmatchedTestElements.iterator();
1✔
574
                    if (it.hasNext()) {
1✔
575
                        Node t = it.next();
1✔
576
                        map.put(n, t);
1✔
577
                        result.add(new Entry(n, t));
1✔
578
                    }
579
                }
580
            }
1✔
581
            return result;
1✔
582
        }
583

584
        private static class Entry implements Map.Entry<Node, Node> {
585
            private final Node key;
586
            private final Node value;
587
            private Entry(Node k, Node v) {
1✔
588
                key = k;
1✔
589
                value = v;
1✔
590
            }
1✔
591
            public Node getKey() { return key; }
1✔
592
            public Node getValue() { return value; }
1✔
593
            public Node setValue(Node v) {
594
                throw new UnsupportedOperationException();
×
595
            }
596
        }
597
    }
598

599
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc