This commit is contained in:
Looly
2022-04-30 19:45:32 +08:00
parent d368fb1949
commit dea8344d91
25 changed files with 255 additions and 140 deletions

View File

@@ -29,9 +29,9 @@ public class AnnotationProxy<T extends Annotation> implements Annotation, Invoca
*
* @param annotation 注解
*/
@SuppressWarnings("unchecked")
public AnnotationProxy(T annotation) {
this.annotation = annotation;
//noinspection unchecked
this.type = (Class<T>) annotation.annotationType();
this.attributes = initAttributes();
}

View File

@@ -74,13 +74,13 @@ public class AnnotationUtil {
* @return 注解对象数组
* @since 5.8.0
*/
@SuppressWarnings("unchecked")
public static <T> T[] getAnnotations(AnnotatedElement annotationEle, boolean isToCombination, Class<T> annotationType) {
final Annotation[] annotations = getAnnotations(annotationEle, isToCombination,
(annotation -> null == annotationType || annotationType.isAssignableFrom(annotation.getClass())));
final T[] result = ArrayUtil.newArray(annotationType, annotations.length);
for (int i = 0; i < annotations.length; i++) {
//noinspection unchecked
result[i] = (T) annotations[i];
}
return result;

View File

@@ -34,6 +34,7 @@ public class BeanToMapCopier extends AbsCopier<Object, Map> {
this.targetType = targetType;
}
@SuppressWarnings("unchecked")
@Override
public Map copy() {
Class<?> actualEditable = source.getClass();
@@ -73,7 +74,6 @@ public class BeanToMapCopier extends AbsCopier<Object, Map> {
// 目标赋值
if(null != sValue || false == copyOptions.ignoreNullValue){
//noinspection unchecked
target.put(sFieldName, sValue);
}
});

View File

@@ -23,18 +23,18 @@ public class Base16Codec implements Encoder<byte[], char[]>, Decoder<CharSequenc
*
* @param lowerCase 是否小写
*/
public Base16Codec(boolean lowerCase) {
public Base16Codec(final boolean lowerCase) {
this.alphabets = (lowerCase ? "0123456789abcdef" : "0123456789ABCDEF").toCharArray();
}
@Override
public char[] encode(byte[] data) {
public char[] encode(final 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]];// 低位
out[j++] = hexDigit(data[i] >> 4);// 高位
out[j++] = hexDigit(data[i]);// 低位
}
return out;
}
@@ -79,12 +79,12 @@ public class Base16Codec implements Encoder<byte[], char[]>, Decoder<CharSequenc
* @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];
public String toUnicodeHex(final char ch) {
return "\\u" +
hexDigit(ch >> 12) +
hexDigit(ch >> 8) +
hexDigit(ch >> 4) +
hexDigit(ch);
}
/**
@@ -94,10 +94,21 @@ public class Base16Codec implements Encoder<byte[], char[]>, Decoder<CharSequenc
* @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]);
//高位
builder.append(hexDigit(b >> 4));
//低位
builder.append(hexDigit(b));
}
/**
* 将byte值转为16进制
*
* @param b byte
* @return hex char
* @since 6.0.0
*/
public char hexDigit(final int b) {
return alphabets[b & 0x0f];
}
/**
@@ -108,7 +119,7 @@ public class Base16Codec implements Encoder<byte[], char[]>, Decoder<CharSequenc
* @return 一个整数
* @throws UtilException 当ch不是一个合法的十六进制字符时抛出运行时异常
*/
private static int toDigit(char ch, int index) {
private static int toDigit(final char ch, final int index) {
int digit = Character.digit(ch, 16);
if (digit < 0) {
throw new UtilException("Illegal hexadecimal character {} at index {}", ch, index);

View File

@@ -1,13 +1,15 @@
package cn.hutool.core.codec;
import cn.hutool.core.text.CharPool;
import cn.hutool.core.text.StrUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.text.StrUtil;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.BitSet;
@@ -30,33 +32,11 @@ import java.util.BitSet;
* @author looly
* @since 5.7.16
*/
public class PercentCodec implements Serializable {
public class PercentCodec implements Encoder<byte[], byte[]>, Serializable {
private static final long serialVersionUID = 1L;
/**
* 从已知PercentCodec创建PercentCodec会复制给定PercentCodec的安全字符
*
* @param codec PercentCodec
* @return PercentCodec
*/
public static PercentCodec of(PercentCodec codec) {
return new PercentCodec((BitSet) codec.safeCharacters.clone());
}
/**
* 创建PercentCodec使用指定字符串中的字符作为安全字符
*
* @param chars 安全字符合集
* @return PercentCodec
*/
public static PercentCodec of(CharSequence chars) {
final PercentCodec codec = new PercentCodec();
final int length = chars.length();
for (int i = 0; i < length; i++) {
codec.addSafe(chars.charAt(i));
}
return codec;
}
private static final char DEFAULT_SIZE = 256;
private static final char ESCAPE_CHAR = CharPool.PERCENT;
/**
* 存放安全编码
@@ -75,7 +55,7 @@ public class PercentCodec implements Serializable {
* [a-zA-Z0-9]默认不被编码
*/
public PercentCodec() {
this(new BitSet(256));
this(new BitSet(DEFAULT_SIZE));
}
/**
@@ -88,68 +68,33 @@ public class PercentCodec implements Serializable {
}
/**
* 增加安全字符<br>
* 安全字符不被编码
* 检查给定字符是否为安全字符
*
* @param c 字符
* @return this
* @return {@code true}表示安全,否则非安全字符
* @since 6.0.0
*/
public PercentCodec addSafe(char c) {
safeCharacters.set(c);
return this;
public boolean isSafe(char c) {
return this.safeCharacters.get(c);
}
/**
* 移除安全字符<br>
* 安全字符不被编码
*
* @param c 字符
* @return this
*/
public PercentCodec removeSafe(char c) {
safeCharacters.clear(c);
return this;
}
@Override
public byte[] encode(byte[] bytes) {
// 初始容量计算简单粗暴假设所有byte都需要转义容量是三倍
final ByteBuffer buffer = ByteBuffer.allocate(bytes.length * 3);
//noinspection ForLoopReplaceableByForEach
for (int i = 0; i < bytes.length; i++) {
encodeTo(buffer, bytes[i]);
}
/**
* 增加安全字符到挡墙的PercentCodec
*
* @param codec PercentCodec
* @return this
*/
public PercentCodec or(PercentCodec codec) {
this.safeCharacters.or(codec.safeCharacters);
return this;
}
/**
* 组合当前PercentCodec和指定PercentCodec为一个新的PercentCodec安全字符为并集
*
* @param codec PercentCodec
* @return 新的PercentCodec
*/
public PercentCodec orNew(PercentCodec codec) {
return of(this).or(codec);
}
/**
* 是否将空格编码为+<br>
* 如果为{@code true},则将空格编码为"+",此项只在"application/x-www-form-urlencoded"中使用<br>
* 如果为{@code false},则空格编码为"%20",此项一般用于URL的Query部分RFC3986规范
*
* @param encodeSpaceAsPlus 是否将空格编码为+
* @return this
*/
public PercentCodec setEncodeSpaceAsPlus(boolean encodeSpaceAsPlus) {
this.encodeSpaceAsPlus = encodeSpaceAsPlus;
return this;
return buffer.array();
}
/**
* 将URL中的字符串编码为%形式
*
* @param path 需要编码的字符串
* @param charset 编码, {@code null}返回原字符串,表示不编码
* @param path 需要编码的字符串
* @param charset 编码, {@code null}返回原字符串,表示不编码
* @param customSafeChar 自定义安全字符
* @return 编码后的字符串
*/
@@ -158,7 +103,7 @@ public class PercentCodec implements Serializable {
return StrUtil.str(path);
}
final StringBuilder rewrittenPath = new StringBuilder(path.length());
final StringBuilder rewrittenPath = new StringBuilder(path.length() * 3);
final ByteArrayOutputStream buf = new ByteArrayOutputStream();
final OutputStreamWriter writer = new OutputStreamWriter(buf, charset);
@@ -184,7 +129,7 @@ public class PercentCodec implements Serializable {
byte[] ba = buf.toByteArray();
for (byte toEncode : ba) {
// Converting each byte in the buffer
rewrittenPath.append('%');
rewrittenPath.append(ESCAPE_CHAR);
HexUtil.appendHex(rewrittenPath, toEncode, false);
}
buf.reset();
@@ -192,4 +137,132 @@ public class PercentCodec implements Serializable {
}
return rewrittenPath.toString();
}
/**
* 将单一byte转义到{@link ByteBuffer}中
*
* @param buffer {@link ByteBuffer}
* @param b 字符byte
*/
private void encodeTo(final ByteBuffer buffer, final byte b) {
if (safeCharacters.get(b)) {
// 跳过安全字符
buffer.put(b);
} else if (encodeSpaceAsPlus && b == CharPool.SPACE) {
// 对于空格单独处理
buffer.put((byte) CharPool.PLUS);
} else {
buffer.put((byte) ESCAPE_CHAR);
buffer.put((byte) Base16Codec.CODEC_UPPER.hexDigit(b >> 4));
buffer.put((byte) Base16Codec.CODEC_UPPER.hexDigit(b));
}
}
/**
* {@link PercentCodec}构建器<br>
* 由于{@link PercentCodec}本身应该是只读对象因此将此对象的构建放在Builder中
*
* @author looly
* @since 6.0.0
*/
public static class Builder implements cn.hutool.core.builder.Builder<PercentCodec> {
/**
* 从已知PercentCodec创建PercentCodec会复制给定PercentCodec的安全字符
*
* @param codec PercentCodec
* @return PercentCodec
*/
public static Builder of(PercentCodec codec) {
return new Builder(new PercentCodec((BitSet) codec.safeCharacters.clone()));
}
/**
* 创建PercentCodec使用指定字符串中的字符作为安全字符
*
* @param chars 安全字符合集
* @return PercentCodec
*/
public static Builder of(CharSequence chars) {
Builder builder = of(new PercentCodec());
final int length = chars.length();
for (int i = 0; i < length; i++) {
builder.addSafe(chars.charAt(i));
}
return builder;
}
private final PercentCodec codec;
private Builder(PercentCodec codec) {
this.codec = codec;
}
/**
* 增加安全字符<br>
* 安全字符不被编码
*
* @param c 字符
* @return this
*/
public Builder addSafe(char c) {
codec.safeCharacters.set(c);
return this;
}
/**
* 增加安全字符<br>
* 安全字符不被编码
*
* @param chars 安全字符
* @return this
*/
public Builder addSafes(String chars) {
final int length = chars.length();
for (int i = 0; i < length; i++) {
addSafe(chars.charAt(i));
}
return this;
}
/**
* 移除安全字符<br>
* 安全字符不被编码
*
* @param c 字符
* @return this
*/
public Builder removeSafe(char c) {
codec.safeCharacters.clear(c);
return this;
}
/**
* 增加安全字符到当前的PercentCodec
*
* @param otherCodec {@link PercentCodec}
* @return this
*/
public Builder or(PercentCodec otherCodec) {
codec.safeCharacters.or(otherCodec.safeCharacters);
return this;
}
/**
* 是否将空格编码为+<br>
* 如果为{@code true},则将空格编码为"+",此项只在"application/x-www-form-urlencoded"中使用<br>
* 如果为{@code false},则空格编码为"%20",此项一般用于URL的Query部分RFC3986规范
*
* @param encodeSpaceAsPlus 是否将空格编码为+
* @return this
*/
public Builder setEncodeSpaceAsPlus(boolean encodeSpaceAsPlus) {
codec.encodeSpaceAsPlus = encodeSpaceAsPlus;
return this;
}
@Override
public PercentCodec build() {
return codec;
}
}
}

View File

@@ -115,9 +115,9 @@ public class UniqueKeySet<K, V> extends AbstractSet<V> implements Serializable {
return map.isEmpty();
}
@SuppressWarnings("unchecked")
@Override
public boolean contains(Object o) {
//noinspection unchecked
return map.containsKey(this.uniqueGenerator.apply((V) o));
}
@@ -151,9 +151,9 @@ public class UniqueKeySet<K, V> extends AbstractSet<V> implements Serializable {
return modified;
}
@SuppressWarnings("unchecked")
@Override
public boolean remove(Object o) {
//noinspection unchecked
return null != map.remove(this.uniqueGenerator.apply((V) o));
}

View File

@@ -101,15 +101,6 @@ public class FastByteArrayOutputStream extends OutputStream {
return toString(CharsetUtil.defaultCharset());
}
/**
* 转为字符串
* @param charsetName 编码
* @return 字符串
*/
public String toString(String charsetName) {
return toString(CharsetUtil.charset(charsetName));
}
/**
* 转为字符串
* @param charset 编码,null表示默认编码

View File

@@ -567,6 +567,7 @@ public class IoUtil extends NioUtil {
* @throws IORuntimeException IO异常
* @throws UtilException ClassNotFoundException包装
*/
@SuppressWarnings("unchecked")
public static <T> T readObj(ValidateObjectInputStream in, Class<T> clazz) throws IORuntimeException, UtilException {
if (in == null) {
throw new IllegalArgumentException("The InputStream must not be null");
@@ -575,7 +576,6 @@ public class IoUtil extends NioUtil {
in.accept(clazz);
}
try {
//noinspection unchecked
return (T) in.readObject();
} catch (IOException e) {
throw new IORuntimeException(e);

View File

@@ -71,12 +71,12 @@ public class CamelCaseMap<K, V> extends FuncKeyMap<K, V> {
*
* @param emptyMapBuilder Map构造器必须构造空的Map
*/
@SuppressWarnings("unchecked")
CamelCaseMap(MapBuilder<K, V> emptyMapBuilder) {
super(emptyMapBuilder.build(), (key) -> {
if (key instanceof CharSequence) {
key = StrUtil.toCamelCase(key.toString());
}
//noinspection unchecked
return (K) key;
});
}

View File

@@ -71,12 +71,12 @@ public class CaseInsensitiveMap<K, V> extends FuncKeyMap<K, V> {
*
* @param emptyMapBuilder 被包装的自定义Map创建器
*/
@SuppressWarnings("unchecked")
CaseInsensitiveMap(MapBuilder<K, V> emptyMapBuilder) {
super(emptyMapBuilder.build(), (key)->{
if (key instanceof CharSequence) {
key = key.toString().toLowerCase();
}
//noinspection unchecked
return (K) key;
});
}

View File

@@ -24,9 +24,9 @@ public abstract class CustomKeyMap<K, V> extends TransMap<K, V> {
super(emptyMap);
}
@SuppressWarnings("unchecked")
@Override
protected V customValue(Object value) {
//noinspection unchecked
return (V)value;
}
}

View File

@@ -37,12 +37,12 @@ public class FuncKeyMap<K, V> extends CustomKeyMap<K, V> {
* @param key KEY
* @return 驼峰Key
*/
@SuppressWarnings("unchecked")
@Override
protected K customKey(Object key) {
if (null != this.keyFunc) {
return keyFunc.apply(key);
}
//noinspection unchecked
return (K)key;
}
}

View File

@@ -53,21 +53,21 @@ public class FuncMap<K, V> extends TransMap<K, V> {
* @param key KEY
* @return 驼峰Key
*/
@SuppressWarnings("unchecked")
@Override
protected K customKey(Object key) {
if (null != this.keyFunc) {
return keyFunc.apply(key);
}
//noinspection unchecked
return (K) key;
}
@SuppressWarnings("unchecked")
@Override
protected V customValue(Object value) {
if (null != this.valueFunc) {
return valueFunc.apply(value);
}
//noinspection unchecked
return (V) value;
}
}

View File

@@ -69,9 +69,9 @@ public abstract class AbsTable<R, C, V> implements Table<R, C, V> {
return new TransIter<>(cellSet().iterator(), Cell::getValue);
}
@SuppressWarnings("unchecked")
@Override
public boolean contains(Object o) {
//noinspection unchecked
return containsValue((V) o);
}

View File

@@ -14,6 +14,6 @@ public class FormUrlencoded {
* query中的value默认除"-", "_", ".", "*"外都编码<br>
* 这个类似于JDK提供的{@link java.net.URLEncoder}
*/
public static final PercentCodec ALL = PercentCodec.of(RFC3986.UNRESERVED)
.removeSafe('~').addSafe('*').setEncodeSpaceAsPlus(true);
public static final PercentCodec ALL = PercentCodec.Builder.of(RFC3986.UNRESERVED)
.removeSafe('~').addSafe('*').setEncodeSpaceAsPlus(true).build();
}

View File

@@ -14,29 +14,29 @@ public class RFC3986 {
/**
* gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
*/
public static final PercentCodec GEN_DELIMS = PercentCodec.of(":/?#[]@");
public static final PercentCodec GEN_DELIMS = PercentCodec.Builder.of(":/?#[]@").build();
/**
* sub-delims = "!" / "$" / "{@code &}" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
*/
public static final PercentCodec SUB_DELIMS = PercentCodec.of("!$&'()*+,;=");
public static final PercentCodec SUB_DELIMS = PercentCodec.Builder.of("!$&'()*+,;=").build();
/**
* reserved = gen-delims / sub-delims<br>
* see<a href="https://www.ietf.org/rfc/rfc3986.html#section-2.2">https://www.ietf.org/rfc/rfc3986.html#section-2.2</a>
*/
public static final PercentCodec RESERVED = GEN_DELIMS.orNew(SUB_DELIMS);
public static final PercentCodec RESERVED = PercentCodec.Builder.of(GEN_DELIMS).or(SUB_DELIMS).build();
/**
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"<br>
* see: <a href="https://www.ietf.org/rfc/rfc3986.html#section-2.3">https://www.ietf.org/rfc/rfc3986.html#section-2.3</a>
*/
public static final PercentCodec UNRESERVED = PercentCodec.of(unreservedChars());
public static final PercentCodec UNRESERVED = PercentCodec.Builder.of(unreservedChars()).build();
/**
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
*/
public static final PercentCodec PCHAR = UNRESERVED.orNew(SUB_DELIMS).or(PercentCodec.of(":@"));
public static final PercentCodec PCHAR = PercentCodec.Builder.of(UNRESERVED).or(SUB_DELIMS).addSafes(":@").build();
/**
* segment = pchar<br>
@@ -46,17 +46,17 @@ public class RFC3986 {
/**
* segment-nz-nc = SEGMENT ; non-zero-length segment without any colon ":"
*/
public static final PercentCodec SEGMENT_NZ_NC = PercentCodec.of(SEGMENT).removeSafe(':');
public static final PercentCodec SEGMENT_NZ_NC = PercentCodec.Builder.of(SEGMENT).removeSafe(':').build();
/**
* path = segment / "/"
*/
public static final PercentCodec PATH = SEGMENT.orNew(PercentCodec.of("/"));
public static final PercentCodec PATH = PercentCodec.Builder.of(SEGMENT).addSafe('/').build();
/**
* query = pchar / "/" / "?"
*/
public static final PercentCodec QUERY = PCHAR.orNew(PercentCodec.of("/?"));
public static final PercentCodec QUERY = PercentCodec.Builder.of(PCHAR).addSafes("/?").build();
/**
* fragment = pchar / "/" / "?"
@@ -67,13 +67,13 @@ public class RFC3986 {
* query中的value<br>
* value不能包含"{@code &}",可以包含 "="
*/
public static final PercentCodec QUERY_PARAM_VALUE = PercentCodec.of(QUERY).removeSafe('&');
public static final PercentCodec QUERY_PARAM_VALUE = PercentCodec.Builder.of(QUERY).removeSafe('&').build();
/**
* query中的key<br>
* key不能包含"{@code &}" 和 "="
*/
public static final PercentCodec QUERY_PARAM_NAME = PercentCodec.of(QUERY_PARAM_VALUE).removeSafe('=');
public static final PercentCodec QUERY_PARAM_NAME = PercentCodec.Builder.of(QUERY_PARAM_VALUE).removeSafe('=').build();
/**
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"

View File

@@ -143,7 +143,7 @@ public class URLDecoder implements Serializable {
if (bytes == null) {
return null;
}
final ByteArrayOutputStream buffer = new ByteArrayOutputStream(bytes.length);
final ByteArrayOutputStream buffer = new ByteArrayOutputStream(bytes.length * 3);
int b;
for (int i = 0; i < bytes.length; i++) {
b = bytes[i];

View File

@@ -83,4 +83,12 @@ public interface CharPool {
* 字符常量:艾特 {@code '@'}
*/
char AT = '@';
/**
* 字符常量:加号 {@code '+'}
*/
char PLUS = '+';
/**
* 字符常量:百分号 {@code '%'}
*/
char PERCENT = '%';
}

View File

@@ -0,0 +1,18 @@
package cn.hutool.core.codec;
import org.junit.Assert;
import org.junit.Test;
public class PercentCodecTest {
@Test
public void isSafeTest(){
PercentCodec codec = PercentCodec.Builder.of("=").build();
Assert.assertTrue(codec.isSafe('='));
codec = PercentCodec.Builder.of("=").or(PercentCodec.Builder.of("abc").build()).build();
Assert.assertTrue(codec.isSafe('a'));
Assert.assertTrue(codec.isSafe('b'));
Assert.assertTrue(codec.isSafe('c'));
}
}

View File

@@ -6,6 +6,12 @@ import org.junit.Test;
public class RFC3986Test {
@Test
public void pacharTest(){
final String encode = RFC3986.PCHAR.encode("=", CharsetUtil.UTF_8);
Assert.assertEquals("=", encode);
}
@Test
public void encodeQueryTest(){
String encode = RFC3986.QUERY_PARAM_VALUE.encode("a=b", CharsetUtil.UTF_8);

View File

@@ -21,4 +21,12 @@ public class URLEncoderTest {
String encode2 = URLEncoder.encodeQuery(body);
Assert.assertEquals("+", encode2);
}
@Test
public void encodeEmojiTest(){
String emoji = "🐶😊😂🤣";
String encode = URLEncoder.encodeAll(emoji);
Assert.assertEquals("%F0%9F%90%B6%F0%9F%98%8A%F0%9F%98%82%F0%9F%A4%A3", encode);
Assert.assertEquals(emoji, URLDecoder.decode(encode));
}
}