Commit 45cda665 authored by ma yanling's avatar ma yanling
Browse files

project commit

parent ad2fb30a
Pipeline #2354 failed with stages
in 0 seconds
package cn.hutool.core.clone;
/**
* 克隆支持类,提供默认的克隆方法
* @author Looly
*
* @param <T> 继承类的类型
*/
public class CloneSupport<T> implements Cloneable<T>{
@SuppressWarnings("unchecked")
@Override
public T clone() {
try {
return (T) super.clone();
} catch (CloneNotSupportedException e) {
throw new CloneRuntimeException(e);
}
}
}
package cn.hutool.core.clone;
/**
* 克隆支持接口
* @author Looly
*
* @param <T> 实现克隆接口的类型
*/
public interface Cloneable<T> extends java.lang.Cloneable{
/**
* 克隆当前对象,浅复制
* @return 克隆后的对象
*/
T clone();
}
package cn.hutool.core.clone;
import cn.hutool.core.util.ReflectUtil;
/**
* 克隆默认实现接口,用于实现返回指定泛型类型的克隆方法
*
* @param <T> 泛型类型
* @since 5.7.17
*/
public interface DefaultCloneable<T> extends java.lang.Cloneable {
/**
* 浅拷贝,提供默认的泛型返回值的clone方法。
*
* @return obj
*/
default T clone0() {
try {
return ReflectUtil.invoke(this, "clone");
} catch (Exception e) {
throw new CloneRuntimeException(e);
}
}
}
/**
* 克隆封装
*
* @author looly
*
*/
package cn.hutool.core.clone;
\ No newline at end of file
package cn.hutool.core.codec;
import cn.hutool.core.lang.Assert;
/**
* BCD码(Binary-Coded Decimal)亦称二进码十进数或二-十进制代码<br>
* BCD码这种编码形式利用了四个位元来储存一个十进制的数码,使二进制和十进制之间的转换得以快捷的进行<br>
* see http://cuisuqiang.iteye.com/blog/1429956
* @author Looly
*
* @deprecated 由于对于ASCII的编码解码有缺陷,且这种BCD实现并不规范,因此会在6.0.0中移除
*/
@Deprecated
public class BCD {
/**
* 字符串转BCD码
* @param asc ASCII字符串
* @return BCD
*/
public static byte[] strToBcd(String asc) {
Assert.notNull(asc, "ASCII must not be null!");
int len = asc.length();
int mod = len % 2;
if (mod != 0) {
asc = "0" + asc;
len = asc.length();
}
byte[] abt;
if (len >= 2) {
len >>= 1;
}
byte[] bbt;
bbt = new byte[len];
abt = asc.getBytes();
int j;
int k;
for (int p = 0; p < asc.length() / 2; p++) {
if ((abt[2 * p] >= '0') && (abt[2 * p] <= '9')) {
j = abt[2 * p] - '0';
} else if ((abt[2 * p] >= 'a') && (abt[2 * p] <= 'z')) {
j = abt[2 * p] - 'a' + 0x0a;
} else {
j = abt[2 * p] - 'A' + 0x0a;
}
if ((abt[2 * p + 1] >= '0') && (abt[2 * p + 1] <= '9')) {
k = abt[2 * p + 1] - '0';
} else if ((abt[2 * p + 1] >= 'a') && (abt[2 * p + 1] <= 'z')) {
k = abt[2 * p + 1] - 'a' + 0x0a;
} else {
k = abt[2 * p + 1] - 'A' + 0x0a;
}
int a = (j << 4) + k;
byte b = (byte) a;
bbt[p] = b;
}
return bbt;
}
/**
* ASCII转BCD
* @param ascii ASCII byte数组
* @return BCD
*/
public static byte[] ascToBcd(byte[] ascii) {
Assert.notNull(ascii, "Ascii must be not null!");
return ascToBcd(ascii, ascii.length);
}
/**
* ASCII转BCD
* @param ascii ASCII byte数组
* @param ascLength 长度
* @return BCD
*/
public static byte[] ascToBcd(byte[] ascii, int ascLength) {
Assert.notNull(ascii, "Ascii must be not null!");
byte[] bcd = new byte[ascLength / 2];
int j = 0;
for (int i = 0; i < (ascLength + 1) / 2; i++) {
bcd[i] = ascToBcd(ascii[j++]);
bcd[i] = (byte) (((j >= ascLength) ? 0x00 : ascToBcd(ascii[j++])) + (bcd[i] << 4));
}
return bcd;
}
/**
* BCD转ASCII字符串
* @param bytes BCD byte数组
* @return ASCII字符串
*/
public static String bcdToStr(byte[] bytes) {
Assert.notNull(bytes, "Bcd bytes must be not null!");
char[] temp = new char[bytes.length * 2];
char val;
for (int i = 0; i < bytes.length; i++) {
val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f);
temp[i * 2] = (char) (val > 9 ? val + 'A' - 10 : val + '0');
val = (char) (bytes[i] & 0x0f);
temp[i * 2 + 1] = (char) (val > 9 ? val + 'A' - 10 : val + '0');
}
return new String(temp);
}
//----------------------------------------------------------------- Private method start
/**
* 转换单个byte为BCD
* @param asc ACSII
* @return BCD
*/
private static byte ascToBcd(byte asc) {
byte bcd;
if ((asc >= '0') && (asc <= '9')) {
bcd = (byte) (asc - '0');
}else if ((asc >= 'A') && (asc <= 'F')) {
bcd = (byte) (asc - 'A' + 10);
}else if ((asc >= 'a') && (asc <= 'f')) {
bcd = (byte) (asc - 'a' + 10);
}else {
bcd = (byte) (asc - 48);
}
return bcd;
}
//----------------------------------------------------------------- Private method end
}
package cn.hutool.core.codec;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.util.StrUtil;
/**
* Base16(Hex)编码解码器<br>
* 十六进制(简写为hex或下标16)在数学中是一种逢16进1的进位制,一般用数字0到9和字母A到F表示(其中:A~F即10~15)。<br>
* 例如十进制数57,在二进制写作111001,在16进制写作39。
*
* @author looly
* @since 5.7.23
*/
public class Base16Codec implements Encoder<byte[], char[]>, Decoder<CharSequence, byte[]> {
public static final Base16Codec CODEC_LOWER = new Base16Codec(true);
public static final Base16Codec CODEC_UPPER = new Base16Codec(false);
private final char[] alphabets;
/**
* 构造
*
* @param lowerCase 是否小写
*/
public Base16Codec(boolean lowerCase) {
this.alphabets = (lowerCase ? "0123456789abcdef" : "0123456789ABCDEF").toCharArray();
}
@Override
public char[] encode(byte[] data) {
final int len = data.length;
final char[] out = new char[len << 1];//len*2
// two characters from the hex value.
for (int i = 0, j = 0; i < len; i++) {
out[j++] = alphabets[(0xF0 & data[i]) >>> 4];// 高位
out[j++] = alphabets[0x0F & data[i]];// 低位
}
return out;
}
@Override
public byte[] decode(CharSequence encoded) {
if (StrUtil.isEmpty(encoded)) {
return null;
}
encoded = StrUtil.cleanBlank(encoded);
int len = encoded.length();
if ((len & 0x01) != 0) {
// 如果提供的数据是奇数长度,则前面补0凑偶数
encoded = "0" + encoded;
len = encoded.length();
}
final byte[] out = new byte[len >> 1];
// two characters form the hex value.
for (int i = 0, j = 0; j < len; i++) {
int f = toDigit(encoded.charAt(j), j) << 4;
j++;
f = f | toDigit(encoded.charAt(j), j);
j++;
out[i] = (byte) (f & 0xFF);
}
return out;
}
/**
* 将指定char值转换为Unicode字符串形式,常用于特殊字符(例如汉字)转Unicode形式<br>
* 转换的字符串如果u后不足4位,则前面用0填充,例如:
*
* <pre>
* '你' =》'\u4f60'
* </pre>
*
* @param ch char值
* @return Unicode表现形式
*/
public String toUnicodeHex(char ch) {
return "\\u" +//
alphabets[(ch >> 12) & 15] +//
alphabets[(ch >> 8) & 15] +//
alphabets[(ch >> 4) & 15] +//
alphabets[(ch) & 15];
}
/**
* 将byte值转为16进制并添加到{@link StringBuilder}中
*
* @param builder {@link StringBuilder}
* @param b byte
*/
public void appendHex(StringBuilder builder, byte b) {
int high = (b & 0xf0) >>> 4;//高位
int low = b & 0x0f;//低位
builder.append(alphabets[high]);
builder.append(alphabets[low]);
}
/**
* 将十六进制字符转换成一个整数
*
* @param ch 十六进制char
* @param index 十六进制字符在字符数组中的位置
* @return 一个整数
* @throws UtilException 当ch不是一个合法的十六进制字符时,抛出运行时异常
*/
private static int toDigit(char ch, int index) {
int digit = Character.digit(ch, 16);
if (digit < 0) {
throw new UtilException("Illegal hexadecimal character {} at index {}", ch, index);
}
return digit;
}
}
package cn.hutool.core.codec;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import java.nio.charset.Charset;
/**
* Base32 - encodes and decodes RFC4648 Base32 (see https://datatracker.ietf.org/doc/html/rfc4648#section-6 )<br>
* base32就是用32(2的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
*/
public class Base32 {
//----------------------------------------------------------------------------------------- encode
/**
* 编码
*
* @param bytes 数据
* @return base32
*/
public static String encode(final byte[] bytes) {
return Base32Codec.INSTANCE.encode(bytes);
}
/**
* base32编码
*
* @param source 被编码的base32字符串
* @return 被加密后的字符串
*/
public static String encode(String source) {
return encode(source, CharsetUtil.CHARSET_UTF_8);
}
/**
* base32编码
*
* @param source 被编码的base32字符串
* @param charset 字符集
* @return 被加密后的字符串
*/
public static String encode(String source, Charset charset) {
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(String base32) {
return Base32Codec.INSTANCE.decode(base32);
}
/**
* base32解码
*
* @param source 被解码的base32字符串
* @return 被加密后的字符串
*/
public static String decodeStr(String source) {
return decodeStr(source, CharsetUtil.CHARSET_UTF_8);
}
/**
* 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);
}
}
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就是用32(2的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;
}
}
}
package cn.hutool.core.codec;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.exceptions.ValidateException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* Base58工具类,提供Base58的编码和解码方案<br>
* 参考: https://github.com/Anujraval24/Base58Encoding<br>
* 规范见:https://en.bitcoin.it/wiki/Base58Check_encoding
*
* @author lin, looly
* @since 5.7.22
*/
public class Base58 {
private static final int CHECKSUM_SIZE = 4;
// -------------------------------------------------------------------- encode
/**
* Base58编码<br>
* 包含版本位和校验位
*
* @param version 编码版本,{@code null}表示不包含版本位
* @param data 被编码的数组,添加校验和。
* @return 编码后的字符串
*/
public static String encodeChecked(Integer version, byte[] data) {
return encode(addChecksum(version, data));
}
/**
* Base58编码
*
* @param data 被编码的数据,不带校验和。
* @return 编码后的字符串
*/
public static String encode(byte[] data) {
return Base58Codec.INSTANCE.encode(data);
}
// -------------------------------------------------------------------- decode
/**
* Base58解码<br>
* 解码包含标志位验证和版本呢位去除
*
* @param encoded 被解码的base58字符串
* @return 解码后的bytes
* @throws ValidateException 标志位验证错误抛出此异常
*/
public static byte[] decodeChecked(CharSequence encoded) throws ValidateException {
try {
return decodeChecked(encoded, true);
} catch (ValidateException ignore) {
return decodeChecked(encoded, false);
}
}
/**
* Base58解码<br>
* 解码包含标志位验证和版本呢位去除
*
* @param encoded 被解码的base58字符串
* @param withVersion 是否包含版本位
* @return 解码后的bytes
* @throws ValidateException 标志位验证错误抛出此异常
*/
public static byte[] decodeChecked(CharSequence encoded, boolean withVersion) throws ValidateException {
byte[] valueWithChecksum = decode(encoded);
return verifyAndRemoveChecksum(valueWithChecksum, withVersion);
}
/**
* Base58解码
*
* @param encoded 被编码的base58字符串
* @return 解码后的bytes
*/
public static byte[] decode(CharSequence encoded) {
return Base58Codec.INSTANCE.decode(encoded);
}
/**
* 验证并去除验证位和版本位
*
* @param data 编码的数据
* @param withVersion 是否包含版本位
* @return 载荷数据
*/
private static byte[] verifyAndRemoveChecksum(byte[] data, boolean withVersion) {
final byte[] payload = Arrays.copyOfRange(data, withVersion ? 1 : 0, data.length - CHECKSUM_SIZE);
final byte[] checksum = Arrays.copyOfRange(data, data.length - CHECKSUM_SIZE, data.length);
final byte[] expectedChecksum = checksum(payload);
if (false == Arrays.equals(checksum, expectedChecksum)) {
throw new ValidateException("Base58 checksum is invalid");
}
return payload;
}
/**
* 数据 + 校验码
*
* @param version 版本,{@code null}表示不添加版本位
* @param payload Base58数据(不含校验码)
* @return Base58数据
*/
private static byte[] addChecksum(Integer version, byte[] payload) {
final byte[] addressBytes;
if (null != version) {
addressBytes = new byte[1 + payload.length + CHECKSUM_SIZE];
addressBytes[0] = (byte) version.intValue();
System.arraycopy(payload, 0, addressBytes, 1, payload.length);
} else {
addressBytes = new byte[payload.length + CHECKSUM_SIZE];
System.arraycopy(payload, 0, addressBytes, 0, payload.length);
}
final byte[] checksum = checksum(payload);
System.arraycopy(checksum, 0, addressBytes, addressBytes.length - CHECKSUM_SIZE, CHECKSUM_SIZE);
return addressBytes;
}
/**
* 获取校验码<br>
* 计算规则为对数据进行两次sha256计算,然后取{@link #CHECKSUM_SIZE}长度
*
* @param data 数据
* @return 校验码
*/
private static byte[] checksum(byte[] data) {
byte[] hash = hash256(hash256(data));
return Arrays.copyOfRange(hash, 0, CHECKSUM_SIZE);
}
/**
* 计算数据的SHA-256值
*
* @param data 数据
* @return sha-256值
*/
private static byte[] hash256(byte[] data) {
try {
return MessageDigest.getInstance("SHA-256").digest(data);
} catch (NoSuchAlgorithmException e) {
throw new UtilException(e);
}
}
}
package cn.hutool.core.codec;
import cn.hutool.core.util.StrUtil;
import java.util.Arrays;
/**
* Base58编码器<br>
* 此编码器不包括校验码、版本等信息
*
* @author lin, looly
* @since 5.7.22
*/
public class Base58Codec implements Encoder<byte[], String>, Decoder<CharSequence, byte[]> {
public static Base58Codec INSTANCE = new Base58Codec();
/**
* Base58编码
*
* @param data 被编码的数据,不带校验和。
* @return 编码后的字符串
*/
@Override
public String encode(byte[] data) {
return Base58Encoder.ENCODER.encode(data);
}
/**
* 解码给定的Base58字符串
*
* @param encoded Base58编码字符串
* @return 解码后的bytes
* @throws IllegalArgumentException 非标准Base58字符串
*/
@Override
public byte[] decode(CharSequence encoded) throws IllegalArgumentException {
return Base58Decoder.DECODER.decode(encoded);
}
/**
* Base58编码器
*
* @since 5.8.0
*/
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);
}
}
/**
* Divides a number, represented as an array of bytes each containing a single digit
* in the specified base, by the given divisor. The given number is modified in-place
* to contain the quotient, and the return value is the remainder.
*
* @param number the number to divide
* @param firstDigit the index within the array of the first non-zero digit
* (this is used for optimization by skipping the leading zeros)
* @param base the base in which the number's digits are represented (up to 256)
* @param divisor the number to divide by (up to 256)
* @return the remainder of the division operation
*/
private static byte divmod(byte[] number, int firstDigit, int base, int divisor) {
// this is just long division which accounts for the base of the input digits
int remainder = 0;
for (int i = firstDigit; i < number.length; i++) {
int digit = (int) number[i] & 0xFF;
int temp = remainder * base + digit;
number[i] = (byte) (temp / divisor);
remainder = temp % divisor;
}
return (byte) remainder;
}
}
package cn.hutool.core.codec;
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>
*
* @author Looly
* @since 4.5.9
*/
public class Base62 {
private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8;
// -------------------------------------------------------------------- encode
/**
* Base62编码
*
* @param source 被编码的Base62字符串
* @return 被加密后的字符串
*/
public static String encode(CharSequence source) {
return encode(source, DEFAULT_CHARSET);
}
/**
* Base62编码
*
* @param source 被编码的Base62字符串
* @param charset 字符集
* @return 被加密后的字符串
*/
public static String encode(CharSequence source, Charset charset) {
return encode(StrUtil.bytes(source, charset));
}
/**
* Base62编码
*
* @param source 被编码的Base62字符串
* @return 被加密后的字符串
*/
public static String encode(byte[] source) {
return new String(Base62Codec.INSTANCE.encode(source));
}
/**
* Base62编码
*
* @param in 被编码Base62的流(一般为图片流或者文件流)
* @return 被加密后的字符串
*/
public static String encode(InputStream in) {
return encode(IoUtil.readBytes(in));
}
/**
* Base62编码
*
* @param file 被编码Base62的文件
* @return 被加密后的字符串
*/
public static String encode(File file) {
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解码
*
* @param source 被解码的Base62字符串
* @return 被加密后的字符串
*/
public static String decodeStrGbk(CharSequence source) {
return decodeStr(source, CharsetUtil.CHARSET_GBK);
}
/**
* Base62解码
*
* @param source 被解码的Base62字符串
* @return 被加密后的字符串
*/
public static String decodeStr(CharSequence source) {
return decodeStr(source, DEFAULT_CHARSET);
}
/**
* Base62解码
*
* @param source 被解码的Base62字符串
* @param charset 字符集
* @return 被加密后的字符串
*/
public static String decodeStr(CharSequence source, Charset charset) {
return StrUtil.str(decode(source), charset);
}
/**
* Base62解码
*
* @param Base62 被解码的Base62字符串
* @param destFile 目标文件
* @return 目标文件
*/
public static File decodeToFile(CharSequence Base62, File destFile) {
return FileUtil.writeBytes(decode(Base62), destFile);
}
/**
* Base62解码
*
* @param base62Str 被解码的Base62字符串
* @param out 写出到的流
* @param isCloseOut 是否关闭输出流
*/
public static void decodeToStream(CharSequence base62Str, OutputStream out, boolean isCloseOut) {
IoUtil.write(out, isCloseOut, decode(base62Str));
}
/**
* Base62解码
*
* @param base62Str 被解码的Base62字符串
* @return 被加密后的字符串
*/
public static byte[] decode(CharSequence base62Str) {
return decode(StrUtil.bytes(base62Str, DEFAULT_CHARSET));
}
/**
* 解码Base62
*
* @param base62bytes Base62输入
* @return 解码后的bytes
*/
public static byte[] decode(byte[] 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);
}
}
package cn.hutool.core.codec;
import cn.hutool.core.util.ArrayUtil;
import java.io.ByteArrayOutputStream;
import java.io.Serializable;
/**
* Base62编码解码实现,常用于短URL<br>
* From https://github.com/seruco/base62
*
* @author Looly, Sebastian Ruhleder, sebastian@seruco.io
* @since 4.5.9
*/
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;
public static Base62Codec INSTANCE = new Base62Codec();
/**
* 编码指定消息bytes为Base62格式的bytes
*
* @param data 被编码的消息
* @return Base62内容
*/
@Override
public byte[] encode(byte[] data) {
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);
}
/**
* 解码Base62消息
*
* @param encoded Base62内容
* @return 消息
*/
@Override
public byte[] decode(byte[] encoded) {
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 dictionary 字典
* @return 转换值
*/
private static byte[] translate(byte[] indices, byte[] dictionary) {
final byte[] translation = new byte[indices.length];
for (int i = 0; i < indices.length; i++) {
translation[i] = dictionary[indices[i]];
}
return translation;
}
/**
* 使用定义的字母表从源基准到目标基准
*
* @param message 消息bytes
* @param sourceBase 源基准长度
* @param targetBase 目标基准长度
* @return 计算结果
*/
private static byte[] convert(byte[] message, int sourceBase, int targetBase) {
// 计算结果长度,算法来自:http://codegolf.stackexchange.com/a/21672
final int estimatedLength = estimateOutputLength(message.length, sourceBase, targetBase);
final ByteArrayOutputStream out = new ByteArrayOutputStream(estimatedLength);
byte[] source = message;
while (source.length > 0) {
final ByteArrayOutputStream quotient = new ByteArrayOutputStream(source.length);
int remainder = 0;
for (byte b : source) {
final int accumulator = (b & 0xFF) + remainder * sourceBase;
final int digit = (accumulator - (accumulator % targetBase)) / targetBase;
remainder = accumulator % targetBase;
if (quotient.size() > 0 || digit > 0) {
quotient.write(digit);
}
}
out.write(remainder);
source = quotient.toByteArray();
}
// pad output with zeroes corresponding to the number of leading zeroes in the message
for (int i = 0; i < message.length - 1 && message[i] == 0; i++) {
out.write(0);
}
return ArrayUtil.reverse(out.toByteArray());
}
/**
* 估算结果长度
*
* @param inputLength 输入长度
* @param sourceBase 源基准长度
* @param targetBase 目标基准长度
* @return 估算长度
*/
private static int estimateOutputLength(int inputLength, int sourceBase, int targetBase) {
return (int) Math.ceil((Math.log(sourceBase) / Math.log(targetBase)) * inputLength);
}
// endregion
}
package cn.hutool.core.codec;
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;
/**
* Base64工具类,提供Base64的编码和解码方案<br>
* base64编码是用64(2的6次方)个ASCII字符来表示256(2的8次方)个ASCII字符,<br>
* 也就是三位二进制数组经过编码后变为四位的ASCII字符显示,长度比原来增加1/3。
*
* @author Looly
*/
public class Base64 {
private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8;
// -------------------------------------------------------------------- encode
/**
* 编码为Base64,非URL安全的
*
* @param arr 被编码的数组
* @param lineSep 在76个char之后是CRLF还是EOF
* @return 编码后的bytes
*/
public static byte[] encode(byte[] arr, boolean lineSep) {
return lineSep ?
java.util.Base64.getMimeEncoder().encode(arr) :
java.util.Base64.getEncoder().encode(arr);
}
/**
* 编码为Base64,URL安全的
*
* @param arr 被编码的数组
* @param lineSep 在76个char之后是CRLF还是EOF
* @return 编码后的bytes
* @since 3.0.6
* @deprecated 按照RFC2045规范,URL安全的Base64无需换行
*/
@Deprecated
public static byte[] encodeUrlSafe(byte[] arr, boolean lineSep) {
return Base64Encoder.encodeUrlSafe(arr, lineSep);
}
/**
* base64编码
*
* @param source 被编码的base64字符串
* @return 被加密后的字符串
*/
public static String encode(CharSequence source) {
return encode(source, DEFAULT_CHARSET);
}
/**
* base64编码,URL安全
*
* @param source 被编码的base64字符串
* @return 被加密后的字符串
* @since 3.0.6
*/
public static String encodeUrlSafe(CharSequence source) {
return encodeUrlSafe(source, DEFAULT_CHARSET);
}
/**
* base64编码
*
* @param source 被编码的base64字符串
* @param charset 字符集
* @return 被加密后的字符串
*/
public static String encode(CharSequence source, String charset) {
return encode(source, CharsetUtil.charset(charset));
}
/**
* base64编码,不进行padding(末尾不会填充'=')
*
* @param source 被编码的base64字符串
* @param charset 编码
* @return 被加密后的字符串
* @since 5.5.2
*/
public static String encodeWithoutPadding(CharSequence source, String charset) {
return encodeWithoutPadding(StrUtil.bytes(source, charset));
}
/**
* base64编码,URL安全
*
* @param source 被编码的base64字符串
* @param charset 字符集
* @return 被加密后的字符串
* @since 3.0.6
* @deprecated 请使用 {@link #encodeUrlSafe(CharSequence, Charset)}
*/
@Deprecated
public static String encodeUrlSafe(CharSequence source, String charset) {
return encodeUrlSafe(source, CharsetUtil.charset(charset));
}
/**
* base64编码
*
* @param source 被编码的base64字符串
* @param charset 字符集
* @return 被编码后的字符串
*/
public static String encode(CharSequence source, Charset charset) {
return encode(StrUtil.bytes(source, charset));
}
/**
* base64编码,URL安全的
*
* @param source 被编码的base64字符串
* @param charset 字符集
* @return 被加密后的字符串
* @since 3.0.6
*/
public static String encodeUrlSafe(CharSequence source, Charset charset) {
return encodeUrlSafe(StrUtil.bytes(source, charset));
}
/**
* base64编码
*
* @param source 被编码的base64字符串
* @return 被加密后的字符串
*/
public static String encode(byte[] source) {
return java.util.Base64.getEncoder().encodeToString(source);
}
/**
* base64编码,不进行padding(末尾不会填充'=')
*
* @param source 被编码的base64字符串
* @return 被加密后的字符串
* @since 5.5.2
*/
public static String encodeWithoutPadding(byte[] source) {
return java.util.Base64.getEncoder().withoutPadding().encodeToString(source);
}
/**
* base64编码,URL安全的
*
* @param source 被编码的base64字符串
* @return 被加密后的字符串
* @since 3.0.6
*/
public static String encodeUrlSafe(byte[] source) {
return java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(source);
}
/**
* base64编码
*
* @param in 被编码base64的流(一般为图片流或者文件流)
* @return 被加密后的字符串
* @since 4.0.9
*/
public static String encode(InputStream in) {
return encode(IoUtil.readBytes(in));
}
/**
* base64编码,URL安全的
*
* @param in 被编码base64的流(一般为图片流或者文件流)
* @return 被加密后的字符串
* @since 4.0.9
*/
public static String encodeUrlSafe(InputStream in) {
return encodeUrlSafe(IoUtil.readBytes(in));
}
/**
* base64编码
*
* @param file 被编码base64的文件
* @return 被加密后的字符串
* @since 4.0.9
*/
public static String encode(File file) {
return encode(FileUtil.readBytes(file));
}
/**
* base64编码,URL安全的
*
* @param file 被编码base64的文件
* @return 被加密后的字符串
* @since 4.0.9
*/
public static String encodeUrlSafe(File file) {
return encodeUrlSafe(FileUtil.readBytes(file));
}
/**
* 编码为Base64字符串<br>
* 如果isMultiLine为{@code true},则每76个字符一个换行符,否则在一行显示
*
* @param arr 被编码的数组
* @param isMultiLine 在76个char之后是CRLF还是EOF
* @param isUrlSafe 是否使用URL安全字符,一般为{@code false}
* @return 编码后的bytes
* @since 5.7.2
*/
public static String encodeStr(byte[] arr, boolean isMultiLine, boolean isUrlSafe) {
return StrUtil.str(encode(arr, isMultiLine, isUrlSafe), DEFAULT_CHARSET);
}
/**
* 编码为Base64<br>
* 如果isMultiLine为{@code true},则每76个字符一个换行符,否则在一行显示
*
* @param arr 被编码的数组
* @param isMultiLine 在76个char之后是CRLF还是EOF
* @param isUrlSafe 是否使用URL安全字符,一般为{@code false}
* @return 编码后的bytes
*/
public static byte[] encode(byte[] arr, boolean isMultiLine, boolean isUrlSafe) {
return Base64Encoder.encode(arr, isMultiLine, isUrlSafe);
}
// -------------------------------------------------------------------- decode
/**
* base64解码
*
* @param source 被解码的base64字符串
* @return 被加密后的字符串
* @since 4.3.2
*/
public static String decodeStrGbk(CharSequence source) {
return Base64Decoder.decodeStr(source, CharsetUtil.CHARSET_GBK);
}
/**
* base64解码
*
* @param source 被解码的base64字符串
* @return 被加密后的字符串
*/
public static String decodeStr(CharSequence source) {
return Base64Decoder.decodeStr(source);
}
/**
* base64解码
*
* @param source 被解码的base64字符串
* @param charset 字符集
* @return 被加密后的字符串
*/
public static String decodeStr(CharSequence source, String charset) {
return decodeStr(source, CharsetUtil.charset(charset));
}
/**
* base64解码
*
* @param source 被解码的base64字符串
* @param charset 字符集
* @return 被加密后的字符串
*/
public static String decodeStr(CharSequence source, Charset charset) {
return Base64Decoder.decodeStr(source, charset);
}
/**
* base64解码
*
* @param base64 被解码的base64字符串
* @param destFile 目标文件
* @return 目标文件
* @since 4.0.9
*/
public static File decodeToFile(CharSequence base64, File destFile) {
return FileUtil.writeBytes(Base64Decoder.decode(base64), destFile);
}
/**
* base64解码
*
* @param base64 被解码的base64字符串
* @param out 写出到的流
* @param isCloseOut 是否关闭输出流
* @since 4.0.9
*/
public static void decodeToStream(CharSequence base64, OutputStream out, boolean isCloseOut) {
IoUtil.write(out, isCloseOut, Base64Decoder.decode(base64));
}
/**
* base64解码
*
* @param base64 被解码的base64字符串
* @return 解码后的bytes
*/
public static byte[] decode(CharSequence base64) {
return Base64Decoder.decode(base64);
}
/**
* 解码Base64
*
* @param in 输入
* @return 解码后的bytes
*/
public static byte[] decode(byte[] in) {
return Base64Decoder.decode(in);
}
/**
* 检查是否为Base64
*
* @param base64 Base64的bytes
* @return 是否为Base64
* @since 5.7.5
*/
public static boolean isBase64(CharSequence base64) {
if (base64 == null || base64.length() < 2) {
return false;
}
final byte[] bytes = StrUtil.utf8Bytes(base64);
if (bytes.length != base64.length()) {
// 如果长度不相等,说明存在双字节字符,肯定不是Base64,直接返回false
return false;
}
return isBase64(bytes);
}
/**
* 检查是否为Base64
*
* @param base64Bytes Base64的bytes
* @return 是否为Base64
* @since 5.7.5
*/
public static boolean isBase64(byte[] base64Bytes) {
if (base64Bytes == null || base64Bytes.length < 3) {
return false;
}
boolean hasPadding = false;
for (byte base64Byte : base64Bytes) {
if (hasPadding) {
if ('=' != base64Byte) {
// 前一个字符是'=',则后边的字符都必须是'=',即'='只能都位于结尾
return false;
}
} else if ('=' == base64Byte) {
// 发现'=' 标记之
hasPadding = true;
} else if (false == (Base64Decoder.isBase64Code(base64Byte) || isWhiteSpace(base64Byte))) {
return false;
}
}
return true;
}
private static boolean isWhiteSpace(byte byteToCheck) {
switch (byteToCheck) {
case ' ':
case '\n':
case '\r':
case '\t':
return true;
default:
return false;
}
}
}
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;
import java.nio.charset.Charset;
/**
* Base64解码实现
*
* @author looly
*
*/
public class Base64Decoder {
private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8;
private static final byte PADDING = -2;
/** Base64解码表,共128位,-1表示非base64字符,-2表示padding */
private static final byte[] DECODE_TABLE = {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, // 20-2f + - /
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, // 30-3f 0-9,-2的位置是'='
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, // 40-4f A-O
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, // 50-5f P-Z _
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 60-6f a-o
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 // 70-7a p-z
};
/**
* base64解码
*
* @param source 被解码的base64字符串
* @return 被加密后的字符串
*/
public static String decodeStr(CharSequence source) {
return decodeStr(source, DEFAULT_CHARSET);
}
/**
* base64解码
*
* @param source 被解码的base64字符串
* @param charset 字符集
* @return 被加密后的字符串
*/
public static String decodeStr(CharSequence source, Charset charset) {
return StrUtil.str(decode(source), charset);
}
/**
* base64解码
*
* @param source 被解码的base64字符串
* @return 被加密后的字符串
*/
public static byte[] decode(CharSequence source) {
return decode(StrUtil.bytes(source, DEFAULT_CHARSET));
}
/**
* 解码Base64
*
* @param in 输入
* @return 解码后的bytes
*/
public static byte[] decode(byte[] in) {
if (ArrayUtil.isEmpty(in)) {
return in;
}
return decode(in, 0, in.length);
}
/**
* 解码Base64
*
* @param in 输入
* @param pos 开始位置
* @param length 长度
* @return 解码后的bytes
*/
public static byte[] decode(byte[] in, int pos, int length) {
if (ArrayUtil.isEmpty(in)) {
return in;
}
final MutableInt offset = new MutableInt(pos);
byte sestet0;
byte sestet1;
byte sestet2;
byte sestet3;
int maxPos = pos + length - 1;
int octetId = 0;
byte[] octet = new byte[length * 3 / 4];// over-estimated if non-base64 characters present
while (offset.intValue() <= maxPos) {
sestet0 = getNextValidDecodeByte(in, offset, maxPos);
sestet1 = getNextValidDecodeByte(in, offset, maxPos);
sestet2 = getNextValidDecodeByte(in, offset, maxPos);
sestet3 = getNextValidDecodeByte(in, offset, maxPos);
if (PADDING != sestet1) {
octet[octetId++] = (byte) ((sestet0 << 2) | (sestet1 >>> 4));
}
if (PADDING != sestet2) {
octet[octetId++] = (byte) (((sestet1 & 0xf) << 4) | (sestet2 >>> 2));
}
if (PADDING != sestet3) {
octet[octetId++] = (byte) (((sestet2 & 3) << 6) | sestet3);
}
}
if (octetId == octet.length) {
return octet;
} else {
// 如果有非Base64字符混入,则实际结果比解析的要短,截取之
return (byte[]) ArrayUtil.copy(octet, new byte[octetId], octetId);
}
}
/**
* 给定的字符是否为Base64字符
*
* @param octet 被检查的字符
* @return 是否为Base64字符
* @since 5.7.5
*/
public static boolean isBase64Code(byte octet) {
return octet == '=' || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1);
}
// ----------------------------------------------------------------------------------------------- Private start
/**
* 获取下一个有效的byte字符
*
* @param in 输入
* @param pos 当前位置,调用此方法后此位置保持在有效字符的下一个位置
* @param maxPos 最大位置
* @return 有效字符,如果达到末尾返回
*/
private static byte getNextValidDecodeByte(byte[] in, MutableInt pos, int maxPos) {
byte base64Byte;
byte decodeByte;
while (pos.intValue() <= maxPos) {
base64Byte = in[pos.intValue()];
pos.increment();
if (base64Byte > -1) {
decodeByte = DECODE_TABLE[base64Byte];
if (decodeByte > -1) {
return decodeByte;
}
}
}
// padding if reached max position
return PADDING;
}
// ----------------------------------------------------------------------------------------------- Private end
}
package cn.hutool.core.codec;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import java.nio.charset.Charset;
/**
* Base64编码<br>
* TODO 6.x移除此类,使用JDK自身
*
* @author looly
* @since 3.2.0
*/
public class Base64Encoder {
private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8;
/**
* 标准编码表
*/
private static final byte[] STANDARD_ENCODE_TABLE = { //
'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', '0', '1', '2', '3', //
'4', '5', '6', '7', '8', '9', '+', '/' //
};
/**
* URL安全的编码表,将 + 和 / 替换为 - 和 _
*/
private static final byte[] URL_SAFE_ENCODE_TABLE = { //
'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', '0', '1', '2', '3', //
'4', '5', '6', '7', '8', '9', '-', '_' //
};
// -------------------------------------------------------------------- encode
/**
* 编码为Base64,非URL安全的
*
* @param arr 被编码的数组
* @param lineSep 在76个char之后是CRLF还是EOF
* @return 编码后的bytes
*/
public static byte[] encode(byte[] arr, boolean lineSep) {
return encode(arr, lineSep, false);
}
/**
* 编码为Base64,URL安全的
*
* @param arr 被编码的数组
* @param lineSep 在76个char之后是CRLF还是EOF
* @return 编码后的bytes
* @since 3.0.6
*/
public static byte[] encodeUrlSafe(byte[] arr, boolean lineSep) {
return encode(arr, lineSep, true);
}
/**
* base64编码
*
* @param source 被编码的base64字符串
* @return 被加密后的字符串
*/
public static String encode(CharSequence source) {
return encode(source, DEFAULT_CHARSET);
}
/**
* base64编码,URL安全
*
* @param source 被编码的base64字符串
* @return 被加密后的字符串
* @since 3.0.6
*/
public static String encodeUrlSafe(CharSequence source) {
return encodeUrlSafe(source, DEFAULT_CHARSET);
}
/**
* base64编码
*
* @param source 被编码的base64字符串
* @param charset 字符集
* @return 被加密后的字符串
*/
public static String encode(CharSequence source, Charset charset) {
return encode(StrUtil.bytes(source, charset));
}
/**
* base64编码,URL安全的
*
* @param source 被编码的base64字符串
* @param charset 字符集
* @return 被加密后的字符串
* @since 3.0.6
*/
public static String encodeUrlSafe(CharSequence source, Charset charset) {
return encodeUrlSafe(StrUtil.bytes(source, charset));
}
/**
* base64编码
*
* @param source 被编码的base64字符串
* @return 被加密后的字符串
*/
public static String encode(byte[] source) {
return StrUtil.str(encode(source, false), DEFAULT_CHARSET);
}
/**
* base64编码,URL安全的
*
* @param source 被编码的base64字符串
* @return 被加密后的字符串
* @since 3.0.6
*/
public static String encodeUrlSafe(byte[] source) {
return StrUtil.str(encodeUrlSafe(source, false), DEFAULT_CHARSET);
}
/**
* 编码为Base64字符串<br>
* 如果isMultiLine为{@code true},则每76个字符一个换行符,否则在一行显示
*
* @param arr 被编码的数组
* @param isMultiLine 在76个char之后是CRLF还是EOF
* @param isUrlSafe 是否使用URL安全字符,在URL Safe模式下,=为URL中的关键字符,不需要补充。空余的byte位要去掉,一般为{@code false}
* @return 编码后的bytes
* @since 5.7.2
*/
public static String encodeStr(byte[] arr, boolean isMultiLine, boolean isUrlSafe) {
return StrUtil.str(encode(arr, isMultiLine, isUrlSafe), DEFAULT_CHARSET);
}
/**
* 编码为Base64<br>
* 如果isMultiLine为{@code true},则每76个字符一个换行符,否则在一行显示
*
* @param arr 被编码的数组
* @param isMultiLine 在76个char之后是CRLF还是EOF
* @param isUrlSafe 是否使用URL安全字符,在URL Safe模式下,=为URL中的关键字符,不需要补充。空余的byte位要去掉,一般为{@code false}
* @return 编码后的bytes
*/
public static byte[] encode(byte[] arr, boolean isMultiLine, boolean isUrlSafe) {
if (null == arr) {
return null;
}
int len = arr.length;
if (len == 0) {
return new byte[0];
}
int evenlen = (len / 3) * 3;
int cnt = ((len - 1) / 3 + 1) << 2;
int destlen = cnt + (isMultiLine ? (cnt - 1) / 76 << 1 : 0);
byte[] dest = new byte[destlen];
byte[] encodeTable = isUrlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE;
for (int s = 0, d = 0, cc = 0; s < evenlen; ) {
int i = (arr[s++] & 0xff) << 16 | (arr[s++] & 0xff) << 8 | (arr[s++] & 0xff);
dest[d++] = encodeTable[(i >>> 18) & 0x3f];
dest[d++] = encodeTable[(i >>> 12) & 0x3f];
dest[d++] = encodeTable[(i >>> 6) & 0x3f];
dest[d++] = encodeTable[i & 0x3f];
if (isMultiLine && ++cc == 19 && d < destlen - 2) {
dest[d++] = '\r';
dest[d++] = '\n';
cc = 0;
}
}
int left = len - evenlen;// 剩余位数
if (left > 0) {
int i = ((arr[evenlen] & 0xff) << 10) | (left == 2 ? ((arr[len - 1] & 0xff) << 2) : 0);
dest[destlen - 4] = encodeTable[i >> 12];
dest[destlen - 3] = encodeTable[(i >>> 6) & 0x3f];
if (isUrlSafe) {
// 在URL Safe模式下,=为URL中的关键字符,不需要补充。空余的byte位要去掉。
int urlSafeLen = destlen - 2;
if (2 == left) {
dest[destlen - 2] = encodeTable[i & 0x3f];
urlSafeLen += 1;
}
byte[] urlSafeDest = new byte[urlSafeLen];
System.arraycopy(dest, 0, urlSafeDest, 0, urlSafeLen);
return urlSafeDest;
} else {
dest[destlen - 2] = (left == 2) ? encodeTable[i & 0x3f] : (byte) '=';
dest[destlen - 1] = '=';
}
}
return dest;
}
}
package cn.hutool.core.codec;
import cn.hutool.core.lang.Assert;
/**
* 凯撒密码实现<br>
* 算法来自:https://github.com/zhaorenjie110/SymmetricEncryptionAndDecryption
*
* @author looly
*/
public class Caesar {
// 26个字母表
public static final String TABLE = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
/**
* 传入明文,加密得到密文
*
* @param message 加密的消息
* @param offset 偏移量
* @return 加密后的内容
*/
public static String encode(String message, int offset) {
Assert.notNull(message, "message must be not null!");
final int len = message.length();
final char[] plain = message.toCharArray();
char c;
for (int i = 0; i < len; i++) {
c = message.charAt(i);
if (false == Character.isLetter(c)) {
continue;
}
plain[i] = encodeChar(c, offset);
}
return new String(plain);
}
/**
* 传入明文解密到密文
*
* @param cipherText 密文
* @param offset 偏移量
* @return 解密后的内容
*/
public static String decode(String cipherText, int offset) {
Assert.notNull(cipherText, "cipherText must be not null!");
final int len = cipherText.length();
final char[] plain = cipherText.toCharArray();
char c;
for (int i = 0; i < len; i++) {
c = cipherText.charAt(i);
if (false == Character.isLetter(c)) {
continue;
}
plain[i] = decodeChar(c, offset);
}
return new String(plain);
}
// ----------------------------------------------------------------------------------------- Private method start
/**
* 加密轮盘
*
* @param c 被加密字符
* @param offset 偏移量
* @return 加密后的字符
*/
private static char encodeChar(char c, int offset) {
int position = (TABLE.indexOf(c) + offset) % 52;
return TABLE.charAt(position);
}
/**
* 解密轮盘
*
* @param c 字符
* @return 解密后的字符
*/
private static char decodeChar(char c, int offset) {
int position = (TABLE.indexOf(c) - offset) % 52;
if (position < 0) {
position += 52;
}
return TABLE.charAt(position);
}
// ----------------------------------------------------------------------------------------- Private method end
}
package cn.hutool.core.codec;
/**
* 解码接口
*
* @param <T> 被解码的数据类型
* @param <R> 解码后的数据类型
* @author looly
* @since 5.7.22
*/
public interface Decoder<T, R> {
/**
* 执行解码
*
* @param encoded 被解码的数据
* @return 解码后的数据
*/
R decode(T encoded);
}
package cn.hutool.core.codec;
/**
* 编码接口
*
* @param <T> 被编码的数据类型
* @param <R> 编码后的数据类型
* @author looly
* @since 5.7.22
*/
public interface Encoder<T, R> {
/**
* 执行编码
*
* @param data 被编码的数据
* @return 编码后的数据
*/
R encode(T data);
}
package cn.hutool.core.codec;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
/**
* <a href="http://hashids.org/">Hashids</a> 协议实现,以实现:
* <ul>
* <li>生成简短、唯一、大小写敏感并无序的hash值</li>
* <li>自然数字的Hash值</li>
* <li>可以设置不同的盐,具有保密性</li>
* <li>可配置的hash长度</li>
* <li>递增的输入产生的输出无法预测</li>
* </ul>
*
* <p>
* 来自:<a href="https://github.com/davidafsilva/java-hashids">https://github.com/davidafsilva/java-hashids</a>
* </p>
*
* <p>
* {@code Hashids}可以将数字或者16进制字符串转为短且唯一不连续的字符串,采用双向编码实现,比如,它可以将347之类的数字转换为yr8之类的字符串,也可以将yr8之类的字符串重新解码为347之类的数字。<br>
* 此编码算法主要是解决爬虫类应用对连续ID爬取问题,将有序的ID转换为无序的Hashids,而且一一对应。
* </p>
*
* @author david
*/
public class Hashids implements Encoder<long[], String>, Decoder<String, long[]> {
private static final int LOTTERY_MOD = 100;
private static final double GUARD_THRESHOLD = 12;
private static final double SEPARATOR_THRESHOLD = 3.5;
// 最小编解码字符串
private static final int MIN_ALPHABET_LENGTH = 16;
private static final Pattern HEX_VALUES_PATTERN = Pattern.compile("[\\w\\W]{1,12}");
// 默认编解码字符串
public static final char[] DEFAULT_ALPHABET = {
'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',
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'
};
// 默认分隔符
private static final char[] DEFAULT_SEPARATORS = {
'c', 'f', 'h', 'i', 's', 't', 'u', 'C', 'F', 'H', 'I', 'S', 'T', 'U'
};
// algorithm properties
private final char[] alphabet;
// 多个数字编解码的分界符
private final char[] separators;
private final Set<Character> separatorsSet;
private final char[] salt;
// 补齐至 minLength 长度添加的字符列表
private final char[] guards;
// 编码后最小的字符长度
private final int minLength;
// region create
/**
* 根据参数值,创建{@code Hashids},使用默认{@link #DEFAULT_ALPHABET}作为字母表,不限制最小长度
*
* @param salt 加盐值
* @return {@code Hashids}
*/
public static Hashids create(final char[] salt) {
return create(salt, DEFAULT_ALPHABET, -1);
}
/**
* 根据参数值,创建{@code Hashids},使用默认{@link #DEFAULT_ALPHABET}作为字母表
*
* @param salt 加盐值
* @param minLength 限制最小长度,-1表示不限制
* @return {@code Hashids}
*/
public static Hashids create(final char[] salt, final int minLength) {
return create(salt, DEFAULT_ALPHABET, minLength);
}
/**
* 根据参数值,创建{@code Hashids}
*
* @param salt 加盐值
* @param alphabet hash字母表
* @param minLength 限制最小长度,-1表示不限制
* @return {@code Hashids}
*/
public static Hashids create(final char[] salt, final char[] alphabet, final int minLength) {
return new Hashids(salt, alphabet, minLength);
}
// endregion
/**
* 构造
*
* @param salt 加盐值
* @param alphabet hash字母表
* @param minLength 限制最小长度,-1表示不限制
*/
public Hashids(final char[] salt, final char[] alphabet, final int minLength) {
this.minLength = minLength;
this.salt = Arrays.copyOf(salt, salt.length);
// filter and shuffle separators
char[] tmpSeparators = shuffle(filterSeparators(DEFAULT_SEPARATORS, alphabet), this.salt);
// validate and filter the alphabet
char[] tmpAlphabet = validateAndFilterAlphabet(alphabet, tmpSeparators);
// check separator threshold
if (tmpSeparators.length == 0 ||
((double) (tmpAlphabet.length / tmpSeparators.length)) > SEPARATOR_THRESHOLD) {
final int minSeparatorsSize = (int) Math.ceil(tmpAlphabet.length / SEPARATOR_THRESHOLD);
// check minimum size of separators
if (minSeparatorsSize > tmpSeparators.length) {
// fill separators from alphabet
final int missingSeparators = minSeparatorsSize - tmpSeparators.length;
tmpSeparators = Arrays.copyOf(tmpSeparators, tmpSeparators.length + missingSeparators);
System.arraycopy(tmpAlphabet, 0, tmpSeparators,
tmpSeparators.length - missingSeparators, missingSeparators);
System.arraycopy(tmpAlphabet, 0, tmpSeparators,
tmpSeparators.length - missingSeparators, missingSeparators);
tmpAlphabet = Arrays.copyOfRange(tmpAlphabet, missingSeparators, tmpAlphabet.length);
}
}
// shuffle the current alphabet
shuffle(tmpAlphabet, this.salt);
// check guards
this.guards = new char[(int) Math.ceil(tmpAlphabet.length / GUARD_THRESHOLD)];
if (alphabet.length < 3) {
System.arraycopy(tmpSeparators, 0, guards, 0, guards.length);
this.separators = Arrays.copyOfRange(tmpSeparators, guards.length, tmpSeparators.length);
this.alphabet = tmpAlphabet;
} else {
System.arraycopy(tmpAlphabet, 0, guards, 0, guards.length);
this.separators = tmpSeparators;
this.alphabet = Arrays.copyOfRange(tmpAlphabet, guards.length, tmpAlphabet.length);
}
// create the separators set
separatorsSet = IntStream.range(0, separators.length)
.mapToObj(idx -> separators[idx])
.collect(Collectors.toSet());
}
/**
* 编码给定的16进制数字
*
* @param hexNumbers 16进制数字
* @return 编码后的值, {@code null} if {@code numbers} 是 {@code null}.
* @throws IllegalArgumentException 数字不支持抛出此异常
*/
public String encodeFromHex(final String hexNumbers) {
if (hexNumbers == null) {
return null;
}
// remove the prefix, if present
final String hex = hexNumbers.startsWith("0x") || hexNumbers.startsWith("0X") ?
hexNumbers.substring(2) : hexNumbers;
// get the associated long value and encode it
LongStream values = LongStream.empty();
final Matcher matcher = HEX_VALUES_PATTERN.matcher(hex);
while (matcher.find()) {
final long value = new BigInteger("1" + matcher.group(), 16).longValue();
values = LongStream.concat(values, LongStream.of(value));
}
return encode(values.toArray());
}
/**
* 编码给定的数字数组
*
* @param numbers 数字数组
* @return 编码后的值, {@code null} if {@code numbers} 是 {@code null}.
* @throws IllegalArgumentException 数字不支持抛出此异常
*/
@Override
public String encode(final long... numbers) {
if (numbers == null) {
return null;
}
// copy alphabet
final char[] currentAlphabet = Arrays.copyOf(alphabet, alphabet.length);
// determine the lottery number
final long lotteryId = LongStream.range(0, numbers.length)
.reduce(0, (state, i) -> {
final long number = numbers[(int) i];
if (number < 0) {
throw new IllegalArgumentException("invalid number: " + number);
}
return state + number % (i + LOTTERY_MOD);
});
final char lottery = currentAlphabet[(int) (lotteryId % currentAlphabet.length)];
// encode each number
final StringBuilder global = new StringBuilder();
IntStream.range(0, numbers.length)
.forEach(idx -> {
// derive alphabet
deriveNewAlphabet(currentAlphabet, salt, lottery);
// encode
final int initialLength = global.length();
translate(numbers[idx], currentAlphabet, global, initialLength);
// prepend the lottery
if (idx == 0) {
global.insert(0, lottery);
}
// append the separator, if more numbers are pending encoding
if (idx + 1 < numbers.length) {
long n = numbers[idx] % (global.charAt(initialLength) + 1);
global.append(separators[(int) (n % separators.length)]);
}
});
// add the guards, if there's any space left
if (minLength > global.length()) {
int guardIdx = (int) ((lotteryId + lottery) % guards.length);
global.insert(0, guards[guardIdx]);
if (minLength > global.length()) {
guardIdx = (int) ((lotteryId + global.charAt(2)) % guards.length);
global.append(guards[guardIdx]);
}
}
// add the necessary padding
int paddingLeft = minLength - global.length();
while (paddingLeft > 0) {
shuffle(currentAlphabet, Arrays.copyOf(currentAlphabet, currentAlphabet.length));
final int alphabetHalfSize = currentAlphabet.length / 2;
final int initialSize = global.length();
if (paddingLeft > currentAlphabet.length) {
// entire alphabet with the current encoding in the middle of it
int offset = alphabetHalfSize + (currentAlphabet.length % 2 == 0 ? 0 : 1);
global.insert(0, currentAlphabet, alphabetHalfSize, offset);
global.insert(offset + initialSize, currentAlphabet, 0, alphabetHalfSize);
// decrease the padding left
paddingLeft -= currentAlphabet.length;
} else {
// calculate the excess
final int excess = currentAlphabet.length + global.length() - minLength;
final int secondHalfStartOffset = alphabetHalfSize + Math.floorDiv(excess, 2);
final int secondHalfLength = currentAlphabet.length - secondHalfStartOffset;
final int firstHalfLength = paddingLeft - secondHalfLength;
global.insert(0, currentAlphabet, secondHalfStartOffset, secondHalfLength);
global.insert(secondHalfLength + initialSize, currentAlphabet, 0, firstHalfLength);
paddingLeft = 0;
}
}
return global.toString();
}
//-------------------------
// Decode
//-------------------------
/**
* 解码Hash值为16进制数字
*
* @param hash hash值
* @return 解码后的16进制值, {@code null} if {@code numbers} 是 {@code null}.
* @throws IllegalArgumentException if the hash is invalid.
*/
public String decodeToHex(final String hash) {
if (hash == null) {
return null;
}
final StringBuilder sb = new StringBuilder();
Arrays.stream(decode(hash))
.mapToObj(Long::toHexString)
.forEach(hex -> sb.append(hex, 1, hex.length()));
return sb.toString();
}
/**
* 解码Hash值为数字数组
*
* @param hash hash值
* @return 解码后的16进制值, {@code null} if {@code numbers} 是 {@code null}.
* @throws IllegalArgumentException if the hash is invalid.
*/
@Override
public long[] decode(final String hash) {
if (hash == null) {
return null;
}
// create a set of the guards
final Set<Character> guardsSet = IntStream.range(0, guards.length)
.mapToObj(idx -> guards[idx])
.collect(Collectors.toSet());
// count the total guards used
final int[] guardsIdx = IntStream.range(0, hash.length())
.filter(idx -> guardsSet.contains(hash.charAt(idx)))
.toArray();
// get the start/end index base on the guards count
final int startIdx, endIdx;
if (guardsIdx.length > 0) {
startIdx = guardsIdx[0] + 1;
endIdx = guardsIdx.length > 1 ? guardsIdx[1] : hash.length();
} else {
startIdx = 0;
endIdx = hash.length();
}
LongStream decoded = LongStream.empty();
// parse the hash
if (hash.length() > 0) {
final char lottery = hash.charAt(startIdx);
// create the initial accumulation string
final int length = hash.length() - guardsIdx.length - 1;
StringBuilder block = new StringBuilder(length);
// create the base salt
final char[] decodeSalt = new char[alphabet.length];
decodeSalt[0] = lottery;
final int saltLength = salt.length >= alphabet.length ? alphabet.length - 1 : salt.length;
System.arraycopy(salt, 0, decodeSalt, 1, saltLength);
final int saltLeft = alphabet.length - saltLength - 1;
// copy alphabet
final char[] currentAlphabet = Arrays.copyOf(alphabet, alphabet.length);
for (int i = startIdx + 1; i < endIdx; i++) {
if (false == separatorsSet.contains(hash.charAt(i))) {
block.append(hash.charAt(i));
// continue if we have not reached the end, yet
if (i < endIdx - 1) {
continue;
}
}
if (block.length() > 0) {
// create the salt
if (saltLeft > 0) {
System.arraycopy(currentAlphabet, 0, decodeSalt,
alphabet.length - saltLeft, saltLeft);
}
// shuffle the alphabet
shuffle(currentAlphabet, decodeSalt);
// prepend the decoded value
final long n = translate(block.toString().toCharArray(), currentAlphabet);
decoded = LongStream.concat(decoded, LongStream.of(n));
// create a new block
block = new StringBuilder(length);
}
}
}
// validate the hash
final long[] decodedValue = decoded.toArray();
if (!Objects.equals(hash, encode(decodedValue))) {
throw new IllegalArgumentException("invalid hash: " + hash);
}
return decodedValue;
}
private StringBuilder translate(final long n, final char[] alphabet,
final StringBuilder sb, final int start) {
long input = n;
do {
// prepend the chosen char
sb.insert(start, alphabet[(int) (input % alphabet.length)]);
// trim the input
input = input / alphabet.length;
} while (input > 0);
return sb;
}
private long translate(final char[] hash, final char[] alphabet) {
long number = 0;
final Map<Character, Integer> alphabetMapping = IntStream.range(0, alphabet.length)
.mapToObj(idx -> new Object[]{alphabet[idx], idx})
.collect(Collectors.groupingBy(arr -> (Character) arr[0],
Collectors.mapping(arr -> (Integer) arr[1],
Collectors.reducing(null, (a, b) -> a == null ? b : a))));
for (int i = 0; i < hash.length; ++i) {
number += alphabetMapping.computeIfAbsent(hash[i], k -> {
throw new IllegalArgumentException("Invalid alphabet for hash");
}) * (long) Math.pow(alphabet.length, hash.length - i - 1);
}
return number;
}
private char[] deriveNewAlphabet(final char[] alphabet, final char[] salt, final char lottery) {
// create the new salt
final char[] newSalt = new char[alphabet.length];
// 1. lottery
newSalt[0] = lottery;
int spaceLeft = newSalt.length - 1;
int offset = 1;
// 2. salt
if (salt.length > 0 && spaceLeft > 0) {
int length = Math.min(salt.length, spaceLeft);
System.arraycopy(salt, 0, newSalt, offset, length);
spaceLeft -= length;
offset += length;
}
// 3. alphabet
if (spaceLeft > 0) {
System.arraycopy(alphabet, 0, newSalt, offset, spaceLeft);
}
// shuffle
return shuffle(alphabet, newSalt);
}
private char[] validateAndFilterAlphabet(final char[] alphabet, final char[] separators) {
// validate size
if (alphabet.length < MIN_ALPHABET_LENGTH) {
throw new IllegalArgumentException(String.format("alphabet must contain at least %d unique " +
"characters: %d", MIN_ALPHABET_LENGTH, alphabet.length));
}
final Set<Character> seen = new LinkedHashSet<>(alphabet.length);
final Set<Character> invalid = IntStream.range(0, separators.length)
.mapToObj(idx -> separators[idx])
.collect(Collectors.toSet());
// add to seen set (without duplicates)
IntStream.range(0, alphabet.length)
.forEach(i -> {
if (alphabet[i] == ' ') {
throw new IllegalArgumentException(String.format("alphabet must not contain spaces: " +
"index %d", i));
}
final Character c = alphabet[i];
if (!invalid.contains(c)) {
seen.add(c);
}
});
// create a new alphabet without the duplicates
final char[] uniqueAlphabet = new char[seen.size()];
int idx = 0;
for (char c : seen) {
uniqueAlphabet[idx++] = c;
}
return uniqueAlphabet;
}
@SuppressWarnings("SameParameterValue")
private char[] filterSeparators(final char[] separators, final char[] alphabet) {
final Set<Character> valid = IntStream.range(0, alphabet.length)
.mapToObj(idx -> alphabet[idx])
.collect(Collectors.toSet());
return IntStream.range(0, separators.length)
.mapToObj(idx -> (separators[idx]))
.filter(valid::contains)
// ugly way to convert back to char[]
.map(c -> Character.toString(c))
.collect(Collectors.joining())
.toCharArray();
}
private char[] shuffle(final char[] alphabet, final char[] salt) {
for (int i = alphabet.length - 1, v = 0, p = 0, j, z; salt.length > 0 && i > 0; i--, v++) {
v %= salt.length;
p += z = salt[v];
j = (z + v + p) % i;
final char tmp = alphabet[j];
alphabet[j] = alphabet[i];
alphabet[i] = tmp;
}
return alphabet;
}
}
package cn.hutool.core.codec;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
/**
* 莫尔斯电码的编码和解码实现<br>
* 参考:https://github.com/TakWolf/Java-MorseCoder
*
* @author looly, TakWolf
* @since 4.4.1
*/
public class Morse {
private static final Map<Integer, String> ALPHABETS = new HashMap<>(); // code point -> morse
private static final Map<String, Integer> DICTIONARIES = new HashMap<>(); // morse -> code point
/**
* 注册莫尔斯电码表
*
* @param abc 字母和字符
* @param dict 二进制
*/
private static void registerMorse(Character abc, String dict) {
ALPHABETS.put((int) abc, dict);
DICTIONARIES.put(dict, (int) abc);
}
static {
// Letters
registerMorse('A', "01");
registerMorse('B', "1000");
registerMorse('C', "1010");
registerMorse('D', "100");
registerMorse('E', "0");
registerMorse('F', "0010");
registerMorse('G', "110");
registerMorse('H', "0000");
registerMorse('I', "00");
registerMorse('J', "0111");
registerMorse('K', "101");
registerMorse('L', "0100");
registerMorse('M', "11");
registerMorse('N', "10");
registerMorse('O', "111");
registerMorse('P', "0110");
registerMorse('Q', "1101");
registerMorse('R', "010");
registerMorse('S', "000");
registerMorse('T', "1");
registerMorse('U', "001");
registerMorse('V', "0001");
registerMorse('W', "011");
registerMorse('X', "1001");
registerMorse('Y', "1011");
registerMorse('Z', "1100");
// Numbers
registerMorse('0', "11111");
registerMorse('1', "01111");
registerMorse('2', "00111");
registerMorse('3', "00011");
registerMorse('4', "00001");
registerMorse('5', "00000");
registerMorse('6', "10000");
registerMorse('7', "11000");
registerMorse('8', "11100");
registerMorse('9', "11110");
// Punctuation
registerMorse('.', "010101");
registerMorse(',', "110011");
registerMorse('?', "001100");
registerMorse('\'', "011110");
registerMorse('!', "101011");
registerMorse('/', "10010");
registerMorse('(', "10110");
registerMorse(')', "101101");
registerMorse('&', "01000");
registerMorse(':', "111000");
registerMorse(';', "101010");
registerMorse('=', "10001");
registerMorse('+', "01010");
registerMorse('-', "100001");
registerMorse('_', "001101");
registerMorse('"', "010010");
registerMorse('$', "0001001");
registerMorse('@', "011010");
}
private final char dit; // short mark or dot
private final char dah; // longer mark or dash
private final char split;
/**
* 构造
*/
public Morse() {
this(CharUtil.DOT, CharUtil.DASHED, CharUtil.SLASH);
}
/**
* 构造
*
* @param dit 点表示的字符
* @param dah 横线表示的字符
* @param split 分隔符
*/
public Morse(char dit, char dah, char split) {
this.dit = dit;
this.dah = dah;
this.split = split;
}
/**
* 编码
*
* @param text 文本
* @return 密文
*/
public String encode(String text) {
Assert.notNull(text, "Text should not be null.");
text = text.toUpperCase();
final StringBuilder morseBuilder = new StringBuilder();
final int len = text.codePointCount(0, text.length());
for (int i = 0; i < len; i++) {
int codePoint = text.codePointAt(i);
String word = ALPHABETS.get(codePoint);
if (word == null) {
word = Integer.toBinaryString(codePoint);
}
morseBuilder.append(word.replace('0', dit).replace('1', dah)).append(split);
}
return morseBuilder.toString();
}
/**
* 解码
*
* @param morse 莫尔斯电码
* @return 明文
*/
public String decode(String morse) {
Assert.notNull(morse, "Morse should not be null.");
final char dit = this.dit;
final char dah = this.dah;
final char split = this.split;
if (false == StrUtil.containsOnly(morse, dit, dah, split)) {
throw new IllegalArgumentException("Incorrect morse.");
}
final List<String> words = StrUtil.split(morse, split);
final StringBuilder textBuilder = new StringBuilder();
Integer codePoint;
for (String word : words) {
if(StrUtil.isEmpty(word)){
continue;
}
word = word.replace(dit, '0').replace(dah, '1');
codePoint = DICTIONARIES.get(word);
if (codePoint == null) {
codePoint = Integer.valueOf(word, 2);
}
textBuilder.appendCodePoint(codePoint);
}
return textBuilder.toString();
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment