diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/PunyCode.java b/hutool-core/src/main/java/cn/hutool/core/codec/PunyCode.java index 23ce39449..babe8a98f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/codec/PunyCode.java +++ b/hutool-core/src/main/java/cn/hutool/core/codec/PunyCode.java @@ -3,6 +3,9 @@ package cn.hutool.core.codec; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Assert; import cn.hutool.core.text.StrUtil; +import cn.hutool.core.util.CharUtil; + +import java.util.List; /** * Punycode是一个根据RFC 3492标准而制定的编码系统,主要用于把域名从地方语言所采用的Unicode编码转换成为可用于DNS系统的编码 @@ -24,6 +27,27 @@ public class PunyCode { public static final String PUNY_CODE_PREFIX = "xn--"; + /** + * 将域名编码为PunyCode,会忽略"."的编码 + * + * @param domain 域名 + * @return 编码后的域名 + * @throws UtilException 计算异常 + */ + public static String encodeDomain(final String domain) throws UtilException { + Assert.notNull(domain, "domain must not be null!"); + final List split = StrUtil.split(domain, CharUtil.DOT); + final StringBuilder result = new StringBuilder(domain.length() * 4); + for (final String str : split) { + if (result.length() != 0) { + result.append(CharUtil.DOT); + } + result.append(encode(str, true)); + } + + return result.toString(); + } + /** * 将内容编码为PunyCode * @@ -48,9 +72,9 @@ public class PunyCode { int n = INITIAL_N; int delta = 0; int bias = INITIAL_BIAS; - final StringBuilder output = new StringBuilder(); - // Copy all basic code points to the output final int length = input.length(); + final StringBuilder output = new StringBuilder(length * 4); + // Copy all basic code points to the output int b = 0; for (int i = 0; i < length; i++) { final char c = input.charAt(i); @@ -119,6 +143,27 @@ public class PunyCode { return output.toString(); } + /** + * 解码 PunyCode为域名 + * + * @param domain 域名 + * @return 解码后的域名 + * @throws UtilException 计算异常 + */ + public static String decodeDomain(final String domain) throws UtilException { + Assert.notNull(domain, "domain must not be null!"); + final List split = StrUtil.split(domain, CharUtil.DOT); + final StringBuilder result = new StringBuilder(domain.length() / 4 + 1); + for (final String str : split) { + if (result.length() != 0) { + result.append(CharUtil.DOT); + } + result.append(decode(str)); + } + + return result.toString(); + } + /** * 解码 PunyCode为字符串 * @@ -133,7 +178,8 @@ public class PunyCode { int n = INITIAL_N; int i = 0; int bias = INITIAL_BIAS; - final StringBuilder output = new StringBuilder(); + final int length = input.length(); + final StringBuilder output = new StringBuilder(length / 4 + 1); int d = input.lastIndexOf(DELIMITER); if (d > 0) { for (int j = 0; j < d; j++) { @@ -146,7 +192,6 @@ public class PunyCode { } else { d = 0; } - final int length = input.length(); while (d < length) { final int oldi = i; int w = 1; diff --git a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java index b4265ca54..a267f954d 100755 --- a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java @@ -8,6 +8,7 @@ import cn.hutool.core.lang.func.Func1; import cn.hutool.core.math.NumberUtil; import cn.hutool.core.regex.ReUtil; import cn.hutool.core.text.finder.CharFinder; +import cn.hutool.core.text.finder.CharMatcherFinder; import cn.hutool.core.text.finder.Finder; import cn.hutool.core.text.finder.StrFinder; import cn.hutool.core.text.split.SplitUtil; @@ -743,11 +744,11 @@ public class CharSequenceUtil extends StrChecker { * @return 字符串含有非检查的字符,返回false * @since 4.4.1 */ - public static boolean containsAll(CharSequence str, CharSequence... testChars) { + public static boolean containsAll(final CharSequence str, final CharSequence... testChars) { if (isBlank(str) || ArrayUtil.isEmpty(testChars)) { return false; } - for (CharSequence testChar : testChars) { + for (final CharSequence testChar : testChars) { if (false == contains(str, testChar)) { return false; } @@ -800,6 +801,23 @@ public class CharSequenceUtil extends StrChecker { return new CharFinder(searchChar).setText(text).setEndIndex(end).start(start); } + /** + * 指定范围内查找指定字符 + * + * @param text 字符串 + * @param matcher 被查找的字符匹配器 + * @param start 起始位置,如果小于0,从0开始查找 + * @param end 终止位置,如果超过str.length()则默认查找到字符串末尾 + * @return 位置 + * @since 6.0.0 + */ + public static int indexOf(final CharSequence text, final Predicate matcher, final int start, final int end) { + if (isEmpty(text)) { + return INDEX_NOT_FOUND; + } + return new CharMatcherFinder(matcher).setText(text).setEndIndex(end).start(start); + } + /** * 指定范围内查找字符串,忽略大小写
* @@ -3182,6 +3200,46 @@ public class CharSequenceUtil extends StrChecker { // ------------------------------------------------------------------------ replace + /** + * 替换字符串中第一个指定字符串 + * + * @param str 字符串 + * @param searchStr 被查找的字符串 + * @param replacedStr 被替换的字符串 + * @param ignoreCase 是否忽略大小写 + * @return 替换后的字符串 + */ + public static String replaceFirst(final CharSequence str, final CharSequence searchStr, final CharSequence replacedStr, final boolean ignoreCase) { + if (isEmpty(str)) { + return str(str); + } + final int startInclude = indexOf(str, searchStr, 0, ignoreCase); + if (INDEX_NOT_FOUND == startInclude) { + return str(str); + } + return replace(str, startInclude, startInclude + searchStr.length(), replacedStr); + } + + /** + * 替换字符串中最后一个指定字符串 + * + * @param str 字符串 + * @param searchStr 被查找的字符串 + * @param replacedStr 被替换的字符串 + * @param ignoreCase 是否忽略大小写 + * @return 替换后的字符串 + */ + public static String replaceLast(final CharSequence str, final CharSequence searchStr, final CharSequence replacedStr, final boolean ignoreCase) { + if (isEmpty(str)) { + return str(str); + } + final int lastIndex = lastIndexOf(str, searchStr, str.length(), ignoreCase); + if (INDEX_NOT_FOUND == lastIndex) { + return str(str); + } + return replace(str, lastIndex, searchStr, replacedStr, ignoreCase); + } + /** * 替换字符串中的指定字符串,忽略大小写 * diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/PunyCodeTest.java b/hutool-core/src/test/java/cn/hutool/core/codec/PunyCodeTest.java index c9354b6cb..95d267ffe 100644 --- a/hutool-core/src/test/java/cn/hutool/core/codec/PunyCodeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/codec/PunyCodeTest.java @@ -6,7 +6,7 @@ import org.junit.Test; public class PunyCodeTest { @Test - public void encodeDecodeTest(){ + public void encodeDecodeTest() { final String text = "Hutool编码器"; final String strPunyCode = PunyCode.encode(text); Assert.assertEquals("Hutool-ux9js33tgln", strPunyCode); @@ -15,4 +15,37 @@ public class PunyCodeTest { decode = PunyCode.decode("xn--Hutool-ux9js33tgln"); Assert.assertEquals(text, decode); } + + @Test + public void encodeDecodeDomainTest() { + // 全中文 + final String text = "百度.中国"; + final String strPunyCode = PunyCode.encodeDomain(text); + Assert.assertEquals("xn--wxtr44c.xn--fiqs8s", strPunyCode); + + final String decode = PunyCode.decodeDomain(strPunyCode); + Assert.assertEquals(text, decode); + } + + @Test + public void encodeDecodeDomainTest2() { + // 中英文分段 + final String text = "hutool.中国"; + final String strPunyCode = PunyCode.encodeDomain(text); + Assert.assertEquals("xn--hutool-.xn--fiqs8s", strPunyCode); + + final String decode = PunyCode.decodeDomain(strPunyCode); + Assert.assertEquals(text, decode); + } + + @Test + public void encodeDecodeDomainTest3() { + // 中英文混合 + final String text = "hutool工具.中国"; + final String strPunyCode = PunyCode.encodeDomain(text); + Assert.assertEquals("xn--hutool-up2j943f.xn--fiqs8s", strPunyCode); + + final String decode = PunyCode.decodeDomain(strPunyCode); + Assert.assertEquals(text, decode); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java index 8da4744b3..3ae6f70a1 100755 --- a/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java @@ -195,4 +195,18 @@ public class CharSequenceUtilTest { (v) -> DateUtil.parse(v, DatePattern.NORM_DATETIME_PATTERN).toInstant(), Instant::now); Assert.assertNotNull(result2); } + + @Test + public void replaceLastTest() { + final String str = "i am jack and jack"; + final String result = StrUtil.replaceLast(str, "JACK", null, true); + Assert.assertEquals(result, "i am jack and "); + } + + @Test + public void replaceFirstTest() { + final String str = "yes and yes i do"; + final String result = StrUtil.replaceFirst(str, "YES", "", true); + Assert.assertEquals(result, " and yes i do"); + } }