mirror of
https://gitee.com/dromara/hutool.git
synced 2026-02-09 09:16:26 +08:00
Merge branch 'dromara:v5-dev' into v5-dev
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
<version>5.7.17-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-core</artifactId>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.hutool.core.clone;
|
||||
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
/**
|
||||
* 克隆默认实现接口,用于实现返回指定泛型类型的克隆方法
|
||||
*
|
||||
* @param <T> 泛型类型
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public interface DefaultCloneable<T> extends java.lang.Cloneable {
|
||||
|
||||
/**
|
||||
* 浅拷贝,提供默认的泛型返回值的clone方法。
|
||||
*
|
||||
* @return obj
|
||||
*/
|
||||
default T clone0() {
|
||||
try {
|
||||
return ReflectUtil.invoke(this, "clone");
|
||||
} catch (Exception e) {
|
||||
throw new CloneRuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,8 +62,11 @@ public class PercentCodec implements Serializable {
|
||||
* 存放安全编码
|
||||
*/
|
||||
private final BitSet safeCharacters;
|
||||
|
||||
/**
|
||||
* 是否编码空格为+
|
||||
* 是否编码空格为+<br>
|
||||
* 如果为{@code true},则将空格编码为"+",此项只在"application/x-www-form-urlencoded"中使用<br>
|
||||
* 如果为{@code false},则空格编码为"%20",此项一般用于URL的Query部分(RFC3986规范)
|
||||
*/
|
||||
private boolean encodeSpaceAsPlus = false;
|
||||
|
||||
@@ -130,7 +133,9 @@ public class PercentCodec implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否将空格编码为+
|
||||
* 是否将空格编码为+<br>
|
||||
* 如果为{@code true},则将空格编码为"+",此项只在"application/x-www-form-urlencoded"中使用<br>
|
||||
* 如果为{@code false},则空格编码为"%20",此项一般用于URL的Query部分(RFC3986规范)
|
||||
*
|
||||
* @param encodeSpaceAsPlus 是否将空格编码为+
|
||||
* @return this
|
||||
@@ -174,6 +179,7 @@ public class PercentCodec implements Serializable {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 兼容双字节的Unicode符处理(如部分emoji)
|
||||
byte[] ba = buf.toByteArray();
|
||||
for (byte toEncode : ba) {
|
||||
// Converting each byte in the buffer
|
||||
|
||||
@@ -179,7 +179,7 @@ public class CollStreamUtil {
|
||||
Set<K> key = new HashSet<>();
|
||||
key.addAll(map1.keySet());
|
||||
key.addAll(map2.keySet());
|
||||
Map<K, V> map = new HashMap<>();
|
||||
Map<K, V> map = MapUtil.newHashMap(key.size());
|
||||
for (K t : key) {
|
||||
X x = map1.get(t);
|
||||
Y y = map2.get(t);
|
||||
|
||||
@@ -693,7 +693,7 @@ public class DateUtil extends CalendarUtil {
|
||||
* @since 5.7.14
|
||||
*/
|
||||
public static DateTime parse(CharSequence dateStr, DateParser parser, boolean lenient) {
|
||||
return new DateTime(dateStr, parser);
|
||||
return new DateTime(dateStr, parser, lenient);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,6 +32,7 @@ import java.io.PushbackInputStream;
|
||||
import java.io.PushbackReader;
|
||||
import java.io.Reader;
|
||||
import java.io.Serializable;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.io.Writer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
@@ -622,6 +623,9 @@ public class IoUtil extends NioUtil {
|
||||
if (in == null) {
|
||||
throw new IllegalArgumentException("The InputStream must not be null");
|
||||
}
|
||||
if(null != clazz){
|
||||
in.accept(clazz);
|
||||
}
|
||||
try {
|
||||
//noinspection unchecked
|
||||
return (T) in.readObject();
|
||||
@@ -1331,4 +1335,19 @@ public class IoUtil extends NioUtil {
|
||||
public static LineIter lineIter(InputStream in, Charset charset) {
|
||||
return new LineIter(in, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ByteArrayOutputStream} 转换为String
|
||||
* @param out {@link ByteArrayOutputStream}
|
||||
* @param charset 编码
|
||||
* @return 字符串
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public static String toStr(ByteArrayOutputStream out, Charset charset){
|
||||
try {
|
||||
return out.toString(charset.name());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package cn.hutool.core.io.resource;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -59,7 +60,7 @@ public class CharSequenceResource implements Resource, Serializable {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.name.toString();
|
||||
return StrUtil.str(this.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package cn.hutool.core.io.resource;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
|
||||
import java.io.File;
|
||||
@@ -18,11 +20,21 @@ public class FileResource implements Resource, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final File file;
|
||||
private final String name;
|
||||
|
||||
// ----------------------------------------------------------------------- Constructor start
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param path 文件绝对路径或相对ClassPath路径,但是这个路径不能指向一个jar包中的文件
|
||||
*/
|
||||
public FileResource(String path) {
|
||||
this(FileUtil.file(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造,文件名使用文件本身的名字,带扩展名
|
||||
*
|
||||
* @param path 文件
|
||||
* @since 4.4.1
|
||||
*/
|
||||
@@ -31,37 +43,31 @@ public class FileResource implements Resource, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* 构造,文件名使用文件本身的名字,带扩展名
|
||||
*
|
||||
* @param file 文件
|
||||
*/
|
||||
public FileResource(File file) {
|
||||
this(file, file.getName());
|
||||
this(file, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param file 文件
|
||||
* @param fileName 文件名,如果为null获取文件本身的文件名
|
||||
* @param fileName 文件名,带扩展名,如果为null获取文件本身的文件名
|
||||
*/
|
||||
public FileResource(File file, String fileName) {
|
||||
Assert.notNull(file, "File must be not null !");
|
||||
this.file = file;
|
||||
this.name = ObjectUtil.defaultIfNull(fileName, file.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param path 文件绝对路径或相对ClassPath路径,但是这个路径不能指向一个jar包中的文件
|
||||
*/
|
||||
public FileResource(String path) {
|
||||
this(FileUtil.file(path));
|
||||
}
|
||||
// ----------------------------------------------------------------------- Constructor end
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.file.getName();
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,6 +95,6 @@ public class FileResource implements Resource, Serializable {
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return (null == this.file) ? "null" : this.file.toString();
|
||||
return this.file.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ public class ConsoleTable {
|
||||
/**
|
||||
* 表格头信息
|
||||
*/
|
||||
private final List<List<String>> HEADER_LIST = new ArrayList<>();
|
||||
private final List<List<String>> headerList = new ArrayList<>();
|
||||
/**
|
||||
* 表格体信息
|
||||
*/
|
||||
private final List<List<String>> BODY_LIST = new ArrayList<>();
|
||||
private final List<List<String>> bodyList = new ArrayList<>();
|
||||
/**
|
||||
* 每列最大字符个数
|
||||
*/
|
||||
@@ -57,7 +57,7 @@ public class ConsoleTable {
|
||||
}
|
||||
List<String> l = new ArrayList<>();
|
||||
fillColumns(l, titles);
|
||||
HEADER_LIST.add(l);
|
||||
headerList.add(l);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ public class ConsoleTable {
|
||||
*/
|
||||
public ConsoleTable addBody(String... values) {
|
||||
List<String> l = new ArrayList<>();
|
||||
BODY_LIST.add(l);
|
||||
bodyList.add(l);
|
||||
fillColumns(l, values);
|
||||
return this;
|
||||
}
|
||||
@@ -101,9 +101,9 @@ public class ConsoleTable {
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
fillBorder(sb);
|
||||
fillRow(sb, HEADER_LIST);
|
||||
fillRow(sb, headerList);
|
||||
fillBorder(sb);
|
||||
fillRow(sb, BODY_LIST);
|
||||
fillRow(sb, bodyList);
|
||||
fillBorder(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -158,4 +158,4 @@ public class ConsoleTable {
|
||||
Console.print(toString());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +24,14 @@
|
||||
*/
|
||||
package cn.hutool.core.lang;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.lang.func.VoidFunc0;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
@@ -49,20 +52,6 @@ public class Opt<T> {
|
||||
*/
|
||||
private static final Opt<?> EMPTY = new Opt<>(null);
|
||||
|
||||
/**
|
||||
* 包裹里实际的元素
|
||||
*/
|
||||
private final T value;
|
||||
|
||||
/**
|
||||
* {@code Opt}的构造函数
|
||||
*
|
||||
* @param value 包裹里的元素
|
||||
*/
|
||||
private Opt(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个空的{@code Opt}
|
||||
*
|
||||
@@ -110,6 +99,31 @@ public class Opt<T> {
|
||||
return StrUtil.isBlankIfStr(value) ? empty() : new Opt<>(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个包裹里{@code List}集合可能为空的{@code Opt},额外判断了集合内元素为空的情况
|
||||
*
|
||||
* @param value 传入需要包裹的元素
|
||||
* @param <T> 包裹里元素的类型
|
||||
* @return 一个包裹里元素可能为空的 {@code Opt}
|
||||
*/
|
||||
public static <T> Opt<List<T>> ofEmptyAble(List<T> value) {
|
||||
return CollectionUtil.isEmpty(value) ? empty() : new Opt<>(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 包裹里实际的元素
|
||||
*/
|
||||
private final T value;
|
||||
|
||||
/**
|
||||
* {@code Opt}的构造函数
|
||||
*
|
||||
* @param value 包裹里的元素
|
||||
*/
|
||||
private Opt(T value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回包裹里的元素,取不到则为{@code null},注意!!!此处和{@link java.util.Optional#get()}不同的一点是本方法并不会抛出{@code NoSuchElementException}
|
||||
* 如果元素为空,则返回{@code null},如果需要一个绝对不能为{@code null}的值,则使用{@link #orElseThrow()}
|
||||
@@ -153,12 +167,14 @@ public class Opt<T> {
|
||||
* }</pre>
|
||||
*
|
||||
* @param action 你想要执行的操作
|
||||
* @return this
|
||||
* @throws NullPointerException 如果包裹里的值存在,但你传入的操作为{@code null}时抛出
|
||||
*/
|
||||
public void ifPresent(Consumer<? super T> action) {
|
||||
if (value != null) {
|
||||
public Opt<T> ifPresent(Consumer<? super T> action) {
|
||||
if (isPresent()) {
|
||||
action.accept(value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,14 +189,40 @@ public class Opt<T> {
|
||||
*
|
||||
* @param action 包裹里的值存在时的操作
|
||||
* @param emptyAction 包裹里的值不存在时的操作
|
||||
* @return this;
|
||||
* @throws NullPointerException 如果包裹里的值存在时,执行的操作为 {@code null}, 或者包裹里的值不存在时的操作为 {@code null},则抛出{@code NPE}
|
||||
*/
|
||||
public void ifPresentOrElse(Consumer<? super T> action, VoidFunc0 emptyAction) {
|
||||
if (value != null) {
|
||||
public Opt<T> ifPresentOrElse(Consumer<? super T> action, VoidFunc0 emptyAction) {
|
||||
if (isPresent()) {
|
||||
action.accept(value);
|
||||
} else {
|
||||
emptyAction.callWithRuntimeException();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 如果包裹里的值存在,就执行传入的值存在时的操作({@link Function#apply(Object)})支持链式调用、转换为其他类型
|
||||
* 否则执行传入的值不存在时的操作({@link VoidFunc0}中的{@link VoidFunc0#call()})
|
||||
*
|
||||
* <p>
|
||||
* 如果值存在就转换为大写,否则用{@code Console.error}打印另一句字符串
|
||||
* <pre>{@code
|
||||
* String hutool = Opt.ofBlankAble("hutool").mapOrElse(String::toUpperCase, () -> Console.log("yes")).mapOrElse(String::intern, () -> Console.log("Value is not present~")).get();
|
||||
* }</pre>
|
||||
*
|
||||
* @param mapper 包裹里的值存在时的操作
|
||||
* @param emptyAction 包裹里的值不存在时的操作
|
||||
* @throws NullPointerException 如果包裹里的值存在时,执行的操作为 {@code null}, 或者包裹里的值不存在时的操作为 {@code null},则抛出{@code NPE}
|
||||
*/
|
||||
public <U> Opt<U> mapOrElse(Function<? super T, ? extends U> mapper, VoidFunc0 emptyAction) {
|
||||
if (isPresent()) {
|
||||
return ofNullable(mapper.apply(value));
|
||||
} else {
|
||||
emptyAction.callWithRuntimeException();
|
||||
return empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,6 +284,28 @@ public class Opt<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果包裹里的值存在,就执行传入的操作({@link Function#apply})并返回该操作返回值
|
||||
* 如果不存在,返回一个空的{@code Opt}
|
||||
* 和 {@link Opt#map}的区别为 传入的操作返回值必须为 {@link Optional}
|
||||
*
|
||||
* @param mapper 值存在时执行的操作
|
||||
* @param <U> 操作返回值的类型
|
||||
* @return 如果包裹里的值存在,就执行传入的操作({@link Function#apply})并返回该操作返回值
|
||||
* 如果不存在,返回一个空的{@code Opt}
|
||||
* @throws NullPointerException 如果给定的操作为 {@code null}或者给定的操作执行结果为 {@code null},抛出 {@code NPE}
|
||||
* @see Optional#flatMap(Function)
|
||||
* @since 5.7.16
|
||||
*/
|
||||
public <U> Opt<U> flattedMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
|
||||
Objects.requireNonNull(mapper);
|
||||
if (isEmpty()) {
|
||||
return empty();
|
||||
} else {
|
||||
return ofNullable(mapper.apply(value).orElse(null));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果包裹里元素的值存在,就执行对应的操作,并返回本身
|
||||
* 如果不存在,返回一个空的{@code Opt}
|
||||
@@ -395,6 +459,32 @@ public class Opt<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 {@link Optional}对象
|
||||
*
|
||||
* @return {@link Optional}对象
|
||||
* @since 5.7.16
|
||||
*/
|
||||
public Optional<T> toOptional() {
|
||||
return Optional.ofNullable(this.value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 执行一系列操作,如果途中发生 {@code NPE} 和 {@code IndexOutOfBoundsException},返回一个空的{@code Opt}
|
||||
*
|
||||
* @param supplier 操作
|
||||
* @param <T> 类型
|
||||
* @return 操作执行后的值
|
||||
*/
|
||||
public static <T> Opt<T> exec(Supplier<T> supplier) {
|
||||
try {
|
||||
return Opt.ofNullable(supplier.get());
|
||||
} catch (NullPointerException | IndexOutOfBoundsException e) {
|
||||
return empty();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断传入参数是否与 {@code Opt}相等
|
||||
* 在以下情况下返回true
|
||||
|
||||
@@ -100,6 +100,11 @@ public interface RegexPool {
|
||||
* 生日
|
||||
*/
|
||||
String BIRTHDAY = "^(\\d{2,4})([/\\-.年]?)(\\d{1,2})([/\\-.月]?)(\\d{1,2})日?$";
|
||||
/**
|
||||
* URI<br>
|
||||
* 定义见:https://www.ietf.org/rfc/rfc3986.html#appendix-B
|
||||
*/
|
||||
String URI = "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?";
|
||||
/**
|
||||
* URL
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.hutool.core.lang.tree;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.lang.Filter;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
@@ -21,7 +22,7 @@ import java.util.function.Consumer;
|
||||
* @author liangbaikai
|
||||
* @since 5.2.1
|
||||
*/
|
||||
public class Tree<T> extends LinkedHashMap<String, Object> implements Node<T> {
|
||||
public class Tree<T> extends LinkedHashMap<String, Object> implements Node<T> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final TreeNodeConfig treeNodeConfig;
|
||||
@@ -175,6 +176,16 @@ import java.util.function.Consumer;
|
||||
return (List<Tree<T>>) this.get(treeNodeConfig.getChildrenKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有子节点,无子节点则此为叶子节点
|
||||
*
|
||||
* @return 是否有子节点
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public boolean hasChild() {
|
||||
return CollUtil.isNotEmpty(getChildren());
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归树并处理子树下的节点:
|
||||
*
|
||||
@@ -184,18 +195,67 @@ import java.util.function.Consumer;
|
||||
public void walk(Consumer<Tree<T>> consumer) {
|
||||
consumer.accept(this);
|
||||
final List<Tree<T>> children = getChildren();
|
||||
if(CollUtil.isNotEmpty(children)){
|
||||
children.forEach((tree)-> tree.walk(consumer));
|
||||
if (CollUtil.isNotEmpty(children)) {
|
||||
children.forEach((tree) -> tree.walk(consumer));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归过滤并生成新的树<br>
|
||||
* 通过{@link Filter}指定的过滤规则,本节点或子节点满足过滤条件,则保留当前节点,否则抛弃节点及其子节点
|
||||
*
|
||||
* @param filter 节点过滤规则函数,只需处理本级节点本身即可
|
||||
* @return 过滤后的节点,{@code null} 表示不满足过滤要求,丢弃之
|
||||
* @see #filter(Filter)
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public Tree<T> filterNew(Filter<Tree<T>> filter) {
|
||||
return cloneTree().filter(filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归过滤当前树,注意此方法会修改当前树<br>
|
||||
* 通过{@link Filter}指定的过滤规则,本节点或子节点满足过滤条件,则保留当前节点,否则抛弃节点及其子节点
|
||||
*
|
||||
* @param filter 节点过滤规则函数,只需处理本级节点本身即可
|
||||
* @return 过滤后的节点,{@code null} 表示不满足过滤要求,丢弃之
|
||||
* @see #filterNew(Filter)
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public Tree<T> filter(Filter<Tree<T>> filter) {
|
||||
final List<Tree<T>> children = getChildren();
|
||||
if (CollUtil.isNotEmpty(children)) {
|
||||
// 递归过滤子节点
|
||||
final List<Tree<T>> filteredChildren = new ArrayList<>(children.size());
|
||||
Tree<T> filteredChild;
|
||||
for (Tree<T> child : children) {
|
||||
filteredChild = child.filter(filter);
|
||||
if (null != filteredChild) {
|
||||
filteredChildren.add(filteredChild);
|
||||
}
|
||||
}
|
||||
if(CollUtil.isNotEmpty(filteredChildren)){
|
||||
// 子节点有符合过滤条件的节点,则本节点保留
|
||||
return this.setChildren(filteredChildren);
|
||||
} else {
|
||||
this.setChildren(null);
|
||||
}
|
||||
}
|
||||
|
||||
// 子节点都不符合过滤条件,检查本节点
|
||||
return filter.accept(this) ? this : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置子节点,设置后会覆盖所有原有子节点
|
||||
*
|
||||
* @param children 子节点列表
|
||||
* @param children 子节点列表,如果为{@code null}表示移除子节点
|
||||
* @return this
|
||||
*/
|
||||
public Tree<T> setChildren(List<Tree<T>> children) {
|
||||
if(null == children){
|
||||
this.remove(treeNodeConfig.getChildrenKey());
|
||||
}
|
||||
this.put(treeNodeConfig.getChildrenKey(), children);
|
||||
return this;
|
||||
}
|
||||
@@ -241,6 +301,34 @@ import java.util.function.Consumer;
|
||||
return stringWriter.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归克隆当前节点(即克隆整个树,保留字段值)<br>
|
||||
* 注意,此方法只会克隆节点,节点属性如果是引用类型,不会克隆
|
||||
*
|
||||
* @return 新的节点
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public Tree<T> cloneTree() {
|
||||
final Tree<T> result = ObjectUtil.clone(this);
|
||||
result.setChildren(cloneChildren());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归复制子节点
|
||||
*
|
||||
* @return 新的子节点列表
|
||||
*/
|
||||
private List<Tree<T>> cloneChildren() {
|
||||
final List<Tree<T>> children = getChildren();
|
||||
if (null == children) {
|
||||
return null;
|
||||
}
|
||||
final List<Tree<T>> newChildren = new ArrayList<>(children.size());
|
||||
children.forEach((t) -> newChildren.add(t.cloneTree()));
|
||||
return newChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印
|
||||
*
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.hutool.core.net;
|
||||
|
||||
import cn.hutool.core.codec.PercentCodec;
|
||||
|
||||
/**
|
||||
* application/x-www-form-urlencoded,遵循W3C HTML Form content types规范,如空格须转+,+须被编码<br>
|
||||
* 规范见:https://url.spec.whatwg.org/#urlencoded-serializing
|
||||
*
|
||||
* @since 5.7.16
|
||||
*/
|
||||
public class FormUrlencoded {
|
||||
|
||||
/**
|
||||
* query中的value,默认除"-", "_", ".", "*"外都编码<br>
|
||||
* 这个类似于JDK提供的{@link java.net.URLEncoder}
|
||||
*/
|
||||
public static final PercentCodec ALL = PercentCodec.of(RFC3986.UNRESERVED)
|
||||
.removeSafe('~').addSafe('*').setEncodeSpaceAsPlus(true);
|
||||
}
|
||||
@@ -3,7 +3,8 @@ package cn.hutool.core.net;
|
||||
import cn.hutool.core.codec.PercentCodec;
|
||||
|
||||
/**
|
||||
* rfc3986 : https://www.ietf.org/rfc/rfc3986.html 编码实现
|
||||
* rfc3986 : https://www.ietf.org/rfc/rfc3986.html 编码实现<br>
|
||||
* 定义见:https://www.ietf.org/rfc/rfc3986.html#appendix-A
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.7.16
|
||||
@@ -13,7 +14,7 @@ public class RFC3986 {
|
||||
/**
|
||||
* gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
|
||||
*/
|
||||
public static final PercentCodec GEN_DELIMS = PercentCodec.of(":/?#[]&");
|
||||
public static final PercentCodec GEN_DELIMS = PercentCodec.of(":/?#[]@");
|
||||
|
||||
/**
|
||||
* sub-delims = "!" / "$" / "{@code &}" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
|
||||
@@ -21,12 +22,14 @@ public class RFC3986 {
|
||||
public static final PercentCodec SUB_DELIMS = PercentCodec.of("!$&'()*+,;=");
|
||||
|
||||
/**
|
||||
* reserved = gen-delims / sub-delims
|
||||
* reserved = gen-delims / sub-delims<br>
|
||||
* see:https://www.ietf.org/rfc/rfc3986.html#section-2.2
|
||||
*/
|
||||
public static final PercentCodec RESERVED = GEN_DELIMS.orNew(SUB_DELIMS);
|
||||
|
||||
/**
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"<br>
|
||||
* see: https://www.ietf.org/rfc/rfc3986.html#section-2.3
|
||||
*/
|
||||
public static final PercentCodec UNRESERVED = PercentCodec.of(unreservedChars());
|
||||
|
||||
@@ -36,7 +39,8 @@ public class RFC3986 {
|
||||
public static final PercentCodec PCHAR = UNRESERVED.orNew(SUB_DELIMS).or(PercentCodec.of(":@"));
|
||||
|
||||
/**
|
||||
* segment = pchar
|
||||
* segment = pchar<br>
|
||||
* see: https://www.ietf.org/rfc/rfc3986.html#section-3.3
|
||||
*/
|
||||
public static final PercentCodec SEGMENT = PCHAR;
|
||||
/**
|
||||
@@ -60,15 +64,17 @@ public class RFC3986 {
|
||||
public static final PercentCodec FRAGMENT = QUERY;
|
||||
|
||||
/**
|
||||
* query中的key
|
||||
*/
|
||||
public static final PercentCodec QUERY_PARAM_NAME = PercentCodec.of(QUERY).removeSafe('&').removeSafe('=');
|
||||
|
||||
/**
|
||||
* query中的value
|
||||
* query中的value<br>
|
||||
* value不能包含"{@code &}",可以包含 "="
|
||||
*/
|
||||
public static final PercentCodec QUERY_PARAM_VALUE = PercentCodec.of(QUERY).removeSafe('&');
|
||||
|
||||
/**
|
||||
* query中的key<br>
|
||||
* key不能包含"{@code &}" 和 "="
|
||||
*/
|
||||
public static final PercentCodec QUERY_PARAM_NAME = PercentCodec.of(QUERY_PARAM_VALUE).removeSafe('=');
|
||||
|
||||
/**
|
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
*
|
||||
|
||||
@@ -41,9 +41,10 @@ public class URLDecoder implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 解码
|
||||
* 解码<br>
|
||||
* 规则见:https://url.spec.whatwg.org/#urlencoded-parsing
|
||||
* <pre>
|
||||
* 1. 将+和%20转换为空格 ;
|
||||
* 1. 将+和%20转换为空格(" ");
|
||||
* 2. 将"%xy"转换为文本形式,xy是两位16进制的数值;
|
||||
* 3. 跳过不符合规范的%形式,直接输出
|
||||
* </pre>
|
||||
@@ -66,10 +67,13 @@ public class URLDecoder implements Serializable {
|
||||
*
|
||||
* @param str 包含URL编码后的字符串
|
||||
* @param isPlusToSpace 是否+转换为空格
|
||||
* @param charset 编码
|
||||
* @param charset 编码,{@code null}表示不做编码
|
||||
* @return 解码后的字符串
|
||||
*/
|
||||
public static String decode(String str, Charset charset, boolean isPlusToSpace) {
|
||||
if(null == charset){
|
||||
return str;
|
||||
}
|
||||
return StrUtil.str(decode(StrUtil.bytes(str, charset), isPlusToSpace), charset);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ import cn.hutool.core.util.StrUtil;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* URL编码工具
|
||||
* URL编码工具<br>
|
||||
* TODO 在6.x中移除此工具(无法很好区分URL编码和www-form编码)
|
||||
*
|
||||
* @since 5.7.13
|
||||
* @author looly
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.util.BitSet;
|
||||
|
||||
/**
|
||||
* URL编码,数据内容的类型是 application/x-www-form-urlencoded。
|
||||
* TODO 6.x移除此类,使用PercentCodec代替(无法很好区分URL编码和www-form编码)
|
||||
*
|
||||
* <pre>
|
||||
* 1.字符"a"-"z","A"-"Z","0"-"9",".","-","*",和"_" 都不会被编码;
|
||||
@@ -21,6 +22,7 @@ import java.util.BitSet;
|
||||
* </pre>
|
||||
*
|
||||
* @author looly
|
||||
* @see cn.hutool.core.codec.PercentCodec
|
||||
*/
|
||||
public class URLEncoder implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@@ -127,6 +127,9 @@ public class UrlPath {
|
||||
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (String segment : segments) {
|
||||
// 根据https://www.ietf.org/rfc/rfc3986.html#section-3.3定义
|
||||
// path的第一部分允许有":",其余部分不允许
|
||||
// 在此处的Path部分特指host之后的部分,即不包含第一部分
|
||||
builder.append(CharUtil.SLASH).append(RFC3986.SEGMENT_NZ_NC.encode(segment, charset));
|
||||
}
|
||||
if (withEngTag || StrUtil.isEmpty(builder)) {
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package cn.hutool.core.net.url;
|
||||
|
||||
import cn.hutool.core.codec.PercentCodec;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.IterUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.map.TableMap;
|
||||
import cn.hutool.core.net.FormUrlencoded;
|
||||
import cn.hutool.core.net.RFC3986;
|
||||
import cn.hutool.core.net.URLDecoder;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Iterator;
|
||||
@@ -25,6 +27,10 @@ import java.util.Map;
|
||||
public class UrlQuery {
|
||||
|
||||
private final TableMap<CharSequence, CharSequence> query;
|
||||
/**
|
||||
* 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+'
|
||||
*/
|
||||
private final boolean isFormUrlEncoded;
|
||||
|
||||
/**
|
||||
* 构建UrlQuery
|
||||
@@ -36,6 +42,17 @@ public class UrlQuery {
|
||||
return new UrlQuery(queryMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建UrlQuery
|
||||
*
|
||||
* @param queryMap 初始化的查询键值对
|
||||
* @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+'
|
||||
* @return UrlQuery
|
||||
*/
|
||||
public static UrlQuery of(Map<? extends CharSequence, ?> queryMap, boolean isFormUrlEncoded) {
|
||||
return new UrlQuery(queryMap, isFormUrlEncoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建UrlQuery
|
||||
*
|
||||
@@ -57,9 +74,21 @@ public class UrlQuery {
|
||||
* @since 5.5.8
|
||||
*/
|
||||
public static UrlQuery of(String queryStr, Charset charset, boolean autoRemovePath) {
|
||||
final UrlQuery urlQuery = new UrlQuery();
|
||||
urlQuery.parse(queryStr, charset, autoRemovePath);
|
||||
return urlQuery;
|
||||
return of(queryStr, charset, autoRemovePath, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建UrlQuery
|
||||
*
|
||||
* @param queryStr 初始化的查询字符串
|
||||
* @param charset decode用的编码,null表示不做decode
|
||||
* @param autoRemovePath 是否自动去除path部分,{@code true}则自动去除第一个?前的内容
|
||||
* @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+'
|
||||
* @return UrlQuery
|
||||
* @since 5.7.16
|
||||
*/
|
||||
public static UrlQuery of(String queryStr, Charset charset, boolean autoRemovePath, boolean isFormUrlEncoded) {
|
||||
return new UrlQuery(isFormUrlEncoded).parse(queryStr, charset, autoRemovePath);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,15 +101,37 @@ public class UrlQuery {
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param queryMap 初始化的查询键值对
|
||||
* @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+'
|
||||
* @since 5.7.16
|
||||
*/
|
||||
public UrlQuery(boolean isFormUrlEncoded) {
|
||||
this(null, isFormUrlEncoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param queryMap 初始化的查询键值对
|
||||
*/
|
||||
public UrlQuery(Map<? extends CharSequence, ?> queryMap) {
|
||||
this(queryMap, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param queryMap 初始化的查询键值对
|
||||
* @param isFormUrlEncoded 是否为x-www-form-urlencoded模式,此模式下空格会编码为'+'
|
||||
* @since 5.7.16
|
||||
*/
|
||||
public UrlQuery(Map<? extends CharSequence, ?> queryMap, boolean isFormUrlEncoded) {
|
||||
if (MapUtil.isNotEmpty(queryMap)) {
|
||||
query = new TableMap<>(queryMap.size());
|
||||
addAll(queryMap);
|
||||
} else {
|
||||
query = new TableMap<>(MapUtil.DEFAULT_INITIAL_CAPACITY);
|
||||
}
|
||||
this.isFormUrlEncoded = isFormUrlEncoded;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,6 +195,103 @@ public class UrlQuery {
|
||||
}
|
||||
}
|
||||
|
||||
return doParse(queryStr, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得查询的Map
|
||||
*
|
||||
* @return 查询的Map,只读
|
||||
*/
|
||||
public Map<CharSequence, CharSequence> getQueryMap() {
|
||||
return MapUtil.unmodifiable(this.query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询值
|
||||
*
|
||||
* @param key 键
|
||||
* @return 值
|
||||
*/
|
||||
public CharSequence get(CharSequence key) {
|
||||
if (MapUtil.isEmpty(this.query)) {
|
||||
return null;
|
||||
}
|
||||
return this.query.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL查询字符串,即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式。<br>
|
||||
* 对于{@code null}处理规则如下:
|
||||
* <ul>
|
||||
* <li>如果key为{@code null},则这个键值对忽略</li>
|
||||
* <li>如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param charset encode编码,null表示不做encode编码
|
||||
* @return URL查询字符串
|
||||
*/
|
||||
public String build(Charset charset) {
|
||||
if (isFormUrlEncoded) {
|
||||
return build(FormUrlencoded.ALL, FormUrlencoded.ALL, charset);
|
||||
}
|
||||
|
||||
return build(RFC3986.QUERY_PARAM_NAME, RFC3986.QUERY_PARAM_VALUE, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL查询字符串,即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式。<br>
|
||||
* 对于{@code null}处理规则如下:
|
||||
* <ul>
|
||||
* <li>如果key为{@code null},则这个键值对忽略</li>
|
||||
* <li>如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param keyCoder 键值对中键的编码器
|
||||
* @param valueCoder 键值对中值的编码器
|
||||
* @param charset encode编码,null表示不做encode编码
|
||||
* @return URL查询字符串
|
||||
* @since 5.7.16
|
||||
*/
|
||||
public String build(PercentCodec keyCoder, PercentCodec valueCoder, Charset charset) {
|
||||
if (MapUtil.isEmpty(this.query)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
CharSequence name;
|
||||
CharSequence value;
|
||||
for (Map.Entry<CharSequence, CharSequence> entry : this.query) {
|
||||
name = entry.getKey();
|
||||
if (null != name) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append("&");
|
||||
}
|
||||
sb.append(keyCoder.encode(name, charset));
|
||||
value = entry.getValue();
|
||||
if (null != value) {
|
||||
sb.append("=").append(valueCoder.encode(value, charset));
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return build(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析URL中的查询字符串<br>
|
||||
* 规则见:https://url.spec.whatwg.org/#urlencoded-parsing
|
||||
*
|
||||
* @param queryStr 查询字符串,类似于key1=v1&key2=&key3=v3
|
||||
* @param charset decode编码,null表示不做decode
|
||||
* @return this
|
||||
* @since 5.5.8
|
||||
*/
|
||||
private UrlQuery doParse(String queryStr, Charset charset) {
|
||||
final int len = queryStr.length();
|
||||
String name = null;
|
||||
int pos = 0; // 未处理字符开始位置
|
||||
@@ -188,68 +336,6 @@ public class UrlQuery {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得查询的Map
|
||||
*
|
||||
* @return 查询的Map,只读
|
||||
*/
|
||||
public Map<CharSequence, CharSequence> getQueryMap() {
|
||||
return MapUtil.unmodifiable(this.query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取查询值
|
||||
*
|
||||
* @param key 键
|
||||
* @return 值
|
||||
*/
|
||||
public CharSequence get(CharSequence key) {
|
||||
if (MapUtil.isEmpty(this.query)) {
|
||||
return null;
|
||||
}
|
||||
return this.query.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建URL查询字符串,即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式。<br>
|
||||
* 对于{@code null}处理规则如下:
|
||||
* <ul>
|
||||
* <li>如果key为{@code null},则这个键值对忽略</li>
|
||||
* <li>如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param charset encode编码,null表示不做encode编码
|
||||
* @return URL查询字符串
|
||||
*/
|
||||
public String build(Charset charset) {
|
||||
if (MapUtil.isEmpty(this.query)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
CharSequence name;
|
||||
CharSequence value;
|
||||
for (Map.Entry<CharSequence, CharSequence> entry : this.query) {
|
||||
name = entry.getKey();
|
||||
if (null != name) {
|
||||
if(sb.length() >0){
|
||||
sb.append("&");
|
||||
}
|
||||
sb.append(RFC3986.QUERY_PARAM_NAME.encode(name, charset));
|
||||
value = entry.getValue();
|
||||
if (null != value) {
|
||||
sb.append("=").append(RFC3986.QUERY_PARAM_VALUE.encode(value, charset));
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return build(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象转换为字符串,用于URL的Query中
|
||||
*
|
||||
@@ -283,11 +369,11 @@ public class UrlQuery {
|
||||
*/
|
||||
private void addParam(String key, String value, Charset charset) {
|
||||
if (null != key) {
|
||||
final String actualKey = URLUtil.decode(key, charset);
|
||||
this.query.put(actualKey, StrUtil.nullToEmpty(URLUtil.decode(value, charset)));
|
||||
final String actualKey = URLDecoder.decode(key, charset, isFormUrlEncoded);
|
||||
this.query.put(actualKey, StrUtil.nullToEmpty(URLDecoder.decode(value, charset, isFormUrlEncoded)));
|
||||
} else if (null != value) {
|
||||
// name为空,value作为name,value赋值null
|
||||
this.query.put(URLUtil.decode(value, charset), null);
|
||||
this.query.put(URLDecoder.decode(value, charset, isFormUrlEncoded), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.lang.Filter;
|
||||
import cn.hutool.core.lang.Matcher;
|
||||
import cn.hutool.core.lang.func.Func1;
|
||||
import cn.hutool.core.text.finder.CharFinder;
|
||||
import cn.hutool.core.text.finder.Finder;
|
||||
import cn.hutool.core.text.finder.StrFinder;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
@@ -35,7 +38,7 @@ import java.util.function.Predicate;
|
||||
*/
|
||||
public class CharSequenceUtil {
|
||||
|
||||
public static final int INDEX_NOT_FOUND = -1;
|
||||
public static final int INDEX_NOT_FOUND = Finder.INDEX_NOT_FOUND;
|
||||
|
||||
/**
|
||||
* 字符串常量:{@code "null"} <br>
|
||||
@@ -1064,7 +1067,7 @@ public class CharSequenceUtil {
|
||||
* @param searchChar 被查找的字符
|
||||
* @return 位置
|
||||
*/
|
||||
public static int indexOf(final CharSequence str, char searchChar) {
|
||||
public static int indexOf(CharSequence str, char searchChar) {
|
||||
return indexOf(str, searchChar, 0);
|
||||
}
|
||||
|
||||
@@ -1087,29 +1090,17 @@ public class CharSequenceUtil {
|
||||
/**
|
||||
* 指定范围内查找指定字符
|
||||
*
|
||||
* @param str 字符串
|
||||
* @param text 字符串
|
||||
* @param searchChar 被查找的字符
|
||||
* @param start 起始位置,如果小于0,从0开始查找
|
||||
* @param end 终止位置,如果超过str.length()则默认查找到字符串末尾
|
||||
* @return 位置
|
||||
*/
|
||||
public static int indexOf(final CharSequence str, char searchChar, int start, int end) {
|
||||
if (isEmpty(str)) {
|
||||
public static int indexOf(CharSequence text, char searchChar, int start, int end) {
|
||||
if (isEmpty(text)) {
|
||||
return INDEX_NOT_FOUND;
|
||||
}
|
||||
final int len = str.length();
|
||||
if (start < 0 || start > len) {
|
||||
start = 0;
|
||||
}
|
||||
if (end > len || end < 0) {
|
||||
end = len;
|
||||
}
|
||||
for (int i = start; i < end; i++) {
|
||||
if (str.charAt(i) == searchChar) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return INDEX_NOT_FOUND;
|
||||
return new CharFinder(searchChar).setText(text).setEndIndex(end).start(start);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1168,40 +1159,22 @@ public class CharSequenceUtil {
|
||||
/**
|
||||
* 指定范围内查找字符串
|
||||
*
|
||||
* @param str 字符串
|
||||
* @param searchStr 需要查找位置的字符串
|
||||
* @param fromIndex 起始位置
|
||||
* @param text 字符串,空则返回-1
|
||||
* @param searchStr 需要查找位置的字符串,空则返回-1
|
||||
* @param from 起始位置(包含)
|
||||
* @param ignoreCase 是否忽略大小写
|
||||
* @return 位置
|
||||
* @since 3.2.1
|
||||
*/
|
||||
public static int indexOf(final CharSequence str, CharSequence searchStr, int fromIndex, boolean ignoreCase) {
|
||||
if (str == null || searchStr == null) {
|
||||
return INDEX_NOT_FOUND;
|
||||
}
|
||||
if (fromIndex < 0) {
|
||||
fromIndex = 0;
|
||||
}
|
||||
|
||||
final int endLimit = str.length() - searchStr.length() + 1;
|
||||
if (fromIndex > endLimit) {
|
||||
return INDEX_NOT_FOUND;
|
||||
}
|
||||
if (searchStr.length() == 0) {
|
||||
return fromIndex;
|
||||
}
|
||||
|
||||
if (false == ignoreCase) {
|
||||
// 不忽略大小写调用JDK方法
|
||||
return str.toString().indexOf(searchStr.toString(), fromIndex);
|
||||
}
|
||||
|
||||
for (int i = fromIndex; i < endLimit; i++) {
|
||||
if (isSubEquals(str, i, searchStr, 0, searchStr.length(), true)) {
|
||||
return i;
|
||||
public static int indexOf(CharSequence text, CharSequence searchStr, int from, boolean ignoreCase) {
|
||||
if (isEmpty(text) || isEmpty(searchStr)) {
|
||||
if (StrUtil.equals(text, searchStr)) {
|
||||
return 0;
|
||||
} else {
|
||||
return INDEX_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
return INDEX_NOT_FOUND;
|
||||
return new StrFinder(searchStr, ignoreCase).setText(text).start(from);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1212,7 +1185,7 @@ public class CharSequenceUtil {
|
||||
* @return 位置
|
||||
* @since 3.2.1
|
||||
*/
|
||||
public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) {
|
||||
public static int lastIndexOfIgnoreCase(CharSequence str, CharSequence searchStr) {
|
||||
return lastIndexOfIgnoreCase(str, searchStr, str.length());
|
||||
}
|
||||
|
||||
@@ -1226,7 +1199,7 @@ public class CharSequenceUtil {
|
||||
* @return 位置
|
||||
* @since 3.2.1
|
||||
*/
|
||||
public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, int fromIndex) {
|
||||
public static int lastIndexOfIgnoreCase(CharSequence str, CharSequence searchStr, int fromIndex) {
|
||||
return lastIndexOf(str, searchStr, fromIndex, true);
|
||||
}
|
||||
|
||||
@@ -1234,37 +1207,23 @@ public class CharSequenceUtil {
|
||||
* 指定范围内查找字符串<br>
|
||||
* fromIndex 为搜索起始位置,从后往前计数
|
||||
*
|
||||
* @param str 字符串
|
||||
* @param text 字符串
|
||||
* @param searchStr 需要查找位置的字符串
|
||||
* @param fromIndex 起始位置,从后往前计数
|
||||
* @param from 起始位置,从后往前计数
|
||||
* @param ignoreCase 是否忽略大小写
|
||||
* @return 位置
|
||||
* @since 3.2.1
|
||||
*/
|
||||
public static int lastIndexOf(final CharSequence str, final CharSequence searchStr, int fromIndex, boolean ignoreCase) {
|
||||
if (str == null || searchStr == null) {
|
||||
return INDEX_NOT_FOUND;
|
||||
}
|
||||
if (fromIndex < 0) {
|
||||
fromIndex = 0;
|
||||
}
|
||||
fromIndex = Math.min(fromIndex, str.length());
|
||||
|
||||
if (searchStr.length() == 0) {
|
||||
return fromIndex;
|
||||
}
|
||||
|
||||
if (false == ignoreCase) {
|
||||
// 不忽略大小写调用JDK方法
|
||||
return str.toString().lastIndexOf(searchStr.toString(), fromIndex);
|
||||
}
|
||||
|
||||
for (int i = fromIndex; i >= 0; i--) {
|
||||
if (isSubEquals(str, i, searchStr, 0, searchStr.length(), true)) {
|
||||
return i;
|
||||
public static int lastIndexOf(CharSequence text, CharSequence searchStr, int from, boolean ignoreCase) {
|
||||
if (isEmpty(text) || isEmpty(searchStr)) {
|
||||
if (StrUtil.equals(text, searchStr)) {
|
||||
return 0;
|
||||
} else {
|
||||
return INDEX_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
return INDEX_NOT_FOUND;
|
||||
return new StrFinder(searchStr, ignoreCase)
|
||||
.setText(text).setNegative(true).start(from);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4063,6 +4022,19 @@ public class CharSequenceUtil {
|
||||
return NamingCase.toCamelCase(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将连接符方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。<br>
|
||||
* 例如:hello_world=》helloWorld; hello-world=》helloWorld
|
||||
*
|
||||
* @param name 转换前的下划线大写方式命名的字符串
|
||||
* @param symbol 连接符
|
||||
* @return 转换后的驼峰式命名的字符串
|
||||
* @see NamingCase#toCamelCase(CharSequence, char)
|
||||
*/
|
||||
public static String toCamelCase(CharSequence name, char symbol) {
|
||||
return NamingCase.toCamelCase(name, symbol);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------ isSurround
|
||||
|
||||
/**
|
||||
|
||||
@@ -58,7 +58,7 @@ public class NamingCase {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将驼峰式命名的字符串转换为使用符号连接方式。如果转换前的驼峰式命名的字符串为空,则返回空字符串。<br>
|
||||
* 将驼峰式命名的字符串转换为使用符号连接方式。如果转换前的驼峰式命名的字符串为空,则返回空字符串。
|
||||
*
|
||||
* @param str 转换前的驼峰式命名的字符串,也可以为符号连接形式
|
||||
* @param symbol 连接符
|
||||
@@ -150,19 +150,31 @@ public class NamingCase {
|
||||
* @return 转换后的驼峰式命名的字符串
|
||||
*/
|
||||
public static String toCamelCase(CharSequence name) {
|
||||
return toCamelCase(name, CharUtil.UNDERLINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将连接符方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。
|
||||
*
|
||||
* @param name 转换前的自定义方式命名的字符串
|
||||
* @param symbol 连接符
|
||||
* @return 转换后的驼峰式命名的字符串
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public static String toCamelCase(CharSequence name, char symbol) {
|
||||
if (null == name) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String name2 = name.toString();
|
||||
if (StrUtil.contains(name2, CharUtil.UNDERLINE)) {
|
||||
if (StrUtil.contains(name2, symbol)) {
|
||||
final int length = name2.length();
|
||||
final StringBuilder sb = new StringBuilder(length);
|
||||
boolean upperCase = false;
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = name2.charAt(i);
|
||||
|
||||
if (c == CharUtil.UNDERLINE) {
|
||||
if (c == symbol) {
|
||||
upperCase = true;
|
||||
} else if (upperCase) {
|
||||
sb.append(Character.toUpperCase(c));
|
||||
@@ -176,4 +188,5 @@ public class NamingCase {
|
||||
return name2;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public class StrFormatter {
|
||||
* 如果想输出占位符使用 \\转义即可,如果想输出占位符之前的 \ 使用双转义符 \\\\ 即可<br>
|
||||
* 例:<br>
|
||||
* 通常使用:format("this is {} for {}", "{}", "a", "b") =》 this is a for b<br>
|
||||
* 转义{}: format("this is \\{} for {}", "{}", "a", "b") =》 this is \{} for a<br>
|
||||
* 转义{}: format("this is \\{} for {}", "{}", "a", "b") =》 this is {} for a<br>
|
||||
* 转义\: format("this is \\\\{} for {}", "{}", "a", "b") =》 this is \a for b<br>
|
||||
*
|
||||
* @param strPattern 字符串模板
|
||||
|
||||
@@ -169,10 +169,9 @@ public class StrSplitter {
|
||||
* @param ignoreEmpty 是否忽略空串
|
||||
* @param ignoreCase 是否忽略大小写
|
||||
* @return 切分后的集合
|
||||
* @since 3.2.1
|
||||
*/
|
||||
public static List<String> split(CharSequence text, char separator, int limit, boolean isTrim, boolean ignoreEmpty, boolean ignoreCase) {
|
||||
return split(text, separator, limit, ignoreEmpty, trimFunc(isTrim));
|
||||
return split(text, separator, limit, ignoreEmpty, ignoreCase, trimFunc(isTrim));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,8 @@ import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
|
||||
/**
|
||||
* 字符查找器
|
||||
* 字符查找器<br>
|
||||
* 查找指定字符在字符串中的位置信息
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.7.14
|
||||
@@ -38,10 +39,18 @@ public class CharFinder extends TextFinder {
|
||||
@Override
|
||||
public int start(int from) {
|
||||
Assert.notNull(this.text, "Text to find must be not null!");
|
||||
final int length = text.length();
|
||||
for (int i = from; i < length; i++) {
|
||||
if (NumberUtil.equals(c, text.charAt(i), caseInsensitive)) {
|
||||
return i;
|
||||
final int limit = getValidEndIndex();
|
||||
if(negative){
|
||||
for (int i = from; i > limit; i--) {
|
||||
if (NumberUtil.equals(c, text.charAt(i), caseInsensitive)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
} else{
|
||||
for (int i = from; i < limit; i++) {
|
||||
if (NumberUtil.equals(c, text.charAt(i), caseInsensitive)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
|
||||
@@ -4,7 +4,8 @@ import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.lang.Matcher;
|
||||
|
||||
/**
|
||||
* 字符匹配查找器
|
||||
* 字符匹配查找器<br>
|
||||
* 查找满足指定{@link Matcher} 匹配的字符所在位置,此类长用于查找某一类字符,如数字等
|
||||
*
|
||||
* @since 5.7.14
|
||||
* @author looly
|
||||
@@ -25,10 +26,18 @@ public class CharMatcherFinder extends TextFinder {
|
||||
@Override
|
||||
public int start(int from) {
|
||||
Assert.notNull(this.text, "Text to find must be not null!");
|
||||
final int length = text.length();
|
||||
for (int i = from; i < length; i++) {
|
||||
if(matcher.match(text.charAt(i))){
|
||||
return i;
|
||||
final int limit = getValidEndIndex();
|
||||
if(negative){
|
||||
for (int i = from; i > limit; i--) {
|
||||
if(matcher.match(text.charAt(i))){
|
||||
return i;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = from; i < limit; i++) {
|
||||
if(matcher.match(text.charAt(i))){
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
|
||||
@@ -8,10 +8,12 @@ package cn.hutool.core.text.finder;
|
||||
*/
|
||||
public interface Finder {
|
||||
|
||||
int INDEX_NOT_FOUND = -1;
|
||||
|
||||
/**
|
||||
* 返回开始位置,即起始字符位置(包含),未找到返回-1
|
||||
*
|
||||
* @param from 查找的开始位置(包含
|
||||
* @param from 查找的开始位置(包含)
|
||||
* @return 起始字符位置,未找到返回-1
|
||||
*/
|
||||
int start(int from);
|
||||
|
||||
@@ -3,7 +3,8 @@ package cn.hutool.core.text.finder;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
|
||||
/**
|
||||
* 固定长度查找器
|
||||
* 固定长度查找器<br>
|
||||
* 给定一个长度,查找的位置为from + length,一般用于分段截取
|
||||
*
|
||||
* @since 5.7.14
|
||||
* @author looly
|
||||
@@ -24,9 +25,18 @@ public class LengthFinder extends TextFinder {
|
||||
@Override
|
||||
public int start(int from) {
|
||||
Assert.notNull(this.text, "Text to find must be not null!");
|
||||
final int result = from + length;
|
||||
if(result < text.length()){
|
||||
return result;
|
||||
final int limit = getValidEndIndex();
|
||||
int result;
|
||||
if(negative){
|
||||
result = from - length;
|
||||
if(result > limit){
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
result = from + length;
|
||||
if(result < limit){
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 正则查找器
|
||||
* 正则查找器<br>
|
||||
* 通过传入正则表达式,查找指定字符串中匹配正则的开始和结束位置
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.7.14
|
||||
@@ -40,17 +41,32 @@ public class PatternFinder extends TextFinder {
|
||||
return super.setText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextFinder setNegative(boolean negative) {
|
||||
throw new UnsupportedOperationException("Negative is invalid for Pattern!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int start(int from) {
|
||||
if (matcher.find(from)) {
|
||||
return matcher.start();
|
||||
// 只有匹配到的字符串结尾在limit范围内,才算找到
|
||||
if(matcher.end() <= getValidEndIndex()){
|
||||
return matcher.start();
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
return INDEX_NOT_FOUND;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int end(int start) {
|
||||
return matcher.end();
|
||||
final int end = matcher.end();
|
||||
final int limit;
|
||||
if(endIndex < 0){
|
||||
limit = text.length();
|
||||
}else{
|
||||
limit = Math.min(endIndex, text.length());
|
||||
}
|
||||
return end < limit ? end : INDEX_NOT_FOUND;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package cn.hutool.core.text.finder;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
|
||||
/**
|
||||
* 字符查找器
|
||||
* 字符串查找器
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.7.14
|
||||
@@ -12,25 +12,46 @@ import cn.hutool.core.util.StrUtil;
|
||||
public class StrFinder extends TextFinder {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final CharSequence str;
|
||||
private final CharSequence strToFind;
|
||||
private final boolean caseInsensitive;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param str 被查找的字符串
|
||||
* @param strToFind 被查找的字符串
|
||||
* @param caseInsensitive 是否忽略大小写
|
||||
*/
|
||||
public StrFinder(CharSequence str, boolean caseInsensitive) {
|
||||
Assert.notEmpty(str);
|
||||
this.str = str;
|
||||
public StrFinder(CharSequence strToFind, boolean caseInsensitive) {
|
||||
Assert.notEmpty(strToFind);
|
||||
this.strToFind = strToFind;
|
||||
this.caseInsensitive = caseInsensitive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int start(int from) {
|
||||
Assert.notNull(this.text, "Text to find must be not null!");
|
||||
return StrUtil.indexOf(text, str, from, caseInsensitive);
|
||||
final int subLen = strToFind.length();
|
||||
|
||||
if (from < 0) {
|
||||
from = 0;
|
||||
}
|
||||
int endLimit = getValidEndIndex();
|
||||
if (negative) {
|
||||
for (int i = from; i > endLimit; i--) {
|
||||
if (CharSequenceUtil.isSubEquals(text, i, strToFind, 0, subLen, caseInsensitive)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
endLimit = endLimit - subLen + 1;
|
||||
for (int i = from; i < endLimit; i++) {
|
||||
if (CharSequenceUtil.isSubEquals(text, i, strToFind, 0, subLen, caseInsensitive)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return INDEX_NOT_FOUND;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -38,6 +59,6 @@ public class StrFinder extends TextFinder {
|
||||
if (start < 0) {
|
||||
return -1;
|
||||
}
|
||||
return start + str.length();
|
||||
return start + strToFind.length();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package cn.hutool.core.text.finder;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
@@ -12,6 +14,8 @@ public abstract class TextFinder implements Finder, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected CharSequence text;
|
||||
protected int endIndex = -1;
|
||||
protected boolean negative;
|
||||
|
||||
/**
|
||||
* 设置被查找的文本
|
||||
@@ -20,7 +24,51 @@ public abstract class TextFinder implements Finder, Serializable {
|
||||
* @return this
|
||||
*/
|
||||
public TextFinder setText(CharSequence text) {
|
||||
this.text = text;
|
||||
this.text = Assert.notNull(text, "Text must be not null!");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置查找的结束位置<br>
|
||||
* 如果从前向后查找,结束位置最大为text.length()<br>
|
||||
* 如果从后向前,结束位置为-1
|
||||
*
|
||||
* @param endIndex 结束位置(不包括)
|
||||
* @return this
|
||||
*/
|
||||
public TextFinder setEndIndex(int endIndex) {
|
||||
this.endIndex = endIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否反向查找,{@code true}表示从后向前查找
|
||||
*
|
||||
* @param negative 结束位置(不包括)
|
||||
* @return this
|
||||
*/
|
||||
public TextFinder setNegative(boolean negative) {
|
||||
this.negative = negative;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取有效结束位置<br>
|
||||
* 如果{@link #endIndex}小于0,在反向模式下是开头(-1),正向模式是结尾(text.length())
|
||||
*
|
||||
* @return 有效结束位置
|
||||
*/
|
||||
protected int getValidEndIndex() {
|
||||
if(negative && -1 == endIndex){
|
||||
// 反向查找模式下,-1表示0前面的位置,即字符串反向末尾的位置
|
||||
return -1;
|
||||
}
|
||||
final int limit;
|
||||
if (endIndex < 0) {
|
||||
limit = endIndex + text.length() + 1;
|
||||
} else {
|
||||
limit = Math.min(endIndex, text.length());
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 文本查找实现,包括:
|
||||
* <ul>
|
||||
* <li>查找文本中的字符(正向、反向)</li>
|
||||
* <li>查找文本中的匹配字符(正向、反向)</li>
|
||||
* <li>查找文本中的字符串(正向、反向)</li>
|
||||
* <li>查找文本中匹配正则的字符串(正向)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.core.text.finder;
|
||||
@@ -0,0 +1,60 @@
|
||||
package cn.hutool.core.thread;
|
||||
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
/**
|
||||
* {@link CompletableFuture}异步工具类<br>
|
||||
* {@link CompletableFuture} 是 Future 的改进,可以通过传入回调对象,在任务完成后调用之
|
||||
*
|
||||
* @author achao1441470436@gmail.com
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public class AsyncUtil {
|
||||
|
||||
/**
|
||||
* 等待所有任务执行完毕,包裹了异常
|
||||
*
|
||||
* @param tasks 并行任务
|
||||
* @throws UndeclaredThrowableException 未受检异常
|
||||
*/
|
||||
public static void waitAll(CompletableFuture<?>... tasks) {
|
||||
try {
|
||||
CompletableFuture.allOf(tasks).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new ThreadException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待任意一个任务执行完毕,包裹了异常
|
||||
*
|
||||
* @param tasks 并行任务
|
||||
* @throws UndeclaredThrowableException 未受检异常
|
||||
*/
|
||||
public static void waitAny(CompletableFuture<?>... tasks) {
|
||||
try {
|
||||
CompletableFuture.anyOf(tasks).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new ThreadException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取异步任务结果,包裹了异常
|
||||
*
|
||||
* @param task 异步任务
|
||||
* @param <T> 任务返回值类型
|
||||
* @return 任务返回值
|
||||
* @throws RuntimeException 未受检异常
|
||||
*/
|
||||
public static <T> T get(CompletableFuture<T> task) {
|
||||
try {
|
||||
return task.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new ThreadException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.hutool.core.thread;
|
||||
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 工具类异常
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public class ThreadException extends RuntimeException {
|
||||
private static final long serialVersionUID = 5253124428623713216L;
|
||||
|
||||
public ThreadException(Throwable e) {
|
||||
super(ExceptionUtil.getMessage(e), e);
|
||||
}
|
||||
|
||||
public ThreadException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ThreadException(String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params));
|
||||
}
|
||||
|
||||
public ThreadException(String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
public ThreadException(String message, Throwable throwable, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, throwable, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
public ThreadException(Throwable throwable, String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params), throwable);
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,8 @@ public class IdcardUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有效身份证号,忽略X的大小写
|
||||
* 是否有效身份证号,忽略X的大小写<br>
|
||||
* 如果身份证号码中含有空格始终返回{@code false}
|
||||
*
|
||||
* @param idCard 身份证号,支持18位、15位和港澳台的10位
|
||||
* @return 是否有效
|
||||
@@ -157,7 +158,7 @@ public class IdcardUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
idCard = idCard.trim();
|
||||
//idCard = idCard.trim();
|
||||
int length = idCard.length();
|
||||
switch (length) {
|
||||
case 18:// 18位身份证
|
||||
|
||||
@@ -12,7 +12,6 @@ import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -1362,13 +1361,12 @@ public class NumberUtil {
|
||||
throw new UtilException("Size is larger than range between begin and end!");
|
||||
}
|
||||
|
||||
Random ran = new Random();
|
||||
Set<Integer> set = new HashSet<>();
|
||||
Set<Integer> set = new HashSet<>(size, 1);
|
||||
while (set.size() < size) {
|
||||
set.add(begin + ran.nextInt(end - begin));
|
||||
set.add(begin + RandomUtil.randomInt(end - begin));
|
||||
}
|
||||
|
||||
return set.toArray(new Integer[size]);
|
||||
return set.toArray(new Integer[0]);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------- range
|
||||
|
||||
@@ -77,20 +77,14 @@ public class ReflectUtil {
|
||||
* 获得一个类中所有构造列表
|
||||
*
|
||||
* @param <T> 构造的对象类型
|
||||
* @param beanClass 类
|
||||
* @param beanClass 类,非{@code null}
|
||||
* @return 字段列表
|
||||
* @throws SecurityException 安全检查异常
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> Constructor<T>[] getConstructors(Class<T> beanClass) throws SecurityException {
|
||||
Assert.notNull(beanClass);
|
||||
Constructor<?>[] constructors = CONSTRUCTORS_CACHE.get(beanClass);
|
||||
if (null != constructors) {
|
||||
return (Constructor<T>[]) constructors;
|
||||
}
|
||||
|
||||
constructors = getConstructorsDirectly(beanClass);
|
||||
return (Constructor<T>[]) CONSTRUCTORS_CACHE.put(beanClass, constructors);
|
||||
return (Constructor<T>[]) CONSTRUCTORS_CACHE.get(beanClass, ()->getConstructorsDirectly(beanClass));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,7 +95,6 @@ public class ReflectUtil {
|
||||
* @throws SecurityException 安全检查异常
|
||||
*/
|
||||
public static Constructor<?>[] getConstructorsDirectly(Class<?> beanClass) throws SecurityException {
|
||||
Assert.notNull(beanClass);
|
||||
return beanClass.getDeclaredConstructors();
|
||||
}
|
||||
|
||||
@@ -179,13 +172,8 @@ public class ReflectUtil {
|
||||
* @throws SecurityException 安全检查异常
|
||||
*/
|
||||
public static Field[] getFields(Class<?> beanClass) throws SecurityException {
|
||||
Field[] allFields = FIELDS_CACHE.get(beanClass);
|
||||
if (null != allFields) {
|
||||
return allFields;
|
||||
}
|
||||
|
||||
allFields = getFieldsDirectly(beanClass, true);
|
||||
return FIELDS_CACHE.put(beanClass, allFields);
|
||||
Assert.notNull(beanClass);
|
||||
return FIELDS_CACHE.get(beanClass, ()->getFieldsDirectly(beanClass, true));
|
||||
}
|
||||
|
||||
|
||||
@@ -641,18 +629,13 @@ public class ReflectUtil {
|
||||
/**
|
||||
* 获得一个类中所有方法列表,包括其父类中的方法
|
||||
*
|
||||
* @param beanClass 类
|
||||
* @param beanClass 类,非{@code null}
|
||||
* @return 方法列表
|
||||
* @throws SecurityException 安全检查异常
|
||||
*/
|
||||
public static Method[] getMethods(Class<?> beanClass) throws SecurityException {
|
||||
Method[] allMethods = METHODS_CACHE.get(beanClass);
|
||||
if (null != allMethods) {
|
||||
return allMethods;
|
||||
}
|
||||
|
||||
allMethods = getMethodsDirectly(beanClass, true);
|
||||
return METHODS_CACHE.put(beanClass, allMethods);
|
||||
Assert.notNull(beanClass);
|
||||
return METHODS_CACHE.get(beanClass, ()-> getMethodsDirectly(beanClass, true));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -319,7 +319,8 @@ public class URLUtil extends URLEncodeUtil {
|
||||
|
||||
/**
|
||||
* 解码application/x-www-form-urlencoded字符<br>
|
||||
* 将%开头的16进制表示的内容解码。
|
||||
* 将%开头的16进制表示的内容解码。<br>
|
||||
* 规则见:https://url.spec.whatwg.org/#urlencoded-parsing
|
||||
*
|
||||
* @param content 被解码内容
|
||||
* @param charset 编码,null表示不解码
|
||||
@@ -327,9 +328,6 @@ public class URLUtil extends URLEncodeUtil {
|
||||
* @since 4.4.1
|
||||
*/
|
||||
public static String decode(String content, Charset charset) {
|
||||
if (null == charset) {
|
||||
return content;
|
||||
}
|
||||
return URLDecoder.decode(content, charset);
|
||||
}
|
||||
|
||||
@@ -344,9 +342,6 @@ public class URLUtil extends URLEncodeUtil {
|
||||
* @since 5.6.3
|
||||
*/
|
||||
public static String decode(String content, Charset charset, boolean isPlusToSpace) {
|
||||
if (null == charset) {
|
||||
return content;
|
||||
}
|
||||
return URLDecoder.decode(content, charset, isPlusToSpace);
|
||||
}
|
||||
|
||||
@@ -360,7 +355,7 @@ public class URLUtil extends URLEncodeUtil {
|
||||
* @throws UtilException UnsupportedEncodingException
|
||||
*/
|
||||
public static String decode(String content, String charset) throws UtilException {
|
||||
return decode(content, CharsetUtil.charset(charset));
|
||||
return decode(content, StrUtil.isEmpty(charset) ? null : CharsetUtil.charset(charset));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package cn.hutool.core.clone;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class DefaultCloneTest {
|
||||
|
||||
@Test
|
||||
public void clone0() {
|
||||
Car oldCar = new Car();
|
||||
oldCar.setId(1);
|
||||
oldCar.setWheelList(Stream.of(new Wheel("h")).collect(Collectors.toList()));
|
||||
|
||||
Car newCar = oldCar.clone0();
|
||||
Assert.assertEquals(oldCar.getId(), newCar.getId());
|
||||
Assert.assertEquals(oldCar.getWheelList(), newCar.getWheelList());
|
||||
|
||||
newCar.setId(2);
|
||||
Assert.assertNotEquals(oldCar.getId(), newCar.getId());
|
||||
newCar.getWheelList().add(new Wheel("s"));
|
||||
|
||||
Assert.assertNotSame(oldCar, newCar);
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
static class Car implements DefaultCloneable<Car> {
|
||||
private Integer id;
|
||||
private List<Wheel> wheelList;
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
static class Wheel {
|
||||
private String direction;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,14 @@ import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ZodiacTest {
|
||||
|
||||
|
||||
@Test
|
||||
public void getZodiacTest() {
|
||||
Assert.assertEquals("摩羯座", Zodiac.getZodiac(Month.JANUARY, 19));
|
||||
Assert.assertEquals("水瓶座", Zodiac.getZodiac(Month.JANUARY, 20));
|
||||
Assert.assertEquals("巨蟹座", Zodiac.getZodiac(6, 17));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getChineseZodiacTest() {
|
||||
Assert.assertEquals("狗", Zodiac.getChineseZodiac(1994));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.hutool.core.lang;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@@ -8,7 +9,11 @@ import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* {@link Opt}的单元测试
|
||||
@@ -43,11 +48,14 @@ public class OptTest {
|
||||
@Test
|
||||
@Ignore
|
||||
public void ifPresentOrElseTest() {
|
||||
// 这是jdk9中的新函数,直接照搬了过来
|
||||
// 存在就打印对应的值,不存在则用{@code System.err.println}打印另一句字符串
|
||||
Opt.ofNullable("Hello Hutool!").ifPresentOrElse(Console::log, () -> Console.error("Ops!Something is wrong!"));
|
||||
|
||||
Opt.empty().ifPresentOrElse(Console::log, () -> Console.error("Ops!Something is wrong!"));
|
||||
|
||||
// 拓展为支持链式调用
|
||||
Opt.empty().ifPresentOrElse(Console::log, () -> Console.error("Ops!Something is wrong!"))
|
||||
.ifPresentOrElse(Console::log, () -> Console.error("Ops!Something is wrong!"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -126,6 +134,61 @@ public class OptTest {
|
||||
Assert.assertNull(exceptionWithMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flattedMapTest() {
|
||||
// 和Optional兼容的flatMap
|
||||
List<User> userList = new ArrayList<>();
|
||||
// 以前,不兼容
|
||||
// Opt.ofNullable(userList).map(List::stream).flatMap(Stream::findFirst);
|
||||
// 现在,兼容
|
||||
User user = Opt.ofNullable(userList).map(List::stream)
|
||||
.flattedMap(Stream::findFirst).orElseGet(User.builder()::build);
|
||||
Assert.assertNull(user.getUsername());
|
||||
Assert.assertNull(user.getNickname());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ofEmptyAbleTest() {
|
||||
// 以前,输入一个CollectionUtil感觉要命,类似前缀的类一大堆,代码补全形同虚设(在项目中起码要输入完CollectionUtil才能在第一个调出这个函数)
|
||||
// 关键它还很常用,判空和判空集合真的太常用了...
|
||||
List<String> past = Opt.ofNullable(Collections.<String>emptyList()).filter(CollectionUtil::isNotEmpty).orElseGet(() -> Collections.singletonList("hutool"));
|
||||
// 现在,一个ofEmptyAble搞定
|
||||
List<String> hutool = Opt.ofEmptyAble(Collections.<String>emptyList()).orElseGet(() -> Collections.singletonList("hutool"));
|
||||
Assert.assertEquals(past, hutool);
|
||||
Assert.assertEquals(hutool, Collections.singletonList("hutool"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapOrElseTest() {
|
||||
// 如果值存在就转换为大写,否则打印一句字符串,支持链式调用、转换为其他类型
|
||||
String hutool = Opt.ofBlankAble("hutool").mapOrElse(String::toUpperCase, () -> Console.log("yes")).mapOrElse(String::intern, () -> Console.log("Value is not present~")).get();
|
||||
Assert.assertEquals("HUTOOL", hutool);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"MismatchedQueryAndUpdateOfCollection", "ConstantConditions"})
|
||||
@Test
|
||||
public void execTest() {
|
||||
// 有一些资深的程序员跟我说你这个lambda,双冒号语法糖看不懂...
|
||||
// 为了尊重资深程序员的意见,并且提升代码可读性,封装了一下 "try catch NPE 和 数组越界"的情况
|
||||
|
||||
// 以前这种写法,简洁但可读性稍低,对资深程序员不太友好
|
||||
List<String> last = null;
|
||||
String npeSituation = Opt.ofEmptyAble(last).flattedMap(l -> l.stream().findFirst()).orElse("hutool");
|
||||
String indexOutSituation = Opt.ofEmptyAble(last).map(l -> l.get(0)).orElse("hutool");
|
||||
|
||||
// 现在代码整洁度降低,但可读性up,如果再人说看不懂这代码...
|
||||
String npe = Opt.exec(() -> last.get(0)).orElse("hutool");
|
||||
String indexOut = Opt.exec(() -> {
|
||||
List<String> list = new ArrayList<>();
|
||||
// 你可以在里面写一长串调用链 list.get(0).getUser().getId()
|
||||
return list.get(0);
|
||||
}).orElse("hutool");
|
||||
Assert.assertEquals(npe, npeSituation);
|
||||
Assert.assertEquals(indexOut, indexOutSituation);
|
||||
Assert.assertEquals("hutool", npe);
|
||||
Assert.assertEquals("hutool", indexOut);
|
||||
}
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
|
||||
@@ -76,4 +76,48 @@ public class TreeTest {
|
||||
|
||||
Assert .assertEquals(7, ids.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cloneTreeTest(){
|
||||
final Tree<String> tree = TreeUtil.buildSingle(nodeList, "0");
|
||||
final Tree<String> cloneTree = tree.cloneTree();
|
||||
|
||||
List<String> ids = new ArrayList<>();
|
||||
cloneTree.walk((tr)-> ids.add(tr.getId()));
|
||||
|
||||
Assert .assertEquals(7, ids.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterTest(){
|
||||
// 经过过滤,丢掉"用户添加"节点
|
||||
final Tree<String> tree = TreeUtil.buildSingle(nodeList, "0");
|
||||
tree.filter((t)->{
|
||||
final CharSequence name = t.getName();
|
||||
return null != name && name.toString().contains("管理");
|
||||
});
|
||||
|
||||
List<String> ids = new ArrayList<>();
|
||||
tree.walk((tr)-> ids.add(tr.getId()));
|
||||
Assert .assertEquals(6, ids.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filterNewTest(){
|
||||
final Tree<String> tree = TreeUtil.buildSingle(nodeList, "0");
|
||||
|
||||
// 经过过滤,生成新的树
|
||||
Tree<String> newTree = tree.filterNew((t)->{
|
||||
final CharSequence name = t.getName();
|
||||
return null != name && name.toString().contains("管理");
|
||||
});
|
||||
|
||||
List<String> ids = new ArrayList<>();
|
||||
newTree.walk((tr)-> ids.add(tr.getId()));
|
||||
Assert .assertEquals(6, ids.size());
|
||||
|
||||
List<String> ids2 = new ArrayList<>();
|
||||
tree.walk((tr)-> ids2.add(tr.getId()));
|
||||
Assert .assertEquals(7, ids2.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.hutool.core.net;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class FormUrlencodedTest {
|
||||
|
||||
@Test
|
||||
public void encodeParamTest(){
|
||||
String encode = FormUrlencoded.ALL.encode("a+b", CharsetUtil.CHARSET_UTF_8);
|
||||
Assert.assertEquals("a%2Bb", encode);
|
||||
|
||||
encode = FormUrlencoded.ALL.encode("a b", CharsetUtil.CHARSET_UTF_8);
|
||||
Assert.assertEquals("a+b", encode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cn.hutool.core.net;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class RFC3986Test {
|
||||
|
||||
@Test
|
||||
public void encodeQueryTest(){
|
||||
final String encode = RFC3986.QUERY_PARAM_VALUE.encode("a=b", CharsetUtil.CHARSET_UTF_8);
|
||||
Assert.assertEquals("a=b", encode);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package cn.hutool.core.net;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class URLEncoderTest {
|
||||
|
||||
@Test
|
||||
public void encodeTest(){
|
||||
String encode = URLEncoder.DEFAULT.encode("+", CharsetUtil.CHARSET_UTF_8);
|
||||
Assert.assertEquals("+", encode);
|
||||
|
||||
encode = URLEncoder.DEFAULT.encode(" ", CharsetUtil.CHARSET_UTF_8);
|
||||
Assert.assertEquals("%20", encode);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package cn.hutool.core.net;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.net.url.UrlBuilder;
|
||||
import cn.hutool.core.net.url.UrlQuery;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
@@ -99,4 +100,26 @@ public class UrlQueryTest {
|
||||
query = URLUtil.buildQuery(map, StandardCharsets.UTF_8);
|
||||
Assert.assertEquals("password==&username%3D=SSM", query);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void plusTest(){
|
||||
// 根据RFC3986,在URL中,+是安全字符,即此符号不转义
|
||||
final String a = UrlQuery.of(MapUtil.of("a+b", "1+2")).build(CharsetUtil.CHARSET_UTF_8);
|
||||
Assert.assertEquals("a+b=1+2", a);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsePlusTest(){
|
||||
// 根据RFC3986,在URL中,+是安全字符,即此符号不转义
|
||||
final String a = UrlQuery.of("a+b=1+2", CharsetUtil.CHARSET_UTF_8)
|
||||
.build(CharsetUtil.CHARSET_UTF_8);
|
||||
Assert.assertEquals("a+b=1+2", a);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void spaceTest(){
|
||||
// 根据RFC3986,在URL中,空格编码为"%20"
|
||||
final String a = UrlQuery.of(MapUtil.of("a ", " ")).build(CharsetUtil.CHARSET_UTF_8);
|
||||
Assert.assertEquals("a%20=%20", a);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,5 +47,22 @@ public class CharSequenceUtilTest {
|
||||
Assert.assertEquals(str1, str2);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------ remove
|
||||
@Test
|
||||
public void indexOfTest(){
|
||||
int index = CharSequenceUtil.indexOf("abc123", '1');
|
||||
Assert.assertEquals(3, index);
|
||||
index = CharSequenceUtil.indexOf("abc123", '3');
|
||||
Assert.assertEquals(5, index);
|
||||
index = CharSequenceUtil.indexOf("abc123", 'a');
|
||||
Assert.assertEquals(0, index);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void indexOfTest2(){
|
||||
int index = CharSequenceUtil.indexOf("abc123", '1', 0, 3);
|
||||
Assert.assertEquals(-1, index);
|
||||
|
||||
index = CharSequenceUtil.indexOf("abc123", 'b', 0, 3);
|
||||
Assert.assertEquals(1, index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.hutool.core.text.finder;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CharFinderTest {
|
||||
|
||||
@Test
|
||||
public void startTest(){
|
||||
int start = new CharFinder('a').setText("cba123").start(2);
|
||||
Assert.assertEquals(2, start);
|
||||
|
||||
start = new CharFinder('c').setText("cba123").start(2);
|
||||
Assert.assertEquals(-1, start);
|
||||
|
||||
start = new CharFinder('3').setText("cba123").start(2);
|
||||
Assert.assertEquals(5, start);
|
||||
}
|
||||
@Test
|
||||
public void negativeStartTest(){
|
||||
int start = new CharFinder('a').setText("cba123").setNegative(true).start(2);
|
||||
Assert.assertEquals(2, start);
|
||||
|
||||
start = new CharFinder('2').setText("cba123").setNegative(true).start(2);
|
||||
Assert.assertEquals(-1, start);
|
||||
|
||||
start = new CharFinder('c').setText("cba123").setNegative(true).start(2);
|
||||
Assert.assertEquals(0, start);
|
||||
}
|
||||
}
|
||||
@@ -135,4 +135,18 @@ public class SplitIterTest {
|
||||
final List<String> strings = splitIter.toList(false);
|
||||
Assert.assertEquals(1, strings.size());
|
||||
}
|
||||
|
||||
// 切割字符串是空字符串时报错
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void splitByEmptyTest(){
|
||||
String text = "aa,bb,cc";
|
||||
SplitIter splitIter = new SplitIter(text,
|
||||
new StrFinder("", false),
|
||||
3,
|
||||
false
|
||||
);
|
||||
|
||||
final List<String> strings = splitIter.toList(false);
|
||||
Assert.assertEquals(1, strings.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.hutool.core.thread;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* CompletableFuture工具类测试
|
||||
*
|
||||
* @author <achao1441470436@gmail.com>
|
||||
* @since 2021/11/10 0010 21:15
|
||||
*/
|
||||
public class AsyncUtilTest {
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void waitAndGetTest() {
|
||||
CompletableFuture<String> hutool = CompletableFuture.supplyAsync(() -> {
|
||||
ThreadUtil.sleep(1, TimeUnit.SECONDS);
|
||||
return "hutool";
|
||||
});
|
||||
CompletableFuture<String> sweater = CompletableFuture.supplyAsync(() -> {
|
||||
ThreadUtil.sleep(2, TimeUnit.SECONDS);
|
||||
return "卫衣";
|
||||
});
|
||||
CompletableFuture<String> warm = CompletableFuture.supplyAsync(() -> {
|
||||
ThreadUtil.sleep(3, TimeUnit.SECONDS);
|
||||
return "真暖和";
|
||||
});
|
||||
// 等待完成
|
||||
AsyncUtil.waitAll(hutool, sweater, warm);
|
||||
// 获取结果
|
||||
Assert.isTrue("hutool卫衣真暖和".equals(AsyncUtil.get(hutool) + AsyncUtil.get(sweater) + AsyncUtil.get(warm)));
|
||||
}
|
||||
}
|
||||
@@ -400,4 +400,10 @@ public class NumberUtilTest {
|
||||
final String s = new BigDecimal(num).toPlainString();
|
||||
Assert.assertEquals("5344342.34", s);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateBySetTest(){
|
||||
final Integer[] integers = NumberUtil.generateBySet(10, 100, 5);
|
||||
Assert.assertEquals(5, integers.length);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ public class StrUtilTest {
|
||||
Assert.assertEquals(5, StrUtil.indexOfIgnoreCase("aabaabaa", "B", 3));
|
||||
Assert.assertEquals(-1, StrUtil.indexOfIgnoreCase("aabaabaa", "B", 9));
|
||||
Assert.assertEquals(2, StrUtil.indexOfIgnoreCase("aabaabaa", "B", -1));
|
||||
Assert.assertEquals(2, StrUtil.indexOfIgnoreCase("aabaabaa", "", 2));
|
||||
Assert.assertEquals(-1, StrUtil.indexOfIgnoreCase("aabaabaa", "", 2));
|
||||
Assert.assertEquals(-1, StrUtil.indexOfIgnoreCase("abc", "", 9));
|
||||
}
|
||||
|
||||
@@ -199,8 +199,8 @@ public class StrUtilTest {
|
||||
Assert.assertEquals(2, StrUtil.lastIndexOfIgnoreCase("aabaabaa", "B", 3));
|
||||
Assert.assertEquals(5, StrUtil.lastIndexOfIgnoreCase("aabaabaa", "B", 9));
|
||||
Assert.assertEquals(-1, StrUtil.lastIndexOfIgnoreCase("aabaabaa", "B", -1));
|
||||
Assert.assertEquals(2, StrUtil.lastIndexOfIgnoreCase("aabaabaa", "", 2));
|
||||
Assert.assertEquals(3, StrUtil.lastIndexOfIgnoreCase("abc", "", 9));
|
||||
Assert.assertEquals(-1, StrUtil.lastIndexOfIgnoreCase("aabaabaa", "", 2));
|
||||
Assert.assertEquals(-1, StrUtil.lastIndexOfIgnoreCase("abc", "", 9));
|
||||
Assert.assertEquals(0, StrUtil.lastIndexOfIgnoreCase("AAAcsd", "aaa"));
|
||||
}
|
||||
|
||||
@@ -384,6 +384,12 @@ public class StrUtilTest {
|
||||
|
||||
String abc1d = StrUtil.toCamelCase("abc_1d");
|
||||
Assert.assertEquals("abc1d", abc1d);
|
||||
|
||||
|
||||
String str2 = "Table-Test-Of-day";
|
||||
String result2 = StrUtil.toCamelCase(str2, CharUtil.DASHED);
|
||||
System.out.println(result2);
|
||||
Assert.assertEquals("tableTestOfDay", result2);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user