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

evolvedbinary / elemental / 810

26 Apr 2025 05:03PM UTC coverage: 56.406% (-0.002%) from 56.408%
810

push

circleci

adamretter
[bugfix] Update Codacy badge

28452 of 55846 branches covered (50.95%)

Branch coverage included in aggregate %.

77458 of 131918 relevant lines covered (58.72%)

0.59 hits per line

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

68.35
/exist-core/src/main/java/org/exist/dom/memtree/DocumentImpl.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
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; version 2.1.
11
 *
12
 * This library is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with this library; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20
 *
21
 * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
22
 *       The original license header is included below.
23
 *
24
 * =====================================================================
25
 *
26
 * eXist-db Open Source Native XML Database
27
 * Copyright (C) 2001 The eXist-db Authors
28
 *
29
 * info@exist-db.org
30
 * http://www.exist-db.org
31
 *
32
 * This library is free software; you can redistribute it and/or
33
 * modify it under the terms of the GNU Lesser General Public
34
 * License as published by the Free Software Foundation; either
35
 * version 2.1 of the License, or (at your option) any later version.
36
 *
37
 * This library is distributed in the hope that it will be useful,
38
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
39
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
40
 * Lesser General Public License for more details.
41
 *
42
 * You should have received a copy of the GNU Lesser General Public
43
 * License along with this library; if not, write to the Free Software
44
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
45
 */
46
package org.exist.dom.memtree;
47

48
import org.exist.Database;
49
import org.exist.EXistException;
50
import org.exist.Namespaces;
51
import org.exist.dom.NodeListImpl;
52
import org.exist.dom.QName;
53
import org.exist.dom.QName.IllegalQNameException;
54
import org.exist.dom.memtree.reference.*;
55
import org.exist.dom.persistent.NodeProxy;
56
import org.exist.numbering.NodeId;
57
import org.exist.numbering.NodeIdFactory;
58
import org.exist.storage.BrokerPool;
59
import org.exist.storage.DBBroker;
60
import org.exist.storage.ElementValue;
61
import org.exist.storage.serializers.Serializer;
62
import org.exist.util.hashtable.NamePool;
63
import org.exist.util.serializer.AttrList;
64
import org.exist.util.serializer.Receiver;
65
import org.exist.xmldb.XmldbURI;
66
import org.exist.xquery.Expression;
67
import org.exist.xquery.NodeTest;
68
import org.exist.xquery.XPathException;
69
import org.exist.xquery.XQueryContext;
70
import org.exist.xquery.value.Sequence;
71
import org.exist.xquery.value.Type;
72
import org.w3c.dom.*;
73
import org.xml.sax.SAXException;
74

75
import javax.annotation.Nullable;
76
import javax.xml.XMLConstants;
77
import java.util.Arrays;
78
import java.util.Objects;
79
import java.util.concurrent.atomic.AtomicLong;
80

81
import static java.nio.charset.StandardCharsets.UTF_8;
82
import static org.exist.dom.QName.Validity.ILLEGAL_FORMAT;
83

84

85
/**
86
 * An in-memory implementation of Document.
87
 *
88
 * Nodes are stored in a series of arrays which are all indexed by the {@code nodeNum} (Node Number).
89
 * Nodes are stored into the arrays in a Depth First Preorder (Root, Left, Right) Traversal.
90
 * The {@code nodeNum} starts at 0, the array {@link #nodeKind} indicates the type of the Node.
91
 * For example, for a complete XML Document, when {@code nodeNum == 0}, then {@code nodeKind[nodeNum] == org.w3c.dom.Node.DOCUMENT_NODE}.
92
 *
93
 * The array {@link #treeLevel} indicates at what level in the tree the node appears, the root of the tree is 0.
94
 * For example, for a complete XML Document, when {@code nodeNum == 0}, then {@code treeLevel[nodeNum] == 0}.
95
 *
96
 * The array {@link #next} gives the {@code nodeNum} of the next node in the tree,
97
 * for example {@code int nextNodeNum = next[nodeNum]}.
98
 *
99
 * The following arrays hold the data of the nodes themselves:
100
 *  * {@link #namespaceParent}
101
 *  * {@link #namespaceCode}
102
 *  * {@link #nodeName}
103
 *  * {@link #alpha}
104
 *  * {@link #alphaLen}
105
 *  * {@link #characters}
106
 *  * {@link #nodeId}
107
 *  * {@link #attrName}
108
 *  * {@link #attrType}
109
 *  * {@link #attrNodeId}
110
 *  * {@link #attrParent}
111
 *  * {@link #attrValue}
112
 *  * {@link #references}
113
 *
114
 * This implementation stores all node data in the document object. Nodes from another document, i.e. a persistent document in the database, can be
115
 * stored as reference nodes, i.e. the nodes are not copied into this document object. Instead a reference is inserted which will only be expanded
116
 * during serialization.
117
 */
