Prepare release

This commit is contained in:
Looly 2022-04-27 16:03:12 +08:00
commit da5e7334fe
132 changed files with 6093 additions and 2578 deletions

File diff suppressed because it is too large Load Diff

1878
CHANGELOG_5.0-5.7.md Executable file

File diff suppressed because it is too large Load Diff

View File

@ -144,18 +144,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.0.M3</version>
<version>5.8.0.M4</version>
</dependency>
```
### 🍐Gradle
```
implementation 'cn.hutool:hutool-all:5.8.0.M3'
implementation 'cn.hutool:hutool-all:5.8.0.M4'
```
## 📥Download
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.0.M3/)
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.0.M4/)
> 🔔note:
> Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available.

View File

@ -144,20 +144,20 @@ Hutool的存在就是为了减少代码搜索成本避免网络上参差不
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.0.M3</version>
<version>5.8.0.M4</version>
</dependency>
```
### 🍐Gradle
```
implementation 'cn.hutool:hutool-all:5.8.0.M3'
implementation 'cn.hutool:hutool-all:5.8.0.M4'
```
### 📥下载jar
点击以下链接,下载`hutool-all-X.X.X.jar`即可:
- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.0.M3/)
- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.0.M4/)
> 🔔️注意
> Hutool 5.x支持JDK8+对Android平台没有测试不能保证所有工具类或工具方法可用。

View File

@ -1 +1 @@
5.8.0.M3
5.8.0.M4

View File

@ -1 +1 @@
var version = '5.8.0.M3'
var version = '5.8.0.M4'

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.0.M3</version>
<version>5.8.0.M4</version>
</parent>
<artifactId>hutool-all</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.0.M3</version>
<version>5.8.0.M4</version>
</parent>
<artifactId>hutool-aop</artifactId>
@ -19,7 +19,7 @@
<properties>
<!-- versions -->
<cglib.version>3.3.0</cglib.version>
<spring.version>5.3.17</spring.version>
<spring.version>5.3.19</spring.version>
</properties>
<dependencies>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.0.M3</version>
<version>5.8.0.M4</version>
</parent>
<artifactId>hutool-bloomFilter</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.0.M3</version>
<version>5.8.0.M4</version>
</parent>
<artifactId>hutool-bom</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.0.M3</version>
<version>5.8.0.M4</version>
</parent>
<artifactId>hutool-cache</artifactId>

View File

@ -1,6 +1,11 @@
package cn.hutool.cache.impl;
import java.util.WeakHashMap;
import cn.hutool.cache.CacheListener;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.lang.mutable.Mutable;
import cn.hutool.core.map.WeakConcurrentMap;
import java.lang.ref.Reference;
/**
* 弱引用缓存<br>
@ -22,6 +27,17 @@ public class WeakCache<K, V> extends TimedCache<K, V>{
* @param timeout 超时时常单位毫秒-1或0表示无限制
*/
public WeakCache(long timeout) {
super(timeout, new WeakHashMap<>());
super(timeout, new WeakConcurrentMap<>());
}
@Override
public WeakCache<K, V> setListener(CacheListener<K, V> listener) {
super.setListener(listener);
final WeakConcurrentMap<Mutable<K>, CacheObj<K, V>> map = (WeakConcurrentMap<Mutable<K>, CacheObj<K, V>>) this.cacheMap;
// WeakKey回收之后key对应的值已经是null了因此此处的key也为null
map.setPurgeListener((key, value)-> listener.onRemove(Opt.ofNullable(key).map(Reference::get).map(Mutable::get).get(), value.getValue()));
return this;
}
}

View File

