Merge remote-tracking branch 'origin/v5-dev' into v5-dev

This commit is contained in:
liuyulin
2022-03-22 14:55:58 +08:00
35 changed files with 1345 additions and 663 deletions

View File

@@ -6,85 +6,28 @@ import cn.hutool.core.util.StrUtil;
import java.nio.charset.Charset;
/**
* Base32 - encodes and decodes RFC3548 Base32 (see http://www.faqs.org/rfcs/rfc3548.html )<br>
* Base32 - encodes and decodes RFC4648 Base32 (see https://datatracker.ietf.org/doc/html/rfc4648#section-6 )<br>
* base32就是用322的5次方个特定ASCII码来表示256个ASCII码。<br>
* 所以5个ASCII字符经过base32编码后会变为8个字符公约数为40长度增加3/5.不足8n用“=”补足。
* see http://blog.csdn.net/earbao/article/details/44453937
* @author Looly
* 所以5个ASCII字符经过base32编码后会变为8个字符公约数为40长度增加3/5.不足8n用“=”补足。<br>
* 根据RFC4648 Base32规范支持两种模式
* <ul>
* <li>Base 32 Alphabet (ABCDEFGHIJKLMNOPQRSTUVWXYZ234567)</li>
* <li>"Extended Hex" Base 32 Alphabet (0123456789ABCDEFGHIJKLMNOPQRSTUV)</li>
* </ul>
*
* @author Looly
*/
public class Base32 {
private Base32() {}
private static final String BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
private static final int[] BASE32_LOOKUP = {//
0xFF, 0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, // '0', '1', '2', '3', '4', '5', '6', '7'
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // '8', '9', ':', ';', '<', '=', '>', '?'
0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G'
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, // 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O'
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, // 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W'
0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 'X', 'Y', 'Z', '[', '\', ']', '^', '_'
0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g'
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, // 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, // 'p', 'q', 'r', 's', 't', 'u', 'v', 'w'
0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 'x', 'y', 'z', '{', '|', '}', '~', 'DEL'
};
private static final int[] BASE32_FILL = {-1, 4, 1, 6, 3};
//----------------------------------------------------------------------------------------- encode
/**
* 编码
*
* @param bytes 数据
* @return base32
*/
public static String encode(final byte[] bytes) {
int i = 0;
int index = 0;
int digit;
int currByte;
int nextByte;
int encodeLen = bytes.length * 8 / 5;
if (encodeLen != 0) {
encodeLen = encodeLen + 1 + BASE32_FILL[(bytes.length * 8) % 5];
}
StringBuilder base32 = new StringBuilder(encodeLen);
while (i < bytes.length) {
// unsign
currByte = (bytes[i] >= 0) ? bytes[i] : (bytes[i] + 256);
/* Is the current digit going to span a byte boundary? */
if (index > 3) {
if ((i + 1) < bytes.length) {
nextByte = (bytes[i + 1] >= 0) ? bytes[i + 1] : (bytes[i + 1] + 256);
} else {
nextByte = 0;
}
digit = currByte & (0xFF >> index);
index = (index + 5) % 8;
digit <<= index;
digit |= nextByte >> (8 - index);
i++;
} else {
digit = (currByte >> (8 - (index + 5))) & 0x1F;
index = (index + 5) % 8;
if (index == 0) {
i++;
}
}
base32.append(BASE32_CHARS.charAt(digit));
}
// 末尾补充不足长度的
while(base32.length() < encodeLen){
base32.append('=');
}
return base32.toString();
return Base32Codec.INSTANCE.encode(bytes);
}
/**
@@ -100,18 +43,7 @@ public class Base32 {
/**
* base32编码
*
* @param source 被编码的base32字符串
* @param charset 字符集
* @return 被加密后的字符串
*/
public static String encode(String source, String charset) {
return encode(StrUtil.bytes(source, charset));
}
/**
* base32编码
*
* @param source 被编码的base32字符串
* @param source 被编码的base32字符串
* @param charset 字符集
* @return 被加密后的字符串
*/
@@ -119,55 +51,47 @@ public class Base32 {
return encode(StrUtil.bytes(source, charset));
}
/**
* 编码
*
* @param bytes 数据Hex模式
* @return base32
*/
public static String encodeHex(final byte[] bytes) {
return Base32Codec.INSTANCE.encode(bytes, true);
}
/**
* base32编码Hex模式
*
* @param source 被编码的base32字符串
* @return 被加密后的字符串
*/
public static String encodeHex(String source) {
return encodeHex(source, CharsetUtil.CHARSET_UTF_8);
}
/**
* base32编码Hex模式
*
* @param source 被编码的base32字符串
* @param charset 字符集
* @return 被加密后的字符串
*/
public static String encodeHex(String source, Charset charset) {
return encodeHex(StrUtil.bytes(source, charset));
}
//----------------------------------------------------------------------------------------- decode
/**
* 解码
*
* @param base32 base32编码
* @return 数据
*/
public static byte[] decode(final String base32) {
int i, index, lookup, offset, digit;
int len = base32.endsWith("=") ? base32.indexOf("=") * 5 / 8 : base32.length() * 5 / 8;
byte[] bytes = new byte[len];
for (i = 0, index = 0, offset = 0; i < base32.length(); i++) {
lookup = base32.charAt(i) - '0';
/* Skip chars outside the lookup table */
if (lookup < 0 || lookup >= BASE32_LOOKUP.length) {
continue;
}
digit = BASE32_LOOKUP[lookup];
/* If this digit is not in the table, ignore it */
if (digit == 0xFF) {
continue;
}
if (index <= 3) {
index = (index + 5) % 8;
if (index == 0) {
bytes[offset] |= digit;
offset++;
if (offset >= bytes.length) {
break;
}
} else {
bytes[offset] |= digit << (8 - index);
}
} else {
index = (index + 5) % 8;
bytes[offset] |= (digit >>> index);
offset++;
if (offset >= bytes.length) {
break;
}
bytes[offset] |= digit << (8 - index);
}
}
return bytes;
public static byte[] decode(String base32) {
return Base32Codec.INSTANCE.decode(base32);
}
/**
@@ -183,22 +107,42 @@ public class Base32 {
/**
* base32解码
*
* @param source 被解码的base32字符串
* @param charset 字符集
* @return 被加密后的字符串
*/
public static String decodeStr(String source, String charset) {
return StrUtil.str(decode(source), charset);
}
/**
* base32解码
*
* @param source 被解码的base32字符串
* @param source 被解码的base32字符串
* @param charset 字符集
* @return 被加密后的字符串
*/
public static String decodeStr(String source, Charset charset) {
return StrUtil.str(decode(source), charset);
}
/**
* 解码
*
* @param base32 base32编码
* @return 数据
*/
public static byte[] decodeHex(String base32) {
return Base32Codec.INSTANCE.decode(base32, true);
}
/**
* base32解码
*
* @param source 被解码的base32字符串
* @return 被加密后的字符串
*/
public static String decodeStrHex(String source) {
return decodeStrHex(source, CharsetUtil.CHARSET_UTF_8);
}
/**
* base32解码
*
* @param source 被解码的base32字符串
* @param charset 字符集
* @return 被加密后的字符串
*/
public static String decodeStrHex(String source, Charset charset) {
return StrUtil.str(decodeHex(source), charset);
}
}

