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

hazendaz / httpunit / 755

14 Feb 2026 07:14PM UTC coverage: 80.526%. Remained the same
755

push

github

hazendaz
[ci] Fix badge

3213 of 4105 branches covered (78.27%)

Branch coverage included in aggregate %.

8245 of 10124 relevant lines covered (81.44%)

0.81 hits per line

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

89.49
/src/main/java/com/meterware/httpunit/dom/NodeImpl.java
1
/*
2
 * SPDX-License-Identifier: MIT
3
 * See LICENSE file for details.
4
 *
5
 * Copyright 2000-2026 Russell Gold
6
 * Copyright 2021-2000 hazendaz
7
 */
8
package com.meterware.httpunit.dom;
9

10
import java.util.ArrayList;
11
import java.util.Hashtable;
12
import java.util.Iterator;
13
import java.util.List;
14

15
import org.w3c.dom.DOMException;
16
import org.w3c.dom.Document;
17
import org.w3c.dom.Element;
18
import org.w3c.dom.NamedNodeMap;
19
import org.w3c.dom.Node;
20
import org.w3c.dom.NodeList;
21
import org.w3c.dom.UserDataHandler;
22
import org.w3c.dom.html.HTMLIFrameElement;
23

24
/**
25
 * The Class NodeImpl.
26
 */
