Allow calls to update with a default configuration

dev
Exlll 2 years ago
parent 6ee99ff619
commit bff27c06b1

@ -41,40 +41,41 @@ the [Configuration properties](#configuration-properties) section.
```java ```java
public final class Example { public final class Example {
// To create a configuration annotate the class with @Configuration and make sure that // * To create a configuration annotate a class with @Configuration and make sure that
// it has a no-args constructor. That's it! Now you can add fields to it which can all // it has a no-args constructor.
// be private; setters are not required! // * Now add fields to that class and assign them default values.
// * That's it! Fields can be private; setters are not required.
@Configuration @Configuration
public static class BaseConfiguration { public static class BaseConfiguration {
private String host = "127.0.0.1"; private String host = "127.0.0.1";
private int port = 1234; private int port = 1234;
// The library supports lists, sets, and maps. // The library supports lists, sets, and maps.
private Set<String> blockedAddresses = Set.of(); private Set<String> blockedAddresses = Set.of("8.8.8.8");
// Fields can be ignored by making them final, transient, static or by // Fields can be ignored by making them final, transient, static or by
// annotating them with @Ignore. // annotating them with @Ignore.
private final double ignoreMe = 3.14; private final double ignoreMe = 3.14;
} }
// This class does not need to be annotated with @Configuration because it // This library supports records; no @Configuration annotation required
// extends a class which already is! public record User(
String username,
@Comment("Please choose a strong password.")
String password
) {}
// Subclassing of configurations and nesting of configurations in other configurations
// is also supported. Subclasses don't need to be annotated again.
public static final class UserConfiguration extends BaseConfiguration { public static final class UserConfiguration extends BaseConfiguration {
// You can add comments with the @Comment annotation. Each string in the comment // You can add comments with the @Comment annotation. Each string in the comment
// array is written (as a comment) on a new line. // array is written (as a comment) on a new line.
@Comment({"The admin user has full access.", "Choose a proper password!"}) @Comment({"The admin user has full access.", "Choose a proper password!"})
User admin = new User("root", "toor"); // The User class is a @Configuration! User admin = new User("root", "toor"); // The User class is a record!
List<User> blockedUsers = List.of( List<User> blockedUsers = List.of(
new User("user1", null), // null values are supported new User("user1", null), // null values are supported
new User("user2", null) new User("user2", null)
); );
} }
// This library supports records; no @Configuration annotation required
public record User(
String username,
@Comment("Please choose a strong password.")
String password
) {}
public static void main(String[] args) { public static void main(String[] args) {
var configFile = Paths.get("/tmp/config.yml"); var configFile = Paths.get("/tmp/config.yml");
var config = new UserConfiguration(); var config = new UserConfiguration();
@ -100,7 +101,8 @@ looks like this:
```yaml ```yaml
host: 127.0.0.1 host: 127.0.0.1
port: 1234 port: 1234
blockedAddresses: [ ] blockedAddresses:
- 8.8.8.8
# The admin user has full access. # The admin user has full access.
# Choose a proper password! # Choose a proper password!
admin: admin:

@ -85,7 +85,7 @@ public final class Configurations {
* Updates a YAML configuration file with a configuration of the given type using a * Updates a YAML configuration file with a configuration of the given type using a
* {@code YamlConfigurationProperties} object with default values. * {@code YamlConfigurationProperties} object with default values.
* <p> * <p>
* See {@link YamlConfigurationStore#save(Object, Path)} for an explanation of how the update is * See {@link YamlConfigurationStore#save(Object, Path)} for an explanation of how the update is // TODO: change #save to #update
* done. * done.
* *
* @param configurationFile the configuration file that is updated * @param configurationFile the configuration file that is updated

@ -45,6 +45,25 @@ public interface FileConfigurationStore<T> {
* @throws ConfigurationException if the configuration cannot be deserialized * @throws ConfigurationException if the configuration cannot be deserialized
* @throws NullPointerException if {@code configurationFile} is null * @throws NullPointerException if {@code configurationFile} is null
* @throws RuntimeException if loading or saving the configuration throws an exception * @throws RuntimeException if loading or saving the configuration throws an exception
* @see #update(Path, Object)
*/ */
T update(Path configurationFile); T update(Path configurationFile);
/**
* Updates the configuration file. If the file does not exist, it is created and populated
* with values taken from the {@code defaultConfiguration} object.
* Otherwise, if the file exists, a new configuration instance is created, initialized with the
* values taken from the configuration file, and immediately saved to reflect possible changes
* of the configuration type.
*
* @param configurationFile the configuration file that is updated
* @param defaultConfiguration the default value used when creating a new file
* @return a configuration initialized with values taken from the configuration file or
* the default value if the file was newly created
* @throws ConfigurationException if the configuration cannot be deserialized
* @throws NullPointerException if any argument is null
* @throws RuntimeException if loading or saving the configuration throws an exception
* @see #update(Path)
*/
T update(Path configurationFile, T defaultConfiguration);
} }

@ -29,7 +29,6 @@ import static de.exlll.configlib.Validator.requireNonNull;
public final class YamlConfigurationStore<T> implements FileConfigurationStore<T> { public final class YamlConfigurationStore<T> implements FileConfigurationStore<T> {
private static final Dump YAML_DUMPER = newYamlDumper(); private static final Dump YAML_DUMPER = newYamlDumper();
private static final Load YAML_LOADER = newYamlLoader(); private static final Load YAML_LOADER = newYamlLoader();
private final Class<T> configurationType;
private final YamlConfigurationProperties properties; private final YamlConfigurationProperties properties;
private final TypeSerializer<T, ?> serializer; private final TypeSerializer<T, ?> serializer;
private final CommentNodeExtractor extractor; private final CommentNodeExtractor extractor;
@ -42,7 +41,7 @@ public final class YamlConfigurationStore<T> implements FileConfigurationStore<T
* @throws NullPointerException if any argument is null * @throws NullPointerException if any argument is null
*/ */
public YamlConfigurationStore(Class<T> configurationType, YamlConfigurationProperties properties) { public YamlConfigurationStore(Class<T> configurationType, YamlConfigurationProperties properties) {
this.configurationType = requireNonNull(configurationType, "configuration type"); requireNonNull(configurationType, "configuration type");
this.properties = requireNonNull(properties, "properties"); this.properties = requireNonNull(properties, "properties");
this.serializer = TypeSerializer.newSerializerFor(configurationType, properties); this.serializer = TypeSerializer.newSerializerFor(configurationType, properties);
this.extractor = new CommentNodeExtractor(properties); this.extractor = new CommentNodeExtractor(properties);
@ -111,14 +110,18 @@ public final class YamlConfigurationStore<T> implements FileConfigurationStore<T
@Override @Override
public T update(Path configurationFile) { public T update(Path configurationFile) {
return update(configurationFile, serializer.newDefaultInstance());
}
@Override
public T update(Path configurationFile, T defaultConfiguration) {
if (Files.exists(configurationFile)) { if (Files.exists(configurationFile)) {
T configuration = load(configurationFile); T configuration = load(configurationFile);
save(configuration, configurationFile); save(configuration, configurationFile);
return configuration; return configuration;
} }
T configuration = serializer.newDefaultInstance(); save(defaultConfiguration, configurationFile);
save(configuration, configurationFile); return defaultConfiguration;
return configuration;
} }
static Dump newYamlDumper() { static Dump newYamlDumper() {

@ -251,6 +251,13 @@ class YamlConfigurationStoreTest {
static final class E { static final class E {
int i = 10; int i = 10;
int j = 11; int j = 11;
public E() {}
public E(int i, int j) {
this.i = i;
this.j = j;
}
} }
@Test @Test
@ -331,6 +338,82 @@ class YamlConfigurationStoreTest {
assertEquals("i: 20\nj: 0", readFile(yamlFile)); assertEquals("i: 20\nj: 0", readFile(yamlFile));
} }
@Test
void updateWithDefaultCreatesConfigurationFileIfItDoesNotExist() {
final YamlConfigurationStore<E> store = newDefaultStore(E.class);
final E defaultConfig = new E(20, 21);
assertFalse(Files.exists(yamlFile));
E config = store.update(yamlFile, defaultConfig);
assertEquals("i: 20\nj: 21", readFile(yamlFile));
assertSame(defaultConfig, config);
}
@Test
void updateWithDefaultCreatesConfigurationFileIfItDoesNotExistRecord() {
record R(int i, char c, String s) {}
final YamlConfigurationStore<R> store = new YamlConfigurationStore<>(
R.class,
YamlConfigurationProperties.newBuilder().outputNulls(true).build()
);
final R defaultConfig = new R(1, 'a', "s");
assertFalse(Files.exists(yamlFile));
R config = store.update(yamlFile, defaultConfig);
assertEquals("i: 1\nc: a\ns: s", readFile(yamlFile));
assertSame(defaultConfig, config);
}
@Test
void updateWithDefaultLoadsConfigurationFileIfItDoesExist() throws IOException {
final YamlConfigurationStore<E> store = newDefaultStore(E.class);
final E defaultConfig = new E(100, 200);
Files.writeString(yamlFile, "i: 20");
E config = store.update(yamlFile, defaultConfig);
assertEquals(20, config.i);
assertEquals(11, config.j);
assertNotSame(defaultConfig, config);
}
@Test
void updateWithDefaultLoadsConfigurationFileIfItDoesExistRecord() throws IOException {
record R(int i, int j) {}
final YamlConfigurationStore<R> store = newDefaultStore(R.class);
final R defaultConfig = new R(100, 200);
Files.writeString(yamlFile, "i: 20");
R config = store.update(yamlFile, defaultConfig);
assertEquals(20, config.i);
assertEquals(0, config.j);
}
@Test
void updateWithDefaultUpdatesFile() throws IOException {
final YamlConfigurationStore<E> store = newDefaultStore(E.class);
final E defaultConfig = new E(100, 200);
Files.writeString(yamlFile, "i: 20\nk: 30");
E config = store.update(yamlFile, defaultConfig);
assertEquals(20, config.i);
assertEquals(11, config.j);
assertEquals("i: 20\nj: 11", readFile(yamlFile));
}
@Test
void updateWithDefaultUpdatesFileRecord() throws IOException {
record R(int i, int j) {}
final YamlConfigurationStore<R> store = newDefaultStore(R.class);
final R defaultConfig = new R(100, 200);
Files.writeString(yamlFile, "i: 20\nk: 30");
R config = store.update(yamlFile, defaultConfig);
assertEquals(20, config.i);
assertEquals(0, config.j);
assertEquals("i: 20\nj: 0", readFile(yamlFile));
}
private static <T> YamlConfigurationStore<T> newDefaultStore(Class<T> configType) { private static <T> YamlConfigurationStore<T> newDefaultStore(Class<T> configType) {
YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder().build(); YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder().build();
return new YamlConfigurationStore<>(configType, properties); return new YamlConfigurationStore<>(configType, properties);

Loading…
Cancel
Save