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 注解 * @param annotation 注解
*/ */
@SuppressWarnings("unchecked")
public AnnotationProxy(T annotation) { public AnnotationProxy(T annotation) {
this.annotation = annotation; this.annotation = annotation;
//noinspection unchecked
this.type = (Class<T>) annotation.annotationType(); this.type = (Class<T>) annotation.annotationType();
this.attributes = initAttributes(); this.attributes = initAttributes();
} }

View File

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

View File

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

View File

@@ -23,18 +23,18 @@ public class Base16Codec implements Encoder<byte[], char[]>, Decoder<CharSequenc
* *
* @param lowerCase 是否小写 * @param lowerCase 是否小写
*/ */
public Base16Codec(boolean lowerCase) { public Base16Codec(final boolean lowerCase) {
this.alphabets = (lowerCase ? "0123456789abcdef" : "0123456789ABCDEF").toCharArray(); this.alphabets = (lowerCase ? "0123456789abcdef" : "0123456789ABCDEF").toCharArray();
} }
@Override @Override
public char[] encode(byte[] data) { public char[] encode(final byte[] data) {
final int len = data.length; final int len = data.length;
final char[] out = new char[len << 1];//len*2 final char[] out = new char[len << 1];//len*2
// two characters from the hex value. // two characters from the hex value.
for (int i = 0, j = 0; i < len; i++) { for (int i = 0, j = 0; i < len; i++) {
out[j++] = alphabets[(0xF0 & data[i]) >>> 4];// 高位 out[j++] = hexDigit(data[i] >> 4);// 高位
out[j++] = alphabets[0x0F & data[i]];// 低位 out[j++] = hexDigit(data[i]);// 低位
} }
return out; return out;
} }
@@ -79,12 +79,12 @@ public class Base16Codec implements Encoder<byte[], char[]>, Decoder<CharSequenc
* @param ch char值 * @param ch char值
* @return Unicode表现形式 * @return Unicode表现形式
*/ */
public String toUnicodeHex(char ch) { public String toUnicodeHex(final char ch) {
return "\\u" +// return "\\u" +
alphabets[(ch >> 12) & 15] +// hexDigit(ch >> 12) +
alphabets[(ch >> 8) & 15] +// hexDigit(ch >> 8) +
alphabets[(ch >> 4) & 15] +// hexDigit(ch >> 4) +
alphabets[(ch) & 15]; hexDigit(ch);
} }
/** /**
@@ -94,10 +94,21 @@ public class Base16Codec implements Encoder<byte[], char[]>, Decoder<CharSequenc
* @param b byte * @param b byte
*/ */
public void appendHex(StringBuilder builder, byte b) { public void appendHex(StringBuilder builder, byte b) {
int high = (b & 0xf0) >>> 4;//高位 //高位
int low = b & 0x0f;//低位 builder.append(hexDigit(b >> 4));
builder.append(alphabets[high]); //低位
builder.append(alphabets[low]); 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 一个整数 * @return 一个整数
* @throws UtilException 当ch不是一个合法的十六进制字符时抛出运行时异常 * @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); int digit = Character.digit(ch, 16);
if (digit < 0) { if (digit < 0) {
throw new UtilException("Illegal hexadecimal character {} at index {}", ch, index); throw new UtilException("Illegal hexadecimal character {} at index {}", ch, index);

View File

@@ -1,13 +1,15 @@
package cn.hutool.core.codec; 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.ArrayUtil;
import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.CharUtil;
import cn.hutool.core.text.StrUtil;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.Serializable; import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.BitSet; import java.util.BitSet;
@@ -30,33 +32,11 @@ import java.util.BitSet;
* @author looly * @author looly
* @since 5.7.16 * @since 5.7.16
*/ */
public class PercentCodec implements Serializable { public class PercentCodec implements Encoder<byte[], byte[]>, Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** private static final char DEFAULT_SIZE = 256;
* 从已知PercentCodec创建PercentCodec会复制给定PercentCodec的安全字符 private static final char ESCAPE_CHAR = CharPool.PERCENT;
*
* @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;
}
/** /**
* 存放安全编码 * 存放安全编码
@@ -75,7 +55,7 @@ public class PercentCodec implements Serializable {
* [a-zA-Z0-9]默认不被编码 * [a-zA-Z0-9]默认不被编码
*/ */
public PercentCodec() { public PercentCodec() {
this(new BitSet(256)); this(new BitSet(DEFAULT_SIZE));
} }
/** /**
@@ -88,61 +68,26 @@ public class PercentCodec implements Serializable {
} }
/** /**
* 增加安全字符<br> * 检查给定字符是否为安全字符
* 安全字符不被编码
* *
* @param c 字符 * @param c 字符
* @return this * @return {@code true}表示安全,否则非安全字符
* @since 6.0.0
*/ */
public PercentCodec addSafe(char c) { public boolean isSafe(char c) {
safeCharacters.set(c); return this.safeCharacters.get(c);
return this;
} }
/** @Override
* 移除安全字符<br> public byte[] encode(byte[] bytes) {
* 安全字符不被编码 // 初始容量计算简单粗暴假设所有byte都需要转义容量是三倍
* final ByteBuffer buffer = ByteBuffer.allocate(bytes.length * 3);
* @param c 字符 //noinspection ForLoopReplaceableByForEach
* @return this for (int i = 0; i < bytes.length; i++) {
*/ encodeTo(buffer, bytes[i]);
public PercentCodec removeSafe(char c) {
safeCharacters.clear(c);
return this;
} }
/** return buffer.array();
* 增加安全字符到挡墙的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;
} }
/** /**
@@ -158,7 +103,7 @@ public class PercentCodec implements Serializable {
return StrUtil.str(path); 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 ByteArrayOutputStream buf = new ByteArrayOutputStream();
final OutputStreamWriter writer = new OutputStreamWriter(buf, charset); final OutputStreamWriter writer = new OutputStreamWriter(buf, charset);
@@ -184,7 +129,7 @@ public class PercentCodec implements Serializable {
byte[] ba = buf.toByteArray(); byte[] ba = buf.toByteArray();
for (byte toEncode : ba) { for (byte toEncode : ba) {
// Converting each byte in the buffer // Converting each byte in the buffer
rewrittenPath.append('%'); rewrittenPath.append(ESCAPE_CHAR);
HexUtil.appendHex(rewrittenPath, toEncode, false); HexUtil.appendHex(rewrittenPath, toEncode, false);
} }
buf.reset(); buf.reset();
@@ -192,4 +137,132 @@ public class PercentCodec implements Serializable {
} }
return rewrittenPath.toString(); 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(); return map.isEmpty();
} }
@SuppressWarnings("unchecked")
@Override @Override
public boolean contains(Object o) { public boolean contains(Object o) {
//noinspection unchecked
return map.containsKey(this.uniqueGenerator.apply((V) o)); return map.containsKey(this.uniqueGenerator.apply((V) o));
} }
@@ -151,9 +151,9 @@ public class UniqueKeySet<K, V> extends AbstractSet<V> implements Serializable {
return modified; return modified;
} }
@SuppressWarnings("unchecked")
@Override @Override
public boolean remove(Object o) { public boolean remove(Object o) {
//noinspection unchecked
return null != map.remove(this.uniqueGenerator.apply((V) o)); return null != map.remove(this.uniqueGenerator.apply((V) o));
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -53,21 +53,21 @@ public class FuncMap<K, V> extends TransMap<K, V> {
* @param key KEY * @param key KEY
* @return 驼峰Key * @return 驼峰Key
*/ */
@SuppressWarnings("unchecked")
@Override @Override
protected K customKey(Object key) { protected K customKey(Object key) {
if (null != this.keyFunc) { if (null != this.keyFunc) {
return keyFunc.apply(key); return keyFunc.apply(key);
} }
//noinspection unchecked
return (K) key; return (K) key;
} }
@SuppressWarnings("unchecked")
@Override @Override
protected V customValue(Object value) { protected V customValue(Object value) {
if (null != this.valueFunc) { if (null != this.valueFunc) {
return valueFunc.apply(value); return valueFunc.apply(value);
} }
//noinspection unchecked
return (V) value; 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); return new TransIter<>(cellSet().iterator(), Cell::getValue);
} }
@SuppressWarnings("unchecked")
@Override @Override
public boolean contains(Object o) { public boolean contains(Object o) {
//noinspection unchecked
return containsValue((V) o); return containsValue((V) o);
} }

View File

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

View File

@@ -14,29 +14,29 @@ public class RFC3986 {
/** /**
* gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" * gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
*/ */
public static final PercentCodec GEN_DELIMS = PercentCodec.of(":/?#[]@"); public static final PercentCodec GEN_DELIMS = PercentCodec.Builder.of(":/?#[]@").build();
/** /**
* sub-delims = "!" / "$" / "{@code &}" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" * 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> * 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> * 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> * 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> * 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 / ":" / "@" * 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> * segment = pchar<br>
@@ -46,17 +46,17 @@ public class RFC3986 {
/** /**
* segment-nz-nc = SEGMENT ; non-zero-length segment without any colon ":" * 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 / "/" * path = segment / "/"
*/ */
public static final PercentCodec PATH = SEGMENT.orNew(PercentCodec.of("/")); public static final PercentCodec PATH = PercentCodec.Builder.of(SEGMENT).addSafe('/').build();
/** /**
* query = pchar / "/" / "?" * query = pchar / "/" / "?"
*/ */
public static final PercentCodec QUERY = PCHAR.orNew(PercentCodec.of("/?")); public static final PercentCodec QUERY = PercentCodec.Builder.of(PCHAR).addSafes("/?").build();
/** /**
* fragment = pchar / "/" / "?" * fragment = pchar / "/" / "?"
@@ -67,13 +67,13 @@ public class RFC3986 {
* query中的value<br> * query中的value<br>
* value不能包含"{@code &}",可以包含 "=" * 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> * query中的key<br>
* key不能包含"{@code &}" 和 "=" * 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 / "-" / "." / "_" / "~" * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"

View File

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

View File

@@ -83,4 +83,12 @@ public interface CharPool {
* 字符常量:艾特 {@code '@'} * 字符常量:艾特 {@code '@'}
*/ */
char AT = '@'; 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 { public class RFC3986Test {
@Test
public void pacharTest(){
final String encode = RFC3986.PCHAR.encode("=", CharsetUtil.UTF_8);
Assert.assertEquals("=", encode);
}
@Test @Test
public void encodeQueryTest(){ public void encodeQueryTest(){
String encode = RFC3986.QUERY_PARAM_VALUE.encode("a=b", CharsetUtil.UTF_8); 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); String encode2 = URLEncoder.encodeQuery(body);
Assert.assertEquals("+", encode2); 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));
}
} }

