diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/collection/CollUtil.java index 38c1b57b1..c4f465558 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/collection/CollUtil.java @@ -298,7 +298,7 @@ public class CollUtil { * @return 并集的集合,返回 {@link ArrayList} */ @SafeVarargs - public static Collection union(final Collection... colls) { + public static Collection union(final Collection... colls) { return CollectionOperation.of(colls).union(); } @@ -313,7 +313,7 @@ public class CollUtil { * @return 并集的集合,返回 {@link LinkedHashSet} */ @SafeVarargs - public static Set unionDistinct(final Collection... colls) { + public static Set unionDistinct(final Collection... colls) { return CollectionOperation.of(colls).unionDistinct(); } @@ -328,7 +328,7 @@ public class CollUtil { * @return 并集的集合,返回 {@link ArrayList} */ @SafeVarargs - public static List unionAll(final Collection... colls) { + public static List unionAll(final Collection... colls) { return CollectionOperation.of(colls).unionAll(); } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/collection/CollectionOperation.java b/hutool-core/src/main/java/org/dromara/hutool/core/collection/CollectionOperation.java index bb700e0f4..6e3faa75c 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/collection/CollectionOperation.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/collection/CollectionOperation.java @@ -45,7 +45,7 @@ public class CollectionOperation { * @return CollectionOperation */ @SafeVarargs - public static CollectionOperation of(final Collection... colls) { + public static CollectionOperation of(final Collection... colls) { return new CollectionOperation<>(colls); } @@ -56,8 +56,9 @@ public class CollectionOperation { * * @param colls 集合数组 */ - public CollectionOperation(final Collection[] colls) { - this.colls = colls; + @SuppressWarnings("unchecked") + public CollectionOperation(final Collection[] colls) { + this.colls = (Collection[]) colls; } /** diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/compress/ZipReplacer.java b/hutool-core/src/main/java/org/dromara/hutool/core/compress/ZipReplacer.java new file mode 100755 index 000000000..f5e9256e2 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/compress/ZipReplacer.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.compress; + +import org.dromara.hutool.core.io.file.FileUtil; +import org.dromara.hutool.core.io.resource.Resource; +import org.dromara.hutool.core.text.StrUtil; + +import java.io.Closeable; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * Zip文件替换,用户替换源Zip文件,并生成新的文件 + * + * @author looly + * @since 6.0.0 + */ +public class ZipReplacer implements Closeable { + + private final ZipReader zipReader; + private final boolean ignoreCase; + + private final Map replacedResources = new HashMap<>(); + + /** + * 构造 + * + * @param zipReader ZipReader + * @param ignoreCase 是否忽略path大小写 + */ + public ZipReplacer(final ZipReader zipReader, final boolean ignoreCase) { + this.zipReader = zipReader; + this.ignoreCase = ignoreCase; + } + + /** + * 增加替换的内容,如果路径不匹配,则不做替换,也不加入 + * + * @param entryPath 路径 + * @param resource 被压缩的内容 + * @return this + */ + public ZipReplacer addReplace(final String entryPath, final Resource resource) { + replacedResources.put(entryPath, resource); + return this; + } + + /** + * 写出到{@link ZipWriter} + * + * @param writer {@link ZipWriter} + */ + public void write(final ZipWriter writer) { + zipReader.read((entry) -> { + String entryName; + for (final String key : replacedResources.keySet()) { + entryName = entry.getName(); + if (isSamePath(entryName, key, ignoreCase)) { + writer.add(key, replacedResources.get(key).getStream()); + } else { + writer.add(entryName, zipReader.get(entryName)); + } + } + }); + } + + @Override + public void close() throws IOException { + this.zipReader.close(); + } + + /** + * 判断路径是否相等 + * + * @param entryPath 路径A + * @param targetPath 路径B + * @param ignoreCase 是否忽略大小写 + * @return ture 路径相等 + */ + private static boolean isSamePath(String entryPath, String targetPath, final boolean ignoreCase) { + entryPath = StrUtil.removePrefix(FileUtil.normalize(entryPath), StrUtil.SLASH); + targetPath = StrUtil.removePrefix(FileUtil.normalize(targetPath), StrUtil.SLASH); + return StrUtil.equals(entryPath, targetPath, ignoreCase); + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/CompositeConverter.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/CompositeConverter.java index e95d3251e..cc33061a9 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/convert/CompositeConverter.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/CompositeConverter.java @@ -231,6 +231,11 @@ public class CompositeConverter extends RegisterConverter { return (T) KBeanConverter.INSTANCE.convert(type, value); } + // issue#I7FQ29 Class + if("java.lang.Class".equals(rowType.getName())){ + return (T) ClassConverter.INSTANCE.convert(type, value); + } + // 表示非需要特殊转换的对象 return null; } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/RegisterConverter.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/RegisterConverter.java index 1fd0a8cf3..26a12b0bb 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/convert/RegisterConverter.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/RegisterConverter.java @@ -12,36 +12,14 @@ package org.dromara.hutool.core.convert; -import org.dromara.hutool.core.convert.impl.AtomicBooleanConverter; -import org.dromara.hutool.core.convert.impl.AtomicIntegerArrayConverter; -import org.dromara.hutool.core.convert.impl.AtomicLongArrayConverter; -import org.dromara.hutool.core.convert.impl.AtomicReferenceConverter; -import org.dromara.hutool.core.convert.impl.BooleanConverter; -import org.dromara.hutool.core.convert.impl.CalendarConverter; -import org.dromara.hutool.core.convert.impl.CharacterConverter; -import org.dromara.hutool.core.convert.impl.CharsetConverter; -import org.dromara.hutool.core.convert.impl.ClassConverter; -import org.dromara.hutool.core.convert.impl.CurrencyConverter; -import org.dromara.hutool.core.convert.impl.DateConverter; -import org.dromara.hutool.core.convert.impl.DurationConverter; -import org.dromara.hutool.core.convert.impl.LocaleConverter; -import org.dromara.hutool.core.convert.impl.OptConverter; -import org.dromara.hutool.core.convert.impl.OptionalConverter; -import org.dromara.hutool.core.convert.impl.PathConverter; -import org.dromara.hutool.core.convert.impl.PeriodConverter; -import org.dromara.hutool.core.convert.impl.ReferenceConverter; -import org.dromara.hutool.core.convert.impl.StackTraceElementConverter; -import org.dromara.hutool.core.convert.impl.StringConverter; -import org.dromara.hutool.core.convert.impl.TemporalAccessorConverter; -import org.dromara.hutool.core.convert.impl.TimeZoneConverter; -import org.dromara.hutool.core.convert.impl.URIConverter; -import org.dromara.hutool.core.convert.impl.URLConverter; -import org.dromara.hutool.core.convert.impl.UUIDConverter; -import org.dromara.hutool.core.convert.impl.XMLGregorianCalendarConverter; -import org.dromara.hutool.core.convert.impl.ZoneIdConverter; +import org.dromara.hutool.core.convert.impl.*; import org.dromara.hutool.core.date.DateTime; import org.dromara.hutool.core.lang.Opt; +import org.dromara.hutool.core.lang.tuple.Pair; +import org.dromara.hutool.core.lang.tuple.Triple; +import org.dromara.hutool.core.lang.tuple.Tuple; import org.dromara.hutool.core.map.SafeConcurrentHashMap; +import org.dromara.hutool.core.reflect.TypeUtil; import javax.xml.datatype.XMLGregorianCalendar; import java.io.Serializable; @@ -52,27 +30,9 @@ import java.net.URI; import java.net.URL; import java.nio.charset.Charset; import java.nio.file.Path; -import java.time.DayOfWeek; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.Month; -import java.time.MonthDay; -import java.time.OffsetDateTime; -import java.time.OffsetTime; -import java.time.Period; -import java.time.ZoneId; -import java.time.ZonedDateTime; +import java.time.*; import java.time.temporal.TemporalAccessor; -import java.util.Calendar; -import java.util.Currency; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.TimeZone; -import java.util.UUID; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicIntegerArray; import java.util.concurrent.atomic.AtomicLongArray; @@ -109,7 +69,7 @@ public class RegisterConverter implements Converter, Serializable { /** * 默认类型转换器 */ - private Map defaultConverterMap; + private Map, Converter> defaultConverterMap; /** * 用户自定义类型转换器 */ @@ -164,7 +124,7 @@ public class RegisterConverter implements Converter, Serializable { * @return 转换器 */ public Converter getDefaultConverter(final Type type) { - return (null == defaultConverterMap) ? null : defaultConverterMap.get(type); + return (null == defaultConverterMap) ? null : defaultConverterMap.get(TypeUtil.getClass(type)); } /** @@ -248,7 +208,6 @@ public class RegisterConverter implements Converter, Serializable { defaultConverterMap.put(AtomicLongArray.class, new AtomicLongArrayConverter()); // 其它类型 - defaultConverterMap.put(Class.class, new ClassConverter()); defaultConverterMap.put(TimeZone.class, new TimeZoneConverter()); defaultConverterMap.put(ZoneId.class, new ZoneIdConverter()); defaultConverterMap.put(Locale.class, new LocaleConverter()); @@ -259,5 +218,8 @@ public class RegisterConverter implements Converter, Serializable { defaultConverterMap.put(StackTraceElement.class, new StackTraceElementConverter());// since 4.5.2 defaultConverterMap.put(Optional.class, new OptionalConverter());// since 5.0.0 defaultConverterMap.put(Opt.class, new OptConverter());// since 5.7.16 + defaultConverterMap.put(Pair.class, PairConverter.INSTANCE);// since 6.0.0 + defaultConverterMap.put(Triple.class, TripleConverter.INSTANCE);// since 6.0.0 + defaultConverterMap.put(Tuple.class, TupleConverter.INSTANCE);// since 6.0.0 } } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/ClassConverter.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/ClassConverter.java index 10c291315..abb417a84 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/ClassConverter.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/ClassConverter.java @@ -24,6 +24,11 @@ import org.dromara.hutool.core.classloader.ClassLoaderUtil; public class ClassConverter extends AbstractConverter { private static final long serialVersionUID = 1L; + /** + * 单例 + */ + public static ClassConverter INSTANCE = new ClassConverter(); + private final boolean isInitialized; /** diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/EntryConverter.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/EntryConverter.java index a0799818f..dfb04359d 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/EntryConverter.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/EntryConverter.java @@ -16,6 +16,7 @@ import org.dromara.hutool.core.bean.BeanUtil; import org.dromara.hutool.core.convert.CompositeConverter; import org.dromara.hutool.core.convert.ConvertException; import org.dromara.hutool.core.convert.Converter; +import org.dromara.hutool.core.lang.tuple.Pair; import org.dromara.hutool.core.map.MapUtil; import org.dromara.hutool.core.reflect.ConstructorUtil; import org.dromara.hutool.core.reflect.TypeReference; @@ -72,6 +73,9 @@ public class EntryConverter implements Converter { if (value instanceof Map.Entry) { final Map.Entry entry = (Map.Entry) value; map = MapUtil.of(entry.getKey(), entry.getValue()); + }else if (value instanceof Pair) { + final Pair entry = (Pair) value; + map = MapUtil.of(entry.getLeft(), entry.getRight()); }else if (value instanceof Map) { map = (Map) value; } else if (value instanceof CharSequence) { diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/PairConverter.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/PairConverter.java new file mode 100755 index 000000000..6befa9814 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/PairConverter.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.convert.impl; + +import org.dromara.hutool.core.bean.BeanUtil; +import org.dromara.hutool.core.convert.CompositeConverter; +import org.dromara.hutool.core.convert.ConvertException; +import org.dromara.hutool.core.convert.Converter; +import org.dromara.hutool.core.lang.tuple.Pair; +import org.dromara.hutool.core.map.MapUtil; +import org.dromara.hutool.core.reflect.TypeReference; +import org.dromara.hutool.core.reflect.TypeUtil; +import org.dromara.hutool.core.text.CharUtil; +import org.dromara.hutool.core.text.StrUtil; + +import java.lang.reflect.Type; +import java.util.Map; + +/** + * {@link Pair} 转换器,支持以下类型转为Pair + *
    + *
  • {@link Map}
  • + *
  • {@link Map.Entry}
  • + *
  • 带分隔符的字符串,支持分隔符{@code :}、{@code =}、{@code ,}
  • + *
  • Bean,包含{@code getLeft}和{@code getRight}方法
  • + *
+ * + * @author looly + */ +public class PairConverter implements Converter { + + /** + * 单例 + */ + public static final PairConverter INSTANCE = new PairConverter(); + + @Override + public Object convert(Type targetType, final Object value) throws ConvertException { + if (targetType instanceof TypeReference) { + targetType = ((TypeReference) targetType).getType(); + } + final Type leftType = TypeUtil.getTypeArgument(targetType, 0); + final Type rightType = TypeUtil.getTypeArgument(targetType, 1); + + return convert(leftType, rightType, value); + } + + /** + * 转换对象为指定键值类型的指定类型Map + * + * @param leftType 键类型 + * @param rightType 值类型 + * @param value 被转换的值 + * @return 转换后的Map + * @throws ConvertException 转换异常或不支持的类型 + */ + @SuppressWarnings("rawtypes") + public Pair convert(final Type leftType, final Type rightType, final Object value) + throws ConvertException { + Map map = null; + if (value instanceof Map.Entry) { + final Map.Entry entry = (Map.Entry) value; + map = MapUtil.of(entry.getKey(), entry.getValue()); + } else if (value instanceof Pair) { + final Pair entry = (Pair) value; + map = MapUtil.of(entry.getLeft(), entry.getRight()); + } else if (value instanceof Map) { + map = (Map) value; + } else if (value instanceof CharSequence) { + final CharSequence str = (CharSequence) value; + map = strToMap(str); + } else if (BeanUtil.isReadableBean(value.getClass())) { + map = BeanUtil.beanToMap(value); + } + + if (null != map) { + return mapToPair(leftType, rightType, map); + } + + throw new ConvertException("Unsupported to map from [{}] of type: {}", value, value.getClass().getName()); + } + + /** + * 字符串转单个键值对的Map,支持分隔符{@code :}、{@code =}、{@code ,} + * + * @param str 字符串 + * @return map or null + */ + private static Map strToMap(final CharSequence str) { + // key:value key=value key,value + final int index = StrUtil.indexOf(str, + c -> c == CharUtil.COLON || c == CharUtil.EQUAL || c == CharUtil.COMMA, + 0, str.length()); + + if (index > -1) { + return MapUtil.of(str.subSequence(0, index), str.subSequence(index + 1, str.length())); + } + return null; + } + + /** + * Map转Pair + * + * @param keyType 键类型 + * @param valueType 值类型 + * @param map 被转换的map + * @return Pair + */ + @SuppressWarnings("rawtypes") + private static Pair mapToPair(final Type keyType, final Type valueType, final Map map) { + + Object left = null; + Object right = null; + if (1 == map.size()) { + final Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); + left = entry.getKey(); + right = entry.getValue(); + } else if (2 == map.size()) { + left = map.get("left"); + right = map.get("right"); + } + + final CompositeConverter convert = CompositeConverter.getInstance(); + return Pair.of( + TypeUtil.isUnknown(keyType) ? left : convert.convert(keyType, left), + TypeUtil.isUnknown(valueType) ? right : convert.convert(valueType, right) + ); + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/TripleConverter.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/TripleConverter.java new file mode 100755 index 000000000..8d7f851e5 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/TripleConverter.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.convert.impl; + +import org.dromara.hutool.core.bean.BeanUtil; +import org.dromara.hutool.core.convert.CompositeConverter; +import org.dromara.hutool.core.convert.ConvertException; +import org.dromara.hutool.core.convert.Converter; +import org.dromara.hutool.core.lang.tuple.Pair; +import org.dromara.hutool.core.lang.tuple.Triple; +import org.dromara.hutool.core.reflect.TypeReference; +import org.dromara.hutool.core.reflect.TypeUtil; + +import java.lang.reflect.Type; +import java.util.Map; + +/** + * {@link Triple} 转换器,支持以下类型转为Triple: + *
    + *
  • Bean,包含{@code getLeft}、{@code getMiddle}和{@code getRight}方法
  • + *
+ * + * @author looly + * @since 6.0.0 + */ +public class TripleConverter implements Converter { + + /** + * 单例 + */ + public static final TripleConverter INSTANCE = new TripleConverter(); + + @Override + public Object convert(Type targetType, final Object value) throws ConvertException { + if (targetType instanceof TypeReference) { + targetType = ((TypeReference) targetType).getType(); + } + final Type leftType = TypeUtil.getTypeArgument(targetType, 0); + final Type middileType = TypeUtil.getTypeArgument(targetType, 1); + final Type rightType = TypeUtil.getTypeArgument(targetType, 2); + + return convert(leftType, middileType, rightType, value); + } + + /** + * 转换对象为指定键值类型的指定类型Map + * + * @param leftType 键类型 + * @param middleType 中值类型 + * @param rightType 值类型 + * @param value 被转换的值 + * @return 转换后的Map + * @throws ConvertException 转换异常或不支持的类型 + */ + @SuppressWarnings("rawtypes") + public Triple convert(final Type leftType, final Type middleType, final Type rightType, final Object value) + throws ConvertException { + Map map = null; + if (BeanUtil.isReadableBean(value.getClass())) { + map = BeanUtil.beanToMap(value); + } + + if (null != map) { + return mapToTriple(leftType, middleType, rightType, map); + } + + throw new ConvertException("Unsupported to map from [{}] of type: {}", value, value.getClass().getName()); + } + + /** + * Map转Entry + * + * @param leftType 键类型 + * @param rightType 值类型 + * @param map 被转换的map + * @return Entry + */ + @SuppressWarnings("rawtypes") + private static Triple mapToTriple(final Type leftType, final Type middleType, final Type rightType, final Map map) { + + final Object left = map.get("left"); + final Object middle = map.get("middle"); + final Object right = map.get("right"); + + final CompositeConverter convert = CompositeConverter.getInstance(); + return Triple.of( + TypeUtil.isUnknown(leftType) ? left : convert.convert(leftType, left), + TypeUtil.isUnknown(middleType) ? middle : convert.convert(middleType, middle), + TypeUtil.isUnknown(rightType) ? right : convert.convert(rightType, right) + ); + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/TupleConverter.java b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/TupleConverter.java new file mode 100755 index 000000000..b0ece7102 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/convert/impl/TupleConverter.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.convert.impl; + +import org.dromara.hutool.core.convert.ConvertException; +import org.dromara.hutool.core.convert.Converter; +import org.dromara.hutool.core.lang.tuple.Tuple; + +import java.lang.reflect.Type; + +/** + * {@link Tuple}转换器 + * + * @author looly + * @since 6.0.0 + */ +public class TupleConverter implements Converter { + + /** + * 单例 + */ + public static final TupleConverter INSTANCE = new TupleConverter(); + + @Override + public Object convert(final Type targetType, final Object value) throws ConvertException { + final Object[] convert = (Object[]) ArrayConverter.INSTANCE.convert(Object[].class, value); + return Tuple.of(convert); + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/date/DateUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/date/DateUtil.java index b01796129..72f08400f 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/date/DateUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/date/DateUtil.java @@ -57,6 +57,7 @@ public class DateUtil extends CalendarUtil { "gmt", "ut", "utc", "est", "edt", "cst", "cdt", "mst", "mdt", "pst", "pdt"// 时间标准 }; + // region ----- date /** * 当前时间,转换为{@link DateTime}对象 * @@ -184,6 +185,7 @@ public class DateUtil extends CalendarUtil { } return new DateTime(temporalAccessor); } + // endregion /** * 当前时间的时间戳 @@ -1688,6 +1690,7 @@ public class DateUtil extends CalendarUtil { return sb.toString(); } + // region ----- range /** * 创建日期范围生成器 * @@ -1793,6 +1796,7 @@ public class DateUtil extends CalendarUtil { public static List rangeToList(final Date start, final Date end, final DateField unit, final int step) { return ListUtil.of((Iterable) new DateRange(start, end, unit, step)); } + // endregion /** * 通过生日计算星座 diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/date/format/parser/ISO8601DateParser.java b/hutool-core/src/main/java/org/dromara/hutool/core/date/format/parser/ISO8601DateParser.java index ddf8e9de5..9caf366c2 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/date/format/parser/ISO8601DateParser.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/date/format/parser/ISO8601DateParser.java @@ -16,6 +16,7 @@ import org.dromara.hutool.core.date.DateException; import org.dromara.hutool.core.date.DatePattern; import org.dromara.hutool.core.date.DateTime; import org.dromara.hutool.core.date.format.DefaultDateBasic; +import org.dromara.hutool.core.lang.Console; import org.dromara.hutool.core.regex.ReUtil; import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.text.CharUtil; @@ -53,8 +54,9 @@ public class ISO8601DateParser extends DefaultDateBasic implements DateParser { final int patternLength = DatePattern.UTC_MS_PATTERN.length(); // 格式类似:2018-09-13T05:34:31.999Z,-4表示减去4个单引号的长度 - // -4 ~ -6范围表示匹配毫秒1~3位的情况 - if (length <= patternLength - 4 && length >= patternLength - 6) { + // 2018-09-13T05:34:31.1Z - 2018-09-13T05:34:31.000000Z + if (length <= patternLength && length >= patternLength - 6) { + // 毫秒部分1-7位支持 return new DateTime(source, DatePattern.UTC_MS_FORMAT); } } else if (StrUtil.contains(source, '+')) { diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/io/file/FileTypeUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/io/file/FileTypeUtil.java index 8c2641c0a..268b21958 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/io/file/FileTypeUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/io/file/FileTypeUtil.java @@ -90,7 +90,7 @@ public class FileTypeUtil { /** * 根据文件流的头部信息获得文件类型
* 注意此方法会读取头部一些bytes,造成此流接下来读取时缺少部分bytes
- * 因此如果想服用此流,流需支持{@link InputStream#reset()}方法。 + * 因此如果想复用此流,流需支持{@link InputStream#reset()}方法。 * @param in {@link InputStream} * @param isExact 是否精确匹配,如果为false,使用前64个bytes匹配,如果为true,使用前8192bytes匹配 * @return 类型,文件的扩展名,未找到为{@code null} @@ -105,7 +105,7 @@ public class FileTypeUtil { /** * 根据文件流的头部信息获得文件类型
* 注意此方法会读取头部64个bytes,造成此流接下来读取时缺少部分bytes
- * 因此如果想服用此流,流需支持{@link InputStream#reset()}方法。 + * 因此如果想复用此流,流需支持{@link InputStream#reset()}方法。 * @param in {@link InputStream} * @return 类型,文件的扩展名,未找到为{@code null} * @throws IORuntimeException 读取流引起的异常 @@ -117,7 +117,7 @@ public class FileTypeUtil { /** * 根据文件流的头部信息获得文件类型 * 注意此方法会读取头部64个bytes,造成此流接下来读取时缺少部分bytes
- * 因此如果想服用此流,流需支持{@link InputStream#reset()}方法。 + * 因此如果想复用此流,流需支持{@link InputStream#reset()}方法。 * *
 	 *     1、无法识别类型默认按照扩展名识别
@@ -137,7 +137,7 @@ public class FileTypeUtil {
 	/**
 	 * 根据文件流的头部信息获得文件类型
 	 * 注意此方法会读取头部一些bytes,造成此流接下来读取时缺少部分bytes
- * 因此如果想服用此流,流需支持{@link InputStream#reset()}方法。 + * 因此如果想复用此流,流需支持{@link InputStream#reset()}方法。 * *
 	 *     1、无法识别类型默认按照扩展名识别
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/io/file/PathUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/io/file/PathUtil.java
index fa113412f..065ea407c 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/io/file/PathUtil.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/io/file/PathUtil.java
@@ -594,8 +594,9 @@ public class PathUtil {
 	public static String getMimeType(final Path file) {
 		try {
 			return Files.probeContentType(file);
-		} catch (final IOException e) {
-			throw new IORuntimeException(e);
+		} catch (final IOException ignore) {
+			// issue#3179,使用OpenJDK可能抛出NoSuchFileException,此处返回null
+			return null;
 		}
 	}
 
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/lang/tuple/Triple.java b/hutool-core/src/main/java/org/dromara/hutool/core/lang/tuple/Triple.java
index 3f4717578..6d1982fdf 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/lang/tuple/Triple.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/lang/tuple/Triple.java
@@ -26,8 +26,6 @@ import java.util.Objects;
 public class Triple extends Pair {
 	private static final long serialVersionUID = 1L;
 
-	protected M middle;
-
 	/**
 	 * 构建Triple对象
 	 *
@@ -44,6 +42,8 @@ public class Triple extends Pair {
 		return new Triple<>(left, middle, right);
 	}
 
+	protected M middle;
+
 	/**
 	 * 构造
 	 *
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/lang/tuple/Tuple.java b/hutool-core/src/main/java/org/dromara/hutool/core/lang/tuple/Tuple.java
index 47a8465a4..9f5469552 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/lang/tuple/Tuple.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/lang/tuple/Tuple.java
@@ -35,6 +35,17 @@ import java.util.stream.StreamSupport;
 public class Tuple implements Iterable, Serializable, Cloneable {
 	private static final long serialVersionUID = -7689304393482182157L;
 
+	/**
+	 * 构建Tuple对象
+	 *
+	 * @param members 成员数组
+	 * @return Tuple
+	 * @since 6.0.0
+	 */
+	public static Tuple of(final Object... members) {
+		return new Tuple(members);
+	}
+
 	private final Object[] members;
 	private int hashCode;
 	private boolean cacheHash;
diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/map/MapUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/map/MapUtil.java
index 29f9ddd9e..bd732887a 100644
--- a/hutool-core/src/main/java/org/dromara/hutool/core/map/MapUtil.java
+++ b/hutool-core/src/main/java/org/dromara/hutool/core/map/MapUtil.java
@@ -253,16 +253,21 @@ public class MapUtil extends MapGetUtil {
 	 */
 	@SuppressWarnings("unchecked")
 	public static  Map createMap(final Class mapType, final Supplier> defaultMap) {
-		if (null == mapType || mapType.isAssignableFrom(AbstractMap.class)) {
-			return defaultMap.get();
-		} else {
-			try {
-				return (Map) ConstructorUtil.newInstance(mapType);
-			} catch (final Exception e) {
-				// 不支持的map类型,返回默认的HashMap
-				return defaultMap.get();
-			}
+		Map result = null;
+		if (null != mapType && !mapType.isAssignableFrom(AbstractMap.class)) {
+			result = (Map) ConstructorUtil.newInstanceIfPossible(mapType);
 		}
+
+		if(null == result){
+			result = defaultMap.get();
+		}
+
+		if(!result.isEmpty()){
+			// issue#3162@Github,在构造中put值,会导致新建map带有值内容,此处清空
+			result.clear();
+		}
+
+		return result;
 	}
 
 	// ----------------------------------------------------------------------------------------------- value of
@@ -657,16 +662,12 @@ public class MapUtil extends MapGetUtil {
 	 * @param editor 编辑器接口
 	 * @return 编辑后的Map
 	 */
-	@SuppressWarnings("unchecked")
 	public static  Map edit(final Map map, final UnaryOperator> editor) {
 		if (null == map || null == editor) {
 			return map;
 		}
 
-		Map map2 = ConstructorUtil.newInstanceIfPossible(map.getClass());
-		if (null == map2) {
-			map2 = new HashMap<>(map.size(), 1f);
-		}
+		final Map map2 = createMap(map.getClass(), ()-> new HashMap<>(map.size(), 1f));
 		if (isEmpty(map)) {
 			return map2;
 		}
@@ -681,6 +682,8 @@ public class MapUtil extends MapGetUtil {
 		return map2;
 	}
 
+
+
 	/**
 	 * 过滤
* 过滤过程通过传入的Editor实现来返回需要的元素内容,这个Filter实现可以实现以下功能: @@ -739,10 +742,7 @@ public class MapUtil extends MapGetUtil { return map; } - Map map2 = ConstructorUtil.newInstanceIfPossible(map.getClass()); - if (null == map2) { - map2 = new HashMap<>(map.size(), 1f); - } + final Map map2 = createMap(map.getClass(), ()-> new HashMap<>(map.size(), 1f)); if (isEmpty(map)) { return map2; } @@ -1013,15 +1013,7 @@ public class MapUtil extends MapGetUtil { return map; } - final Iterator> iter = map.entrySet().iterator(); - Entry entry; - while (iter.hasNext()) { - entry = iter.next(); - if (null == entry.getValue()) { - iter.remove(); - } - } - + map.entrySet().removeIf(entry -> null == entry.getValue()); return map; } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/math/MathUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/math/MathUtil.java index bbcbe4c5c..15eaf731b 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/math/MathUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/math/MathUtil.java @@ -18,6 +18,8 @@ import org.dromara.hutool.core.text.StrUtil; import java.math.BigInteger; import java.util.List; +import static java.lang.Math.min; + /** * 数学相关方法工具类
* 此工具类与{@link NumberUtil}属于一类工具,NumberUtil偏向于简单数学计算的封装,MathUtil偏向复杂数学计算 @@ -31,11 +33,12 @@ public class MathUtil { * 0-20对应的阶乘,超过20的阶乘会超过Long.MAX_VALUE */ private static final long[] FACTORIALS = new long[]{ - 1L, 1L, 2L, 6L, 24L, 120L, 720L, 5040L, 40320L, 362880L, 3628800L, 39916800L, 479001600L, 6227020800L, - 87178291200L, 1307674368000L, 20922789888000L, 355687428096000L, 6402373705728000L, 121645100408832000L, - 2432902008176640000L}; + 1L, 1L, 2L, 6L, 24L, 120L, 720L, 5040L, 40320L, 362880L, 3628800L, 39916800L, 479001600L, 6227020800L, + 87178291200L, 1307674368000L, 20922789888000L, 355687428096000L, 6402373705728000L, 121645100408832000L, + 2432902008176640000L}; //--------------------------------------------------------------------------------------------- Arrangement + /** * 计算排列数,即A(n, m) = n!/(n-m)! * @@ -61,7 +64,7 @@ public class MathUtil { * 排列选择(从列表中选择n个排列) * * @param datas 待选列表 - * @param m 选择个数 + * @param m 选择个数 * @return 所有排列列表 */ public static List arrangementSelect(final String[] datas, final int m) { @@ -79,6 +82,7 @@ public class MathUtil { } //--------------------------------------------------------------------------------------------- Combination + /** * 计算组合数,即C(n, m) = n!/((n-m)!* m!) * @@ -94,7 +98,7 @@ public class MathUtil { * 组合选择(从列表中选择n个组合) * * @param datas 待选列表 - * @param m 选择个数 + * @param m 选择个数 * @return 所有组合列表 */ public static List combinationSelect(final String[] datas, final int m) { @@ -270,19 +274,57 @@ public class MathUtil { } /** - * 最大公约数 + * 最大公约数
+ * 见:https://stackoverflow.com/questions/4009198/java-get-greatest-common-divisor
+ * 来自Guava的IntMath.gcd * - * @param m 第一个值 - * @param n 第二个值 + * @param a 第一个值 + * @param b 第二个值 * @return 最大公约数 */ - public static int divisor(int m, int n) { - while (m % n != 0) { - final int temp = m % n; - m = n; - n = temp; + public static int gcd(int a, int b) { + /* + * The reason we require both arguments to be >= 0 is because otherwise, what do you return on + * gcd(0, Integer.MIN_VALUE)? BigInteger.gcd would return positive 2^31, but positive 2^31 + * isn't an int. + */ + Assert.isTrue(a >= 0, "a must be >= 0"); + Assert.isTrue(b >= 0, "b must be >= 0"); + if (a == 0) { + // 0 % b == 0, so b divides a, but the converse doesn't hold. + // BigInteger.gcd is consistent with this decision. + return b; + } else if (b == 0) { + return a; // similar logic } - return n; + /* + * Uses the binary GCD algorithm; see http://en.wikipedia.org/wiki/Binary_GCD_algorithm. + * This is >40% faster than the Euclidean algorithm in benchmarks. + */ + final int aTwos = Integer.numberOfTrailingZeros(a); + a >>= aTwos; // divide out all 2s + final int bTwos = Integer.numberOfTrailingZeros(b); + b >>= bTwos; // divide out all 2s + while (a != b) { // both a, b are odd + // The key to the binary GCD algorithm is as follows: + // Both a and b are odd. Assume a > b; then gcd(a - b, b) = gcd(a, b). + // But in gcd(a - b, b), a - b is even and b is odd, so we can divide out powers of two. + + // We bend over backwards to avoid branching, adapting a technique from + // http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax + + final int delta = a - b; // can't overflow, since a and b are nonnegative + + final int minDeltaOrZero = delta & (delta >> (Integer.SIZE - 1)); + // equivalent to Math.min(delta, 0) + + a = delta - minDeltaOrZero - minDeltaOrZero; // sets a to Math.abs(a - b) + // a is now nonnegative and even + + b += minDeltaOrZero; // sets b to min(old a, b) + a >>= Integer.numberOfTrailingZeros(a); // divide out all 2s, since 2 doesn't divide b + } + return a << min(aTwos, bTwos); } /** @@ -293,7 +335,7 @@ public class MathUtil { * @return 最小公倍数 */ public static int multiple(final int m, final int n) { - return m * n / divisor(m, n); + return m * n / gcd(m, n); } private static int mathSubNode(final int selectNum, final int minNum) { diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberUtil.java index 51cc720ea..ec3881378 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/math/NumberUtil.java @@ -34,7 +34,7 @@ import java.util.Objects; * JDK7中BigDecimal(double val)构造方法的结果有一定的不可预知性,例如: * *
- * new BigDecimal(0.1)
+ * new BigDecimal(0.1)和 BigDecimal.valueOf(0.1)
  * 
*

* 表示的不是0.1而是0.1000000000000000055511151231257827021181583404541015625 diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/net/Ipv4Util.java b/hutool-core/src/main/java/org/dromara/hutool/core/net/Ipv4Util.java index 07ba4cf78..17f2d640c 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/net/Ipv4Util.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/net/Ipv4Util.java @@ -17,9 +17,10 @@ import org.dromara.hutool.core.collection.ListUtil; import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.core.lang.Singleton; import org.dromara.hutool.core.regex.PatternPool; +import org.dromara.hutool.core.regex.ReUtil; +import org.dromara.hutool.core.text.CharUtil; import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.text.split.SplitUtil; -import org.dromara.hutool.core.text.CharUtil; import java.net.Inet4Address; import java.net.InetAddress; @@ -567,6 +568,34 @@ public class Ipv4Util implements Ipv4Pool { throw new IllegalArgumentException("Illegal position of ip Long: " + position); } } + + /** + * 检测指定 IP 地址是否匹配通配符 wildcard + * + * @param wildcard 通配符,如 192.168.*.1 + * @param ipAddress 待检测的 IP 地址 + * @return 是否匹配 + */ + public static boolean matches(final String wildcard, final String ipAddress) { + if (!ReUtil.isMatch(PatternPool.IPV4, ipAddress)) { + return false; + } + + final String[] wildcardSegments = SplitUtil.splitToArray(wildcard, StrUtil.DOT); + final String[] ipSegments = SplitUtil.splitToArray(ipAddress, StrUtil.DOT); + + if (wildcardSegments.length != ipSegments.length) { + return false; + } + + for (int i = 0; i < wildcardSegments.length; i++) { + if (!"*".equals(wildcardSegments[i]) && !wildcardSegments[i].equals(ipSegments[i])) { + return false; + } + } + return true; + } + //-------------------------------------------------------------------------------- Private method start /** diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/TypeUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/TypeUtil.java index 86ec1b7d5..768065df8 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/TypeUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/TypeUtil.java @@ -13,6 +13,7 @@ package org.dromara.hutool.core.reflect; import org.dromara.hutool.core.array.ArrayUtil; +import org.dromara.hutool.core.collection.ListUtil; import org.dromara.hutool.core.util.ObjUtil; import java.lang.reflect.Field; @@ -21,6 +22,7 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; +import java.util.List; import java.util.Map; /** @@ -276,23 +278,69 @@ public class TypeUtil { * @since 4.5.2 */ public static ParameterizedType toParameterizedType(final Type type) { - ParameterizedType result = null; + return toParameterizedType(type, 0); + } + + /** + * 将{@link Type} 转换为{@link ParameterizedType}
+ * {@link ParameterizedType}用于获取当前类或父类中泛型参数化后的类型
+ * 一般用于获取泛型参数具体的参数类型,例如: + * + *

{@code
+	 *   class A
+	 *   class B extends A;
+	 * }
+ *

+ * 通过此方法,传入B.class即可得到B对应的{@link ParameterizedType},从而获取到String + * + * @param type {@link Type} + * @param interfaceIndex 实现的第几个接口 + * @return {@link ParameterizedType} + * @since 4.5.2 + */ + public static ParameterizedType toParameterizedType(final Type type, final int interfaceIndex) { if (type instanceof ParameterizedType) { - result = (ParameterizedType) type; - } else if (type instanceof Class) { - final Class clazz = (Class) type; - Type genericSuper = clazz.getGenericSuperclass(); - if (null == genericSuper || Object.class.equals(genericSuper)) { - // 如果类没有父类,而是实现一些定义好的泛型接口,则取接口的Type - final Type[] genericInterfaces = clazz.getGenericInterfaces(); - if (ArrayUtil.isNotEmpty(genericInterfaces)) { - // 默认取第一个实现接口的泛型Type - genericSuper = genericInterfaces[0]; + return (ParameterizedType) type; + } + + if (type instanceof Class) { + final ParameterizedType[] generics = getGenerics((Class) type); + if(generics.length > interfaceIndex){ + return generics[interfaceIndex]; + } + } + + return null; + } + + /** + * 获取指定类所有泛型父类和泛型接口 + * + * @param clazz 类 + * @return 泛型父类或接口数组 + * @since 6.0.0 + */ + public static ParameterizedType[] getGenerics(final Class clazz) { + final List result = ListUtil.of(false); + // 泛型父类(父类及祖类优先级高) + final Type genericSuper = clazz.getGenericSuperclass(); + if(null != genericSuper && !Object.class.equals(genericSuper)){ + final ParameterizedType parameterizedType = toParameterizedType(genericSuper); + if(null != parameterizedType){ + result.add(parameterizedType); + } + } + + // 泛型接口 + final Type[] genericInterfaces = clazz.getGenericInterfaces(); + if (ArrayUtil.isNotEmpty(genericInterfaces)) { + for (final Type genericInterface : genericInterfaces) { + if (genericInterface instanceof ParameterizedType) { + result.add((ParameterizedType) genericInterface); } } - result = toParameterizedType(genericSuper); } - return result; + return result.toArray(new ParameterizedType[0]); } /** diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/CharSequenceUtil.java index 37ff073c9..55ef5c4f8 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/text/CharSequenceUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/CharSequenceUtil.java @@ -32,7 +32,11 @@ import org.dromara.hutool.core.util.ByteUtil; import org.dromara.hutool.core.util.CharsetUtil; import org.dromara.hutool.core.util.ObjUtil; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; import java.text.MessageFormat; import java.text.Normalizer; import java.util.HashSet; @@ -3183,6 +3187,59 @@ public class CharSequenceUtil extends StrValidator { } return sub(string, 0, length) + "..."; } + + /** + * 截断字符串,使用UTF8编码为字节后不超过maxBytes长度 + * + * @param str 原始字符串 + * @param maxBytesLength 最大字节数 + * @param appendDots 截断后是否追加省略号(...) + * @return 限制后的长度 + */ + public static String limitByteLengthUtf8(final CharSequence str, final int maxBytesLength, final boolean appendDots) { + return limitByteLength(str, CharsetUtil.UTF_8, maxBytesLength, 4, appendDots); + } + + /** + * 截断字符串,使用其按照指定编码为字节后不超过maxBytes长度 + * + * @param str 原始字符串 + * @param charset 指定编码 + * @param maxBytesLength 最大字节数 + * @param factor 速算因子,取该编码下单个字符的最大可能字节数 + * @param appendDots 截断后是否追加省略号(...) + * @return 限制后的长度 + */ + public static String limitByteLength(final CharSequence str, final Charset charset, final int maxBytesLength, + final int factor, final boolean appendDots) { + //字符数*速算因子<=最大字节数 + if (str == null || str.length() * factor <= maxBytesLength) { + return str(str); + } + final byte[] sba = ByteUtil.toBytes(str, charset); + if (sba.length <= maxBytesLength) { + return str(str); + } + //限制字节数 + final int limitBytes; + if (appendDots) { + limitBytes = maxBytesLength - "...".getBytes(charset).length; + } else { + limitBytes = maxBytesLength; + } + final ByteBuffer bb = ByteBuffer.wrap(sba, 0, limitBytes); + final CharBuffer cb = CharBuffer.allocate(limitBytes); + final CharsetDecoder decoder = charset.newDecoder(); + //忽略被截断的字符 + decoder.onMalformedInput(CodingErrorAction.IGNORE); + decoder.decode(bb, cb, true); + decoder.flush(cb); + final String result = new String(cb.array(), 0, cb.position()); + if (appendDots) { + return result + "..."; + } + return result; + } // endregion // region ----- firstXXX diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/thread/ExecutorBuilder.java b/hutool-core/src/main/java/org/dromara/hutool/core/thread/ExecutorBuilder.java index d32e68b4e..48ab387cb 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/thread/ExecutorBuilder.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/thread/ExecutorBuilder.java @@ -15,16 +15,7 @@ package org.dromara.hutool.core.thread; import org.dromara.hutool.core.lang.builder.Builder; import org.dromara.hutool.core.util.ObjUtil; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.RejectedExecutionHandler; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; /** * {@link ThreadPoolExecutor} 建造者 @@ -42,8 +33,10 @@ import java.util.concurrent.TimeUnit; public class ExecutorBuilder implements Builder { private static final long serialVersionUID = 1L; - /** 默认的等待队列容量 */ - public static final int DEFAULT_QUEUE_CAPACITY = 1024; + /** + * 默认的等待队列容量 + */ + public static final int DEFAULT_QUEUE_CAPACITY = Integer.MAX_VALUE; /** * 初始池大小 @@ -137,6 +130,18 @@ public class ExecutorBuilder implements Builder { return this; } + /** + * 使用{@link LinkedBlockingQueue} 作为等待队列
+ * 队列满时,运行线程小于maxPoolSize时会创建新线程,否则触发异常策略 + * + * @param capacity 队列容量 + * @return this + * @since 6.0.0 + */ + public ExecutorBuilder useLinkedBlockingQueue(final int capacity) { + return setWorkQueue(new LinkedBlockingQueue<>(capacity)); + } + /** * 使用{@link ArrayBlockingQueue} 做为等待队列
* 有界队列,相对无界队列有利于控制队列大小,队列满时,运行线程小于maxPoolSize时会创建新线程,否则触发异常策略 @@ -257,12 +262,12 @@ public class ExecutorBuilder implements Builder { final RejectedExecutionHandler handler = ObjUtil.defaultIfNull(builder.handler, RejectPolicy.ABORT.getValue()); final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(// - corePoolSize, // - maxPoolSize, // - keepAliveTime, TimeUnit.NANOSECONDS, // - workQueue, // - threadFactory, // - handler// + corePoolSize, // + maxPoolSize, // + keepAliveTime, TimeUnit.NANOSECONDS, // + workQueue, // + threadFactory, // + handler// ); if (null != builder.allowCoreThreadTimeOut) { threadPoolExecutor.allowCoreThreadTimeOut(builder.allowCoreThreadTimeOut); diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/thread/ThreadUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/thread/ThreadUtil.java index e61577bdd..ac529e2f9 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/thread/ThreadUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/thread/ThreadUtil.java @@ -39,25 +39,6 @@ import java.util.function.Supplier; */ public class ThreadUtil { - /** - * 新建一个线程池,默认的策略如下: - *

-	 *    1. 初始线程数为corePoolSize指定的大小
-	 *    2. 没有最大线程数限制
-	 *    3. 默认使用LinkedBlockingQueue,默认队列大小为1024
-	 * 
- * - * @param corePoolSize 同时执行的线程数大小 - * @return ExecutorService - */ - public static ExecutorService newExecutor(final int corePoolSize) { - final ExecutorBuilder builder = ExecutorBuilder.of(); - if (corePoolSize > 0) { - builder.setCorePoolSize(corePoolSize); - } - return builder.build(); - } - /** * 获得一个新的线程池,默认的策略如下: *
@@ -86,10 +67,25 @@ public class ThreadUtil {
 	 */
 	public static ExecutorService newSingleExecutor() {
 		return ExecutorBuilder.of()//
-				.setCorePoolSize(1)//
-				.setMaxPoolSize(1)//
-				.setKeepAliveTime(0)//
-				.buildFinalizable();
+			.setCorePoolSize(1)//
+			.setMaxPoolSize(1)//
+			.setKeepAliveTime(0)//
+			.buildFinalizable();
+	}
+
+	/**
+	 * 新建一个线程池,默认的策略如下:
+	 * 
+	 *    1. 初始线程数为poolSize指定的大小
+	 *    2. 最大线程数为poolSize指定的大小
+	 *    3. 默认使用LinkedBlockingQueue,默认无界队列
+	 * 
+ * + * @param poolSize 同时执行的线程数大小 + * @return ExecutorService + */ + public static ExecutorService newExecutor(final int poolSize) { + return newExecutor(poolSize, poolSize); } /** @@ -102,9 +98,9 @@ public class ThreadUtil { */ public static ThreadPoolExecutor newExecutor(final int corePoolSize, final int maximumPoolSize) { return ExecutorBuilder.of() - .setCorePoolSize(corePoolSize) - .setMaxPoolSize(maximumPoolSize) - .build(); + .setCorePoolSize(corePoolSize) + .setMaxPoolSize(maximumPoolSize) + .build(); } /** @@ -119,10 +115,10 @@ public class ThreadUtil { */ public static ExecutorService newExecutor(final int corePoolSize, final int maximumPoolSize, final int maximumQueueSize) { return ExecutorBuilder.of() - .setCorePoolSize(corePoolSize) - .setMaxPoolSize(maximumPoolSize) - .setWorkQueue(new LinkedBlockingQueue<>(maximumQueueSize)) - .build(); + .setCorePoolSize(corePoolSize) + .setMaxPoolSize(maximumPoolSize) + .useLinkedBlockingQueue(maximumQueueSize) + .build(); } /** @@ -184,7 +180,7 @@ public class ThreadUtil { */ public static ExecutorService newFixedExecutor(final int nThreads, final int maximumQueueSize, final String threadNamePrefix, final boolean isBlocked) { return newFixedExecutor(nThreads, maximumQueueSize, threadNamePrefix, - (isBlocked ? RejectPolicy.BLOCK : RejectPolicy.ABORT).getValue()); + (isBlocked ? RejectPolicy.BLOCK : RejectPolicy.ABORT).getValue()); } /** @@ -207,11 +203,11 @@ public class ThreadUtil { final String threadNamePrefix, final RejectedExecutionHandler handler) { return ExecutorBuilder.of() - .setCorePoolSize(nThreads).setMaxPoolSize(nThreads) - .setWorkQueue(new LinkedBlockingQueue<>(maximumQueueSize)) - .setThreadFactory(createThreadFactory(threadNamePrefix)) - .setHandler(handler) - .build(); + .setCorePoolSize(nThreads).setMaxPoolSize(nThreads) + .setWorkQueue(new LinkedBlockingQueue<>(maximumQueueSize)) + .setThreadFactory(createThreadFactory(threadNamePrefix)) + .setHandler(handler) + .build(); } /** diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/util/RandomUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/util/RandomUtil.java index f34278d87..9be5f6738 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/util/RandomUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/util/RandomUtil.java @@ -56,6 +56,8 @@ public class RandomUtil { */ public static final String BASE_CHAR_NUMBER = BASE_CHAR.toUpperCase() + BASE_CHAR_NUMBER_LOWER; + // region ----- get or create Random + /** * 获取随机数生成器对象
* ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。 @@ -165,6 +167,7 @@ public class RandomUtil { public static Random getRandom(final boolean isSecure) { return isSecure ? getSecureRandom() : getRandom(); } + // endregion /** * 获得随机Boolean值 @@ -176,6 +179,18 @@ public class RandomUtil { return 0 == randomInt(2); } + /** + * 随机bytes + * + * @param length 长度 + * @return bytes + */ + public static byte[] randomBytes(final int length) { + final byte[] bytes = new byte[length]; + getRandom().nextBytes(bytes); + return bytes; + } + /** * 随机汉字('\u4E00'-'\u9FFF') * @@ -186,16 +201,7 @@ public class RandomUtil { return (char) randomInt('\u4E00', '\u9FFF'); } - /** - * 获得指定范围内的随机数 - * - * @param min 最小数(包含) - * @param max 最大数(不包含) - * @return 随机数 - */ - public static int randomInt(final int min, final int max) { - return getRandom().nextInt(min, max); - } + // region ----- randomInt /** * 获得随机数int值 @@ -210,27 +216,63 @@ public class RandomUtil { /** * 获得指定范围内的随机数 [0,limit) * - * @param limit 限制随机数的范围,不包括这个数 + * @param limitExclude 限制随机数的范围,不包括这个数 * @return 随机数 * @see Random#nextInt(int) */ - public static int randomInt(final int limit) { - return getRandom().nextInt(limit); + public static int randomInt(final int limitExclude) { + return getRandom().nextInt(limitExclude); } /** - * 获得指定范围内的随机数[min, max) + * 获得指定范围内的随机数 * - * @param min 最小数(包含) - * @param max 最大数(不包含) + * @param minInclude 最小数(包含) + * @param maxExclude 最大数(不包含) * @return 随机数 - * @see ThreadLocalRandom#nextLong(long, long) - * @since 3.3.0 */ - public static long randomLong(final long min, final long max) { - return getRandom().nextLong(min, max); + public static int randomInt(final int minInclude, final int maxExclude) { + return randomInt(minInclude, maxExclude, true, false); } + /** + * 获得指定范围内的随机数 + * + * @param min 最小数 + * @param max 最大数 + * @param includeMin 是否包含最小值 + * @param includeMax 是否包含最大值 + * @return 随机数 + */ + public static int randomInt(int min, int max, final boolean includeMin, final boolean includeMax) { + if (!includeMin) { + min++; + } + if (includeMax) { + max--; + } + return getRandom().nextInt(min, max); + } + + /** + * 创建指定长度的随机索引 + * + * @param length 长度 + * @return 随机索引 + * @since 5.2.1 + */ + public static int[] randomInts(final int length) { + final int[] range = NumberUtil.range(length); + for (int i = 0; i < length; i++) { + final int random = randomInt(i, length); + ArrayUtil.swap(range, i, random); + } + return range; + } + // endregion + + // region ----- randomLong + /** * 获得随机数 * @@ -245,39 +287,114 @@ public class RandomUtil { /** * 获得指定范围内的随机数 [0,limit) * - * @param limit 限制随机数的范围,不包括这个数 + * @param limitExclude 限制随机数的范围,不包括这个数 * @return 随机数 * @see ThreadLocalRandom#nextLong(long) */ - public static long randomLong(final long limit) { - return getRandom().nextLong(limit); + public static long randomLong(final long limitExclude) { + return getRandom().nextLong(limitExclude); + } + + /** + * 获得指定范围内的随机数[min, max) + * + * @param minInclude 最小数(包含) + * @param maxExclude 最大数(不包含) + * @return 随机数 + * @see ThreadLocalRandom#nextLong(long, long) + * @since 3.3.0 + */ + public static long randomLong(final long minInclude, final long maxExclude) { + return randomLong(minInclude, maxExclude, true, false); } /** * 获得指定范围内的随机数 * - * @param min 最小数(包含) - * @param max 最大数(不包含) + * @param min 最小数 + * @param max 最大数 + * @param includeMin 是否包含最小值 + * @param includeMax 是否包含最大值 + * @return 随机数 + */ + public static long randomLong(long min, long max, final boolean includeMin, final boolean includeMax) { + if (!includeMin) { + min++; + } + if (includeMax) { + max--; + } + return getRandom().nextLong(min, max); + } + // endregion + + // region ----- randomFloat + + /** + * 获得随机数[0, 1) + * + * @return 随机数 + * @see ThreadLocalRandom#nextFloat() + */ + public static float randomFloat() { + return getRandom().nextFloat(); + } + + /** + * 获得指定范围内的随机数 [0,limit) + * + * @param limitExclude 限制随机数的范围,不包括这个数 + * @return 随机数 + */ + public static float randomFloat(final float limitExclude) { + return randomFloat(0, limitExclude); + } + + /** + * 获得指定范围内的随机数[min, max) + * + * @param minInclude 最小数(包含) + * @param maxExclude 最大数(不包含) + * @return 随机数 + * @see ThreadLocalRandom#nextFloat() + */ + public static float randomFloat(final float minInclude, final float maxExclude) { + if (minInclude == maxExclude) { + return minInclude; + } + + return minInclude + ((maxExclude - minInclude) * getRandom().nextFloat()); + } + // endregion + + // region ----- randomDouble + + /** + * 获得指定范围内的随机数 + * + * @param minInclude 最小数(包含) + * @param maxExclude 最大数(不包含) * @return 随机数 * @see ThreadLocalRandom#nextDouble(double, double) * @since 3.3.0 */ - public static double randomDouble(final double min, final double max) { - return getRandom().nextDouble(min, max); + public static double randomDouble(final double minInclude, final double maxExclude) { + return getRandom().nextDouble(minInclude, maxExclude); } /** * 获得指定范围内的随机数 * - * @param min 最小数(包含) - * @param max 最大数(不包含) + * @param minInclude 最小数(包含) + * @param maxExclude 最大数(不包含) * @param scale 保留小数位数 * @param roundingMode 保留小数的模式 {@link RoundingMode} * @return 随机数 * @since 4.0.8 */ - public static double randomDouble(final double min, final double max, final int scale, final RoundingMode roundingMode) { - return NumberUtil.round(randomDouble(min, max), scale, roundingMode).doubleValue(); + public static double randomDouble(final double minInclude, final double maxExclude, final int scale, + final RoundingMode roundingMode) { + return NumberUtil.round(randomDouble(minInclude, maxExclude), scale, roundingMode).doubleValue(); } /** @@ -327,6 +444,9 @@ public class RandomUtil { public static double randomDouble(final double limit, final int scale, final RoundingMode roundingMode) { return NumberUtil.round(randomDouble(limit), scale, roundingMode).doubleValue(); } + // endregion + + // region ----- randomBigDecimal /** * 获得指定范围内的随机数[0, 1) @@ -341,37 +461,28 @@ public class RandomUtil { /** * 获得指定范围内的随机数 [0,limit) * - * @param limit 最大数(不包含) + * @param limitExclude 最大数(不包含) * @return 随机数 * @since 4.0.9 */ - public static BigDecimal randomBigDecimal(final BigDecimal limit) { - return NumberUtil.toBigDecimal(getRandom().nextDouble(limit.doubleValue())); + public static BigDecimal randomBigDecimal(final BigDecimal limitExclude) { + return NumberUtil.toBigDecimal(getRandom().nextDouble(limitExclude.doubleValue())); } /** * 获得指定范围内的随机数 * - * @param min 最小数(包含) - * @param max 最大数(不包含) + * @param minInclude 最小数(包含) + * @param maxExclude 最大数(不包含) * @return 随机数 * @since 4.0.9 */ - public static BigDecimal randomBigDecimal(final BigDecimal min, final BigDecimal max) { - return NumberUtil.toBigDecimal(getRandom().nextDouble(min.doubleValue(), max.doubleValue())); + public static BigDecimal randomBigDecimal(final BigDecimal minInclude, final BigDecimal maxExclude) { + return NumberUtil.toBigDecimal(getRandom().nextDouble(minInclude.doubleValue(), maxExclude.doubleValue())); } + // endregion - /** - * 随机bytes - * - * @param length 长度 - * @return bytes - */ - public static byte[] randomBytes(final int length) { - final byte[] bytes = new byte[length]; - getRandom().nextBytes(bytes); - return bytes; - } + // region ----- randomEle /** * 随机获得列表中的元素 @@ -470,8 +581,8 @@ public class RandomUtil { /** * 生成从种子中获取随机数字 * - * @param size 指定产生随机数的个数 - * @param seed 种子,用于取随机数的int池 + * @param size 指定产生随机数的个数 + * @param seed 种子,用于取随机数的int池 * @return 随机int数组 * @since 5.4.5 */ @@ -514,22 +625,9 @@ public class RandomUtil { return result; } + // endregion - /** - * 创建指定长度的随机索引 - * - * @param length 长度 - * @return 随机索引 - * @since 5.2.1 - */ - public static int[] randomInts(final int length) { - final int[] range = NumberUtil.range(length); - for (int i = 0; i < length; i++) { - final int random = randomInt(i, length); - ArrayUtil.swap(range, i, random); - } - return range; - } + // region ----- randomString /** * 获得一个随机的字符串(只包含数字和大小写字母) @@ -596,7 +694,7 @@ public class RandomUtil { if (StrUtil.isEmpty(baseString)) { return StrUtil.EMPTY; } - if(length < 1){ + if (length < 1) { length = 1; } @@ -608,6 +706,9 @@ public class RandomUtil { } return sb.toString(); } + // endregion + + // region ---- randomChar /** * 随机数字,数字为0~9单个数字 @@ -639,6 +740,9 @@ public class RandomUtil { public static char randomChar(final String baseString) { return baseString.charAt(randomInt(baseString.length())); } + // endregion + + // region ----- weightRandom /** * 带有权重的随机生成器 @@ -663,6 +767,9 @@ public class RandomUtil { public static WeightRandomSelector weightRandom(final Iterable> weightObjs) { return new WeightRandomSelector<>(weightObjs); } + // endregion + + // region ----- randomDate /** * 以当天为基准,随机产生一个日期 @@ -693,5 +800,5 @@ public class RandomUtil { return DateUtil.offset(baseDate, dateField, randomInt(min, max)); } - + // endregion } diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/collection/CollUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/collection/CollUtilTest.java index 8e3bab963..91ee1ff88 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/collection/CollUtilTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/collection/CollUtilTest.java @@ -1090,6 +1090,26 @@ public class CollUtilTest { } } + @ToString(callSuper = true) + @EqualsAndHashCode(callSuper = true) + @Data + static class Cat extends Animal { + + public Cat(final String name, final Integer age) { + super(name, age); + } + } + + @ToString(callSuper = true) + @EqualsAndHashCode(callSuper = true) + @Data + static class Pig extends Animal { + + public Pig(final String name, final Integer age) { + super(name, age); + } + } + @Test public void getFirstTest() { Assertions.assertNull(CollUtil.getFirst(null)); @@ -1162,6 +1182,29 @@ public class CollUtilTest { Assertions.assertNull(CollUtil.max(null)); } + @Test + public void unionExtendTest() { + final List dog = Arrays.asList(new Dog("dog1", 12), new Dog("dog2", 12)); + final List cat = Arrays.asList(new Cat("cat1", 12), new Cat("cat2", 12)); + Assertions.assertEquals(CollUtil.union(dog, cat).size(), dog.size() + cat.size()); + } + + @Test + public void unionAllExtendTest() { + final List dog = Arrays.asList(new Dog("dog1", 12), new Dog("dog2", 12)); + final List cat = Arrays.asList(new Cat("cat1", 12), new Cat("cat2", 12)); + final List pig = Arrays.asList(new Pig("pig1", 12), new Pig("pig2", 12)); + Assertions.assertEquals(CollUtil.unionAll(dog, cat, pig).size(), dog.size() + cat.size() + pig.size()); + } + + @Test + public void unionDistinctExtendTest() { + final List dog = Arrays.asList(new Dog("dog1", 12), new Dog("dog1", 12)); // same + final List cat = Arrays.asList(new Cat("cat1", 12), new Cat("cat2", 12)); + final List pig = Arrays.asList(new Pig("pig1", 12), new Pig("pig2", 12)); + Assertions.assertEquals(CollUtil.unionDistinct(dog, cat, pig).size(), 5); + } + @Test public void flatListTest1() { diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/date/DateUtilTest.java index 64c97cefa..984c5f246 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/date/DateUtilTest.java @@ -1088,4 +1088,11 @@ public class DateUtilTest { Assertions.assertEquals(71, DateUtil.age(DateUtil.parse("1952-02-13"), DateUtil.parse("2023-02-14"))); Assertions.assertEquals(0, DateUtil.age(DateUtil.parse("2023-02-14"), DateUtil.parse("2023-02-14"))); } + + @Test + void issueI7H34NTest() { + final DateTime parse = DateUtil.parse("2019-10-22T09:56:03.000123Z"); + Assertions.assertNotNull(parse); + Assertions.assertEquals("2019-10-22 09:56:03", parse.toString()); + } } diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/io/file/PathUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/io/file/PathUtilTest.java index 6aefc7dbc..58ddec385 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/io/file/PathUtilTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/io/file/PathUtilTest.java @@ -93,4 +93,10 @@ public class PathUtilTest { public void moveTest2(){ PathUtil.move(Paths.get("D:\\project\\test1.txt"), Paths.get("D:\\project\\test2.txt"), false); } + + @Test + public void issue3179Test() { + final String mimeType = PathUtil.getMimeType(Paths.get("xxxx.jpg")); + Assertions.assertEquals("image/jpeg", mimeType); + } } diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/map/MapUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/map/MapUtilTest.java index 1d91bf2e9..c2726b91c 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/map/MapUtilTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/map/MapUtilTest.java @@ -285,4 +285,26 @@ public class MapUtilTest { final Map map = MapUtil.renameKey(v1, "name", "newName"); Assertions.assertEquals("张三", map.get("newName")); } + + @Test + public void removeNullValueTest() { + final Dict v1 = Dict.of().set("id", 12).set("name", null).set("age", null); + final Map map = MapUtil.removeNullValue(v1); + Assertions.assertEquals(1, map.size()); + } + + @Test + public void issue3162Test() { + final Map map = new HashMap() { + private static final long serialVersionUID = 1L; + { + put("a", "1"); + put("b", "2"); + put("c", "3"); + }}; + final Map filtered = MapUtil.filter(map, "a", "b"); + Assertions.assertEquals(2, filtered.size()); + Assertions.assertEquals("1", filtered.get("a")); + Assertions.assertEquals("2", filtered.get("b")); + } } diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/net/Ipv4UtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/net/Ipv4UtilTest.java index 2af24db1c..42ac19ca6 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/net/Ipv4UtilTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/net/Ipv4UtilTest.java @@ -229,4 +229,13 @@ public class Ipv4UtilTest { final boolean match = ReUtil.isMatch(PatternPool.MAC_ADDRESS, macAddress); Assertions.assertTrue(match); } + + @Test + public void matchesTest() { + final boolean matches1 = Ipv4Util.matches("127.*.*.1", "127.0.0.1"); + Assertions.assertTrue(matches1); + + final boolean matches2 = Ipv4Util.matches("192.168.*.1", "127.0.0.1"); + Assertions.assertFalse(matches2); + } } diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/reflect/IssueI7CRIWTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/reflect/IssueI7CRIWTest.java new file mode 100755 index 000000000..313b710da --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/reflect/IssueI7CRIWTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.reflect; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Type; + +public class IssueI7CRIWTest { + + @Test + void getTypeArgumentsTest() { + // 无法从继承获取泛型,则从接口获取 + Type type = TypeUtil.getTypeArgument(C.class); + Assertions.assertEquals(type, String.class); + + // 继承和第一个接口都非泛型接口,则从找到的第一个泛型接口获取 + type = TypeUtil.getTypeArgument(D.class); + Assertions.assertEquals(type, String.class); + } + + static class A{ + + } + + static class AT{ + + } + + interface Face1{ + + } + + interface Face2{ + + } + + static class B extends A{ + + } + + static class C extends A implements Face1{ + + } + + static class D extends A implements Face2, Face1{ + + } +} diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/text/CharSequenceUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/text/CharSequenceUtilTest.java index ee54a47ea..7588123e9 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/text/CharSequenceUtilTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/text/CharSequenceUtilTest.java @@ -287,9 +287,33 @@ public class CharSequenceUtilTest { @Test void codeLengthTest() { - String a = "🍒🐽"; + final String a = "🍒🐽"; final int i = StrUtil.codeLength(a); Assertions.assertEquals(4, a.length()); Assertions.assertEquals(2, i); } + + @Test + public void limitByteLengthUtf8Test() { + final String str = "这是This一段中英文"; + String ret = StrUtil.limitByteLengthUtf8(str, 12, true); + Assertions.assertEquals("这是Thi...", ret); + + ret = StrUtil.limitByteLengthUtf8(str, 13, true); + Assertions.assertEquals("这是This...", ret); + + ret = StrUtil.limitByteLengthUtf8(str, 14, true); + Assertions.assertEquals("这是This...", ret); + + ret = StrUtil.limitByteLengthUtf8(str, 999, true); + Assertions.assertEquals(str, ret); + } + + @Test + public void limitByteLengthTest() { + final String str = "This is English"; + final String ret = StrUtil.limitByteLength(str, CharsetUtil.ISO_8859_1,10, 1, false); + Assertions.assertEquals("This is En", ret); + + } } diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/thread/Issue3167Test.java b/hutool-core/src/test/java/org/dromara/hutool/core/thread/Issue3167Test.java new file mode 100755 index 000000000..a29de963d --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/thread/Issue3167Test.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.thread; + +import org.dromara.hutool.core.lang.Console; + +import java.util.concurrent.ExecutorService; + +public class Issue3167Test { + public static void main(final String[] args) { + final ExecutorService executorService = ThreadUtil.newExecutor(2); + + for (int i = 0; i < 1035; i++) { + final int finalI = i; + executorService.submit(() -> { + Console.log(Thread.currentThread().getName(), finalI); + ThreadUtil.sleep(5000); + }); + } + } +} diff --git a/hutool-db/src/test/java/org/dromara/hutool/db/IssueI7GUKOTest.java b/hutool-db/src/test/java/org/dromara/hutool/db/IssueI7GUKOTest.java new file mode 100755 index 000000000..b7a84988b --- /dev/null +++ b/hutool-db/src/test/java/org/dromara/hutool/db/IssueI7GUKOTest.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.db; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class IssueI7GUKOTest { + @Test + void entityTest() { + final Entity entity = Entity.of("data") + .set("sort", "test") + .set("pcode", "test") + .set("code", "test") + .set("define", "test") + .set("icon", "test") + .set("label", "test") + .set("stu", "test"); + + Assertions.assertEquals("[sort, pcode, code, define, icon, label, stu]", entity.keySet().toString()); + } +} diff --git a/hutool-db/src/test/java/org/dromara/hutool/db/meta/MetaUtilTest.java b/hutool-db/src/test/java/org/dromara/hutool/db/meta/MetaUtilTest.java index 85f944093..93161e448 100644 --- a/hutool-db/src/test/java/org/dromara/hutool/db/meta/MetaUtilTest.java +++ b/hutool-db/src/test/java/org/dromara/hutool/db/meta/MetaUtilTest.java @@ -22,7 +22,7 @@ public class MetaUtilTest { @Test public void getTablesTest() { final List tables = MetaUtil.getTables(ds); - Assertions.assertEquals("user", tables.get(0)); + Assertions.assertTrue(tables.contains("user")); } @Test diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/convert/JSONConverter.java b/hutool-json/src/main/java/org/dromara/hutool/json/convert/JSONConverter.java index 44e8a0866..ca407f0af 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/convert/JSONConverter.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/convert/JSONConverter.java @@ -124,7 +124,7 @@ public class JSONConverter implements Converter { *
  • String: 转换为相应的对象,"和'包围的字符串返回原字符串,""返回{@code null}
  • *
  • Array、Iterable、Iterator:转换为JSONArray
  • *
  • Bean对象:转为JSONObject
  • - *
  • Number:返回原对象
  • + *
  • Number、Boolean:返回原对象
  • *
  • null:返回{@code null}
  • * * @@ -138,7 +138,7 @@ public class JSONConverter implements Converter { return null; } final JSON json; - if (obj instanceof JSON || obj instanceof Number) { + if (obj instanceof JSON || obj instanceof Number || obj instanceof Boolean) { return obj; } else if (obj instanceof CharSequence) { final String jsonStr = StrUtil.trim((CharSequence) obj); diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/IssueI7FQ29Test.java b/hutool-json/src/test/java/org/dromara/hutool/json/IssueI7FQ29Test.java new file mode 100755 index 000000000..8c92949fa --- /dev/null +++ b/hutool-json/src/test/java/org/dromara/hutool/json/IssueI7FQ29Test.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.json; + +import org.dromara.hutool.core.reflect.TypeReference; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +/** + * https://gitee.com/dromara/hutool/issues/I7FQ29 + */ +public class IssueI7FQ29Test { + + @Test + void toMapTest() { + final String jsonStr = "{\"trans_no\": \"java.lang.String\"}"; + final Map> map = JSONUtil.toBean(jsonStr, new TypeReference>>() { + }); + + Assertions.assertNotNull(map); + Assertions.assertEquals(String.class, map.get("trans_no")); + } +} diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/IssueI7GPGXTest.java b/hutool-json/src/test/java/org/dromara/hutool/json/IssueI7GPGXTest.java new file mode 100755 index 000000000..4e9343670 --- /dev/null +++ b/hutool-json/src/test/java/org/dromara/hutool/json/IssueI7GPGXTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.json; + +import org.dromara.hutool.core.lang.Console; +import org.dromara.hutool.core.lang.tuple.Pair; +import org.dromara.hutool.core.lang.tuple.Triple; +import org.dromara.hutool.core.lang.tuple.Tuple; +import org.dromara.hutool.core.reflect.TypeReference; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class IssueI7GPGXTest { + @Test + public void pairToBeanTest() { + final Pair hutoolPair = Pair.of("test1", true); + final String a = JSONUtil.toJsonStr(hutoolPair); + final Pair pair = JSONUtil.toBean(a, new TypeReference>() {}); + Assertions.assertEquals(hutoolPair, pair); + } + + @Test + void tripleToBeanTest() { + final Triple hutoolTriple = Triple.of("aaa", 123, true); + final String a = JSONUtil.toJsonStr(hutoolTriple); + final Triple pair = JSONUtil.toBean(a, new TypeReference>() {}); + Assertions.assertEquals(hutoolTriple, pair); + } + + @Test + void tupleToBeanTest() { + final Tuple hutoolTriple = Tuple.of("aaa", 123, true); + final String a = JSONUtil.toJsonStr(hutoolTriple); + final Tuple pair = JSONUtil.toBean(a, Tuple.class); + Assertions.assertEquals(hutoolTriple, pair); + } +} diff --git a/hutool-json/src/test/java/org/dromara/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/org/dromara/hutool/json/JSONUtilTest.java index 3ef1a19d5..33c1a00a4 100644 --- a/hutool-json/src/test/java/org/dromara/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/org/dromara/hutool/json/JSONUtilTest.java @@ -2,6 +2,7 @@ package org.dromara.hutool.json; import org.dromara.hutool.core.collection.ListUtil; import org.dromara.hutool.core.date.DateUtil; +import org.dromara.hutool.core.lang.Console; import org.dromara.hutool.core.map.MapUtil; import org.dromara.hutool.core.math.NumberUtil; import org.dromara.hutool.json.serialize.JSONStringer; @@ -334,4 +335,10 @@ public class JSONUtilTest { private Byte[] d = new Byte[0]; private Byte[] e = new Byte[1]; } + + @Test + void toJsonStrOfBooleanTest() { + final String jsonStr = JSONUtil.toJsonStr(true); + Assertions.assertEquals("true", jsonStr); + } } diff --git a/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/ExcelReader.java index 106470458..b540b2b9b 100644 --- a/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/org/dromara/hutool/poi/excel/ExcelReader.java @@ -428,13 +428,20 @@ public class ExcelReader extends ExcelBase { /** * 获取Excel写出器
    - * 在读取Excel并做一定编辑后,获取写出器写出
    - * 注意,只读方式下,此方法无效 + * 在读取Excel并做一定编辑后,获取写出器写出,规则如下: + *
      + *
    • 1. 当从流中读取时,转换为Writer直接使用Sheet对象,此时修改不会影响源文件,Writer中flush需要指定新的路径。
    • + *
    • 2. 当从文件读取时,直接获取文件及sheet名称,此时可以修改原文件。
    • + *
    * * @return {@link ExcelWriter} * @since 4.0.6 */ public ExcelWriter getWriter() { + if (null == this.destFile) { + // 非读取文件形式,直接获取sheet操作。 + return new ExcelWriter(this.sheet); + } return ExcelUtil.getWriter(this.destFile, this.sheet.getSheetName()); } diff --git a/hutool-setting/src/main/java/org/dromara/hutool/setting/SettingLoader.java b/hutool-setting/src/main/java/org/dromara/hutool/setting/SettingLoader.java index 78b1ceae7..09e6a65fc 100644 --- a/hutool-setting/src/main/java/org/dromara/hutool/setting/SettingLoader.java +++ b/hutool-setting/src/main/java/org/dromara/hutool/setting/SettingLoader.java @@ -125,7 +125,7 @@ public class SettingLoader { if (line == null) { break; } - line = line.trim(); + line = StrUtil.trim(line); // 跳过注释行和空行 if (StrUtil.isBlank(line) || StrUtil.startWith(line, COMMENT_FLAG_PRE)) { continue; @@ -133,7 +133,7 @@ public class SettingLoader { // 记录分组名 if (StrUtil.isWrap(line, CharUtil.BRACKET_START, CharUtil.BRACKET_END)) { - group = line.substring(1, line.length() - 1).trim(); + group = StrUtil.trim(line.substring(1, line.length() - 1)); continue; } @@ -149,7 +149,7 @@ public class SettingLoader { if (this.isUseVariable) { value = replaceVar(group, value); } - this.groupedMap.put(group, keyValue[0].trim(), value); + this.groupedMap.put(group, StrUtil.trim(keyValue[0]), value); } } finally { IoUtil.closeQuietly(reader); diff --git a/hutool-setting/src/test/java/org/dromara/hutool/setting/IssueI7G34ETest.java b/hutool-setting/src/test/java/org/dromara/hutool/setting/IssueI7G34ETest.java new file mode 100755 index 000000000..07883812b --- /dev/null +++ b/hutool-setting/src/test/java/org/dromara/hutool/setting/IssueI7G34ETest.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.setting; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class IssueI7G34ETest { + + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") + @Test + void readWithBomTest() { + final Setting setting = new Setting("test_with_bom.setting"); + final String s = setting.getStrByGroup("key1", "line1"); + + Assertions.assertEquals("value1", s); + } +} diff --git a/hutool-setting/src/test/resources/test_with_bom.setting b/hutool-setting/src/test/resources/test_with_bom.setting new file mode 100755 index 000000000..32e92aa16 --- /dev/null +++ b/hutool-setting/src/test/resources/test_with_bom.setting @@ -0,0 +1,3 @@ +[line1] +key1 = value1 +key2 = value2