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

evolvedbinary / elemental / 982

29 Apr 2025 08:34PM UTC coverage: 56.409% (+0.007%) from 56.402%
982

push

circleci

adamretter
[feature] Improve README.md badges

28451 of 55847 branches covered (50.94%)

Branch coverage included in aggregate %.

77468 of 131924 relevant lines covered (58.72%)

0.59 hits per line

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

76.74
/exist-core/src/main/java/org/exist/dom/persistent/StoredNode.java
1
/*
2
 * Elemental
3
 * Copyright (C) 2024, Evolved Binary Ltd
4
 *
5
 * admin@evolvedbinary.com
6
 * https://www.evolvedbinary.com | https://www.elemental.xyz
7
 *
8
 * Use of this software is governed by the Business Source License 1.1
9
 * included in the LICENSE file and at www.mariadb.com/bsl11.
10
 *
11
 * Change Date: 2028-04-27
12
 *
13
 * On the date above, in accordance with the Business Source License, use
14
 * of this software will be governed by the Apache License, Version 2.0.
15
 *
16
 * Additional Use Grant: Production use of the Licensed Work for a permitted
17
 * purpose. A Permitted Purpose is any purpose other than a Competing Use.
18
 * A Competing Use means making the Software available to others in a commercial
19
 * product or service that: substitutes for the Software; substitutes for any
20
 * other product or service we offer using the Software that exists as of the
21
 * date we make the Software available; or offers the same or substantially
22
 * similar functionality as the Software.
23
 *
24
 * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
25
 *       The original license header is included below.
26
 *
27
 * =====================================================================
28
 *
29
 * eXist-db Open Source Native XML Database
30
 * Copyright (C) 2001 The eXist-db Authors
31
 *
32
 * info@exist-db.org
33
 * http://www.exist-db.org
34
 *
35
 * This library is free software; you can redistribute it and/or
36
 * modify it under the terms of the GNU Lesser General Public
37
 * License as published by the Free Software Foundation; either
38
 * version 2.1 of the License, or (at your option) any later version.
39
 *
40
 * This library is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43
 * Lesser General Public License for more details.
44
 *
45
 * You should have received a copy of the GNU Lesser General Public
46
 * License along with this library; if not, write to the Free Software
47
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
48
 */
49
package org.exist.dom.persistent;
50

51
import org.exist.EXistException;
52
import org.exist.dom.QName;
53
import org.exist.numbering.NodeId;
54
import org.exist.stax.ExtendedXMLStreamReader;
55
import org.exist.stax.IEmbeddedXMLStreamReader;
56
import org.exist.storage.DBBroker;
57
import org.exist.storage.NodePath;
58
import org.exist.storage.NodePath2;
59
import org.exist.storage.Signatures;
60
import org.exist.storage.dom.INodeIterator;
61
import org.exist.util.pool.NodePool;
62
import org.exist.xquery.Constants;
63
import org.exist.xquery.Expression;
64
import org.w3c.dom.Attr;
65
import org.w3c.dom.DOMException;
66
import org.w3c.dom.Node;
67

68
import javax.xml.stream.XMLStreamConstants;
69
import javax.xml.stream.XMLStreamException;
70
import java.io.IOException;
71

72
/**
73
 * The base class for all persistent DOM nodes in the database.
74
 *
75
 * @author <a href="mailto:meier@ifs.tu-darmstadt.de">Wolfgang Meier</a>
76
 */
