This commit is contained in:
Looly 2024-09-24 00:35:58 +08:00
parent 4afb8c581e
commit 1b92f58bae
32 changed files with 554 additions and 374 deletions

View File

@ -42,10 +42,11 @@ import java.util.Iterator;
* ['person']['friends'][5]['name']
* </pre>
*
* @param <T> Bean类型
* @author Looly
* @since 6.0.0
*/
public class BeanPath implements Iterator<BeanPath> {
public class BeanPath<T> implements Iterator<BeanPath<T>> {
/**
* 表达式边界符号数组
@ -58,21 +59,34 @@ public class BeanPath implements Iterator<BeanPath> {
* @param expression 表达式
* @return BeanPath
*/
public static BeanPath of(final String expression) {
return new BeanPath(expression);
public static BeanPath<Object> of(final String expression) {
return new BeanPath<>(expression, DefaultNodeBeanFactory.INSTANCE);
}
/**
* 创建Bean路径
*
* @param expression 表达式
* @param beanFactory NodeBean工厂用于Bean的值创建获取和设置
* @param <T> Bean类型
* @return BeanPath
*/
public static <T> BeanPath<T> of(final String expression, final NodeBeanFactory<T> beanFactory) {
return new BeanPath<>(expression, beanFactory);
}
private final Node node;
private final String child;
private NodeBeanCreator beanCreator;
private final NodeBeanFactory<T> beanFactory;
/**
* 构造
*
* @param expression 表达式
* @param expression 表达式
* @param beanFactory NodeBean工厂用于Bean的值创建获取和设置
*/
public BeanPath(final String expression) {
this.beanCreator = DefaultNodeBeanCreator.INSTANCE;
public BeanPath(final String expression, final NodeBeanFactory<T> beanFactory) {
this.beanFactory = beanFactory;
final int length = expression.length();
final StringBuilder builder = new StringBuilder();
@ -127,17 +141,6 @@ public class BeanPath implements Iterator<BeanPath> {
}
}
/**
* 设置Bean创建器用于创建Bean对象默认为{@link DefaultNodeBeanCreator}
*
* @param beanCreator Bean创建器
* @return this
*/
public BeanPath setBeanCreator(final NodeBeanCreator beanCreator) {
this.beanCreator = beanCreator;
return this;
}
/**
* 获取节点
*
@ -162,8 +165,8 @@ public class BeanPath implements Iterator<BeanPath> {
}
@Override
public BeanPath next() {
return new BeanPath(this.child);
public BeanPath<T> next() {
return new BeanPath<>(this.child, this.beanFactory);
}
/**
@ -172,15 +175,16 @@ public class BeanPath implements Iterator<BeanPath> {
* @param bean Bean对象
* @return 路径对应的值
*/
public Object getValue(final Object bean) {
final Object value = this.node.getValue(bean);
if(null == value){
@SuppressWarnings("unchecked")
public Object getValue(final T bean) {
final Object value = beanFactory.getValue(bean, this);
if (null == value) {
return null;
}
if (!hasNext()) {
return value;
}
return next().getValue(value);
return next().getValue((T) value);
}
/**
@ -190,25 +194,27 @@ public class BeanPath implements Iterator<BeanPath> {
* @param value 设置的值
* @return bean如果在原Bean对象基础上设置值返回原Bean否则返回新的Bean
*/
public Object setValue(final Object bean, final Object value) {
@SuppressWarnings({"ReassignedVariable", "unchecked"})
public Object setValue(final T bean, final Object value) {
final NodeBeanFactory<T> beanFactory = this.beanFactory;
if (!hasNext()) {
// 根节点直接赋值
return this.node.setValue(bean, value);
return beanFactory.setValue(bean, value, this);
}
final BeanPath childBeanPath = next();
Object subBean = this.node.getValue(bean);
final BeanPath<T> childBeanPath = next();
Object subBean = beanFactory.getValue(bean, this);
if (null == subBean) {
subBean = beanCreator.create(bean, this);
this.node.setValue(bean, subBean);
subBean = beanFactory.create(bean, this);
beanFactory.setValue(bean, subBean, this);
// 如果自定义put方法修改了value返回修改后的value避免值丢失
subBean = this.node.getValue(bean);
subBean = beanFactory.getValue(bean, this);
}
// 递归逐层查找子节点赋值
final Object newSubBean = childBeanPath.setValue(subBean, value);
if(newSubBean != subBean){
final Object newSubBean = childBeanPath.setValue((T) subBean, value);
if (newSubBean != subBean) {
//对于数组对象set新值后会返回新的数组此时将新对象再加入父bean中覆盖旧数组
this.node.setValue(bean, newSubBean);
beanFactory.setValue(bean, newSubBean, this);
}
return bean;
}

View File

@ -1,69 +0,0 @@
/*
* Copyright (c) 2024 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.hutool.core.bean.path;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.bean.path.node.NameNode;
import org.dromara.hutool.core.bean.path.node.Node;
import org.dromara.hutool.core.reflect.ConstructorUtil;
import org.dromara.hutool.core.reflect.FieldUtil;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 默认的Bean创建器
*
* @author looly
* @since 6.0.0
*/
public class DefaultNodeBeanCreator implements NodeBeanCreator {
/**
* 单例
*/
public static final NodeBeanCreator INSTANCE = new DefaultNodeBeanCreator();
@Override
public Object create(final Object parent, final BeanPath beanPath) {
if(parent instanceof Map || parent instanceof List || ArrayUtil.isArray(parent)){
// 根据下一个节点类型判断当前节点名称对应类型
final Node node = beanPath.next().getNode();
if (node instanceof NameNode) {
return ((NameNode) node).isNumber() ? new ArrayList<>() : new HashMap<>();
}
return new HashMap<>();
}
// 普通Bean
final Node node = beanPath.getNode();
if(node instanceof NameNode){
final String name = ((NameNode) node).getName();
final Field field = FieldUtil.getField(parent.getClass(), name);
if(null == field){
throw new IllegalArgumentException("No field found for name: " + name);
}
return ConstructorUtil.newInstanceIfPossible(field.getType());
}
throw new UnsupportedOperationException("Unsupported node type: " + node.getClass());
}
}

View File

@ -0,0 +1,167 @@
/*
* Copyright (c) 2024 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.hutool.core.bean.path;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.bean.BeanUtil;
import org.dromara.hutool.core.bean.DynaBean;
import org.dromara.hutool.core.bean.path.node.*;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.convert.ConvertUtil;
import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.reflect.ClassUtil;
import org.dromara.hutool.core.reflect.ConstructorUtil;
import org.dromara.hutool.core.reflect.FieldUtil;
import org.dromara.hutool.core.text.StrUtil;
import java.lang.reflect.Field;
import java.util.*;
/**
* 默认的Bean创建器
*
* @author looly
* @since 6.0.0
*/
public class DefaultNodeBeanFactory implements NodeBeanFactory<Object> {
/**
* 单例
*/
public static final DefaultNodeBeanFactory INSTANCE = new DefaultNodeBeanFactory();
@Override
public Object create(final Object parent, final BeanPath<Object> beanPath) {
if (parent instanceof Map || parent instanceof List || ArrayUtil.isArray(parent)) {
// 根据下一个节点类型判断当前节点名称对应类型
final Node node = beanPath.next().getNode();
if (node instanceof NameNode) {
return ((NameNode) node).isNumber() ? new ArrayList<>() : new HashMap<>();
}
return new HashMap<>();
}
// 普通Bean
final Node node = beanPath.getNode();
if (node instanceof NameNode) {
final String name = ((NameNode) node).getName();
final Field field = FieldUtil.getField(parent.getClass(), name);
if (null == field) {
throw new IllegalArgumentException("No field found for name: " + name);
}
return ConstructorUtil.newInstanceIfPossible(field.getType());
}
throw new UnsupportedOperationException("Unsupported node type: " + node.getClass());
}
@Override
public Object getValue(final Object bean, final BeanPath<Object> beanPath) {
final Node node = beanPath.getNode();
if (null == node || node instanceof EmptyNode) {
return null;
} else if (node instanceof ListNode) {
return getValueByListNode(bean, (ListNode) node);
} else if (node instanceof NameNode) {
return getValueByNameNode(bean, (NameNode) node);
} else if (node instanceof RangeNode) {
return getValueByRangeNode(bean, (RangeNode) node);
}
throw new UnsupportedOperationException("Unsupported node type: " + node.getClass());
}
@Override
public Object setValue(final Object bean, final Object value, final BeanPath<Object> beanPath) {
final Node node = beanPath.getNode();
if (null == node || node instanceof EmptyNode) {
return bean;
} else if (node instanceof NameNode) {
return DynaBean.of(bean).set(((NameNode) node).getName(), value).getBean();
}
throw new UnsupportedOperationException("Unsupported node type: " + node.getClass());
}
/**
* 获取指定名称或下标列表对应的值<br>
* 如果为name列表则获取Map或Bean中对应key或字段值列表<br>
* 如果为数字列表则获取对应下标值列表
*
* @param bean Bean
* @param node 列表节点
* @return
*/
@SuppressWarnings("unchecked")
private static Object getValueByListNode(final Object bean, final ListNode node) {
final String[] names = node.getUnWrappedNames();
if (bean instanceof Collection) {
return CollUtil.getAny((Collection<?>) bean, ConvertUtil.convert(int[].class, names));
} else if (ArrayUtil.isArray(bean)) {
return ArrayUtil.getAny(bean, ConvertUtil.convert(int[].class, names));
} else {
final Map<String, Object> map;
if (bean instanceof Map) {
// 只支持String为key的Map
map = (Map<String, Object>) bean;
} else {
// 一次性使用包装Bean避免无用转换
map = BeanUtil.toBeanMap(bean);
}
return MapUtil.getAny(map, names);
}
}
/**
* 获取指定名称的值支持MapBean等
*
* @param bean Bean
* @param node 节点
* @return
*/
private static Object getValueByNameNode(final Object bean, final NameNode node) {
final String name = node.getName();
if ("$".equals(name)) {
return bean;
}
Object value = DynaBean.of(bean).get(name);
if (null == value && StrUtil.lowerFirst(ClassUtil.getClassName(bean, true)).equals(name)) {
// 如果bean类名与属性名相同则返回bean本身
value = bean;
}
return value;
}
/**
* 获取指定范围的值只支持集合和数组
*
* @param bean Bean
* @param node 范围节点
* @return
*/
private static Object getValueByRangeNode(final Object bean, final RangeNode node) {
if (bean instanceof Collection) {
return CollUtil.sub((Collection<?>) bean, node.getStart(), node.getEnd(), node.getStep());
} else if (ArrayUtil.isArray(bean)) {
return ArrayUtil.sub(bean, node.getStart(), node.getEnd(), node.getStep());
}
throw new UnsupportedOperationException("Can not get range value for: " + bean.getClass());
}
}

View File

@ -17,22 +17,41 @@
package org.dromara.hutool.core.bean.path;
/**
* BeanPath节点对应的Bean创建器<br>
* 用于创建Bean路径节点对应的Bean
* BeanPath节点对应的Bean工厂提供Bean的创建获取和设置接口<br>
*
* @param <T> Bean类型
* @author looly
* @since 6.0.0
*/
public interface NodeBeanCreator {
public interface NodeBeanFactory<T> {
/**
* 创建Bean<br>
* beanPath对应当前的路径即如果父对象为a则beanPath为a.b则创建的Bean为a.b.c对应的Bean对象<br>
* 给定的a一定存在但是本路径中b对应的Bean不存在则创建的对象是b的值这个值用c表示
*
* @param parent 父Bean
* @param beanPath 当前路径
* @param parent 父Bean
* @param beanPath 当前路径
* @return Bean
*/
Object create(final Object parent, final BeanPath beanPath);
T create(final T parent, final BeanPath<T> beanPath);
/**
* 获取Bean对应节点的值
*
* @param bean bean对象
* @param beanPath 当前路径
* @return 节点值
*/
Object getValue(T bean, final BeanPath<T> beanPath);
/**
* 设置节点值
*
* @param bean bean对象
* @param value 节点值
* @param beanPath 当前路径
* @return bean对象如果在原Bean对象基础上设置值返回原Bean否则返回新的Bean
*/
T setValue(T bean, Object value, final BeanPath<T> beanPath);
}

View File

@ -27,15 +27,4 @@ public class EmptyNode implements Node {
* 单例
*/
public static EmptyNode INSTANCE = new EmptyNode();
@Override
public Object getValue(final Object bean) {
return null;
}
@Override
public Object setValue(final Object bean, final Object value) {
// do nothing
return bean;
}
}

View File

@ -16,18 +16,11 @@
package org.dromara.hutool.core.bean.path.node;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.bean.BeanUtil;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.convert.ConvertUtil;
import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.text.split.SplitUtil;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* 列表节点
@ -35,56 +28,34 @@ import java.util.Map;
*
* @author looly
*/
public class ListNode implements Node{
public class ListNode implements Node {
final List<String> names;
/**
* 列表节点
*
* @param expression 表达式
*/
public ListNode(final String expression) {
this.names = SplitUtil.splitTrim(expression, StrUtil.COMMA);
}
@SuppressWarnings("unchecked")
@Override
public Object getValue(final Object bean) {
final List<String> names = this.names;
if (bean instanceof Collection) {
return CollUtil.getAny((Collection<?>) bean, ConvertUtil.convert(int[].class, names));
} else if (ArrayUtil.isArray(bean)) {
return ArrayUtil.getAny(bean, ConvertUtil.convert(int[].class, names));
} else {
final String[] unWrappedNames = getUnWrappedNames(names);
if (bean instanceof Map) {
// 只支持String为key的Map
return MapUtil.getAny((Map<String, ?>) bean, unWrappedNames);
} else {
// 一次性使用包装Bean避免无用转换
final Map<String, Object> map = BeanUtil.toBeanMap(bean);
return MapUtil.getAny(map, unWrappedNames);
}
}
}
@Override
public Object setValue(final Object bean, final Object value) {
throw new UnsupportedOperationException("Can not set value to multi names.");
}
@Override
public String toString() {
return this.names.toString();
/**
* 获取列表中的name不去除单引号
*
* @return name列表
*/
public String[] getNames() {
return this.names.toArray(new String[0]);
}
/**
* 将列表中的name去除单引号
* @param names name列表
*
* @return 处理后的name列表
*/
private String[] getUnWrappedNames(final List<String> names){
public String[] getUnWrappedNames() {
final String[] unWrappedNames = new String[names.size()];
for (int i = 0; i < unWrappedNames.length; i++) {
unWrappedNames[i] = StrUtil.unWrap(names.get(i), CharUtil.SINGLE_QUOTE);
@ -92,4 +63,9 @@ public class ListNode implements Node{
return unWrappedNames;
}
@Override
public String toString() {
return this.names.toString();
}
}

View File

@ -16,10 +16,7 @@
package org.dromara.hutool.core.bean.path.node;
import org.dromara.hutool.core.bean.DynaBean;
import org.dromara.hutool.core.math.NumberUtil;
import org.dromara.hutool.core.reflect.ClassUtil;
import org.dromara.hutool.core.text.StrUtil;
/**
* 处理名称节点或序号节点
@ -61,27 +58,6 @@ public class NameNode implements Node {
return NumberUtil.isInteger(name);
}
@Override
public Object getValue(final Object bean) {
if(null == bean){
return null;
}
if ("$".equals(name)) {
return bean;
}
Object value = DynaBean.of(bean).get(this.name);
if(null == value && StrUtil.lowerFirst(ClassUtil.getClassName(bean, true)).equals(this.name)){
// 如果bean类名与属性名相同则返回bean本身
value = bean;
}
return value;
}
@Override
public Object setValue(final Object bean, final Object value) {
return DynaBean.of(bean).set(this.name, value).getBean();
}
@Override
public String toString() {
return this.name;

View File

@ -22,20 +22,5 @@ package org.dromara.hutool.core.bean.path.node;
* @author looly
*/
public interface Node {
/**
* 获取Bean对应节点的值
*
* @param bean bean对象
* @return 节点值
*/
Object getValue(Object bean);
/**
* 设置节点值
*
* @param bean bean对象
* @param value 节点值
* @return bean对象如果在原Bean对象基础上设置值返回原Bean否则返回新的Bean
*/
Object setValue(Object bean, Object value);
}

View File

@ -16,12 +16,9 @@
package org.dromara.hutool.core.bean.path.node;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.text.split.SplitUtil;
import java.util.Collection;
import java.util.List;
/**
@ -51,20 +48,31 @@ public class RangeNode implements Node {
this.step = step;
}
@Override
public Object getValue(final Object bean) {
if (bean instanceof Collection) {
return CollUtil.sub((Collection<?>) bean, this.start, this.end, this.step);
} else if (ArrayUtil.isArray(bean)) {
return ArrayUtil.sub(bean, this.start, this.end, this.step);
}
throw new UnsupportedOperationException("Can not get range value for: " + bean.getClass());
/**
* 获取起始值
*
* @return 起始值
*/
public int getStart() {
return start;
}
@Override
public Object setValue(final Object bean, final Object value) {
throw new UnsupportedOperationException("Can not set value with step name.");
/**
* 获取结束值
*
* @return 结束值
*/
public int getEnd() {
return end;
}
/**
* 获取步进值
*
* @return 步进值
*/
public int getStep() {
return step;
}
@Override

View File

@ -28,7 +28,7 @@ import java.util.function.Function;
* 此类用于解决在JDK8中调用{@link ConcurrentHashMap#computeIfAbsent(Object, Function)}可能造成的死循环问题<br>
* issues#2349<br>
* <p>
* 相关bug见@see <a href="https://bugs.openjdk.java.net/browse/JDK-8161372">https://bugs.openjdk.java.net/browse/JDK-8161372</a>
* 相关bug见https://bugs.openjdk.java.net/browse/JDK-8161372
*
* @param <K> 键类型
* @param <V> 值类型

View File

@ -70,14 +70,14 @@ public class BeanPathGetOrSetValueTest {
@Test
public void getValueTest() {
final BeanPath pattern = new BeanPath("$.userInfo.examInfoDict[0].id");
final BeanPath<Object> pattern = BeanPath.of("$.userInfo.examInfoDict[0].id");
final Object result = pattern.getValue(tempMap);
Assertions.assertEquals(1, result);
}
@Test
public void setValueTest() {
final BeanPath pattern = new BeanPath("userInfo.examInfoDict[0].id");
final BeanPath<Object> pattern = BeanPath.of("userInfo.examInfoDict[0].id");
pattern.setValue(tempMap, 2);
final Object result = pattern.getValue(tempMap);
Assertions.assertEquals(2, result);
@ -85,7 +85,7 @@ public class BeanPathGetOrSetValueTest {
@Test
public void getMapTest () {
final BeanPath pattern = new BeanPath("userInfo[id, photoPath]");
final BeanPath<Object> pattern = BeanPath.of("userInfo[id, photoPath]");
@SuppressWarnings("unchecked")
final Map<String, Object> result = (Map<String, Object>)pattern.getValue(tempMap);
Assertions.assertEquals(1, result.get("id"));
@ -98,7 +98,7 @@ public class BeanPathGetOrSetValueTest {
dataMap.put("aa", "value0");
dataMap.put("aa.bb.cc", "value111111");// key 是类名 格式 ' . '
final BeanPath pattern = new BeanPath("'aa.bb.cc'");
final BeanPath<Object> pattern = BeanPath.of("'aa.bb.cc'");
Assertions.assertEquals("value111111", pattern.getValue(dataMap));
}
@ -106,7 +106,7 @@ public class BeanPathGetOrSetValueTest {
public void issue2362Test() {
final Map<String, Object> map = new HashMap<>();
BeanPath beanPath = BeanPath.of("list[0].name");
BeanPath<Object> beanPath = BeanPath.of("list[0].name");
beanPath.setValue(map, "张三");
Assertions.assertEquals("{list=[{name=张三}]}", map.toString());
@ -125,7 +125,7 @@ public class BeanPathGetOrSetValueTest {
public void putTest() {
final Map<String, Object> map = new HashMap<>();
final BeanPath beanPath = BeanPath.of("list[1].name");
final BeanPath<Object> beanPath = BeanPath.of("list[1].name");
beanPath.setValue(map, "张三");
Assertions.assertEquals("{list=[null, {name=张三}]}", map.toString());
}

View File

@ -23,7 +23,7 @@ public class BeanPathTest {
@Test
void parseDotTest() {
BeanPath beanPath = new BeanPath("userInfo.examInfoDict[0].id");
BeanPath<Object> beanPath = BeanPath.of("userInfo.examInfoDict[0].id");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("examInfoDict[0].id", beanPath.getChild());
@ -42,7 +42,7 @@ public class BeanPathTest {
@Test
void parseDotWithQuoteTest() {
BeanPath beanPath = new BeanPath("'userInfo'.examInfoDict[0].'id'");
BeanPath<Object> beanPath = BeanPath.of("'userInfo'.examInfoDict[0].'id'");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("examInfoDict[0].'id'", beanPath.getChild());
@ -61,7 +61,7 @@ public class BeanPathTest {
@Test
void parseDotWithQuoteTest2() {
BeanPath beanPath = new BeanPath("userInfo.'examInfoDict'[0].id");
BeanPath<Object> beanPath = BeanPath.of("userInfo.'examInfoDict'[0].id");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("'examInfoDict'[0].id", beanPath.getChild());
@ -80,7 +80,7 @@ public class BeanPathTest {
@Test
void parseBucketTest() {
BeanPath beanPath = new BeanPath("[userInfo][examInfoDict][0][id]");
BeanPath<Object> beanPath = BeanPath.of("[userInfo][examInfoDict][0][id]");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("[examInfoDict][0][id]", beanPath.getChild());
@ -99,7 +99,7 @@ public class BeanPathTest {
@Test
void parseBucketWithQuoteTest() {
BeanPath beanPath = new BeanPath("['userInfo']['examInfoDict'][0][id]");
BeanPath<Object> beanPath = BeanPath.of("['userInfo']['examInfoDict'][0][id]");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("['examInfoDict'][0][id]", beanPath.getChild());
@ -118,7 +118,7 @@ public class BeanPathTest {
@Test
void parseBucketWithQuoteTest2() {
BeanPath beanPath = new BeanPath("[userInfo][examInfoDict][0]['id']");
BeanPath<Object> beanPath = BeanPath.of("[userInfo][examInfoDict][0]['id']");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("[examInfoDict][0]['id']", beanPath.getChild());
@ -137,7 +137,7 @@ public class BeanPathTest {
@Test
void rangePathTest() {
BeanPath beanPath = new BeanPath("[userInfo][2:3]");
BeanPath<Object> beanPath = BeanPath.of("[userInfo][2:3]");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("[2:3]", beanPath.getChild());
@ -148,7 +148,7 @@ public class BeanPathTest {
@Test
void listPathTest() {
BeanPath beanPath = new BeanPath("[userInfo][1,2,3]");
BeanPath<Object> beanPath = BeanPath.of("[userInfo][1,2,3]");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("[1,2,3]", beanPath.getChild());
@ -159,7 +159,7 @@ public class BeanPathTest {
@Test
void listKeysPathTest() {
BeanPath beanPath = new BeanPath("[userInfo]['a', 'b', 'c']");
BeanPath<Object> beanPath = BeanPath.of("[userInfo]['a', 'b', 'c']");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("['a', 'b', 'c']", beanPath.getChild());

View File

@ -329,7 +329,7 @@ public class ConvertTest {
@Test
public void toClassTest(){
final Class<?> convert = ConvertUtil.convert(Class.class, "org.dromara.hutool.core.support.ConvertTest.Product");
final Class<?> convert = ConvertUtil.convert(Class.class, "org.dromara.hutool.core.convert.ConvertTest.Product");
Assertions.assertSame(Product.class, convert);
}

View File

@ -16,12 +16,14 @@
package org.dromara.hutool.core.lang;
import lombok.Data;
import org.dromara.hutool.core.exception.HutoolException;
import org.dromara.hutool.core.thread.ThreadUtil;
import lombok.Data;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.JRE;
import java.time.Duration;
import java.util.concurrent.LinkedBlockingQueue;
@ -30,8 +32,13 @@ import java.util.concurrent.TimeUnit;
public class SingletonTest {
/**
* JDK8下使用了SafeConcurrentHashMap为了解决JDK-8161372问题<br>
* 但是会导致可能的对象多次创建此处屏蔽JDK8的测试
*/
@SuppressWarnings("resource")
@Test
@DisabledOnJre(JRE.JAVA_8)
public void getTest(){
// 此测试中使用1000个线程获取单例对象其间对象只被创建一次
ThreadUtil.concurrencyTest(1000, ()-> Singleton.get(TestBean.class));

View File

@ -17,7 +17,6 @@
package org.dromara.hutool.core.util;
import org.dromara.hutool.core.lang.Console;
import org.dromara.hutool.core.util.JdkUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

View File

@ -21,7 +21,7 @@ import org.dromara.hutool.core.lang.mutable.MutableEntry;
import org.dromara.hutool.json.serializer.JSONDeserializer;
import org.dromara.hutool.json.serializer.JSONMapper;
import org.dromara.hutool.json.serializer.TypeAdapterManager;
import org.dromara.hutool.json.support.JSONNodeBeanCreator;
import org.dromara.hutool.json.support.JSONNodeBeanFactory;
import org.dromara.hutool.json.writer.JSONWriter;
import java.io.Serializable;
@ -139,7 +139,7 @@ public interface JSON extends Serializable {
* @param value
*/
default void putByPath(final String expression, final Object value) {
BeanPath.of(expression).setBeanCreator(new JSONNodeBeanCreator(config())).setValue(this, value);
BeanPath.of(expression, new JSONNodeBeanFactory(config())).setValue(this, value);
}
/**

View File

@ -84,6 +84,43 @@ public class JSONUtil {
public static JSONArray ofArray(final JSONConfig config) {
return new JSONArray(config);
}
/**
* 创建JSONPrimitive对象用于创建非JSON对象例如
* <pre>{@code
* JSONUtil.ofPrimitive(1);
* JSONUtil.ofPrimitive(1L);
* JSONUtil.ofPrimitive(1.0);
* JSONUtil.ofPrimitive(true);
* JSONUtil.ofPrimitive("str");
* }</pre>
*
* @param value
* @return JSONPrimitive对象
* @since 6.0.0
*/
public static JSONPrimitive ofPrimitive(final Object value) {
return ofPrimitive(value, JSONConfig.of());
}
/**
* 创建JSONPrimitive对象用于创建非JSON对象例如
* <pre>{@code
* JSONUtil.ofPrimitive(1, config);
* JSONUtil.ofPrimitive(1L, config);
* JSONUtil.ofPrimitive(1.0, config);
* JSONUtil.ofPrimitive(true, config);
* JSONUtil.ofPrimitive("str", config);
* }</pre>
*
* @param value
* @param config 配置
* @return JSONPrimitive对象
* @since 6.0.0
*/
public static JSONPrimitive ofPrimitive(final Object value, final JSONConfig config) {
return new JSONPrimitive(value, config);
}
// endregion
// region ----- parse
@ -121,7 +158,7 @@ public class JSONUtil {
* @return JSONObject
*/
public static JSONObject parseObj(Object obj, final JSONConfig config, final Predicate<MutableEntry<Object, Object>> predicate) {
if(obj instanceof byte[]){
if (obj instanceof byte[]) {
obj = new ByteArrayInputStream((byte[]) obj);
}
return (JSONObject) parse(obj, config, predicate);
@ -155,12 +192,12 @@ public class JSONUtil {
*
* @param arrayOrCollection 数组或集合对象
* @param config JSON配置
* @param predicate index和值对过滤编辑器可以通过实现此接口完成解析前对键值对的过滤和修改操作{@link Predicate#test(Object)}{@code true}保留
* @param predicate index和值对过滤编辑器可以通过实现此接口完成解析前对键值对的过滤和修改操作{@link Predicate#test(Object)}{@code true}保留
* @return JSONArray
* @since 5.3.1
*/
public static JSONArray parseArray(final Object arrayOrCollection, final JSONConfig config, final Predicate<MutableEntry<Object, Object>> predicate) {
if(arrayOrCollection instanceof JSONObject){
if (arrayOrCollection instanceof JSONObject) {
final JSONMapper jsonMapper = JSONMapper.of(config, predicate);
return jsonMapper.mapFromJSONObject((JSONObject) arrayOrCollection);
}
@ -192,8 +229,8 @@ public class JSONUtil {
* <li>Bean对象转为JSONObject</li>
* </ul>
*
* @param obj 对象
* @param config JSON配置{@code null}使用默认配置
* @param obj 对象
* @param config JSON配置{@code null}使用默认配置
* @return JSONJSONObject or JSONArray
*/
public static JSON parse(final Object obj, final JSONConfig config) {

View File

@ -17,8 +17,7 @@
package org.dromara.hutool.json.serializer;
import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.json.JSON;
import org.dromara.hutool.json.JSONConfig;
import org.dromara.hutool.json.*;
/**
* JSON序列化上下文用于获取当前JSON对象以便在序列化过程中获取配置信息
@ -43,4 +42,45 @@ public interface JSONContext {
default JSONConfig config() {
return ObjUtil.apply(getContextJson(), JSON::config);
}
/**
* 获取当前JSON对象如果为非JSONObject则创建一个JSONObject对象
*
* @return JSON对象
*/
default JSONObject getOrCreateObj() {
final JSON contextJson = getContextJson();
if (contextJson instanceof JSONObject) {
return (JSONObject) contextJson;
}
return JSONUtil.ofObj(config());
}
/**
* 获取当前JSON对象如果为非JSONArray则创建一个JSONArray对象
*
* @return JSON对象
*/
default JSONArray getOrCreateArray() {
final JSON contextJson = getContextJson();
if (contextJson instanceof JSONArray) {
return (JSONArray) contextJson;
}
return JSONUtil.ofArray(config());
}
/**
* 获取当前JSON对象如果为非JSONPrimitive则创建一个JSONPrimitive对象
*
* @param value
* @return JSON对象
*/
default JSONPrimitive getOrCreatePrimitive(final Object value) {
final JSON contextJson = getContextJson();
if (contextJson instanceof JSONPrimitive) {
return ((JSONPrimitive) contextJson).setValue(value);
}
return JSONUtil.ofPrimitive(value, config());
}
}

View File

@ -95,13 +95,7 @@ public class ArrayTypeAdapter implements MatcherJSONSerializer<Object>, MatcherJ
// https://github.com/dromara/hutool/issues/2369
// 非标准的二进制流则按照普通数组对待
final JSONArray result;
final JSON contextJson = context.getContextJson();
if (contextJson instanceof JSONArray) {
result = (JSONArray) contextJson;
} else {
result = JSONUtil.ofArray(config);
}
final JSONArray result = context.getOrCreateArray();
for (final byte b : bytes) {
result.set(b);
}

View File

@ -60,10 +60,7 @@ public class BeanTypeAdapter implements MatcherJSONSerializer<Object>, MatcherJS
@Override
public JSON serialize(final Object bean, final JSONContext context) {
JSONObject contextJson = (JSONObject) ObjUtil.apply(context, JSONContext::getContextJson);
if(null == contextJson){
contextJson = new JSONObject(context.config());
}
final JSONObject contextJson = context.getOrCreateObj();
final BeanToMapCopier copier = new BeanToMapCopier(
bean,

View File

@ -57,10 +57,7 @@ public class CharSequenceTypeAdapter implements MatcherJSONSerializer<CharSequen
final String jsonStr = StrUtil.trim(bean);
if (StrUtil.startWith(jsonStr, '<')) {
// 可能为XML
JSONObject jsonObject = (JSONObject) context.getContextJson();
if(null == jsonObject){
jsonObject = JSONUtil.ofObj(context.config());
}
final JSONObject jsonObject = context.getOrCreateObj();
JSONXMLParser.of(ParseConfig.of(), null).parseJSONObject(jsonStr, jsonObject);
return jsonObject;
}

View File

@ -22,7 +22,6 @@ import org.dromara.hutool.core.reflect.ConstructorUtil;
import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.json.JSON;
import org.dromara.hutool.json.JSONObject;
import org.dromara.hutool.json.JSONUtil;
import org.dromara.hutool.json.serializer.JSONContext;
import org.dromara.hutool.json.serializer.MatcherJSONDeserializer;
import org.dromara.hutool.json.serializer.MatcherJSONSerializer;
@ -59,15 +58,8 @@ public class EntryTypeAdapter implements MatcherJSONSerializer<Map.Entry<?, ?>>,
@Override
public JSON serialize(final Map.Entry<?, ?> bean, final JSONContext context) {
final JSONObject result;
final JSON contextJson = context.getContextJson();
if(contextJson instanceof JSONObject){
result = contextJson.asJSONObject();
}else{
result = JSONUtil.ofObj(context.config());
}
result.set(ConvertUtil.toStr(bean.getKey()), bean.getValue());
return result;
return context.getOrCreateObj()
.set(ConvertUtil.toStr(bean.getKey()), bean.getValue());
}
@Override

View File

@ -19,11 +19,9 @@ package org.dromara.hutool.json.serializer.impl;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.map.MapWrapper;
import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.json.JSON;
import org.dromara.hutool.json.JSONArray;
import org.dromara.hutool.json.JSONObject;
import org.dromara.hutool.json.JSONUtil;
import org.dromara.hutool.json.serializer.JSONContext;
import org.dromara.hutool.json.serializer.MatcherJSONDeserializer;
import org.dromara.hutool.json.serializer.MatcherJSONSerializer;
@ -72,10 +70,7 @@ public class IterTypeAdapter implements MatcherJSONSerializer<Object>, MatcherJS
iter = ((Iterable<?>) bean).iterator();
}
JSONArray json = (JSONArray) context.getContextJson();
if(null == json){
json = JSONUtil.ofArray(ObjUtil.apply(context, JSONContext::config));
}
final JSONArray json = context.getOrCreateArray();
mapFromIterator(bean, iter, json);
return json;
}

View File

@ -58,13 +58,7 @@ public class JSONPrimitiveTypeAdapter implements MatcherJSONSerializer<Object>,
bean = bean.toString();
}
final JSONPrimitive json = (JSONPrimitive) context.getContextJson();
if (null != json) {
json.setValue(bean);
return json;
}
return new JSONPrimitive(bean, context.config());
return context.getOrCreatePrimitive(bean);
}
@Override

View File

@ -23,7 +23,6 @@ import org.dromara.hutool.core.reflect.TypeUtil;
import org.dromara.hutool.core.util.ObjUtil;
import org.dromara.hutool.json.JSON;
import org.dromara.hutool.json.JSONObject;
import org.dromara.hutool.json.JSONUtil;
import org.dromara.hutool.json.serializer.JSONContext;
import org.dromara.hutool.json.serializer.MatcherJSONDeserializer;
import org.dromara.hutool.json.serializer.MatcherJSONSerializer;
@ -61,10 +60,7 @@ public class MapTypeAdapter implements MatcherJSONSerializer<Map<?, ?>>, Matcher
@Override
public JSON serialize(final Map<?, ?> bean, final JSONContext context) {
final JSON contextJson = context.getContextJson();
final JSONObject result = contextJson instanceof JSONObject ?
(JSONObject) contextJson : JSONUtil.ofObj(context.config());
final JSONObject result = context.getOrCreateObj();
// 注入键值对
for (final Map.Entry<?, ?> e : bean.entrySet()) {
result.set(ConvertUtil.toStr(e.getKey()), e.getValue());

View File

@ -48,15 +48,7 @@ public class ResourceBundleSerializer implements MatcherJSONSerializer<ResourceB
@Override
public JSON serialize(final ResourceBundle bean, final JSONContext context) {
final JSONObject result;
final JSON json = context.getContextJson();
if (json instanceof JSONObject) {
result = (JSONObject) json;
} else {
result = JSONUtil.ofObj(context.config());
}
final JSONObject result = context.getOrCreateObj();
mapFromResourceBundle(bean, result);
return result;
}

View File

@ -70,23 +70,22 @@ public class TemporalTypeAdapter implements MatcherJSONSerializer<TemporalAccess
@Override
public JSON serialize(final TemporalAccessor bean, final JSONContext context) {
final JSONConfig config = context.config();
// 如果上下文为JSONObject转为键值对形式
final JSON contextJson = context.getContextJson();
if(contextJson instanceof JSONObject){
if (contextJson instanceof JSONObject) {
toJSONObject(bean, contextJson.asJSONObject());
return contextJson;
}
if (bean instanceof Month) {
return new JSONPrimitive(((Month) bean).getValue(), config);
return context.getOrCreatePrimitive(((Month) bean).getValue());
} else if (bean instanceof DayOfWeek) {
return new JSONPrimitive(((DayOfWeek) bean).getValue(), config);
return context.getOrCreatePrimitive(((DayOfWeek) bean).getValue());
} else if (bean instanceof MonthDay) {
return new JSONPrimitive(((MonthDay) bean).toString(), config);
return context.getOrCreatePrimitive(((MonthDay) bean).toString());
}
final String format = ObjUtil.apply(config, JSONConfig::getDateFormat);
final String format = ObjUtil.apply(context.config(), JSONConfig::getDateFormat);
final Object value;
// 默认为时间戳
@ -98,7 +97,7 @@ public class TemporalTypeAdapter implements MatcherJSONSerializer<TemporalAccess
value = TimeUtil.format(bean, format);
}
return new JSONPrimitive(value, config);
return context.getOrCreatePrimitive(value);
}
@Override
@ -130,7 +129,7 @@ public class TemporalTypeAdapter implements MatcherJSONSerializer<TemporalAccess
/**
* {@link TemporalAccessor}转换为JSONObject
*
* @param bean {@link TemporalAccessor}
* @param bean {@link TemporalAccessor}
* @param json JSONObject
*/
private static void toJSONObject(final TemporalAccessor bean, final JSONObject json) {

View File

@ -1,60 +0,0 @@
/*
* Copyright (c) 2024 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.hutool.json.support;
import org.dromara.hutool.core.bean.path.BeanPath;
import org.dromara.hutool.core.bean.path.NodeBeanCreator;
import org.dromara.hutool.core.bean.path.node.NameNode;
import org.dromara.hutool.core.bean.path.node.Node;
import org.dromara.hutool.json.JSONConfig;
import org.dromara.hutool.json.JSONUtil;
/**
* JSON节点Bean创建器
*
* @author looly
* @since 6.0.0
*/
public class JSONNodeBeanCreator implements NodeBeanCreator {
private final JSONConfig config;
/**
* 构造
*
* @param config JSON配置
*/
public JSONNodeBeanCreator(final JSONConfig config) {
this.config = config;
}
@Override
public Object create(final Object parent, final BeanPath beanPath) {
final BeanPath next = beanPath.next();
if (null != next) {
final Node node = next.getNode();
if (node instanceof NameNode) {
final NameNode nameNode = (NameNode) node;
if (nameNode.isNumber()) {
return JSONUtil.ofArray(config);
}
return JSONUtil.ofObj(config);
}
}
return JSONUtil.ofObj(config);
}
}

View File

@ -0,0 +1,152 @@
/*
* Copyright (c) 2024 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.hutool.json.support;
import org.dromara.hutool.core.bean.path.BeanPath;
import org.dromara.hutool.core.bean.path.NodeBeanFactory;
import org.dromara.hutool.core.bean.path.node.*;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.convert.ConvertUtil;
import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.json.*;
/**
* JSON节点Bean创建器
*
* @author looly
* @since 6.0.0
*/
public class JSONNodeBeanFactory implements NodeBeanFactory<JSON> {
private final JSONConfig config;
/**
* 构造
*
* @param config JSON配置
*/
public JSONNodeBeanFactory(final JSONConfig config) {
this.config = config;
}
@Override
public JSON create(final JSON parent, final BeanPath<JSON> beanPath) {
final BeanPath<JSON> next = beanPath.next();
if (null != next) {
final Node node = next.getNode();
if (node instanceof NameNode) {
final NameNode nameNode = (NameNode) node;
if (nameNode.isNumber()) {
return JSONUtil.ofArray(config);
}
return JSONUtil.ofObj(config);
}
}
return JSONUtil.ofObj(config);
}
@Override
public Object getValue(final JSON bean, final BeanPath<JSON> beanPath) {
final Node node = beanPath.getNode();
if (null == node || node instanceof EmptyNode) {
return null;
} else if (node instanceof ListNode) {
return getValueByListNode(bean, (ListNode) node);
} else if (node instanceof NameNode) {
return getValueByNameNode(bean, (NameNode) node);
} else if (node instanceof RangeNode) {
return getValueByRangeNode(bean, (RangeNode) node);
}
throw new UnsupportedOperationException("Unsupported node type: " + node.getClass());
}
@Override
public JSON setValue(final JSON bean, final Object value, final BeanPath<JSON> beanPath) {
final Node node = beanPath.getNode();
if (node instanceof EmptyNode) {
return bean;
} else if (node instanceof NameNode) {
if(bean instanceof JSONObject){
((JSONObject) bean).set(((NameNode) node).getName(), value);
} else if(bean instanceof JSONArray){
((JSONArray) bean).setValue(Integer.parseInt(((NameNode) node).getName()), value);
}
return bean;
}
throw new UnsupportedOperationException("Unsupported node type: " + node.getClass());
}
/**
* 获取指定下标的值<br>
* 如果Bean为JSONArray则返回指定下标数组的值如果Bean为JSONObject则返回指定key数组的值
*
* @param bean Bean
* @param node 下标节点
* @return
*/
private Object getValueByListNode(final JSON bean, final ListNode node) {
final String[] names = node.getUnWrappedNames();
if (bean instanceof JSONArray) {
return CollUtil.getAny((JSONArray) bean, ConvertUtil.convert(int[].class, names));
} else if (bean instanceof JSONObject) {
MapUtil.getAny((JSONObject) bean, names);
}
throw new UnsupportedOperationException("Can not get by list for: " + bean.getClass());
}
/**
* 获取指定key的值<br>
* 如果Bean为JSONObject则返回指定key的值如果Bean为JSONArray则返回指定下标数组的值
*
* @param bean Bean
* @param node key节点
* @return
*/
private Object getValueByNameNode(final JSON bean, final NameNode node) {
final String name = node.getName();
if ("$".equals(name)) {
return bean;
}
if (bean instanceof JSONObject) {
return ((JSONObject) bean).get(name);
} else if (bean instanceof JSONArray) {
return ((JSONArray) bean).get(Integer.parseInt(name));
}
throw new UnsupportedOperationException("Can not get by name for: " + bean.getClass());
}
/**
* 获取指定下标范围的值<br>
* 如果Bean为JSONArray则返回指定下标范围的值
*
* @param bean Bean
* @param node 下标range节点
* @return
*/
private Object getValueByRangeNode(final JSON bean, final RangeNode node) {
if (bean instanceof JSONArray) {
return CollUtil.sub((JSONArray) bean, node.getStart(), node.getEnd(), node.getStep());
}
throw new UnsupportedOperationException("Can not get range value for: " + bean.getClass());
}
}

View File

@ -33,10 +33,7 @@ public class CustomSerializeTest {
public void init() {
TypeAdapterManager.getInstance().register(CustomBean.class,
(JSONSerializer<CustomBean>) (bean, context) ->{
JSONObject contextJson = (JSONObject) context.getContextJson();
if(null == contextJson){
contextJson = JSONUtil.ofObj(context.config());
}
final JSONObject contextJson = context.getOrCreateObj();
return contextJson.set("customName", bean.name);
});
}

View File

@ -57,7 +57,7 @@ public class Issue2555Test {
public static class MySerializer implements JSONSerializer<MyType> {
@Override
public JSON serialize(final MyType bean, final JSONContext context) {
return ((JSONObject)context.getContextJson()).set("addr", bean.getAddress());
return context.getOrCreateObj().set("addr", bean.getAddress());
}
}

View File

@ -70,12 +70,7 @@ public class Issue3086Test {
public JSON serialize(final TestBean bean, final JSONContext context) {
final List<String> strings = bean.getAuthorities()
.stream().map(SimpleGrantedAuthority::getAuthority).collect(Collectors.toList());
JSONObject contextJson = (JSONObject) context.getContextJson();
if(null == contextJson){
contextJson = new JSONObject(context.config());
}
contextJson.set("authorities",strings);
return contextJson;
return context.getOrCreateObj().set("authorities",strings);
}
}
}