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

This commit is contained in:
lzpeng723 2020-11-26 19:15:39 +08:00
commit 211413f789
13 changed files with 123 additions and 93 deletions

View File

@ -3,7 +3,7 @@
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------
# 5.5.2 (2020-11-20) # 5.5.2 (2020-11-25)
### 新特性 ### 新特性
* 【crypto 】 KeyUtil增加重载AES构造增加重载issue#I25NNZ@Gitee * 【crypto 】 KeyUtil增加重载AES构造增加重载issue#I25NNZ@Gitee
@ -12,12 +12,17 @@
* 【extra 】 新增Rhino表达式执行引擎pr#1229@Github * 【extra 】 新增Rhino表达式执行引擎pr#1229@Github
* 【crypto 】 增加判空issue#1230@Github * 【crypto 】 增加判空issue#1230@Github
* 【core 】 xml.setXmlStandalone(true)格式优化pr#1234@Github * 【core 】 xml.setXmlStandalone(true)格式优化pr#1234@Github
* 【core 】 AnnotationUtil增加setValue方法pr#1250@Github
* 【core 】 ZipUtil增加get方法
* 【cache 】 对CacheObj等变量使用volatile关键字
* 【core 】 Base64增加encodeWithoutPadding方法issue#I26J16@Gitee
### Bug修复 ### Bug修复
* 【cron 】 修复CronTimer可能死循环的问题issue#1224@Github * 【cron 】 修复CronTimer可能死循环的问题issue#1224@Github
* 【core 】 修复Calculator.conversion单个数字越界问题issue#1222@Github * 【core 】 修复Calculator.conversion单个数字越界问题issue#1222@Github
* 【poi 】 修复ExcelUtil.getSaxReader使用非MarkSupport流报错问题issue#1225@Github * 【poi 】 修复ExcelUtil.getSaxReader使用非MarkSupport流报错问题issue#1225@Github
* 【core 】 修复HexUtil.format问题issue#I268XT@Gitee * 【core 】 修复HexUtil.format问题issue#I268XT@Gitee
* 【core 】 修复ZipUtil判断压缩文件是否位于压缩目录内的逻辑有误的问题issue#1251@Github
------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------

View File

@ -6,6 +6,7 @@ import cn.hutool.core.lang.func.Func0;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.StampedLock; import java.util.concurrent.locks.StampedLock;
/** /**
@ -44,11 +45,11 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
/** /**
* 命中数 * 命中数
*/ */
protected int hitCount; protected AtomicLong hitCount;
/** /**
* 丢失数 * 丢失数
*/ */
protected int missCount; protected AtomicLong missCount;
// ---------------------------------------------------------------- put start // ---------------------------------------------------------------- put start
@Override @Override
@ -113,15 +114,15 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
/** /**
* @return 命中数 * @return 命中数
*/ */
public int getHitCount() { public long getHitCount() {
return hitCount; return hitCount.get();
} }
/** /**
* @return 丢失数 * @return 丢失数
*/ */
public int getMissCount() { public long getMissCount() {
return missCount; return missCount.get();
} }
@Override @Override
@ -157,15 +158,15 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
// 不存在或已移除 // 不存在或已移除
final CacheObj<K, V> co = cacheMap.get(key); final CacheObj<K, V> co = cacheMap.get(key);
if (null == co) { if (null == co) {
missCount++; missCount.getAndIncrement();
return null; return null;
} }
if (co.isExpired()) { if (co.isExpired()) {
missCount++; missCount.getAndIncrement();
} else{ } else{
// 命中 // 命中
hitCount++; hitCount.getAndIncrement();
return co.get(isUpdateLastAccess); return co.get(isUpdateLastAccess);
} }
} finally { } finally {
@ -318,7 +319,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
final CacheObj<K, V> co = cacheMap.remove(key); final CacheObj<K, V> co = cacheMap.remove(key);
if (withMissCount) { if (withMissCount) {
// 在丢失计数有效的情况下移除一般为get时的超时操作此处应该丢失数+1 // 在丢失计数有效的情况下移除一般为get时的超时操作此处应该丢失数+1
this.missCount++; this.missCount.getAndIncrement();
} }
return co; return co;
} }

View File