27
public abstract class NodeImpl extends AbstractDomComponent implements Node {
1✔
28

29
    /** The Constant serialVersionUID. */
30
    private static final long serialVersionUID = 1L;
31

32
    /** The owner document. */
33
    private DocumentImpl _ownerDocument;
34

35
    /** The parent node. */
36
    private NodeImpl _parentNode;
37

38
    /** The first child. */
39
    private NodeImpl _firstChild;
40

41
    /** The next sibling. */
42
    private NodeImpl _nextSibling;
43

44
    /** The previous sibling. */
45
    private NodeImpl _previousSibling;
46

47
    /** The user data. */
48
    private Hashtable _userData = new Hashtable<>();
1✔
49

50
    /** The skip iframes. */
51
    static IteratorMask SKIP_IFRAMES = subtreeRoot -> subtreeRoot instanceof HTMLIFrameElement;
1✔
52

53
    /**
54
     * Initialize.
55
     *
56
     * @param ownerDocument
57
     *            the owner document
58
     */
59
    protected void initialize(DocumentImpl ownerDocument) {
60
        if (_ownerDocument != null) {
1!
61
            throw new IllegalStateException("NodeImpl already initialized");
×
62
        }
63
        if (ownerDocument == null) {
1!
64
            throw new IllegalArgumentException("No owner document specified");
×
65
        }
66
        _ownerDocument = ownerDocument;
1✔
67
    }
1✔
68

69
    // ------------------------------------------ ScriptableObject methods
70
    // --------------------------------------------------
71

72
    // ------------------------------------------ ScriptingEngine methods
73
    // --------------------------------------------------
74

75
    // ----------------------------------------------- Node methods
76
    // ---------------------------------------------------------
77

78
    @Override
79
    public Node getParentNode() {
80
        return _parentNode;
1✔
81
    }
82

83
    @Override
84
    public NodeList getChildNodes() {
85
        ArrayList list = new ArrayList<>();
1✔
86
        for (NodeImpl child = _firstChild; child != null; child = child._nextSibling) {
1✔
87
            list.add(child);
1✔
88
        }
89
        return new NodeListImpl(list);
1✔
90
    }
91

92
    @Override
93
    public Node getFirstChild() {
94
        return _firstChild;
1✔
95
    }
96

97
    @Override
98
    public Node getLastChild() {
99
        if (_firstChild == null) {
1✔
100
            return null;
1✔
101
        }
102

103
        Node child = _firstChild;
1✔
104
        while (child.getNextSibling() != null) {
1✔
105
            child = child.getNextSibling();
1✔
106
        }
107
        return child;
1✔
108
    }
109

110
    @Override
111
    public Node getPreviousSibling() {
112
        return _previousSibling;
1✔
113
    }
114

115
    @Override
116
    public Node getNextSibling() {
117
        return _nextSibling;
1✔
118
    }
119

120
    @Override
121
    public NamedNodeMap getAttributes() {
122
        return null;
1✔
123
    }
124

125
    @Override
126
    public Document getOwnerDocument() {
127
        return _ownerDocument;
1✔
128
    }
129

130
    @Override
131
    public Node insertBefore(Node newChild, Node refChild) throws DOMException {
132
        NodeImpl refChildNode = (NodeImpl) refChild;
1✔
133
        if (refChildNode.getParentNode() != this) {
1✔
134
            throw new DOMException(DOMException.NOT_FOUND_ERR, "Must specify an existing child as the reference");
1✔
135
        }
136
        NodeImpl newChildNode = getChildIfPermitted(newChild);
1✔
137
        removeFromTree(newChildNode);
1✔
138
        newChildNode._parentNode = this;
1✔
139
        if (refChildNode._previousSibling == null) {
1✔
140
            _firstChild = newChildNode;
1✔
141
        } else {
142
            refChildNode._previousSibling.setNextSibling(newChildNode);
1✔
143
        }
144
        newChildNode.setNextSibling(refChildNode);
1✔
145
        return newChildNode;
1✔
146
    }
147

148
    /**
149
     * Removes the from tree.
150
     *
151
     * @param childNode
152
     *            the child node
153
     */
154
    private void removeFromTree(NodeImpl childNode) {
155
        if (childNode._parentNode != null) {
1✔
156
            if (childNode._previousSibling != null) {
1✔
157
                childNode._previousSibling.setNextSibling(childNode._nextSibling);
1✔
158
            } else {
159
                childNode._parentNode._firstChild = childNode._nextSibling;
1✔
160
                childNode._nextSibling._previousSibling = null;
1✔
161
            }
162
            childNode._parentNode = null;
1✔
163
        }
164
    }
1✔
165

166
    @Override
167
    public Node replaceChild(Node newChild, Node oldChild) throws DOMException {
168
        insertBefore(newChild, oldChild);
1✔
169
        return removeChild(oldChild);
1✔
170
    }
171

172
    @Override
173
    public Node removeChild(Node oldChild) throws DOMException {
174
        if (oldChild.getParentNode() != this) {
1✔
175
            throw new DOMException(DOMException.NOT_FOUND_ERR, "May only remove a node from its own parent");
1✔
176
        }
177
        removeFromTree((NodeImpl) oldChild);
1✔
178
        return oldChild;
1✔
179
    }
180

181
    @Override
182
    public Node appendChild(Node newChild) throws DOMException {
183
        if (newChild == null) {
1!
184
            throw new IllegalArgumentException("child to append may not be null");
×
185
        }
186

187
        NodeImpl childNode = getChildIfPermitted(newChild);
1✔
188
        removeFromTree(childNode);
1✔
189
        childNode._parentNode = this;
1✔
190
        if (_firstChild == null) {
1✔
191
            _firstChild = childNode;
1✔
192
        } else {
193
            ((NodeImpl) getLastChild()).setNextSibling(childNode);
1✔
194
        }
195
        return newChild;
1✔
196
    }
197

198
    /**
199
     * Gets the child if permitted.
200
     *
201
     * @param proposedChild
202
     *            the proposed child
203
     *
204
     * @return the child if permitted
205
     */
206
    protected NodeImpl getChildIfPermitted(Node proposedChild) {
207
        if (!(proposedChild instanceof NodeImpl)) {
1!
208
            throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
×
209
                    "Specified node is from a different DOM implementation");
210
        }
211
        NodeImpl childNode = (NodeImpl) proposedChild;
1✔
212
        if (getOwnerDocument() != childNode._ownerDocument) {
1✔
213
            throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "Specified node is from a different document");
1✔
214
        }
215
        for (Node parent = this; parent != null; parent = parent.getParentNode()) {
1✔
216
            if (proposedChild == parent) {
1✔
217
                throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "May not add node as its own descendant");
1✔
218
            }
219
        }
220

221
        return childNode;
1✔
222
    }
223

224
    /**
225
     * Sets the next sibling.
226
     *
227
     * @param sibling
228
     *            the new next sibling
229
     */
230
    private void setNextSibling(NodeImpl sibling) {
231
        _nextSibling = sibling;
1✔
232
        if (sibling != null) {
1✔
233
            sibling._previousSibling = this;
1✔
234
        }
235
    }
1✔
236

237
    @Override
238
    public boolean hasChildNodes() {
239
        return _firstChild != null;
1✔
240
    }
241

242
    @Override
