From 71497e96b126237bd931370a8288e93505c21f08 Mon Sep 17 00:00:00 2001 From: Javid Khan Date: Tue, 27 Oct 2026 06:28:01 +0530 Subject: [PATCH] apply path fences to the default lookups of a fenced interpolator interpolatorStringLookup() on a factory built with setFences returned the shared unfenced InterpolatorStringLookup.INSTANCE, and the other overloads took their defaults from StringLookupFactory.INSTANCE, so the file, properties and xml lookups of the resulting interpolator ignored the fences. Build the interpolator from this factory and have addDefaultStringLookups swap in the fenced lookups when fences are set. --- .../text/lookup/InterpolatorStringLookup.java | 15 +++++++++++++- .../text/lookup/StringLookupFactory.java | 14 +++++++++---- .../text/lookup/StringLookupFactoryTest.java | 20 +++++++++++++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/apache/commons/text/lookup/InterpolatorStringLookup.java b/src/main/java/org/apache/commons/text/lookup/InterpolatorStringLookup.java index 0b292315e3..c6925fe9d6 100644 --- a/src/main/java/org/apache/commons/text/lookup/InterpolatorStringLookup.java +++ b/src/main/java/org/apache/commons/text/lookup/InterpolatorStringLookup.java @@ -69,10 +69,23 @@ final class InterpolatorStringLookup extends AbstractStringLookup { */ InterpolatorStringLookup(final Map stringLookupMap, final StringLookup defaultStringLookup, final boolean addDefaultLookups) { + this(stringLookupMap, defaultStringLookup, addDefaultLookups, StringLookupFactory.INSTANCE); + } + + /** + * Constructs a fully customized instance. + * + * @param stringLookupMap The map of string lookups. + * @param defaultStringLookup The default string lookup. + * @param addDefaultLookups whether the default lookups should be used. + * @param factory The factory providing the default lookups, and their Path fences. + */ + InterpolatorStringLookup(final Map stringLookupMap, final StringLookup defaultStringLookup, + final boolean addDefaultLookups, final StringLookupFactory factory) { this.defaultStringLookup = defaultStringLookup; this.stringLookupMap = stringLookupMap.entrySet().stream().collect(Collectors.toMap(e -> StringLookupFactory.toKey(e.getKey()), Entry::getValue)); if (addDefaultLookups) { - StringLookupFactory.INSTANCE.addDefaultStringLookups(this.stringLookupMap); + factory.addDefaultStringLookups(this.stringLookupMap); } } diff --git a/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java b/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java index af41f0cc1c..1e2e02e98f 100644 --- a/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java +++ b/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java @@ -672,6 +672,12 @@ private StringLookupFactory(final Path[] fences) { public void addDefaultStringLookups(final Map stringLookupMap) { if (stringLookupMap != null) { stringLookupMap.putAll(DefaultStringLookupsHolder.INSTANCE.getDefaultStringLookups()); + if (fences != null) { + // The defaults are unfenced singletons, replace them with lookups guarded by our fences. + stringLookupMap.replace(toKey(KEY_FILE), DefaultStringLookup.FILE.getStringLookup(), fileStringLookup(fences)); + stringLookupMap.replace(toKey(KEY_PROPERTIES), DefaultStringLookup.PROPERTIES.getStringLookup(), propertiesStringLookup(fences)); + stringLookupMap.replace(toKey(KEY_XML), DefaultStringLookup.XML.getStringLookup(), xmlStringLookup(Collections.emptyMap(), fences)); + } } } @@ -1031,7 +1037,7 @@ public StringLookup functionStringLookup(final Function function) * @return The default {@link InterpolatorStringLookup}. */ public StringLookup interpolatorStringLookup() { - return InterpolatorStringLookup.INSTANCE; + return fences != null ? interpolatorStringLookup(Collections.emptyMap(), mapStringLookup(null), true) : InterpolatorStringLookup.INSTANCE; } /** @@ -1048,7 +1054,7 @@ public StringLookup interpolatorStringLookup() { */ public StringLookup interpolatorStringLookup(final Map stringLookupMap, final StringLookup defaultStringLookup, final boolean addDefaultLookups) { - return new InterpolatorStringLookup(stringLookupMap, defaultStringLookup, addDefaultLookups); + return new InterpolatorStringLookup(stringLookupMap, defaultStringLookup, addDefaultLookups, this); } /** @@ -1060,7 +1066,7 @@ public StringLookup interpolatorStringLookup(final Map str * @return A new InterpolatorStringLookup. */ public StringLookup interpolatorStringLookup(final Map map) { - return new InterpolatorStringLookup(map); + return interpolatorStringLookup(mapStringLookup(map)); } /** @@ -1071,7 +1077,7 @@ public StringLookup interpolatorStringLookup(final Map map) { * @return A new InterpolatorStringLookup. */ public StringLookup interpolatorStringLookup(final StringLookup defaultStringLookup) { - return new InterpolatorStringLookup(defaultStringLookup); + return interpolatorStringLookup(Collections.emptyMap(), defaultStringLookup, true); } /** diff --git a/src/test/java/org/apache/commons/text/lookup/StringLookupFactoryTest.java b/src/test/java/org/apache/commons/text/lookup/StringLookupFactoryTest.java index 67ab3cb804..b07fff665a 100644 --- a/src/test/java/org/apache/commons/text/lookup/StringLookupFactoryTest.java +++ b/src/test/java/org/apache/commons/text/lookup/StringLookupFactoryTest.java @@ -106,6 +106,26 @@ void testAddDefaultStringLookupsNull() { StringLookupFactory.INSTANCE.addDefaultStringLookups(null); } + @Test + void testFencedInterpolatorStringLookup() { + final StringLookupFactory stringLookupFactory = StringLookupFactory.builder().setFences(XmlStringLookupTest.FENCE_ROOT).get(); + final String fileKey = "file:UTF-8:" + XmlStringLookupTest.DOC_DIR + "document.properties"; + final String propertiesKey = "properties:" + XmlStringLookupTest.DOC_DIR + "document.properties::mykey"; + final String xmlKey = "xml:" + XmlStringLookupTest.DOC_DIR + "document.xml:/root/path/to/node"; + // Unfenced, the documents outside the fence resolve. + assertEquals("Hello World!", StringLookupFactory.INSTANCE.interpolatorStringLookup().apply(propertiesKey)); + assertEquals("Hello World!", StringLookupFactory.INSTANCE.interpolatorStringLookup().apply(xmlKey)); + // Fenced, the default lookups of every interpolator must reject them. + for (final StringLookup stringLookup : new StringLookup[] { stringLookupFactory.interpolatorStringLookup(), + stringLookupFactory.interpolatorStringLookup(Collections.emptyMap()), + stringLookupFactory.interpolatorStringLookup(StringLookupFactory.INSTANCE.nullStringLookup()), + stringLookupFactory.interpolatorStringLookup(Collections.emptyMap(), null, true) }) { + assertThrows(IllegalArgumentException.class, () -> stringLookup.apply(fileKey)); + assertThrows(IllegalArgumentException.class, () -> stringLookup.apply(propertiesKey)); + assertThrows(IllegalArgumentException.class, () -> stringLookup.apply(xmlKey)); + } + } + @Test void testClear() { // this will clear out the global cache in ConstantStringLookup