From 1023af3e40cff83482c18a86102414c72499f930 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 15 Nov 2023 00:27:24 +0800 Subject: [PATCH] add BeanPath --- .../bean/{BeanPath.java => BeanPathOld.java} | 8 +- .../dromara/hutool/core/bean/BeanUtil.java | 8 +- .../dromara/hutool/core/bean/DynaBean.java | 2 +- .../hutool/core/bean/path/BeanPath.java | 195 ++++++++++ .../hutool/core/bean/path/node/EmptyNode.java | 36 ++ .../hutool/core/bean/path/node/ListNode.java | 90 +++++ .../hutool/core/bean/path/node/NameNode.java | 66 ++++ .../hutool/core/bean/path/node/Node.java | 36 ++ .../core/bean/path/node/NodeFactory.java | 47 +++ .../hutool/core/bean/path/node/RangeNode.java | 70 ++++ .../core/bean/path/node/package-info.java | 18 + .../hutool/core/bean/path/package-info.java | 19 + .../org/dromara/hutool/core/map/Dict.java | 8 +- .../core/text/placeholder/StrTemplate.java | 40 +- .../placeholder/segment/package-info.java | 16 + .../template/NamedPlaceholderStrTemplate.java | 9 +- .../SinglePlaceholderStrTemplate.java | 349 +++++++++--------- .../placeholder/template/package-info.java | 16 + .../hutool/core/bean/BeanPathTest.java | 40 +- .../hutool/core/bean/BeanUtilTest.java | 2 +- .../bean/path/BeanPathGetOrSetValueTest.java | 117 ++++++ .../hutool/core/bean/path/BeanPathTest.java | 166 +++++++++ .../java/org/dromara/hutool/json/JSON.java | 10 +- 23 files changed, 1133 insertions(+), 235 deletions(-) rename hutool-core/src/main/java/org/dromara/hutool/core/bean/{BeanPath.java => BeanPathOld.java} (98%) create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/bean/path/BeanPath.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/EmptyNode.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/ListNode.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/NameNode.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/Node.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/NodeFactory.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/RangeNode.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/package-info.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/bean/path/package-info.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/segment/package-info.java create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/template/package-info.java create mode 100644 hutool-core/src/test/java/org/dromara/hutool/core/bean/path/BeanPathGetOrSetValueTest.java create mode 100644 hutool-core/src/test/java/org/dromara/hutool/core/bean/path/BeanPathTest.java diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanPath.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanPathOld.java similarity index 98% rename from hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanPath.java rename to hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanPathOld.java index 336731462..42bd9f4aa 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanPath.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanPathOld.java @@ -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); } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanUtil.java index 9657a85de..b0e6edb90 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/BeanUtil.java @@ -355,7 +355,7 @@ public class BeanUtil { * @param bean Bean对象,支持Map、List、Collection、Array * @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对象,支持Map、List、Collection、Array * @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 diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/DynaBean.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/DynaBean.java index ac45105bc..7e5be41ec 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/bean/DynaBean.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/DynaBean.java @@ -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); diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/BeanPath.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/BeanPath.java new file mode 100644 index 000000000..0189639ba --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/BeanPath.java @@ -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对象
+ * 根据给定的表达式,查找Bean中对应的属性值对象。 表达式分为两种: + *
    + *
  1. .表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值
  2. + *
  3. []表达式,可以获取集合等对象中对应index的值
  4. + *
+ *

+ * 表达式栗子: + * + *

