This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-text.git
The following commit(s) were added to refs/heads/master by this push: new 797b4c42 Add StringLookupFactory.propertiesStringLookup(Path...) and deprecated propertiesStringLookup() 797b4c42 is described below commit 797b4c42d4631a143bbcb63a3701a3613866c833 Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Fri Apr 5 12:00:30 2024 -0400 Add StringLookupFactory.propertiesStringLookup(Path...) and deprecated propertiesStringLookup() --- src/changes/changes.xml | 1 + .../text/lookup/AbstractPathFencedLookup.java | 66 ++++++++++++++++++ .../commons/text/lookup/FileStringLookup.java | 28 +------- .../text/lookup/PropertiesStringLookup.java | 16 +++-- .../commons/text/lookup/StringLookupFactory.java | 53 +++++++++++++-- .../text/lookup/PropertiesStringLookupTest.java | 78 +++++++++++++++------- 6 files changed, 179 insertions(+), 63 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 9fb6ce03..ce772d7f 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -48,6 +48,7 @@ The <action> type attribute can be add,update,fix,remove. <release version="1.12.0" date="YYYY-MM-DD" description="Release 1.12.0. Requires Java 8 or above."> <!-- ADD --> <action type="add" dev="ggregory" due-to="Gary Gregory">Add StringLookupFactory.fileStringLookup(Path...) and deprecated fileStringLookup().</action> + <action type="add" dev="ggregory" due-to="Gary Gregory">Add StringLookupFactory.propertiesStringLookup(Path...) and deprecated propertiesStringLookup().</action> <!-- FIX --> <action issue="TEXT-232" type="fix" dev="ggregory" due-to="Arnout Engelen, Gary Gregory">WordUtils.containsAllWords​() may throw PatternSyntaxException.</action> <action issue="TEXT-175" type="fix" dev="ggregory" due-to="David Lavati, seanfabs, Gary Gregory, Bruno P. Kinoshita">Fix regression for determining whitespace in WordUtils #519.</action> diff --git a/src/main/java/org/apache/commons/text/lookup/AbstractPathFencedLookup.java b/src/main/java/org/apache/commons/text/lookup/AbstractPathFencedLookup.java new file mode 100644 index 00000000..e8bce89e --- /dev/null +++ b/src/main/java/org/apache/commons/text/lookup/AbstractPathFencedLookup.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.commons.text.lookup; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Abstracts guarding Path lookups with fences. + */ +abstract class AbstractPathFencedLookup extends AbstractStringLookup { + + /** + * Fences guarding Path resolution. + */ + protected final List<Path> fences; + + /** + * Constructs a new instance. + * + * @param fences The fences guarding Path resolution. + */ + AbstractPathFencedLookup(final Path... fences) { + this.fences = fences != null ? Arrays.asList(fences).stream().map(Path::toAbsolutePath).collect(Collectors.toList()) : Collections.emptyList(); + } + + /** + * Gets a Path for the given file name checking that it resolves within our fences. + * + * @param fileName the file name to resolve. + * @return a fenced Path. + * @throws IllegalArgumentException if the file name is not without our fences. + */ + protected Path getPath(final String fileName) { + final Path path = Paths.get(fileName); + if (fences.isEmpty()) { + return path; + } + final Path pathAbs = path.normalize().toAbsolutePath(); + final Optional<Path> first = fences.stream().filter(pathAbs::startsWith).findFirst(); + if (first.isPresent()) { + return path; + } + throw new IllegalArgumentException(String.format("[%s] -> [%s] not in %s", fileName, pathAbs, fences)); + } + +} diff --git a/src/main/java/org/apache/commons/text/lookup/FileStringLookup.java b/src/main/java/org/apache/commons/text/lookup/FileStringLookup.java index 68594472..26933b77 100644 --- a/src/main/java/org/apache/commons/text/lookup/FileStringLookup.java +++ b/src/main/java/org/apache/commons/text/lookup/FileStringLookup.java @@ -19,12 +19,6 @@ package org.apache.commons.text.lookup; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.commons.text.StringSubstitutor; @@ -54,38 +48,20 @@ import org.apache.commons.text.StringSubstitutor; * * @since 1.5 */ -final class FileStringLookup extends AbstractStringLookup { +final class FileStringLookup extends AbstractPathFencedLookup { /** * Defines the singleton for this class. */ static final AbstractStringLookup INSTANCE = new FileStringLookup((Path[]) null); - /** - * Fences guarding Path resolution. - */ - private final List<Path> fences; - /** * Constructs a new instance. * * @param fences The fences guarding Path resolution. */ FileStringLookup(final Path... fences) { - this.fences = fences != null ? Arrays.asList(fences).stream().map(Path::toAbsolutePath).collect(Collectors.toList()) : Collections.emptyList(); - } - - private Path getPath(final String fileName) { - final Path path = Paths.get(fileName); - if (fences.isEmpty()) { - return path; - } - final Path pathAbs = path.normalize().toAbsolutePath(); - final Optional<Path> first = fences.stream().filter(pathAbs::startsWith).findFirst(); - if (first.isPresent()) { - return path; - } - throw new IllegalArgumentException(String.format("[%s] -> [%s] not in %s", fileName, pathAbs, fences)); + super(fences); } /** diff --git a/src/main/java/org/apache/commons/text/lookup/PropertiesStringLookup.java b/src/main/java/org/apache/commons/text/lookup/PropertiesStringLookup.java index 7e155dbe..19346f5e 100644 --- a/src/main/java/org/apache/commons/text/lookup/PropertiesStringLookup.java +++ b/src/main/java/org/apache/commons/text/lookup/PropertiesStringLookup.java @@ -19,7 +19,7 @@ package org.apache.commons.text.lookup; import java.io.InputStream; import java.nio.file.Files; -import java.nio.file.Paths; +import java.nio.file.Path; import java.util.Properties; import org.apache.commons.lang3.StringUtils; @@ -39,12 +39,12 @@ import org.apache.commons.lang3.StringUtils; * @see Properties * @since 1.5 */ -final class PropertiesStringLookup extends AbstractStringLookup { +final class PropertiesStringLookup extends AbstractPathFencedLookup { /** * Defines the singleton for this class. */ - static final PropertiesStringLookup INSTANCE = new PropertiesStringLookup(); + static final PropertiesStringLookup INSTANCE = new PropertiesStringLookup((Path[]) null); /** Separates file and key. */ static final String SEPARATOR = "::"; @@ -57,10 +57,12 @@ final class PropertiesStringLookup extends AbstractStringLookup { } /** - * No need to build instances for now. + * Constructs a new instance. + * + * @param fences The fences guarding Path resolution. */ - private PropertiesStringLookup() { - // empty + PropertiesStringLookup(final Path... fences) { + super(fences); } /** @@ -90,7 +92,7 @@ final class PropertiesStringLookup extends AbstractStringLookup { final String propertyKey = StringUtils.substringAfter(key, SEPARATOR); try { final Properties properties = new Properties(); - try (InputStream inputStream = Files.newInputStream(Paths.get(documentPath))) { + try (InputStream inputStream = Files.newInputStream(getPath(documentPath))) { properties.load(inputStream); } return properties.getProperty(propertyKey); 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 8f2379d3..51096ea1 100644 --- a/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java +++ b/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java @@ -237,7 +237,6 @@ public final class StringLookupFactory { */ private static void addLookup(final DefaultStringLookup lookup, final Map<String, StringLookup> map) { map.put(toKey(lookup.getKey()), lookup.getStringLookup()); - if (DefaultStringLookup.BASE64_DECODER.equals(lookup)) { // "base64" is deprecated in favor of KEY_BASE64_DECODER. map.put(toKey("base64"), lookup.getStringLookup()); @@ -280,7 +279,6 @@ public final class StringLookupFactory { */ private static Map<String, StringLookup> parseStringLookups(final String str) { final Map<String, StringLookup> lookupMap = new HashMap<>(); - try { for (final String lookupName : str.split("[\\s,]+")) { if (!lookupName.isEmpty()) { @@ -290,7 +288,6 @@ public final class StringLookupFactory { } catch (final IllegalArgumentException exc) { throw new IllegalArgumentException("Invalid default string lookups definition: " + str, exc); } - return lookupMap; } @@ -306,7 +303,6 @@ public final class StringLookupFactory { props.containsKey(StringLookupFactory.DEFAULT_STRING_LOOKUPS_PROPERTY) ? parseStringLookups(props.getProperty(StringLookupFactory.DEFAULT_STRING_LOOKUPS_PROPERTY)) : createDefaultStringLookups(); - defaultStringLookups = Collections.unmodifiableMap(lookups); } @@ -868,7 +864,7 @@ public final class StringLookupFactory { * StringLookupFactory.INSTANCE.fileStringLookup(Paths.get("")).lookup("UTF-8:com/domain/document.properties"); * </pre> * <p> - * Using a {@link StringSubstitutor}: + * Using a {@link StringSubstitutor} fenced by the current directory ({@code Paths.get("")}): * </p> * * <pre> @@ -1102,11 +1098,58 @@ public final class StringLookupFactory { * * @return The PropertiesStringLookup singleton instance. * @since 1.5 + * @deprecated Use {@link #propertiesStringLookup(Path...)}. */ + @Deprecated public StringLookup propertiesStringLookup() { return PropertiesStringLookup.INSTANCE; } + /** + * Returns a fenced PropertiesStringLookup instance. + * <p> + * Looks up the value for the key in the format "DocumentPath::MyKey":. + * </p> + * <p> + * Note the use of "::" instead of ":" to allow for "C:" drive letters in paths. + * </p> + * <p> + * For example: "com/domain/document.properties::MyKey". + * </p> + * + * <p> + * Using a {@link StringLookup} from the {@link StringLookupFactory} fenced by the current directory ({@code Paths.get("")}): + * </p> + * + * <pre> + * StringLookupFactory.INSTANCE.propertiesStringLookup(Paths.get("")).lookup("com/domain/document.properties::MyKey"); + * </pre> + * <p> + * Using a {@link StringSubstitutor} fenced by the current directory ({@code Paths.get("")}): + * </p> + * + * <pre> + * StringSubstitutor stringSubstitutor = StringSubstitutor.createInterpolator(); + * final InterpolatorStringLookup stringLookup = (InterpolatorStringLookup) stringSubstitutor.getStringLookup(); + * stringLookup.getStringLookupMap().replace(StringLookupFactory.KEY_PROPERTIES, StringLookupFactory.INSTANCE.fileStringLookup(Paths.get(""))); + * stringSubstitutor.replace("... ${properties:com/domain/document.properties::MyKey} ...")); + * </pre> + * <p> + * The above examples convert {@code "com/domain/document.properties::MyKey"} to the key value in the properties + * file at the path "com/domain/document.properties". + * </p> + * <p> + * Methods {@link StringSubstitutor#replace(String)} will throw a {@link IllegalArgumentException} when a file doesn't resolves in a fence. + * </p> + * + * @param fences The fences guarding Path resolution. + * @return The PropertiesStringLookup singleton instance. + * @since 1.12.0 + */ + public StringLookup propertiesStringLookup(final Path... fences) { + return new PropertiesStringLookup(fences); + } + /** * Returns the ResourceBundleStringLookup singleton instance. * <p> diff --git a/src/test/java/org/apache/commons/text/lookup/PropertiesStringLookupTest.java b/src/test/java/org/apache/commons/text/lookup/PropertiesStringLookupTest.java index 7f7655e0..26aacf3f 100644 --- a/src/test/java/org/apache/commons/text/lookup/PropertiesStringLookupTest.java +++ b/src/test/java/org/apache/commons/text/lookup/PropertiesStringLookupTest.java @@ -20,6 +20,7 @@ package org.apache.commons.text.lookup; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; @@ -33,34 +34,46 @@ import org.junit.jupiter.api.Test; */ public class PropertiesStringLookupTest { - private static final String DOC_PATH = "src/test/resources/org/apache/commons/text/document.properties"; + private static final Path[] NULL_PATH_ARRAY = null; + private static final Path CURRENT_PATH = Paths.get(""); // NOT "."! + private static final String DOC_RELATIVE = "src/test/resources/org/apache/commons/text/document.properties"; + private static final String DOC_ROOT = "/foo.txt"; private static final String KEY = "mykey"; - private static final String KEY_PATH = PropertiesStringLookup.toPropertyKey(DOC_PATH, KEY); + private static final String KEY_RELATIVE = PropertiesStringLookup.toPropertyKey(DOC_RELATIVE, KEY); + private static final String KEY_ROOT = PropertiesStringLookup.toPropertyKey(DOC_ROOT, KEY); @Test public void testInterpolator() { final StringSubstitutor stringSubstitutor = StringSubstitutor.createInterpolator(); - assertEquals("Hello World!", stringSubstitutor.replace("${properties:" + KEY_PATH + "}")); + assertEquals("Hello World!", stringSubstitutor.replace("${properties:" + KEY_RELATIVE + "}")); } @Test public void testInterpolatorReplaceFile() { final StringSubstitutor stringSubstitutor = StringSubstitutor.createInterpolator(); - assertEquals("Hello World!", stringSubstitutor.replace("${properties:" + KEY_PATH + "}")); + assertEquals("Hello World!", stringSubstitutor.replace("${properties:" + KEY_RELATIVE + "}")); final InterpolatorStringLookup stringLookup = (InterpolatorStringLookup) stringSubstitutor.getStringLookup(); - stringLookup.getStringLookupMap().replace(StringLookupFactory.KEY_FILE, StringLookupFactory.INSTANCE.fileStringLookup(Paths.get(""))); - assertEquals("Hello World!", stringSubstitutor.replace("${properties:" + KEY_PATH + "}")); + stringLookup.getStringLookupMap().replace(StringLookupFactory.KEY_FILE, StringLookupFactory.INSTANCE.fileStringLookup(CURRENT_PATH)); + assertEquals("Hello World!", stringSubstitutor.replace("${properties:" + KEY_RELATIVE + "}")); assertThrows(IllegalArgumentException.class, () -> stringSubstitutor.replace("${file:UTF-8:/foo.txt}")); } + @Test + public void testInterpolatorReplaceProperties() { + final StringSubstitutor stringSubstitutor = StringSubstitutor.createInterpolator(); + assertEquals("Hello World!", stringSubstitutor.replace("${properties:" + KEY_RELATIVE + "}")); + final InterpolatorStringLookup stringLookup = (InterpolatorStringLookup) stringSubstitutor.getStringLookup(); + stringLookup.getStringLookupMap().replace(StringLookupFactory.KEY_PROPERTIES, StringLookupFactory.INSTANCE.propertiesStringLookup(CURRENT_PATH)); + assertEquals("Hello World!", stringSubstitutor.replace("${properties:" + KEY_RELATIVE + "}")); + assertThrows(IllegalArgumentException.class, () -> stringSubstitutor.replace("${properties:UTF-8:/foo.txt}")); + } + @Test public void testInterpolatorNestedColon() { final StringSubstitutor stringSubstitutor = StringSubstitutor.createInterpolator(); // Need to handle "C:" in the sys prop user.dir. - final String replaced = stringSubstitutor.replace("$${properties:${sys:user.dir}/" + KEY_PATH + "}"); - assertEquals( - "${properties:" + System.getProperty("user.dir") + "/src/test/resources/org/apache/commons/text/document.properties::mykey}", - replaced); + final String replaced = stringSubstitutor.replace("$${properties:${sys:user.dir}/" + KEY_RELATIVE + "}"); + assertEquals("${properties:" + System.getProperty("user.dir") + "/src/test/resources/org/apache/commons/text/document.properties::mykey}", replaced); assertEquals("Hello World!", stringSubstitutor.replace(replaced)); } @@ -68,11 +81,9 @@ public class PropertiesStringLookupTest { public void testInterpolatorWithParameterizedKey() { final Map<String, String> map = new HashMap<>(); map.put("KeyIsHere", KEY); - final StringSubstitutor stringSubstitutor = new StringSubstitutor( - StringLookupFactory.INSTANCE.interpolatorStringLookup(map)); - final String replaced = stringSubstitutor - .replace("$${properties:" + PropertiesStringLookup.toPropertyKey(DOC_PATH, "${KeyIsHere}}")); - assertEquals("${properties:" + PropertiesStringLookup.toPropertyKey(DOC_PATH, "mykey}"), replaced); + final StringSubstitutor stringSubstitutor = new StringSubstitutor(StringLookupFactory.INSTANCE.interpolatorStringLookup(map)); + final String replaced = stringSubstitutor.replace("$${properties:" + PropertiesStringLookup.toPropertyKey(DOC_RELATIVE, "${KeyIsHere}}")); + assertEquals("${properties:" + PropertiesStringLookup.toPropertyKey(DOC_RELATIVE, "mykey}"), replaced); assertEquals("Hello World!", stringSubstitutor.replace(replaced)); } @@ -80,12 +91,10 @@ public class PropertiesStringLookupTest { public void testInterpolatorWithParameterizedKey2() { final Map<String, String> map = new HashMap<>(); map.put("KeyIsHere", KEY); - final StringSubstitutor stringSubstitutor = new StringSubstitutor( - StringLookupFactory.INSTANCE.interpolatorStringLookup(map)); - final String replaced = stringSubstitutor.replace( - "$${properties:${sys:user.dir}/" + PropertiesStringLookup.toPropertyKey(DOC_PATH, "${KeyIsHere}}")); - assertEquals("${properties:" + System.getProperty("user.dir") + "/" - + PropertiesStringLookup.toPropertyKey(DOC_PATH, "mykey}"), replaced); + final StringSubstitutor stringSubstitutor = new StringSubstitutor(StringLookupFactory.INSTANCE.interpolatorStringLookup(map)); + final String replaced = stringSubstitutor + .replace("$${properties:${sys:user.dir}/" + PropertiesStringLookup.toPropertyKey(DOC_RELATIVE, "${KeyIsHere}}")); + assertEquals("${properties:" + System.getProperty("user.dir") + "/" + PropertiesStringLookup.toPropertyKey(DOC_RELATIVE, "mykey}"), replaced); assertEquals("Hello World!", stringSubstitutor.replace(replaced)); } @@ -96,29 +105,48 @@ public class PropertiesStringLookupTest { @Test public void testMissingFileWithKey() { - assertThrows(IllegalArgumentException.class, () -> PropertiesStringLookup.INSTANCE - .lookup(PropertiesStringLookup.toPropertyKey("MissingFile", "AnyKey"))); + assertThrows(IllegalArgumentException.class, + () -> PropertiesStringLookup.INSTANCE.lookup(PropertiesStringLookup.toPropertyKey("MissingFile", "AnyKey"))); } @Test public void testMissingKey() { - assertThrows(IllegalArgumentException.class, () -> PropertiesStringLookup.INSTANCE.lookup(DOC_PATH)); + assertThrows(IllegalArgumentException.class, () -> PropertiesStringLookup.INSTANCE.lookup(DOC_RELATIVE)); + assertThrows(IllegalArgumentException.class, () -> new PropertiesStringLookup().lookup(DOC_RELATIVE)); + assertThrows(IllegalArgumentException.class, () -> new PropertiesStringLookup(NULL_PATH_ARRAY).lookup(DOC_RELATIVE)); + assertThrows(IllegalArgumentException.class, () -> new PropertiesStringLookup(CURRENT_PATH).lookup(DOC_RELATIVE)); } @Test public void testNull() { Assertions.assertNull(PropertiesStringLookup.INSTANCE.lookup(null)); + Assertions.assertNull(new PropertiesStringLookup().lookup(null)); + Assertions.assertNull(new PropertiesStringLookup(NULL_PATH_ARRAY).lookup(null)); + Assertions.assertNull(new PropertiesStringLookup(CURRENT_PATH).lookup(null)); } @Test public void testOne() { - assertEquals("Hello World!", PropertiesStringLookup.INSTANCE.lookup(KEY_PATH)); + assertEquals("Hello World!", PropertiesStringLookup.INSTANCE.lookup(KEY_RELATIVE)); + assertEquals("Hello World!", new PropertiesStringLookup().lookup(KEY_RELATIVE)); + assertEquals("Hello World!", new PropertiesStringLookup(NULL_PATH_ARRAY).lookup(KEY_RELATIVE)); + assertEquals("Hello World!", new PropertiesStringLookup(CURRENT_PATH).lookup(KEY_RELATIVE)); + assertThrows(IllegalArgumentException.class, () -> new PropertiesStringLookup(CURRENT_PATH).lookup(KEY_ROOT)); + } + + @Test + public void testFenceOne() { + assertThrows(IllegalArgumentException.class, () -> new PropertiesStringLookup(CURRENT_PATH).lookup(KEY_ROOT)); + assertThrows(IllegalArgumentException.class, () -> new PropertiesStringLookup(Paths.get("not a dir at all"), CURRENT_PATH).lookup(KEY_ROOT)); } @Test public void testToString() { // does not blow up and gives some kind of string. Assertions.assertFalse(PropertiesStringLookup.INSTANCE.toString().isEmpty()); + Assertions.assertFalse(new PropertiesStringLookup().toString().isEmpty()); + Assertions.assertFalse(new PropertiesStringLookup(NULL_PATH_ARRAY).toString().isEmpty()); + Assertions.assertFalse(new PropertiesStringLookup(CURRENT_PATH).toString().isEmpty()); } }