View File

@@ -0,0 +1,215 @@
package cn.hutool.core.codec;
import java.util.Arrays;
/**
* Base32 - encodes and decodes RFC4648 Base32 (see https://datatracker.ietf.org/doc/html/rfc4648#section-6 )<br>
* base32就是用322的5次方个特定ASCII码来表示256个ASCII码。<br>
* 所以5个ASCII字符经过base32编码后会变为8个字符公约数为40长度增加3/5.不足8n用“=”补足。<br>
* 根据RFC4648 Base32规范支持两种模式
* <ul>
* <li>Base 32 Alphabet (ABCDEFGHIJKLMNOPQRSTUVWXYZ234567)</li>
* <li>"Extended Hex" Base 32 Alphabet (0123456789ABCDEFGHIJKLMNOPQRSTUV)</li>
* </ul>
*
* @author Looly
* @since 5.8.0
*/
public class Base32Codec implements Encoder<byte[], String>, Decoder<CharSequence, byte[]> {
public static Base32Codec INSTANCE = new Base32Codec();
@Override
public String encode(byte[] data) {
return encode(data, false);
}
/**
* 编码数据
*
* @param data 数据
* @param useHex 是否使用Hex Alphabet
* @return 编码后的Base32字符串
*/
public String encode(byte[] data, boolean useHex) {
final Base32Encoder encoder = useHex ? Base32Encoder.HEX_ENCODER : Base32Encoder.ENCODER;
return encoder.encode(data);
}
@Override
public byte[] decode(CharSequence encoded) {
return decode(encoded, false);
}
/**
* 解码数据
*
* @param encoded base32字符串
* @param useHex 是否使用Hex Alphabet
* @return 解码后的内容
*/
public byte[] decode(CharSequence encoded, boolean useHex) {
final Base32Decoder decoder = useHex ? Base32Decoder.HEX_DECODER : Base32Decoder.DECODER;
return decoder.decode(encoded);
}
/**
* Bas32编码器
*/
public static class Base32Encoder implements Encoder<byte[], String> {
private static final String DEFAULT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
private static final String HEX_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
private static final Character DEFAULT_PAD = '=';
private static final int[] BASE32_FILL = {-1, 4, 1, 6, 3};
public static final Base32Encoder ENCODER = new Base32Encoder(DEFAULT_ALPHABET, DEFAULT_PAD);
public static final Base32Encoder HEX_ENCODER = new Base32Encoder(HEX_ALPHABET, DEFAULT_PAD);
private final char[] alphabet;
private final Character pad;
/**
* 构造
*
* @param alphabet 自定义编码字母表,见 {@link #DEFAULT_ALPHABET}和 {@link #HEX_ALPHABET}
* @param pad 补位字符
*/
public Base32Encoder(String alphabet, Character pad) {
this.alphabet = alphabet.toCharArray();
this.pad = pad;
}
@Override
public String encode(byte[] data) {
int i = 0;
int index = 0;
int digit;
int currByte;
int nextByte;
int encodeLen = data.length * 8 / 5;
if (encodeLen != 0) {
encodeLen = encodeLen + 1 + BASE32_FILL[(data.length * 8) % 5];
}
StringBuilder base32 = new StringBuilder(encodeLen);
while (i < data.length) {
// unsign
currByte = (data[i] >= 0) ? data[i] : (data[i] + 256);
/* Is the current digit going to span a byte boundary? */
if (index > 3) {
if ((i + 1) < data.length) {
nextByte = (data[i + 1] >= 0) ? data[i + 1] : (data[i + 1] + 256);
} else {
nextByte = 0;
}
digit = currByte & (0xFF >> index);
index = (index + 5) % 8;
digit <<= index;
digit |= nextByte >> (8 - index);
i++;
} else {
digit = (currByte >> (8 - (index + 5))) & 0x1F;
index = (index + 5) % 8;
if (index == 0) {
i++;
}
}
base32.append(alphabet[digit]);
}
if (null != pad) {
// 末尾补充不足长度的
while (base32.length() < encodeLen) {
base32.append(pad.charValue());
}
}
return base32.toString();
}
}
/**
* Base32解码器
*/
public static class Base32Decoder implements Decoder<CharSequence, byte[]> {
private static final char BASE_CHAR = '0';
public static final Base32Decoder DECODER = new Base32Decoder(Base32Encoder.DEFAULT_ALPHABET);
public static final Base32Decoder HEX_DECODER = new Base32Decoder(Base32Encoder.HEX_ALPHABET);
private final byte[] lookupTable;
/**
* 构造
*
* @param alphabet 编码字母表
*/
public Base32Decoder(String alphabet) {
lookupTable = new byte[128];
Arrays.fill(lookupTable, (byte) -1);
final int length = alphabet.length();
char c;
for (int i = 0; i < length; i++) {
c = alphabet.charAt(i);
lookupTable[c - BASE_CHAR] = (byte) i;
// 支持小写字母解码
if(c >= 'A' && c <= 'Z'){
lookupTable[Character.toLowerCase(c) - BASE_CHAR] = (byte) i;
}
}
}
@Override
public byte[] decode(CharSequence encoded) {
int i, index, lookup, offset, digit;
final String base32 = encoded.toString();
int len = base32.endsWith("=") ? base32.indexOf("=") * 5 / 8 : base32.length() * 5 / 8;
byte[] bytes = new byte[len];
for (i = 0, index = 0, offset = 0; i < base32.length(); i++) {
lookup = base32.charAt(i) - BASE_CHAR;
/* Skip chars outside the lookup table */
if (lookup < 0 || lookup >= lookupTable.length) {
continue;
}
digit = lookupTable[lookup];
/* If this digit is not in the table, ignore it */
if (digit < 0) {
continue;
}
if (index <= 3) {
index = (index + 5) % 8;
if (index == 0) {
bytes[offset] |= digit;
offset++;
if (offset >= bytes.length) {
break;
}
} else {
bytes[offset] |= digit << (8 - index);
}
} else {
index = (index + 5) % 8;
bytes[offset] |= (digit >>> index);
offset++;
if (offset >= bytes.length) {
break;
}
bytes[offset] |= digit << (8 - index);
}
}
return bytes;
}
}
}

