/*
* Copyright 2015 the original author or authors.
*
* Licensed 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 io.atomix.catalyst.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.function.Function;
/**
* Utility for reading configuration information from properties.
*
* @author <a href="http://github.com/kuujo>Jordan Halterman</a>
*/
public final class PropertiesReader {
/**
* Loads a properties reader for the given properties file on the local filesystem.
*
* @param propertiesFile The properties file for which to load the reader.
* @return A new properties reader.
*/
public static PropertiesReader load(String propertiesFile) {
return new PropertiesReader(loadProperties(propertiesFile));
}
/**
* Loads a properties reader for the given properties file on the classpath.
*
* @param propertiesFile The properties file for which to load the reader.
* @return A new properties reader.
*/
public static PropertiesReader loadFromClasspath(String propertiesFile) {
return new PropertiesReader(loadPropertiesFromClasspath(propertiesFile));
}
/**
* Loads properties from a properties file on the local filesystem.
*/
private static Properties loadProperties(String propertiesFile) {
Properties properties = new Properties();
try (InputStream is = new FileInputStream(propertiesFile)) {
properties.load(is);
} catch (IOException e) {
throw new RuntimeException("failed to load properties", e);
}
return properties;
}
/**
* Loads properties from a properties file on the classpath.
*/
private static Properties loadPropertiesFromClasspath(String propertiesFile) {
Properties properties = new Properties();
try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(propertiesFile)) {
properties.load(is);
} catch (IOException e) {
throw new RuntimeException("failed to load properties", e);
}
return properties;
}
private final Properties properties;
public PropertiesReader(Properties properties) {
this.properties = Assert.notNull(properties, "properties");
}
/**
* Returns the underlying properties.
*
* @return The underlying properties.
*/
public Properties properties() {
return properties;
}
/**
* Reads a collection of properties based on a prefix.
*
* @param prefix The prefix for which to read properties.
* @param factory The factory to call for each property name in the collection.
* @param <T> The collection value type.
* @return The collection.
*/
public <T> Collection<T> getCollection(String prefix, Function<String, T> factory) {
Collection<T> collection = new ArrayList<>();
for (String property : properties.stringPropertyNames()) {
if (property.startsWith(prefix + ".")) {
collection.add(factory.apply(property));
}
}
return collection;
}
/**
* Returns a map of properties for a given prefix.
*
* @param prefix The prefix for which to return a map of property values.
* @param keyFactory A converter function to convert the map keys.
* @param valueFactory A converter function to convert the map values.
* @param <K> The map key type.
* @param <V> The map value type.
* @return The map.
*/
public <K, V> Map<K, V> getMap(String prefix, Function<String, K> keyFactory, Function<String, V> valueFactory) {
Map<K, V> map = new HashMap<>();
for (String property : properties.stringPropertyNames()) {
if (property.startsWith(prefix + ".")) {
map.put(keyFactory.apply(property.substring(prefix.length() + 1)),
valueFactory.apply(properties.getProperty(property)));
}
}
return map;
}
/**
* Reads a class property.
*
* @param property The property name.
* @return The property value.
* @throws ConfigurationException if the property is not present
*/
public Class<?> getClass(String property) {
return getProperty(property, className -> {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
throw new ConfigurationException("unknown class: " + className, e);
}
});
}
/**
* Reads a class property.
*
* @param property The property name.
* @param defaultValue The default value to return if the property is not present
* @return The property value.
*/
public Class<?> getClass(String property, Class<?> defaultValue) {
return getProperty(property, defaultValue, className -> {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
throw new ConfigurationException("unknown class: " + className, e);
}
});
}
/**
* Reads a file property.
*
* @param property The property name.
* @return The property value.
* @throws ConfigurationException if the property is not present
*/
public File getFile(String property) {
return getProperty(property, File::new);
}
/**
* Reads a file property.
*
* @param property The property name.
* @param defaultValue The default value to return if the property is not present
* @return The property value.
*/
public File getFile(String property, File defaultValue) {
return getProperty(property, defaultValue, File::new);
}
/**
* Reads an enum property.
*
* @param property The property name.
* @param type The enum type.
* @param <T> The enum type.
* @return The property value.
* @throws ConfigurationException if the property is not present
*/
public <T extends Enum<T>> T getEnum(String property, Class<T> type) {
return Enum.valueOf(type, getString(property));
}
/**
* Reads an enum property.
*
* @param property The property name.
* @param type The enum type.
* @param defaultValue The default value to return if the property is not present
* @param <T> The enum type.
* @return The property value.
*/
public <T extends Enum<T>> T getEnum(String property, Class<T> type, T defaultValue) {
return Enum.valueOf(type, getString(property, defaultValue.name()));
}
/**
* Reads a string property, returning a default value if the property is not present.
*
* @param property The property name.
* @return The property value.
* @throws ConfigurationException if the property is not present
*/
public String getString(String property) {
return getProperty(property, v -> v);
}
/**
* Reads a string property, returning a default value if the property is not present.
*
* @param property The property name.
* @param defaultValue The default value to return if the property is not present.
* @return The property value.
*/
public String getString(String property, String defaultValue) {
return getProperty(property, defaultValue, v -> v);
}
/**
* Reads a boolean property.
*
* @param property The property name.
* @return The property value.
* @throws ConfigurationException if the property is not present
*/
public boolean getBoolean(String property) {
return getProperty(property, value -> {
switch (value.trim().toLowerCase()) {
case "true":
case "1":
return true;
case "false":
case "0":
return false;
default:
throw new ConfigurationException("invalid property value: " + property + " must be a boolean");
}
});
}
/**
* Reads a boolean property.
*
* @param property The property name.
* @param defaultValue The default value to return if the property is not present
* @return The property value.
*/
public boolean getBoolean(String property, boolean defaultValue) {
return getProperty(property, defaultValue, value -> {
switch (value.trim().toLowerCase()) {
case "true":
case "1":
return true;
case "false":
case "0":
return false;
default:
throw new ConfigurationException("invalid property value: " + property + " must be a boolean");
}
});
}
/**
* Reads a short property.
*
* @param property The property name.
* @return The property value.
* @throws ConfigurationException if the property is not present
*/
public short getShort(String property) {
return getProperty(property, value -> {
try {
return Short.valueOf(value);
} catch (NumberFormatException e) {
throw new ConfigurationException("malformed property value: " + property + " must be a short");
}
});
}
/**
* Reads a short property.
*
* @param property The property name.
* @param defaultValue The default value to return if the property is not present
* @return The property value.
*/
public short getShort(String property, short defaultValue) {
return getProperty(property, defaultValue, value -> {
try {
return Short.valueOf(value);
} catch (NumberFormatException e) {
throw new ConfigurationException("malformed property value: " + property + " must be a short");
}
});
}
/**
* Reads an integer property.
*
* @param property The property name.
* @return The property value.
* @throws ConfigurationException if the property is not present
*/
public int getInteger(String property) {
return getProperty(property, value -> {
try {
return Integer.valueOf(value);
} catch (NumberFormatException e) {
throw new ConfigurationException("malformed property value: " + property + " must be an integer");
}
});
}
/**
* Reads an integer property.
*
* @param property The property name.
* @param defaultValue The default value to return if the property is not present
* @return The property value.
*/
public int getInteger(String property, int defaultValue) {
return getProperty(property, defaultValue, value -> {
try {
return Integer.valueOf(value);
} catch (NumberFormatException e) {
throw new ConfigurationException("malformed property value: " + property + " must be an integer");
}
});
}
/**
* Reads a long property.
*
* @param property The property name.
* @return The property value.
* @throws ConfigurationException if the property is not present
*/
public long getLong(String property) {
return getProperty(property, value -> {
try {
return Long.valueOf(value);
} catch (NumberFormatException e) {
throw new ConfigurationException("malformed property value: " + property + " must be a long");
}
});
}
/**
* Reads a long property.
*
* @param property The property name.
* @param defaultValue The default value to return if the property is not present
* @return The property value.
*/
public long getLong(String property, long defaultValue) {
return getProperty(property, defaultValue, value -> {
try {
return Long.valueOf(value);
} catch (NumberFormatException e) {
throw new ConfigurationException("malformed property value: " + property + " must be a long");
}
});
}
/**
* Reads a float property.
*
* @param property The property name.
* @return The property value.
* @throws ConfigurationException if the property is not present
*/
public float getFloat(String property) {
return getProperty(property, value -> {
try {
return Float.valueOf(value);
} catch (NumberFormatException e) {
throw new ConfigurationException("malformed property value: " + property + " must be a float");
}
});
}
/**
* Reads a float property.
*
* @param property The property name.
* @param defaultValue The default value to return if the property is not present
* @return The property value.
*/
public float getFloat(String property, float defaultValue) {
return getProperty(property, defaultValue, value -> {
try {
return Float.valueOf(value);
} catch (NumberFormatException e) {
throw new ConfigurationException("malformed property value: " + property + " must be a float");
}
});
}
/**
* Reads a double property.
*
* @param property The property name.
* @return The property value.
* @throws ConfigurationException if the property is not present
*/
public double getDouble(String property) {
return getProperty(property, value -> {
try {
return Double.valueOf(value);
} catch (NumberFormatException e) {
throw new ConfigurationException("malformed property value: " + property + " must be a double");
}
});
}
/**
* Reads a double property.
*
* @param property The property name.
* @param defaultValue The default value to return if the property is not present
* @return The property value.
*/
public double getDouble(String property, double defaultValue) {
return getProperty(property, defaultValue, value -> {
try {
return Double.valueOf(value);
} catch (NumberFormatException e) {
throw new ConfigurationException("malformed property value: " + property + " must be a double");
}
});
}
/**
* Reads a duration property.
*
* @param property The property name.
* @return The property value.
* @throws ConfigurationException if the property is not present
*/
public Duration getDuration(String property) {
return getProperty(property, value -> {
try {
return Duration.ofMillis(Long.valueOf(value));
} catch (NumberFormatException e) {
throw new ConfigurationException("malformed property value: " + property + " must be a number");
}
});
}
/**
* Reads a duration property.
*
* @param property The property name.
* @param defaultValue The default value to return if the property is not present
* @return The property value.
*/
public Duration getDuration(String property, Duration defaultValue) {
return getProperty(property, defaultValue, value -> {
try {
return Durations.of(value);
} catch (NumberFormatException e) {
throw new ConfigurationException("malformed property value: " + property + " must be a number");
}
});
}
/**
* Reads an arbitrary property.
*
* @param property The property name.
* @param transformer A transformer function with which to transform the property value to its appropriate type.
* @param <T> The property type.
* @return The property value.
* @throws ConfigurationException if the property is not present
*/
private <T> T getProperty(String property, Function<String, T> transformer) {
Assert.notNull(property, "property");
String value = properties.getProperty(property);
if (value == null)
throw new ConfigurationException("missing property: " + property);
return transformer.apply(value);
}
/**
* Reads an arbitrary property.
*
* @param property The property name.
* @param defaultValue The default property value.
* @param transformer A transformer function with which to transform the property value to its appropriate type.
* @param <T> The property type.
* @return The property value.
*/
private <T> T getProperty(String property, T defaultValue, Function<String, T> transformer) {
Assert.notNull(property, "property");
String value = properties.getProperty(property);
if (value == null)
return defaultValue;
return transformer.apply(value);
}
}