@ -1,6 +1,7 @@
package cn.hutool.cache.impl; package cn.hutool.cache.impl;
import java.io.Serializable; import java.io.Serializable;
import java.util.concurrent.atomic.AtomicLong;
/** /**
* 缓存对象 * 缓存对象
@ -16,9 +17,9 @@ public class CacheObj<K, V> implements Serializable{
protected final V obj; protected final V obj;
/** 上次访问时间 */ /** 上次访问时间 */
private long lastAccess; private volatile long lastAccess;
/** 访问次数 */ /** 访问次数 */
protected long accessCount; protected AtomicLong accessCount;
/** 对象存活时长0表示永久存活*/ /** 对象存活时长0表示永久存活*/
private final long ttl; private final long ttl;
@ -61,7 +62,7 @@ public class CacheObj<K, V> implements Serializable{
if(isUpdateLastAccess) { if(isUpdateLastAccess) {
lastAccess = System.currentTimeMillis(); lastAccess = System.currentTimeMillis();
} }
accessCount++; accessCount.getAndIncrement();
return this.obj; return this.obj;
} }

View File

@ -69,21 +69,20 @@ public class LFUCache<K, V> extends AbstractCache<K, V> {
} }
//找出访问最少的对象 //找出访问最少的对象
if (comin == null || co.accessCount < comin.accessCount) { if (comin == null || co.accessCount.get() < comin.accessCount.get()) {
comin = co; comin = co;
} }
} }
// 减少所有对象访问量并清除减少后为0的访问对象 // 减少所有对象访问量并清除减少后为0的访问对象
if (isFull() && comin != null) { if (isFull() && comin != null) {
long minAccessCount = comin.accessCount; long minAccessCount = comin.accessCount.get();
values = cacheMap.values().iterator(); values = cacheMap.values().iterator();
CacheObj<K, V> co1; CacheObj<K, V> co1;
while (values.hasNext()) { while (values.hasNext()) {
co1 = values.next(); co1 = values.next();
co1.accessCount -= minAccessCount; if (co1.accessCount.addAndGet(-minAccessCount) <= 0) {
if (co1.accessCount <= 0) {
values.remove(); values.remove();
onRemove(co1.key, co1.obj); onRemove(co1.key, co1.obj);
count++; count++;

View File

@ -13,6 +13,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -200,4 +201,18 @@ public class AnnotationUtil {
public static boolean isInherited(Class<? extends Annotation> annotationType) { public static boolean isInherited(Class<? extends Annotation> annotationType) {
return annotationType.isAnnotationPresent(Inherited.class); return annotationType.isAnnotationPresent(Inherited.class);
} }
/**
* 设置新的注解的属性字段
*
* @param annotation 注解对象
* @param annotationField 注解属性字段名称
* @param value 要更新的属性值
* @since 5.5.2
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public static void setValue(Annotation annotation, String annotationField, Object value) {
final Map memberValues = (Map) ReflectUtil.getFieldValue(Proxy.getInvocationHandler(annotation), "memberValues");
memberValues.put(annotationField, value);
}
} }

View File

@ -3,6 +3,7 @@ package cn.hutool.core.codec;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
@ -75,6 +76,17 @@ public class Base64 {
return encode(source, CharsetUtil.charset(charset)); return encode(source, CharsetUtil.charset(charset));
} }
/**
* base64编码不进行padding(末尾不会填充'=')
*
* @param source 被编码的base64字符串
* @return 被加密后的字符串
* @since 5.5.2
*/
public static String encodeWithoutPadding(CharSequence source, String charset) {
return encodeWithoutPadding(StrUtil.bytes(source, charset));
}
/** /**
* base64编码,URL安全 * base64编码,URL安全
* *
@ -120,6 +132,17 @@ public class Base64 {
return Base64Encoder.encode(source); return Base64Encoder.encode(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安全的 * base64编码,URL安全的
* *
@ -175,60 +198,6 @@ public class Base64 {
return Base64Encoder.encodeUrlSafe(FileUtil.readBytes(file)); return Base64Encoder.encodeUrlSafe(FileUtil.readBytes(file));
} }
/**
* base64编码
*
* @param source 被编码的base64字符串
* @param charset 字符集
* @return 被加密后的字符串
* @deprecated 编码参数无意义作废
*/
@Deprecated
public static String encode(byte[] source, String charset) {
return Base64Encoder.encode(source);
}
/**
* base64编码URL安全的
*
* @param source 被编码的base64字符串
* @param charset 字符集
* @return 被加密后的字符串
* @since 3.0.6
* @deprecated 编码参数无意义作废
*/
@Deprecated
public static String encodeUrlSafe(byte[] source, String charset) {
return Base64Encoder.encodeUrlSafe(source);
}
/**
* base64编码
*
* @param source 被编码的base64字符串
* @param charset 字符集
* @return 被加密后的字符串
* @deprecated 编码参数无意义作废
*/
@Deprecated
public static String encode(byte[] source, Charset charset) {
return Base64Encoder.encode(source);
}
/**
* base64编码URL安全的
*
* @param source 被编码的base64字符串
* @param charset 字符集
* @return 被加密后的字符串
* @since 3.0.6
* @deprecated 编码参数无意义作废
*/
@Deprecated
public static String encodeUrlSafe(byte[] source, Charset charset) {
return Base64Encoder.encodeUrlSafe(source);
}
/** /**
* 编码为Base64<br> * 编码为Base64<br>
* 如果isMultiLine为<code>true</code>则每76个字符一个换行符否则在一行显示 * 如果isMultiLine为<code>true</code>则每76个字符一个换行符否则在一行显示

View File

@ -819,10 +819,10 @@ public class DateUtil extends CalendarUtil {
int length = utcString.length(); int length = utcString.length();
if (StrUtil.contains(utcString, 'Z')) { if (StrUtil.contains(utcString, 'Z')) {
if (length == DatePattern.UTC_PATTERN.length() - 4) { if (length == DatePattern.UTC_PATTERN.length() - 4) {
// 格式类似2018-09-13T05:34:31Z // 格式类似2018-09-13T05:34:31Z-4表示减去4个单引号的长度
return parse(utcString, DatePattern.UTC_FORMAT); return parse(utcString, DatePattern.UTC_FORMAT);
} else if (length == DatePattern.UTC_MS_PATTERN.length() - 4) { } else if (length == DatePattern.UTC_MS_PATTERN.length() - 4) {
// 格式类似2018-09-13T05:34:31.999Z // 格式类似2018-09-13T05:34:31.999Z-4表示减去4个单引号的长度
return parse(utcString, DatePattern.UTC_MS_FORMAT); return parse(utcString, DatePattern.UTC_MS_FORMAT);
} }
} else { } else {

View File

@ -1972,7 +1972,7 @@ public class NumberUtil {
* @param number A Number * @param number A Number
* @return A String. * @return A String.
*/ */
public static String toStr(Number number) { public static String toStr(Number number) {
Assert.notNull(number, "Number is null !"); Assert.notNull(number, "Number is null !");
// BigDecimal单独处理使用非科学计数法 // BigDecimal单独处理使用非科学计数法

View File

@ -459,13 +459,12 @@ public class ZipUtil {
* @throws IORuntimeException IO异常 * @throws IORuntimeException IO异常
* @since 4.5.8 * @since 4.5.8
*/ */
@SuppressWarnings("unchecked")
public static File unzip(ZipFile zipFile, File outFile) throws IORuntimeException { public static File unzip(ZipFile zipFile, File outFile) throws IORuntimeException {
if(outFile.exists() && outFile.isFile()){ if(outFile.exists() && outFile.isFile()){
throw new UtilException("Target path [{}] exist!", outFile.getAbsolutePath()); throw new UtilException("Target path [{}] exist!", outFile.getAbsolutePath());
} }
try { try {
final Enumeration<ZipEntry> em = (Enumeration<ZipEntry>) zipFile.entries(); final Enumeration<? extends ZipEntry> em = zipFile.entries();
ZipEntry zipEntry; ZipEntry zipEntry;
File outItemFile; File outItemFile;
while (em.hasMoreElements()) { while (em.hasMoreElements()) {
@ -487,6 +486,40 @@ public class ZipUtil {
return outFile; return outFile;
} }
/**
* 获取压缩包中的指定文件流
* @param zipFile 压缩文件
* @param path 需要提取文件的文件名或路径
* @return 压缩文件流如果未找到返回{@code null}
* @since 5.5.2
*/
public static InputStream get(File zipFile, Charset charset, String path){
try {
return get(new ZipFile(zipFile, charset), path);
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
/**
* 获取压缩包中的指定文件流
* @param zipFile 压缩文件
* @param path 需要提取文件的文件名或路径
* @return 压缩文件流如果未找到返回{@code null}
* @since 5.5.2
*/
public static InputStream get(ZipFile zipFile, String path){
final ZipEntry entry = zipFile.getEntry(path);
if(null != entry){
try {
return zipFile.getInputStream(entry);
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
return null;
}
/** /**
* 解压<br> * 解压<br>
* ZIP条目不使用高速缓冲 * ZIP条目不使用高速缓冲
@ -1024,15 +1057,9 @@ public class ZipUtil {
throw new UtilException(StrUtil.format("File [{}] not exist!", srcFile.getAbsolutePath())); throw new UtilException(StrUtil.format("File [{}] not exist!", srcFile.getAbsolutePath()));
} }
try { // 压缩文件不能位于被压缩的目录内
final File parentFile = zipFile.getCanonicalFile().getParentFile(); if(srcFile.isDirectory() && FileUtil.isSub(srcFile, zipFile.getParentFile())){
// 压缩文件不能位于被压缩的目录内 throw new UtilException("Zip file path [{}] must not be the child directory of [{}] !", zipFile.getPath(), srcFile.getPath());
if (srcFile.isDirectory() && parentFile.getCanonicalPath().contains(srcFile.getCanonicalPath())) {
throw new UtilException("Zip file path [{}] must not be the child directory of [{}] !", zipFile.getCanonicalPath(), srcFile.getCanonicalPath());
}
} catch (IOException e) {
throw new UtilException(e);
} }
} }
} }

View File

@ -377,4 +377,11 @@ public class FileUtilTest {
String mimeType = FileUtil.getMimeType("test2Write.jpg"); String mimeType = FileUtil.getMimeType("test2Write.jpg");
Assert.assertEquals("image/jpeg", mimeType); Assert.assertEquals("image/jpeg", mimeType);
} }
@Test
public void isSubTest() {
File file = new File("d:/test");
File file2 = new File("d:/test2/aaa");
Assert.assertFalse(FileUtil.isSub(file, file2));
}
} }

View File

@ -179,7 +179,7 @@ public class BCrypt {
private static byte char64(char x) { private static byte char64(char x) {
if ((int) x > index_64.length) if ((int) x > index_64.length)
return -1; return -1;
return index_64[(int) x]; return index_64[x];
} }
/** /**

View File

@ -446,8 +446,7 @@ public class JschUtil {
/** /**
* 执行Shell命令 * 执行Shell命令
* <p> * <p>
* 此方法单次发送一个命令到服务端自动读取环境变量执行结束后自动关闭channel不会产生阻塞<br> * 此方法单次发送一个命令到服务端自动读取环境变量执行结束后自动关闭channel不会产生阻塞
* 此方法返回数据中可能
* </p> * </p>
* *
* @param session Session会话 * @param session Session会话

View File

@ -20,7 +20,7 @@ public class JSONUtilTest {
* 出现语法错误时报错检查解析\x字符时是否会导致死循环异常 * 出现语法错误时报错检查解析\x字符时是否会导致死循环异常
*/ */
@Test(expected = JSONException.class) @Test(expected = JSONException.class)
public void parseTest(){ public void parseTest() {
JSONArray jsonArray = JSONUtil.parseArray("[{\"a\":\"a\\x]"); JSONArray jsonArray = JSONUtil.parseArray("[{\"a\":\"a\\x]");
Console.log(jsonArray); Console.log(jsonArray);
} }
@ -29,7 +29,7 @@ public class JSONUtilTest {
* 数字解析为JSONArray报错 * 数字解析为JSONArray报错
*/ */
@Test(expected = JSONException.class) @Test(expected = JSONException.class)
public void parseNumberTest(){ public void parseNumberTest() {
JSONArray json = JSONUtil.parseArray(123L); JSONArray json = JSONUtil.parseArray(123L);
Console.log(json); Console.log(json);
} }
@ -38,7 +38,7 @@ public class JSONUtilTest {
* 数字解析为JSONObject忽略 * 数字解析为JSONObject忽略
*/ */
@Test @Test
public void parseNumberTest2(){ public void parseNumberTest2() {
JSONObject json = JSONUtil.parseObj(123L); JSONObject json = JSONUtil.parseObj(123L);
Assert.assertEquals(new JSONObject(), json); Assert.assertEquals(new JSONObject(), json);
} }
@ -156,11 +156,18 @@ public class JSONUtilTest {
} }
@Test @Test
public void doubleTest(){ public void doubleTest() {
String json = "{\"test\": 12.00}"; String json = "{\"test\": 12.00}";
final JSONObject jsonObject = JSONUtil.parseObj(json); final JSONObject jsonObject = JSONUtil.parseObj(json);
//noinspection BigDecimalMethodWithoutRoundingCalled //noinspection BigDecimalMethodWithoutRoundingCalled
Assert.assertEquals("12.00", jsonObject.getBigDecimal("test").setScale(2).toString()); Assert.assertEquals("12.00", jsonObject.getBigDecimal("test").setScale(2).toString());
} }
@Test
public void parseObjTest() {
final JSONObject jsonObject = JSONUtil.parseObj("{\n" +
" \"test\": \"\\\\地库地库\",\n" +
"}");
}
} }