add BeanPath

This commit is contained in:
Looly 2023-11-15 00:27:24 +08:00
parent f9165d06b7
commit 1023af3e40
23 changed files with 1133 additions and 235 deletions

View File

@ -46,7 +46,7 @@ import java.util.*;
* @author Looly
* @since 4.0.6
*/
public class BeanPath implements Serializable {
public class BeanPathOld implements Serializable {
private static final long serialVersionUID = 1L;
/**
@ -79,8 +79,8 @@ public class BeanPath implements Serializable {
* @param expression 表达式
* @return BeanPath
*/
public static BeanPath of(final String expression) {
return new BeanPath(expression);
public static BeanPathOld of(final String expression) {
return new BeanPathOld(expression);
}
/**
@ -88,7 +88,7 @@ public class BeanPath implements Serializable {
*
* @param expression 表达式
*/
public BeanPath(final String expression) {
public BeanPathOld(final String expression) {
init(expression);
}

View File

@ -355,7 +355,7 @@ public class BeanUtil {
* @param bean Bean对象支持MapListCollectionArray
* @param expression 表达式例如person.friend[5].name
* @return Bean属性值bean为{@code null}或者express为空返回{@code null}
* @see BeanPath#get(Object)
* @see BeanPathOld#get(Object)
* @since 3.0.7
*/
@SuppressWarnings("unchecked")
@ -363,7 +363,7 @@ public class BeanUtil {
if (null == bean || StrUtil.isBlank(expression)) {
return null;
}
return (T) BeanPath.of(expression).get(bean);
return (T) BeanPathOld.of(expression).get(bean);
}
/**
@ -372,11 +372,11 @@ public class BeanUtil {
* @param bean Bean对象支持MapListCollectionArray
* @param expression 表达式例如person.friend[5].name
* @param value 属性值
* @see BeanPath#get(Object)
* @see BeanPathOld#get(Object)
* @since 4.0.6
*/
public static void setProperty(final Object bean, final String expression, final Object value) {
BeanPath.of(expression).set(bean, value);
BeanPathOld.of(expression).set(bean, value);
}
// --------------------------------------------------------------------------------------------- mapToBean

View File

@ -99,7 +99,7 @@ public class DynaBean implements Cloneable, Serializable {
return (T) CollUtil.get((Collection<?>) bean, Integer.parseInt(fieldName));
} catch (final NumberFormatException e) {
// 非数字see pr#254@Gitee
return (T) CollUtil.map((Collection<?>) bean, (beanEle) -> BeanUtil.getFieldValue(beanEle, fieldName), false);
return (T) CollUtil.map((Collection<?>) bean, (beanEle) -> DynaBean.of(beanEle).get(fieldName), false);
}
} else {
final PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName);

View File

@ -0,0 +1,195 @@
/*
* 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.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.bean.path.node.NodeFactory;
import org.dromara.hutool.core.lang.Console;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.core.text.StrUtil;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
/**
* 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 6.0.0
*/
public class BeanPath implements Node, Iterator<BeanPath> {
/**
* 表达式边界符号数组
*/
private static final char[] EXP_CHARS = {CharUtil.DOT, CharUtil.BRACKET_START, CharUtil.BRACKET_END};
/**
* 创建Bean路径
*
* @param expression 表达式
* @return BeanPath
*/
public static BeanPath of(final String expression) {
return new BeanPath(expression);
}
private final Node node;
private final String child;
/**
* 构造
*
* @param expression 表达式
*/
public BeanPath(final String expression) {
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 ('\'' == 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) {
this.node = NodeFactory.createNode(builder.toString());
// 如果以[结束表示后续还有表达式需保留'['如name[0]
this.child = StrUtil.nullIfEmpty(expression.substring(c == CharUtil.BRACKET_START ? i : i + 1));
return;
}
} else {
// 非边界符号追加字符
builder.append(c);
}
}
// 最后的节点
if (isNumStart) {
throw new IllegalArgumentException(StrUtil.format("Bad expression '{}':{}, we find '[' but no ']' !", expression, length - 1));
} else {
this.node = NodeFactory.createNode(builder.toString());
this.child = null;
}
}
/**
* 获取节点
*
* @return 节点
*/
public Node getNode() {
return this.node;
}
/**
* 获取子表达式
*
* @return 子表达式
*/
public String getChild() {
return this.child;
}
@Override
public boolean hasNext() {
return null != this.child;
}
@Override
public BeanPath next() {
return new BeanPath(this.child);
}
@Override
public Object getValue(final Object bean) {
Object value = this.node.getValue(bean);
if (hasNext()) {
value = next().getValue(value);
}
return value;
}
@Override
public void setValue(Object bean, final Object value) {
Object parentBean;
BeanPath beanPath = this;
while (beanPath.hasNext()) {
parentBean = bean;
bean = beanPath.node.getValue(bean);
beanPath = beanPath.next();
if(null == bean && beanPath.hasNext()){
final Node node = beanPath.node;
if(node instanceof NameNode){
bean = ((NameNode) node).isNumber() ? new ArrayList<>() : new HashMap<>();
}else{
throw new IllegalArgumentException("Invalid node to create sub bean");
}
beanPath.node.setValue(parentBean, bean);
}
}
Console.log(beanPath, bean, value);
beanPath.node.setValue(bean, value);
}
@Override
public String toString() {
return "BeanPath{" +
"node=" + node +
", child='" + child + '\'' +
'}';
}
}

View File

@ -0,0 +1,36 @@
/*
* 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.path.node;
/**
* 空节点
*
* @author looly
*/
public class EmptyNode implements Node {
/**
* 单例
*/
public static EmptyNode INSTANCE = new EmptyNode();
@Override
public Object getValue(final Object bean) {
return null;
}
@Override
public void setValue(final Object bean, final Object value) {
// do nothing
}
}

View File

@ -0,0 +1,90 @@
/*
* 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.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.Convert;
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;
/**
* 列表节点
* [num0,num1,num2...]模式或者['key0','key1']模式
*
* @author looly
*/
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, Convert.convert(int[].class, names));
} else if (ArrayUtil.isArray(bean)) {
return ArrayUtil.getAny(bean, Convert.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 {
final Map<String, Object> map = BeanUtil.beanToMap(bean);
return MapUtil.getAny(map, unWrappedNames);
}
}
}
@Override
public void 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去除单引号
* @param names name列表
* @return 处理后的name列表
*/
private String[] getUnWrappedNames(final List<String> names){
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);
}
return unWrappedNames;
}
}