View File

@@ -15,10 +15,6 @@ public class Base58Codec implements Encoder<byte[], String>, Decoder<CharSequenc
public static Base58Codec INSTANCE = new Base58Codec();
private final char[] alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
private final char ENCODED_ZERO = alphabet[0];
private final int[] lookup = initLookup();
/**
* Base58编码
*
@@ -27,36 +23,7 @@ public class Base58Codec implements Encoder<byte[], String>, Decoder<CharSequenc
*/
@Override
public String encode(byte[] data) {
if (null == data) {
return null;
}
if (data.length == 0) {
return StrUtil.EMPTY;
}
// 计算开头0的个数
int zeroCount = 0;
while (zeroCount < data.length && data[zeroCount] == 0) {
++zeroCount;
}
// 将256位编码转换为58位编码
data = Arrays.copyOf(data, data.length); // since we modify it in-place
final char[] encoded = new char[data.length * 2]; // upper bound
int outputStart = encoded.length;
for (int inputStart = zeroCount; inputStart < data.length; ) {
encoded[--outputStart] = alphabet[divmod(data, inputStart, 256, 58)];
if (data[inputStart] == 0) {
++inputStart; // optimization - skip leading zeros
}
}
// Preserve exactly as many leading encoded zeros in output as there were leading zeros in input.
while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) {
++outputStart;
}
while (--zeroCount >= 0) {
encoded[--outputStart] = ENCODED_ZERO;
}
// Return encoded string (including encoded leading zeros).
return new String(encoded, outputStart, encoded.length - outputStart);
return Base58Encoder.ENCODER.encode(data);
}
/**
@@ -68,52 +35,130 @@ public class Base58Codec implements Encoder<byte[], String>, Decoder<CharSequenc
*/
@Override
public byte[] decode(CharSequence encoded) throws IllegalArgumentException {
if (encoded.length() == 0) {
return new byte[0];
}
// Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits).
final byte[] input58 = new byte[encoded.length()];
for (int i = 0; i < encoded.length(); ++i) {
char c = encoded.charAt(i);
int digit = c < 128 ? lookup[c] : -1;
if (digit < 0) {
throw new IllegalArgumentException(StrUtil.format("Invalid char '{}' at [{}]", c, i));
}
input58[i] = (byte) digit;
}
// Count leading zeros.
int zeros = 0;
while (zeros < input58.length && input58[zeros] == 0) {
++zeros;
}
// Convert base-58 digits to base-256 digits.
byte[] decoded = new byte[encoded.length()];
int outputStart = decoded.length;
for (int inputStart = zeros; inputStart < input58.length; ) {
decoded[--outputStart] = divmod(input58, inputStart, 58, 256);
if (input58[inputStart] == 0) {
++inputStart; // optimization - skip leading zeros
}
}
// Ignore extra leading zeroes that were added during the calculation.
while (outputStart < decoded.length && decoded[outputStart] == 0) {
++outputStart;
}
// Return decoded data (including original number of leading zeros).
return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length);
return Base58Decoder.DECODER.decode(encoded);
}
/**
* 初始化字符序号查找表
* Base58编码器
*
* @return 字符序号查找表
* @since 5.8.0
*/
private int[] initLookup() {
final int[] lookup = new int['z' + 1];
Arrays.fill(lookup, -1);
for (int i = 0; i < alphabet.length; i++)
lookup[alphabet[i]] = i;
return lookup;
public static class Base58Encoder implements Encoder<byte[], String> {
private static final String DEFAULT_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
public static final Base58Encoder ENCODER = new Base58Encoder(DEFAULT_ALPHABET.toCharArray());
private final char[] alphabet;
private final char alphabetZero;
/**
* 构造
*
* @param alphabet 编码字母表
*/
public Base58Encoder(char[] alphabet) {
this.alphabet = alphabet;
alphabetZero = alphabet[0];
}
@Override
public String encode(byte[] data) {
if (null == data) {
return null;
}
if (data.length == 0) {
return StrUtil.EMPTY;
}
// 计算开头0的个数
int zeroCount = 0;
while (zeroCount < data.length && data[zeroCount] == 0) {
++zeroCount;
}
// 将256位编码转换为58位编码
data = Arrays.copyOf(data, data.length); // since we modify it in-place
final char[] encoded = new char[data.length * 2]; // upper bound
int outputStart = encoded.length;
for (int inputStart = zeroCount; inputStart < data.length; ) {
encoded[--outputStart] = alphabet[divmod(data, inputStart, 256, 58)];
if (data[inputStart] == 0) {
++inputStart; // optimization - skip leading zeros
}
}
// Preserve exactly as many leading encoded zeros in output as there were leading zeros in input.
while (outputStart < encoded.length && encoded[outputStart] == alphabetZero) {
++outputStart;
}
while (--zeroCount >= 0) {
encoded[--outputStart] = alphabetZero;
}
// Return encoded string (including encoded leading zeros).
return new String(encoded, outputStart, encoded.length - outputStart);
}
}
/**
* Base58解码器
*
* @since 5.8.0
*/
public static class Base58Decoder implements Decoder<CharSequence, byte[]> {
public static Base58Decoder DECODER = new Base58Decoder(Base58Encoder.DEFAULT_ALPHABET);
private final byte[] lookupTable;
/**
* 构造
*
* @param alphabet 编码字符表
*/
public Base58Decoder(String alphabet) {
final byte[] lookupTable = new byte['z' + 1];
Arrays.fill(lookupTable, (byte) -1);
final int length = alphabet.length();
for (int i = 0; i < length; i++) {
lookupTable[alphabet.charAt(i)] = (byte) i;
}
this.lookupTable = lookupTable;
}
@Override
public byte[] decode(CharSequence encoded) {
if (encoded.length() == 0) {
return new byte[0];
}
// Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits).
final byte[] input58 = new byte[encoded.length()];
for (int i = 0; i < encoded.length(); ++i) {
char c = encoded.charAt(i);
int digit = c < 128 ? lookupTable[c] : -1;
if (digit < 0) {
throw new IllegalArgumentException(StrUtil.format("Invalid char '{}' at [{}]", c, i));
}
input58[i] = (byte) digit;
}
// Count leading zeros.
int zeros = 0;
while (zeros < input58.length && input58[zeros] == 0) {
++zeros;
}
// Convert base-58 digits to base-256 digits.
byte[] decoded = new byte[encoded.length()];
int outputStart = decoded.length;
for (int inputStart = zeros; inputStart < input58.length; ) {
decoded[--outputStart] = divmod(input58, inputStart, 58, 256);
if (input58[inputStart] == 0) {
++inputStart; // optimization - skip leading zeros
}
}
// Ignore extra leading zeroes that were added during the calculation.
while (outputStart < decoded.length && decoded[outputStart] == 0) {
++outputStart;
}
// Return decoded data (including original number of leading zeros).
return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length);
}
}
/**

View File

@@ -1,15 +1,15 @@
package cn.hutool.core.codec;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
/**
* Base62工具类提供Base62的编码和解码方案<br>
*
@@ -19,7 +19,6 @@ import cn.hutool.core.util.StrUtil;
public class Base62 {
private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8;
private static final Base62Codec CODEC = Base62Codec.createGmp();
// -------------------------------------------------------------------- encode
/**
@@ -50,7 +49,7 @@ public class Base62 {
* @return 被加密后的字符串
*/
public static String encode(byte[] source) {
return new String(CODEC.encode(source));
return new String(Base62Codec.INSTANCE.encode(source));
}
/**
@@ -73,6 +72,57 @@ public class Base62 {
return encode(FileUtil.readBytes(file));
}
/**
* Base62编码反转字母表模式
*
* @param source 被编码的Base62字符串
* @return 被加密后的字符串
*/
public static String encodeInverted(CharSequence source) {
return encodeInverted(source, DEFAULT_CHARSET);
}
/**
* Base62编码反转字母表模式
*
* @param source 被编码的Base62字符串
* @param charset 字符集
* @return 被加密后的字符串
*/
public static String encodeInverted(CharSequence source, Charset charset) {
return encodeInverted(StrUtil.bytes(source, charset));
}
/**
* Base62编码反转字母表模式
*
* @param source 被编码的Base62字符串
* @return 被加密后的字符串
*/
public static String encodeInverted(byte[] source) {
return new String(Base62Codec.INSTANCE.encode(source, true));
}
/**
* Base62编码
*
* @param in 被编码Base62的流一般为图片流或者文件流
* @return 被加密后的字符串
*/
public static String encodeInverted(InputStream in) {
return encodeInverted(IoUtil.readBytes(in));
}
/**
* Base62编码反转字母表模式
*
* @param file 被编码Base62的文件
* @return 被加密后的字符串
*/
public static String encodeInverted(File file) {
return encodeInverted(FileUtil.readBytes(file));
}
// -------------------------------------------------------------------- decode
/**
* Base62解码
@@ -144,6 +194,69 @@ public class Base62 {
* @return 解码后的bytes
*/
public static byte[] decode(byte[] base62bytes) {
return CODEC.decode(base62bytes);
return Base62Codec.INSTANCE.decode(base62bytes);
}
/**
* Base62解码反转字母表模式
*
* @param source 被解码的Base62字符串
* @return 被加密后的字符串
*/
public static String decodeStrInverted(CharSequence source) {
return decodeStrInverted(source, DEFAULT_CHARSET);
}
/**
* Base62解码反转字母表模式
*
* @param source 被解码的Base62字符串
* @param charset 字符集
* @return 被加密后的字符串
*/
public static String decodeStrInverted(CharSequence source, Charset charset) {
return StrUtil.str(decodeInverted(source), charset);
}
/**
* Base62解码反转字母表模式
*
* @param Base62 被解码的Base62字符串
* @param destFile 目标文件
* @return 目标文件
*/
public static File decodeToFileInverted(CharSequence Base62, File destFile) {
return FileUtil.writeBytes(decodeInverted(Base62), destFile);
}
/**
* Base62解码反转字母表模式
*
* @param base62Str 被解码的Base62字符串
* @param out 写出到的流
* @param isCloseOut 是否关闭输出流
*/
public static void decodeToStreamInverted(CharSequence base62Str, OutputStream out, boolean isCloseOut) {
IoUtil.write(out, isCloseOut, decodeInverted(base62Str));
}
/**
* Base62解码反转字母表模式
*
* @param base62Str 被解码的Base62字符串
* @return 被加密后的字符串
*/
public static byte[] decodeInverted(CharSequence base62Str) {
return decodeInverted(StrUtil.bytes(base62Str, DEFAULT_CHARSET));
}
/**
* 解码Base62反转字母表模式
*
* @param base62bytes Base62输入
* @return 解码后的bytes
*/
public static byte[] decodeInverted(byte[] base62bytes) {
return Base62Codec.INSTANCE.decode(base62bytes, true);
}
}