118
public class DocumentImpl extends NodeImpl<DocumentImpl> implements Document {
119

120
    private static final AtomicLong nextDocId = new AtomicLong();
1✔
121

122
    private static final int NODE_SIZE = 16;
123
    private static final int ATTR_SIZE = 8;
124
    private static final int CHAR_BUF_SIZE = 256;
125
    private static final int REF_SIZE = 8;
1✔
126

127
    protected DocumentType docType = null;
1✔
128

129
    // holds the node type of a node
130
    protected short[] nodeKind = null;
1✔
131

132
    // the tree level of a node
133
    protected short[] treeLevel;
134

135
    // the node number of the next sibling
136
    protected int[] next;
137

138
    // pointer into the namePool
139
    protected QName[] nodeName;
140

141
    protected NodeId[] nodeId;
142

143
    //alphanumeric content
144
    protected int[] alpha;
145
    protected int[] alphaLen;
146
    protected char[] characters = null;
1✔
147
    protected int nextChar = 0;
1✔
148

149
    // attributes
150
    protected QName[] attrName;
151
    protected int[] attrType;
152
    protected NodeId[] attrNodeId;
153
    protected int[] attrParent;
154
    protected String[] attrValue;
155
    protected int nextAttr = 0;
1✔
156

157
    // namespaces
158
    protected int[] namespaceParent = null;
1✔
159
    protected QName[] namespaceCode = null;
1✔
160
    protected int nextNamespace = 0;
1✔
161

162
    // the current number of nodes in the doc
163
    protected int size = 1;
1✔
164

165
    protected int documentRootNode = -1;
1✔
166

167
    protected String documentURI = null;
1✔
168

169
    // reference nodes (link to an external, persistent document fragment)
170
    protected NodeProxy[] references = null;
1✔
171
    protected int nextReferenceIdx = 0;
1✔
172
    // end reference nodes
173

174

175
    protected XQueryContext context;
176
    protected final boolean explicitlyCreated;
177
    protected final long docId;
178
    private Database db = null;
1✔
179
    protected NamePool namePool;
180

181
    boolean replaceAttribute = false;
1✔
182

183

184
    public DocumentImpl(final XQueryContext context, final boolean explicitlyCreated) {
185
        this(null, context, explicitlyCreated);
×
186
    }
×
187

188

189
    public DocumentImpl(final Expression expression, final XQueryContext context, final boolean explicitlyCreated) {
190
        super(expression, null, 0);
1✔
191
        this.context = context;
1✔
192
        this.explicitlyCreated = explicitlyCreated;
1✔
193
        this.docId = nextDocId.incrementAndGet();
1✔
194
        if(context == null) {
1✔
195
            namePool = new NamePool();
1✔
196
        } else {
1✔
197
            db = context.getDatabase();
1✔
198
            namePool = context.getSharedNamePool();
1✔
199
        }
200
    }
1✔
201

202
    private Database getDatabase() {
203
        if(db == null) {
1!
204
            try {
205
                db = BrokerPool.getInstance();
×
206
            } catch(final EXistException e) {
×
207
                throw new RuntimeException(e);
×
208
            }
209
        }
210
        return db;
1✔
211
    }
212

213
    private void init() {
214
        nodeKind = new short[NODE_SIZE];
1✔
215
        treeLevel = new short[NODE_SIZE];
1✔
216
        next = new int[NODE_SIZE];
1✔
217
        Arrays.fill(next, -1);
1✔
218
        nodeName = new QName[NODE_SIZE];
1✔
219
        nodeId = new NodeId[NODE_SIZE];
1✔
220
        alpha = new int[NODE_SIZE];
1✔
221
        alphaLen = new int[NODE_SIZE];
1✔
222
        Arrays.fill(alphaLen, -1);
1✔
223
        attrName = new QName[ATTR_SIZE];
1✔
224
        attrParent = new int[ATTR_SIZE];
1✔
225
        attrValue = new String[ATTR_SIZE];
1✔
226
        attrType = new int[ATTR_SIZE];
1✔
227
        attrNodeId = new NodeId[NODE_SIZE];
1✔
228
        treeLevel[0] = 0;
1✔
229
        nodeKind[0] = Node.DOCUMENT_NODE;
1✔
230
        document = this;
1✔
231
    }
1✔
232

233
    public void reset() {
234
        size = 0;
×
235
        nextChar = 0;
×
236
        nextAttr = 0;
×
237
        nextReferenceIdx = 0;
×
238
        references = null;
×
239
    }
×
240

241
    public int getSize() {
242
        return size;
1✔
243
    }
244

245
    public long getDocId() {
246
        return docId;
1✔
247
    }
248

249
    public boolean isExplicitlyCreated() {
250
        return explicitlyCreated;
1✔
251
    }
252

253
    public int addNode(final short kind, final short level, @Nullable final QName qname) {
254
        if (nodeKind == null) {
1✔
255
            init();
1✔
256
        }
257
        if (size == nodeKind.length) {
1✔
258
            grow();
1✔
259
        }
260
        nodeKind[size] = kind;
1✔
261
        treeLevel[size] = level;
1✔
262
        nodeName[size] = qname != null ? namePool.getSharedName(qname) : null;
1✔
263
        alpha[size] = -1; // undefined
1✔
264
        next[size] = -1;
1✔
265
        return (size++);
1✔
266
    }
267

268
    public void addChars(final int nodeNum, final char[] ch, final int start, final int len) {
269
        if(nodeKind == null) {
1!
270
            init();
×
271
        }
272
        if(characters == null) {
1✔
273
            characters = new char[len > CHAR_BUF_SIZE ? len : CHAR_BUF_SIZE];
1✔
274
        } else if((nextChar + len) >= characters.length) {
1✔
275
            int newLen = (characters.length * 3) / 2;
1✔
276
            if(newLen < (nextChar + len)) {
1✔
277
                newLen = nextChar + len;
1✔
278
            }
279
            final char[] nc = new char[newLen];
1✔
280
            System.arraycopy(characters, 0, nc, 0, characters.length);
1✔
281
            characters = nc;
1✔
282
        }
283
        alpha[nodeNum] = nextChar;
1✔
284
        alphaLen[nodeNum] = len;
1✔
285
        System.arraycopy(ch, start, characters, nextChar, len);
1✔
286
        nextChar += len;
1✔
287
    }
1✔
288

289
    public void addChars(final int nodeNum, final CharSequence s) {
290
        if(nodeKind == null) {
1!
291
            init();
×
292
        }
293
        int len = (s == null) ? 0 : s.length();
1!
294
        if(characters == null) {
1✔
295
            characters = new char[(len > CHAR_BUF_SIZE) ? len : CHAR_BUF_SIZE];
1✔
296
        } else if((nextChar + len) >= characters.length) {
1✔
297
            int newLen = (characters.length * 3) / 2;
1✔
298
            if(newLen < (nextChar + len)) {
1✔
299
                newLen = nextChar + len;
1✔
300
            }
301
            final char[] nc = new char[newLen];
1✔
302
            System.arraycopy(characters, 0, nc, 0, characters.length);
1✔
303
            characters = nc;
1✔
304
        }
305
        alpha[nodeNum] = nextChar;
1✔
306
        alphaLen[nodeNum] = len;
1✔
307
        for(int i = 0; i < len; i++) {
1✔
308
            characters[nextChar++] = s.charAt(i);
1✔
309
        }
310
    }
1✔
311

312
    public void appendChars(final int nodeNum, final char[] ch, final int start, final int len) {
313
        if(characters == null) {
1!
314
            characters = new char[(len > CHAR_BUF_SIZE) ? len : CHAR_BUF_SIZE];
×
315
        } else if((nextChar + len) >= characters.length) {
1✔
316
            int newLen = (characters.length * 3) / 2;
1✔
317
            if(newLen < (nextChar + len)) {
1!
318
                newLen = nextChar + len;
×
319
            }
320
            final char[] nc = new char[newLen];
1✔
321
            System.arraycopy(characters, 0, nc, 0, characters.length);
1✔
322
            characters = nc;
1✔
323
        }
324
        alphaLen[nodeNum] = alphaLen[nodeNum] + len;
1✔
325
        System.arraycopy(ch, start, characters, nextChar, len);
1✔
326
        nextChar += len;
1✔
327
    }
1✔
328

329
    public void appendChars(final int nodeNum, final CharSequence s) {
330
        final int len = s.length();
1✔
331
        if(characters == null) {
1!
332
            characters = new char[(len > CHAR_BUF_SIZE) ? len : CHAR_BUF_SIZE];
×
333
        } else if((nextChar + len) >= characters.length) {
1!
334
            int newLen = (characters.length * 3) / 2;
×
335
            if(newLen < (nextChar + len)) {
×
336
                newLen = nextChar + len;
×
337
            }
338
            final char[] nc = new char[newLen];
×
339
            System.arraycopy(characters, 0, nc, 0, characters.length);
×
340
            characters = nc;
×
341
        }
342
        alphaLen[nodeNum] = alphaLen[nodeNum] + len;
1✔
343
        for(int i = 0; i < len; i++) {
1✔
344
            characters[nextChar++] = s.charAt(i);
1✔
345
        }
346
    }
1✔
347

348
    void addReferenceNode(final int nodeNum, final NodeProxy proxy) {
349
        if (nodeKind == null) {
1!
350
            init();
×
351
        }
352
        if (references == null || nextReferenceIdx == references.length) {
1!
353
            growReferences();
1✔
354
        }
355
        references[nextReferenceIdx] = proxy;
1✔
356
        alpha[nodeNum] = nextReferenceIdx++;
1✔
357
    }
1✔
358

359
    public boolean hasReferenceNodes() {
360
        return references != null && references[0] != null;
1!
361
    }
362

363
    public void replaceReferenceNode(final int nodeNum, final CharSequence ch) {
364
        nodeKind[nodeNum] = Node.TEXT_NODE;
1✔
365
        references[alpha[nodeNum]] = null;
1✔
366
        addChars(nodeNum, ch);
1✔
367
    }
1✔
368

369
    public int addAttribute(final int nodeNum, final QName qname, final String value, final int type) throws DOMException {
370
        if(nodeKind == null) {
1✔
371
            init();
1✔
372
        }
373
        if((nodeNum > 0) && !(nodeKind[nodeNum] == Node.ELEMENT_NODE || nodeKind[nodeNum] == NodeImpl.NAMESPACE_NODE)) {
1!
374
            throw (new DOMException(DOMException.INUSE_ATTRIBUTE_ERR,
×
375
                "err:XQTY0024: An attribute node cannot follow a node that is not an element or namespace node."));
×
376
        }
377
        int prevAttr = nextAttr - 1;
1✔
378
        int attrN;
379
        //Check if an attribute with the same qname exists in the parent element
380
        while((nodeNum > 0) && (prevAttr > -1) && (attrParent[prevAttr] == nodeNum)) {
1✔
381
            attrN = prevAttr--;
1✔
382
            final QName prevQn = attrName[attrN];
1✔
383
            if(prevQn.equals(qname)) {
1✔
384
                if(replaceAttribute) {
1!
385
                    attrValue[attrN] = value;
×
386
                    attrType[attrN] = type;
×
387
                    return attrN;
×
388
                } else {
389
                    throw new DOMException(DOMException.INUSE_ATTRIBUTE_ERR,
1✔
390
                        "err:XQDY0025: element has more than one attribute '" + qname + "'");
1✔
391
                }
392
            }
393
        }
394
        if(nextAttr == attrName.length) {
1✔
395
            growAttributes();
1✔
396
        }
397
        final QName attrQname = new QName(qname.getLocalPart(), qname.getNamespaceURI(), qname.getPrefix(), ElementValue.ATTRIBUTE);
1✔
398
        attrParent[nextAttr] = nodeNum;
1✔
399
        attrName[nextAttr] = namePool.getSharedName(attrQname);
1✔
400
        attrValue[nextAttr] = value;
1✔
401
        attrType[nextAttr] = type;
1✔
402
        if(alpha[nodeNum] < 0) {
1✔
403
            alpha[nodeNum] = nextAttr;
1✔
404
        }
405
        return (nextAttr++);
1✔
406
    }
407

408
    public int addNamespace(final int nodeNum, final QName qname) {
409
        if(nodeKind == null) {
1✔
410
            init();
1✔
411
        }
412
        if((namespaceCode == null) || (nextNamespace == namespaceCode.length)) {
1✔
413
            growNamespaces();
1✔
414
        }
415
        namespaceCode[nextNamespace] = namePool.getSharedName(qname);
1✔
416
        namespaceParent[nextNamespace] = nodeNum;
1✔
417
        if(alphaLen[nodeNum] < 0) {
1✔
418
            alphaLen[nodeNum] = nextNamespace;
1✔
419
        }
420
        return nextNamespace++;
1✔
421
    }
422

423
    public short getTreeLevel(final int nodeNum) {
424
        return treeLevel[nodeNum];
1✔
425
    }
426

427
    public int getLastNode() {
428
        return size - 1;
1✔
429
    }
430

431
    public short getNodeType(final int nodeNum) {
432
        if((nodeKind == null) || (nodeNum < 0)) {
1!
433
            return -1;
×
434
        }
435
        return nodeKind[nodeNum];
1✔
436
    }
437

438
    @Override
439
    public String getStringValue() {
440
        if(document == null) {
1!
441
            return "";
×
442
        }
443
        return super.getStringValue();
1✔
444
    }
445

446
    private void grow() {
447
        final int newSize = (size * 3) / 2;
1✔
448

449
        final short[] newNodeKind = new short[newSize];
1✔
450
        System.arraycopy(nodeKind, 0, newNodeKind, 0, size);
1✔
451
        nodeKind = newNodeKind;
1✔
452

453
        final short[] newTreeLevel = new short[newSize];
1✔
454
        System.arraycopy(treeLevel, 0, newTreeLevel, 0, size);
1✔
455
        treeLevel = newTreeLevel;
1✔
456

457
        final int[] newNext = new int[newSize];
1✔
458
        Arrays.fill(newNext, -1);
1✔
459
        System.arraycopy(next, 0, newNext, 0, size);
1✔
460
        next = newNext;
1✔
461

462
        final QName[] newNodeName = new QName[newSize];
1✔
463
        System.arraycopy(nodeName, 0, newNodeName, 0, size);
1✔
464
        nodeName = newNodeName;
1✔
465

466
        final NodeId[] newNodeId = new NodeId[newSize];
1✔
467
        System.arraycopy(nodeId, 0, newNodeId, 0, size);
1✔
468
        nodeId = newNodeId;
1✔
469

470
        final int[] newAlpha = new int[newSize];
1✔
471
        System.arraycopy(alpha, 0, newAlpha, 0, size);
1✔
472
        alpha = newAlpha;
1✔
473

474
        final int[] newAlphaLen = new int[newSize];
1✔
475
        Arrays.fill(newAlphaLen, -1);
1✔
476
        System.arraycopy(alphaLen, 0, newAlphaLen, 0, size);
1✔
477
        alphaLen = newAlphaLen;
1✔
478
    }
1✔
479

480
    private void growAttributes() {
481
        final int size = attrName.length;
1✔
482
        final int newSize = (size * 3) / 2;
1✔
483

484
        final QName[] newAttrName = new QName[newSize];
1✔
485
        System.arraycopy(attrName, 0, newAttrName, 0, size);
1✔
486
        attrName = newAttrName;
1✔
487

488
        final int[] newAttrParent = new int[newSize];
1✔
489
        System.arraycopy(attrParent, 0, newAttrParent, 0, size);
1✔
490
        attrParent = newAttrParent;
1✔
491

492
        final String[] newAttrValue = new String[newSize];
1✔
493
        System.arraycopy(attrValue, 0, newAttrValue, 0, size);
1✔
494
        attrValue = newAttrValue;
1✔
495

496
        final int[] newAttrType = new int[newSize];
1✔
497
        System.arraycopy(attrType, 0, newAttrType, 0, size);
1✔
498
        attrType = newAttrType;
1✔
499

500
        final NodeId[] newNodeId = new NodeId[newSize];
1✔
501
        System.arraycopy(attrNodeId, 0, newNodeId, 0, size);
1✔
502
        attrNodeId = newNodeId;
1✔
503
    }
1✔
504

505
    private void growReferences() {
506
        if(references == null) {
1!
507
            references = new NodeProxy[REF_SIZE];
1✔
508
        } else {
1✔
509
            final int size = references.length;
×
510
            final int newSize = (size * 3) / 2;
×
511
            final NodeProxy[] newReferences = new NodeProxy[newSize];
×
512
            System.arraycopy(references, 0, newReferences, 0, size);
×
513
            references = newReferences;
×
514
        }
515
    }
1✔
516

517
    private void growNamespaces() {
518
        if(namespaceCode == null) {
1✔
519
            namespaceCode = new QName[5];
1✔
520
            namespaceParent = new int[5];
1✔
521
        } else {
1✔
522
            final int size = namespaceCode.length;
1✔
523
            final int newSize = (size * 3) / 2;
1✔
524

525
            final QName[] newCodes = new QName[newSize];
1✔
526
            System.arraycopy(namespaceCode, 0, newCodes, 0, size);
1✔
527
            namespaceCode = newCodes;
1✔
528

529
            final int[] newParents = new int[newSize];
1✔
530
            System.arraycopy(namespaceParent, 0, newParents, 0, size);
1✔
531
            namespaceParent = newParents;
1✔
532
        }
533
    }
1✔
534

535
    public NodeImpl getAttribute(final int nodeNum) throws DOMException {
536
        return new AttrImpl(getExpression(), this, nodeNum);
1✔
537
    }
538

539
    public NodeImpl getNamespaceNode(final int nodeNum) throws DOMException {
540
        return new NamespaceNode(getExpression(), this, nodeNum);
1✔
541
    }
542

543
    public NodeImpl getNode(final int nodeNum) throws DOMException {
544
        if (nodeNum == 0) {
1✔
545
            return this;
1✔
546
        }
547

548
        if (nodeNum >= size) {
1!
549
            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR, "node not found");
×
550
        }
551

552
        final NodeImpl<?> node;
553
        switch (nodeKind[nodeNum]) {
1!
554
            case Node.ELEMENT_NODE:
555
                node = new ElementImpl(getExpression(), this, nodeNum);
1✔
556
                break;
1✔
557

558
            case Node.TEXT_NODE:
559
                node = new TextImpl(getExpression(), this, nodeNum);
1✔
560
                break;
1✔
561

562
            case Node.COMMENT_NODE:
563
                node = new CommentImpl(getExpression(), this, nodeNum);
1✔
564
                break;
1✔
565

566
            case Node.PROCESSING_INSTRUCTION_NODE:
567
                node = new ProcessingInstructionImpl(getExpression(), this, nodeNum);
1✔
568
                break;
1✔
569

570
            case Node.CDATA_SECTION_NODE:
571
                node = new CDATASectionImpl(getExpression(), this, nodeNum);
1✔
572
                break;
1✔
573

574
            case NodeImpl.REFERENCE_NODE:
575
                final NodeProxy nodeProxy = references[alpha[nodeNum]];
1✔
576
                node = referenceFromNodeProxy(nodeNum, nodeProxy);
1✔
577
                break;
1✔
578

579
            default:
580
                throw new DOMException(DOMException.NOT_FOUND_ERR, "node not found");
×
581
        }
582
        return node;
1✔
583
    }