View File

@ -0,0 +1,66 @@
/*
* 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.path.node;
import org.dromara.hutool.core.bean.DynaBean;
import org.dromara.hutool.core.math.NumberUtil;
/**
* 处理名称节点或序号节点
* <ul>
* <li>name</li>
* <li>1</li>
* </ul>
*
* @author looly
*/
public class NameNode implements Node {
private final String name;
/**
* 构造
*
* @param name 节点名
*/
public NameNode(final String name) {
this.name = name;
}
/**
* 是否为数字节点
*
* @return 是否为数字节点
*/
public boolean isNumber() {
return NumberUtil.isInteger(name);
}
@Override
public Object getValue(final Object bean) {
if ("$".equals(name)) {
return bean;
}
return DynaBean.of(bean).get(this.name);
}
@Override
public void setValue(final Object bean, final Object value) {
DynaBean.of(bean).set(this.name, value);
}
@Override
public String toString() {
return this.name;
}
}

View File

@ -0,0 +1,36 @@
/*
* 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.path.node;
/**
* Bean路径节点接口
*
* @author looly
*/
public interface Node {
/**
* 获取Bean对应节点的值
*
* @param bean bean对象
* @return 节点值
*/
Object getValue(Object bean);
/**
* 设置节点值
*
* @param bean bean对象
* @param value 节点值
*/
void setValue(Object bean, Object value);
}

View File

