diff --git a/source/net/yacy/kelondro/util/FileUtils.java b/source/net/yacy/kelondro/util/FileUtils.java index 6948eaedd..29c930d97 100644 --- a/source/net/yacy/kelondro/util/FileUtils.java +++ b/source/net/yacy/kelondro/util/FileUtils.java @@ -56,6 +56,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.zip.GZIPInputStream; @@ -539,7 +540,7 @@ public final class FileUtils { final Object lock = SAVE_MAP_LOCKS.computeIfAbsent(lockKey, k -> new Object()); synchronized (lock) { /* Take the snapshot in write order so delayed callers cannot persist stale state. */ - final Map propsCopy = new HashMap<>(props); + final Map propsCopy = new TreeMap<>(props); File tf = null; PrintWriter pw = null; try { diff --git a/test/java/net/yacy/kelondro/util/FileUtilsTest.java b/test/java/net/yacy/kelondro/util/FileUtilsTest.java index 7d6ced99e..e962883ba 100644 --- a/test/java/net/yacy/kelondro/util/FileUtilsTest.java +++ b/test/java/net/yacy/kelondro/util/FileUtilsTest.java @@ -23,12 +23,18 @@ package net.yacy.kelondro.util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.io.StringWriter; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.junit.Assert; import org.junit.BeforeClass; @@ -258,4 +264,31 @@ public class FileUtilsTest { Assert.assertEquals(sourceBytes.length, FileUtils.read(source, sourceBytes.length + 10).length); } } + + /** + * Test saving a map to a file writes keys in a stable alphabetical order. + * @throws IOException when a read/write error occurred + */ + @Test + public void testSaveMapSortedKeys() throws IOException { + final Map props = new HashMap<>(); + props.put("zeta", "1"); + props.put("alpha", "2"); + props.put("mu", "3"); + props.put("beta", "4"); + final File file = File.createTempFile("FileUtilsTest", ".conf"); + try { + FileUtils.saveMap(file, props, "test"); + final List keys = new ArrayList<>(); + for (final String line : Files.readAllLines(file.toPath(), StandardCharsets.UTF_8)) { + if (!line.startsWith("#")) { + keys.add(line.substring(0, line.indexOf('='))); + } + } + Assert.assertEquals(List.of("alpha", "beta", "mu", "zeta"), keys); + Assert.assertEquals(props, FileUtils.loadMap(file)); + } finally { + file.delete(); + } + } }