243
    public Node cloneNode(boolean deep) {
244
        return getOwnerDocument().importNode(this, deep);
1✔
245
    }
246

247
    @Override
248
    public void normalize() {
249
    }
×
250

251
    @Override
252
    public boolean isSupported(String feature, String version) {
253
        return false;
×
254
    }
255

256
    @Override
257
    public String getNamespaceURI() {
258
        return null;
1✔
259
    }
260

261
    @Override
262
    public String getPrefix() {
263
        return null;
×
264
    }
265

266
    @Override
267
    public void setPrefix(String prefix) throws DOMException {
268
    }
×
269

270
    @Override
271
    public String getLocalName() {
272
        return null;
1✔
273
    }
274

275
    @Override
276
    public boolean hasAttributes() {
277
        return false;
×
278
    }
279

280
    // ------------------------------------ DOM level 3 methods
281
    // -------------------------------------------------------------
282

283
    @Override
284
    public Object setUserData(String key, Object data, UserDataHandler handler) {
285
        return _userData.put(key, data);
×
286
    }
287

288
    @Override
289
    public Object getUserData(String key) {
290
        return _userData.get(key);
×
291
    }
292

293
    @Override
294
    public Object getFeature(String feature, String version) {
295
        return null;
×
296
    }
297

298
    @Override
299
    public boolean isEqualNode(Node arg) {
300
        return false; // To change body of implemented methods use File | Settings | File Templates.
×
301
    }
302

303
    @Override
304
    public String lookupNamespaceURI(String prefix) {
305
        return null; // To change body of implemented methods use File | Settings | File Templates.
×
306
    }
307

308
    @Override
309
    public String getBaseURI() {
310
        return null; // To change body of implemented methods use File | Settings | File Templates.
×
311
    }
312

313
    @Override
314
    public short compareDocumentPosition(Node other) throws DOMException {
315
        return 0; // To change body of implemented methods use File | Settings | File Templates.
×
316
    }
317

318
    @Override
319
    public String getTextContent() throws DOMException {
320
        return null; // To change body of implemented methods use File | Settings | File Templates.
×
321
    }
322

323
    @Override
324
    public void setTextContent(String textContent) throws DOMException {
325
        // To change body of implemented methods use File | Settings | File Templates.
326
    }
×
327

328
    @Override
329
    public boolean isSameNode(Node other) {
330
        return this == other;
1✔
331
    }
332

333
    @Override
334
    public String lookupPrefix(String namespaceURI) {
335
        return null; // To change body of implemented methods use File | Settings | File Templates.
×
336
    }
337

338
    @Override
339
    public boolean isDefaultNamespace(String namespaceURI) {
340
        return false; // To change body of implemented methods use File | Settings | File Templates.
×
341
    }
342

343
    // ----------------------------------------- implementation internals
344
    // ---------------------------------------------------
345

346
    /**
347
     * Gets the elements by tag name.
348
     *
349
     * @param name
350
     *            the name
351
     *
352
     * @return the elements by tag name
353
     */
354
    public NodeList getElementsByTagName(String name) {
355
        List matchingElements = new ArrayList<>();
1✔
356
        appendElementsWithTag(name, matchingElements);
1✔
357
        return new NodeListImpl(matchingElements);
1✔
358
    }
359

360
    /**
361
     * Append elements with tag.
362
     *
363
     * @param name
364
     *            the name
365
     * @param matchingElements
366
     *            the matching elements
367
     */
368
    private void appendElementsWithTag(String name, List matchingElements) {
369
        for (Node child = getFirstChild(); child != null; child = child.getNextSibling()) {
1✔
370
            if (child.getNodeType() != ELEMENT_NODE) {
1✔
371
                continue;
1✔
372
            }
373
            if (name.equals("*") || ((Element) child).getTagName().equalsIgnoreCase(name)) {
1✔
374
                matchingElements.add(child);
1✔
375
            }
376
            ((NodeImpl) child).appendElementsWithTag(name, matchingElements);
1✔
377
        }
378
    }
1✔
379

380
    /**
381
     * Gets the elements by tag names.
382
     *
383
     * @param names
384
     *            the names
385
     *
386
     * @return the elements by tag names
387
     */
388
    protected NodeList getElementsByTagNames(String[] names) {
389
        List matchingElements = new ArrayList<>();
1✔
390
        appendElementsWithTags(names, matchingElements);
1✔
391
        return new NodeListImpl(matchingElements);
1✔
392
    }
