• 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

62.74
/exist-core/src/main/java/org/exist/dom/persistent/AttrImpl.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.Namespaces;
52
import org.exist.dom.QName;
53
import org.exist.numbering.NodeId;
54
import org.exist.storage.DBBroker;
55
import org.exist.storage.RangeIndexSpec;
56
import org.exist.storage.Signatures;
57
import org.exist.util.ByteArrayPool;
58
import org.exist.util.ByteConversion;
59
import org.exist.util.UTF8;
60
import org.exist.util.XMLString;
61
import org.exist.util.pool.NodePool;
62
import org.exist.util.serializer.AttrList;
63
import org.exist.xquery.Expression;
64
import org.w3c.dom.*;
65

66
import javax.xml.XMLConstants;
67

68
import static java.nio.charset.StandardCharsets.UTF_8;
69

70
public class AttrImpl extends NamedNode<AttrImpl> implements Attr {
71

72
    public static final int LENGTH_NS_ID = 2; //sizeof short
73
    public static final int LENGTH_PREFIX_LENGTH = 2; //sizeof short
74

75
    public static final int CDATA = 0;
76
    public static final int ID = 1;
77
    public static final int IDREF = 2;
78
    public static final int IDREFS = 3;
79

80
    private static final int DEFAULT_ATTRIBUTE_TYPE = CDATA;
81

82
    private int attributeType = DEFAULT_ATTRIBUTE_TYPE;
1✔
83
    private int indexType = RangeIndexSpec.NO_INDEX;
1✔
84
    private XMLString value = null;
1✔
85

86
    public AttrImpl() {
87
        this((Expression) null);
×
88
    }
×
89

90
    public AttrImpl(final Expression expression) {
91
        super(expression, Node.ATTRIBUTE_NODE);
1✔
92
    }
1✔
93

94
    public AttrImpl(final QName name, final SymbolTable symbols) throws DOMException {
95
        this(null, name, symbols);
×
96
    }
×
97

98
    public AttrImpl(final Expression expression, final QName name, final SymbolTable symbols) throws DOMException {
99
        super(expression, Node.ATTRIBUTE_NODE, name);
1✔
100
        if(symbols != null && symbols.getSymbol(nodeName.getLocalPart()) < 0) {
1!
101
            throw new DOMException(DOMException.INVALID_ACCESS_ERR,
×
102
                "Too many element/attribute names registered in the database. No of distinct names is limited to 16bit. Aborting store.");
×
103
        }
104
    }
1✔
105

106
    public AttrImpl(final QName name, final String str, final SymbolTable symbols) throws DOMException {
107
        this(null, name, str, symbols);
×
108
    }
×
109

110
    public AttrImpl(final Expression expression, final QName name, final String str, final SymbolTable symbols) throws DOMException {
111
        this(expression, name, symbols);
1✔
112
        this.value = new XMLString(str.toCharArray());
1✔
113
    }
1✔
114

115
    public AttrImpl(final AttrImpl other) {
116
        this(null, other);
×
117
    }
×
118

119
    public AttrImpl(final Expression expression, final AttrImpl other) {
120
        super(expression, other);
1✔
121
        this.attributeType = other.attributeType;
1✔
122
        this.value = new XMLString(other.value);
1✔
123
    }
1✔
124

125
    @Override
126
    public void clear() {
127
        super.clear();
1✔
128
        this.attributeType = DEFAULT_ATTRIBUTE_TYPE;
1✔
129
        this.value.reset();
1✔
130
        this.value = null;
1✔
131
    }
1✔
132

133
    /**
134
     * Serializes a (persistent DOM) Attr to a byte array
135
     *
136
     * data = signature nodeIdUnitsLength nodeId localNameId namespace? value
137
     *
138
     * signature = [byte] 0x80 | localNameType | attrType | hasNamespace?
139
     *
140
     * localNameType = noContent OR intContent OR shortContent OR byteContent
141
     * noContent = 0x0
142
     * intContent = 0x1
143
     * shortContent = 0x2
144
     * byteContent = 0x3
145
     *
146
     * attrType = cdata OR id OR idref OR idrefs
147
     * cdata = 0x0;
148
     * id = 0x4
149
     * idref = 0x8
150
     * idrefs = 0xC
151
     *
152
     * hasNamespace = 0x10
153
     *
154
     * nodeIdUnitsLength = [short] (2 bytes) The number of units of the attr's NodeId
155
     * nodeId = {@link org.exist.numbering.DLNBase#serialize(byte[], int)}
156
     *
157
     * localNameId = [int] (4 bytes) | [short] (2 bytes) | [byte] 1 byte. The Id of the attr's local name from SymbolTable (symbols.dbx)
158
     *
159
     * namespace = namespaceUriId namespacePrefixLength attrNamespacePrefix?
160
     * namespaceUriId = [short] (2 bytes) The Id of the namespace URI from SymbolTable (symbols.dbx)
161
     * namespacePrefixLength = [short] (2 bytes)
162
     * attrNamespacePrefix = eUtf8
163
     *
164
     * value = eUtf8
165
     *
166
     * eUtf8 = {@link org.exist.util.UTF8#encode(java.lang.String, byte[], int)}
167
     *
168
     * @return the returned byte array after use must be returned to the ByteArrayPool
169
     *     by calling {@link ByteArrayPool#releaseByteArray(byte[])}
170
     */
171
    @Override
172
    public byte[] serialize() {
173
        if(nodeName.getLocalPart() == null) {
1!
174
            throw new RuntimeException("Local name is null");
×
175
        }
176
        final short id = ownerDocument.getBrokerPool().getSymbols().getSymbol(this);
1✔
177
        final byte idSizeType = Signatures.getSizeType(id);
1✔
178
        int prefixLen = 0;
1✔
179
        if(nodeName.hasNamespace() && nodeName.getPrefix() != null && nodeName.getPrefix().length() > 0) {
1!
180
            prefixLen = UTF8.encoded(nodeName.getPrefix());
1✔
181
        }
182
        final int nodeIdLen = nodeId.size();
1✔
183
        final byte[] data = ByteArrayPool.getByteArray(
1✔
184
            LENGTH_SIGNATURE_LENGTH + NodeId.LENGTH_NODE_ID_UNITS + nodeIdLen +
1✔
185
                Signatures.getLength(idSizeType) +
1✔
186
                (nodeName.hasNamespace() ? LENGTH_NS_ID + LENGTH_PREFIX_LENGTH + prefixLen : 0) +
1✔
187
                value.UTF8Size());
1✔
188
        int pos = 0;
1✔
189
        data[pos] = (byte) (Signatures.Attr << 0x5);
1✔
190
        data[pos] |= idSizeType;
1✔
191
        data[pos] |= (byte) (attributeType << 0x2);
1✔
192
        if(nodeName.hasNamespace()) {
1✔
193
            data[pos] |= 0x10;
1✔
194
        }
195
        pos += StoredNode.LENGTH_SIGNATURE_LENGTH;
1✔
196
        ByteConversion.shortToByte((short) nodeId.units(), data, pos);
1✔
197
        pos += NodeId.LENGTH_NODE_ID_UNITS;
1✔
198
        nodeId.serialize(data, pos);
1✔
199
        pos += nodeIdLen;
1✔
200
        Signatures.write(idSizeType, id, data, pos);
1✔
201
        pos += Signatures.getLength(idSizeType);
1✔
202
        if(nodeName.hasNamespace()) {
1✔
203
            final short nsId = ownerDocument.getBrokerPool().getSymbols().getNSSymbol(nodeName.getNamespaceURI());
1✔
204
            ByteConversion.shortToByte(nsId, data, pos);
1✔
205
            pos += LENGTH_NS_ID;
1✔
206
            ByteConversion.shortToByte((short) prefixLen, data, pos);
1✔
207
            pos += LENGTH_PREFIX_LENGTH;
1✔
208
            if(nodeName.getPrefix() != null && nodeName.getPrefix().length() > 0) {
1!
209
                UTF8.encode(nodeName.getPrefix(), data, pos);
1✔
210
            }
211
            pos += prefixLen;
1✔
212
        }
213
        value.UTF8Encode(data, pos);
1✔
214
        return data;
1✔
215
    }
216

217
    public static StoredNode deserialize(final byte[] data, final int start, final int len, final DocumentImpl doc, final boolean pooled) {
218
        int pos = start;
1✔
219
        final byte idSizeType = (byte) (data[pos] & 0x3);
1✔
220
        final boolean hasNamespace = (data[pos] & 0x10) == 0x10;
1✔
221
        final int attrType = (data[pos] & 0x4) >> 0x2;
1✔
222
        pos += StoredNode.LENGTH_SIGNATURE_LENGTH;
1✔
223
        final int dlnLen = ByteConversion.byteToShort(data, pos);
1✔
224
        pos += NodeId.LENGTH_NODE_ID_UNITS;
1✔
225
        final NodeId dln = doc.getBrokerPool().getNodeFactory().createFromData(dlnLen, data, pos);
1✔
226
        pos += dln.size();
1✔
227
        final short id = (short) Signatures.read(idSizeType, data, pos);
1✔
228
        pos += Signatures.getLength(idSizeType);
1✔
229
        final String name = doc.getBrokerPool().getSymbols().getName(id);
1✔
230
        if(name == null) {
1!
231
            throw new RuntimeException("no symbol for id " + id);
×
232
        }
233
        short nsId = 0;
1✔
234
        String prefix = null;
1✔
235
        if(hasNamespace) {
1✔
236
            nsId = ByteConversion.byteToShort(data, pos);
1✔
237
            pos += LENGTH_NS_ID;
1✔
238
            int prefixLen = ByteConversion.byteToShort(data, pos);
1✔
239
            pos += LENGTH_PREFIX_LENGTH;
1✔
240
            if(prefixLen > 0) {
1!
241
                prefix = UTF8.decode(data, pos, prefixLen).toString();
1✔
242
            }
243
            pos += prefixLen;
1✔
244
        }
245
        final String namespace = nsId == 0 ? "" : doc.getBrokerPool().getSymbols().getNamespace(nsId);
1✔
246
        final XMLString value = UTF8.decode(data, pos, len - (pos - start));
1✔
247

248
        //OK : we have the necessary material to build the attribute
249
        final AttrImpl attr;
250
        if(pooled) {
1!
251
            attr = (AttrImpl) NodePool.getInstance().borrowNode(Node.ATTRIBUTE_NODE);
×
252
        } else {
×
253
            attr = new AttrImpl((Expression) null);
1✔
254
        }
255
        attr.setNodeName(doc.getBrokerPool().getSymbols().getQName(Node.ATTRIBUTE_NODE, namespace, name, prefix));
1✔
256
        if (attr.value != null) {
1!
257
            attr.value.reset();
×
258
        }
259
        attr.value = value;
1✔
260
        attr.setNodeId(dln);
1✔
261
        attr.setType(attrType);
1✔
262
        return attr;
1✔
263
    }
264

265
    public static void addToList(final DBBroker broker, final byte[] data, final int start, final int len, final AttrList list) {
266
        int pos = start;
1✔
267
        final byte idSizeType = (byte) (data[pos] & 0x3);
1✔
268
        final boolean hasNamespace = (data[pos] & 0x10) == 0x10;
1!
269
        final int attrType = (data[pos] & 0x4) >> 0x2;
1✔
270
        pos += StoredNode.LENGTH_SIGNATURE_LENGTH;
1✔
271
        final int dlnLen = ByteConversion.byteToShort(data, pos);
1✔
272
        pos += NodeId.LENGTH_NODE_ID_UNITS;
1✔
273
        final NodeId dln = broker.getBrokerPool().getNodeFactory().createFromData(dlnLen, data, pos);
1✔
274
        pos += dln.size();
1✔
275
        final short id = (short) Signatures.read(idSizeType, data, pos);
1✔
276
        pos += Signatures.getLength(idSizeType);
1✔
277
        final String name = broker.getBrokerPool().getSymbols().getName(id);
1✔
278
        if(name == null) {
1!
279
            throw new RuntimeException("no symbol for id " + id);
×
280
        }
281
        short nsId = 0;
1✔
282
        String prefix = null;
1✔
283
        if(hasNamespace) {
1!
284
            nsId = ByteConversion.byteToShort(data, pos);
×
285
            pos += LENGTH_NS_ID;
×
286
            int prefixLen = ByteConversion.byteToShort(data, pos);
×
287
            pos += LENGTH_PREFIX_LENGTH;
×
288
            if(prefixLen > 0) {
×
289
                prefix = UTF8.decode(data, pos, prefixLen).toString();
×
290
            }
291
            pos += prefixLen;
×
292
        }
293
        final String namespace = nsId == 0 ? XMLConstants.NULL_NS_URI : broker.getBrokerPool().getSymbols().getNamespace(nsId);
1!
294
        final String value = new String(data, pos, len - (pos - start), UTF_8);
1✔
295

296
        list.addAttribute(broker.getBrokerPool().getSymbols().getQName(Node.ATTRIBUTE_NODE, namespace, name, prefix), value, attrType, dln);
1✔
297
    }
1✔
298

299
    @Override
300
    public String getName() {
301
        return getNodeName();
1✔
302
    }
303

304
    public int getType() {
305
        return attributeType;
1✔
306
    }
307

308
    public void setType(final int type) {
309
        //TODO : range check -pb
310
        this.attributeType = type;
1✔
311
    }
1✔
312

313
    public static String getAttributeType(final int type) {
314
        return switch (type) {
×
315
            case AttrImpl.ID -> "ID";
×
316
            case AttrImpl.IDREF -> "IDREF";
×
317
            case AttrImpl.IDREFS -> "IDREFS";
×
318
            case AttrImpl.CDATA -> "CDATA";
×
319
            default -> null;
×
320
        };
321
    }
322

323
    public void setIndexType(final int idxType) {
324
        this.indexType = idxType;
1✔
325
    }
1✔
326

327
    public int getIndexType() {
328
        return indexType;
1✔
329
    }
330

331
    @Override
332
    public String getValue() {
333
        if(value == null) {
1!
334
            return "";
×
335
        } else {
336
            return value.toString();
1✔
337
        }
338
    }
339

340
    @Override
341
    public void setValue(final String value) throws DOMException {
342
        if (this.value != null) {
1✔
343
            this.value.reset();
1✔
344
            this.value.append(value);
1✔
345
        } else {
1✔
346
            this.value = new XMLString(value.toCharArray());
1✔
347
        }
348
    }
1✔
349

350
    @Override
351
    public String getNodeValue() {
352
        return getValue();
1✔
353
    }
354

355
    @Override
356
    public void setNodeValue(final String nodeValue) throws DOMException {
357
       setValue(nodeValue);
×
358
    }
×
359

360
    @Override
361
    public Element getOwnerElement() {
362
        return (Element)getOwnerDocument().getNode(nodeId.getParentId());
1✔
363
    }
364

365
    @Override
366
    public Node getParentNode() {
367
        return null;
1✔
368
    }
369

370
    @Override
371
    public StoredNode getParentStoredNode() {
372
        return (StoredNode)getOwnerDocument().getNode(nodeId.getParentId());
1✔
373
    }
374

375
    @Override
376
    public boolean getSpecified() {
377
        return true;
1✔
378
    }
379

380
    @Override
381
    public String toString() {
382
        return String.valueOf(nodeName) + "=\"" + value + "\"";
×
383
    }
384

385
    @Override
386
    public String toString(final boolean top) {
387
        if(top) {
×
388
            return"<exist:attribute " + "xmlns:exist=\"" + Namespaces.EXIST_NS + "\" " +
×
389
                    "exist:id=\"" +  getNodeId() +  "\" exist:source=\"" +
×
390
                    getOwnerDocument().getFileURI() + "\" " +  getNodeName() + "=\"" + getValue() + "\"/>";
×
391
        } else {
392
            return toString();
×
393
        }
394
    }
395

396
    @Override
397
    public int getChildCount() {
398
        return 0;
×
399
    }
400

401
    @Override
402
    public Node getFirstChild() {
403
        return null;
×
404
    }
405

406
    @Override
407
    public Node getPreviousSibling() {
408
        return null;
×
409
    }
410

411
    @Override
412
    public Node getNextSibling() {
413
        return null;
×
414
    }
415

416
    @Override
417
    public TypeInfo getSchemaTypeInfo() {
418
        return null;
×
419
    }
420

421
    @Override
422
    public boolean isId() {
423
        return this.getType() == ID;
1✔
424
    }
425

426
    @Override
427
    public String getBaseURI() {
428
        final Element e = getOwnerElement();
×
429
        if(e != null) {
×
430
            return e.getBaseURI();
×
431
        }
432
        return null;
×
433
    }
434

435
    @Override
436
    public short compareDocumentPosition(final Node other) throws DOMException {
437
        return 0;
×
438
    }
439

440
    @Override
441
    public String getTextContent() throws DOMException {
442
        return getNodeValue();
×
443
    }
444

445
    @Override
446
    public void setTextContent(final String textContent) throws DOMException {
447
        setNodeValue(textContent);
×
448
    }
×
449

450
    @Override
451
    public String lookupPrefix(final String namespaceURI) {
452
        return null;
×
453
    }
454

455
    @Override
456
    public boolean isDefaultNamespace(final String namespaceURI) {
457
        return false;
×
458
    }
459

460
    @Override
461
    public String lookupNamespaceURI(final String prefix) {
462
        return null;
×
463
    }
464

465
    @Override
466
    public boolean isEqualNode(final Node arg) {
467
        return false;
×
468
    }
469

470
    @Override
471
    public Object getFeature(final String feature, final String version) {
472
        return null;
×
473
    }
474
    @Override
475
    public Object setUserData(final String key, final Object data, final UserDataHandler handler) {
476
        return null;
×
477
    }
478

479
    @Override
480
    public Object getUserData(final String key) {
481
        return null;
×
482
    }
483

484
    @Override
485
    public boolean equals(final Object obj) {
486
        if(!super.equals(obj)) {
×
487
            return false;
×
488
        }
489

490
        if(obj instanceof AttrImpl other) {
×
491
            return other.getQName().equals(getQName())
×
492
                    && other.value.equals(value);
×
493
        }
494

495
        return false;
×
496
    }
497
}
498

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