This commit is contained in:
Looly 2022-07-16 22:46:31 +08:00
parent 8e7d09cfc0
commit 681535c575
30 changed files with 1296 additions and 45 deletions

View File

@ -1,5 +1,7 @@
package cn.hutool.core.collection.partition;
import cn.hutool.core.lang.Assert;
import java.util.AbstractList;
import java.util.List;
@ -25,7 +27,7 @@ public class Partition<T> extends AbstractList<List<T>> {
* @param size 每个分区的长度
*/
public Partition(final List<T> list, final int size) {
this.list = list;
this.list = Assert.notNull(list);
this.size = Math.min(size, list.size());
}
@ -41,11 +43,9 @@ public class Partition<T> extends AbstractList<List<T>> {
// 此处采用动态计算以应对list变
final int size = this.size;
final int total = list.size();
int length = total / size;
if(total % size > 0){
length += 1;
}
return length;
// 类似于判断余数当总数非整份size时多余的数>=1则相当于被除数多一个size做到+1目的
// 类似于if(total % size > 0){length += 1;}
return (total + size - 1) / size;
}
@Override

View File

@ -85,6 +85,8 @@ public class TemporalAccessorConverter extends AbstractConverter {
protected TemporalAccessor convertInternal(final Class<?> targetClass, final Object value) {
if (value instanceof Long) {
return parseFromLong(targetClass, (Long) value);
}else if (value instanceof Integer) {
return parseFromLong(targetClass, ((Integer) value).longValue());
} else if (value instanceof TemporalAccessor) {
return parseFromTemporalAccessor(targetClass, (TemporalAccessor) value);
} else if (value instanceof Date) {

View File

@ -599,4 +599,28 @@ public class TimeUtil {
public static int weekOfYear(final TemporalAccessor date) {
return TemporalAccessorUtil.get(date, WeekFields.ISO.weekOfYear());
}
/**
* 比较两个日期是否为同一天
*
* @param date1 日期1
* @param date2 日期2
* @return 是否为同一天
* @since 5.8.5
*/
public static boolean isSameDay(final LocalDateTime date1, final LocalDateTime date2) {
return date1 != null && date2 != null && isSameDay(date1.toLocalDate(), date2.toLocalDate());
}
/**
* 比较两个日期是否为同一天
*
* @param date1 日期1
* @param date2 日期2
* @return 是否为同一天
* @since 5.8.5
*/
public static boolean isSameDay(final LocalDate date1, final LocalDate date2) {
return date1 != null && date2 != null && date1.isEqual(date2);
}
}

View File

@ -5,6 +5,7 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.SetUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.exceptions.CloneRuntimeException;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.func.Func0;
import cn.hutool.core.lang.func.LambdaUtil;
@ -23,11 +24,11 @@ import java.util.Map;
import java.util.Objects;
/**
* 字典对象扩充了HashMap中的方法
* 字典对象扩充了LinkedHashMap中的方法
*
* @author loolly
* @author looly
*/
public class Dict extends LinkedHashMap<String, Object> implements BasicTypeGetter<String> {
public class Dict extends CustomKeyMap<String, Object> implements BasicTypeGetter<String> {
private static final long serialVersionUID = 6135423866861206530L;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
@ -165,7 +166,7 @@ public class Dict extends LinkedHashMap<String, Object> implements BasicTypeGett
* @since 4.5.16
*/
public Dict(final int initialCapacity, final float loadFactor, final boolean caseInsensitive) {
super(initialCapacity, loadFactor);
super(new LinkedHashMap<>(initialCapacity, loadFactor));
this.caseInsensitive = caseInsensitive;
}
@ -568,37 +569,21 @@ public class Dict extends LinkedHashMap<String, Object> implements BasicTypeGett
}
// -------------------------------------------------------------------- Get end
@Override
public Object get(final Object key) {
return super.get(customKey((String) key));
}
@Override
public Object put(final String key, final Object value) {
return super.put(customKey(key), value);
}
@Override
public void putAll(final Map<? extends String, ?> m) {
m.forEach(this::put);
}
@Override
public Dict clone() {
return (Dict) super.clone();
try {
return (Dict) super.clone();
} catch (CloneNotSupportedException e) {
throw new CloneRuntimeException(e);
}
}
/**
* 将Key转为小写
*
* @param key KEY
* @return 小写KEY
*/
private String customKey(String key) {
@Override
protected String customKey(Object key) {
if (this.caseInsensitive && null != key) {
key = key.toLowerCase();
key = ((String)key).toLowerCase();
}
return key;
return (String) key;
}
/**

View File

@ -0,0 +1,312 @@
package cn.hutool.core.map;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.SetUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.ObjUtil;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
/**
* 基于多个{@link TreeEntry}构成的彼此平行的树结构构成的森林集合
*
* @param <K> key类型
* @param <V> value类型
* @author huangchengxing
* @see TreeEntry
*/
public interface ForestMap<K, V> extends Map<K, TreeEntry<K, V>> {
// ===================== Map接口方法的重定义 =====================
/**
* 添加一个节点效果等同于 {@code putNode(key, node.getValue())}
* <ul>
* <li>若key对应节点不存在则以传入的键值创建一个新的节点</li>
* <li>若key对应节点存在则将该节点的值替换为{@code node}指定的值</li>
* </ul>
*
* @param key 节点的key值
* @param node 节点
* @return 节点若key已有对应节点则返回具有旧值的节点否则返回null
* @see #putNode(Object, Object)
*/
@Override
default TreeEntry<K, V> put(K key, TreeEntry<K, V> node) {
return putNode(key, node.getValue());
}
/**
* 批量添加节点若节点具有父节点或者子节点则一并在当前实例中引入该关系
*
* @param treeEntryMap 节点集合
*/
@Override
default void putAll(Map<? extends K, ? extends TreeEntry<K, V>> treeEntryMap) {
if (CollUtil.isEmpty(treeEntryMap)) {
return;
}
treeEntryMap.forEach((k, v) -> {
if (v.hasParent()) {
final TreeEntry<K, V> parent = v.getDeclaredParent();
putLinkedNodes(parent.getKey(), parent.getValue(), v.getKey(), v.getValue());
} else {
putNode(v.getKey(), v.getValue());
}
});
}
// ===================== 节点操作 =====================
/**
* 批量添加节点
*
* @param <C> 集合类型
* @param values 要添加的值
* @param keyGenerator 从值中获取key的方法
* @param parentKeyGenerator 从值中获取父节点key的方法
* @param ignoreNullNode 是否获取到的key为null的子节点/父节点
*/
default <C extends Collection<V>> void putAllNode(
C values, Function<V, K> keyGenerator, Function<V, K> parentKeyGenerator, boolean ignoreNullNode) {
if (CollUtil.isEmpty(values)) {
return;
}
values.forEach(v -> {
final K key = keyGenerator.apply(v);
final K parentKey = parentKeyGenerator.apply(v);
// 不忽略keu为null节点
final boolean hasKey = ObjUtil.isNotNull(key);
final boolean hasParentKey = ObjUtil.isNotNull(parentKey);
if (!ignoreNullNode || (hasKey && hasParentKey)) {
linkNodes(parentKey, key);
get(key).setValue(v);
return;
}
// 父子节点的key都为null
if (!hasKey && !hasParentKey) {
return;
}
// 父节点key为null
if (hasKey) {
putNode(key, v);
return;
}
// 子节点key为null
putNode(parentKey, null);
});
}
/**
* 添加一个节点
* <ul>
* <li>若key对应节点不存在则以传入的键值创建一个新的节点</li>
* <li>若key对应节点存在则将该节点的值替换为{@code node}指定的值</li>
* </ul>
*
* @param key 节点的key
* @param value 节点的value
* @return 节点若key已有对应节点则返回具有旧值的节点否则返回null
*/
TreeEntry<K, V> putNode(K key, V value);
/**
* 同时添加父子节点
* <ul>
* <li>{@code parentKey}{@code childKey}对应的节点不存在则会根据键值创建一个对应的节点</li>
* <li>{@code parentKey}{@code childKey}对应的节点存在则会更新对应节点的值</li>
* </ul>
* 该操作等同于
* <pre>{@code
* putNode(parentKey, parentValue);
* putNode(childKey, childValue);
* linkNodes(parentKey, childKey);
* }</pre>
*
* @param parentKey 父节点的key
* @param parentValue 父节点的value
* @param childKey 子节点的key
* @param childValue 子节点的值
*/
default void putLinkedNodes(K parentKey, V parentValue, K childKey, V childValue) {
putNode(parentKey, parentValue);
putNode(childKey, childValue);
linkNodes(parentKey, childKey);
}
/**
* 添加子节点并为子节点指定父节点
* <ul>
* <li>{@code parentKey}{@code childKey}对应的节点不存在则会根据键值创建一个对应的节点</li>
* <li>{@code parentKey}{@code childKey}对应的节点存在则会更新对应节点的值</li>
* </ul>
*
* @param parentKey 父节点的key
* @param childKey 子节点的key
* @param childValue 子节点的值
*/
void putLinkedNodes(K parentKey, K childKey, V childValue);
/**
* 为集合中的指定的节点建立父子关系
*
* @param parentKey 父节点的key
* @param childKey 子节点的key
*/
default void linkNodes(K parentKey, K childKey) {
linkNodes(parentKey, childKey, null);
}
/**
* 为集合中的指定的节点建立父子关系
*
* @param parentKey 父节点的key
* @param childKey 子节点的key
* @param consumer 对父节点和子节点的操作允许为null
*/
void linkNodes(K parentKey, K childKey, BiConsumer<TreeEntry<K, V>, TreeEntry<K, V>> consumer);
/**
* {@code parentKey}{@code childKey}对应节点都存在则移除指定该父节点与其直接关联的指定子节点间的引用关系
*
* @param parentKey 父节点的key
* @param childKey 子节点
*/
void unlinkNode(K parentKey, K childKey);
// ===================== 父节点相关方法 =====================
/**
* 获取指定节点所在树结构的全部树节点 <br>
* 比如存在 a -&gt; b -&gt; c 的关系则输入 a/b/c 都将返回 a, b, c
*
* @param key 指定节点的key
* @return 节点
*/
default Set<TreeEntry<K, V>> getTreeNodes(K key) {
final TreeEntry<K, V> target = get(key);
if (ObjUtil.isNull(target)) {
return Collections.emptySet();
}
final Set<TreeEntry<K, V>> results = SetUtil.ofLinked(target.getRoot());
CollUtil.addAll(results, target.getRoot().getChildren().values());
return results;
}
/**
* 获取以指定节点作为叶子节点的树结构然后获取该树结构的根节点 <br>
* 比如存在 a -&gt; b -&gt; c 的关系则输入 a/b/c 都将返回 a
*
* @param key 指定节点的key
* @return 节点
*/
default TreeEntry<K, V> getRootNode(K key) {
return Opt.ofNullable(get(key))
.map(TreeEntry::getRoot)
.orElse(null);
}
/**
* 获取指定节点的直接父节点 <br>
* 比如若存在 a -&gt; b -&gt; c 的关系此时输入 a 将返回 null输入 b 将返回 a输入 c 将返回 b
*
* @param key 指定节点的key
* @return 节点
*/
default TreeEntry<K, V> getDeclaredParentNode(K key) {
return Opt.ofNullable(get(key))
.map(TreeEntry::getDeclaredParent)
.orElse(null);
}
/**
* 获取以指定节点作为叶子节点的树结构然后获取该树结构中指定节点的指定父节点
*
* @param key 指定节点的key
* @param parentKey 指定父节点key
* @return 节点
*/
default TreeEntry<K, V> getParentNode(K key, K parentKey) {
return Opt.ofNullable(get(key))
.map(t -> t.getParent(parentKey))
.orElse(null);
}
/**
* 获取以指定节点作为叶子节点的树结构然后确认该树结构中当前节点是否存在指定父节点
*
* @param key 指定节点的key
* @param parentKey 指定父节点的key
* @return 是否
*/
default boolean containsParentNode(K key, K parentKey) {
return Opt.ofNullable(get(key))
.map(m -> m.containsParent(parentKey))
.orElse(false);
}
/**
* 获取指定节点的值
*
* @param key 节点的key
* @return 节点值若节点不存在或节点值为null都将返回null
*/
default V getNodeValue(K key) {
return Opt.ofNullable(get(key))
.map(TreeEntry::getValue)
.get();
}
// ===================== 子节点相关方法 =====================
/**
* 判断以该父节点作为根节点的树结构中是否具有指定子节点
*
* @param parentKey 父节点
* @param childKey 子节点
* @return 是否
*/
default boolean containsChildNode(K parentKey, K childKey) {
return Opt.ofNullable(get(parentKey))
.map(m -> m.containsChild(childKey))
.orElse(false);
}
/**
* 获取指定父节点直接关联的子节点 <br>
* 比如若存在 a -&gt; b -&gt; c 的关系此时输入 b 将返回 c输入 a 将返回 b
*
* @param key key
* @return 节点
*/
default Collection<TreeEntry<K, V>> getDeclaredChildNodes(K key) {
return Opt.ofNullable(get(key))
.map(TreeEntry::getDeclaredChildren)
.map(Map::values)
.orElseGet(Collections::emptyList);
}
/**
* 获取指定父节点的全部子节点 <br>
* 比如若存在 a -&gt; b -&gt; c 的关系此时输入 b 将返回 c输入 a 将返回 bc
*
* @param key key
* @return 该节点的全部子节点
*/
default Collection<TreeEntry<K, V>> getChildNodes(K key) {
return Opt.ofNullable(get(key))
.map(TreeEntry::getChildren)
.map(Map::values)
.orElseGet(Collections::emptyList);
}
}

View File

@ -0,0 +1,744 @@
package cn.hutool.core.map;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.reflect.ClassUtil;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ObjUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* {@link ForestMap}的基本实现
*
* <p>该集合可以被视为以{@link TreeEntryNode#getKey()}作为key{@link TreeEntryNode}实例作为value的{@link LinkedHashMap}<br>
* 使用时将每一对键与值对视为一个{@link TreeEntryNode}节点节点的id即为{@link TreeEntryNode#getKey()}
* 任何情况下使用相同的key都将会访问到同一个节点<br>
*
* <p>节点通过key形成父子关系并最终构成多叉树结构多组平行的多叉树将在当前集合中构成森林
* 使用者可以通过{@link ForestMap}本身的方法来对森林进行操作或访问
* 也可以在获取到{@link TreeEntry}使用节点本身的方法对数进行操作或访问
*
* @param <K> key类型
* @author huangchengxing
*/
public class LinkedForestMap<K, V> implements ForestMap<K, V> {
/**
* 节点集合
*/
private final Map<K, TreeEntryNode<K, V>> nodes;
/**
* 当指定节点已经与其他节点构成了父子关系是否允许将该节点的父节点强制替换为指定节点
*/
private final boolean allowOverrideParent;
/**
* 构建{@link LinkedForestMap}
*
* @param allowOverrideParent 当指定节点已经与其他节点构成了父子关系是否允许将该节点的父节点强制替换为指定节点
*/
public LinkedForestMap(boolean allowOverrideParent) {
this.allowOverrideParent = allowOverrideParent;
this.nodes = new LinkedHashMap<>();
}
// ====================== Map接口实现 ======================
/**
* 获取当前实例中的节点个数
*
* @return 节点个数
*/
@Override
public int size() {
return nodes.size();
}
/**
* 当前实例是否为空
*
* @return 是否
*/
@Override
public boolean isEmpty() {
return nodes.isEmpty();
}
/**
* 当前实例中是否存在key对应的节点
*
* @param key key
* @return 是否
*/
@Override
public boolean containsKey(Object key) {
return nodes.containsKey(key);
}
/**
* 当前实例中是否存在对应的{@link TreeEntry}实例
*
* @param value {@link TreeEntry}实例
* @return 是否
*/
@Override
public boolean containsValue(Object value) {
return nodes.containsValue(value);
}
/**
* 获取key对应的节点
*
* @param key key
* @return 节点
*/
@Override
public TreeEntry<K, V> get(Object key) {
return nodes.get(key);
}
/**
* 将指定节点从当前{@link Map}中删除
* <ul>
* <li>若存在父节点或子节点则将其断开其与父节点或子节点的引用关系</li>
* <li>
* 若同时存在父节点或子节点则会在删除后将让子节点直接成为父节点的子节点比如<br>
* 现有引用关系 a -&gt; b -&gt; c删除 b 将有 a -&gt; c
* </li>
* </ul>
*
* @param key 节点的key
* @return 删除的且引用关系已经改变的节点若key没有对应节点则返回null
*/
@Override
public TreeEntry<K, V> remove(Object key) {
final TreeEntryNode<K, V> target = nodes.remove(key);
if (ObjUtil.isNull(target)) {
return null;
}
// 若存在父节点
// 1.将该目标从父节点的子节点中移除
// 2.将目标的子节点直接将目标的父节点作为父节点
if (target.hasParent()) {
final TreeEntryNode<K, V> parent = target.getDeclaredParent();
final Map<K, TreeEntry<K, V>> targetChildren = target.getChildren();
parent.removeDeclaredChild(target.getKey());
target.clear();
targetChildren.forEach((k, c) -> parent.addChild((TreeEntryNode<K, V>) c));
}
return target;
}
/**
* 将当前集合清空并清除全部节点间的引用关系
*/
@Override
public void clear() {
nodes.values().forEach(TreeEntryNode::clear);
nodes.clear();
}
/**
* 返回当前实例中全部的key组成的{@link Set}集合
*
* @return 集合
*/
@Override
public Set<K> keySet() {
return nodes.keySet();
}
/**
* 返回当前实例中全部{@link TreeEntry}组成的{@link Collection}集合
*
* @return 集合
*/
@Override
public Collection<TreeEntry<K, V>> values() {
return new ArrayList<>(nodes.values());
}
/**
* 由key与{@link TreeEntry}组成的键值对实体的{@link Set}集合
* 注意返回集合中{@link Map.Entry#setValue(Object)}不支持调用
*
* @return 集合
*/
@Override
public Set<Map.Entry<K, TreeEntry<K, V>>> entrySet() {
return nodes.entrySet().stream()
.map(this::wrap)
.collect(Collectors.toSet());
}
/**
* {@link TreeEntryNode}包装为{@link EntryNodeWrapper}
*/
private Map.Entry<K, TreeEntry<K, V>> wrap(Map.Entry<K, TreeEntryNode<K, V>> nodeEntry) {
return new EntryNodeWrapper<>(nodeEntry.getValue());
}
// ====================== ForestMap接口实现 ======================
/**
* 添加一个节点
* <ul>
* <li>若key对应节点不存在则以传入的键值创建一个新的节点</li>
* <li>若key对应节点存在则将该节点的值替换为{@code node}指定的值</li>
* </ul>
*
* @param key 节点的key
* @param value 节点的value
* @return 节点若key已有对应节点则返回具有旧值的节点否则返回null
*/
@Override
public TreeEntryNode<K, V> putNode(K key, V value) {
TreeEntryNode<K, V> target = nodes.get(key);
if (ObjUtil.isNotNull(target)) {
final V oldVal = target.getValue();
target.setValue(value);
return target.copy(oldVal);
}
target = new TreeEntryNode<>(null, key, value);
nodes.put(key, target);
return null;
}
/**
* 同时添加父子节点
* <ul>
* <li>{@code parentKey}{@code childKey}对应的节点不存在则会根据键值创建一个对应的节点</li>
* <li>{@code parentKey}{@code childKey}对应的节点存在则会更新对应节点的值</li>
* </ul>
* 该操作等同于
* <pre>
* TreeEntry&lt;K, V&gt; parent = putNode(parentKey, parentValue);
* TreeEntry&lt;K, V&gt; child = putNode(childKey, childValue);
* linkNodes(parentKey, childKey);
* </pre>
*
* @param parentKey 父节点的key
* @param parentValue 父节点的value
* @param childKey 子节点的key
* @param childValue 子节点的值
*/
@Override
public void putLinkedNodes(K parentKey, V parentValue, K childKey, V childValue) {
linkNodes(parentKey, childKey, (parent, child) -> {
parent.setValue(parentValue);
child.setValue(childValue);
});
}
/**
* 添加子节点并为子节点指定父节点
* <ul>
* <li>{@code parentKey}{@code childKey}对应的节点不存在则会根据键值创建一个对应的节点</li>
* <li>{@code parentKey}{@code childKey}对应的节点存在则会更新对应节点的值</li>
* </ul>
*
* @param parentKey 父节点的key
* @param childKey 子节点的key
* @param childValue 子节点的值
*/
@Override
public void putLinkedNodes(K parentKey, K childKey, V childValue) {
linkNodes(parentKey, childKey, (parent, child) -> child.setValue(childValue));
}
/**
* 为指定的节点建立父子关系{@code parentKey}{@code childKey}对应节点不存在则会创建一个对应的值为null的空节点
*
* @param parentKey 父节点的key
* @param childKey 子节点的key
* @param consumer 对父节点和子节点的操作允许为null
*/
@Override
public void linkNodes(K parentKey, K childKey, BiConsumer<TreeEntry<K, V>, TreeEntry<K, V>> consumer) {
consumer = ObjUtil.defaultIfNull(consumer, (parent, child) -> {
});
final TreeEntryNode<K, V> parentNode = nodes.computeIfAbsent(parentKey, t -> new TreeEntryNode<>(null, t));
TreeEntryNode<K, V> childNode = nodes.get(childKey);
// 1.子节点不存在
if (ObjUtil.isNull(childNode)) {
childNode = new TreeEntryNode<>(parentNode, childKey);
consumer.accept(parentNode, childNode);
nodes.put(childKey, childNode);
return;
}
// 2.子节点存在且已经是该父节点的子节点了
if (ObjUtil.equals(parentNode, childNode.getDeclaredParent())) {
consumer.accept(parentNode, childNode);
return;
}
// 3.子节点存在但是未与其他节点构成父子关系
if (false == childNode.hasParent()) {
parentNode.addChild(childNode);
}
// 4.子节点存在且已经与其他节点构成父子关系但是允许子节点直接修改其父节点
else if (allowOverrideParent) {
childNode.getDeclaredParent().removeDeclaredChild(childNode.getKey());
parentNode.addChild(childNode);
}
// 5.子节点存在且已经与其他节点构成父子关系但是不允许子节点直接修改其父节点
else {
throw new IllegalArgumentException(StrUtil.format(
"[{}] has been used as child of [{}], can not be overwrite as child of [{}]",
childNode.getKey(), childNode.getDeclaredParent().getKey(), parentKey
));
}
consumer.accept(parentNode, childNode);
}
/**
* 移除指定父节点与其直接关联的子节点间的引用关系但是不会将该节点从集合中删除
*
* @param parentKey 父节点的key
* @param childKey 子节点
*/
@Override
public void unlinkNode(K parentKey, K childKey) {
final TreeEntryNode<K, V> childNode = nodes.get(childKey);
if (ObjUtil.isNull(childNode)) {
return;
}
if (childNode.hasParent()) {
childNode.getDeclaredParent().removeDeclaredChild(childNode.getKey());
}
}
/**
* 树节点
*
* @param <K> key类型
* @author huangchengxing
*/
public static class TreeEntryNode<K, V> implements TreeEntry<K, V> {
/**
* 根节点
*/
private TreeEntryNode<K, V> root;
/**
* 父节点
*/
private TreeEntryNode<K, V> parent;
/**
* 权重表示到根节点的距离
*/
private int weight;
/**
* 子节点
*/
private final Map<K, TreeEntryNode<K, V>> children;
/**
* key
*/
private final K key;
/**
*
*/
private V value;
/**
* 创建一个节点
*
* @param parent 节点的父节点
* @param key 节点的key
*/
public TreeEntryNode(TreeEntryNode<K, V> parent, K key) {
this(parent, key, null);
}
/**
* 创建一个节点
*
* @param parent 节点的父节点
* @param key 节点的key
* @param value 节点的value
*/
public TreeEntryNode(TreeEntryNode<K, V> parent, K key, V value) {
this.parent = parent;
this.key = key;
this.value = value;
this.children = new LinkedHashMap<>();
if (ObjUtil.isNull(parent)) {
this.root = this;
this.weight = 0;
} else {
parent.addChild(this);
this.weight = parent.weight + 1;
this.root = parent.root;
}
}
/**
* 获取当前节点的key
*
* @return 节点的key
*/
@Override
public K getKey() {
return key;
}
/**
* 获取当前节点与根节点的距离
*
* @return 当前节点与根节点的距离
*/
@Override
public int getWeight() {
return weight;
}
/**
* 获取节点的value
*
* @return 节点的value
*/
@Override
public V getValue() {
return value;
}
/**
* 设置节点的value
*
* @param value 节点的value
* @return 节点的旧value
*/
@Override
public V setValue(V value) {
final V oldVal = getValue();
this.value = value;
return oldVal;
}
// ================== 父节点的操作 ==================
/**
* 从当前节点开始向上递归当前节点的父节点
*
* @param includeCurrent 是否处理当前节点
* @param consumer 对节点的操作
* @param breakTraverse 是否终止遍历
* @return 遍历到的最后一个节点
*/
TreeEntryNode<K, V> traverseParentNodes(
boolean includeCurrent, Consumer<TreeEntryNode<K, V>> consumer, Predicate<TreeEntryNode<K, V>> breakTraverse) {
breakTraverse = ObjUtil.defaultIfNull(breakTraverse, n -> false);
TreeEntryNode<K, V> curr = includeCurrent ? this : this.parent;
while (ObjUtil.isNotNull(curr)) {
consumer.accept(curr);
if (breakTraverse.test(curr)) {
break;
}
curr = curr.parent;
}
return curr;
}
/**
* 当前节点是否为根节点
*
* @return 当前节点是否为根节点
*/
public boolean isRoot() {
return getRoot() == this;
}
/**
* 获取以当前节点作为叶子节点的树结构然后获取该树结构的根节点
*
* @return 根节点
*/
@Override
public TreeEntryNode<K, V> getRoot() {
if (ObjUtil.isNotNull(this.root)) {
return this.root;
} else {
this.root = traverseParentNodes(true, p -> {
}, p -> !p.hasParent());
}
return this.root;
}
/**
* 获取当前节点直接关联的父节点
*
* @return 父节点当节点不存在对应父节点时返回null
*/
@Override
public TreeEntryNode<K, V> getDeclaredParent() {
return parent;
}
/**
* 获取以当前节点作为叶子节点的树结构然后获取该树结构中当前节点的指定父节点
*
* @param key 指定父节点的key
* @return 指定父节点当不存在时返回null
*/
@Override
public TreeEntryNode<K, V> getParent(K key) {
return traverseParentNodes(false, p -> {
}, p -> p.equalsKey(key));
}
/**
* 获取以当前节点作为根节点的树结构然后遍历所有节点
*
* @param includeSelf 是否处理当前节点
* @param nodeConsumer 对节点的处理
*/
@Override
public void forEachChild(boolean includeSelf, Consumer<TreeEntry<K, V>> nodeConsumer) {
traverseChildNodes(includeSelf, (index, child) -> nodeConsumer.accept(child), null);
}
/**
* 指定key与当前节点的key是否相等
*
* @param key 要比较的key
* @return 是否key一致
*/
public boolean equalsKey(K key) {
return ObjUtil.equals(getKey(), key);
}
// ================== 子节点的操作 ==================
/**
* 从当前节点开始按广度优先向下遍历当前节点的所有子节点
*
* @param includeCurrent 是否包含当前节点
* @param consumer 对节点与节点和当前节点的距离的操作{code includeCurrent}为false时下标从1开始否则从0开始
* @param breakTraverse 是否终止遍历为null时默认总是返回{@code true}
* @return 遍历到的最后一个节点
*/
TreeEntryNode<K, V> traverseChildNodes(
boolean includeCurrent, BiConsumer<Integer, TreeEntryNode<K, V>> consumer, BiPredicate<Integer, TreeEntryNode<K, V>> breakTraverse) {
breakTraverse = ObjUtil.defaultIfNull(breakTraverse, (i, n) -> false);
final Deque<List<TreeEntryNode<K, V>>> keyNodeDeque = ListUtil.ofLinked(ListUtil.of(this));
boolean needProcess = includeCurrent;
int index = includeCurrent ? 0 : 1;
TreeEntryNode<K, V> lastNode = null;
while (!keyNodeDeque.isEmpty()) {
final List<TreeEntryNode<K, V>> curr = keyNodeDeque.removeFirst();
final List<TreeEntryNode<K, V>> next = new ArrayList<>();
for (final TreeEntryNode<K, V> node : curr) {
if (needProcess) {
consumer.accept(index, node);
if (breakTraverse.test(index, node)) {
return node;
}
} else {
needProcess = true;
}
CollUtil.addAll(next, node.children.values());
}
if (!next.isEmpty()) {
keyNodeDeque.addLast(next);
}
lastNode = CollUtil.getLast(next);
index++;
}
return lastNode;
}
/**
* 添加子节点
*
* @param child 子节点
* @throws IllegalArgumentException 当要添加的子节点已经是其自身父节点时抛出
*/
void addChild(TreeEntryNode<K, V> child) {
if (containsChild(child.key)) {
return;
}
// 检查循环引用
traverseParentNodes(true, s -> Assert.notEquals(
s.key, child.key,
"circular reference between [{}] and [{}]!",
s.key, this.key
), null);
// 调整该节点的信息
child.parent = this;
child.traverseChildNodes(true, (i, c) -> {
c.root = getRoot();
c.weight = i + getWeight() + 1;
}, null);
// 将该节点添加为当前节点的子节点
children.put(child.key, child);
}
/**
* 移除子节点
*
* @param key 子节点
*/
void removeDeclaredChild(K key) {
final TreeEntryNode<K, V> child = children.get(key);
if (ObjUtil.isNull(child)) {
return;
}
// 断开该节点与其父节点的关系
this.children.remove(key);
// 重置子节点及其下属节点的相关属性
child.parent = null;
child.traverseChildNodes(true, (i, c) -> {
c.root = child;
c.weight = i;
}, null);
}
/**
* 获取以当前节点作为根节点的树结构然后获取该树结构中的当前节点的指定子节点
*
* @param key 指定子节点的key
* @return 节点
*/
@Override
public TreeEntryNode<K, V> getChild(K key) {
return traverseChildNodes(false, (i, c) -> {
}, (i, c) -> c.equalsKey(key));
}
/**
* 获取当前节点直接关联的子节点
*
* @return 节点
*/
@Override
public Map<K, TreeEntry<K, V>> getDeclaredChildren() {
return new LinkedHashMap<>(this.children);
}
/**
* 获取以当前节点作为根节点的树结构然后按广度优先获取该树结构中的当前节点的全部子节点
*
* @return 节点
*/
@Override
public Map<K, TreeEntry<K, V>> getChildren() {
final Map<K, TreeEntry<K, V>> childrenMap = new LinkedHashMap<>();
traverseChildNodes(false, (i, c) -> childrenMap.put(c.getKey(), c), null);
return childrenMap;
}
/**
* 移除对子节点父节点与根节点的全部引用
*/
void clear() {
this.root = null;
this.children.clear();
this.parent = null;
}
/**
* 比较目标对象与当前{@link TreeEntry}是否相等<br>
* 默认只要{@link TreeEntry#getKey()}的返回值相同即认为两者相等
*
* @param o 目标对象
* @return 是否
*/
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || this.getClass().equals(o.getClass()) || ClassUtil.isAssignable(this.getClass(), o.getClass())) {
return false;
}
final TreeEntry<?, ?> treeEntry = (TreeEntry<?, ?>) o;
return ObjUtil.equals(this.getKey(), treeEntry.getKey());
}
/**
* 返回当前{@link TreeEntry}的哈希值<br>
* 默认总是返回{@link TreeEntry#getKey()}的哈希值
*
* @return 哈希值
*/
@Override
public int hashCode() {
return Objects.hash(getKey());
}
/**
* 复制一个当前节点
*
* @param value 复制的节点的值
* @return 节点
*/
TreeEntryNode<K, V> copy(V value) {
TreeEntryNode<K, V> copiedNode = new TreeEntryNode<>(this.parent, this.key, ObjUtil.defaultIfNull(value, this.value));
copiedNode.children.putAll(children);
return copiedNode;
}
}
/**
* {@link java.util.Map.Entry}包装类
*
* @param <K> key类型
* @param <V> value类型
* @param <N> 包装的{@link TreeEntry}类型
* @see #entrySet()
* @see #values()
*/
public static class EntryNodeWrapper<K, V, N extends TreeEntry<K, V>> implements Map.Entry<K, TreeEntry<K, V>> {
private final N entryNode;
EntryNodeWrapper(N entryNode) {
this.entryNode = entryNode;
}
@Override
public K getKey() {
return entryNode.getKey();
}
@Override
public TreeEntry<K, V> getValue() {
return entryNode;
}
@Override
public TreeEntry<K, V> setValue(TreeEntry<K, V> value) {
throw new UnsupportedOperationException();
}
}
}

View File

@ -70,7 +70,7 @@ public abstract class TransMap<K, V> extends MapWrapper<K, V> {
@Override
public boolean replace(final K key, final V oldValue, final V newValue) {
return super.replace(customKey(key), customValue(oldValue), customValue(values()));
return super.replace(customKey(key), customValue(oldValue), customValue(newValue));
}
@Override

View File

@ -0,0 +1,122 @@
package cn.hutool.core.map;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import java.util.Map;
import java.util.function.Consumer;
/**
* 允许拥有一个父节点与多个子节点的{@link Map.Entry}实现
* 表示一个以key作为唯一标识并且可以挂载一个对应值的树节点
* 提供一些基于该节点对其所在树结构进行访问的方法
*
* @param <V> 节点的key类型
* @param <K> 节点的value类型
* @author huangchengxing
* @see ForestMap
*/
public interface TreeEntry<K, V> extends Map.Entry<K, V> {
// ===================== 父节点相关方法 =====================
/**
* 获取以当前节点作为叶子节点的树结构然后获取当前节点与根节点的距离
*
* @return 当前节点与根节点的距离
*/
int getWeight();
/**
* 获取以当前节点作为叶子节点的树结构然后获取该树结构的根节点
*
* @return 根节点
*/
TreeEntry<K, V> getRoot();
/**
* 当前节点是否存在直接关联的父节点
*
* @return 是否
*/
default boolean hasParent() {
return ObjUtil.isNotNull(getDeclaredParent());
}
/**
* 获取当前节点直接关联的父节点
*
* @return 父节点当节点不存在对应父节点时返回null
*/
TreeEntry<K, V> getDeclaredParent();
/**
* 获取以当前节点作为叶子节点的树结构然后获取该树结构中当前节点的指定父节点
*
* @param key 指定父节点的key
* @return 指定父节点当不存在时返回null
*/
TreeEntry<K, V> getParent(K key);
/**
* 获取以当前节点作为叶子节点的树结构然后确认该树结构中当前节点是否存在指定父节点
*
* @param key 指定父节点的key
* @return 是否
*/
default boolean containsParent(K key) {
return ObjUtil.isNotNull(getParent(key));
}
// ===================== 子节点相关方法 =====================
/**
* 获取以当前节点作为根节点的树结构然后遍历所有节点
*
* @param includeSelf 是否处理当前节点
* @param nodeConsumer 对节点的处理
*/
void forEachChild(boolean includeSelf, Consumer<TreeEntry<K, V>> nodeConsumer);
/**
* 获取当前节点直接关联的子节点
*
* @return 节点
*/
Map<K, TreeEntry<K, V>> getDeclaredChildren();
/**
* 获取以当前节点作为根节点的树结构然后获取该树结构中的当前节点的全部子节点
*
* @return 节点
*/
Map<K, TreeEntry<K, V>> getChildren();
/**
* 当前节点是否有子节点
*
* @return 是否
*/
default boolean hasChildren() {
return CollUtil.isNotEmpty(getDeclaredChildren());
}
/**
* 获取以当前节点作为根节点的树结构然后获取该树结构中的当前节点的指定子节点
*
* @param key 指定子节点的key
* @return 节点
*/
TreeEntry<K, V> getChild(K key);
/**
* 获取以当前节点作为根节点的树结构然后确认该树结构中当前节点是否存在指定子节点
*
* @param key 指定子节点的key
* @return 是否
*/
default boolean containsChild(K key) {
return ObjUtil.isNotNull(getChild(key));
}
}

View File

View File

View File

View File

View File

View File

View File

View File

View File

@ -88,7 +88,7 @@ public class ListUtilTest {
@Test
public void editTest() {
final List<String> a = ListUtil.ofLinked("1", "2", "3");
final List<String> filter = (List<String>) CollUtil.edit(a, str -> "edit" + str);
final List<String> filter = CollUtil.edit(a, str -> "edit" + str);
Assert.assertEquals("edit1", filter.get(0));
Assert.assertEquals("edit2", filter.get(1));
Assert.assertEquals("edit3", filter.get(2));

View File

@ -185,7 +185,7 @@ public class CRUDTest {
Console.log(data1);
final int[] result = db.insert(ListUtil.of(data1));
final int[] result = db.insert(ListUtil.of(new Entity[]{data1}));
Console.log(result);
}

View File

@ -72,6 +72,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
* @param isIgnoreBody 是否忽略读取响应体
* @since 3.1.2
*/
@SuppressWarnings("resource")
protected HttpResponse(final HttpConnection httpConnection, final HttpConfig config, final Charset charset, final boolean isAsync, final boolean isIgnoreBody) {
this.httpConnection = httpConnection;
this.config = config;
@ -249,6 +250,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
*
* @return byte[]
*/
@SuppressWarnings("resource")
@Override
public byte[] bodyBytes() {
sync();
@ -403,6 +405,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
* @param bodyBytes 主体
* @return this
*/
@SuppressWarnings("resource")
public HttpResponse body(final byte[] bodyBytes) {
sync();
if (null != bodyBytes) {
@ -479,6 +482,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
* @return this
* @throws HttpException IO异常
*/
@SuppressWarnings("resource")
private HttpResponse initWithDisconnect() throws HttpException {
try {
init();

View File

@ -51,7 +51,7 @@ public class HttpUtil {
* @return 是否https
*/
public static boolean isHttps(final String url) {
return url.toLowerCase().startsWith("https:");
return StrUtil.startWithIgnoreCase(url, "https:");
}
/**
@ -62,7 +62,7 @@ public class HttpUtil {
* @since 5.3.8
*/
public static boolean isHttp(final String url) {
return url.toLowerCase().startsWith("http:");
return StrUtil.startWithIgnoreCase(url, "http:");
}
/**
@ -118,6 +118,7 @@ public class HttpUtil {
* @param customCharset 自定义请求字符集如果字符集获取不到使用此字符集
* @return 返回内容如果只检查状态码正常只返回 ""不正常返回 null
*/
@SuppressWarnings("resource")
public static String get(final String urlString, final Charset customCharset) {
return HttpRequest.get(urlString).charset(customCharset).execute().body();
}
@ -140,6 +141,7 @@ public class HttpUtil {
* @return 返回内容如果只检查状态码正常只返回 ""不正常返回 null
* @since 3.2.0
*/
@SuppressWarnings("resource")
public static String get(final String urlString, final int timeout) {
return HttpRequest.get(urlString).timeout(timeout).execute().body();
}
@ -151,6 +153,7 @@ public class HttpUtil {
* @param paramMap post表单数据
* @return 返回数据
*/
@SuppressWarnings("resource")
public static String get(final String urlString, final Map<String, Object> paramMap) {
return HttpRequest.get(urlString).form(paramMap).execute().body();
}
@ -164,6 +167,7 @@ public class HttpUtil {
* @return 返回数据
* @since 3.3.0
*/
@SuppressWarnings("resource")
public static String get(final String urlString, final Map<String, Object> paramMap, final int timeout) {
return HttpRequest.get(urlString).form(paramMap).timeout(timeout).execute().body();
}
@ -188,6 +192,7 @@ public class HttpUtil {
* @return 返回数据
* @since 3.2.0
*/
@SuppressWarnings("resource")
public static String post(final String urlString, final Map<String, Object> paramMap, final int timeout) {
return HttpRequest.post(urlString).form(paramMap).timeout(timeout).execute().body();
}
@ -224,6 +229,7 @@ public class HttpUtil {
* @return 返回数据
* @since 3.2.0
*/
@SuppressWarnings("resource")
public static String post(final String urlString, final String body, final int timeout) {
return HttpRequest.post(urlString).timeout(timeout).body(body).execute().body();
}

View File

@ -14,8 +14,24 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@SuppressWarnings("resource")
public class HttpUtilTest {
@Test
public void isHttpTest(){
Assert.assertTrue(HttpUtil.isHttp("Http://aaa.bbb"));
Assert.assertTrue(HttpUtil.isHttp("HTTP://aaa.bbb"));
Assert.assertFalse(HttpUtil.isHttp("FTP://aaa.bbb"));
}
@Test
public void isHttpsTest(){
Assert.assertTrue(HttpUtil.isHttps("Https://aaa.bbb"));
Assert.assertTrue(HttpUtil.isHttps("HTTPS://aaa.bbb"));
Assert.assertTrue(HttpUtil.isHttps("https://aaa.bbb"));
Assert.assertFalse(HttpUtil.isHttps("ftp://aaa.bbb"));
}
@Test
@Ignore
public void postTest() {

View File

@ -0,0 +1,29 @@
package cn.hutool.json;
import lombok.Data;
import org.junit.Assert;
import org.junit.Test;
import java.time.LocalDateTime;
public class Issue2447Test {
@Test
public void addIntegerTest() {
Time time = new Time();
time.setTime(LocalDateTime.of(1970, 1, 2, 10, 0, 1, 0));
String timeStr = JSONUtil.toJsonStr(time);
Assert.assertEquals(timeStr, "{\"time\":93601000}");
Assert.assertEquals(JSONUtil.toBean(timeStr, Time.class).getTime(), time.getTime());
}
@Data
static class Time {
private LocalDateTime time;
@Override
public String toString() {
return time.toString();
}
}
}

View File

@ -23,8 +23,9 @@ public class SocketUtil {
*
* @param channel {@link AsynchronousSocketChannel}
* @return 远程端的地址信息包括host和端口null表示channel为null或者远程主机未连接
* @throws IORuntimeException IO异常
*/
public static SocketAddress getRemoteAddress(final AsynchronousSocketChannel channel) {
public static SocketAddress getRemoteAddress(final AsynchronousSocketChannel channel) throws IORuntimeException {
try {
return (null == channel) ? null : channel.getRemoteAddress();
} catch (final ClosedChannelException e) {
@ -41,8 +42,9 @@ public class SocketUtil {
*
* @param channel {@link AsynchronousSocketChannel}
* @return 远程主机是否处于连接状态
* @throws IORuntimeException IO异常
*/
public static boolean isConnected(final AsynchronousSocketChannel channel) {
public static boolean isConnected(final AsynchronousSocketChannel channel) throws IORuntimeException{
return null != getRemoteAddress(channel);
}

View File

@ -3,6 +3,7 @@ package cn.hutool.socket.nio;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.log.Log;
import cn.hutool.socket.SocketRuntimeException;
import java.io.Closeable;
@ -21,6 +22,7 @@ import java.util.Iterator;
* @since 4.4.5
*/
public class NioClient implements Closeable {
private static final Log log = Log.get();
private Selector selector;
private SocketChannel channel;
@ -32,6 +34,7 @@ public class NioClient implements Closeable {
* @param host 服务器地址
* @param port 端口
*/
@SuppressWarnings("resource")
public NioClient(final String host, final int port) {
init(new InetSocketAddress(host, port));
}
@ -41,6 +44,7 @@ public class NioClient implements Closeable {
*
* @param address 服务器地址
*/
@SuppressWarnings("resource")
public NioClient(final InetSocketAddress address) {
init(address);
}
@ -91,7 +95,7 @@ public class NioClient implements Closeable {
try {
doListen();
} catch (final IOException e) {
e.printStackTrace();
log.error("Listen failed", e);
}
});
}

View File

@ -3,7 +3,6 @@ package cn.hutool.socket.nio;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.log.Log;
import cn.hutool.log.StaticLog;
import java.io.Closeable;
import java.io.IOException;
@ -34,6 +33,7 @@ public class NioServer implements Closeable {
*
* @param port 端口
*/
@SuppressWarnings("resource")
public NioServer(final int port) {
init(new InetSocketAddress(port));
}
@ -58,6 +58,7 @@ public class NioServer implements Closeable {
// 服务器套接字注册到Selector中 并指定Selector监控连接事件
this.serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (final IOException e) {
close();
throw new IORuntimeException(e);
}
@ -140,7 +141,7 @@ public class NioServer implements Closeable {
handler.handle(socketChannel);
} catch (final Exception e){
IoUtil.close(socketChannel);
StaticLog.error(e);
log.error(e);
}
}
}