584

585
    private NodeImpl referenceFromNodeProxy(final int nodeNum, final NodeProxy nodeProxy) {
586
        final NodeImpl<?> node;
587
        switch (nodeProxy.getNodeType()) {
1!
588
            case Node.ELEMENT_NODE:
589
                node = new ElementReferenceImpl(getExpression(), this, nodeNum, nodeProxy);
1✔
590
                break;
1✔
591

592
            case Node.TEXT_NODE:
593
                node = new TextReferenceImpl(getExpression(), this, nodeNum, nodeProxy);
1✔
594
                break;
1✔
595

596
            case Node.PROCESSING_INSTRUCTION_NODE:
597
                node = new ProcessingInstructionReferenceImpl(getExpression(), this, nodeNum, nodeProxy);
1✔
598
                break;
1✔
599

600
            case Node.COMMENT_NODE:
601
                node = new CommentReferenceImpl(getExpression(), this, nodeNum, nodeProxy);
1✔
602
                break;
1✔
603

604
            default:
605
                throw new DOMException(DOMException.NOT_FOUND_ERR, "reference node not found");
×
606
        }
607

608
        return node;
1✔
609
    }
610

611
    public NodeImpl getLastAttr() {
612
        if(nextAttr == 0) {
×
613
            return null;
×
614
        }
615
        return new AttrImpl(getExpression(), this, nextAttr - 1);
×
616
    }
617

618
    @Override
619
    public Node getParentNode() {
620
        return null;
1✔
621
    }
622

623
    @Override
624
    public DocumentType getDoctype() {
625
        return docType;
1✔
626
    }
627

628
    public void setDoctype(final DocumentType docType) {
629
        this.docType = docType;
1✔
630
    }
1✔
631

632
    @Override
633
    public DOMImplementation getImplementation() {
634
        return new DOMImplementationImpl(getExpression());
×
635
    }
636

637
    @Override
638
    public Element getDocumentElement() {
639
        if(size == 1) {
1✔
640
            return null;
1✔
641
        }
642
        int nodeNum = 1;
1✔
643
        while(nodeKind[nodeNum] != Node.ELEMENT_NODE) {
1✔
644
            if(next[nodeNum] < nodeNum) {
1✔
645
                return null;
1✔
646
            }
647
            nodeNum = next[nodeNum];
1✔
648
        }
649
        return (Element)getNode(nodeNum);
1✔
650
    }
