XmlObjectWriter.javaDocument). 387 lines. Javadoc explains the motivation: "A simple solution for streaming xml objects and to prevent default values from the xml output (because this feature isn't yet available in XStream)." — at the time of writing, XStream couldn't skip fields with default values, and for data export this is critical (to prevent XML bloat from thousands of <deleted>false</deleted> tags).
Pairs with XmlObjectReader: Writer serializes Java → XML, Reader deserializes XML → Java. Both use dom4j as the XML library and @XmlObject/@XmlField annotations for format control.
ATTR_ID and ATTR_REF_IDATTR_ID = "o-id" — unique object identifier in XML. Writer assigns each new object an incrementing refIdCounter and writes as attribute o-id="0", o-id="1", etc.ATTR_REF_ID = "ref-id" — reference to an already-written object. If an object was already serialized (found in writtenObjects cache), Writer does NOT write its contents again, but only sets ref-id="0" — a reference to the element with o-id="0"This mechanism solves circular references and deduplication. Example: object A contains a reference to object B, and B contains a back-reference to A. Without deduplication, Writer would enter infinite recursion. With deduplication: A is written with o-id=0, B with o-id=1, back-reference to A is written as ref-id=0.
writtenObjectsMap<String, Element> where key is className:hashCode (e.g., "org.projectforge.business.user.PFUserDO:987345678854"). This is an identity-based cache (by hashCode), not equals-based — two different objects with identical fields will both be written, but one object encountered twice will be written only once. Using hashCode() for identity is theoretically unreliable (collisions possible), but in practice for typical XML sizes (hundreds to thousands of objects), collision probability is vanishingly small.
write(Branch parent, Object obj, String name, boolean asAttribute, boolean asCDATA)Recursive serialization method. Logic mirrors the Reader's type-checking chain but in reverse:
writtenObjects — writes ref-id instead of full contentsconverter.toString() and writes result as element text or attributeenum.name() (e.g., "ACTIVE", "PAID")writtenObjects, iterates all fields via reflection, calls writeField() for each non-default fieldMethod isDefaultType(ann, value) checks whether the field value equals the default from @XmlField.default*. If so, the field is NOT written. This is why XmlStream was created:
false — all false-fields are skippeddefaultDoubleValue() via compareTo()defaultIntValue(). Magic number MAGIC_INT_NUMBER from XmlConstants serves as "unset" sentineldefaultStringValue(). Magic string MAGIC_STRING is the sentinelhasDefaultType() checks whether a default was declared at all (distinguishes "default not set" from "default == false/0/null"). For Boolean — always true (false is a valid default). For other types — compares against MAGIC sentinels.
asAttributeAsDefault()Some types are serialized as attributes by default (compact):
xmlRegistry.asAttributeAsDefault(type) — global setting (e.g., all primitive types → attributes)Enum.class.isAssignableFrom(type) — all enums always as attributes (short names like "ACTIVE" fit well in attributes)@XmlField.asAttribute=true — per-field override@XmlField.asElement=true — force as element (even if type defaults to attribute)ignoreField(Field)Static method used by both Writer and Reader. Skips fields with modifiers: static (class constants), final (immutable after creation), transient (explicitly non-serializable). Also, the protected method ignoreField(Object, Field) can be overridden by subclasses for additional filtering. Plus the @XmlOmitField annotation — an explicit skip marker regardless of modifiers.
868d6abb7 2025 → 2026 (copyright year update) 63081666f Source file headers: 2024→2025 9f874e26c MAJOR-CHANGE! Migration of integer id's to Long id's b6092df09 Copyright 2023 → 2024 ab45d51fa Copyright 2001-2022 → 2001-2023 5f7ef41b8 Copyright 2021 → 2022 cd27dd997 package xstream → xmlstream ceb63e8a1 Source code header: (C) 2001-2021 7c79f1922 Copyright of source header → 2020 24019a0f5 Eliminate DateHolder occurences af35917ac More code cleanup 73a9755df Code cleanup (StringBuffer→StringBuilder, diamond, deprecated classes) 32f634b88 Optimize imports 000ca723d Remove pointless boolean expressions dd5ca38ac CopyRight of all java file-header updated or created a5bbdca6a Change logger to slf4j f979e8a42 MGC-UPDATE: Update auf Version 3.0.0-SNAPSHOT 9ebb88522 Initial commit
9f874e26c — mass migration of all identifiers from Integer to Long (including refIdCounter). Previously object IDs in XML were Integer (max ~2 billion), after — Long (max ~9 quintillion). Critical for large installations where the counter could overflow. 24019a0f5 — eliminated DateHolder (pre-Java-8 class) from Writer, replaced with java.util.Date and java.time types via converters. cd27dd997 — package renamed from xstream to xmlstream; the commit message "(should be replaced by xstream later)" reveals the author planned to eventually replace the custom framework with XStream — a plan that was never executed.