This is an automated email from the ASF dual-hosted git repository.

wanghailin pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git


The following commit(s) were added to refs/heads/dev by this push:
     new 88be4fd236 [Hotfix][Config] Fix configuration key sort disorder (#7893)
88be4fd236 is described below

commit 88be4fd236e82b5cb7c31d311c1a4741398fb5eb
Author: hailin0 <[email protected]>
AuthorDate: Fri Oct 25 10:53:39 2024 +0800

    [Hotfix][Config] Fix configuration key sort disorder (#7893)
---
 seatunnel-config/seatunnel-config-base/pom.xml     |  18 +
 .../shade/com/typesafe/config/impl/ConfigImpl.java | 471 +++++++++++++++++++++
 .../typesafe/config/impl/SimpleConfigObject.java   |  10 +-
 .../org/apache/seatunnel/config/ConfigTest.java    |  43 ++
 .../core/starter/utils/ConfigBuilder.java          |   6 +-
 .../core/starter/utils/ConfigBuilderTest.java      |  45 ++
 6 files changed, 585 insertions(+), 8 deletions(-)

diff --git a/seatunnel-config/seatunnel-config-base/pom.xml 
b/seatunnel-config/seatunnel-config-base/pom.xml
index 6c75e35cbd..5610cab85e 100644
--- a/seatunnel-config/seatunnel-config-base/pom.xml
+++ b/seatunnel-config/seatunnel-config-base/pom.xml
@@ -69,11 +69,29 @@
                                 
<exclude>com/typesafe/config/ConfigParseOptions.class</exclude>
                                 
<exclude>com/typesafe/config/ConfigMergeable.class</exclude>
                                 
<exclude>com/typesafe/config/impl/ConfigParser.class</exclude>
+                                
<exclude>com/typesafe/config/impl/ConfigParser$1.class</exclude>
+                                
<exclude>com/typesafe/config/impl/ConfigParser$ParseContext.class</exclude>
                                 
<exclude>com/typesafe/config/impl/ConfigNodePath.class</exclude>
                                 
<exclude>com/typesafe/config/impl/PathParser.class</exclude>
+                                
<exclude>com/typesafe/config/impl/PathParser$Element.class</exclude>
                                 
<exclude>com/typesafe/config/impl/Path.class</exclude>
                                 
<exclude>com/typesafe/config/impl/SimpleConfigObject.class</exclude>
+                                
<exclude>com/typesafe/config/impl/SimpleConfigObject$1.class</exclude>
+                                
<exclude>com/typesafe/config/impl/SimpleConfigObject$RenderComparator.class</exclude>
+                                
<exclude>com/typesafe/config/impl/SimpleConfigObject$ResolveModifier.class</exclude>
                                 
<exclude>com/typesafe/config/impl/PropertiesParser.class</exclude>
+                                
<exclude>com/typesafe/config/impl/PropertiesParser$1.class</exclude>
+                                
<exclude>com/typesafe/config/impl/ConfigImpl.class</exclude>
+                                
<exclude>com/typesafe/config/impl/ConfigImpl$1.class</exclude>
+                                
<exclude>com/typesafe/config/impl/ConfigImpl$ClasspathNameSource.class</exclude>
+                                
<exclude>com/typesafe/config/impl/ConfigImpl$ClasspathNameSourceWithClass.class</exclude>
+                                
<exclude>com/typesafe/config/impl/ConfigImpl$DebugHolder.class</exclude>
+                                
<exclude>com/typesafe/config/impl/ConfigImpl$DefaultIncluderHolder.class</exclude>
+                                
<exclude>com/typesafe/config/impl/ConfigImpl$EnvVariablesHolder.class</exclude>
+                                
<exclude>com/typesafe/config/impl/ConfigImpl$FileNameSource.class</exclude>
+                                
<exclude>com/typesafe/config/impl/ConfigImpl$LoaderCache.class</exclude>
+                                
<exclude>com/typesafe/config/impl/ConfigImpl$LoaderCacheHolder.class</exclude>
+                                
<exclude>com/typesafe/config/impl/ConfigImpl$SystemPropertiesHolder.class</exclude>
                             </excludes>
                         </filter>
                     </filters>
diff --git 
a/seatunnel-config/seatunnel-config-shade/src/main/java/org/apache/seatunnel/shade/com/typesafe/config/impl/ConfigImpl.java
 
b/seatunnel-config/seatunnel-config-shade/src/main/java/org/apache/seatunnel/shade/com/typesafe/config/impl/ConfigImpl.java
new file mode 100644
index 0000000000..f078897ed2
--- /dev/null
+++ 
b/seatunnel-config/seatunnel-config-shade/src/main/java/org/apache/seatunnel/shade/com/typesafe/config/impl/ConfigImpl.java
@@ -0,0 +1,471 @@
+/*
+ *   Copyright (C) 2011-2012 Typesafe Inc. <http://typesafe.com>
+ */
+
+package org.apache.seatunnel.shade.com.typesafe.config.impl;
+
+import org.apache.seatunnel.shade.com.typesafe.config.Config;
+import org.apache.seatunnel.shade.com.typesafe.config.ConfigException;
+import org.apache.seatunnel.shade.com.typesafe.config.ConfigIncluder;
+import org.apache.seatunnel.shade.com.typesafe.config.ConfigMemorySize;
+import org.apache.seatunnel.shade.com.typesafe.config.ConfigObject;
+import org.apache.seatunnel.shade.com.typesafe.config.ConfigOrigin;
+import org.apache.seatunnel.shade.com.typesafe.config.ConfigParseOptions;
+import org.apache.seatunnel.shade.com.typesafe.config.ConfigParseable;
+import org.apache.seatunnel.shade.com.typesafe.config.ConfigValue;
+
+import java.io.File;
+import java.lang.ref.WeakReference;
+import java.net.URL;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.Callable;
+
+/**
+ * Internal implementation detail, not ABI stable, do not touch. For use only 
by the {@link
+ * com.typesafe.config} package.
+ */
+public class ConfigImpl {
+
+    private static class LoaderCache {
+        private Config currentSystemProperties;
+        private WeakReference<ClassLoader> currentLoader;
+        private Map<String, Config> cache;
+
+        LoaderCache() {
+            this.currentSystemProperties = null;
+            this.currentLoader = new WeakReference<ClassLoader>(null);
+            this.cache = new LinkedHashMap<String, Config>();
+        }
+
+        // for now, caching as long as the loader remains the same,
+        // drop entire cache if it changes.
+        synchronized Config getOrElseUpdate(
+                ClassLoader loader, String key, Callable<Config> updater) {
+            if (loader != currentLoader.get()) {
+                // reset the cache if we start using a different loader
+                cache.clear();
+                currentLoader = new WeakReference<ClassLoader>(loader);
+            }
+
+            Config systemProperties = systemPropertiesAsConfig();
+            if (systemProperties != currentSystemProperties) {
+                cache.clear();
+                currentSystemProperties = systemProperties;
+            }
+
+            Config config = cache.get(key);
+            if (config == null) {
+                try {
+                    config = updater.call();
+                } catch (RuntimeException e) {
+                    throw e; // this will include ConfigException
+                } catch (Exception e) {
+                    throw new ConfigException.Generic(e.getMessage(), e);
+                }
+                if (config == null)
+                    throw new ConfigException.BugOrBroken("null config from 
cache updater");
+                cache.put(key, config);
+            }
+
+            return config;
+        }
+    }
+
+    private static class LoaderCacheHolder {
+        static final LoaderCache cache = new LoaderCache();
+    }
+
+    public static Config computeCachedConfig(
+            ClassLoader loader, String key, Callable<Config> updater) {
+        LoaderCache cache;
+        try {
+            cache = LoaderCacheHolder.cache;
+        } catch (ExceptionInInitializerError e) {
+            throw ConfigImplUtil.extractInitializerError(e);
+        }
+        return cache.getOrElseUpdate(loader, key, updater);
+    }
+
+    static class FileNameSource implements SimpleIncluder.NameSource {
+        @Override
+        public ConfigParseable nameToParseable(String name, ConfigParseOptions 
parseOptions) {
+            return Parseable.newFile(new File(name), parseOptions);
+        }
+    };
+
+    static class ClasspathNameSource implements SimpleIncluder.NameSource {
+        @Override
+        public ConfigParseable nameToParseable(String name, ConfigParseOptions 
parseOptions) {
+            return Parseable.newResources(name, parseOptions);
+        }
+    };
+
+    static class ClasspathNameSourceWithClass implements 
SimpleIncluder.NameSource {
+        private final Class<?> klass;
+
+        public ClasspathNameSourceWithClass(Class<?> klass) {
+            this.klass = klass;
+        }
+
+        @Override
+        public ConfigParseable nameToParseable(String name, ConfigParseOptions 
parseOptions) {
+            return Parseable.newResources(klass, name, parseOptions);
+        }
+    };
+
+    public static ConfigObject parseResourcesAnySyntax(
+            Class<?> klass, String resourceBasename, ConfigParseOptions 
baseOptions) {
+        SimpleIncluder.NameSource source = new 
ClasspathNameSourceWithClass(klass);
+        return SimpleIncluder.fromBasename(source, resourceBasename, 
baseOptions);
+    }
+
+    public static ConfigObject parseResourcesAnySyntax(
+            String resourceBasename, ConfigParseOptions baseOptions) {
+        SimpleIncluder.NameSource source = new ClasspathNameSource();
+        return SimpleIncluder.fromBasename(source, resourceBasename, 
baseOptions);
+    }
+
+    public static ConfigObject parseFileAnySyntax(File basename, 
ConfigParseOptions baseOptions) {
+        SimpleIncluder.NameSource source = new FileNameSource();
+        return SimpleIncluder.fromBasename(source, basename.getPath(), 
baseOptions);
+    }
+
+    static AbstractConfigObject emptyObject(String originDescription) {
+        ConfigOrigin origin =
+                originDescription != null ? 
SimpleConfigOrigin.newSimple(originDescription) : null;
+        return emptyObject(origin);
+    }
+
+    public static Config emptyConfig(String originDescription) {
+        return emptyObject(originDescription).toConfig();
+    }
+
+    static AbstractConfigObject empty(ConfigOrigin origin) {
+        return emptyObject(origin);
+    }
+
+    // default origin for values created with fromAnyRef and no origin 
specified
+    private static final ConfigOrigin defaultValueOrigin =
+            SimpleConfigOrigin.newSimple("hardcoded value");
+    private static final ConfigBoolean defaultTrueValue =
+            new ConfigBoolean(defaultValueOrigin, true);
+    private static final ConfigBoolean defaultFalseValue =
+            new ConfigBoolean(defaultValueOrigin, false);
+    private static final ConfigNull defaultNullValue = new 
ConfigNull(defaultValueOrigin);
+    private static final SimpleConfigList defaultEmptyList =
+            new SimpleConfigList(defaultValueOrigin, 
Collections.<AbstractConfigValue>emptyList());
+    private static final SimpleConfigObject defaultEmptyObject =
+            SimpleConfigObject.empty(defaultValueOrigin);
+
+    private static SimpleConfigList emptyList(ConfigOrigin origin) {
+        if (origin == null || origin == defaultValueOrigin) return 
defaultEmptyList;
+        else return new SimpleConfigList(origin, 
Collections.<AbstractConfigValue>emptyList());
+    }
+
+    private static AbstractConfigObject emptyObject(ConfigOrigin origin) {
+        // we want null origin to go to SimpleConfigObject.empty() to get the
+        // origin "empty config" rather than "hardcoded value"
+        if (origin == defaultValueOrigin) return defaultEmptyObject;
+        else return SimpleConfigObject.empty(origin);
+    }
+
+    private static ConfigOrigin valueOrigin(String originDescription) {
+        if (originDescription == null) return defaultValueOrigin;
+        else return SimpleConfigOrigin.newSimple(originDescription);
+    }
+
+    public static ConfigValue fromAnyRef(Object object, String 
originDescription) {
+        ConfigOrigin origin = valueOrigin(originDescription);
+        return fromAnyRef(object, origin, FromMapMode.KEYS_ARE_KEYS);
+    }
+
+    public static ConfigObject fromPathMap(
+            Map<String, ? extends Object> pathMap, String originDescription) {
+        ConfigOrigin origin = valueOrigin(originDescription);
+        return (ConfigObject) fromAnyRef(pathMap, origin, 
FromMapMode.KEYS_ARE_PATHS);
+    }
+
+    static AbstractConfigValue fromAnyRef(Object object, ConfigOrigin origin, 
FromMapMode mapMode) {
+        if (origin == null) throw new ConfigException.BugOrBroken("origin not 
supposed to be null");
+
+        if (object == null) {
+            if (origin != defaultValueOrigin) return new ConfigNull(origin);
+            else return defaultNullValue;
+        } else if (object instanceof AbstractConfigValue) {
+            return (AbstractConfigValue) object;
+        } else if (object instanceof Boolean) {
+            if (origin != defaultValueOrigin) {
+                return new ConfigBoolean(origin, (Boolean) object);
+            } else if ((Boolean) object) {
+                return defaultTrueValue;
+            } else {
+                return defaultFalseValue;
+            }
+        } else if (object instanceof String) {
+            return new ConfigString.Quoted(origin, (String) object);
+        } else if (object instanceof Number) {
+            // here we always keep the same type that was passed to us,
+            // rather than figuring out if a Long would fit in an Int
+            // or a Double has no fractional part. i.e. deliberately
+            // not using ConfigNumber.newNumber() when we have a
+            // Double, Integer, or Long.
+            if (object instanceof Double) {
+                return new ConfigDouble(origin, (Double) object, null);
+            } else if (object instanceof Integer) {
+                return new ConfigInt(origin, (Integer) object, null);
+            } else if (object instanceof Long) {
+                return new ConfigLong(origin, (Long) object, null);
+            } else {
+                return ConfigNumber.newNumber(origin, ((Number) 
object).doubleValue(), null);
+            }
+        } else if (object instanceof Duration) {
+            return new ConfigLong(origin, ((Duration) object).toMillis(), 
null);
+        } else if (object instanceof Map) {
+            if (((Map<?, ?>) object).isEmpty()) return emptyObject(origin);
+
+            if (mapMode == FromMapMode.KEYS_ARE_KEYS) {
+                Map<String, AbstractConfigValue> values =
+                        new LinkedHashMap<String, AbstractConfigValue>();
+                for (Map.Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) {
+                    Object key = entry.getKey();
+                    if (!(key instanceof String))
+                        throw new ConfigException.BugOrBroken(
+                                "bug in method caller: not valid to create 
ConfigObject from map with non-String key: "
+                                        + key);
+                    AbstractConfigValue value = fromAnyRef(entry.getValue(), 
origin, mapMode);
+                    values.put((String) key, value);
+                }
+
+                return new SimpleConfigObject(origin, values);
+            } else {
+                return PropertiesParser.fromPathMap(origin, (Map<?, ?>) 
object);
+            }
+        } else if (object instanceof Iterable) {
+            Iterator<?> i = ((Iterable<?>) object).iterator();
+            if (!i.hasNext()) return emptyList(origin);
+
+            List<AbstractConfigValue> values = new 
ArrayList<AbstractConfigValue>();
+            while (i.hasNext()) {
+                AbstractConfigValue v = fromAnyRef(i.next(), origin, mapMode);
+                values.add(v);
+            }
+
+            return new SimpleConfigList(origin, values);
+        } else if (object instanceof ConfigMemorySize) {
+            return new ConfigLong(origin, ((ConfigMemorySize) 
object).toBytes(), null);
+        } else {
+            throw new ConfigException.BugOrBroken(
+                    "bug in method caller: not valid to create ConfigValue 
from: " + object);
+        }
+    }
+
+    private static class DefaultIncluderHolder {
+        static final ConfigIncluder defaultIncluder = new SimpleIncluder(null);
+    }
+
+    static ConfigIncluder defaultIncluder() {
+        try {
+            return DefaultIncluderHolder.defaultIncluder;
+        } catch (ExceptionInInitializerError e) {
+            throw ConfigImplUtil.extractInitializerError(e);
+        }
+    }
+
+    private static Properties getSystemProperties() {
+        // Avoid ConcurrentModificationException due to parallel setting of 
system properties by
+        // copying properties
+        final Properties systemProperties = System.getProperties();
+        final Properties systemPropertiesCopy = new Properties();
+        synchronized (systemProperties) {
+            systemPropertiesCopy.putAll(systemProperties);
+        }
+        return systemPropertiesCopy;
+    }
+
+    private static AbstractConfigObject loadSystemProperties() {
+        return (AbstractConfigObject)
+                Parseable.newProperties(
+                                getSystemProperties(),
+                                ConfigParseOptions.defaults()
+                                        .setOriginDescription("system 
properties"))
+                        .parse();
+    }
+
+    private static class SystemPropertiesHolder {
+        // this isn't final due to the reloadSystemPropertiesConfig() hack 
below
+        static volatile AbstractConfigObject systemProperties = 
loadSystemProperties();
+    }
+
+    static AbstractConfigObject systemPropertiesAsConfigObject() {
+        try {
+            return SystemPropertiesHolder.systemProperties;
+        } catch (ExceptionInInitializerError e) {
+            throw ConfigImplUtil.extractInitializerError(e);
+        }
+    }
+
+    public static Config systemPropertiesAsConfig() {
+        return systemPropertiesAsConfigObject().toConfig();
+    }
+
+    public static void reloadSystemPropertiesConfig() {
+        // ConfigFactory.invalidateCaches() relies on this having the side
+        // effect that it drops all caches
+        SystemPropertiesHolder.systemProperties = loadSystemProperties();
+    }
+
+    private static AbstractConfigObject loadEnvVariables() {
+        return PropertiesParser.fromStringMap(newSimpleOrigin("env 
variables"), System.getenv());
+    }
+
+    private static class EnvVariablesHolder {
+        static volatile AbstractConfigObject envVariables = loadEnvVariables();
+    }
+
+    static AbstractConfigObject envVariablesAsConfigObject() {
+        try {
+            return EnvVariablesHolder.envVariables;
+        } catch (ExceptionInInitializerError e) {
+            throw ConfigImplUtil.extractInitializerError(e);
+        }
+    }
+
+    public static Config envVariablesAsConfig() {
+        return envVariablesAsConfigObject().toConfig();
+    }
+
+    public static void reloadEnvVariablesConfig() {
+        // ConfigFactory.invalidateCaches() relies on this having the side
+        // effect that it drops all caches
+        EnvVariablesHolder.envVariables = loadEnvVariables();
+    }
+
+    public static Config defaultReference(final ClassLoader loader) {
+        return computeCachedConfig(
+                loader,
+                "defaultReference",
+                new Callable<Config>() {
+                    @Override
+                    public Config call() {
+                        Config unresolvedResources =
+                                Parseable.newResources(
+                                                "reference.conf",
+                                                ConfigParseOptions.defaults()
+                                                        
.setClassLoader(loader))
+                                        .parse()
+                                        .toConfig();
+                        return systemPropertiesAsConfig()
+                                .withFallback(unresolvedResources)
+                                .resolve();
+                    }
+                });
+    }
+
+    private static class DebugHolder {
+        private static String LOADS = "loads";
+        private static String SUBSTITUTIONS = "substitutions";
+
+        private static Map<String, Boolean> loadDiagnostics() {
+            Map<String, Boolean> result = new LinkedHashMap<String, Boolean>();
+            result.put(LOADS, false);
+            result.put(SUBSTITUTIONS, false);
+
+            // People do -Dconfig.trace=foo,bar to enable tracing of different 
things
+            String s = System.getProperty("config.trace");
+            if (s == null) {
+                return result;
+            } else {
+                String[] keys = s.split(",");
+                for (String k : keys) {
+                    if (k.equals(LOADS)) {
+                        result.put(LOADS, true);
+                    } else if (k.equals(SUBSTITUTIONS)) {
+                        result.put(SUBSTITUTIONS, true);
+                    } else {
+                        System.err.println(
+                                "config.trace property contains unknown trace 
topic '" + k + "'");
+                    }
+                }
+                return result;
+            }
+        }
+
+        private static final Map<String, Boolean> diagnostics = 
loadDiagnostics();
+
+        private static final boolean traceLoadsEnabled = 
diagnostics.get(LOADS);
+        private static final boolean traceSubstitutionsEnabled = 
diagnostics.get(SUBSTITUTIONS);
+
+        static boolean traceLoadsEnabled() {
+            return traceLoadsEnabled;
+        }
+
+        static boolean traceSubstitutionsEnabled() {
+            return traceSubstitutionsEnabled;
+        }
+    }
+
+    public static boolean traceLoadsEnabled() {
+        try {
+            return DebugHolder.traceLoadsEnabled();
+        } catch (ExceptionInInitializerError e) {
+            throw ConfigImplUtil.extractInitializerError(e);
+        }
+    }
+
+    public static boolean traceSubstitutionsEnabled() {
+        try {
+            return DebugHolder.traceSubstitutionsEnabled();
+        } catch (ExceptionInInitializerError e) {
+            throw ConfigImplUtil.extractInitializerError(e);
+        }
+    }
+
+    public static void trace(String message) {
+        System.err.println(message);
+    }
+
+    public static void trace(int indentLevel, String message) {
+        while (indentLevel > 0) {
+            System.err.print("  ");
+            indentLevel -= 1;
+        }
+        System.err.println(message);
+    }
+
+    // the basic idea here is to add the "what" and have a canonical
+    // toplevel error message. the "original" exception may however have extra
+    // detail about what happened. call this if you have a better "what" than
+    // further down on the stack.
+    static ConfigException.NotResolved improveNotResolved(
+            Path what, ConfigException.NotResolved original) {
+        String newMessage =
+                what.render()
+                        + " has not been resolved, you need to call 
Config#resolve(),"
+                        + " see API docs for Config#resolve()";
+        if (newMessage.equals(original.getMessage())) return original;
+        else return new ConfigException.NotResolved(newMessage, original);
+    }
+
+    public static ConfigOrigin newSimpleOrigin(String description) {
+        if (description == null) {
+            return defaultValueOrigin;
+        } else {
+            return SimpleConfigOrigin.newSimple(description);
+        }
+    }
+
+    public static ConfigOrigin newFileOrigin(String filename) {
+        return SimpleConfigOrigin.newFile(filename);
+    }
+
+    public static ConfigOrigin newURLOrigin(URL url) {
+        return SimpleConfigOrigin.newURL(url);
+    }
+}
diff --git 
a/seatunnel-config/seatunnel-config-shade/src/main/java/org/apache/seatunnel/shade/com/typesafe/config/impl/SimpleConfigObject.java
 
b/seatunnel-config/seatunnel-config-shade/src/main/java/org/apache/seatunnel/shade/com/typesafe/config/impl/SimpleConfigObject.java
index 735df6829c..b10148977b 100644
--- 
a/seatunnel-config/seatunnel-config-shade/src/main/java/org/apache/seatunnel/shade/com/typesafe/config/impl/SimpleConfigObject.java
+++ 
b/seatunnel-config/seatunnel-config-shade/src/main/java/org/apache/seatunnel/shade/com/typesafe/config/impl/SimpleConfigObject.java
@@ -20,6 +20,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -277,7 +278,7 @@ final class SimpleConfigObject extends AbstractConfigObject 
implements Serializa
             boolean changed = false;
             boolean allResolved = true;
             Map<String, AbstractConfigValue> merged = new LinkedHashMap<>();
-            Set<String> allKeys = new HashSet<>();
+            Set<String> allKeys = new LinkedHashSet<>();
             allKeys.addAll(this.keySet());
             allKeys.addAll(fallback.keySet());
 
@@ -386,8 +387,7 @@ final class SimpleConfigObject extends AbstractConfigObject 
implements Serializa
             ResolveSource sourceWithParent = source.pushParent(this);
 
             try {
-                SimpleConfigObject.ResolveModifier modifier =
-                        new SimpleConfigObject.ResolveModifier(context, 
sourceWithParent);
+                ResolveModifier modifier = new ResolveModifier(context, 
sourceWithParent);
                 AbstractConfigValue value = this.modifyMayThrow(modifier);
                 return ResolveResult.make(modifier.context, 
value).asObjectResult();
             } catch (NotPossibleToResolve | RuntimeException var6) {
@@ -562,7 +562,7 @@ final class SimpleConfigObject extends AbstractConfigObject 
implements Serializa
     }
 
     public Set<Entry<String, ConfigValue>> entrySet() {
-        HashSet<Entry<String, ConfigValue>> entries = new HashSet<>();
+        HashSet<Entry<String, ConfigValue>> entries = new LinkedHashSet<>();
 
         for (Entry<String, AbstractConfigValue> stringAbstractConfigValueEntry 
:
                 this.value.entrySet()) {
@@ -584,7 +584,7 @@ final class SimpleConfigObject extends AbstractConfigObject 
implements Serializa
     }
 
     public Collection<ConfigValue> values() {
-        return new HashSet<>(this.value.values());
+        return new ArrayList<>(this.value.values());
     }
 
     static SimpleConfigObject empty() {
diff --git 
a/seatunnel-config/seatunnel-config-shade/src/test/java/org/apache/seatunnel/config/ConfigTest.java
 
b/seatunnel-config/seatunnel-config-shade/src/test/java/org/apache/seatunnel/config/ConfigTest.java
new file mode 100644
index 0000000000..6d8eb73ffa
--- /dev/null
+++ 
b/seatunnel-config/seatunnel-config-shade/src/test/java/org/apache/seatunnel/config/ConfigTest.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.config;
+
+import org.apache.seatunnel.shade.com.typesafe.config.Config;
+import org.apache.seatunnel.shade.com.typesafe.config.ConfigFactory;
+import org.apache.seatunnel.shade.com.typesafe.config.ConfigRenderOptions;
+
+import org.apache.seatunnel.config.utils.FileUtils;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.net.URISyntaxException;
+
+public class ConfigTest {
+
+    @Test
+    public void testConfigKeyOrder() throws URISyntaxException {
+        String expected =
+                
"{\"env\":{\"job.mode\":\"BATCH\"},\"source\":[{\"row.num\":100,\"schema\":{\"fields\":{\"name\":\"string\",\"age\":\"int\"}},\"plugin_name\":\"FakeSource\"}],\"sink\":[{\"plugin_name\":\"Console\"}]}";
+
+        Config config =
+                ConfigFactory.parseFile(
+                        
FileUtils.getFileFromResources("/seatunnel/serialize.conf"));
+        Assertions.assertEquals(expected, 
config.root().render(ConfigRenderOptions.concise()));
+    }
+}
diff --git 
a/seatunnel-core/seatunnel-core-starter/src/main/java/org/apache/seatunnel/core/starter/utils/ConfigBuilder.java
 
b/seatunnel-core/seatunnel-core-starter/src/main/java/org/apache/seatunnel/core/starter/utils/ConfigBuilder.java
index 40dea79166..47d47b0f4c 100644
--- 
a/seatunnel-core/seatunnel-core-starter/src/main/java/org/apache/seatunnel/core/starter/utils/ConfigBuilder.java
+++ 
b/seatunnel-core/seatunnel-core-starter/src/main/java/org/apache/seatunnel/core/starter/utils/ConfigBuilder.java
@@ -38,7 +38,7 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -123,7 +123,7 @@ public class ConfigBuilder {
     public static Map<String, Object> configDesensitization(Map<String, 
Object> configMap) {
         return configMap.entrySet().stream()
                 .collect(
-                        HashMap::new,
+                        LinkedHashMap::new,
                         (m, p) -> {
                             String key = p.getKey();
                             Object value = p.getValue();
@@ -154,7 +154,7 @@ public class ConfigBuilder {
                                 }
                             }
                         },
-                        HashMap::putAll);
+                        LinkedHashMap::putAll);
     }
 
     public static Config of(
diff --git 
a/seatunnel-core/seatunnel-core-starter/src/test/java/org/apache/seatunnel/core/starter/utils/ConfigBuilderTest.java
 
b/seatunnel-core/seatunnel-core-starter/src/test/java/org/apache/seatunnel/core/starter/utils/ConfigBuilderTest.java
new file mode 100644
index 0000000000..a9196c19a3
--- /dev/null
+++ 
b/seatunnel-core/seatunnel-core-starter/src/test/java/org/apache/seatunnel/core/starter/utils/ConfigBuilderTest.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.core.starter.utils;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ConfigBuilderTest {
+
+    @Test
+    public void testConfigDesensitizationSort() {
+        Map<String, Object> config = new LinkedHashMap<>();
+        config.put("a", "1");
+        config.put("b", "1");
+        config.put("c", "1");
+        config.put("d", "1");
+        config.put("e", "1");
+        config.put("f", "1");
+
+        Map<String, Object> desensitizationConfig = 
ConfigBuilder.configDesensitization(config);
+        List<String> keys = new ArrayList<>(desensitizationConfig.keySet());
+        Assertions.assertIterableEquals(Arrays.asList("a", "b", "c", "d", "e", 
"f"), keys);
+    }
+}

Reply via email to