From e021f09f9fc12eedfb0a91ac516105ac4579e227 Mon Sep 17 00:00:00 2001 From: Gunther Rademacher Date: Tue, 1 Sep 2026 11:21:24 +0200 Subject: [PATCH 1/2] add LRU cache for generated parsers --- .../basex/query/func/fn/FnInvisibleXml.java | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/basex-core/src/main/java/org/basex/query/func/fn/FnInvisibleXml.java b/basex-core/src/main/java/org/basex/query/func/fn/FnInvisibleXml.java index 06f0e79a00..af3b0476d4 100644 --- a/basex-core/src/main/java/org/basex/query/func/fn/FnInvisibleXml.java +++ b/basex-core/src/main/java/org/basex/query/func/fn/FnInvisibleXml.java @@ -3,6 +3,7 @@ import static org.basex.query.QueryError.*; import java.io.*; +import java.util.*; import org.basex.build.*; import org.basex.core.*; @@ -74,6 +75,17 @@ private final class Generator { private static final Option[] NO_OPTS = new Blitz.Option[0]; /** Markup Blitz options for fail-on-error. */ private static final Option[] FAIL_OPTS = new Blitz.Option[] { Blitz.Option.FAIL_ON_ERROR }; + /** Maximum number of entries. */ + private static final int MAX = 15; + /** Generated parser cache. */ + private static final LinkedHashMap PARSERS = + new LinkedHashMap<>(MAX + 1, 1.0f, true) { + @Override + protected boolean removeEldestEntry( + final Map.Entry eldest) { + return size() > MAX; + } + }; /** * Generate a parser from an invisible XML grammar. @@ -97,7 +109,7 @@ public FuncItem generate(final QueryContext qc) throws QueryException { final de.bottlecaps.markup.blitz.Parser parser; final Blitz.Option[] opts = options.get(IxmlOptions.FAIL_ON_ERROR) ? FAIL_OPTS : NO_OPTS; try { - parser = xml ? Blitz.generateFromXml(grmmr, opts) : Blitz.generate(grmmr, opts); + parser = parser(grmmr, xml, opts); } catch(final BlitzParseException ex) { throw IXML_GRM_X_X_X.get(info, ex.getOffendingToken(), ex.getLine(), ex.getColumn()); } catch(final BlitzException ex) { @@ -110,6 +122,34 @@ public FuncItem generate(final QueryContext qc) throws QueryException { final FuncType ft = FuncType.get(parseFunction.seqType(), Types.STRING_O); return new FuncItem(info, parseFunction, params, AnnList.EMPTY, ft, params.length, null); } + + /** + * Returns a cached parser. + * @param grammar grammar string + * @param xml XML grammar flag + * @param opts generation options + * @return parser + */ + private de.bottlecaps.markup.blitz.Parser parser(final String grammar, final boolean xml, + final Blitz.Option[] opts) { + final String key = grammar + "\0" + xml + "\0" + Arrays.toString(opts); + de.bottlecaps.markup.blitz.Parser parser; + synchronized(PARSERS) { + parser = PARSERS.get(key); + } + if(parser == null) { + parser = xml ? Blitz.generateFromXml(grammar, opts) : Blitz.generate(grammar, opts); + synchronized(PARSERS) { + final de.bottlecaps.markup.blitz.Parser cached = PARSERS.get(key); + if(cached != null) { + parser = cached; + } else { + PARSERS.put(key, parser); + } + } + } + return parser; + } } /** From b2d4eba30ac3deb957fc2a0c44470ff0687fc9c9 Mon Sep 17 00:00:00 2001 From: Gunther Rademacher Date: Tue, 1 Sep 2026 11:43:36 +0200 Subject: [PATCH 2/2] minor changes --- .../main/java/org/basex/query/func/fn/FnInvisibleXml.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/basex-core/src/main/java/org/basex/query/func/fn/FnInvisibleXml.java b/basex-core/src/main/java/org/basex/query/func/fn/FnInvisibleXml.java index af3b0476d4..04fc7940dc 100644 --- a/basex-core/src/main/java/org/basex/query/func/fn/FnInvisibleXml.java +++ b/basex-core/src/main/java/org/basex/query/func/fn/FnInvisibleXml.java @@ -75,15 +75,15 @@ private final class Generator { private static final Option[] NO_OPTS = new Blitz.Option[0]; /** Markup Blitz options for fail-on-error. */ private static final Option[] FAIL_OPTS = new Blitz.Option[] { Blitz.Option.FAIL_ON_ERROR }; - /** Maximum number of entries. */ - private static final int MAX = 15; + /** Maximum number of parser cache entries. */ + private static final int CACHE_SIZE = 15; /** Generated parser cache. */ private static final LinkedHashMap PARSERS = - new LinkedHashMap<>(MAX + 1, 1.0f, true) { + new LinkedHashMap<>(CACHE_SIZE + 1, 1.0f, true) { @Override protected boolean removeEldestEntry( final Map.Entry eldest) { - return size() > MAX; + return size() > CACHE_SIZE; } };