@ -26,11 +26,13 @@ public class WeakCacheTest {
@Ignore
public void removeByGcTest(){
// https://gitee.com/dromara/hutool/issues/I51O7M
// 经过GC
WeakCache<String, String> cache = new WeakCache<>(-1);
cache.put("a", "1");
cache.put("b", "2");
// 监听
Assert.assertEquals(2, cache.size());
cache.setListener(Console::log);
// GC测试
int i=0;

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.0.M3</version>
<version>5.8.0.M4</version>
</parent>
<artifactId>hutool-captcha</artifactId>

114
hutool-core/README.md Executable file
View File

@ -0,0 +1,114 @@
<p align="center">
<a href="https://hutool.cn/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/logo.jpg" width="45%"></a>
</p>
<p align="center">
<strong>🍬A set of tools that keep Java sweet.</strong>
</p>
<p align="center">
👉 <a href="https://hutool.cn">https://hutool.cn/</a> 👈
</p>
## 📚Hutool-core 模块介绍
`Hutool-core`提供了最常使用的基础工具类包括集合、Map、IO、线程、Bean、图片处理、线程并发等便捷工具。
-------------------------------------------------------------------------------
## 🛠️包含内容
### 注解(annotation)
提供了注解工具类,以及一些注解封装。如`CombinationAnnotationElement`组合注解以及Alias别名注解等。
### bean(bean)
提供了Bean工具类以及Bean属性解析、Bean拷贝、动态Bean等。
### 构建器(builder)
抽象了Builder接口提供建造者模式的封装并默认提供了包括equals封装、Bean构建封装、比较器封装等。
### 克隆(clone)
提供`Cloneable`接口,明确`clone`方法,并提供默认实现类。
### 编码(codec)
提供了BaseN编码Base16、Base32、Base58、Base62、Base64编码实现。并提供了包括BCD、PunyCode、百分号编码的实现。
同时提供了包括莫尔斯电码、凯撒密码、RotN这类有趣功能的实现。
### 集合(collection)
集合中主要是提供了针对`Iterator`实现类的工具封装方法`IterUtil`和集合类封装的工具类`CollUtil`,并提供了一些特别的集合封装。
### 比较器(comparator)
主要是一些比较器的实现如Bean字段比较器、自定义函数比较器、版本比较器等。
### 动态编译(compiler)
提供`javax.tools.JavaCompiler`的包装简化服务,形成源码动态编译工具类`CompilerUtil`,完成代码动态编译及热部署。
### 压缩(compress)
主要针对`java.util.zip`中的相关类封装工具提供Zip、Gzip、Zlib等格式的压缩解压缩封装`ZipUtil`提供服务。
### 转换(convert)
“万能”转换器,提供整套的类型转换方式。通过`Converter`接口和`ConverterRegistry`转换登记中心,完成任意数据类型转换和自定义转换。
### 日期时间(date)
提供`Date``Calendar``java.time`相关API的工具化封装。包括时间解析、格式化、偏移等。
### 异常(exceptions)
提供异常工具`ExceptionUtil`,以及一些工具内部使用的异常。
### getter接口(getter)
提供各种类型的get操作接口封装。
### 图片(img)
提供图片、绘图、字体等工具封装并提供GIF生成器和解析器实现。
### IO流和文件(io)
提供IO流工具、文件工具、文件类型工具等并提供流拷贝、Checksum、文件监听功能实现。
### 语言特性(lang)
超级大杂项,提供一些设计模式的抽象实现(如单例模式`Singleton`还有正则、Id生成器、函数、Hash算法、可变对象、树形结构、字典等。
### Map(map)
提供Map工具类和各类Map实现封装如行列键的Table实现、自定义键值对转换的Map、线程安全的WeakMap实现等。
### 数学(math)
提供简单数学计算封装,如排列组合、货币类等。
### 网络(net)
提供网络相关工具封装以及Ip地址工具类、SSL工具类、URL编码解码等。
### StreamAPI封装(stream)
提供简单的Stream相关封装。
### Swing和AWT(swing)
提供桌面应用API的工具封装如启动应用、控制键盘鼠标操作、截屏等功能。
### 文本字符串(text)
提供强大的字符串文本封装包括字符转换、字符串查找、字符串替换、字符串切分、Unicode工具等并提供CSV格式封装。
### 线程及并发(thread)
线程并发封装,包括线程工具、锁工具、`CompletableFuture`封装工具、线程池构建等。
### 工具杂项(util)
提供其他不便归类的杂项工具类。如数组、编码、字符、Class、坐标系、身份证、组织机构代码、脱敏、枚举、转义、XML、进制转换、随机数、反射、正则、SPI等各种工具。

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.0.M3</version>
<version>5.8.0.M4</version>
</parent>
<artifactId>hutool-core</artifactId>

View File

@ -16,6 +16,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
/**
* 注解工具类<br>
@ -43,11 +44,74 @@ public class AnnotationUtil {
* 获取指定注解
*
* @param annotationEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @param isToCombination 是否为转换为组合注解
* @param isToCombination 是否为转换为组合注解组合注解可以递归获取注解的注解
* @return 注解对象
*/
public static Annotation[] getAnnotations(AnnotatedElement annotationEle, boolean isToCombination) {
return (null == annotationEle) ? null : (isToCombination ? toCombination(annotationEle) : annotationEle).getAnnotations();
return getAnnotations(annotationEle, isToCombination, (Predicate<Annotation>) null);
}
/**
* 获取组合注解
*
* @param <T> 注解类型
* @param annotationEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @param annotationType 限定的
* @return 注解对象数组
* @since 5.8.0
*/
public static <T> T[] getCombinationAnnotations(AnnotatedElement annotationEle, Class<T> annotationType) {
return getAnnotations(annotationEle, true, annotationType);
}
/**
* 获取指定注解
*
* @param <T> 注解类型
* @param annotationEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @param isToCombination 是否为转换为组合注解组合注解可以递归获取注解的注解
* @param annotationType 限定的
* @return 注解对象数组
* @since 5.8.0
*/
public static <T> T[] getAnnotations(AnnotatedElement annotationEle, boolean isToCombination, Class<T> annotationType) {
final Annotation[] annotations = getAnnotations(annotationEle, isToCombination,
(annotation -> null == annotationType || annotationType.isAssignableFrom(annotation.getClass())));
final T[] result = ArrayUtil.newArray(annotationType, annotations.length);
for (int i = 0; i < annotations.length; i++) {
//noinspection unchecked
result[i] = (T) annotations[i];
}
return result;
}
/**
* 获取指定注解
*
* @param annotationEle {@link AnnotatedElement}可以是ClassMethodFieldConstructorReflectPermission
* @param isToCombination 是否为转换为组合注解组合注解可以递归获取注解的注解
* @param predicate 过滤器{@link Predicate#test(Object)}返回{@code true}保留否则不保留
* @return 注解对象
* @since 5.8.0
*/
public static Annotation[] getAnnotations(AnnotatedElement annotationEle, boolean isToCombination, Predicate<Annotation> predicate) {
if (null == annotationEle) {
return null;
}
if (isToCombination) {
if (null == predicate) {
return toCombination(annotationEle).getAnnotations();
}
return CombinationAnnotationElement.of(annotationEle, predicate).getAnnotations();
}
final Annotation[] result = annotationEle.getAnnotations();
if (null == predicate) {
return result;
}
return ArrayUtil.filter(result, predicate::test);
}
/**

View File

@ -1,6 +1,7 @@
package cn.hutool.core.annotation;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.TableMap;
import java.io.Serializable;
import java.lang.annotation.Annotation;
@ -11,22 +12,36 @@ import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
/**
* 组合注解 对JDK的原生注解机制做一个增强支持类似Spring的组合注解<br>
* 核心实现使用了递归获取指定元素上的注解以及注解的注解以实现复合注解的获取
*
* @author Succy,Looly
* @author Succy, Looly
* @since 4.0.9
**/
public class CombinationAnnotationElement implements AnnotatedElement, Serializable {
private static final long serialVersionUID = 1L;
/** 元注解 */
/**
* 创建CombinationAnnotationElement
*
* @param element 需要解析注解的元素可以是ClassMethodFieldConstructorReflectPermission
* @param predicate 过滤器{@link Predicate#test(Object)}返回{@code true}保留否则不保留
* @return CombinationAnnotationElement
* @since 5.8.0
*/
public static CombinationAnnotationElement of(AnnotatedElement element, Predicate<Annotation> predicate) {
return new CombinationAnnotationElement(element, predicate);
}
/**
* 元注解
*/
private static final Set<Class<? extends Annotation>> META_ANNOTATIONS = CollUtil.newHashSet(Target.class, //
Retention.class, //
Inherited.class, //
@ -36,10 +51,18 @@ public class CombinationAnnotationElement implements AnnotatedElement, Serializa
Deprecated.class//
);
/** 注解类型与注解对象对应表 */
/**
* 注解类型与注解对象对应表
*/
private Map<Class<? extends Annotation>, Annotation> annotationMap;
/** 直接注解类型与注解对象对应表 */
/**
* 直接注解类型与注解对象对应表
*/
private Map<Class<? extends Annotation>, Annotation> declaredAnnotationMap;
/**
* 过滤器
*/
private final Predicate<Annotation> predicate;
/**
* 构造
@ -47,6 +70,18 @@ public class CombinationAnnotationElement implements AnnotatedElement, Serializa
* @param element 需要解析注解的元素可以是ClassMethodFieldConstructorReflectPermission
*/
public CombinationAnnotationElement(AnnotatedElement element) {
this(element, null);
}
/**
* 构造
*
* @param element 需要解析注解的元素可以是ClassMethodFieldConstructorReflectPermission
* @param predicate 过滤器{@link Predicate#test(Object)}返回{@code true}保留否则不保留
* @since 5.8.0
*/
public CombinationAnnotationElement(AnnotatedElement element, Predicate<Annotation> predicate) {
this.predicate = predicate;
init(element);
}
@ -81,14 +116,14 @@ public class CombinationAnnotationElement implements AnnotatedElement, Serializa
*/
private void init(AnnotatedElement element) {
final Annotation[] declaredAnnotations = element.getDeclaredAnnotations();
this.declaredAnnotationMap = new HashMap<>();
this.declaredAnnotationMap = new TableMap<>();
parseDeclared(declaredAnnotations);
final Annotation[] annotations = element.getAnnotations();
if(Arrays.equals(declaredAnnotations, annotations)) {
if (Arrays.equals(declaredAnnotations, annotations)) {
this.annotationMap = this.declaredAnnotationMap;
}else {
this.annotationMap = new HashMap<>();
} else {
this.annotationMap = new TableMap<>();
parse(annotations);
}
}
@ -104,7 +139,10 @@ public class CombinationAnnotationElement implements AnnotatedElement, Serializa
for (Annotation annotation : annotations) {
annotationType = annotation.annotationType();
if (false == META_ANNOTATIONS.contains(annotationType)) {
declaredAnnotationMap.put(annotationType, annotation);
if(test(annotation)){
declaredAnnotationMap.put(annotationType, annotation);
}
// 测试不通过的注解不影响继续递归
parseDeclared(annotationType.getDeclaredAnnotations());
}
}
@ -120,9 +158,22 @@ public class CombinationAnnotationElement implements AnnotatedElement, Serializa
for (Annotation annotation : annotations) {
annotationType = annotation.annotationType();
if (false == META_ANNOTATIONS.contains(annotationType)) {
annotationMap.put(annotationType, annotation);
if(test(annotation)){
annotationMap.put(annotationType, annotation);
}
// 测试不通过的注解不影响继续递归
parse(annotationType.getAnnotations());
}
}
}
}
/**
* 检查给定的注解是否符合过滤条件
*
* @param annotation 注解对象
* @return 是否符合条件
*/
private boolean test(Annotation annotation) {
return null == this.predicate || this.predicate.test(annotation);
}
}

View File

@ -1,7 +1,7 @@
package cn.hutool.core.bean;
import cn.hutool.core.lang.SimpleCache;
import cn.hutool.core.lang.func.Func0;
import cn.hutool.core.map.WeakConcurrentMap;
/**
* Bean属性缓存<br>
@ -12,7 +12,7 @@ import cn.hutool.core.lang.func.Func0;
public enum BeanDescCache {
INSTANCE;
private final SimpleCache<Class<?>, BeanDesc> bdCache = new SimpleCache<>();
private final WeakConcurrentMap<Class<?>, BeanDesc> bdCache = new WeakConcurrentMap<>();
/**
* 获得属性名和{@link BeanDesc}Map映射
@ -23,7 +23,7 @@ public enum BeanDescCache {
* @since 5.4.2
*/
public BeanDesc getBeanDesc(Class<?> beanClass, Func0<BeanDesc> supplier) {
return bdCache.get(beanClass, supplier);
return bdCache.computeIfAbsent(beanClass, (key)->supplier.callWithRuntimeException());
}
/**

View File

@ -1,7 +1,8 @@
package cn.hutool.core.bean;
import cn.hutool.core.lang.SimpleCache;
import cn.hutool.core.lang.func.Func0;
import cn.hutool.core.map.ReferenceConcurrentMap;
import cn.hutool.core.map.WeakConcurrentMap;
import java.beans.PropertyDescriptor;
import java.util.Map;
@ -15,8 +16,8 @@ import java.util.Map;
public enum BeanInfoCache {
INSTANCE;
private final SimpleCache<Class<?>, Map<String, PropertyDescriptor>> pdCache = new SimpleCache<>();
private final SimpleCache<Class<?>, Map<String, PropertyDescriptor>> ignoreCasePdCache = new SimpleCache<>();
private final WeakConcurrentMap<Class<?>, Map<String, PropertyDescriptor>> pdCache = new WeakConcurrentMap<>();
private final WeakConcurrentMap<Class<?>, Map<String, PropertyDescriptor>> ignoreCasePdCache = new WeakConcurrentMap<>();
/**
* 获得属性名和{@link PropertyDescriptor}Map映射
@ -42,7 +43,7 @@ public enum BeanInfoCache {
Class<?> beanClass,
boolean ignoreCase,
Func0<Map<String, PropertyDescriptor>> supplier) {
return getCache(ignoreCase).get(beanClass, supplier);
return getCache(ignoreCase).computeIfAbsent(beanClass, (key)->supplier.callWithRuntimeException());
}
/**
@ -70,10 +71,10 @@ public enum BeanInfoCache {
* 根据是否忽略字段名的大小写返回不用Cache对象
*
* @param ignoreCase 是否忽略大小写
* @return SimpleCache
* @return {@link ReferenceConcurrentMap}
* @since 5.4.1
*/
private SimpleCache<Class<?>, Map<String, PropertyDescriptor>> getCache(boolean ignoreCase) {
private ReferenceConcurrentMap<Class<?>, Map<String, PropertyDescriptor>> getCache(boolean ignoreCase) {
return ignoreCase ? ignoreCasePdCache : pdCache;
}
}

View File

@ -30,6 +30,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
@ -548,10 +549,24 @@ public class BeanUtil {
* @since 5.2.4
*/
public static <T> T toBean(Object source, Class<T> clazz, CopyOptions options) {
if (null == source) {
return toBean(source, () -> ReflectUtil.newInstanceIfPossible(clazz), options);
}
/**
* 对象或Map转Bean
*
* @param <T> 转换的Bean类型
* @param source Bean对象或Map
* @param targetSupplier 目标的Bean创建器
* @param options 属性拷贝选项
* @return Bean对象
* @since 5.8.0
*/
public static <T> T toBean(Object source, Supplier<T> targetSupplier, CopyOptions options) {
if (null == source || null == targetSupplier) {
return null;
}
final T target = ReflectUtil.newInstanceIfPossible(clazz);
final T target = targetSupplier.get();
copyProperties(source, target, options);
return target;
}

View File

@ -0,0 +1,506 @@
package cn.hutool.core.codec;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
/**
* <a href="http://hashids.org/">Hashids</a> 协议实现以实现
* <ul>
* <li>生成简短唯一大小写敏感并无序的hash值</li>
* <li>自然数字的Hash值</li>
* <li>可以设置不同的盐具有保密性</li>
* <li>可配置的hash长度</li>
* <li>递增的输入产生的输出无法预测</li>
* </ul>
*
* <p>
* 来自<a href="https://github.com/davidafsilva/java-hashids">https://github.com/davidafsilva/java-hashids</a>
* </p>
*
* <p>
* {@code Hashids}可以将数字或者16进制字符串转为短且唯一不连续的字符串采用双向编码实现比如它可以将347之类的数字转换为yr8之类的字符串也可以将yr8之类的字符串重新解码为347之类的数字<br>
* 此编码算法主要是解决爬虫类应用对连续ID爬取问题将有序的ID转换为无序的Hashids而且一一对应
* </p>
*
* @author david
*/
public class Hashids implements Encoder<long[], String>, Decoder<String, long[]> {
private static final int LOTTERY_MOD = 100;
private static final double GUARD_THRESHOLD = 12;
private static final double SEPARATOR_THRESHOLD = 3.5;
// 最小编解码字符串
private static final int MIN_ALPHABET_LENGTH = 16;
private static final Pattern HEX_VALUES_PATTERN = Pattern.compile("[\\w\\W]{1,12}");
// 默认编解码字符串
public static final char[] DEFAULT_ALPHABET = {
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'
};
// 默认分隔符
private static final char[] DEFAULT_SEPARATORS = {
'c', 'f', 'h', 'i', 's', 't', 'u', 'C', 'F', 'H', 'I', 'S', 'T', 'U'
};
// algorithm properties
private final char[] alphabet;
// 多个数字编解码的分界符
private final char[] separators;
private final Set<Character> separatorsSet;
private final char[] salt;
// 补齐至 minLength 长度添加的字符列表
private final char[] guards;
// 编码后最小的字符长度
private final int minLength;
// region create
/**
* 根据参数值创建{@code Hashids}使用默认{@link #DEFAULT_ALPHABET}作为字母表不限制最小长度
*
* @param salt 加盐值
* @return {@code Hashids}
*/
public static Hashids create(final char[] salt) {
return create(salt, DEFAULT_ALPHABET, -1);
}
/**
* 根据参数值创建{@code Hashids}使用默认{@link #DEFAULT_ALPHABET}作为字母表
*
* @param salt 加盐值
* @param minLength 限制最小长度-1表示不限制
* @return {@code Hashids}
*/
public static Hashids create(final char[] salt, final int minLength) {
return create(salt, DEFAULT_ALPHABET, minLength);
}
/**
* 根据参数值创建{@code Hashids}
*
* @param salt 加盐值
* @param alphabet hash字母表
* @param minLength 限制最小长度-1表示不限制
* @return {@code Hashids}
*/
public static Hashids create(final char[] salt, final char[] alphabet, final int minLength) {
return new Hashids(salt, alphabet, minLength);
}
// endregion
/**
* 构造
*
* @param salt 加盐值
* @param alphabet hash字母表
* @param minLength 限制最小长度-1表示不限制
*/
public Hashids(final char[] salt, final char[] alphabet, final int minLength) {
this.minLength = minLength;
this.salt = Arrays.copyOf(salt, salt.length);
// filter and shuffle separators
char[] tmpSeparators = shuffle(filterSeparators(DEFAULT_SEPARATORS, alphabet), this.salt);
// validate and filter the alphabet
char[] tmpAlphabet = validateAndFilterAlphabet(alphabet, tmpSeparators);
// check separator threshold
if (tmpSeparators.length == 0 ||
((double) (tmpAlphabet.length / tmpSeparators.length)) > SEPARATOR_THRESHOLD) {
final int minSeparatorsSize = (int) Math.ceil(tmpAlphabet.length / SEPARATOR_THRESHOLD);
// check minimum size of separators
if (minSeparatorsSize > tmpSeparators.length) {
// fill separators from alphabet
final int missingSeparators = minSeparatorsSize - tmpSeparators.length;
tmpSeparators = Arrays.copyOf(tmpSeparators, tmpSeparators.length + missingSeparators);
System.arraycopy(tmpAlphabet, 0, tmpSeparators,
tmpSeparators.length - missingSeparators, missingSeparators);
System.arraycopy(tmpAlphabet, 0, tmpSeparators,
tmpSeparators.length - missingSeparators, missingSeparators);
tmpAlphabet = Arrays.copyOfRange(tmpAlphabet, missingSeparators, tmpAlphabet.length);
}
}
// shuffle the current alphabet
shuffle(tmpAlphabet, this.salt);
// check guards
this.guards = new char[(int) Math.ceil(tmpAlphabet.length / GUARD_THRESHOLD)];
if (alphabet.length < 3) {
System.arraycopy(tmpSeparators, 0, guards, 0, guards.length);
this.separators = Arrays.copyOfRange(tmpSeparators, guards.length, tmpSeparators.length);
this.alphabet = tmpAlphabet;
} else {
System.arraycopy(tmpAlphabet, 0, guards, 0, guards.length);
this.separators = tmpSeparators;
this.alphabet = Arrays.copyOfRange(tmpAlphabet, guards.length, tmpAlphabet.length);
}
// create the separators set
separatorsSet = IntStream.range(0, separators.length)
.mapToObj(idx -> separators[idx])
.collect(Collectors.toSet());
}
/**
* 编码给定的16进制数字
*
* @param hexNumbers 16进制数字
* @return 编码后的值, {@code null} if {@code numbers} {@code null}.
* @throws IllegalArgumentException 数字不支持抛出此异常
*/
public String encodeFromHex(final String hexNumbers) {
if (hexNumbers == null) {
return null;
}
// remove the prefix, if present
final String hex = hexNumbers.startsWith("0x") || hexNumbers.startsWith("0X") ?
hexNumbers.substring(2) : hexNumbers;
// get the associated long value and encode it
LongStream values = LongStream.empty();
final Matcher matcher = HEX_VALUES_PATTERN.matcher(hex);
while (matcher.find()) {
final long value = new BigInteger("1" + matcher.group(), 16).longValue();
values = LongStream.concat(values, LongStream.of(value));
}
return encode(values.toArray());
}
/**
* 编码给定的数字数组
*
* @param numbers 数字数组
* @return 编码后的值, {@code null} if {@code numbers} {@code null}.
* @throws IllegalArgumentException 数字不支持抛出此异常
*/
@Override
public String encode(final long... numbers) {
if (numbers == null) {
return null;
}
// copy alphabet
final char[] currentAlphabet = Arrays.copyOf(alphabet, alphabet.length);
// determine the lottery number
final long lotteryId = LongStream.range(0, numbers.length)
.reduce(0, (state, i) -> {
final long number = numbers[(int) i];
if (number < 0) {
throw new IllegalArgumentException("invalid number: " + number);
}
return state + number % (i + LOTTERY_MOD);
});
final char lottery = currentAlphabet[(int) (lotteryId % currentAlphabet.length)];
// encode each number
final StringBuilder global = new StringBuilder();
IntStream.range(0, numbers.length)
.forEach(idx -> {
// derive alphabet
deriveNewAlphabet(currentAlphabet, salt, lottery);
// encode
final int initialLength = global.length();
translate(numbers[idx], currentAlphabet, global, initialLength);
// prepend the lottery
if (idx == 0) {
global.insert(0, lottery);
}
// append the separator, if more numbers are pending encoding
if (idx + 1 < numbers.length) {
long n = numbers[idx] % (global.charAt(initialLength) + 1);
global.append(separators[(int) (n % separators.length)]);
}
});
// add the guards, if there's any space left
if (minLength > global.length()) {
int guardIdx = (int) ((lotteryId + lottery) % guards.length);
global.insert(0, guards[guardIdx]);
if (minLength > global.length()) {
guardIdx = (int) ((lotteryId + global.charAt(2)) % guards.length);
global.append(guards[guardIdx]);
}
}
// add the necessary padding
int paddingLeft = minLength - global.length();
while (paddingLeft > 0) {
shuffle(currentAlphabet, Arrays.copyOf(currentAlphabet, currentAlphabet.length));
final int alphabetHalfSize = currentAlphabet.length / 2;
final int initialSize = global.length();
if (paddingLeft > currentAlphabet.length) {
// entire alphabet with the current encoding in the middle of it
int offset = alphabetHalfSize + (currentAlphabet.length % 2 == 0 ? 0 : 1);
global.insert(0, currentAlphabet, alphabetHalfSize, offset);
global.insert(offset + initialSize, currentAlphabet, 0, alphabetHalfSize);
// decrease the padding left
paddingLeft -= currentAlphabet.length;
} else {
// calculate the excess
final int excess = currentAlphabet.length + global.length() - minLength;
final int secondHalfStartOffset = alphabetHalfSize + Math.floorDiv(excess, 2);
final int secondHalfLength = currentAlphabet.length - secondHalfStartOffset;
final int firstHalfLength = paddingLeft - secondHalfLength;
global.insert(0, currentAlphabet, secondHalfStartOffset, secondHalfLength);
global.insert(secondHalfLength + initialSize, currentAlphabet, 0, firstHalfLength);
paddingLeft = 0;
}
}
return global.toString();
}
//-------------------------
// Decode
//-------------------------
/**
* 解码Hash值为16进制数字
*
* @param hash hash值
* @return 解码后的16进制值, {@code null} if {@code numbers} {@code null}.
* @throws IllegalArgumentException if the hash is invalid.
*/
public String decodeToHex(final String hash) {
if (hash == null) {
return null;
}
final StringBuilder sb = new StringBuilder();
Arrays.stream(decode(hash))
.mapToObj(Long::toHexString)
.forEach(hex -> sb.append(hex, 1, hex.length()));
return sb.toString();
}
/**
* 解码Hash值为数字数组
*
* @param hash hash值
* @return 解码后的16进制值, {@code null} if {@code numbers} {@code null}.
* @throws IllegalArgumentException if the hash is invalid.
*/
@Override
public long[] decode(final String hash) {
if (hash == null) {
return null;
}
// create a set of the guards
final Set<Character> guardsSet = IntStream.range(0, guards.length)
.mapToObj(idx -> guards[idx])
.collect(Collectors.toSet());
// count the total guards used
final int[] guardsIdx = IntStream.range(0, hash.length())
.filter(idx -> guardsSet.contains(hash.charAt(idx)))
.toArray();
// get the start/end index base on the guards count
final int startIdx, endIdx;
if (guardsIdx.length > 0) {
startIdx = guardsIdx[0] + 1;
endIdx = guardsIdx.length > 1 ? guardsIdx[1] : hash.length();
} else {
startIdx = 0;
endIdx = hash.length();
}
LongStream decoded = LongStream.empty();
// parse the hash
if (hash.length() > 0) {
final char lottery = hash.charAt(startIdx);
// create the initial accumulation string
final int length = hash.length() - guardsIdx.length - 1;
StringBuilder block = new StringBuilder(length);
// create the base salt
final char[] decodeSalt = new char[alphabet.length];
decodeSalt[0] = lottery;
final int saltLength = salt.length >= alphabet.length ? alphabet.length - 1 : salt.length;
System.arraycopy(salt, 0, decodeSalt, 1, saltLength);
final int saltLeft = alphabet.length - saltLength - 1;
// copy alphabet
final char[] currentAlphabet = Arrays.copyOf(alphabet, alphabet.length);
for (int i = startIdx + 1; i < endIdx; i++) {
if (false == separatorsSet.contains(hash.charAt(i))) {
block.append(hash.charAt(i));
// continue if we have not reached the end, yet
if (i < endIdx - 1) {
continue;
}
}
if (block.length() > 0) {
// create the salt
if (saltLeft > 0) {
System.arraycopy(currentAlphabet, 0, decodeSalt,
alphabet.length - saltLeft, saltLeft);
}
// shuffle the alphabet
shuffle(currentAlphabet, decodeSalt);
// prepend the decoded value
final long n = translate(block.toString().toCharArray(), currentAlphabet);
decoded = LongStream.concat(decoded, LongStream.of(n));
// create a new block
block = new StringBuilder(length);
}
}
}
// validate the hash
final long[] decodedValue = decoded.toArray();
if (!Objects.equals(hash, encode(decodedValue))) {
throw new IllegalArgumentException("invalid hash: " + hash);
}
return decodedValue;
}
private StringBuilder translate(final long n, final char[] alphabet,
final StringBuilder sb, final int start) {
long input = n;
do {
// prepend the chosen char
sb.insert(start, alphabet[(int) (input % alphabet.length)]);
// trim the input
input = input / alphabet.length;
} while (input > 0);
return sb;
}
private long translate(final char[] hash, final char[] alphabet) {
long number = 0;
final Map<Character, Integer> alphabetMapping = IntStream.range(0, alphabet.length)
.mapToObj(idx -> new Object[]{alphabet[idx], idx})
.collect(Collectors.groupingBy(arr -> (Character) arr[0],
Collectors.mapping(arr -> (Integer) arr[1],
Collectors.reducing(null, (a, b) -> a == null ? b : a))));
for (int i = 0; i < hash.length; ++i) {
number += alphabetMapping.computeIfAbsent(hash[i], k -> {
throw new IllegalArgumentException("Invalid alphabet for hash");
}) * (long) Math.pow(alphabet.length, hash.length - i - 1);
}
return number;
}
private char[] deriveNewAlphabet(final char[] alphabet, final char[] salt, final char lottery) {
// create the new salt
final char[] newSalt = new char[alphabet.length];
// 1. lottery
newSalt[0] = lottery;
int spaceLeft = newSalt.length - 1;
int offset = 1;
// 2. salt
if (salt.length > 0 && spaceLeft > 0) {
int length = Math.min(salt.length, spaceLeft);
System.arraycopy(salt, 0, newSalt, offset, length);
spaceLeft -= length;
offset += length;
}
// 3. alphabet
if (spaceLeft > 0) {
System.arraycopy(alphabet, 0, newSalt, offset, spaceLeft);
}
// shuffle
return shuffle(alphabet, newSalt);
}
private char[] validateAndFilterAlphabet(final char[] alphabet, final char[] separators) {
// validate size
if (alphabet.length < MIN_ALPHABET_LENGTH) {
throw new IllegalArgumentException(String.format("alphabet must contain at least %d unique " +
"characters: %d", MIN_ALPHABET_LENGTH, alphabet.length));
}
final Set<Character> seen = new LinkedHashSet<>(alphabet.length);
final Set<Character> invalid = IntStream.range(0, separators.length)
.mapToObj(idx -> separators[idx])
.collect(Collectors.toSet());
// add to seen set (without duplicates)
IntStream.range(0, alphabet.length)
.forEach(i -> {
if (alphabet[i] == ' ') {
throw new IllegalArgumentException(String.format("alphabet must not contain spaces: " +
"index %d", i));
}
final Character c = alphabet[i];
if (!invalid.contains(c)) {
seen.add(c);
}
});
// create a new alphabet without the duplicates
final char[] uniqueAlphabet = new char[seen.size()];
int idx = 0;
for (char c : seen) {
uniqueAlphabet[idx++] = c;
}
return uniqueAlphabet;
}
@SuppressWarnings("SameParameterValue")
private char[] filterSeparators(final char[] separators, final char[] alphabet) {
final Set<Character> valid = IntStream.range(0, alphabet.length)
.mapToObj(idx -> alphabet[idx])
.collect(Collectors.toSet());
return IntStream.range(0, separators.length)
.mapToObj(idx -> (separators[idx]))
.filter(valid::contains)
// ugly way to convert back to char[]
.map(c -> Character.toString(c))
.collect(Collectors.joining())
.toCharArray();
}
private char[] shuffle(final char[] alphabet, final char[] salt) {
for (int i = alphabet.length - 1, v = 0, p = 0, j, z; salt.length > 0 && i > 0; i--, v++) {
v %= salt.length;
p += z = salt[v];
j = (z + v + p) % i;
final char tmp = alphabet[j];
alphabet[j] = alphabet[i];
alphabet[i] = tmp;
}
return alphabet;
}
}

View File

@ -1051,6 +1051,31 @@ public class CollUtil {
}
}
/**
* 根据函数生成的KEY去重集合如根据Bean的某个或者某些字段完成去重<br>
* 去重可选是保留最先加入的值还是后加入的值
*
* @param <T> 集合元素类型
* @param <K> 唯一键类型
* @param collection 集合
* @param override 是否覆盖模式如果为{@code true}加入的新值会覆盖相同key的旧值否则会忽略新加值
* @return {@link ArrayList}
* @since 5.8.0
*/
public static <T, K> List<T> distinct(Collection<T> collection, Function<T, K> uniqueGenerator, boolean override) {
if (isEmpty(collection)) {
return new ArrayList<>();
}
final UniqueKeySet<K, T> set = new UniqueKeySet<>(true, uniqueGenerator);
if (override) {
set.addAll(collection);
} else {
set.addAllIfAbsent(collection);
}
return new ArrayList<>(set);
}
/**
* 截取列表的部分
*

View File

@ -42,6 +42,17 @@ public class UniqueKeySet<K, V> extends AbstractSet<V> implements Serializable {
this(false, uniqueGenerator);
}
/**
* 构造
*
* @param uniqueGenerator 唯一键生成规则函数用于生成对象对应的唯一键
* @param c 初始化加入的集合
* @since 5.8.0
*/
public UniqueKeySet(Function<V, K> uniqueGenerator, Collection<? extends V> c) {
this(false, uniqueGenerator, c);
}
/**
* 构造
*
@ -52,6 +63,19 @@ public class UniqueKeySet<K, V> extends AbstractSet<V> implements Serializable {
this(MapBuilder.create(isLinked), uniqueGenerator);
}
/**
* 构造
*
* @param isLinked 是否保持加入顺序
* @param uniqueGenerator 唯一键生成规则函数用于生成对象对应的唯一键
* @param c 初始化加入的集合
* @since 5.8.0
*/
public UniqueKeySet(boolean isLinked, Function<V, K> uniqueGenerator, Collection<? extends V> c) {
this(isLinked, uniqueGenerator);
addAll(c);
}
/**
* 构造
*
@ -73,6 +97,7 @@ public class UniqueKeySet<K, V> extends AbstractSet<V> implements Serializable {
this.map = builder.build();
this.uniqueGenerator = uniqueGenerator;
}
//endregion
@Override

View File

@ -3,8 +3,8 @@ package cn.hutool.core.convert.impl;
import cn.hutool.core.convert.AbstractConverter;
import cn.hutool.core.convert.ConvertException;
import cn.hutool.core.lang.EnumItem;
import cn.hutool.core.lang.SimpleCache;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.map.WeakConcurrentMap;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.ModifierUtil;
@ -25,7 +25,7 @@ import java.util.stream.Collectors;
public class EnumConverter extends AbstractConverter<Object> {
private static final long serialVersionUID = 1L;
private static final SimpleCache<Class<?>, Map<Class<?>, Method>> VALUE_OF_METHOD_CACHE = new SimpleCache<>();
private static final WeakConcurrentMap<Class<?>, Map<Class<?>, Method>> VALUE_OF_METHOD_CACHE = new WeakConcurrentMap<>();
private final Class enumClass;
@ -132,7 +132,7 @@ public class EnumConverter extends AbstractConverter<Object> {
* @return 转换方法mapkey为方法参数类型value为方法
*/
private static Map<Class<?>, Method> getMethodMap(Class<?> enumClass) {
return VALUE_OF_METHOD_CACHE.get(enumClass, () -> Arrays.stream(enumClass.getMethods())
return VALUE_OF_METHOD_CACHE.computeIfAbsent(enumClass, (key) -> Arrays.stream(enumClass.getMethods())
.filter(ModifierUtil::isStatic)
.filter(m -> m.getReturnType() == enumClass)
.filter(m -> m.getParameterCount() == 1)

View File

@ -1813,7 +1813,7 @@ public class DateUtil extends CalendarUtil {
/**
* HH:mm:ss 时间格式字符串转为秒数<br>
* 参考https://github.com/iceroot
* 参考<a href="https://github.com/iceroot">https://github.com/iceroot</a>
*
* @param timeStr 字符串时分秒(HH:mm:ss)格式
* @return 时分秒转换后的秒数
@ -1836,7 +1836,7 @@ public class DateUtil extends CalendarUtil {
/**
* 秒数转为时间格式(HH:mm:ss)<br>
* 参考https://github.com/iceroot
* 参考<a href="https://github.com/iceroot">https://github.com/iceroot</a>
*
* @param seconds 需要转换的秒数
* @return 转换后的字符串

View File

@ -74,7 +74,7 @@ public enum Week {
/**
* 获得星期对应{@link Calendar} 中的Week值
*
* @return 星期对应{@link Calendar} 中的Week值
* @return 星期对应 {@link Calendar} 中的Week值
*/
public int getValue() {
return this.value;

View File

@ -3246,7 +3246,7 @@ public class FileUtil extends PathUtil {
/**
* 可读的文件大小<br>
* 参考 http://stackoverflow.com/questions/3263892/format-file-size-as-mb-gb-etc
* 参考 <a href="http://stackoverflow.com/questions/3263892/format-file-size-as-mb-gb-etc">http://stackoverflow.com/questions/3263892/format-file-size-as-mb-gb-etc</a>
*
* @param size Long类型大小
* @return 大小

View File

@ -153,7 +153,7 @@ public class Tailer implements Serializable {
Stack<String> stack = new Stack<>();
long start = this.randomAccessFile.getFilePointer();
long nextEnd = len - 1;
long nextEnd = (len - 1) < 0 ? 0 : len - 1;
this.randomAccessFile.seek(nextEnd);
int c;
int currentLine = 0;

View File

@ -1,5 +1,7 @@
package cn.hutool.core.lang;
import cn.hutool.core.map.WeakConcurrentMap;
import java.util.regex.Pattern;
/**
@ -174,7 +176,7 @@ public class PatternPool {
/**
* Pattern池
*/
private static final SimpleCache<RegexWithFlag, Pattern> POOL = new SimpleCache<>();
private static final WeakConcurrentMap<RegexWithFlag, Pattern> POOL = new WeakConcurrentMap<>();
/**
* 先从Pattern池中查找正则对应的{@link Pattern}找不到则编译正则表达式并入池
@ -195,7 +197,7 @@ public class PatternPool {
*/
public static Pattern get(String regex, int flags) {
final RegexWithFlag regexWithFlag = new RegexWithFlag(regex, flags);
return POOL.get(regexWithFlag, ()-> Pattern.compile(regex, flags));
return POOL.computeIfAbsent(regexWithFlag, (key)-> Pattern.compile(regex, flags));
}
/**

View File

@ -200,6 +200,7 @@ public interface RegexPool {
* ----------
* </pre>
* 总结中文姓名2-60位只能是中文和维吾尔族的点·
* 放宽汉字范围如生僻姓名 刘欣䶮yǎn
*/
String CHINESE_NAME = "^[\u4E00-\u9FFF·]{2,60}$";
String CHINESE_NAME = "^[\u2E80-\u9FFF·]{2,60}$";
}

View File

@ -4,6 +4,7 @@ import cn.hutool.core.collection.TransIter;
import cn.hutool.core.lang.func.Func0;
import cn.hutool.core.lang.mutable.Mutable;
import cn.hutool.core.lang.mutable.MutableObj;
import cn.hutool.core.map.WeakConcurrentMap;
import java.io.Serializable;
import java.util.Iterator;
@ -11,12 +12,13 @@ import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Predicate;
/**
* 简单缓存无超时实现默认使用{@link WeakHashMap}实现缓存自动清理
* 简单缓存无超时实现默认使用{@link WeakConcurrentMap}实现缓存自动清理
*
* @param <K> 键类型
* @param <V> 值类型
@ -30,7 +32,7 @@ public class SimpleCache<K, V> implements Iterable<Map.Entry<K, V>>, Serializabl
*/
private final Map<Mutable<K>, V> rawMap;
// 乐观读写锁
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
/**
* 写的时候每个key一把锁降低锁的粒度
*/
@ -40,7 +42,7 @@ public class SimpleCache<K, V> implements Iterable<Map.Entry<K, V>>, Serializabl
* 构造默认使用{@link WeakHashMap}实现缓存自动清理
*/
public SimpleCache() {
this(new WeakHashMap<>());
this(new WeakConcurrentMap<>());
}
/**
@ -94,6 +96,9 @@ public class SimpleCache<K, V> implements Iterable<Map.Entry<K, V>>, Serializabl
*/
public V get(K key, Predicate<V> validPredicate, Func0<V> supplier) {
V v = get(key);
if((null != validPredicate && false == validPredicate.test(v))){
v = null;
}
if (null == v && null != supplier) {
//每个key单独获取一把锁降低锁的粒度提高并发能力see pr#1385@Github
final Lock keyLock = keyLockMap.computeIfAbsent(key, k -> new ReentrantLock());

View File

@ -6,7 +6,7 @@ import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
/**
* 单例类<br>
@ -16,7 +16,7 @@ import java.util.HashMap;
*/
public final class Singleton {
private static final SimpleCache<String, Object> POOL = new SimpleCache<>(new HashMap<>());
private static final ConcurrentHashMap<String, Object> POOL = new ConcurrentHashMap<>();
private Singleton() {
}
@ -50,7 +50,7 @@ public final class Singleton {
*/
@SuppressWarnings("unchecked")
public static <T> T get(String key, Func0<T> supplier) {
return (T) POOL.get(key, supplier::call);
return (T) POOL.computeIfAbsent(key, (k)-> supplier.callWithRuntimeException());
}
/**

View File

@ -0,0 +1,83 @@
package cn.hutool.core.lang.ansi;
import cn.hutool.core.lang.Assert;
/**
* ANSI 8-bit前景或背景色即8位编码共256种颜色2^8 <br>
* <ul>
* <li>0-7 标准颜色同ESC [ 3037 m</li>
* <li>8-15 高强度颜色同ESC [ 9097 m</li>
* <li>16-2316 × 6 × 6 216色 16 + 36 × r + 6 × g + b (0 r, g, b 5)</li>
* <li>232-255 从黑到白的24阶灰度色</li>
* </ul>
*
* <p>来自Spring Boot</p>
*
* @author Toshiaki Maki, Phillip Webb
* @see #foreground(int)
* @see #background(int)
* @since 5.8.0
*/
public final class Ansi8BitColor implements AnsiElement {
private static final String PREFIX_FORE = "38;5;";
private static final String PREFIX_BACK = "48;5;";
/**
* 前景色ANSI颜色实例
*
* @param code 颜色代码(0-255)
* @return 前景色ANSI颜色实例
*/
public static Ansi8BitColor foreground(int code) {
return new Ansi8BitColor(PREFIX_FORE, code);
}
/**
* 背景色ANSI颜色实例
*
* @param code 颜色代码(0-255)
* @return 背景色ANSI颜色实例
*/
public static Ansi8BitColor background(int code) {
return new Ansi8BitColor(PREFIX_BACK, code);
}
private final String prefix;
private final int code;
/**
* 构造
*
* @param prefix 前缀
* @param code 颜色代码(0-255)
* @throws IllegalArgumentException 颜色代码不在0~255范围内
*/
private Ansi8BitColor(String prefix, int code) {
Assert.isTrue(code >= 0 && code <= 255, "Code must be between 0 and 255");
this.prefix = prefix;
this.code = code;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Ansi8BitColor other = (Ansi8BitColor) obj;
return this.prefix.equals(other.prefix) && this.code == other.code;
}
@Override
public int hashCode() {
return this.prefix.hashCode() * 31 + this.code;
}
@Override
public String toString() {
return this.prefix + this.code;
}
}

View File

@ -0,0 +1,109 @@
package cn.hutool.core.lang.ansi;
/**
* ANSI背景颜色枚举
*
* <p>来自Spring Boot</p>
*
* @author Phillip Webb, Geoffrey Chandler
* @since 5.8.0
*/
public enum AnsiBackground implements AnsiElement {
/**
* 默认背景色
*/
DEFAULT("49"),
/**
* 黑色
*/
BLACK("40"),
/**
*
*/
RED("41"),
/**
* 绿
*/
GREEN("42"),
/**
*
*/
YELLOW("43"),
/**
*
*/
BLUE("44"),
/**
* 品红
*/
MAGENTA("45"),
/**
*
*/
CYAN("46"),
/**
*
*/
WHITE("47"),
/**
* 亮黑
*/
BRIGHT_BLACK("100"),
/**
* 亮红
*/
BRIGHT_RED("101"),
/**
* 亮绿
*/
BRIGHT_GREEN("102"),
/**
* 亮黄
*/
BRIGHT_YELLOW("103"),
/**
* 亮蓝
*/
BRIGHT_BLUE("104"),
/**
* 亮品红
*/
BRIGHT_MAGENTA("105"),
/**
* 亮青
*/
BRIGHT_CYAN("106"),
/**
* 亮白
*/
BRIGHT_WHITE("107");
private final String code;
AnsiBackground(String code) {
this.code = code;
}
@Override
public String toString() {
return this.code;
}
}

View File

@ -0,0 +1,109 @@
package cn.hutool.core.lang.ansi;
/**
* ANSI标准颜色
*
* <p>来自Spring Boot</p>
*
* @author Phillip Webb, Geoffrey Chandler
* @since 5.8.0
*/
public enum AnsiColor implements AnsiElement {
/**
* 默认前景色
*/
DEFAULT("39"),
/**
*
*/
BLACK("30"),
/**
*
*/
RED("31"),
/**
* 绿
*/
GREEN("32"),
/**
*
*/
YELLOW("33"),
/**
*
*/
BLUE("34"),
/**
* 品红
*/
MAGENTA("35"),
/**
*
*/
CYAN("36"),
/**
*
*/
WHITE("37"),
/**
* 亮黑
*/
BRIGHT_BLACK("90"),
/**
* 亮红
*/
BRIGHT_RED("91"),
/**
* 亮绿
*/
BRIGHT_GREEN("92"),
/**
* 亮黄
*/
BRIGHT_YELLOW("93"),
/**
* 亮蓝
*/
BRIGHT_BLUE("94"),
/**
* 亮品红
*/
BRIGHT_MAGENTA("95"),
/**
* 亮青
*/
BRIGHT_CYAN("96"),
/**
* 亮白
*/
BRIGHT_WHITE("97");
private final String code;
AnsiColor(String code) {
this.code = code;
}
@Override
public String toString() {
return this.code;
}
}

View File

@ -0,0 +1,18 @@
package cn.hutool.core.lang.ansi;
/**
* ANSI可转义节点接口实现为ANSI颜色等
*
* <p>来自Spring Boot</p>
*
* @author Phillip Webb
*/
public interface AnsiElement {
/**
* @return ANSI转义编码
*/
@Override
String toString();
}

View File

@ -0,0 +1,65 @@
package cn.hutool.core.lang.ansi;
/**
* 生成ANSI格式的编码输出
*
* @author Phillip Webb
* @since 1.0.0
*/
public abstract class AnsiEncoder {
private static final String ENCODE_JOIN = ";";
private static final String ENCODE_START = "\033[";
private static final String ENCODE_END = "m";
private static final String RESET = "0;" + AnsiColor.DEFAULT;
/**
* 创建ANSI字符串参数中的{@link AnsiElement}会被转换为编码形式
*
* @param elements 节点数组
* @return ANSI字符串
*/
public static String encode(Object... elements) {
final StringBuilder sb = new StringBuilder();
buildEnabled(sb, elements);
return sb.toString();
}
/**
* 追加需要需转义的节点
*
* @param sb {@link StringBuilder}
* @param elements 节点列表
*/
private static void buildEnabled(StringBuilder sb, Object[] elements) {
boolean writingAnsi = false;
boolean containsEncoding = false;
for (Object element : elements) {
if (null == element) {
continue;
}
if (element instanceof AnsiElement) {
containsEncoding = true;
if (writingAnsi) {
sb.append(ENCODE_JOIN);
} else {
sb.append(ENCODE_START);
writingAnsi = true;
}
} else {
if (writingAnsi) {
sb.append(ENCODE_END);
writingAnsi = false;
}
}
sb.append(element);
}
// 恢复默认
if (containsEncoding) {
sb.append(writingAnsi ? ENCODE_JOIN : ENCODE_START);
sb.append(RESET);
sb.append(ENCODE_END);
}
}
}

View File

@ -0,0 +1,49 @@
package cn.hutool.core.lang.ansi;
/**
* ANSI文本样式风格枚举
*
* <p>来自Spring Boot</p>
*
* @author Phillip Webb
* @since 5.8.0
*/
public enum AnsiStyle implements AnsiElement {
/**
* 重置/正常
*/
NORMAL("0"),
/**
* 粗体或增加强度
*/
BOLD("1"),
/**
* 弱化降低强度
*/
FAINT("2"),
/**
* 斜体
*/
ITALIC("3"),
/**
* 下划线
*/
UNDERLINE("4");
private final String code;
AnsiStyle(String code) {
this.code = code;
}
@Override
public String toString() {
return this.code;
}
}

View File

@ -0,0 +1,6 @@
/**
* 命令行终端中ANSI 转义序列相关封装如ANSI颜色等
*
* @author spring, looly
*/
package cn.hutool.core.lang.ansi;

View File

@ -35,13 +35,13 @@ public class CallerUtil {
* 调用者层级关系
*
* <pre>
* 0 {@link CallerUtil}
* 1 调用{@link CallerUtil}中方法的类
* 0 CallerUtil
* 1 调用CallerUtil中方法的类
* 2 调用者的调用者
* ...
* </pre>
*
* @param depth 层级0表示{@link CallerUtil}本身1表示调用{@link CallerUtil}的类2表示调用者的调用者依次类推
* @param depth 层级0表示CallerUtil本身1表示调用CallerUtil的类2表示调用者的调用者依次类推
* @return 第几级调用者
*/
public static Class<?> getCaller(int depth) {

View File

@ -1,7 +1,7 @@
package cn.hutool.core.lang.func;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.SimpleCache;
import cn.hutool.core.map.WeakConcurrentMap;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
@ -18,7 +18,7 @@ import java.lang.invoke.SerializedLambda;
*/
public class LambdaUtil {
private static final SimpleCache<String, SerializedLambda> cache = new SimpleCache<>();
private static final WeakConcurrentMap<String, SerializedLambda> cache = new WeakConcurrentMap<>();
/**
* 通过对象的方法或类的静态方法引用获取lambda实现类
@ -202,7 +202,7 @@ public class LambdaUtil {
* @return 返回解析后的结果
*/
private static SerializedLambda _resolve(Serializable func) {
return cache.get(func.getClass().getName(), () -> ReflectUtil.invoke(func, "writeReplace"));
return cache.computeIfAbsent(func.getClass().getName(), (key) -> ReflectUtil.invoke(func, "writeReplace"));
}
//endregion
}

View File

@ -1,6 +1,6 @@
package cn.hutool.core.lang.intern;
import cn.hutool.core.lang.SimpleCache;
import cn.hutool.core.map.WeakConcurrentMap;
/**
* 使用WeakHashMap(线程安全)存储对象的规范化对象注意此对象需单例使用<br>
@ -10,13 +10,13 @@ import cn.hutool.core.lang.SimpleCache;
*/
public class WeakInterner<T> implements Interner<T>{
private final SimpleCache<T, T> cache = new SimpleCache<>();
private final WeakConcurrentMap<T, T> cache = new WeakConcurrentMap<>();
@Override
public T intern(T sample) {
if(null == sample){
return null;
}
return cache.get(sample, ()->sample);
return cache.computeIfAbsent(sample, (key)->sample);
}
}

View File

@ -1,7 +1,7 @@
package cn.hutool.core.lang.reflect;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.SimpleCache;
import cn.hutool.core.map.WeakConcurrentMap;
import cn.hutool.core.util.TypeUtil;
import java.lang.reflect.ParameterizedType;
@ -18,7 +18,7 @@ import java.util.Map;
*/
public class ActualTypeMapperPool {
private static final SimpleCache<Type, Map<Type, Type>> CACHE = new SimpleCache<>();
private static final WeakConcurrentMap<Type, Map<Type, Type>> CACHE = new WeakConcurrentMap<>();
/**
* 获取泛型变量和泛型实际类型的对应关系Map
@ -27,7 +27,7 @@ public class ActualTypeMapperPool {
* @return 泛型对应关系Map
*/
public static Map<Type, Type> get(Type type) {
return CACHE.get(type, () -> createTypeMap(type));
return CACHE.computeIfAbsent(type, (key) -> createTypeMap(type));
}
/**

View File

@ -13,9 +13,7 @@ import java.lang.reflect.Method;
* 时会出现权限不够问题抛出"no private access for invokespecial"异常因此针对JDK8及JDK9+分别封装lookup方法
*
* 参考
* <ul>
* <li>https://blog.csdn.net/u013202238/article/details/108687086</li>
* </ul>
* <p><a href="https://blog.csdn.net/u013202238/article/details/108687086">https://blog.csdn.net/u013202238/article/details/108687086</a></p>
*
* @author looly
* @since 5.7.7

View File

@ -1,7 +1,6 @@
package cn.hutool.core.map;
import java.util.Map;
import java.util.function.BiFunction;
/**
* 自定义键的Map默认HashMap实现
@ -11,7 +10,7 @@ import java.util.function.BiFunction;
* @author Looly
* @since 4.0.7
*/
public abstract class CustomKeyMap<K, V> extends MapWrapper<K, V> {
public abstract class CustomKeyMap<K, V> extends TransMap<K, V> {
private static final long serialVersionUID = 4043263744224569870L;
/**
@ -26,78 +25,8 @@ public abstract class CustomKeyMap<K, V> extends MapWrapper<K, V> {
}
@Override
public V get(Object key) {
return super.get(customKey(key));
}
@SuppressWarnings("unchecked")
@Override
public V put(K key, V value) {
return super.put((K) customKey(key), value);
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
m.forEach(this::put);
}
@Override
public boolean containsKey(Object key) {
return super.containsKey(customKey(key));
}
@Override
public V remove(Object key) {
return super.remove(customKey(key));
}
@Override
public boolean remove(Object key, Object value) {
return super.remove(customKey(key), value);
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
protected V customValue(Object value) {
//noinspection unchecked
return super.replace((K) customKey(key), oldValue, newValue);
return (V)value;
}
@Override
public V replace(K key, V value) {
//noinspection unchecked
return super.replace((K) customKey(key), value);
}
//---------------------------------------------------------------------------- Override default methods start
@Override
public V getOrDefault(Object key, V defaultValue) {
return super.getOrDefault(customKey(key), defaultValue);
}
@Override
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
//noinspection unchecked
return super.computeIfPresent((K) customKey(key), remappingFunction);
}
@Override
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
//noinspection unchecked
return super.compute((K) customKey(key), remappingFunction);
}
@Override
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
//noinspection unchecked
return super.merge((K) customKey(key), value, remappingFunction);
}
//---------------------------------------------------------------------------- Override default methods end
/**
* 自定义键
*
* @param key KEY
* @return 自定义KEY
*/
protected abstract Object customKey(Object key);
}

View File

@ -38,10 +38,11 @@ public class FuncKeyMap<K, V> extends CustomKeyMap<K, V> {
* @return 驼峰Key
*/
@Override
protected Object customKey(Object key) {
protected K customKey(Object key) {
if (null != this.keyFunc) {
return keyFunc.apply(key);
}
return key;
//noinspection unchecked
return (K)key;
}
}

View File

@ -0,0 +1,73 @@
package cn.hutool.core.map;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* 自定义键值函数风格的Map
*
* @param <K> 键类型
* @param <V> 值类型
* @author Looly
* @since 5.8.0
*/
public class FuncMap<K, V> extends TransMap<K, V> {
private static final long serialVersionUID = 1L;
private final Function<Object, K> keyFunc;
private final Function<Object, V> valueFunc;
// ------------------------------------------------------------------------- Constructor start
/**
* 构造<br>
* 注意提供的Map中不能有键值对否则可能导致自定义key失效
*
* @param mapFactory Map提供的空map
* @param keyFunc 自定义KEY的函数
* @param valueFunc 自定义value函数
*/
public FuncMap(Supplier<Map<K, V>> mapFactory, Function<Object, K> keyFunc, Function<Object, V> valueFunc) {
this(mapFactory.get(), keyFunc, valueFunc);
}
/**
* 构造<br>
* 注意提供的Map中不能有键值对否则可能导致自定义key失效
*
* @param emptyMap Map提供的空map
* @param keyFunc 自定义KEY的函数
* @param valueFunc 自定义value函数
*/
public FuncMap(Map<K, V> emptyMap, Function<Object, K> keyFunc, Function<Object, V> valueFunc) {
super(emptyMap);
this.keyFunc = keyFunc;
this.valueFunc = valueFunc;
}
// ------------------------------------------------------------------------- Constructor end
/**
* 根据函数自定义键
*
* @param key KEY
* @return 驼峰Key
*/
@Override
protected K customKey(Object key) {
if (null != this.keyFunc) {
return keyFunc.apply(key);
}
//noinspection unchecked
return (K) key;
}
@Override
protected V customValue(Object value) {
if (null != this.valueFunc) {
return valueFunc.apply(value);
}
//noinspection unchecked
return (V) value;
}
}

View File

@ -2,6 +2,9 @@ package cn.hutool.core.map;
import cn.hutool.core.util.ObjectUtil;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
@ -11,6 +14,7 @@ import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Map包装类通过包装一个已有Map实现特定功能例如自定义Key的规则或Value规则
@ -34,6 +38,17 @@ public class MapWrapper<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, S
private Map<K, V> raw;
/**
* 构造<br>
* 通过传入一个Map从而确定Map的类型子类需创建一个空的Map而非传入一个已有Map否则值可能会被修改
*
* @param mapFactory 空Map创建工厂
* @since 5.8.0
*/
public MapWrapper(Supplier<Map<K, V>> mapFactory) {
this(mapFactory.get());
}
/**
* 构造
*
@ -199,11 +214,23 @@ public class MapWrapper<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, S
@Override
public MapWrapper<K, V> clone() throws CloneNotSupportedException {
@SuppressWarnings("unchecked")
final MapWrapper<K, V> clone = (MapWrapper<K, V>) super.clone();
@SuppressWarnings("unchecked") final MapWrapper<K, V> clone = (MapWrapper<K, V>) super.clone();
clone.raw = ObjectUtil.clone(raw);
return clone;
}
//---------------------------------------------------------------------------- Override default methods end
// region 序列化与反序列化重写
private void writeObject(final ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeObject(this.raw);
}
@SuppressWarnings("unchecked")
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
raw = (Map<K, V>) in.readObject();
}
// endregion
}

View File

@ -0,0 +1,322 @@
package cn.hutool.core.map;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.func.Func0;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReferenceUtil;
import java.io.Serializable;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 线程安全的ReferenceMap实现<br>
* 参考jdk.management.resource.internal.WeakKeyConcurrentHashMap
*
* @param <K> 键类型
* @param <V> 值类型
* @author looly
* @since 5.8.0
*/
public class ReferenceConcurrentMap<K, V> implements ConcurrentMap<K, V>, Iterable<Map.Entry<K, V>>, Serializable {
final ConcurrentMap<Reference<K>, V> raw;
private final ReferenceQueue<K> lastQueue;
private final ReferenceUtil.ReferenceType keyType;
/**
* 回收监听
*/
private BiConsumer<Reference<? extends K>, V> purgeListener;
// region 构造
/**
* 构造
*
* @param raw {@link ConcurrentMap}实现
* @param referenceType Reference类型
*/
public ReferenceConcurrentMap(ConcurrentMap<Reference<K>, V> raw, ReferenceUtil.ReferenceType referenceType) {
this.raw = raw;
this.keyType = referenceType;
lastQueue = new ReferenceQueue<>();
}
// endregion
/**
* 设置对象回收清除监听
*
* @param purgeListener 监听函数
*/
public void setPurgeListener(BiConsumer<Reference<? extends K>, V> purgeListener) {
this.purgeListener = purgeListener;
}
@Override
public int size() {
this.purgeStaleKeys();
return this.raw.size();
}
@Override
public boolean isEmpty() {
return 0 == size();
}
@Override
public V get(Object key) {
this.purgeStaleKeys();
//noinspection unchecked
return this.raw.get(ofKey((K) key, null));
}
@Override
public boolean containsKey(Object key) {
this.purgeStaleKeys();
//noinspection unchecked
return this.raw.containsKey(ofKey((K) key, null));
}
@Override
public boolean containsValue(Object value) {
this.purgeStaleKeys();
return this.raw.containsValue(value);
}
@Override
public V put(K key, V value) {
this.purgeStaleKeys();
return this.raw.put(ofKey(key, this.lastQueue), value);
}
@Override
public V putIfAbsent(K key, V value) {
this.purgeStaleKeys();
return this.raw.putIfAbsent(ofKey(key, this.lastQueue), value);
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
m.forEach(this::put);
}
@Override
public V replace(K key, V value) {
this.purgeStaleKeys();
return this.raw.replace(ofKey(key, this.lastQueue), value);
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
this.purgeStaleKeys();
return this.raw.replace(ofKey(key, this.lastQueue), oldValue, newValue);
}
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
this.purgeStaleKeys();
this.raw.replaceAll((kWeakKey, value) -> function.apply(kWeakKey.get(), value));
}
@Override
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
this.purgeStaleKeys();
return this.raw.computeIfAbsent(ofKey(key, this.lastQueue), kWeakKey -> mappingFunction.apply(key));
}
@Override
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
this.purgeStaleKeys();
return this.raw.computeIfPresent(ofKey(key, this.lastQueue), (kWeakKey, value) -> remappingFunction.apply(key, value));
}
/**
* 从缓存中获得对象当对象不在缓存中或已经过期返回Func0回调产生的对象
*
* @param key
* @param supplier 如果不存在回调方法用于生产值对象
* @return 值对象
*/
public V computeIfAbsent(K key, Func0<? extends V> supplier) {
return computeIfAbsent(key, (keyParam) -> supplier.callWithRuntimeException());
}
@Override
public V remove(Object key) {
this.purgeStaleKeys();
//noinspection unchecked
return this.raw.remove(ofKey((K) key, null));
}
@Override
public boolean remove(Object key, Object value) {
this.purgeStaleKeys();
//noinspection unchecked
return this.raw.remove(ofKey((K) key, null), value);
}
@Override
public void clear() {
this.raw.clear();
//noinspection StatementWithEmptyBody
while (lastQueue.poll() != null) ;
}
@Override
public Set<K> keySet() {
// TODO 非高效方式的set转换应该返回一个view
final Collection<K> trans = CollUtil.trans(this.raw.keySet(), (reference) -> null == reference ? null : reference.get());
return new HashSet<>(trans);
}
@Override
public Collection<V> values() {
this.purgeStaleKeys();
return this.raw.values();
}
@Override
public Set<Entry<K, V>> entrySet() {
this.purgeStaleKeys();
return this.raw.entrySet().stream()
.map(entry -> new AbstractMap.SimpleImmutableEntry<>(entry.getKey().get(), entry.getValue()))
.collect(Collectors.toSet());
}
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
this.purgeStaleKeys();
this.raw.forEach((key, value)-> action.accept(key.get(), value));
}
@Override
public Iterator<Entry<K, V>> iterator() {
return entrySet().iterator();
}
@Override
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
this.purgeStaleKeys();
return this.raw.compute(ofKey(key, this.lastQueue), (kWeakKey, value) -> remappingFunction.apply(key, value));
}
@Override
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
this.purgeStaleKeys();
return this.raw.merge(ofKey(key, this.lastQueue), value, remappingFunction);
}
/**
* 清除被回收的键
*/
private void purgeStaleKeys() {
Reference<? extends K> reference;
V value;
while ((reference = this.lastQueue.poll()) != null) {
value = this.raw.remove(reference);
if (null != purgeListener) {
purgeListener.accept(reference, value);
}
}
}
/**
* 根据Reference类型构建key对应的{@link Reference}
*
* @param key
* @param queue {@link ReferenceQueue}
* @return {@link Reference}
*/
private Reference<K> ofKey(K key, ReferenceQueue<? super K> queue) {
switch (keyType) {
case WEAK:
return new WeakKey<>(key, queue);
case SOFT:
return new SoftKey<>(key, queue);
}
throw new IllegalArgumentException("Unsupported key type: " + keyType);
}
/**
* 弱键
*
* @param <K> 键类型
*/
private static class WeakKey<K> extends WeakReference<K> {
private final int hashCode;
/**
* 构造
*
* @param key 原始Key不能为{@code null}
* @param queue {@link ReferenceQueue}
*/
WeakKey(K key, ReferenceQueue<? super K> queue) {
super(key, queue);
hashCode = key.hashCode();
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
} else if (other instanceof WeakKey) {
return ObjectUtil.equals(((WeakKey<?>) other).get(), get());
}
return false;
}
}
/**
* 弱键
*
* @param <K> 键类型
*/
private static class SoftKey<K> extends SoftReference<K> {
private final int hashCode;
/**
* 构造
*
* @param key 原始Key不能为{@code null}
* @param queue {@link ReferenceQueue}
*/
SoftKey(K key, ReferenceQueue<? super K> queue) {
super(key, queue);
hashCode = key.hashCode();
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
} else if (other instanceof SoftKey) {
return ObjectUtil.equals(((SoftKey<?>) other).get(), get());
}
return false;
}
}
}

View File

@ -27,9 +27,18 @@ import java.util.Set;
public class TableMap<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, Serializable {
private static final long serialVersionUID = 1L;
private static final int DEFAULT_CAPACITY = 10;
private final List<K> keys;
private final List<V> values;
/**
* 构造
*/
public TableMap() {
this(DEFAULT_CAPACITY);
}
/**
* 构造
*
@ -85,11 +94,12 @@ public class TableMap<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, Ser
/**
* 根据value获得对应的key只返回找到的第一个value对应的key值
*
* @param value
* @return
* @since 5.3.3
*/
public K getKey(V value){
public K getKey(V value) {
final int index = values.indexOf(value);
if (index > -1 && index < keys.size()) {
return keys.get(index);
@ -160,7 +170,17 @@ public class TableMap<K, V> implements Map<K, V>, Iterable<Map.Entry<K, V>>, Ser
@Override
public Set<K> keySet() {
return new HashSet<>(keys);
return new HashSet<>(this.keys);
}
/**
* 获取所有键可重复不可修改
*
* @return 键列表
* @since 5.8.0
*/
public List<K> keys() {
return Collections.unmodifiableList(this.keys);
}
@Override

View File

@ -0,0 +1,118 @@
package cn.hutool.core.map;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Supplier;
/**
* 自定义键和值转换的的Map<br>
* 继承此类后通过实现{@link #customKey(Object)}{@link #customValue(Object)}按照给定规则加入到map或获取值
*
* @param <K> 键类型
* @param <V> 值类型
* @author Looly
* @since 5.8.0
*/
public abstract class TransMap<K, V> extends MapWrapper<K, V> {
private static final long serialVersionUID = 1L;
/**
* 构造<br>
* 通过传入一个Map从而确定Map的类型子类需创建一个空的Map而非传入一个已有Map否则值可能会被修改
*
* @param mapFactory 空Map创建工厂
* @since 5.8.0
*/
public TransMap(Supplier<Map<K, V>> mapFactory) {
super(mapFactory);
}
/**
* 构造<br>
* 通过传入一个Map从而确定Map的类型子类需创建一个空的Map而非传入一个已有Map否则值可能会被修改
*
* @param emptyMap Map 被包装的Map必须为空Map否则自定义key会无效
* @since 3.1.2
*/
public TransMap(Map<K, V> emptyMap) {
super(emptyMap);
}
@Override
public V get(Object key) {
return super.get(customKey(key));
}
@Override
public V put(K key, V value) {
return super.put(customKey(key), customValue(value));
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
m.forEach(this::put);
}
@Override
public boolean containsKey(Object key) {
return super.containsKey(customKey(key));
}
@Override
public V remove(Object key) {
return super.remove(customKey(key));
}
@Override
public boolean remove(Object key, Object value) {
return super.remove(customKey(key), customValue(value));
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
return super.replace(customKey(key), customValue(oldValue), customValue(values()));
}
@Override
public V replace(K key, V value) {
return super.replace(customKey(key), customValue(value));
}
//---------------------------------------------------------------------------- Override default methods start
@Override
public V getOrDefault(Object key, V defaultValue) {
return super.getOrDefault(customKey(key), customValue(defaultValue));
}
@Override
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return super.computeIfPresent(customKey(key), (k, v) -> remappingFunction.apply(customKey(k), customValue(v)));
}
@Override
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return super.compute(customKey(key), (k, v) -> remappingFunction.apply(customKey(k), customValue(v)));
}
@Override
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
return super.merge(customKey(key), customValue(value), (v1, v2) -> remappingFunction.apply(customValue(v1), customValue(v2)));
}
//---------------------------------------------------------------------------- Override default methods end
/**
* 自定义键
*
* @param key KEY
* @return 自定义KEY
*/
protected abstract K customKey(Object key);
/**
* 自定义值
*
* @param value
* @return 自定义值
*/
protected abstract V customValue(Object value);
}

View File

@ -0,0 +1,35 @@
package cn.hutool.core.map;
import cn.hutool.core.util.ReferenceUtil;
import java.lang.ref.Reference;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* 线程安全的WeakMap实现<br>
* 参考jdk.management.resource.internal.WeakKeyConcurrentHashMap
*
* @param <K> 键类型
* @param <V> 值类型
* @author looly
* @since 5.8.0
*/
public class WeakConcurrentMap<K, V> extends ReferenceConcurrentMap<K, V> {
/**
* 构造
*/
public WeakConcurrentMap() {
this(new ConcurrentHashMap<>());
}
/**
* 构造
*
* @param raw {@link ConcurrentMap}实现
*/
public WeakConcurrentMap(ConcurrentMap<Reference<K>, V> raw) {
super(raw, ReferenceUtil.ReferenceType.WEAK);
}
}

View File

@ -9,8 +9,11 @@ import cn.hutool.core.map.MapUtil;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -198,6 +201,15 @@ public class RowKeyTable<R, C, V> extends AbsTable<R, C, V> {
//endregion
//region getColumn
@Override
public List<C> columnKeys() {
final Collection<Map<C, V>> values = this.raw.values();
final List<C> result = new ArrayList<>(values.size() * 16);
for (Map<C, V> map : values) {
map.forEach((key, value)->{result.add(key);});
}
return result;
}
@Override
public Map<R, V> getColumn(C columnKey) {

View File

@ -1,10 +1,13 @@
package cn.hutool.core.map.multi;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.lang.func.Consumer3;
import cn.hutool.core.map.MapUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -101,6 +104,25 @@ public interface Table<R, C, V> extends Iterable<Table.Cell<R, C, V>> {
return Opt.ofNullable(columnMap()).map(Map::keySet).get();
}
/**
* 返回所有列的key列的key如果实现Map是可重复key则返回对应不去重的List
*
* @return 列set
* @since 5.8.0
*/
default List<C> columnKeys() {
final Map<C, Map<R, V>> columnMap = columnMap();
if(MapUtil.isEmpty(columnMap)){
return ListUtil.empty();
}
final List<C> result = new ArrayList<>(columnMap.size());
for (Map.Entry<C, Map<R, V>> cMapEntry : columnMap.entrySet()) {
result.add(cMapEntry.getKey());
}
return result;
}
/**
* 返回列-行对应的map
*

View File

@ -3,8 +3,8 @@ package cn.hutool.core.net;
import cn.hutool.core.codec.PercentCodec;
/**
* rfc3986 : https://www.ietf.org/rfc/rfc3986.html 编码实现<br>
* 定义见https://www.ietf.org/rfc/rfc3986.html#appendix-A
* <a href="https://www.ietf.org/rfc/rfc3986.html">RFC3986</a> 编码实现<br>
* 定义见<a href="https://www.ietf.org/rfc/rfc3986.html#appendix-A">https://www.ietf.org/rfc/rfc3986.html#appendix-A</a>
*
* @author looly
* @since 5.7.16
@ -23,13 +23,13 @@ public class RFC3986 {
/**
* reserved = gen-delims / sub-delims<br>
* seehttps://www.ietf.org/rfc/rfc3986.html#section-2.2
* see<a href="https://www.ietf.org/rfc/rfc3986.html#section-2.2">https://www.ietf.org/rfc/rfc3986.html#section-2.2</a>
*/
public static final PercentCodec RESERVED = GEN_DELIMS.orNew(SUB_DELIMS);
/**
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"<br>
* see: https://www.ietf.org/rfc/rfc3986.html#section-2.3
* see: <a href="https://www.ietf.org/rfc/rfc3986.html#section-2.3">https://www.ietf.org/rfc/rfc3986.html#section-2.3</a>
*/
public static final PercentCodec UNRESERVED = PercentCodec.of(unreservedChars());
@ -40,7 +40,7 @@ public class RFC3986 {
/**
* segment = pchar<br>
* see: https://www.ietf.org/rfc/rfc3986.html#section-3.3
* see: <a href="https://www.ietf.org/rfc/rfc3986.html#section-3.3">https://www.ietf.org/rfc/rfc3986.html#section-3.3</a>
*/
public static final PercentCodec SEGMENT = PCHAR;
/**

View File

@ -3879,7 +3879,7 @@ public class CharSequenceUtil {
* @since 5.4.1
*/
@SuppressWarnings("unchecked")
public <T extends CharSequence> T firstNonNull(T... strs) {
public static <T extends CharSequence> T firstNonNull(T... strs) {
return ArrayUtil.firstNonNull(strs);
}
@ -3893,7 +3893,7 @@ public class CharSequenceUtil {
* @since 5.4.1
*/
@SuppressWarnings("unchecked")
public <T extends CharSequence> T firstNonEmpty(T... strs) {
public static <T extends CharSequence> T firstNonEmpty(T... strs) {
return ArrayUtil.firstMatch(StrUtil::isNotEmpty, strs);
}
@ -3907,7 +3907,7 @@ public class CharSequenceUtil {
* @since 5.4.1
*/
@SuppressWarnings("unchecked")
public <T extends CharSequence> T firstNonBlank(T... strs) {
public static <T extends CharSequence> T firstNonBlank(T... strs) {
return ArrayUtil.firstMatch(StrUtil::isNotBlank, strs);
}

View File

@ -35,6 +35,7 @@ public class StrMatcher {
/**
* 匹配并提取匹配到的内容
*
* @param text 被匹配的文本
* @return 匹配的mapkey为变量名value为匹配到的值
*/
@ -49,7 +50,7 @@ public class StrMatcher {
key = StrUtil.sub(part, 2, part.length() - 1);
} else {
to = text.indexOf(part, from);
if(to < 0){
if (to < 0) {
//普通字符串未匹配到说明整个模式不能匹配返回空
return MapUtil.empty();
}
@ -73,6 +74,7 @@ public class StrMatcher {
/**
* 解析表达式
*
* @param pattern 表达式使用${XXXX}作为变量占位符
* @return 表达式
*/
@ -82,7 +84,7 @@ public class StrMatcher {
char c = 0;
char pre;
boolean inVar = false;
StrBuilder part = StrUtil.strBuilder();
StringBuilder part = StrUtil.builder();
for (int i = 0; i < length; i++) {
pre = c;
c = pattern.charAt(i);
@ -92,16 +94,17 @@ public class StrMatcher {
// 变量结束
inVar = false;
patterns.add(part.toString());
part.clear();
part.setLength(0);
}
} else if ('{' == c && '$' == pre) {
// 变量开始
inVar = true;
final String preText = part.subString(0, part.length() - 1);
final String preText = part.substring(0, part.length() - 1);
if (StrUtil.isNotEmpty(preText)) {
patterns.add(preText);
}
part.reset().append(pre).append(c);
part.setLength(0);
part.append(pre).append(c);
} else {
// 普通字符
part.append(c);

View File

@ -12,6 +12,8 @@ import java.util.concurrent.locks.Lock;
*/
public class NoLock implements Lock{
public static NoLock INSTANCE = new NoLock();
@Override
public void lock() {
}

View File

@ -0,0 +1,22 @@
package cn.hutool.core.thread.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
/**
* 无锁的读写锁实现
*
* @author looly
* @since 5.8.0
*/
public class NoReadWriteLock implements ReadWriteLock {
@Override
public Lock readLock() {
return NoLock.INSTANCE;
}
@Override
public Lock writeLock() {
return NoLock.INSTANCE;
}
}

View File

@ -2,6 +2,7 @@ package cn.hutool.core.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.UniqueKeySet;
import cn.hutool.core.comparator.CompareUtil;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.Assert;
@ -1633,6 +1634,34 @@ public class ArrayUtil extends PrimitiveArrayUtil {
return toArray(set, (Class<T>) getComponentType(array));
}
/**
* 去重数组中的元素去重后生成新的数组原数组不变<br>
* 此方法通过{@link LinkedHashSet} 去重
*
* @param <T> 数组元素类型
* @param <K> 唯一键类型
* @param array 数组
* @param override 是否覆盖模式如果为{@code true}加入的新值会覆盖相同key的旧值否则会忽略新加值
* @return 去重后的数组
* @since 5.8.0
*/
@SuppressWarnings("unchecked")
public static <T, K> T[] distinct(T[] array, Function<T, K> uniqueGenerator, boolean override) {
if (isEmpty(array)) {
return array;
}
final UniqueKeySet<K, T> set = new UniqueKeySet<>(true, uniqueGenerator);
if(override){
Collections.addAll(set, array);
} else{
for (T t : array) {
set.addIfAbsent(t);
}
}
return toArray(set, (Class<T>) getComponentType(array));
}
/**
* 按照指定规则将一种类型的数组转换为另一种类型
*

View File

@ -4,7 +4,8 @@ import cn.hutool.core.convert.BasicType;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.JarClassLoader;
import cn.hutool.core.lang.SimpleCache;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.map.WeakConcurrentMap;
import cn.hutool.core.text.CharPool;
import java.io.File;
@ -49,7 +50,7 @@ public class ClassLoaderUtil {
* 原始类型名和其class对应表例如int = int.class
*/
private static final Map<String, Class<?>> PRIMITIVE_TYPE_NAME_MAP = new ConcurrentHashMap<>(32);
private static final SimpleCache<String, Class<?>> CLASS_CACHE = new SimpleCache<>();
private static final WeakConcurrentMap<Pair<String, ClassLoader>, Class<?>> CLASS_CACHE = new WeakConcurrentMap<>();
static {
List<Class<?>> primitiveTypes = new ArrayList<>(32);
@ -179,7 +180,7 @@ public class ClassLoaderUtil {
* </pre>
*
* @param name 类名
* @param classLoader {@link ClassLoader}{@code null} 则使用系统默认ClassLoader
* @param classLoader {@link ClassLoader}{@code null} 则使用{@link #getClassLoader()}获取
* @param isInitialized 是否初始化类调用static模块内容和初始化static属性
* @return 类名对应的类
* @throws UtilException 包装{@link ClassNotFoundException}没有类名对应的类时抛出此异常
@ -189,49 +190,18 @@ public class ClassLoaderUtil {
// 自动将包名中的"/"替换为"."
name = name.replace(CharPool.SLASH, CharPool.DOT);
if(null == classLoader){
classLoader = getClassLoader();
}
// 加载原始类型和缓存中的类
Class<?> clazz = loadPrimitiveClass(name);
if (clazz == null) {
clazz = CLASS_CACHE.get(name);
final String finalName = name;
final ClassLoader finalClassLoader = classLoader;
clazz = CLASS_CACHE.computeIfAbsent(Pair.of(name, classLoader), (key)-> doLoadClass(finalName, finalClassLoader, isInitialized));
}
if (clazz != null) {
return clazz;
}
if (name.endsWith(ARRAY_SUFFIX)) {
// 对象数组"java.lang.String[]"风格
final String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
final Class<?> elementClass = loadClass(elementClassName, classLoader, isInitialized);
clazz = Array.newInstance(elementClass, 0).getClass();
} else if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) {
// "[Ljava.lang.String;" 风格
final String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1);
final Class<?> elementClass = loadClass(elementName, classLoader, isInitialized);
clazz = Array.newInstance(elementClass, 0).getClass();
} else if (name.startsWith(INTERNAL_ARRAY_PREFIX)) {
// "[[I" "[[Ljava.lang.String;" 风格
final String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length());
final Class<?> elementClass = loadClass(elementName, classLoader, isInitialized);
clazz = Array.newInstance(elementClass, 0).getClass();
} else {
// 加载普通类
if (null == classLoader) {
classLoader = getClassLoader();
}
try {
clazz = Class.forName(name, isInitialized, classLoader);
} catch (ClassNotFoundException ex) {
// 尝试获取内部类例如java.lang.Thread.State =java.lang.Thread$State
clazz = tryLoadInnerClass(name, classLoader, isInitialized);
if (null == clazz) {
throw new UtilException(ex);
}
}
}
// 加入缓存并返回
return CLASS_CACHE.put(name, clazz);
return clazz;
}
/**
@ -311,6 +281,47 @@ public class ClassLoaderUtil {
}
// ----------------------------------------------------------------------------------- Private method start
/**
* 加载非原始类类无缓存
* @param name 类名
* @param classLoader {@link ClassLoader}
* @param isInitialized 是否初始化
* @return
*/
private static Class<?> doLoadClass(String name, ClassLoader classLoader, boolean isInitialized){
Class<?> clazz;
if (name.endsWith(ARRAY_SUFFIX)) {
// 对象数组"java.lang.String[]"风格
final String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
final Class<?> elementClass = loadClass(elementClassName, classLoader, isInitialized);
clazz = Array.newInstance(elementClass, 0).getClass();
} else if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) {
// "[Ljava.lang.String;" 风格
final String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1);
final Class<?> elementClass = loadClass(elementName, classLoader, isInitialized);
clazz = Array.newInstance(elementClass, 0).getClass();
} else if (name.startsWith(INTERNAL_ARRAY_PREFIX)) {
// "[[I" "[[Ljava.lang.String;" 风格
final String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length());
final Class<?> elementClass = loadClass(elementName, classLoader, isInitialized);
clazz = Array.newInstance(elementClass, 0).getClass();
} else {
// 加载普通类
if (null == classLoader) {
classLoader = getClassLoader();
}
try {
clazz = Class.forName(name, isInitialized, classLoader);
} catch (ClassNotFoundException ex) {
// 尝试获取内部类例如java.lang.Thread.State =java.lang.Thread$State
clazz = tryLoadInnerClass(name, classLoader, isInitialized);
if (null == clazz) {
throw new UtilException(ex);
}
}
}
return clazz;
}
/**
* 尝试转换并加载内部类例如java.lang.Thread.State =java.lang.Thread$State

View File

@ -999,26 +999,44 @@ public class ClassUtil {
* @since 3.0.8
*/
public static Object getDefaultValue(Class<?> clazz) {
// 原始类型
if (clazz.isPrimitive()) {
if (long.class == clazz) {
return 0L;
} else if (int.class == clazz) {
return 0;
} else if (short.class == clazz) {
return (short) 0;
} else if (char.class == clazz) {
return (char) 0;
} else if (byte.class == clazz) {
return (byte) 0;
} else if (double.class == clazz) {
return 0D;
} else if (float.class == clazz) {
return 0f;
} else if (boolean.class == clazz) {
return false;
}
return getPrimitiveDefaultValue(clazz);
}
return null;
}
/**
* 获取指定原始类型分的默认值<br>
* 默认值规则为
*
* <pre>
* 1如果为原始类型返回0
* 2非原始类型返回{@code null}
* </pre>
*
* @param clazz
* @return 默认值
* @since 5.8.0
*/
public static Object getPrimitiveDefaultValue(Class<?> clazz) {
if (long.class == clazz) {
return 0L;
} else if (int.class == clazz) {
return 0;
} else if (short.class == clazz) {
return (short) 0;
} else if (char.class == clazz) {
return (char) 0;
} else if (byte.class == clazz) {
return (byte) 0;
} else if (double.class == clazz) {
return 0D;
} else if (float.class == clazz) {
return 0f;
} else if (boolean.class == clazz) {
return false;
}
return null;
}

View File

@ -15,11 +15,11 @@ import java.util.Objects;
/**
* 身份证相关工具类<br>
* see https://www.oschina.net/code/snippet_1611_2881
* see <a href="https://www.oschina.net/code/snippet_1611_2881">https://www.oschina.net/code/snippet_1611_2881</a>
*
* <p>
* 本工具并没有对行政区划代码做校验如有需求请参阅2018年10月
* http://www.mca.gov.cn/article/sj/xzqh/2018/201804-12/20181011221630.html
* <a href="http://www.mca.gov.cn/article/sj/xzqh/2018/201804-12/20181011221630.html">http://www.mca.gov.cn/article/sj/xzqh/2018/201804-12/20181011221630.html</a>
* </p>
*
* @author Looly

View File

@ -2541,7 +2541,7 @@ public class NumberUtil {
/**
* int值转byte数组使用大端字节序高位字节在前低位字节在后<br>
* http://www.ruanyifeng.com/blog/2016/11/byte-order.html
* <a href="http://www.ruanyifeng.com/blog/2016/11/byte-order.html">http://www.ruanyifeng.com/blog/2016/11/byte-order.html</a>
*
* @param value
* @return byte数组
@ -2560,7 +2560,7 @@ public class NumberUtil {
/**
* byte数组转int使用大端字节序高位字节在前低位字节在后<br>
* http://www.ruanyifeng.com/blog/2016/11/byte-order.html
* <a href="http://www.ruanyifeng.com/blog/2016/11/byte-order.html">http://www.ruanyifeng.com/blog/2016/11/byte-order.html</a>
*
* @param bytes byte数组
* @return int

View File

@ -8,11 +8,12 @@ import cn.hutool.core.convert.Convert;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.lang.SimpleCache;
import cn.hutool.core.lang.reflect.MethodHandleUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.map.WeakConcurrentMap;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
@ -36,15 +37,15 @@ public class ReflectUtil {
/**
* 构造对象缓存
*/
private static final SimpleCache<Class<?>, Constructor<?>[]> CONSTRUCTORS_CACHE = new SimpleCache<>();
private static final WeakConcurrentMap<Class<?>, Constructor<?>[]> CONSTRUCTORS_CACHE = new WeakConcurrentMap<>();
/**
* 字段缓存
*/
private static final SimpleCache<Class<?>, Field[]> FIELDS_CACHE = new SimpleCache<>();
private static final WeakConcurrentMap<Class<?>, Field[]> FIELDS_CACHE = new WeakConcurrentMap<>();
/**
* 方法缓存
*/
private static final SimpleCache<Class<?>, Method[]> METHODS_CACHE = new SimpleCache<>();
private static final WeakConcurrentMap<Class<?>, Method[]> METHODS_CACHE = new WeakConcurrentMap<>();
// --------------------------------------------------------------------------------------------------------- Constructor
@ -86,7 +87,7 @@ public class ReflectUtil {
@SuppressWarnings("unchecked")
public static <T> Constructor<T>[] getConstructors(Class<T> beanClass) throws SecurityException {
Assert.notNull(beanClass);
return (Constructor<T>[]) CONSTRUCTORS_CACHE.get(beanClass, () -> getConstructorsDirectly(beanClass));
return (Constructor<T>[]) CONSTRUCTORS_CACHE.computeIfAbsent(beanClass, () -> getConstructorsDirectly(beanClass));
}
/**
@ -175,7 +176,7 @@ public class ReflectUtil {
*/
public static Field[] getFields(Class<?> beanClass) throws SecurityException {
Assert.notNull(beanClass);
return FIELDS_CACHE.get(beanClass, () -> getFieldsDirectly(beanClass, true));
return FIELDS_CACHE.computeIfAbsent(beanClass, () -> getFieldsDirectly(beanClass, true));
}
@ -651,7 +652,7 @@ public class ReflectUtil {
*/
public static Method[] getMethods(Class<?> beanClass) throws SecurityException {
Assert.notNull(beanClass);
return METHODS_CACHE.get(beanClass,
return METHODS_CACHE.computeIfAbsent(beanClass,
() -> getMethodsDirectly(beanClass, true, true));
}
@ -864,30 +865,45 @@ public class ReflectUtil {
* </pre>
*
* @param <T> 对象类型
* @param beanClass 被构造的类
* @param type 被构造的类
* @return 构造后的对象构造失败返回{@code null}
*/
@SuppressWarnings("unchecked")
public static <T> T newInstanceIfPossible(Class<T> beanClass) {
Assert.notNull(beanClass);
public static <T> T newInstanceIfPossible(Class<T> type) {
Assert.notNull(type);
// 原始类型
if(type.isPrimitive()){
return (T) ClassUtil.getPrimitiveDefaultValue(type);
}
// 某些特殊接口的实例化按照默认实现进行
if (beanClass.isAssignableFrom(AbstractMap.class)) {
beanClass = (Class<T>) HashMap.class;
} else if (beanClass.isAssignableFrom(List.class)) {
beanClass = (Class<T>) ArrayList.class;
} else if (beanClass.isAssignableFrom(Set.class)) {
beanClass = (Class<T>) HashSet.class;
if (type.isAssignableFrom(AbstractMap.class)) {
type = (Class<T>) HashMap.class;
} else if (type.isAssignableFrom(List.class)) {
type = (Class<T>) ArrayList.class;
} else if (type.isAssignableFrom(Set.class)) {
type = (Class<T>) HashSet.class;
}
try {
return newInstance(beanClass);
return newInstance(type);
} catch (Exception e) {
// ignore
// 默认构造不存在的情况下查找其它构造
}
final Constructor<T>[] constructors = getConstructors(beanClass);
// 枚举
if (type.isEnum()) {
return type.getEnumConstants()[0];
}
// 数组
if (type.isArray()) {
return (T) Array.newInstance(type.getComponentType(), 0);
}
final Constructor<T>[] constructors = getConstructors(type);
Class<?>[] parameterTypes;
for (Constructor<T> constructor : constructors) {
parameterTypes = constructor.getParameterTypes();

View File

@ -7,7 +7,7 @@ import java.lang.annotation.Target;
/**
* 用于单元测试的注解类<br>
* 注解类相关说明见https://www.cnblogs.com/xdp-gacl/p/3622275.html
* 注解类相关说明见<a href="https://www.cnblogs.com/xdp-gacl/p/3622275.html">https://www.cnblogs.com/xdp-gacl/p/3622275.html</a>
*
* @author looly
*/

View File

@ -3,8 +3,25 @@ package cn.hutool.core.annotation;
import org.junit.Assert;
import org.junit.Test;
import java.lang.annotation.Annotation;
public class AnnotationUtilTest {
@Test
public void getCombinationAnnotationsTest(){
Annotation[] annotations = AnnotationUtil.getAnnotations(ClassWithAnnotation.class, true);
Assert.assertNotNull(annotations);
Assert.assertEquals(3, annotations.length);
}
@Test
public void getCombinationAnnotationsWithClassTest(){
AnnotationForTest[] annotations = AnnotationUtil.getCombinationAnnotations(ClassWithAnnotation.class, AnnotationForTest.class);
Assert.assertNotNull(annotations);
Assert.assertEquals(2, annotations.length);
Assert.assertEquals("测试", annotations[0].value());
}
@Test
public void getAnnotationValueTest() {
Object value = AnnotationUtil.getAnnotationValue(ClassWithAnnotation.class, AnnotationForTest.class);
@ -23,6 +40,7 @@ public class AnnotationUtilTest {
}
@AnnotationForTest("测试")
@RepeatAnnotationForTest
static class ClassWithAnnotation{
public void test(){

View File

@ -0,0 +1,16 @@
package cn.hutool.core.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author hongda.li 2022-04-26 17:09
*/
@AnnotationForTest("repeat-annotation")
@Retention(RetentionPolicy.RUNTIME)
// Target注解决定MyAnnotation注解可以加在哪些成分上如加在类身上或者属性身上或者方法身上等成分
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface RepeatAnnotationForTest {
}

View File

@ -777,4 +777,20 @@ public class BeanUtilTest {
public static class Test2 {
private List<Integer> strList;
}
@Test
public void issuesI53O9JTest(){
Map<String, String> map = new HashMap<>();
map.put("statusIdUpdateTime", "");
WkCrmCustomer customer = new WkCrmCustomer();
BeanUtil.copyProperties(map, customer);
Assert.assertNull(customer.getStatusIdUpdateTime());
}
@Data
public static class WkCrmCustomer{
private LocalDateTime statusIdUpdateTime;
}
}

View File

@ -0,0 +1,20 @@
package cn.hutool.core.codec;
import org.junit.Assert;
import org.junit.Test;
public class HashidsTest {
@Test
public void hexEncodeDecode() {
final Hashids hashids = Hashids.create("my awesome salt".toCharArray());
final String encoded1 = hashids.encodeFromHex("507f1f77bcf86cd799439011");
final String encoded2 = hashids.encodeFromHex("0x507f1f77bcf86cd799439011");
final String encoded3 = hashids.encodeFromHex("0X507f1f77bcf86cd799439011");
Assert.assertEquals("R2qnd2vkOJTXm7XV7yq4", encoded1);
Assert.assertEquals(encoded1, encoded2);
Assert.assertEquals(encoded1, encoded3);
final String decoded = hashids.decodeToHex(encoded1);
Assert.assertEquals("507f1f77bcf86cd799439011", decoded);
}
}

View File

@ -878,6 +878,36 @@ public class CollUtilTest {
Assert.assertEquals(people.get(1).getGender(), "小孩");
}
@Test
public void distinctTest(){
final ArrayList<Integer> distinct = CollUtil.distinct(ListUtil.of(5, 3, 10, 9, 0, 5, 10, 9));
Assert.assertEquals(ListUtil.of(5, 3, 10, 9, 0), distinct);
}
@Test
public void distinctByFunctionTest(){
List<Person> people = Arrays.asList(
new Person("aa", 12, "man", 1),
new Person("bb", 13, "woman", 2),
new Person("cc", 14, "man", 3),
new Person("dd", 15, "woman", 4),
new Person("ee", 16, "woman", 5),
new Person("ff", 17, "man", 6)
);
// 覆盖模式下ff覆盖了aaee覆盖了bb
List<Person> distinct = CollUtil.distinct(people, Person::getGender, true);
Assert.assertEquals(2, distinct.size());
Assert.assertEquals("ff", distinct.get(0).getName());
Assert.assertEquals("ee", distinct.get(1).getName());
// 非覆盖模式下保留了最早加入的aa和bb
distinct = CollUtil.distinct(people, Person::getGender, false);
Assert.assertEquals(2, distinct.size());
Assert.assertEquals("aa", distinct.get(0).getName());
Assert.assertEquals("bb", distinct.get(1).getName());
}
@Data
@AllArgsConstructor
static class Person {

View File

@ -1029,6 +1029,7 @@ public class DateUtilTest {
}
@Test
@SuppressWarnings("ConstantConditions")
public void isOverlapTest() {
DateTime oneStartTime = DateUtil.parse("2022-01-01 10:10:10");
DateTime oneEndTime = DateUtil.parse("2022-01-01 11:10:10");
@ -1055,6 +1056,16 @@ public class DateUtilTest {
Assert.assertFalse(DateUtil.isOverlap(realStartTime1,realEndTime1,startTime,endTime));
Assert.assertFalse(DateUtil.isOverlap(startTime,endTime,realStartTime1,realEndTime1));
}
@Test
public void isInTest(){
String sourceStr = "2022-04-19 00:00:00";
String startTimeStr = "2022-04-19 00:00:00";
String endTimeStr = "2022-04-19 23:59:59";
boolean between = DateUtil.isIn(DateUtil.parse(startTimeStr),
DateUtil.parse(endTimeStr),
DateUtil.parse(sourceStr));
Assert.assertTrue(between);
}
}

View File

@ -5,7 +5,9 @@ import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
/**
* 文件类型判断单元测试
@ -62,4 +64,15 @@ public class FileTypeUtilTest {
Assert.assertEquals("xlsx", type);
}
@Test
@Ignore
public void getTypeFromInputStream() throws IOException {
File file = FileUtil.file("d:/test/pic.jpg");
final BufferedInputStream inputStream = FileUtil.getInputStream(file);
inputStream.mark(0);
String type = FileTypeUtil.getType(inputStream);
inputStream.reset();
}
}

View File

@ -75,4 +75,9 @@ public class ConsoleTest {
}
}
@Test
public void printColorTest(){
System.out.print("\33[30;1m A \u001b[31;2m B \u001b[32;1m C \u001b[33;1m D \u001b[0m");
}
}

View File

@ -50,7 +50,7 @@ public class SimpleCacheTest {
final SimpleCache<String, String> cache = new SimpleCache<>();
final ConcurrencyTester tester = new ConcurrencyTester(9000);
tester.test(()-> cache.get("aaa", ()-> {
ThreadUtil.sleep(1000);
ThreadUtil.sleep(200);
return "aaaValue";
}));

View File

@ -0,0 +1,13 @@
package cn.hutool.core.lang.ansi;
import org.junit.Assert;
import org.junit.Test;
public class AnsiEncoderTest {
@Test
public void encodeTest(){
final String encode = AnsiEncoder.encode(AnsiColor.GREEN, "Hutool test");
Assert.assertEquals("\u001B[32mHutool test\u001B[0;39m", encode);
}
}

View File

@ -0,0 +1,22 @@
package cn.hutool.core.map;
import org.junit.Assert;
import org.junit.Test;
import java.util.HashMap;
public class FuncMapTest {
@Test
public void putGetTest(){
final FuncMap<Object, Object> map = new FuncMap<>(HashMap::new,
(key)->key.toString().toLowerCase(),
(value)->value.toString().toUpperCase());
map.put("aaa", "b");
map.put("BBB", "c");
Assert.assertEquals("B", map.get("aaa"));
Assert.assertEquals("C", map.get("bbb"));
}
}

View File

@ -0,0 +1,52 @@
package cn.hutool.core.map;
import cn.hutool.core.thread.ConcurrencyTester;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import org.junit.Assert;
import org.junit.Test;
public class WeakConcurrentMapTest {
@Test
public void putAndGetTest(){
final WeakConcurrentMap<Object, Object> map = new WeakConcurrentMap<>();
Object
key1 = new Object(), value1 = new Object(),
key2 = new Object(), value2 = new Object(),
key3 = new Object(), value3 = new Object(),
key4 = new Object(), value4 = new Object();
map.put(key1, value1);
map.put(key2, value2);
map.put(key3, value3);
map.put(key4, value4);
Assert.assertEquals(value1, map.get(key1));
Assert.assertEquals(value2, map.get(key2));
Assert.assertEquals(value3, map.get(key3));
Assert.assertEquals(value4, map.get(key4));
// 清空引用
//noinspection UnusedAssignment
key1 = null;
//noinspection UnusedAssignment
key2 = null;
System.gc();
ThreadUtil.sleep(200L);
Assert.assertEquals(2, map.size());
}
@Test
public void getConcurrencyTest(){
final WeakConcurrentMap<String, String> cache = new WeakConcurrentMap<>();
final ConcurrencyTester tester = new ConcurrencyTester(9000);
tester.test(()-> cache.computeIfAbsent("aaa" + RandomUtil.randomInt(2), (key)-> "aaaValue"));
Assert.assertTrue(tester.getInterval() > 0);
String value = ObjectUtil.defaultIfNull(cache.get("aaa0"), cache.get("aaa1"));
Assert.assertEquals("aaaValue", value);
}
}

View File

@ -148,4 +148,16 @@ public class CharSequenceUtilTest {
Assert.assertEquals("ABCde", CharSequenceUtil.removeSuffixIgnoreCase("ABCde", "ABCdef"));
Assert.assertNull(CharSequenceUtil.removeSuffixIgnoreCase(null, "ABCdef"));
}
@Test
public void trimToNullTest(){
String a = " ";
Assert.assertNull(CharSequenceUtil.trimToNull(a));
a = "";
Assert.assertNull(CharSequenceUtil.trimToNull(a));
a = null;
Assert.assertNull(CharSequenceUtil.trimToNull(a));
}
}

View File

@ -1,6 +1,5 @@
package cn.hutool.core.text;
import cn.hutool.core.lang.Console;
import org.junit.Assert;
import org.junit.Test;
@ -34,7 +33,7 @@ public class StrMatcherTest {
// 当有无匹配项的时候按照全不匹配对待
final StrMatcher strMatcher = new StrMatcher("${name}经过${year}年");
final Map<String, String> match = strMatcher.match("小明经过20年成长为一个大人。");
Console.log(match);
//Console.log(match);
Assert.assertEquals("小明", match.get("name"));
Assert.assertEquals("20", match.get("year"));
}

View File

@ -1,10 +1,15 @@
package cn.hutool.core.text.csv;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.CharsetUtil;
import org.junit.Ignore;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class CsvWriterTest {
@Test
@ -23,4 +28,20 @@ public class CsvWriterTest {
writer.writeLine("李四", "", "XX市XX区,01号");
writer.close();
}
@Test
@Ignore
public void issue2255Test(){
String fileName = "D:/test/" + new Random().nextInt(100) + "-a.csv";
CsvWriter writer = CsvUtil.getWriter(fileName, CharsetUtil.CHARSET_UTF_8);
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
list.add(i+"");
}
Console.log("{} : {}", fileName, list.size());
for (String s : list) {
writer.writeLine(s);
}
writer.close();
}
}

View File

@ -285,6 +285,19 @@ public class ArrayUtilTest {
Assert.assertArrayEquals(new String[]{"aa", "bb", "cc", "dd"}, distinct);
}
@Test
public void distinctByFunctionTest() {
String[] array = {"aa", "Aa", "BB", "bb"};
// 覆盖模式下保留最后加入的两个元素
String[] distinct = ArrayUtil.distinct(array, String::toLowerCase, true);
Assert.assertArrayEquals(new String[]{"Aa", "bb"}, distinct);
// 忽略模式下保留最早加入的两个元素
distinct = ArrayUtil.distinct(array, String::toLowerCase, false);
Assert.assertArrayEquals(new String[]{"aa", "BB"}, distinct);
}
@Test
public void toStingTest() {
int[] a = {1, 3, 56, 6, 7};

View File

@ -2,6 +2,7 @@ package cn.hutool.core.util;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.date.Week;
import cn.hutool.core.lang.Console;
import cn.hutool.core.lang.test.bean.ExamInfoDict;
import cn.hutool.core.util.ClassUtilTest.TestSubClass;
@ -12,6 +13,8 @@ import org.junit.Test;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
/**
* 反射工具类单元测试
@ -202,10 +205,13 @@ public class ReflectUtilTest {
}
interface TestInterface1 {
@SuppressWarnings("unused")
void getA();
@SuppressWarnings("unused")
void getB();
@SuppressWarnings("unused")
default void getC() {
}
@ -220,6 +226,7 @@ public class ReflectUtilTest {
void get3();
}
@SuppressWarnings("InnerClassMayBeStatic")
class C1 implements TestInterface2 {
@Override
@ -239,4 +246,26 @@ public class ReflectUtilTest {
}
}
@Test
public void newInstanceIfPossibleTest(){
//noinspection ConstantConditions
int intValue = ReflectUtil.newInstanceIfPossible(int.class);
Assert.assertEquals(0, intValue);
Integer integer = ReflectUtil.newInstanceIfPossible(Integer.class);
Assert.assertEquals(new Integer(0), integer);
Map<?, ?> map = ReflectUtil.newInstanceIfPossible(Map.class);
Assert.assertNotNull(map);
Collection<?> collection = ReflectUtil.newInstanceIfPossible(Collection.class);
Assert.assertNotNull(collection);
Week week = ReflectUtil.newInstanceIfPossible(Week.class);
Assert.assertEquals(Week.SUNDAY, week);
int[] intArray = ReflectUtil.newInstanceIfPossible(int[].class);
Assert.assertArrayEquals(new int[0], intArray);
}
}

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.0.M3</version>
<version>5.8.0.M4</version>
</parent>
<artifactId>hutool-cron</artifactId>

View File

@ -5,6 +5,7 @@ import cn.hutool.core.date.CalendarUtil;
import cn.hutool.cron.pattern.matcher.PatternMatcher;
import cn.hutool.cron.pattern.parser.PatternParser;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
@ -129,6 +130,18 @@ public class CronPattern {
return match(PatternUtil.getFields(calendar, isMatchSecond));
}
/**
* 给定时间是否匹配定时任务表达式
*
* @param dateTime 时间
* @param isMatchSecond 是否匹配秒
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
* @since 5.8.0
*/
public boolean match(LocalDateTime dateTime, boolean isMatchSecond) {
return match(PatternUtil.getFields(dateTime, isMatchSecond));
}
/**
* 返回匹配到的下一个时间
*
@ -137,7 +150,7 @@ public class CronPattern {
*/
public Calendar nextMatchAfter(Calendar calendar) {
Calendar next = nextMatchAfter(PatternUtil.getFields(calendar, true), calendar.getTimeZone());
if(false == match(next, true)){
if (false == match(next, true)) {
next.set(Calendar.DAY_OF_MONTH, next.get(Calendar.DAY_OF_MONTH) + 1);
next = CalendarUtil.beginOfDay(next);
return nextMatchAfter(next);

View File

@ -1,5 +1,8 @@
package cn.hutool.cron.pattern;
import cn.hutool.core.date.Week;
import java.time.LocalDateTime;
import java.util.Calendar;
/**
@ -10,6 +13,26 @@ import java.util.Calendar;
*/
class PatternUtil {
/**
* 获取处理后的字段列表<br>
* 月份从1开始周从0开始
*
* @param dateTime {@link Calendar}
* @param isMatchSecond 是否匹配秒{@link false}则秒返回-1
* @return 字段值列表
* @since 5.8.0
*/
static int[] getFields(LocalDateTime dateTime, boolean isMatchSecond) {
final int second = isMatchSecond ? dateTime.getSecond() : -1;
final int minute = dateTime.getMinute();
final int hour = dateTime.getHour();
final int dayOfMonth = dateTime.getDayOfMonth();
final int month = dateTime.getMonthValue();// 月份从1开始
final int dayOfWeek = Week.of(dateTime.getDayOfWeek()).getValue() - 1; // 星期从0开始0和7都表示周日
final int year = dateTime.getYear();
return new int[]{second, minute, hour, dayOfMonth, month, dayOfWeek, year};
}
/**
* 获取处理后的字段列表<br>
* 月份从1开始周从0开始

View File

@ -72,6 +72,17 @@ public class PatternMatcher {
return match(fields[0], fields[1], fields[2], fields[3], fields[4], fields[5], fields[6]);
}
/**
* 给定周的值是否匹配定时任务表达式对应部分
*
* @param dayOfWeekValue dayOfMonth值星期从0开始0和7都表示周日
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
* @since 5.8.0
*/
public boolean matchWeek(int dayOfWeekValue) {
return matchers[5].match(dayOfWeekValue);
}
/**
* 给定时间是否匹配定时任务表达式
*
@ -88,7 +99,7 @@ public class PatternMatcher {
return ((second < 0) || matchers[0].match(second)) // 匹配秒非秒匹配模式下始终返回true
&& matchers[1].match(minute)// 匹配分
&& matchers[2].match(hour)// 匹配时
&& isMatchDayOfMonth(matchers[3], dayOfMonth, month, Year.isLeap(year))// 匹配日
&& matchDayOfMonth(matchers[3], dayOfMonth, month, Year.isLeap(year))// 匹配日
&& matchers[4].match(month) // 匹配月
&& matchers[5].match(dayOfWeek)// 匹配周
&& matchers[6].match(year);// 匹配年
@ -103,7 +114,7 @@ public class PatternMatcher {
* @param isLeapYear 是否闰年
* @return 是否匹配
*/
private static boolean isMatchDayOfMonth(PartMatcher matcher, int dayOfMonth, int month, boolean isLeapYear) {
private static boolean matchDayOfMonth(PartMatcher matcher, int dayOfMonth, int month, boolean isLeapYear) {
return ((matcher instanceof DayOfMonthMatcher) //
? ((DayOfMonthMatcher) matcher).match(dayOfMonth, month, isLeapYear) //
: matcher.match(dayOfMonth));

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.0.M3</version>
<version>5.8.0.M4</version>
</parent>
<artifactId>hutool-crypto</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.0.M3</version>
<version>5.8.0.M4</version>
</parent>
<artifactId>hutool-db</artifactId>
@ -20,14 +20,14 @@
<!-- versions -->
<c3p0.version>0.9.5.5</c3p0.version>
<dbcp2.version>2.9.0</dbcp2.version>
<tomcat-jdbc.version>10.0.16</tomcat-jdbc.version>
<druid.version>1.2.8</druid.version>
<tomcat-jdbc.version>10.0.20</tomcat-jdbc.version>
<druid.version>1.2.9</druid.version>
<hikariCP.version>2.4.13</hikariCP.version>
<mongo4.version>4.5.0</mongo4.version>
<mongo4.version>4.6.0</mongo4.version>
<sqlite.version>3.36.0.3</sqlite.version>
<!-- 此处固定2.5.x支持到JDK8 -->
<hsqldb.version>2.5.2</hsqldb.version>
<jedis.version>4.2.1</jedis.version>
<jedis.version>4.2.2</jedis.version>
</properties>
<dependencies>
@ -81,7 +81,7 @@
<dependency>
<groupId>com.github.chris2018998</groupId>
<artifactId>beecp</artifactId>
<version>3.3.4</version>
<version>3.3.5</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
@ -142,13 +142,13 @@
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
<version>8.0.29</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.3.3</version>
<version>42.3.4</version>
<scope>test</scope>
</dependency>
<dependency>
@ -166,7 +166,7 @@
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.1.210</version>
<version>2.1.212</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.0.M3</version>
<version>5.8.0.M4</version>
</parent>
<artifactId>hutool-dfa</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.8.0.M3</version>
<version>5.8.0.M4</version>
</parent>
<artifactId>hutool-extra</artifactId>
@ -26,12 +26,12 @@
<thymeleaf.version>3.0.15.RELEASE</thymeleaf.version>
<mail.version>1.6.2</mail.version>
<jsch.version>0.1.55</jsch.version>
<sshj.version>0.32.0</sshj.version>
<sshj.version>0.33.0</sshj.version>
<zxing.version>3.4.1</zxing.version>
<net.version>3.8.0</net.version>
<emoji-java.version>5.1.1</emoji-java.version>
<servlet-api.version>4.0.1</servlet-api.version>
<spring-boot.version>2.6.6</spring-boot.version>
<spring-boot.version>2.6.7</spring-boot.version>
<cglib.version>3.3.0</cglib.version>
</properties>
@ -53,6 +53,13 @@
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>5.0.0</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<!-- 模板引擎 -->
<dependency>
@ -249,7 +256,7 @@
<dependency>
<groupId>com.hankcs</groupId>
<artifactId>hanlp</artifactId>
<version>portable-1.8.2</version>
<version>portable-1.8.3</version>
<optional>true</optional>
</dependency>
<dependency>
@ -398,7 +405,7 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.9</version>
<version>1.2.11</version>
<scope>test</scope>
<exclusions>
<exclusion>
@ -411,7 +418,7 @@
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>5.3.0</version>
<version>5.3.1</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
@ -446,7 +453,7 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.18</version>
<version>5.3.19</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>

View File

@ -1,6 +1,6 @@
package cn.hutool.extra.cglib;
import cn.hutool.core.lang.SimpleCache;
import cn.hutool.core.map.WeakConcurrentMap;
import cn.hutool.core.util.StrUtil;
import net.sf.cglib.beans.BeanCopier;
import net.sf.cglib.core.Converter;
@ -18,7 +18,7 @@ public enum BeanCopierCache {
*/
INSTANCE;
private final SimpleCache<String, BeanCopier> cache = new SimpleCache<>();
private final WeakConcurrentMap<String, BeanCopier> cache = new WeakConcurrentMap<>();
/**
* 获得类与转换器生成的key在{@link BeanCopier}的Map中对应的元素
@ -43,7 +43,7 @@ public enum BeanCopierCache {
*/
public BeanCopier get(Class<?> srcClass, Class<?> targetClass, boolean useConverter) {
final String key = genKey(srcClass, targetClass, useConverter);
return cache.get(key, () -> BeanCopier.create(srcClass, targetClass, useConverter));
return cache.computeIfAbsent(key, () -> BeanCopier.create(srcClass, targetClass, useConverter));
}
/**

View File

@ -418,7 +418,7 @@ public class Ftp extends AbstractFtp {
}
/**
* 判断ftp服务器文件是否存在
* 判断ftp服务器目录内是否还有子元素目录或文件
*
* @param path 文件路径
* @return 是否存在

View File

@ -1,6 +1,5 @@
package cn.hutool.extra.mail;
import cn.hutool.core.lang.Console;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
@ -579,7 +578,6 @@ public class MailAccount implements Serializable {
// SSL
if (null != this.sslEnable && this.sslEnable) {
Console.log("{} {}", SOCKET_FACTORY, socketFactoryClass);
p.put(SSL_ENABLE, "true");
p.put(SOCKET_FACTORY, socketFactoryClass);
p.put(SOCKET_FACTORY_FALLBACK, String.valueOf(this.socketFactoryFallback));

View File

@ -0,0 +1,643 @@
package cn.hutool.extra.servlet;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.bean.copier.ValueProvider;
import cn.hutool.core.collection.ArrayIter;
import cn.hutool.core.collection.IterUtil;
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.map.CaseInsensitiveMap;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.net.multipart.MultipartFormData;
import cn.hutool.core.net.multipart.UploadSetting;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* Servlet相关工具类封装
*
* @author looly
* @since 3.2.0
*/
public class JakartaServletUtil {
public static final String METHOD_DELETE = "DELETE";
public static final String METHOD_HEAD = "HEAD";
public static final String METHOD_GET = "GET";
public static final String METHOD_OPTIONS = "OPTIONS";
public static final String METHOD_POST = "POST";
public static final String METHOD_PUT = "PUT";
public static final String METHOD_TRACE = "TRACE";
// --------------------------------------------------------- getParam start
/**
* 获得所有请求参数
*
* @param request 请求对象{@link ServletRequest}
* @return Map
*/
public static Map<String, String[]> getParams(ServletRequest request) {
final Map<String, String[]> map = request.getParameterMap();
return Collections.unmodifiableMap(map);
}
/**
* 获得所有请求参数
*
* @param request 请求对象{@link ServletRequest}
* @return Map
*/
public static Map<String, String> getParamMap(ServletRequest request) {
Map<String, String> params = new HashMap<>();
for (Map.Entry<String, String[]> entry : getParams(request).entrySet()) {
params.put(entry.getKey(), ArrayUtil.join(entry.getValue(), StrUtil.COMMA));
}
return params;
}
/**
* 获取请求体<br>
* 调用该方法后getParam方法将失效
*
* @param request {@link ServletRequest}
* @return 获得请求体
* @since 4.0.2
*/
public static String getBody(ServletRequest request) {
try (final BufferedReader reader = request.getReader()) {
return IoUtil.read(reader);
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
/**
* 获取请求体byte[]<br>
* 调用该方法后getParam方法将失效
*
* @param request {@link ServletRequest}
* @return 获得请求体byte[]
* @since 4.0.2
*/
public static byte[] getBodyBytes(ServletRequest request) {
try {
return IoUtil.readBytes(request.getInputStream());
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
// --------------------------------------------------------- getParam end
// --------------------------------------------------------- fillBean start
/**
* ServletRequest 参数转Bean
*
* @param <T> Bean类型
* @param request ServletRequest
* @param bean Bean
* @param copyOptions 注入时的设置
* @return Bean
* @since 3.0.4
*/
public static <T> T fillBean(final ServletRequest request, T bean, CopyOptions copyOptions) {
final String beanName = StrUtil.lowerFirst(bean.getClass().getSimpleName());
return BeanUtil.fillBean(bean, new ValueProvider<String>() {
@Override
public Object value(String key, Type valueType) {
String[] values = request.getParameterValues(key);
if (ArrayUtil.isEmpty(values)) {
values = request.getParameterValues(beanName + StrUtil.DOT + key);
if (ArrayUtil.isEmpty(values)) {
return null;
}
}
if (1 == values.length) {
// 单值表单直接返回这个值
return values[0];
} else {
// 多值表单返回数组
return values;
}
}
@Override
public boolean containsKey(String key) {
// 对于Servlet来说返回值null意味着无此参数
return (null != request.getParameter(key)) || (null != request.getParameter(beanName + StrUtil.DOT + key));
}
}, copyOptions);
}
/**
* ServletRequest 参数转Bean
*
* @param <T> Bean类型
* @param request {@link ServletRequest}
* @param bean Bean
* @param isIgnoreError 是否忽略注入错误
* @return Bean
*/
public static <T> T fillBean(ServletRequest request, T bean, boolean isIgnoreError) {
return fillBean(request, bean, CopyOptions.create().setIgnoreError(isIgnoreError));
}
/**
* ServletRequest 参数转Bean
*
* @param <T> Bean类型
* @param request ServletRequest
* @param beanClass Bean Class
* @param isIgnoreError 是否忽略注入错误
* @return Bean
*/
public static <T> T toBean(ServletRequest request, Class<T> beanClass, boolean isIgnoreError) {
return fillBean(request, ReflectUtil.newInstanceIfPossible(beanClass), isIgnoreError);
}
// --------------------------------------------------------- fillBean end
/**
* 获取客户端IP
*
* <p>
* 默认检测的Header:
*
* <pre>
* 1X-Forwarded-For
* 2X-Real-IP
* 3Proxy-Client-IP
* 4WL-Proxy-Client-IP
* </pre>
*
* <p>
* otherHeaderNames参数用于自定义检测的Header<br>
* 需要注意的是使用此方法获取的客户IP地址必须在Http服务器例如Nginx中配置头信息否则容易造成IP伪造
* </p>
*
* @param request 请求对象{@link HttpServletRequest}
* @param otherHeaderNames 其他自定义头文件通常在Http服务器例如Nginx中配置
* @return IP地址
*/
public static String getClientIP(HttpServletRequest request, String... otherHeaderNames) {
String[] headers = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
if (ArrayUtil.isNotEmpty(otherHeaderNames)) {
headers = ArrayUtil.addAll(headers, otherHeaderNames);
}
return getClientIPByHeader(request, headers);
}
/**
* 获取客户端IP
*
* <p>
* headerNames参数用于自定义检测的Header<br>
* 需要注意的是使用此方法获取的客户IP地址必须在Http服务器例如Nginx中配置头信息否则容易造成IP伪造
* </p>
*
* @param request 请求对象{@link HttpServletRequest}
* @param headerNames 自定义头通常在Http服务器例如Nginx中配置
* @return IP地址
* @since 4.4.1
*/
public static String getClientIPByHeader(HttpServletRequest request, String... headerNames) {
String ip;
for (String header : headerNames) {
ip = request.getHeader(header);
if (false == NetUtil.isUnknown(ip)) {
return NetUtil.getMultistageReverseProxyIp(ip);
}
}
ip = request.getRemoteAddr();
return NetUtil.getMultistageReverseProxyIp(ip);
}
/**
* 获得MultiPart表单内容多用于获得上传的文件 在同一次请求中此方法只能被执行一次
*
* @param request {@link ServletRequest}
* @return MultipartFormData
* @throws IORuntimeException IO异常
* @since 4.0.2
*/
public static MultipartFormData getMultipart(ServletRequest request) throws IORuntimeException {
return getMultipart(request, new UploadSetting());
}
/**
* 获得multipart/form-data 表单内容<br>
* 包括文件和普通表单数据<br>
* 在同一次请求中此方法只能被执行一次
*
* @param request {@link ServletRequest}
* @param uploadSetting 上传文件的设定包括最大文件大小保存在内存的边界大小临时目录扩展名限定等
* @return MultiPart表单
* @throws IORuntimeException IO异常
* @since 4.0.2
*/
public static MultipartFormData getMultipart(ServletRequest request, UploadSetting uploadSetting) throws IORuntimeException {
final MultipartFormData formData = new MultipartFormData(uploadSetting);
try {
formData.parseRequestStream(request.getInputStream(), CharsetUtil.charset(request.getCharacterEncoding()));
} catch (IOException e) {
throw new IORuntimeException(e);
}
return formData;
}
// --------------------------------------------------------- Header start
/**
* 获取请求所有的头header信息
*
* @param request 请求对象{@link HttpServletRequest}
* @return header值
* @since 4.6.2
*/
public static Map<String, String> getHeaderMap(HttpServletRequest request) {
final Map<String, String> headerMap = new HashMap<>();
final Enumeration<String> names = request.getHeaderNames();
String name;
while (names.hasMoreElements()) {
name = names.nextElement();
headerMap.put(name, request.getHeader(name));
}
return headerMap;
}
/**
* 忽略大小写获得请求header中的信息
*
* @param request 请求对象{@link HttpServletRequest}
* @param nameIgnoreCase 忽略大小写头信息的KEY
* @return header值
*/
public static String getHeaderIgnoreCase(HttpServletRequest request, String nameIgnoreCase) {
final Enumeration<String> names = request.getHeaderNames();
String name;
while (names.hasMoreElements()) {
name = names.nextElement();
if (name != null && name.equalsIgnoreCase(nameIgnoreCase)) {
return request.getHeader(name);
}
}
return null;
}
/**
* 获得请求header中的信息
*
* @param request 请求对象{@link HttpServletRequest}
* @param name 头信息的KEY
* @param charsetName 字符集
* @return header值
*/
public static String getHeader(HttpServletRequest request, String name, String charsetName) {
return getHeader(request, name, CharsetUtil.charset(charsetName));
}
/**
* 获得请求header中的信息
*
* @param request 请求对象{@link HttpServletRequest}
* @param name 头信息的KEY
* @param charset 字符集
* @return header值
* @since 4.6.2
*/
public static String getHeader(HttpServletRequest request, String name, Charset charset) {
final String header = request.getHeader(name);
if (null != header) {
return CharsetUtil.convert(header, CharsetUtil.CHARSET_ISO_8859_1, charset);
}
return null;
}
/**
* 客户浏览器是否为IE
*
* @param request 请求对象{@link HttpServletRequest}
* @return 客户浏览器是否为IE
*/
public static boolean isIE(HttpServletRequest request) {
String userAgent = getHeaderIgnoreCase(request, "User-Agent");
if (StrUtil.isNotBlank(userAgent)) {
//noinspection ConstantConditions
userAgent = userAgent.toUpperCase();
return userAgent.contains("MSIE") || userAgent.contains("TRIDENT");
}
return false;
}
/**
* 是否为GET请求
*
* @param request 请求对象{@link HttpServletRequest}
* @return 是否为GET请求
*/
public static boolean isGetMethod(HttpServletRequest request) {
return METHOD_GET.equalsIgnoreCase(request.getMethod());
}
/**
* 是否为POST请求
*
* @param request 请求对象{@link HttpServletRequest}
* @return 是否为POST请求
*/
public static boolean isPostMethod(HttpServletRequest request) {
return METHOD_POST.equalsIgnoreCase(request.getMethod());
}
/**
* 是否为Multipart类型表单此类型表单用于文件上传
*
* @param request 请求对象{@link HttpServletRequest}
* @return 是否为Multipart类型表单此类型表单用于文件上传
*/
public static boolean isMultipart(HttpServletRequest request) {
if (false == isPostMethod(request)) {
return false;
}
String contentType = request.getContentType();
if (StrUtil.isBlank(contentType)) {
return false;
}
return contentType.toLowerCase().startsWith("multipart/");
}
// --------------------------------------------------------- Header end
// --------------------------------------------------------- Cookie start
/**
* 获得指定的Cookie
*
* @param httpServletRequest {@link HttpServletRequest}
* @param name cookie名
* @return Cookie对象
*/
public static Cookie getCookie(HttpServletRequest httpServletRequest, String name) {
return readCookieMap(httpServletRequest).get(name);
}
/**
* 将cookie封装到Map里面
*
* @param httpServletRequest {@link HttpServletRequest}
* @return Cookie map
*/
public static Map<String, Cookie> readCookieMap(HttpServletRequest httpServletRequest) {
final Cookie[] cookies = httpServletRequest.getCookies();
if (ArrayUtil.isEmpty(cookies)) {
return MapUtil.empty();
}
return IterUtil.toMap(
new ArrayIter<>(httpServletRequest.getCookies()),
new CaseInsensitiveMap<>(),
Cookie::getName);
}
/**
* 设定返回给客户端的Cookie
*
* @param response 响应对象{@link HttpServletResponse}
* @param cookie Servlet Cookie对象
*/
public static void addCookie(HttpServletResponse response, Cookie cookie) {
response.addCookie(cookie);
}
/**
* 设定返回给客户端的Cookie
*
* @param response 响应对象{@link HttpServletResponse}
* @param name Cookie名
* @param value Cookie值
*/
public static void addCookie(HttpServletResponse response, String name, String value) {
response.addCookie(new Cookie(name, value));
}
/**
* 设定返回给客户端的Cookie
*
* @param response 响应对象{@link HttpServletResponse}
* @param name cookie名
* @param value cookie值
* @param maxAgeInSeconds -1: 关闭浏览器清除Cookie. 0: 立即清除Cookie. &gt;0 : Cookie存在的秒数.
* @param path Cookie的有效路径
* @param domain the domain name within which this cookie is visible; form is according to RFC 2109
*/
public static void addCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds, String path, String domain) {
Cookie cookie = new Cookie(name, value);
if (domain != null) {
cookie.setDomain(domain);
}
cookie.setMaxAge(maxAgeInSeconds);
cookie.setPath(path);
addCookie(response, cookie);
}
/**
* 设定返回给客户端的Cookie<br>
* Path: "/"<br>
* No Domain
*
* @param response 响应对象{@link HttpServletResponse}
* @param name cookie名
* @param value cookie值
* @param maxAgeInSeconds -1: 关闭浏览器清除Cookie. 0: 立即清除Cookie. &gt;0 : Cookie存在的秒数.
*/
public static void addCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds) {
addCookie(response, name, value, maxAgeInSeconds, "/", null);
}
// --------------------------------------------------------- Cookie end
// --------------------------------------------------------- Response start
/**
* 获得PrintWriter
*
* @param response 响应对象{@link HttpServletResponse}
* @return 获得PrintWriter
* @throws IORuntimeException IO异常
*/
public static PrintWriter getWriter(HttpServletResponse response) throws IORuntimeException {
try {
return response.getWriter();
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
/**
* 返回数据给客户端
*
* @param response 响应对象{@link HttpServletResponse}
* @param text 返回的内容
* @param contentType 返回的类型
*/
public static void write(HttpServletResponse response, String text, String contentType) {
response.setContentType(contentType);
Writer writer = null;
try {
writer = response.getWriter();
writer.write(text);
writer.flush();
} catch (IOException e) {
throw new UtilException(e);
} finally {
IoUtil.close(writer);
}
}
/**
* 返回文件给客户端
*
* @param response 响应对象{@link HttpServletResponse}
* @param file 写出的文件对象
* @since 4.1.15
*/
public static void write(HttpServletResponse response, File file) {
final String fileName = file.getName();
final String contentType = ObjectUtil.defaultIfNull(FileUtil.getMimeType(fileName), "application/octet-stream");
BufferedInputStream in = null;
try {
in = FileUtil.getInputStream(file);
write(response, in, contentType, fileName);
} finally {
IoUtil.close(in);
}
}
/**
* 返回数据给客户端
*
* @param response 响应对象{@link HttpServletResponse}
* @param in 需要返回客户端的内容
* @param contentType 返回的类型可以使用{@link FileUtil#getMimeType(String)}获取对应扩展名的MIME信息
* <ul>
* <li>application/pdf</li>
* <li>application/vnd.ms-excel</li>
* <li>application/msword</li>
* <li>application/vnd.ms-powerpoint</li>
* </ul>
* docxxlsx 这种 office 2007 格式 设置 MIME;网页里面docx 文件是没问题但是下载下来了之后就变成doc格式了
* 参考<a href="https://my.oschina.net/shixiaobao17145/blog/32489">https://my.oschina.net/shixiaobao17145/blog/32489</a>
* <ul>
* <li>MIME_EXCELX_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";</li>
* <li>MIME_PPTX_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.presentation";</li>
* <li>MIME_WORDX_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";</li>
* <li>MIME_STREAM_TYPE = "application/octet-stream;charset=utf-8"; #原始字节流</li>
* </ul>
* @param fileName 文件名自动添加双引号
* @since 4.1.15
*/
public static void write(HttpServletResponse response, InputStream in, String contentType, String fileName) {
final String charset = ObjectUtil.defaultIfNull(response.getCharacterEncoding(), CharsetUtil.UTF_8);
response.setHeader("Content-Disposition", StrUtil.format("attachment;filename=\"{}\"",
URLUtil.encode(fileName, CharsetUtil.charset(charset))));
response.setContentType(contentType);
write(response, in);
}
/**
* 返回数据给客户端
*
* @param response 响应对象{@link HttpServletResponse}
* @param in 需要返回客户端的内容
* @param contentType 返回的类型
*/
public static void write(HttpServletResponse response, InputStream in, String contentType) {
response.setContentType(contentType);
write(response, in);
}
/**
* 返回数据给客户端
*
* @param response 响应对象{@link HttpServletResponse}
* @param in 需要返回客户端的内容
*/
public static void write(HttpServletResponse response, InputStream in) {
write(response, in, IoUtil.DEFAULT_BUFFER_SIZE);
}
/**
* 返回数据给客户端
*
* @param response 响应对象{@link HttpServletResponse}
* @param in 需要返回客户端的内容
* @param bufferSize 缓存大小
*/
public static void write(HttpServletResponse response, InputStream in, int bufferSize) {
ServletOutputStream out = null;
try {
out = response.getOutputStream();
IoUtil.copy(in, out, bufferSize);
} catch (IOException e) {
throw new UtilException(e);
} finally {
IoUtil.close(out);
IoUtil.close(in);
}
}
/**
* 设置响应的Header
*
* @param response 响应对象{@link HttpServletResponse}
* @param name
* @param value 可以是StringDate int
*/
public static void setHeader(HttpServletResponse response, String name, Object value) {
if (value instanceof String) {
response.setHeader(name, (String) value);
} else if (Date.class.isAssignableFrom(value.getClass())) {
response.setDateHeader(name, ((Date) value).getTime());
} else if (value instanceof Integer || "int".equalsIgnoreCase(value.getClass().getSimpleName())) {
response.setIntHeader(name, (int) value);
} else {
response.setHeader(name, value.toString());
}
}
// --------------------------------------------------------- Response end
}

View File

@ -561,7 +561,7 @@ public class ServletUtil {
* <li>application/vnd.ms-powerpoint</li>
* </ul>
* docxxlsx 这种 office 2007 格式 设置 MIME;网页里面docx 文件是没问题但是下载下来了之后就变成doc格式了
* 参考https://my.oschina.net/shixiaobao17145/blog/32489
* 参考<a href="https://my.oschina.net/shixiaobao17145/blog/32489">https://my.oschina.net/shixiaobao17145/blog/32489</a>
* <ul>
* <li>MIME_EXCELX_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";</li>
* <li>MIME_PPTX_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.presentation";</li>

View File

@ -340,7 +340,7 @@ public class Sftp extends AbstractFtp {
} catch (SftpException e) {
final String msg = e.getMessage();
// issue#I4P9ED@Gitee
if (msg.contains("No such file") || msg.contains("does not exist")) {
if (StrUtil.containsAnyIgnoreCase(msg, "No such file", "does not exist")) {
// 文件不存在直接返回false
// pr#378@Gitee
return false;

View File

@ -0,0 +1,7 @@
/**
* jetbrick-template实现特殊资源加载器<br>
* 模板引擎介绍见<a href="https://github.com/subchen/jetbrick-template-2x">https://github.com/subchen/jetbrick-template-2x</a>
*
* @author looly
*/
package cn.hutool.extra.template.engine.jetbrick.loader;

View File

@ -1,5 +1,6 @@
package cn.hutool.extra.servlet;
import cn.hutool.core.util.StrUtil;
import org.junit.Ignore;
import org.junit.Test;
@ -11,8 +12,8 @@ import java.nio.charset.StandardCharsets;
* ServletUtil工具类测试
*
* @author dazer
* @date 2021/3/24 15:02
* @see ServletUtil
* @see JakartaServletUtil
*/
public class ServletUtilTest {
@ -20,7 +21,7 @@ public class ServletUtilTest {
@Ignore
public void writeTest() {
HttpServletResponse response = null;
byte[] bytes = "地球是我们共同的家园,需要大家珍惜.".getBytes(StandardCharsets.UTF_8);
byte[] bytes = StrUtil.utf8Bytes("地球是我们共同的家园,需要大家珍惜.");
//下载文件
// 这里没法直接测试直接写到这里方便调用
@ -32,4 +33,21 @@ public class ServletUtilTest {
ServletUtil.write(response, new ByteArrayInputStream(bytes), contentType, fileName);
}
}
@Test
@Ignore
public void jakartaWriteTest() {
jakarta.servlet.http.HttpServletResponse response = null;
byte[] bytes = StrUtil.utf8Bytes("地球是我们共同的家园,需要大家珍惜.");
//下载文件
// 这里没法直接测试直接写到这里方便调用
//noinspection ConstantConditions
if (response != null) {
String fileName = "签名文件.pdf";
String contentType = "application/pdf";// application/octet-streamimage/jpegimage/gif
response.setCharacterEncoding(StandardCharsets.UTF_8.name()); // 必须设置否则乱码; 但是 safari乱码
JakartaServletUtil.write(response, new ByteArrayInputStream(bytes), contentType, fileName);
}
}
}

Some files were not shown because too many files have changed in this diff Show More