@ -0,0 +1,47 @@
/*
* 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.path.node;
import org.dromara.hutool.core.text.CharUtil;
import org.dromara.hutool.core.text.StrUtil;
/**
* 节点简单工厂
*
* @author looly
* @since 6.0.0
*/
public class NodeFactory {
/**
* 根据表达式创建对应的节点
*
* @param expression 表达式
* @return 节点
*/
public static Node createNode(final String expression) {
if (StrUtil.isEmpty(expression)) {
return EmptyNode.INSTANCE;
}
if (StrUtil.contains(expression, CharUtil.COLON)) {
return new RangeNode(expression);
}
if (StrUtil.contains(expression, CharUtil.COMMA)) {
return new ListNode(expression);
}
return new NameNode(expression);
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.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;
/**
* [start:end:step] 模式节点
*
* @author looly
*/
public class RangeNode implements Node {
private final int start;
private final int end;
private final int step;
/**
* 构造
*
* @param expression 表达式
*/
public RangeNode(final String expression) {
final List<String> parts = SplitUtil.splitTrim(expression, StrUtil.COLON);
this.start = Integer.parseInt(parts.get(0));
this.end = Integer.parseInt(parts.get(1));
int step = 1;
if (3 == parts.size()) {
step = Integer.parseInt(parts.get(2));
}
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());
}
@Override
public void setValue(final Object bean, final Object value) {
throw new UnsupportedOperationException("Can not set value with step name.");
}
@Override
public String toString() {
return StrUtil.format("[{}:{}:{}]", this.start, this.end, this.step);
}
}

View File

@ -0,0 +1,18 @@
/*
* 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.
*/
/**
* Bean路径节点
*
* @author looly
*/
package org.dromara.hutool.core.bean.path.node;

View File

@ -0,0 +1,19 @@
/*
* 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.
*/
/**
* Bean路径通过路径表达式查找或设置对象或子对象中的值
*
* @author looly
* @since 6.0.0
*/
package org.dromara.hutool.core.bean.path;

View File

@ -12,7 +12,7 @@
package org.dromara.hutool.core.map;
import org.dromara.hutool.core.bean.BeanPath;
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.collection.set.SetUtil;
@ -423,12 +423,12 @@ public class Dict extends CustomKeyMap<String, Object> implements TypeGetter<Str
* @param <T> 目标类型
* @param expression 表达式
* @return 对象
* @see BeanPath#get(Object)
* @see BeanPathOld#get(Object)
* @since 5.7.14
*/
@SuppressWarnings("unchecked")
public <T> T getByPath(final String expression) {
return (T) BeanPath.of(expression).get(this);
return (T) BeanPathOld.of(expression).get(this);
}
/**
@ -453,7 +453,7 @@ public class Dict extends CustomKeyMap<String, Object> implements TypeGetter<Str
* @param expression 表达式
* @param resultType 返回值类型
* @return 对象
* @see BeanPath#get(Object)
* @see BeanPathOld#get(Object)
* @since 5.7.14
*/
public <T> T getByPath(final String expression, final Type resultType) {

View File

@ -69,7 +69,7 @@ public abstract class StrTemplate {
* @param template 字符串模板
* @return 单占位符 模板对象的 Builder
*/
public static SinglePlaceholderStrTemplate.Builder of(String template) {
public static SinglePlaceholderStrTemplate.Builder of(final String template) {
return SinglePlaceholderStrTemplate.builder(template);
}
@ -80,7 +80,7 @@ public abstract class StrTemplate {
* @param template 字符串模板
* @return 有前缀和后缀的占位符模板对象的 Builder
*/
public static NamedPlaceholderStrTemplate.Builder ofNamed(String template) {
public static NamedPlaceholderStrTemplate.Builder ofNamed(final String template) {
return NamedPlaceholderStrTemplate.builder(template);
}
@ -192,7 +192,7 @@ public abstract class StrTemplate {
int startIdx = 0, findIdx;
boolean hasPlaceholder = false;
String text;
for (StrTemplateSegment segment : segments) {
for (final StrTemplateSegment segment : segments) {
if (segment instanceof LiteralSegment) {
text = segment.getText();
findIdx = str.indexOf(text, startIdx);
@ -274,7 +274,7 @@ public abstract class StrTemplate {
int totalTextLength = this.fixedTextTotalLength;
String valueStr;
for (AbstractPlaceholderSegment segment : placeholderSegments) {
for (final AbstractPlaceholderSegment segment : placeholderSegments) {
// 根据 占位符 返回 需要序列化的值
valueStr = valueSupplier.apply(segment);
if (valueStr == null) {
@ -287,7 +287,7 @@ public abstract class StrTemplate {
final StringBuilder sb = new StringBuilder(totalTextLength);
int index = 0;
// 构造格式化结果字符串
for (StrTemplateSegment segment : segments) {
for (final StrTemplateSegment segment : segments) {
if (segment instanceof LiteralSegment) {
sb.append(segment.getText());
}
@ -331,7 +331,7 @@ public abstract class StrTemplate {
protected String formatBySegment(final Function<AbstractPlaceholderSegment, ?> valueSupplier) {
return formatRawBySegment(segment -> {
// 根据 占位符 返回 需要序列化的值
Object value = valueSupplier.apply(segment);
final Object value = valueSupplier.apply(segment);
if (value != null) {
if (value instanceof String) {
return (String) value;
@ -428,7 +428,7 @@ public abstract class StrTemplate {
int startIdx = 0, findIdx;
AbstractPlaceholderSegment placeholderSegment = null;
String text;
for (StrTemplateSegment segment : segments) {
for (final StrTemplateSegment segment : segments) {
if (segment instanceof LiteralSegment) {
text = segment.getText();
// 查找固定文本
@ -616,7 +616,7 @@ public abstract class StrTemplate {
// 计算 固定文本segment 数量 文本总长度
int literalSegmentSize = 0, fixedTextTotalLength = 0;
for (StrTemplateSegment segment : this.segments) {
for (final StrTemplateSegment segment : this.segments) {
if (segment instanceof LiteralSegment) {
++literalSegmentSize;
fixedTextTotalLength += segment.getText().length();
@ -630,7 +630,7 @@ public abstract class StrTemplate {
this.placeholderSegments = Collections.emptyList();
} else {
final List<AbstractPlaceholderSegment> placeholderSegments = new ArrayList<>(placeholderSegmentsSize);
for (StrTemplateSegment segment : segments) {
for (final StrTemplateSegment segment : segments) {
if (segment instanceof AbstractPlaceholderSegment) {
placeholderSegments.add((AbstractPlaceholderSegment) segment);
}
@ -646,14 +646,14 @@ public abstract class StrTemplate {
* @param list 已保存的segment列表
* @param newText 新的固定文本
*/
protected void addLiteralSegment(boolean isLastLiteralSegment, List<StrTemplateSegment> list, String newText) {
protected void addLiteralSegment(final boolean isLastLiteralSegment, final List<StrTemplateSegment> list, final String newText) {
if (newText.isEmpty()) {
return;
}
if (isLastLiteralSegment) {
// 最后的固定文本segment 新固定文本 合并为一个
int lastIdx = list.size() - 1;
StrTemplateSegment lastLiteralSegment = list.get(lastIdx);
final int lastIdx = list.size() - 1;
final StrTemplateSegment lastLiteralSegment = list.get(lastIdx);
list.set(lastIdx, new LiteralSegment(lastLiteralSegment.getText() + newText));
} else {
list.add(new LiteralSegment(newText));
@ -756,7 +756,7 @@ public abstract class StrTemplate {
*/
public BuilderChild addFeatures(final Feature... appendFeatures) {
if (ArrayUtil.isNotEmpty(appendFeatures)) {
for (Feature feature : appendFeatures) {
for (final Feature feature : appendFeatures) {
this.features = feature.set(this.features);
}
}
@ -772,7 +772,7 @@ public abstract class StrTemplate {
*/
public BuilderChild removeFeatures(final Feature... removeFeatures) {
if (ArrayUtil.isNotEmpty(removeFeatures)) {
for (Feature feature : removeFeatures) {
for (final Feature feature : removeFeatures) {
this.features = feature.clear(this.features);
}
}
@ -978,7 +978,7 @@ public abstract class StrTemplate {
* @param bitStart 同组第一个策略的掩码位数
* @param bitLen 同组策略数量
*/
Feature(int bitPos, int bitStart, int bitLen) {
Feature(final int bitPos, final int bitStart, final int bitLen) {
this.mask = 1 << bitPos;
this.clearMask = (-1 << (bitStart + bitLen)) | ((1 << bitStart) - 1);
}
@ -989,7 +989,7 @@ public abstract class StrTemplate {
* @param features 外部的策略值
* @return 是否为当前策略
*/
public boolean contains(int features) {
public boolean contains(final int features) {
return (features & mask) != 0;
}
@ -999,7 +999,7 @@ public abstract class StrTemplate {
* @param features 外部的策略值
* @return 添加后的策略值
*/
public int set(int features) {
public int set(final int features) {
return (features & clearMask) | mask;
}
@ -1009,7 +1009,7 @@ public abstract class StrTemplate {
* @param features 外部的策略值
* @return 移除后的策略值
*/
public int clear(int features) {
public int clear(final int features) {
return (features & clearMask);
}
@ -1019,13 +1019,13 @@ public abstract class StrTemplate {
* @param features 策略枚举数组
* @return 总的策略值
*/
public static int of(Feature... features) {
public static int of(final Feature... features) {
if (features == null) {
return 0;
}
int value = 0;
for (Feature feature : features) {
for (final Feature feature : features) {
value = feature.set(value);
}

View File

@ -0,0 +1,16 @@
/*
* 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.text.placeholder.segment;

View File

@ -76,7 +76,7 @@ public class NamedPlaceholderStrTemplate extends StrTemplate {
// 记录 下标占位符 最大的 下标值
if (!placeholderSegments.isEmpty()) {
for (AbstractPlaceholderSegment segment : placeholderSegments) {
for (final AbstractPlaceholderSegment segment : placeholderSegments) {
if (segment instanceof IndexedPlaceholderSegment) {
this.indexedSegmentMaxIdx = Math.max(this.indexedSegmentMaxIdx, ((IndexedPlaceholderSegment) segment).getIndex());
}
@ -95,7 +95,7 @@ public class NamedPlaceholderStrTemplate extends StrTemplate {
final int openLength = prefix.length();
final int closeLength = suffix.length();
List<StrTemplateSegment> segments = new ArrayList<>();
final List<StrTemplateSegment> segments = new ArrayList<>();
int closeCursor = 0;
// 开始匹配
final char[] src = template.toCharArray();
@ -299,7 +299,7 @@ public class NamedPlaceholderStrTemplate extends StrTemplate {
* @param missingIndexHandler 集合中不存在下标位置时的处理器根据 下标 返回 代替值
* @return 格式化字符串
*/
public String formatIndexed(final Collection<?> collection, IntFunction<String> missingIndexHandler) {
public String formatIndexed(final Collection<?> collection, final IntFunction<String> missingIndexHandler) {
if (collection == null) {
return getTemplate();
}
@ -579,6 +579,9 @@ public class NamedPlaceholderStrTemplate extends StrTemplate {
return new Builder(template);
}
/**
* 构造器
*/
public static class Builder extends AbstractBuilder<Builder, NamedPlaceholderStrTemplate> {
/**
* 占位符前缀默认为 {@link NamedPlaceholderStrTemplate#DEFAULT_PREFIX}

View File

@ -34,196 +34,199 @@ import java.util.function.UnaryOperator;
* @since 6.0.0
*/
public class SinglePlaceholderStrTemplate extends StrTemplate {
/**
* 默认的占位符
*/
public static final String DEFAULT_PLACEHOLDER = StrPool.EMPTY_JSON;
/**
* 默认的占位符
*/
public static final String DEFAULT_PLACEHOLDER = StrPool.EMPTY_JSON;
/**
* 占位符默认为: {@link StrPool#EMPTY_JSON}
*/
protected String placeholder;
/**
* 占位符默认为: {@link StrPool#EMPTY_JSON}
*/
protected String placeholder;
protected SinglePlaceholderStrTemplate(final String template, final int features, final String placeholder, final char escape,
final String defaultValue, final UnaryOperator<String> defaultValueHandler) {
super(template, escape, defaultValue, defaultValueHandler, features);
protected SinglePlaceholderStrTemplate(final String template, final int features, final String placeholder, final char escape,
final String defaultValue, final UnaryOperator<String> defaultValueHandler) {
super(template, escape, defaultValue, defaultValueHandler, features);
Assert.notEmpty(placeholder);
this.placeholder = placeholder;
Assert.notEmpty(placeholder);
this.placeholder = placeholder;
// 初始化Segment列表
afterInit();
}
// 初始化Segment列表
afterInit();
}
@Override
protected List<StrTemplateSegment> parseSegments(final String template) {
final int placeholderLength = placeholder.length();
final int strPatternLength = template.length();
// 记录已经处理到的位置
int handledPosition = 0;
// 占位符所在位置
int delimIndex;
// 上一个解析的segment是否是固定文本如果是则需要和当前新的文本部分合并
boolean lastIsLiteralSegment = false;
// 复用的占位符变量
final SinglePlaceholderSegment singlePlaceholderSegment = SinglePlaceholderSegment.newInstance(placeholder);
List<StrTemplateSegment> segments = null;
while (true) {
delimIndex = template.indexOf(placeholder, handledPosition);
if (delimIndex == -1) {
// 整个模板都不带占位符
if (handledPosition == 0) {
return Collections.singletonList(new LiteralSegment(template));
}
// 字符串模板剩余部分不再包含占位符
if (handledPosition < strPatternLength) {
addLiteralSegment(lastIsLiteralSegment, segments, template.substring(handledPosition));
}
return segments;
} else if (segments == null) {
segments = new ArrayList<>();
}
@Override
protected List<StrTemplateSegment> parseSegments(final String template) {
final int placeholderLength = placeholder.length();
final int strPatternLength = template.length();
// 记录已经处理到的位置
int handledPosition = 0;
// 占位符所在位置
int delimIndex;
// 上一个解析的segment是否是固定文本如果是则需要和当前新的文本部分合并
boolean lastIsLiteralSegment = false;
// 复用的占位符变量
final SinglePlaceholderSegment singlePlaceholderSegment = SinglePlaceholderSegment.newInstance(placeholder);
List<StrTemplateSegment> segments = null;
while (true) {
delimIndex = template.indexOf(placeholder, handledPosition);
if (delimIndex == -1) {
// 整个模板都不带占位符
if (handledPosition == 0) {
return Collections.singletonList(new LiteralSegment(template));
}
// 字符串模板剩余部分不再包含占位符
if (handledPosition < strPatternLength) {
addLiteralSegment(lastIsLiteralSegment, segments, template.substring(handledPosition));
}
return segments;
} else if (segments == null) {
segments = new ArrayList<>();
}
// 存在 转义符
if (delimIndex > 0 && template.charAt(delimIndex - 1) == escape) {
// 存在 双转义符
if (delimIndex > 1 && template.charAt(delimIndex - 2) == escape) {
// 转义符之前还有一个转义符形如"//{"占位符依旧有效
addLiteralSegment(lastIsLiteralSegment, segments, template.substring(handledPosition, delimIndex - 1));
segments.add(singlePlaceholderSegment);
lastIsLiteralSegment = false;
handledPosition = delimIndex + placeholderLength;
} else {
// 占位符被转义形如"/{"当前字符并不是一个真正的占位符而是普通字符串的一部分
addLiteralSegment(lastIsLiteralSegment, segments, template.substring(handledPosition, delimIndex - 1) + placeholder.charAt(0));
lastIsLiteralSegment = true;
handledPosition = delimIndex + 1;
}
} else {
// 正常占位符
addLiteralSegment(lastIsLiteralSegment, segments, template.substring(handledPosition, delimIndex));
segments.add(singlePlaceholderSegment);
lastIsLiteralSegment = false;
handledPosition = delimIndex + placeholderLength;
}
}
}
// region 格式化方法
// ################################################## 格式化方法 ##################################################
// 存在 转义符
if (delimIndex > 0 && template.charAt(delimIndex - 1) == escape) {
// 存在 双转义符
if (delimIndex > 1 && template.charAt(delimIndex - 2) == escape) {
// 转义符之前还有一个转义符形如"//{"占位符依旧有效
addLiteralSegment(lastIsLiteralSegment, segments, template.substring(handledPosition, delimIndex - 1));
segments.add(singlePlaceholderSegment);
lastIsLiteralSegment = false;
handledPosition = delimIndex + placeholderLength;
} else {
// 占位符被转义形如"/{"当前字符并不是一个真正的占位符而是普通字符串的一部分
addLiteralSegment(lastIsLiteralSegment, segments, template.substring(handledPosition, delimIndex - 1) + placeholder.charAt(0));
lastIsLiteralSegment = true;
handledPosition = delimIndex + 1;
}
} else {
// 正常占位符
addLiteralSegment(lastIsLiteralSegment, segments, template.substring(handledPosition, delimIndex));
segments.add(singlePlaceholderSegment);
lastIsLiteralSegment = false;
handledPosition = delimIndex + placeholderLength;
}
}
}
// region 格式化方法
// ################################################## 格式化方法 ##################################################
/**
* 按顺序使用 数组元素 替换 占位符
*
* @param args 可变参数
* @return 格式化字符串
*/
public String format(final Object... args) {
return formatArray(args);
}
/**
* 按顺序使用 数组元素 替换 占位符
*
* @param args 可变参数
* @return 格式化字符串
*/
public String format(final Object... args) {
return formatArray(args);
}
/**
* 按顺序使用 原始数组元素 替换 占位符
*
* @param array 原始类型数组例如: {@code int[]}
* @return 格式化字符串
*/
public String formatArray(final Object array) {
return formatArray(ArrayUtil.wrap(array));
}
/**
* 按顺序使用 原始数组元素 替换 占位符
*
* @param array 原始类型数组例如: {@code int[]}
* @return 格式化字符串
*/
public String formatArray(final Object array) {
return formatArray(ArrayUtil.wrap(array));
}
/**
* 按顺序使用 数组元素 替换 占位符
*
* @param array 数组
* @return 格式化字符串
*/
public String formatArray(final Object[] array) {
if (array == null) {
return getTemplate();
}
return format(Arrays.asList(array));
}
/**
* 按顺序使用 数组元素 替换 占位符
*
* @param array 数组
* @return 格式化字符串
*/
public String formatArray(final Object[] array) {
if (array == null) {
return getTemplate();
}
return format(Arrays.asList(array));
}
/**
* 按顺序使用 迭代器元素 替换 占位符
*
* @param iterable iterable
* @return 格式化字符串
*/
public String format(final Iterable<?> iterable) {
return super.formatSequence(iterable);
}
// endregion
/**
* 按顺序使用 迭代器元素 替换 占位符
*
* @param iterable iterable
* @return 格式化字符串
*/
public String format(final Iterable<?> iterable) {
return super.formatSequence(iterable);
}
// endregion
// region 解析方法
// ################################################## 解析方法 ##################################################
// region 解析方法
// ################################################## 解析方法 ##################################################
/**
* 占位符位置的值 按顺序解析为 字符串数组
*
* @param str 待解析的字符串一般是格式化方法的返回值
* @return 参数值数组
*/
public String[] matchesToArray(final String str) {
return matches(str).toArray(new String[0]);
}
/**
* 占位符位置的值 按顺序解析为 字符串数组
*
* @param str 待解析的字符串一般是格式化方法的返回值
* @return 参数值数组
*/
public String[] matchesToArray(final String str) {
return matches(str).toArray(new String[0]);
}
/**
* 占位符位置的值 按顺序解析为 字符串列表
*
* @param str 待解析的字符串一般是格式化方法的返回值
* @return 参数值列表
*/
public List<String> matches(final String str) {
return super.matchesSequence(str);
}
// endregion
/**
* 占位符位置的值 按顺序解析为 字符串列表
*
* @param str 待解析的字符串一般是格式化方法的返回值
* @return 参数值列表
*/
public List<String> matches(final String str) {
return super.matchesSequence(str);
}
// endregion
/**
* 创建 builder
*
* @param template 字符串模板不能为 {@code null}
* @return builder实例
*/
public static Builder builder(final String template) {
return new Builder(template);
}
/**
* 创建 builder
*
* @param template 字符串模板不能为 {@code null}
* @return builder实例
*/
public static Builder builder(final String template) {
return new Builder(template);
}
public static class Builder extends AbstractBuilder<Builder, SinglePlaceholderStrTemplate> {
/**
* 单占位符
* <p>例如"?""{}"</p>
* <p>默认为 {@link SinglePlaceholderStrTemplate#DEFAULT_PLACEHOLDER}</p>
*/
protected String placeholder;
/**
* 构造器
*/
public static class Builder extends AbstractBuilder<Builder, SinglePlaceholderStrTemplate> {
/**
* 单占位符
* <p>例如"?""{}"</p>
* <p>默认为 {@link SinglePlaceholderStrTemplate#DEFAULT_PLACEHOLDER}</p>
*/
protected String placeholder;
protected Builder(final String template) {
super(template);
}
protected Builder(final String template) {
super(template);
}
/**
* 设置 占位符
*
* @param placeholder 占位符不能为 {@code null} {@code ""}
* @return builder
*/
public Builder placeholder(final String placeholder) {
this.placeholder = placeholder;
return this;
}
/**
* 设置 占位符
*
* @param placeholder 占位符不能为 {@code null} {@code ""}
* @return builder
*/
public Builder placeholder(final String placeholder) {
this.placeholder = placeholder;
return this;
}
@Override
protected SinglePlaceholderStrTemplate buildInstance() {
if (this.placeholder == null) {
this.placeholder = DEFAULT_PLACEHOLDER;
}
return new SinglePlaceholderStrTemplate(this.template, this.features, this.placeholder, this.escape,
this.defaultValue, this.defaultValueHandler);
}
@Override
protected SinglePlaceholderStrTemplate buildInstance() {
if (this.placeholder == null) {
this.placeholder = DEFAULT_PLACEHOLDER;
}
return new SinglePlaceholderStrTemplate(this.template, this.features, this.placeholder, this.escape,
this.defaultValue, this.defaultValueHandler);
}
@Override
protected Builder self() {
return this;
}
}
@Override
protected Builder self() {
return this;
}
}
}

View File

@ -0,0 +1,16 @@
/*
* 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.text.placeholder.template;

View File

@ -27,7 +27,7 @@ import java.util.List;
import java.util.Map;
/**
* {@link BeanPath} 单元测试
* {@link BeanPathOld} 单元测试
*
* @author looly
*
@ -73,7 +73,7 @@ public class BeanPathTest {
@Test
public void beanPathTest1() {
final BeanPath pattern = new BeanPath("userInfo.examInfoDict[0].id");
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));
@ -83,7 +83,7 @@ public class BeanPathTest {
@Test
public void beanPathTest2() {
final BeanPath pattern = new BeanPath("[userInfo][examInfoDict][0][id]");
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));
@ -92,7 +92,7 @@ public class BeanPathTest {
@Test
public void beanPathTest3() {
final BeanPath pattern = new BeanPath("['userInfo']['examInfoDict'][0]['id']");
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));
@ -101,14 +101,14 @@ public class BeanPathTest {
@Test
public void getTest() {
final BeanPath pattern = BeanPath.of("userInfo.examInfoDict[0].id");
final BeanPathOld pattern = BeanPathOld.of("userInfo.examInfoDict[0].id");
final Object result = pattern.get(tempMap);
Assertions.assertEquals(1, result);
}
@Test
public void setTest() {
final BeanPath pattern = BeanPath.of("userInfo.examInfoDict[0].id");
final BeanPathOld pattern = BeanPathOld.of("userInfo.examInfoDict[0].id");
pattern.set(tempMap, 2);
final Object result = pattern.get(tempMap);
Assertions.assertEquals(2, result);
@ -116,7 +116,7 @@ public class BeanPathTest {
@Test
public void getMapTest () {
final BeanPath pattern = BeanPath.of("userInfo[id, photoPath]");
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"));
@ -129,13 +129,13 @@ public class BeanPathTest {
dataMap.put("aa", "value0");
dataMap.put("aa.bb.cc", "value111111");// key 是类名 格式 ' . '
final BeanPath pattern = BeanPath.of("'aa.bb.cc'");
final BeanPathOld pattern = BeanPathOld.of("'aa.bb.cc'");
Assertions.assertEquals("value111111", pattern.get(dataMap));
}
@Test
public void compileTest(){
final BeanPath of = BeanPath.of("'abc.dd'.ee.ff'.'");
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));
@ -145,17 +145,17 @@ public class BeanPathTest {
public void issue2362Test() {
final Map<String, Object> map = new HashMap<>();
BeanPath beanPath = BeanPath.of("list[0].name");
BeanPathOld beanPath = BeanPathOld.of("list[0].name");
beanPath.set(map, "张三");
Assertions.assertEquals("{list=[{name=张三}]}", map.toString());
map.clear();
beanPath = BeanPath.of("list[1].name");
beanPath = BeanPathOld.of("list[1].name");
beanPath.set(map, "张三");
Assertions.assertEquals("{list=[null, {name=张三}]}", map.toString());
map.clear();
beanPath = BeanPath.of("list[0].1.name");
beanPath = BeanPathOld.of("list[0].1.name");
beanPath.set(map, "张三");
Assertions.assertEquals("{list=[[null, {name=张三}]]}", map.toString());
}
@ -164,7 +164,7 @@ public class BeanPathTest {
public void putTest() {
final Map<String, Object> map = new HashMap<>();
BeanPath beanPath = BeanPath.of("list[1].name");
final BeanPathOld beanPath = BeanPathOld.of("list[1].name");
beanPath.set(map, "张三");
Assertions.assertEquals("{list=[null, {name=张三}]}", map.toString());
}
@ -172,7 +172,7 @@ public class BeanPathTest {
@Test
public void putByPathTest() {
final Dict dict = new Dict();
BeanPath.of("aa.bb").set(dict, "BB");
BeanPathOld.of("aa.bb").set(dict, "BB");
Assertions.assertEquals("{aa={bb=BB}}", dict.toString());
}
@ -180,9 +180,9 @@ public class BeanPathTest {
public void appendArrayTest(){
// issue#3008@Github
final MyUser myUser = new MyUser();
BeanPath.of("hobby[0]").set(myUser, "LOL");
BeanPath.of("hobby[1]").set(myUser, "KFC");
BeanPath.of("hobby[2]").set(myUser, "COFFE");
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()));
}
@ -191,9 +191,9 @@ public class BeanPathTest {
public void appendArrayTest2(){
// issue#3008@Github
final MyUser2 myUser = new MyUser2();
BeanPath.of("myUser.hobby[0]").set(myUser, "LOL");
BeanPath.of("myUser.hobby[1]").set(myUser, "KFC");
BeanPath.of("myUser.hobby[2]").set(myUser, "COFFE");
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()));
}

View File

@ -767,7 +767,7 @@ public class BeanUtilTest {
testPojo.setTestPojo2List(new TestPojo2[]{testPojo2, testPojo3});
final BeanPath beanPath = BeanPath.of("testPojo2List.age");
final BeanPathOld beanPath = BeanPathOld.of("testPojo2List.age");
final Object o = beanPath.get(testPojo);
Assertions.assertEquals(Integer.valueOf(2), ArrayUtil.get(o, 0));

View File

@ -0,0 +1,117 @@
/*
* 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.path;
import org.dromara.hutool.core.bean.BeanPathOld;
import org.dromara.hutool.core.lang.test.bean.ExamInfoDict;
import org.dromara.hutool.core.lang.test.bean.UserInfoDict;
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;
public class BeanPathGetOrSetValueTest {
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 getValueTest() {
final BeanPath pattern = new BeanPath("$.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");
pattern.setValue(tempMap, 2);
final Object result = pattern.getValue(tempMap);
Assertions.assertEquals(2, result);
}
@Test
public void getMapTest () {
final BeanPath pattern = new BeanPath("userInfo[id, photoPath]");
@SuppressWarnings("unchecked")
final Map<String, Object> result = (Map<String, Object>)pattern.getValue(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 BeanPath pattern = new BeanPath("'aa.bb.cc'");
Assertions.assertEquals("value111111", pattern.getValue(dataMap));
}
@Test
public void issue2362Test() {
final Map<String, Object> map = new HashMap<>();
BeanPath beanPath = BeanPath.of("list[0].name");
beanPath.setValue(map, "张三");
Assertions.assertEquals("{list=[{name=张三}]}", map.toString());
map.clear();
beanPath = BeanPath.of("list[1].name");
beanPath.setValue(map, "张三");
Assertions.assertEquals("{list=[null, {name=张三}]}", map.toString());
map.clear();
beanPath = BeanPath.of("list[0].1.name");
beanPath.setValue(map, "张三");
Assertions.assertEquals("{list=[[null, {name=张三}]]}", map.toString());
}
}

View File

@ -0,0 +1,166 @@
/*
* 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.path;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class BeanPathTest {
@Test
void parseDotTest() {
BeanPath beanPath = new BeanPath("userInfo.examInfoDict[0].id");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("examInfoDict[0].id", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("examInfoDict", beanPath.getNode().toString());
Assertions.assertEquals("[0].id", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("0", beanPath.getNode().toString());
Assertions.assertEquals(".id", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("id", beanPath.getNode().toString());
Assertions.assertNull(beanPath.getChild());
}
@Test
void parseDotWithQuoteTest() {
BeanPath beanPath = new BeanPath("'userInfo'.examInfoDict[0].'id'");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("examInfoDict[0].'id'", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("examInfoDict", beanPath.getNode().toString());
Assertions.assertEquals("[0].'id'", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("0", beanPath.getNode().toString());
Assertions.assertEquals(".'id'", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("id", beanPath.getNode().toString());
Assertions.assertNull(beanPath.getChild());
}
@Test
void parseDotWithQuoteTest2() {
BeanPath beanPath = new BeanPath("userInfo.'examInfoDict'[0].id");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("'examInfoDict'[0].id", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("examInfoDict", beanPath.getNode().toString());
Assertions.assertEquals("[0].id", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("0", beanPath.getNode().toString());
Assertions.assertEquals(".id", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("id", beanPath.getNode().toString());
Assertions.assertNull(beanPath.getChild());
}
@Test
void parseBucketTest() {
BeanPath beanPath = new BeanPath("[userInfo][examInfoDict][0][id]");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("[examInfoDict][0][id]", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("examInfoDict", beanPath.getNode().toString());
Assertions.assertEquals("[0][id]", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("0", beanPath.getNode().toString());
Assertions.assertEquals("[id]", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("id", beanPath.getNode().toString());
Assertions.assertNull(beanPath.getChild());
}
@Test
void parseBucketWithQuoteTest() {
BeanPath beanPath = new BeanPath("['userInfo']['examInfoDict'][0][id]");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("['examInfoDict'][0][id]", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("examInfoDict", beanPath.getNode().toString());
Assertions.assertEquals("[0][id]", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("0", beanPath.getNode().toString());
Assertions.assertEquals("[id]", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("id", beanPath.getNode().toString());
Assertions.assertNull(beanPath.getChild());
}
@Test
void parseBucketWithQuoteTest2() {
BeanPath beanPath = new BeanPath("[userInfo][examInfoDict][0]['id']");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("[examInfoDict][0]['id']", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("examInfoDict", beanPath.getNode().toString());
Assertions.assertEquals("[0]['id']", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("0", beanPath.getNode().toString());
Assertions.assertEquals("['id']", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("id", beanPath.getNode().toString());
Assertions.assertNull(beanPath.getChild());
}
@Test
void rangePathTest() {
BeanPath beanPath = new BeanPath("[userInfo][2:3]");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("[2:3]", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("[2:3:1]", beanPath.getNode().toString());
Assertions.assertNull(beanPath.getChild());
}
@Test
void listPathTest() {
BeanPath beanPath = new BeanPath("[userInfo][1,2,3]");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("[1,2,3]", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("[1, 2, 3]", beanPath.getNode().toString());
Assertions.assertNull(beanPath.getChild());
}
@Test
void listKeysPathTest() {
BeanPath beanPath = new BeanPath("[userInfo]['a', 'b', 'c']");
Assertions.assertEquals("userInfo", beanPath.getNode().toString());
Assertions.assertEquals("['a', 'b', 'c']", beanPath.getChild());
beanPath = beanPath.next();
Assertions.assertEquals("[a, b, c]", beanPath.getNode().toString());
Assertions.assertNull(beanPath.getChild());
}
}

View File

@ -12,7 +12,7 @@
package org.dromara.hutool.json;
import org.dromara.hutool.core.bean.BeanPath;
import org.dromara.hutool.core.bean.BeanPathOld;
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 BeanPath#get(Object)
* @see BeanPathOld#get(Object)
* @since 4.0.6
*/
default Object getByPath(final String expression) {
return BeanPath.of(expression).get(this);
return BeanPathOld.of(expression).get(this);
}
/**
@ -93,7 +93,7 @@ public interface JSON extends Converter, Cloneable, Serializable {
* @param value
*/
default void putByPath(final String expression, final Object value) {
BeanPath.of(expression).set(this, value);
BeanPathOld.of(expression).set(this, value);
}
/**
@ -118,7 +118,7 @@ public interface JSON extends Converter, Cloneable, Serializable {
* @param expression 表达式
* @param resultType 返回值类型
* @return 对象
* @see BeanPath#get(Object)
* @see BeanPathOld#get(Object)
* @since 4.0.6
*/
@SuppressWarnings("unchecked")