651

652
    @Override
653
    public Node getFirstChild() {
654
        if(size > 1) {
1!
655
            return getNode(1);
1✔
656
        }
657
        return null;
×
658
    }
659

660
    @Override
661
    public Node getLastChild() {
662
        return getFirstChild();
×
663
    }
664

665
    public int getAttributesCountFor(final int nodeNumber) {
666
        int count = 0;
1✔
667
        int attr = alpha[nodeNumber];
1✔
668
        if(-1 < attr) {
1✔
669
            while((attr < nextAttr) && (attrParent[attr++] == nodeNumber)) {
1✔
670
                ++count;
1✔
671
            }
672
        }
673
        return count;
1✔
674
    }
675

676
    public int getNamespacesCountFor(final int nodeNumber) {
677
        int count = 0;
1✔
678
        int ns = alphaLen[nodeNumber];
1✔
679
        if(-1 < ns) {
1✔
680
            while((ns < nextNamespace) && (namespaceParent[ns++] == nodeNumber)) {
1!
681
                ++count;
1✔
682
            }
683
        }
684
        return count;
1✔
685
    }
686

687
    public int getChildCountFor(final int nr) {
688
        int count = 0;
1✔
689
        int nextNode = getFirstChildFor(nr);
1✔
690
        while(nextNode > nr) {
1✔
691
            ++count;
1✔
692
            nextNode = next[nextNode];
1✔
693
        }
694
        return count;
1✔
695
    }
696

697
    public int getFirstChildFor(final int nodeNumber) {
698
        if (nodeNumber == 0) {
1✔
699
            // optimisation for document-node
700
            if (size > 1) {
1!
701
                return 1;
1✔
702
            } else {
703
                return -1;
×
704
            }
705
        }
706

707
        final short level = treeLevel[nodeNumber];
1✔
708
        final int nextNode = nodeNumber + 1;
1✔
709
        if((nextNode < size) && (treeLevel[nextNode] > level)) {
1✔
710
            return nextNode;
1✔
711
        }
712
        return -1;
1✔
713
    }
714

715
    public int getNextSiblingFor(final int nodeNumber) {
716
        final int nextNr = next[nodeNumber];
1✔
717
        return nextNr < nodeNumber ? -1 : nextNr;
1✔
718
    }
719

720
    public int getParentNodeFor(final int nodeNumber) {
721
        int nextNode = next[nodeNumber];
1✔
722
        while(nextNode > nodeNumber) {
1✔
723
            nextNode = next[nextNode];
1✔
724
        }
725
        return nextNode;
1✔
726
    }
727

728
    @Override
729
    public void selectChildren(final NodeTest test, final Sequence result) throws XPathException {
730
        if(size == 1) {
1!
731
            return;
×
732
        }
733
        NodeImpl next = (NodeImpl) getFirstChild();
1✔
734
        while(next != null) {
1✔
735
            if(test.matches(next)) {
1✔
736
                result.add(next);
1✔
737
            }
738
            next = (NodeImpl) next.getNextSibling();
1✔
739
        }
740
    }
1✔
741

742
    @Override
743
    public void selectDescendants(final boolean includeSelf, final NodeTest test, final Sequence result)
744
        throws XPathException {
745
        if(includeSelf && test.matches(this)) {
1!
746
            result.add(this);
×
747
        }
748
        if(size == 1) {
1✔
749
            return;
1✔
750
        }
751
        NodeImpl next = (NodeImpl) getFirstChild();
1✔
752
        while(next != null) {
1✔
753
            if(test.matches(next)) {
1✔
754
                result.add(next);
1✔
755
            }
756
            next.selectDescendants(includeSelf, test, result);
1✔
757
            next = (NodeImpl) next.getNextSibling();
1✔
758
        }
759
    }
1✔
760

761
    @Override
762
    public void selectDescendantAttributes(final NodeTest test, final Sequence result)
763
        throws XPathException {
764
        if(size == 1) {
1!
765
            return;
×
766
        }
767
        NodeImpl next = (NodeImpl) getFirstChild();
1✔
768
        while(next != null) {
1✔
769
            if(test.matches(next)) {
1!
770
                result.add(next);
×
771
            }
772
            next.selectDescendantAttributes(test, result);
1✔
773
            next = (NodeImpl) next.getNextSibling();
1✔
774
        }
775
    }
1✔
776

777
    /**
778
     * Gets a specified node of this document.
779
     *
780
     * @param   id  the ID of the node to select
781
     * @return  the specified node of this document, or null if this document
782
     *          does not have the specified node
783
     */
784
    public NodeImpl selectById(final String id) {
785
        return selectById(id, false);
1✔
786
    }
787

788
    /**
789
     * Gets a specified node of this document.
790
     *
791
     * @param   id              the ID of the node to select
792
     * @param   typeConsidered  if true, this method should consider node
793
     *                          type attributes (i.e. <code>xsi:type="xs:ID"</code>);
794
     *                          if false, this method should not consider
795
     *                          node type attributes
796
     * @return  the specified node of this document, or null if this document
797
     *          does not have the specified node
798
     */
799
    public NodeImpl selectById(final String id, final boolean typeConsidered) {
800
        if(size == 1) {
1!
801
            return null;
×
802
        }
803
        expand();
1✔
804
        final ElementImpl root = (ElementImpl) getDocumentElement();
1✔
805
        if (hasIdAttribute(root.getNodeNumber(), id)) {
1✔
806
            return root;
1✔
807
        }
808
        final int treeLevel = this.treeLevel[root.getNodeNumber()];
1✔
809
        int nextNode = root.getNodeNumber();
1✔
810
        while((++nextNode < document.size) && (document.treeLevel[nextNode] > treeLevel)) {
1!
811
            if (document.nodeKind[nextNode] == Node.ELEMENT_NODE) {
1✔
812
                if (hasIdAttribute(nextNode, id)) {
1✔
813
                    return getNode(nextNode);
1✔
814
                } else if (hasIdTypeAttribute(nextNode, id)) {
1✔
815
                    return typeConsidered ? (NodeImpl) getNode(nextNode).getParentNode() : getNode(nextNode);
1✔
816
                } else if (getNode(nextNode).getNodeName().equalsIgnoreCase("id") &&
1✔
817
                        getNode(nextNode).getStringValue().equals(id)) {
1✔
818
                    return typeConsidered ? (NodeImpl) getNode(nextNode).getParentNode() : getNode(nextNode);
1✔
819
                }
820
            }
821
        }
822
        return null;
1✔
823
    }
824

825
    public NodeImpl selectByIdref(final String id) {
826
        if(size == 1) {
×
827
            return null;
×
828
        }
829
        expand();
×
830
        final ElementImpl root = (ElementImpl) getDocumentElement();
×
831
        AttrImpl attr = getIdrefAttribute(root.getNodeNumber(), id);
×
832
        if(attr != null) {
×
833
            return attr;
×
834
        }
835
        final int treeLevel = this.treeLevel[root.getNodeNumber()];
×
836
        int nextNode = root.getNodeNumber();
×
837
        while((++nextNode < document.size) && (document.treeLevel[nextNode] > treeLevel)) {
×
838
            if(document.nodeKind[nextNode] == Node.ELEMENT_NODE) {
×
839
                attr = getIdrefAttribute(nextNode, id);
×
840
                if(attr != null) {
×
841
                    return attr;
×
842
                }
843
            }
844
        }
845
        return null;
×
846
    }
847

848
    private boolean hasIdAttribute(final int nodeNumber, final String id) {
849
        int attr = document.alpha[nodeNumber];
1✔
850
        if(-1 < attr) {
1✔
851
            while((attr < document.nextAttr) && (document.attrParent[attr] == nodeNumber)) {
1✔
852
                if((document.attrType[attr] == AttrImpl.ATTR_ID_TYPE) &&
1✔
853
                        id.equals(document.attrValue[attr])) {
1✔
854
                    return true;
1✔
855
                } else if (document.attrName[attr].getLocalPart().equals("id") &&
1✔
856
                           Objects.equals(document.attrValue[attr], id)) {
1✔
857
                    return true;
1✔
858
                }
859
                ++attr;
1✔
860
            }
861
        }
862
        return false;
1✔
863
    }
864

865
    private boolean hasIdTypeAttribute(final int nodeNumber, final String id) {
866
        int attr = document.alpha[nodeNumber];
1✔
867
        if(-1 < attr) {
1✔
868
            while((attr < document.nextAttr) && (document.attrParent[attr] == nodeNumber)) {
1✔
869
                if (document.attrName[attr].getStringValue().equals(Namespaces.XSI_TYPE_QNAME.getStringValue()) &&
1✔
870
                        document.attrValue[attr].equals(Namespaces.XS_ID_QNAME.getStringValue()) &&
1!
871
                        document.getNode(nodeNumber).getStringValue().equals(id)) {
1!
872
                    return true;
1✔
873
                }
874
                ++attr;
1✔
875
            }
876
        }
877
        return false;
1✔
878
    }
