EN · DE · RU · FR · ES

#757: XmlObjectReader.java

projectforge-business/src/main/java/org/projectforge/framework/xmlstream/XmlObjectReader.java Java deserialization engine — the heart of XmlStream reading. 514 lines. Uses dom4j. Source: projectforge-business/src/main/java/org/projectforge/framework/xmlstream/XmlObjectReader.java 514 lines · 402 code · 74 comments · 38 blank
Purpose: The deserialization core of the XmlStream framework — converts XML (as dom4j Element trees) back into Java objects with populated fields. 514 lines. This is the largest file in the XmlStream framework. Javadoc: "Parses objects serialized by the XmlObjectWriter. Uses the dom4j." Works in pair with XmlObjectWriter — Writer serializes objects to XML, Reader deserializes XML back to objects.

Architecture — State Machine with Backtracking

Reader is a stateful object (unlike the stateless Writer). It holds state during a single read session:

Entry Points — Two read() Methods

read(String xml) — public API

Takes a raw XML string. Parses it via XmlHelper.fromString() → dom4j Element. Clears state (processedElements, processedAttributes, warnings). Calls internal read(Element). After reading, runs checkForIgnoredElements() — if any XML tags/attributes weren't processed, they appear in warnings. Returns the root deserialized object.

read(Element el) — internal API

Takes a dom4j Element, finds the class by tag name (via getClass()), and calls recursive read(Class, Element, ...). If class not found — returns null.

Three-Level Class Resolution — getClass(String elementName)

When encountering an XML tag, Reader searches for the corresponding Java class in three places (in priority order):

  1. Own AliasMap: this.aliasMap.getClassForAlias(name) — if explicitly set via setAliasMap()
  2. Global XmlRegistry: xmlRegistry.getClassForAlias(name) — classes registered at startup
  3. Class.forName() (fallback): attempt to load by FQCN. If class is an interface, checks implementationMapping. If not found, logs error and returns null (element will be ignored)

This three-level system allows flexible mapping management: AliasMap for local overrides, XmlRegistry for global rules, Class.forName for backward compatibility with old XML files where tags were FQCNs.

Core Recursive Method — read(Class, Element, attrName, attrValue)

The heart of Reader. Takes a field type, XML element, and optional attribute name/value. Logic is a chain of type checks:

  1. Reference check: if element has attribute o-ref-id — it's a reference to an already-deserialized object. Looks up in referenceObjects and returns cached object (deduplication). If ref-id not found — logs error
  2. Converter check: if type has a registered IConverter (via xmlRegistry.getConverter(clazz)) — calls converter.fromString(). For simple types: String, Integer, Date, BigDecimal, Boolean
  3. Enum check: if class is enum, calls enumFromString(). Tries Enum.valueOf(clazz, val). On IllegalArgumentException, does a second attempt with toUpperCase() — backward compatibility with old XML where enums were lowercase. Value "null" is treated as null
  4. Collection check: if class is Collection (List/Set/SortedSet), creates the appropriate implementation (ArrayList/HashSet/TreeSet), and recursively reads all child XML elements as collection items. If ignoreEmptyCollections=true and collection is empty — leaves field null
  5. Complex object: for all other types (classes with @XmlObject) — creates instance via BeanHelper.newInstance(), checks o-id attribute (saves in referenceObjects for future ref-id lookups), and calls read(obj, el) for recursive field population

Field Population — read(Object obj, Element el)

Iterates all fields of the object (via BeanHelper.getAllDeclaredFields() — including inherited), makes them accessible (reflection), and for each XML attribute and child element finds the corresponding field:

Important: the o-id and ref-id fields (Writer's XML attributes) are explicitly skipped — they are not searched among object fields.

Warnings — checkForIgnoredElements()

After reading all XML, Reader checks: were all elements from the source XML tree marked as processed? If not — the XML contains tags for which no fields were found in the Java class. This may indicate:

All unmarked elements and attributes are collected into a warnings string. If the string exceeds 500 characters — truncated (ABBREVIATE_WARNING=500) to avoid spamming logs.

Template Method Hooks for Subclasses

Three protected methods can be overridden in subclasses:

These hooks allow subclasses to modify behavior without rewriting the entire logic — e.g., skip certain fields, filter collections, or post-process objects.

initialize(Class) — Pre-scanning

Can be called before reading to pre-register all @XmlObject classes in AliasMap. Recursively walks the type tree: for a class with @XmlObject, registers the alias, then for each non-primitive field recursively calls initialize(). Uses a processed Set to prevent infinite recursion on circular references. This allows passing just the root class — Reader itself finds all nested types.

Git History

868d6abb7 2025 → 2026 (copyright year update)
63081666f Source file headers: 2024→2025
a72903e36 *.java, *.kt: StringBuffer → StringBuilder
b6092df09 Copyright 2023 → 2024
ab45d51fa Copyright 2001-2022 → 2001-2023
73b0be50b org.apache.commons.collections → org.apache.commons.collections4
5f7ef41b8 Copyright 2021 → 2022
cd27dd997 package xstream → xmlstream
ceb63e8a1 Source code header: (C) 2001-2021
7c79f1922 Copyright of source header → 2020
73a9755df More code cleanup (StringBuffer→StringBuilder, diamond operator, etc.)
32f634b88 Optimize imports
000ca723d Remove pointless boolean expressions (business)
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
Key changes: 73b0be50b migrated from commons-collections 3 to commons-collections4 — affecting CollectionUtils usage. a72903e36 and 73a9755df both replaced StringBuffer with StringBuilder (non-thread-safe but faster, since Reader is single-threaded per session). a5bbdca6a switched to SLF4J logging.