001/**
002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com).
003 * <p>
004 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
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 io.jboot.app.config;
017
018
019import java.io.File;
020import java.lang.reflect.*;
021import java.util.*;
022import java.util.concurrent.ConcurrentHashMap;
023
024public class JbootConfigKit {
025
026
027    public static <T> T newInstance(Class<T> clazz) {
028        try {
029            Constructor<T> constructor = clazz.getDeclaredConstructor();
030            constructor.setAccessible(true);
031            return constructor.newInstance();
032        } catch (Exception e) {
033            e.printStackTrace();
034        }
035        return null;
036    }
037
038    public static List<ConfigPara> parseParas(String string) {
039        if (isBlank(string)) {
040            return null;
041        }
042
043        List<ConfigPara> paras = null;
044        ConfigPara para = null;
045        int index = 0;
046        boolean hasDefaultValue = false;
047        char[] chars = string.toCharArray();
048        for (char c : chars) {
049            //第一个字符是 '{' 会出现 ArrayIndexOutOfBoundsException 错误
050            if (c == '{' && index > 0 && chars[index - 1] == '$' && para == null) {
051                para = new ConfigPara();
052                hasDefaultValue = false;
053                para.setStart(index - 1);
054            } else if (c == '}' && para != null) {
055                para.setEnd(index);
056                if (paras == null) {
057                    paras = new LinkedList<>();
058                }
059                paras.add(para);
060                para = null;
061            } else if (para != null) {
062                if (c == ':' && !hasDefaultValue) {
063                    hasDefaultValue = true;
064                } else if (hasDefaultValue) {
065                    para.appendToDefaultValue(c);
066                } else {
067                    para.appendToKey(c);
068                }
069            }
070            index++;
071        }
072        return paras;
073    }
074
075
076    public static String parseValue(String value) {
077        return parseValue(JbootConfigManager.me(), value);
078    }
079
080    public static String parseValue(JbootConfigManager manager, String value) {
081        List<ConfigPara> paras = parseParas(value);
082        if (paras == null || paras.size() == 0) {
083            return value;
084        }
085        StringBuilder retBuilder = new StringBuilder(value.length());
086        int index = 0;
087        for (ConfigPara para : paras) {
088            if (para.getStart() > index) {
089                retBuilder.append(value, index, para.getStart());
090            }
091
092            String configValue = manager.getConfigValue(para.getKey());
093            configValue = isNotBlank(configValue) ? configValue : para.getDefaultValue();
094            retBuilder.append(configValue);
095            index = para.getEnd() + 1;
096        }
097
098        if (index < value.length()) {
099            retBuilder.append(value, index, value.length());
100        }
101
102        return retBuilder.toString();
103    }
104
105
106    public static List<Method> getClassSetMethods(Class<?> clazz) {
107        List<Method> setMethods = new ArrayList<>();
108        Method[] methods = clazz.getMethods();
109        for (Method method : methods) {
110            if (method.getName().startsWith("set")
111                    && method.getName().length() > 3
112                    && Character.isUpperCase(method.getName().charAt(3))
113                    && method.getParameterCount() == 1
114                    && Modifier.isPublic(method.getModifiers())
115                    && !Modifier.isStatic(method.getModifiers())) {
116
117                setMethods.add(method);
118            }
119        }
120        return setMethods;
121    }
122
123    public static String firstCharToLowerCase(String str) {
124        char firstChar = str.charAt(0);
125        if (firstChar >= 'A' && firstChar <= 'Z') {
126            char[] arr = str.toCharArray();
127            arr[0] += ('a' - 'A');
128            return new String(arr);
129        }
130        return str;
131    }
132
133    public static boolean isBlank(String str) {
134        if (str == null) {
135            return true;
136        }
137
138        for (int i = 0, len = str.length(); i < len; i++) {
139            if (str.charAt(i) > ' ') {
140                return false;
141            }
142        }
143        return true;
144    }
145
146    public static boolean isNotBlank(Object str) {
147        return str != null && !isBlank(str.toString());
148    }
149
150    public static boolean areNotBlank(String... strs) {
151        if (strs == null || strs.length == 0) {
152            return false;
153        }
154
155        for (String string : strs) {
156            if (isBlank(string)) {
157                return false;
158            }
159        }
160        return true;
161    }
162
163
164    static Properties readProperties(String fileName) {
165        return readProperties(null,fileName);
166    }
167
168    static Properties readProperties(String path, String fileName) {
169        fileName = appendSuffixIfNecessary(fileName);
170        if (path != null && path.trim().length() > 0) {
171            return new JbootProp(new File(path, fileName)).getProperties();
172        } else {
173            return new JbootProp(fileName).getProperties();
174        }
175    }
176
177
178    static Properties readExternalProperties(String fileName) {
179        fileName = appendSuffixIfNecessary(fileName);
180        String jarPath = JbootConfigKit.class.getProtectionDomain().getCodeSource().getLocation().getFile();
181        File parentPath = new File(jarPath).getParentFile();
182        File externalProperties = new File(parentPath, fileName);
183        return readPropertiesFile(externalProperties);
184    }
185
186
187    private static String appendSuffixIfNecessary(String fileName) {
188        fileName = fileName.trim();
189        return fileName.toLowerCase().endsWith(".properties") ? fileName : fileName + ".properties";
190    }
191
192
193    static Properties readPropertiesFile(File propFile) {
194        if (propFile.exists()) {
195            return new JbootProp(propFile).getProperties();
196        }
197        return new Properties();
198    }
199
200
201    public static Object convert(Class<?> convertClass, String s, Type genericType) {
202
203        if (convertClass == String.class || s == null || convertClass == Object.class) {
204            return s;
205        }
206
207        if (convertClass == Integer.class || convertClass == int.class) {
208            return Integer.parseInt(s);
209        } else if (convertClass == Long.class || convertClass == long.class) {
210            return Long.parseLong(s);
211        } else if (convertClass == Double.class || convertClass == double.class) {
212            return Double.parseDouble(s);
213        } else if (convertClass == Float.class || convertClass == float.class) {
214            return Float.parseFloat(s);
215        } else if (convertClass == Boolean.class || convertClass == boolean.class) {
216            String value = s.toLowerCase();
217            if ("1".equals(value) || "true".equals(value)) {
218                return Boolean.TRUE;
219            } else if ("0".equals(value) || "false".equals(value)) {
220                return Boolean.FALSE;
221            } else {
222                throw new RuntimeException("Can not parse to boolean type of value: " + s);
223            }
224        } else if (convertClass == java.math.BigDecimal.class) {
225            return new java.math.BigDecimal(s);
226        } else if (convertClass == java.math.BigInteger.class) {
227            return new java.math.BigInteger(s);
228        } else if (convertClass == byte[].class) {
229            return s.getBytes();
230        } else if (Map.class.isAssignableFrom(convertClass)) {
231            if (!s.contains(":") || !genericClassCheck(genericType)) {
232                return null;
233            } else {
234                Map map = convertClass == ConcurrentHashMap.class ? new ConcurrentHashMap() : new HashMap();
235                String[] strings = s.split(",");
236                for (String kv : strings) {
237                    int indexOf = kv.indexOf(":");
238                    if (indexOf > 0 && indexOf < kv.trim().length() - 1) {
239                        map.put(kv.substring(0, indexOf).trim(), kv.substring(indexOf + 1).trim());
240                    }
241                }
242                return map;
243            }
244        } else if (List.class.isAssignableFrom(convertClass)) {
245            if (genericClassCheck(genericType)) {
246                List list = LinkedList.class == convertClass ? new LinkedList() : new ArrayList();
247                String[] strings = s.split(",");
248                for (String s1 : strings) {
249                    if (s1.trim().length() > 0) {
250                        list.add(s1.trim());
251                    }
252                }
253                return list;
254            } else {
255                return null;
256            }
257        } else if (Set.class.isAssignableFrom(convertClass)) {
258            if (genericClassCheck(genericType)) {
259                Set set = LinkedHashSet.class == convertClass ? new LinkedHashSet() : new HashSet();
260                String[] strings = s.split(",");
261                for (String s1 : strings) {
262                    if (s1.trim().length() > 0) {
263                        set.add(s1.trim());
264                    }
265                }
266                return set;
267            } else {
268                return null;
269            }
270        } else if (convertClass.isArray() && convertClass.getComponentType() == String.class) {
271            List<String> list = new LinkedList();
272            String[] strings = s.split(",");
273            if (strings.length > 0) {
274                for (String s1 : strings) {
275                    if (s1 != null && s1.trim().length() != 0) {
276                        list.add(s1.trim());
277                    }
278                }
279            }
280            return list.toArray(new String[0]);
281        } else if (Class.class == convertClass) {
282            try {
283                return Class.forName(s, false, Thread.currentThread().getContextClassLoader());
284            } catch (ClassNotFoundException e) {
285                e.printStackTrace();
286            }
287        }
288
289        throw new RuntimeException(convertClass.getName() + " can not be converted, please use other type in your config class!");
290
291    }
292
293    /**
294     * 对泛型类型进行检测,只支持 String 类型的泛型,或者不是泛型才会支持
295     *
296     * @param type
297     * @return
298     */
299    private static boolean genericClassCheck(Type type) {
300        if (type instanceof ParameterizedType) {
301            for (Type at : ((ParameterizedType) type).getActualTypeArguments()) {
302                if (String.class != at) {
303                    return false;
304                }
305            }
306        }
307        return true;
308    }
309
310}