• 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

42.1
/exist-core/src/main/java/org/exist/dom/persistent/NodeProxy.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.collections.Collection;
53
import org.exist.collections.ManagedLocks;
54
import org.exist.dom.QName;
55
import org.exist.dom.memtree.DocumentBuilderReceiver;
56
import org.exist.numbering.NodeId;
57
import org.exist.stax.IEmbeddedXMLStreamReader;
58
import org.exist.storage.DBBroker;
59
import org.exist.storage.RangeIndexSpec;
60
import org.exist.storage.StorageAddress;
61
import org.exist.storage.lock.LockManager;
62
import org.exist.storage.lock.ManagedDocumentLock;
63
import org.exist.storage.serializers.Serializer;
64
import org.exist.util.LockException;
65
import org.exist.xmldb.XmldbURI;
66
import org.exist.xquery.*;
67
import org.exist.xquery.value.*;
68
import org.w3c.dom.Element;
69
import org.w3c.dom.Node;
70
import org.w3c.dom.NodeList;
71
import org.xml.sax.ContentHandler;
72
import org.xml.sax.SAXException;
73
import org.xml.sax.ext.LexicalHandler;
74

75
import javax.annotation.Nullable;
76
import javax.xml.stream.XMLStreamException;
77
import javax.xml.stream.XMLStreamReader;
78
import java.io.IOException;
79
import java.lang.ref.WeakReference;
80
import java.util.Iterator;
81
import java.util.NoSuchElementException;
82
import java.util.Properties;
83

84
/**
85
 * Placeholder class for DOM nodes.
86
 *
87
 * NodeProxy is an internal proxy class, acting as a placeholder for all types of persistent XML nodes
88
 * during query processing. NodeProxy just stores the node's unique id and the document it belongs to.
89
 * Query processing deals with these proxy most of the time. Using a NodeProxy is much cheaper
90
 * than loading the actual node from the database. The real DOM node is only loaded,
91
 * if further information is required for the evaluation of an XPath expression. To obtain
92
 * the real node for a proxy, simply call {@link #getNode()}.
93
 *
94
 * All sets of type NodeSet operate on NodeProxy objects. A node set is a special type of
95
 * sequence, so NodeProxy also implements {@link org.exist.xquery.value.Item}, and
96
 * can thus be an item in a sequence. Since according to XPath 2, a single node is also
97
 * a sequence, NodeProxy itself extends NodeSet. It thus represents a node set containing
98
 * just one single node.
99
 *
100
 * @author <a href="mailto:adam@evolvedbinary.com">Adam Retter</a>
101
 * @author <a href="mailto:wolfgang@exist-db.org">Wolfgang Meier</a>
102
 */
