All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.feilong.core.util.ResourceBundleUtil Maven / Gradle / Ivy

Go to download

feilong is a suite of core and expanded libraries that include utility classes, http, excel,cvs, io classes, and much much more.

There is a newer version: 4.0.8
Show newest version
/*
 * Copyright (C) 2008 feilong
 *
 * Licensed 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 com.feilong.core.util;

import static com.feilong.core.Validator.isNotNullOrEmpty;
import static com.feilong.core.Validator.isNullOrEmpty;
import static com.feilong.core.lang.reflect.ConstructorUtil.newInstance;
import static com.feilong.core.lang.ObjectUtil.defaultIfNull;
import static com.feilong.core.lang.StringUtil.EMPTY;
import static java.util.Collections.emptyMap;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.TreeMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.feilong.core.bean.BeanUtil;
import com.feilong.core.bean.ConvertUtil;
import com.feilong.core.text.MessageFormatUtil;
import com.feilong.lib.lang3.StringUtils;
import com.feilong.core.Validate;

/**
 * {@link java.util.ResourceBundle ResourceBundle} 工具类.
 * 
 * 

* 该类专注于解析配置文件,至于解析到的结果你可以使用 {@link ConvertUtil}来进行转换成你需要的类型 *

* *

如果现在多种资源文件一起出现,该如何访问?

* *
*

* 如果一个项目中同时存在Message.properties、Message_zh_CN.properties、Message_zh_ CN.class 3个类型的文件,那最终使用的是哪一个?
* 只会使用一个,按照优先级使用.
* 顺序为Message_zh_CN.class、Message_zh_CN.properties、Message.properties.
*

* *

* 解析原理,参见:
* java.util.ResourceBundle#loadBundle(CacheKey, List, Control, boolean)
* {@link java.util.ResourceBundle.Control#newBundle(String, Locale, String, ClassLoader, boolean)} *

*
* * @author feilong * @see MessageFormatUtil#format(String, Object...) * @see java.util.ResourceBundle * @see java.util.PropertyResourceBundle * @see java.util.ListResourceBundle * @see "org.springframework.core.io.support.LocalizedResourceHelper" * @since 1.4.0 */ public final class ResourceBundleUtil{ /** The Constant LOGGER. */ private static final Logger LOGGER = LoggerFactory.getLogger(ResourceBundleUtil.class); /** Don't let anyone instantiate this class. */ private ResourceBundleUtil(){ //AssertionError不是必须的. 但它可以避免不小心在类的内部调用构造器. 保证该类在任何情况下都不会被实例化. //see 《Effective Java》 2nd throw new AssertionError("No " + getClass().getName() + " instances for you!"); } //--------------------------------------------------------------- /** * 获取resourceBundle 配置文件指定 key键的值. * *

说明:

*
*
    *
  1. 支持配置文件含参数信息 arguments ,使用 {@link MessageFormatUtil#format(String, Object...)} 来解析
  2. *
*
* *

示例:

* *
* *

* 如果有配置文件 messages\feilong-core-test.properties,内容如下: *

* *
     * test.arguments=my name is {0},age is {1}
     * 
* *

* 此时调用方法: *

* *
     * ResourceBundle resourceBundle = ResourceBundle.getBundle("messages/feilong-core-test");
     * ResourceBundleUtil.getValueWithArguments(resourceBundle, "test.arguments", "feilong", "18");
     * 
* * 返回: * *
     * my name is feilong,age is 18
     * 
* *
* * @param resourceBundle * the resource bundle * @param key * Properties配置文件键名 * @param arguments * 此处可以传递Object[]数组过来 * @return 如果配置文件中,key不存在,LOGGER.warn警告输出,并返回 {@link StringUtils#EMPTY}
* @throws NullPointerException * 如果 resourceBundle 或者 key 是null * @throws IllegalArgumentException * 如果 key 是blank,抛出 {@link IllegalArgumentException} * @see java.util.ResourceBundle#getString(String) * @see MessageFormatUtil#format(String, Object...) * @since 1.8.1 support arguments param */ public static String getValue(ResourceBundle resourceBundle,String key,Object...arguments){ Validate.notNull(resourceBundle, "resourceBundle can't be null!"); Validate.notBlank(key, "key can't be null/empty!"); if (!resourceBundle.containsKey(key)){ LOGGER.warn("resourceBundle:[{}] don't containsKey:[{}]", resourceBundle, key); return EMPTY; } //--------------------------------------------------------------- String value = resourceBundle.getString(key); if (isNullOrEmpty(value)){ LOGGER.trace("resourceBundle has key:[{}],but value is null/empty", key); } return isNullOrEmpty(value) ? EMPTY : MessageFormatUtil.format(value, arguments);// 支持 arguments 为null,原样返回 } //--------------------------------------------------------------- /** * 解析 baseNamess 成map. * *

说明:

*
*
    *
  1. JDK默认使用的是{@link java.util.PropertyResourceBundle},内部是使用 hashmap来存储数据的,
    * 本方法出于log以及使用方便,返回的是 TreeMap
  2. *
  3. 后面的配置文件会覆盖前面的配置文件
  4. *
  5. 如果文件不存在,将会被忽略
  6. *
*
* *

示例:

* *
* *

* 在 classpath messages 目录下面有 memcached.properties,内容如下: *

* *
     * # 注意此处 ip出现 - 横杆 仅作测试使用
     * memcached.serverlist=172.20.3-1.23:11211,172.20.31.22:11211 
     * memcached.poolname=sidsock2
     * #单位分钟
     * memcached.expiretime=180
     * 
     * memcached.serverweight=2
     * 
     * memcached.initconnection=10
     * memcached.minconnection=5
     * memcached.maxconnection=250
     * 
     * #设置主线程睡眠时间,每30秒苏醒一次,维持连接池大小
     * memcached.maintSleep=30
     * 
     * #关闭套接字缓存
     * memcached.nagle=false
     * 
     * #连接建立后的超时时间
     * memcached.socketto=3000
     * memcached.alivecheck=false
     * 
* * * 此时你可以如此调用代码: * * *
     * Map{@code } map = toMap("messages/memcached");
     * LOGGER.debug(JsonUtil.format(map));
     * 
* * 返回: * *
     * {
     * "memcached.alivecheck": "false",
     * "memcached.expiretime": "180",
     * "memcached.initconnection": "10",
     * "memcached.maintSleep": "30",
     * "memcached.maxconnection": "250",
     * "memcached.minconnection": "5",
     * "memcached.nagle": "false",
     * "memcached.poolname": "sidsock2",
     * "memcached.serverlist": "172.20.3-1.23:11211,172.20.31.22:11211",
     * "memcached.serverweight": "2",
     * "memcached.socketto": "3000"
     * }
     * 
* *
* * @param baseNames * the base names * @return 如果 baseNames 是null,抛出 {@link NullPointerException}
* 如果 baseNames 是empty,抛出 {@link IllegalArgumentException}
* * 如果 baseNames 中有元素是null,抛出 {@link NullPointerException}
* 如果 baseNames 中有元素是blank,抛出 {@link IllegalArgumentException}
* 否则,解析所有的key和value转成 {@link TreeMap}
* @since 3.0.0 */ public static Map toMap(String...baseNames){ Validate.notEmpty(baseNames, "baseNames can't be null/empty!"); //--------------------------------------------------------------- Map map = new TreeMap<>();//为了log方便,使用 treeMap for (String baseName : baseNames){ Validate.notBlank(baseName, "baseName is null or empty,[%s]", ConvertUtil.toString(baseNames, ",")); //--------------------------------------------------------------- try{ ResourceBundle resourceBundle = getResourceBundle(baseName); Map littleMap = toMap(resourceBundle); if (isNotNullOrEmpty(littleMap)){ map.putAll(littleMap); } }catch (Exception e){ LOGGER.warn("baseName:[{}],message:[{}],ignore~", baseName, e.getMessage()); } } return map; } /** * 将 resourceBundle 转成map. * *

说明:

*
*
    *
  1. JDK默认使用的是{@link java.util.PropertyResourceBundle},内部是使用 hashmap来存储数据的,
    * 本方法出于log以及使用方便,返回的是 TreeMap
  2. *
*
* *

示例:

* *
* *

* 在 classpath messages 目录下面有 memcached.properties,内容如下: *

* *
     * # 注意此处 ip出现 - 横杆 仅作测试使用
     * memcached.serverlist=172.20.3-1.23:11211,172.20.31.22:11211 
     * memcached.poolname=sidsock2
     * #单位分钟
     * memcached.expiretime=180
     * 
     * memcached.serverweight=2
     * 
     * memcached.initconnection=10
     * memcached.minconnection=5
     * memcached.maxconnection=250
     * 
     * #设置主线程睡眠时间,每30秒苏醒一次,维持连接池大小
     * memcached.maintSleep=30
     * 
     * #关闭套接字缓存
     * memcached.nagle=false
     * 
     * #连接建立后的超时时间
     * memcached.socketto=3000
     * memcached.alivecheck=false
     * 
* * * 此时你可以如此调用代码: * * *
     * Map{@code } map = toMap(getResourceBundle("messages/memcached"));
     * LOGGER.debug(JsonUtil.format(map));
     * 
* * 返回: * *
     * {
     * "memcached.alivecheck": "false",
     * "memcached.expiretime": "180",
     * "memcached.initconnection": "10",
     * "memcached.maintSleep": "30",
     * "memcached.maxconnection": "250",
     * "memcached.minconnection": "5",
     * "memcached.nagle": "false",
     * "memcached.poolname": "sidsock2",
     * "memcached.serverlist": "172.20.3-1.23:11211,172.20.31.22:11211",
     * "memcached.serverweight": "2",
     * "memcached.socketto": "3000"
     * }
     * 
* *
* * @param resourceBundle * the resource bundle * @return 如果 resourceBundle 是null,抛出 {@link NullPointerException}
* 如果 resourceBundle 没有key,则返回{@link java.util.Collections#emptyMap()}
* 否则,解析所有的key和value转成 {@link TreeMap}
* @since 1.8.8 */ public static Map toMap(ResourceBundle resourceBundle){ Validate.notNull(resourceBundle, "resourceBundle can't be null!"); Enumeration keysEnumeration = resourceBundle.getKeys(); if (isNullOrEmpty(keysEnumeration)){ return emptyMap(); } //--------------------------------------------------------------- Map map = new TreeMap<>();//为了log方便,使用 treeMap while (keysEnumeration.hasMoreElements()){ String key = keysEnumeration.nextElement(); map.put(key, resourceBundle.getString(key)); } return map; } /** * 将 resourceBundle 转成Properties. * *

示例:

* *
* *

* 在 classpath messages 目录下面有 memcached.properties,内容如下: *

* *
     * # 注意此处 ip出现 - 横杆 仅作测试使用
     * memcached.serverlist=172.20.3-1.23:11211,172.20.31.22:11211 
     * memcached.poolname=sidsock2
     * #单位分钟
     * memcached.expiretime=180
     * 
     * memcached.serverweight=2
     * 
     * memcached.initconnection=10
     * memcached.minconnection=5
     * memcached.maxconnection=250
     * 
     * #设置主线程睡眠时间,每30秒苏醒一次,维持连接池大小
     * memcached.maintSleep=30
     * 
     * #关闭套接字缓存
     * memcached.nagle=false
     * 
     * #连接建立后的超时时间
     * memcached.socketto=3000
     * memcached.alivecheck=false
     * 
* * * 此时你可以如此调用代码: * * *
     * Properties properties = ResourceBundleUtil.toProperties("messages.memcached");
     * LOGGER.debug(JsonUtil.format(properties));
     * 
* * 返回: * *
     * {
     * "memcached.serverlist": "172.20.3-1.23:11211,172.20.31.22:11211",
     * "memcached.maxconnection": "250",
     * "memcached.socketto": "3000",
     * "memcached.initconnection": "10",
     * "memcached.nagle": "false",
     * "memcached.expiretime": "180",
     * "memcached.maintSleep": "30",
     * "memcached.alivecheck": "false",
     * "memcached.serverweight": "2",
     * "memcached.poolname": "sidsock2",
     * "memcached.minconnection": "5"
     * }
     * 
* *
* * @param resourceBundle * the resource bundle * @return 如果 resourceBundle 没有key value,则返回 new Properties
* 否则,解析所有的key和value转成 {@link Properties} * @throws NullPointerException * 如果 resourceBundle 是null * @see #toMap(ResourceBundle) * @see ConvertUtil#toProperties(Map) * @since 1.8.8 */ public static Properties toProperties(ResourceBundle resourceBundle){ return ConvertUtil.toProperties(toMap(resourceBundle)); } /** * 将 resourceBundle 转换成aliasBean. * *

示例:

* *
* *

* 在 classpath messages 目录下面有 memcached.properties,内容如下: *

* *
     * # 注意此处 ip出现 - 横杆 仅作测试使用
     * memcached.serverlist=172.20.3-1.23:11211,172.20.31.22:11211 
     * memcached.poolname=sidsock2
     * #单位分钟
     * memcached.expiretime=180
     * 
     * memcached.serverweight=2
     * 
     * memcached.initconnection=10
     * memcached.minconnection=5
     * memcached.maxconnection=250
     * 
     * #设置主线程睡眠时间,每30秒苏醒一次,维持连接池大小
     * memcached.maintSleep=30
     * 
     * #关闭套接字缓存
     * memcached.nagle=false
     * 
     * #连接建立后的超时时间
     * memcached.socketto=3000
     * memcached.alivecheck=false
     * 
     * 
* *

* 有以下aliasBean信息: *

* *
     * 
     * public class DangaMemCachedConfig{
     * 
     *     //** The serverlist.
     *     @Alias(name = "memcached.serverlist",sampleValue = "172.20.31.23:11211,172.20.31.22:11211")
     *     private String[] serverList;
     * 
     *     //@Alias(name = "memcached.poolname",sampleValue = "sidsock2")
     *     private String poolName;
     * 
     *     //** The expire time 单位分钟.
     *     @Alias(name = "memcached.expiretime",sampleValue = "180")
     *     private Integer expireTime;
     * 
     *     //** 权重. 
     *     @Alias(name = "memcached.serverweight",sampleValue = "2,1")
     *     private Integer[] weight;
     * 
     *     //** The init connection. 
     *     @Alias(name = "memcached.initconnection",sampleValue = "10")
     *     private Integer initConnection;
     * 
     *     //** The min connection.
     *     @Alias(name = "memcached.minconnection",sampleValue = "5")
     *     private Integer minConnection;
     * 
     *     //** The max connection. 
     *     @Alias(name = "memcached.maxconnection",sampleValue = "250")
     *     private Integer maxConnection;
     * 
     *     //** 设置主线程睡眠时间,每30秒苏醒一次,维持连接池大小.
     *     @Alias(name = "memcached.maintSleep",sampleValue = "30")
     *     private Integer maintSleep;
     * 
     *     //** 关闭套接字缓存. 
     *     @Alias(name = "memcached.nagle",sampleValue = "false")
     *     private Boolean nagle;
     * 
     *     //** 连接建立后的超时时间.
     *     @Alias(name = "memcached.socketto",sampleValue = "3000")
     *     private Integer socketTo;
     * 
     *     //** The alive check.
     *     @Alias(name = "memcached.alivecheck",sampleValue = "false")
     *     private Boolean aliveCheck;
     * 
     *     //setter getter 略
     * }
     * 
* * * 此时你可以如此调用代码: * * *
     * DangaMemCachedConfig dangaMemCachedConfig = ResourceBundleUtil.toAliasBean("messages.memcached", DangaMemCachedConfig.class);
     * LOGGER.debug(JsonUtil.format(dangaMemCachedConfig));
     * 
* * 返回: * *
     * {
     *         "maxConnection": 250,
     *         "expireTime": 180,
     *         "serverList":         [
     *             "172.20.3-1.23",
     *             "11211",
     *             "172.20.31.22",
     *             "11211"
     *         ],
     *         "weight": [2],
     *         "nagle": false,
     *         "initConnection": 10,
     *         "aliveCheck": false,
     *         "poolName": "sidsock2",
     *         "maintSleep": 30,
     *         "socketTo": 3000,
     *         "minConnection": 5
     * }
     * 
* *

* 你会发现类型会自动转换,虽然properties里面存储key和value都是string,但是使用该方法,可以自动类型转换,转成bean里面声明的类型 *

* *

* 但是同时,你也会发现,上面的 serverList 期望值是 ["172.20.3-1.23:11211","172.20.31.22:11211"],但是和你的期望值不符合,
* 因为, {@link com.feilong.lib.beanutils.converters.ArrayConverter} 默认允许的字符 allowedChars 只有 '.', '-',其他都会被做成分隔符 *

* *

* 你需要如此这般: *

* *
     * ArrayConverter arrayConverter = new ArrayConverter(String[].class, new StringConverter(), 2);
     * char[] allowedChars = { ':' };
     * arrayConverter.setAllowedChars(allowedChars);
     * 
     * ConvertUtils.register(arrayConverter, String[].class);
     * 
     * DangaMemCachedConfig dangaMemCachedConfig = ResourceBundleUtil.toAliasBean("messages.memcached", DangaMemCachedConfig.class);
     * LOGGER.debug(JsonUtil.format(dangaMemCachedConfig));
     * 
* * 返回: * *
     * {
     *         "maxConnection": 250,
     *         "expireTime": 180,
     *         "serverList":         [
     *             "172.20.3-1.23:11211",
     *             "172.20.31.22:11211"
     *         ],
     *         "weight": [2],
     *         "nagle": false,
     *         "initConnection": 10,
     *         "aliveCheck": false,
     *         "poolName": "sidsock2",
     *         "maintSleep": 30,
     *         "socketTo": 3000,
     *         "minConnection": 5
     * }
     * 
* *
* * @param * the generic type * @param resourceBundle * the resource bundle * @param aliasBeanClass * the alias bean class * @return the t * @throws NullPointerException * 如果 resourceBundle或者 aliasBean 是null * @see BeanUtil#populateAliasBean(Object, Map) * @since 1.8.8 */ public static T toAliasBean(ResourceBundle resourceBundle,Class aliasBeanClass){ Validate.notNull(resourceBundle, "resourceBundle can't be null/empty!"); Validate.notNull(aliasBeanClass, "aliasBeanClass can't be null!"); return BeanUtil.populateAliasBean(newInstance(aliasBeanClass), toMap(resourceBundle)); } //----------------------------getResourceBundle----------------------------------- /** * 使用 {@link Locale#getDefault()} 获得{@link ResourceBundle}. * *

示例:

* *
* *

* 场景: 如项目的classpath下面有 messages/feilong-core-test.properties,内容如下 *

* *
     * config_test_array=5,8,7,6
     * test.arguments=my name is {0},age is {1}
     * 
* *

* 你可以使用以下代码来读取内容: *

* *
     * ResourceBundle resourceBundle = getResourceBundle("messages/feilong-core-test");
     * LOGGER.debug(JsonUtil.format(toMap(resourceBundle)));
     * 
* * 返回: * *
     * {
     * "config_test_array": "5,8,7,6",
     * "test.arguments": "my name is {0},age is {1}",
     * }
     * 
* *
* * @param baseName * 一个完全限定类名,配置文件的包+类全名,比如 message.feilong-core-test (不要尾缀);
* 但是,为了和早期版本兼容,也可使用路径名来访问,比如message/feilong-core-test(使用 "/") * @return 如果资源文件 baseName 里面没有任何内容,返回不是null的 {@link ResourceBundle} * @throws NullPointerException * 如果 baseName 是null * @throws IllegalArgumentException * 如果 baseName 是 blank * @throws MissingResourceException * 如果资源文件 baseName 不存在 * @see java.util.Locale#getDefault() * @see #getResourceBundle(String, Locale) */ public static ResourceBundle getResourceBundle(String baseName){ return getResourceBundle(baseName, null); } /** * 使用 baseNamelocale 获得{@link ResourceBundle}. * *

示例:

* *
* *

* 场景: 比如在 classpath 下面有 messages\feilong-archetypes_en.propertiesmessages\feilong-archetypes_zh_CN.properties * 两个配置文件,内容如下 *

* *

* messages\feilong-archetypes_en.properties *

* *
     * feilong-archetypes.welcome=欢迎(简体)
     * 
* *

* messages\feilong-archetypes_zh_CN.properties *

* *
     * feilong-archetypes.welcome=欢迎(简体)
     * 
* *

* 此时,我要读取 英文的配置文件,你可以这么写 *

* *
     * ResourceBundle resourceBundle = getResourceBundle("messages/feilong-archetypes", Locale.ENGLISH);
     * Map{@code } map = toMap(resourceBundle);
     * 
     * LOGGER.debug(JsonUtil.format(map));
     * 
* * 返回: * *
     * {"feilong-archetypes.welcome": "welcome(english)"}
     * 
* *
* * @param baseName * 一个完全限定类名,配置文件的包+类全名,比如 message.feilong-core-test (不要尾缀);
* 但是,为了和早期版本兼容,也可使用路径名来访问,比如message/feilong-core-test(使用 "/") * @param locale * the locale for which a resource bundle is desired,如果是null,将使用 {@link Locale#getDefault()} * @return 如果资源文件 baseName 里面没有任何内容,返回不是null的 {@link ResourceBundle}
* 如果是null,将使用 {@link Locale#getDefault()}来获取 * @throws NullPointerException * 如果 baseName 是null * @throws IllegalArgumentException * 如果 baseName 是 blank * @throws MissingResourceException * 如果资源文件 baseName 不存在 * @see java.util.ResourceBundle#getBundle(String, Locale) */ public static ResourceBundle getResourceBundle(String baseName,Locale locale){ Validate.notBlank(baseName, "baseName can't be null/empty!"); return ResourceBundle.getBundle(baseName, defaultIfNull(locale, Locale.getDefault())); } //--------------------------------------------------------------- /** * 获得ResourceBundle({@link PropertyResourceBundle}),新增这个方法的初衷是为了能读取任意的资源(包括本地file等). * *

示例:

* *
* *

* 场景: 有配置文件在 E:\\DataCommon\\Files\\Java\\config\\mail-read.properties (通常敏感的配置文件不会随着项目走),现在需要读取里面的信息 *

* *
     * ResourceBundle resourceBundle = getResourceBundle(
     *                 FileUtil.getFileInputStream("E:\\DataCommon\\Files\\Java\\config\\mail-read.properties"));
     * LOGGER.debug(JsonUtil.format(toMap(resourceBundle)));
     * 
* * 返回: * *
     * {
     * "incoming.imap.hostname": "imap.exmail.qq.com",
     * "incoming.pop.hostname": "pop.exmail.qq.com",
     * }
     * 
* *
* * @param inputStream * the input stream * @return 如果 inputStream 是null,抛出 {@link NullPointerException}
* 否则返回 {@link java.util.PropertyResourceBundle#PropertyResourceBundle(InputStream)} * @see java.util.PropertyResourceBundle#PropertyResourceBundle(InputStream) * @since 1.0.9 */ public static ResourceBundle getResourceBundle(InputStream inputStream){ Validate.notNull(inputStream, "inputStream can't be null!"); //--------------------------------------------------------------- try{ return new PropertyResourceBundle(inputStream); }catch (IOException e){ throw new UncheckedIOException(e); }finally{ try{ inputStream.close(); }catch (IOException e){ LOGGER.error("", e); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy