mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
Prepare release
This commit is contained in:
commit
da5e7334fe
1911
CHANGELOG.md
1911
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
1878
CHANGELOG_5.0-5.7.md
Executable file
1878
CHANGELOG_5.0-5.7.md
Executable file
File diff suppressed because it is too large
Load Diff
@ -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.
|
||||
|
@ -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平台没有测试,不能保证所有工具类或工具方法可用。
|
||||
|
@ -1 +1 @@
|
||||
5.8.0.M3
|
||||
5.8.0.M4
|
||||
|
@ -1 +1 @@
|
||||
var version = '5.8.0.M3'
|
||||
var version = '5.8.0.M4'
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
114
hutool-core/README.md
Executable 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等各种工具。
|
@ -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>
|
||||
|
@ -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},可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @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},可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @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},可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @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},可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 需要解析注解的元素:可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @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 需要解析注解的元素:可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
*/
|
||||
public CombinationAnnotationElement(AnnotatedElement element) {
|
||||
this(element, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param element 需要解析注解的元素:可以是Class、Method、Field、Constructor、ReflectPermission
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
506
hutool-core/src/main/java/cn/hutool/core/codec/Hashids.java
Executable file
506
hutool-core/src/main/java/cn/hutool/core/codec/Hashids.java
Executable 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取列表的部分
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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 转换方法map,key为方法参数类型,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)
|
||||
|
@ -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 转换后的字符串
|
||||
|
@ -74,7 +74,7 @@ public enum Week {
|
||||
/**
|
||||
* 获得星期对应{@link Calendar} 中的Week值
|
||||
*
|
||||
* @return 星期对应{@link Calendar} 中的Week值
|
||||
* @return 星期对应 {@link Calendar} 中的Week值
|
||||
*/
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
|
@ -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 大小
|
||||
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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}$";
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
83
hutool-core/src/main/java/cn/hutool/core/lang/ansi/Ansi8BitColor.java
Executable file
83
hutool-core/src/main/java/cn/hutool/core/lang/ansi/Ansi8BitColor.java
Executable 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 [ 30–37 m)</li>
|
||||
* <li>8-15: 高强度颜色(同ESC [ 90–97 m)</li>
|
||||
* <li>16-231(6 × 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;
|
||||
}
|
||||
}
|
109
hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiBackground.java
Executable file
109
hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiBackground.java
Executable 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;
|
||||
}
|
||||
|
||||
}
|
109
hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiColor.java
Executable file
109
hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiColor.java
Executable 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;
|
||||
}
|
||||
|
||||
}
|
18
hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiElement.java
Executable file
18
hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiElement.java
Executable 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();
|
||||
|
||||
}
|
65
hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiEncoder.java
Executable file
65
hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiEncoder.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
49
hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiStyle.java
Executable file
49
hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiStyle.java
Executable 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;
|
||||
}
|
||||
|
||||
}
|
6
hutool-core/src/main/java/cn/hutool/core/lang/ansi/package-info.java
Executable file
6
hutool-core/src/main/java/cn/hutool/core/lang/ansi/package-info.java
Executable file
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 命令行终端中ANSI 转义序列相关封装,如ANSI颜色等
|
||||
*
|
||||
* @author spring, looly
|
||||
*/
|
||||
package cn.hutool.core.lang.ansi;
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
73
hutool-core/src/main/java/cn/hutool/core/map/FuncMap.java
Executable file
73
hutool-core/src/main/java/cn/hutool/core/map/FuncMap.java
Executable 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;
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
322
hutool-core/src/main/java/cn/hutool/core/map/ReferenceConcurrentMap.java
Executable file
322
hutool-core/src/main/java/cn/hutool/core/map/ReferenceConcurrentMap.java
Executable 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
118
hutool-core/src/main/java/cn/hutool/core/map/TransMap.java
Executable file
118
hutool-core/src/main/java/cn/hutool/core/map/TransMap.java
Executable 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);
|
||||
}
|
35
hutool-core/src/main/java/cn/hutool/core/map/WeakConcurrentMap.java
Executable file
35
hutool-core/src/main/java/cn/hutool/core/map/WeakConcurrentMap.java
Executable 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);
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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>
|
||||
* see:https://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;
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,7 @@ public class StrMatcher {
|
||||
|
||||
/**
|
||||
* 匹配并提取匹配到的内容
|
||||
*
|
||||
* @param text 被匹配的文本
|
||||
* @return 匹配的map,key为变量名,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);
|
||||
|
@ -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() {
|
||||
}
|
||||
|
22
hutool-core/src/main/java/cn/hutool/core/thread/lock/NoReadWriteLock.java
Executable file
22
hutool-core/src/main/java/cn/hutool/core/thread/lock/NoReadWriteLock.java
Executable 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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按照指定规则,将一种类型的数组转换为另一种类型
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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(){
|
||||
|
||||
|
@ -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 {
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
20
hutool-core/src/test/java/cn/hutool/core/codec/HashidsTest.java
Executable file
20
hutool-core/src/test/java/cn/hutool/core/codec/HashidsTest.java
Executable 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);
|
||||
}
|
||||
}
|
@ -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覆盖了aa,ee覆盖了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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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";
|
||||
}));
|
||||
|
||||
|
13
hutool-core/src/test/java/cn/hutool/core/lang/ansi/AnsiEncoderTest.java
Executable file
13
hutool-core/src/test/java/cn/hutool/core/lang/ansi/AnsiEncoderTest.java
Executable 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);
|
||||
}
|
||||
}
|
22
hutool-core/src/test/java/cn/hutool/core/map/FuncMapTest.java
Executable file
22
hutool-core/src/test/java/cn/hutool/core/map/FuncMapTest.java
Executable 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"));
|
||||
}
|
||||
}
|
52
hutool-core/src/test/java/cn/hutool/core/map/WeakConcurrentMapTest.java
Executable file
52
hutool-core/src/test/java/cn/hutool/core/map/WeakConcurrentMapTest.java
Executable 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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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开始
|
||||
|
@ -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));
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -418,7 +418,7 @@ public class Ftp extends AbstractFtp {
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断ftp服务器文件是否存在
|
||||
* 判断ftp服务器目录内是否还有子元素(目录或文件)
|
||||
*
|
||||
* @param path 文件路径
|
||||
* @return 是否存在
|
||||
|
@ -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));
|
||||
|
643
hutool-extra/src/main/java/cn/hutool/extra/servlet/JakartaServletUtil.java
Executable file
643
hutool-extra/src/main/java/cn/hutool/extra/servlet/JakartaServletUtil.java
Executable 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>
|
||||
* 1、X-Forwarded-For
|
||||
* 2、X-Real-IP
|
||||
* 3、Proxy-Client-IP
|
||||
* 4、WL-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. >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. >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>
|
||||
* docx、xlsx 这种 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 值,可以是String,Date, 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
|
||||
}
|
@ -561,7 +561,7 @@ public class ServletUtil {
|
||||
* <li>application/vnd.ms-powerpoint</li>
|
||||
* </ul>
|
||||
* docx、xlsx 这种 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>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
@ -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-stream、image/jpeg、image/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
Loading…
x
Reference in New Issue
Block a user