Merge branch 'v5-dev' of https://github.com/totalo/hutool into v5-dev

This commit is contained in:
totalo
2020-10-10 23:11:43 +08:00
53 changed files with 1024 additions and 502 deletions

View File

@@ -252,8 +252,8 @@ public class ListUtil {
return new ArrayList<>(0);
}
}
if ((pageNo * pageSize) > resultSize) {
// 相乘可能会导致越界 临时用long
if (((long)pageNo * pageSize) > resultSize) {
// 越界直接返回空
return new ArrayList<>(0);
}

View File

@@ -167,7 +167,7 @@ public class ChineseDate {
* @return 是否为闰月
* @since 5.4.2
*/
public boolean isLeapMonth(){
public boolean isLeapMonth() {
return ChineseMonth.isLeapMonth(this.year, this.month);
}
@@ -230,7 +230,7 @@ public class ChineseDate {
* @return 获得农历节日
*/
public String getFestivals() {
return StrUtil.join(",", LunarFestival.getFestivals(this.month, this.day));
return StrUtil.join(",", LunarFestival.getFestivals(this.year, this.month, day));
}
/**
@@ -258,7 +258,7 @@ public class ChineseDate {
* @return 获得天干地支的年月日信息
*/
public String getCyclicalYMD() {
if (gyear >= LunarInfo.BASE_YEAR && gmonth > 0 && gday > 0){
if (gyear >= LunarInfo.BASE_YEAR && gmonth > 0 && gday > 0) {
return (cyclicalm(gyear, gmonth, gday));
}
return null;

View File

@@ -17,6 +17,7 @@ public class LunarFestival {
// 来自https://baike.baidu.com/item/%E4%B8%AD%E5%9B%BD%E4%BC%A0%E7%BB%9F%E8%8A%82%E6%97%A5/396100
private static final TableMap<Pair<Integer, Integer>, String> L_FTV = new TableMap<>(16);
static{
// 节日
L_FTV.put(new Pair<>(1, 1), "春节");
L_FTV.put(new Pair<>(1, 2), "犬日");
L_FTV.put(new Pair<>(1, 3), "猪日");
@@ -78,7 +79,6 @@ public class LunarFestival {
L_FTV.put(new Pair<>(12, 8), "腊八节");
L_FTV.put(new Pair<>(12, 16), "尾牙");
L_FTV.put(new Pair<>(12, 23), "小年");
L_FTV.put(new Pair<>(12, 29), "除夕");
L_FTV.put(new Pair<>(12, 30), "除夕");
}
@@ -88,6 +88,24 @@ public class LunarFestival {
* @param month 月
* @param day 日
* @return 获得农历节日
* @since 5.4.5
*/
public static List<String> getFestivals(int year, int month, int day) {
// 春节判断如果12月是小月则29为除夕否则30为除夕
if(12 == month && 29 == day){
if(29 == LunarInfo.monthDays(year, month)){
day++;
}
}
return getFestivals(month, day);
}
/**
* 获得节日列表,此方法无法判断月是否为大月或小月
*
* @param month 月
* @param day 日
* @return 获得农历节日
*/
public static List<String> getFestivals(int month, int day) {
return L_FTV.getValues(new Pair<>(month, day));

View File

@@ -0,0 +1,151 @@
package cn.hutool.core.lang;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 控制台打印表格工具
*
* @author 孙宇
* @since 5.4.4
*/
public class ConsoleTable {
private static final char ROW_LINE = '-';
private static final char COLUMN_LINE = '|';
private static final char CORNER = '+';
private static final char SPACE = '\u3000';
private static final char LF = CharUtil.LF;
/**
* 表格头信息
*/
private final List<List<String>> HEADER_LIST = new ArrayList<>();
/**
* 表格体信息
*/
private final List<List<String>> BODY_LIST = new ArrayList<>();
/**
* 每列最大字符个数
*/
private List<Integer> columnCharNumber;
/**
* 添加头信息
*
* @param titles 列名
* @return 自身对象
*/
public ConsoleTable addHeader(String... titles) {
if (columnCharNumber == null) {
columnCharNumber = new ArrayList<>(Collections.nCopies(titles.length, 0));
}
List<String> l = new ArrayList<>();
fillColumns(l, titles);
HEADER_LIST.add(l);
return this;
}
/**
* 添加体信息
*
* @param values 列值
* @return 自身对象
*/
public ConsoleTable addBody(String... values) {
List<String> l = new ArrayList<>();
BODY_LIST.add(l);
fillColumns(l, values);
return this;
}
/**
* 填充表格头或者体
*
* @param l 被填充列表
* @param columns 填充内容
*/
private void fillColumns(List<String> l, String[] columns) {
for (int i = 0; i < columns.length; i++) {
String column = columns[i];
String col = Convert.toSBC(column);
l.add(col);
int width = col.length();
if (width > columnCharNumber.get(i)) {
columnCharNumber.set(i, width);
}
}
}
/**
* 获取表格字符串
*
* @return 表格字符串
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
fillBorder(sb);
fillRow(sb, HEADER_LIST);
fillBorder(sb);
fillRow(sb, BODY_LIST);
fillBorder(sb);
return sb.toString();
}
/**
* 填充表头或者表体信息
*
* @param sb
* @param list 表头列表或者表体列表
*/
private void fillRow(StringBuilder sb, List<List<String>> list) {
for (List<String> r : list) {
for (int i = 0; i < r.size(); i++) {
if (i == 0) {
sb.append(COLUMN_LINE);
}
String header = r.get(i);
sb.append(SPACE);
sb.append(header);
sb.append(SPACE);
int l = header.length();
int lw = columnCharNumber.get(i);
if (lw > l) {
for (int j = 0; j < (lw - l); j++) {
sb.append(SPACE);
}
}
sb.append(COLUMN_LINE);
}
sb.append(LF);
}
}
/**
* 拼装边框
*
* @param sb StringBuilder
*/
private void fillBorder(StringBuilder sb) {
sb.append(CORNER);
for (Integer width : columnCharNumber) {
sb.append(Convert.toSBC(StrUtil.fillAfter("", ROW_LINE, width + 2)));
sb.append(CORNER);
}
sb.append(LF);
}
/**
* 打印到控制台
*/
public void print() {
Console.print(toString());
}
}

View File

@@ -1,177 +0,0 @@
package cn.hutool.core.lang;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 控制台打印表格工具
*
* @author 孙宇
*/
public class ConsoleTableUtil {
/**
* 表格头信息
*/
private final List<List<String>> HEADER_LIST = new ArrayList<>();
/**
* 表格体信息
*/
private final List<List<String>> BODY_LIST = new ArrayList<>();
/**
* 每列最大字符个数
*/
private List<Integer> columnCharNumber;
/**
* 测试
*
* @param args
*/
public static void main(String[] args) {
ConsoleTableUtil t = new ConsoleTableUtil();
t.addHeader("姓名", "年龄");
t.addBody("张三", "15");
t.addBody("李四", "29");
t.addBody("王二麻子", "37");
t.print();
t = new ConsoleTableUtil();
t.addHeader("体温", "占比");
t.addHeader("", "%");
t.addBody("36.8", "10");
t.addBody("37", "5");
t.print();
t = new ConsoleTableUtil();
t.addHeader("标题1", "标题2");
t.addBody("12345", "混合321654asdfcSDF");
t.addBody("sd e3ee ff22", "ff值");
t.print();
}
/**
* 添加头信息
*
* @param titles 列名
* @return 自身对象
*/
public ConsoleTableUtil addHeader(String... titles) {
if (columnCharNumber == null) {
columnCharNumber = new ArrayList<>(Collections.nCopies(titles.length, 0));
}
List<String> l = new ArrayList<>();
HEADER_LIST.add(l);
fillColumns(l, titles);
return this;
}
/**
* 添加体信息
*
* @param values 列值
* @return 自身对象
*/
public ConsoleTableUtil addBody(String... values) {
List<String> l = new ArrayList<>();
BODY_LIST.add(l);
fillColumns(l, values);
return this;
}
/**
* 填充表格头或者体
*
* @param l
* @param columns
*/
private void fillColumns(List<String> l, String[] columns) {
for (int i = 0; i < columns.length; i++) {
String column = columns[i];
String col = Convert.toSBC(column);
l.add(col);
int width = col.length();
if (width > columnCharNumber.get(i)) {
columnCharNumber.set(i, width);
}
}
}
/**
* 获取表格字符串
*
* @return 表格字符串
*/
public String toString() {
StringBuilder sb = new StringBuilder();
fillBorder(sb);
for (List<String> headers : HEADER_LIST) {
for (int i = 0; i < headers.size(); i++) {
if (i == 0) {
sb.append('|');
}
String header = headers.get(i);
sb.append(Convert.toSBC(" "));
sb.append(header);
sb.append(Convert.toSBC(" "));
int l = header.length();
int lw = columnCharNumber.get(i);
if (lw > l) {
for (int j = 0; j < (lw - l); j++) {
sb.append(Convert.toSBC(" "));
}
}
sb.append('|');
}
sb.append('\n');
}
fillBorder(sb);
for (List<String> bodys : BODY_LIST) {
for (int i = 0; i < bodys.size(); i++) {
if (i == 0) {
sb.append('|');
}
String body = bodys.get(i);
sb.append(Convert.toSBC(" "));
sb.append(body);
sb.append(Convert.toSBC(" "));
int l = body.length();
int lw = columnCharNumber.get(i);
if (lw > l) {
for (int j = 0; j < (lw - l); j++) {
sb.append(Convert.toSBC(" "));
}
}
sb.append('|');
}
sb.append('\n');
}
fillBorder(sb);
return sb.toString();
}
/**
* 拼装边框
*
* @param sb
*/
private void fillBorder(StringBuilder sb) {
sb.append('*');
for (Integer width : columnCharNumber) {
sb.append(Convert.toSBC(StrUtil.fillAfter("", '-', width + 2)));
sb.append('*');
}
sb.append('\n');
}
/**
* 打印到控制台
*/
public void print() {
Console.print(toString());
}
}

View File

@@ -1613,7 +1613,7 @@ public class ArrayUtil {
public static int indexOf(double[] array, double value) {
if (null != array) {
for (int i = 0; i < array.length; i++) {
if (value == array[i]) {
if (NumberUtil.equals(value, array[i])) {
return i;
}
}
@@ -1632,7 +1632,7 @@ public class ArrayUtil {
public static int lastIndexOf(double[] array, double value) {
if (null != array) {
for (int i = array.length - 1; i >= 0; i--) {
if (value == array[i]) {
if (NumberUtil.equals(value, array[i])) {
return i;
}
}
@@ -1663,7 +1663,7 @@ public class ArrayUtil {
public static int indexOf(float[] array, float value) {
if (null != array) {
for (int i = 0; i < array.length; i++) {
if (value == array[i]) {
if (NumberUtil.equals(value, array[i])) {
return i;
}
}
@@ -1682,7 +1682,7 @@ public class ArrayUtil {
public static int lastIndexOf(float[] array, float value) {
if (null != array) {
for (int i = array.length - 1; i >= 0; i--) {
if (value == array[i]) {
if (NumberUtil.equals(value, array[i])) {
return i;
}
}
@@ -1777,7 +1777,7 @@ public class ArrayUtil {
}
/**
* 包装类数组转为原始类型数组
* 包装类数组转为原始类型数组null转为0
*
* @param values 包装类型数组
* @return 原始类型数组
@@ -1793,7 +1793,7 @@ public class ArrayUtil {
final int[] array = new int[length];
for (int i = 0; i < length; i++) {
array[i] = values[i];
array[i] = ObjectUtil.defaultIfNull(values[i], 0);
}
return array;
}
@@ -1837,7 +1837,7 @@ public class ArrayUtil {
final long[] array = new long[length];
for (int i = 0; i < length; i++) {
array[i] = values[i];
array[i] = ObjectUtil.defaultIfNull(values[i], 0L);
}
return array;
}
@@ -1881,7 +1881,7 @@ public class ArrayUtil {
char[] array = new char[length];
for (int i = 0; i < length; i++) {
array[i] = values[i];
array[i] = ObjectUtil.defaultIfNull(values[i], Character.MIN_VALUE);
}
return array;
}

View File

@@ -98,11 +98,11 @@ public class CreditCodeUtil {
}
for (int i = 2; i < 8; i++) {
int num = RandomUtil.randomInt(10);
buf.append(Character.toUpperCase(BASE_CODE_ARRAY[num]));
buf.append(BASE_CODE_ARRAY[num]);
}
for (int i = 8; i < 17; i++) {
int num = RandomUtil.randomInt(BASE_CODE_ARRAY.length - 1);
buf.append(Character.toUpperCase(BASE_CODE_ARRAY[num]));
buf.append(BASE_CODE_ARRAY[num]);
}
final String code = buf.toString();

View File

@@ -1712,7 +1712,7 @@ public class NumberUtil {
/**
* 比较大小,值相等 返回true<br>
* 此方法通过调用{@link BigDecimal#compareTo(BigDecimal)}方法来判断是否相等<br>
* 此方法通过调用{@link Double#doubleToLongBits(double)}方法来判断是否相等<br>
* 此方法判断值相等时忽略精度的即0.00 == 0
*
* @param num1 数字1
@@ -1721,7 +1721,21 @@ public class NumberUtil {
* @since 5.4.2
*/
public static boolean equals(double num1, double num2) {
return equals(toBigDecimal(num1), toBigDecimal(num2));
return Double.doubleToLongBits(num1) == Double.doubleToLongBits(num2);
}
/**
* 比较大小,值相等 返回true<br>
* 此方法通过调用{@link Double#doubleToLongBits(double)}方法来判断是否相等<br>
* 此方法判断值相等时忽略精度的即0.00 == 0
*
* @param num1 数字1
* @param num2 数字2
* @return 是否相等
* @since 5.4.5
*/
public static boolean equals(float num1, float num2) {
return Float.floatToIntBits(num1) == Float.floatToIntBits(num2);
}
/**

View File

@@ -48,8 +48,8 @@ public class RandomUtil {
* ThreadLocalRandom是JDK 7之后提供并发产生随机数能够解决多个线程发生的竞争争夺。
*
* <p>
* 注意:此方法返回的{@link ThreadLocalRandom}不可以在多线程环境下共享对象,否则有重复随机数问题。
* https://www.jianshu.com/p/89dfe990295c
* 注意:此方法返回的{@link ThreadLocalRandom}不可以在多线程环境下共享对象,否则有重复随机数问题。
* 见https://www.jianshu.com/p/89dfe990295c
* </p>
*
* @return {@link ThreadLocalRandom}
@@ -522,9 +522,10 @@ public class RandomUtil {
*
* @return 随机颜色
* @since 4.1.5
* @deprecated 使用{@link ImagUtil#randomColor()}
* @deprecated 使用ImgUtil.randomColor()
*/
public static Color randomColor() {
@Deprecated
public static Color randomColor() {
final Random random = getRandom();
return new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256));
}

View File

@@ -34,53 +34,95 @@ public class StrUtil {
public static final int INDEX_NOT_FOUND = -1;
/** 字符常量:空格符 ' ' */
/**
* 字符常量:空格符 ' '
*/
public static final char C_SPACE = CharUtil.SPACE;
/** 字符常量:制表符 \t */
/**
* 字符常量:制表符 \t
*/
public static final char C_TAB = CharUtil.TAB;
/** 字符常量:点 . */
/**
* 字符常量:点 .
*/
public static final char C_DOT = CharUtil.DOT;
/** 字符常量:斜杠 / */
/**
* 字符常量:斜杠 /
*/
public static final char C_SLASH = CharUtil.SLASH;
/** 字符常量:反斜杠 \ */
/**
* 字符常量:反斜杠 \
*/
public static final char C_BACKSLASH = CharUtil.BACKSLASH;
/** 字符常量:回车符 \r */
/**
* 字符常量:回车符 \r
*/
public static final char C_CR = CharUtil.CR;
/** 字符常量:换行符 \n */
/**
* 字符常量:换行符 \n
*/
public static final char C_LF = CharUtil.LF;
/** 字符常量:下划线 _ */
/**
* 字符常量:下划线 _
*/
public static final char C_UNDERLINE = CharUtil.UNDERLINE;
/** 字符常量:逗号 , */
/**
* 字符常量:逗号 ,
*/
public static final char C_COMMA = CharUtil.COMMA;
/** 字符常量:花括号(左) { */
/**
* 字符常量:花括号(左) {
*/
public static final char C_DELIM_START = CharUtil.DELIM_START;
/** 字符常量:花括号(右) } */
/**
* 字符常量:花括号(右) }
*/
public static final char C_DELIM_END = CharUtil.DELIM_END;
/** 字符常量:中括号(左) [ */
/**
* 字符常量:中括号(左) [
*/
public static final char C_BRACKET_START = CharUtil.BRACKET_START;
/** 字符常量:中括号(右) ] */
/**
* 字符常量:中括号(右) ]
*/
public static final char C_BRACKET_END = CharUtil.BRACKET_END;
/** 字符常量:冒号 : */
/**
* 字符常量:冒号 :
*/
public static final char C_COLON = CharUtil.COLON;
/** 字符常量:艾特 @ */
/**
* 字符常量:艾特 @
*/
public static final char C_AT = CharUtil.AT;
/** 字符串常量:空格符 ' ' */
/**
* 字符串常量:空格符 ' '
*/
public static final String SPACE = " ";
/** 字符串常量:制表符 \t */
/**
* 字符串常量:制表符 \t
*/
public static final String TAB = " ";
/** 字符串常量:点 . */
/**
* 字符串常量:点 .
*/
public static final String DOT = ".";
/**
* 字符串常量:双点 ..
* 用途:作为指向上级文件夹的路径 "../path"
*/
public static final String DOUBLE_DOT = "..";
/** 字符串常量:斜杠 / */
/**
* 字符串常量:斜杠 /
*/
public static final String SLASH = "/";
/** 字符串常量:反斜杠 \ */
/**
* 字符串常量:反斜杠 \
*/
public static final String BACKSLASH = "\\";
/** 字符串常量:空字符串 "" */
/**
* 字符串常量:空字符串 ""
*/
public static final String EMPTY = "";
/**
* 字符串常量:"null"
@@ -92,45 +134,79 @@ public class StrUtil {
* 解释:该字符常用于表示 Linux 系统和 MacOS 系统下的文本换行
*/
public static final String CR = "\r";
/** 字符串常量:换行符 \n */
/**
* 字符串常量:换行符 \n
*/
public static final String LF = "\n";
/**
* 字符串常量Windows 换行 \r\n
* 解释:该字符串常用于表示 Windows 系统下的文本换行
*/
public static final String CRLF = "\r\n";
/** 字符串常量:下划线 _ */
/**
* 字符串常量:下划线 _
*/
public static final String UNDERLINE = "_";
/** 字符串常量:减号(中划线) - */
/**
* 字符串常量:减号(中划线) -
*/
public static final String DASHED = "-";
/** 字符串常量:逗号 , */
/**
* 字符串常量:逗号 ,
*/
public static final String COMMA = ",";
/** 字符串常量:花括号(左) { */
/**
* 字符串常量:花括号(左) {
*/
public static final String DELIM_START = "{";
/** 字符串常量:花括号(右) } */
/**
* 字符串常量:花括号(右) }
*/
public static final String DELIM_END = "}";
/** 字符串常量:中括号(左) [ */
/**
* 字符串常量:中括号(左) [
*/
public static final String BRACKET_START = "[";
/** 字符串常量:中括号(右) ] */
/**
* 字符串常量:中括号(右) ]
*/
public static final String BRACKET_END = "]";
/** 字符串常量:冒号 : */
/**
* 字符串常量:冒号 :
*/
public static final String COLON = ":";
/** 字符串常量:艾特 @ */
/**
* 字符串常量:艾特 @
*/
public static final String AT = "@";
/** 字符串常量HTML 空格转义 */
/**
* 字符串常量HTML 空格转义
*/
public static final String HTML_NBSP = "&nbsp;";
/** 字符串常量HTML And 符转义 &amp; */
/**
* 字符串常量HTML And 符转义 &amp;
*/
public static final String HTML_AMP = "&amp;";
/** 字符串常量HTML 双引号转义 " */
/**
* 字符串常量HTML 双引号转义 "
*/
public static final String HTML_QUOTE = "&quot;";
/** 字符串常量HTML 单引号转义 ' */
/**
* 字符串常量HTML 单引号转义 '
*/
public static final String HTML_APOS = "&apos;";
/** 字符串常量HTML 小于号转义 &lt; */
/**
* 字符串常量HTML 小于号转义 &lt;
*/
public static final String HTML_LT = "&lt;";
/** 字符串常量HTML 大于号转义 &gt; */
/**
* 字符串常量HTML 大于号转义 &gt;
*/
public static final String HTML_GT = "&gt;";
/** 字符串常量:空 JSON "{}" */
/**
* 字符串常量:空 JSON "{}"
*/
public static final String EMPTY_JSON = "{}";
// ------------------------------------------------------------------------ Blank
@@ -189,8 +265,7 @@ public class StrUtil {
*
* @param obj 对象
* @return 如果为字符串是否为空串
*
* @see StrUtil#isBlank(CharSequence)
* @see StrUtil#isBlank(CharSequence)
* @since 3.3.0
*/
public static boolean isBlankIfStr(Object obj) {
@@ -667,6 +742,9 @@ public class StrUtil {
* @return 是否开始
*/
public static boolean startWith(CharSequence str, char c) {
if (isEmpty(str)) {
return false;
}
return c == str.charAt(0);
}
@@ -674,8 +752,8 @@ public class StrUtil {
* 是否以指定字符串开头<br>
* 如果给定的字符串和开头字符串都为null则返回true否则任意一个值为null返回false
*
* @param str 被监测字符串
* @param prefix 开头字符串
* @param str 被监测字符串
* @param prefix 开头字符串
* @param ignoreCase 是否忽略大小写
* @return 是否以指定字符串开头
* @since 5.4.3
@@ -690,14 +768,14 @@ public class StrUtil {
*
* @param str 被监测字符串
* @param prefix 开头字符串
* @param ignoreCase 是否忽略大小写
* @param ignoreCase 是否忽略大小写
* @param ignoreEquals 是否忽略字符串相等的情况
* @return 是否以指定字符串开头
* @since 5.4.3
*/
public static boolean startWith(CharSequence str, CharSequence prefix, boolean ignoreCase, boolean ignoreEquals) {
if (null == str || null == prefix) {
if(false == ignoreEquals){
if (false == ignoreEquals) {
return false;
}
return null == str && null == prefix;
@@ -710,7 +788,7 @@ public class StrUtil {
isStartWith = str.toString().startsWith(prefix.toString());
}
if(isStartWith){
if (isStartWith) {
return (false == ignoreEquals) || (false == equals(str, prefix, ignoreCase));
}
return false;
@@ -779,6 +857,9 @@ public class StrUtil {
* @return 是否结尾
*/
public static boolean endWith(CharSequence str, char c) {
if (isEmpty(str)) {
return false;
}
return c == str.charAt(str.length() - 1);
}
@@ -1073,7 +1154,8 @@ public class StrUtil {
* @return 移除后的字符串
*/
public static String removeAll(CharSequence str, CharSequence strToRemove) {
if (isEmpty(str)) {
// strToRemove如果为空 也不用继续后面的逻辑
if (isEmpty(str) || isEmpty(strToRemove)) {
return str(str);
}
return str.toString().replace(strToRemove, EMPTY);
@@ -2125,7 +2207,6 @@ public class StrUtil {
}
final List<String> result = new LinkedList<>();
final String[] split = split(str, prefix);
for (String fragment : split(str, prefix)) {
int suffixIndex = fragment.indexOf(suffix.toString());
if (suffixIndex > 0) {
@@ -2205,10 +2286,10 @@ public class StrUtil {
if (null == str) {
return null;
}
if (count <= 0) {
if (count <= 0 || str.length() == 0) {
return EMPTY;
}
if (count == 1 || str.length() == 0) {
if (count == 1) {
return str.toString();
}
@@ -3997,11 +4078,12 @@ public class StrUtil {
return false;
}
int len = value.length();
boolean isAllMatch = true;
for (int i = 0; i < len; i++) {
isAllMatch &= matcher.match(value.charAt(i));
if (false == matcher.match(value.charAt(i))) {
return false;
}
}
return isAllMatch;
return true;
}
/**

View File

@@ -4,6 +4,7 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.BiMap;
@@ -13,7 +14,11 @@ import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
@@ -21,6 +26,8 @@ import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
@@ -62,6 +69,10 @@ public class XmlUtil {
* 在XML中无效的字符 正则
*/
public static final String INVALID_REGEX = "[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]";
/**
* 在XML中注释的内容 正则
*/
public static final String COMMENT_REGEX = "(?s)<!--.+?-->";
/**
* XML格式化输出默认缩进量
*/
@@ -76,6 +87,10 @@ public class XmlUtil {
* 是否打开命名空间支持
*/
private static boolean namespaceAware = true;
/**
* Sax读取器工厂缓存
*/
private static SAXParserFactory factory;
/**
* 禁用默认的DocumentBuilderFactory禁用后如果有第三方的实现如oracle的xdb包中的xmlparse将会自动加载实现。
@@ -184,6 +199,92 @@ public class XmlUtil {
}
}
/**
* 使用Sax方式读取指定的XML<br>
* 如果用户传入的contentHandler为{@link DefaultHandler},则其接口都会被处理
*
* @param file XML源文件,使用后自动关闭
* @param contentHandler XML流处理器用于按照Element处理xml
* @since 5.4.4
*/
public static void readBySax(File file, ContentHandler contentHandler) {
InputStream in = null;
try{
in = FileUtil.getInputStream(file);
readBySax(new InputSource(in), contentHandler);
} finally {
IoUtil.close(in);
}
}
/**
* 使用Sax方式读取指定的XML<br>
* 如果用户传入的contentHandler为{@link DefaultHandler},则其接口都会被处理
*
* @param reader XML源Reader,使用后自动关闭
* @param contentHandler XML流处理器用于按照Element处理xml
* @since 5.4.4
*/
public static void readBySax(Reader reader, ContentHandler contentHandler) {
try{
readBySax(new InputSource(reader), contentHandler);
} finally {
IoUtil.close(reader);
}
}
/**
* 使用Sax方式读取指定的XML<br>
* 如果用户传入的contentHandler为{@link DefaultHandler},则其接口都会被处理
*
* @param source XML源流,使用后自动关闭
* @param contentHandler XML流处理器用于按照Element处理xml
* @since 5.4.4
*/
public static void readBySax(InputStream source, ContentHandler contentHandler) {
try{
readBySax(new InputSource(source), contentHandler);
} finally {
IoUtil.close(source);
}
}
/**
* 使用Sax方式读取指定的XML<br>
* 如果用户传入的contentHandler为{@link DefaultHandler},则其接口都会被处理
*
* @param source XML源可以是文件、流、路径等
* @param contentHandler XML流处理器用于按照Element处理xml
* @since 5.4.4
*/
public static void readBySax(InputSource source, ContentHandler contentHandler) {
// 1.获取解析工厂
if (null == factory) {
factory = SAXParserFactory.newInstance();
factory.setValidating(false);
factory.setNamespaceAware(namespaceAware);
}
// 2.从解析工厂获取解析器
final SAXParser parse;
XMLReader reader;
try {
parse = factory.newSAXParser();
if (contentHandler instanceof DefaultHandler) {
parse.parse(source, (DefaultHandler) contentHandler);
return;
}
// 3.得到解读器
reader = parse.getXMLReader();
reader.setContentHandler(contentHandler);
reader.parse(source);
} catch (ParserConfigurationException | SAXException e) {
throw new UtilException(e);
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
/**
* 将String类型的XML转换为XML文档
*
@@ -574,6 +675,20 @@ public class XmlUtil {
return xmlContent.replaceAll(INVALID_REGEX, "");
}
/**
* 去除XML文本中的注释内容
*
* @param xmlContent XML文本
* @return 当传入为null时返回null
* @since 5.4.5
*/
public static String cleanComment(String xmlContent) {
if (xmlContent == null) {
return null;
}
return xmlContent.replaceAll(COMMENT_REGEX, StrUtil.EMPTY);
}
/**
* 根据节点名获得子节点列表
*

View File

@@ -1,5 +1,6 @@
package cn.hutool.core.date;
import cn.hutool.core.util.StrUtil;
import org.junit.Assert;
import org.junit.Test;
@@ -77,4 +78,11 @@ public class ChineseDateTest {
chineseDate = new ChineseDate(2020,4,15);
Assert.assertEquals("闰四月", chineseDate.getChineseMonth());
}
@Test
public void getFestivalsTest(){
// issue#I1XHSF@Gitee2023-01-20对应农历腊月29非除夕
ChineseDate chineseDate = new ChineseDate(DateUtil.parseDate("2023-01-20"));
Assert.assertTrue(StrUtil.isEmpty(chineseDate.getFestivals()));
}
}

View File

@@ -25,7 +25,9 @@ public class ImgUtilTest {
@Test
@Ignore
public void scaleTest2() {
ImgUtil.scale(FileUtil.file("e:/pic/test.jpg"), FileUtil.file("e:/pic/test_result.jpg"), 0.8f);
ImgUtil.scale(
FileUtil.file("d:/test/2.png"),
FileUtil.file("d:/test/2_result.jpg"), 600, 337, null);
}
@Test

View File

@@ -0,0 +1,35 @@
package cn.hutool.core.lang;
import org.junit.Ignore;
import org.junit.Test;
public class ConsoleTableTest {
@Test
@Ignore
public void printTest() {
ConsoleTable t = new ConsoleTable();
t.addHeader("姓名", "年龄");
t.addBody("张三", "15");
t.addBody("李四", "29");
t.addBody("王二麻子", "37");
t.print();
Console.log();
t = new ConsoleTable();
t.addHeader("体温", "占比");
t.addHeader("", "%");
t.addBody("36.8", "10");
t.addBody("37", "5");
t.print();
Console.log();
t = new ConsoleTable();
t.addHeader("标题1", "标题2");
t.addBody("12345", "混合321654asdfcSDF");
t.addBody("sd e3ee ff22", "ff值");
t.print();
}
}

View File

@@ -263,10 +263,10 @@ public class NumberUtilTest {
@Test
public void isPowerOfTwoTest() {
Assert.assertEquals(false, NumberUtil.isPowerOfTwo(-1));
Assert.assertEquals(true, NumberUtil.isPowerOfTwo(16));
Assert.assertEquals(true, NumberUtil.isPowerOfTwo(65536));
Assert.assertEquals(true, NumberUtil.isPowerOfTwo(1));
Assert.assertEquals(false, NumberUtil.isPowerOfTwo(17));
Assert.assertFalse(NumberUtil.isPowerOfTwo(-1));
Assert.assertTrue(NumberUtil.isPowerOfTwo(16));
Assert.assertTrue(NumberUtil.isPowerOfTwo(65536));
Assert.assertTrue(NumberUtil.isPowerOfTwo(1));
Assert.assertFalse(NumberUtil.isPowerOfTwo(17));
}
}

View File

@@ -9,10 +9,13 @@ import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.w3c.dom.Document;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.xpath.XPathConstants;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* {@link XmlUtil} 工具类
@@ -148,6 +151,18 @@ public class XmlUtilTest {
Assert.assertNotNull(doc);
}
@Test
public void readBySaxTest(){
final Set<String> eles = CollUtil.newHashSet(
"returnsms", "returnstatus", "message", "remainpoint", "taskID", "successCounts");
XmlUtil.readBySax(ResourceUtil.getStream("test.xml"), new DefaultHandler(){
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
Assert.assertTrue(eles.contains(localName));
}
});
}
@Test
public void mapToXmlTestWithOmitXmlDeclaration() {
@@ -196,6 +211,13 @@ public class XmlUtilTest {
Assert.assertEquals(testBean.getBankCode(), testBean2.getBankCode());
}
@Test
public void cleanCommentTest() {
final String xmlContent = "<info><title>hutool</title><!-- 这是注释 --><lang>java</lang></info>";
final String ret = XmlUtil.cleanComment(xmlContent);
Assert.assertEquals("<info><title>hutool</title><lang>java</lang></info>", ret);
}
@Data
public static class TestBean {
private String ReqCode;