!976 新增StrTemplate工具类

Merge pull request !976 from emptypoint/add-StrTemplate
This commit is contained in:
Looly 2023-04-17 15:59:41 +00:00 committed by Gitee
commit 6e6079725d
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
13 changed files with 2738 additions and 56 deletions

View File

@ -129,7 +129,7 @@ public class PlaceholderParser implements UnaryOperator<String> {
continue;
}
// 记录当前位符的开始符号与上一占位符的结束符号间的字符串
// 记录当前位符的开始符号与上一占位符的结束符号间的字符串
result.append(src, closeCursor, openCursor - closeCursor);
// 重置结束游标至当前占位符的开始处
@ -160,7 +160,7 @@ public class PlaceholderParser implements UnaryOperator<String> {
result.append(processor.apply(expression.toString()));
expression.setLength(0);
// 完成当前占位符的处理匹配寻找下一个
closeCursor = end + close.length();
closeCursor = end + closeLength;
}
// 寻找下一个开始符号

View File

@ -13,7 +13,8 @@
package org.dromara.hutool.core.text;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.util.CharUtil;
import org.dromara.hutool.core.text.placeholder.StrTemplate;
import org.dromara.hutool.core.text.placeholder.template.NamedPlaceholderStrTemplate;
import java.util.Map;
@ -60,50 +61,10 @@ public class StrFormatter {
if (StrUtil.isBlank(strPattern) || StrUtil.isBlank(placeHolder) || ArrayUtil.isEmpty(argArray)) {
return strPattern;
}
final int strPatternLength = strPattern.length();
final int placeHolderLength = placeHolder.length();
// 初始化定义好的长度以获得更好的性能
final StringBuilder sbuf = new StringBuilder(strPatternLength + 50);
int handledPosition = 0;// 记录已经处理到的位置
int delimIndex;// 占位符所在位置
for (int argIndex = 0; argIndex < argArray.length; argIndex++) {
delimIndex = strPattern.indexOf(placeHolder, handledPosition);
if (delimIndex == -1) {// 剩余部分无占位符
if (handledPosition == 0) { // 不带占位符的模板直接返回
return strPattern;
}
// 字符串模板剩余部分不再包含占位符加入剩余部分后返回结果
sbuf.append(strPattern, handledPosition, strPatternLength);
return sbuf.toString();
}
// 转义符
if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == CharUtil.BACKSLASH) {// 转义符
if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == CharUtil.BACKSLASH) {// 双转义符
// 转义符之前还有一个转义符占位符依旧有效
sbuf.append(strPattern, handledPosition, delimIndex - 1);
sbuf.append(StrUtil.utf8Str(argArray[argIndex]));
handledPosition = delimIndex + placeHolderLength;
} else {
// 占位符被转义
argIndex--;
sbuf.append(strPattern, handledPosition, delimIndex - 1);
sbuf.append(placeHolder.charAt(0));
handledPosition = delimIndex + 1;
}
} else {// 正常占位符
sbuf.append(strPattern, handledPosition, delimIndex);
sbuf.append(StrUtil.utf8Str(argArray[argIndex]));
handledPosition = delimIndex + placeHolderLength;
}
}
// 加入最后一个占位符后所有的字符
sbuf.append(strPattern, handledPosition, strPatternLength);
return sbuf.toString();
return StrTemplate.of(strPattern)
.placeholder(placeHolder)
.build()
.format(argArray);
}
/**
@ -124,15 +85,12 @@ public class StrFormatter {
return template.toString();
}
String template2 = template.toString();
String value;
for (final Map.Entry<?, ?> entry : map.entrySet()) {
value = StrUtil.utf8Str(entry.getValue());
if (null == value && ignoreNull) {
continue;
}
template2 = StrUtil.replace(template2, "{" + entry.getKey() + "}", value);
final NamedPlaceholderStrTemplate.Builder builder = StrTemplate.ofNamed(template.toString());
if (ignoreNull) {
builder.addFeatures(StrTemplate.Feature.FORMAT_NULL_VALUE_TO_WHOLE_PLACEHOLDER);
} else {
builder.addFeatures(StrTemplate.Feature.FORMAT_NULL_VALUE_TO_EMPTY);
}
return template2;
return builder.build().format(map);
}
}

View File

@ -0,0 +1,39 @@
package org.dromara.hutool.core.text.placeholder.segment;
import java.util.Iterator;
/**
* 字符串模板-占位符-抽象 Segment
* <p>例如{@literal "???"->"???", "{}"->"{}", "{name}"->"name"}</p>
*
* @author emptypoint
* @since 6.0.0
*/
public abstract class AbstractPlaceholderSegment implements StrTemplateSegment {
/**
* 占位符变量
* <p>例如{@literal "???"->"???", "{}"->"{}", "{name}"->"name"}</p>
*/
private final String placeholder;
protected AbstractPlaceholderSegment(final String placeholder) {
this.placeholder = placeholder;
}
@Override
public String getText() {
return placeholder;
}
@Override
public void format(final StringBuilder sb, final Iterator<String> valueIterator) {
// 当前是 占位符直接 替换为 参数值
if (valueIterator.hasNext()) {
sb.append(valueIterator.next());
}
}
public String getPlaceholder() {
return placeholder;
}
}

View File

@ -0,0 +1,24 @@
package org.dromara.hutool.core.text.placeholder.segment;
/**
* 基字符串模板-基于下标的占位符 Segment
* <p>例如"{1}"</p>
*
* @author emptypoint
* @since 6.0.0
*/
public class IndexedPlaceholderSegment extends NamedPlaceholderSegment {
/**
* 下标值
*/
private final int index;
public IndexedPlaceholderSegment(final String idxStr, final String wholePlaceholder) {
super(idxStr, wholePlaceholder);
this.index = Integer.parseInt(idxStr);
}
public int getIndex() {
return index;
}
}

View File

@ -0,0 +1,32 @@
package org.dromara.hutool.core.text.placeholder.segment;
import java.util.Iterator;
/**
* 字符串模板-固定文本 Segment
*
* @author emptypoint
* @since 6.0.0
*/
public class LiteralSegment implements StrTemplateSegment {
/**
* 模板中固定的一段文本
*/
private final String text;
public LiteralSegment(final String text) {
this.text = text;
}
@Override
public String getText() {
return text;
}
@Override
public void format(final StringBuilder sb, final Iterator<String> valueIterator) {
// 在格式化中 拼接 固定文本
sb.append(text);
}
}

View File

@ -0,0 +1,27 @@
package org.dromara.hutool.core.text.placeholder.segment;
/**
* 字符串模板-有前后缀的变量占位符 Segment
* <p>例如"{1}", "{name}", "#{id}"</p>
*
* @author emptypoint
* @since 6.0.0
*/
public class NamedPlaceholderSegment extends AbstractPlaceholderSegment {
/**
* 占位符完整文本
* <p>例如{@literal "{name}"->"{name}"}</p>
*/
private final String wholePlaceholder;
public NamedPlaceholderSegment(final String name, final String wholePlaceholder) {
super(name);
this.wholePlaceholder = wholePlaceholder;
}
@Override
public String getText() {
return wholePlaceholder;
}
}

View File

@ -0,0 +1,19 @@
package org.dromara.hutool.core.text.placeholder.segment;
/**
* 字符串模板-单变量占位符 Segment
* <p>例如"?", "{}", "$$$"</p>
*
* @author emptypoint
* @since 6.0.0
*/
public class SinglePlaceholderSegment extends AbstractPlaceholderSegment {
private SinglePlaceholderSegment(final String placeholder) {
super(placeholder);
}
public static SinglePlaceholderSegment newInstance(final String placeholder) {
return new SinglePlaceholderSegment(placeholder);
}
}

View File

@ -0,0 +1,29 @@
package org.dromara.hutool.core.text.placeholder.segment;
import java.util.Iterator;
/**
* 字符串模板-抽象 Segment
*
* @author emptypoint
* @since 6.0.0
*/
public interface StrTemplateSegment {
/**
* 在格式化中按顺序 拼接 参数值
*
* <p>如果是固定文本则直接拼接如果是占位符则拼接参数值</p>
*
* @param sb 存储格式化结果的变量
* @param valueIterator 与占位符依次对应的参数值列表
*/
void format(final StringBuilder sb, final Iterator<String> valueIterator);
/**
* 获取文本值
*
* @return 文本值对于固定文本Segment返回文本值对于单占位符Segment返回占位符对于有前后缀的占位符Segment返回占位符完整文本例如: "{name}"
*/
String getText();
}