879

880
    private AttrImpl getIdrefAttribute(final int nodeNumber, final String id) {
881
        int attr = document.alpha[nodeNumber];
×
882
        if(-1 < attr) {
×
883
            while((attr < document.nextAttr) && (document.attrParent[attr] == nodeNumber)) {
×
884
                if((document.attrType[attr] == AttrImpl.ATTR_IDREF_TYPE) &&
×
885
                    id.equals(document.attrValue[attr])) {
×
886
                    return new AttrImpl(getExpression(), this, attr);
×
887
                }
888
                ++attr;
×
889
            }
890
        }
891
        return null;
×
892
    }
893

894
    @Override
895
    public boolean matchChildren(final NodeTest test) throws XPathException {
896
        if(size == 1) {
×
897
            return false;
×
898
        }
899
        NodeImpl next = (NodeImpl) getFirstChild();
×
900
        while(next != null) {
×
901
            if(test.matches(next)) {
×
902
                return true;
×
903
            }
904
            next = (NodeImpl) next.getNextSibling();
×
905
        }
906
        return false;
×
907
    }
908

909
    @Override
910
    public boolean matchDescendants(final boolean includeSelf, final NodeTest test) throws XPathException {
911
        if(includeSelf && test.matches(this)) {
×
912
            return true;
×
913
        }
914
        if(size == 1) {
×
915
            return true;
×
916
        }
917
        NodeImpl next = (NodeImpl) getFirstChild();
×
918
        while(next != null) {
×
919
            if(test.matches(next)) {
×
920
                return true;
×
921
            }
922
            if(next.matchDescendants(includeSelf, test)) {
×
923
                return true;
×
924
            }
925
            next = (NodeImpl) next.getNextSibling();
×
926
        }
927
        return false;
×
928
    }
929

930
    @Override
931
    public boolean matchDescendantAttributes(final NodeTest test) throws XPathException {
932
        if(size == 1) {
×
933
            return false;
×
934
        }
935
        NodeImpl next = (NodeImpl) getFirstChild();
×
936
        while(next != null) {
×
937
            if(test.matches(next)) {
×
938
                return true;
×
939
            }
940
            if(next.matchDescendantAttributes(test)) {
×
941
                return true;
×
942
            }
943
            next = (NodeImpl) next.getNextSibling();
×
944
        }
945
        return false;
×
946
    }
947

948
    @Override
949
    public Element createElement(final String tagName) throws DOMException {
950
        final QName qname;
951
        try {
952
            if (getContext() != null) {
×
953
                qname = QName.parse(getContext(), tagName);
×
954
            } else {
×
955
                qname = new QName(tagName);
×
956
            }
957
        } catch(final IllegalQNameException e) {
×
958
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, e.getMessage());
×
959
        }
960

961
        // check the QName is valid for use
962
        if(qname.isValid(false) != QName.Validity.VALID.val) {
×
963
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, "name is invalid");
×
964
        }
965

966
        final int nodeNum = addNode(Node.ELEMENT_NODE, (short) 1, qname);
×
967
        return new ElementImpl(getExpression(), this, nodeNum);
×
968
    }
969

970
    @Override
971
    public Element createElementNS(final String namespaceURI, final String qualifiedName) throws DOMException {
972
        final QName qname;
973
        try {
974
            if (getContext() != null) {
×
975
                qname = QName.parse(getContext(), qualifiedName, namespaceURI);
×
976
            } else {
×
977
                qname = QName.parse(namespaceURI, qualifiedName);
×
978
            }
979
        } catch(final IllegalQNameException e) {
×
980
            final short errCode;
981
            if(e.getValidity() == ILLEGAL_FORMAT.val || (e.getValidity() & QName.Validity.INVALID_NAMESPACE.val) == QName.Validity.INVALID_NAMESPACE.val) {
×
982
                errCode = DOMException.NAMESPACE_ERR;
×
983
            } else {
×
984
                errCode = DOMException.INVALID_CHARACTER_ERR;
×
985
            }
986
            throw new DOMException(errCode, "qualified name is invalid");
×
987
        }
988

989
        // check the QName is valid for use
990
        final byte validity = qname.isValid(false);
×
991
        if((validity & QName.Validity.INVALID_LOCAL_PART.val) == QName.Validity.INVALID_LOCAL_PART.val) {
×
992
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, "qualified name is invalid");
×
993
        } else if((validity & QName.Validity.INVALID_NAMESPACE.val) == QName.Validity.INVALID_NAMESPACE.val) {
×
994
            throw new DOMException(DOMException.NAMESPACE_ERR, "qualified name is invalid");
×
995
        }
996

997
        final int nodeNum = addNode(Node.ELEMENT_NODE, (short) 1, qname);
×
998
        return new ElementImpl(getExpression(), this, nodeNum);
×
999
    }
1000

1001
    @Override
1002
    public DocumentFragment createDocumentFragment() {
1003
        return new DocumentFragmentImpl(getExpression());
×
1004
    }
1005

1006
    @Override
1007
    public Text createTextNode(final String data) {
1008
        return null;
×
1009
    }
1010

1011
    @Override
1012
    public Comment createComment(final String data) {
1013
        return null;
×
1014
    }
1015

1016
    @Override
1017
    public CDATASection createCDATASection(final String data) throws DOMException {
1018
        return null;
×
1019
    }
1020

1021
    @Override
1022
    public ProcessingInstruction createProcessingInstruction(final String target, final String data)
1023
            throws DOMException {
1024
        return null;
×
1025
    }
1026

1027
    @Override
1028
    public Attr createAttribute(final String name) throws DOMException {
1029
        final QName qname;
1030
        try {
1031
            if(getContext() != null) {
×
1032
                qname = QName.parse(getContext(), name);
×
1033
            } else {
×
1034
                qname = new QName(name);
×
1035
            }
1036
        } catch (final IllegalQNameException e) {
×
1037
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, e.getMessage());
×
1038
        }
1039

1040
        // check the QName is valid for use
1041
        if(qname.isValid(false) != QName.Validity.VALID.val) {
×
1042
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, "name is invalid");
×
1043
        }
1044

1045
        // TODO(AR) implement this!
1046
        throw unsupported();
×
1047
    }
1048

1049
    @Override
1050
    public Attr createAttributeNS(final String namespaceURI, final String qualifiedName) throws DOMException {
1051
        final QName qname;
1052
        try {
1053
            if(getContext() != null) {
×
1054
                qname = QName.parse(getContext(), qualifiedName, namespaceURI);
×
1055
            } else {
×
1056
                qname = QName.parse(namespaceURI, qualifiedName);
×
1057
            }
1058
        } catch (final IllegalQNameException e) {
×
1059
            final short errCode;
1060
            if(e.getValidity() == ILLEGAL_FORMAT.val || (e.getValidity() & QName.Validity.INVALID_NAMESPACE.val) == QName.Validity.INVALID_NAMESPACE.val) {
×
1061
                errCode = DOMException.NAMESPACE_ERR;
×
1062
            } else {
×
1063
                errCode = DOMException.INVALID_CHARACTER_ERR;
×
1064
            }
1065
            throw new DOMException(errCode, "qualified name is invalid");
×
1066
        }
1067

1068
        // check the QName is valid for use
1069
        final byte validity = qname.isValid(false);
×
1070
        if((validity & QName.Validity.INVALID_LOCAL_PART.val) == QName.Validity.INVALID_LOCAL_PART.val) {
×
1071
            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, "qualified name is invalid");
×
1072
        } else if((validity & QName.Validity.INVALID_NAMESPACE.val) == QName.Validity.INVALID_NAMESPACE.val) {
×
1073
            throw new DOMException(DOMException.NAMESPACE_ERR, "qualified name is invalid");
×
1074
        }
1075

1076
        // TODO(AR) implement this!
1077
        throw unsupported();
×
1078
    }
1079

1080
    @Override
1081
    public EntityReference createEntityReference(final String name) throws DOMException {
1082
        return null;
×
1083
    }
1084

1085
    @Override
1086
    public NodeList getElementsByTagName(final String tagname) {
1087
        if(tagname != null && tagname.equals(QName.WILDCARD)) {
1!
1088
            return getElementsByTagName(new QName.WildcardLocalPartQName(XMLConstants.DEFAULT_NS_PREFIX));
×
1089
        } else {
1090
            final QName qname;
1091
            try {
1092
                if (document.getContext() != null) {
1!
1093
                    qname = QName.parse(document.context, tagname);
×
1094

1095
                } else {
×
1096
                    qname = new QName(tagname);
1✔
1097
                }
1098
            } catch (final IllegalQNameException e) {
1✔
1099
                throw new DOMException(DOMException.INVALID_CHARACTER_ERR, e.getMessage());
×
1100
            }
1101
            return getElementsByTagName(qname);
1✔
1102
        }
1103
    }
