38

I have int, float, boolean and string from Properties file. Everything has loaded in Properties. Currently, I am parsing values as I know expected value for particular key.

Boolean.parseBoolean("false");
Integer.parseInt("3")

What is better way of setting these constants values, If I don't know what could be primitive value datatype for a key.

public class Messages {

    Properties appProperties = null;
    FileInputStream file = null;

    public void initialization() throws Exception {

        appProperties = new Properties();
        try {

            loadPropertiesFile();

        } catch (Exception e) {
            throw new Exception(e.getMessage(), e);
        }
    }

    public void loadPropertiesFile() throws IOException {

        String path = "./cfg/message.properties";
        file = new FileInputStream(path);
        appProperties.load(file);
        file.close();
    }
}

Properties File. messassge.properties

SSO_URL = https://example.com/connect/token
SSO_API_USERNAME = test
SSO_API_PASSWORD = Uo88YmMpKUp
SSO_API_SCOPE = intraday_api
SSO_IS_PROXY_ENABLED = false
SSO_MAX_RETRY_COUNT = 3
SSO_FLOAT_VALUE = 3.0

Constant.java

public class Constants {
    public static String SSO_URL = null;
    public static String SSO_API_USERNAME = null;
    public static String SSO_API_PASSWORD = null;
    public static String SSO_API_SCOPE = null;
    public static boolean SSO_IS_PROXY_ENABLED = false;
    public static int SSO_MAX_RETRY_COUNT = 0;
    public static float SSO_FLOAT_VALUE = 0;
}
Pratiyush Kumar Singh
  • 1,977
  • 3
  • 19
  • 39
  • 3
    The problem is everything is a string in the property file. Unless you want to use exceptions and try every parse manually (which is awful), I don't see how you can parse something automatically. Afterall what do the strings `3` or `false` mean to the compiler? Nothing... – dambros Apr 19 '16 at 16:11
  • what do you mean by "I don't know what could be Key and Value"? your question is not clear – Nicolas Filotto Apr 19 '16 at 16:23
  • Well, thinking again if you only only want to parse boolean, int and double, it is doable using a combination of regex (for validation and finding the type) + reflection (for populating the constant). I think it is a little better than using exceptions. – dambros Apr 19 '16 at 16:31
  • 4
    Dambros, That could be one solution to use regex. I did get one idea to create different properties file for different primitive then it will be type safe. – Pratiyush Kumar Singh Apr 19 '16 at 16:35
  • You definitely need metadata about the property data types _in_ the properties file. What if somebody adds another property `IS_ENABLED = true`, and your logic automagically parses it into a `boolean` but the actual logic in the code using the property treats it like a `String`. – Siddharth Apr 28 '16 at 18:25

8 Answers8

37

If you have a class of configuration values, like your Constants class, and you want to load all values from a configuration (properties) file, you can create a little helper class and use reflection:

public class ConfigLoader {
    public static void load(Class<?> configClass, String file) {
        try {
            Properties props = new Properties();
            try (FileInputStream propStream = new FileInputStream(file)) {
                props.load(propStream);
            }
            for (Field field : configClass.getDeclaredFields())
                if (Modifier.isStatic(field.getModifiers()))
                    field.set(null, getValue(props, field.getName(), field.getType()));
        } catch (Exception e) {
            throw new RuntimeException("Error loading configuration: " + e, e);
        }
    }
    private static Object getValue(Properties props, String name, Class<?> type) {
        String value = props.getProperty(name);
        if (value == null)
            throw new IllegalArgumentException("Missing configuration value: " + name);
        if (type == String.class)
            return value;
        if (type == boolean.class)
            return Boolean.parseBoolean(value);
        if (type == int.class)
            return Integer.parseInt(value);
        if (type == float.class)
            return Float.parseFloat(value);
        throw new IllegalArgumentException("Unknown configuration value type: " + type.getName());
    }
}

Then you call it like this:

ConfigLoader.load(Constants.class, "/path/to/constants.properties");

You can extend the code to handle more types. You can also change it to ignore missing properties, instead of failing like it does now, such that assignments in the field declaration will remain unchanged, i.e. be the default.

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Good Solution. But it doesn't work when I add final in constants. It works fine when I set Accessible true and setInt with two parameters field, field.getModifiers() & ~Modifier.FINAL. – Pratiyush Kumar Singh Apr 27 '16 at 17:25
  • 2
    @PratiyushKumarSingh you never mentioned in your question that the constants can be final, IMHO this is clearly the best option you have by far – Nicolas Filotto Apr 29 '16 at 16:40
  • @PratiyushKumarSingh if you mark your fields as `final` and initialize them, you will not be able to change them, as per the JLS. – Matthieu Apr 30 '16 at 17:12
  • @PratiyushKumarSingh but if you don't want to make them `public`, mark them `private` and use `public static getXXX()` methods. – Matthieu Apr 30 '16 at 17:32
7

If you know the type of constant, you can use Apache Commons Collections.

For example, you can use some utilities method based on type of your constant.

booelan SSO_IS_PROXY_ENABLED = MapUtils.getBooleanValue(appProperties, "SSO_IS_PROXY_ENABLED", false);
String SSO_URL = MapUtils.getString(appProperties, "SSO_URL", "https://example.com/connect/token");

You can even use default values to avoid errors.