393

394
    /**
395
     * Append elements with tags.
396
     *
397
     * @param names
398
     *            the names
399
     * @param matchingElements
400
     *            the matching elements
401
     */
402
    void appendElementsWithTags(String[] names, List matchingElements) {
403
        for (Node child = getFirstChild(); child != null; child = child.getNextSibling()) {
1✔
404
            if (child.getNodeType() != ELEMENT_NODE) {
1✔
405
                continue;
1✔
406
            }
407
            String tagName = ((Element) child).getTagName();
1✔
408
            for (String name : names) {
1✔
409
                if (tagName.equalsIgnoreCase(name)) {
1✔
410
                    matchingElements.add(child);
1✔
411
                }
412
            }
413
            ((NodeImpl) child).appendElementsWithTags(names, matchingElements);
1✔
414
        }
415
    }
1✔
416

417
    /**
418
     * As text.
419
     *
420
     * @return the string
421
     */
422
    String asText() {
423
        StringBuilder sb = new StringBuilder();
1✔
424
        appendContents(sb);
1✔
425
        return sb.toString();
1✔
426
    }
427

428
    /**
429
     * Append contents.
430
     *
431
     * @param sb
432
     *            the sb
433
     */
434
    void appendContents(StringBuilder sb) {
435
        NodeList nl = getChildNodes();
1✔
436
        for (int i = 0; i < nl.getLength(); i++) {
1✔
437
            ((NodeImpl) nl.item(i)).appendContents(sb);
1✔
438
        }
439
    }
1✔
440

441
    /**
442
     * Pre order iterator.
443
     *
444
     * @return the iterator
445
     */
446
    public Iterator preOrderIterator() {
447
        return new PreOrderIterator(this);
1✔
448
    }
449

450
    /**
451
     * Pre order iterator.
452
     *
453
     * @param mask
454
     *            the mask
455
     *
456
     * @return the iterator
457
     */
458
    public Iterator preOrderIterator(IteratorMask mask) {
459
        return new PreOrderIterator(this, mask);
1✔
460
    }
461

462
    /**
463
     * Pre order iterator after node.
464
     *
465
     * @return the iterator
466
     */
467
    public Iterator preOrderIteratorAfterNode() {
468
        return new PreOrderIterator(PreOrderIterator.nextNode(this));
1✔
469
    }
470

471
    /**
472
     * Pre order iterator within node.
473
     *
474
     * @return the iterator
475
     */
476
    public Iterator preOrderIteratorWithinNode() {
477
        PreOrderIterator result = new PreOrderIterator(PreOrderIterator.nextNode(this));
1✔
478
        result.setDoNotLeaveNode(this);
1✔
479
        return result;
1✔
480
    }
481

482
    /**
483
     * Pre order iterator within node.
484
     *
485
     * @param mask
486
     *            the mask
487
     *
488
     * @return the iterator
489
     */
490
    public Iterator preOrderIteratorWithinNode(IteratorMask mask) {
491
        PreOrderIterator result = new PreOrderIterator(PreOrderIterator.nextNode(this), mask);
1✔
492
        result.setDoNotLeaveNode(this);
1✔
493
        return result;
1✔
494
    }
495

496
    /**
497
     * Pre order iterator after node.
498
     *
499
     * @param mask
500
     *            the mask
501
     *
502
     * @return the iterator
503
     */
504
    public Iterator preOrderIteratorAfterNode(IteratorMask mask) {
505
        return new PreOrderIterator(PreOrderIterator.nextNode(this), mask);
×
506
    }
507

508
    @Override
509
    protected String getJavaPropertyName(String propertyName) {
510
        if (propertyName.equals("document")) {
1✔
511
            return "ownerDocument";
1✔
512
        }
513
        return propertyName;
1✔
514
    }
515

516
    /**
517
     * allow masking of the iteration.
518
     */
519
    interface IteratorMask {
520

521
        /**
522
         * Skip subtree.
523
         *
524
         * @param subtreeRoot
525
         *            the subtree root
526
         *
527
         * @return true, if successful
528
         */
529
        // skip a given subtree
530
        boolean skipSubtree(Node subtreeRoot);
531
    }
532

533
    /**
534
     * iterator for Nodetrees that can be influenced with an Iterator mask to skip specific parts.
535
     */