View File

@ -0,0 +1,622 @@
package org.dromara.hutool.core.text.placeholder.template;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.bean.BeanDesc;
import org.dromara.hutool.core.bean.BeanUtil;
import org.dromara.hutool.core.collection.CollUtil;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.convert.Convert;
import org.dromara.hutool.core.exceptions.UtilException;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.lang.func.LambdaUtil;
import org.dromara.hutool.core.math.NumberUtil;
import org.dromara.hutool.core.text.StrPool;
import org.dromara.hutool.core.text.placeholder.StrTemplate;
import org.dromara.hutool.core.text.placeholder.segment.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.*;
/**
* 有前后缀的字符串模板
* <p>例如"{1}", "{name}", "#{id}"</p>
*
* @author emptypoint
* @since 6.0.0
*/
public class NamedPlaceholderStrTemplate extends StrTemplate {
/**
* 默认前缀
*/
public static final String DEFAULT_PREFIX = StrPool.DELIM_START;
/**
* 默认后缀
*/
public static final String DEFAULT_SUFFIX = StrPool.DELIM_END;
/**
* 占位符前缀默认为: {@link #DEFAULT_PREFIX}
*/
protected String prefix;
/**
* 占位符后缀默认为: {@link #DEFAULT_SUFFIX}
*/
protected String suffix;
/**
* 下标占位符中最大的下标值
*/
protected int indexedSegmentMaxIdx = 0;
protected NamedPlaceholderStrTemplate(final String template, final int features, final String prefix,
final String suffix, final char escape, final String defaultValue,
final UnaryOperator<String> defaultValueHandler) {
super(template, escape, defaultValue, defaultValueHandler, features);
Assert.notEmpty(prefix);
Assert.notEmpty(suffix);
this.prefix = prefix;
this.suffix = suffix;
// 一些初始化后续操作
afterInit();
// 记录 下标占位符 最大的 下标值
if (!placeholderSegments.isEmpty()) {
for (AbstractPlaceholderSegment segment : placeholderSegments) {
if (segment instanceof IndexedPlaceholderSegment) {
this.indexedSegmentMaxIdx = Math.max(this.indexedSegmentMaxIdx, ((IndexedPlaceholderSegment) segment).getIndex());
}
}
}
}
@Override
protected List<StrTemplateSegment> parseSegments(final String template) {
// 寻找第一个前缀符号
int openCursor = template.indexOf(prefix);
// 没有任何占位符
if (openCursor == -1) {
return Collections.singletonList(new LiteralSegment(template));
}
final int openLength = prefix.length();
final int closeLength = suffix.length();
List<StrTemplateSegment> segments = new ArrayList<>();
int closeCursor = 0;
// 开始匹配
final char[] src = template.toCharArray();
final StringBuilder expression = new StringBuilder(16);
boolean hasDoubleEscape = false;
// 占位变量名称
String variableName;
// 完整的占位符
String wholePlaceholder;
while (openCursor > -1) {
// 开始符号是否被转义若是则跳过并寻找下一个开始符号
if (openCursor > 0 && src[openCursor - 1] == escape) {
// 存在 双转义符转义符之前还有一个转义符形如"\\{"占位符依旧有效
if (openCursor > 1 && src[openCursor - 2] == escape) {
hasDoubleEscape = true;
} else {
// 开始符号被转义跳过寻找下一个开始符号
segments.add(new LiteralSegment(
template.substring(closeCursor, openCursor - 1) + prefix
));
closeCursor = openCursor + openLength;
openCursor = template.indexOf(prefix, closeCursor);
continue;
}
}
// 没有双转义符
if (!hasDoubleEscape) {
if (closeCursor < openCursor) {
// 完整记录当前占位符的开始符号与上一占位符的结束符号间的字符串
segments.add(new LiteralSegment(template.substring(closeCursor, openCursor)));
}
} else {
// 存在双转义符只能保留一个转义符
hasDoubleEscape = false;
segments.add(new LiteralSegment(template.substring(closeCursor, openCursor - 1)));
}
// 重置结束游标至当前占位符的开始处
closeCursor = openCursor + openLength;
// 寻找结束符号下标
int end = template.indexOf(suffix, closeCursor);
while (end > -1) {
// 结束符号被转义寻找下一个结束符号
if (end > closeCursor && src[end - 1] == escape) {
// 双转义符保留一个转义符并且找到了结束符
if (end > 1 && src[end - 2] == escape) {
expression.append(src, closeCursor, end - closeCursor - 1);
break;
} else {
expression.append(src, closeCursor, end - closeCursor - 1).append(suffix);
closeCursor = end + closeLength;
end = template.indexOf(suffix, closeCursor);
}
}
// 找到结束符号
else {
expression.append(src, closeCursor, end - closeCursor);
break;
}
}
// 未能找到结束符号说明匹配异常
if (end == -1) {
throw new UtilException("\"{}\" 中字符下标 {} 处的开始符没有找到对应的结束符", template, openCursor);
}
// 找到结束符号开始到结束符号 之间的字符串 就是占位变量
else {
// 占位变量名称
variableName = expression.toString();
expression.setLength(0);
// 完整的占位符
wholePlaceholder = expression.append(prefix).append(variableName).append(suffix).toString();
expression.setLength(0);
// 如果是整数则当作下标处理
if (NumberUtil.isInteger(variableName)) {
segments.add(new IndexedPlaceholderSegment(variableName, wholePlaceholder));
} else {
// 当作变量名称处理
segments.add(new NamedPlaceholderSegment(variableName, wholePlaceholder));
}
// 完成当前占位符的处理匹配寻找下一个
closeCursor = end + closeLength;
}
// 寻找下一个开始符号
openCursor = template.indexOf(prefix, closeCursor);
}
// 若匹配结束后仍有未处理的字符串则直接将其拼接到表达式上
if (closeCursor < src.length) {
segments.add(new LiteralSegment(template.substring(closeCursor, src.length)));
}
return segments;
}
// region 格式化方法
// ################################################## 格式化方法 ##################################################
// region 基于顺序的格式化方法
// ############################## 基于顺序的格式化方法 ##############################
/**
* 按顺序使用 数组元素 替换 占位符
*
* @param args 可变参数
* @return 格式化字符串
*/
public String formatSequence(final Object... args) {
return formatArraySequence(args);
}
/**
* 按顺序使用 原始数组元素 替换 占位符
*
* @param array 原始类型数组例如: {@code int[]}
* @return 格式化字符串
*/
public String formatArraySequence(final Object array) {
return formatArraySequence(ArrayUtil.wrap(array));
}
/**
* 按顺序使用 数组元素 替换 占位符
*
* @param array 数组
* @return 格式化字符串
*/
public String formatArraySequence(final Object[] array) {
if (array == null) {
return getTemplate();
}
return formatSequence(Arrays.asList(array));
}
/**
* 按顺序使用 迭代器元素 替换 占位符
*
* @param iterable iterable
* @return 格式化字符串
*/
@Override
public String formatSequence(final Iterable<?> iterable) {
return super.formatSequence(iterable);
}
// endregion
// region 基于下标的格式化方法
// ############################## 基于下标的格式化方法 ##############################
/**
* 下标 使用 数组元素 替换 占位符
*
* @param args 可变参数
* @return 格式化字符串
*/
public String formatIndexed(final Object... args) {
return formatArrayIndexed(args);
}
/**
* 下标 使用 原始数组元素 替换 占位符
*
* @param array 原始类型数组
* @return 格式化字符串
*/
public String formatArrayIndexed(final Object array) {
return formatArrayIndexed(ArrayUtil.wrap(array));
}
/**
* 下标 使用 数组元素 替换 占位符
*
* @param array 数组
* @return 格式化字符串
*/
public String formatArrayIndexed(final Object[] array) {
if (array == null) {
return getTemplate();
}
return formatIndexed(Arrays.asList(array));
}
/**
* 下标 使用 集合元素 替换 占位符
*
* @param collection 集合元素
* @return 格式化字符串
*/
public String formatIndexed(final Collection<?> collection) {
return formatIndexed(collection, null);
}
/**
* 下标 使用 集合元素 替换 占位符
*
* @param collection 集合元素
* @param missingIndexHandler 集合中不存在下标位置时的处理器根据 下标 返回 代替值
* @return 格式化字符串
*/
public String formatIndexed(final Collection<?> collection, IntFunction<String> missingIndexHandler) {
if (collection == null) {
return getTemplate();
}
final int size = collection.size();
final boolean isList = collection instanceof List;
return formatBySegment(segment -> {
int index = ((IndexedPlaceholderSegment) segment).getIndex();
if (index < 0) {
index += size;
}
if (index >= 0 && index < size) {
if (isList) {
return ((List<?>) collection).get(index);
}
return CollUtil.get(collection, index);
}
// 下标越界代表 占位符 没有对应值尝试获取代替值
else if (missingIndexHandler != null) {
return missingIndexHandler.apply(index);
} else {
return formatMissingKey(segment);
}
});
}
// endregion
// region 基于键值的格式化方法
// ############################## 基于键值的格式化方法 ##############################
/**
* 使用 占位变量名称 Bean Map 中查询值来 替换 占位符
*
* @param beanOrMap Bean Map 实例
* @return 格式化字符串
*/
@SuppressWarnings("unchecked")
public String format(final Object beanOrMap) {
if (beanOrMap == null) {
return getTemplate();
}
if (beanOrMap instanceof Map) {
return format((Map<String, ?>) beanOrMap);
} else if (BeanUtil.isReadableBean(beanOrMap.getClass())) {
final BeanDesc beanDesc = BeanUtil.getBeanDesc(beanOrMap.getClass());
return format(fieldName -> {
final Method getterMethod = beanDesc.getGetter(fieldName);
if (getterMethod == null) {
return null;
}
return LambdaUtil.buildGetter(getterMethod).apply(beanOrMap);
});
}
return format(fieldName -> BeanUtil.getFieldValue(beanOrMap, fieldName));
}
/**
* 使用 占位变量名称 Map 中查询值来 替换 占位符
*
* @param map map
* @return 格式化字符串
*/
public String format(final Map<String, ?> map) {
if (map == null) {
return getTemplate();
}
return format(map::get, map::containsKey);
}
/**
* 使用 占位变量名称 valueSupplier 中查询值来 替换 占位符
*
* @param valueSupplier 根据 占位变量名称 返回
* @return 格式化字符串
*/
public String format(final Function<String, ?> valueSupplier) {
if (valueSupplier == null) {
return getTemplate();
}
return formatBySegment(segment -> valueSupplier.apply(segment.getPlaceholder()));
}
/**
* 使用 占位变量名称 valueSupplier 中查询值来 替换 占位符
*
* @param valueSupplier 根据 占位变量名称 返回
* @param containsKey 占位变量名称 是否存在例如{@code map.containsKey(key)}
* @return 格式化字符串
*/
public String format(final Function<String, ?> valueSupplier, final Predicate<String> containsKey) {
if (valueSupplier == null || containsKey == null) {
return getTemplate();
}
return formatBySegment(segment -> {
final String placeholder = segment.getPlaceholder();
if (containsKey.test(placeholder)) {
return valueSupplier.apply(placeholder);
}
return formatMissingKey(segment);
});
}
// endregion
// endregion
// region 解析方法
// ################################################## 解析方法 ##################################################
// region 基于顺序的解析方法
// ############################## 基于顺序的解析方法 ##############################
/**
* 占位符位置的值 按顺序解析为 字符串数组
*
* @param str 待解析的字符串一般是格式化方法的返回值
* @return 字符串数组
*/
public String[] matchesSequenceToArray(final String str) {
return matchesSequence(str).toArray(new String[0]);
}
/**
* 占位符位置的值 按顺序解析为 字符串列表
*
* @param str 待解析的字符串一般是格式化方法的返回值
* @return 字符串列表
*/
@Override
public List<String> matchesSequence(final String str) {
return super.matchesSequence(str);
}
// endregion
// region 基于下标的解析方法
// ############################## 基于下标的解析方法 ##############################
/**
* 占位符位置的值 占位符下标值 解析为 字符串数组
*
* @param str 待解析的字符串一般是格式化方法的返回值
* @return 字符串数组
* @see #matchesIndexed(String, IntFunction)
*/
public String[] matchesIndexedToArray(final String str) {
return matchesIndexed(str, null).toArray(new String[0]);
}
/**
* 占位符位置的值 占位符下标值 解析为 字符串数组
*
* @param str 待解析的字符串一般是格式化方法的返回值
* @param missingIndexHandler 根据 下标 返回 默认值该参数可以为 {@code null}仅在 {@link Feature#MATCH_EMPTY_VALUE_TO_DEFAULT_VALUE} 策略时生效
* @return 字符串数组
* @see #matchesIndexed(String, IntFunction)
*/
public String[] matchesIndexedToArray(final String str, final IntFunction<String> missingIndexHandler) {
return matchesIndexed(str, missingIndexHandler).toArray(new String[0]);
}
/**
* 占位符位置的值 占位符下标值 解析为 字符串列表
*
* @param str 待解析的字符串一般是格式化方法的返回值
* @return 字符串列表
* @see #matchesIndexed(String, IntFunction)
*/
public List<String> matchesIndexed(final String str) {
return matchesIndexed(str, null);
}
/**
* 占位符位置的值 占位符下标值 解析为 字符串列表
*
* <p>例如模板中为 {@literal "This is between {1} and {2}"}格式化结果为 {@literal "This is between 666 and 999"},
* 由于其最大下标为 2, 则解析结果中固定有 3 个元素解析结果为 {@code [null, "666", "999"]}</p>
*
* @param str 待解析的字符串一般是格式化方法的返回值
* @param missingIndexHandler 根据 下标 返回 默认值该参数可以为 {@code null}仅在 {@link Feature#MATCH_EMPTY_VALUE_TO_DEFAULT_VALUE} 策略时生效
* @return 字符串列表
*/
public List<String> matchesIndexed(final String str, final IntFunction<String> missingIndexHandler) {
if (str == null || placeholderSegments.isEmpty() || !isMatches(str)) {
return ListUtil.zero();
}
final List<String> params = new ArrayList<>(this.indexedSegmentMaxIdx + 1);
// 用null值填充所有位置
ListUtil.setOrPadding(params, this.indexedSegmentMaxIdx, null, null);
matchesIndexed(str, params::set, missingIndexHandler);
return params;
}
/**
* 根据 下标 下标占位符位置的值自行提取结果值
*
* <p>例如模板中为 {@literal "This is between {1} and {2}"}格式化结果为 {@literal "This is between 666 and 999"},
* 由于其最大下标为 2, 则解析结果中固定有 3 个元素解析结果为 {@code [null, "666", "999"]}</p>
*
* @param str 待解析的字符串一般是格式化方法的返回值
* @param idxValueConsumer 处理 下标 下标占位符位置的值 的消费者例如<br>{@code (idx, value) -> list.set(idx, value)}
* @param missingIndexHandler 根据 下标 返回 默认值该参数可以为 {@code null}仅在 {@link Feature#MATCH_EMPTY_VALUE_TO_DEFAULT_VALUE} 策略时生效
*/
public void matchesIndexed(final String str, final BiConsumer<Integer, String> idxValueConsumer,
final IntFunction<String> missingIndexHandler) {
if (str == null || CollUtil.isEmpty(placeholderSegments) || !isMatches(str)) {
return;
}
if (missingIndexHandler == null) {
matchesByKey(str, (key, value) -> idxValueConsumer.accept(Integer.parseInt(key), value));
} else {
matchesByKey(str, (key, value) -> idxValueConsumer.accept(Integer.parseInt(key), value), true, segment -> {
if ((segment instanceof IndexedPlaceholderSegment)) {
return missingIndexHandler.apply(((IndexedPlaceholderSegment) segment).getIndex());
}
return getDefaultValue(segment);
});
}
}
// endregion
// region 基于键值的解析方法
// ############################## 基于键值的解析方法 ##############################
/**
* 根据 占位变量 对应位置解析值 构造 {@link Map}
*
* @param str 待解析的字符串一般是格式化方法的返回值
* @return {@link Map}
*/
public Map<String, String> matches(final String str) {
return matches(str, HashMap::new);
}
/**
* 根据 占位变量 对应位置解析值 构造 map 或者 bean 实例
*
* @param str 待解析的字符串一般是格式化方法的返回值
* @param beanOrMapSupplier 提供一个 bean 或者 map例如{@code HashMap::new}
* @param <T> 返回结果对象类型
* @return map 或者 bean 实例
*/
public <T> T matches(final String str, final Supplier<T> beanOrMapSupplier) {
Assert.notNull(beanOrMapSupplier, "beanOrMapSupplier cannot be null");
final T obj = beanOrMapSupplier.get();
if (str == null || obj == null || placeholderSegments.isEmpty() || !isMatches(str)) {
return obj;
}
if (obj instanceof Map) {
@SuppressWarnings("unchecked") final Map<String, String> map = (Map<String, String>) obj;
matchesByKey(str, map::put);
} else if (BeanUtil.isReadableBean(obj.getClass())) {
final BeanDesc beanDesc = BeanUtil.getBeanDesc(obj.getClass());
matchesByKey(str, (key, value) -> {
final Field field = beanDesc.getField(key);
final Method setterMethod = beanDesc.getSetter(key);
if (field == null || setterMethod == null) {
return;
}
final Object convert = Convert.convert(field.getType(), value);
LambdaUtil.buildSetter(setterMethod).accept(obj, convert);
});
}
return obj;
}
// endregion
// endregion
/**
* 创建 builder
*
* @param template 字符串模板不能为 {@code null}
* @return builder实例
*/
public static Builder builder(final String template) {
return new Builder(template);
}
public static class Builder extends AbstractBuilder<Builder, NamedPlaceholderStrTemplate> {
/**
* 占位符前缀默认为 {@link NamedPlaceholderStrTemplate#DEFAULT_PREFIX}
* <p>不能为空字符串</p>
*/
protected String prefix;
/**
* 占位符后缀默认为 {@link NamedPlaceholderStrTemplate#DEFAULT_SUFFIX}
* <p>不能为空字符串</p>
*/
protected String suffix;
protected Builder(final String template) {
super(template);
}
/**
* 设置 占位符前缀
*
* @param prefix 占位符前缀不能为空字符串
* @return builder
*/
public Builder prefix(final String prefix) {
this.prefix = prefix;
return this;
}
/**
* 设置 占位符后缀
*
* @param suffix 占位符后缀不能为空字符串
* @return builder
*/
public Builder suffix(final String suffix) {
this.suffix = suffix;
return this;
}
@Override
protected NamedPlaceholderStrTemplate buildInstance() {
if (this.prefix == null) {
this.prefix = DEFAULT_PREFIX;
}
if (this.suffix == null) {
this.suffix = DEFAULT_SUFFIX;
}
return new NamedPlaceholderStrTemplate(this.template, this.features, this.prefix, this.suffix, this.escape, this.defaultValue, this.defaultValueHandler);
}
@Override
protected Builder self() {
return this;
}
}
}

