Skip to content

Commit 9fdfed6

Browse files
pjfanningclaude
andcommitted
Fold _canHavePrefixUri into Xobj._bits to shrink AttrXobj by 8 bytes
NamedNodeXobj carries a single boolean, _canHavePrefixUri, to record whether a node was created through the DOM Level 1 factory methods. Xobj already ends on an 8-byte boundary, so that one byte costs AttrXobj a whole 8-byte slot: 88 bytes of fields become a 96-byte object with 7 bytes of padding. Xobj._bits already holds kind, domType and three flags, with the highest bit in use at 0x400. Moving the boolean there as CAN_HAVE_PREFIX_URI = 0x800 removes the field, and AttrXobj drops to 88 bytes. Measured with Unsafe.objectFieldOffset on JDK 17 and 21, with and without compressed oops. ElementXobj is unchanged at 96 - it adds a 4-byte _attributes reference on top of 88. Reported against POI as a 600MB workbook holding 2.35 million AttrXobj, where this is roughly 19MB. Refs apache/poi#992 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent fd2b423 commit 9fdfed6

4 files changed

Lines changed: 112 additions & 7 deletions

File tree

src/main/java/org/apache/xmlbeans/impl/store/DomImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,7 @@ public static Element document_createElement(Dom d, String name) {
661661
c.createElement(l.makeQualifiedQName("", name));
662662
ElementXobj e = (ElementXobj) c.getDom();
663663
c.release();
664-
e._canHavePrefixUri = false;
664+
e.clearBit(Xobj.CAN_HAVE_PREFIX_URI);
665665
return e;
666666
}
667667

@@ -698,7 +698,7 @@ public static Attr document_createAttribute(Dom d, String name) {
698698
c.createAttr(l.makeQualifiedQName("", name));
699699
AttrXobj e = (AttrXobj) c.getDom();
700700
c.release();
701-
e._canHavePrefixUri = false;
701+
e.clearBit(Xobj.CAN_HAVE_PREFIX_URI);
702702
return e;
703703
}
704704

src/main/java/org/apache/xmlbeans/impl/store/NamedNodeXobj.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,12 @@
1818
abstract class NamedNodeXobj extends NodeXobj {
1919
NamedNodeXobj(Locale l, int kind, int domType) {
2020
super(l, kind, domType);
21-
_canHavePrefixUri = true;
21+
setBit(CAN_HAVE_PREFIX_URI);
2222
}
2323

2424
public boolean nodeCanHavePrefixUri() {
25-
return _canHavePrefixUri;
25+
return bitIsSet(CAN_HAVE_PREFIX_URI);
2626
}
2727

28-
boolean _canHavePrefixUri;
2928
}
3029

src/main/java/org/apache/xmlbeans/impl/store/Xobj.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -567,8 +567,7 @@ final void setName(QName newName) {
567567

568568
_name = newName;
569569
if (this instanceof NamedNodeXobj) {
570-
NamedNodeXobj me = (NamedNodeXobj) this;
571-
me._canHavePrefixUri = true;
570+
setBit(CAN_HAVE_PREFIX_URI);
572571
}
573572

574573
if (!isProcinst()) {
@@ -1298,6 +1297,9 @@ final boolean bitIsClear(int mask) {
12981297
static final int VACANT = 0x100;
12991298
static final int STABLE_USER = 0x200;
13001299
static final int INHIBIT_DISCONNECT = 0x400;
1300+
// only NamedNodeXobj reads this one - it lives here so that NamedNodeXobj
1301+
// needs no field of its own, which would cost 8 bytes per AttrXobj in padding
1302+
static final int CAN_HAVE_PREFIX_URI = 0x800;
13011303

13021304
final boolean isVacant() {
13031305
return bitIsSet(VACANT);
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/* Licensed to the Apache Software Foundation (ASF) under one or more
2+
* contributor license agreements. See the NOTICE file distributed with
3+
* this work for additional information regarding copyright ownership.
4+
* The ASF licenses this file to You under the Apache License, Version 2.0
5+
* (the "License"); you may not use this file except in compliance with
6+
* the License. You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.apache.xmlbeans.impl.store;
18+
19+
import org.apache.xmlbeans.XmlCursor;
20+
import org.apache.xmlbeans.XmlException;
21+
import org.apache.xmlbeans.XmlObject;
22+
import org.junit.jupiter.api.Test;
23+
import org.w3c.dom.Attr;
24+
import org.w3c.dom.Document;
25+
import org.w3c.dom.Element;
26+
27+
import javax.xml.namespace.QName;
28+
29+
import static org.junit.jupiter.api.Assertions.assertEquals;
30+
import static org.junit.jupiter.api.Assertions.assertNull;
31+
32+
/**
33+
* DOM Level 1 factory methods produce nodes with no namespace information, which
34+
* the store tracks per node. These pin that behaviour across the three places the
35+
* state is written.
36+
*/
37+
public class NodePrefixUriFlagTest {
38+
39+
private static final String URI = "http://example.org/ns";
40+
41+
private static XmlObject parse() throws XmlException {
42+
return XmlObject.Factory.parse("<root/>");
43+
}
44+
45+
private static Document doc() throws XmlException {
46+
return (Document) parse().getDomNode();
47+
}
48+
49+
@Test
50+
void level1ElementHasNoNamespaceInfo() throws XmlException {
51+
Element e = doc().createElement("foo");
52+
53+
assertNull(e.getLocalName());
54+
assertNull(e.getNamespaceURI());
55+
assertNull(e.getPrefix());
56+
}
57+
58+
@Test
59+
void level2ElementKeepsNamespaceInfo() throws XmlException {
60+
Element e = doc().createElementNS(URI, "p:foo");
61+
62+
assertEquals("foo", e.getLocalName());
63+
assertEquals(URI, e.getNamespaceURI());
64+
assertEquals("p", e.getPrefix());
65+
}
66+
67+
@Test
68+
void level1AttributeHasNoNamespaceInfo() throws XmlException {
69+
Attr a = doc().createAttribute("bar");
70+
71+
assertNull(a.getLocalName());
72+
assertNull(a.getNamespaceURI());
73+
assertNull(a.getPrefix());
74+
}
75+
76+
@Test
77+
void level2AttributeKeepsNamespaceInfo() throws XmlException {
78+
Attr a = doc().createAttributeNS(URI, "p:bar");
79+
80+
assertEquals("bar", a.getLocalName());
81+
assertEquals(URI, a.getNamespaceURI());
82+
assertEquals("p", a.getPrefix());
83+
}
84+
85+
@Test
86+
void renamingALevel1ElementRestoresNamespaceInfo() throws XmlException {
87+
XmlObject xo = parse();
88+
Document doc = (Document) xo.getDomNode();
89+
90+
Element child = doc.createElement("child");
91+
doc.getDocumentElement().appendChild(child);
92+
assertNull(child.getLocalName());
93+
94+
try (XmlCursor c = xo.newCursor()) {
95+
c.toFirstChild(); // root
96+
c.toFirstChild(); // child
97+
c.setName(new QName(URI, "child", "p"));
98+
}
99+
100+
assertEquals("child", child.getLocalName());
101+
assertEquals(URI, child.getNamespaceURI());
102+
assertEquals("p", child.getPrefix());
103+
}
104+
}

0 commit comments

Comments
 (0)