77
public abstract class StoredNode<T extends StoredNode> extends NodeImpl<T> implements Visitable, NodeHandle, IStoredNode<T> {
78

79
    public static final int LENGTH_SIGNATURE_LENGTH = 1; //sizeof byte
80
    public static final long UNKNOWN_NODE_IMPL_ADDRESS = -1;
81

82
    protected NodeId nodeId = null;
1✔
83

84
    protected DocumentImpl ownerDocument = null;
1✔
85

86
    private long internalAddress = UNKNOWN_NODE_IMPL_ADDRESS;
1✔
87

88
    protected final short nodeType;
89

90
    /**
91
     * Creates a new <code>StoredNode</code> instance.
92
     *
93
     * @param nodeType a <code>short</code> value
94
     * return new StoredNode
95
     */
96
    protected StoredNode(final short nodeType) {
97
        this(null, nodeType);
×
98
    }
×
99

100
    /**
101
     * Creates a new <code>StoredNode</code> instance.
102
     *
103
     * @param expression the expression from which the node derives
104
     * @param nodeType a <code>short</code> value
105
     * return new StoredNode
106
     */
107
    protected StoredNode(final Expression expression, final short nodeType) {
108
        super(expression);
1✔
109
        this.nodeType = nodeType;
1✔
110
    }
1✔
111

112
    /**
113
     * Creates a new <code>StoredNode</code> instance.
114
     *
115
     * @param nodeType   a <code>short</code> value
116
     * @param nodeId     a <code>NodeId</code> value
117
     */
118
    protected StoredNode(final short nodeType, final NodeId nodeId) {
119
        this(null, nodeType, nodeId);
×
120
    }
×
121

122
    /**
123
     * Creates a new <code>StoredNode</code> instance.
124
     *
125
     * @param expression the expression from which the node derives
126
     * @param nodeType a <code>short</code> value
127
     * @param nodeId   a <code>NodeId</code> value
128
     */
129
    protected StoredNode(final Expression expression, final short nodeType, final NodeId nodeId) {
130
        super(expression);
1✔
131
        this.nodeType = nodeType;
1✔
132
        this.nodeId = nodeId;
1✔
133
    }
1✔
134

135
    protected StoredNode(final short nodeType, final NodeId nodeId, final DocumentImpl ownerDocument, long internalAddress) {
136
        this(null, nodeType, nodeId, ownerDocument, internalAddress);
×
137
    }
×
138

139
    protected StoredNode(final Expression expression, final short nodeType, final NodeId nodeId, final DocumentImpl ownerDocument, long internalAddress) {
140
        this(expression, nodeType, nodeId);
1✔
141
        this.ownerDocument = ownerDocument;
1✔
142
        this.internalAddress = internalAddress;
1✔
143
    }
1✔
144

145
    protected StoredNode(final StoredNode other) {
146
        this(null, other);
×
147
    }
×
148

149
    protected StoredNode(final Expression expression, final StoredNode other) {
150
        super(expression);
1✔
151
        this.nodeType = other.nodeType;
1✔
152
        this.nodeId = other.nodeId;
1✔
153
        this.internalAddress = other.internalAddress;
1✔
154
        this.ownerDocument = other.ownerDocument;
1✔
155
    }
1✔
156

157
    /**
158
     * Extracts just the details of the StoredNode
159
     * @return details of the stored node
160
     *
161
     */
162
    public StoredNode extract() {
163
        return new StoredNode(getExpression(), this) {
×
164
        };
165
    }
166

167
    /**
168
     * Reset this object to its initial state. Required by the
169
     * parser to be able to reuse node objects.
170
     */
171
    public void clear() {
172
        this.nodeId = null;
1✔
173
        this.internalAddress = UNKNOWN_NODE_IMPL_ADDRESS;
1✔
174
    }
1✔
175

176
    @Override
177
    public byte[] serialize() {
178
        throw new DOMException(DOMException.INVALID_ACCESS_ERR, "Can't serialize " + getClass().getName());
×
179
    }
180

181
    /**
182
     * Read a node from the specified byte array.
183
     *
184
     * This checks the node type and calls the {@link #deserialize(byte[], int, int, DocumentImpl, boolean)}
185
     * method of the corresponding node class.
186
     *
187
     * @param data the byte array to read a node from
188
     * @param start where to start
189
     * @param len how much to read
190
     * @param doc the doc to store the result in
191
     * @return StoredNode of given byte array
192
     */
193
    public static StoredNode deserialize(final byte[] data, final int start, final int len, final DocumentImpl doc) {
194
        return deserialize(data, start, len, doc, false);
1✔
195
    }
196

197
    /**
198
     * Read a node from the specified byte array.
199
     *
200
     * This checks the node type and calls the {@link #deserialize(byte[], int, int, DocumentImpl, boolean)}
201
     * method of the corresponding node class. The node will be allocated in the pool
202
     * and should be released once it is no longer needed.
203
     *
204
     * @param data the byte array to read a node from
205
      * @param start where to start
206
     * @param len how much to read
207
     * @param doc the doc to store the result in
208
     * @param pooled if true the node will be allocated in the pool
209
     * @return StoredNode of given byte array
210
     */
211
    public static StoredNode deserialize(final byte[] data, final int start, final int len, final DocumentImpl doc, boolean pooled) {
212
        final short type = Signatures.getType(data[start]);
1✔
213
        switch(type) {
1!
214
            case Node.TEXT_NODE:
215
                return TextImpl.deserialize(data, start, len, doc, pooled);
1✔
216
            case Node.ELEMENT_NODE:
217
                return ElementImpl.deserialize(data, start, len, doc, pooled);
1✔
218
            case Node.ATTRIBUTE_NODE:
219
                return AttrImpl.deserialize(data, start, len, doc, pooled);
1✔
220
            case Node.PROCESSING_INSTRUCTION_NODE:
221
                return ProcessingInstructionImpl.deserialize(data, start, len, doc, pooled);
1✔
222
            case Node.COMMENT_NODE:
223
                return CommentImpl.deserialize(data, start, len, doc, pooled);
1✔
224
            case Node.CDATA_SECTION_NODE:
225
                return CDATASectionImpl.deserialize(data, start, len, doc, pooled);
1✔
226
            default:
227
                LOG.error("Unknown node type: {}", type);
×
228
                Thread.dumpStack();
×
229
                return null;
×
230
        }
231
    }
232

233
    @Override
234
    public QName getQName() {
235
        return switch (getNodeType()) {
1!
236
            case Node.DOCUMENT_NODE -> QName.DOCUMENT_QNAME;
×
237
            case Node.TEXT_NODE -> QName.TEXT_QNAME;
1✔
238
            case Node.COMMENT_NODE -> QName.COMMENT_QNAME;
1✔
239
            case Node.DOCUMENT_TYPE_NODE -> QName.DOCTYPE_QNAME;
×
240
            case Node.CDATA_SECTION_NODE -> QName.CDATA_SECTION_QNAME;
1✔
241
            default -> {
242
                LOG.error("Unknown node type: {}", getNodeType());
×
243
                yield null;
×
244
            }
245
        };
246
    }
247

248
    @Override
249
    public void setQName(final QName qname) {
250
        //do nothing
251
    }
×
252

253
    @Override
254
    public boolean equals(final Object obj) {
255
        if(!(obj instanceof StoredNode)) {
1!
256
            return false;
×
257
        }
258
        return ((StoredNode) obj).nodeId.equals(nodeId);
1✔
259
    }
260

261
    @Override
262
    public void setNodeId(final NodeId dln) {
263
        this.nodeId = dln;
1✔
264
    }
1✔
265

266
    public NodeId getNodeId() {
267
        return nodeId;
1✔
268
    }
269

270
    @Override
271
    public long getInternalAddress() {
272
        return internalAddress;
1✔
273
    }
274

275
    @Override
276
    public void setInternalAddress(final long internalAddress) {
277
        this.internalAddress = internalAddress;
1✔
278
    }
1✔
279

280
    /**
281
     * Returns true if the node was modified recently and nodes
282
     * were inserted at the start or in the middle of its children.
283
     *
284
     * @return TRUE when node is 'dirty'
285
     */
286
    public boolean isDirty() {
287
        return true;
×
288
    }
289

290
    @Override
291
    public void setDirty(final boolean dirty) {
292
        //Nothing to do
293
    }
×
294

295
    @Override
296
    public short getNodeType() {
297
        return this.nodeType;
1✔
298
    }
299

300
    @Override
301
    public DocumentImpl getOwnerDocument() {
302
        return ownerDocument;
1✔
303
    }
304

305
    @Override
306
    public void setOwnerDocument(final DocumentImpl ownerDocument) {
307
        this.ownerDocument = ownerDocument;
1✔
308
    }
1✔
309

310
    @Override
311
    public Node getParentNode() {
312
        final NodeId parentId = nodeId.getParentId();
1✔
313
        if(parentId == NodeId.DOCUMENT_NODE) {
1✔
314
            return ownerDocument;
1✔
315
        }
316
        // Filter out the temporary nodes wrapper element
317
        if(parentId.getTreeLevel() == 1 && getOwnerDocument().getCollection().isTempCollection()) {
1!
318
            return ownerDocument;
×
319
        }
320
        return ownerDocument.getNode(parentId);
1✔
321
    }
322

323
    @Override
324
    public StoredNode getParentStoredNode() {
325
        final Node parent = getParentNode();
1✔
326
        return parent instanceof StoredNode ? (StoredNode) parent : null;
1✔
327
    }
328

329
    @Override
330
    public Node getPreviousSibling() {
331
        return getPreviousSibling(false);
1✔
332
    }
333

334
    Node getPreviousSibling(final boolean includeAttributes) {
335
        // if we are the root node, there is no sibling
336
        if(nodeId.equals(NodeId.ROOT_NODE)) {
1✔
337
            return null;
1✔
338
        }
339

340
        // handle siblings of level 1, e.g. a comment before/after a document element
341
        if(nodeId.getTreeLevel() == 1) {
1✔
342
            final NodeId siblingId = nodeId.precedingSibling();
1✔
343
            try(final DBBroker broker = ownerDocument.getBrokerPool().getBroker()) {
1✔
344
                return broker.objectWith(ownerDocument, siblingId);
1✔
345
            } catch(final EXistException e) {
×
346
                LOG.error("Internal error while reading previous sibling node: {}", e.getMessage(), e);
×
347
                //TODO : throw exception -pb
348
            }
349
        }
350

351
        // handle siblings of level 1+n
352
        final StoredNode parent = getParentStoredNode();
1✔
353
        if(parent != null && parent.isDirty()) {
1!
354
            try(final DBBroker broker = ownerDocument.getBrokerPool().getBroker()) {
1✔
355
                final int parentLevel = parent.getNodeId().getTreeLevel();
1✔
356
                final int level = nodeId.getTreeLevel();
1✔
357

358
                final IEmbeddedXMLStreamReader reader = broker.getXMLStreamReader(parent, true);
1✔
359

360
                IStoredNode last = null;
1✔
361
                while(reader.hasNext()) {
1!
362
                    final int status = reader.next();
1✔
363
                    final NodeId currentId = (NodeId) reader.getProperty(ExtendedXMLStreamReader.PROPERTY_NODE_ID);
1✔
364

365
                    if(status != XMLStreamConstants.END_ELEMENT) {
1✔
366
                        if (currentId.getTreeLevel() == level) {
1✔
367
                            if (currentId.equals(nodeId)) {
1✔
368
                                return last;
1✔
369
                            }
370
                            last = reader.getNode();
1✔
371
                        }
372
                    } else if (status == XMLStreamConstants.END_ELEMENT && currentId.getTreeLevel() == parentLevel) {
1!
373
                        // reached the end of the parent element
374
                        break;  // exit while loop
×
375
                    }
376
                }
377
            } catch(final IOException | XMLStreamException | EXistException e) {
×
378
                LOG.error("Internal error while reading child nodes: {}", e.getMessage(), e);
×
379
                //TODO : throw exception -pb
380
            }
381
            return null;
×
382
        }
383

384
        final NodeId firstChild = parent.getNodeId().newChild();
1✔
385
        if (nodeId.equals(firstChild)) {
1✔
386
            return null;
1✔
387
        }
388

389
        final NodeId siblingId = nodeId.precedingSibling();
1✔
390
        final Node precedingSibling = ownerDocument.getNode(siblingId);
1✔
391
        if (!includeAttributes && precedingSibling.getNodeType() == Node.ATTRIBUTE_NODE) {
1✔
392
            // NOTE(AR) guard against returning attributes
393
            return null;
1✔
394
        }
395
        return precedingSibling;
1✔
396
    }
397

398
    @Override
399
    public Node getNextSibling() {
400
        if(nodeId.getTreeLevel() == 2 && getOwnerDocument().getCollection().isTempCollection()) {
1!
401
            return null;
×
402
        }
403

404
        // handle siblings of level 1, e.g. a comment before/after a document element
405
        if(nodeId.getTreeLevel() == 1) {
1✔
406
            final NodeId siblingId = nodeId.nextSibling();
1✔
407
            try(final DBBroker broker = ownerDocument.getBrokerPool().getBroker()) {
1✔
408
                return broker.objectWith(ownerDocument, siblingId);
1✔
409
            } catch(final EXistException e) {
×
410
                LOG.error("Internal error while reading next sibling node: {}", e.getMessage(), e);
×
411
                //TODO : throw exception -pb
412
            }
413
        }
414

415
        // handle siblings of level 1+n
416
        final StoredNode parent = getParentStoredNode();
1✔
417
        if(parent != null && parent.isDirty()) {
1!
418
            try(final DBBroker broker = ownerDocument.getBrokerPool().getBroker()) {
1✔
419
                final int parentLevel = parent.getNodeId().getTreeLevel();
1✔
420
                final int level = nodeId.getTreeLevel();
1✔
421

422
                final IEmbeddedXMLStreamReader reader = broker.getXMLStreamReader(parent, true);
1✔
423

424
                while(reader.hasNext()) {
1!
425
                    final int status = reader.next();
1✔
426
                    final NodeId currentId = (NodeId) reader.getProperty(ExtendedXMLStreamReader.PROPERTY_NODE_ID);
1✔
427

428
                    if(status != XMLStreamConstants.END_ELEMENT
1✔
429
                            && currentId.getTreeLevel() == level
1✔
430
                            && currentId.compareTo(nodeId) > 0) {
1✔
431
                        return reader.getNode();
1✔
432
                    } else if(status == XMLStreamConstants.END_ELEMENT && currentId.getTreeLevel() == parentLevel) {
1!
433
                        // reached the end of the parent element
434
                        break;  // exit while loop
×
435
                    }
436
                }
437
            } catch(final IOException | XMLStreamException | EXistException e) {
×
438
                LOG.error("Internal error while reading child nodes: {}", e.getMessage(), e);
×
439
                //TODO : throw exception -pb
440
            }
441
            return null;
×
442

443
        }
444

445
        final NodeId siblingId = nodeId.nextSibling();
1✔
446
        return ownerDocument.getNode(siblingId);
1✔
447
    }
448

449
    protected IStoredNode getLastNode(final IStoredNode node) {
450

451
        // only applicable to elements with children or attributes
452
        if(!(node.hasChildNodes() || node.hasAttributes())) {
1✔
453
            return node;
1✔
454
        }
455

456
        try (final DBBroker broker = ownerDocument.getBrokerPool().getBroker()) {
1✔
457
            final int thisLevel = node.getNodeId().getTreeLevel();
1✔
458
            final int childLevel = thisLevel + 1;
1✔
459

460
            final IEmbeddedXMLStreamReader reader = broker.getXMLStreamReader(node, true);
1✔
461
            while (reader.hasNext()) {
1!
462
                final int status = reader.next();
1✔
463
                final NodeId otherId = (NodeId) reader.getProperty(ExtendedXMLStreamReader.PROPERTY_NODE_ID);
1✔
464
                final int otherLevel = otherId.getTreeLevel();
1✔
465

466
                //NOTE(AR): The order of the checks below has been carefully chosen to optimize non-empty children, which is likely the most common case!
467

468
                // skip descendants
469
                if (otherLevel > childLevel) {
1✔
470
                    continue;
1✔
471
                }
472

473
                if (status == XMLStreamConstants.END_ELEMENT && otherLevel == thisLevel) {
1✔
474
                    // we have finished scanning the children of the element...
475
                    break;  // exit-while
1✔
476
                }
477
            }
478

479
            return reader.getPreviousNode();
1✔
480
        } catch(final IOException | XMLStreamException | EXistException e) {
×
481
            LOG.error("Internal error while reading child nodes: {}", e.getMessage(), e);
×
482
            //TODO : throw exception -pb
483
        }
484
        return null;
×
485
    }
486

487
//    protected StoredNode getLastNode(final Iterator<StoredNode> iterator, final StoredNode node) {
488
//        if(!node.hasChildNodes()) {
489
//            return node;
490
//        }
491
//        final int children = node.getChildCount();
492
//        StoredNode next = null;
493
//        for(int i = 0; i < children; i++) {
494
//            next = iterator.next();
495
//            //Recursivity helps taversing...
496
//            next = getLastNode(iterator, next);
497
//        }
498
//        return next;
499
//    }
500

501
    @Override
502
    public NodePath getPath() {
503
        final NodePath2 path = new NodePath2();
1✔
504
        if(getNodeType() == Node.ELEMENT_NODE) {
1✔
505
            path.addNode(this);
1✔
506
        }
507

508
        Node parent;
509
        if(getNodeType() == Node.ATTRIBUTE_NODE) {
1✔
510
            parent = ((Attr)this).getOwnerElement();
1✔
511
        } else {
1✔
512
            parent = getParentNode();
1✔
513
        }
514

515
        while(parent != null && parent.getNodeType() != Node.DOCUMENT_NODE) {
1!
516
            path.addNode(parent);
1✔
517
            parent = parent.getParentNode();
1✔
518
        }
519

520
        path.reverseNodes();
1✔
521

522
        return path;
1✔
523
    }
524

525
    @Override
526
    public NodePath getPath(final NodePath parentPath) {
527
        if(getNodeType() == Node.ELEMENT_NODE) {
1✔
528
            parentPath.addComponent(getQName());
1✔
529
        }
530
        return parentPath;
1✔
531
    }
532

533
    @Override
534
    public String toString() {
535
        return nodeId.toString() + '\t' + getQName();
×
536
    }
537

538
    public String toString(final boolean top) {
539
        return toString();
×
540
    }
541

542
    /**
543
     * Release all memory resources hold by this node.
544
     */
545
    @Override
546
    public void release() {
547
        ownerDocument = null;
1✔
548
        clear();
1✔
549
        NodePool.getInstance().returnNode(this);
1✔
550
    }
1✔
551

552
    public boolean accept(final NodeVisitor visitor) {
553
        try(final DBBroker broker = ownerDocument.getBrokerPool().getBroker();
1✔
554
                final INodeIterator iterator = broker.getNodeIterator(this)) {
1✔
555
            iterator.next();
1✔
556
            return accept(iterator, visitor);
1✔
557
        } catch(final EXistException | IOException e) {
×
558
            LOG.error("Exception while reading node: {}", e.getMessage(), e);
×
559
            //TODO : throw exception -pb
560
        }
561
        return false;
×
562
    }
563

564
    @Override
565
    public boolean accept(final INodeIterator iterator, final NodeVisitor visitor) {
566
        return visitor.visit(this); //TODO iterator is not used here?
1✔
567
    }
568

569
    @Override
570
    public int compareTo(final StoredNode other) {
571
        if(other.ownerDocument == ownerDocument) {
×
572
            return nodeId.compareTo(other.nodeId);
×
573
        } else if(ownerDocument.getDocId() < other.ownerDocument.getDocId()) {
×
574
            return Constants.INFERIOR;
×
575
        } else {
576
            return Constants.SUPERIOR;
×
577
        }
578
    }
579

580
    @Override
581
    public boolean isSameNode(final Node other) {
582
        // This function is used by Saxon in some circumstances, and is required for proper Saxon operation.
583
        if(other instanceof IStoredNode) {
1!
584
            return (this.nodeId.equals(((IStoredNode<?>) other).getNodeId()) &&
1✔
585
                    this.ownerDocument.getDocId() == ((IStoredNode<? extends IStoredNode>) other).getOwnerDocument().getDocId());
1✔
586
        } else {
587
            return false;
×
588
        }
589
    }
590
}
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