View File

@ -0,0 +1,215 @@
package org.dromara.hutool.core.text.placeholder.template;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.text.StrPool;
import org.dromara.hutool.core.text.placeholder.StrTemplate;
import org.dromara.hutool.core.text.placeholder.segment.LiteralSegment;
import org.dromara.hutool.core.text.placeholder.segment.SinglePlaceholderSegment;
import org.dromara.hutool.core.text.placeholder.segment.StrTemplateSegment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.UnaryOperator;
/**
* 单占位符字符串模板
* <p>例如"?", "{}", "$$$"</p>
*
* @author emptypoint
* @since 6.0.0
*/
public class SinglePlaceholderStrTemplate extends StrTemplate {
/**
* 默认的占位符
*/
public static final String DEFAULT_PLACEHOLDER = StrPool.EMPTY_JSON;
/**
* 占位符默认为: {@link StrPool#EMPTY_JSON}
*/
protected String placeholder;
protected SinglePlaceholderStrTemplate(final String template, final int features, final String placeholder, final char escape,
final String defaultValue, final UnaryOperator<String> defaultValueHandler) {
super(template, escape, defaultValue, defaultValueHandler, features);
Assert.notEmpty(placeholder);
this.placeholder = placeholder;
// 初始化Segment列表
afterInit();
}
@Override
protected List<StrTemplateSegment> parseSegments(final String template) {
final int placeholderLength = placeholder.length();
final int strPatternLength = template.length();
// 记录已经处理到的位置
int handledPosition = 0;
// 占位符所在位置
int delimIndex;
// 复用的占位符变量
final SinglePlaceholderSegment singlePlaceholderSegment = SinglePlaceholderSegment.newInstance(placeholder);
List<StrTemplateSegment> segments = null;
while (true) {
delimIndex = template.indexOf(placeholder, handledPosition);
if (delimIndex == -1) {
// 整个模板都不带占位符
if (handledPosition == 0) {
return Collections.singletonList(new LiteralSegment(template));
}
// 字符串模板剩余部分不再包含占位符
if (handledPosition < strPatternLength) {
segments.add(new LiteralSegment(template.substring(handledPosition, strPatternLength)));
}
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) {
// 转义符之前还有一个转义符形如"//{"占位符依旧有效
segments.add(new LiteralSegment(template.substring(handledPosition, delimIndex - 1)));
segments.add(singlePlaceholderSegment);
handledPosition = delimIndex + placeholderLength;
} else {
// 占位符被转义形如"/{"当前字符并不是一个真正的占位符而是普通字符串的一部分
segments.add(new LiteralSegment(
template.substring(handledPosition, delimIndex - 1) + placeholder.charAt(0)
));
handledPosition = delimIndex + 1;
}
} else {
// 正常占位符
segments.add(new LiteralSegment(template.substring(handledPosition, delimIndex)));
segments.add(singlePlaceholderSegment);
handledPosition = delimIndex + placeholderLength;
}
}
}
// region 格式化方法
// ################################################## 格式化方法 ##################################################
/**
* 按顺序使用 数组元素 替换 占位符
*
* @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 数组
* @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
// region 解析方法
// ################################################## 解析方法 ##################################################
/**
* 占位符位置的值 按顺序解析为 字符串数组
*
* @param str 待解析的字符串一般是格式化方法的返回值
* @return 参数值数组
*/
public String[] matchesToArray(final String str) {
return matches(str).toArray(new String[0]);
}
/**
* 占位符位置的值 按顺序解析为 字符串列表
*
* @param str 待解析的字符串一般是格式化方法的返回值
* @return 参数值列表
*/
public List<String> matches(final String str) {
return super.matchesSequence(str);
}
// endregion
/**
* 创建 builder
*
* @param template 字符串模板不能为 {@code null}
* @return builder实例
*/
public static Builder builder(final String template) {
return new Builder(template);
}
public static class Builder extends AbstractBuilder<Builder, SinglePlaceholderStrTemplate> {
/**
* 单占位符
* <p>例如"?""{}"</p>
* <p>默认为 {@link SinglePlaceholderStrTemplate#DEFAULT_PLACEHOLDER}</p>
*/
protected String placeholder;
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;
}
@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;
}
}
}

