Can Java properties file reference other properties file?
## define a default directory for Input files
dir.default=/home/data/in/
dir.proj1=${dir.default}p1
dir.proj2=${dir.default}p2
dir.proj3=${dir.default}p3
Is this possible?
Can Java properties file reference other properties file?
## define a default directory for Input files
dir.default=/home/data/in/
dir.proj1=${dir.default}p1
dir.proj2=${dir.default}p2
dir.proj3=${dir.default}p3
Is this possible?
Standard properties files are just key-value pairs. In the text format, Properties just separates key from value and does some simple things such as allowing escaped characters. You might be able to define entities in the verbose XML syntax.
If you want your own substitution syntax, then you can manipulate a returned value as you would with any other string. Alternatively, you could write your own version of Properties or do the substitution when generating the file.
The java.util.Properties class won't do this for you. It wouldn't be too difficult to subclass Properties, override the load() method and do the substitution yourself.
A 'pure' Java implementation:
static final Pattern PATTERN = Pattern.compile("\\$\\{([^}]+)}");
private static void macro(final Properties properties)
{
properties.replaceAll((k, v) -> PATTERN.matcher((String) v).replaceAll(mr -> properties.getProperty(mr.group(1), mr.group(0)).replace("$", "\\$")));
}
Which can be incorporated into a trivial subclass of Properties, like this:
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;
import java.util.regex.Pattern;
public class MacroProperties extends Properties
{
static final Pattern PATTERN = Pattern.compile("\\$\\{([^}]+)}", 0);
@Override
public synchronized void load(final Reader reader) throws IOException
{
super.load(reader);
macro(this);
}
@Override
public synchronized void load(final InputStream inStream) throws IOException
{
super.load(inStream);
macro(this);
}
private static void macro(final Properties properties)
{
properties.replaceAll((k, v) -> PATTERN.matcher((String) v).replaceAll(mr -> properties.getProperty(mr.group(1), mr.group(0)).replace("$", "\\$")));
}
}
How does it work?
PATTERN is a regexp that matches simple ${foo} patterns, and captures the text between the braces as a group.
Properties.replaceAll applies a function to replace each value with the result of the function.
Matcher.replaceAll applies a function to replace each match of PATTERN.
Our implementation of this function looks up match group 1 in the properties or defaults to the match (i.e. does not actually do a replacement).
Matcher.replaceAll also interprets the replacement string looking for group references so we also need to use String.replace to backslash escape $.
None of the given solutions I really liked. EProperties is not maintained, and it is not available in Maven Central. Commons Config is too big for this. StrSubstitutor in commons-lang is deprecated.
My solution just relies on common-text:
public static Properties interpolateProperties(Properties rawProperties) {
Properties newProperties = new Properties();
interpolateProperties(rawProperties, newProperties);
return newProperties;
}
public static void interpolateProperties(Properties rawProperties, Properties dstProperties) {
StringSubstitutor sub = new StringSubstitutor((Map)rawProperties);
for (Map.Entry<Object, Object> e : rawProperties.entrySet()) {
dstProperties.put(e.getKey(), sub.replace(e.getValue()));
}
}
ie:
Properties props = new Properties();
props.put("another_name", "lqbweb");
props.put("car", "this is a car from ${name}");
props.put("name", "${another_name}");
System.out.println(interpolateProperties(props));
prints out:
{car=this is a car from ruben, name=ruben, another_name=ruben}
I like the idea of the solutions above, but I really wanted something to replace Properties. The class below builds on those ideas above. It still uses the Apache Commons-text StringSubstitutor, and looks for keys in the Properties class, the Java System defines or the System's Env. This class extends Properties and overrides getProperty(...) methods, so it is a drop in replacement. You can get the original key's value with the 'lookup()' method, but it will return a value from one of those 3 locations. If you want to determine if the key exists at all in the properties, then use the Map's underlying get().
The Apache commons dependency:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.3</version>
</dependency>
Class source.
import java.util.Properties;
import org.apache.commons.text.StringSubstitutor;
import org.apache.commons.text.lookup.StringLookup;
/**
* This extends Properties to provide macros substitution and includes getting properties from
* the Java System properties or the System's Environment. This could be used to consolidate
* getting a system variable regardless if it is defined in the Java system or in the System's
* environment, without any other actual properties.
*
* The macro substitution is recursive so that given the following properties:
<code>
myProg=Program1
outDir=./target
inputs'./data/${defman }Templates
outputs=${outDir}/${myProg}
log=${outDir}/${myProg}/build_report.txt
homeLog=${HOMEDRIVE}/${log}
</code>
* And assuming the environment variable HOMEDRIVE=C:
* the getProperties("homeLog") would result in: C:/./target/Program1/build_report.txt
*
* Although based on the article below, this version substitutes during the getProperty() functions
* instead of the loading functions explained in the article.
*
* Based on this article:
* https://stackoverflow.com/questions/872272/how-to-reference-another-property-in-java-util-properties
*
* @author Tim Gallagher
* @license - You are free to use, alter etc. for any reason
*/
public class MacroProperties extends Properties implements StringLookup {
// Substitutes variables within a string by values.
public final StringSubstitutor macroSubstiitutor;
public MacroProperties() {
this.macroSubstiitutor = new StringSubstitutor(this);
}
public MacroProperties(Properties prprts) {
super(prprts);
this.macroSubstiitutor = new StringSubstitutor(this);
}
/**
* The value of the key is first searched in the properties, and if not found there, it is then
* searched in the system properties, and if still not found, then it is search in the
* system Env.
*
* @param key non-null string.
* @return may be null.
*/
@Override
public String lookup(String key) {
// get the Property first - this must look up in the parent class
// or we'll get into an endless loop
String value = super.getProperty(key);
if (value == null) {
// if not found, get the Java system property which may have been defined on the
// Java command line with '-D...'
value = System.getProperty(key);
if (value == null) {
// if not found, get the System's environment variable.
value = System.getenv(key);
}
}
return value;
}
@Override
public String getProperty(String key, String defaultValue) {
/*
* Replaces all the occurrences of variables with their matching values from the resolver
* using the given source string as a template. By using the default ${} the corresponding
* value replaces the ${variableName} sequence.
*/
String value = lookup(key);
if (value != null) {
value = macroSubstiitutor.replace(value);
} else {
value = defaultValue;
}
return value;
}
@Override
public String getProperty(String key) {
return getProperty(key, null);
}
}