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