add BeanPath

This commit is contained in:
Looly 2023-11-15 20:01:18 +08:00
parent b9274f5d6e
commit ed1c3e48af
19 changed files with 178 additions and 638 deletions

View File

@ -1,317 +0,0 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.bean;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.convert.Convert;
import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.math.NumberUtil;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.text.split.SplitUtil;
import java.io.Serializable;
import java.util.*;
/**
* Bean路径表达式用于获取多层嵌套Bean中的字段值或Bean对象<br>
* 根据给定的表达式查找Bean中对应的属性值对象 表达式分为两种
* <ol>
* <li>.表达式可以获取Bean对象中的属性字段值或者Map中key对应的值</li>
* <li>[]表达式可以获取集合等对象中对应index的值</li>
* </ol>
* <p>
* 表达式栗子
*
* <pre>
* persion
* persion.name
* persons[3]
* person.friends[5].name
* ['person']['friends'][5]['name']
* </pre>
*
* @author Looly
* @since 4.0.6
*/
public class BeanPathOld implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 表达式边界符号数组
*/
private static final char[] EXP_CHARS = {CharUtil.DOT, CharUtil.BRACKET_START, CharUtil.BRACKET_END};
private boolean isStartWith = false;
protected List<String> patternParts;
/**
* 解析Bean路径表达式为Bean模式<br>
* Bean表达式用于获取多层嵌套Bean中的字段值或Bean对象<br>
* 根据给定的表达式查找Bean中对应的属性值对象 表达式分为两种
* <ol>
* <li>.表达式可以获取Bean对象中的属性字段值或者Map中key对应的值</li>
* <li>[]表达式可以获取集合等对象中对应index的值</li>
* </ol>
* <p>
* 表达式栗子
*
* <pre>
* persion
* persion.name
* persons[3]
* person.friends[5].name
* ['person']['friends'][5]['name']
* </pre>
*
* @param expression 表达式
* @return BeanPath
*/
public static BeanPathOld of(final String expression) {
return new BeanPathOld(expression);
}
/**
* 构造
*
* @param expression 表达式
*/
public BeanPathOld(final String expression) {
init(expression);
}
/**
* 获取表达式解析后的分段列表
*
* @return 表达式分段列表
*/
public List<String> getPatternParts() {
return this.patternParts;
}
/**
* 获取Bean中对应表达式的值
*
* @param bean Bean对象或Map或List等
* @return 如果对应值不存在则返回null
*/
public Object get(final Object bean) {
return get(this.patternParts, bean);
}
/**
* 设置表达式指定位置或filed对应的值<br>
* 若表达式指向一个List则设置其坐标对应位置的值若指向Map则put对应key的值Bean则设置字段的值<br>
* 注意
*
* <pre>
* 1. 如果为List如果下标不大于List长度则替换原有值否则追加值
* 2. 如果为数组如果下标不大于数组长度则替换原有值否则追加值
* </pre>
*
* @param bean BeanMap或List
* @param value
*/
public void set(final Object bean, final Object value) {
Objects.requireNonNull(bean);
Object subBean = bean;
Object previousBean = null;
boolean isFirst = true;
String patternPart;
// 尝试找到倒数第二个子对象, 最终需要设置它的字段值
final int length = patternParts.size() - 1;
// 填充父字段缺失的对象
for (int i = 0; i < length; i++) {
patternPart = patternParts.get(i);
// 保存当前操作的bean, 以便subBean不存在时, 可以用来填充缺失的子对象
previousBean = subBean;
// 获取当前对象的子对象
subBean = getFieldValue(subBean, patternPart);
if (null == subBean) {
// 支持表达式的第一个对象为Bean本身若用户定义表达式$开头则不做此操作
if (isFirst && !this.isStartWith && BeanUtil.isMatchName(bean, patternPart, true)) {
subBean = bean;
isFirst = false;
} else {
// 填充缺失的子对象, 根据下一个表达式决定填充的值, 如果是整数(下标)则使用列表, 否则当做Map对象
subBean = NumberUtil.isInteger(patternParts.get(i + 1)) ? new ArrayList<>() : new HashMap<>();
BeanUtil.setFieldValue(previousBean, patternPart, subBean);
// 上面setFieldValue中有可能发生对象转换, 因此此处重新获取子对象
// 欲知详情请自行阅读FieldUtil.setFieldValue(Object, Field, Object)
subBean = BeanUtil.getFieldValue(previousBean, patternPart);
}
}
}
// 设置最终的当前字段值
final Object newSubBean = BeanUtil.setFieldValue(subBean, patternParts.get(length), value);
if(newSubBean != subBean && null != previousBean){
// 对象变更重新加入
BeanUtil.setFieldValue(previousBean, patternParts.get(length - 1), newSubBean);
}
}
@Override
public String toString() {
return this.patternParts.toString();
}
//region Private Methods
/**
* 获取Bean中对应表达式的值
*
* @param patternParts 表达式分段列表
* @param bean Bean对象或Map或List等
* @return 如果对应值不存在则返回null
*/
private Object get(final List<String> patternParts, final Object bean) {
Object subBean = bean;
boolean isFirst = true;
for (final String patternPart : patternParts) {
subBean = getFieldValue(subBean, patternPart);
if (null == subBean) {
// 支持表达式的第一个对象为Bean本身若用户定义表达式$开头则不做此操作
if (isFirst && !this.isStartWith && BeanUtil.isMatchName(bean, patternPart, true)) {
subBean = bean;
isFirst = false;
} else {
return null;
}
}
}
return subBean;
}
@SuppressWarnings("unchecked")
private static Object getFieldValue(final Object bean, final String expression) {
if (StrUtil.isBlank(expression)) {
return null;
}
if (StrUtil.contains(expression, CharUtil.COLON)) {
// [start:end:step] 模式
final List<String> parts = SplitUtil.splitTrim(expression, StrUtil.COLON);
final int start = Integer.parseInt(parts.get(0));
final int end = Integer.parseInt(parts.get(1));
int step = 1;
if (3 == parts.size()) {
step = Integer.parseInt(parts.get(2));
}
if (bean instanceof Collection) {
return CollUtil.sub((Collection<?>) bean, start, end, step);
} else if (ArrayUtil.isArray(bean)) {
return ArrayUtil.sub(bean, start, end, step);
}
} else if (StrUtil.contains(expression, ',')) {
// [num0,num1,num2...]模式或者['key0','key1']模式
final List<String> keys = SplitUtil.splitTrim(expression, StrUtil.COMMA);
if (bean instanceof Collection) {
return CollUtil.getAny((Collection<?>) bean, Convert.convert(int[].class, keys));
} else if (ArrayUtil.isArray(bean)) {
return ArrayUtil.getAny(bean, Convert.convert(int[].class, keys));
} else {
final String[] unWrappedKeys = new String[keys.size()];
for (int i = 0; i < unWrappedKeys.length; i++) {
unWrappedKeys[i] = StrUtil.unWrap(keys.get(i), CharUtil.SINGLE_QUOTE);
}
if (bean instanceof Map) {
// 只支持String为key的Map
return MapUtil.getAny((Map<String, ?>) bean, unWrappedKeys);
} else {
final Map<String, Object> map = BeanUtil.beanToMap(bean);
return MapUtil.getAny(map, unWrappedKeys);
}
}
} else {
// 数字或普通字符串
return BeanUtil.getFieldValue(bean, expression);
}
return null;
}
/**
* 初始化
*
* @param expression 表达式
*/
private void init(final String expression) {
final List<String> localPatternParts = new ArrayList<>();
final int length = expression.length();
final StringBuilder builder = new StringBuilder();
char c;
boolean isNumStart = false;// 下标标识符开始
boolean isInWrap = false; //标识是否在引号内
for (int i = 0; i < length; i++) {
c = expression.charAt(i);
if (0 == i && '$' == c) {
// 忽略开头的$符表示当前对象
isStartWith = true;
continue;
}
if ('\'' == c) {
// 结束
isInWrap = (!isInWrap);
continue;
}
if (!isInWrap && ArrayUtil.contains(EXP_CHARS, c)) {
// 处理边界符号
if (CharUtil.BRACKET_END == c) {
// 中括号数字下标结束
if (!isNumStart) {
throw new IllegalArgumentException(StrUtil.format("Bad expression '{}':{}, we find ']' but no '[' !", expression, i));
}
isNumStart = false;
// 中括号结束加入下标
} else {
if (isNumStart) {
// 非结束中括号情况下发现起始中括号报错中括号未关闭
throw new IllegalArgumentException(StrUtil.format("Bad expression '{}':{}, we find '[' but no ']' !", expression, i));
} else if (CharUtil.BRACKET_START == c) {
// 数字下标开始
isNumStart = true;
}
// 每一个边界符之前的表达式是一个完整的KEY开始处理KEY
}
if (builder.length() > 0) {
localPatternParts.add(builder.toString());
}
builder.setLength(0);
} else {
// 非边界符号追加字符
builder.append(c);
}
}
// 末尾边界符检查
if (isNumStart) {
throw new IllegalArgumentException(StrUtil.format("Bad expression '{}':{}, we find '[' but no ']' !", expression, length - 1));
} else {
if (builder.length() > 0) {
localPatternParts.add(builder.toString());
}
}
// 不可变List
this.patternParts = ListUtil.view(localPatternParts);
}
//endregion
}