536
    static class PreOrderIterator implements Iterator {
537

538
        /** The next node. */
539
        private NodeImpl _nextNode;
540

541
        /** The mask. */
542
        private IteratorMask _mask;
543

544
        /** The do not leave node. */
545
        private NodeImpl _doNotLeaveNode = null;
1✔
546

547
        /**
548
         * get the limit node.
549
         *
550
         * @return the do not leave node
551
         */
552
        public NodeImpl getDoNotLeaveNode() {
553
            return _doNotLeaveNode;
×
554
        }
555

556
        /**
557
         * limit the PreOrderIterator not to leave the given node.
558
         *
559
         * @param doNotLeaveNode
560
         *            the new do not leave node
561
         */
562
        public void setDoNotLeaveNode(NodeImpl doNotLeaveNode) {
563
            _doNotLeaveNode = doNotLeaveNode;
1✔
564
        }
1✔
565

566
        /**
567
         * check whether the node is a child of the doNotLeaveNode (if one is set).
568
         *
569
         * @param node
570
         *            the node
571
         *
572
         * @return true, if is child
573
         */
574
        private boolean isChild(Node node) {
575
            if (node == null) {
1✔
576
                return false;
1✔
577
            }
578
            if (_doNotLeaveNode == null) {
1✔
579
                return true;
1✔
580
            }
581
            Node parent = node.getParentNode();
1✔
582
            if (parent == null) {
1✔
583
                return false;
1✔
584
            }
585
            if (parent.isSameNode(_doNotLeaveNode)) {
1✔
586
                return true;
1✔
587
            }
588
            return isChild(parent);
1✔
589
        }
590

591
        /**
592
         * create a PreOrderIterator starting at a given currentNode.
593
         *
594
         * @param currentNode
595
         *            the current node
596
         */
597
        PreOrderIterator(NodeImpl currentNode) {
1✔
598
            _nextNode = currentNode;
1✔
599
        }
1✔
600

601
        /**
602
         * create a PreOrderIterator starting at a given currentNode and setting the iterator mask to the given mask.
603
         *
604
         * @param currentNode
605
         *            the current node
606
         * @param mask
607
         *            the mask
608
         */
609
        PreOrderIterator(NodeImpl currentNode, IteratorMask mask) {
610
            this(currentNode);
1✔
611
            _mask = mask;
1✔
612
        }
1✔
613

614
        /**
615
         * is there still a next node?
616
         */
617
        @Override
618
        public boolean hasNext() {
619
            return null != _nextNode;
1✔
620
        }
621

622
        /**
623
         * move one step in the tree
624
         */
625
        @Override
626
        public Object next() {
627
            NodeImpl currentNode = _nextNode;
1✔
628
            _nextNode = nextNode(_nextNode);
1✔
629
            while (_mask != null && _nextNode != null && _mask.skipSubtree(_nextNode)) {
1✔
630
                _nextNode = nextSubtree(_nextNode);
1✔
631
            }
632
            // check that we fit the doNotLeaveNode condition in case there is one
633
            if (!isChild(_nextNode)) {
1✔
634
                _nextNode = null;
1✔
635
            }
636
            return currentNode;
1✔
637
        }
638

639
        @Override
640
        public void remove() {
641
            throw new java.lang.UnsupportedOperationException();
×
642
        }
643

644
        /**
645
         * Next node.
646
         *
647
         * @param node
648
         *            the node
649
         *
650
         * @return the node impl
651
         */
652
        static NodeImpl nextNode(NodeImpl node) {
653
            if (node._firstChild != null) {
1✔
654
                return node._firstChild;
1✔
655
            }
656
            return nextSubtree(node);
1✔
657
        }
658

659
        /**
660
         * Next subtree.
661
         *
662
         * @param node
663
         *            the node
664
         *
665
         * @return the node impl
666
         */
667
        private static NodeImpl nextSubtree(NodeImpl node) {
668
            if (node._nextSibling != null) {
1✔
669
                return node._nextSibling;
1✔
670
            }
671
            while (node._parentNode != null) {
1✔
672
                node = node._parentNode;
1✔
673
                if (node._nextSibling != null) {
1✔
674
                    return node._nextSibling;
1✔
675
                }
676
            }
677
            return null;
1✔
678
        }
679
    }
680

681
}
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

© 2026 Coveralls, Inc