+ * persion
+ * persion.name
+ * persons[3]
+ * person.friends[5].name
+ * ['person']['friends'][5]['name']
+ * 
+ * + * @author Looly + * @since 6.0.0 + */ +public class BeanPath implements Node, Iterator { + + /** + * 表达式边界符号数组 + */ + 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 + '\'' + + '}'; + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/EmptyNode.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/EmptyNode.java new file mode 100644 index 000000000..56a4296e0 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/EmptyNode.java @@ -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 + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/ListNode.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/ListNode.java new file mode 100644 index 000000000..e042e83ed --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/ListNode.java @@ -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 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 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) bean, unWrappedNames); + } else { + final Map 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 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; + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/NameNode.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/NameNode.java new file mode 100644 index 000000000..954ed8214 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/NameNode.java @@ -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; + +/** + * 处理名称节点或序号节点,如: + *
    + *
  • name
  • + *
  • 1
  • + *
+ * + * @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; + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/Node.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/Node.java new file mode 100644 index 000000000..86581480c --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/Node.java @@ -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); +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/NodeFactory.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/NodeFactory.java new file mode 100644 index 000000000..3080262a9 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/NodeFactory.java @@ -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); + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/RangeNode.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/RangeNode.java new file mode 100644 index 000000000..ed0b7d843 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/RangeNode.java @@ -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 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); + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/package-info.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/package-info.java new file mode 100644 index 000000000..a6ab876d2 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/node/package-info.java @@ -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; diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/package-info.java b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/package-info.java new file mode 100644 index 000000000..4e76b0066 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/bean/path/package-info.java @@ -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; diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/map/Dict.java b/hutool-core/src/main/java/org/dromara/hutool/core/map/Dict.java index 2471f06eb..b0c7cdc1d 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/map/Dict.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/map/Dict.java @@ -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 implements TypeGetter 目标类型 * @param expression 表达式 * @return 对象 - * @see BeanPath#get(Object) + * @see BeanPathOld#get(Object) * @since 5.7.14 */ @SuppressWarnings("unchecked") public 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 implements TypeGetter T getByPath(final String expression, final Type resultType) { diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/StrTemplate.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/StrTemplate.java index ff5613ec1..aefb1537f 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/StrTemplate.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/StrTemplate.java @@ -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 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 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 list, String newText) { + protected void addLiteralSegment(final boolean isLastLiteralSegment, final List 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); } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/segment/package-info.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/segment/package-info.java new file mode 100644 index 000000000..81cc4bbea --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/segment/package-info.java @@ -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; diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/template/NamedPlaceholderStrTemplate.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/template/NamedPlaceholderStrTemplate.java index f167b1d61..75d780c10 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/template/NamedPlaceholderStrTemplate.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/template/NamedPlaceholderStrTemplate.java @@ -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 segments = new ArrayList<>(); + final List 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 missingIndexHandler) { + public String formatIndexed(final Collection collection, final IntFunction 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 { /** * 占位符前缀,默认为 {@link NamedPlaceholderStrTemplate#DEFAULT_PREFIX} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/template/SinglePlaceholderStrTemplate.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/template/SinglePlaceholderStrTemplate.java index e3b112140..bb5b11f89 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/template/SinglePlaceholderStrTemplate.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/template/SinglePlaceholderStrTemplate.java @@ -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 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 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 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 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 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 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 matches(final String str) { - return super.matchesSequence(str); - } - // endregion + /** + * 将 占位符位置的值 按顺序解析为 字符串列表 + * + * @param str 待解析的字符串,一般是格式化方法的返回值 + * @return 参数值列表 + */ + public List 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 { - /** - * 单占位符 - *

例如:"?"、"{}"

- *

默认为 {@link SinglePlaceholderStrTemplate#DEFAULT_PLACEHOLDER}

- */ - protected String placeholder; + /** + * 构造器 + */ + public static class Builder extends AbstractBuilder { + /** + * 单占位符 + *

例如:"?"、"{}"

+ *

默认为 {@link SinglePlaceholderStrTemplate#DEFAULT_PLACEHOLDER}

+ */ + 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; + } + } } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/template/package-info.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/template/package-info.java new file mode 100644 index 000000000..f592a5bb6 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/placeholder/template/package-info.java @@ -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; diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/bean/BeanPathTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/bean/BeanPathTest.java index 9d28e7733..668c3cdc9 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/bean/BeanPathTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/bean/BeanPathTest.java @@ -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 result = (Map)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 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 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())); } diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/bean/BeanUtilTest.java index c7f38aa03..88b405f42 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/bean/BeanUtilTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/bean/BeanUtilTest.java @@ -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)); diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/bean/path/BeanPathGetOrSetValueTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/bean/path/BeanPathGetOrSetValueTest.java new file mode 100644 index 000000000..26d16d9e9 --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/bean/path/BeanPathGetOrSetValueTest.java @@ -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 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 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 result = (Map)pattern.getValue(tempMap); + Assertions.assertEquals(1, result.get("id")); + Assertions.assertEquals("yx.mm.com", result.get("photoPath")); + } + + @Test + public void getKeyWithDotTest () { + final Map 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 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()); + } +} diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/bean/path/BeanPathTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/bean/path/BeanPathTest.java new file mode 100644 index 000000000..fca57a7ef --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/bean/path/BeanPathTest.java @@ -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()); + } +} diff --git a/hutool-json/src/main/java/org/dromara/hutool/json/JSON.java b/hutool-json/src/main/java/org/dromara/hutool/json/JSON.java index bc24f7d5f..42d422a1f 100644 --- a/hutool-json/src/main/java/org/dromara/hutool/json/JSON.java +++ b/hutool-json/src/main/java/org/dromara/hutool/json/JSON.java @@ -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")