CoralConfig is a thread-safe configuration framework that offers a fluent API, aliases, deprecation support, default values, strongly typed configuration types (including enum), and more. It provides a robust solution to manage the many configuration parameters and their types that applications typically require.
public class Basics {
public static final ConfigKey<Integer> MAX_RETRIES = intKey().def(4); // or intKey(4)
private final int maxRetries;
public Basics(Configuration config) {
this.maxRetries = config.get(MAX_RETRIES);
}
}To create an empty configuration:
MapConfiguration mc = new MapConfiguration(Basics.class);To get the default value:
int maxRetries = mc.get(MAX_RETRIES); // => 4 (the default)To overwrite a default value:
mc.overwriteDefault(MAX_RETRIES, 6);
int maxRetries = mc.get(MAX_RETRIES); // => 6 (overwritten default)To configure (add) a value so this value is returned instead of the (overwritten or not) default value:
mc.add(MAX_RETRIES, 2);
int maxRetries = mc.get(MAX_RETRIES); // => 2 (configured)To create a configuration with some values, you can pass a list of params to the constructor:
MapConfiguration mc = new MapConfiguration("maxRetries=1 username=saoj heartbeat=30",
Basics.class);
int maxRetries = mc.get(MAX_RETRIES); // => 1 (configured)The configuration can handle multiple holder classes with distinct configuration keys. The configuration enforces that there are no duplicate keys across all holder classes.
public class Client {
public static final ConfigKey<Integer> HEARTBEAT = intKey(5);
}
public class TcpClient extends Client {
public static final ConfigKey<String> USERNAME = stringKey().def("saoj");
}Simply pass a list of holder classes:
MapConfiguration mc = new MapConfiguration("heartbeat=2 username=rpaiva",
Client.class, TcpClient.class);If you get the type of the config key wrong, it won't even compile:
// DOESN'T COMPILE (wrong type)
String maxRetries = mc.get(MAX_RETRIES);Don't like the name of a configuration key? Simply create an alias:
public class Client {
public static final ConfigKey<Integer> HEARTBEAT_INTERVAL = intKey().def(5);
public static final ConfigKey<Integer> HEARTBEAT = intKey().alias(HEARTBEAT_INTERVAL);
}We did not like the name heartbeat so we created heartbeatInterval as the primary config key and added heartbeat as an alias for backwards compatibility. Now we can do:
MapConfiguration mc = new MapConfiguration(Client.class);
int heartbeatInterval = mc.get(HEARTBEAT_INTERVAL); // => 5 (default)
// This also works with the default value of the primary config key!
int heartbeat = mc.get(HEARTBEAT); // => 5 (default)If heartbeatInterval is the only configured value in the key group, both lookups resolve to it:
mc.add(HEARTBEAT_INTERVAL, 2);
int heartbeatInterval = mc.get(HEARTBEAT_INTERVAL); // => 2 (configured)
// The alias also resolves to the only configured value.
int heartbeat = mc.get(HEARTBEAT); // => 2 (configured)Or, if heartbeat is the only configured value:
mc.add(HEARTBEAT, 2);
// The primary key resolves to the value configured for its alias.
int heartbeatInterval = mc.get(HEARTBEAT_INTERVAL); // => 2 (configured)
int heartbeat = mc.get(HEARTBEAT); // => 2 (configured)You can have as many aliases as you want:
public class Client {
public static final ConfigKey<Integer> HEARTBEAT_INTERVAL = intKey().def(5);
public static final ConfigKey<Integer> HEARTBEAT = intKey().alias(HEARTBEAT_INTERVAL);
public static final ConfigKey<Integer> HEARTBEAT_TIME = intKey().alias(HEARTBEAT_INTERVAL);
}Chose a bad name for a configuration? Simply deprecate it:
public class Client {
public static final ConfigKey<Integer> HEARTBEAT_INTERVAL = intKey().def(5);
public static final ConfigKey<Integer> HEARTBEAT = intKey().deprecated(HEARTBEAT_INTERVAL);
}We chose a bad name by mistake (heartbeat) so we created heartbeatInterval as the primary config key and added heartbeat as deprecated for backwards compatibility. Now we can do:
MapConfiguration mc = new MapConfiguration(Client.class);
int heartbeatInterval = mc.get(HEARTBEAT_INTERVAL); // => 5 (default)
// This also works with the default value of the primary config key!
int heartbeat = mc.get(HEARTBEAT); // => 5 (default)If heartbeatInterval is the only configured value in the key group, both lookups resolve to it:
mc.add(HEARTBEAT_INTERVAL, 2);
int heartbeatInterval = mc.get(HEARTBEAT_INTERVAL); // => 2 (configured)
// The deprecated key also resolves to the only configured value.
int heartbeat = mc.get(HEARTBEAT); // => 2 (configured)Or, if heartbeat is the only configured value:
mc.add(HEARTBEAT, 2);
// The primary key resolves to the value configured for its deprecated key.
int heartbeatInterval = mc.get(HEARTBEAT_INTERVAL); // => 2 (configured)
int heartbeat = mc.get(HEARTBEAT); // => 2 (configured)You can have as many deprecated configuration keys as you want:
public class Client {
public static final ConfigKey<Integer> HEARTBEAT_INTERVAL = intKey().def(5);
public static final ConfigKey<Integer> HEARTBEAT = intKey().deprecated(HEARTBEAT_INTERVAL);
public static final ConfigKey<Integer> HEARTBEAT_TIME = intKey().deprecated(HEARTBEAT_INTERVAL);
}And finally, if you want to know the deprecated configuration keys that you are still using, you can add a DeprecatedListener to your configuration. Below a full example:
public class DeprecationBasics {
public static final ConfigKey<Integer> MAX_NUMBER_OF_RETRIES = intKey().def(4);
public static final ConfigKey<Integer> MAX_RETRIES = intKey().deprecated(MAX_NUMBER_OF_RETRIES);
private final int maxRetries;
public DeprecationBasics(Configuration config) {
this.maxRetries = config.get(MAX_RETRIES); // using deprecated key!
}
public static void main(String[] args) {
MapConfiguration config = new MapConfiguration(DeprecationBasics.class);
DeprecatedListener listener = new DeprecatedListener() {
@Override
public void deprecatedConfig(ConfigKey<?> deprecatedKey, ConfigKey<?> primaryKey) {
DeprecatedListener.super.deprecatedConfig(deprecatedKey, primaryKey);
}
};
config.addListener(listener);
new DeprecationBasics(config);
}
}When you run the program above you see in the stdout:
---CoralConfig---> You are using a deprecated config key!
holder=com.coralblocks.coralconfig.example.DeprecationBasics
deprecatedKey=MAX_RETRIES("maxRetries")
inFavorOf=MAX_NUMBER_OF_RETRIES("maxNumberOfRetries")
@Test
public void testEnum() {
enum TestEnum { BALL, BOB, BILL }
class Base {
public static final ConfigKey<TestEnum> MY_ENUM = enumKey(TestEnum.class, TestEnum.BOB);
}
MapConfiguration mc = new MapConfiguration(Base.class);
assertEquals(TestEnum.BOB, mc.get(Base.MY_ENUM)); // test default
mc.overwriteDefault(Base.MY_ENUM, TestEnum.BILL);
assertEquals(TestEnum.BILL, mc.get(Base.MY_ENUM)); // test overwritten default
mc.add(Base.MY_ENUM, TestEnum.BALL);
assertEquals(TestEnum.BALL, mc.get(Base.MY_ENUM)); // test configured
mc.remove(Base.MY_ENUM);
assertEquals(TestEnum.BILL, mc.get(Base.MY_ENUM)); // test overwritten default
mc.overwriteDefault(Base.MY_ENUM, null); // String and Enum support NULL !!!
assertEquals(null, mc.get(Base.MY_ENUM)); // test overwritten default
mc.removeOverwrittenDefault(Base.MY_ENUM);
assertEquals(TestEnum.BOB, mc.get(Base.MY_ENUM)); // test default
}You can use the ConfigPrinter utility class to write configuration metadata as CSV:
java -cp target/coralconfig-all.jar \
com.coralblocks.coralconfig.ConfigPrinter \
includeHeaderLine=true \
includeParamName=false \
includeHolderClass=false \
com.coralblocks.coralconfig.example.Client \
com.coralblocks.coralconfig.example.TcpClient \
> config-keys.csv
Open the result with a CSV-aware viewer. It will display the data in columns like this:
Field Name Type Default Value Kind Parent Primary Aliases Deprecated Description
CLIENT_SEND_STRATEGY Enum REGULAR PRIMARY
CLIENT_USERNAME String testClient PRIMARY USERNAME
HEARTBEAT Float 4.444 DEPRECATED HEARTBEAT_INTERVAL
HEARTBEAT_INTERVAL Integer 5 PRIMARY HEARTBEAT
MESSAGES_TO_SEND Integer 10 PRIMARY The number of messages to send when the client connects
USERNAME String =REQUIRED= ALIAS CLIENT_USERNAME