View File

@ -39,6 +39,13 @@ public class PlaceholderParserTest {
"i [a][m] a jvav programmer",
parser.apply(text)
);
text = "select * from #[tableName] where id = #[id]";
parser = new PlaceholderParser(str -> "?", "#[", "]");
Assertions.assertEquals(
"select * from ? where id = ?",
parser.apply(text)
);
}
}

View File

@ -0,0 +1,671 @@
package org.dromara.hutool.core.text;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.exceptions.UtilException;
import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.text.placeholder.StrTemplate;
import org.dromara.hutool.core.text.placeholder.template.NamedPlaceholderStrTemplate;
import org.dromara.hutool.core.text.placeholder.template.SinglePlaceholderStrTemplate;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
/**
* test for {@link StrTemplate}
*
* @author emptypoint
*/
public class StrTemplateTest {
@Test
public void singlePlaceholderFormatTest() {
// 默认值
testSinglePlaceholderFormat("{}", '\\');
// 自定义占位符
testSinglePlaceholderFormat("?", '\\');
// 自定义多个占位符
testSinglePlaceholderFormat("$$$", '\\');
// 自定义多个占位符和转义符
testSinglePlaceholderFormat("$$$", '/');
}
@Test
public void namedPlaceholderFormatSequenceTest() {
String text = "select * from #[tableName] where id = #[id]";
NamedPlaceholderStrTemplate strTemplate = StrTemplate.ofNamed(text).prefix("#[").suffix("]").build();
Assertions.assertEquals(
"select * from user where id = 1001",
strTemplate.formatSequence("user", 1001)
);
Assertions.assertEquals(
"select * from user where id = 1001",
strTemplate.formatArraySequence(new String[]{"user", "1001"})
);
Assertions.assertEquals(
"select * from 123 where id = 456",
strTemplate.formatArraySequence(new int[]{123, 456})
);
Assertions.assertEquals(
"select * from user where id = 1001",
strTemplate.formatSequence(ListUtil.of("user", 1001))
);
}
@Test
public void namedPlaceholderFormatIndexedTest() {
String text = "select * from #[1] where id = #[2]";
NamedPlaceholderStrTemplate strTemplate = StrTemplate.ofNamed(text).prefix("#[").suffix("]").build();
Assertions.assertEquals(
"select * from user where id = 1001",
strTemplate.formatIndexed("hutool", "user", 1001)
);
Assertions.assertEquals(
"select * from user where id = 1001",
strTemplate.formatArrayIndexed(new String[]{"hutool", "user", "1001"})
);
Assertions.assertEquals(
"select * from 123 where id = 456",
strTemplate.formatArrayIndexed(new int[]{666, 123, 456})
);
Assertions.assertEquals(
"select * from user where id = 1001",
strTemplate.formatIndexed(ListUtil.of("hutool", "user", 1001))
);
Assertions.assertEquals(
"select * from user where id = ?",
strTemplate.formatIndexed(ListUtil.of("hutool", "user"), idx -> "?")
);
}
@Test
public void namedPlaceholderFormatTest() {
String text = "select * from #[tableName] where id = #[id]";
NamedPlaceholderStrTemplate strTemplate = StrTemplate.ofNamed(text).prefix("#[").suffix("]").build();
Map<String, Object> map = MapUtil.<String, Object>builder().put("tableName", "user").put("id", 1001).build();
Assertions.assertEquals(
"select * from user where id = 1001",
strTemplate.format(map)
);
Assertions.assertEquals(
"select * from user where id = 1001",
strTemplate.format((Object) map)
);
FormatEntity entity = new FormatEntity().setTableName("user").setId(1001);
Assertions.assertEquals(
"select * from user where id = 1001",
strTemplate.format(entity)
);
entity = new FormatEntity().setTableName("user").setId(1001);
Assertions.assertEquals(
"select * from user where id = 1001",
strTemplate.format(entity)
);
}
@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public static class FormatEntity {
private String tableName;
private Integer id;
}
@Test
public void namedPlaceholderFormatDefaultValueTest() {
String text = "i {a}{m} a {jvav} programmer";
NamedPlaceholderStrTemplate.Builder strTemplate = StrTemplate.ofNamed(text)
.addFeatures(StrTemplate.Feature.FORMAT_MISSING_KEY_PRINT_DEFAULT_VALUE);
Assertions.assertEquals(
"i a programmer",
strTemplate.defaultValue(s -> "")
.build()
.formatSequence()
);
Assertions.assertEquals(
"i ?? a ? programmer",
strTemplate.defaultValue(s -> "?")
.build()
.formatSequence()
);
Assertions.assertEquals(
"i $$$$$$ a $$$ programmer",
strTemplate.defaultValue(s -> "$$$")
.build()
.formatSequence()
);
text = "select * from #[tableName] where id = #[id]";
strTemplate = StrTemplate.ofNamed(text).prefix("#[").suffix("]");
Assertions.assertEquals(
"select * from user where id = 1001",
strTemplate.defaultValue(s -> "?")
.build()
.formatSequence("user", 1001)
);
Assertions.assertEquals(
"select * from user where id = ?",
strTemplate.defaultValue(s -> "?")
.addFeatures(StrTemplate.Feature.FORMAT_MISSING_KEY_PRINT_DEFAULT_VALUE)
.build()
.formatSequence("user")
);
Assertions.assertEquals(
"select * from user where id = ?",
strTemplate.defaultValue(s -> "?")
.build()
.formatArraySequence(new String[]{"user"})
);
Assertions.assertEquals(
"select * from user where id = ?",
strTemplate.defaultValue(s -> "?")
.build()
.formatSequence(ListUtil.of("user"))
);
text = "select * from #[1] where id = #[2]";
strTemplate = StrTemplate.ofNamed(text).prefix("#[").suffix("]").addFeatures(StrTemplate.Feature.FORMAT_MISSING_KEY_PRINT_DEFAULT_VALUE);
Assertions.assertEquals(
"select * from user where id = ?",
strTemplate.defaultValue(s -> "?")
.build()
.formatIndexed("hutool", "user")
);
Assertions.assertEquals(
"select * from user where id = ?",
strTemplate.defaultValue(s -> "?")
.build()
.formatArrayIndexed(new String[]{"hutool", "user"})
);
Assertions.assertEquals(
"select * from user where id = ?",
strTemplate.defaultValue(s -> "?")
.build()
.formatIndexed(ListUtil.of("hutool", "user"))
);
}
@Test
public void namedPlaceholderEscapeTest() {
Map<String, Object> map = MapUtil.<String, Object>builder().put("tableName", "user").put("id", 1001).build();
// 转义符
String text = "select * from \\#[tableName] where id = \\#[id]";
NamedPlaceholderStrTemplate strTemplate = StrTemplate.ofNamed(text).prefix("#[").suffix("]").build();
Assertions.assertEquals(
"select * from #[tableName] where id = #[id]",
strTemplate.format(map)
);
text = "select * from \\#[tableName] where id = #[id\\]]";
strTemplate = StrTemplate.ofNamed(text).prefix("#[").suffix("]").build();
Assertions.assertEquals(
"select * from #[tableName] where id = 1001",
strTemplate.format(MapUtil.<String, Object>builder().put("tableName", "user").put("id]", 1001).build())
);
// 转义 转义符
text = "select * from \\\\#[tableName] where id = #[id]";
strTemplate = StrTemplate.ofNamed(text).prefix("#[").suffix("]").build();
Assertions.assertEquals(
"select * from \\user where id = 1001",
strTemplate.format(map)
);
text = "select * from \\\\#[tableName] where id = \\\\#[id]";
strTemplate = StrTemplate.ofNamed(text).prefix("#[").suffix("]").build();
Assertions.assertEquals(
"select * from \\user where id = \\1001",
strTemplate.format(map)
);
text = "select * from \\\\#[tableName] where id = #[id\\\\]";
strTemplate = StrTemplate.ofNamed(text).prefix("#[").suffix("]").build();
Assertions.assertEquals(
"select * from \\user where id = 1001",
strTemplate.format(MapUtil.<String, Object>builder().put("tableName", "user").put("id\\", 1001).build())
);
text = "select * from #[tableName\\\\] where id = #[id\\\\]";
strTemplate = StrTemplate.ofNamed(text).prefix("#[").suffix("]").build();
Assertions.assertEquals(
"select * from user where id = 1001",
strTemplate.format(MapUtil.<String, Object>builder().put("tableName\\", "user").put("id\\", 1001).build())
);
// 自定义 转义符
text = "select * from /#[tableName] where id = /#[id]";
strTemplate = StrTemplate.ofNamed(text).prefix("#[").suffix("]").escape('/').build();
Assertions.assertEquals(
"select * from #[tableName] where id = #[id]",
strTemplate.format(map)
);
text = "select * from //#[tableName] where id = //#[id]";
strTemplate = StrTemplate.ofNamed(text).prefix("#[").suffix("]").escape('/').build();
Assertions.assertEquals(
"select * from /user where id = /1001",
strTemplate.format(map)
);
text = "select * from /#[tableName] where id = #[id/]]";
strTemplate = StrTemplate.ofNamed(text).prefix("#[").suffix("]").escape('/').build();
Assertions.assertEquals(
"select * from #[tableName] where id = 1001",
strTemplate.format(MapUtil.<String, Object>builder().put("tableName", "user").put("id]", 1001).build())
);
}
private void testSinglePlaceholderFormat(String placeholder, char escape) {
// 通常使用
String commonTemplate = "this is " + placeholder + " for " + placeholder;
SinglePlaceholderStrTemplate template = StrTemplate.of(commonTemplate)
.placeholder(placeholder)
.escape(escape)
.build();
// 普通使用
Assertions.assertEquals("this is a for 666",
template.format("a", 666)
);
Assertions.assertEquals("this is a for 666",
template.format(ListUtil.of("a", 666))
);
Assertions.assertEquals("this is 123 for 456",
template.formatArray(new int[]{123, 456})
);
Assertions.assertEquals("this is 123 for 456",
template.formatArray(new Integer[]{123, 456})
);
// 转义占位符
Assertions.assertEquals("this is " + placeholder + " for a",
StrTemplate.of("this is " + escape + placeholder + " for " + placeholder)
.placeholder(placeholder)
.escape(escape)
.build()
.format("a", "b")
);
// 转义"转义符"
Assertions.assertEquals("this is " + escape + "a for b",
StrTemplate.of("this is " + escape + escape + placeholder + " for " + placeholder)
.placeholder(placeholder)
.escape(escape)
.build()
.format("a", "b")
);
// 填充null值
Assertions.assertEquals("this is " + null + " for b",
template.format(null, "b")
);
Assertions.assertEquals("this is a for null",
template.format("a", null)
);
// 序列化参数 小于 占位符数量
Assertions.assertEquals("this is a for " + placeholder,
template.format("a")
);
SinglePlaceholderStrTemplate.Builder builder = StrTemplate.of(commonTemplate)
.placeholder(placeholder)
.escape(escape)
.addFeatures(StrTemplate.Feature.FORMAT_MISSING_KEY_PRINT_DEFAULT_VALUE);
Assertions.assertEquals("this is a for ",
builder.defaultValue("")
.build()
.format("a")
);
Assertions.assertEquals("this is a for 666",
builder.defaultValue("666")
.build()
.format("a")
);
builder = StrTemplate.of(commonTemplate)
.placeholder(placeholder)
.escape(escape)
.addFeatures(StrTemplate.Feature.FORMAT_MISSING_KEY_PRINT_DEFAULT_VALUE);
Assertions.assertEquals("this is a for ",
builder.defaultValue(s -> "")
.build()
.format("a")
);
Assertions.assertEquals("this is a for 666",
builder.defaultValue(s -> "666")
.build()
.format("a")
);
// 序列化参数 超过 占位符数量
Assertions.assertEquals("this is a for b",
builder.build()
.format("a", "b", "c")
);
// 残缺的占位符
if (placeholder.length() >= 2) {
Assertions.assertEquals("this is " + placeholder.charAt(0) + " for a",
StrTemplate.of("this is " + placeholder.charAt(0) + " for " + placeholder)
.placeholder(placeholder)
.escape(escape)
.build()
.format("a")
);
Assertions.assertEquals("this is " + placeholder.charAt(1) + " for a",
StrTemplate.of("this is " + placeholder.charAt(1) + " for " + placeholder)
.placeholder(placeholder)
.escape(escape)
.build()
.format("a")
);
Assertions.assertEquals("this is " + placeholder.charAt(0) + ' ' + placeholder.charAt(1) + " for a",
StrTemplate.of("this is " + placeholder.charAt(0) + ' ' + placeholder.charAt(1) + " for " + placeholder)
.placeholder(placeholder)
.escape(escape)
.build()
.format("a")
);
}
}
@Test
public void isMatchesTest() {
SinglePlaceholderStrTemplate strTemplate = StrTemplate.of("this is {} for {}").build();
Assertions.assertTrue(strTemplate.isMatches("this is a for b"));
Assertions.assertTrue(strTemplate.isMatches("this is aaa for 666"));
Assertions.assertTrue(strTemplate.isMatches("this is a for b "));
Assertions.assertTrue(strTemplate.isMatches("this is a x for b"));
Assertions.assertTrue(strTemplate.isMatches("this is {} for {}"));
Assertions.assertTrue(strTemplate.isMatches("this is { } for {}"));
Assertions.assertTrue(strTemplate.isMatches("this is { } for { }"));
Assertions.assertTrue(strTemplate.isMatches("this is a for b"));
Assertions.assertTrue(strTemplate.isMatches("this is a for b"));
Assertions.assertTrue(strTemplate.isMatches("this is a for b"));
Assertions.assertTrue(strTemplate.isMatches("this is a for "));
Assertions.assertTrue(strTemplate.isMatches("this is for b"));
Assertions.assertTrue(strTemplate.isMatches("this is for "));
Assertions.assertFalse(strTemplate.isMatches(""));
Assertions.assertFalse(strTemplate.isMatches(" "));
Assertions.assertFalse(strTemplate.isMatches(" \r\n \n "));
Assertions.assertFalse(strTemplate.isMatches(" this is a for b"));
Assertions.assertFalse(strTemplate.isMatches("this is a forb"));
Assertions.assertFalse(strTemplate.isMatches("this is a for b"));
Assertions.assertFalse(strTemplate.isMatches("this are a for b"));
Assertions.assertFalse(strTemplate.isMatches("that is a for b"));
// 占位符在最前和最后
strTemplate = StrTemplate.of("{}, this is for {}").build();
Assertions.assertTrue(strTemplate.isMatches("Cleveland, this is for you"));
Assertions.assertTrue(strTemplate.isMatches("Cleveland, this is for you "));
Assertions.assertTrue(strTemplate.isMatches(" Cleveland, this is for you"));
Assertions.assertTrue(strTemplate.isMatches("Cleveland, this is for you "));
Assertions.assertTrue(strTemplate.isMatches("Cleveland, this is for you ?"));
Assertions.assertTrue(strTemplate.isMatches("Cleveland , this is for you"));
Assertions.assertTrue(strTemplate.isMatches(":)Cleveland, this is for you"));
Assertions.assertFalse(strTemplate.isMatches("Cleveland, this is for you"));
Assertions.assertFalse(strTemplate.isMatches("Cleveland, this is for you"));
Assertions.assertFalse(strTemplate.isMatches("Cleveland, this is for you"));
Assertions.assertFalse(strTemplate.isMatches("Cleveland, this is four you"));
Assertions.assertFalse(strTemplate.isMatches("Cleveland, this are for you"));
Assertions.assertFalse(strTemplate.isMatches("Cleveland, that is for you"));
}
@Test
public void singlePlaceholderMatchesTest() {
SinglePlaceholderStrTemplate strTemplate = StrTemplate.of("this is {} for {}").build();
Assertions.assertEquals(ListUtil.of("a", "b"), strTemplate.matches("this is a for b"));
Assertions.assertEquals(ListUtil.of("aaa", "666"), strTemplate.matches("this is aaa for 666"));
Assertions.assertEquals(ListUtil.of("a", "b "), strTemplate.matches("this is a for b "));
Assertions.assertEquals(ListUtil.of("a x", "b"), strTemplate.matches("this is a x for b"));
Assertions.assertEquals(ListUtil.of("{}", "{}"), strTemplate.matches("this is {} for {}"));
Assertions.assertEquals(ListUtil.of("{ }", "{}"), strTemplate.matches("this is { } for {}"));
Assertions.assertEquals(ListUtil.of("{ }", "{ }"), strTemplate.matches("this is { } for { }"));
Assertions.assertEquals(ListUtil.of(" a", "b"), strTemplate.matches("this is a for b"));
Assertions.assertEquals(ListUtil.of(" a", " b"), strTemplate.matches("this is a for b"));
Assertions.assertEquals(ListUtil.of("a ", "b"), strTemplate.matches("this is a for b"));
Assertions.assertEquals(ListUtil.of("a", null), strTemplate.matches("this is a for "));
Assertions.assertEquals(ListUtil.of(null, "b"), strTemplate.matches("this is for b"));
Assertions.assertEquals(ListUtil.of(null, null), strTemplate.matches("this is for "));
final List<Object> emptyList = Collections.emptyList();
Assertions.assertEquals(emptyList, strTemplate.matches(""));
Assertions.assertEquals(emptyList, strTemplate.matches(" "));
Assertions.assertEquals(emptyList, strTemplate.matches(" \r\n \n "));
Assertions.assertEquals(emptyList, strTemplate.matches(" this is a for b"));
Assertions.assertEquals(emptyList, strTemplate.matches("this is a forb"));
Assertions.assertEquals(emptyList, strTemplate.matches("this is a for b"));
Assertions.assertEquals(emptyList, strTemplate.matches("this are a for b"));
Assertions.assertEquals(emptyList, strTemplate.matches("that is a for b"));
strTemplate = StrTemplate.of("{}, this is for {}").build();
Assertions.assertEquals(ListUtil.of("Cleveland", "you"), strTemplate.matches("Cleveland, this is for you"));
Assertions.assertEquals(ListUtil.of(" Cleveland", "you"), strTemplate.matches(" Cleveland, this is for you"));
Assertions.assertEquals(ListUtil.of("Cleveland ", "you"), strTemplate.matches("Cleveland , this is for you"));
Assertions.assertEquals(ListUtil.of("Cleveland", "you "), strTemplate.matches("Cleveland, this is for you "));
Assertions.assertEquals(ListUtil.of("Cleveland", " you"), strTemplate.matches("Cleveland, this is for you"));
Assertions.assertEquals(ListUtil.of("Cleveland", " you "), strTemplate.matches("Cleveland, this is for you "));
Assertions.assertEquals(ListUtil.of("Cleveland", "you ?"), strTemplate.matches("Cleveland, this is for you ?"));
Assertions.assertEquals(ListUtil.of(":)Cleveland", "you:("), strTemplate.matches(":)Cleveland, this is for you:("));
Assertions.assertEquals(emptyList, strTemplate.matches("Cleveland, this is for you"));
Assertions.assertEquals(emptyList, strTemplate.matches("Cleveland, this is for you"));
Assertions.assertEquals(emptyList, strTemplate.matches("Cleveland, this is for you"));
Assertions.assertEquals(emptyList, strTemplate.matches("Cleveland, this is four you"));
Assertions.assertEquals(emptyList, strTemplate.matches("Cleveland, this are for you"));
Assertions.assertEquals(emptyList, strTemplate.matches("Cleveland, that is for you"));
}
@Test
public void namedPlaceholderMatchesSequenceTest() {
NamedPlaceholderStrTemplate strTemplate = StrTemplate.ofNamed("this is {a} for {b}").build();
Assertions.assertEquals(ListUtil.of("a", "b"), strTemplate.matchesSequence("this is a for b"));
Assertions.assertEquals(ListUtil.of("aaa", "666"), strTemplate.matchesSequence("this is aaa for 666"));
Assertions.assertEquals(ListUtil.of("a", "b "), strTemplate.matchesSequence("this is a for b "));
Assertions.assertEquals(ListUtil.of("a x", "b"), strTemplate.matchesSequence("this is a x for b"));
Assertions.assertEquals(ListUtil.of("{}", "{}"), strTemplate.matchesSequence("this is {} for {}"));
Assertions.assertEquals(ListUtil.of("{ }", "{}"), strTemplate.matchesSequence("this is { } for {}"));
Assertions.assertEquals(ListUtil.of("{ }", "{ }"), strTemplate.matchesSequence("this is { } for { }"));
Assertions.assertEquals(ListUtil.of(" a", "b"), strTemplate.matchesSequence("this is a for b"));
Assertions.assertEquals(ListUtil.of(" a", " b"), strTemplate.matchesSequence("this is a for b"));
Assertions.assertEquals(ListUtil.of("a ", "b"), strTemplate.matchesSequence("this is a for b"));
Assertions.assertEquals(ListUtil.of("a", null), strTemplate.matchesSequence("this is a for "));
Assertions.assertEquals(ListUtil.of(null, "b"), strTemplate.matchesSequence("this is for b"));
Assertions.assertEquals(ListUtil.of(null, null), strTemplate.matchesSequence("this is for "));
final List<Object> emptyList = Collections.emptyList();
Assertions.assertEquals(emptyList, strTemplate.matchesSequence(""));
Assertions.assertEquals(emptyList, strTemplate.matchesSequence(" "));
Assertions.assertEquals(emptyList, strTemplate.matchesSequence(" \r\n \n "));
Assertions.assertEquals(emptyList, strTemplate.matchesSequence(" this is a for b"));
Assertions.assertEquals(emptyList, strTemplate.matchesSequence("this is a forb"));
Assertions.assertEquals(emptyList, strTemplate.matchesSequence("this is a for b"));
Assertions.assertEquals(emptyList, strTemplate.matchesSequence("this are a for b"));
Assertions.assertEquals(emptyList, strTemplate.matchesSequence("that is a for b"));
strTemplate = StrTemplate.ofNamed("{a}, this is for {b}").build();
Assertions.assertEquals(ListUtil.of("Cleveland", "you"), strTemplate.matchesSequence("Cleveland, this is for you"));
Assertions.assertEquals(ListUtil.of(" Cleveland", "you"), strTemplate.matchesSequence(" Cleveland, this is for you"));
Assertions.assertEquals(ListUtil.of("Cleveland ", "you"), strTemplate.matchesSequence("Cleveland , this is for you"));
Assertions.assertEquals(ListUtil.of("Cleveland", "you "), strTemplate.matchesSequence("Cleveland, this is for you "));
Assertions.assertEquals(ListUtil.of("Cleveland", " you"), strTemplate.matchesSequence("Cleveland, this is for you"));
Assertions.assertEquals(ListUtil.of("Cleveland", " you "), strTemplate.matchesSequence("Cleveland, this is for you "));
Assertions.assertEquals(ListUtil.of("Cleveland", "you ?"), strTemplate.matchesSequence("Cleveland, this is for you ?"));
Assertions.assertEquals(ListUtil.of(":)Cleveland", "you:("), strTemplate.matchesSequence(":)Cleveland, this is for you:("));
Assertions.assertEquals(emptyList, strTemplate.matchesSequence("Cleveland, this is for you"));
Assertions.assertEquals(emptyList, strTemplate.matchesSequence("Cleveland, this is for you"));
Assertions.assertEquals(emptyList, strTemplate.matchesSequence("Cleveland, this is for you"));
Assertions.assertEquals(emptyList, strTemplate.matchesSequence("Cleveland, this is four you"));
Assertions.assertEquals(emptyList, strTemplate.matchesSequence("Cleveland, this are for you"));
Assertions.assertEquals(emptyList, strTemplate.matchesSequence("Cleveland, that is for you"));
}
@Test
public void namedPlaceholderMatchesIndexedTest() {
NamedPlaceholderStrTemplate strTemplate = StrTemplate.ofNamed("this is {2} for {1}").build();
Assertions.assertEquals(ListUtil.of(null, "b", "a"), strTemplate.matchesIndexed("this is a for b", null));
Assertions.assertEquals(ListUtil.of(null, "666", "aaa"), strTemplate.matchesIndexed("this is aaa for 666", null));
Assertions.assertEquals(ListUtil.of(null, "b", null), strTemplate.matchesIndexed("this is for b", null));
Assertions.assertEquals(ListUtil.of(null, null, "aaa"), strTemplate.matchesIndexed("this is aaa for ", null));
Assertions.assertEquals(ListUtil.of(null, null, null), strTemplate.matchesIndexed("this is for ", null));
strTemplate = StrTemplate.ofNamed("this is {2} for {1}")
.addFeatures(StrTemplate.Feature.MATCH_EMPTY_VALUE_TO_DEFAULT_VALUE)
.build();
Assertions.assertEquals(ListUtil.of(null, "b", "a"), strTemplate.matchesIndexed("this is a for b", idx -> "?"));
Assertions.assertEquals(ListUtil.of(null, "666", "aaa"), strTemplate.matchesIndexed("this is aaa for 666", idx -> "?"));
Assertions.assertEquals(ListUtil.of(null, "b", "?"), strTemplate.matchesIndexed("this is for b", idx -> "?"));
Assertions.assertEquals(ListUtil.of(null, "?", "aaa"), strTemplate.matchesIndexed("this is aaa for ", idx -> "?"));
Assertions.assertEquals(ListUtil.of(null, "?", "?"), strTemplate.matchesIndexed("this is for ", idx -> "?"));
strTemplate = StrTemplate.ofNamed("this is {2} for {1}").build();
final List<Object> emptyList = Collections.emptyList();
Assertions.assertEquals(emptyList, strTemplate.matchesIndexed("", null));
Assertions.assertEquals(emptyList, strTemplate.matchesIndexed(" ", null));
Assertions.assertEquals(emptyList, strTemplate.matchesIndexed(" \r\n \n ", null));
Assertions.assertEquals(emptyList, strTemplate.matchesIndexed(" this is a for b", null));
Assertions.assertEquals(emptyList, strTemplate.matchesIndexed("this is a forb", null));
Assertions.assertEquals(emptyList, strTemplate.matchesIndexed("this is a for b", null));
Assertions.assertEquals(emptyList, strTemplate.matchesIndexed("this are a for b", null));
Assertions.assertEquals(emptyList, strTemplate.matchesIndexed("that is a for b", null));
Assertions.assertEquals(emptyList, strTemplate.matchesIndexed(" this is a for b", idx -> "?"));
Assertions.assertEquals(emptyList, strTemplate.matchesIndexed("this is a forb", idx -> "?"));
Assertions.assertEquals(emptyList, strTemplate.matchesIndexed("this is a for b", idx -> "?"));
Assertions.assertEquals(emptyList, strTemplate.matchesIndexed("this are a for b", idx -> "?"));
Assertions.assertEquals(emptyList, strTemplate.matchesIndexed("that is a for b", idx -> "?"));
}
@Test
public void namedPlaceholderMatchesTest() {
NamedPlaceholderStrTemplate strTemplate = StrTemplate.ofNamed("this is {tableName} for {id}").build();
Supplier<Map<String, String>> mapSupplier = HashMap::new;
Assertions.assertEquals(MapUtil.builder("tableName", "aaa").put("id", "666").build(), strTemplate.matches("this is aaa for 666", mapSupplier));
Assertions.assertEquals(MapUtil.builder("tableName", null).put("id", "666").build(), strTemplate.matches("this is for 666", mapSupplier));
Assertions.assertEquals(MapUtil.builder("tableName", "aaa").put("id", null).build(), strTemplate.matches("this is aaa for ", mapSupplier));
Assertions.assertEquals(MapUtil.builder("tableName", null).put("id", null).build(), strTemplate.matches("this is for ", mapSupplier));
Assertions.assertEquals(Collections.emptyMap(), strTemplate.matches("", mapSupplier));
Supplier<FormatEntity> beanSupplier = FormatEntity::new;
Assertions.assertEquals(new FormatEntity("aaa", 666), strTemplate.matches("this is aaa for 666", beanSupplier));
Assertions.assertEquals(new FormatEntity(null, 666), strTemplate.matches("this is for 666", beanSupplier));
Assertions.assertEquals(new FormatEntity("aaa", null), strTemplate.matches("this is aaa for ", beanSupplier));
Assertions.assertEquals(new FormatEntity(null, null), strTemplate.matches("this is for ", beanSupplier));
Assertions.assertEquals(new FormatEntity(), strTemplate.matches("", beanSupplier));
}
@Test
public void featureTest() {
// 通常使用
String commonTemplate = "this is {tableName} for {id}";
// ##### 使用新的策略 替换 默认策略 #####
NamedPlaceholderStrTemplate template = StrTemplate.ofNamed(commonTemplate)
.features(StrTemplate.Feature.FORMAT_MISSING_KEY_PRINT_EMPTY, StrTemplate.Feature.MATCH_IGNORE_EMPTY_VALUE)
.build();
testFeature(template);
// 添加新策略互斥的策略则算作设置新策略旧策略失效
template = StrTemplate.ofNamed(commonTemplate)
.addFeatures(StrTemplate.Feature.FORMAT_MISSING_KEY_PRINT_EMPTY, StrTemplate.Feature.MATCH_IGNORE_DEFAULT_VALUE, StrTemplate.Feature.MATCH_IGNORE_EMPTY_VALUE)
.build();
testFeature(template);
// ##### 删除策略 #####
NamedPlaceholderStrTemplate template2 = StrTemplate.ofNamed(commonTemplate)
.removeFeatures(StrTemplate.Feature.FORMAT_MISSING_KEY_PRINT_WHOLE_PLACEHOLDER)
.build();
Assertions.assertEquals("this is aaa for 666", template2.format(MapUtil.builder("tableName", "aaa").put("id", "666").build()));
Assertions.assertThrows(UtilException.class, () -> template2.format(MapUtil.builder("tableName", "aaa").build()));
// ##### 空字符串策略 #####
template = StrTemplate.ofNamed(commonTemplate)
// 解析时空字符串 转为 null值
.addFeatures(StrTemplate.Feature.FORMAT_MISSING_KEY_PRINT_EMPTY, StrTemplate.Feature.MATCH_EMPTY_VALUE_TO_NULL)
.build();
Assertions.assertEquals("this is aaa for ", template.format(MapUtil.builder("tableName", "aaa").build()));
Assertions.assertEquals(MapUtil.builder("tableName", "aaa").put("id", null).build(), template.matches("this is aaa for null"));
// 解析时空字符串 转为 默认值
template = StrTemplate.ofNamed(commonTemplate)
.addFeatures(StrTemplate.Feature.FORMAT_MISSING_KEY_PRINT_EMPTY, StrTemplate.Feature.MATCH_EMPTY_VALUE_TO_DEFAULT_VALUE)
.defaultValue("?")
.build();
Assertions.assertEquals("this is aaa for ", template.format(MapUtil.builder("tableName", "aaa").build()));
Assertions.assertEquals(MapUtil.builder("tableName", "aaa").put("id", "?").build(), template.matches("this is aaa for "));
// 默认值 空字符串解析时空字符串 转为 默认值
template = StrTemplate.ofNamed(commonTemplate)
.addFeatures(StrTemplate.Feature.FORMAT_MISSING_KEY_PRINT_EMPTY, StrTemplate.Feature.MATCH_EMPTY_VALUE_TO_DEFAULT_VALUE)
.defaultValue("")
.build();
Assertions.assertEquals("this is aaa for ", template.format(MapUtil.builder("tableName", "aaa").build()));
Assertions.assertEquals(MapUtil.builder("tableName", "aaa").put("id", "").build(), template.matches("this is aaa for "));
// ##### null值策略 #####
// 解析时null字符串 转为 null值
template = StrTemplate.ofNamed(commonTemplate)
.addFeatures(StrTemplate.Feature.FORMAT_MISSING_KEY_PRINT_NULL, StrTemplate.Feature.MATCH_NULL_STR_TO_NULL)
.build();
Assertions.assertEquals("this is aaa for null", template.format(MapUtil.builder("tableName", "aaa").build()));
Assertions.assertEquals(MapUtil.builder("tableName", "aaa").put("id", null).build(), template.matches("this is aaa for null"));
// 格式化时null值 转为 默认值 解析时null字符串 转为 null值
template = StrTemplate.ofNamed(commonTemplate)
.addFeatures(StrTemplate.Feature.FORMAT_MISSING_KEY_PRINT_DEFAULT_VALUE, StrTemplate.Feature.MATCH_NULL_STR_TO_NULL)
.defaultValue("null")
.build();
Assertions.assertEquals("this is aaa for null", template.format(MapUtil.builder("tableName", "aaa").build()));
Assertions.assertEquals(MapUtil.builder("tableName", "aaa").put("id", null).build(), template.matches("this is aaa for null"));
// 解析时忽略 null字符串
template = StrTemplate.ofNamed(commonTemplate)
.addFeatures(StrTemplate.Feature.FORMAT_MISSING_KEY_PRINT_NULL, StrTemplate.Feature.MATCH_IGNORE_NULL_STR)
.build();
Assertions.assertEquals("this is aaa for null", template.format(MapUtil.builder("tableName", "aaa").build()));
Assertions.assertEquals(MapUtil.builder("tableName", "aaa").build(), template.matches("this is aaa for null"));
// 格式化时null值 转为 默认值 解析时忽略 null字符串
template = StrTemplate.ofNamed(commonTemplate)
.addFeatures(StrTemplate.Feature.FORMAT_MISSING_KEY_PRINT_DEFAULT_VALUE, StrTemplate.Feature.MATCH_IGNORE_NULL_STR)
.defaultValue("null")
.build();
Assertions.assertEquals("this is aaa for null", template.format(MapUtil.builder("tableName", "aaa").build()));
Assertions.assertEquals(MapUtil.builder("tableName", "aaa").build(), template.matches("this is aaa for null"));
// 解析时null字符串 依然为 "null"字符串
template = StrTemplate.ofNamed(commonTemplate)
.addFeatures(StrTemplate.Feature.FORMAT_MISSING_KEY_PRINT_NULL, StrTemplate.Feature.MATCH_KEEP_NULL_STR)
.build();
Assertions.assertEquals("this is aaa for null", template.format(MapUtil.builder("tableName", "aaa").build()));
Assertions.assertEquals(MapUtil.builder("tableName", "aaa").put("id", "null").build(), template.matches("this is aaa for null"));
}
private void testFeature(NamedPlaceholderStrTemplate template) {
// 格式化
Assertions.assertEquals("this is aaa for 666", template.format(MapUtil.builder("tableName", "aaa").put("id", "666").build()));
Assertions.assertEquals("this is aaa for ", template.format(MapUtil.builder("tableName", "aaa").build()));
Assertions.assertEquals("this is for 666", template.format(MapUtil.builder("id", "666").build()));
Assertions.assertEquals("this is for ", template.format(MapUtil.builder().build()));
// 解析
Assertions.assertEquals(MapUtil.builder("tableName", "aaa").put("id", "666").build(), template.matches("this is aaa for 666"));
Assertions.assertEquals(MapUtil.builder("tableName", "aaa").build(), template.matches("this is aaa for "));
Assertions.assertEquals(MapUtil.builder("id", "666").build(), template.matches("this is for 666"));
Assertions.assertEquals(MapUtil.builder().build(), template.matches("this is for "));
}
}