View File

@@ -12,73 +12,13 @@ import java.io.Serializable;
* @author Looly, Sebastian Ruhleder, sebastian@seruco.io
* @since 4.5.9
*/
public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byte[]>, Serializable{
public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byte[]>, Serializable {
private static final long serialVersionUID = 1L;
private static final int STANDARD_BASE = 256;
private static final int TARGET_BASE = 62;
/**
* GMP风格
*/
private static final byte[] GMP = { //
'0', '1', '2', '3', '4', '5', '6', '7', //
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F', //
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', //
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', //
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', //
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', //
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', //
'u', 'v', 'w', 'x', 'y', 'z' //
};
/**
* 反转风格即将GMP风格中的大小写做转换
*/
private static final byte[] INVERTED = { //
'0', '1', '2', '3', '4', '5', '6', '7', //
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f', //
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', //
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', //
'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', //
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', //
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', //
'U', 'V', 'W', 'X', 'Y', 'Z' //
};
/**
* 创建GMP风格的Base62编码解码器对象
*
* @return Base62Codec
*/
public static Base62Codec createGmp() {
return new Base62Codec(GMP);
}
/**
* 创建Inverted风格的Base62编码解码器对象
*
* @return Base62Codec
*/
public static Base62Codec createInverted() {
return new Base62Codec(INVERTED);
}
private final byte[] alphabet;
private final byte[] lookup;
/**
* 构造
*
* @param alphabet 自定义字母表
*/
public Base62Codec(byte[] alphabet) {
this.alphabet = alphabet;
lookup = new byte[256];
for (int i = 0; i < alphabet.length; i++) {
lookup[alphabet[i]] = (byte) (i & 0xFF);
}
}
public static Base62Codec INSTANCE = new Base62Codec();
/**
* 编码指定消息bytes为Base62格式的bytes
@@ -88,8 +28,19 @@ public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byt
*/
@Override
public byte[] encode(byte[] data) {
final byte[] indices = convert(data, STANDARD_BASE, TARGET_BASE);
return translate(indices, alphabet);
return encode(data, false);
}
/**
* 编码指定消息bytes为Base62格式的bytes
*
* @param data 被编码的消息
* @param useInverted 是否使用反转风格即将GMP风格中的大小写做转换
* @return Base62内容
*/
public byte[] encode(byte[] data, boolean useInverted) {
final Base62Encoder encoder = useInverted ? Base62Encoder.INVERTED_ENCODER : Base62Encoder.GMP_ENCODER;
return encoder.encode(data);
}
/**
@@ -100,15 +51,114 @@ public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byt
*/
@Override
public byte[] decode(byte[] encoded) {
final byte[] prepared = translate(encoded, lookup);
return convert(prepared, TARGET_BASE, STANDARD_BASE);
return decode(encoded, false);
}
/**
* 解码Base62消息
*
* @param encoded Base62内容
* @param useInverted 是否使用反转风格即将GMP风格中的大小写做转换
* @return 消息
*/
public byte[] decode(byte[] encoded, boolean useInverted) {
final Base62Decoder decoder = useInverted ? Base62Decoder.INVERTED_DECODER : Base62Decoder.GMP_DECODER;
return decoder.decode(encoded);
}
/**
* Base62编码器
*
* @since 5.8.0
*/
public static class Base62Encoder implements Encoder<byte[], byte[]> {
/**
* GMP风格
*/
private static final byte[] GMP = { //
'0', '1', '2', '3', '4', '5', '6', '7', //
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F', //
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', //
'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', //
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', //
'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', //
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', //
'u', 'v', 'w', 'x', 'y', 'z' //
};
/**
* 反转风格即将GMP风格中的大小写做转换
*/
private static final byte[] INVERTED = { //
'0', '1', '2', '3', '4', '5', '6', '7', //
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f', //
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', //
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', //
'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', //
'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', //
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', //
'U', 'V', 'W', 'X', 'Y', 'Z' //
};
public static Base62Encoder GMP_ENCODER = new Base62Encoder(GMP);
public static Base62Encoder INVERTED_ENCODER = new Base62Encoder(INVERTED);
private final byte[] alphabet;
/**
* 构造
*
* @param alphabet 字符表
*/
public Base62Encoder(byte[] alphabet) {
this.alphabet = alphabet;
}
@Override
public byte[] encode(byte[] data) {
final byte[] indices = convert(data, STANDARD_BASE, TARGET_BASE);
return translate(indices, alphabet);
}
}
/**
* Base62解码器
*
* @since 5.8.0
*/
public static class Base62Decoder implements Decoder<byte[], byte[]> {
public static Base62Decoder GMP_DECODER = new Base62Decoder(Base62Encoder.GMP);
public static Base62Decoder INVERTED_DECODER = new Base62Decoder(Base62Encoder.INVERTED);
private final byte[] lookupTable;
/**
* 构造
*
* @param alphabet 字母表
*/
public Base62Decoder(byte[] alphabet) {
lookupTable = new byte['z' + 1];
for (int i = 0; i < alphabet.length; i++) {
lookupTable[alphabet[i]] = (byte) i;
}
}
@Override
public byte[] decode(byte[] encoded) {
final byte[] prepared = translate(encoded, lookupTable);
return convert(prepared, TARGET_BASE, STANDARD_BASE);
}
}
// region Private Methods
/**
* 按照字典转换bytes
*
* @param indices 内容
* @param indices 内容
* @param dictionary 字典
* @return 转换值
*/
@@ -125,7 +175,7 @@ public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byt
/**
* 使用定义的字母表从源基准到目标基准
*
* @param message 消息bytes
* @param message 消息bytes
* @param sourceBase 源基准长度
* @param targetBase 目标基准长度
* @return 计算结果
@@ -171,8 +221,8 @@ public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byt
* 估算结果长度
*
* @param inputLength 输入长度
* @param sourceBase 源基准长度
* @param targetBase 目标基准长度
* @param sourceBase 源基准长度
* @param targetBase 目标基准长度
* @return 估算长度
*/
private static int estimateOutputLength(int inputLength, int sourceBase, int targetBase) {

View File

@@ -217,7 +217,7 @@ public class Base64 {
* @since 5.7.2
*/
public static String encodeStr(byte[] arr, boolean isMultiLine, boolean isUrlSafe) {
return Base64Encoder.encodeStr(arr, isMultiLine, isUrlSafe);
return StrUtil.str(encode(arr, isMultiLine, isUrlSafe), DEFAULT_CHARSET);
}
/**

View File

@@ -1,5 +1,6 @@
package cn.hutool.core.codec;
import cn.hutool.core.lang.mutable.MutableInt;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
@@ -87,7 +88,7 @@ public class Base64Decoder {
return in;
}
final IntWrapper offset = new IntWrapper(pos);
final MutableInt offset = new MutableInt(pos);
byte sestet0;
byte sestet1;
@@ -96,7 +97,7 @@ public class Base64Decoder {
int maxPos = pos + length - 1;
int octetId = 0;
byte[] octet = new byte[length * 3 / 4];// over-estimated if non-base64 characters present
while (offset.value <= maxPos) {
while (offset.intValue() <= maxPos) {
sestet0 = getNextValidDecodeByte(in, offset, maxPos);
sestet1 = getNextValidDecodeByte(in, offset, maxPos);
sestet2 = getNextValidDecodeByte(in, offset, maxPos);
@@ -141,11 +142,12 @@ public class Base64Decoder {
* @param maxPos 最大位置
* @return 有效字符,如果达到末尾返回
*/
private static byte getNextValidDecodeByte(byte[] in, IntWrapper pos, int maxPos) {
private static byte getNextValidDecodeByte(byte[] in, MutableInt pos, int maxPos) {
byte base64Byte;
byte decodeByte;
while (pos.value <= maxPos) {
base64Byte = in[pos.value++];
while (pos.intValue() <= maxPos) {
base64Byte = in[pos.intValue()];
pos.increment();
if (base64Byte > -1) {
decodeByte = DECODE_TABLE[base64Byte];
if (decodeByte > -1) {
@@ -156,19 +158,5 @@ public class Base64Decoder {
// padding if reached max position
return PADDING;
}
/**
* int包装使之可变
*
* @author looly
*
*/
private static class IntWrapper {
int value;
IntWrapper(int value) {
this.value = value;
}
}
// ----------------------------------------------------------------------------------------------- Private end
}

View File

@@ -27,11 +27,11 @@ public class PunyCode {
/**
* 将内容编码为PunyCode
*
* @param input 字符串
* @param input 字符串
* @return PunyCode字符串
* @throws UtilException 计算异常
*/
public static String encode(String input) throws UtilException {
public static String encode(CharSequence input) throws UtilException {
return encode(input, false);
}
@@ -43,7 +43,7 @@ public class PunyCode {
* @return PunyCode字符串
* @throws UtilException 计算异常
*/
public static String encode(String input, boolean withPrefix) throws UtilException {
public static String encode(CharSequence input, boolean withPrefix) throws UtilException {
int n = INITIAL_N;
int delta = 0;
int bias = INITIAL_BIAS;
@@ -112,7 +112,7 @@ public class PunyCode {
n++;
}
if(withPrefix){
if (withPrefix) {
output.insert(0, PUNY_CODE_PREFIX);
}
return output.toString();
@@ -214,6 +214,7 @@ public class PunyCode {
* ...
* 35 -&gt; '9'
* </pre>
*
* @param d 输入字符
* @return 转换后的字符
* @throws UtilException 无效字符
@@ -242,6 +243,7 @@ public class PunyCode {
* ...
* '9' -&gt; 35
* </pre>
*
* @param c 输入字符
* @return 转换后的字符
* @throws UtilException 无效字符

View File

@@ -1756,7 +1756,7 @@ public class DateUtil extends CalendarUtil {
* @return 是否闰年
*/
public static boolean isLeapYear(int year) {
return new GregorianCalendar().isLeapYear(year);
return Year.isLeap(year);
}
/**

View File

@@ -29,7 +29,7 @@ import cn.hutool.core.lang.func.Func0;
import cn.hutool.core.lang.func.VoidFunc0;
import cn.hutool.core.util.StrUtil;
import java.util.List;
import java.util.Collection;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
@@ -108,7 +108,7 @@ public class Opt<T> {
* @return 一个包裹里元素可能为空的 {@code Opt}
* @since 5.7.17
*/
public static <T> Opt<List<T>> ofEmptyAble(List<T> value) {
public static <T, R extends Collection<T>> Opt<R> ofEmptyAble(R value) {
return CollectionUtil.isEmpty(value) ? empty() : new Opt<>(value);
}

View File

@@ -24,6 +24,18 @@ public class LazyFunLoader<T> extends LazyLoader<T> {
*/
private Supplier<T> supplier;
/**
* 静态工厂方法,提供语义性与编码便利性
* @param supplier 用于生成对象的函数
* @param <T> 对象类型
* @return 函数式懒加载加载器对象
* @since 5.8.0
*/
public static <T> LazyFunLoader<T> on(final Supplier<T> supplier) {
Assert.notNull(supplier, "supplier must be not null!");
return new LazyFunLoader<>(supplier);
}
/**
* 构造
*

View File

@@ -152,12 +152,12 @@ public class MutableInt extends Number implements Comparable<MutableInt>, Mutabl
* 相等需同时满足如下条件:
* <ol>
* <li>非空</li>
* <li>类型为 {@link MutableInt}</li>
* <li>类型为 MutableInt</li>
* <li>值相等</li>
* </ol>
*
* @param obj 比对的对象
* @return 相同返回<code>true</code>,否则 <code>false</code>
* @return 相同返回<code>true</code>,否则 {@code false}
*/
@Override
public boolean equals(final Object obj) {
@@ -176,7 +176,7 @@ public class MutableInt extends Number implements Comparable<MutableInt>, Mutabl
/**
* 比较
*
* @param other 其它 {@link MutableInt} 对象
* @param other 其它 MutableInt 对象
* @return x==y返回0x&lt;y返回-1x&gt;y返回1
*/
@Override

View File

@@ -137,6 +137,7 @@ public class AntPathMatcher {
*
* @param cachePatterns 是否缓存表达式
* @see #getStringMatcher(String)
* @return this
*/
public AntPathMatcher setCachePatterns(boolean cachePatterns) {
this.cachePatterns = cachePatterns;

View File

@@ -1,6 +1,7 @@
package cn.hutool.core.codec;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import org.junit.Assert;
import org.junit.Test;
@@ -14,6 +15,24 @@ public class Base32Test {
String decodeStr = Base32.decodeStr(encode);
Assert.assertEquals(a, decodeStr);
// 支持小写模式解码
decodeStr = Base32.decodeStr(encode.toLowerCase());
Assert.assertEquals(a, decodeStr);
}
@Test
public void hexEncodeAndDecodeTest(){
String a = "伦家是一个非常长的字符串";
String encode = Base32.encodeHex(StrUtil.utf8Bytes(a));
Assert.assertEquals("SIUADPDEMRJ9HBV4N20E9E5AT6EPTPDON3KPBFV7JA2EBBCNSUMADP5OM8======", encode);
String decodeStr = Base32.decodeStrHex(encode);
Assert.assertEquals(a, decodeStr);
// 支持小写模式解码
decodeStr = Base32.decodeStrHex(encode.toLowerCase());
Assert.assertEquals(a, decodeStr);
}
@Test

View File

@@ -1,5 +1,6 @@
package cn.hutool.core.codec;
import cn.hutool.core.util.RandomUtil;
import org.junit.Assert;
import org.junit.Test;
@@ -20,4 +21,30 @@ public class Base62Test {
String decodeStr = Base62.decodeStr(encode);
Assert.assertEquals(a, decodeStr);
}
@Test
public void encodeAndDecodeInvertedTest() {
String a = "伦家是一个非常长的字符串66";
String encode = Base62.encodeInverted(a);
Assert.assertEquals("17Vku8w4jmg8Dqf8LK9vnNKDmoEwN4RjmVA6f0xSlRRt53IkbNQO", encode);
String decodeStr = Base62.decodeStrInverted(encode);
Assert.assertEquals(a, decodeStr);
}
@Test
public void encodeAndDecodeRandomTest() {
String a = RandomUtil.randomString(RandomUtil.randomInt(1000));
String encode = Base62.encode(a);
String decodeStr = Base62.decodeStr(encode);
Assert.assertEquals(a, decodeStr);
}
@Test
public void encodeAndDecodeInvertedRandomTest() {
String a = RandomUtil.randomString(RandomUtil.randomInt(1000));
String encode = Base62.encodeInverted(a);
String decodeStr = Base62.decodeStrInverted(encode);
Assert.assertEquals(a, decodeStr);
}
}

View File

@@ -42,4 +42,32 @@ public class LazyFunLoaderTest {
Assert.assertFalse(loader.isInitialize());
}
@Test
public void testOnLoadStaticFactoryMethod1() {
LazyFunLoader<BigObject> loader = LazyFunLoader.on(BigObject::new);
Assert.assertNotNull(loader.get());
Assert.assertTrue(loader.isInitialize());
// 对于某些对象,在程序关闭时,需要进行销毁操作
loader.ifInitialized(BigObject::destroy);
Assert.assertTrue(loader.get().isDestroy);
}
@Test
public void testOnLoadStaticFactoryMethod2() {
LazyFunLoader<BigObject> loader = LazyFunLoader.on(BigObject::new);
// 若从未使用,则可以避免不必要的初始化
loader.ifInitialized(it -> {
Assert.fail();
it.destroy();
});
}
}