View File

@ -16,6 +16,7 @@ import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.bean.copier.BeanCopier;
import org.dromara.hutool.core.bean.copier.CopyOptions;
import org.dromara.hutool.core.bean.copier.ValueProvider;
import org.dromara.hutool.core.bean.path.BeanPath;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.collection.set.SetUtil;
@ -67,7 +68,7 @@ public class BeanUtil {
* @see #hasPublicField(Class)
*/
public static boolean isReadableBean(final Class<?> clazz) {
if(null == clazz){
if (null == clazz) {
return false;
}
return hasGetter(clazz) || hasPublicField(clazz);
@ -87,7 +88,7 @@ public class BeanUtil {
* @see #hasPublicField(Class)
*/
public static boolean isWritableBean(final Class<?> clazz) {
if(null == clazz){
if (null == clazz) {
return false;
}
return hasSetter(clazz) || hasPublicField(clazz);
@ -244,7 +245,7 @@ public class BeanUtil {
private static Map<String, PropertyDescriptor> internalGetPropertyDescriptorMap(final Class<?> clazz, final boolean ignoreCase) throws BeanException {
final PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(clazz);
final Map<String, PropertyDescriptor> map = ignoreCase ? new CaseInsensitiveMap<>(propertyDescriptors.length, 1f)
: new HashMap<>(propertyDescriptors.length, 1);
: new HashMap<>(propertyDescriptors.length, 1);
for (final PropertyDescriptor propertyDescriptor : propertyDescriptors) {
map.put(propertyDescriptor.getName(), propertyDescriptor);
@ -355,7 +356,7 @@ public class BeanUtil {
* @param bean Bean对象支持MapListCollectionArray
* @param expression 表达式例如person.friend[5].name
* @return Bean属性值bean为{@code null}或者express为空返回{@code null}
* @see BeanPathOld#get(Object)
* @see BeanPath#getValue(Object)
* @since 3.0.7
*/
@SuppressWarnings("unchecked")
@ -363,7 +364,7 @@ public class BeanUtil {
if (null == bean || StrUtil.isBlank(expression)) {
return null;
}
return (T) BeanPathOld.of(expression).get(bean);
return (T) BeanPath.of(expression).getValue(bean);
}
/**
@ -372,11 +373,11 @@ public class BeanUtil {
* @param bean Bean对象支持MapListCollectionArray
* @param expression 表达式例如person.friend[5].name
* @param value 属性值
* @see BeanPathOld#get(Object)
* @see BeanPath#setValue(Object, Object)
* @since 4.0.6
*/
public static void setProperty(final Object bean, final String expression, final Object value) {
BeanPathOld.of(expression).set(bean, value);
BeanPath.of(expression).setValue(bean, value);
}
// --------------------------------------------------------------------------------------------- mapToBean
@ -625,9 +626,9 @@ public class BeanUtil {
}
return BeanCopier.of(bean, targetMap,
CopyOptions.of()
.setIgnoreNullValue(ignoreNullValue)
.setFieldEditor(keyEditor)
CopyOptions.of()
.setIgnoreNullValue(ignoreNullValue)
.setFieldEditor(keyEditor)
).copy();
}
@ -673,7 +674,7 @@ public class BeanUtil {
if (null == source) {
return null;
}
if(RecordUtil.isRecord(tClass)){
if (RecordUtil.isRecord(tClass)) {
// issue#I7EO3U
// 转换record时ignoreProperties无效
return (T) RecordConverter.INSTANCE.convert(tClass, source);
@ -741,7 +742,7 @@ public class BeanUtil {
}
// issue#3091
if(ClassUtil.isBasicType(targetType) || String.class == targetType){
if (ClassUtil.isBasicType(targetType) || String.class == targetType) {
return Convert.toList(targetType, collection);
}
@ -864,7 +865,7 @@ public class BeanUtil {
* @since 5.0.7
*/
public static boolean isNotEmpty(final Object bean, final String... ignoreFieldNames) {
if(null == bean){
if (null == bean) {
return false;
}
@ -887,7 +888,7 @@ public class BeanUtil {
public static boolean hasNullField(final Object bean, final String... ignoreFieldNames) {
return checkBean(bean, field ->
(!ArrayUtil.contains(ignoreFieldNames, field.getName()))
&& null == FieldUtil.getFieldValue(bean, field)
&& null == FieldUtil.getFieldValue(bean, field)
);
}
@ -913,11 +914,11 @@ public class BeanUtil {
* 断言为{@code true} 返回{@code true}并不再检查后续字段<br>
* 断言为{@code false}继续检查后续字段
*
* @param bean Bean
* @param bean Bean
* @param predicate 断言
* @return 是否触发断言为真
*/
public static boolean checkBean(final Object bean, final Predicate<Field> predicate){
public static boolean checkBean(final Object bean, final Predicate<Field> predicate) {
if (null == bean) {
return true;
}

View File

@ -12,6 +12,7 @@
package org.dromara.hutool.core.bean;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.convert.Convert;
@ -37,7 +38,7 @@ public class DynaBean implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private final Class<?> beanClass;
private final Object bean;
private Object bean;
/**
* 创建一个DynaBean
@ -101,6 +102,13 @@ public class DynaBean implements Cloneable, Serializable {
// 非数字see pr#254@Gitee
return (T) CollUtil.map((Collection<?>) bean, (beanEle) -> DynaBean.of(beanEle).get(fieldName), false);
}
} else if (ArrayUtil.isArray(bean)) {
try {
return ArrayUtil.get(bean, Integer.parseInt(fieldName));
} catch (final NumberFormatException e) {
// 非数字see pr#254@Gitee
return (T) ArrayUtil.map(bean, Object.class, (beanEle) -> DynaBean.of(beanEle).get(fieldName));
}
} else {
final PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName);
if (null == prop) {
@ -148,21 +156,27 @@ public class DynaBean implements Cloneable, Serializable {
*
* @param fieldName 字段名
* @param value 字段值
* @return this;
* @throws BeanException 反射获取属性值或字段值导致的异常
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public void set(final String fieldName, final Object value) throws BeanException {
public DynaBean set(final String fieldName, final Object value) throws BeanException {
if (Map.class.isAssignableFrom(beanClass)) {
((Map) bean).put(fieldName, value);
} else if (bean instanceof List) {
ListUtil.setOrPadding((List) bean, Convert.toInt(fieldName), value);
} else if (ArrayUtil.isArray(bean)) {
// issue#3008追加产生新数组此处返回新数组
this.bean = ArrayUtil.setOrPadding(bean, Convert.toInt(fieldName), value);
} else {
final PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName);
if (null == prop) {
throw new BeanException("No public field or set method for '{}'", fieldName);
}
prop.setValue(bean, value);
prop.setValue(bean, value, false, false);
}
return this;
}
/**

View File

@ -16,7 +16,10 @@ import org.dromara.hutool.core.annotation.AnnotationUtil;
import org.dromara.hutool.core.annotation.PropIgnore;
import org.dromara.hutool.core.convert.Convert;
import org.dromara.hutool.core.func.LambdaUtil;
import org.dromara.hutool.core.reflect.*;
import org.dromara.hutool.core.reflect.FieldUtil;
import org.dromara.hutool.core.reflect.ModifierUtil;
import org.dromara.hutool.core.reflect.ReflectUtil;
import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.reflect.method.MethodUtil;
import java.beans.Transient;

View File

@ -171,23 +171,29 @@ public class BeanPath implements Iterator<BeanPath> {
*
* @param bean Bean对象
* @param value 设置的值
* @return bean如果在原Bean对象基础上设置值返回原Bean否则返回新的Bean
*/
public void setValue(Object bean, final Object value) {
Object parentBean;
BeanPath beanPath = this;
while (beanPath.hasNext()) {
parentBean = bean;
bean = beanPath.node.getValue(bean);
if (null == bean) {
final BeanPath child = beanPath.next();
bean = isListNode(child.node) ? new ArrayList<>() : new HashMap<>();
beanPath.node.setValue(parentBean, bean);
// 如果自定义put方法修改了value此处二次get避免丢失
bean = beanPath.node.getValue(parentBean);
}
beanPath = beanPath.next();
public Object setValue(final Object bean, final Object value) {
if (!hasNext()) {
// 根节点直接赋值
return this.node.setValue(bean, value);
}
beanPath.node.setValue(bean, value);
final BeanPath childBeanPath = next();
Object subBean = this.node.getValue(bean);
if (null == subBean) {
subBean = isListNode(childBeanPath.node) ? new ArrayList<>() : new HashMap<>();
this.node.setValue(bean, subBean);
// 如果自定义put方法修改了value返回修改后的value避免值丢失
subBean = this.node.getValue(bean);
}
// 递归逐层查找子节点赋值
final Object newSubBean = childBeanPath.setValue(subBean, value);
if(newSubBean != subBean){
//对于数组对象set新值后会返回新的数组此时将新对象再加入父bean中覆盖旧数组
this.node.setValue(bean, newSubBean);
}
return bean;
}
@Override

View File

@ -30,7 +30,8 @@ public class EmptyNode implements Node {
}
@Override
public void setValue(final Object bean, final Object value) {
public Object setValue(final Object bean, final Object value) {
// do nothing
return bean;
}
}

View File

@ -65,7 +65,7 @@ public class ListNode implements Node{
}
@Override
public void setValue(final Object bean, final Object value) {
public Object setValue(final Object bean, final Object value) {
throw new UnsupportedOperationException("Can not set value to multi names.");
}

View File

@ -55,8 +55,8 @@ public class NameNode implements Node {
}
@Override
public void setValue(final Object bean, final Object value) {
DynaBean.of(bean).set(this.name, value);
public Object setValue(final Object bean, final Object value) {
return DynaBean.of(bean).set(this.name, value).getBean();
}
@Override

View File

@ -31,6 +31,7 @@ public interface Node {
*
* @param bean bean对象
* @param value 节点值
* @return bean对象如果在原Bean对象基础上设置值返回原Bean否则返回新的Bean
*/
void setValue(Object bean, Object value);
Object setValue(Object bean, Object value);
}

View File

@ -59,7 +59,7 @@ public class RangeNode implements Node {
}
@Override
public void setValue(final Object bean, final Object value) {
public Object setValue(final Object bean, final Object value) {
throw new UnsupportedOperationException("Can not set value with step name.");
}

View File

@ -12,26 +12,21 @@
package org.dromara.hutool.core.map;
import org.dromara.hutool.core.bean.BeanPathOld;
import org.dromara.hutool.core.bean.BeanUtil;
import org.dromara.hutool.core.bean.copier.CopyOptions;
import org.dromara.hutool.core.bean.path.BeanPath;
import org.dromara.hutool.core.collection.set.SetUtil;
import org.dromara.hutool.core.convert.Convert;
import org.dromara.hutool.core.exception.CloneException;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.func.LambdaInfo;
import org.dromara.hutool.core.func.LambdaUtil;
import org.dromara.hutool.core.func.SerFunction;
import org.dromara.hutool.core.func.SerSupplier;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.lang.getter.TypeGetter;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.*;
/**
* 字典对象扩充了LinkedHashMap中的方法
@ -423,12 +418,12 @@ public class Dict extends CustomKeyMap<String, Object> implements TypeGetter<Str
* @param <T> 目标类型
* @param expression 表达式
* @return 对象
* @see BeanPathOld#get(Object)
* @see BeanPath#getValue(Object)
* @since 5.7.14
*/
@SuppressWarnings("unchecked")
public <T> T getByPath(final String expression) {
return (T) BeanPathOld.of(expression).get(this);
return (T) BeanPath.of(expression).getValue(this);
}
/**
@ -453,7 +448,7 @@ public class Dict extends CustomKeyMap<String, Object> implements TypeGetter<Str
* @param expression 表达式
* @param resultType 返回值类型
* @return 对象
* @see BeanPathOld#get(Object)
* @see BeanPath#getValue(Object)
* @since 5.7.14
*/
public <T> T getByPath(final String expression, final Type resultType) {

View File

@ -1,210 +0,0 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.bean;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.lang.test.bean.ExamInfoDict;
import org.dromara.hutool.core.lang.test.bean.UserInfoDict;
import org.dromara.hutool.core.map.Dict;
import lombok.Data;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* {@link BeanPathOld} 单元测试
*
* @author looly
*
*/
public class BeanPathTest {
Map<String, Object> tempMap;
@BeforeEach
public void init() {
// ------------------------------------------------- 考试信息列表
final ExamInfoDict examInfoDict = new ExamInfoDict();
examInfoDict.setId(1);
examInfoDict.setExamType(0);
examInfoDict.setAnswerIs(1);
final ExamInfoDict examInfoDict1 = new ExamInfoDict();
examInfoDict1.setId(2);
examInfoDict1.setExamType(0);
examInfoDict1.setAnswerIs(0);
final ExamInfoDict examInfoDict2 = new ExamInfoDict();
examInfoDict2.setId(3);
examInfoDict2.setExamType(1);
examInfoDict2.setAnswerIs(0);
final List<ExamInfoDict> examInfoDicts = new ArrayList<>();
examInfoDicts.add(examInfoDict);
examInfoDicts.add(examInfoDict1);
examInfoDicts.add(examInfoDict2);
// ------------------------------------------------- 用户信息
final UserInfoDict userInfoDict = new UserInfoDict();
userInfoDict.setId(1);
userInfoDict.setPhotoPath("yx.mm.com");
userInfoDict.setRealName("张三");
userInfoDict.setExamInfoDict(examInfoDicts);
tempMap = new HashMap<>();
tempMap.put("userInfo", userInfoDict);
tempMap.put("flag", 1);
}
@Test
public void beanPathTest1() {
final BeanPathOld pattern = new BeanPathOld("userInfo.examInfoDict[0].id");
Assertions.assertEquals("userInfo", pattern.patternParts.get(0));
Assertions.assertEquals("examInfoDict", pattern.patternParts.get(1));
Assertions.assertEquals("0", pattern.patternParts.get(2));
Assertions.assertEquals("id", pattern.patternParts.get(3));
}
@Test
public void beanPathTest2() {
final BeanPathOld pattern = new BeanPathOld("[userInfo][examInfoDict][0][id]");
Assertions.assertEquals("userInfo", pattern.patternParts.get(0));
Assertions.assertEquals("examInfoDict", pattern.patternParts.get(1));
Assertions.assertEquals("0", pattern.patternParts.get(2));
Assertions.assertEquals("id", pattern.patternParts.get(3));
}
@Test
public void beanPathTest3() {
final BeanPathOld pattern = new BeanPathOld("['userInfo']['examInfoDict'][0]['id']");
Assertions.assertEquals("userInfo", pattern.patternParts.get(0));
Assertions.assertEquals("examInfoDict", pattern.patternParts.get(1));
Assertions.assertEquals("0", pattern.patternParts.get(2));
Assertions.assertEquals("id", pattern.patternParts.get(3));
}
@Test
public void getTest() {
final BeanPathOld pattern = BeanPathOld.of("userInfo.examInfoDict[0].id");
final Object result = pattern.get(tempMap);
Assertions.assertEquals(1, result);
}
@Test
public void setTest() {
final BeanPathOld pattern = BeanPathOld.of("userInfo.examInfoDict[0].id");
pattern.set(tempMap, 2);
final Object result = pattern.get(tempMap);
Assertions.assertEquals(2, result);
}
@Test
public void getMapTest () {
final BeanPathOld pattern = BeanPathOld.of("userInfo[id, photoPath]");
@SuppressWarnings("unchecked")
final Map<String, Object> result = (Map<String, Object>)pattern.get(tempMap);
Assertions.assertEquals(1, result.get("id"));
Assertions.assertEquals("yx.mm.com", result.get("photoPath"));
}
@Test
public void getKeyWithDotTest () {
final Map<String, Object> dataMap = new HashMap<>(16);
dataMap.put("aa", "value0");
dataMap.put("aa.bb.cc", "value111111");// key 是类名 格式 ' . '
final BeanPathOld pattern = BeanPathOld.of("'aa.bb.cc'");
Assertions.assertEquals("value111111", pattern.get(dataMap));
}
@Test
public void compileTest(){
final BeanPathOld of = BeanPathOld.of("'abc.dd'.ee.ff'.'");
Assertions.assertEquals("abc.dd", of.getPatternParts().get(0));
Assertions.assertEquals("ee", of.getPatternParts().get(1));
Assertions.assertEquals("ff.", of.getPatternParts().get(2));
}
@Test
public void issue2362Test() {
final Map<String, Object> map = new HashMap<>();
BeanPathOld beanPath = BeanPathOld.of("list[0].name");
beanPath.set(map, "张三");
Assertions.assertEquals("{list=[{name=张三}]}", map.toString());
map.clear();
beanPath = BeanPathOld.of("list[1].name");
beanPath.set(map, "张三");
Assertions.assertEquals("{list=[null, {name=张三}]}", map.toString());
map.clear();
beanPath = BeanPathOld.of("list[0].1.name");
beanPath.set(map, "张三");
Assertions.assertEquals("{list=[[null, {name=张三}]]}", map.toString());
}
@Test
public void putTest() {
final Map<String, Object> map = new HashMap<>();
final BeanPathOld beanPath = BeanPathOld.of("list[1].name");
beanPath.set(map, "张三");
Assertions.assertEquals("{list=[null, {name=张三}]}", map.toString());
}
@Test
public void putByPathTest() {
final Dict dict = new Dict();
BeanPathOld.of("aa.bb").set(dict, "BB");
Assertions.assertEquals("{aa={bb=BB}}", dict.toString());
}
@Test
public void appendArrayTest(){
// issue#3008@Github
final MyUser myUser = new MyUser();
BeanPathOld.of("hobby[0]").set(myUser, "LOL");
BeanPathOld.of("hobby[1]").set(myUser, "KFC");
BeanPathOld.of("hobby[2]").set(myUser, "COFFE");
Assertions.assertEquals("[LOL, KFC, COFFE]", ArrayUtil.toString(myUser.getHobby()));
}
@Test
public void appendArrayTest2(){
// issue#3008@Github
final MyUser2 myUser = new MyUser2();
BeanPathOld.of("myUser.hobby[0]").set(myUser, "LOL");
BeanPathOld.of("myUser.hobby[1]").set(myUser, "KFC");
BeanPathOld.of("myUser.hobby[2]").set(myUser, "COFFE");
Assertions.assertEquals("[LOL, KFC, COFFE]", ArrayUtil.toString(myUser.getMyUser().getHobby()));
}
@Data
static class MyUser {
private String[] hobby;
}
@Data
static class MyUser2 {
private MyUser myUser;
}
}

View File

@ -12,10 +12,16 @@
package org.dromara.hutool.core.bean;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.dromara.hutool.core.annotation.Alias;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.bean.copier.CopyOptions;
import org.dromara.hutool.core.bean.copier.ValueProvider;
import org.dromara.hutool.core.bean.path.BeanPath;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.collection.set.SetUtil;
import org.dromara.hutool.core.map.MapBuilder;
@ -23,11 +29,6 @@ import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.thread.ThreadUtil;
import org.dromara.hutool.core.util.ObjUtil;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@ -767,8 +768,8 @@ public class BeanUtilTest {
testPojo.setTestPojo2List(new TestPojo2[]{testPojo2, testPojo3});
final BeanPathOld beanPath = BeanPathOld.of("testPojo2List.age");
final Object o = beanPath.get(testPojo);
final BeanPath beanPath = BeanPath.of("testPojo2List.age");
final Object o = beanPath.getValue(testPojo);
Assertions.assertEquals(Integer.valueOf(2), ArrayUtil.get(o, 0));
Assertions.assertEquals(Integer.valueOf(3), ArrayUtil.get(o, 1));

View File

@ -12,8 +12,11 @@
package org.dromara.hutool.core.bean.path;
import lombok.Data;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.lang.test.bean.ExamInfoDict;
import org.dromara.hutool.core.lang.test.bean.UserInfoDict;
import org.dromara.hutool.core.map.Dict;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -113,4 +116,52 @@ public class BeanPathGetOrSetValueTest {
beanPath.setValue(map, "张三");
Assertions.assertEquals("{list=[[null, {name=张三}]]}", map.toString());
}
@Test
public void putTest() {
final Map<String, Object> map = new HashMap<>();
final BeanPath beanPath = BeanPath.of("list[1].name");
beanPath.setValue(map, "张三");
Assertions.assertEquals("{list=[null, {name=张三}]}", map.toString());
}
@Test
public void putByPathTest() {
final Dict dict = new Dict();
BeanPath.of("aa.bb").setValue(dict, "BB");
Assertions.assertEquals("{aa={bb=BB}}", dict.toString());
}
@Test
public void appendArrayTest(){
// issue#3008@Github
final MyUser myUser = new MyUser();
BeanPath.of("hobby[0]").setValue(myUser, "LOL");
BeanPath.of("hobby[1]").setValue(myUser, "KFC");
BeanPath.of("hobby[2]").setValue(myUser, "COFFE");
Assertions.assertEquals("[LOL, KFC, COFFE]", ArrayUtil.toString(myUser.getHobby()));
}
@Test
public void appendArrayTest2(){
// issue#3008@Github
final MyUser2 myUser = new MyUser2();
BeanPath.of("myUser.hobby[0]").setValue(myUser, "LOL");
BeanPath.of("myUser.hobby[1]").setValue(myUser, "KFC");
BeanPath.of("myUser.hobby[2]").setValue(myUser, "COFFE");
Assertions.assertEquals("[LOL, KFC, COFFE]", ArrayUtil.toString(myUser.getMyUser().getHobby()));
}
@Data
static class MyUser {
private String[] hobby;
}
@Data
static class MyUser2 {
private MyUser myUser;
}
}

View File

@ -13,11 +13,6 @@
package org.dromara.hutool.core.reflect;
import lombok.Data;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
/**
* 反射工具类单元测试

View File

@ -12,7 +12,7 @@
package org.dromara.hutool.json;
import org.dromara.hutool.core.bean.BeanPathOld;
import org.dromara.hutool.core.bean.path.BeanPath;
import org.dromara.hutool.core.convert.ConvertException;
import org.dromara.hutool.core.convert.Converter;
import org.dromara.hutool.core.lang.mutable.MutableEntry;
@ -64,11 +64,11 @@ public interface JSON extends Converter, Cloneable, Serializable {
*
* @param expression 表达式
* @return 对象
* @see BeanPathOld#get(Object)
* @see BeanPath#getValue(Object)
* @since 4.0.6
*/
default Object getByPath(final String expression) {
return BeanPathOld.of(expression).get(this);
return BeanPath.of(expression).getValue(this);
}
/**
@ -93,7 +93,7 @@ public interface JSON extends Converter, Cloneable, Serializable {
* @param value
*/
default void putByPath(final String expression, final Object value) {
BeanPathOld.of(expression).set(this, value);
BeanPath.of(expression).setValue(this, value);
}
/**
@ -118,11 +118,11 @@ public interface JSON extends Converter, Cloneable, Serializable {
* @param expression 表达式
* @param resultType 返回值类型
* @return 对象
* @see BeanPathOld#get(Object)
* @see BeanPath#getValue(Object)
* @since 4.0.6
*/
@SuppressWarnings("unchecked")
default <T> T getByPath(final String expression, final Type resultType){
default <T> T getByPath(final String expression, final Type resultType) {
return (T) config().getConverter().convert(resultType, getByPath(expression));
}

View File

@ -340,7 +340,7 @@ public class JWT implements RegisteredPayload<JWT> {
*
* <p>此方法会补充如下的header</p>
* <ul>
* <li>当用户未定义"typ"赋默认值"JWT"</li>
* <li>当用户未定义"typ"不设置默认值</li>
* <li>当用户未定义"alg"根据传入的{@link JWTSigner}对象类型赋值对应ID</li>
* </ul>
*
@ -350,12 +350,6 @@ public class JWT implements RegisteredPayload<JWT> {
public String sign(final JWTSigner signer) {
Assert.notNull(signer, () -> new JWTException("No Signer provided!"));
// 检查tye信息
final String type = (String) this.header.getClaim(JWTHeader.TYPE);
if (StrUtil.isBlank(type)) {
this.header.setType("JWT");
}
// 检查头信息中是否有算法信息
final String algorithm = (String) this.header.getClaim(JWTHeader.ALGORITHM);
if (StrUtil.isBlank(algorithm)) {

View File

@ -12,6 +12,7 @@
package org.dromara.hutool.json.jwt;
import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.json.jwt.signers.JWTSigner;
import java.util.Map;
@ -29,7 +30,7 @@ public class JWTUtil {
* @return JWT Token
*/
public static String createToken(final Map<String, Object> payload, final byte[] key) {
return createToken(null, payload, key);
return createToken(MapUtil.of(JWTHeader.TYPE, "JWT"), payload, key);
}
/**

View File

@ -27,18 +27,19 @@ import java.util.*;
public class JWTTest {
@Test
public void createHs256Test(){
public void createHs256Test() {
final byte[] key = "1234567890".getBytes();
final JWT jwt = JWT.of()
.setPayload("sub", "1234567890")
.setPayload("name", "looly")
.setPayload("admin", true)
.setExpiresAt(DateUtil.parse("2022-01-01"))
.setKey(key);
.setHeader(JWTHeader.TYPE, "JWT")
.setPayload("sub", "1234567890")
.setPayload("name", "looly")
.setPayload("admin", true)
.setExpiresAt(DateUtil.parse("2022-01-01"))
.setKey(key);
final String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." +
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Imxvb2x5IiwiYWRtaW4iOnRydWUsImV4cCI6MTY0MDk2NjQwMH0." +
"bXlSnqVeJXWqUIt7HyEhgKNVlIPjkumHlAwFY-5YCtk";
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Imxvb2x5IiwiYWRtaW4iOnRydWUsImV4cCI6MTY0MDk2NjQwMH0." +
"bXlSnqVeJXWqUIt7HyEhgKNVlIPjkumHlAwFY-5YCtk";
final String token = jwt.sign();
Assertions.assertEquals(rightToken, token);
@ -47,10 +48,10 @@ public class JWTTest {
}
@Test
public void parseTest(){
public void parseTest() {
final String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9." +
"eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9." +
"U2aQkC2THYV9L0fTN-yBBI7gmo5xhmvMhATtu8v0zEA";
"eyJzdWIiOiIxMjM0NTY3ODkwIiwiYWRtaW4iOnRydWUsIm5hbWUiOiJsb29seSJ9." +
"U2aQkC2THYV9L0fTN-yBBI7gmo5xhmvMhATtu8v0zEA";
final JWT jwt = JWT.of(rightToken);
@ -68,15 +69,16 @@ public class JWTTest {
}
@Test
public void createNoneTest(){
public void createNoneTest() {
final JWT jwt = JWT.of()
.setPayload("sub", "1234567890")
.setPayload("name", "looly")
.setPayload("admin", true)
.setSigner(JWTSignerUtil.none());
.setHeader(JWTHeader.TYPE, "JWT")
.setPayload("sub", "1234567890")
.setPayload("name", "looly")
.setPayload("admin", true)
.setSigner(JWTSignerUtil.none());
final String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0." +
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Imxvb2x5IiwiYWRtaW4iOnRydWV9.";
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Imxvb2x5IiwiYWRtaW4iOnRydWV9.";
final String token = jwt.sign();
Assertions.assertEquals(rightToken, token);
@ -88,8 +90,8 @@ public class JWTTest {
* 必须定义签名器
*/
@Test
public void needSignerTest(){
Assertions.assertThrows(JWTException.class, ()->{
public void needSignerTest() {
Assertions.assertThrows(JWTException.class, () -> {
final JWT jwt = JWT.of()
.setPayload("sub", "1234567890")
.setPayload("name", "looly")
@ -100,10 +102,10 @@ public class JWTTest {
}
@Test
public void verifyTest(){
public void verifyTest() {
final String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." +
"eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2MjQwMDQ4MjIsInVzZXJJZCI6MSwiYXV0aG9yaXRpZXMiOlsiUk9MRV_op5LoibLkuozlj7ciLCJzeXNfbWVudV8xIiwiUk9MRV_op5LoibLkuIDlj7ciLCJzeXNfbWVudV8yIl0sImp0aSI6ImQ0YzVlYjgwLTA5ZTctNGU0ZC1hZTg3LTVkNGI5M2FhNmFiNiIsImNsaWVudF9pZCI6ImhhbmR5LXNob3AifQ." +
"aixF1eKlAKS_k3ynFnStE7-IRGiD5YaqznvK2xEjBew";
"eyJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2MjQwMDQ4MjIsInVzZXJJZCI6MSwiYXV0aG9yaXRpZXMiOlsiUk9MRV_op5LoibLkuozlj7ciLCJzeXNfbWVudV8xIiwiUk9MRV_op5LoibLkuIDlj7ciLCJzeXNfbWVudV8yIl0sImp0aSI6ImQ0YzVlYjgwLTA5ZTctNGU0ZC1hZTg3LTVkNGI5M2FhNmFiNiIsImNsaWVudF9pZCI6ImhhbmR5LXNob3AifQ." +
"aixF1eKlAKS_k3ynFnStE7-IRGiD5YaqznvK2xEjBew";
final boolean verify = JWT.of(token).setKey(ByteUtil.toUtf8Bytes("123456")).verify();
Assertions.assertTrue(verify);
@ -114,6 +116,7 @@ public class JWTTest {
private String name;
private Integer age;
}
@Test
public void payloadTest() {
@ -130,6 +133,7 @@ public class JWTTest {
map.put("test2", "2");
final Map<String, Object> payload = new HashMap<String, Object>() {
private static final long serialVersionUID = 1L;
{
put("username", username);
put("bean", bean);
@ -161,11 +165,11 @@ public class JWTTest {
}
@Test()
public void getDateTest(){
public void getDateTest() {
final String token = JWT.of()
.setIssuedAt(DateUtil.parse("2022-02-02"))
.setKey("123456".getBytes())
.sign();
.setIssuedAt(DateUtil.parse("2022-02-02"))
.setKey("123456".getBytes())
.sign();
// 签发时间早于被检查的时间
final Date date = JWT.of(token).getPayload().getClaimsJson().getDate(JWTPayload.ISSUED_AT);
@ -173,7 +177,7 @@ public class JWTTest {
}
@Test
public void issue2581Test(){
public void issue2581Test() {
final Map<String, Object> map = new HashMap<>();
map.put("test2", 22222222222222L);
final JWTSigner jwtSigner = JWTSignerUtil.createSigner(AlgorithmUtil.getAlgorithm("HS256"), Base64.getDecoder().decode("abcdefghijklmn"));
@ -183,17 +187,17 @@ public class JWTTest {
}
@Test
public void getLongTest(){
public void getLongTest() {
final String rightToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9"
+ ".eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJhZG1pbiIsImRldmljZSI6ImRlZmF1bHQtZGV2aWNlIiwiZWZmIjoxNjc4Mjg1NzEzOTM1LCJyblN0ciI6IkVuMTczWFhvWUNaaVZUWFNGOTNsN1pabGtOalNTd0pmIn0"
+ ".wRe2soTaWYPhwcjxdzesDi1BgEm9D61K-mMT3fPc4YM";
+ ".eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJhZG1pbiIsImRldmljZSI6ImRlZmF1bHQtZGV2aWNlIiwiZWZmIjoxNjc4Mjg1NzEzOTM1LCJyblN0ciI6IkVuMTczWFhvWUNaaVZUWFNGOTNsN1pabGtOalNTd0pmIn0"
+ ".wRe2soTaWYPhwcjxdzesDi1BgEm9D61K-mMT3fPc4YM";
final JWT jwt = JWTUtil.parseToken(rightToken);
Assertions.assertEquals(
"{\"loginType\":\"login\",\"loginId\":\"admin\",\"device\":\"default-device\"," +
"\"eff\":1678285713935,\"rnStr\":\"En173XXoYCZiVTXSF93l7ZZlkNjSSwJf\"}",
jwt.getPayloads().toString());
"{\"loginType\":\"login\",\"loginId\":\"admin\",\"device\":\"default-device\"," +
"\"eff\":1678285713935,\"rnStr\":\"En173XXoYCZiVTXSF93l7ZZlkNjSSwJf\"}",
jwt.getPayloads().toString());
Assertions.assertEquals(Long.valueOf(1678285713935L), jwt.getPayloads().getLong("eff"));
}
}