diff --git a/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/template/support/DefaultStringTemplate.java b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/template/support/DefaultStringTemplate.java index 168a84eb4..f4c0764f3 100644 --- a/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/template/support/DefaultStringTemplate.java +++ b/framework/fel/java/fel-core/src/main/java/modelengine/fel/core/template/support/DefaultStringTemplate.java @@ -1,14 +1,15 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fel.core.template.support; import modelengine.fel.core.template.StringTemplate; import modelengine.fitframework.inspection.Validation; import modelengine.fitframework.merge.ConflictResolutionPolicy; +import modelengine.fitframework.parameterization.ParameterizationMode; import modelengine.fitframework.parameterization.ParameterizedString; import modelengine.fitframework.parameterization.ParameterizedStringResolver; import modelengine.fitframework.parameterization.ResolvedParameter; @@ -28,7 +29,7 @@ */ public class DefaultStringTemplate implements StringTemplate { private static final ParameterizedStringResolver FORMATTER = - ParameterizedStringResolver.create("{{", "}}", '\\', false); + ParameterizedStringResolver.create("{{", "}}", '\\', ParameterizationMode.LENIENT_STRICT_PARAMETERS); private final ParameterizedString parameterizedString; diff --git a/framework/fel/java/fel-core/src/test/java/modelengine/fel/core/template/BulkStringTemplateTest.java b/framework/fel/java/fel-core/src/test/java/modelengine/fel/core/template/BulkStringTemplateTest.java index e8447d240..9a7d33b42 100644 --- a/framework/fel/java/fel-core/src/test/java/modelengine/fel/core/template/BulkStringTemplateTest.java +++ b/framework/fel/java/fel-core/src/test/java/modelengine/fel/core/template/BulkStringTemplateTest.java @@ -1,15 +1,16 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fel.core.template; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.catchThrowableOfType; import modelengine.fel.core.template.support.DefaultBulkStringTemplate; +import modelengine.fitframework.parameterization.StringFormatException; import modelengine.fitframework.util.MapBuilder; import org.junit.jupiter.api.DisplayName; @@ -80,7 +81,8 @@ void giveMissValueThenThrowException() { List> values = new ArrayList<>(); values.add(MapBuilder.get().put("adjective", "funny").build()); values.add(MapBuilder.get().put("content", "rabbits").build()); - assertThatThrownBy(() -> new DefaultBulkStringTemplate(template, "\n").render(values)).isInstanceOf( - IllegalArgumentException.class); + StringFormatException cause = catchThrowableOfType(StringFormatException.class, + () -> new DefaultBulkStringTemplate(template, "\n").render(values)); + assertThat(cause).hasMessage("Required parameters are missing."); } } diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/ClassAccessException.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/ClassAccessException.java index cd4c68705..3c36dc016 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/ClassAccessException.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/ClassAccessException.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.exception; @@ -10,7 +10,7 @@ * 当访问类型发生异常时引发的异常。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public class ClassAccessException extends RuntimeException { /** diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/DateFormatException.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/DateFormatException.java index e477049f8..e03e7bc6d 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/DateFormatException.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/DateFormatException.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.exception; @@ -10,7 +10,7 @@ * 当发现日期格式错误时引发的异常。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public class DateFormatException extends IllegalArgumentException { /** diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/FieldVisitException.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/FieldVisitException.java index 570ce953c..b39aef135 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/FieldVisitException.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/FieldVisitException.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.exception; @@ -10,7 +10,7 @@ * 当访问字段失败时引发的异常。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public class FieldVisitException extends RuntimeException { /** diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/MethodInvocationException.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/MethodInvocationException.java index 7a5c92da9..b7dc09918 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/MethodInvocationException.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/MethodInvocationException.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.exception; @@ -10,7 +10,7 @@ * 当调用方法失败时引发的异常。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public class MethodInvocationException extends RuntimeException { /** diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/MethodNotFoundException.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/MethodNotFoundException.java index 0d87778de..12b9fbc7b 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/MethodNotFoundException.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/exception/MethodNotFoundException.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.exception; @@ -10,7 +10,7 @@ * 当没有指定的方法时引发的异常。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public class MethodNotFoundException extends RuntimeException { /** diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/ParameterizationMode.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/ParameterizationMode.java new file mode 100644 index 000000000..56e5f7fc6 --- /dev/null +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/ParameterizationMode.java @@ -0,0 +1,128 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.parameterization; + +/** + * 参数化字符串的格式化模式。 + * + * @author 季聿阶 + * @since 2025-09-23 + */ +public enum ParameterizationMode { + /** + * 严格模式: + *
    + *
  • 参数数量必须完全匹配
  • + *
  • 语法错误抛异常
  • + *
  • 缺失参数抛异常
  • + *
+ */ + STRICT(true, true, MissingParameterBehavior.THROW_EXCEPTION), + + /** + * 宽松模式 - 使用空字符串: + *
    + *
  • 允许多余参数
  • + *
  • 语法错误当普通字符处理
  • + *
  • 缺失参数使用空字符串
  • + *
+ */ + LENIENT_EMPTY(false, false, MissingParameterBehavior.USE_EMPTY_STRING), + + /** + * 宽松模式 - 使用默认值: + *
    + *
  • 允许多余参数
  • + *
  • 语法错误当普通字符处理
  • + *
  • 缺失参数使用默认值
  • + *
+ */ + LENIENT_DEFAULT(false, false, MissingParameterBehavior.USE_DEFAULT_VALUE), + + /** + * 宽松模式 - 保持占位符: + *
    + *
  • 允许多余参数
  • + *
  • 语法错误当普通字符处理
  • + *
  • 缺失参数保持原占位符
  • + *
+ */ + LENIENT_KEEP_PLACEHOLDER(false, false, MissingParameterBehavior.KEEP_PLACEHOLDER), + + /** + * 混合模式: + *
    + *
  • 允许多余参数
  • + *
  • 语法错误当普通字符处理
  • + *
  • 但缺失参数仍抛异常(便于调试)
  • + *
+ */ + LENIENT_STRICT_PARAMETERS(false, false, MissingParameterBehavior.THROW_EXCEPTION); + + private final boolean requireExactParameterCount; + private final boolean strictSyntax; + private final MissingParameterBehavior missingParameterBehavior; + + ParameterizationMode(boolean requireExactParameterCount, boolean strictSyntax, + MissingParameterBehavior missingParameterBehavior) { + this.requireExactParameterCount = requireExactParameterCount; + this.strictSyntax = strictSyntax; + this.missingParameterBehavior = missingParameterBehavior; + } + + /** + * 是否要求参数数量必须完全匹配。 + * + * @return 如果要求参数数量必须完全匹配,则返回 {@code true},否则,返回 {@code false}。 + */ + public boolean isRequireExactParameterCount() { + return this.requireExactParameterCount; + } + + /** + * 是否要求语法错误抛异常。 + * + * @return 如果要求语法错误抛异常,则返回 {@code true},否则,返回 {@code false}。 + */ + public boolean isStrictSyntax() { + return this.strictSyntax; + } + + /** + * 获取缺失参数处理行为。 + * + * @return 表示缺失参数处理行为的 {@link MissingParameterBehavior}。 + */ + public MissingParameterBehavior getMissingParameterBehavior() { + return this.missingParameterBehavior; + } + + /** + * 缺失参数处理行为。 + */ + public enum MissingParameterBehavior { + /** + * 抛异常。 + */ + THROW_EXCEPTION, + + /** + * 使用空字符串。 + */ + USE_EMPTY_STRING, + + /** + * 使用默认值。 + */ + USE_DEFAULT_VALUE, + + /** + * 保持原占位符。 + */ + KEEP_PLACEHOLDER + } +} diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/ParameterizedString.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/ParameterizedString.java index ba6386bcd..e9ea5fa73 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/ParameterizedString.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/ParameterizedString.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.parameterization; @@ -13,7 +13,7 @@ * 为参数解析提供结果。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public interface ParameterizedString { /** @@ -45,4 +45,14 @@ public interface ParameterizedString { * @throws StringFormatException 当需要但是未提供指定名称的参数时。 */ String format(Map args); + + /** + * 使用指定的参数映射格式化字符串。 + * + * @param args 表示参数映射的 {@link Map}{@code }。 + * @param defaultValue 表示缺少对应参数时的默认值的 {@link String}。 + * @return 表示格式化后的字符串的 {@link String}。 + * @throws StringFormatException 当需要但是未提供指定名称的参数时。 + */ + String format(Map args, String defaultValue); } diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/ParameterizedStringResolver.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/ParameterizedStringResolver.java index 2d1a78d40..d335f36e3 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/ParameterizedStringResolver.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/ParameterizedStringResolver.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.parameterization; @@ -13,11 +13,11 @@ *

从字符串中解析具备指定前缀和后缀的变量,并通过指定的参数映射对变量进行替换,以得到最终结果。

*

例如,对于字符串 {@code "My name is ${name}."},变量以 {@code "$\u007b"} 为 {@link * ParameterizedStringResolver#getParameterPrefix() 前缀},以 {@code "\u007d"} 为 {@link - * ParameterizedStringResolver#getParameterSuffix() 后缀},并提供变量 {@code "name"} 的值为 {@code "梁济时"},则:

+ * ParameterizedStringResolver#getParameterSuffix() 后缀},并提供变量 {@code "name"} 的值为 {@code "Tom"},则:

*
  *     VariableResolver resolver = VariableResolver.create("${", "}", '/'); // 得到一个解析器
  *     String source = "My name is ${name}."; // 表示原始字符串
- *     Map<String, String> params = MapBuilder.get().put("name", "梁济时").build(); // 参数映射
+ *     Map<String, String> params = MapBuilder.get().put("name", "Tom").build(); // 参数映射
  *     VariableResolvingResult result = resolver.resolve(source, params); // 解析字符串
  *     String resolved = result.getResolvedString(); // 得到解析后的字符串
  * 
@@ -27,9 +27,10 @@ * 实例作为静态变量,以降低内存开销。

* * @author 梁济时 + * @author 季聿阶 * @see ParameterizedString * @see ResolvedParameter - * @since 1.0 + * @since 2020-07-24 */ public interface ParameterizedStringResolver { /** @@ -70,13 +71,11 @@ public interface ParameterizedStringResolver { * @param suffix 表示变量的后缀的 {@link String}。 * @param escapeCharacter 表示转义字符的 {@code char}。 * @return 表示新实例化的解析工具的 {@link ParameterizedStringResolver}。 - * @throws IllegalArgumentException {@code prefix} 为 {@code null} 或空字符串。 - * @throws IllegalArgumentException {@code suffix} 为 {@code null} 或空字符串。 - * @throws IllegalArgumentException {@code prefix} 中包含 {@code escapeCharacter}。 - * @throws IllegalArgumentException {@code suffix} 中包含 {@code escapeCharacter}。 + * @throws IllegalArgumentException 当变量前缀或后缀为 {@code null} 或空字符串时,或当变量前缀或后缀中包含了转义字符时,或当 + * {@code mode} 为 {@code null} 时。 */ static ParameterizedStringResolver create(String prefix, String suffix, char escapeCharacter) { - return new DefaultParameterizedStringResolver(prefix, suffix, escapeCharacter, true); + return new DefaultParameterizedStringResolver(prefix, suffix, escapeCharacter, ParameterizationMode.STRICT); } /** @@ -87,12 +86,29 @@ static ParameterizedStringResolver create(String prefix, String suffix, char esc * @param escapeCharacter 表示转义字符的 {@code char}。 * @param isStrict 表示是否采用严格校验模式的 {@code boolean}。 * @return 表示新实例化的解析工具的 {@link ParameterizedStringResolver}。 - * @throws IllegalArgumentException {@code prefix} 为 {@code null} 或空字符串。 - * @throws IllegalArgumentException {@code suffix} 为 {@code null} 或空字符串。 - * @throws IllegalArgumentException {@code prefix} 中包含 {@code escapeCharacter}。 - * @throws IllegalArgumentException {@code suffix} 中包含 {@code escapeCharacter}。 + * @throws IllegalArgumentException 当变量前缀或后缀为 {@code null} 或空字符串时,或当变量前缀或后缀中包含了转义字符时,或当 + * {@code mode} 为 {@code null} 时。 */ static ParameterizedStringResolver create(String prefix, String suffix, char escapeCharacter, boolean isStrict) { - return new DefaultParameterizedStringResolver(prefix, suffix, escapeCharacter, isStrict); + return new DefaultParameterizedStringResolver(prefix, + suffix, + escapeCharacter, + isStrict ? ParameterizationMode.STRICT : ParameterizationMode.LENIENT_EMPTY); + } + + /** + * 使用变量的前缀、后缀及转义字符实例化一个 {@link ParameterizedStringResolver} 的默认实现。 + * + * @param prefix 表示变量的前缀的 {@link String}。 + * @param suffix 表示变量的后缀的 {@link String}。 + * @param escapeCharacter 表示转义字符的 {@code char}。 + * @param mode 表示描述参数解析模式的 {@link ParameterizationMode}。 + * @return 描述新实例化的解析工具的 {@link ParameterizedStringResolver}。 + * @throws IllegalArgumentException 当变量前缀或后缀为 {@code null} 或空字符串时,或当变量前缀或后缀中包含了转义字符时,或当 + * {@code mode} 为 {@code null} 时。 + */ + static ParameterizedStringResolver create(String prefix, String suffix, char escapeCharacter, + ParameterizationMode mode) { + return new DefaultParameterizedStringResolver(prefix, suffix, escapeCharacter, mode); } } diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/StringFormatException.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/StringFormatException.java index ee71f392a..1fb7c80dc 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/StringFormatException.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/StringFormatException.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.parameterization; @@ -10,7 +10,7 @@ * 当发现参数化字符串的格式错误,或参数未提供时引发的异常。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public class StringFormatException extends IllegalArgumentException { /** diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/support/DefaultParameterizedString.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/support/DefaultParameterizedString.java index b6b906dc6..3fef204da 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/support/DefaultParameterizedString.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/support/DefaultParameterizedString.java @@ -1,11 +1,12 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.parameterization.support; +import modelengine.fitframework.parameterization.ParameterizationMode; import modelengine.fitframework.parameterization.ParameterizedString; import modelengine.fitframework.parameterization.ParameterizedStringResolver; import modelengine.fitframework.parameterization.ResolvedParameter; @@ -20,7 +21,6 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import java.util.function.BiFunction; import java.util.stream.Collectors; /** @@ -31,29 +31,25 @@ * @since 2020-07-24 */ class DefaultParameterizedString implements ParameterizedString { - private static final BiFunction, Long, Boolean> STRICT_CHECK = - (map, count) -> MapUtils.count(map) == count; - private static final BiFunction, Long, Boolean> RELAXED_CHECK = - (map, count) -> MapUtils.count(map) >= count; - private final ParameterizedStringResolver resolver; private final String originalString; private String escapedString; private final List parameters; - private final boolean isStrict; + private final ParameterizationMode mode; /** * 使用源字符串及解析到的参数信息的集合初始化 {@link DefaultParameterizedString} 类的新实例。 * * @param resolver 表示解析得到当前参数化字符串的解析器的 {@link DefaultParameterizedStringResolver}。 * @param originalString 表示源字符串的 {@link String}。 - * @param isStrict 表示是否采用严格校验模式的 {@code boolean}。 + * @param mode 表示描述参数化字符串的解析模式的 {@link ParameterizationMode}。 */ - private DefaultParameterizedString(ParameterizedStringResolver resolver, String originalString, boolean isStrict) { + private DefaultParameterizedString(ParameterizedStringResolver resolver, String originalString, + ParameterizationMode mode) { this.resolver = resolver; this.originalString = originalString; this.parameters = new ArrayList<>(); - this.isStrict = isStrict; + this.mode = mode; } @Override @@ -73,24 +69,40 @@ public List getParameters() { @Override public String format(Map args) { + return this.format(args, null); + } + + @Override + public String format(Map args, String defaultValue) { Map actualArgs = ObjectUtils.nullIf(args, Collections.EMPTY_MAP); long count = this.getParameters().stream().map(ResolvedParameter::getName).distinct().count(); - BiFunction, Long, Boolean> countCheck = this.isStrict ? STRICT_CHECK : RELAXED_CHECK; - if (!countCheck.apply(actualArgs, count)) { - throw new StringFormatException("The provided args is not match the required args."); + + // 根据模式检查参数数量。 + if (this.mode.isRequireExactParameterCount()) { + if (MapUtils.count(actualArgs) != count) { + throw new StringFormatException("The provided args is not match the required args."); + } + } else { + if (MapUtils.count(actualArgs) < count) { + // 在非严格模式下,如果缺失参数策略是抛异常,仍然检查。 + if (this.mode.getMissingParameterBehavior() + == ParameterizationMode.MissingParameterBehavior.THROW_EXCEPTION) { + throw new StringFormatException("Required parameters are missing."); + } + } } + if (CollectionUtils.isEmpty(this.getParameters())) { return this.escapedString; } else { - List sortedParameters = this.parameters.stream() - .sorted(Comparator.comparingInt(ResolvedParameter::getPosition)) - .collect(Collectors.toList()); + List sortedParameters = + this.parameters.stream().sorted(Comparator.comparingInt(ResolvedParameter::getPosition)).toList(); int index = 0; int affixLength = this.measureAffix(); StringBuilder builder = new StringBuilder(this.escapedString.length() << 1); for (DefaultResolvedParameter parameter : sortedParameters) { builder.append(this.escapedString, index, parameter.getEscapedPosition()); - builder.append(StringUtils.normalize(getParameterValue(actualArgs, parameter.getName()))); + builder.append(StringUtils.normalize(getParameterValue(actualArgs, parameter.getName(), defaultValue))); index = parameter.getEscapedPosition() + affixLength + measure(parameter.getName()); } builder.append(this.escapedString.substring(index)); @@ -103,11 +115,20 @@ public String format(Map args) { * * @param args 表示参数映射的 {@link Map}。 * @param name 表示所需参数的名称的 {@link String}。 + * @param defaultValue 表示当参数缺失时使用的默认值的 {@link String}。 * @return 表示参数的值的 {@link String}。 */ - private static String getParameterValue(Map args, String name) { + private String getParameterValue(Map args, String name, String defaultValue) { if (!args.containsKey(name)) { - throw new StringFormatException(StringUtils.format("Parameter '{0}' required but not supplied.", name)); + return switch (this.mode.getMissingParameterBehavior()) { + case THROW_EXCEPTION -> throw new StringFormatException(StringUtils.format( + "Parameter '{0}' required but not supplied.", + name)); + case USE_EMPTY_STRING -> StringUtils.EMPTY; + case USE_DEFAULT_VALUE -> StringUtils.normalize(defaultValue); + case KEEP_PLACEHOLDER -> + this.getResolver().getParameterPrefix() + name + this.getResolver().getParameterSuffix(); + }; } return ObjectUtils.toString(args.get(name)); } @@ -143,15 +164,14 @@ public String toString() { * * @param resolver 表示用以解析字符串的解析器的 {@link DefaultParameterizedStringResolver}。 * @param originalString 表示待解析的字符串的 {@link String}。 - * @param isStrict 表示是否采用严格校验模式的 {@code boolean}。 + * @param mode 表示描述参数化字符串的解析模式的 {@link ParameterizationMode}。 * @return 表示解析后得到的参数化字符串的 {@link ParameterizedString}。 * @throws StringFormatException 当源字符串格式不满足解析器的要求时。 */ static DefaultParameterizedString resolve(DefaultParameterizedStringResolver resolver, String originalString, - boolean isStrict) { - DefaultParameterizedString parameterizedString = - new DefaultParameterizedString(resolver, originalString, isStrict); - parameterizedString.new Resolver(isStrict).resolve(); + ParameterizationMode mode) { + DefaultParameterizedString parameterizedString = new DefaultParameterizedString(resolver, originalString, mode); + parameterizedString.new Resolver(mode).resolve(); return parameterizedString; } @@ -161,11 +181,11 @@ static DefaultParameterizedString resolve(DefaultParameterizedStringResolver res * 方法时对所属的参数化字符串产生影响。

* * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ private class Resolver { private final StringBuilder escaped; - private final boolean isStrict; + private final ParameterizationMode mode; private int position; private StringBuilder parameter; private int parameterEscapedCharacters; @@ -173,11 +193,11 @@ private class Resolver { /** * 初始化一个 {@link Resolver} 类的新实例。 * - * @param isStrict 表示是否采用严格校验模式的 {@code boolean}。 + * @param mode 表示描述参数化字符串的解析模式的 {@link ParameterizationMode}。 */ - Resolver(boolean isStrict) { + Resolver(ParameterizationMode mode) { this.escaped = new StringBuilder(this.getOriginalString().length()); - this.isStrict = isStrict; + this.mode = mode; } /** @@ -304,7 +324,7 @@ private boolean isSuffix() { */ private void resolveSuffix() { if (this.parameter == null) { - if (this.isStrict) { + if (this.mode.isStrictSyntax()) { throw new StringFormatException(StringUtils.format( "Invalid suffix position. [string={0}, position={1}]", this.getOriginalString(), diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/support/DefaultParameterizedStringResolver.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/support/DefaultParameterizedStringResolver.java index 1be531185..fddc9e25e 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/support/DefaultParameterizedStringResolver.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/parameterization/support/DefaultParameterizedStringResolver.java @@ -1,14 +1,16 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.parameterization.support; import static modelengine.fitframework.inspection.Validation.notBlank; +import static modelengine.fitframework.inspection.Validation.notNull; import modelengine.fitframework.inspection.Validation; +import modelengine.fitframework.parameterization.ParameterizationMode; import modelengine.fitframework.parameterization.ParameterizedString; import modelengine.fitframework.parameterization.ParameterizedStringResolver; import modelengine.fitframework.util.StringUtils; @@ -17,13 +19,14 @@ * 为 {@link ParameterizedStringResolver} 提供默认实现。 * * @author 梁济时 - * @since 1.0 + * @author 季聿阶 + * @since 2020-07-24 */ public class DefaultParameterizedStringResolver implements ParameterizedStringResolver { private final String prefix; private final String suffix; private final char escapeCharacter; - private final boolean isStrict; + private final ParameterizationMode mode; /** * 使用变量的前缀、后缀及转义字符初始化 {@link DefaultParameterizedStringResolver} 类的新实例。 @@ -31,13 +34,12 @@ public class DefaultParameterizedStringResolver implements ParameterizedStringRe * @param prefix 表示变量前缀的 {@link String}。 * @param suffix 表示变量后缀的 {@link String}。 * @param escapeCharacter 表示转义字符的 {@code char}。 - * @param isStrict 表示是否采用严格校验模式的 {@code boolean}。 - * @throws IllegalArgumentException 当变量前缀为 {@code null} 或空字符串时。 - * @throws IllegalArgumentException 当变量后缀为 {@code null} 或空字符串时。 - * @throws IllegalArgumentException 当变量前缀中包含了转义字符时。 - * @throws IllegalArgumentException 当变量后缀中包含了转义字符时。 + * @param mode 表示描述参数化字符串的解析模式的 {@link ParameterizationMode}。 + * @throws IllegalArgumentException 当变量前缀或后缀为 {@code null} 或空字符串时,或当变量前缀或后缀中包含了转义字符时,或当 + * {@code mode} 为 {@code null} 时。 */ - public DefaultParameterizedStringResolver(String prefix, String suffix, char escapeCharacter, boolean isStrict) { + public DefaultParameterizedStringResolver(String prefix, String suffix, char escapeCharacter, + ParameterizationMode mode) { this.prefix = notBlank(prefix, "The prefix to resolve variables cannot be null or empty."); this.suffix = notBlank(suffix, "The suffix to resolve variables cannot be null or empty."); validateEscapeCharacter(prefix, @@ -47,7 +49,7 @@ public DefaultParameterizedStringResolver(String prefix, String suffix, char esc escapeCharacter, "The suffix cannot contains the escape character. [suffix={0}, escape={1}, position={2}]"); this.escapeCharacter = escapeCharacter; - this.isStrict = isStrict; + this.mode = notNull(mode, "The parameterization mode cannot be null."); } /** @@ -82,6 +84,6 @@ public char getEscapeCharacter() { @Override public ParameterizedString resolve(String originalString) { Validation.notNull(originalString, "The string to resolve as a parameterized string cannot be null."); - return DefaultParameterizedString.resolve(this, originalString, this.isStrict); + return DefaultParameterizedString.resolve(this, originalString, this.mode); } } diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/CodeableEnum.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/CodeableEnum.java index fcc3af784..3d178d5ee 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/CodeableEnum.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/CodeableEnum.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util; @@ -21,7 +21,7 @@ * * @param 表示枚举的实际类型。 * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public interface CodeableEnum & CodeableEnum> { /** diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/Convert.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/Convert.java index 6c2c2e548..3edadc4cc 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/Convert.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/Convert.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util; @@ -18,7 +18,7 @@ * 为对象类型转换提供工具方法。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public final class Convert { /** diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/DateUtils.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/DateUtils.java index 3cf8ba454..a4e37acdb 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/DateUtils.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/DateUtils.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util; @@ -22,7 +22,7 @@ * 为日期提供工具方法。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public final class DateUtils { /** 表示默认的格式化字符串。 */ diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/EnumUtils.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/EnumUtils.java index ca4ba3867..476f5780c 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/EnumUtils.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/EnumUtils.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util; @@ -17,7 +17,7 @@ * 为枚举提供工具方法。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public final class EnumUtils { /** diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/Equalizer.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/Equalizer.java index 8ff201d06..f45b09442 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/Equalizer.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/Equalizer.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util; @@ -11,7 +11,7 @@ * * @param 表示待比较对象的类型。 * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ @FunctionalInterface public interface Equalizer { diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/ParsingResult.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/ParsingResult.java index c284217f1..5d522e18d 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/ParsingResult.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/ParsingResult.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util; @@ -11,7 +11,7 @@ * * @param 表示解析结果的类型的 {@link T}。 * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public interface ParsingResult { /** diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/ParsingResultUtils.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/ParsingResultUtils.java index f58c47a52..6d5eb5900 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/ParsingResultUtils.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/ParsingResultUtils.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util; @@ -12,7 +12,7 @@ * 为 {@link ParsingResult} 提供工具方法。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ class ParsingResultUtils { /** 表示失败的转换结果。 */ diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/StringUtils.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/StringUtils.java index 5436702c4..4bae4d30e 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/StringUtils.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/StringUtils.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util; @@ -11,6 +11,7 @@ import modelengine.fitframework.inspection.Validation; import modelengine.fitframework.io.ByteReader; +import modelengine.fitframework.parameterization.ParameterizationMode; import modelengine.fitframework.parameterization.ParameterizedString; import modelengine.fitframework.parameterization.ParameterizedStringResolver; import modelengine.fitframework.util.support.ArrayIterator; @@ -38,7 +39,7 @@ * * @author 梁济时 * @author 季聿阶 - * @since 1.0 + * @since 2020-07-24 */ public final class StringUtils { /** 表示空字符串。 */ @@ -47,7 +48,8 @@ public final class StringUtils { /** 表示空的字符串数组。 */ public static final String[] EMPTY_ARRAY = new String[0]; - private static final ParameterizedStringResolver FORMATTER = ParameterizedStringResolver.create("{", "}", '/'); + private static final ParameterizedStringResolver FORMATTER = + ParameterizedStringResolver.create("{", "}", '/', ParameterizationMode.STRICT); private static final ParameterizedStringResolver NON_STRICT_FORMATTER = ParameterizedStringResolver.create("{", "}", '/', false); @@ -289,6 +291,87 @@ public static String format(String format, boolean isStrict, Map return parameterizedString.format(params); } + /** + * 使用指定的格式化字符串对参数进行格式化,并返回格式化后的字符串。 + * + * @param format 表示格式化字符串的 {@link String}。 + * @param mode 表示参数化字符串的格式化模式的 {@link ParameterizationMode}。 + * @param args 表示用以格式化字符串的参数的 {@link Map}{@code <}{@link String}{@code , }{@link Object}{@code >}。如果参数中存在 + * {@code null},其对应的格式化后会变成空字符串。 + * @return 表示格式化得到的字符串的 {@link String}。 + * @throws modelengine.fitframework.parameterization.StringFormatException 当所提供的格式化字符串与格式化参数不匹配时。 + * @see #format(String, ParameterizationMode, Map, String) + */ + public static String format(String format, ParameterizationMode mode, Map args) { + return format(format, mode, args, null); + } + + /** + * 使用指定的格式化字符串对参数进行格式化,并返回格式化后的字符串。 + *

注意:{@code format} 中如果含有如下特殊字符({@code '\u007b'},{@code '\u007d'},{@code '/'}),需要在该字符前增加转义字符 + * {@code '/'} 进行转义。

+ * + * @param format 表示格式化字符串的 {@link String}。 + * @param mode 表示参数化字符串的格式化模式的 {@link ParameterizationMode}。 + * @param args 表示用以格式化字符串的参数的 {@link Map}{@code <}{@link String}{@code , }{@link Object}{@code >}。 + * @param defaultValue 占位符匹配模式为 {@link ParameterizationMode#LENIENT_DEFAULT} 时,如果参数中不存在对应的参数,则返回该值。 + * @return 表示格式化得到的字符串的 {@link String}。 + * @throws modelengine.fitframework.parameterization.StringFormatException 当所提供的格式化字符串与格式化参数不匹配时。 + */ + public static String format(String format, ParameterizationMode mode, Map args, + String defaultValue) { + if (isBlank(format)) { + return format; + } + Map params = nullIf(args, Collections.emptyMap()); + ParameterizedStringResolver resolver = ParameterizedStringResolver.create("{", "}", '/', mode); + ParameterizedString parameterizedString = resolver.resolve(format); + return parameterizedString.format(params, defaultValue); + } + + /** + * 使用指定的格式化字符串对参数进行格式化,并返回格式化后的字符串。 + *

采用宽松校验模式 {@link ParameterizationMode#LENIENT_EMPTY},同时不存在参数时使用 {@link StringUtils#EMPTY} 代替。

+ * + * @param format 表示待格式化的字符串的 {@link String}。 + * @param args 表示待格式化的参数的 {@link Map}{@code <}{@link String}{@code , }{@link Object}{@code >}。 + * @return 表示格式化得到的字符串的 {@link String}。 + * @throws modelengine.fitframework.parameterization.StringFormatException 当所提供的格式化字符串与格式化参数不匹配时。 + * @see #format(String, ParameterizationMode, Map, String) + */ + public static String formatLenient(String format, Map args) { + return format(format, ParameterizationMode.LENIENT_EMPTY, args); + } + + /** + * 使用指定的格式化字符串对参数进行格式化,并返回格式化后的字符串。 + *

采用宽松校验模式 {@link ParameterizationMode#LENIENT_DEFAULT},同时不存在参数时使用 {@code defaultValue} 代替。

+ * + * @param format 表示待格式化的字符串的 {@link String}。 + * @param args 表示待格式化的参数的 {@link Map}{@code <}{@link String}{@code , }{@link Object}{@code >}。 + * @param defaultValue 表示参数不存在时的默认值的 {@link String}。 + * @return 表示格式化得到的字符串的 {@link String}。 + * @throws modelengine.fitframework.parameterization.StringFormatException 当所提供的格式化字符串与格式化参数不匹配时。 + * @see #format(String, ParameterizationMode, Map, String) + */ + public static String formatLenientWithDefault(String format, Map args, String defaultValue) { + return format(format, ParameterizationMode.LENIENT_DEFAULT, args, defaultValue); + } + + /** + * 使用指定的格式化字符串对参数进行格式化,并返回格式化后的字符串。 + *

采用宽松校验模式 {@link ParameterizationMode#LENIENT_KEEP_PLACEHOLDER},同时不存在参数时使用占位符代替。

+ * + * @param format 表示待格式化的字符串的 {@link String}。 + * @param args 表示待格式化的参数的 {@link Map}{@code <}{@link String}{@code , }{@link Object}{@code >}。 + * @return 表示格式化得到的字符串的 {@link String}。 + * @throws modelengine.fitframework.parameterization.StringFormatException 当所提供的格式化字符串与格式化参数不匹配时。 + * @see #format(String, ParameterizationMode, Map, String) + */ + public static String formatKeepPlaceholder(String format, Map args) { + return format(format, ParameterizationMode.LENIENT_KEEP_PLACEHOLDER, args); + } + /** * 检查指定字符串是否为 {@code null}、空字符串或只有空白字符的字符串。 * diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/support/ArrayIterator.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/support/ArrayIterator.java index 90f0f3307..43ed51099 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/support/ArrayIterator.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/support/ArrayIterator.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util.support; @@ -13,7 +13,7 @@ * 为数组提供迭代器。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public class ArrayIterator implements Iterator { private final T[] items; diff --git a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/support/DefaultParsingResult.java b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/support/DefaultParsingResult.java index db4bb7b1d..061317547 100644 --- a/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/support/DefaultParsingResult.java +++ b/framework/fit/java/fit-util/src/main/java/modelengine/fitframework/util/support/DefaultParsingResult.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util.support; @@ -14,7 +14,7 @@ * * @param 表示结果的类型。 * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public class DefaultParsingResult implements ParsingResult { private boolean parsed; diff --git a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/parameterization/ParameterizationModeTest.java b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/parameterization/ParameterizationModeTest.java new file mode 100644 index 000000000..3c24a3460 --- /dev/null +++ b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/parameterization/ParameterizationModeTest.java @@ -0,0 +1,289 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) 2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +package modelengine.fitframework.parameterization; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.catchThrowableOfType; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.Map; + +/** + * 为新增的参数化模式和缺失参数处理策略提供测试。 + * + * @author 季聿阶 + * @since 2025-09-23 + */ +@Nested +@DisplayName("验证新增的参数化模式功能") +class ParameterizationModeTest { + @Nested + @DisplayName("测试不同的参数化模式") + class TestDifferentModes { + @Test + @DisplayName("严格模式:缺失参数时抛出异常") + void strictModeShouldThrowWhenParameterMissing() { + ParameterizedStringResolver resolver = + ParameterizedStringResolver.create("${", "}", '/', ParameterizationMode.STRICT); + ParameterizedString parameterizedString = resolver.resolve("Hello ${name}, welcome to ${city}!"); + + Map incompleteParams = Map.of("name", "张三"); + + StringFormatException exception = catchThrowableOfType(StringFormatException.class, + () -> parameterizedString.format(incompleteParams)); + assertThat(exception).isNotNull().hasMessage("The provided args is not match the required args."); + } + + @Test + @DisplayName("宽松空字符串模式:缺失参数使用空字符串") + void lenientEmptyModeShouldUseEmptyString() { + ParameterizedStringResolver resolver = + ParameterizedStringResolver.create("${", "}", '/', ParameterizationMode.LENIENT_EMPTY); + ParameterizedString parameterizedString = resolver.resolve("Hello ${name}, welcome to ${city}!"); + + Map incompleteParams = Map.of("name", "张三"); + String result = parameterizedString.format(incompleteParams); + + assertThat(result).isEqualTo("Hello 张三, welcome to !"); + } + + @Test + @DisplayName("宽松默认值模式:缺失参数使用指定默认值") + void lenientDefaultModeShouldUseDefaultValue() { + ParameterizedStringResolver resolver = + ParameterizedStringResolver.create("${", "}", '/', ParameterizationMode.LENIENT_DEFAULT); + ParameterizedString parameterizedString = resolver.resolve("Hello ${name}, welcome to ${city}!"); + + Map incompleteParams = Map.of("name", "张三"); + String result = parameterizedString.format(incompleteParams, "未知城市"); + + assertThat(result).isEqualTo("Hello 张三, welcome to 未知城市!"); + } + + @Test + @DisplayName("宽松默认值模式:默认值为null时使用空字符串") + void lenientDefaultModeShouldUseEmptyWhenDefaultIsNull() { + ParameterizedStringResolver resolver = + ParameterizedStringResolver.create("${", "}", '/', ParameterizationMode.LENIENT_DEFAULT); + ParameterizedString parameterizedString = resolver.resolve("Hello ${name}, welcome to ${city}!"); + + Map incompleteParams = Map.of("name", "张三"); + String result = parameterizedString.format(incompleteParams, null); + + assertThat(result).isEqualTo("Hello 张三, welcome to !"); + } + + @Test + @DisplayName("宽松保持占位符模式:缺失参数保持原占位符") + void lenientKeepPlaceholderModeShouldKeepPlaceholder() { + ParameterizedStringResolver resolver = + ParameterizedStringResolver.create("${", "}", '/', ParameterizationMode.LENIENT_KEEP_PLACEHOLDER); + ParameterizedString parameterizedString = resolver.resolve("Hello ${name}, welcome to ${city}!"); + + Map incompleteParams = Map.of("name", "张三"); + String result = parameterizedString.format(incompleteParams); + + assertThat(result).isEqualTo("Hello 张三, welcome to ${city}!"); + } + + @Test + @DisplayName("混合模式:允许多余参数但缺失参数仍抛异常") + void lenientStrictParametersModeShouldAllowExtraButThrowOnMissing() { + ParameterizedStringResolver resolver = + ParameterizedStringResolver.create("${", "}", '/', ParameterizationMode.LENIENT_STRICT_PARAMETERS); + ParameterizedString parameterizedString = resolver.resolve("Hello ${name}!"); + + // 测试允许多余参数 + Map extraParams = Map.of("name", "张三", "extra", "多余参数"); + String result1 = parameterizedString.format(extraParams); + assertThat(result1).isEqualTo("Hello 张三!"); + + // 测试缺失参数仍然抛异常 + Map incompleteParams = Map.of("extra", "多余参数"); + StringFormatException exception = catchThrowableOfType(StringFormatException.class, + () -> parameterizedString.format(incompleteParams)); + assertThat(exception).isNotNull().hasMessage("Parameter 'name' required but not supplied."); + } + } + + @Nested + @DisplayName("测试语法错误处理") + class TestSyntaxErrorHandling { + @Test + @DisplayName("严格模式:孤立后缀符号抛出异常") + void strictModeShouldThrowOnInvalidSuffix() { + ParameterizedStringResolver resolver = + ParameterizedStringResolver.create("${", "}", '/', ParameterizationMode.STRICT); + + StringFormatException exception = + catchThrowableOfType(StringFormatException.class, () -> resolver.resolve("Hello world}")); + assertThat(exception).isNotNull(); + } + + @Test + @DisplayName("宽松模式:孤立后缀符号当作普通字符") + void lenientModeShouldTreatInvalidSuffixAsNormalChar() { + ParameterizedStringResolver resolver = + ParameterizedStringResolver.create("${", "}", '/', ParameterizationMode.LENIENT_EMPTY); + ParameterizedString parameterizedString = resolver.resolve("Hello world}"); + + String result = parameterizedString.format(Collections.emptyMap()); + assertThat(result).isEqualTo("Hello world}"); + } + } + + @Nested + @DisplayName("测试复杂场景") + class TestComplexScenarios { + @Test + @DisplayName("JSON配置文件场景:混合占位符和JSON语法") + void shouldHandleJsonConfigurationCorrectly() { + String jsonTemplate = """ + { + "user": "${username}", + "config": { + "timeout": ${timeout}, + "features": ["feature1", "feature2"], + "metadata": {"version": "1.0", "type": "${appType}"} + }, + "missing": "${missingField}" + }"""; + + ParameterizedStringResolver resolver = + ParameterizedStringResolver.create("${", "}", '/', ParameterizationMode.LENIENT_DEFAULT); + ParameterizedString parameterizedString = resolver.resolve(jsonTemplate); + + Map params = Map.of("username", "testUser", "timeout", "5000", "appType", "web" + // 注意:缺少 missingField + ); + + String result = parameterizedString.format(params, "defaultValue"); + + // 验证JSON结构保持完整,只有占位符被替换 + assertThat(result).contains("\"user\": \"testUser\""); + assertThat(result).contains("\"timeout\": 5000"); + assertThat(result).contains("\"type\": \"web\""); + assertThat(result).contains("\"missing\": \"defaultValue\""); + // JSON语法应该保持不变 + assertThat(result).contains("\"features\": [\"feature1\", \"feature2\"]"); + assertThat(result).contains("\"metadata\": {\"version\": \"1.0\""); + } + + @Test + @DisplayName("日志模板场景:允许部分参数缺失") + void shouldHandleLogTemplateWithMissingParameters() { + String logTemplate = + "[${timestamp}] ${level} ${class} - ${message} (user: ${userId}, session: ${sessionId})"; + + ParameterizedStringResolver resolver = + ParameterizedStringResolver.create("${", "}", '/', ParameterizationMode.LENIENT_EMPTY); + ParameterizedString parameterizedString = resolver.resolve(logTemplate); + + Map params = Map.of("timestamp", + "2024-01-15 10:30:00", + "level", + "INFO", + "class", + "UserService", + "message", + "User login successful" + // 缺少 userId 和 sessionId + ); + + String result = parameterizedString.format(params); + + assertThat(result).isEqualTo( + "[2024-01-15 10:30:00] INFO UserService - User login successful (user: , session: )"); + } + + @Test + @DisplayName("配置文件场景:使用统一默认值") + void shouldHandleConfigFileWithUniformDefault() { + String configTemplate = """ + server.host=${host} + server.port=${port} + database.url=${dbUrl} + database.username=${dbUser} + database.password=${dbPassword} + cache.enabled=${cacheEnabled} + """; + + ParameterizedStringResolver resolver = + ParameterizedStringResolver.create("${", "}", '/', ParameterizationMode.LENIENT_DEFAULT); + ParameterizedString parameterizedString = resolver.resolve(configTemplate); + + Map params = Map.of("host", "localhost", "port", "8080" + // 其他参数缺失 + ); + + String result = parameterizedString.format(params, "NOT_SET"); + + assertThat(result).contains("server.host=localhost"); + assertThat(result).contains("server.port=8080"); + assertThat(result).contains("database.url=NOT_SET"); + assertThat(result).contains("database.username=NOT_SET"); + assertThat(result).contains("database.password=NOT_SET"); + assertThat(result).contains("cache.enabled=NOT_SET"); + } + } + + @Nested + @DisplayName("测试边界情况") + class TestEdgeCases { + @Test + @DisplayName("无占位符的字符串") + void shouldHandleStringWithoutPlaceholders() { + ParameterizedStringResolver resolver = + ParameterizedStringResolver.create("${", "}", '/', ParameterizationMode.LENIENT_EMPTY); + ParameterizedString parameterizedString = resolver.resolve("Plain text without placeholders"); + + String result = parameterizedString.format(Map.of("unused", "value")); + + assertThat(result).isEqualTo("Plain text without placeholders"); + } + + @Test + @DisplayName("空字符串模板") + void shouldHandleEmptyTemplate() { + ParameterizedStringResolver resolver = + ParameterizedStringResolver.create("${", "}", '/', ParameterizationMode.LENIENT_DEFAULT); + ParameterizedString parameterizedString = resolver.resolve(""); + + String result = parameterizedString.format(Collections.emptyMap(), "default"); + + assertThat(result).isEqualTo(""); + } + + @Test + @DisplayName("只有占位符的模板") + void shouldHandleTemplateWithOnlyPlaceholders() { + ParameterizedStringResolver resolver = + ParameterizedStringResolver.create("${", "}", '/', ParameterizationMode.LENIENT_KEEP_PLACEHOLDER); + ParameterizedString parameterizedString = resolver.resolve("${param1}${param2}${param3}"); + + String result = parameterizedString.format(Map.of("param2", "middle")); + + assertThat(result).isEqualTo("${param1}middle${param3}"); + } + + @Test + @DisplayName("重复占位符") + void shouldHandleDuplicatePlaceholders() { + ParameterizedStringResolver resolver = + ParameterizedStringResolver.create("${", "}", '/', ParameterizationMode.LENIENT_DEFAULT); + ParameterizedString parameterizedString = resolver.resolve("${name} says hello to ${name} again!"); + + String result = parameterizedString.format(Collections.emptyMap(), "Unknown"); + + assertThat(result).isEqualTo("Unknown says hello to Unknown again!"); + } + } +} diff --git a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/CodeableEnumTest.java b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/CodeableEnumTest.java index b6a8ee0e3..4dd4f380c 100644 --- a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/CodeableEnumTest.java +++ b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/CodeableEnumTest.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util; @@ -17,7 +17,7 @@ * 为 {@link CodeableEnum} 提供单元测试。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public class CodeableEnumTest { /** @@ -109,7 +109,7 @@ public void should_return_item_when_code_found() { * 为 {@link CodeableEnum} 提供用以验证的枚举定义。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ enum DemoCodeableEnum implements CodeableEnum { /** 表示第一个枚举项。 */ diff --git a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/ConvertTest.java b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/ConvertTest.java index 315e2d1e2..35cd4dce1 100644 --- a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/ConvertTest.java +++ b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/ConvertTest.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util; @@ -22,7 +22,7 @@ * 为 {@link Convert} 提供单元测试。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public class ConvertTest { /** 表示待转换的值。 */ diff --git a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/DateUtilsTest.java b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/DateUtilsTest.java index fd869f077..7a1648ed2 100644 --- a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/DateUtilsTest.java +++ b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/DateUtilsTest.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util; @@ -29,7 +29,7 @@ * 为 {@link DateUtils} 提供单元测试。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public class DateUtilsTest { /** 表示用以测试的年份。 */ diff --git a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/EnumUtilsTest.java b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/EnumUtilsTest.java index 9fdd014e3..37d948705 100644 --- a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/EnumUtilsTest.java +++ b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/EnumUtilsTest.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util; @@ -21,7 +21,7 @@ * 为 {@link EnumUtils} 提供工具方法。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public class EnumUtilsTest { /** @@ -87,7 +87,7 @@ public void should_return_all_matched_when_no_predicate() { * 为测试 {@link EnumUtils} 提供用以验证的枚举定义。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ enum DemoEnum { /** 表示第一个枚举项。 */ diff --git a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/StringUtilsTest.java b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/StringUtilsTest.java index 50192fad9..985bf8034 100644 --- a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/StringUtilsTest.java +++ b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/StringUtilsTest.java @@ -1,14 +1,16 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.catchThrowableOfType; +import modelengine.fitframework.parameterization.ParameterizationMode; + import org.assertj.core.api.ThrowableAssert; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -18,9 +20,11 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; @@ -31,7 +35,7 @@ * * @author 梁济时 * @author 季聿阶 - * @since 1.0 + * @since 2020-07-24 */ public class StringUtilsTest { /** 表示一个泛空格符。 */ @@ -478,6 +482,310 @@ void givenFormatWithEscapeCharacterThenReturnStringWithoutEscapeCharacterOnly() String actual = StringUtils.format("/{///}"); assertThat(actual).isEqualTo("{/}"); } + + @Nested + @DisplayName("测试带模式参数的format方法") + class TestFormatWithMode { + @Test + @DisplayName("使用严格模式格式化") + void shouldFormatWithStrictMode() { + String template = "Hello {0}, age is {1}"; + Map params = Map.of("0", "Alice", "1", "25"); + + String result = StringUtils.format(template, ParameterizationMode.STRICT, params); + + assertThat(result).isEqualTo("Hello Alice, age is 25"); + } + + @Test + @DisplayName("使用宽松空字符串模式格式化") + void shouldFormatWithLenientEmptyMode() { + String template = "Hello {name}, welcome to {city}!"; + Map params = Map.of("name", "Bob"); + + String result = StringUtils.format(template, ParameterizationMode.LENIENT_EMPTY, params); + + assertThat(result).isEqualTo("Hello Bob, welcome to !"); + } + + @Test + @DisplayName("使用宽松默认值模式格式化") + void shouldFormatWithLenientDefaultMode() { + String template = "User: {username}, Status: {status}, Role: {role}"; + Map params = Map.of("username", "testUser", "status", "active"); + + String result = StringUtils.format(template, ParameterizationMode.LENIENT_DEFAULT, params, "guest"); + + assertThat(result).isEqualTo("User: testUser, Status: active, Role: guest"); + } + + @Test + @DisplayName("使用保持占位符模式格式化") + void shouldFormatWithKeepPlaceholderMode() { + String template = "Config: host={host}, port={port}, debug={debug}"; + Map params = Map.of("host", "localhost", "port", "8080"); + + String result = StringUtils.format(template, ParameterizationMode.LENIENT_KEEP_PLACEHOLDER, params); + + assertThat(result).isEqualTo("Config: host=localhost, port=8080, debug={debug}"); + } + } + + @Nested + @DisplayName("测试便捷方法") + class TestConvenienceMethods { + @Test + @DisplayName("formatLenient方法") + void shouldFormatLenient() { + String template = "Processing {item} of {total}"; + Map params = Map.of("item", "5"); + + String result = StringUtils.formatLenient(template, params); + + assertThat(result).isEqualTo("Processing 5 of "); + } + + @Test + @DisplayName("formatLenientWithDefault方法") + void shouldFormatLenientWithDefault() { + String template = "File: {filename}, Size: {size}, Type: {type}"; + Map params = Map.of("filename", "document.pdf", "size", "1.2MB"); + + String result = StringUtils.formatLenientWithDefault(template, params, "unknown"); + + assertThat(result).isEqualTo("File: document.pdf, Size: 1.2MB, Type: unknown"); + } + + @Test + @DisplayName("formatKeepPlaceholder方法") + void shouldFormatKeepPlaceholder() { + String template = "Database: {host}:{port}//{database}"; + Map params = Map.of("host", "db.example.com", "database", "myapp"); + + String result = StringUtils.formatKeepPlaceholder(template, params); + + assertThat(result).isEqualTo("Database: db.example.com:{port}/myapp"); + } + + @Test + @DisplayName("便捷方法应该处理null参数") + void convenienceMethodsShouldHandleNullParams() { + String template = "Hello {name}!"; + + String result1 = StringUtils.formatLenient(template, null); + String result2 = StringUtils.formatLenientWithDefault(template, null, "Guest"); + String result3 = StringUtils.formatKeepPlaceholder(template, null); + + assertThat(result1).isEqualTo("Hello !"); + assertThat(result2).isEqualTo("Hello Guest!"); + assertThat(result3).isEqualTo("Hello {name}!"); + } + } + + @Nested + @DisplayName("测试实际应用场景") + class TestRealWorldScenarios { + @Test + @DisplayName("SQL查询模板场景") + void shouldHandleSqlQueryTemplate() { + String sqlTemplate = "SELECT * FROM {table} WHERE {whereClause} ORDER BY {orderBy} LIMIT {limit}"; + Map params = Map.of("table", "users", "whereClause", "status = 'active'"); + + String result = StringUtils.formatLenientWithDefault(sqlTemplate, params, "id"); + + assertThat(result).isEqualTo("SELECT * FROM users WHERE status = 'active' ORDER BY id LIMIT id"); + } + + @Test + @DisplayName("邮件模板场景") + void shouldHandleEmailTemplate() { + String emailTemplate = """ + Dear {customerName}, + + Thank you for your order #{orderNumber}. + Your order of {itemCount} items totaling ${totalAmount} has been {status}. + + Estimated delivery: {deliveryDate} + Tracking number: {trackingNumber} + + Best regards, + {companyName} + """; + + Map params = Map.of("customerName", + "John Doe", + "orderNumber", + "ORD-2024-001", + "itemCount", + "3", + "totalAmount", + "99.99", + "status", + "confirmed", + "companyName", + "ACME Corp" + // 缺少 deliveryDate 和 trackingNumber + ); + + String result = StringUtils.formatLenientWithDefault(emailTemplate, params, "TBD"); + + assertThat(result).contains("Dear John Doe,"); + assertThat(result).contains("order #ORD-2024-001"); + assertThat(result).contains("3 items totaling $99.99"); + assertThat(result).contains("has been confirmed"); + assertThat(result).contains("delivery: TBD"); + assertThat(result).contains("Tracking number: TBD"); + assertThat(result).contains("ACME Corp"); + } + + @Test + @DisplayName("API响应模板场景") + void shouldHandleApiResponseTemplate() { + String responseTemplate = """ + /{ + "status": "{status}", + "data": /{ + "userId": "{userId}", + "username": "{username}", + "email": "{email}", + "primaryRole": "{primaryRole}", + "secondaryRole": "{secondaryRole}", + "roles": ["{primaryRole}", "{secondaryRole}"] + }, + "meta": /{ + "timestamp": "{timestamp}", + "version": "{apiVersion}" + } + }"""; + + Map params = Map.of("status", + "success", + "userId", + "12345", + "username", + "johndoe", + "email", + "john@example.com", + "primaryRole", + "user", + "timestamp", + "2024-01-15T10:30:00Z" + // 缺少 secondaryRole 和 apiVersion + ); + + // 使用空字符串处理缺失字段,保持JSON结构有效 + String result = StringUtils.formatLenient(responseTemplate, params); + + assertThat(result).contains("\"status\": \"success\""); + assertThat(result).contains("\"userId\": \"12345\""); + assertThat(result).contains("\"username\": \"johndoe\""); + assertThat(result).contains("\"email\": \"john@example.com\""); + assertThat(result).contains("\"primaryRole\": \"user\""); + assertThat(result).contains("\"secondaryRole\": \"\""); // 空字符串 + assertThat(result).contains("\"timestamp\": \"2024-01-15T10:30:00Z\""); + assertThat(result).contains("\"version\": \"\""); // 空字符串 + } + + @Test + @DisplayName("配置文件生成场景") + void shouldHandleConfigFileGeneration() { + String configTemplate = """ + # Application Configuration + app.name={appName} + app.version={appVersion} + app.environment={environment} + + # Server Configuration + server.host={serverHost} + server.port={serverPort} + server.ssl.enabled={sslEnabled} + + # Database Configuration + db.url={dbUrl} + db.username={dbUser} + db.password={dbPassword} + db.pool.size={poolSize} + + # Cache Configuration + cache.enabled={cacheEnabled} + cache.ttl={cacheTtl} + """; + + Map params = Map.of("appName", + "MyApplication", + "appVersion", + "1.0.0", + "environment", + "production", + "serverHost", + "0.0.0.0", + "serverPort", + "8080" + // 其他配置项缺失,使用占位符保持可见性 + ); + + String result = StringUtils.formatKeepPlaceholder(configTemplate, params); + + assertThat(result).contains("app.name=MyApplication"); + assertThat(result).contains("app.version=1.0.0"); + assertThat(result).contains("app.environment=production"); + assertThat(result).contains("server.host=0.0.0.0"); + assertThat(result).contains("server.port=8080"); + assertThat(result).contains("server.ssl.enabled={sslEnabled}"); + assertThat(result).contains("db.url={dbUrl}"); + assertThat(result).contains("cache.enabled={cacheEnabled}"); + } + } + + @Nested + @DisplayName("测试性能和边界情况") + class TestPerformanceAndEdgeCases { + @Test + @DisplayName("大量占位符性能测试") + void shouldHandleManyPlaceholders() { + StringBuilder templateBuilder = new StringBuilder(); + Map params = new HashMap<>(); + + // 创建包含100个占位符的模板 + for (int i = 0; i < 100; i++) { + templateBuilder.append("Param").append(i).append(": {param").append(i).append("} "); + if (i % 2 == 0) { // 只提供一半的参数 + params.put("param" + i, "value" + i); + } + } + + String template = templateBuilder.toString(); + String result = StringUtils.formatLenientWithDefault(template, params, "DEFAULT"); + + // 验证结果包含预期的值和默认值 + assertThat(result).contains("Param0: value0"); + assertThat(result).contains("Param1: DEFAULT"); + assertThat(result).contains("Param2: value2"); + assertThat(result).contains("Param3: DEFAULT"); + } + + @Test + @DisplayName("特殊字符处理") + void shouldHandleSpecialCharacters() { + String template = "Message: {msg}, Unicode: {unicode}, Emoji: {emoji}"; + Map params = Map.of("msg", "Hello\nWorld\t!", "unicode", "测试数据", "emoji", "🚀🎉"); + + String result = StringUtils.format(template, ParameterizationMode.STRICT, params); + + assertThat(result).isEqualTo("Message: Hello\nWorld\t!, Unicode: 测试数据, Emoji: 🚀🎉"); + } + + @Test + @DisplayName("嵌套类似占位符语法") + void shouldHandleNestedPlaceholderLikeSyntax() { + String template = "Config: {config}, JSON: /{\"key\": \"{value}\"}, Placeholder: {real}"; + Map params = Map.of("config", "prod", "real", "actualValue"); + + String result = StringUtils.formatKeepPlaceholder(template, params); + + assertThat(result).isEqualTo("Config: prod, JSON: {\"key\": \"{value}\"}, Placeholder: actualValue"); + } + } } /** diff --git a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/support/DefaultParsingResultTest.java b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/support/DefaultParsingResultTest.java index b6ec14169..d0c0a24a6 100644 --- a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/support/DefaultParsingResultTest.java +++ b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/support/DefaultParsingResultTest.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util.support; @@ -14,7 +14,7 @@ * 为 {@link DefaultParsingResult} 提供单元测试。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public class DefaultParsingResultTest { /** 表示用以测试的对象实例。 */ diff --git a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/support/DefaultRangeResultTest.java b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/support/DefaultRangeResultTest.java index a5fffe338..74ff73340 100644 --- a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/support/DefaultRangeResultTest.java +++ b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/support/DefaultRangeResultTest.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util.support; @@ -16,7 +16,7 @@ * 为 {@link DefaultRangeResult} 提供单元测试。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public class DefaultRangeResultTest { /** 表示用以测试的对象实例。 */ diff --git a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/support/DefaultRangeTest.java b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/support/DefaultRangeTest.java index 9854a52bf..9018a7d2e 100644 --- a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/support/DefaultRangeTest.java +++ b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/support/DefaultRangeTest.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util.support; @@ -16,7 +16,7 @@ * 为 {@link DefaultRange} 提供单元测试。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public class DefaultRangeTest { /** 表示用以测试的对象实例。 */ diff --git a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/support/DefaultRangedResultSetTest.java b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/support/DefaultRangedResultSetTest.java index 7c15b7adb..8a34997a6 100644 --- a/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/support/DefaultRangedResultSetTest.java +++ b/framework/fit/java/fit-util/src/test/java/modelengine/fitframework/util/support/DefaultRangedResultSetTest.java @@ -1,8 +1,8 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) 2024 Huawei Technologies Co., Ltd. All rights reserved. - * This file is a part of the ModelEngine Project. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ +/* + * Copyright (c) 2024-2025 Huawei Technologies Co., Ltd. All rights reserved. + * This file is a part of the ModelEngine Project. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ package modelengine.fitframework.util.support; @@ -16,7 +16,7 @@ * 为 {@link DefaultRangedResultSet} 提供单元测试。 * * @author 梁济时 - * @since 1.0 + * @since 2020-07-24 */ public class DefaultRangedResultSetTest { /** 表示用以测试的对象实例。 */