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 * @author Looly
* @since 4.0.6 * @since 4.0.6
*/ */
public class BeanPath implements Serializable { public class BeanPathOld implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** /**
@ -79,8 +79,8 @@ public class BeanPath implements Serializable {
* @param expression 表达式 * @param expression 表达式
* @return BeanPath * @return BeanPath
*/ */
public static BeanPath of(final String expression) { public static BeanPathOld of(final String expression) {
return new BeanPath(expression); return new BeanPathOld(expression);
} }
/** /**
@ -88,7 +88,7 @@ public class BeanPath implements Serializable {
* *
* @param expression 表达式 * @param expression 表达式
*/ */
public BeanPath(final String expression) { public BeanPathOld(final String expression) {
init(expression); init(expression);
} }

View File

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

View File

@ -99,7 +99,7 @@ public class DynaBean implements Cloneable, Serializable {
return (T) CollUtil.get((Collection<?>) bean, Integer.parseInt(fieldName)); return (T) CollUtil.get((Collection<?>) bean, Integer.parseInt(fieldName));
} catch (final NumberFormatException e) { } catch (final NumberFormatException e) {
// 非数字see pr#254@Gitee // 非数字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 { } else {
final PropDesc prop = BeanUtil.getBeanDesc(beanClass).getProp(fieldName); 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; 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.BeanUtil;
import org.dromara.hutool.core.bean.copier.CopyOptions; import org.dromara.hutool.core.bean.copier.CopyOptions;
import org.dromara.hutool.core.collection.set.SetUtil; 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 <T> 目标类型
* @param expression 表达式 * @param expression 表达式
* @return 对象 * @return 对象
* @see BeanPath#get(Object) * @see BeanPathOld#get(Object)
* @since 5.7.14 * @since 5.7.14
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T getByPath(final String expression) { 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 expression 表达式
* @param resultType 返回值类型 * @param resultType 返回值类型
* @return 对象 * @return 对象
* @see BeanPath#get(Object) * @see BeanPathOld#get(Object)
* @since 5.7.14 * @since 5.7.14
*/ */
public <T> T getByPath(final String expression, final Type resultType) { public <T> T getByPath(final String expression, final Type resultType) {

View File

@ -69,7 +69,7 @@ public abstract class StrTemplate {
* @param template 字符串模板 * @param template 字符串模板
* @return 单占位符 模板对象的 Builder * @return 单占位符 模板对象的 Builder
*/ */
public static SinglePlaceholderStrTemplate.Builder of(String template) { public static SinglePlaceholderStrTemplate.Builder of(final String template) {
return SinglePlaceholderStrTemplate.builder(template); return SinglePlaceholderStrTemplate.builder(template);
} }
@ -80,7 +80,7 @@ public abstract class StrTemplate {
* @param template 字符串模板 * @param template 字符串模板
* @return 有前缀和后缀的占位符模板对象的 Builder * @return 有前缀和后缀的占位符模板对象的 Builder
*/ */
public static NamedPlaceholderStrTemplate.Builder ofNamed(String template) { public static NamedPlaceholderStrTemplate.Builder ofNamed(final String template) {
return NamedPlaceholderStrTemplate.builder(template); return NamedPlaceholderStrTemplate.builder(template);
} }
@ -192,7 +192,7 @@ public abstract class StrTemplate {
int startIdx = 0, findIdx; int startIdx = 0, findIdx;
boolean hasPlaceholder = false; boolean hasPlaceholder = false;
String text; String text;
for (StrTemplateSegment segment : segments) { for (final StrTemplateSegment segment : segments) {
if (segment instanceof LiteralSegment) { if (segment instanceof LiteralSegment) {
text = segment.getText(); text = segment.getText();
findIdx = str.indexOf(text, startIdx); findIdx = str.indexOf(text, startIdx);
@ -274,7 +274,7 @@ public abstract class StrTemplate {
int totalTextLength = this.fixedTextTotalLength; int totalTextLength = this.fixedTextTotalLength;
String valueStr; String valueStr;
for (AbstractPlaceholderSegment segment : placeholderSegments) { for (final AbstractPlaceholderSegment segment : placeholderSegments) {
// 根据 占位符 返回 需要序列化的值 // 根据 占位符 返回 需要序列化的值
valueStr = valueSupplier.apply(segment); valueStr = valueSupplier.apply(segment);
if (valueStr == null) { if (valueStr == null) {
@ -287,7 +287,7 @@ public abstract class StrTemplate {
final StringBuilder sb = new StringBuilder(totalTextLength); final StringBuilder sb = new StringBuilder(totalTextLength);
int index = 0; int index = 0;
// 构造格式化结果字符串 // 构造格式化结果字符串
for (StrTemplateSegment segment : segments) { for (final StrTemplateSegment segment : segments) {
if (segment instanceof LiteralSegment) { if (segment instanceof LiteralSegment) {
sb.append(segment.getText()); sb.append(segment.getText());
} }
@ -331,7 +331,7 @@ public abstract class StrTemplate {
protected String formatBySegment(final Function<AbstractPlaceholderSegment, ?> valueSupplier) { protected String formatBySegment(final Function<AbstractPlaceholderSegment, ?> valueSupplier) {
return formatRawBySegment(segment -> { return formatRawBySegment(segment -> {
// 根据 占位符 返回 需要序列化的值 // 根据 占位符 返回 需要序列化的值
Object value = valueSupplier.apply(segment); final Object value = valueSupplier.apply(segment);
if (value != null) { if (value != null) {
if (value instanceof String) { if (value instanceof String) {
return (String) value; return (String) value;
@ -428,7 +428,7 @@ public abstract class StrTemplate {
int startIdx = 0, findIdx; int startIdx = 0, findIdx;
AbstractPlaceholderSegment placeholderSegment = null; AbstractPlaceholderSegment placeholderSegment = null;
String text; String text;
for (StrTemplateSegment segment : segments) { for (final StrTemplateSegment segment : segments) {
if (segment instanceof LiteralSegment) { if (segment instanceof LiteralSegment) {
text = segment.getText(); text = segment.getText();
// 查找固定文本 // 查找固定文本
@ -616,7 +616,7 @@ public abstract class StrTemplate {
// 计算 固定文本segment 数量 文本总长度 // 计算 固定文本segment 数量 文本总长度
int literalSegmentSize = 0, fixedTextTotalLength = 0; int literalSegmentSize = 0, fixedTextTotalLength = 0;
for (StrTemplateSegment segment : this.segments) { for (final StrTemplateSegment segment : this.segments) {
if (segment instanceof LiteralSegment) { if (segment instanceof LiteralSegment) {
++literalSegmentSize; ++literalSegmentSize;
fixedTextTotalLength += segment.getText().length(); fixedTextTotalLength += segment.getText().length();
@ -630,7 +630,7 @@ public abstract class StrTemplate {
this.placeholderSegments = Collections.emptyList(); this.placeholderSegments = Collections.emptyList();
} else { } else {
final List<AbstractPlaceholderSegment> placeholderSegments = new ArrayList<>(placeholderSegmentsSize); final List<AbstractPlaceholderSegment> placeholderSegments = new ArrayList<>(placeholderSegmentsSize);
for (StrTemplateSegment segment : segments) { for (final StrTemplateSegment segment : segments) {
if (segment instanceof AbstractPlaceholderSegment) { if (segment instanceof AbstractPlaceholderSegment) {
placeholderSegments.add((AbstractPlaceholderSegment) segment); placeholderSegments.add((AbstractPlaceholderSegment) segment);
} }
@ -646,14 +646,14 @@ public abstract class StrTemplate {
* @param list 已保存的segment列表 * @param list 已保存的segment列表
* @param newText 新的固定文本 * @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()) { if (newText.isEmpty()) {
return; return;
} }
if (isLastLiteralSegment) { if (isLastLiteralSegment) {
// 最后的固定文本segment 新固定文本 合并为一个 // 最后的固定文本segment 新固定文本 合并为一个
int lastIdx = list.size() - 1; final int lastIdx = list.size() - 1;
StrTemplateSegment lastLiteralSegment = list.get(lastIdx); final StrTemplateSegment lastLiteralSegment = list.get(lastIdx);
list.set(lastIdx, new LiteralSegment(lastLiteralSegment.getText() + newText)); list.set(lastIdx, new LiteralSegment(lastLiteralSegment.getText() + newText));
} else { } else {
list.add(new LiteralSegment(newText)); list.add(new LiteralSegment(newText));
@ -756,7 +756,7 @@ public abstract class StrTemplate {
*/ */
public BuilderChild addFeatures(final Feature... appendFeatures) { public BuilderChild addFeatures(final Feature... appendFeatures) {
if (ArrayUtil.isNotEmpty(appendFeatures)) { if (ArrayUtil.isNotEmpty(appendFeatures)) {
for (Feature feature : appendFeatures) { for (final Feature feature : appendFeatures) {
this.features = feature.set(this.features); this.features = feature.set(this.features);
} }
} }
@ -772,7 +772,7 @@ public abstract class StrTemplate {
*/ */
public BuilderChild removeFeatures(final Feature... removeFeatures) { public BuilderChild removeFeatures(final Feature... removeFeatures) {
if (ArrayUtil.isNotEmpty(removeFeatures)) { if (ArrayUtil.isNotEmpty(removeFeatures)) {
for (Feature feature : removeFeatures) { for (final Feature feature : removeFeatures) {
this.features = feature.clear(this.features); this.features = feature.clear(this.features);
} }
} }
@ -978,7 +978,7 @@ public abstract class StrTemplate {
* @param bitStart 同组第一个策略的掩码位数 * @param bitStart 同组第一个策略的掩码位数
* @param bitLen 同组策略数量 * @param bitLen 同组策略数量
*/ */
Feature(int bitPos, int bitStart, int bitLen) { Feature(final int bitPos, final int bitStart, final int bitLen) {
this.mask = 1 << bitPos; this.mask = 1 << bitPos;
this.clearMask = (-1 << (bitStart + bitLen)) | ((1 << bitStart) - 1); this.clearMask = (-1 << (bitStart + bitLen)) | ((1 << bitStart) - 1);
} }
@ -989,7 +989,7 @@ public abstract class StrTemplate {
* @param features 外部的策略值 * @param features 外部的策略值
* @return 是否为当前策略 * @return 是否为当前策略
*/ */
public boolean contains(int features) { public boolean contains(final int features) {
return (features & mask) != 0; return (features & mask) != 0;
} }
@ -999,7 +999,7 @@ public abstract class StrTemplate {
* @param features 外部的策略值 * @param features 外部的策略值
* @return 添加后的策略值 * @return 添加后的策略值
*/ */
public int set(int features) { public int set(final int features) {
return (features & clearMask) | mask; return (features & clearMask) | mask;
} }
@ -1009,7 +1009,7 @@ public abstract class StrTemplate {
* @param features 外部的策略值 * @param features 外部的策略值
* @return 移除后的策略值 * @return 移除后的策略值
*/ */
public int clear(int features) { public int clear(final int features) {
return (features & clearMask); return (features & clearMask);
} }
@ -1019,13 +1019,13 @@ public abstract class StrTemplate {
* @param features 策略枚举数组 * @param features 策略枚举数组
* @return 总的策略值 * @return 总的策略值
*/ */
public static int of(Feature... features) { public static int of(final Feature... features) {
if (features == null) { if (features == null) {
return 0; return 0;
} }
int value = 0; int value = 0;
for (Feature feature : features) { for (final Feature feature : features) {
value = feature.set(value); 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()) { if (!placeholderSegments.isEmpty()) {
for (AbstractPlaceholderSegment segment : placeholderSegments) { for (final AbstractPlaceholderSegment segment : placeholderSegments) {
if (segment instanceof IndexedPlaceholderSegment) { if (segment instanceof IndexedPlaceholderSegment) {
this.indexedSegmentMaxIdx = Math.max(this.indexedSegmentMaxIdx, ((IndexedPlaceholderSegment) segment).getIndex()); 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 openLength = prefix.length();
final int closeLength = suffix.length(); final int closeLength = suffix.length();
List<StrTemplateSegment> segments = new ArrayList<>(); final List<StrTemplateSegment> segments = new ArrayList<>();
int closeCursor = 0; int closeCursor = 0;
// 开始匹配 // 开始匹配
final char[] src = template.toCharArray(); final char[] src = template.toCharArray();
@ -299,7 +299,7 @@ public class NamedPlaceholderStrTemplate extends StrTemplate {
* @param missingIndexHandler 集合中不存在下标位置时的处理器根据 下标 返回 代替值 * @param missingIndexHandler 集合中不存在下标位置时的处理器根据 下标 返回 代替值
* @return 格式化字符串 * @return 格式化字符串
*/ */
public String formatIndexed(final Collection<?> collection, IntFunction<String> missingIndexHandler) { public String formatIndexed(final Collection<?> collection, final IntFunction<String> missingIndexHandler) {
if (collection == null) { if (collection == null) {
return getTemplate(); return getTemplate();
} }
@ -579,6 +579,9 @@ public class NamedPlaceholderStrTemplate extends StrTemplate {
return new Builder(template); return new Builder(template);
} }
/**
* 构造器
*/
public static class Builder extends AbstractBuilder<Builder, NamedPlaceholderStrTemplate> { public static class Builder extends AbstractBuilder<Builder, NamedPlaceholderStrTemplate> {
/** /**
* 占位符前缀默认为 {@link NamedPlaceholderStrTemplate#DEFAULT_PREFIX} * 占位符前缀默认为 {@link NamedPlaceholderStrTemplate#DEFAULT_PREFIX}

View File

@ -34,196 +34,199 @@ import java.util.function.UnaryOperator;
* @since 6.0.0 * @since 6.0.0
*/ */
public class SinglePlaceholderStrTemplate extends StrTemplate { 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} * 占位符默认为: {@link StrPool#EMPTY_JSON}
*/ */
protected String placeholder; protected String placeholder;
protected SinglePlaceholderStrTemplate(final String template, final int features, final String placeholder, final char escape, protected SinglePlaceholderStrTemplate(final String template, final int features, final String placeholder, final char escape,
final String defaultValue, final UnaryOperator<String> defaultValueHandler) { final String defaultValue, final UnaryOperator<String> defaultValueHandler) {
super(template, escape, defaultValue, defaultValueHandler, features); super(template, escape, defaultValue, defaultValueHandler, features);
Assert.notEmpty(placeholder); Assert.notEmpty(placeholder);
this.placeholder = placeholder; this.placeholder = placeholder;
// 初始化Segment列表 // 初始化Segment列表
afterInit(); afterInit();
} }
@Override @Override
protected List<StrTemplateSegment> parseSegments(final String template) { protected List<StrTemplateSegment> parseSegments(final String template) {
final int placeholderLength = placeholder.length(); final int placeholderLength = placeholder.length();
final int strPatternLength = template.length(); final int strPatternLength = template.length();
// 记录已经处理到的位置 // 记录已经处理到的位置
int handledPosition = 0; int handledPosition = 0;
// 占位符所在位置 // 占位符所在位置
int delimIndex; int delimIndex;
// 上一个解析的segment是否是固定文本如果是则需要和当前新的文本部分合并 // 上一个解析的segment是否是固定文本如果是则需要和当前新的文本部分合并
boolean lastIsLiteralSegment = false; boolean lastIsLiteralSegment = false;
// 复用的占位符变量 // 复用的占位符变量
final SinglePlaceholderSegment singlePlaceholderSegment = SinglePlaceholderSegment.newInstance(placeholder); final SinglePlaceholderSegment singlePlaceholderSegment = SinglePlaceholderSegment.newInstance(placeholder);
List<StrTemplateSegment> segments = null; List<StrTemplateSegment> segments = null;
while (true) { while (true) {
delimIndex = template.indexOf(placeholder, handledPosition); delimIndex = template.indexOf(placeholder, handledPosition);
if (delimIndex == -1) { if (delimIndex == -1) {
// 整个模板都不带占位符 // 整个模板都不带占位符
if (handledPosition == 0) { if (handledPosition == 0) {
return Collections.singletonList(new LiteralSegment(template)); return Collections.singletonList(new LiteralSegment(template));
} }
// 字符串模板剩余部分不再包含占位符 // 字符串模板剩余部分不再包含占位符
if (handledPosition < strPatternLength) { if (handledPosition < strPatternLength) {
addLiteralSegment(lastIsLiteralSegment, segments, template.substring(handledPosition)); addLiteralSegment(lastIsLiteralSegment, segments, template.substring(handledPosition));
} }
return segments; return segments;
} else if (segments == null) { } else if (segments == null) {
segments = new ArrayList<>(); segments = new ArrayList<>();
} }
// 存在 转义符 // 存在 转义符
if (delimIndex > 0 && template.charAt(delimIndex - 1) == escape) { if (delimIndex > 0 && template.charAt(delimIndex - 1) == escape) {
// 存在 双转义符 // 存在 双转义符
if (delimIndex > 1 && template.charAt(delimIndex - 2) == escape) { if (delimIndex > 1 && template.charAt(delimIndex - 2) == escape) {
// 转义符之前还有一个转义符形如"//{"占位符依旧有效 // 转义符之前还有一个转义符形如"//{"占位符依旧有效
addLiteralSegment(lastIsLiteralSegment, segments, template.substring(handledPosition, delimIndex - 1)); addLiteralSegment(lastIsLiteralSegment, segments, template.substring(handledPosition, delimIndex - 1));
segments.add(singlePlaceholderSegment); segments.add(singlePlaceholderSegment);
lastIsLiteralSegment = false; lastIsLiteralSegment = false;
handledPosition = delimIndex + placeholderLength; handledPosition = delimIndex + placeholderLength;
} else { } else {
// 占位符被转义形如"/{"当前字符并不是一个真正的占位符而是普通字符串的一部分 // 占位符被转义形如"/{"当前字符并不是一个真正的占位符而是普通字符串的一部分
addLiteralSegment(lastIsLiteralSegment, segments, template.substring(handledPosition, delimIndex - 1) + placeholder.charAt(0)); addLiteralSegment(lastIsLiteralSegment, segments, template.substring(handledPosition, delimIndex - 1) + placeholder.charAt(0));
lastIsLiteralSegment = true; lastIsLiteralSegment = true;
handledPosition = delimIndex + 1; handledPosition = delimIndex + 1;
} }
} else { } else {
// 正常占位符 // 正常占位符
addLiteralSegment(lastIsLiteralSegment, segments, template.substring(handledPosition, delimIndex)); addLiteralSegment(lastIsLiteralSegment, segments, template.substring(handledPosition, delimIndex));
segments.add(singlePlaceholderSegment); segments.add(singlePlaceholderSegment);
lastIsLiteralSegment = false; lastIsLiteralSegment = false;
handledPosition = delimIndex + placeholderLength; handledPosition = delimIndex + placeholderLength;
} }
} }
} }
// region 格式化方法 // region 格式化方法
// ################################################## 格式化方法 ################################################## // ################################################## 格式化方法 ##################################################
/** /**
* 按顺序使用 数组元素 替换 占位符 * 按顺序使用 数组元素 替换 占位符
* *
* @param args 可变参数 * @param args 可变参数
* @return 格式化字符串 * @return 格式化字符串
*/ */
public String format(final Object... args) { public String format(final Object... args) {
return formatArray(args); return formatArray(args);
} }
/** /**
* 按顺序使用 原始数组元素 替换 占位符 * 按顺序使用 原始数组元素 替换 占位符
* *
* @param array 原始类型数组例如: {@code int[]} * @param array 原始类型数组例如: {@code int[]}
* @return 格式化字符串 * @return 格式化字符串
*/ */
public String formatArray(final Object array) { public String formatArray(final Object array) {
return formatArray(ArrayUtil.wrap(array)); return formatArray(ArrayUtil.wrap(array));
} }
/** /**
* 按顺序使用 数组元素 替换 占位符 * 按顺序使用 数组元素 替换 占位符
* *
* @param array 数组 * @param array 数组
* @return 格式化字符串 * @return 格式化字符串
*/ */
public String formatArray(final Object[] array) { public String formatArray(final Object[] array) {
if (array == null) { if (array == null) {
return getTemplate(); return getTemplate();
} }
return format(Arrays.asList(array)); return format(Arrays.asList(array));
} }
/** /**
* 按顺序使用 迭代器元素 替换 占位符 * 按顺序使用 迭代器元素 替换 占位符
* *
* @param iterable iterable * @param iterable iterable
* @return 格式化字符串 * @return 格式化字符串
*/ */
public String format(final Iterable<?> iterable) { public String format(final Iterable<?> iterable) {
return super.formatSequence(iterable); return super.formatSequence(iterable);
} }
// endregion // endregion
// region 解析方法 // region 解析方法
// ################################################## 解析方法 ################################################## // ################################################## 解析方法 ##################################################
/** /**
* 占位符位置的值 按顺序解析为 字符串数组 * 占位符位置的值 按顺序解析为 字符串数组
* *
* @param str 待解析的字符串一般是格式化方法的返回值 * @param str 待解析的字符串一般是格式化方法的返回值
* @return 参数值数组 * @return 参数值数组
*/ */
public String[] matchesToArray(final String str) { public String[] matchesToArray(final String str) {
return matches(str).toArray(new String[0]); return matches(str).toArray(new String[0]);
} }
/** /**
* 占位符位置的值 按顺序解析为 字符串列表 * 占位符位置的值 按顺序解析为 字符串列表
* *
* @param str 待解析的字符串一般是格式化方法的返回值 * @param str 待解析的字符串一般是格式化方法的返回值
* @return 参数值列表 * @return 参数值列表
*/ */
public List<String> matches(final String str) { public List<String> matches(final String str) {
return super.matchesSequence(str); return super.matchesSequence(str);
} }
// endregion // endregion
/** /**
* 创建 builder * 创建 builder
* *
* @param template 字符串模板不能为 {@code null} * @param template 字符串模板不能为 {@code null}
* @return builder实例 * @return builder实例
*/ */
public static Builder builder(final String template) { public static Builder builder(final String template) {
return new Builder(template); return new Builder(template);
} }
public static class Builder extends AbstractBuilder<Builder, SinglePlaceholderStrTemplate> { /**
/** * 构造器
* 单占位符 */
* <p>例如"?""{}"</p> public static class Builder extends AbstractBuilder<Builder, SinglePlaceholderStrTemplate> {
* <p>默认为 {@link SinglePlaceholderStrTemplate#DEFAULT_PLACEHOLDER}</p> /**
*/ * 单占位符
protected String placeholder; * <p>例如"?""{}"</p>
* <p>默认为 {@link SinglePlaceholderStrTemplate#DEFAULT_PLACEHOLDER}</p>
*/
protected String placeholder;
protected Builder(final String template) { protected Builder(final String template) {
super(template); super(template);
} }
/** /**
* 设置 占位符 * 设置 占位符
* *
* @param placeholder 占位符不能为 {@code null} {@code ""} * @param placeholder 占位符不能为 {@code null} {@code ""}
* @return builder * @return builder
*/ */
public Builder placeholder(final String placeholder) { public Builder placeholder(final String placeholder) {
this.placeholder = placeholder; this.placeholder = placeholder;
return this; return this;
} }
@Override @Override
protected SinglePlaceholderStrTemplate buildInstance() { protected SinglePlaceholderStrTemplate buildInstance() {
if (this.placeholder == null) { if (this.placeholder == null) {
this.placeholder = DEFAULT_PLACEHOLDER; this.placeholder = DEFAULT_PLACEHOLDER;
} }
return new SinglePlaceholderStrTemplate(this.template, this.features, this.placeholder, this.escape, return new SinglePlaceholderStrTemplate(this.template, this.features, this.placeholder, this.escape,
this.defaultValue, this.defaultValueHandler); this.defaultValue, this.defaultValueHandler);
} }
@Override @Override
protected Builder self() { protected Builder self() {
return this; 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; import java.util.Map;
/** /**
* {@link BeanPath} 单元测试 * {@link BeanPathOld} 单元测试
* *
* @author looly * @author looly
* *
@ -73,7 +73,7 @@ public class BeanPathTest {
@Test @Test
public void beanPathTest1() { 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("userInfo", pattern.patternParts.get(0));
Assertions.assertEquals("examInfoDict", pattern.patternParts.get(1)); Assertions.assertEquals("examInfoDict", pattern.patternParts.get(1));
Assertions.assertEquals("0", pattern.patternParts.get(2)); Assertions.assertEquals("0", pattern.patternParts.get(2));
@ -83,7 +83,7 @@ public class BeanPathTest {
@Test @Test
public void beanPathTest2() { 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("userInfo", pattern.patternParts.get(0));
Assertions.assertEquals("examInfoDict", pattern.patternParts.get(1)); Assertions.assertEquals("examInfoDict", pattern.patternParts.get(1));
Assertions.assertEquals("0", pattern.patternParts.get(2)); Assertions.assertEquals("0", pattern.patternParts.get(2));
@ -92,7 +92,7 @@ public class BeanPathTest {
@Test @Test
public void beanPathTest3() { 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("userInfo", pattern.patternParts.get(0));
Assertions.assertEquals("examInfoDict", pattern.patternParts.get(1)); Assertions.assertEquals("examInfoDict", pattern.patternParts.get(1));
Assertions.assertEquals("0", pattern.patternParts.get(2)); Assertions.assertEquals("0", pattern.patternParts.get(2));
@ -101,14 +101,14 @@ public class BeanPathTest {
@Test @Test
public void getTest() { 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); final Object result = pattern.get(tempMap);
Assertions.assertEquals(1, result); Assertions.assertEquals(1, result);
} }
@Test @Test
public void setTest() { 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); pattern.set(tempMap, 2);
final Object result = pattern.get(tempMap); final Object result = pattern.get(tempMap);
Assertions.assertEquals(2, result); Assertions.assertEquals(2, result);
@ -116,7 +116,7 @@ public class BeanPathTest {
@Test @Test
public void getMapTest () { public void getMapTest () {
final BeanPath pattern = BeanPath.of("userInfo[id, photoPath]"); final BeanPathOld pattern = BeanPathOld.of("userInfo[id, photoPath]");
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Map<String, Object> result = (Map<String, Object>)pattern.get(tempMap); final Map<String, Object> result = (Map<String, Object>)pattern.get(tempMap);
Assertions.assertEquals(1, result.get("id")); Assertions.assertEquals(1, result.get("id"));
@ -129,13 +129,13 @@ public class BeanPathTest {
dataMap.put("aa", "value0"); dataMap.put("aa", "value0");
dataMap.put("aa.bb.cc", "value111111");// key 是类名 格式 ' . ' 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)); Assertions.assertEquals("value111111", pattern.get(dataMap));
} }
@Test @Test
public void compileTest(){ 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("abc.dd", of.getPatternParts().get(0));
Assertions.assertEquals("ee", of.getPatternParts().get(1)); Assertions.assertEquals("ee", of.getPatternParts().get(1));
Assertions.assertEquals("ff.", of.getPatternParts().get(2)); Assertions.assertEquals("ff.", of.getPatternParts().get(2));
@ -145,17 +145,17 @@ public class BeanPathTest {
public void issue2362Test() { public void issue2362Test() {
final Map<String, Object> map = new HashMap<>(); final Map<String, Object> map = new HashMap<>();
BeanPath beanPath = BeanPath.of("list[0].name"); BeanPathOld beanPath = BeanPathOld.of("list[0].name");
beanPath.set(map, "张三"); beanPath.set(map, "张三");
Assertions.assertEquals("{list=[{name=张三}]}", map.toString()); Assertions.assertEquals("{list=[{name=张三}]}", map.toString());
map.clear(); map.clear();
beanPath = BeanPath.of("list[1].name"); beanPath = BeanPathOld.of("list[1].name");
beanPath.set(map, "张三"); beanPath.set(map, "张三");
Assertions.assertEquals("{list=[null, {name=张三}]}", map.toString()); Assertions.assertEquals("{list=[null, {name=张三}]}", map.toString());
map.clear(); map.clear();
beanPath = BeanPath.of("list[0].1.name"); beanPath = BeanPathOld.of("list[0].1.name");
beanPath.set(map, "张三"); beanPath.set(map, "张三");
Assertions.assertEquals("{list=[[null, {name=张三}]]}", map.toString()); Assertions.assertEquals("{list=[[null, {name=张三}]]}", map.toString());
} }
@ -164,7 +164,7 @@ public class BeanPathTest {
public void putTest() { public void putTest() {
final Map<String, Object> map = new HashMap<>(); 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, "张三"); beanPath.set(map, "张三");
Assertions.assertEquals("{list=[null, {name=张三}]}", map.toString()); Assertions.assertEquals("{list=[null, {name=张三}]}", map.toString());
} }
@ -172,7 +172,7 @@ public class BeanPathTest {
@Test @Test
public void putByPathTest() { public void putByPathTest() {
final Dict dict = new Dict(); 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()); Assertions.assertEquals("{aa={bb=BB}}", dict.toString());
} }
@ -180,9 +180,9 @@ public class BeanPathTest {
public void appendArrayTest(){ public void appendArrayTest(){
// issue#3008@Github // issue#3008@Github
final MyUser myUser = new MyUser(); final MyUser myUser = new MyUser();
BeanPath.of("hobby[0]").set(myUser, "LOL"); BeanPathOld.of("hobby[0]").set(myUser, "LOL");
BeanPath.of("hobby[1]").set(myUser, "KFC"); BeanPathOld.of("hobby[1]").set(myUser, "KFC");
BeanPath.of("hobby[2]").set(myUser, "COFFE"); BeanPathOld.of("hobby[2]").set(myUser, "COFFE");
Assertions.assertEquals("[LOL, KFC, COFFE]", ArrayUtil.toString(myUser.getHobby())); Assertions.assertEquals("[LOL, KFC, COFFE]", ArrayUtil.toString(myUser.getHobby()));
} }
@ -191,9 +191,9 @@ public class BeanPathTest {
public void appendArrayTest2(){ public void appendArrayTest2(){
// issue#3008@Github // issue#3008@Github
final MyUser2 myUser = new MyUser2(); final MyUser2 myUser = new MyUser2();
BeanPath.of("myUser.hobby[0]").set(myUser, "LOL"); BeanPathOld.of("myUser.hobby[0]").set(myUser, "LOL");
BeanPath.of("myUser.hobby[1]").set(myUser, "KFC"); BeanPathOld.of("myUser.hobby[1]").set(myUser, "KFC");
BeanPath.of("myUser.hobby[2]").set(myUser, "COFFE"); BeanPathOld.of("myUser.hobby[2]").set(myUser, "COFFE");
Assertions.assertEquals("[LOL, KFC, COFFE]", ArrayUtil.toString(myUser.getMyUser().getHobby())); 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}); 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); final Object o = beanPath.get(testPojo);
Assertions.assertEquals(Integer.valueOf(2), ArrayUtil.get(o, 0)); 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; 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.ConvertException;
import org.dromara.hutool.core.convert.Converter; import org.dromara.hutool.core.convert.Converter;
import org.dromara.hutool.core.lang.mutable.MutableEntry; import org.dromara.hutool.core.lang.mutable.MutableEntry;
@ -64,11 +64,11 @@ public interface JSON extends Converter, Cloneable, Serializable {
* *
* @param expression 表达式 * @param expression 表达式
* @return 对象 * @return 对象
* @see BeanPath#get(Object) * @see BeanPathOld#get(Object)
* @since 4.0.6 * @since 4.0.6
*/ */
default Object getByPath(final String expression) { 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 * @param value
*/ */
default void putByPath(final String expression, final Object 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 expression 表达式
* @param resultType 返回值类型 * @param resultType 返回值类型
* @return 对象 * @return 对象
* @see BeanPath#get(Object) * @see BeanPathOld#get(Object)
* @since 4.0.6 * @since 4.0.6
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")