1104

1105
    @Override
1106
    public NodeList getElementsByTagNameNS(final String namespaceURI, final String localName) {
1107
        final boolean wildcardNS = namespaceURI != null && namespaceURI.equals(QName.WILDCARD);
×
1108
        final boolean wildcardLocalPart = localName != null && localName.equals(QName.WILDCARD);
×
1109

1110
        if(wildcardNS && wildcardLocalPart) {
×
1111
            return getElementsByTagName(QName.WildcardQName.getInstance());
×
1112
        } else if(wildcardNS) {
×
1113
            return getElementsByTagName(new QName.WildcardNamespaceURIQName(localName));
×
1114
        } else if(wildcardLocalPart) {
×
1115
            return getElementsByTagName(new QName.WildcardLocalPartQName(namespaceURI));
×
1116
        } else {
1117
            final QName qname;
1118
            if (document.getContext() != null) {
×
1119
                try {
1120
                    qname = QName.parse(document.context, localName, namespaceURI);
×
1121
                } catch (final IllegalQNameException e) {
×
1122
                    throw new DOMException(DOMException.INVALID_CHARACTER_ERR, e.getMessage());
×
1123
                }
1124
            } else {
1125
                qname = new QName(localName, namespaceURI);
×
1126
            }
1127
            return getElementsByTagName(qname);
×
1128
        }
1129
    }
1130

1131
    private NodeList getElementsByTagName(final QName qname) {
1132
        final NodeListImpl nl = new NodeListImpl();
1✔
1133
        for(int i = 1; i < size; i++) {
1✔
1134
            if(nodeKind[i] == Node.ELEMENT_NODE) {
1✔
1135
                final QName qn = nodeName[i];
1✔
1136
                if(qn.matches(qname)) {
1✔
1137
                    nl.add(getNode(i));
1✔
1138
                }
1139
            }
1140
        }
1141
        return nl;
1✔
1142
    }
1143

1144
    @Override
1145
    public Node importNode(final Node importedNode, final boolean deep) throws DOMException {
1146
        return null;
×
1147
    }
1148

1149
    @Override
1150
    public Element getElementById(final String elementId) {
1151
        return null;
×
1152
    }
1153

1154
    @Override
1155
    public DocumentImpl getOwnerDocument() {
1156
        return null;
1✔
1157
    }
1158

1159
    /**
1160
     * Copy the document fragment starting at the specified node to the given document builder.
1161
     *
1162
     * @param node node to provide document fragment
1163
     * @param receiver document builder
1164
     * @throws SAXException DOCUMENT ME!
1165
     */
1166
    public void copyTo(final NodeImpl node, final DocumentBuilderReceiver receiver) throws SAXException {
1167
        copyTo(null, node, receiver);
1✔
1168
    }
1✔
1169

1170
    private void copyTo(@Nullable final Serializer serializer, NodeImpl node, final DocumentBuilderReceiver receiver) throws SAXException {
1171
        final NodeImpl top = node;
1✔
1172

1173
        @Nullable NodeImpl nextNode;
1174
        while (node != null) {
1✔
1175
            if (node instanceof AbstractReferenceNodeImpl) {
1✔
1176
                if (serializer != null) {
1✔
1177
                    serializer.toReceiver(((AbstractReferenceNodeImpl) node).getNodeProxy(), false, false);
1✔
1178
                } else {
1✔
1179
                    receiver.addReferenceNode(document.references[document.alpha[node.nodeNumber]]);
1✔
1180
                }
1181
                nextNode = (NodeImpl) node.getNextSibling();
1✔
1182

1183
            } else {
1✔
1184
                copyStartNode(node, receiver);
1✔
1185
                nextNode = (NodeImpl) node.getFirstChild();
1✔
1186
            }
1187

1188
            while (nextNode == null) {
1✔
1189
                if (!(node instanceof AbstractReferenceNodeImpl)) {
1✔
1190
                    copyEndNode(node, receiver);
1✔
1191

1192
                    if (top.nodeNumber == node.nodeNumber) {
1✔
1193
                        break;
1✔
1194
                    }
1195
                }
1196

1197
                //No nextNode if the top node is a Document node
1198
                nextNode = (NodeImpl) node.getNextSibling();
1✔
1199
                if (nextNode == null) {
1✔
1200
                    node = (NodeImpl) node.getParentNode();
1✔
1201
                    if (node == null || top.nodeNumber == node.nodeNumber) {
1✔
1202
                        if (node != null) {
1✔
1203
                            copyEndNode(node, receiver);
1✔
1204
                        }
1205
                        break;
1✔
1206
                    }
1207
                }
1208
            }
1209

1210
            node = nextNode;
1✔
1211
        }
1212
    }
1✔
1213

1214
    private void copyStartNode(final NodeImpl node, final DocumentBuilderReceiver receiver) throws SAXException {
1215
        final int nr = node.nodeNumber;
1✔
1216
        switch (node.getNodeType()) {
1✔
1217
            case Node.ELEMENT_NODE: {
1218
                final QName nodeName = document.nodeName[nr];
1✔
1219
                receiver.startElement(nodeName, null);
1✔
1220
                int attr = document.alpha[nr];
1✔
1221
                if(-1 < attr) {
1✔
1222
                    while((attr < document.nextAttr) && (document.attrParent[attr] == nr)) {
1✔
1223
                        final QName attrQName = document.attrName[attr];
1✔
1224
                        receiver.attribute(attrQName, attrValue[attr]);
1✔
1225
                        ++attr;
1✔
1226
                    }
1227
                }
1228
                int ns = document.alphaLen[nr];
1✔
1229
                if(-1 < ns) {
1✔
1230
                    while((ns < document.nextNamespace) && (document.namespaceParent[ns] == nr)) {
1!
1231
                        final QName nsQName = document.namespaceCode[ns];
1✔
1232
                        receiver.addNamespaceNode(nsQName);
1✔
1233
                        ++ns;
1✔
1234
                    }
1235
                }
1236
                break;
×
1237
            }
1238

1239
            case Node.TEXT_NODE:
1240
                receiver.characters(document.characters, document.alpha[nr], document.alphaLen[nr]);
1✔
1241
                break;
1✔
1242

1243
            case Node.CDATA_SECTION_NODE:
1244
                receiver.cdataSection(document.characters, document.alpha[nr], document.alphaLen[nr]);
1✔
1245
                break;
1✔
1246

1247
            case Node.ATTRIBUTE_NODE:
1248
                final QName attrQName = document.attrName[nr];
1✔
1249
                receiver.attribute(attrQName, attrValue[nr]);
1✔
1250
                break;
1✔
1251

1252
            case Node.COMMENT_NODE:
1253
                receiver.comment(document.characters, document.alpha[nr], document.alphaLen[nr]);
1✔
1254
                break;
1✔
1255

1256
            case Node.PROCESSING_INSTRUCTION_NODE:
1257
                final QName piQName = document.nodeName[nr];
1✔
1258
                final String data = new String(document.characters, document.alpha[nr], document.alphaLen[nr]);
1✔
1259
                receiver.processingInstruction(piQName.getLocalPart(), data);
1✔
1260
                break;
1✔
1261

1262
            case NodeImpl.NAMESPACE_NODE:
1263
                receiver.addNamespaceNode(document.namespaceCode[nr]);
1✔
1264
                break;
1265
        }
1266
    }
1✔
1267

1268
    private void copyEndNode(final NodeImpl node, final DocumentBuilderReceiver receiver) throws SAXException {
1269
        if (node.getNodeType() == Node.ELEMENT_NODE) {
1✔
1270
            receiver.endElement(node.getQName());
1✔
1271
        }
1272
    }
1✔
1273

1274
    /**
1275
     * Expand all reference nodes in the current document, i.e. replace them by real nodes. Reference nodes are just pointers to nodes from other
1276
     * documents stored in the database. The XQuery engine uses reference nodes to speed up the creation of temporary doc fragments.
1277
     *
1278
     * This method creates a new copy of the document contents and expands all reference nodes.
1279
     *
1280
     * @throws DOMException DOCUMENT ME!
1281
     */
1282
    @Override
1283
    public void expand() throws DOMException {
1284
        // TODO(AR) see if we can get rid of expansion now we have org.exist.dom.memtree.reference.* classes
1285
        if(size == 0) {
1!
1286
            return;
×
1287
        }
1288
        final DocumentImpl newDoc = expandRefs(null);
1✔
1289
        copyDocContents(newDoc);
1✔
1290
    }
1✔
1291

1292
    // TODO(AR) see if we can get rid of expansion now we have org.exist.dom.memtree.reference.* classes
