diff --git a/CHANGELOG.md b/CHANGELOG.md index ec59619be..0c6036887 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.5.2 (2020-11-20) +# 5.5.2 (2020-11-25) ### 新特性 * 【crypto 】 KeyUtil增加重载,AES构造增加重载(issue#I25NNZ@Gitee) @@ -12,12 +12,17 @@ * 【extra 】 新增Rhino表达式执行引擎(pr#1229@Github) * 【crypto 】 增加判空(issue#1230@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修复 * 【cron 】 修复CronTimer可能死循环的问题(issue#1224@Github) * 【core 】 修复Calculator.conversion单个数字越界问题(issue#1222@Github) * 【poi 】 修复ExcelUtil.getSaxReader使用非MarkSupport流报错问题(issue#1225@Github) * 【core 】 修复HexUtil.format问题(issue#I268XT@Gitee) +* 【core 】 修复ZipUtil判断压缩文件是否位于压缩目录内的逻辑有误的问题(issue#1251@Github) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java index f46009980..8888324f8 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java @@ -6,6 +6,7 @@ import cn.hutool.core.lang.func.Func0; import java.util.Iterator; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.StampedLock; /** @@ -44,11 +45,11 @@ public abstract class AbstractCache implements Cache { /** * 命中数 */ - protected int hitCount; + protected AtomicLong hitCount; /** * 丢失数 */ - protected int missCount; + protected AtomicLong missCount; // ---------------------------------------------------------------- put start @Override @@ -113,15 +114,15 @@ public abstract class AbstractCache implements Cache { /** * @return 命中数 */ - public int getHitCount() { - return hitCount; + public long getHitCount() { + return hitCount.get(); } /** * @return 丢失数 */ - public int getMissCount() { - return missCount; + public long getMissCount() { + return missCount.get(); } @Override @@ -157,15 +158,15 @@ public abstract class AbstractCache implements Cache { // 不存在或已移除 final CacheObj co = cacheMap.get(key); if (null == co) { - missCount++; + missCount.getAndIncrement(); return null; } if (co.isExpired()) { - missCount++; + missCount.getAndIncrement(); } else{ // 命中 - hitCount++; + hitCount.getAndIncrement(); return co.get(isUpdateLastAccess); } } finally { @@ -318,7 +319,7 @@ public abstract class AbstractCache implements Cache { final CacheObj co = cacheMap.remove(key); if (withMissCount) { // 在丢失计数有效的情况下,移除一般为get时的超时操作,此处应该丢失数+1 - this.missCount++; + this.missCount.getAndIncrement(); } return co; } diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/CacheObj.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/CacheObj.java index 62a4cf2bd..fea9009dd 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/CacheObj.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/CacheObj.java @@ -1,6 +1,7 @@ package cn.hutool.cache.impl; import java.io.Serializable; +import java.util.concurrent.atomic.AtomicLong; /** * 缓存对象 @@ -16,9 +17,9 @@ public class CacheObj implements Serializable{ protected final V obj; /** 上次访问时间 */ - private long lastAccess; + private volatile long lastAccess; /** 访问次数 */ - protected long accessCount; + protected AtomicLong accessCount; /** 对象存活时长,0表示永久存活*/ private final long ttl; @@ -61,7 +62,7 @@ public class CacheObj implements Serializable{ if(isUpdateLastAccess) { lastAccess = System.currentTimeMillis(); } - accessCount++; + accessCount.getAndIncrement(); return this.obj; } diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java index 3b04b8f33..d06a0ef98 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java @@ -69,21 +69,20 @@ public class LFUCache extends AbstractCache { } //找出访问最少的对象 - if (comin == null || co.accessCount < comin.accessCount) { + if (comin == null || co.accessCount.get() < comin.accessCount.get()) { comin = co; } } // 减少所有对象访问量,并清除减少后为0的访问对象 if (isFull() && comin != null) { - long minAccessCount = comin.accessCount; + long minAccessCount = comin.accessCount.get(); values = cacheMap.values().iterator(); CacheObj co1; while (values.hasNext()) { co1 = values.next(); - co1.accessCount -= minAccessCount; - if (co1.accessCount <= 0) { + if (co1.accessCount.addAndGet(-minAccessCount) <= 0) { values.remove(); onRemove(co1.key, co1.obj); count++; diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java index ad07b7f73..7c6b841fd 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java @@ -13,6 +13,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; @@ -200,4 +201,18 @@ public class AnnotationUtil { public static boolean isInherited(Class annotationType) { 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); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java b/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java index 979b568c8..5aa6ea320 100644 --- a/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java +++ b/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java @@ -3,6 +3,7 @@ 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; @@ -75,6 +76,17 @@ public class Base64 { 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安全 * @@ -120,6 +132,17 @@ public class Base64 { 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安全的 * @@ -175,60 +198,6 @@ public class Base64 { 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
* 如果isMultiLine为true,则每76个字符一个换行符,否则在一行显示 diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java index 5815c3632..365b42b0d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java @@ -819,10 +819,10 @@ public class DateUtil extends CalendarUtil { int length = utcString.length(); if (StrUtil.contains(utcString, 'Z')) { 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); } 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); } } else { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java index 6603a135a..96f17de45 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java @@ -1972,7 +1972,7 @@ public class NumberUtil { * @param number A Number * @return A String. */ - public static String toStr(Number number) { + public static String toStr(Number number) { Assert.notNull(number, "Number is null !"); // BigDecimal单独处理,使用非科学计数法 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index a8d025c5d..ccafd1506 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -459,13 +459,12 @@ public class ZipUtil { * @throws IORuntimeException IO异常 * @since 4.5.8 */ - @SuppressWarnings("unchecked") public static File unzip(ZipFile zipFile, File outFile) throws IORuntimeException { if(outFile.exists() && outFile.isFile()){ throw new UtilException("Target path [{}] exist!", outFile.getAbsolutePath()); } try { - final Enumeration em = (Enumeration) zipFile.entries(); + final Enumeration em = zipFile.entries(); ZipEntry zipEntry; File outItemFile; while (em.hasMoreElements()) { @@ -487,6 +486,40 @@ public class ZipUtil { 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; + } + /** * 解压
* ZIP条目不使用高速缓冲。 @@ -1024,15 +1057,9 @@ public class ZipUtil { throw new UtilException(StrUtil.format("File [{}] not exist!", srcFile.getAbsolutePath())); } - try { - final File parentFile = zipFile.getCanonicalFile().getParentFile(); - // 压缩文件不能位于被压缩的目录内 - 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); + // 压缩文件不能位于被压缩的目录内 + 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()); } } } diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index d1f45e077..43de63973 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -377,4 +377,11 @@ public class FileUtilTest { String mimeType = FileUtil.getMimeType("test2Write.jpg"); 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)); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/BCrypt.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/BCrypt.java index 7113bb467..cf96e7997 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/BCrypt.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/BCrypt.java @@ -179,7 +179,7 @@ public class BCrypt { private static byte char64(char x) { if ((int) x > index_64.length) return -1; - return index_64[(int) x]; + return index_64[x]; } /** diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschUtil.java index cd663a15d..199c39397 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschUtil.java @@ -446,8 +446,7 @@ public class JschUtil { /** * 执行Shell命令 *

- * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭channel,不会产生阻塞。
- * 此方法返回数据中可能 + * 此方法单次发送一个命令到服务端,自动读取环境变量,执行结束后自动关闭channel,不会产生阻塞。 *

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