diff --git a/src/main/java/org/codejive/properties/Properties.java b/src/main/java/org/codejive/properties/Properties.java
index 1d264bf..d6087c2 100644
--- a/src/main/java/org/codejive/properties/Properties.java
+++ b/src/main/java/org/codejive/properties/Properties.java
@@ -13,6 +13,8 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
/**
* This class is a replacement for java.util.Properties, with the difference that it
@@ -36,6 +38,15 @@ public Properties(Properties defaults) {
tokens = new ArrayList<>();
}
+ private Properties(Properties defaults, List tokens) {
+ this.defaults = defaults;
+ values = new LinkedHashMap<>();
+ this.tokens = tokens;
+ rawEntrySet().forEach(e -> {
+ values.put(unescape(e.getKey()), unescape(e.getValue()));
+ });
+ }
+
/**
* Searches for the property with the specified key in this property list. If the key is not
* found in this property list, the default property list, and its defaults, recursively, are
@@ -186,12 +197,24 @@ public void storeToXML(OutputStream os, String comment, String encoding) throws
}
/**
- * Returns the current properties table with all its defaults as a single flattened properties
- * table
+ * Returns the current properties table with all its defaults as a single
+ * flattened properties table. NB: Result will have no formatting or comments!
*
* @return a Properties object
+ * @deprecated Use flattened()
*/
+ @Deprecated
public Properties flatten() {
+ return flattened();
+ }
+
+ /**
+ * Returns the current properties table with all its defaults as a single
+ * flattened properties table. NB: Result will have no formatting or comments!
+ *
+ * @return a Properties object
+ */
+ public Properties flattened() {
Properties result = new Properties();
flatten(result);
return result;
@@ -261,12 +284,25 @@ public Set rawKeySet() {
* @return a collection of raw values.
*/
public Collection rawValues() {
- return IntStream.range(0, tokens.size())
- .filter(idx -> tokens.get(idx).type == PropertiesParser.Type.KEY)
- .mapToObj(idx -> tokens.get(idx + 2).getRaw())
+ return combined(tokens)
+ .filter(ts -> ts.get(0).type == PropertiesParser.Type.KEY)
+ .map(ts -> ts.get(2).getRaw())
.collect(Collectors.toList());
}
+ /**
+ * Works like entrySet() but returning the raw values. Meaning that the values have
+ * not been unescaped before being returned.
+ *
+ * @return A set of raw key-value entries
+ */
+ public Set> rawEntrySet() {
+ return combined(tokens)
+ .filter(ts -> ts.get(0).type == PropertiesParser.Type.KEY)
+ .map(ts -> new SimpleEntry<>(ts.get(0).getRaw(), ts.get(2).getRaw()))
+ .collect(Collectors.toCollection(LinkedHashSet::new));
+ }
+
@Override
public String get(Object key) {
return values.get(key);
@@ -296,11 +332,11 @@ public String put(String key, String value) {
if (key == null || value == null) {
throw new NullPointerException();
}
- String rawValue = escape(value, false);
+ String rawValue = escapeValue(value);
if (values.containsKey(key)) {
replaceValue(key, rawValue, value);
} else {
- String rawKey = escape(key, true);
+ String rawKey = escapeKey(key);
addNewKeyValue(rawKey, key, rawValue, value);
}
return values.put(key, value);
@@ -575,23 +611,48 @@ private Cursor indexOf(String key) {
return index(
tokens.indexOf(
new PropertiesParser.Token(
- PropertiesParser.Type.KEY, escape(key, true), key)));
- }
-
- private String escape(String raw, boolean forKey) {
- raw = raw.replace("\n", "\\n");
- raw = raw.replace("\r", "\\r");
- raw = raw.replace("\t", "\\t");
- raw = raw.replace("\f", "\\f");
- if (forKey) {
- raw = raw.replace(" ", "\\ ");
+ PropertiesParser.Type.KEY, escapeKey(key), key)));
+ }
+
+ private static String escapeValue(String value) {
+ return value
+ .replace("\\", "\\\\")
+ .replace("\n", "\\n")
+ .replace("\r", "\\r")
+ .replace("\t", "\\t")
+ .replace("\f", "\\f");
+ }
+
+ private static String escapeKey(String key) {
+ return escapeValue(key).replace(" ", "\\ ");
+ }
+
+ private static String escapeUnicode(String text) {
+ return replace(
+ text,
+ "[^\\x{0000}-\\x{00FF}]",
+ m -> "\\\\u" + String.format("%04x", (int)m.group(0).charAt(0)));
+ }
+
+ private static String unescapeUnicode(String escape) {
+ StringBuilder txt = new StringBuilder();
+ for (int i = 0; i < escape.length(); i++) {
+ char ch = escape.charAt(i);
+ if (ch == '\\') {
+ ch = escape.charAt(++i);
+ if (ch == 'u') {
+ String num = escape.substring(i + 1, i + 5);
+ txt.append((char) Integer.parseInt(num, 16));
+ i += 4;
+ } else {
+ txt.append('\\');
+ txt.append(ch);
+ }
+ } else {
+ txt.append(ch);
+ }
}
- raw =
- replace(
- raw,
- "[^\\x{0000}-\\x{00FF}]",
- m -> "\\\\u" + String.format("%04x", (int)m.group(0).charAt(0)));
- return raw;
+ return txt.toString();
}
private static String replace(String input, String regex, Function callback) {
@@ -609,6 +670,87 @@ private static String replace(String input, Pattern regex, Functionstore() to write to an output that does not support UTF8.
+ *
+ * @return A Properties with encoded keys and values
+ */
+ public Properties escaped() {
+ return new Properties(defaults != null ? defaults.escaped() : null, escapeTokens(tokens));
+ }
+
+ private static List escapeTokens(List tokens) {
+ return mapKeyValues(tokens, ts -> Arrays.asList(escapeToken(ts.get(0)), ts.get(1), escapeToken(ts.get(2))));
+ }
+
+ private static PropertiesParser.Token escapeToken(PropertiesParser.Token token) {
+ String raw = escapeUnicode(token.raw);
+ if (!raw.equals(token.raw)) {
+ token = new PropertiesParser.Token(token.type, raw, token.text);
+ }
+ return token;
+ }
+
+ /**
+ * Returns a copy of the object where all Unicode escape sequences, in keys and values,
+ * have been decoded into their actual Unicode characters. This is useful when using
+ * store() to write to an output that supports UTF8.
+ *
+ * @return A Properties without Unicode escape sequences in its keys and values
+ */
+ public Properties unescaped() {
+ return new Properties(defaults != null ? defaults.unescaped() : null, unescapeTokens(tokens));
+ }
+
+ private static List unescapeTokens(List tokens) {
+ return mapKeyValues(tokens, ts -> Arrays.asList(unescapeToken(ts.get(0)), ts.get(1), unescapeToken(ts.get(2))));
+ }
+
+ private static PropertiesParser.Token unescapeToken(PropertiesParser.Token token) {
+ String raw = unescapeUnicode(token.raw);
+ if (!raw.equals(token.raw)) {
+ token = new PropertiesParser.Token(token.type, raw, token.text);
+ }
+ return token;
+ }
+
+ private static List mapKeyValues(
+ List tokens,
+ Function, List> mapper) {
+ return combined(tokens).map(ts -> {
+ if (ts.get(0).type == PropertiesParser.Type.KEY) {
+ return mapper.apply(ts);
+ } else {
+ return ts;
+ }
+ }).flatMap(Collection::stream).collect(Collectors.toList());
+ }
+
+ private static Stream> combined(List tokens) {
+ Iterator> iter = new Iterator>() {
+ Iterator i = tokens.iterator();
+
+ @Override
+ public boolean hasNext() {
+ return i.hasNext();
+ }
+
+ @Override
+ public List next() {
+ PropertiesParser.Token t = i.next();
+ if (t.type == PropertiesParser.Type.KEY) {
+ return Arrays.asList(t, i.next(), i.next());
+ } else {
+ return Collections.singletonList(t);
+ }
+ }
+ };
+
+ return StreamSupport.stream(Spliterators.spliterator(iter, tokens.size(), Spliterator.SORTED), false);
+ }
+
/**
* Returns a java.util.Properties with the same contents as this object. The
* information is a copy, changes to one Properties object will not affect the other.
diff --git a/src/main/java/org/codejive/properties/PropertiesParser.java b/src/main/java/org/codejive/properties/PropertiesParser.java
index 4f197e7..2388e44 100644
--- a/src/main/java/org/codejive/properties/PropertiesParser.java
+++ b/src/main/java/org/codejive/properties/PropertiesParser.java
@@ -21,7 +21,9 @@ class PropertiesParser {
public enum Type {
/** The key part of a key-value pair */
KEY,
- /** The separator between a key and a value */
+ /** The separator between a key and a value. This will include any whitespace that exists
+ * before and after the separator!
+ */
SEPARATOR,
/** The value part of a key-value pair */
VALUE,
@@ -293,6 +295,13 @@ private String string() {
return result;
}
+ /**
+ * Returns a copy of the given string where all escape sequences
+ * have been turned into their representative values.
+ *
+ * @param escape Input string
+ * @return Decoded string
+ */
static String unescape(String escape) {
StringBuilder txt = new StringBuilder();
for (int i = 0; i < escape.length(); i++) {
diff --git a/src/test/java/org/codejive/properties/TestProperties.java b/src/test/java/org/codejive/properties/TestProperties.java
index dd84047..badd1de 100644
--- a/src/test/java/org/codejive/properties/TestProperties.java
+++ b/src/test/java/org/codejive/properties/TestProperties.java
@@ -7,6 +7,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.AbstractMap;
import java.util.Collections;
import java.util.Iterator;
import org.junit.jupiter.api.Test;
@@ -30,7 +31,7 @@ void testLoad() throws IOException, URISyntaxException {
"everywhere ",
"value",
"one two three",
- "\u1234");
+ "\u1234\u1234");
assertThat(p.rawValues())
.containsExactly(
"simple",
@@ -39,7 +40,25 @@ void testLoad() throws IOException, URISyntaxException {
"everywhere ",
"value",
"one \\\n two \\\n\tthree",
- "\\u1234");
+ "\\u1234\u1234");
+ assertThat(p.entrySet())
+ .containsExactly(
+ new AbstractMap.SimpleEntry<>("one", "simple"),
+ new AbstractMap.SimpleEntry<>("two", "value containing spaces"),
+ new AbstractMap.SimpleEntry<>("three", "and escapes\n\t\r\f"),
+ new AbstractMap.SimpleEntry<>(" with spaces", "everywhere "),
+ new AbstractMap.SimpleEntry<>("altsep", "value"),
+ new AbstractMap.SimpleEntry<>("multiline", "one two three"),
+ new AbstractMap.SimpleEntry<>("key.4", "\u1234\u1234"));
+ assertThat(p.rawEntrySet())
+ .containsExactly(
+ new AbstractMap.SimpleEntry<>("one", "simple"),
+ new AbstractMap.SimpleEntry<>("two", "value containing spaces"),
+ new AbstractMap.SimpleEntry<>("three", "and escapes\\n\\t\\r\\f"),
+ new AbstractMap.SimpleEntry<>("\\ with\\ spaces", "everywhere "),
+ new AbstractMap.SimpleEntry<>("altsep", "value"),
+ new AbstractMap.SimpleEntry<>("multiline", "one \\\n two \\\n\tthree"),
+ new AbstractMap.SimpleEntry<>("key.4", "\\u1234\u1234"));
}
@Test
@@ -69,7 +88,7 @@ void testGet() throws IOException, URISyntaxException {
assertThat(p.get(" with spaces")).isEqualTo("everywhere ");
assertThat(p.get("altsep")).isEqualTo("value");
assertThat(p.get("multiline")).isEqualTo("one two three");
- assertThat(p.get("key.4")).isEqualTo("\u1234");
+ assertThat(p.get("key.4")).isEqualTo("\u1234\u1234");
}
@Test
@@ -102,7 +121,7 @@ void testGetProperty() throws IOException, URISyntaxException {
assertThat(p.getProperty(" with spaces")).isEqualTo("everywhere ");
assertThat(p.getProperty("altsep")).isEqualTo("");
assertThat(p.getProperty("multiline")).isEqualTo("one two three");
- assertThat(p.getProperty("key.4")).isEqualTo("\u1234");
+ assertThat(p.getProperty("key.4")).isEqualTo("\u1234\u1234");
assertThat(p.getProperty("five")).isEqualTo("5");
assertThat(p.getPropertyComment("five")).containsExactly("# a new comment");
StringWriter sw = new StringWriter();
@@ -119,7 +138,7 @@ void testGetRaw() throws IOException, URISyntaxException {
assertThat(p.getRaw(" with spaces")).isEqualTo("everywhere ");
assertThat(p.getRaw("altsep")).isEqualTo("value");
assertThat(p.getRaw("multiline")).isEqualTo("one \\\n two \\\n\tthree");
- assertThat(p.getRaw("key.4")).isEqualTo("\\u1234");
+ assertThat(p.getRaw("key.4")).isEqualTo("\\u1234\u1234");
}
@Test
@@ -154,7 +173,7 @@ void testPut() throws IOException, URISyntaxException {
p.put(" with spaces", "everywhere ");
p.put("altsep", "value");
p.put("multiline", "one two three");
- p.put("key.4", "\u1234");
+ p.put("key.4", "\u1234\u1234");
assertThat(p).size().isEqualTo(7);
assertThat(p.keySet())
.containsExactly(
@@ -170,7 +189,7 @@ void testPut() throws IOException, URISyntaxException {
"everywhere ",
"value",
"one two three",
- "\u1234");
+ "\u1234\u1234");
assertThat(p.rawValues())
.containsExactly(
"simple",
@@ -179,7 +198,25 @@ void testPut() throws IOException, URISyntaxException {
"everywhere ",
"value",
"one two three",
- "\\u1234");
+ "\u1234\u1234");
+ assertThat(p.entrySet())
+ .containsExactly(
+ new AbstractMap.SimpleEntry<>("one", "simple"),
+ new AbstractMap.SimpleEntry<>("two", "value containing spaces"),
+ new AbstractMap.SimpleEntry<>("three", "and escapes\n\t\r\f"),
+ new AbstractMap.SimpleEntry<>(" with spaces", "everywhere "),
+ new AbstractMap.SimpleEntry<>("altsep", "value"),
+ new AbstractMap.SimpleEntry<>("multiline", "one two three"),
+ new AbstractMap.SimpleEntry<>("key.4", "\u1234\u1234"));
+ assertThat(p.rawEntrySet())
+ .containsExactly(
+ new AbstractMap.SimpleEntry<>("one", "simple"),
+ new AbstractMap.SimpleEntry<>("two", "value containing spaces"),
+ new AbstractMap.SimpleEntry<>("three", "and escapes\\n\\t\\r\\f"),
+ new AbstractMap.SimpleEntry<>("\\ with\\ spaces", "everywhere "),
+ new AbstractMap.SimpleEntry<>("altsep", "value"),
+ new AbstractMap.SimpleEntry<>("multiline", "one two three"),
+ new AbstractMap.SimpleEntry<>("key.4", "\u1234\u1234"));
StringWriter sw = new StringWriter();
p.store(sw);
assertThat(sw.toString()).isEqualTo(readAll(getResource("/test-put.properties")));
@@ -195,7 +232,7 @@ void testSetProperty() throws IOException, URISyntaxException {
p.setProperty(" with spaces", "everywhere ");
p.setProperty("altsep", "value");
p.setProperty("multiline", "one two three");
- p.setProperty("key.4", "\u1234");
+ p.setProperty("key.4", "\u1234\u1234");
StringWriter sw = new StringWriter();
p.store(sw);
assertThat(sw.toString()).isEqualTo(readAll(getResource("/test-setproperty.properties")));
@@ -210,7 +247,7 @@ void testPutRaw() throws IOException, URISyntaxException {
p.putRaw("\\ with\\ spaces", "everywhere ");
p.putRaw("altsep", "value");
p.putRaw("multiline", "one \\\n two \\\n\tthree");
- p.putRaw("key.4", "\\u1234");
+ p.putRaw("key.4", "\\u1234\u1234");
assertThat(p).size().isEqualTo(7);
assertThat(p.keySet())
.containsExactly(
@@ -226,7 +263,7 @@ void testPutRaw() throws IOException, URISyntaxException {
"everywhere ",
"value",
"one two three",
- "\u1234");
+ "\u1234\u1234");
assertThat(p.rawValues())
.containsExactly(
"simple",
@@ -235,7 +272,25 @@ void testPutRaw() throws IOException, URISyntaxException {
"everywhere ",
"value",
"one \\\n two \\\n\tthree",
- "\\u1234");
+ "\\u1234\u1234");
+ assertThat(p.entrySet())
+ .containsExactly(
+ new AbstractMap.SimpleEntry<>("one", "simple"),
+ new AbstractMap.SimpleEntry<>("two", "value containing spaces"),
+ new AbstractMap.SimpleEntry<>("three", "and escapes\n\t\r\f"),
+ new AbstractMap.SimpleEntry<>(" with spaces", "everywhere "),
+ new AbstractMap.SimpleEntry<>("altsep", "value"),
+ new AbstractMap.SimpleEntry<>("multiline", "one two three"),
+ new AbstractMap.SimpleEntry<>("key.4", "\u1234\u1234"));
+ assertThat(p.rawEntrySet())
+ .containsExactly(
+ new AbstractMap.SimpleEntry<>("one", "simple"),
+ new AbstractMap.SimpleEntry<>("two", "value containing spaces"),
+ new AbstractMap.SimpleEntry<>("three", "and escapes\\n\\t\\r\\f"),
+ new AbstractMap.SimpleEntry<>("\\ with\\ spaces", "everywhere "),
+ new AbstractMap.SimpleEntry<>("altsep", "value"),
+ new AbstractMap.SimpleEntry<>("multiline", "one \\\n two \\\n\tthree"),
+ new AbstractMap.SimpleEntry<>("key.4", "\\u1234\u1234"));
StringWriter sw = new StringWriter();
p.store(sw);
assertThat(sw.toString()).isEqualTo(readAll(getResource("/test-putraw.properties")));
@@ -330,7 +385,8 @@ void testPutNull() throws IOException, URISyntaxException {
@Test
void testPutUnicode() throws IOException, URISyntaxException {
Properties p = new Properties();
- p.put("test", "الألبانية");
+ p.putRaw("encoded", "\\u0627\\u0644\\u0623\\u0644\\u0628\\u0627\\u0646\\u064a\\u0629");
+ p.put("text", "\u0627\u0644\u0623\u0644\u0628\u0627\u0646\u064a\u0629");
StringWriter sw = new StringWriter();
p.store(sw);
assertThat(sw.toString())
@@ -431,7 +487,7 @@ public void testInteropLoad() throws IOException, URISyntaxException {
"everywhere ",
"value",
"one two three",
- "\u1234");
+ "\u1234\u1234");
}
@Test
@@ -445,7 +501,7 @@ void testInteropStore() throws IOException, URISyntaxException {
assertThat(sw.toString()).contains("\\ with\\ spaces=everywhere \n");
assertThat(sw.toString()).contains("altsep=value\n");
assertThat(sw.toString()).contains("multiline=one two three\n");
- assertThat(sw.toString()).contains("key.4=\u1234\n");
+ assertThat(sw.toString()).contains("key.4=\u1234\u1234\n");
}
@Test
@@ -484,6 +540,22 @@ void testInteropPutLoad() throws IOException, URISyntaxException {
"\u1234");
}
+ @Test
+ void testEscaped() throws IOException, URISyntaxException {
+ Properties p = Properties.loadProperties(getResource("/test.properties"));
+ StringWriter sw = new StringWriter();
+ p.escaped().store(sw);
+ assertThat(sw.toString()).isEqualTo(readAll(getResource("/test-escaped.properties")));
+ }
+
+ @Test
+ void testUnescaped() throws IOException, URISyntaxException {
+ Properties p = Properties.loadProperties(getResource("/test.properties"));
+ StringWriter sw = new StringWriter();
+ p.unescaped().store(sw);
+ assertThat(sw.toString()).isEqualTo(readAll(getResource("/test-unescaped.properties")));
+ }
+
private Path getResource(String name) throws URISyntaxException {
return Paths.get(getClass().getResource(name).toURI());
}
diff --git a/src/test/resources/test-comment.properties b/src/test/resources/test-comment.properties
index b02026c..0e0d3d1 100644
--- a/src/test/resources/test-comment.properties
+++ b/src/test/resources/test-comment.properties
@@ -13,5 +13,5 @@ altsep:value
multiline = one \
two \
three
-key.4 = \u1234
+key.4 = \u1234ሴ
# final comment
diff --git a/src/test/resources/test-escaped.properties b/src/test/resources/test-escaped.properties
new file mode 100644
index 0000000..7c6e49c
--- /dev/null
+++ b/src/test/resources/test-escaped.properties
@@ -0,0 +1,17 @@
+#comment1
+# comment2
+
+! comment3
+one=simple
+two=value containing spaces
+# another comment
+! and a comment
+! block
+three=and escapes\n\t\r\f
+\ with\ spaces = everywhere
+altsep:value
+multiline = one \
+ two \
+ three
+key.4 = \u1234\u1234
+# final comment
diff --git a/src/test/resources/test-getproperty.properties b/src/test/resources/test-getproperty.properties
index fb1e57b..146224a 100644
--- a/src/test/resources/test-getproperty.properties
+++ b/src/test/resources/test-getproperty.properties
@@ -4,5 +4,5 @@ three=and escapes\n\t\r\f
\ with\ spaces=everywhere
altsep=
multiline=one two three
-key.4=\u1234
+key.4=ሴሴ
five=5
\ No newline at end of file
diff --git a/src/test/resources/test-put.properties b/src/test/resources/test-put.properties
index 5c61be4..17e673a 100644
--- a/src/test/resources/test-put.properties
+++ b/src/test/resources/test-put.properties
@@ -4,4 +4,4 @@ three=and escapes\n\t\r\f
\ with\ spaces=everywhere
altsep=value
multiline=one two three
-key.4=\u1234
\ No newline at end of file
+key.4=ሴሴ
\ No newline at end of file
diff --git a/src/test/resources/test-putnew.properties b/src/test/resources/test-putnew.properties
index 69ecb32..5e960de 100644
--- a/src/test/resources/test-putnew.properties
+++ b/src/test/resources/test-putnew.properties
@@ -13,6 +13,6 @@ altsep:value
multiline = one \
two \
three
-key.4 = \u1234
+key.4 = \u1234ሴ
five=5
# final comment
diff --git a/src/test/resources/test-putraw.properties b/src/test/resources/test-putraw.properties
index ff54e36..61b173f 100644
--- a/src/test/resources/test-putraw.properties
+++ b/src/test/resources/test-putraw.properties
@@ -6,4 +6,4 @@ altsep=value
multiline=one \
two \
three
-key.4=\u1234
\ No newline at end of file
+key.4=\u1234ሴ
\ No newline at end of file
diff --git a/src/test/resources/test-putunicode.properties b/src/test/resources/test-putunicode.properties
index 3978ea1..29bca09 100644
--- a/src/test/resources/test-putunicode.properties
+++ b/src/test/resources/test-putunicode.properties
@@ -1 +1,2 @@
-test=\u0627\u0644\u0623\u0644\u0628\u0627\u0646\u064a\u0629
\ No newline at end of file
+encoded=\u0627\u0644\u0623\u0644\u0628\u0627\u0646\u064a\u0629
+text=الألبانية
\ No newline at end of file
diff --git a/src/test/resources/test-removecomment.properties b/src/test/resources/test-removecomment.properties
index f891dec..f3fb147 100644
--- a/src/test/resources/test-removecomment.properties
+++ b/src/test/resources/test-removecomment.properties
@@ -12,5 +12,5 @@ altsep:value
multiline = one \
two \
three
-key.4 = \u1234
+key.4 = \u1234ሴ
# final comment
diff --git a/src/test/resources/test-removefirst.properties b/src/test/resources/test-removefirst.properties
index 88d9407..369c459 100644
--- a/src/test/resources/test-removefirst.properties
+++ b/src/test/resources/test-removefirst.properties
@@ -11,5 +11,5 @@ altsep:value
multiline = one \
two \
three
-key.4 = \u1234
+key.4 = \u1234ሴ
# final comment
diff --git a/src/test/resources/test-removemiddle.properties b/src/test/resources/test-removemiddle.properties
index aaa6461..6d9e10b 100644
--- a/src/test/resources/test-removemiddle.properties
+++ b/src/test/resources/test-removemiddle.properties
@@ -9,5 +9,5 @@ altsep:value
multiline = one \
two \
three
-key.4 = \u1234
+key.4 = \u1234ሴ
# final comment
diff --git a/src/test/resources/test-setproperty.properties b/src/test/resources/test-setproperty.properties
index d1e31fc..e4433fa 100644
--- a/src/test/resources/test-setproperty.properties
+++ b/src/test/resources/test-setproperty.properties
@@ -8,4 +8,4 @@ three=and escapes\n\t\r\f
\ with\ spaces=everywhere
altsep=value
multiline=one two three
-key.4=\u1234
\ No newline at end of file
+key.4=ሴሴ
\ No newline at end of file
diff --git a/src/test/resources/test-storeheader.properties b/src/test/resources/test-storeheader.properties
index 7ca6eec..3ab1ef7 100644
--- a/src/test/resources/test-storeheader.properties
+++ b/src/test/resources/test-storeheader.properties
@@ -12,5 +12,5 @@ altsep:value
multiline = one \
two \
three
-key.4 = \u1234
+key.4 = \u1234ሴ
# final comment
diff --git a/src/test/resources/test-unescaped.properties b/src/test/resources/test-unescaped.properties
new file mode 100644
index 0000000..bac7a7f
--- /dev/null
+++ b/src/test/resources/test-unescaped.properties
@@ -0,0 +1,17 @@
+#comment1
+# comment2
+
+! comment3
+one=simple
+two=value containing spaces
+# another comment
+! and a comment
+! block
+three=and escapes\n\t\r\f
+\ with\ spaces = everywhere
+altsep:value
+multiline = one \
+ two \
+ three
+key.4 = ሴሴ
+# final comment
diff --git a/src/test/resources/test.properties b/src/test/resources/test.properties
index ed2a301..f2d73bd 100644
--- a/src/test/resources/test.properties
+++ b/src/test/resources/test.properties
@@ -13,5 +13,5 @@ altsep:value
multiline = one \
two \
three
-key.4 = \u1234
+key.4 = \u1234ሴ
# final comment