1293
    public DocumentImpl expandRefs(final NodeImpl rootNode) throws DOMException {
1294
        if(nextReferenceIdx == 0) {
1✔
1295
            computeNodeIds();
1✔
1296
            return this;
1✔
1297
        }
1298
        final MemTreeBuilder builder = new MemTreeBuilder(getExpression(), context);
1✔
1299
        final DocumentBuilderReceiver receiver = new DocumentBuilderReceiver(getExpression(), builder);
1✔
1300
        try (final DBBroker broker = getDatabase().getBroker()) {
1✔
1301
            final Serializer serializer = broker.borrowSerializer();
1✔
1302
            try {
1303
                serializer.setProperty(Serializer.GENERATE_DOC_EVENTS, "false");
1✔
1304
                serializer.setReceiver(receiver);
1✔
1305

1306
                builder.startDocument();
1✔
1307
                NodeImpl node = (rootNode == null) ? (NodeImpl) getFirstChild() : rootNode;
1!
1308
                while (node != null) {
1✔
1309
                    copyTo(serializer, node, receiver);
1✔
1310
                    node = (NodeImpl) node.getNextSibling();
1✔
1311
                }
1312
                receiver.endDocument();
1✔
1313
            } finally {
1✔
1314
                broker.returnSerializer(serializer);
1✔
1315
            }
1316
        } catch (final SAXException | EXistException e) {
×
1317
            throw new DOMException(DOMException.INVALID_STATE_ERR, e.getMessage());
×
1318
        }
1319
        final DocumentImpl newDoc = builder.getDocument();
1✔
1320
        newDoc.computeNodeIds();
1✔
1321
        return newDoc;
1✔
1322
    }
1323

1324
    public NodeImpl getNodeById(final NodeId id) {
1325
        expand();
×
1326
        for(int i = 0; i < size; i++) {
×
1327
            if(id.equals(nodeId[i])) {
×
1328
                return getNode(i);
×
1329
            }
1330
        }
1331
        return null;
×
1332
    }
1333

1334
    private void computeNodeIds() {
1335
        if(nodeId[0] != null) {
1✔
1336
            return;
1✔
1337
        }
1338
        final NodeIdFactory nodeFactory = getDatabase().getNodeFactory();
1✔
1339
        nodeId[0] = nodeFactory.documentNodeId();
1✔
1340
        if(size == 1) {
1✔
1341
            return;
1✔
1342
        }
1343
        NodeId nextId = nodeFactory.createInstance();
1✔
1344
        NodeImpl next = (NodeImpl) getFirstChild();
1✔
1345
        while(next != null) {
1✔
1346
            computeNodeIds(nextId, next.nodeNumber);
1✔
1347
            next = (NodeImpl) next.getNextSibling();
1✔
1348
            nextId = nextId.nextSibling();
1✔
1349
        }
1350
    }
1✔
1351

1352
    private void computeNodeIds(final NodeId id, final int nodeNum) {
1353
        nodeId[nodeNum] = id;
1✔
1354
        if(nodeKind[nodeNum] == Node.ELEMENT_NODE) {
1✔
1355
            NodeId nextId = id.newChild();
1✔
1356
            int attr = document.alpha[nodeNum];
1✔
1357
            if(-1 < attr) {
1✔
1358
                while((attr < document.nextAttr) && (document.attrParent[attr] == nodeNum)) {
1✔
1359
                    attrNodeId[attr] = nextId;
1✔
1360
                    nextId = nextId.nextSibling();
1✔
1361
                    ++attr;
1✔
1362
                }
1363
            }
1364
            int nextNode = getFirstChildFor(nodeNum);
1✔
1365
            while(nextNode > nodeNum) {
1✔
1366
                computeNodeIds(nextId, nextNode);
1✔
1367
                nextNode = document.next[nextNode];
1✔
1368
                if(nextNode > nodeNum) {
1✔
1369
                    nextId = nextId.nextSibling();
1✔
1370
                }
1371
            }
1372
        }
1373
    }
1✔
1374

1375
    /**
1376
     * DOCUMENT ME!
1377
     *
1378
     * @param newDoc
1379
     */
1380
    private void copyDocContents(final DocumentImpl newDoc) {
1381
        namePool = newDoc.namePool;
1✔
1382
        nodeKind = newDoc.nodeKind;
1✔
1383
        treeLevel = newDoc.treeLevel;
1✔
1384
        next = newDoc.next;
1✔
1385
        nodeName = newDoc.nodeName;
1✔
1386
        nodeId = newDoc.nodeId;
1✔
1387
        alpha = newDoc.alpha;
1✔
1388
        alphaLen = newDoc.alphaLen;
1✔
1389
        characters = newDoc.characters;
1✔
1390
        nextChar = newDoc.nextChar;
1✔
1391
        attrName = newDoc.attrName;
1✔
1392
        attrNodeId = newDoc.attrNodeId;
1✔
1393
        attrParent = newDoc.attrParent;
1✔
1394
        attrValue = newDoc.attrValue;
1✔
1395
        attrType = newDoc.attrType;
1✔
1396
        nextAttr = newDoc.nextAttr;
1✔
1397
        namespaceParent = newDoc.namespaceParent;
1✔
1398
        namespaceCode = newDoc.namespaceCode;
1✔
1399
        nextNamespace = newDoc.nextNamespace;
1✔
1400
        size = newDoc.size;
1✔
1401
        documentRootNode = newDoc.documentRootNode;
1✔
1402
        references = newDoc.references;
1✔
1403
        nextReferenceIdx = newDoc.nextReferenceIdx;
1✔
1404
    }
1✔
1405

1406
    /**
1407
     * Stream the specified document fragment to a receiver. This method
1408
     * is called by the serializer to output in-memory nodes.
1409
     *
1410
     * @param serializer the serializer
1411
     * @param node node to be serialized
1412
     * @param receiver the receiveer
1413
     * @throws SAXException DOCUMENT ME
1414
     */
1415
    public void streamTo(final Serializer serializer, NodeImpl node, final Receiver receiver) throws SAXException {
1416
        final NodeImpl top = node;
1✔
1417

1418
        int level = node.getNodeType() == Node.DOCUMENT_NODE ? 0 : 1;
1✔
1419

1420
        while (node != null) {
1✔
1421

1422
            @Nullable NodeImpl nextNode;
1423
            if (node instanceof AbstractReferenceNodeImpl) {
1✔
1424
                serializer.toReceiver(((AbstractReferenceNodeImpl) node).getNodeProxy(), true, level == 1);
1✔
1425
                nextNode = (NodeImpl) node.getNextSibling();
1✔
1426

1427
            } else {
1✔
1428
                startNode(node, receiver);
1✔
1429
                nextNode = (NodeImpl) node.getFirstChild();
1✔
1430
                level++;
1✔
1431
            }
1432

1433
            while (nextNode == null) {
1✔
1434
                if (!(node instanceof AbstractReferenceNodeImpl)) {
1✔
1435
                    endNode(node, receiver);
1✔
1436
                    level--;
1✔
1437

1438
                    if (top.nodeNumber == node.nodeNumber) {
1✔
1439
                        break;
1✔
1440
                    }
1441
                }
1442

1443
                //No nextNode if the top node is a Document node
1444
                nextNode = (NodeImpl) node.getNextSibling();
1✔
1445
                if (nextNode == null) {
1✔
1446
                    node = (NodeImpl) node.getParentNode();
1✔
1447
                    if (node == null || top.nodeNumber == node.nodeNumber) {
1✔
1448
                        if (node != null) {
1✔
1449
                            endNode(node, receiver);
1✔
1450
                            level--;
1✔
1451
                        }
1452
                        break;
1✔
1453
                    }
1454
                }
1455
            }
1456

1457
            node = nextNode;
1✔
1458
        }
1459
    }
1✔
1460

