diff --git a/CHANGELOG.md b/CHANGELOG.md index 58a5de0e6..3b93dc404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,17 @@ ------------------------------------------------------------------------------------------------------------- -# 5.6.3 (2021-03-31) +# 5.6.3 (2021-04-04) ### 新特性 * 【core 】 修改数字转换的实现,增加按照指定端序转换(pr#1492@Github) * 【core 】 修改拆分byte数组时最后一组长度的规则(pr#1494@Github) +* 【core 】 新增根据日期获取节气(pr#1496@Github) +* 【core 】 mapToBean()添加对布尔值is前缀的识别(pr#294@Gitee) + ### Bug修复 +* 【core 】 修复Validator.isUrl()传空返回true(issue#I3ETTY@Gitee) +* 【db 】 修复数据库driver根据url的判断识别错误问题(issue#I3EWBI@Gitee) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java index 16c1f3d74..7c84e4074 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java @@ -10,7 +10,14 @@ import java.util.Map; /** * Map值提供者,支持驼峰和下划线的key兼容。
- * 假设目标属性为firstName,则Map中为firstName或first_name都可以对应到值。 + * 假设目标属性为firstName,则Map中以下形式的值都可以对应: + * + * 为firstName或first_name都可以对应到值。 * * @author looly */ @@ -50,23 +57,53 @@ public class MapValueProvider implements ValueProvider { @Override public Object value(String key, Type valueType) { - Object value = map.get(key); - if (null == value) { - //检查下划线模式 - value = map.get(StrUtil.toUnderlineCase(key)); + final String key1 = getKey(key, valueType); + if (null == key1) { + return null; } + final Object value = map.get(key1); return Convert.convertWithCheck(valueType, value, null, this.ignoreError); } @Override public boolean containsKey(String key) { + return null != getKey(key, null); + } + + /** + * 获得map中可能包含的key,不包含返回null + * + * @param key map中可能包含的key + * @param valueType 值类型,用于判断是否为Boolean,可以为null + * @return map中可能包含的key + */ + private String getKey(String key, Type valueType) { if (map.containsKey(key)) { - return true; - } else { - //检查下划线模式 - return map.containsKey(StrUtil.toUnderlineCase(key)); + return key; } + + //检查下划线模式 + String customKey = StrUtil.toUnderlineCase(key); + if (map.containsKey(customKey)) { + return customKey; + } + + //检查boolean类型 + if (null == valueType || Boolean.class == valueType || boolean.class == valueType) { + //boolean类型字段字段名支持两种方式 + customKey = StrUtil.upperFirstAndAddPre(key, "is"); + if (map.containsKey(customKey)) { + return customKey; + } + + //检查下划线模式 + customKey = StrUtil.toUnderlineCase(customKey); + if (map.containsKey(customKey)) { + return customKey; + } + } + return null; } } diff --git a/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java b/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java index b9b08cd92..5d4ea75b7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java @@ -5,6 +5,7 @@ import cn.hutool.core.date.chinese.ChineseMonth; import cn.hutool.core.date.chinese.GanZhi; import cn.hutool.core.date.chinese.LunarFestival; import cn.hutool.core.date.chinese.LunarInfo; +import cn.hutool.core.date.chinese.SolarTerms; import cn.hutool.core.util.StrUtil; import java.util.Calendar; @@ -327,6 +328,16 @@ public class ChineseDate { return null; } + + /** + * 获得节气 + * @return 获得节气 + * @since 5.6.3 + */ + public String getTerm() { + return SolarTerms.getTerm(gyear, gmonth, gday); + } + /** * 转换为标准的日期格式来表示农历日期,例如2020-01-13 * diff --git a/hutool-core/src/main/java/cn/hutool/core/date/chinese/SolarTerms.java b/hutool-core/src/main/java/cn/hutool/core/date/chinese/SolarTerms.java index 597e60bc2..13c7c3b83 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/chinese/SolarTerms.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/chinese/SolarTerms.java @@ -1,45 +1,21 @@ package cn.hutool.core.date.chinese; +import cn.hutool.core.date.ChineseDate; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.NumberUtil; +import java.time.LocalDate; +import java.util.Date; + /** * 24节气相关信息 * - * @author looly + * @author looly, zak * @since 5.4.1 */ public class SolarTerms { - /** - * 根据节气修正干支月 - * - * @param y 月 - * @param n 节气 - * @return 干支月 - */ - public static int getTerm(int y, int n) { - if (y < 1900 || y > 2100) { - return -1; - } - if (n < 1 || n > 24) { - return -1; - } - - String _table = S_TERM_INFO[y - 1900]; - Integer[] _info = new Integer[6]; - for (int i = 0; i < 6; i++) { - _info[i] = NumberUtil.parseInt("0x" + _table.substring(i * 5, 5 * (i + 1))); - } - String[] _calday = new String[24]; - for (int i = 0; i < 6; i++) { - _calday[4 * i] = _info[i].toString().substring(0, 1); - _calday[4 * i + 1] = _info[i].toString().substring(1, 3); - _calday[4 * i + 2] = _info[i].toString().substring(3, 4); - _calday[4 * i + 3] = _info[i].toString().substring(4, 6); - } - return NumberUtil.parseInt(_calday[n - 1]); - } - /** * 1900-2100各年的24节气日期速查表 * 此表来自:https://github.com/jjonline/calendar.js/blob/master/calendar.js @@ -112,4 +88,121 @@ public class SolarTerms { "7f0e36665b66a449801e9808297c35", "665f67f0e37f14898082b072297c35", "7ec967f0e37f14998082b0787b06bd", "7f07e7f0e47f531b0723b0b6fb0721", "7f0e26665b66a449801e9808297c35", "665f67f0e37f1489801eb072297c35", "7ec967f0e37f14998082b0787b06bd", "7f07e7f0e47f531b0723b0b6fb0721", "7f0e27f1487f531b0b0bb0b6fb0722"}; + + /** + * 24节气 + */ + private static final String[] TERMS = { + "小寒", "大寒", "立春", "雨水", "惊蛰", "春分", + "清明", "谷雨", "立夏", "小满", "芒种", "夏至", + "小暑", "大暑", "立秋", "处暑", "白露", "秋分", + "寒露", "霜降", "立冬", "小雪", "大雪", "冬至" + }; + + /** + * 传入公历y年获得该年第n个节气的公历日期 + * + * @param y 公历年(1900-2100) + * @param n 二十四节气中的第几个节气(1~24);从n=1(小寒)算起 + * @return getTerm(1987,3) -》4;意即1987年2月4日立春 + */ + public static int getTerm(int y, int n) { + if (y < 1900 || y > 2100) { + return -1; + } + if (n < 1 || n > 24) { + return -1; + } + + final String _table = S_TERM_INFO[y - 1900]; + Integer[] _info = new Integer[6]; + for (int i = 0; i < 6; i++) { + _info[i] = Integer.parseInt(_table.substring(i * 5, 5 * (i + 1)), 16); + } + String[] _calday = new String[24]; + for (int i = 0; i < 6; i++) { + _calday[4 * i] = _info[i].toString().substring(0, 1); + _calday[4 * i + 1] = _info[i].toString().substring(1, 3); + _calday[4 * i + 2] = _info[i].toString().substring(3, 4); + _calday[4 * i + 3] = _info[i].toString().substring(4, 6); + } + return NumberUtil.parseInt(_calday[n - 1]); + } + + /** + * 根据日期获取节气 + * @param date 日期 + * @return 返回指定日期所处的节气 + */ + public static String getTerm(Date date) { + final DateTime dt = DateUtil.date(date); + return getTermInternal(dt.year(), dt.month() + 1, dt.dayOfMonth()); + } + + + /** + * 根据农历日期获取节气 + * @param chineseDate 农历日期 + * @return 返回指定日期所处的节气 + */ + public static String getTerm(ChineseDate chineseDate) { + return chineseDate.getTerm(); + } + + /** + * 根据日期获取节气 + * @param date 日期 + * @return 返回指定日期所处的节气 + */ + public static String getTerm(LocalDate date) { + return getTermInternal(date.getYear(), date.getMonthValue(), date.getDayOfMonth()); + } + + /** + * 根据年月日获取节气 + * @param year 公历年 + * @param mouth 公历月,从1开始 + * @param day 公历日,从1开始 + * @return 返回指定年月日所处的节气 + */ + public static String getTerm(int year, int mouth, int day) { + return getTerm(LocalDate.of(year, mouth, day)); + } + + /** + * 根据年月日获取节气, 内部方法,不对月和日做有效校验 + * @param year 年 + * @param mouth 月,从1计数 + * @param day 日,从1计数 + * @return 返回指定年月日所处的节气 + */ + private static String getTermInternal(int year, int mouth, int day) { + if (year < 1900 || year > 2100) { + throw new IllegalArgumentException("只支持1900-2100之间的日期获取节气"); + } + + final String termTable = S_TERM_INFO[year - 1900]; + + // 节气速查表中每5个字符含有4个节气,通过月份直接计算偏移 + final int segment = (mouth + 1) / 2 - 1; + final int termInfo = Integer.parseInt(termTable.substring(segment * 5, (segment + 1) * 5), 16); + final String termInfoStr = String.valueOf(termInfo); + + final String[] segmentTable = new String[24]; + segmentTable[0] = termInfoStr.substring(0, 1); + segmentTable[1] = termInfoStr.substring(1, 3); + segmentTable[2] = termInfoStr.substring(3, 4); + segmentTable[3] = termInfoStr.substring(4, 6); + + // 奇数月份的节气在前2个,偶数月份的节气在后两个 + int segmentOffset = (mouth & 1) == 1 ? 0 : 2; + if (day < NumberUtil.parseInt(segmentTable[segmentOffset])) { + int idx = segment * 4 + segmentOffset - 1; + return TERMS[idx < 0 ? 23 : idx]; + } + if(day >= NumberUtil.parseInt(segmentTable[segmentOffset + 1])) { + return TERMS[segment * 4 + segmentOffset + 1]; + } + return TERMS[segment * 4 + segmentOffset]; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java b/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java index bca1d0ae5..cde7cb4ab 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java @@ -951,6 +951,9 @@ public class Validator { * @return 是否为URL */ public static boolean isUrl(CharSequence value) { + if(StrUtil.isBlank(value)){ + return false; + } try { new java.net.URL(StrUtil.str(value)); } catch (MalformedURLException e) { diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java index 209e4dba5..2c3621871 100644 --- a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java @@ -10,6 +10,7 @@ import cn.hutool.core.util.StrUtil; import lombok.Data; import lombok.Getter; import lombok.Setter; +import lombok.ToString; import org.junit.Assert; import org.junit.Test; @@ -18,13 +19,7 @@ import java.io.Serializable; import java.lang.reflect.Type; import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.UUID; +import java.util.*; /** * Bean工具单元测试 @@ -201,6 +196,8 @@ public class BeanUtilTest { person.setName("测试A11"); person.setSubName("sub名字"); person.setSlow(true); + person.setBooleana(true); + person.setBooleanb(true); Map map = BeanUtil.beanToMap(person); Assert.assertEquals("sub名字", map.get("aliasSubName")); @@ -211,9 +208,13 @@ public class BeanUtilTest { Map map = MapUtil.newHashMap(); map.put("aliasSubName", "sub名字"); map.put("slow", true); + map.put("is_booleana", "1"); + map.put("is_booleanb", true); final SubPersonWithAlias subPersonWithAlias = BeanUtil.toBean(map, SubPersonWithAlias.class); Assert.assertEquals("sub名字", subPersonWithAlias.getSubName()); + Assert.assertTrue(subPersonWithAlias.isBooleana()); + Assert.assertEquals(true, subPersonWithAlias.getBooleanb()); } @Test @@ -360,11 +361,14 @@ public class BeanUtilTest { @Getter @Setter + @ToString public static class SubPersonWithAlias extends Person { // boolean参数值非isXXX形式 @Alias("aliasSubName") private String subName; private Boolean slow; + private boolean booleana; + private Boolean booleanb; } @Getter diff --git a/hutool-core/src/test/java/cn/hutool/core/date/chinese/SolarTermsTest.java b/hutool-core/src/test/java/cn/hutool/core/date/chinese/SolarTermsTest.java new file mode 100644 index 000000000..909af0fb2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/date/chinese/SolarTermsTest.java @@ -0,0 +1,105 @@ +package cn.hutool.core.date.chinese; + +import cn.hutool.core.date.ChineseDate; +import cn.hutool.core.date.DateUtil; +import org.junit.Assert; +import org.junit.Test; + +public class SolarTermsTest { + + @Test + public void getTermTest1(){ + final int term = SolarTerms.getTerm(1987, 3); + Assert.assertEquals(4, term); + } + + @Test + public void getTermTest() { + Assert.assertEquals("冬至", SolarTerms.getTerm(2021, 1, 4)); + + Assert.assertEquals("小寒", SolarTerms.getTerm(2021, 1, 5)); + Assert.assertEquals("小寒", SolarTerms.getTerm(2021, 1, 19)); + + Assert.assertEquals("大寒", SolarTerms.getTerm(2021, 1, 20)); + Assert.assertEquals("大寒", SolarTerms.getTerm(2021, 2, 2)); + + Assert.assertEquals("立春", SolarTerms.getTerm(2021, 2, 3)); + Assert.assertEquals("立春", SolarTerms.getTerm(2021, 2, 17)); + + Assert.assertEquals("雨水", SolarTerms.getTerm(2021, 2, 18)); + Assert.assertEquals("雨水", SolarTerms.getTerm(2021, 3, 4)); + + Assert.assertEquals("惊蛰", SolarTerms.getTerm(2021, 3, 5)); + Assert.assertEquals("惊蛰", SolarTerms.getTerm(2021, 3, 19)); + + Assert.assertEquals("春分", SolarTerms.getTerm(2021, 3, 20)); + Assert.assertEquals("春分", SolarTerms.getTerm(2021, 4, 3)); + + Assert.assertEquals("清明", SolarTerms.getTerm(2021, 4, 4)); + Assert.assertEquals("清明", SolarTerms.getTerm(2021, 4, 10)); + Assert.assertEquals("清明", SolarTerms.getTerm(2021, 4, 19)); + + Assert.assertEquals("谷雨", SolarTerms.getTerm(2021, 4, 20)); + Assert.assertEquals("谷雨", SolarTerms.getTerm(2021, 4, 29)); + Assert.assertEquals("谷雨", SolarTerms.getTerm(2021, 5, 4)); + + Assert.assertEquals("立夏", SolarTerms.getTerm(2021, 5, 5)); + Assert.assertEquals("立夏", SolarTerms.getTerm(2021, 5, 9)); + Assert.assertEquals("立夏", SolarTerms.getTerm(2021, 5, 20)); + + Assert.assertEquals("小满", SolarTerms.getTerm(2021, 5, 21)); + Assert.assertEquals("小满", SolarTerms.getTerm(2021, 6, 4)); + + Assert.assertEquals("芒种", SolarTerms.getTerm(2021, 6, 5)); + Assert.assertEquals("芒种", SolarTerms.getTerm(2021, 6, 20)); + + Assert.assertEquals("夏至", SolarTerms.getTerm(2021, 6, 21)); + Assert.assertEquals("夏至", SolarTerms.getTerm(2021, 7, 6)); + + Assert.assertEquals("小暑", SolarTerms.getTerm(2021, 7, 7)); + Assert.assertEquals("小暑", SolarTerms.getTerm(2021, 7, 21)); + + Assert.assertEquals("大暑", SolarTerms.getTerm(2021, 7, 22)); + Assert.assertEquals("大暑", SolarTerms.getTerm(2021, 8, 6)); + + Assert.assertEquals("立秋", SolarTerms.getTerm(2021, 8, 7)); + + Assert.assertEquals("处暑", SolarTerms.getTerm(2021, 8, 23)); + Assert.assertEquals("处暑", SolarTerms.getTerm(2021, 9, 6)); + + Assert.assertEquals("白露", SolarTerms.getTerm(2021, 9, 7)); + Assert.assertEquals("白露", SolarTerms.getTerm(2021, 9, 22)); + + Assert.assertEquals("秋分", SolarTerms.getTerm(2021, 9, 23)); + Assert.assertEquals("秋分", SolarTerms.getTerm(2021, 10, 7)); + + Assert.assertEquals("寒露", SolarTerms.getTerm(2021, 10, 8)); + Assert.assertEquals("寒露", SolarTerms.getTerm(2021, 10, 22)); + + Assert.assertEquals("霜降", SolarTerms.getTerm(2021, 10, 23)); + Assert.assertEquals("霜降", SolarTerms.getTerm(2021, 11, 6)); + + Assert.assertEquals("立冬", SolarTerms.getTerm(2021, 11, 7)); + Assert.assertEquals("立冬", SolarTerms.getTerm(2021, 11, 21)); + + Assert.assertEquals("小雪", SolarTerms.getTerm(2021, 11, 22)); + Assert.assertEquals("小雪", SolarTerms.getTerm(2021, 12, 6)); + + Assert.assertEquals("大雪", SolarTerms.getTerm(2021, 12, 7)); + Assert.assertEquals("大雪", SolarTerms.getTerm(2021, 12, 20)); + + Assert.assertEquals("冬至", SolarTerms.getTerm(2021, 12, 21)); + } + + + @Test + public void getTermByDateTest() { + Assert.assertEquals("春分", SolarTerms.getTerm(DateUtil.parseDate("2021-04-02"))); + } + + + @Test + public void getTermByChineseDateTest() { + Assert.assertEquals("清明", SolarTerms.getTerm(new ChineseDate(2021, 2, 25))); + } +} diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java index 0140ac43c..78e35fa2a 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java @@ -1,6 +1,7 @@ package cn.hutool.db.dialect; import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.dialect.impl.AnsiSqlDialect; import cn.hutool.db.dialect.impl.H2Dialect; @@ -115,6 +116,12 @@ public class DialectFactory { // 全部转为小写,忽略大小写 nameContainsProductInfo = StrUtil.cleanBlank(nameContainsProductInfo.toLowerCase()); + // 首先判断是否为标准的JDBC URL,截取jdbc:xxxx:中间部分 + final String name = ReUtil.getGroup1("jdbc:(.*?):", nameContainsProductInfo); + if(StrUtil.isNotBlank(name)){ + nameContainsProductInfo = name; + } + String driver = null; if (nameContainsProductInfo.contains("mysql")) { driver = ClassLoaderUtil.isPresent(DRIVER_MYSQL_V6) ? DRIVER_MYSQL_V6 : DRIVER_MYSQL; diff --git a/hutool-db/src/test/java/cn/hutool/db/dialect/DriverUtilTest.java b/hutool-db/src/test/java/cn/hutool/db/dialect/DriverUtilTest.java new file mode 100644 index 000000000..7dacba36a --- /dev/null +++ b/hutool-db/src/test/java/cn/hutool/db/dialect/DriverUtilTest.java @@ -0,0 +1,14 @@ +package cn.hutool.db.dialect; + +import org.junit.Assert; +import org.junit.Test; + +public class DriverUtilTest { + + @Test + public void identifyDriverTest(){ + String url = "jdbc:h2:file:./db/test;AUTO_SERVER=TRUE;DB_CLOSE_ON_EXIT=FALSE;MODE=MYSQL"; + String driver = DriverUtil.identifyDriver(url); // driver 返回 mysql 的 driver + Assert.assertEquals("org.h2.Driver", driver); + } +}