View File

@@ -103,6 +103,7 @@ public class JSONConverter implements Converter<JSON> {
* @throws ConvertException 转换失败 * @throws ConvertException 转换失败
* @since 5.7.10 * @since 5.7.10
*/ */
@SuppressWarnings("unchecked")
protected static <T> T jsonToBean(Type targetType, Object value, boolean ignoreError) throws ConvertException { protected static <T> T jsonToBean(Type targetType, Object value, boolean ignoreError) throws ConvertException {
if (JSONUtil.isNull(value)) { if (JSONUtil.isNull(value)) {
return null; return null;
@@ -111,7 +112,6 @@ public class JSONConverter implements Converter<JSON> {
if(value instanceof JSON){ if(value instanceof JSON){
final JSONDeserializer<?> deserializer = GlobalSerializeMapping.getDeserializer(targetType); final JSONDeserializer<?> deserializer = GlobalSerializeMapping.getDeserializer(targetType);
if(null != deserializer) { if(null != deserializer) {
//noinspection unchecked
return (T) deserializer.deserialize((JSON) value); return (T) deserializer.deserialize((JSON) value);
} }

View File

@@ -182,6 +182,7 @@ public class ExcelBase<T extends ExcelBase<T>> implements Closeable {
* @return this * @return this
* @since 5.7.10 * @since 5.7.10
*/ */
@SuppressWarnings("unchecked")
public T cloneSheet(int sheetIndex, String newSheetName, boolean setAsCurrentSheet) { public T cloneSheet(int sheetIndex, String newSheetName, boolean setAsCurrentSheet) {
Sheet sheet; Sheet sheet;
if (this.workbook instanceof XSSFWorkbook) { if (this.workbook instanceof XSSFWorkbook) {
@@ -194,7 +195,6 @@ public class ExcelBase<T extends ExcelBase<T>> implements Closeable {
if (setAsCurrentSheet) { if (setAsCurrentSheet) {
this.sheet = sheet; this.sheet = sheet;
} }
//noinspection unchecked
return (T) this; return (T) this;
} }
@@ -526,9 +526,9 @@ public class ExcelBase<T extends ExcelBase<T>> implements Closeable {
* @param headerAlias 别名Map * @param headerAlias 别名Map
* @return this * @return this
*/ */
@SuppressWarnings("unchecked")
public T setHeaderAlias(Map<String, String> headerAlias) { public T setHeaderAlias(Map<String, String> headerAlias) {
this.headerAlias = headerAlias; this.headerAlias = headerAlias;
//noinspection unchecked
return (T) this; return (T) this;
} }
@@ -539,6 +539,7 @@ public class ExcelBase<T extends ExcelBase<T>> implements Closeable {
* @param alias 别名 * @param alias 别名
* @return this * @return this
*/ */
@SuppressWarnings("unchecked")
public T addHeaderAlias(String header, String alias) { public T addHeaderAlias(String header, String alias) {
Map<String, String> headerAlias = this.headerAlias; Map<String, String> headerAlias = this.headerAlias;
if (null == headerAlias) { if (null == headerAlias) {
@@ -546,7 +547,6 @@ public class ExcelBase<T extends ExcelBase<T>> implements Closeable {
} }
this.headerAlias = headerAlias; this.headerAlias = headerAlias;
this.headerAlias.put(header, alias); this.headerAlias.put(header, alias);
//noinspection unchecked
return (T) this; return (T) this;
} }
@@ -556,9 +556,9 @@ public class ExcelBase<T extends ExcelBase<T>> implements Closeable {
* @param header 标题 * @param header 标题
* @return this * @return this
*/ */
@SuppressWarnings("unchecked")
public T removeHeaderAlias(String header) { public T removeHeaderAlias(String header) {
this.headerAlias.remove(header); this.headerAlias.remove(header);
//noinspection unchecked
return (T) this; return (T) this;
} }
@@ -567,9 +567,9 @@ public class ExcelBase<T extends ExcelBase<T>> implements Closeable {
* *
* @return this * @return this
*/ */
@SuppressWarnings("unchecked")
public T clearHeaderAlias() { public T clearHeaderAlias() {
this.headerAlias = null; this.headerAlias = null;
//noinspection unchecked
return (T) this; return (T) this;
} }
} }

View File

@@ -84,10 +84,10 @@ public class YamlUtil {
* @param isCloseReader 加载完毕后是否关闭{@link Reader} * @param isCloseReader 加载完毕后是否关闭{@link Reader}
* @return 加载的内容默认Map * @return 加载的内容默认Map
*/ */
@SuppressWarnings("unchecked")
public static <T> T load(Reader reader, Class<T> type, boolean isCloseReader) { public static <T> T load(Reader reader, Class<T> type, boolean isCloseReader) {
Assert.notNull(reader, "Reader must be not null !"); Assert.notNull(reader, "Reader must be not null !");
if (null == type) { if (null == type) {
//noinspection unchecked
type = (Class<T>) Object.class; type = (Class<T>) Object.class;
} }

View File

@@ -36,15 +36,15 @@
<!-- versions --> <!-- versions -->
<compile.version>8</compile.version> <compile.version>8</compile.version>
<junit.version>4.13.2</junit.version> <junit.version>5.8.2</junit.version>
<lombok.version>1.18.24</lombok.version> <lombok.version>1.18.24</lombok.version>
</properties> </properties>
<dependencies> <dependencies>
<!-- 全局单元测试 --> <!-- 全局单元测试 -->
<dependency> <dependency>
<groupId>junit</groupId> <groupId>org.junit.vintage</groupId>
<artifactId>junit</artifactId> <artifactId>junit-vintage-engine</artifactId>
<version>${junit.version}</version> <version>${junit.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>