103
public class NodeProxy implements NodeSet, NodeValue, NodeHandle, DocumentSet, Comparable<Object> {
104

105
    public static final short UNKNOWN_NODE_TYPE = -1;
106
    public static final int UNKNOWN_NODE_LEVEL = -1;
107

108
    /**
109
     * The owner document of this node.
110
     */
111
    private DocumentImpl doc;
112
    private NodeId nodeId;
113

114
    /**
115
     * The internal storage address of this node in the
116
     * dom.dbx file, if known.
117
     *
118
     * @link #UNKNOWN_NODE_ADDRESS
119
     */
120
    private long internalAddress = StoredNode.UNKNOWN_NODE_IMPL_ADDRESS;
1✔
121

122
    /**
123
     * The type of this node (as defined by DOM), if known.
124
     *
125
     * @link #UNKNOWN_NODE_TYPE
126
     */
127
    private short nodeType = UNKNOWN_NODE_TYPE;
1✔
128

129
    /**
130
     * The first {@link Match} object associated with this node.
131
     * Match objects are used to track hits throughout query processing.
132
     *
133
     * Matches are stored as a linked list.
134
     */
135
    private Match match = null;
1✔
136

137
    private ContextItem context = null;
1✔
138

139
    private QName qname = null;
1✔
140

141
    private final Expression expression;
142

143
    /**
144
     * Creates a new <code>NodeProxy</code> instance.
145
     *
146
     * @param doc    a <code>DocumentImpl</code> value
147
     * @param nodeId a <code>NodeId</code> value
148
     */
149
    public NodeProxy(final DocumentImpl doc, final NodeId nodeId) {
150
        this(null, doc, nodeId);
×
151
    }
×
152

153
    /**
154
     * Creates a new <code>NodeProxy</code> instance.
155
     *
156
     * @param expression the expression from which this node value derives
157
     * @param doc        a <code>DocumentImpl</code> value
158
     * @param nodeId     a <code>NodeId</code> value
159
     */
160
    public NodeProxy(final Expression expression, final DocumentImpl doc, final NodeId nodeId) {
161
        this(expression, doc, nodeId, UNKNOWN_NODE_TYPE, StoredNode.UNKNOWN_NODE_IMPL_ADDRESS);
1✔
162
    }
1✔
163

164
    /**
165
     * Creates a new <code>NodeProxy</code> instance.
166
     *
167
     * @param doc     a <code>DocumentImpl</code> value
168
     * @param nodeId  a <code>NodeId</code> value
169
     * @param address a <code>long</code> value
170
     */
171
    public NodeProxy(final DocumentImpl doc, final NodeId nodeId, final long address) {
172
        this(null, doc, nodeId, address);
×
173
    }
×
174

175
    /**
176
     * Creates a new <code>NodeProxy</code> instance.
177
     *
178
     * @param expression the expression from which this node value derives
179
     * @param doc        a <code>DocumentImpl</code> value
180
     * @param nodeId     a <code>NodeId</code> value
181
     * @param address    a <code>long</code> value
182
     */
183
    public NodeProxy(final Expression expression, final DocumentImpl doc, final NodeId nodeId, final long address) {
184
        this(expression, doc, nodeId, UNKNOWN_NODE_TYPE, address);
1✔
185
    }
1✔
186

187
    /**
188
     * Creates a new <code>NodeProxy</code> instance.
189
     *
190
     * @param doc      a <code>DocumentImpl</code> value
191
     * @param nodeId   a <code>NodeId</code> value
192
     * @param nodeType a <code>short</code> value
193
     */
194
    public NodeProxy(final DocumentImpl doc, final NodeId nodeId, final short nodeType) {
195
        this(null, doc, nodeId, nodeType);
×
196
    }
×
197

198
    /**
199
     * Creates a new <code>NodeProxy</code> instance.
200
     *
201
     * @param expression the expression from which this node value derives
202
     * @param doc        a <code>DocumentImpl</code> value
203
     * @param nodeId     a <code>NodeId</code> value
204
     * @param nodeType   a <code>short</code> value
205
     */
206
    public NodeProxy(final Expression expression, final DocumentImpl doc, final NodeId nodeId, final short nodeType) {
207
        this(expression, doc, nodeId, nodeType, StoredNode.UNKNOWN_NODE_IMPL_ADDRESS);
1✔
208
    }
1✔
209

210
    /**
211
     * Creates a new <code>NodeProxy</code> instance.
212
     *
213
     * @param doc      a <code>DocumentImpl</code> value
214
     * @param nodeId   a <code>NodeId</code> value
215
     * @param nodeType a <code>short</code> value
216
     * @param address  a <code>long</code> value
217
     */
218
    public NodeProxy(final DocumentImpl doc, final NodeId nodeId, final short nodeType, final long address) {
219
        this(null, doc, nodeId, nodeType, address);
×
220
    }
×
221

222
    /**
223
     * Creates a new <code>NodeProxy</code> instance.
224
     *
225
     * @param expression the expression from which this node value derives
226
     * @param doc        a <code>DocumentImpl</code> value
227
     * @param nodeId     a <code>NodeId</code> value
228
     * @param nodeType   a <code>short</code> value
229
     * @param address    a <code>long</code> value
230
     */
231
    public NodeProxy(final Expression expression, final DocumentImpl doc, final NodeId nodeId, final short nodeType, final long address) {
1✔
232
        this.expression = (expression == null && doc != null) ? doc.getExpression() : expression;
1✔
233
        this.doc = doc;
1✔
234
        this.nodeType = nodeType;
1✔
235
        this.internalAddress = address;
1✔
236
        this.nodeId = nodeId;
1✔
237
    }
1✔
238

239
    public void update(final ElementImpl element) {
240
        invalidateCachedNode();
×
241
        this.doc = element.getOwnerDocument();
×
242
        this.nodeType = UNKNOWN_NODE_TYPE;
×
243
        this.internalAddress = StoredNode.UNKNOWN_NODE_IMPL_ADDRESS;
×
244
        this.nodeId = element.getNodeId();
×
245
        this.match = null;
×
246
        this.context = null;
×
247
        this.cachedNode = new WeakReference<>(element);
×
248
    }
×
249

250
    /**
251
     * Creates a new <code>NodeProxy</code> instance.
252
     *
253
     * @param n a <code>NodeHandle</code> value
254
     */
255
    public NodeProxy(final NodeHandle n) {
256
        this(null, n);
×
257
    }
×
258

259
    /**
260
     * Creates a new <code>NodeProxy</code> instance.
261
     *
262
     * @param expression the expression from which the node handle derives
263
     * @param n a <code>NodeHandle</code> value
264
     */
265
    public NodeProxy(final Expression expression, final NodeHandle n) {
266
        this((expression == null && n instanceof NodeProxy) ? ((NodeProxy) n).getExpression() : expression, n.getOwnerDocument(), n.getNodeId(), n.getNodeType(), n.getInternalAddress());
1✔
267
        if(n instanceof NodeProxy) {
1✔
268
            this.match = ((NodeProxy) n).match;
1✔
269
            this.context = ((NodeProxy) n).context;
1✔
270
        }
271
    }
1✔
272

273
    /**
274
     * create a proxy to a document node
275
     *
276
     * @param doc a <code>DocumentImpl</code> value
277
     */
278
    public NodeProxy(final DocumentImpl doc) {
279
        this(null, doc);
×
280
    }
×
281

282
    /**
283
     * create a proxy to a document node
284
     *
285
     * @param expression the expression from which the document node derives
286
     * @param doc a <code>DocumentImpl</code> value
287
     */
288
    public NodeProxy(final Expression expression, final DocumentImpl doc) {
289
        this(expression, doc, NodeId.DOCUMENT_NODE, Node.DOCUMENT_NODE, StoredNode.UNKNOWN_NODE_IMPL_ADDRESS);
1✔
290
    }
1✔
291

292
    public static NodeProxy wrap(@Nullable Expression expression, final NodeImpl node) {
293
        final DocumentImpl doc = node instanceof DocumentImpl ? (DocumentImpl) node : (DocumentImpl) node.getOwnerDocument();
1✔
294
        expression = expression != null ? expression : doc.getExpression();
1✔
295

296
        final NodeProxy wrapper = new NodeProxy(expression, doc, node.getNodeId(), node.getNodeType());
1✔
297
        wrapper.cachedNode = new WeakReference<>(node);
1✔
298

299
        return wrapper;
1✔
300
    }
301

302
    public Expression getExpression() {
303
        return expression;
1✔
304
    }
305

306
    @Override
307
    public void setNodeId(final NodeId id) {
308
        invalidateCachedNode();
×
309
        this.nodeId = id;
×
310
    }
×
311

312
    @Override
313
    public NodeId getNodeId() {
314
        return nodeId;
1✔
315
    }
316

317
    @Override
318
    public QName getQName() {
319
        if (qname == null) {
1!
320
            getNode();
1✔
321
        }
322
        return qname;
1✔
323
    }
324

325
    public void setQName(QName qname) {
326
        this.qname = qname;
1✔
327
    }
1✔
328

329
    @Override
330
    public int getImplementationType() {
331
        return NodeValue.PERSISTENT_NODE;
1✔
332
    }
333

334
    @Override
335
    public NodeSet copy() {
336
        // return this, because there's no other node in the set
337
        return this;
×
338
    }
339

340
    @Override
341
    public Sequence tail() throws XPathException {
342
        return Sequence.EMPTY_SEQUENCE;
×
343
    }
344

345
    /**
346
     * The method <code>compareTo</code>
347
     *
348
     * @param other an <code>Object</code> value
349
     * @return an <code>int</code> value
350
     */
351
    @Override
352
    public int compareTo(final Object other) {
353
        if(!(other instanceof NodeProxy)) {
1✔
354
            //We are always superior...
355
            return Constants.SUPERIOR;
1✔
356
        } else {
357
            return compareTo((NodeProxy) other);
1✔
358
        }
359
    }
360

361
    /**
362
     * Ordering first according to document ID; then if equal
363
     * according to node gid.
364
     *
365
     * @param other a <code>NodeProxy</code> value
366
     * @return an <code>int</code> value
367
     */
368
    public int compareTo(final NodeProxy other) {
369
        final int diff = doc.getDocId() - other.doc.getDocId();
1✔
370
        if(diff != Constants.EQUAL) {
1✔
371
            return diff;
1✔
372
        } else {
373
            return nodeId.compareTo(other.nodeId);
1✔
374
        }
375
    }
376

377
    /**
378
     * The method <code>equals</code>
379
     *
380
     * @param other an <code>Object</code> value
381
     * @return a <code>boolean</code> value
382
     */
383
    @Override
384
    public boolean equals(final Object other) {
385
        if(!(other instanceof final NodeProxy otherNode)) {
×
386
            return false;
×
387
        }
388

389
        if(otherNode.doc.getDocId() != doc.getDocId()) {
×
390
            return false;
×
391
        }
392
        return otherNode.nodeId.equals(nodeId);
×
393
    }
394

395
    @Override
396
    public boolean equals(final NodeValue other) throws XPathException {
397
        if(other.getImplementationType() != NodeValue.PERSISTENT_NODE) {
1!
398
            throw new XPathException(expression, "Cannot compare persistent node with in-memory node");
×
399
        }
400
        final NodeProxy otherNode = (NodeProxy) other;
1✔
401
        if(otherNode.doc.getDocId() != doc.getDocId()) {
1✔
402
            return false;
1✔
403
        }
404
        return otherNode.nodeId.equals(nodeId);
1✔
405
    }
406

407
    @Override
408
    public boolean before(final NodeValue other, final boolean isPreceding) throws XPathException {
409
        if(other.getImplementationType() != NodeValue.PERSISTENT_NODE) {
×
410
            throw new XPathException(expression, "Cannot compare persistent node with in-memory node");
×
411
        }
412
        final NodeProxy otherNode = (NodeProxy) other;
×
413
        if(doc.getDocId() != otherNode.doc.getDocId()) {
×
414
            //Totally arbitrary
415
            return doc.getDocId() < otherNode.doc.getDocId();
×
416
        }
417
        return nodeId.before(otherNode.nodeId, isPreceding);
×
418
    }
419

420
    @Override
421
    public boolean after(final NodeValue other, final boolean isFollowing) throws XPathException {
422
        if(other.getImplementationType() != NodeValue.PERSISTENT_NODE) {
×
423
            throw new XPathException(expression, "Cannot compare persistent node with in-memory node");
×
424
        }
425
        final NodeProxy otherNode = (NodeProxy) other;
×
426
        if(doc.getDocId() != otherNode.doc.getDocId()) {
×
427
            //Totally arbitrary
428
            return doc.getDocId() > otherNode.doc.getDocId();
×
429
        }
430
        return nodeId.after(otherNode.nodeId, isFollowing);
×
431
    }
432

433
    @Override
434
    public DocumentImpl getOwnerDocument() {
435
        return doc;
1✔
436
    }
437

438
    /**
439
     * The method <code>isDocument</code>
440
     *
441
     * @return a <code>boolean</code> value
442
     */
443
    public boolean isDocument() {
444
        return nodeType == Node.DOCUMENT_NODE;
1✔
445
    }
446

447

448
    private @Nullable WeakReference<Node> cachedNode = null;
1✔
449

450
    private void invalidateCachedNode() {
451
        this.cachedNode = null;
1✔
452
    }
1✔
453

454
    /**
455
     * Gets the node from the broker, i.e. fom the underlying file system
456
     * Call this method <em>only</em> when necessary
457
     * @see org.exist.xquery.value.NodeValue#getNode()
458
     */
459
    @Override
460
    public Node getNode() {
461
        if (isDocument()) {
1✔
462
            return doc;
1✔
463
        }
464

465
        // try and get cached node
466
        @Nullable Node node = null;
1✔
467
        if (cachedNode != null) {
1✔
468
            node = cachedNode.get();
1✔
469
        }
470

471
        if (node == null) {
1✔
472
            // no cached node, get the node from the database
473
            final NodeImpl realNode = (NodeImpl) doc.getNode(this);
1✔
474
            if (realNode != null) {
1✔
475
                this.nodeType = realNode.getNodeType();
1✔
476
                this.qname = realNode.getQName();
1✔
477
            }
478
            cachedNode = new WeakReference<>(realNode);
1✔
479
            node = realNode;
1✔
480
        }
481

482
        return node;
1✔
483
    }
484

485
    @Override
486
    public short getNodeType() {
487
        return nodeType;
1✔
488
    }
489

490
    /**
491
     * Sets the nodeType.
492
     *
493
     * @param nodeType The nodeType to set
494
     */
495
    public void setNodeType(final short nodeType) {
496
        this.nodeType = nodeType;
1✔
497
    }
1✔
498

499
    @Override
500
    public long getInternalAddress() {
501
        return internalAddress;
1✔
502
    }
503

504
    @Override
505
    public void setInternalAddress(final long internalAddress) {
506
        invalidateCachedNode();
1✔
507
        this.internalAddress = internalAddress;
1✔
508
    }
1✔
509

510
    public void setIndexType(final int type) {
511
        invalidateCachedNode();
1✔
512
        this.internalAddress = StorageAddress.setIndexType(internalAddress, (short) type);
1✔
513
    }
1✔
514

515
    @Override
516
    public int getIndexType() {
517
        if(internalAddress == -1) {
1!
518
            return Type.ITEM;
×
519
        }
520
        return RangeIndexSpec.indexTypeToXPath(StorageAddress.indexTypeFromPointer(internalAddress));
1✔
521
    }
522

523
    @Override
524
    public void setTrackMatches(boolean track) {
525
    }
×
526

527
    @Override
528
    public boolean getTrackMatches() {
529
        return true;
1✔
530
    }
531

532
    public Match getMatches() {
533
        return match;
1✔
534
    }
535

536
    public void setMatches(final Match match) {
537
        this.match = match;
1✔
538
    }
1✔
539

540
    public void addMatch(final Match m) {
541
        if (this.match == null) {
×
542
            this.match = m;
×
543
            if (match.getNextMatch() != null) {
×
544
                match.setNextMatch(null);
×
545
            }
546
            return;
×
547
        }
548

549
        Match next = match;
×
550
        while (next != null) {
×
551
            if (next.equals(m)) {
×
552
                next.mergeOffsets(m);
×
553
                return;
×
554
            }
555
            if (next.getNextMatch() == null) {
×
556
                next.setNextMatch(m);
×
557
                break;
×
558
            }
559
            next = next.getNextMatch();
×
560
        }
561
    }
×
562

563
    public void addMatches(final NodeProxy p) {
564
        if (p == this) {
1✔
565
            return;
1✔
566
        }
567

568
        Match m = p.getMatches();
1✔
569
        if (Match.matchListEquals(m, this.match)) {
1!
570
            return;
1✔
571
        }
572

573
        while (m != null) {
×
574
            addMatch(m.newCopy());
×
575
            m = m.getNextMatch();
×
576
        }
577
    }
×
578

579
    /**
580
     * Add a node to the list of context nodes for this node.
581
     *
582
     * NodeProxy internally stores the context nodes of the XPath context, for which
583
     * this node has been selected during a previous processing step.
584
     *
585
     * Since eXist tries to process many expressions in one, single processing step,
586
     * the context information is required to resolve predicate expressions. For
587
     * example, for an expression like //SCENE[SPEECH/SPEAKER='HAMLET'],
588
     * we have to remember the SCENE nodes for which the equality expression
589
     * in the predicate was true.  Thus, when evaluating the step SCENE[SPEECH], the
590
     * SCENE nodes become context items of the SPEECH nodes and this context
591
     * information is preserved through all following steps.
592
     *
593
     * To process the predicate expression, {@link org.exist.xquery.Predicate} will take the
594
     * context nodes returned by the filter expression and compare them to its context
595
     * node set.
596
     */
597
    @Override
598
    public void addContextNode(final int contextId, final NodeValue node) {
599
        if(node.getImplementationType() != NodeValue.PERSISTENT_NODE) {
1!
600
            return;
×
601
        }
602
        final NodeProxy contextNode = (NodeProxy) node;
1✔
603
        if(context == null) {
1✔
604
            context = new ContextItem(contextId, contextNode);
1✔
605
            return;
1✔
606
        }
607
        ContextItem next = context;
1✔
608
        while(next != null) {
1!
609
            if(contextId == next.getContextId() &&
1✔
610
                next.getNode().getNodeId().equals(contextNode.getNodeId())) {
1✔
611
                // Ignore duplicate context nodes
612
                break;
1✔
613
            }
614
            if(next.getNextDirect() == null) {
1✔
615
                if(next == context) {
1✔
616
                    // context items should not be shared between proxies,
617
                    // but for performance reason, if there's only a single
618
                    // context item, it will be shared. we thus have to create
619
                    // a copy before appending a new item.
620
                    next = new ContextItem(next.getContextId(), next.getNode());
1✔
621
                    context = next;
1✔
622
                }
623
                next.setNextContextItem(new ContextItem(contextId, contextNode));
1✔
624
                break;
1✔
625
            }
626
            next = next.getNextDirect();
1✔
627
        }
628
    }
1✔
629

630
    /**
631
     * Add all context nodes from the other NodeProxy to the
632
     * context of this NodeProxy.
633
     *
634
     * @param other NodePoxy to take context from
635
     */
636
    public void addContext(final NodeProxy other) {
637
        ContextItem next = other.context;
1✔
638
        while(next != null) {
1✔
639
            addContextNode(next.getContextId(), next.getNode());
1✔
640
            next = next.getNextDirect();
1✔
641
        }
642
    }
1✔
643

644
    public void copyContext(final NodeProxy node) {
645
        deepCopyContext(node);
1✔
646
    }
1✔
647

648
    /**
649
     * Copy the context items from the given node into this node.
650
     * Context items are used to keep track of context nodes inside predicates.
651
     *
652
     * @param node a <code>NodeProxy</code> value
653
     */
654
    public void deepCopyContext(final NodeProxy node) {
655
        context = null;
1✔
656
        if(node.context == null) {
1✔
657
            return;
1✔
658
        }
659
        if(node.context.getNextDirect() == null) {
1✔
660
            // if there's a single context item, we just
661
            // copy a reference to it. addContextNode will take
662
            // care of this and create a copy before appending
663
            // a new item
664
            context = node.context;
1✔
665
        } else {
1✔
666
            ContextItem next = node.context;
1✔
667
            ContextItem newContext = new ContextItem(next.getContextId(), next.getNode());
1✔
668
            context = newContext;
1✔
669
            next = next.getNextDirect();
1✔
670
            while(next != null) {
1✔
671
                newContext.setNextContextItem(new ContextItem(next.getContextId(), next.getNode()));
1✔
672
                newContext = newContext.getNextDirect();
1✔
673
                next = next.getNextDirect();
1✔
674
            }
675
        }
676
    }
1✔
677

678
    public void deepCopyContext(final NodeProxy node, final int addContextId) {
679
        if(context == null) {
1!
680
            deepCopyContext(node);
1✔
681
        }
682
        addContextNode(addContextId, node);
1✔
683
    }
1✔
684

685
    /**
686
     * The method <code>clearContext</code>
687
     *
688
     * @param contextId an <code>int</code> value
689
     */
690
    @Override
691
    public void clearContext(final int contextId) {
692
        if(contextId == Expression.IGNORE_CONTEXT) {
1✔
693
            context = null;
1✔
694
            return;
1✔
695
        }
696
        ContextItem newContext = null;
1✔
697
        ContextItem last = null;
1✔
698
        ContextItem next = context;
1✔
699
        while(next != null) {
1✔
700
            if(next.getContextId() != contextId) {
1✔
701
                if(newContext == null) {
1!
702
                    newContext = next;
1✔
703
                } else {
1✔
704
                    last.setNextContextItem(next);
×
705
                }
706
                last = next;
1✔
707
                last.setNextContextItem(null);
1✔
708
            }
709
            next = next.getNextDirect();
1✔
710
        }
711
        this.context = newContext;
1✔
712
    }
1✔
713

714
    public ContextItem getContext() {
715
        return context;
1✔
716
    }
717

718
    public String debugContext() {
719
        final StringBuilder buf = new StringBuilder();
×
720
        buf.append("Context for ").append(nodeId).append(" [ ").append(toString()).append("] : ");
×
721
        ContextItem next = context;
×
722
        while(next != null) {
×
723
            buf.append('[');
×
724
            buf.append(next.getNode().getNodeId());
×
725
            buf.append(':');
×
726
            buf.append(next.getContextId());
×
727
            buf.append("] ");
×
728
            next = next.getNextDirect();
×
729
        }
730
        return buf.toString();
×
731
    }
732

733
    //        methods of interface Item
734
    @Override
735
    public int getType() {
736
        if (nodeType == UNKNOWN_NODE_TYPE) {
1✔
737
            return Type.NODE;
1✔
738
        }
739
        return Type.fromDomNodeType(nodeType);
1✔
740
    }
741

742
    @Override
743
    public boolean isPersistentSet() {
744
        return true;
1✔
745
    }
746

747
    @Override
748
    public void nodeMoved(final NodeId oldNodeId, final NodeHandle newNode) {
749
        if (nodeId.equals(oldNodeId)) {
×
750
            // update myself
751
            invalidateCachedNode();
×
752
            this.nodeId = newNode.getNodeId();
×
753
            this.internalAddress = newNode.getInternalAddress();
×
754
        }
755
    }
×
756

757
    @Override
758
    public Sequence toSequence() {
759
        return this;
1✔
760
    }
761

762
    public String getNodeValue() {
763
        try(final DBBroker broker = doc.getBrokerPool().getBroker()) {
1✔
764
            if(isDocument()) {
1!
765
                final Element e = doc.getDocumentElement();
×
766
                if(e instanceof NodeProxy) {
×
767
                    return broker.getNodeValue(((StoredNode) e).extract(), false);
×
768
                } else if(e != null) {
×
769
                    return broker.getNodeValue((ElementImpl) e, false);
×
770
                } else
771
                // probably a binary resource
772
                {
773
                    return "";
×
774
                }
775
            } else {
776
                return broker.getNodeValue(this.asStoredNode(), false);
1✔
777
            }
778
        } catch(final EXistException e) {
×
779
            //TODO : raise an exception here ! -pb
780
        }
781
        return "";
×
782
    }
783

784
    //TODO this should be improved. Consider an interface that contains just the
785
    // getters from INodeHandle and persistent.NodeHandle
786
    public StoredNode asStoredNode() {
787
        return new StoredNode(
1✔
788
            this.getExpression(),
1✔
789
            this.getNodeType(),
1✔
790
            this.getNodeId(),
1✔
791
            this.getOwnerDocument(),
1✔
792
            this.getInternalAddress()) {
1✔
793
        };
794
    }
795

796
    @Override
797
    public String getStringValue() {
798
        return getNodeValue();
1✔
799
    }
800

801
    @Override
802
    public AtomicValue convertTo(final int requiredType) throws XPathException {
803
        return UntypedAtomicValue.convertTo(getNodeValue(), requiredType, null);
1✔
804
    }
805

806
    @Override
807
    public AtomicValue atomize() throws XPathException {
808
        return new UntypedAtomicValue(getNodeValue());
1✔
809
    }
810

811
    @Override
812
    public void toSAX(final DBBroker broker, final ContentHandler handler, final Properties properties) throws SAXException {
813
        final Serializer serializer = broker.borrowSerializer();
1✔
814
        try {
815
            serializer.setProperty(Serializer.GENERATE_DOC_EVENTS, "false");
1✔
816
            if (properties != null) {
1✔
817
                serializer.setProperties(properties);
1✔
818
            }
819

820
            if (handler instanceof LexicalHandler) {
1!
821
                serializer.setSAXHandlers(handler, (LexicalHandler) handler);
1✔
822
            } else {
1✔
823
                serializer.setSAXHandlers(handler, null);
×
824
            }
825
            serializer.toSAX(this);
1✔
826
        } finally {
1✔
827
            broker.returnSerializer(serializer);
1✔
828
        }
829
    }
1✔
830

831
    @Override
832
    public void copyTo(final DBBroker broker, final DocumentBuilderReceiver receiver) throws SAXException {
833
        NodeImpl node = null;
1✔
834
        if(nodeType < 0) {
1!
835
            node = (NodeImpl) getNode();
×
836
        }
837
        if(nodeType == Node.ATTRIBUTE_NODE) {
1✔
838
            final AttrImpl attr = (node == null ? (AttrImpl) getNode() : (AttrImpl) node);
1!
839
            receiver.attribute(attr.getQName(), attr.getValue());
1✔
840
        } else {
1✔
841
            receiver.addReferenceNode(this);
1✔
842
        }
843
    }
1✔
844

845
    @Override
846
    public int conversionPreference(final Class javaClass) {
847
        if(javaClass.isAssignableFrom(NodeProxy.class)) {
×
848
            return 0;
×
849
        } else if(javaClass.isAssignableFrom(Node.class)) {
×
850
            return 1;
×
851
        } else if(javaClass == String.class || javaClass == CharSequence.class) {
×
852
            return 2;
×
853
        } else if(javaClass == Character.class || javaClass == char.class) {
×
854
            return 2;
×
855
        } else if(javaClass == Double.class || javaClass == double.class) {
×
856
            return 10;
×
857
        } else if(javaClass == Float.class || javaClass == float.class) {
×
858
            return 11;
×
859
        } else if(javaClass == Long.class || javaClass == long.class) {
×
860
            return 12;
×
861
        } else if(javaClass == Integer.class || javaClass == int.class) {
×
862
            return 13;
×
863
        } else if(javaClass == Short.class || javaClass == short.class) {
×
864
            return 14;
×
865
        } else if(javaClass == Byte.class || javaClass == byte.class) {
×
866
            return 15;
×
867
        } else if(javaClass == Boolean.class || javaClass == boolean.class) {
×
868
            return 16;
×
869
        } else if(javaClass == Object.class) {
×
870
            return 20;
×
871
        } else {
872
            return Integer.MAX_VALUE;
×
873
        }
874
    }
875

876
    @Override
877
    public <T> T toJavaObject(final Class<T> target) throws XPathException {
878
        if(target.isAssignableFrom(NodeProxy.class)) {
1!
879
            return (T) this;
×
880
        } else if(target.isAssignableFrom(Node.class) || target == Object.class) {
1!
881
            return (T) getNode();
×
882
        } else {
883
            final StringValue v = new StringValue(getStringValue());
1✔
884
            return v.toJavaObject(target);
1✔
885
        }
886
    }
887

888
    /*
889
     * Methods of interface Sequence:
890
     */
891

892
    @Override
893
    public int getItemType() {
894
        return getType();
1✔
895
    }
896

897
    @Override
898
    public Cardinality getCardinality() {
899
        return Cardinality.EXACTLY_ONE;
1✔
900
    }
901

902
    @Override
903
    public boolean isCached() {
904
        return false;
×
905
    }
906

907
    @Override
908
    public void setIsCached(final boolean cached) {
909
        //TODO : return something useful ? -pb
910
    }
×
911

912
    @Override
913
    public NodeSet toNodeSet() throws XPathException {
914
        return this;
1✔
915
    }
916

917
    @Override
918
    public MemoryNodeSet toMemNodeSet() throws XPathException {
919
        return null;
×
920
    }
921

922
    @Override
923
    public boolean effectiveBooleanValue() throws XPathException {
924
        return true;
1✔
925
    }
926

927
    @Override
928
    public void removeDuplicates() {
929
        // single node: no duplicates
930
    }
1✔
931

932
    @Override
933
    public void setSelfAsContext(final int contextId) {
934
        addContextNode(contextId, this);
1✔
935
    }
1✔
936

937
    /* -----------------------------------------------*
938
     * Methods of class NodeSet
939
     * -----------------------------------------------*/
940

941
    @Override
942
    public NodeSetIterator iterator() {
943
        return new SingleNodeIterator(this);
1✔
944
    }
945

946
    @Override
947
    public SequenceIterator iterate() {
948
        return new SingleNodeIterator(this);
1✔
949
    }
950

951
    @Override
952
    public SequenceIterator unorderedIterator() {
953
        return new SingleNodeIterator(this);
×
954
    }
955

956
    @Override
957
    public boolean contains(final NodeProxy proxy) {
958
        if(doc.getDocId() != proxy.doc.getDocId()) {
1!
959
            return false;
×
960
        } else {
961
            return nodeId.equals(proxy.getNodeId());
1✔
962
        }
963
    }
964

965
    @Override
966
    public void addAll(final NodeSet other) {
967
        throw new UnsupportedOperationException();
×
968
    }
969

970
    @Override
971
    public boolean isEmpty() {
972
        return false;
1✔
973
    }
974

975
    @Override
976
    public boolean hasOne() {
977
        return true;
1✔
978
    }
979

980
    @Override
981
    public boolean hasMany() {
982
        return false;
1✔
983
    }
984

985
    @Override
986
    public void add(final NodeProxy proxy) {
987
        throw new UnsupportedOperationException();
×
988
    }
989

990
    @Override
991
    public void add(final Item item) throws XPathException {
992
        throw new UnsupportedOperationException();
×
993
    }
994

995
    @Override
996
    public void add(final NodeProxy proxy, final int sizeHint) {
997
        throw new UnsupportedOperationException();
×
998
    }
999

1000
    @Override
1001
    public void addAll(final Sequence other) throws XPathException {
1002
        throw new UnsupportedOperationException();
×
1003
    }
1004

1005
    @Override
1006
    public int getLength() {
1007
        //TODO : how to delegate to the real node implementation's getLength() ?
1008
        return 1;
1✔
1009
    }
1010

1011
    @Override
1012
    public long getItemCountLong() {
1013
        return 1;
1✔
1014
    }
1015

1016
    @Override
1017
    public Node item(final int pos) {
1018
        return pos > 0 ? null : getNode();
×
1019
    }
1020

1021
    @Override
1022
    public Item itemAt(final int pos) {
1023
        return pos > 0 ? null : this;
1!
1024
    }
1025

1026
    @Override
1027
    public NodeProxy get(final int pos) {
1028
        return pos > 0 ? null : this;
1!
1029
    }
1030

1031
    @Override
1032
    public NodeProxy get(final NodeProxy p) {
1033
        return contains(p) ? this : null;
×
1034
    }
1035

1036
    @Override
1037
    public NodeProxy get(final DocumentImpl document, final NodeId nodeId) {
1038
        if(!this.nodeId.equals(nodeId)) {
1✔
1039
            return null;
1✔
1040
        } else if(this.doc.getDocId() != document.getDocId()) {
1!
1041
            return null;
×
1042
        } else {
1043
            return this;
1✔
1044
        }
1045
    }
1046

1047
    @Override
1048
    public NodeProxy parentWithChild(final NodeProxy proxy, final boolean directParent,
1049
            final boolean includeSelf, final int level) {
1050
        return parentWithChild(proxy.getOwnerDocument(), proxy.getNodeId(), directParent, includeSelf);
×
1051
    }
1052

1053
    @Override
1054
    public NodeProxy parentWithChild(final DocumentImpl otherDoc, final NodeId otherId,
1055
            final boolean directParent, final boolean includeSelf) {
1056
        if(otherDoc.getDocId() != doc.getDocId()) {
×
1057
            return null;
×
1058
        } else if(includeSelf && otherId.compareTo(nodeId) == 0) {
×
1059
            return this;
×
1060
        } else {
1061
            NodeId parentId = otherId.getParentId();
×
1062
            while(parentId != null) {
×
1063
                if(parentId.compareTo(nodeId) == 0) {
×
1064
                    return this;
×
1065
                } else if(directParent) {
×
1066
                    return null;
×
1067
                }
1068
                parentId = parentId.getParentId();
×
1069
            }
1070
            return null;
×
1071
        }
1072
    }
1073

1074
    @Override
1075
    public NodeSet getContextNodes(final int contextId) {
1076
        final NewArrayNodeSet result = new NewArrayNodeSet();
×
1077
        ContextItem contextNode = getContext();
×
1078
        while(contextNode != null) {
×
1079
            final NodeProxy p = contextNode.getNode();
×
1080
            p.addMatches(this);
×
1081
            if(!result.contains(p)) {
×
1082
                //TODO : why isn't "this" involved here ? -pb
1083
                if(contextId != Expression.NO_CONTEXT_ID) {
×
1084
                    p.addContextNode(contextId, p);
×
1085
                }
1086
                result.add(p);
×
1087
            }
1088
            contextNode = contextNode.getNextDirect();
×
1089
        }
1090
        return result;
×
1091
    }
1092

1093
    @Override
1094
    public int getState() {
1095
        return 0;
1✔
1096
    }
1097

1098
    @Override
1099
    public boolean hasChanged(final int previousState) {
1100
        return false;
×
1101
    }
1102

1103
    @Override
1104
    public boolean containsReference(final Item item) {
1105
        return this == item;
×
1106
    }
1107

1108
    @Override
1109
    public boolean contains(final Item item) {
1110
        return this.equals(item);
×
1111
    }
1112

1113
    @Override
1114
    public void destroy(final XQueryContext context, @Nullable final Sequence contextSequence) {
1115
        // Nothing to do
1116
    }
1✔
1117

1118
    @Override
1119
    public boolean isCacheable() {
1120
        return true;
1✔
1121
    }
1122

1123
    @Override
1124
    public int getSizeHint(final DocumentImpl document) {
1125
        if(document.getDocId() == doc.getDocId()) {
×
1126
            return 1;
×
1127
        } else {
1128
            return Constants.NO_SIZE_HINT;
×
1129
        }
1130
    }
1131

1132
    @Override
1133
    public DocumentSet getDocumentSet() {
1134
        return this;
1✔
1135
    }
1136

1137
    @Override
1138
    public Iterator<Collection> getCollectionIterator() {
1139
        return new Iterator<Collection>() {
1✔
1140
            boolean hasNext = true;
1✔
1141

1142
            @Override
1143
            public final boolean hasNext() {
1144
                return hasNext;
1✔
1145
            }
1146

1147
            @Override
1148
            public final Collection next() {
1149
                hasNext = false;
1✔
1150
                return NodeProxy.this.getOwnerDocument().getCollection();
1✔
1151
            }
1152

1153
            @Override
1154
            public final void remove() {
1155
                throw new UnsupportedOperationException("Remove is not implemented for NodeProxt#getCollectionIterator");
×
1156
            }
1157
        };
1158
    }
1159

1160
    @Override
1161
    public NodeSet intersection(final NodeSet other) {
1162
        if(other.contains(this)) {
×
1163
            return this;
×
1164
        } else {
1165
            return NodeSet.EMPTY_SET;
×
1166
        }
1167
    }
1168

1169
    @Override
1170
    public NodeSet deepIntersection(final NodeSet other) {
1171
        final NodeProxy p = other.parentWithChild(this, false, true, UNKNOWN_NODE_LEVEL);
×
1172
        if(p == null) {
×
1173
            return NodeSet.EMPTY_SET;
×
1174
        } else if(!nodeId.equals(p.nodeId)) {
×
1175
            p.addMatches(this);
×
1176
        }
1177
        return p;
×
1178
    }
1179

1180
    @Override
1181
    public NodeSet union(final NodeSet other) {
1182
        if(other.isEmpty()) {
×
1183
            return this;
×
1184
        }
1185
        final NewArrayNodeSet result = new NewArrayNodeSet();
×
1186
        result.addAll(other);
×
1187
        result.add(this);
×
1188
        return result;
×
1189
    }
1190

1191
    @Override
1192
    public NodeSet except(final NodeSet other) {
1193
        return other.contains(this) ? NodeSet.EMPTY_SET : this;
×
1194
    }
1195

1196
    @Override
1197
    public NodeSet filterDocuments(final NodeSet otherSet) {
1198
        final DocumentSet docs = otherSet.getDocumentSet();
1✔
1199
        if(docs.contains(doc.getDocId())) {
1✔
1200
            return this;
1✔
1201
        }
1202
        return NodeSet.EMPTY_SET;
1✔
1203
    }
1204

1205
    @Override
1206
    public void setProcessInReverseOrder(final boolean inReverseOrder) {
1207
        //Nothing to do
1208
    }
×
1209

1210
    @Override
1211
    public boolean getProcessInReverseOrder() {
1212
        return false;
1✔
1213
    }
1214

1215
    @Override
1216
    public NodeSet getParents(final int contextId) {
1217
        final NodeId pid = nodeId.getParentId();
1✔
1218
        if (pid == null) {
1!
1219
            return NodeSet.EMPTY_SET;
×
1220
        }
1221

1222
        final NodeProxy parent = new NodeProxy(expression, doc, pid, pid == NodeId.DOCUMENT_NODE ? Node.DOCUMENT_NODE : Node.ELEMENT_NODE);
1✔
1223
        if(contextId != Expression.NO_CONTEXT_ID) {
1✔
1224
            parent.addContextNode(contextId, this);
1✔
1225
        } else {
1✔
1226
            parent.copyContext(this);
1✔
1227
        }
1228
        parent.addMatches(this);
1✔
1229
        return parent;
1✔
1230
    }
1231

1232
    @Override
1233
    public NodeSet getAncestors(final int contextId, final boolean includeSelf) {
1234
        final NodeSet ancestors = new NewArrayNodeSet();
×
1235
        if(includeSelf) {
×
1236
            ancestors.add(this);
×
1237
        }
1238

1239
        NodeId parentID = nodeId.getParentId();
×
1240
        while(parentID != null) {
×
1241
            final NodeProxy parent = new NodeProxy(expression, getOwnerDocument(), parentID, Node.ELEMENT_NODE);
×
1242
            if(contextId != Expression.NO_CONTEXT_ID) {
×
1243
                parent.addContextNode(contextId, this);
×
1244
            } else {
×
1245
                parent.copyContext(this);
×
1246
            }
1247
            parent.addMatches(this);
×
1248
            ancestors.add(parent);
×
1249
            parentID = parentID.getParentId();
×
1250
        }
1251
        return ancestors;
×
1252
    }
1253

1254
    @Override
1255
    public NodeSet selectParentChild(final NodeSet al, final int mode) {
1256
        return selectParentChild(al, mode, Expression.NO_CONTEXT_ID);
×
1257
    }
1258

1259
    @Override
1260
    public NodeSet selectParentChild(final NodeSet al, final int mode, final int contextId) {
1261
        return NodeSetHelper.selectParentChild(this, al, mode, contextId);
×
1262
    }
1263

1264
    @Override
1265
    public boolean matchParentChild(final NodeSet al, final int mode, final int contextId) {
1266
        return NodeSetHelper.matchParentChild(this, al, mode, contextId);
×
1267
    }
1268

1269
    /* (non-Javadoc)
1270
     * @see org.exist.dom.persistent.NodeSet#selectAncestors(org.exist.dom.persistent.NodeSet, boolean, int)
1271
     */
1272
    @Override
1273
    public NodeSet selectAncestors(final NodeSet al, final boolean includeSelf, final int contextId) {
1274
        return NodeSetHelper.selectAncestors(this, al, includeSelf, contextId);
1✔
1275
    }
1276

1277
    public boolean matchAncestors(final NodeSet al, final boolean includeSelf, final int contextId) {
1278
        return NodeSetHelper.matchAncestors(this, al, includeSelf, contextId);
×
1279
    }
1280

1281
    @Override
1282
    public NodeSet selectPrecedingSiblings(final NodeSet siblings, final int contextId) {
1283
        return NodeSetHelper.selectPrecedingSiblings(this, siblings, contextId);
×
1284
    }
1285

1286
    @Override
1287
    public NodeSet selectFollowingSiblings(final NodeSet siblings, final int contextId) {
1288
        return NodeSetHelper.selectFollowingSiblings(this, siblings, contextId);
×
1289
    }
1290

1291
    @Override
1292
    public NodeSet selectAncestorDescendant(final NodeSet al, final int mode,
1293
            final boolean includeSelf, final int contextId, final boolean copyMatches) {
1294
        return NodeSetHelper.selectAncestorDescendant(this, al, mode, includeSelf, contextId);
×
1295
    }
1296

1297
    @Override
1298
    public boolean matchAncestorDescendant(final NodeSet al, final int mode,
1299
            final boolean includeSelf, final int contextId, final boolean copyMatches) {
1300
        return NodeSetHelper.matchAncestorDescendant(this, al, mode, includeSelf, contextId);
×
1301
    }
1302

1303
    @Override
1304
    public NodeSet selectPreceding(final NodeSet preceding, final int contextId) throws XPathException {
1305
        return NodeSetHelper.selectPreceding(this, preceding);
×
1306
    }
1307

1308
    @Override
1309
    public NodeSet selectPreceding(final NodeSet preceding, final int position,
1310
            final int contextId) throws XPathException, UnsupportedOperationException {
1311
        throw new UnsupportedOperationException();
×
1312
    }
1313

1314
    @Override
1315
    public NodeSet selectFollowing(final NodeSet following, final int contextId) throws XPathException {
1316
        return NodeSetHelper.selectFollowing(this, following);
×
1317
    }
1318

1319
    @Override
1320
    public NodeSet selectFollowing(final NodeSet following, final int position, final int contextId) throws XPathException {
1321
        throw new UnsupportedOperationException();
×
1322
    }
1323

1324
    @Override
1325
    public NodeSet directSelectAttribute(final DBBroker broker, final NodeTest test, final int contextId) {
1326
        if(nodeType != UNKNOWN_NODE_TYPE && nodeType != Node.ELEMENT_NODE) {
×
1327
            return NodeSet.EMPTY_SET;
×
1328
        }
1329

1330
        try {
1331
            NewArrayNodeSet result = null;
×
1332
            final IEmbeddedXMLStreamReader reader = broker.getXMLStreamReader(this, true);
×
1333
            int status = reader.next();
×
1334
            if(status != XMLStreamReader.START_ELEMENT) {
×
1335
                return NodeSet.EMPTY_SET;
×
1336
            }
1337

1338
            final int attrs = reader.getAttributeCount();
×
1339
            for (int i = 0; i < attrs; i++) {
×
1340
                status = reader.next();
×
1341
                if(status != XMLStreamReader.ATTRIBUTE) {
×
1342
                    break;
×
1343
                }
1344

1345
                final AttrImpl attr = (AttrImpl) reader.getNode();
×
1346
                if (test.matches(attr)) {
×
1347
                    final NodeProxy child = new NodeProxy(expression, attr);
×
1348
                    if (Expression.NO_CONTEXT_ID != contextId) {
×
1349
                        child.addContextNode(contextId, this);
×
1350
                    } else {
×
1351
                        child.copyContext(this);
×
1352
                    }
1353
                    if(!test.isWildcardTest()) {
×
1354
                        return child;
×
1355
                    }
1356
                    if(result == null) {
×
1357
                        result = new NewArrayNodeSet();
×
1358
                    }
1359
                    result.add(child);
×
1360
                }
1361
            }
1362
            return result == null ? NodeSet.EMPTY_SET : result;
×
1363
        } catch (final IOException | XMLStreamException e) {
×
1364
            throw new RuntimeException(e.getMessage(), e);
×
1365
        }
1366
    }
1367

1368
    public NodeSet directSelectChild(final QName qname, final int contextId) {
1369
        if(nodeType != UNKNOWN_NODE_TYPE && nodeType != Node.ELEMENT_NODE) {
×
1370
            return NodeSet.EMPTY_SET;
×
1371
        }
1372
        final NodeImpl node = (NodeImpl) getNode();
×
1373
        if(node.getNodeType() != Node.ELEMENT_NODE) {
×
1374
            return NodeSet.EMPTY_SET;
×
1375
        }
1376
        final NodeList children = node.getChildNodes();
×
1377
        if(children.getLength() == 0) {
×
1378
            return NodeSet.EMPTY_SET;
×
1379
        }
1380
        final NewArrayNodeSet result = new NewArrayNodeSet();
×
1381
        IStoredNode<?> child;
1382
        for(int i = 0; i < children.getLength(); i++) {
×
1383
            child = (IStoredNode<?>) children.item(i);
×
1384
            if(child.getQName().equals(qname)) {
×
1385
                final NodeProxy p = new NodeProxy(expression, doc, child.getNodeId(), Node.ELEMENT_NODE, child.getInternalAddress());
×
1386
                if(Expression.NO_CONTEXT_ID != contextId) {
×
1387
                    p.addContextNode(contextId, this);
×
1388
                } else {
×
1389
                    p.copyContext(this);
×
1390
                }
1391
                p.addMatches(this);
×
1392
                result.add(p);
×
1393
            }
1394
        }
1395
        return result;
×
1396
    }
1397

1398
    @Override
1399
    public String toString() {
1400
        if(nodeId == NodeId.DOCUMENT_NODE) {
1!
1401
            return "Document node proxy (docId=" + doc.getDocId() + ")";
1✔
1402
        } else {
1403
            return doc.getNode(nodeId).getNodeName();
×
1404
        }
1405
    }
1406

1407
    private static final class SingleNodeIterator implements NodeSetIterator, SequenceIterator {
1408

1409
        private boolean hasNext = true;
1✔
1410
        private NodeProxy node;
1411

1412
        public SingleNodeIterator(final NodeProxy node) {
1✔
1413
            this.node = node;
1✔
1414
        }
1✔
1415

1416
        @Override
1417
        public final boolean hasNext() {
1418
            return hasNext;
1✔
1419
        }
1420

1421
        @Override
1422
        public final NodeProxy next() {
1423
            if(!hasNext) {
1!
1424
                throw new NoSuchElementException();
×
1425
            } else {
1426
                hasNext = false;
1✔
1427
                return node;
1✔
1428
            }
1429
        }
1430

1431
        @Override
1432
        public long skippable() {
1433
            if (hasNext) {
1✔
1434
                return 1;
1✔
1435
            }
1436
            return 0;
1✔
1437
        }
1438

1439
        @Override
1440
        public long skip(final long n) {
1441
            final long skip = Math.min(n, hasNext ? 1 : 0);
1✔
1442
            if(skip == 1) {
1✔
1443
                hasNext = false;
1✔
1444
            }
1445
            return skip;
1✔
1446
        }
1447

1448
        @Override
1449
        public final NodeProxy peekNode() {
1450
            return node;
×
1451
        }
1452

1453
        @Override
1454
        public final void remove() {
1455
            throw new UnsupportedOperationException("remove is not implemented for SingleNodeIterator");
×
1456
        }
1457

1458
        @Override
1459
        public final Item nextItem() {
1460
            if(!hasNext) {
1✔
1461
                return null;
1✔
1462
            } else {
1463
                hasNext = false;
1✔
1464
                return node;
1✔
1465
            }
1466
        }
1467

1468
        @Override
1469
        public final void setPosition(final NodeProxy proxy) {
1470
            node = proxy;
×
1471
            hasNext = true;
×
1472
        }
×
1473
    }
1474

1475
    /**
1476
     * *********************************************
1477
     * Methods of MutableDocumentSet
1478
     * **********************************************
1479
     */
1480

1481
    @Override
1482
    public Iterator<DocumentImpl> getDocumentIterator() {
1483
        return new Iterator<DocumentImpl>() {
1✔
1484

1485
            private boolean hasMore = true;
1✔
1486

1487
            @Override
1488
            public final boolean hasNext() {
1489
                return hasMore;
1✔
1490
            }
1491

1492
            @Override
1493
            public final DocumentImpl next() {
1494
                if(!hasMore) {
1!
1495
                    throw new NoSuchElementException();
×
1496
                } else {
1497
                    hasMore = false;
1✔
1498
                    return doc;
1✔
1499
                }
1500
            }
1501

1502
            @Override
1503
            public final void remove() {
1504
                throw new UnsupportedOperationException("remove is not implemented for NodeProxy#getDocumentIterator");
×
1505
            }
1506
        };
1507
    }
1508

1509
    @Override
1510
    public int getDocumentCount() {
1511
        return 1;
1✔
1512
    }
1513

1514
    public DocumentImpl getDoc() {
1515
        return doc;
×
1516
    }
1517

1518
    @Override
1519
    public DocumentImpl getDoc(final int docId) {
1520
        if(docId == this.doc.getDocId()) {
1!
1521
            return this.doc;
1✔
1522
        }
1523
        return null;
×
1524
    }
1525

1526
    @Override
1527
    public XmldbURI[] getNames() {
1528
        return new XmldbURI[]{
×
1529
            this.doc.getURI()
×
1530
        };
1531
    }
1532

1533
    @Override
1534
    public DocumentSet intersection(final DocumentSet other) {
1535
        if(other.contains(doc.getDocId())) {
×
1536
            final DefaultDocumentSet r = new DefaultDocumentSet();
×
1537
            r.add(doc);
×
1538
            return r;
×
1539
        } else {
1540
            return DefaultDocumentSet.EMPTY_DOCUMENT_SET;
×
1541
        }
1542
    }
1543

1544
    @Override
1545
    public boolean contains(final DocumentSet other) {
1546
        if(other.getDocumentCount() > 1) {
×
1547
            return false;
×
1548
        } else if(other.getDocumentCount() == 0) {
×
1549
            return true;
×
1550
        } else {
1551
            return other.contains(doc.getDocId());
×
1552
        }
1553
    }
1554

1555
    @Override
1556
    public boolean contains(final int docId) {
1557
        return doc.getDocId() == docId;
1!
1558
    }
1559

1560
    @Override
1561
    public NodeSet docsToNodeSet() {
1562
        return new NodeProxy(expression, doc, NodeId.DOCUMENT_NODE);
×
1563
    }
1564

1565
    @Override
1566
    public ManagedLocks<ManagedDocumentLock> lock(final DBBroker broker, final boolean exclusive) throws LockException {
1567
        final LockManager lockManager = broker.getBrokerPool().getLockManager();
1✔
1568
        final ManagedDocumentLock docLock;
1569
        if(exclusive) {
1!
1570
            docLock = lockManager.acquireDocumentWriteLock(doc.getURI());
1✔
1571
        } else {
1✔
1572
            docLock = lockManager.acquireDocumentReadLock(doc.getURI());
×
1573
        }
1574
        return new ManagedLocks<>(docLock);
1✔
1575
    }
1576

1577
    @Override
1578
    public boolean equalDocs(final DocumentSet other) {
1579
        if(this == other) {
1!
1580
            // we are comparing the same objects
1581
            return true;
×
1582
        } else if(other.getDocumentCount() != 1) {
1!
1583
            return false;
×
1584
        } else {
1585
            return other.contains(doc.getDocId());
1✔
1586
        }
1587
    }
1588

1589
    @Override
1590
    public boolean directMatchAttribute(final DBBroker broker, final NodeTest test, final int contextId) {
1591
        if(nodeType != UNKNOWN_NODE_TYPE && nodeType != Node.ELEMENT_NODE) {
×
1592
            return false;
×
1593
        }
1594
        try {
1595
            final IEmbeddedXMLStreamReader reader = broker.getXMLStreamReader(this, true);
×
1596
            int status = reader.next();
×
1597
            if (status != XMLStreamReader.START_ELEMENT) {
×
1598
                return false;
×
1599
            }
1600

1601
            final int attrs = reader.getAttributeCount();
×
1602
            for (int i = 0; i < attrs; i++) {
×
1603
                status = reader.next();
×
1604
                if (status != XMLStreamReader.ATTRIBUTE) {
×
1605
                    break;
×
1606
                }
1607

1608
                final AttrImpl attr = (AttrImpl) reader.getNode();
×
1609
                if (test.matches(attr)) {
×
1610
                    return true;
×
1611
                }
1612
            }
1613
            return false;
×
1614
        } catch (final IOException | XMLStreamException e) {
×
1615
            throw new RuntimeException(e.getMessage(), e);
×
1616
        }
1617
    }
1618

1619
    public boolean directMatchChild(final QName qname, final int contextId) {
1620
        if(nodeType != UNKNOWN_NODE_TYPE && nodeType != Node.ELEMENT_NODE) {
×
1621
            return false;
×
1622
        }
1623
        final NodeImpl node = (NodeImpl) getNode();
×
1624
        if(node.getNodeType() != Node.ELEMENT_NODE) {
×
1625
            return false;
×
1626
        }
1627
        final NodeList children = node.getChildNodes();
×
1628
        if(children.getLength() == 0) {
×
1629
            return false;
×
1630
        }
1631
        IStoredNode<?> child;
1632
        for(int i = 0; i < children.getLength(); i++) {
×
1633
            child = (IStoredNode<?>) children.item(i);
×
1634
            if(child.getQName().equals(qname)) {
×
1635
                return true;
×
1636
            }
1637
        }
1638
        return false;
×
1639
    }
1640
}
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