001/**
002 * Copyright (c) 2025-2026, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
004 * Licensed under the GNU Lesser General Public License (LGPL) ,Version 3.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * http://www.gnu.org/licenses/lgpl-3.0.txt
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package dev.tinyflow.core.util;
017
018
019import com.alibaba.fastjson.JSONPath;
020import org.slf4j.Logger;
021import org.slf4j.LoggerFactory;
022
023import java.util.*;
024import java.util.function.Function;
025
026public class MapUtil {
027    private static final Logger log = LoggerFactory.getLogger(MapUtil.class);
028
029    private static final boolean IS_JDK8 = (8 == getJvmVersion0());
030
031    private MapUtil() {
032    }
033
034    private static String tryTrim(String string) {
035        return string != null ? string.trim() : "";
036    }
037
038    private static int getJvmVersion0() {
039        int jvmVersion = -1;
040        try {
041            String javaSpecVer = tryTrim(System.getProperty("java.specification.version"));
042            if (StringUtil.hasText(javaSpecVer)) {
043                if (javaSpecVer.startsWith("1.")) {
044                    javaSpecVer = javaSpecVer.substring(2);
045                }
046                if (javaSpecVer.indexOf('.') == -1) {
047                    jvmVersion = Integer.parseInt(javaSpecVer);
048                }
049            }
050        } catch (Throwable ignore) {
051            // ignore
052        }
053        // default is jdk8
054        if (jvmVersion == -1) {
055            jvmVersion = 8;
056        }
057        return jvmVersion;
058    }
059
060    /**
061     * A temporary workaround for Java 8 specific performance issue JDK-8161372 .<br>
062     * This class should be removed once we drop Java 8 support.
063     *
064     * @see <a href=
065     * "https://bugs.openjdk.java.net/browse/JDK-8161372">https://bugs.openjdk.java.net/browse/JDK-8161372</a>
066     */
067    public static <K, V> V computeIfAbsent(Map<K, V> map, K key, Function<K, V> mappingFunction) {
068        if (IS_JDK8) {
069            V value = map.get(key);
070            if (value != null) {
071                return value;
072            }
073        }
074        return map.computeIfAbsent(key, mappingFunction);
075    }
076
077    public static Object getByPath(Map<String, Object> from, String keyOrPath) {
078        if (StringUtil.noText(keyOrPath) || from == null || from.isEmpty()) {
079            return null;
080        }
081
082        Object result = from.get(keyOrPath);
083        if (result != null) {
084            return result;
085        }
086
087        List<String> parts = Arrays.asList(keyOrPath.split("\\."));
088        if (parts.isEmpty()) {
089            return null;
090        }
091
092        int matchedLevels = 0;
093        for (int i = parts.size(); i > 0; i--) {
094            String tryKey = String.join(".", parts.subList(0, i));
095            Object tempResult = from.get(tryKey);
096            if (tempResult != null) {
097                result = tempResult;
098                matchedLevels = i;
099                break;
100            }
101        }
102
103        if (result == null) {
104            return null;
105        }
106
107        if (result instanceof Collection) {
108            List<Object> results = new ArrayList<>();
109            for (Object item : ((Collection<?>) result)) {
110                results.add(getResult(parts, matchedLevels, item));
111            }
112            return results;
113        }
114
115        return getResult(parts, matchedLevels, result);
116    }
117
118    private static Object getResult(List<String> parts, int matchedLevels, Object result) {
119        List<String> remainingParts = parts.subList(matchedLevels, parts.size());
120        String jsonPath = "$." + String.join(".", remainingParts);
121        try {
122            return JSONPath.eval(result, jsonPath);
123        } catch (Exception e) {
124            log.error(e.toString(), e);
125        }
126
127        return null;
128    }
129
130}