1461
    private void startNode(final NodeImpl node, final Receiver receiver) throws SAXException {
1462
        final int nodeNumber = node.nodeNumber;
1✔
1463
        switch (node.getNodeType()) {
1!
1464
            case Node.ELEMENT_NODE:
1465
                final QName nodeName = node.getQName();
1✔
1466

1467
                //Output required namespace declarations
1468
                int ns = document.alphaLen[nodeNumber];
1✔
1469
                if (ns > -1) {
1✔
1470
                    while((ns < document.nextNamespace) && (document.namespaceParent[ns] == nodeNumber)) {
1✔
1471
                        final QName nsQName = document.namespaceCode[ns];
1✔
1472
                        if(XMLConstants.XMLNS_ATTRIBUTE.equals(nsQName.getLocalPart())) {
1✔
1473
                            receiver.startPrefixMapping(XMLConstants.DEFAULT_NS_PREFIX, nsQName.getNamespaceURI());
1✔
1474
                        } else {
1✔
1475
                            receiver.startPrefixMapping(nsQName.getLocalPart(), nsQName.getNamespaceURI());
1✔
1476
                        }
1477
                        ++ns;
1✔
1478
                    }
1479
                }
1480

1481
                //Create the attribute list
1482
                @Nullable final AttrList attribs = node.getAttrList();
1✔
1483
                receiver.startElement(nodeName, attribs);
1✔
1484
                break;
1✔
1485

1486
            case Node.TEXT_NODE:
1487
                receiver.characters(((TextImpl) node).getData());
1✔
1488
                break;
1✔
1489

1490
            case Node.ATTRIBUTE_NODE:
1491
                receiver.attribute(node.getQName(), ((Attr) node).getValue());
×
1492
                break;
×
1493

1494
            case Node.COMMENT_NODE:
1495
                final char[] commentData = ((Comment) node).getData().toCharArray();
1✔
1496
                receiver.comment(commentData, 0, commentData.length);
1✔
1497
                break;
1✔
1498

1499
            case Node.PROCESSING_INSTRUCTION_NODE:
1500
                receiver.processingInstruction(node.getQName().getLocalPart(), ((ProcessingInstruction) node).getData());
1✔
1501
                break;
1✔
1502

1503
            case Node.CDATA_SECTION_NODE:
1504
                final char[] cdataSectionData = ((CDATASection) node).getData().toCharArray();
1✔
1505
                receiver.cdataSection(cdataSectionData, 0, cdataSectionData.length);
1✔
1506
                break;
1507
        }
1508
    }
1✔
1509

1510
    private void endNode(final NodeImpl node, final Receiver receiver) throws SAXException {
1511
        if(node.getNodeType() == Node.ELEMENT_NODE) {
1✔
1512
            receiver.endElement(node.getQName());
1✔
1513
            //End all prefix mappings used for the element
1514
            final int nr = node.nodeNumber;
1✔
1515
            int ns = document.alphaLen[nr];
1✔
1516
            if(ns > -1) {
1✔
1517
                while((ns < document.nextNamespace) && (document.namespaceParent[ns] == nr)) {
1✔
1518
                    final QName nsQName = document.namespaceCode[ns];
1✔
1519
                    if(XMLConstants.XMLNS_ATTRIBUTE.equals(nsQName.getLocalPart())) {
1✔
1520
                        receiver.endPrefixMapping(XMLConstants.DEFAULT_NS_PREFIX);
1✔
1521
                    } else {
1✔
1522
                        receiver.endPrefixMapping(nsQName.getLocalPart());
1✔
1523
                    }
1524
                    ++ns;
1✔
1525
                }
1526
            }
1527
        }
1528
    }
1✔
1529

1530
    public org.exist.dom.persistent.DocumentImpl makePersistent() throws XPathException {
1531
        if(size <= 1) {
×
1532
            return null;
×
1533
        }
1534
        return context.storeTemporaryDoc(this);
×
1535
    }
1536

1537
    // this is DOM specific
1538
    public int getChildCount() {
1539
        int count = 0;
1✔
1540
        int top = (size > 1) ? 1 : -1;
1!
1541
        while(top > 0) {
1✔
1542
            ++count;
1✔
1543
            top = getNextSiblingFor(top);
1✔
1544
        }
1545
        return count;
1✔
1546
    }
1547

1548
    @Override
1549
    public boolean hasChildNodes() {
1550
        return getChildCount() > 0;
1!
1551
    }
1552

1553
    @Override
1554
    public NodeList getChildNodes() {
1555
        if (size == 1) {
1✔
1556
            return new NodeListImpl(0);
1✔
1557
        }
1558

1559
        final NodeListImpl children = new NodeListImpl(1);  // most likely a single element!
1✔
1560
        int nextChildNodeNum = 1;
1✔
1561
        while (nextChildNodeNum > 0) {
1✔
1562
            final NodeImpl child = getNode(nextChildNodeNum);
1✔
1563
            children.add(child);
1✔
1564
            nextChildNodeNum = next[nextChildNodeNum];
1✔
1565
        }
1566

1567
        return children;
1✔
1568
    }
1569

1570
    @Override
1571
    public String getInputEncoding() {
1572
        return null;
×
1573
    }
1574

1575
    @Override
1576
    public String getXmlEncoding() {
1577
        return UTF_8.name();    //TODO(AR) this should be recorded from the XML document and not hard coded
1✔
1578
    }
1579

1580
    @Override
1581
    public boolean getXmlStandalone() {
1582
        return false;   //TODO(AR) this should be recorded from the XML document and not hard coded
1✔
1583
    }
1584

1585
    @Override
1586
    public void setXmlStandalone(final boolean xmlStandalone) throws DOMException {
1587
    }
×
1588

1589
    @Override
1590
    public String getXmlVersion() {
1591
        return "1.0";   //TODO(AR) this should be recorded from the XML document and not hard coded
1✔
1592
    }
1593

1594
    @Override
1595
    public void setXmlVersion(final String xmlVersion) throws DOMException {
1596
    }
×
1597

1598
    @Override
1599
    public boolean getStrictErrorChecking() {
1600
        return false;
×
1601
    }
1602

1603
    @Override
1604
    public void setStrictErrorChecking(final boolean strictErrorChecking) {
1605
    }
×
1606

1607
    @Override
1608
    public String getDocumentURI() {
1609
        return documentURI;
1✔
1610
    }
1611

1612
    @Override
1613
    public void setDocumentURI(final String documentURI) {
1614
        this.documentURI = documentURI;
1✔
1615
    }
1✔
1616

1617
    @Override
1618
    public Node adoptNode(final Node source) throws DOMException {
1619
        return null;
×
1620
    }
1621

1622
    @Override
1623
    public DOMConfiguration getDomConfig() {
1624
        return null;
×
1625
    }
1626

1627
    @Override
1628
    public void normalizeDocument() {
1629
    }
×
1630

1631
    @Override
1632
    public Node renameNode(final Node n, final String namespaceURI, final String qualifiedName)
1633
        throws DOMException {
1634
        return null;
×
1635
    }
1636

1637
    public void setContext(final XQueryContext context) {
1638
        this.context = context;
×
1639
    }
×
1640

1641
    public XQueryContext getContext() {
1642
        return context;
1✔
1643
    }
1644

1645
    @Override
1646
    public String getBaseURI() {
1647
        final Element el = getDocumentElement();
1✔
1648
        if(el != null) {
1!
1649
            final String baseURI = getDocumentElement().getAttributeNS(Namespaces.XML_NS, "base");
1✔
1650
            if(baseURI != null) {
1!
1651
                return baseURI;
1✔
1652
            }
1653
        }
1654
        final String docURI = getDocumentURI();
×
1655
        if(docURI != null) {
×
1656
            return docURI;
×
1657
        } else {
1658
            if(context!=null && context.isBaseURIDeclared()) {
×
1659
                try {
1660
                    return context.getBaseURI().getStringValue();
×
1661
                } catch(final XPathException e) {
×
1662
                    //TODO : make something !
1663
                }
1664
            }
1665
            return XmldbURI.EMPTY_URI.toString();
×
1666
        }
1667
    }
1668

1669
    @Override
1670
    public int getItemType() {
1671
        return Type.DOCUMENT;
1✔
1672
    }
1673

1674
    @Override
1675
    public String toString() {
1676
        final StringBuilder result = new StringBuilder();
×
1677
        result.append("in-memory#");
×
1678
        result.append("document {");
×
1679
        if(size != 1) {
×
1680
            int nodeNum = 1;
×
1681
            while(true) {
×
1682
                result.append(getNode(nodeNum).toString());
×
1683
                if(next[nodeNum] < nodeNum) {
×
1684
                    break;
×
1685
                }
1686
                nodeNum = next[nodeNum];
×
1687
            }
1688
        }
1689
        result.append("} ");
×
1690
        return result.toString();
×
1691
    }
1692

1693
    @Override
1694
    public void selectAttributes(final NodeTest test, final Sequence result)
1695
        throws XPathException {
1696
    }
×
1697

1698
    @Override
1699
    public Node appendChild(final Node newChild) throws DOMException {
1700
        if(newChild.getNodeType() != Node.DOCUMENT_NODE && newChild.getOwnerDocument() != this) {
×
1701
            throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, "Owning document IDs do not match");
×
1702
        }
1703

1704
        if(newChild == this) {
×
1705
            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
×
1706
                    "Cannot append a document to itself");
×
1707
        }
1708

1709
        if(newChild.getNodeType() == DOCUMENT_NODE) {
×
1710
            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
×
1711
                    "A Document Node may not be appended to a Document Node");
×
1712
        }
1713

1714
        if(newChild.getNodeType() == ELEMENT_NODE && getDocumentElement() != null) {
×
1715
            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
×
1716
                    "A Document Node may only have a single document element");
×
1717
        }
1718

1719
        if(newChild.getNodeType() == DOCUMENT_TYPE_NODE && getDoctype() != null) {
×
1720
            throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
×
1721
                    "A Document Node may only have a single document type");
×
1722
        }
1723

1724
        throw unsupported();
×
1725
    }
1726
}
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