From dcb8aafb4aac6ca09e8b9d14910357d5b99d9bbc Mon Sep 17 00:00:00 2001 From: William Date: Fri, 26 Jan 2024 13:06:42 +0000 Subject: [PATCH] Add builder option to specify charset for file encoding (#28) * feat: Make read/write charset configurable, default to UTF-8 * refactor: Make charset final in FileConfigurationProperties * test: add unit tests * refactor: use system/environment encoding by default * test: fix spacing in test class * test: removed unused import --- .../FileConfigurationProperties.java | 29 +++++++++++ .../FileConfigurationPropertiesTest.java | 7 +++ .../java/de/exlll/configlib/TestUtils.java | 12 +++-- .../configlib/YamlConfigurationStore.java | 2 +- .../java/de/exlll/configlib/YamlWriter.java | 3 +- .../de/exlll/configlib/YamlWriterTest.java | 50 +++++++++++++++++-- 6 files changed, 95 insertions(+), 8 deletions(-) diff --git a/configlib-core/src/main/java/de/exlll/configlib/FileConfigurationProperties.java b/configlib-core/src/main/java/de/exlll/configlib/FileConfigurationProperties.java index cd8b66b..bdff1b0 100644 --- a/configlib-core/src/main/java/de/exlll/configlib/FileConfigurationProperties.java +++ b/configlib-core/src/main/java/de/exlll/configlib/FileConfigurationProperties.java @@ -1,5 +1,7 @@ package de.exlll.configlib; +import java.nio.charset.Charset; + /** * An extension of the {@code ConfigurationProperties} class that allows configuring properties * that are more specific to files. @@ -8,6 +10,7 @@ public class FileConfigurationProperties extends ConfigurationProperties { private final String header; private final String footer; private final boolean createParentDirectories; + private final Charset charset; /** * Constructs a new instance of this class with values taken from the given builder. @@ -20,6 +23,7 @@ protected FileConfigurationProperties(Builder builder) { this.header = builder.header; this.footer = builder.footer; this.createParentDirectories = builder.createParentDirectories; + this.charset = builder.charset; } /** @@ -57,6 +61,7 @@ public static abstract class Builder> private String header = null; private String footer = null; private boolean createParentDirectories = true; + private Charset charset = Charset.defaultCharset(); /** * The default constructor. @@ -74,6 +79,7 @@ protected Builder(FileConfigurationProperties properties) { this.header = properties.header; this.footer = properties.footer; this.createParentDirectories = properties.createParentDirectories; + this.charset = properties.charset; } /** @@ -113,6 +119,19 @@ public final B createParentDirectories(boolean createParentDirectories) { return getThis(); } + /** + * Sets the charset used to read and write configuration files. + *

+ * Defaults to the system's default charset ({@code Charset.defaultCharset()}). + * + * @param charset the charset + * @return this builder + */ + public final B charset(Charset charset) { + this.charset = charset; + return getThis(); + } + /** * Builds a {@code ConfigurationProperties} instance. * @@ -155,4 +174,14 @@ public final String getFooter() { public final boolean createParentDirectories() { return createParentDirectories; } + + /** + * Returns the charset used to read and write configuration files. + * + * @return the charset + */ + public final Charset getCharset() { + return charset; + } + } diff --git a/configlib-core/src/test/java/de/exlll/configlib/FileConfigurationPropertiesTest.java b/configlib-core/src/test/java/de/exlll/configlib/FileConfigurationPropertiesTest.java index e821c2c..4fb9f22 100644 --- a/configlib-core/src/test/java/de/exlll/configlib/FileConfigurationPropertiesTest.java +++ b/configlib-core/src/test/java/de/exlll/configlib/FileConfigurationPropertiesTest.java @@ -1,6 +1,8 @@ package de.exlll.configlib; import org.junit.jupiter.api.Test; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -13,6 +15,7 @@ void builderDefaultValues() { assertNull(properties.getHeader()); assertNull(properties.getFooter()); assertTrue(properties.createParentDirectories()); + assertEquals(Charset.defaultCharset(), properties.getCharset()); } @Test @@ -21,10 +24,12 @@ void builderCopiesValues() { .header("THE HEADER") .footer("THE FOOTER") .createParentDirectories(false) + .charset(StandardCharsets.ISO_8859_1) .build(); assertEquals("THE HEADER", properties.getHeader()); assertEquals("THE FOOTER", properties.getFooter()); assertFalse(properties.createParentDirectories()); + assertEquals(StandardCharsets.ISO_8859_1, properties.getCharset()); } @Test @@ -34,6 +39,7 @@ void builderCtorCopiesValues() { .header("A") .footer("B") .createParentDirectories(false) + .charset(StandardCharsets.ISO_8859_1) .build() .toBuilder() .build(); @@ -42,5 +48,6 @@ void builderCtorCopiesValues() { assertThat(properties.getHeader(), is("A")); assertThat(properties.getFooter(), is("B")); assertThat(properties.createParentDirectories(), is(false)); + assertThat(properties.getCharset(), is(StandardCharsets.ISO_8859_1)); } } \ No newline at end of file diff --git a/configlib-core/src/testFixtures/java/de/exlll/configlib/TestUtils.java b/configlib-core/src/testFixtures/java/de/exlll/configlib/TestUtils.java index 8ed490f..2ab1d3c 100644 --- a/configlib-core/src/testFixtures/java/de/exlll/configlib/TestUtils.java +++ b/configlib-core/src/testFixtures/java/de/exlll/configlib/TestUtils.java @@ -3,13 +3,15 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.function.Executable; -import java.awt.Point; +import java.awt.*; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.math.BigInteger; +import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.*; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -276,14 +278,18 @@ public static > boolean collectionOfArraysDeepEqual return c1.equals(c2); } - public static String readFile(Path file) { + public static String readFile(Path file, Charset charset) { try { - return Files.readString(file); + return Files.readString(file, charset); } catch (IOException e) { throw new RuntimeException(e); } } + public static String readFile(Path file) { + return readFile(file, Charset.defaultCharset()); + } + public static ConfigurationElement fieldAsElement(Class type, String fieldName) { Field field = getField(type, fieldName); return new ConfigurationElements.FieldElement(field); diff --git a/configlib-yaml/src/main/java/de/exlll/configlib/YamlConfigurationStore.java b/configlib-yaml/src/main/java/de/exlll/configlib/YamlConfigurationStore.java index 9658613..e9f9ae4 100644 --- a/configlib-yaml/src/main/java/de/exlll/configlib/YamlConfigurationStore.java +++ b/configlib-yaml/src/main/java/de/exlll/configlib/YamlConfigurationStore.java @@ -126,7 +126,7 @@ public T read(InputStream inputStream) { @Override public T load(Path configurationFile) { requireNonNull(configurationFile, "configuration file"); - try (var reader = Files.newBufferedReader(configurationFile)) { + try (var reader = Files.newBufferedReader(configurationFile, properties.getCharset())) { var yaml = YAML_LOADER.loadFromReader(reader); var conf = requireYamlMapForLoad(yaml, configurationFile); return serializer.deserialize(conf); diff --git a/configlib-yaml/src/main/java/de/exlll/configlib/YamlWriter.java b/configlib-yaml/src/main/java/de/exlll/configlib/YamlWriter.java index 7addc7b..b74fe72 100644 --- a/configlib-yaml/src/main/java/de/exlll/configlib/YamlWriter.java +++ b/configlib-yaml/src/main/java/de/exlll/configlib/YamlWriter.java @@ -36,7 +36,8 @@ final class YamlWriter { } public void writeYaml(String yaml, Queue nodes) { - try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream))) { + try (BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter(outputStream, properties.getCharset()))) { this.writer = writer; writeHeader(); writeContent(yaml, nodes); diff --git a/configlib-yaml/src/test/java/de/exlll/configlib/YamlWriterTest.java b/configlib-yaml/src/test/java/de/exlll/configlib/YamlWriterTest.java index c485601..e27fd0c 100644 --- a/configlib-yaml/src/test/java/de/exlll/configlib/YamlWriterTest.java +++ b/configlib-yaml/src/test/java/de/exlll/configlib/YamlWriterTest.java @@ -8,6 +8,8 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; @@ -499,16 +501,20 @@ void lengthCommonPrefix() { assertEquals(0, YamlWriter.lengthCommonPrefix(abcd, def)); } - String readFile() { - return TestUtils.readFile(yamlFile); + String readFile(Charset charset) { + return TestUtils.readFile(yamlFile, charset); } String readOutputStream() { return outputStream.toString(); } + void assertFileContentEquals(String expected, Charset charset) { + assertEquals(expected, readFile(charset)); + } + void assertFileContentEquals(String expected) { - assertEquals(expected, readFile()); + assertFileContentEquals(expected, Charset.defaultCharset()); } void assertStreamContentEquals(String expected) { @@ -566,4 +572,42 @@ static YamlWriterArguments argsFromConfig( Queue nodes = extractor.extractCommentNodes(c); return new YamlWriterArguments(yaml, nodes, properties); } + + @Configuration + static class N { + @Comment("テスト") + String s = "テスト test"; + } + + @Test + void writeYamlToFileInUTF8WithUnicodeCharacters() { + Consumer> builderConsumer = builder -> builder + .charset(StandardCharsets.UTF_8); + + writeConfigToFile(N.class, builderConsumer); + + String expected = """ + # テスト + s: テスト test + """; + + assertFileContentEquals(expected, StandardCharsets.UTF_8); + } + + @Test + void writeYamlToFileInASCIIWithUnicodeCharacters() { + Consumer> builderConsumer = builder -> builder + .charset(StandardCharsets.US_ASCII); + + writeConfigToFile(N.class, builderConsumer); + + // UTF-8 characters will be replaced with question mark points + String expected = """ + # ??? + s: ??? test + """; + + assertFileContentEquals(expected, StandardCharsets.US_ASCII); + } + }