net.freeutils.util.config.Configuration Maven / Gradle / Ivy
Show all versions of jelementary Show documentation
/*
* Copyright © 2003-2024 Amichai Rothman
*
* This file is part of JElementary - the Java Elementary Utilities package.
*
* JElementary is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* JElementary is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with JElementary. If not, see .
*
* For additional info see https://www.freeutils.net/source/jelementary/
*/
package net.freeutils.util.config;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.freeutils.util.Containers;
import net.freeutils.util.Strings;
import net.freeutils.util.Utils;
/**
* The {@code Configuration} class encapsulates a configuration section.
*
* Each configuration section consists of various (ordered) configuration
* items, each with a name and one or more values. A value can be either a
* String or a nested Configuration section.
*
* This forms a tree hierarchy similar to XML, but with no
* dependency on a particular persistent format or implementation.
*/
public class Configuration {
/**
* An empty configuration instance which can be used as a configuration placeholder.
*/
public static Configuration EMPTY = new Configuration("EMPTY");
protected String name;
protected final Map items = new LinkedHashMap<>();
/**
* Constructs a Configuration section instance. This constructor can be
* called only from subclasses, for populating the configuration contents.
*
* Note that you can easily use an anonymous subclass to create an in-code
* Configuration instance, for example:
*
* Configuration conf = new Configuration() {{
* add(param1, value1);
* // additional configuration items...
* }};
*
*
* @param name the name of this configuration section
*/
protected Configuration(String name) {
this.name = name;
}
/**
* Constructs a Configuration section instance. This constructor can be
* called only from subclasses, for populating the configuration contents.
*/
protected Configuration() {}
/**
* Returns the name of this configuration section
*
* @return the name of this configuration section
*/
public String getName() {
return name;
}
/**
* Substitutes variables within the configuration values according
* to the given substitution map.
*
* Variables within configuration values are of the form ${NAME},
* and are replaced by the corresponding value, if it exists
* (otherwise the variable literal is preserved).
*
* @param variables the substitution variables name-value map
*/
public void substituteVariables(Map variables) {
Pattern pattern = Pattern.compile("\\$\\{([^}]*)\\}");
for (Map.Entry e : items.entrySet()) {
Object item = e.getValue();
if (item instanceof String[]) {
String[] strings = (String[])item;
for (int i = 0; i < strings.length; i++) {
if (strings[i].contains("${")) {
// replace all variables in string
Matcher matcher = pattern.matcher(strings[i]);
StringBuffer sb = new StringBuffer(strings[i].length());
while (matcher.find()) {
String name = matcher.group(1);
String val = Utils.def(variables.get(name), matcher.group(0));
matcher.appendReplacement(sb, Strings.escape(val, "$\\"));
}
matcher.appendTail(sb);
strings[i] = sb.toString();
}
}
} else if (item instanceof Configuration[]) {
for (Configuration c : (Configuration[])item)
c.substituteVariables(variables);
}
}
}
/**
* Sets an item with the given name and configuration section values.
* If an item with the same name already exists, it is overwritten.
*
* @param name the item's name
* @param values the configuration section values
* @throws NullPointerException if name or values is null
*/
protected void set(String name, Configuration... values) {
if (name == null || values == null)
throw new NullPointerException();
if (values.length > 0) // invariant: values are never empty
items.put(name, values);
}
/**
* Sets an item with the given name and string values.
* If an item with the same name already exists, it is overwritten.
*
* @param name the item's name
* @param values the string values
* @throws NullPointerException if name or values is null
*/
protected void set(String name, String... values) {
if (name == null || values == null)
throw new NullPointerException();
if (values.length > 0) // invariant: values are never empty
items.put(name, values);
}
/**
* Adds the given configuration section values to the item with the given name.
* If an item with the given name does not exist, it is created.
*
* @param name the item's name
* @param values the configuration section values
* @throws NullPointerException if name or values is null
* @throws ConfigurationException if an item with the given name already exists,
* but its value is of a different type
*/
protected void add(String name, Configuration... values) throws ConfigurationException {
if (name == null || values == null)
throw new NullPointerException();
try {
Configuration[] v = (Configuration[])items.get(name);
set(name, Containers.concat(v, values));
} catch (ClassCastException cce) {
throw new ConfigurationException(name + " already exists with a different type of value");
}
}
/**
* Adds the given string values to the item with the given name.
* If an item with the given name does not exist, it is created.
*
* @param name the item's name
* @param values the string values
* @throws NullPointerException if name or values is null
* @throws ConfigurationException if an item with the given name already exists,
* but its value is of a different type
*/
protected void add(String name, String... values) throws ConfigurationException {
if (name == null || values == null)
throw new NullPointerException();
try {
String[] v = (String[])items.get(name);
set(name, Containers.concat(v, values));
} catch (ClassCastException cce) {
throw new ConfigurationException(name + " already exists with a different type of value");
}
}
/**
* Returns whether this configuration section contains an item with the given name.
*
* @param name the name to check
* @return true if an item with the given name exists, false otherwise
*/
public boolean contains(String name) {
return items.containsKey(name);
}
/**
* Returns a copy of all items in this configuration section.
*
* @return a copy of all items in this configuration section
*/
public Map items() {
return new LinkedHashMap<>(items);
}
/**
* Returns a copy of all items whose values are configuration sections.
*
* @return a copy of all items whose values are configuration sections
*/
public Map sections() {
Map m = new LinkedHashMap<>(items.size());
for (Map.Entry e : items.entrySet())
if (e.getValue() instanceof Configuration[])
m.put(e.getKey(), (Configuration[])e.getValue());
return m;
}
/**
* Returns copy of all items whose values are strings.
*
* @return a copy of all items whose values are strings
*/
public Map allStrings() {
Map m = new LinkedHashMap<>(items.size());
for (Map.Entry e : items.entrySet())
if (e.getValue() instanceof String[])
m.put(e.getKey(), (String[])e.getValue());
return m;
}
/**
* Returns copy of all items whose values are strings, with only the first string value for each.
*
* @return a copy of all items whose values are strings
*/
public Map strings() {
Map m = new LinkedHashMap<>(items.size());
for (Map.Entry e : items.entrySet())
if (e.getValue() instanceof String[])
m.put(e.getKey(), ((String[])e.getValue())[0]);
return m;
}
/**
* Returns a string representation of all configuration items.
*
* @return a string representation of all configuration items
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (Map.Entry e : items.entrySet())
sb.append(e.getKey()).append(": ").append(Strings.contentOfArray(e.getValue())).append('\n');
return sb.toString();
}
/**
* Returns the section values corresponding to the given name.
*
* @param name the name
* @param required specifies the behavior if the given name is not found:
* if true, an exception is thrown, and if false, an empty array
* is returned
* @return the section values corresponding to the given name, or an empty array
* @throws ConfigurationException if the given name is not found and required is true
*/
public Configuration[] getSections(String name, boolean required) throws ConfigurationException {
Configuration[] v;
try {
v = (Configuration[])items.get(name);
} catch (ClassCastException cce) {
throw new ConfigurationException(name + " values are of a different type");
}
if (v == null) {
if (required)
throw new ConfigurationException("item not found: " + name);
v = new Configuration[0];
}
return v;
}
/**
* Returns the section values corresponding to the given name.
*
* @param name the name
* @return the section values corresponding to the given name
* @throws ConfigurationException if the given name is not found
*/
public Configuration[] getSections(String name) throws ConfigurationException {
return getSections(name, true);
}
/**
* Returns the first section corresponding to the given name.
*
* @param name the name
* @param required specifies the behavior if the given name is not found:
* if true, an exception is thrown, and if false, null is returned
* @return the first section corresponding to the given name, or null
* @throws ConfigurationException if the given name is not found and required is true
*/
public Configuration getSection(String name, boolean required) throws ConfigurationException {
Configuration[] v = getSections(name, required);
return v.length > 0 ? v[0] : null;
}
/**
* Returns the first section corresponding to the given name.
*
* @param name the name
* @return the first section corresponding to the given name
* @throws ConfigurationException if the given name is not found
*/
public Configuration getSection(String name) throws ConfigurationException {
return getSections(name, true)[0];
}
/**
* Returns the string values corresponding to the given name.
*
* @param name the name
* @param required specifies the behavior if the given name is not found:
* if true, an exception is thrown, and if false, an empty array
* is returned
* @return the string values corresponding to the given name, or an empty array
* @throws ConfigurationException if the given name is not found and required is true
*/
public String[] getStrings(String name, boolean required) throws ConfigurationException {
String[] v;
try {
v = (String[])items.get(name);
} catch (ClassCastException cce) {
throw new ConfigurationException(name + " values are of a different type");
}
if (v == null) {
if (required)
throw new ConfigurationException("item not found: " + name);
v = new String[0];
}
return v;
}
/**
* Returns the string values corresponding to the given name.
*
* @param name the name
* @return the string values corresponding to the given name
* @throws ConfigurationException if the given name is not found
*/
public String[] getStrings(String name) throws ConfigurationException {
return getStrings(name, true);
}
/**
* Returns the string value corresponding to the given name.
*
* @param name the name
* @return the string value corresponding to the given name
* @throws ConfigurationException if there is no value corresponding to the given name
*/
public String get(String name) throws ConfigurationException {
return getStrings(name, true)[0];
}
/**
* Returns the string value corresponding to the given name, or the given default value if none exists.
*
* @param name the name
* @param def the default value
* @return the string value corresponding to the given name, or the default value if none exists
*/
public String get(String name, String def) {
String[] v = getStrings(name, false);
return v.length > 0 ? v[0] : def;
}
/**
* Returns the integer value corresponding to the given name.
*
* @param name the name
* @return the integer value corresponding to the given name
* @throws ConfigurationException if there is no value corresponding to the given name
*/
public int getInt(String name) throws ConfigurationException {
return Integer.parseInt(get(name));
}
/**
* Returns the integer value corresponding to the given name, or the given default value if none exists.
*
* @param name the name
* @param def the default value
* @return the integer value corresponding to the given name, or the default value if none exists
*/
public int getInt(String name, int def) {
String v = get(name, null);
return v == null ? def : Integer.parseInt(v);
}
/**
* Returns the long value corresponding to the given name.
*
* @param name the name
* @return the long value corresponding to the given name
* @throws ConfigurationException if there is no value corresponding to the given name
*/
public long getLong(String name) throws ConfigurationException {
return Long.parseLong(get(name));
}
/**
* Returns the long value corresponding to the given name, or the given default value if none exists.
*
* @param name the name
* @param def the default value
* @return the long value corresponding to the given name, or the default value if none exists
*/
public long getLong(String name, long def) {
String v = get(name, null);
return v == null ? def : Long.parseLong(v);
}
/**
* Returns the boolean value corresponding to the given name.
*
* @param name the name
* @return the boolean value corresponding to the given name
* @throws ConfigurationException if there is no value corresponding to the given name
*/
public boolean getBoolean(String name) throws ConfigurationException {
return Boolean.parseBoolean(get(name));
}
/**
* Returns the boolean value corresponding to the given name, or the given default value if none exists.
*
* @param name the name
* @param def the default value
* @return the boolean value corresponding to the given name, or the default value if none exists
*/
public boolean getBoolean(String name, boolean def) {
String v = get(name, null);
return v == null ? def : Boolean.parseBoolean(v);
}
/**
* Returns the enum value corresponding to the given name. The value is looked up in uppercase form.
*
* @param the enum type
* @param name the name
* @param enumType the Class object of the enum type from which to return a constant
* @return the enum value corresponding to the given name
* @throws ConfigurationException if there is no value corresponding to the given name
* @throws IllegalArgumentException if the specified enum type has
* no constant with the configuration value's name, or the specified
* class object does not represent an enum type
*/
public > T getEnum(String name, Class enumType) throws ConfigurationException {
return Enum.valueOf(enumType, get(name).toUpperCase());
}
/**
* Returns the enum value corresponding to the given name, or the given default value if none exists.
* The value is looked up in uppercase form.
*
* @param the enum type
* @param name the name
* @param enumType the Class object of the enum type from which to return a constant
* @param def the default value
* @return the enum value corresponding to the given name, or the default value if none exists
* @throws IllegalArgumentException if the specified enum type has
* no constant with the configuration value's name, or the specified
* class object does not represent an enum type
*/
public > T getEnum(String name, Class enumType, T def) {
String v = get(name, null);
return v == null ? def : Enum.valueOf(enumType, v.toUpperCase());
}
}