josivan
  • 1,963
  • 1
  • 15
  • 26
  • 2
    True, It works fine if we know datatype of primitive. But we cannot hardcode or write code for 100 of such property values. – Pratiyush Kumar Singh Apr 19 '16 at 16:29
  • 2
    @PratiyushKumarSingh Why can't you hardcode for datatype, when you have to hardcode the type? E.g. `SSO_URL` is *defined* as a `String` (hardcoded), so call `getString()` (matching hardcode). – Andreas Apr 19 '16 at 16:44
  • 2
    Yes, Andreas, currently I am doing that and It doen`t look like optimized solution. – Pratiyush Kumar Singh Apr 19 '16 at 16:51
5

Dambros is right, every thing you store inside a Properties file is as a String value. You can track your different primitive data types after retrieving properties value as below like ref. -

Java Properties File: How to Read config.properties Values in Java?

package crunchify.com.tutorial;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Properties;

/**
 * @author Crunchify.com
 * 
 */

public class CrunchifyGetPropertyValues {
    String result = "";
    InputStream inputStream;

    public String getPropValues() throws IOException {

        try {
            Properties prop = new Properties();
            String propFileName = "config.properties";

            inputStream = getClass().getClassLoader().getResourceAsStream(propFileName);

            if (inputStream != null) {
                prop.load(inputStream);
            } else {
                throw new FileNotFoundException("property file '" + propFileName + "' not found in the classpath");
            }

            Date time = new Date(System.currentTimeMillis());

            // get the property value and print it out
            String user = prop.getProperty("user");
            String company1 = prop.getProperty("company1");
            String company2 = prop.getProperty("company2");
            String company3 = prop.getProperty("company3");

            result = "Company List = " + company1 + ", " + company2 + ", " + company3;
            System.out.println(result + "\nProgram Ran on " + time + " by user=" + user);
        } catch (Exception e) {
            System.out.println("Exception: " + e);
        } finally {
            inputStream.close();
        }
        return result;
    }
}

and later convert to primitive - How to convert String to primitive type value?

I suggest you to track your data types value by putting the key values inside String type switch statement and later retrieve the related data type value by using key name cases. String type switch case is possible after Java 7.

ArifMustafa
  • 4,617
  • 5
  • 40
  • 48
  • 3
    Yes, this is also possible solution. Check out idea suggested by Dambros, also, we can use different files for primitives like int_message.properties , string_messge.properties ...so on. – Pratiyush Kumar Singh Apr 19 '16 at 16:39
4

Not entirely sure whether I exactly understand the problem but a possibility could be to include the type of the property value in the (String) value. So for example the properties you showed would become something like:

SSO_URL = URL:https://example.com/connect/token
SSO_API_USERNAME = STRING:test
SSO_API_PASSWORD = STRING:Uo88YmMpKUp
SSO_API_SCOPE = STRING:intraday_api
SSO_IS_PROXY_ENABLED = BOOLEAN:false
SSO_MAX_RETRY_COUNT = INTEGER:3
SSO_FLOAT_VALUE = FLOAT:3.0

During the parsing of the property values you first determine the type of the property by looking at the part before : and use the part after for the actual parsing.

private static Object getValue(Properties props, String name) {
    String propertyValue = props.getProperty(name);
    if (propertyValue == null) {
        throw new IllegalArgumentException("Missing configuration value: " + name); 
    } else {
        String[] parts = string.split(":");
        switch(parts[0]) {
            case "STRING":
                return parts[1];
            case "BOOLEAN":
                return Boolean.parseBoolean(parts[1]);
            ....
            default:
                throw new IllegalArgumentException("Unknown configuration value type: " + parts[0]);
        }
    }
 }
uniknow
  • 938
  • 6
  • 5
4

Follow the dropwizard configuration pattern where you define your constants using YAML instead of Properties and use Jackson to deserialize it into your Class. Other than type safety, dropwizard's configuration pattern goes one step further by allowing Hibernate Validator annotations to validate that the values fall into your expected ranges.

For dropwizard's example...

For more information about the technology involved...

  • github.com/FasterXML/jackson-dataformat-yaml
  • hibernate.org/validator/
3

Spring Boot has ready to use and feature reach solution for type-safe configuration properties.

Definitely, use of the Spring just for this task is overkill but Spring has a lot of cool features and this one can attract you to right side ;)

Andrew Kolpakov
  • 429
  • 3
  • 8
1

You can define your configurable parameters as 'static' in your class of choice, and from a static init call a method that loads the parameter values from a properties file.

For example:

public class MyAppConfig {

    final static String propertiesPath="/home/workspace/MyApp/src/config.properties";
    static String strParam;
    static boolean boolParam;
    static int intParam;
    static double dblParam;

    static {

       // Other static initialization tasks...
       loadParams();
    }

    private static void loadParams(){

        Properties prop = new Properties();

        try (InputStream propStream=new FileInputStream(propertiesPath)){           
            // Load parameters from config file
            prop.load(propStream);
            // Second param is default value in case key-pair is missing
            strParam=prop.getProperty("StrParam", "foo");
            boolParam=Boolean.parseBoolean(prop.getProperty("boolParam", "false")); 
            intParam= Integer.parseInt(prop.getProperty("intParam", "1")); 
            dblParam=Double.parseDouble(prop.getProperty("dblParam", "0.05")); 

        } catch (IOException e) {
            logger.severe(e.getMessage());
            e.printStackTrace();
        } 
    }
}
Alicia
  • 144
  • 1
  • 9
-1

This might help:

props.getProperty("name", Integer.class);
Kelvin Schoofs
  • 8,323
  • 1
  • 12
  • 31