From 50eceab463d76a3dd52f175193a1517868a4d55d Mon Sep 17 00:00:00 2001 From: Clemens Portele Date: Mon, 29 Jun 2026 09:02:54 +0200 Subject: [PATCH] improve performance of property reordering during feature decoding The schema-order reordering pass sorted each object's children with a comparator that re-resolved a node's schema position on every comparison, and the coalesce step resolved it again two or three times per child, so the per-path lookup ran O(n log n) times for a node with n children. Cache the resolved position on the node - computed once per reordering pass, where the mapping is constant - so each node is resolved at most once. Output is unchanged. --- .../domain/transform/FeatureEventBuffer.java | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/xtraplatform-features/src/main/java/de/ii/xtraplatform/features/domain/transform/FeatureEventBuffer.java b/xtraplatform-features/src/main/java/de/ii/xtraplatform/features/domain/transform/FeatureEventBuffer.java index f28e0bcd7..3bbd877d7 100644 --- a/xtraplatform-features/src/main/java/de/ii/xtraplatform/features/domain/transform/FeatureEventBuffer.java +++ b/xtraplatform-features/src/main/java/de/ii/xtraplatform/features/domain/transform/FeatureEventBuffer.java @@ -413,21 +413,33 @@ && positionOf(previous, mapping) == positionOf(child, mapping)) { } private static int positionOf(Node node, SchemaMapping mapping) { + if (node.position != Node.POSITION_UNCOMPUTED) { + return node.position; + } List path = node.path(); + int position; if (path.isEmpty()) { - return Integer.MAX_VALUE; + position = Integer.MAX_VALUE; + } else { + List positions = mapping.getPositionsForTargetPath(path); + int p = positions.isEmpty() ? -1 : positions.get(0); + // keep paths without a known position at the end, in their original (stable) order + position = p < 0 ? Integer.MAX_VALUE : p; } - List positions = mapping.getPositionsForTargetPath(path); - int position = positions.isEmpty() ? -1 : positions.get(0); - // keep paths without a known position at the end, in their original (stable) order - return position < 0 ? Integer.MAX_VALUE : position; + node.position = position; + return position; } private static final class Node { + private static final int POSITION_UNCOMPUTED = Integer.MIN_VALUE; + private final FeatureTokenType type; private final List open; private final List children = new ArrayList<>(); private List close; + // schema position (positionOf), memoized for the duration of one ordering pass: the sort + // comparator and the coalesce pass would otherwise re-resolve the same path on every comparison + private int position = POSITION_UNCOMPUTED; private Node(FeatureTokenType type, List open) { this.type = type;