From 0664e1e2a591daf12be38ea600151e9ea9327f01 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Tue, 21 Apr 2026 19:46:09 +0200 Subject: [PATCH] improve performance of XmlComplexContentImpl.arraySetterHelper format --- .../impl/values/XmlComplexContentImpl.java | 88 ++++++++++++++----- 1 file changed, 67 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/apache/xmlbeans/impl/values/XmlComplexContentImpl.java b/src/main/java/org/apache/xmlbeans/impl/values/XmlComplexContentImpl.java index 2407c785e..6f5f95eca 100644 --- a/src/main/java/org/apache/xmlbeans/impl/values/XmlComplexContentImpl.java +++ b/src/main/java/org/apache/xmlbeans/impl/values/XmlComplexContentImpl.java @@ -22,6 +22,7 @@ import javax.xml.namespace.QName; import java.math.BigDecimal; import java.math.BigInteger; +import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; @@ -342,7 +343,14 @@ protected void arraySetterHelper(XmlObject[] sources, QName elemName, QNameSet s } } if (i < sources.length) { - TypeStoreUser current = (set == null) ? store.find_element_user(elemName, 0) : store.find_element_user(set, 0); + // Get all existing elements upfront to avoid O(n^2) from repeated find_element_user calls + List existingList = new ArrayList<>(m); + if (set == null) { + store.find_all_element_users(elemName, existingList); + } else { + store.find_all_element_users(set, existingList); + } + TypeStoreUser current = existingList.isEmpty() ? null : (TypeStoreUser) existingList.get(0); if (current == sources[i]) { // The new object matches what already exists in the array // Heuristic: we optimize for the case where the new elements @@ -351,16 +359,26 @@ protected void arraySetterHelper(XmlObject[] sources, QName elemName, QNameSet s // First insert the new element in the array at position 0 int j; + int originalI = i; // capture for index arithmetic in the inner loop for (j = 0; j < i; j++) { TypeStoreUser user = (set == null) ? store.insert_element_user(elemName, j) : store.insert_element_user(set, elemName, j); ((XmlObjectBase) user).set(sources[j]); } + // Track inserts made inside this loop so we can compute the correct + // index into existingList without calling find_element_user each time. + int insertCount = 0; for (i++, j++; i < sources.length; i++, j++) { // Cursor is implicitly closed XmlCursor c = sources[i].isImmutable() ? null : sources[i].newCursor(); if (c != null && c.toParent() && c.getObject() == this) { c.close(); - current = (set == null) ? store.find_element_user(elemName, j) : store.find_element_user(set, j); + // Each of the originalI pre-inserts shifted existing elements right by + // originalI positions; each subsequent insert in this loop shifts them + // one more. So j (the current store position) maps to + // existingList[j - originalI - insertCount], where originalI accounts + // for pre-loop inserts and insertCount for in-loop inserts so far. + int existingIdx = j - originalI - insertCount; + current = (existingIdx < existingList.size()) ? (TypeStoreUser) existingList.get(existingIdx) : null; if (current != sources[i]) { // Fall back to the general case break; @@ -372,11 +390,12 @@ protected void arraySetterHelper(XmlObject[] sources, QName elemName, QNameSet s // Insert before the current element TypeStoreUser user = (set == null) ? store.insert_element_user(elemName, j) : store.insert_element_user(set, elemName, j); ((XmlObjectBase) user).set(sources[i]); + insertCount++; } } startDest = j; startSrc = i; - m = store.count_elements(elemName); + m = (set == null) ? store.count_elements(elemName) : store.count_elements(set); } // Fall through } else { @@ -407,19 +426,28 @@ protected void arraySetterHelper(XmlObject[] sources, QName elemName, QNameSet s } } + // Get existing elements upfront to avoid O(n^2) from repeated find_element_user calls. + // The list is built lazily since it may not be needed when all remaining elements are new. + List finalList = null; int j; for (i = startSrc, j = startDest; i < n; i++, j++) { - TypeStoreUser user; + XmlObjectBase user; if (j >= m) { - user = store.add_element_user(elemName); - } else if (set == null) { - user = store.find_element_user(elemName, j); + user = (XmlObjectBase) store.add_element_user(elemName); } else { - user = store.find_element_user(set, j); + if (finalList == null) { + finalList = new ArrayList<>(m); + if (set == null) { + store.find_all_element_users(elemName, finalList); + } else { + store.find_all_element_users(set, finalList); + } + } + user = (XmlObjectBase) finalList.get(j); } - ((XmlObjectBase) user).set(sources[i]); + user.set(sources[i]); } // We can't just delegate to array_setter because we need @@ -446,17 +474,26 @@ private void commonSetterHelper(QName elemName, QNameSet set, int n, BiConsumer< } } + // Get existing elements upfront to avoid O(n^2) from repeated find_element_user calls + List existing = new ArrayList<>(m); + if (m > 0) { + if (set == null) { + store.find_all_element_users(elemName, existing); + } else { + store.find_all_element_users(set, existing); + } + } + for (int i = 0; i < n; i++) { - TypeStoreUser user; + XmlObjectBase user; if (i >= m) { - user = store.add_element_user(elemName); - } else if (set == null) { - user = store.find_element_user(elemName, i); + user = (XmlObjectBase) store.add_element_user(elemName); } else { - user = store.find_element_user(set, i); + user = (XmlObjectBase) existing.get(i); } - fun.accept((XmlObjectBase) user, i); + + fun.accept(user, i); } } @@ -475,17 +512,26 @@ private void commonSetterHelper2(QName elemName, QNameSet set, T[] sources, } } + // Get existing elements upfront to avoid O(n^2) from repeated find_element_user calls + List existing = new ArrayList<>(m); + if (m > 0) { + if (set == null) { + store.find_all_element_users(elemName, existing); + } else { + store.find_all_element_users(set, existing); + } + } + for (int i = 0; i < n; i++) { - TypeStoreUser user; + XmlObjectBase user; if (i >= m) { - user = store.add_element_user(elemName); - } else if (set == null) { - user = store.find_element_user(elemName, i); + user = (XmlObjectBase) store.add_element_user(elemName); } else { - user = store.find_element_user(set, i); + user = (XmlObjectBase) existing.get(i); } - c.accept((XmlObjectBase) user, sources[i]); + + c.accept(user, sources[i]); } } }