mirror of https://github.com/apache/lucene.git
LUCENE-8261: InterpolatedProperties.interpolate and recursive property references.
This commit is contained in:
parent
f9942b525b
commit
445c0aa47e
|
@ -183,6 +183,9 @@ Bug Fixes
|
||||||
|
|
||||||
Other
|
Other
|
||||||
|
|
||||||
|
* LUCENE-8261: InterpolatedProperties.interpolate and recursive property
|
||||||
|
references. (Steve Rowe, Dawid Weiss)
|
||||||
|
|
||||||
* LUCENE-8228: removed obsolete IndexDeletionPolicy clone() requirements from
|
* LUCENE-8228: removed obsolete IndexDeletionPolicy clone() requirements from
|
||||||
the javadoc. (Dawid Weiss)
|
the javadoc. (Dawid Weiss)
|
||||||
|
|
||||||
|
|
|
@ -19,17 +19,28 @@ package org.apache.lucene.dependencies;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a properties file, performing non-recursive Ant-like
|
* Parse a properties file, performing non-recursive Ant-like
|
||||||
* property value interpolation, and return the resulting Properties.
|
* property value interpolation, and return the resulting Properties.
|
||||||
*/
|
*/
|
||||||
public class InterpolatedProperties extends Properties {
|
public class InterpolatedProperties extends Properties {
|
||||||
private static final Pattern PROPERTY_REFERENCE_PATTERN = Pattern.compile("\\$\\{([^}]+)\\}");
|
private static final Pattern PROPERTY_REFERENCE_PATTERN = Pattern.compile("\\$\\{(?<name>[^}]+)\\}");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the properties file via {@link Properties#load(InputStream)},
|
* Loads the properties file via {@link Properties#load(InputStream)},
|
||||||
|
@ -46,26 +57,110 @@ public class InterpolatedProperties extends Properties {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void load(Reader reader) throws IOException {
|
public void load(Reader reader) throws IOException {
|
||||||
super.load(reader);
|
Properties p = new Properties();
|
||||||
interpolate();
|
p.load(reader);
|
||||||
|
|
||||||
|
LinkedHashMap<String, String> props = new LinkedHashMap<>();
|
||||||
|
Enumeration<?> e = p.propertyNames();
|
||||||
|
while (e.hasMoreElements()) {
|
||||||
|
String key = (String) e.nextElement();
|
||||||
|
props.put(key, p.getProperty(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(props).forEach((k, v) -> this.setProperty(k, v));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static Map<String,String> resolve(Map<String,String> props) {
|
||||||
* Perform non-recursive Ant-like property value interpolation
|
LinkedHashMap<String, String> resolved = new LinkedHashMap<>();
|
||||||
*/
|
HashSet<String> recursive = new HashSet<>();
|
||||||
private void interpolate() {
|
props.forEach((k, v) -> {
|
||||||
StringBuffer buffer = new StringBuffer();
|
resolve(props, resolved, recursive, k, v);
|
||||||
for (Map.Entry<?,?> entry : entrySet()) {
|
});
|
||||||
buffer.setLength(0);
|
return resolved;
|
||||||
Matcher matcher = PROPERTY_REFERENCE_PATTERN.matcher(entry.getValue().toString());
|
}
|
||||||
|
|
||||||
|
private static String resolve(Map<String,String> props,
|
||||||
|
LinkedHashMap<String, String> resolved,
|
||||||
|
Set<String> recursive,
|
||||||
|
String key,
|
||||||
|
String value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new IllegalArgumentException("Missing replaced property key: " + key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recursive.contains(key)) {
|
||||||
|
throw new IllegalArgumentException("Circular recursive property resolution: " + recursive);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resolved.containsKey(key)) {
|
||||||
|
recursive.add(key);
|
||||||
|
StringBuffer buffer = new StringBuffer();
|
||||||
|
Matcher matcher = PROPERTY_REFERENCE_PATTERN.matcher(value);
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
String interpolatedValue = getProperty(matcher.group(1));
|
String referenced = matcher.group("name");
|
||||||
if (null != interpolatedValue) {
|
String concrete = resolve(props, resolved, recursive, referenced, props.get(referenced));
|
||||||
matcher.appendReplacement(buffer, interpolatedValue);
|
matcher.appendReplacement(buffer, Matcher.quoteReplacement(concrete));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
matcher.appendTail(buffer);
|
matcher.appendTail(buffer);
|
||||||
setProperty((String) entry.getKey(), buffer.toString());
|
resolved.put(key, buffer.toString());
|
||||||
|
recursive.remove(key);
|
||||||
|
}
|
||||||
|
assert resolved.get(key).equals(value);
|
||||||
|
return resolved.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String [] args) {
|
||||||
|
{
|
||||||
|
Map<String, String> props = new LinkedHashMap<>();
|
||||||
|
props.put("a", "${b}");
|
||||||
|
props.put("b", "${c}");
|
||||||
|
props.put("c", "foo");
|
||||||
|
props.put("d", "${a}/${b}/${c}");
|
||||||
|
assertEquals(resolve(props), "a=foo", "b=foo", "c=foo", "d=foo/foo/foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Map<String, String> props = new LinkedHashMap<>();
|
||||||
|
props.put("a", "foo");
|
||||||
|
props.put("b", "${a}");
|
||||||
|
assertEquals(resolve(props), "a=foo", "b=foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Map<String, String> props = new LinkedHashMap<>();
|
||||||
|
props.put("a", "${b}");
|
||||||
|
props.put("b", "${c}");
|
||||||
|
props.put("c", "${a}");
|
||||||
|
try {
|
||||||
|
resolve(props);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// Expected, circular reference.
|
||||||
|
if (!e.getMessage().contains("Circular recursive")) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Map<String, String> props = new LinkedHashMap<>();
|
||||||
|
props.put("a", "${b}");
|
||||||
|
try {
|
||||||
|
resolve(props);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// Expected, no referenced value.
|
||||||
|
if (!e.getMessage().contains("Missing replaced")) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertEquals(Map<String,String> resolved, String... keyValuePairs) {
|
||||||
|
List<String> result = resolved.entrySet().stream().sorted((a, b) -> a.getKey().compareTo(b.getKey()))
|
||||||
|
.map(e -> e.getKey() + "=" + e.getValue())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (!result.equals(Arrays.asList(keyValuePairs))) {
|
||||||
|
throw new AssertionError("Mismatch: \n" + result + "\nExpected: " + Arrays.asList(keyValuePairs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue