Compare commits

...

12 Commits

Author SHA1 Message Date
Looly
5ae493119f fix doc 2025-11-26 20:05:44 +08:00
Looly
ea717843f6 fix doc 2025-11-26 20:04:48 +08:00
Looly
b8908cf3ef 增强HexUtil自动去除0x#前缀(pr#4163@Github) 2025-11-26 20:02:12 +08:00
Golden Looly
ab1774d4e6
Merge pull request #4163 from sunshineflymeat/hutool-1126-3
Fix issue 4162
2025-11-26 19:57:15 +08:00
Looly
ccaecf6bc9 Word07Writer增加addText重载,支持字体颜色(pr#1388@Gitee) 2025-11-26 19:44:34 +08:00
Looly
3d2dd38add
!1388 feat:Word生成器,增加段落新增字体颜色参数
Merge pull request !1388 from liyong473/v5-dev
2025-11-26 11:41:26 +00:00
Looly
d64a0d36aa 修复DateModifier处理AM和PM的ceiling和round问题(pr#4161@Github) 2025-11-26 17:24:16 +08:00
Golden Looly
ab936327a2
Merge pull request #4161 from asukavuuyn/v5-dev
fix:12小时制ceiling和round问题
2025-11-26 17:22:07 +08:00
liyong
6ef5f1c1fd feat:Word生成器,增加段落新增字体颜色参数 2025-11-26 17:16:51 +08:00
ZWM
6776ddb29d Fix issue 4162 2025-11-26 17:11:18 +08:00
Looly
449df10509 修复ReflectUtil.newInstanceIfPossible传入Object逻辑错误(pr#4160@Github) 2025-11-26 16:30:34 +08:00
asukavuuyn
fc5e1ecff9 fix:12小时制ceiling和round问题 2025-11-26 15:19:41 +08:00
10 changed files with 200 additions and 39 deletions

View File

@ -11,6 +11,8 @@
* 【core 】 `Combination``Arrangement `重构避免数组频繁拷贝并避免溢出pr#4144@Github * 【core 】 `Combination``Arrangement `重构避免数组频繁拷贝并避免溢出pr#4144@Github
* 【core 】 优化`EscapeUtil`兼容不规范的转义pr#4150@Github * 【core 】 优化`EscapeUtil`兼容不规范的转义pr#4150@Github
* 【core 】 优化`ObjectUtil.contains`String改为CharSequencepr#4154@Github * 【core 】 优化`ObjectUtil.contains`String改为CharSequencepr#4154@Github
* 【poi 】 `Word07Writer`增加addText重载支持字体颜色pr#1388@Gitee
* 【core 】 增强`HexUtil`自动去除`0x``#`前缀pr#4163@Github
### 🐞Bug修复 ### 🐞Bug修复
* 【jwt 】 修复verify方法在定义alg为`none`时验证失效问题issue#4105@Github * 【jwt 】 修复verify方法在定义alg为`none`时验证失效问题issue#4105@Github
@ -31,6 +33,8 @@
* 【core 】 修复`URLUtil.url`未断开连接问题pr#4149@Github * 【core 】 修复`URLUtil.url`未断开连接问题pr#4149@Github
* 【core 】 修复`Bimap.put`重复put问题pr#4150@Github * 【core 】 修复`Bimap.put`重复put问题pr#4150@Github
* 【core 】 修复`StrUtil.str(ByteBuffer, Charset)` 方法修改入参 `ByteBuffer``position`,导致入参变化 pr#4153@Github * 【core 】 修复`StrUtil.str(ByteBuffer, Charset)` 方法修改入参 `ByteBuffer``position`,导致入参变化 pr#4153@Github
* 【core 】 修复`ReflectUtil.newInstanceIfPossible`传入Object逻辑错误pr#4160@Github
* 【core 】 修复`DateModifier`处理AM和PM的ceiling和round问题pr#4161@Github
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------
# 5.8.41(2025-10-12) # 5.8.41(2025-10-12)

View File

@ -73,13 +73,13 @@ public class DateModifier {
case ROUND: case ROUND:
int min = isAM ? 0 : 12; int min = isAM ? 0 : 12;
int max = isAM ? 11 : 23; int max = isAM ? 11 : 23;
int href = (max - min) / 2 + 1; int href = min + (max - min) / 2 + 1;
int value = calendar.get(Calendar.HOUR_OF_DAY); int value = calendar.get(Calendar.HOUR_OF_DAY);
calendar.set(Calendar.HOUR_OF_DAY, (value < href) ? min : max); calendar.set(Calendar.HOUR_OF_DAY, (value < href) ? min : max);
break; break;
} }
// 处理下一级别字段 // 处理下一级别字段
return modify(calendar, dateField + 1, modifyType); return modify(calendar, dateField + 1, modifyType, truncateMillisecond);
} }
final int endField = truncateMillisecond ? Calendar.SECOND : Calendar.MILLISECOND; final int endField = truncateMillisecond ? Calendar.SECOND : Calendar.MILLISECOND;

View File

@ -57,9 +57,9 @@ public class Combination implements Serializable {
* 1. 利用对称性 m = min(m, n-m) * 1. 利用对称性 m = min(m, n-m)
* 2. 每一步先乘 BigInteger再除以当前 i保证数值不暴涨 * 2. 每一步先乘 BigInteger再除以当前 i保证数值不暴涨
* *
* @param n 总数 n必须 >= 0 * @param n 总数 n必须 大于等于 0
* @param m 取出 m必须 >= 0 * @param m 取出 m必须 大于等于 0
* @return C(n, m) BigInteger 精确值 m > n 时返回 BigInteger.ZERO * @return C(n, m) BigInteger 精确值 m 大于 n 时返回 BigInteger.ZERO
*/ */
public static BigInteger countBig(int n, int m) { public static BigInteger countBig(int n, int m) {
if (n < 0 || m < 0) { if (n < 0 || m < 0) {
@ -87,9 +87,10 @@ public class Combination implements Serializable {
/** /**
* 安全组合数 long 版本 * 安全组合数 long 版本
* *
* @param n 总数 n必须 >= 0 * @param n 总数 n必须 大于等于 0
* @param m 取出 m必须 >= 0 * @param m 取出 m必须 大于等于 0
* <p>若结果超出 long 范围会抛 ArithmeticException而非溢出</p> * <p>若结果超出 long 范围会抛 ArithmeticException而非溢出</p>
* @return C(n, m) long 精确值 m 大于 n 时返回 0L
*/ */
public static long countSafe(int n, int m) { public static long countSafe(int n, int m) {
BigInteger big = countBig(n, m); BigInteger big = countBig(n, m);

View File

@ -27,7 +27,7 @@ public class HexUtil {
* @return 是否为16进制 * @return 是否为16进制
*/ */
public static boolean isHexNumber(String value) { public static boolean isHexNumber(String value) {
if(StrUtil.startWith(value, '-')){ if (StrUtil.startWith(value, '-')) {
// issue#2875 // issue#2875
return false; return false;
} }
@ -35,7 +35,7 @@ public class HexUtil {
if (value.startsWith("0x", index) || value.startsWith("0X", index)) { if (value.startsWith("0x", index) || value.startsWith("0X", index)) {
index += 2; index += 2;
} else if (value.startsWith("#", index)) { } else if (value.startsWith("#", index)) {
index ++; index++;
} }
try { try {
new BigInteger(value.substring(index), 16); new BigInteger(value.substring(index), 16);
@ -304,7 +304,7 @@ public class HexUtil {
* @since 5.7.4 * @since 5.7.4
*/ */
public static int hexToInt(String value) { public static int hexToInt(String value) {
return Integer.parseInt(value, 16); return Integer.parseInt(removeHexPrefix(value), 16);
} }
/** /**
@ -326,7 +326,7 @@ public class HexUtil {
* @since 5.7.4 * @since 5.7.4
*/ */
public static long hexToLong(String value) { public static long hexToLong(String value) {
return Long.parseLong(value, 16); return Long.parseLong(removeHexPrefix(value), 16);
} }
/** /**
@ -352,7 +352,7 @@ public class HexUtil {
if (null == hexStr) { if (null == hexStr) {
return null; return null;
} }
return new BigInteger(hexStr, 16); return new BigInteger(removeHexPrefix(hexStr), 16);
} }
/** /**
@ -392,4 +392,24 @@ public class HexUtil {
return builder.toString(); return builder.toString();
} }
/**
* 去除十六进制字符串的常见前缀 0x0X#
*
* @param hexStr 十六进制字符串
* @return 去除前缀后的字符串
*/
private static String removeHexPrefix(String hexStr) {
if (StrUtil.length(hexStr) > 1) {
final char c0 = hexStr.charAt(0);
switch (c0) {
case '0':
if (hexStr.charAt(1) == 'x' || hexStr.charAt(1) == 'X') {
return hexStr.substring(2);
}
case '#':
return hexStr.substring(1);
}
}
return hexStr;
}
} }

View File

@ -888,13 +888,19 @@ public class ReflectUtil {
return (T) ClassUtil.getPrimitiveDefaultValue(type); return (T) ClassUtil.getPrimitiveDefaultValue(type);
} }
// 某些特殊接口的实例化按照默认实现进行 if (Object.class != type) {
if (type.isAssignableFrom(AbstractMap.class)) { // 某些特殊接口的实例化按照默认实现进行
type = (Class<T>) HashMap.class; if (type.isAssignableFrom(AbstractMap.class)) {
} else if (type.isAssignableFrom(List.class)) { type = (Class<T>) HashMap.class;
type = (Class<T>) ArrayList.class; } else if (type.isAssignableFrom(List.class)) {
} else if (type.isAssignableFrom(Set.class)) { type = (Class<T>) ArrayList.class;
type = (Class<T>) HashSet.class; } else if (type.isAssignableFrom(Set.class)) {
type = (Class<T>) HashSet.class;
} else if (type.isAssignableFrom(Queue.class)) {
type = (Class<T>) LinkedList.class;
} else if (type.isAssignableFrom(Deque.class)) {
type = (Class<T>) LinkedList.class;
}
} }
try { try {

View File

@ -130,6 +130,33 @@ public class DateUtilTest {
assertEquals("2020-02-29 12:59:59.000", dateTime.toString(DatePattern.NORM_DATETIME_MS_PATTERN)); assertEquals("2020-02-29 12:59:59.000", dateTime.toString(DatePattern.NORM_DATETIME_MS_PATTERN));
} }
@Test
public void cellingAmPmTest(){
final String dateStr2 = "2020-02-29 10:59:34";
final Date date2 = DateUtil.parse(dateStr2);
DateTime dateTime = DateUtil.ceiling(date2, DateField.AM_PM);
assertEquals("2020-02-29 11:59:59.999", dateTime.toString(DatePattern.NORM_DATETIME_MS_PATTERN));
dateTime = DateUtil.ceiling(date2, DateField.AM_PM, true);
assertEquals("2020-02-29 11:59:59.000", dateTime.toString(DatePattern.NORM_DATETIME_MS_PATTERN));
}
@Test void roundAmPmTest() {
final String dateStr = "2020-02-29 13:59:34";
final Date date = DateUtil.parse(dateStr);
DateTime dateTime = DateUtil.round(date, DateField.AM_PM);
assertEquals("2020-02-29 12:59:59.000", dateTime.toString(DatePattern.NORM_DATETIME_MS_PATTERN));
final String dateStr2 = "2020-02-29 18:59:34";
final Date date2 = DateUtil.parse(dateStr2);
DateTime dateTime2 = DateUtil.round(date2, DateField.AM_PM);
assertEquals("2020-02-29 23:59:59.000", dateTime2.toString(DatePattern.NORM_DATETIME_MS_PATTERN));
}
@Test @Test
public void ceilingDayTest() { public void ceilingDayTest() {
final String dateStr2 = "2020-02-29 12:59:34"; final String dateStr2 = "2020-02-29 12:59:34";

View File

@ -3,6 +3,7 @@ package cn.hutool.core.util;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
/** /**
@ -85,4 +86,34 @@ public class HexUtilTest {
final String s1 = HexUtil.decodeHexStr(s); final String s1 = HexUtil.decodeHexStr(s);
assertEquals("6", s1); assertEquals("6", s1);
} }
@Test
public void hexToIntTest() {
final String hex1 = "FF";
assertEquals(255, HexUtil.hexToInt(hex1));
final String hex2 = "0xFF";
assertEquals(255, HexUtil.hexToInt(hex2));
final String hex3 = "#FF";
assertEquals(255, HexUtil.hexToInt(hex3));
}
@Test
public void hexToLongTest() {
final String hex1 = "FF";
assertEquals(255L, HexUtil.hexToLong(hex1));
final String hex2 = "0xFF";
assertEquals(255L, HexUtil.hexToLong(hex2));
final String hex3 = "#FF";
assertEquals(255L, HexUtil.hexToLong(hex3));
}
@Test
public void toBigIntegerTest() {
final String hex1 = "FF";
assertEquals(new BigInteger("FF", 16), HexUtil.toBigInteger(hex1));
final String hex2 = "0xFF";
assertEquals(new BigInteger("FF", 16), HexUtil.toBigInteger(hex2));
final String hex3 = "#FF";
assertEquals(new BigInteger("FF", 16), HexUtil.toBigInteger(hex3));
}
} }

View File

@ -151,7 +151,7 @@ public class ReflectUtilTest {
final TestClass testClass = new TestClass(); final TestClass testClass = new TestClass();
final Method method = ReflectUtil.getMethod(TestClass.class, "setA", int.class); final Method method = ReflectUtil.getMethod(TestClass.class, "setA", int.class);
assertThrows(IllegalArgumentException.class, assertThrows(IllegalArgumentException.class,
() -> ReflectUtil.invoke(testClass, method, "NaN")); () -> ReflectUtil.invoke(testClass, method, "NaN"));
} }
@Test @Test
@ -210,6 +210,7 @@ public class ReflectUtilTest {
private String n; private String n;
} }
@SuppressWarnings("UnusedReturnValue")
public static Method getMethodWithReturnTypeCheck(final Class<?> clazz, final boolean ignoreCase, final String methodName, final Class<?>... paramTypes) throws SecurityException { public static Method getMethodWithReturnTypeCheck(final Class<?> clazz, final boolean ignoreCase, final String methodName, final Class<?>... paramTypes) throws SecurityException {
if (null == clazz || StrUtil.isBlank(methodName)) { if (null == clazz || StrUtil.isBlank(methodName)) {
return null; return null;
@ -220,9 +221,9 @@ public class ReflectUtilTest {
if (ArrayUtil.isNotEmpty(methods)) { if (ArrayUtil.isNotEmpty(methods)) {
for (final Method method : methods) { for (final Method method : methods) {
if (StrUtil.equals(methodName, method.getName(), ignoreCase) if (StrUtil.equals(methodName, method.getName(), ignoreCase)
&& ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes) && ClassUtil.isAllAssignableFrom(method.getParameterTypes(), paramTypes)
&& (res == null && (res == null
|| res.getReturnType().isAssignableFrom(method.getReturnType()))) { || res.getReturnType().isAssignableFrom(method.getReturnType()))) {
res = method; res = method;
} }
} }
@ -300,6 +301,7 @@ public class ReflectUtilTest {
} }
class C2 extends C1 { class C2 extends C1 {
@SuppressWarnings("RedundantMethodOverride")
@Override @Override
public void getA() { public void getA() {
@ -307,7 +309,7 @@ public class ReflectUtilTest {
} }
@Test @Test
public void newInstanceIfPossibleTest(){ public void newInstanceIfPossibleTest() {
//noinspection ConstantConditions //noinspection ConstantConditions
final int intValue = ReflectUtil.newInstanceIfPossible(int.class); final int intValue = ReflectUtil.newInstanceIfPossible(int.class);
assertEquals(0, intValue); assertEquals(0, intValue);
@ -330,19 +332,19 @@ public class ReflectUtilTest {
public static class JdbcDialects { public static class JdbcDialects {
private static final List<Number> DIALECTS = private static final List<Number> DIALECTS =
Arrays.asList(1L, 2L, 3L); Arrays.asList(1L, 2L, 3L);
} }
@Test @Test
public void setFieldValueWithFinalTest() { public void setFieldValueWithFinalTest() {
final String fieldName = "DIALECTS"; final String fieldName = "DIALECTS";
final List<Number> dialects = final List<Number> dialects =
Arrays.asList( Arrays.asList(
1, 1,
2, 2,
3, 3,
99 99
); );
final Field field = ReflectUtil.getField(JdbcDialects.class, fieldName); final Field field = ReflectUtil.getField(JdbcDialects.class, fieldName);
ReflectUtil.removeFinalModify(field); ReflectUtil.removeFinalModify(field);
ReflectUtil.setFieldValue(JdbcDialects.class, fieldName, dialects); ReflectUtil.setFieldValue(JdbcDialects.class, fieldName, dialects);
@ -351,24 +353,63 @@ public class ReflectUtilTest {
} }
@Test @Test
public void issue2625Test(){ public void issue2625Test() {
// 内部类继承的情况下父类方法会被定义为桥接方法因此按照pr#1965@Github判断返回值的继承关系来代替判断桥接 // 内部类继承的情况下父类方法会被定义为桥接方法因此按照pr#1965@Github判断返回值的继承关系来代替判断桥接
final Method getThis = ReflectUtil.getMethod(A.C.class, "getThis"); final Method getThis = ReflectUtil.getMethod(A.C.class, "getThis");
assertTrue(getThis.isBridge()); assertTrue(getThis.isBridge());
} }
@SuppressWarnings("InnerClassMayBeStatic") @SuppressWarnings("InnerClassMayBeStatic")
public class A{ public class A {
public class C extends B{ public class C extends B {
} }
protected class B{ protected class B {
public B getThis(){ public B getThis() {
return this; return this;
} }
} }
} }
@Test
public void newInstanceIfPossibleTest2() {
// 测试Object.class不应该被错误地实例化为HashMap应该返回Object实例
Object objectInstance = ReflectUtil.newInstanceIfPossible(Object.class);
assertNotNull(objectInstance);
assertEquals(Object.class, objectInstance.getClass());
// 测试Map.class能够正确实例化为HashMap
Map<?, ?> mapInstance = ReflectUtil.newInstanceIfPossible(Map.class);
assertNotNull(mapInstance);
assertInstanceOf(HashMap.class, mapInstance);
// 测试Collection.class能够正确实例化为ArrayList
Collection<?> collectionInstance = ReflectUtil.newInstanceIfPossible(Collection.class);
assertNotNull(collectionInstance);
assertInstanceOf(ArrayList.class, collectionInstance);
// 测试List.class能够正确实例化为ArrayList
List<?> listInstance = ReflectUtil.newInstanceIfPossible(List.class);
assertNotNull(listInstance);
assertInstanceOf(ArrayList.class, listInstance);
// 测试Set.class能够正确实例化为HashSet
Set<?> setInstance = ReflectUtil.newInstanceIfPossible(Set.class);
assertNotNull(setInstance);
assertInstanceOf(HashSet.class, setInstance);
// 测试Queue接口能够正确实例化为LinkedList
Queue<?> queueInstance = ReflectUtil.newInstanceIfPossible(Queue.class);
assertNotNull(queueInstance);
assertInstanceOf(LinkedList.class, queueInstance);
// 测试Deque接口能够正确实例化为LinkedList
Deque<?> dequeInstance = ReflectUtil.newInstanceIfPossible(Deque.class);
assertNotNull(dequeInstance);
assertInstanceOf(LinkedList.class, dequeInstance);
}
} }

View File

@ -13,7 +13,7 @@ import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph; import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun; import org.apache.poi.xwpf.usermodel.XWPFRun;
import java.awt.Font; import java.awt.*;
import java.io.Closeable; import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -102,7 +102,19 @@ public class Word07Writer implements Closeable {
* @return this * @return this
*/ */
public Word07Writer addText(Font font, String... texts) { public Word07Writer addText(Font font, String... texts) {
return addText(null, font, texts); return addText(null, font, null, texts);
}
/**
* 增加一个段落
*
* @param font 字体信息{@link Font}
* @param color 字体颜色{@link Color}
* @param texts 段落中的文本支持多个文本作为一个段落
* @return this
*/
public Word07Writer addText(Font font, Color color, String... texts) {
return addText(null, font, color, texts);
} }
/** /**
@ -114,6 +126,20 @@ public class Word07Writer implements Closeable {
* @return this * @return this
*/ */
public Word07Writer addText(ParagraphAlignment align, Font font, String... texts) { public Word07Writer addText(ParagraphAlignment align, Font font, String... texts) {
return addText(align, font, null, texts);
}
/**
* 增加一个段落
*
* @param align 段落对齐方式{@link ParagraphAlignment}
* @param font 字体信息{@link Font}
* @param color 字体颜色{@link Color}
* @param texts 段落中的文本支持多个文本作为一个段落
* @return this
* @since 5.8.42
*/
public Word07Writer addText(ParagraphAlignment align, Font font, Color color, String... texts) {
final XWPFParagraph p = this.doc.createParagraph(); final XWPFParagraph p = this.doc.createParagraph();
if (null != align) { if (null != align) {
p.setAlignment(align); p.setAlignment(align);
@ -129,6 +155,10 @@ public class Word07Writer implements Closeable {
run.setBold(font.isBold()); run.setBold(font.isBold());
run.setItalic(font.isItalic()); run.setItalic(font.isItalic());
} }
if (null != color) {
String hexColor = String.format("%02X", color.getRGB());
run.setColor(hexColor);
}
} }
} }
return this; return this;

View File

@ -8,7 +8,7 @@ import cn.hutool.core.lang.Console;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.awt.Font; import java.awt.*;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -23,6 +23,7 @@ public class WordWriterTest {
Word07Writer writer = new Word07Writer(); Word07Writer writer = new Word07Writer();
writer.addText(new Font("方正小标宋简体", Font.PLAIN, 22), "我是第一部分", "我是第二部分"); writer.addText(new Font("方正小标宋简体", Font.PLAIN, 22), "我是第一部分", "我是第二部分");
writer.addText(new Font("宋体", Font.PLAIN, 22), "我是正文第一部分", "我是正文第二部分"); writer.addText(new Font("宋体", Font.PLAIN, 22), "我是正文第一部分", "我是正文第二部分");
writer.addText(new Font("宋体", Font.PLAIN, 22), Color.RED, "我是正文第三部分", "我是正文第四部分");
writer.flush(FileUtil.file("e:/wordWrite.docx")); writer.flush(FileUtil.file("e:/wordWrite.docx"));
writer.close(); writer.close();
Console.log("OK"); Console.log("OK");