diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/model/Chinese2ndGenIDCardNumber.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/model/Chinese2ndGenIDCardNumber.java
index 611780e..984334e 100644
--- a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/model/Chinese2ndGenIDCardNumber.java
+++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/model/Chinese2ndGenIDCardNumber.java
@@ -33,7 +33,7 @@ import com.google.errorprone.annotations.Immutable;
import xyz.zhouxy.plusone.commons.annotation.ReaderMethod;
import xyz.zhouxy.plusone.commons.annotation.ValueObject;
-import xyz.zhouxy.plusone.commons.constant.PatternConsts;
+import xyz.zhouxy.plusone.commons.regex.PatternConsts;
import xyz.zhouxy.plusone.commons.util.StringTools;
/**
@@ -44,7 +44,7 @@ import xyz.zhouxy.plusone.commons.util.StringTools;
*
* @author ZhouXY
* @since 1.0.0
- * @see xyz.zhouxy.plusone.commons.constant.PatternConsts#CHINESE_2ND_ID_CARD_NUMBER
+ * @see xyz.zhouxy.plusone.commons.regex.PatternConsts#CHINESE_2ND_ID_CARD_NUMBER
*/
@ValueObject
@Immutable
diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/AbstractMatcher.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/AbstractMatcher.java
new file mode 100644
index 0000000..29c2e22
--- /dev/null
+++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/AbstractMatcher.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package xyz.zhouxy.plusone.commons.regex;
+
+import java.util.regex.Matcher;
+
+public abstract class AbstractMatcher {
+ private final Matcher matcher;
+
+ AbstractMatcher(Matcher matcher) {
+ this.matcher = matcher;
+ }
+
+ public final Matcher matcher() {
+ return this.matcher;
+ }
+
+ public final boolean matches() {
+ return this.matcher.matches();
+ }
+
+ public final String getGroupValue(String groupName) {
+ return this.matcher.group(groupName);
+ }
+}
diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/Chinese2ndIdCardNumberMatcher.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/Chinese2ndIdCardNumberMatcher.java
new file mode 100644
index 0000000..f41662d
--- /dev/null
+++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/Chinese2ndIdCardNumberMatcher.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package xyz.zhouxy.plusone.commons.regex;
+
+import java.util.regex.Matcher;
+
+import xyz.zhouxy.plusone.commons.model.Gender;
+
+public final class Chinese2ndIdCardNumberMatcher extends AbstractMatcher {
+
+ Chinese2ndIdCardNumberMatcher(Matcher matcher) {
+ super(matcher);
+ }
+
+ public final String getProvince() {
+ return getGroupValue("province");
+ }
+
+ public final String getCity() {
+ return getGroupValue("city");
+ }
+
+ public final String getCounty() {
+ return getGroupValue("county");
+ }
+
+ public final String getBirthDate() {
+ return getGroupValue("birthDate");
+ }
+
+ public final String getOrderCode() {
+ return getGroupValue("orderCode");
+ }
+
+ public final String getGenderCode() {
+ return getGroupValue("gender");
+ }
+
+ public final Gender getGender() {
+ final int genderCode = Integer.parseInt(getGenderCode());
+ return genderCode % 2 == 0 ? Gender.FEMALE : Gender.MALE;
+ }
+
+ public final String getCheckDigit() {
+ return getGroupValue("checkDigit");
+ }
+}
diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/LocalDateMatcher.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/LocalDateMatcher.java
new file mode 100644
index 0000000..a2064c3
--- /dev/null
+++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/LocalDateMatcher.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package xyz.zhouxy.plusone.commons.regex;
+
+import java.util.regex.Matcher;
+
+public final class LocalDateMatcher extends AbstractMatcher {
+
+ LocalDateMatcher(Matcher matcher) {
+ super(matcher);
+ }
+
+ public String getYear() {
+ return getGroupValue("yyyy");
+ }
+
+ public String getMonth() {
+ return getGroupValue("MM");
+ }
+
+ public String getDay() {
+ return getGroupValue("dd");
+ }
+}
diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/constant/PatternConsts.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/PatternConsts.java
similarity index 98%
rename from plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/constant/PatternConsts.java
rename to plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/PatternConsts.java
index afc80ec..7886101 100644
--- a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/constant/PatternConsts.java
+++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/PatternConsts.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package xyz.zhouxy.plusone.commons.constant;
+package xyz.zhouxy.plusone.commons.regex;
import java.util.regex.Pattern;
diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/PatternInfo.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/PatternInfo.java
new file mode 100644
index 0000000..e22de6a
--- /dev/null
+++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/PatternInfo.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package xyz.zhouxy.plusone.commons.regex;
+
+import java.util.regex.Pattern;
+
+public abstract class PatternInfo {
+ private final String regex;
+ private final Pattern pattern;
+
+ PatternInfo(String regex, Pattern pattern) {
+ this.regex = regex;
+ this.pattern = pattern;
+ }
+
+ public final String regex() {
+ return this.regex;
+ }
+
+ public final Pattern pattern() {
+ return this.pattern;
+ }
+
+ public abstract T matcher(String input);
+}
diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/PatternInfos.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/PatternInfos.java
new file mode 100644
index 0000000..7843109
--- /dev/null
+++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/PatternInfos.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package xyz.zhouxy.plusone.commons.regex;
+
+import java.util.regex.Matcher;
+
+public final class PatternInfos {
+
+ // @see RegexConsts#BASIC_ISO_DATE
+ public static final PatternInfo BASIC_ISO_DATE = new PatternInfo(
+ RegexConsts.BASIC_ISO_DATE,
+ PatternConsts.BASIC_ISO_DATE) {
+ @Override
+ public LocalDateMatcher matcher(String input) {
+ Matcher matcher = pattern().matcher(input);
+ return new LocalDateMatcher(matcher);
+ }
+ };
+
+ // @see RegexConsts#ISO_LOCAL_DATE
+ public static final PatternInfo ISO_LOCAL_DATE = new PatternInfo(
+ RegexConsts.ISO_LOCAL_DATE,
+ PatternConsts.ISO_LOCAL_DATE) {
+ @Override
+ public LocalDateMatcher matcher(String input) {
+ Matcher matcher = pattern().matcher(input);
+ return new LocalDateMatcher(matcher);
+ }
+ };
+
+ // @see RegexConsts#PASSWORD
+ public static final PatternInfo PASSWORD = new PatternInfo(
+ RegexConsts.PASSWORD,
+ PatternConsts.PASSWORD) {
+ @Override
+ public Matcher matcher(String input) {
+ return pattern().matcher(input);
+ }
+ };
+
+ // @see RegexConsts#CAPTCHA
+ public static final PatternInfo CAPTCHA = new PatternInfo(
+ RegexConsts.CAPTCHA,
+ PatternConsts.CAPTCHA) {
+ @Override
+ public Matcher matcher(String input) {
+ return pattern().matcher(input);
+ }
+ };
+
+ // @see RegexConsts#EMAIL
+ public static final PatternInfo EMAIL = new PatternInfo(
+ RegexConsts.EMAIL,
+ PatternConsts.EMAIL) {
+ @Override
+ public Matcher matcher(String input) {
+ return pattern().matcher(input);
+ }
+ };
+
+ public static final PatternInfo MOBILE_PHONE = new PatternInfo(
+ RegexConsts.MOBILE_PHONE,
+ PatternConsts.MOBILE_PHONE) {
+ @Override
+ public Matcher matcher(String input) {
+ return pattern().matcher(input);
+ }
+ };
+
+ public static final PatternInfo USERNAME = new PatternInfo(
+ RegexConsts.USERNAME,
+ PatternConsts.USERNAME) {
+ @Override
+ public Matcher matcher(String input) {
+ return pattern().matcher(input);
+ }
+ };
+
+ public static final PatternInfo NICKNAME = new PatternInfo(
+ RegexConsts.NICKNAME,
+ PatternConsts.NICKNAME) {
+ @Override
+ public Matcher matcher(String input) {
+ return pattern().matcher(input);
+ }
+ };
+
+ public static final PatternInfo CHINESE_2ND_ID_CARD_NUMBER = new PatternInfo(
+ RegexConsts.CHINESE_2ND_ID_CARD_NUMBER,
+ PatternConsts.CHINESE_2ND_ID_CARD_NUMBER) {
+ @Override
+ public Chinese2ndIdCardNumberMatcher matcher(String input) {
+ Matcher matcher = pattern().matcher(input);
+ return new Chinese2ndIdCardNumberMatcher(matcher);
+ }
+ };
+
+ private PatternInfos() {
+ throw new IllegalStateException("Utility class");
+ }
+}
diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/constant/RegexConsts.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/RegexConsts.java
similarity index 90%
rename from plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/constant/RegexConsts.java
rename to plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/RegexConsts.java
index 4cdaa58..2f94e88 100644
--- a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/constant/RegexConsts.java
+++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/RegexConsts.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package xyz.zhouxy.plusone.commons.constant;
+package xyz.zhouxy.plusone.commons.regex;
/**
* 正则表达式常量
@@ -41,12 +41,15 @@ public final class RegexConsts {
public static final String MOBILE_PHONE = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$";
- public static final String USERNAME = "^[\\w-_.@]{4,36}$";
+ public static final String USERNAME = "^[\\w-_]{4,36}$";
public static final String NICKNAME = "^[\\w-_.@]{4,36}$";
public static final String CHINESE_2ND_ID_CARD_NUMBER
- = "^(?(?(?\\d{2})\\d{2})\\d{2})(?\\d{8})\\d{2}(?\\d)([\\dX])$";
+ = "^(?(?(?\\d{2})\\d{2})\\d{2})"
+ + "(?\\d{8})"
+ + "(?\\d{2}(?\\d))"
+ + "(?[\\dX])$";
private RegexConsts() {
throw new IllegalStateException("Utility class");
diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/constant/package-info.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/package-info.java
similarity index 65%
rename from plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/constant/package-info.java
rename to plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/package-info.java
index 0e1cfa9..6bf300e 100644
--- a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/constant/package-info.java
+++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/regex/package-info.java
@@ -14,16 +14,4 @@
* limitations under the License.
*/
-/**
- * 常量
- *
- *
- * 1. 正则常量
- *
- * {@link RegexConsts} 包含常见正则表达式;{@link PatternConsts} 包含对应的 {@link Pattern} 对象。
- *
- * @author ZhouXY
- */
-package xyz.zhouxy.plusone.commons.constant;
-
-import java.util.regex.Pattern;
+package xyz.zhouxy.plusone.commons.regex;
diff --git a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/util/StringTools.java b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/util/StringTools.java
index 3fa2f1b..c5f2dcf 100644
--- a/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/util/StringTools.java
+++ b/plusone-commons/src/main/java/xyz/zhouxy/plusone/commons/util/StringTools.java
@@ -26,7 +26,7 @@ import javax.annotation.Nullable;
import com.google.common.annotations.Beta;
-import xyz.zhouxy.plusone.commons.constant.PatternConsts;
+import xyz.zhouxy.plusone.commons.regex.PatternConsts;
/**
* StringTools
diff --git a/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/model/ValidatableStringRecordTests.java b/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/model/ValidatableStringRecordTests.java
index 7bafc90..70e25d5 100644
--- a/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/model/ValidatableStringRecordTests.java
+++ b/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/model/ValidatableStringRecordTests.java
@@ -31,7 +31,7 @@ import org.slf4j.LoggerFactory;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
import xyz.zhouxy.plusone.commons.annotation.ValueObject;
-import xyz.zhouxy.plusone.commons.constant.PatternConsts;
+import xyz.zhouxy.plusone.commons.regex.PatternConsts;
import java.util.Arrays;
import java.util.Collections;
diff --git a/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/constant/PatternConstsTests.java b/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/regex/PatternConstsTests.java
similarity index 99%
rename from plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/constant/PatternConstsTests.java
rename to plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/regex/PatternConstsTests.java
index 5a216a4..02a258e 100644
--- a/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/constant/PatternConstsTests.java
+++ b/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/regex/PatternConstsTests.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package xyz.zhouxy.plusone.commons.constant;
+package xyz.zhouxy.plusone.commons.regex;
import static org.junit.jupiter.api.Assertions.*;
diff --git a/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/regex/PatternInfosTests.java b/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/regex/PatternInfosTests.java
new file mode 100644
index 0000000..3b6b1f5
--- /dev/null
+++ b/plusone-commons/src/test/java/xyz/zhouxy/plusone/commons/regex/PatternInfosTests.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2024-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package xyz.zhouxy.plusone.commons.regex;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.lang.reflect.Constructor;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import lombok.extern.slf4j.Slf4j;
+import xyz.zhouxy.plusone.commons.model.Gender;
+
+@Slf4j
+public //
+class PatternInfosTests {
+
+ // ================================
+ // #region - BASIC_ISO_DATE
+ // ================================
+
+ @Test
+ void testBasicIsoDate_ValidDate() {
+ LocalDateMatcher localDateMatcher = PatternInfos.BASIC_ISO_DATE.matcher("20241229");
+ Matcher matcher = localDateMatcher.matcher();
+ assertTrue(matcher.matches());
+ assertTrue(localDateMatcher.matches());
+
+ assertEquals("2024", localDateMatcher.getYear());
+ assertEquals("12", localDateMatcher.getMonth());
+ assertEquals("29", localDateMatcher.getDay());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "20231301", // InvalidMonth
+ "20230230", // InvalidDay
+ "20210229", // NonLeapYearFeb29
+ })
+ void testBasicIsoDate_InvalidDate_butMatches(String date) {
+ // 虽然日期有误,但这个正则无法判断。实际工作中,应使用日期时间 API。
+ LocalDateMatcher matcher = PatternInfos.BASIC_ISO_DATE.matcher(date);
+ assertTrue(matcher.matches());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "2023041", // TooShort
+ "99999999990415", // TooLong
+ "2023-04-15", // NonNumeric
+ })
+ void testBasicIsoDate_InvalidDate_Mismatches(String date) {
+ LocalDateMatcher matcher = PatternInfos.BASIC_ISO_DATE.matcher(date);
+ assertFalse(matcher.matches());
+ }
+
+ // ================================
+ // #endregion - BASIC_ISO_DATE
+ // ================================
+
+ // ================================
+ // #region - ISO_LOCAL_DATE
+ // ================================
+
+ @Test
+ void testIsoLocalDate_ValidDate() {
+ LocalDateMatcher matcher = PatternInfos.ISO_LOCAL_DATE.matcher("2024-12-29");
+ assertTrue(matcher.matches());
+ assertEquals("2024", matcher.getYear());
+ assertEquals("12", matcher.getMonth());
+ assertEquals("29", matcher.getDay());
+
+ // LeapYearFeb29()
+ assertTrue(PatternInfos.ISO_LOCAL_DATE.matcher("2020-02-29").matches());
+
+ // BoundaryMin()
+ assertTrue(PatternInfos.ISO_LOCAL_DATE.matcher("0000-01-01").matches());
+
+ // BoundaryMax()
+ assertTrue(PatternInfos.ISO_LOCAL_DATE.matcher("999999999-12-31").matches());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "2023-13-01", // InvalidMonth
+ "2023-02-30", // InvalidDay
+ "2021-02-29", // NonLeapYearFeb29
+ })
+ void testIsoLocalDate_InvalidDate_butMatches(String date) {
+ // 虽然日期有误,但这个正则无法判断。实际工作中,应使用日期时间 API。
+ LocalDateMatcher matcher = PatternInfos.ISO_LOCAL_DATE.matcher(date);
+ assertTrue(matcher.matches());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "2023-04-1", // TooShort
+ "9999999999-04-15", // TooLong
+ "20230415",
+ })
+ void testIsoLocalDate_InvalidDate_Mismatches(String date) {
+ LocalDateMatcher matcher = PatternInfos.ISO_LOCAL_DATE.matcher(date);
+ assertFalse(matcher.matches());
+ }
+
+ // ================================
+ // #endregion - ISO_LOCAL_DATE
+ // ================================
+
+ // ================================
+ // #region - PASSWORD
+ // ================================
+
+ @Test
+ void testPassword_ValidPassword_Matches() {
+ assertTrue(PatternInfos.PASSWORD.matcher("Abc123!@#").matches());
+ }
+
+ @Test
+ void testPassword_InvalidPassword_Mismatches() {
+ assertFalse(PatternInfos.PASSWORD.matcher("Abc123 !@#").matches()); // 带空格
+ assertFalse(PatternInfos.PASSWORD.matcher("Abc123!@# ").matches()); // 带空格
+ assertFalse(PatternInfos.PASSWORD.matcher(" Abc123!@#").matches()); // 带空格
+ assertFalse(PatternInfos.PASSWORD.matcher(" Abc123!@# ").matches()); // 带空格
+ assertFalse(PatternInfos.PASSWORD.matcher("77553366998844113322").matches()); // 纯数字
+ assertFalse(PatternInfos.PASSWORD.matcher("poiujhgbfdsazxcfvghj").matches()); // 纯小写字母
+ assertFalse(PatternInfos.PASSWORD.matcher("POIUJHGBFDSAZXCFVGHJ").matches()); // 纯大写字母
+ assertFalse(PatternInfos.PASSWORD.matcher("!#$%&'*\\+-/=?^`{|}~@()[]\",.;':").matches()); // 纯特殊字符
+ assertFalse(PatternInfos.PASSWORD.matcher("sdfrghbv525842582752").matches()); // 没有小写字母
+ assertFalse(PatternInfos.PASSWORD.matcher("SDFRGHBV525842582752").matches()); // 没有小写字母
+ assertFalse(PatternInfos.PASSWORD.matcher("sdfrghbvSDFRGHBV").matches()); // 没有数字
+ assertFalse(PatternInfos.PASSWORD.matcher("Abc1!").matches()); // 太短
+ assertFalse(PatternInfos.PASSWORD.matcher("Abc1!Abc1!Abc1!Abc1!Abc1!Abc1!Abc1!").matches()); // 太长
+ assertFalse(PatternInfos.PASSWORD.matcher("").matches());
+ assertFalse(PatternInfos.PASSWORD.matcher(" ").matches());
+ }
+
+ // ================================
+ // #endregion - PASSWORD
+ // ================================
+
+ // ================================
+ // #region - EMAIL
+ // ================================
+
+ @Test
+ public void testValidEmails() {
+ assertTrue(PatternInfos.EMAIL.matcher("test@example.com").matches());
+ assertTrue(PatternInfos.EMAIL.matcher("user.name+tag+sorting@example.com").matches());
+ assertTrue(PatternInfos.EMAIL.matcher("user@sub.example.com").matches());
+ assertTrue(PatternInfos.EMAIL.matcher("user@123.123.123.123").matches());
+ }
+
+ @Test
+ public void testInvalidEmails() {
+ assertFalse(PatternInfos.EMAIL.matcher(".username@example.com").matches());
+ assertFalse(PatternInfos.EMAIL.matcher("@missingusername.com").matches());
+ assertFalse(PatternInfos.EMAIL.matcher("plainaddress").matches());
+ assertFalse(PatternInfos.EMAIL.matcher("username..username@example.com").matches());
+ assertFalse(PatternInfos.EMAIL.matcher("username.@example.com").matches());
+ assertFalse(PatternInfos.EMAIL.matcher("username@-example.com").matches());
+ assertFalse(PatternInfos.EMAIL.matcher("username@-example.com").matches());
+ assertFalse(PatternInfos.EMAIL.matcher("username@.com.com").matches());
+ assertFalse(PatternInfos.EMAIL.matcher("username@.com.my").matches());
+ assertFalse(PatternInfos.EMAIL.matcher("username@.com").matches());
+ assertFalse(PatternInfos.EMAIL.matcher("username@com.").matches());
+ assertFalse(PatternInfos.EMAIL.matcher("username@com").matches());
+ assertFalse(PatternInfos.EMAIL.matcher("username@example..com").matches());
+ assertFalse(PatternInfos.EMAIL.matcher("username@example.com-").matches());
+ assertFalse(PatternInfos.EMAIL.matcher("username@example.com.").matches());
+ assertFalse(PatternInfos.EMAIL.matcher("username@example").matches());
+ }
+
+ // ================================
+ // #endregion - EMAIL
+ // ================================
+
+ // ================================
+ // #region - Chinese2ndIdCardNumber
+ // ================================
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "44520019900101456X",
+ "44520019900101456x",
+ "445200199001014566",
+ })
+ void testChinese2ndIdCardNumber_ValidChinese2ndIdCardNumber(String value) {
+ Chinese2ndIdCardNumberMatcher chinese2ndIdCardNumberMatcher = PatternInfos.CHINESE_2ND_ID_CARD_NUMBER.matcher(value);
+ Matcher matcher = chinese2ndIdCardNumberMatcher.matcher();
+ assertTrue(matcher.matches());
+ assertEquals("44", chinese2ndIdCardNumberMatcher.getProvince());
+ assertEquals("4452", chinese2ndIdCardNumberMatcher.getCity());
+ assertEquals("445200", chinese2ndIdCardNumberMatcher.getCounty());
+ assertEquals("19900101", chinese2ndIdCardNumberMatcher.getBirthDate());
+ assertEquals("456", chinese2ndIdCardNumberMatcher.getOrderCode());
+ assertEquals("6", chinese2ndIdCardNumberMatcher.getGenderCode());
+ assertEquals(Gender.FEMALE, chinese2ndIdCardNumberMatcher.getGender());
+
+ String checkDigit = value.substring(value.length() - 1);
+ assertEquals(checkDigit, chinese2ndIdCardNumberMatcher.getCheckDigit());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "4452200199001014566",
+ "44520199001014566",
+ " ",
+ "",
+ })
+ void testChinese2ndIdCardNumber_InvalidChinese2ndIdCardNumber(String value) {
+ assertFalse(PatternInfos.CHINESE_2ND_ID_CARD_NUMBER.matcher(value).matches());
+ }
+
+ // ================================
+ // #endregion - Chinese2ndIdCardNumber
+ // ================================
+
+ // ================================
+ // #region - invoke constructor
+ // ================================
+
+ @Test
+ void test_constructor_isNotAccessible_ThrowsIllegalStateException() {
+ Constructor>[] constructors;
+ constructors = PatternInfos.class.getDeclaredConstructors();
+ Arrays.stream(constructors)
+ .forEach(constructor -> {
+ assertFalse(constructor.isAccessible());
+ constructor.setAccessible(true);
+ Throwable cause = assertThrows(Exception.class, constructor::newInstance)
+ .getCause();
+ assertInstanceOf(IllegalStateException.class, cause);
+ assertEquals("Utility class", cause.getMessage());
+ });
+ }
+
+ // ================================
+ // #endregion - invoke constructor
+ // ================================
+}