From 5cdd2487b9554301300903056d441306fea064a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 11:07:26 +0000 Subject: [PATCH 1/2] Initial plan From bbd74f8e58f9ed2d39e4e06e4ea0b026303ef190 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 11:10:42 +0000 Subject: [PATCH 2/2] Use tailMap for O(matches) wildcard key iteration instead of O(totalKeys) Co-authored-by: NassimBtk <14110109+NassimBtk@users.noreply.github.com> --- src/main/java/org/metricshub/jflat/JFlat.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/metricshub/jflat/JFlat.java b/src/main/java/org/metricshub/jflat/JFlat.java index d5a6a93..1a8c283 100644 --- a/src/main/java/org/metricshub/jflat/JFlat.java +++ b/src/main/java/org/metricshub/jflat/JFlat.java @@ -406,16 +406,19 @@ public StringBuilder toCSV(String csvEntryKey, String[] csvProperties, String se for (String existingEntry : entries) { if (isWildcard) { - // Wildcard expansion: scan the flat map for all direct children of existingEntry. - // We iterate over every key in the map and find those that start with the - // current entry's prefix (e.g. "/members/"). From each matching key, we - // extract the immediate child name by looking for the next "/" or "[" - // delimiter, which marks a deeper level or an array index. + // We iterate over keys in the map that are at or after the current entry's + // prefix (e.g. "/members/") and stop once keys no longer match that prefix. + // From each matching key, we extract the immediate child name by looking for + // the next "/" or "[" delimiter, which marks a deeper level or an array index. // Duplicates are skipped (case-insensitive) to ensure each child appears once. String prefix = existingEntry.equals("/") ? "/" : existingEntry + "/"; - for (String key : map.keySet()) { + for (String key : map.tailMap(prefix, true).keySet()) { + // Stop once keys are no longer under the prefix (taking case-insensitive match into account) + if (!key.regionMatches(true, 0, prefix, 0, prefix.length())) { + break; + } // Only consider keys that are strictly under the prefix - if (key.length() > prefix.length() && key.regionMatches(true, 0, prefix, 0, prefix.length())) { + if (key.length() > prefix.length()) { // Extract the portion after the prefix, e.g. "abc123/name" from "/members/abc123/name" String remainder = key.substring(prefix.length());