1

How to load property file into Property object in Java and get property values parsed (${x} gets replaced as it is done for ant properties)? For example using this property file:

foo=1
bar=${foo}.0

I need to get bar property as 1.0 instead ${foo}.0. Is there a easy way for this?

EDIT: Alex's solution works for simple scenarios. In my case I had to solve another issue leading to this question: Pulling values from a Java Properties file in order?.

Resulting sample code for loading and parsing properties:

import java.util.*;
import java.io.*;
import org.apache.commons.text.StringSubstitutor;

public class Prop {

    Properties          parsedProperties   = null;

    public static Properties parseProperties(String filename) {
        // inner helper class keeping order of properties:
        class LinkedProperties extends Properties {

            private static final long serialVersionUID = 1L;
            private final HashSet<Object> keys = new LinkedHashSet<Object>();

            public LinkedProperties() {
            }

            public Iterable<Object> orderedKeys() {
                return Collections.list(keys());
            }

            public Enumeration<Object> keys() {
                return Collections.<Object>enumeration(keys);
            }

            public Object put(Object key, Object value) {
                keys.add(key);
                return super.put(key, value);
            }
        }

        LinkedProperties result = new LinkedProperties();
        try (InputStream input  = new FileInputStream(filename)) {

            result.load(input);

            @SuppressWarnings("unchecked")
            StringSubstitutor sub = new StringSubstitutor((Map) result);

            for (Object k : result.orderedKeys()) {
                result.setProperty((String)k, sub.replace(result.getProperty((String)k)));
            }
        } catch (IOException ex) { ex.printStackTrace(); }

        return ((Properties)result);
    }

    public static void main(String[] args) {
        Prop app = new Prop();

        // test - write sample properties file:
        try {
            PrintWriter writer = new PrintWriter(new FileWriter("config.properties"));
            writer.println("foo=1");
            writer.println("bar=1.${foo}");
            writer.println("baz=${bar}.0");
            writer.println("xxx=V.${baz}");
            writer.close();
        } catch (IOException ex) { ex.printStackTrace(); }

        // read and parse properties:
        app.parsedProperties = parseProperties("config.properties");

        // debug print:
        for (Object k : app.parsedProperties.keySet()) {
            System.out.println((String)k + " = " + app.parsedProperties.getProperty((String)k));
        }
    }
}
LiborStefek
  • 400
  • 4
  • 16
  • Spring has this, though not sure how easy it is to get it without pulling enormous amount of other spring functionality dependencies – Coderino Javarino May 04 '20 at 11:13
  • Thanks. Ant has this, but the same applies here, how to avoid pulling whole bunch of dependencies from ant Project etc? – LiborStefek May 04 '20 at 11:16

1 Answers1

3

You may use StringSubstitutor from Apache Commons Text, its Maven dependency is pretty modest (~200K):

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-text -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-text</artifactId>
    <version>1.8</version>
</dependency>

Code example:

// init sample properties
Properties p = new Properties();
p.setProperty("foo", "${baz}.${baz}");
p.setProperty("bar", "${foo}.0");
p.setProperty("baz", "5");

Properties resolved = parseProperties(p);

System.out.println("resolved: " + resolved);
/////

public static Properties parseProperties(Properties orig) {
    Properties result = new Properties();
    StringSubstitutor sub = new StringSubstitutor((Map) orig);
    orig.entrySet().forEach(e -> result.put(e.getKey(), sub.replace(e.getValue())));
    return result;
}

Output:

resolved: {bar=5.5.0, foo=5.5, baz=5}
Nowhere Man
  • 19,170
  • 9
  • 17
  • 42
  • Looks promissing. Could it be used for a opposite way? I mean during loading properties file? – LiborStefek May 05 '20 at 05:43
  • I don't think so - you need to provide both input string where variables have to be replaced (the contents of the .properties file) and the map containing the variables (the loaded instance of properties). And after resolving the variables you'll have to "reload" the properties once again. – Nowhere Man May 05 '20 at 06:11
  • I think this would work: public static Properties parseProperties(Properties orig) { Properties result = new Properties(); StringSubstitutor sub = new StringSubstitutor((Map) orig); for (Map.Entry e : orig.entrySet()) { result.put(e.getKey(), sub.replace(e.getValue())); } return result; } and then simply use this method like: Properties parsedProp = parseProperties(prop); – LiborStefek May 05 '20 at 06:58