package com.github.looly.hutool; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.lang.reflect.Method; import java.net.URL; import java.nio.charset.Charset; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.Set; import org.slf4j.Logger; import com.github.looly.hutool.exceptions.SettingException; /** * 设置工具类。 用于支持设置文件<br> * 1、支持变量,默认变量命名为 ${变量名},变量只能识别读入行的变量,例如第6行的变量在第三行无法读取 * 2、支持分组,分组为中括号括起来的内容,中括号以下的行都为此分组的内容,无分组相当于空字符分组<br> * 若某个key是name,加上分组后的键相当于group.name * 3、注释以#开头,但是空行和不带“=”的行也会被跳过,但是建议加# * 4、store方法不会保存注释内容,慎重使用 * @author xiaoleilu * */ public class Setting extends HashMap<String, String> { private static final long serialVersionUID = -477527787843971824L; private static Logger log = Log.get(); /** 默认字符集 */ public final static String DEFAULT_CHARSET = "utf8"; /** 数组类型值默认分隔符 */ public final static String DEFAULT_DELIMITER= ","; /** 注释符号(当有此符号在行首,表示此行为注释) */ protected final static String COMMENT_FLAG_PRE = "#"; /** 赋值分隔符(用于分隔键值对) */ protected final static String ASSIGN_FLAG = "="; /** 分组行识别的环绕标记 */ protected final static char[] GROUP_SURROUND = { '[', ']' }; /** 变量名称的正则 */ private String reg_var = "\\$\\{(.*?)\\}"; /** 本设置对象的字符集 */ private Charset charset; /** 是否使用变量 */ private boolean isUseVariable; /** 设定文件的URL */ private URL settingUrl; private LinkedList<String> groups = new LinkedList<String>(); /** * 基本构造<br/> * 需自定义初始化配置文件<br/> * * @param charset 字符集 * @param isUseVariable 是否使用变量 */ public Setting(Charset charset, boolean isUseVariable) { this.charset = charset; this.isUseVariable = isUseVariable; } /** * 构造,使用相对于Class文件根目录的相对路径 * * @param pathBaseClassLoader 相对路径(相对于当前项目的classes路径) * @param charset 字符集 * @param isUseVariable 是否使用变量 */ public Setting(String pathBaseClassLoader, String charset, boolean isUseVariable) { if(null == pathBaseClassLoader) { pathBaseClassLoader = StrUtil.EMPTY; } final URL url = URLUtil.getURL(pathBaseClassLoader); if(url == null) { throw new RuntimeException(StrUtil.format("Can not find Setting file: [{}]", pathBaseClassLoader)); } this.init(url, charset, isUseVariable); } /** * 构造 * * @param configFile 配置文件对象 * @param charset 字符集 * @param isUseVariable 是否使用变量 */ public Setting(File configFile, String charset, boolean isUseVariable) { if (configFile == null) { throw new RuntimeException("Null Setting file!"); } final URL url = URLUtil.getURL(configFile); if(url == null) { throw new RuntimeException(StrUtil.format("Can not find Setting file: [{}]", configFile.getAbsolutePath())); } this.init(url, charset, isUseVariable); } /** * 构造,相对于classes读取文件 * * @param path 相对路径 * @param clazz 基准类 * @param charset 字符集 * @param isUseVariable 是否使用变量 */ public Setting(String path, Class<?> clazz, String charset, boolean isUseVariable) { final URL url = URLUtil.getURL(path, clazz); if(url == null) { throw new RuntimeException(StrUtil.format("Can not find Setting file: [{}]", path)); } this.init(url, charset, isUseVariable); } /** * 构造 * * @param url 设定文件的URL * @param charset 字符集 * @param isUseVariable 是否使用变量 */ public Setting(URL url, String charset, boolean isUseVariable) { if(url == null) { throw new RuntimeException("Null url define!"); } this.init(url, charset, isUseVariable); } /** * 构造 * @param pathBaseClassLoader 相对路径(相对于当前项目的classes路径) */ public Setting(String pathBaseClassLoader) { this(pathBaseClassLoader, DEFAULT_CHARSET, false); } /*--------------------------公有方法 start-------------------------------*/ /** * 初始化设定文件 * * @param settingUrl 设定文件的URL * @param charset 字符集 * @param isUseVariable 是否使用变量 * @return 成功初始化与否 */ public boolean init(URL settingUrl, String charset, boolean isUseVariable) { if (settingUrl == null) { throw new RuntimeException("Null setting url or charset define!"); } try { this.charset = Charset.forName(charset); } catch (Exception e) { log.warn("User custom charset [{}] parse error, use default charset: [{}]", charset, DEFAULT_CHARSET); this.charset = Charset.forName(DEFAULT_CHARSET); } this.isUseVariable = isUseVariable; this.settingUrl = settingUrl; return this.load(settingUrl); } /** * 加载设置文件 * * @param settingUrl 配置文件URL * @return 加载是否成功 */ synchronized public boolean load(URL settingUrl) { if (settingUrl == null) { throw new RuntimeException("Null setting url define!"); } log.debug("Load setting file [{}]", settingUrl.getPath()); InputStream settingStream = null; try { settingStream = settingUrl.openStream(); load(settingStream, isUseVariable); } catch (IOException e) { log.error("Load setting error!", e); return false; } finally { FileUtil.close(settingStream); } return true; } /** * 重新加载配置文件 */ public void reload() { this.load(settingUrl); } /** * 加载设置文件。 此方法不会关闭流对象 * * @param settingStream 文件流 * @param isUseVariable 是否使用变量(替换配置文件值中含有的变量) * @return 加载成功与否 * @throws IOException */ public boolean load(InputStream settingStream, boolean isUseVariable) throws IOException { this.clear(); BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(settingStream, charset)); // 分组 String group = null; while (true) { String line = reader.readLine(); if (line == null) { break; } line = line.trim(); // 跳过注释行和空行 if (StrUtil.isBlank(line) || line.startsWith(COMMENT_FLAG_PRE)) { continue; } // 记录分组名 if (line.charAt(0) == GROUP_SURROUND[0] && line.charAt(line.length() - 1) == GROUP_SURROUND[1]) { group = line.substring(1, line.length() - 1).trim(); this.groups.add(group); continue; } String[] keyValue = line.split(ASSIGN_FLAG, 2); // 跳过不符合简直规范的行 if (keyValue.length < 2) { continue; } String key = keyValue[0].trim(); if (false == StrUtil.isBlank(group)) { key = group + StrUtil.DOT + key; } String value = keyValue[1].trim(); // 替换值中的所有变量变量(变量必须是此行之前定义的变量,否则无法找到) if (isUseVariable) { value = replaceVar(value); } put(key, value); } } finally { FileUtil.close(reader); } return true; } /** * 设置变量的正则<br/> * 正则只能有一个group表示变量本身,剩余为字符 例如 \$\{(name)\}表示${name}变量名为name的一个变量表示 * * @param regex 正则 */ public void setVarRegex(String regex) { this.reg_var = regex; } /** * @return 获得设定文件的路径 */ public String getSettingPath() { return settingUrl.getPath(); } //--------------------------------------------------------------- Get /** * 带有日志提示的get,如果没有定义指定的KEY,则打印debug日志 * * @param key 键 * @return 值 */ public String getWithLog(String key) { return get(key, null); } /** * 带有日志提示的get,如果没有定义指定的KEY,则打印debug日志 * * @param key 键 * @return 值 */ public String getWithLog(String key, String group) { final String value = get(key, group); if (value == null) { log.debug("No key define for [{}]!", key); } return value; } /** * 获得指定分组的键对应值 * * @param key 键 * @param group 分组 * @return 值 */ public String get(String key, String group) { String keyWithGroup = key; if (!StrUtil.isBlank(group)) { keyWithGroup = group + "." + keyWithGroup; } return get(keyWithGroup); } //--------------------------------------------------------------- Get String /** * 获取字符型型属性值 * * @param key 属性名 * @return 属性值 */ public String getString(String key) { return getString(key, null); } /** * 获取字符型型属性值<br> * 若获得的值为不可见字符,使用默认值 * * @param key 属性名 * @param defaultValue 默认值 * @return 属性值 */ public String getStringWithDefault(String key, String defaultValue) { return getStringWithDefault(key, null, defaultValue); } /** * 获取字符型型属性值 * * @param key 属性名 * @param group 分组名 * @return 属性值 */ public String getString(String key, String group) { return get(key, group); } /** * 获取字符型型属性值<br> * 若获得的值为不可见字符,使用默认值 * * @param key 属性名 * @param group 分组名 * @param defaultValue 默认值 * @return 属性值 */ public String getStringWithDefault(String key, String group, String defaultValue) { final String value = getString(key, group); if(StrUtil.isBlank(value)) { return defaultValue; } return value; } //--------------------------------------------------------------- Get string array /** * 获得数组型 * @param key 属性名 * @return 属性值 */ public String[] getStrings(String key) { return getStrings(key, null); } /** * 获得数组型 * @param key 属性名 * @param defaultValue 默认的值 * @return 属性值 */ public String[] getStringsWithDefault(String key, String[] defaultValue) { String[] value = getStrings(key, null); if(null == value) { value = defaultValue; } return value; } /** * 获得数组型 * @param key 属性名 * @param group 分组名 * @return 属性值 */ public String[] getStrings(String key, String group) { return getStrings(key, group, DEFAULT_DELIMITER); } /** * 获得数组型 * @param key 属性名 * @param group 分组名 * @param delimiter 分隔符 * @return 属性值 */ public String[] getStrings(String key, String group, String delimiter) { final String value = getString(key, group); if(StrUtil.isBlank(value)) { return null; } return StrUtil.split(value, delimiter); } //--------------------------------------------------------------- Get int /** * 获取数字型型属性值 * * @param key 属性名 * @return 属性值 */ public Integer getInt(String key) throws NumberFormatException { return getInt(key, null); } /** * 获取数字型型属性值 * * @param key 属性名 * @param group 分组名 * @return 属性值 */ public Integer getInt(String key, String group) { return getInt(key, group, null); } /** * 获取数字型型属性值 * * @param key 属性名 * @param group 分组名 * @param defaultValue 默认值 * @return 属性值 */ public Integer getInt(String key, String group, Integer defaultValue) { return Conver.toInt(get(key, group), defaultValue); } //--------------------------------------------------------------- Get bool /** * 获取波尔型属性值 * * @param key 属性名 * @return 属性值 */ public Boolean getBool(String key) throws NumberFormatException { return getBool(key, null); } /** * 获取波尔型属性值 * * @param key 属性名 * @param group 分组名 * @return 属性值 */ public Boolean getBool(String key, String group) { return getBool(key, group, null); } /** * 获取波尔型型属性值 * * @param key 属性名 * @param group 分组名 * @param defaultValue 默认值 * @return 属性值 */ public Boolean getBool(String key, String group, Boolean defaultValue) { return Conver.toBool(get(key, group), defaultValue); } //--------------------------------------------------------------- Get long /** * 获取long类型属性值 * * @param key 属性名 * @return 属性值 */ public Long getLong(String key) throws NumberFormatException { return getLong(key, null); } /** * 获取long类型属性值 * * @param key 属性名 * @param group 分组名 * @return 属性值 */ public Long getLong(String key, String group) { return getLong(key, group, null); } /** * 获取long类型属性值 * * @param key 属性名 * @param group 分组名 * @param defaultValue 默认值 * @return 属性值 */ public Long getLong(String key, String group, Long defaultValue) { return Conver.toLong(get(key, group), defaultValue); } //--------------------------------------------------------------- Get char /** * 获取char类型属性值 * * @param key 属性名 * @return 属性值 */ public Character getChar(String key) { return getChar(key, null); } /** * 获取char类型属性值 * * @param key 属性名 * @param group 分组名 * @return 属性值 */ public Character getChar(String key, String group) { final String value = get(key, group); if(StrUtil.isBlank(value)) { return null; } return value.charAt(0); } //--------------------------------------------------------------- Get double /** * 获取double类型属性值 * * @param key 属性名 * @return 属性值 */ public Double getDouble(String key) { return getDouble(key, null); } /** * 获取double类型属性值 * * @param key 属性名 * @param group 分组名 * @return 属性值 */ public Double getDouble(String key, String group) { return getDouble(key, group, null); } /** * 获取double类型属性值 * * @param key 属性名 * @param group 分组名 * @param defaultValue 默认值 * @return 属性值 */ public Double getDouble(String key, String group, Double defaultValue) { return Conver.toDouble(get(key, group), defaultValue); } //--------------------------------------------------------------- Set /** * 设置值,无给定键创建之。设置后未持久化 * * @param key 键 * @param value 值 */ public void setSetting(String key, Object value) { put(key, value.toString()); } //--------------------------------------------------------------------------------- Functions /** * 持久化当前设置,会覆盖掉之前的设置<br> * 持久化会不会保留之前的分组 * @param absolutePath 设置文件的绝对路径 */ public void store(String absolutePath) { try { FileUtil.touch(absolutePath); OutputStream out = FileUtil.getOutputStream(absolutePath); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, charset)); Set<java.util.Map.Entry<String, String>> entrySet = this.entrySet(); for (java.util.Map.Entry<String, String> entry : entrySet) { writer.write(entry.getKey() + ASSIGN_FLAG + entry.getValue()); } writer.close(); } catch (FileNotFoundException e) { throw new RuntimeException(StrUtil.format("Can not find file [{}]!", absolutePath), e); } catch (IOException e) { throw new RuntimeException("Store Setting error!", e); } } /** * 存储当前设置,会覆盖掉以前的设置 * * @param path 相对路径 * @param clazz 相对的类 */ public void store(String path, Class<?> clazz) { this.store(FileUtil.getAbsolutePath(path, clazz)); } /** * 将setting中的键值关系映射到对象中,原理是调用对象对应的set方法<br/> * 只支持基本类型的转换 * * @param object 被调用的对象 * @throws SettingException */ public void toObject(String group, Object object) throws SettingException { try { Method[] methods = object.getClass().getMethods(); for (Method method : methods) { String methodName = method.getName(); if (methodName.startsWith("set")) { String field = StrUtil.getGeneralField(methodName); Object value = get(field, group); if (value != null) { Class<?>[] parameterTypes = method.getParameterTypes(); if(parameterTypes.length != 1) { continue; } Object castedValue = ClassUtil.parse(parameterTypes[0], value); method.invoke(object, castedValue); log.debug("Parse setting to object field [{}={}]", field, value); } } } } catch (Exception e) { throw new SettingException("Parse setting to object error!", e); } } /** * 将setting中的键值关系映射到对象中,原理是调用对象对应的set方法<br/> * 只支持基本类型的转换 * * @param object * @throws SettingException */ public void toObject(Object object) throws SettingException { toObject(null, object); } /** * @return 获得所有分组名 */ public LinkedList<String> getGroups() { return this.groups; } /*--------------------------公有方法 end-------------------------------*/ /*--------------------------Private Method start-------------------------------*/ /** * 替换给定值中的变量标识 * @param value 值 * @return 替换后的字符串 */ private String replaceVar(String value) { // 找到所有变量标识 final Set<String> vars = ReUtil.findAll(reg_var, value, 0, new HashSet<String>()); for (String var : vars) { // 查找变量名对应的值 String varValue = this.get(ReUtil.get(reg_var, var, 1)); if (null != varValue) { // 替换标识 value = value.replace(var, varValue); } } return value; } /*--------------------------Private Method end-------------------------------*/ }