From 0d7ef8f0928d6a4e51112ba53c4ec47643117bbd Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 26 Mar 2020 23:46:36 +0800 Subject: [PATCH 001/157] add LockUtil and fix Cache --- CHANGELOG.md | 1 + .../cn/hutool/cache/impl/AbstractCache.java | 154 +++++++----------- .../cache/test/CacheConcurrentTest.java | 74 ++++----- .../java/cn/hutool/cache/test/CacheTest.java | 2 +- .../java/cn/hutool/core/lang/SimpleCache.java | 66 ++++---- .../cn/hutool/core/thread/lock/LockUtil.java | 43 +++++ .../cn/hutool/core/thread/lock/NoLock.java | 6 +- .../cn/hutool/core/lang/SimpleCacheTest.java | 46 ++++++ hutool-script/pom.xml | 32 +++- .../cn/hutool/script/JavaScriptEngine.java | 29 ++-- .../hutool/script/ScriptRuntimeException.java | 5 +- .../java/cn/hutool/script/ScriptUtil.java | 111 ++++++++++--- .../cn/hutool/script/test/ScriptUtilTest.java | 28 +++- 13 files changed, 379 insertions(+), 218 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 394e1b4f2..19257f5e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * 【core 】 CollUtil.newHashSet重载歧义,更换为set方法 * 【core 】 增加ListUtil,增加Hash32、Hash64、Hash128接口 * 【crypto 】 BCUtil增加readPemPrivateKey和readPemPublicKey方法 +* 【cache 】 替换读写锁为StampedLock,增加LockUtil ### Bug修复 * 【core 】 修复NumberWordFormatter拼写错误(issue#799@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 bb67d4e91..143afde03 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,9 +6,7 @@ import cn.hutool.core.lang.func.Func0; import java.util.Iterator; import java.util.Map; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; +import java.util.concurrent.locks.StampedLock; /** * 超时和限制大小的缓存的默认实现
@@ -17,32 +15,39 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; *
  • 创建一个新的Map
  • *
  • 实现 prune 策略
  • * - * - * @author Looly,jodd * * @param 键类型 * @param 值类型 + * @author Looly, jodd */ public abstract class AbstractCache implements Cache { private static final long serialVersionUID = 1L; protected Map> cacheMap; - private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock(); - private final ReadLock readLock = cacheLock.readLock(); - private final WriteLock writeLock = cacheLock.writeLock(); + private final StampedLock lock = new StampedLock(); - /** 返回缓存容量,0表示无大小限制 */ + /** + * 返回缓存容量,0表示无大小限制 + */ protected int capacity; - /** 缓存失效时长, 0 表示无限制,单位毫秒 */ + /** + * 缓存失效时长, 0 表示无限制,单位毫秒 + */ protected long timeout; - /** 每个对象是否有单独的失效时长,用于决定清理过期对象是否有必要。 */ + /** + * 每个对象是否有单独的失效时长,用于决定清理过期对象是否有必要。 + */ protected boolean existCustomTimeout; - /** 命中数 */ + /** + * 命中数 + */ protected int hitCount; - /** 丢失数 */ + /** + * 丢失数 + */ protected int missCount; // ---------------------------------------------------------------- put start @@ -53,20 +58,19 @@ public abstract class AbstractCache implements Cache { @Override public void put(K key, V object, long timeout) { - writeLock.lock(); - + final long stamp = lock.writeLock(); try { putWithoutLock(key, object, timeout); } finally { - writeLock.unlock(); + lock.unlockWrite(stamp); } } /** * 加入元素,无锁 - * - * @param key 键 - * @param object 值 + * + * @param key 键 + * @param object 值 * @param timeout 超时时长 * @since 4.5.16 */ @@ -85,8 +89,7 @@ public abstract class AbstractCache implements Cache { // ---------------------------------------------------------------- get start @Override public boolean containsKey(K key) { - readLock.lock(); - + final long stamp = lock.readLock(); try { // 不存在或已移除 final CacheObj co = cacheMap.get(key); @@ -99,7 +102,7 @@ public abstract class AbstractCache implements Cache { return true; } } finally { - readLock.unlock(); + lock.unlockRead(stamp); } // 过期 @@ -111,24 +114,14 @@ public abstract class AbstractCache implements Cache { * @return 命中数 */ public int getHitCount() { - this.readLock.lock(); - try { - return hitCount; - } finally { - this.readLock.unlock(); - } + return hitCount; } /** * @return 丢失数 */ public int getMissCount() { - this.readLock.lock(); - try { - return missCount; - } finally { - this.readLock.unlock(); - } + return missCount; } @Override @@ -140,11 +133,11 @@ public abstract class AbstractCache implements Cache { public V get(K key, Func0 supplier) { V v = get(key); if (null == v && null != supplier) { - writeLock.lock(); + final long stamp = lock.writeLock(); try { // 双重检查锁 final CacheObj co = cacheMap.get(key); - if(null == co || co.isExpired() || null == co.getValue()) { + if (null == co || co.isExpired()) { try { v = supplier.call(); } catch (Exception e) { @@ -155,7 +148,7 @@ public abstract class AbstractCache implements Cache { v = co.get(true); } } finally { - writeLock.unlock(); + lock.unlockWrite(stamp); } } return v; @@ -163,23 +156,25 @@ public abstract class AbstractCache implements Cache { @Override public V get(K key, boolean isUpdateLastAccess) { - readLock.lock(); - + // 尝试读取缓存,使用乐观读锁 + long stamp = lock.readLock(); try { // 不存在或已移除 final CacheObj co = cacheMap.get(key); - if (co == null) { + if (null == co) { missCount++; return null; } - if (false == co.isExpired()) { + if (co.isExpired()) { + missCount++; + } else{ // 命中 hitCount++; return co.get(isUpdateLastAccess); } } finally { - readLock.unlock(); + lock.unlock(stamp); } // 过期 @@ -199,30 +194,32 @@ public abstract class AbstractCache implements Cache { @Override public Iterator> cacheObjIterator() { CopiedIter> copiedIterator; - readLock.lock(); + final long stamp = lock.readLock(); try { copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator()); } finally { - readLock.unlock(); + lock.unlockRead(stamp); } return new CacheObjIterator<>(copiedIterator); } // ---------------------------------------------------------------- prune start + /** - * 清理实现 - * + * 清理实现
    + * 子类实现此方法时无需加锁 + * * @return 清理数 */ protected abstract int pruneCache(); @Override public final int prune() { - writeLock.lock(); + final long stamp = lock.writeLock(); try { return pruneCache(); } finally { - writeLock.unlock(); + lock.unlockWrite(stamp); } } // ---------------------------------------------------------------- prune end @@ -235,7 +232,7 @@ public abstract class AbstractCache implements Cache { /** * @return 默认缓存失效时长。
    - * 每个对象可以单独设置失效时长 + * 每个对象可以单独设置失效时长 */ @Override public long timeout() { @@ -244,26 +241,16 @@ public abstract class AbstractCache implements Cache { /** * 只有设置公共缓存失效时长或每个对象单独的失效时长时清理可用 - * + * * @return 过期对象清理是否可用,内部使用 */ protected boolean isPruneExpiredActive() { - this.readLock.lock(); - try { - return (timeout != 0) || existCustomTimeout; - } finally { - this.readLock.unlock(); - } + return (timeout != 0) || existCustomTimeout; } @Override public boolean isFull() { - this.readLock.lock(); - try { - return (capacity > 0) && (cacheMap.size() >= capacity); - } finally { - this.readLock.unlock(); - } + return (capacity > 0) && (cacheMap.size() >= capacity); } @Override @@ -273,49 +260,34 @@ public abstract class AbstractCache implements Cache { @Override public void clear() { - writeLock.lock(); + final long stamp = lock.writeLock(); try { cacheMap.clear(); } finally { - writeLock.unlock(); + lock.unlockWrite(stamp); } } @Override public int size() { - this.readLock.lock(); - try { - return cacheMap.size(); - } finally { - this.readLock.unlock(); - } + return cacheMap.size(); } @Override public boolean isEmpty() { - this.readLock.lock(); - try { - return cacheMap.isEmpty(); - } finally { - this.readLock.unlock(); - } + return cacheMap.isEmpty(); } @Override public String toString() { - this.readLock.lock(); - try { - return this.cacheMap.toString(); - } finally { - this.readLock.unlock(); - } + return this.cacheMap.toString(); } // ---------------------------------------------------------------- common end /** * 对象移除回调。默认无动作 - * - * @param key 键 + * + * @param key 键 * @param cachedObject 被缓存的对象 */ protected void onRemove(K key, V cachedObject) { @@ -324,27 +296,27 @@ public abstract class AbstractCache implements Cache { /** * 移除key对应的对象 - * - * @param key 键 + * + * @param key 键 * @param withMissCount 是否计数丢失数 */ private void remove(K key, boolean withMissCount) { - writeLock.lock(); + final long stamp = lock.writeLock(); CacheObj co; try { co = removeWithoutLock(key, withMissCount); } finally { - writeLock.unlock(); + lock.unlockWrite(stamp); } if (null != co) { onRemove(co.key, co.obj); } } - + /** * 移除key对应的对象,不加锁 - * - * @param key 键 + * + * @param key 键 * @param withMissCount 是否计数丢失数 * @return 移除的对象,无返回null */ diff --git a/hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java b/hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java index 166c57a00..0fd8b80d9 100644 --- a/hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java +++ b/hutool-cache/src/test/java/cn/hutool/cache/test/CacheConcurrentTest.java @@ -1,15 +1,12 @@ package cn.hutool.cache.test; -import java.util.Iterator; - -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.cache.Cache; import cn.hutool.cache.impl.FIFOCache; import cn.hutool.cache.impl.LRUCache; import cn.hutool.core.lang.Console; import cn.hutool.core.thread.ThreadUtil; +import org.junit.Ignore; +import org.junit.Test; /** * 缓存单元测试 @@ -28,30 +25,22 @@ public class CacheConcurrentTest { // 由于缓存容量只有3,当加入第四个元素的时候,根据FIFO规则,最先放入的对象将被移除 for (int i = 0; i < threadCount; i++) { - ThreadUtil.execute(new Runnable() { - @Override - public void run() { - cache.put("key1", "value1", System.currentTimeMillis() * 3); - cache.put("key2", "value2", System.currentTimeMillis() * 3); - cache.put("key3", "value3", System.currentTimeMillis() * 3); - cache.put("key4", "value4", System.currentTimeMillis() * 3); - ThreadUtil.sleep(1000); - cache.put("key5", "value5", System.currentTimeMillis() * 3); - cache.put("key6", "value6", System.currentTimeMillis() * 3); - cache.put("key7", "value7", System.currentTimeMillis() * 3); - cache.put("key8", "value8", System.currentTimeMillis() * 3); - Console.log("put all"); - } + ThreadUtil.execute(() -> { + cache.put("key1", "value1", System.currentTimeMillis() * 3); + cache.put("key2", "value2", System.currentTimeMillis() * 3); + cache.put("key3", "value3", System.currentTimeMillis() * 3); + cache.put("key4", "value4", System.currentTimeMillis() * 3); + ThreadUtil.sleep(1000); + cache.put("key5", "value5", System.currentTimeMillis() * 3); + cache.put("key6", "value6", System.currentTimeMillis() * 3); + cache.put("key7", "value7", System.currentTimeMillis() * 3); + cache.put("key8", "value8", System.currentTimeMillis() * 3); + Console.log("put all"); }); } for (int i = 0; i < threadCount; i++) { - ThreadUtil.execute(new Runnable() { - @Override - public void run() { - show(cache); - } - }); + ThreadUtil.execute(() -> show(cache)); } System.out.println("=============================="); @@ -66,23 +55,20 @@ public class CacheConcurrentTest { for (int i = 0; i < threadCount; i++) { final int index = i; - ThreadUtil.execute(new Runnable() { - @Override - public void run() { - cache.put("key1"+ index, "value1"); - cache.put("key2"+ index, "value2", System.currentTimeMillis() * 3); - - int size = cache.size(); - int capacity = cache.capacity(); - if(size > capacity) { - Console.log("{} {}", size, capacity); - } - ThreadUtil.sleep(1000); - size = cache.size(); - capacity = cache.capacity(); - if(size > capacity) { - Console.log("## {} {}", size, capacity); - } + ThreadUtil.execute(() -> { + cache.put("key1"+ index, "value1"); + cache.put("key2"+ index, "value2", System.currentTimeMillis() * 3); + + int size = cache.size(); + int capacity = cache.capacity(); + if(size > capacity) { + Console.log("{} {}", size, capacity); + } + ThreadUtil.sleep(1000); + size = cache.size(); + capacity = cache.capacity(); + if(size > capacity) { + Console.log("## {} {}", size, capacity); } }); } @@ -91,10 +77,8 @@ public class CacheConcurrentTest { } private void show(Cache cache) { - Iterator its = cache.iterator(); - while (its.hasNext()) { - Object tt = its.next(); + for (Object tt : cache) { Console.log(tt); } } diff --git a/hutool-cache/src/test/java/cn/hutool/cache/test/CacheTest.java b/hutool-cache/src/test/java/cn/hutool/cache/test/CacheTest.java index 12ccbe74c..c1832ce28 100644 --- a/hutool-cache/src/test/java/cn/hutool/cache/test/CacheTest.java +++ b/hutool-cache/src/test/java/cn/hutool/cache/test/CacheTest.java @@ -58,7 +58,7 @@ public class CacheTest { //使用时间推近 lruCache.get("key1"); lruCache.put("key4", "value4", DateUnit.SECOND.getMillis() * 3); - + String value1 = lruCache.get("key1"); Assert.assertNotNull(value1); //由于缓存容量只有3,当加入第四个元素的时候,根据LRU规则,最少使用的将被移除(2被移除) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java b/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java index cdcdc0efc..0c2cff152 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java @@ -1,13 +1,11 @@ package cn.hutool.core.lang; +import cn.hutool.core.lang.func.Func0; + import java.io.Serializable; import java.util.Map; import java.util.WeakHashMap; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; - -import cn.hutool.core.lang.func.Func0; +import java.util.concurrent.locks.StampedLock; /** * 简单缓存,无超时实现,使用{@link WeakHashMap}实现缓存自动清理 @@ -21,10 +19,9 @@ public class SimpleCache implements Serializable{ /** 池 */ private final Map cache = new WeakHashMap<>(); - - private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock(); - private final ReadLock readLock = cacheLock.readLock(); - private final WriteLock writeLock = cacheLock.writeLock(); + + // 乐观读写锁 + private final StampedLock lock = new StampedLock (); /** * 从缓存池中查找值 @@ -33,15 +30,12 @@ public class SimpleCache implements Serializable{ * @return 值 */ public V get(K key) { - // 尝试读取缓存 - readLock.lock(); - V value; + long stamp = lock.readLock(); try { - value = cache.get(key); + return cache.get(key); } finally { - readLock.unlock(); + lock.unlockRead(stamp); } - return value; } /** @@ -52,12 +46,25 @@ public class SimpleCache implements Serializable{ * @return 值对象 */ public V get(K key, Func0 supplier) { - V v = get(key); - if (null == v && null != supplier) { - writeLock.lock(); - try { - // 双重检查锁 + if(null == supplier){ + return get(key); + } + + long stamp = lock.readLock(); + V v; + try{ + v = cache.get(key); + if (null == v) { + // 尝试转换独占写锁 + long writeStamp = lock.tryConvertToWriteLock(stamp); + if(0 == writeStamp){ + // 转换失败,手动更新为写锁 + lock.unlockRead(stamp); + writeStamp = lock.writeLock(); + } + stamp = writeStamp; v = cache.get(key); + // 双重检查,防止在竞争锁的过程中已经有其它线程写入 if(null == v) { try { v = supplier.call(); @@ -66,9 +73,9 @@ public class SimpleCache implements Serializable{ } cache.put(key, v); } - } finally { - writeLock.unlock(); } + } finally { + lock.unlock(stamp); } return v; } @@ -80,11 +87,12 @@ public class SimpleCache implements Serializable{ * @return 值 */ public V put(K key, V value){ - writeLock.lock(); + // 独占写锁 + final long stamp = lock.writeLock(); try { cache.put(key, value); } finally { - writeLock.unlock(); + lock.unlockWrite(stamp); } return value; } @@ -96,11 +104,12 @@ public class SimpleCache implements Serializable{ * @return 移除的值 */ public V remove(K key) { - writeLock.lock(); + // 独占写锁 + final long stamp = lock.writeLock(); try { return cache.remove(key); } finally { - writeLock.unlock(); + lock.unlockWrite(stamp); } } @@ -108,11 +117,12 @@ public class SimpleCache implements Serializable{ * 清空缓存池 */ public void clear() { - writeLock.lock(); + // 独占写锁 + final long stamp = lock.writeLock(); try { this.cache.clear(); } finally { - writeLock.unlock(); + lock.unlockWrite(stamp); } } } diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java b/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java new file mode 100644 index 000000000..051134330 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java @@ -0,0 +1,43 @@ +package cn.hutool.core.thread.lock; + +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.locks.StampedLock; + +/** + * 锁相关工具 + * + * @author looly + * @since 5.2.5 + */ +public class LockUtil { + + private static NoLock NO_LOCK = new NoLock(); + + /** + * 创建{@link StampedLock}锁 + * + * @return {@link StampedLock}锁 + */ + public static StampedLock createStampLock() { + return new StampedLock(); + } + + /** + * 创建{@link ReentrantReadWriteLock}锁 + * + * @param fair 是否公平锁 + * @return {@link ReentrantReadWriteLock}锁 + */ + public static ReentrantReadWriteLock createReadWriteLock(boolean fair) { + return new ReentrantReadWriteLock(fair); + } + + /** + * 获取单例的无锁对象 + * + * @return {@link NoLock} + */ + public static NoLock getNoLock(){ + return NO_LOCK; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java b/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java index 12032abb0..912d21359 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java @@ -17,7 +17,7 @@ public class NoLock implements Lock{ } @Override - public void lockInterruptibly() throws InterruptedException { + public void lockInterruptibly() { } @Override @@ -25,8 +25,9 @@ public class NoLock implements Lock{ return true; } + @SuppressWarnings("NullableProblems") @Override - public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + public boolean tryLock(long time, TimeUnit unit) { return true; } @@ -34,6 +35,7 @@ public class NoLock implements Lock{ public void unlock() { } + @SuppressWarnings("NullableProblems") @Override public Condition newCondition() { return null; diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java new file mode 100644 index 000000000..bac03887b --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java @@ -0,0 +1,46 @@ +package cn.hutool.core.lang; + +import cn.hutool.core.thread.ThreadUtil; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class SimpleCacheTest { + + @Before + public void putTest(){ + final SimpleCache cache = new SimpleCache<>(); + ThreadUtil.execute(()->cache.put("key1", "value1")); + ThreadUtil.execute(()->cache.get("key1")); + ThreadUtil.execute(()->cache.put("key2", "value2")); + ThreadUtil.execute(()->cache.get("key2")); + ThreadUtil.execute(()->cache.put("key3", "value3")); + ThreadUtil.execute(()->cache.get("key3")); + ThreadUtil.execute(()->cache.put("key4", "value4")); + ThreadUtil.execute(()->cache.get("key4")); + ThreadUtil.execute(()->cache.get("key5", ()->"value5")); + + cache.get("key5", ()->"value5"); + } + + @Test + public void getTest(){ + final SimpleCache cache = new SimpleCache<>(); + cache.put("key1", "value1"); + cache.get("key1"); + cache.put("key2", "value2"); + cache.get("key2"); + cache.put("key3", "value3"); + cache.get("key3"); + cache.put("key4", "value4"); + cache.get("key4"); + cache.get("key5", ()->"value5"); + + Assert.assertEquals("value1", cache.get("key1")); + Assert.assertEquals("value2", cache.get("key2")); + Assert.assertEquals("value3", cache.get("key3")); + Assert.assertEquals("value4", cache.get("key4")); + Assert.assertEquals("value5", cache.get("key5")); + Assert.assertEquals("value6", cache.get("key6", ()-> "value6")); + } +} diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 8125484cc..f3424433c 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -1,9 +1,10 @@ - + 4.0.0 jar - + cn.hutool hutool-parent @@ -13,12 +14,37 @@ hutool-script ${project.artifactId} Hutool 脚本执行封装 - + + + 2.7.0 + 3.0.1 + 3.0.2 + + cn.hutool hutool-core ${project.parent.version} + + org.python + jython + ${jython.version} + provided + + + org.luaj + luaj-jse + ${luaj.version} + provided + + + org.codehaus.groovy + groovy-all + ${groovy.version} + pom + provided + diff --git a/hutool-script/src/main/java/cn/hutool/script/JavaScriptEngine.java b/hutool-script/src/main/java/cn/hutool/script/JavaScriptEngine.java index d6f0b9d46..c62283d0b 100644 --- a/hutool-script/src/main/java/cn/hutool/script/JavaScriptEngine.java +++ b/hutool-script/src/main/java/cn/hutool/script/JavaScriptEngine.java @@ -1,65 +1,64 @@ package cn.hutool.script; -import java.io.Reader; - import javax.script.Bindings; import javax.script.Compilable; import javax.script.CompiledScript; import javax.script.Invocable; import javax.script.ScriptContext; import javax.script.ScriptEngineFactory; -import javax.script.ScriptEngineManager; import javax.script.ScriptException; +import java.io.Reader; /** * Javascript引擎类 - * @author Looly * + * @author Looly */ -public class JavaScriptEngine extends FullSupportScriptEngine{ - +public class JavaScriptEngine extends FullSupportScriptEngine { + public JavaScriptEngine() { - super(new ScriptEngineManager().getEngineByName("javascript")); + super(ScriptUtil.getJsEngine()); } - + /** * 引擎实例 + * * @return 引擎实例 */ - public static JavaScriptEngine instance(){ + public static JavaScriptEngine instance() { return new JavaScriptEngine(); } //----------------------------------------------------------------------------------------------- Invocable @Override public Object invokeMethod(Object thiz, String name, Object... args) throws ScriptException, NoSuchMethodException { - return ((Invocable)engine).invokeMethod(thiz, name, args); + return ((Invocable) engine).invokeMethod(thiz, name, args); } @Override public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException { - return ((Invocable)engine).invokeFunction(name, args); + return ((Invocable) engine).invokeFunction(name, args); } @Override public T getInterface(Class clasz) { - return ((Invocable)engine).getInterface(clasz); + return ((Invocable) engine).getInterface(clasz); } @Override public T getInterface(Object thiz, Class clasz) { - return ((Invocable)engine).getInterface(thiz, clasz); + return ((Invocable) engine).getInterface(thiz, clasz); } //----------------------------------------------------------------------------------------------- Compilable @Override public CompiledScript compile(String script) throws ScriptException { - return ((Compilable)engine).compile(script); + return ((Compilable) engine).compile(script); } @Override public CompiledScript compile(Reader script) throws ScriptException { - return ((Compilable)engine).compile(script); + return ((Compilable) engine).compile(script); } //----------------------------------------------------------------------------------------------- ScriptEngine diff --git a/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java b/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java index fe530ee02..d252a9099 100644 --- a/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java +++ b/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java @@ -1,10 +1,10 @@ package cn.hutool.script; -import javax.script.ScriptException; - import cn.hutool.core.exceptions.ExceptionUtil; import cn.hutool.core.util.StrUtil; +import javax.script.ScriptException; + /** * 脚本运行时异常 * @@ -50,7 +50,6 @@ public class ScriptRuntimeException extends RuntimeException { super(message); this.fileName = fileName; this.lineNumber = lineNumber; - this.columnNumber = -1; } /** diff --git a/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java b/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java index 559a58ea9..991c425c6 100644 --- a/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java +++ b/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java @@ -1,5 +1,8 @@ package cn.hutool.script; +import cn.hutool.core.lang.SimpleCache; +import cn.hutool.core.util.StrUtil; + import javax.script.Bindings; import javax.script.Compilable; import javax.script.CompiledScript; @@ -10,34 +13,92 @@ import javax.script.ScriptException; /** * 脚本工具类 - * - * @author Looly * + * @author Looly */ public class ScriptUtil { + private static final ScriptEngineManager manager = new ScriptEngineManager(); + private static SimpleCache cache = new SimpleCache<>(); + /** * 获得 {@link ScriptEngine} 实例 - * - * @param name 脚本名称 + * + * @param nameOrExtOrMime 脚本名称 * @return {@link ScriptEngine} 实例 */ - public static ScriptEngine getScript(String name) { - return new ScriptEngineManager().getEngineByName(name); + public static ScriptEngine getScript(String nameOrExtOrMime) { + return cache.get(nameOrExtOrMime, ()->{ + ScriptEngine engine = manager.getEngineByName(nameOrExtOrMime); + if (null == engine) { + engine = manager.getEngineByExtension(nameOrExtOrMime); + } + if (null == engine) { + engine = manager.getEngineByMimeType(nameOrExtOrMime); + } + if (null == engine) { + throw new NullPointerException(StrUtil.format("Script for [{}] not support !", nameOrExtOrMime)); + } + return engine; + }); } /** * 获得 Javascript引擎 {@link JavaScriptEngine} - * + * * @return {@link JavaScriptEngine} */ public static JavaScriptEngine getJavaScriptEngine() { return new JavaScriptEngine(); } - + /** - * 编译脚本 - * + * 获得 JavaScript引擎 + * + * @return Python引擎 + * @since 5.2.5 + */ + public static ScriptEngine getJsEngine() { + return getScript("js"); + } + + /** + * 获得 Python引擎
    + * 需要引入org.python:jython + * + * @return Python引擎 + * @since 5.2.5 + */ + public static ScriptEngine getPythonEngine() { + System.setProperty("python.import.site", "false"); + return getScript("python"); + } + + /** + * 获得Lua引擎
    + * 需要引入org.luaj:luaj-jse + * + * @return Lua引擎 + * @since 5.2.5 + */ + public static ScriptEngine getLuaEngine() { + return getScript("lua"); + } + + /** + * 获得Groovy引擎
    + * 需要引入org.codehaus.groovy:groovy-all + * + * @return Groovy引擎 + * @since 5.2.5 + */ + public static ScriptEngine getGroovyEngine() { + return getScript("groovy"); + } + + /** + * 执行脚本 + * * @param script 脚本内容 * @return {@link CompiledScript} * @throws ScriptRuntimeException 脚本异常 @@ -45,16 +106,16 @@ public class ScriptUtil { */ public static Object eval(String script) throws ScriptRuntimeException { try { - return compile(script).eval(); + return getJsEngine().eval(script); } catch (ScriptException e) { throw new ScriptRuntimeException(e); } } - + /** - * 编译脚本 - * - * @param script 脚本内容 + * 执行脚本 + * + * @param script 脚本内容 * @param context 脚本上下文 * @return {@link CompiledScript} * @throws ScriptRuntimeException 脚本异常 @@ -62,16 +123,16 @@ public class ScriptUtil { */ public static Object eval(String script, ScriptContext context) throws ScriptRuntimeException { try { - return compile(script).eval(context); + return getJsEngine().eval(script, context); } catch (ScriptException e) { throw new ScriptRuntimeException(e); } } - + /** - * 编译脚本 - * - * @param script 脚本内容 + * 执行脚本 + * + * @param script 脚本内容 * @param bindings 绑定的参数 * @return {@link CompiledScript} * @throws ScriptRuntimeException 脚本异常 @@ -79,7 +140,7 @@ public class ScriptUtil { */ public static Object eval(String script, Bindings bindings) throws ScriptRuntimeException { try { - return compile(script).eval(bindings); + return getJsEngine().eval(script, bindings); } catch (ScriptException e) { throw new ScriptRuntimeException(e); } @@ -87,7 +148,7 @@ public class ScriptUtil { /** * 编译脚本 - * + * * @param script 脚本内容 * @return {@link CompiledScript} * @throws ScriptRuntimeException 脚本异常 @@ -95,7 +156,7 @@ public class ScriptUtil { */ public static CompiledScript compile(String script) throws ScriptRuntimeException { try { - return compile(getJavaScriptEngine(), script); + return compile(getJsEngine(), script); } catch (ScriptException e) { throw new ScriptRuntimeException(e); } @@ -103,7 +164,7 @@ public class ScriptUtil { /** * 编译脚本 - * + * * @param engine 引擎 * @param script 脚本内容 * @return {@link CompiledScript} @@ -111,7 +172,7 @@ public class ScriptUtil { */ public static CompiledScript compile(ScriptEngine engine, String script) throws ScriptException { if (engine instanceof Compilable) { - Compilable compEngine = (Compilable) engine; + final Compilable compEngine = (Compilable) engine; return compEngine.compile(script); } return null; diff --git a/hutool-script/src/test/java/cn/hutool/script/test/ScriptUtilTest.java b/hutool-script/src/test/java/cn/hutool/script/test/ScriptUtilTest.java index fda465b73..6fcbf57c3 100644 --- a/hutool-script/src/test/java/cn/hutool/script/test/ScriptUtilTest.java +++ b/hutool-script/src/test/java/cn/hutool/script/test/ScriptUtilTest.java @@ -1,12 +1,12 @@ package cn.hutool.script.test; -import javax.script.CompiledScript; -import javax.script.ScriptException; - -import org.junit.Test; - import cn.hutool.script.ScriptRuntimeException; import cn.hutool.script.ScriptUtil; +import org.junit.Test; + +import javax.script.CompiledScript; +import javax.script.ScriptEngine; +import javax.script.ScriptException; /** * 脚本单元测试类 @@ -30,4 +30,22 @@ public class ScriptUtilTest { public void evalTest() { ScriptUtil.eval("print('Script test!');"); } + + @Test + public void pythonTest() throws ScriptException { + final ScriptEngine pythonEngine = ScriptUtil.getPythonEngine(); + pythonEngine.eval("print('Hello Python')"); + } + + @Test + public void luaTest() throws ScriptException { + final ScriptEngine engine = ScriptUtil.getLuaEngine(); + engine.eval("print('Hello Lua')"); + } + + @Test + public void groovyTest() throws ScriptException { + final ScriptEngine engine = ScriptUtil.getGroovyEngine(); + engine.eval("println 'Hello Groovy'"); + } } From 4d8b21f831343f43a61b3273eecaf26bf9537293 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 26 Mar 2020 23:58:44 +0800 Subject: [PATCH 002/157] release 5.2.5 --- CHANGELOG.md | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- .../src/main/java/cn/hutool/core/collection/CollUtil.java | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19257f5e9..25ce1489d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -## 5.2.5 +## 5.2.5 (2020-03-26) ### 新特性 * 【core 】 增加逻辑,对于原始类型注入,使用默认值(issue#797@Github) diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 7610de4f3..db751c6ec 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index dcba385ba..3c1b19771 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index 2a6d645d9..a0d3b5c32 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 3662d2a7b..8665e9a56 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 780da5d5f..b062128a5 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index dd94659d2..3aec64a4b 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 9434f0194..4bbdc77ef 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-core diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index 0f116f6a2..3cb0ee020 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -486,7 +486,7 @@ public class CollUtil { */ @SafeVarargs public static LinkedHashSet newLinkedHashSet(T... ts) { - return (LinkedHashSet) newHashSet(true, ts); + return (LinkedHashSet) set(true, ts); } /** diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 4a9afa1f5..0639fe711 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 8986ece74..9005a6b8b 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 84ab625ab..3acfd3042 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index a714f97f0..59db5d9af 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index f43628c29..af811b3e5 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 7ea30fb2b..c5f1ab944 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index f5837ea56..caf67181d 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index e550aae74..391bff51e 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index de9ae72c0..c540b906a 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index f3424433c..feb304160 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index dff1168d2..871e1578e 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index b1be9e927..07bb0deda 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 20d5ff1fd..9ca9d35ad 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool-system diff --git a/pom.xml b/pom.xml index 020ceffb0..e2047009f 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.2.5-SNAPSHOT + 5.2.5 hutool 提供丰富的Java工具方法 https://github.com/looly/hutool From 130fa344c4a6f333b7ba496eca4498a477a40f15 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 27 Mar 2020 00:31:35 +0800 Subject: [PATCH 003/157] prepare 5.2.6 --- CHANGELOG.md | 7 +++++++ README.md | 10 +++++----- bin/version.txt | 2 +- docs/js/version.js | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 25 files changed, 35 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25ce1489d..5d246a0e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ ------------------------------------------------------------------------------------------------------------- +## 5.2.6 (2020-03-26) + +### 新特性 +### Bug修复 + +------------------------------------------------------------------------------------------------------------- + ## 5.2.5 (2020-03-26) ### 新特性 diff --git a/README.md b/README.md index 5fd7294b6..bf9a22421 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ -- 主页:https://hutool.cn/ | https://www.hutool.club/ --

    - -- QQ群③:555368316 -- + -- QQ群③:555368316 -- -- QQ群④:718802356 --

    @@ -116,21 +116,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.2.5 + 5.2.6 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.2.5' +compile 'cn.hutool:hutool-all:5.2.6' ``` ### 非Maven项目 点击以下任一链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.2.5/) -- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.2.5/) +- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.2.6/) +- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.2.6/) > 注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类获工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index 462faf748..a944d7e61 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.2.5 +5.2.6 diff --git a/docs/js/version.js b/docs/js/version.js index 3c62295c9..b5a3cc686 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.2.5' \ No newline at end of file +var version = '5.2.6' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index db751c6ec..0d5f10848 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 3c1b19771..3c011fb9a 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index a0d3b5c32..e7bf57f83 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 8665e9a56..403ddad61 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index b062128a5..008e8704e 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 3aec64a4b..0f9c30525 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 4bbdc77ef..94bd039cc 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 0639fe711..d7e2134a8 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 9005a6b8b..ccdd83b22 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 3acfd3042..fa32455c3 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 59db5d9af..82e7f9473 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index af811b3e5..8298dc798 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index c5f1ab944..0086696a9 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index caf67181d..af66a23a4 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 391bff51e..dc713a7b5 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index c540b906a..379315af5 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index feb304160..9833e6f62 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 871e1578e..eb777bc49 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 07bb0deda..011ef7557 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 9ca9d35ad..487c2c2d5 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index e2047009f..e443672b0 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.2.5 + 5.2.6-SNAPSHOT hutool 提供丰富的Java工具方法 https://github.com/looly/hutool From 11191b3cfee9ded6a81906fb25e1a936ad5b4bdb Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 27 Mar 2020 09:34:34 +0800 Subject: [PATCH 004/157] fix S --- CHANGELOG.md | 1 + .../src/main/java/cn/hutool/extra/spring/SpringUtil.java | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d246a0e9..5256e7d5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### 新特性 ### Bug修复 +* 【extra 】 修复SpringUtil使用devtools重启报错问题 ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index f066c6f96..39f706d7a 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -22,9 +22,7 @@ public class SpringUtil implements ApplicationContextAware { @SuppressWarnings("NullableProblems") @Override public void setApplicationContext(ApplicationContext applicationContext) { - if (SpringUtil.applicationContext == null) { - SpringUtil.applicationContext = applicationContext; - } + SpringUtil.applicationContext = applicationContext; } /** From c97c76d49d6e47e84786ec028de05084dc6854ef Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 29 Mar 2020 09:28:11 +0800 Subject: [PATCH 005/157] add execByShell --- CHANGELOG-v4.md | 1461 ----------------- CHANGELOG.md | 2 + .../java/cn/hutool/extra/ssh/JschUtil.java | 55 +- 3 files changed, 54 insertions(+), 1464 deletions(-) delete mode 100644 CHANGELOG-v4.md diff --git a/CHANGELOG-v4.md b/CHANGELOG-v4.md deleted file mode 100644 index 34186edf5..000000000 --- a/CHANGELOG-v4.md +++ /dev/null @@ -1,1461 +0,0 @@ - -# Changelog - -------------------------------------------------------------------------------------------------------------- - -## 4.6.9 - -### 新特性 -* 【all】 修复注释中的错别字(issue#I12XE6@Gitee) -* 【core】 CsvWriter支持其它类型的参数(issue#I12XE3@Gitee) -* 【core】 ClassScanner支持自定义ClassLoader -* 【core】 修改错别字(pr#568@Github) -* 【core】 增加DateUtil.parseCST方法(issue#570@Github) -* 【core】 增加defaultIfEmpty方法 -* 【crypto】 修改bigIntToFixexLengthBytes为bigIntToFixedLengthBytes(pr#575@Github) -* 【core】 RandomUtil增加randomStringWithoutStr(pr#76@Gitee) - -### Bug修复 -* 【all】 修复阶乘计算错误bug(issue#I12XE4@Gitee) -* 【http】 修复disableCookie无效问题(issue#572@Github) -* 【http】 修复HttpResponse.getCookies导致的问题(issue#572@Github) -* 【cron】 修复年无效匹配错误问题(pr#578@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.6.8 - -### 新特性 -* 【core】 ArrayUtil.isEmpty可变长参数改为数组(issue#555@Github) -* 【core】 新增Convert.toMap方法(issue#I12ISI@Gitee) -* 【aop 】 增加返回值获取支持,优化逻辑和接口(pr#561@Github) -* 【aop 】 改进HtmlUtil.removeHtmlAttr(issue#556@Github) -* 【crypto】 增加SM3和SM4类 - -### Bug修复 -* 【extra】 修复Mail中sslEnable无效问题(pr#74@Gitee) -* 【extra】 修复CsvParser中最后一行双引号没有去除的问题(pr#73@Gitee) -* 【crypto】 修复SM2算法在自定义密钥时无效问题(issue#I12P5I@Gitee) -* 【core】 修复StopWatch.prettyPrint条件问题(issue#I12RAC@Gitee) -* 【core】 修复StrBuilder.del无法删除最后一个字符的问题(issue#I12R14@Gitee) -* 【poi】 修复sax方式读取复用行导致的问题(issue#I12O0U@Gitee) -* 【core】 修复ClassUtil循环调用问题 -* 【core】 修复MapConvert转换Bean为Map类型没有转换成功问题 - -------------------------------------------------------------------------------------------------------------- - - -## 4.6.7 - -### 新特性 -* 【core】 ImgUtil.rotate支持负数(issue#543@Github) -* 【http】 body方法传null跳过而非报错(issue#I12AP2@Gitee) -* 【core】 TimeInterval增加intervalPretty方法(issue#I12A6T@Gitee) -* 【core】 改进ArrayUtil.toString,提高性能 -* 【system】 增加SystemPropsKeys(issue#550@Github) -* 【core】 FileUtil.normalize在win下支持samba路径(issue#549@Github) -* 【core】 修复Validator注释错误(pr#70@Gitee) -* 【cron】 添加获取任务表的方法(issue#I12E5H@Gitee) -* 【http】 SoapClient增加reset方法用于此对象的复用(issue#I12CCC@Gitee) -* 【db】 StatementUtil增加setParam方法 -* 【db】 Entity.fieldList改为有序实现 -* 【crypto】 AES、DES增加对ZeroPadding的支持(issue#551@Github) -* 【db】 优化批量插入代码,减少类型判断导致的性能问题(issue#I12B4Z@Gitee) -* 【db】 优化SQL日志格式和日志显示 - -### Bug修复 -* 【core】 修复DateUtil.offset导致的时区错误问题(issue#I1294O@Gitee) -* 【core】 修复RuntimeUtil.exec重载导致的问题(issue#544@Github) -* 【db】 修复StatementUtil.getGeneratedKeys返回主键数量不足问题 -* 【db】 修复锁的问题(issue#546@Github) -* 【db】 修复CombinationAnnotationElement问题(issue#547@Github) -* 【core】 修复Validator.isGeneral问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.6.6 - -### 新特性 -* 【core】 MapUtil增加newConcurrentHashMap(pr#538@Github) -* 【core】 增加StopWatch(issue#539@Github) -* 【core】 增加ZipUtil.listFiles(issue#541@Github) - -### Bug修复 -* 【core】 修复DateUtil.endOfYear计算错误问题(issuepr#540@Github) -* 【core】 修复FileUtil.listFileNames在jar中匹配问题,增加(issuepr#541@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.6.5 - -### 新特性 -* 【core】 CollUtil增加filterNew等方法(原filter变更为filterNew,新增filter) -* 【crypto】 Sign增加setParameter方法 -* 【extra】 Sftp得put方法增加进度支持(issue#518@Github) -* 【core】 ArrayUtil增加distinct方法 -* 【http】 去除log模块依赖,Cookie中去除日志提示,body方法传入JSON对象废弃,未来移除json模块依赖 -* 【extra】 添加MyNLP支持(issue#519@Github) -* 【json】 添加自定义序列化反序列化支持(issue#I1052A@Gitee) -* 【dfa】 优化特殊字符构建,优化查找,改为使用StrBuilder -* 【core】 ZipUtil增加FileFilter参数的重载,支持文件过滤(issue#I11RTP@Gitee) -* 【http】 HttpRequest增加setChunkedStreamingMode方法(issue#525@Github) -* 【setting】 SettingLoader支持自定义分隔符 -* 【http】 Content-Type添加默认值(issue#I11YHI@Gitee) -* 【socket】 增加Closeable接口(issue#532@Github) -* 【core】 CollUtil增加min和max方法 - -### Bug修复 -* 【core】 修复NetUtil.getUsableLocalPort问题(pr#69@Gitee) -* 【core】 修复MathUtil.arrangementSelect重复元素导致无结果问题(issue#529@Gitee) -* 【core】 修复RandomUtil.randomEleSet越界问题(issue#535@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.6.4 - -### 新特性 -* 【http】 自动关闭HttpURLConnection的头安全检查(issue#512@Github) -* 【setting】 Setting变量替换支持从系统参数中取值(issue#I11BV1@Gitee) -* 【core】 改进NumberUtil.isNumber方法(pr#68@Gitee) -* 【system】 增加Oshi工具封装 - -### Bug修复 -* 【db】 解决ThreadLocalConnection多数据源被移除问题(pr#66@Gitee) -* 【core】 解决ArrayUtil.emptyCount计数错误问题(issue#509@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.6.3 - -### 新特性 -* 【core】 改进CollUtil.zip逻辑,减少内存复制(issue#I10T01@Gitee) -* 【extra】 邮件增加图片支持(pr#495@Github) -* 【core】 MapUtil、CollUtil增加emptyIfNull(issue#502@Github) -* 【core】 增加emptyIfNull等(issue#503@Github) -* 【setting】 Props增加toBean方法(issue#499@Github) -* 【poi】 CellUtil增加getMergedRegionValue方法,ExcelWriter增加getDisposition方法 -* 【http】 HttpBase增加headerMap方法 -* 【core】 FileUtil.loopFile增加重载,支持定义深度 - -### Bug修复 -* 【http】 修复HttpRquest中body方法长度计算问题(issue#I10UPG@Gitee) -* 【system】 修复获取本地IP问题(pr#65@Gitee) -* 【poi】 修复设置单元格样式无效问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.6.2 - -### 新特性 -* 【core】 Tuple增加支持equals和hashcode(issue#469@Github) -* 【http】 Accept修改默认权重,json优先(issue#472@Github) -* 【http】 增加HttpGlobalConfig(issue#I10DHC@Gitee) -* 【core】 CollUtil.getFieldValues避免空指针(issue#I10FK9@Gitee) -* 【http】 改进HtmlUtil.unescape改为EscapeUtil.unescapeHtml4实现(issue#I10AUY@Gitee) -* 【core】 TextSimilarity改进判断(issue#456@Github) -* 【poi】 ExcelWriter支持下拉列表(issue#476@Github) -* 【core】 强化ExceptionUtil(issue#459@Github) -* 【core】 增强日期工具类(pr#455@Github) -* 【setting】 构造Setting增加默认字符编码 -* 【extra】 ServletUtil增加getHeaderMap方法 -* 【poi】 CellUtil改进数字支持,解决空指针问题(pr#489@Github) -* 【core】 增加DEFAULT_BUFFER_SIZE - -### Bug修复 -* 【cache】 修复missCount规则(issue#465@Github) -* 【core】 修复父目录拷贝到子目录导致的递归问题 -* 【crypto】 修复RSA中分段加密计算导致的异常(issue#481@Github) -* 【json】 修复TypeReference传入Type类型参数导致的异常(issue#488@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.6.0 - -### 新特性 -* 【all】 增加hutool-bom模块,用于可排除的依赖引入 -* 【core】 ResourceUtil增加readBytes方法 -* 【captcha】 更换为逻辑字体 -* 【extra】 Mail增加reply(issue#445@Github) -* 【core】 去掉重复方法(issue#IZQYR@Gitee) -* 【db】 改进结果集转Bean的下划线和驼峰兼容性(issue#IZOPL@Gitee) -* 【system】 增加JavaInfo对新版本java的支持(pr#454@Github) -* 【extra】 增加可选标志位,是否返回当前目录(issue#446@Github) - -### Bug修复 -* 【core】 修复ImgUtil.slice宽高取反问题(issue#438@Github) -* 【crypto】 修复MD516位摘要长度错误问题(issue#IZNPE@Gitee) -* 【core】 修复ImgUtil.hexToColor调用参数问题(issue#449@Github) -* 【http】 修复可能存在的Http请求结束未关闭连接的情况(issue#449@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.5.18 - -### 新特性 -* 【poi】 增加ExcelUtil.getWriterWithSheet方法(感谢@【长沙】NULL) -* 【core】 EnumUtil和ObjectUtil增加方法(pr#57@Gitee) -* 【core】 EnumUtil增加fromString重载支持默认值(issue#IZFXJ@Gitee) -* 【core】 DateUtil.parse增加Locale对象重载(issue#437@Github) - -### Bug修复 -* 【core】 修复无效的日志打印(issue#IZFW9@Gitee) -* 【core】 修复Validator.isBirthday注释(issue#IZFMG@Gitee) -* 【core】 修复TextSimilarity 的bug(issue#435@Github) -* 【core】 修复Tailer预读取行bug(issue#IZHAT@Gitee) -* 【core】 修复使用slf4j-simple不打印日志问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.5.17 - -### 新特性 -* 【http】 SoapClient增加超时设置(issue#IYQHK@Gitee) -* 【captcha】 修正验证码位置,增加可选文字透明度(issue#421@Github) -* 【poi】 ExcelWriter.setRowHeight增加空指针检查(issue#IYN63@Gitee) -* 【core】 ImgUtil增加copyImage可选背景色(issue#IYX3E@Gitee) -* 【core】 CollUtil.sub方法在空列表时返回空数组而非null(issue#430@Github) -* 【core】 改进本地IP地址获取方法(issue#428@Github) -* 【core】 WatchMonitor增加ClosedWatchServiceException异常处理(issue#427@Github) - -### Bug修复 -* 【crypto】 修复DigestUtil.md5方法的注释(issue#IYQHG@Gitee) -* 【core】 修复MapUtil.newHashMap初始容量问题(issue#IYKJJ@Gitee) -* 【core】 修复HttpUtil.encodeParam多出=问题(issue#IZ3PI@Gitee) -* 【core】 修复Img.scale变形问题(issue#431@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.5.16 - -### 新特性 -* 【cache】 缓存增加get重载(pr#404@Github) -* 【poi】 增加WordUtil -* 【core】 改进fnvHash避免负数(issue#IYDK6@Gitee) -* 【core】 改进BeanCoper逻辑(pr#45@Gitee) -* 【all】 实现必要序列化接口 -* 【db】 Entity增加可选忽略大小写(issue#IYGVW@Gitee) -* 【core】 MapUtil增加renameKey方法(感谢@【帝都】宁静) - -### Bug修复 -* 【poi】 修复sax中读取Excel普通单元格设置日期格式识别问题(issue#IYD0L@Gitee) -* 【http】 修复setParam非String值失效问题(issue#IYF9Y@Gitee) -* 【core】 修复FileUtil.cleanEmpty第二层直接删除文件夹的问题(感谢@【上海】风景) - -------------------------------------------------------------------------------------------------------------- - -## 4.5.15 - -### 新特性 - -### Bug修复 -* 【extra】 修复JschUtil.exec不执行命名的问题(issue#405@Github) -* 【http】 修复CookieManager全局设定导致的可能存在的冲突,增加自定义的GlobalCookieManager - -------------------------------------------------------------------------------------------------------------- - -## 4.5.14 - -### 新特性 -* 【poi】 增加TableUtil -* 【http】 HttpRequest增加setCookieManager方法 -* 【http】 改进url错误时的报错信息(感谢@【北京】thumb) - -### Bug修复 -* 【core】 修复ZipUtil.zlib压缩识别问题(感谢@【上海】 沙漏) -* 【log】 调整log模块层次结构,兼容slf4j的API(issue#IY8DX@Gitee) -* 【core】 Convert.toXXX带默认值换成convertQuietly实现,避免异常(issue#403@Gitee) -* 【log】 解决行号错误问题 -* 【log】 修复decimalFormatMoney中整数丢失问题(issue#IY9OV@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.5.13 - -### 新特性 -* 【crypto】 提供HmacSM3支持(issue#396@Github) -* 【setting】 SettingLoader添加同步锁(issue#396@Github) - -### Bug修复 -* 【log】 修复log模块模板拼接时没有判断等级关闭与否的问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.5.12 - -### 新特性 -* 【json】 解析JSON字符串去除两边空白符(同时解决字符串中bom问题(issue#381@Github) -* 【poi】 Sax解析增加在异常后关闭文件的逻辑(issue#IXBOU@Gitee) -* 【core】 MapUtil增加get重载(TypeReference)(issue#IXL81@Gitee) -* 【crypto】 RC4增加encryptHex和encryptBase64方法(issue#387@Github) -* 【core】 DateUtil.parse增加格式(issue#385@Github) -* 【core】 增加CollUtil.containsAny(感谢【北京】宁静) -* 【core】 增加CollUtil.keySet和values(issue#IXYQJ@Gitee) - -### Bug修复 -* 【poi】 解决三目运算符导致类型转换问题(issue#385@Github) -* 【core】 解决NumberUtil.decimalFormatMoney格式错误问题(issue#391@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.5.11 - -### 新特性 -* 【core】 DateUtil.parse方法识别时间增强(issue#IWMM6@Gitee) -* 【extra】 Mail中Files附件可选为空(issue#365@Github) -* 【extra】 EmojiUtil增加containsEmoji方法(pr#373@Github) -* 【core】 Convert.toDBC()增加空校验(issue#369@Github) - -### Bug修复 -* 【core】 修复NumberUtil.decimalFormatMoney只有整数的bug(issue#IWKVL@Gitee) -* 【bloomFilter】 修复BitMapBloomFilter构造数bug(issue#IWMIN@Gitee) -* 【extra】 MailUtil.send方法传入自定义Setting失效问题(感谢@【上海】康) -* 【core】 修复NetUtil.localIpv4s方法名,改为localIps(issue#IWS2C@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.5.10 - -### 新特性 -* 【extra】 修改MailUtil中的逻辑,默认为非单例邮件客户端(issue#IWFRQ@Gitee) - -### Bug修复 -* 【http】 修复HttpUtil.toParams方法某些符号未转义问题(issue#356@Github) -* 【captcha】 修复验证码被遮挡问题(issue#IWERW@Gitee) -* 【poi】 修复readBySax重复问题(issue#IVKLQ@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.5.9 - -### 新特性 -* 【core】 修改Singleton单例策略,IdUtil增加getSnowflake(issue#IWA0G@Gitee) -* 【core】 增加RandomUtil.randomBoolean(issue#351@Github) -* 【core】 增加Base62实现,Base62类 - -### Bug修复 -* 【json】 修复JSON中含有日期导致的时间戳包含双引号问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.5.8 - -### 新特性 -* 【cron】 CronPatternUtil增加nextDateAfter方法(issue#IVYNL@Github) -* 【core】 增加RandomUtil.randomDate方法(issue#IW49T@Github) -* 【db】 Table增加comment字段,调整元信息逻辑(issue#IW49S@Gitee) -* 【core】 增加ConcurrencyTester(pr#41@Gitee) -* 【core】 ZipUtil增加对流的解压支持(issue#IW798@Gitee) - -### Bug修复 -* 【core】 修复Enjoy模板创建多个引擎报错问题(issue#344@Github) -* 【crypto】 修复Linux下RSA/ECB/PKCS1Padding算法无效问题 -* 【core】 修复ImgUtil.scale方法操作png图片透明失效问题(issue#341@Github) -* 【core】 修复JSON自定义日期格式无引号问题(issue#IW4F6@Gitee) -* 【core】 修复Android下CallerUtil.getCallerCaller空指针问题(issue#IW68U@Gitee) -* 【cache】 修复Cache中超时太大导致Long越界问题(issue#347@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.5.7 - -### 新特性 -* 【core】 新增StrClipboardListener(issue#325@Github) -* 【core】 新增DesktopUtil(issue#326@Github) -* 【core】 CollUtil.getFieldValues增加可选是否忽略null值(issue#IVGEE@Gitee) -* 【http】 新增SoapUtil,SoapClient支持返回SOAPMessage -* 【core】 RobotUtil增加鼠标相关操作 -* 【core】 增加DateModifier,DateUtil增加truncate和ceiling方法(issue#IVL9A@Gitee) -* 【core】 PageUtil增加getStart(issue#IVN0C@Gitee) -* 【core】 CopyOptions增加ignoreXXX方法(感谢@【南昌】...) -* 【core】 ObjectUtil增加isEmpty方法(感谢@【成都】AliK) - -### Bug修复 -* 【core】 修复PatternPool中的URL_HTTP不支持端口的问题(issue#IVF1V@Gitee) -* 【extra】 修复JschUtil.exec多次connect的问题(issue#339@Github) -* 【http】 修复SoapUtil.toString乱码问题(pr#337@Github) -* 【http】 解决Cookie不规范导致的请求响应失败问题(issue#336@Github) -* 【setting】 GroupedMap增加读写锁解决并发问题(issue#336@Github) -* 【json】 修复JSONArray中add方法导致覆盖问题(感谢@【江门】小草哥) -* 【core】 修复Convert对泛型支持不完善的问题(issue#IVMD5@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.5.6 - -### 新特性 -* 【http】 SoapClient增加setParams,增加构造使用默认的namespaceURI方法 -* 【core】 FileUtil增加cleanEmpty方法(issue#319@Github) -* 【core】 增加ClipboardMonitor(issue#320@Github) -* 【http】 SoapClient增加部分方法 -* 【http】 HttpRequest增加setConnectionTimeout和setReadTimeout(issue#322@Github) -* 【core】 Console增printPrograss -* 【core】 DateBetween增加null校验(issue#IVC23@Gitee) -* 【core】 增加CollUtil.getFieldValues重载(issue#IV96S@Gitee) -* 【db】 SqlExecutor和Db增加executeBatch重载,支持批量SQL(issue#324@Github) - -### Bug修复 -* 【bloomFilter】修复负数导致的问题(issue#IV6X6@Gitee) -* 【setting】 修复Props监听问题 -* 【json】 修复TypeUtil中空指针导致的注入失败问题(issue#IVCLW@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.5.5 - -### 新特性 - -### Bug修复 -* 【core】 Assert中NullPointerException改为IllegalArgumentException(issue#IV41L@Gitee) -* 【core】 修复创建新sheet时比较器未清空导致的顺序问题(issue#318@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.5.4 - -### 新特性 -* 【core】 NetUtil增加getUsableLocalPort方法,并迁移至cn.hutool.core.net包 -* 【core】 FileUtil增加isSub方法(pr#39@Gitee) -* 【core】 增加VoidFunc -* 【extra】 mail适配mail.setting和config/mail.setting双配置文件(感谢@【江门】小草哥) -* 【corn】 cron适配cron.setting和config/cron.setting双配置文件(感谢@【江门】小草哥) -* 【poi】 ExcelWriter增加autoSizeColumnAll方法,ExcelBase增加getColumnCount、getRowCount方法(感谢@@【长沙】M) -* 【http】 添加SoapClient,删除SoapRequest - -### Bug修复 -* 【db】 修复Session中事务问题(issue#IUQMN@Gitee) -* 【db】 修复Db中关闭逻辑错误导致的事务问题(感谢@【宁波】mojie126) -* 【http】 修复form方法使用Resource可能导致的空指针问题 -* 【crypto】 修复SM2Engine逻辑错误(感谢bcgit/bc-java) - -------------------------------------------------------------------------------------------------------------- - -## 4.5.3 - -### 新特性 -* 【core】 Simhash添加读写锁(issue#IUF9O@Gitee) -* 【core】 Img增加round方法,圆角给定图片 -* 【extra】 二维码中的图片做圆角处理 -* 【core】 CsvData实现Iterable接口 -* 【extra】 Ftp增加重连方法(pr#38@Gitee) -* 【extra】 Velocity升级至2.x,不再兼容1.7 - -### Bug修复 -* 【core】 修复ReflectUtil新建Map对象错误问题(issue#IUF9O@Gitee) -* 【core】 修复ImgUtil字体为null导致的空指针问题(issue#IUF3X@Gitee) -* 【extra】 修复Ftp中文件上传mkdirs方法创建多余文件夹的问题(issue#ITAYV@Gitee) -* 【extra】 修复Ftp中文件上传mkdirs方法创建多余文件夹的问题(issue#ITAYV@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.5.2 - -### 新特性 -* 【crypto】 增加读取pem格式私钥文件和公钥证书的方法,位于BCUtil(issue#ISJ5M@Gitee) -* 【core】 增加StrUtil.byteLength(issue#284@Github) -* 【core】 增加GlobalBouncyCastleProvider,单例使用BouncyCastleProvider -* 【crypto】 增强对BC库的兼容性,明确RSA为RSA/ECB/PKCS1Padding -* 【core】 snowflake生成器添加id反推生成时间等信息的方法(pr#293@Github) -* 【poi】 CellUtil.getCellValue增加null验证 -* 【core】 增加文件内容跟随器Tailer -* 【crypto】 增加RC4算法 -* 【core】 增加FixedLinkedHashMap -* 【extra】 增加ChannelType,JschUtil增加createSession、createChannel、openChannel等方法 -* 【core】 WatchUtil增加createModify -* 【core】 新增ImgUtil,废弃ImageUtil - -### Bug修复 -* 【core】 修复ExceptionUtil(pr#35@Gitee) -* 【core】 修复RandomUtil注释标注问题(pr#288@Github) -* 【core】 修复TimedCache中onRemove失效问题(issue#ITD0O@Gitee) -* 【core】 修复DateConverter日期负数问题(issue#ITWK4@Gitee) -* 【json】 修复toBean时父类定义泛型字段导致的注入问题(issue#ITGGN@Gitee) -* 【cahce】 修复读锁导致的LRU异常(issue#303@Gtihub) -* 【captcha】 修复在某些未知情况下获取字体高度导致的问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.5.1 - -### 新特性 -* 【socket】 socket模块加入到all中 -* 【core】 增加Jdk8DateConverter用于支持jdk8中的时间(issue#IS32N@Gitee) -* 【core】 StrUtil.subPreGbk优化代码规范(pull#277@Github) -* 【crypto】 MD5支持16位值生成 -* 【crypto】 Digester支持自定义盐所在位置 -* 【captcha】 增加算数计算类验证码(issue#282@Github) - -### Bug修复 -* 【json】 修复JSON中toString导致的中文引号被转义问题(感谢@【内蒙】程序员) -* 【core】 修复15位身份证生日校验问题(issue#ISBUO@Gitee) -* 【extra】 修复部分模板引擎classpath路径获取失败问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.5.0 - -### 新特性 -* 【socket】 增加Socket模块 -* 【core】 Validator增加isIpV4方法(issue#IRQ6W@Gitee) -* 【crypto】 增加SM2Engine,支持C1C2C3和C1C3C2两种模式 -* 【core】 StrUtil.splitTrim支持其它空白符(issue#IRVPC@Gitee) -* 【http】 请求支持DELETE附带参数模式(issue#IRW9E@Gitee) -* 【bloomFilter】调整BitMap注释 - -### Bug修复 -* 【crypto】 修复KeyUtil中使用BC库导致的其它密钥生成异常 -* 【core】 修正DateUtil.formatHttpDate方法 -* 【extra】 修复FTP.ls无法遍历文件问题(issue#IRTA3@Gitee) -* 【extra】 修复QrCodeUtil中ratio参数失效问题,调整默认纠错为M(感谢@【上海】皮皮今) -* 【core】 修复FileTypeUtil对jpg文件识别问题(issue#275@Github) -* 【cache】 修复cache使用读锁导致的删除节点并发问题(issue#IRZTL@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.4.5 - -### 新特性 -* 【core】 增加StrFormater代码逻辑可读性(pr#269@Github) -* 【core】 Validator中使用泛型 -* 【core】 NumberUtil增加toBytes和toInt方法 -* 【core】 XmlUtil增加format方法,支持缩进 -* 【http】 SoapRequest增加executeBody方法(issue#IRN6I@Gitee) -* 【core】 调整XmlUtil.toStr方法对编码的逻辑 - -### Bug修复 -* 【core】 修复AnnotationUtil.getAnnotationValue获取对象错误问题(issue#271@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.4.4 - -### 新特性 -* 【crypto】 增加EC公钥压缩/解压缩(pr#264@Github) -* 【db】 Entity支持IS NOT NULL形式,调整逻辑,强化Condition的toString(issue#267@Github) - -### Bug修复 -* 【core】 修复Profile中路径参数失效问题(issue#265@Github) -* 【core】 修复MapConvert中值类型转换错误的问题(issue#268@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.4.3 - -### 新特性 -* 【crypto】 MD5以及Digester增加加盐支持(issue#256@Github) -* 【crypto】 整理KeyUtil,减少冗余代码 -* 【core】 增加Zodiac类,DateUtil增加getZodiac、getChineseZodiac用于获取星座和生肖(issue#260@Github) - -### Bug修复 -* 【core】 修复ExceptionUtil.stacktraceToString中limit参数无效问题(issue#IR7UE@Gitee) -* 【core】 修复StrUtil.repeatByLength中数组越界问题(issue#IRB2C@Gitee) -* 【core】 修复FileUtil.remove移动后删除失败问题(issue#IRF8R@Gitee) -* 【extra】 修复Ftp中delDir逻辑导致的问题(issue#IRCQ8@Gitee) -* 【core】 修复XmlUtil.mapToXml中map值为空导致的空指针问题。(issue#IRD7X@Gitee) -* 【poi】 修复ExcelWriter中setOnlyAlias没有排除值的问题。(issue#IRF9L@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.4.2 - -### 新特性 -* 【core】 JSON中添加getStrEscaped方法,并修改原getStr逻辑,不再自动转义(issue#IR7SW@Gitee) -* 【core】 CLassLoaderUtil增加getJarClassLoader和loadClass重载方法(issue#IR94T@Gitee) -* 【crypto】 SM2密钥生成曲线修改为使用sm2p256v1(pr#249@Github) -* 【json】 JSONUtil增加空判断(issue#253@Github) -* 【core】 改进HexUtil.isHexNumber(issue#254@Github) -* 【http】 HttpRequest增加getConnection方法(issue#251@Github) - -### Bug修复 -* 【core】 修复URL转义问题(issue#IR6QP@Gitee) -* 【core】 修复WeightRandom权重为0的对象问题(issue#252@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.4.1 - -### 新特性 -* 【core】 增加Rot(回转N位简易替换密码)、凯撒密码和莫尔斯电码 -* 【crypto】 增加Vigenere密码 -* 【db】 增加达梦7的驱动识别 -* 【extra】 TemplateEngine适配更广泛的参数类型 -* 【core】 HexUtil增加toHex方法,增加CRC8和CRC16(issue#IQWNB@Gitee) -* 【http】 添加text/xml ContentType(pr#31@Gitee) -* 【core】 Img、ImageUtil增加Resource和Path参数支持 -* 【extra】 ServletUtil.getClientIP增加注释,提示IP伪造风险 -* 【poi】 增加Word07Writer -* 【crypto】 增加KeyUtil,SecureUtil中的密钥生成迁移至此工具类中 -* 【core】 增加URLEncoder(自行实现解决空格转义问题),HttpUtil废弃encode和decode方法 - -### Bug修复 -* 【poi】 解决ExcelWriter中setSheet报错问题(issue#235@Github) -* 【crypto】 解决SecureUtil.readCertificate密码无效问题(issue#240@Github) -* 【json】 修复JSONUtil.toList针对对象中的类无法实例化导致的null问题(issue#239@Github) -* 【db】 修复MongoDS在Single模式下检查配置文件导致的问题(issue#IR2BF@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.4.0 - -### 新特性 -* 【core】 增加MurmurHash(Murmur3算法实现),HashUtil增加murmur32、murmur64、murmur128方法 -* 【core】 增加Simhash(用于海量文本去重) -* 【extra】 增加分词封装,封装了ansj、HanLP、IKAnalyzer、Jcseg、Jieba、MMSeg、Lucene-analysis、Word的实现,统一了接口 -* 【core】 去除NumberUtil.parseInt和parseLong的8进制支持(issue#234@Github) -* 【extra】 Template部分修改命名减少歧义(Engine->TemplateEngine,EngineFactory->TemplateFactory) -* 【poi】 ExcelWriter中Map支持alias(issue#IQISU@Gitee) - -### Bug修复 - -## 4.3.3 - -### 新特性 -* 【poi】 ExcelWriter增加write重载,可选强制加标题(感谢@【北京】大熊) -* 【core】 ExceptionUtil增加isFromOrSuppressedThrowable(pr#29@Gitee) -* 【core】 ExceptionUtil增加convertFromOrSuppressedThrowable(pr#30@Gitee) -* 【crypto】 非对称和SM2构造传入的私钥和公钥支持Hex和Base64自动识别 - -### Bug修复 -* 【core】 修复padAfter和padPre结果错误问题(issue#IQANO@Gitee) -* 【crypto】 修复SM2签名验证异常(issue#IQAY0@Gitee) -* 【extra】 修复Freemarker字符串模板无效问题(issue#231@Github) -* 【core】 修复StrUtil.strip问题(issue#232@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.3.2 - -### 新特性 -* 【core】 StrUtil增加equalsAny和equalsAnyIgnoreCase方法(issue#IPUQK@Gitee) -* 【http】 StrUtil增加equalsAny和equalsAnyIgnoreCase方法(issue#223@Github) -* 【http】 StrUtil增加padPre、padAfter、center方法(issue#IPWR0@Gitee) -* 【core】 ImageUtil增加compress方法(issue#IPYIF@Gitee) -* 【core】 ReflectUtil增加getMethodByName、getMethodByNameIgnoreCase(issue#IQ2BO@Gitee) -* 【crypto】 增加SmUtil国密算法工具类(issue#225@Github) -* 【crypto】 增加SM2非对称加密(issue#225@Github) -* 【db】 增加AbstractDSFactory,减少冗余代码 -* 【json】 JSONUtil.toBean增加可选是否忽略错误(issue@227@Gtihub) - -### Bug修复 -* 【core】 修复FileUtil.lastIndexOfSeparator空指针问题(issue#IPXPK@Gitee) -* 【core】 修复ArrayUtil.newArray泛型问题 -* 【core】 修复CsvWriter循环调用问题(issue#IQ8T6@Gitee) -* 【poi】 修复ExcelReader读取Map空头导致的问题(issue#IQ6F2@Gitee) -* 【db】 修复Driver识别导致的SQL Server方言异常(issue#IQ687@Gitee) -* 【core】 修复Number.isInteger和isLong判断问题(issue#229@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.3.1 - -### 新特性 -* 【core】 新增DateUtil.dateNew方法(issue#217@Github) -* 【extra】 JschUtil.exec增加重载,可选错误输出(issue#IPNAB@Gitee) -* 【core】 增加NoLock(issue#218@Github) -* 【core】 QrCode.decode改进 -* 【core】 合并无必要的构造方法 -* 【setting】 Setting.getMap方法在分组不存在时返回空Map而非null(issue#IPU2X@Gitee) - -### Bug修复 -* 【db】 解决数据源识别错误问题(issue#IPNI7@Gitee) -* 【core】 修复DateField.of缺失字段问题(issue#IPP51@Gitee) -* 【core】 JSONObject中忽略空值失效问题(issue#221@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.3.0 - -### 新特性 -* 【core】 增加TypeReference类(issue#IPAML@Gitee) -* 【json】 支持TypeReference类转换,并对toBean逻辑做了大量变动(issue#IPAML@Gitee) -* 【core】 ArrayUtil.get和CollUtil.get返回null而非空指针(issue#IPKZO@Gitee) - -### Bug修复 -* 【extra】 修复VelocityEngine中模板中文乱码问题(issue#216@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.2.2 - -### 新特性 -* 【json】 JSONObject调整构造方法,支持对象转为JSON可选是否有序(issue#IP1Q2@Gitee) -* 【core】 BeanUtil增加hasGetter和hasSetter方法 -* 【core】 StrUtil增加isUperCase和isLowerCase方法,增加removeAll和removeAllLineBreaks(issue#IP7PT@Gitee) -* 【db】 增加PostgreSQL的单元测试 -* 【core】 ArrayUtil增加sub方法泛型支持 -* 【core】 从Apache-commons-lang3移植Builder(issue#IPALY@Gitee) -* 【core】 增加Func1接口,ReUtil和StrUtil增加Func1参数的replace方法(pr#27@Gitee) -* 【db】 Table增加getColumn方法,Column补充注释(issue#209@Github) - -### Bug修复 -* 【cron】 修复L代表的最后一天无效问题(issue#IP5PB@Gitee) -* 【core】 修复验证15位身份证月的判断问题(issue#IP70D@Gitee) -* 【poi】 修复多次调用write方法写出多个标题问题(issue#212@Github) -* 【extra】 修复模板写出文件空白问题(issue#208@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.2.1 - -### 新特性 -* 【extra】 增加基于emoji-java的EmojiUtil -* 【http】 增加User-agent解析 -* 【crypto】 引入bouncycastle从而对国密SM2、SM3、SM4支持 -* 【poi】 新增ExcelFileUtil,改进错误提示 - -### Bug修复 - -------------------------------------------------------------------------------------------------------------- - -## 4.1.22 - -### 新特性 -* 【core】 BeanUtil.copyProperties方法支持目标为Map(issue#IOQHZ@Gitee) -* 【poi】 ExcelWriter增加方法setOnlyAlias,用于特定字段剔除(issue#IOOVK@Gitee) -* 【captcha】 增加setBackground方法(issue#200@Github) -* 【core】 NetUtil增加idnToASCII方法(issue#201@Github) -* 【log】 增加JBoss-Logging支持(issue#IOVS1@Gitee) -* 【http】 增加URL标准化,从而支持非http开头的URL字符串 - -### Bug修复 -* 【core】 修复Validator.isBirthday - -------------------------------------------------------------------------------------------------------------- - -## 4.1.21 - -### 新特性 -* 【core】 RuntimeUtil增加getErrorResult方法(issue#199@Github) -* 【core】 ReflectUtil增加hasField方法(感谢@【杭州】J辉) -* 【core】 BeanUtil增加toBean方法(感谢@【杭州】J辉) -* 【db】 增加对HSQLDB支持,改进Driver自定识别 - -### Bug修复 -* 【core】 修复EnumUtil.getFieldNames定义name属性重复问题(感谢@【杭州】J辉) -* 【json】 修复List多层嵌套toBean转换失败问题 -* 【core】 修复ObjectUtil.toString问题(issue#IONLA@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.1.20 - -### 新特性 -* 【http】 增强SoapRequest的兼容性(感谢@【南京】陽光) -* 【core】 改进ZipUtil错误提示 -* 【core】 DateUtil.parse方法读取时间时,年月日按照当天计算。(issue#INYCF@Gitee) -* 【core】 DateUtil.parse改进支持UTC时间格式。 -* 【db】 MongoDS支持客户端验证(issue#IO2DS@Gitee) -* 【core】 改进字符串转集合和数组(支持逗号分隔形式)(pr#26@Gitee) -* 【core】 改进DateConverter(issue#IOCWR@Gitee) -* 【core】 改进NumberUtil中转数字,支持字母结尾(issue#IOCWR@Gitee) -* 【poi】 ExcelUtil增加indexToColName和colNameToIndex方法(issue#IO8ZH@Gitee) -* 【core】 Convert.toList修改为泛型(issue#IOJZV@Gitee) -* 【core】 BeanDesc中属性修改为使用LinkedHashMap存储 -* 【core】 ArrayUtil.get和CollUtil.get对于越界返回null而非抛出异常(issue#IOFKL@Gitee) -* 【core】 EnumUtil增加likeValueOf方法(issue#IOFKL@Gitee) -* 【core】 删除CollUtil.sortPageAll2方法,增加ColllUtil.page方法 - -### Bug修复 -* 【core】 修正CollUtil.sortPageAll逻辑(pr#186@Github) -* 【core】 修复ClassLoaderUtil.loadClass不能加载内部类问题(issue#IO4GF@Gitee) -* 【core】 修复CustomKeyLinkedMap继承问题(issue#IO5Y2@Gitee) -* 【core】 修复NumberUtil.isPrimes没有参数校验导致的问题(issue#IO57Q@Gitee) -* 【extra】 修复QrConfig 引入包错误问题(pr#194@Github) -* 【extra】 修复Sftp创建目录问题(issue#INZUP@Gitee) -* 【core】 修复CollUtil.sortPageAll方法 -* 【core】 修复ImageUtil图片旋转出现黑边问题(pr#189@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.1.19 - -### 新特性 -* 【extra】 Ftp增加setMode方法(issue#INPMZ@Gitee) -* 【core】 IdUtil增加fastUUID和fastSimpleUUID方法(issue#INU37@Gitee) -* 【core】 DateUtil增加formatChineseDate方法(issue#INT6I@Gitee) -* 【core】 ClassUtil中部分方法迁移至ReflectUtil -* 【json】 新增JSONConfig,统一JSON配置,并添加可选的自定义输出日期格式支持 - -### Bug修复 -* 【core】 修复ImageUtil文件流未关闭问题(感谢@【西安】追寻) -* 【core】 修复ZipUtil中gzip和zlib方法未调用finish导致的问题(issue#INSXF@Gitee) -* 【core】 修复ZipUtil中文件目录同名无法压缩的问题(issue#INQ1K@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.1.18 - -### 新特性 -* 【http】 改进字符串匹配正则(issue#INHPD@Gitee) -* 【core】 增加gzip和UnGzip针对流的方法(issue#INKMP@Gitee) -* 【http】 增加ThreadLocalCookieStore - -### Bug修复 -* 【core】 修复BeanUtil.copyProperties参数多余问题 -* 【cron】 修复表达式匹配错误问题(issue#INLEE@Gitee) -* 【core】 修复ReflectUtil获取空参数方法导致的问题(issue#INN5W@Gitee) -* 【json】 修复JSONArray.toList方法导致的问题(issue#INO3F@Gitee) -* 【core】 修复NumberUtil.parseLong中0转换问题方法导致的问题(issue#INO3F@Gitee) -* 【core】 修复CompareUtil循环引用问题(issue#180@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.1.17 - -### 新特性 - -### Bug修复 -* 【core】 修复JDK7之后比较器中违反自反性导致的问题 -* 【cron】 修改部分逻辑 - -------------------------------------------------------------------------------------------------------------- - -## 4.1.16 - -### 新特性 -* 【core】 Convert.增加boolean类型转数字(issue#INCKM@Gitee) -* 【core】 新增BooleanUtil - -### Bug修复 -* 【core】 修复JDK11下Caller被弃用导致的问题(issue#174@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.1.15 - -### 新特性 -* 【core】 Convert.toInt增加容错,NumberUtil增加toNumber方法(issue#IN2LP@Gitee) -* 【core】 ImageUtil增加cut切圆形方法(issue#IN3JJ@Gitee) -* 【core】 Img增加setPositionBaseCentre可选坐标计算基于中心(issue#IN3JM@Gitee) -* 【core】 ImageUtil增加逻辑判断颜色模式,避免失色问题(issue#IN3JK@Gitee) -* 【cron】 改进规则支持20/2这类形式 -* 【extra】 ServletUtil.write增加重载方法支持文件(issue#IN9O0@Gitee) - -### Bug修复 -* 【core】 修复DateUtil.yearAndQuarter计算错误的问题(issue#IN38V@Gitee) -* 【core】 修复ClassUtil.isPublic判断问题(issue#IN38V@Gitee) -* 【extra】 修复JschUtil中Session关闭未移除出池导致的问题(issue#171@Github) -* 【core】 修复NumberUtil.isInteger中0判断问题(issue#IN9BS@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.1.14 - -### 新特性 -* 【core】 StrUtil增加hide方法 -* 【core】 PatternPool增加URL_HTTP,原URL规则变更 -* 【extra】 统一FTP和SFTP接口规范 -* 【extra】 QrCodeUtil支持二维码中贴Logo图片 -* 【core】 校准ImageUtil.pressText文字位置 -* 【core】 ImageUtil增加getColor等方法 -* 【core】 增加RobotUtil提供截屏等封装,增加ScreenUtil用于获取屏幕属性 -* 【extra】 QrCodeUtil增加条形码等其它类型支持(issue#IN1CR@Gitee) -* 【core】 增加DateUtil.parseUTC方法(issue#IN1IO@Gitee) -* 【core】 增加DateUtil.isWeekend方法 -* 【all】 加入Travis-CI验证项目构建 - -### Bug修复 -* 【core】 修复ImageUtil.convert转换png变色问题(issue#IMWUO@Gitee) -* 【core】 修复FileUtil.newerThan中null判断的问题(issue#165@Github) -* 【extra】 修复Ftp中mkdir方法引起的数组越界问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.1.13 - -### 新特性 -* 【core】 增加RejectPolicy线程池线程拒绝策略枚举 -* 【core】 DateUtil增加isSame方法 -* 【core】 FileUtil.getAbsolutePath方法在获取不到ClassPath情况下返回原路径 -* 【core】 打印SQL日志覆盖每一个方法 -* 【core】 Convert.toXXX转数字的时候默认去除两边空白符 -* 【poi】 增加BigExcelWriter,支持Excel大数据导出(issue#IK47S@Gitee) -* 【core】 ExceptionUtil增加isCausedBy和getCausedBy方法 -* 【poi】 EnumUtil增加toString和fromString -* 【poi】 新增IdUtil工具类 - -### Bug修复 -* 【core】 修复RuntimeUtil.getResultLines未关闭Process问题(pr#164@Github) -* 【core】 修复ClassPathResource在jar运行模式下的空指针问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.1.12 - -### 新特性 -* 【core】 ExcelReader.read方法返回的Map默认有序 - -### Bug修复 -* 【core】 修复ZipUtil以及FileUtil中slip漏洞(issue#162@Github) -* 【core】 修复ZipUtil路径问题(issue#IMUEK@Gitee) -* 【core】 修复FileUtil.getParent方法获取父路径不严格导致空指针问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.1.11 - -### 新特性 -* 【core】 Convert增加toList方法 -* 【core】 StrUtil增加containsAny针对char的重载 -* 【core】 FileUtil.mainName修正处理逻辑 -* 【core】 CharUtil增加isFileSeparator方法 -* 【core】 增加UUID类,提升Simple模式下性能 -* 【poi】 ExcelUtil增加setStyleSet方法,修改write逻辑,对于单列数据输出,而非忽略(感谢@【宁波】mojie126) -* 【core】 新增WebAppResource类 -* 【extra】 新增Thymeleaf模板支持 -* 【setting】 去除Setting日志 - -### Bug修复 -* 【script】 修复FullSupportScriptEngine构造中ext和mimeType方式获取引擎丢失问题 -* 【cron】 修复定时任务执行阻塞问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.1.10 - -### 新特性 -* 【extra】 Template增加Jfinal的Enjoy模板支持 -* 【core】 Assert增加checkBetween方法,Validator增加isBetween和validatorBetween -* 【core】 增加CollUtil.getLast方法(感谢@【帝都】宁静) -* 【core】 修改Assert.notNull注释(issue#IMI3Z@Gitee) -* 【core】 BeanUtil增加isEmpty和hasNullField方法(pr#157@Github) -* 【log】 ConsoleLog增加setLevel方法(issue#IMLZ3@Gitee) -* 【captcha】 解决验证码超出背景的问题(issue#IHWHE@Gitee) - -### Bug修复 -* 【core】 修复BOMInputStream构造的问题(pr#22@Gitee) -* 【json】 修复toBean中如果字段中为字符串而JSON中为JSONObject对象注入失败问题(issue#IMGBJ@Gitee) -* 【setting】 修复keySet总返回空问题(issue#IMHD7@Gitee) -* 【extra】 修复starttls和SSL连接混淆问题(issue#IMLMD@Gitee) -* 【setting】 修复getStr无法获取默认值问题(issue#IMLMI@Gitee) -* 【core】 修复BeanUtil.mapToBean设置别名失效问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.1.9 - -### 新特性 -* 【core】 MapUtil增加toObjectArray方法 -* 【core】 URLUtil.normalize增加反斜杠处理(issue#IM8BI@Gitee) -* 【core】 增加ClassUtil.getShortClassName(issue#IM8XM@Gitee) -* 【core】 增加ThreadFactoryBuilder和ExecutorBuilder -* 【cron】 定时任务改为线程池实现 -* 【core】 Assert增加checkIndex方法 -* 【core】 parseBoolean增加on、off关键字支持可选字符串 -* 【core】 URLUtil.formatUrl方法兼容更多情况(issue#IMAEA@Gitee) -* 【core】 改进NumberUtil.isInteger和isLong判断(issue#IMDGB@Gitee) -* 【http】 HttpResponse增加isOk方法(issue#155@Github) -* 【http】 改进HttpUtil.downloadXXX方法,返回非2XX抛出异常(issue#IMCTT@Gitee) -* 【http】 HttpRequest增加setUrlHandler方法(issue#IMD1X@Gitee) -* 【http】 HttpRequest增加getCookieManager和closeCookie方法(issue#IMDND@Gitee) - -### Bug修复 -* 【core】 修复IdcardUtil中isValidCard10空指针问题(issue#IMB7R@Gitee) -* 【core】 修复SoapRequest空指针问题(issue#IMBUN@Gitee) -* 【http】 修复文件上传没有关闭File的问题(issue#IMDUY@Gitee) -* 【json】 修复toBean中有Map参数导致的值丢失问题(issue#IMDEM@Gitee) -* 【bloomFilter】修复hash值负数问题(issue#154@Github) -* 【core】 修复Convert中Map强转导致的问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.1.8 - -### 新特性 -* 【http】 HttpRequest增加getUrl、getMethod等方法 -* 【core】 Validator增加isWord和ValidateWord(感谢@【帝都】宁静) -* 【core】 增加CollUtil.filter针对List的重载(issue#IM1NI@Gitee) -* 【core】 增加ImageUtil.toBase64 -* 【http】 增加SoapRequest -* 【poi】 ExcelWriter增加renameSheet方法(issue#150@Github) -* 【core】 ZipUtil增加unzipFileBytes方法(issue#IM5KO@Gitee) -* 【aop】 加入Cglib实现的切面支持(issue#IM4Y2@Gitee) -* 【extra】 加入FTP客户端支持,基于commons-net封装 - -### Bug修复 -* 【http】 修复编码自动识别的bug(issue#IM33O@Gitee) -* 【db】 修复Session中ds引起的空指针问题(感谢@【武汉】jellard) -* 【core】 修复ReflectUtil.newInstance二次调用资源问题(issue#IM51X@Gitee) -* 【core】 修复ClassScaner包名前缀引起的问题(issue#IM5OJ@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.1.7 - -### 新特性 -* 【db】 SqlRunner被弃用 - -### Bug修复 -* 【db】 修复Oracle分页问题(issue#ILZDA@Gitee) -* 【db】 Dialect使用单例 - -------------------------------------------------------------------------------------------------------------- - -## 4.1.6 - -### 新特性 -* 【core】 OptNullBasicTypeGetter增加getDate方法(issue#ILUQM@Gitee) -* 【core】 RuntimeUtil增加可选环境变量参数(issue#ILV2I@Gitee) -* 【core】 修改Caller结构 - -### Bug修复 -* 【db】 修复Oracle分页多一条问题(issue#ILUQM@Gitee) -* 【poi】 修复ExcelWriter换行问题(issue#ILXLI@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.1.5 - -### 新特性 -* 【poi】 ExcelWriter支持通过别名方式设置Bean写出的顺序(感谢@【武汉】zzz) -* 【db】 SQL日志打印扩展到所有SQL(感谢@【河北】理想主义) -* 【core】 增加FileUtil.copyFilesFromDir方法(issue#ILRLG@Gitee) -* 【core】 EscapeUtil.unescapeHtml4和EscapeUtil.escapeHtml4(issue#112@Github) -* 【http】 增加CustomProtocolsSSLFactory和AndroidSupportSSLFactory(pr#142@Github) -* 【setting】 添加SettingUtil(感谢@【杭州】t-io) -* 【bloomFilter】添加BloomFilterUtil -* 【core】 添加Img类 - -### Bug修复 -* 【http】 修复body方法判断Content-Type失效问题(感谢@【上海】皮皮今) -* 【core】 修复FileUtil.copy方法在目标不存在的情况下报错问题 -* 【core】 修复ClassScaner在Spring boot fat jar下扫描失败的问题(issue#IKDJW@Gitee) -* 【json】 修复JSONObject构造names列表为空导致的构造空对象(issue#143@Github ) -* 【core】 修复ImageUtil.pressText图片有黑边的问题(issue#141@Github) - - -------------------------------------------------------------------------------------------------------------- - -## 4.1.4 - -### 新特性 -* 【all】 补充package-info -* 【db】 增加方法SqlExecutor.callQuery(issue#ILJ0N@Gitee) -* 【core】 ExceptionUtil增加部分方法 -* 【system】 SystemUtil增加部分方法 -* 【core】 新增NamedThreadLocal(issue#ILJ0Z@Gitee) -* 【core】 ZipUtil新增Zlib压缩解压 -* 【core】 NumberUtil增加parseInt和parseLong,支持10进制、8进制和16进制自动识别 -* 【db】 Table继承自LinkedHashMap保证字段读出有序(感谢@【帝都】宁静) -* 【json】 JSONObject子类自动判断是否有序(感谢@【帝都】宁静) -* 【poi】 抽象ExcelBase,提取共用方法 - -### Bug修复 -* 【http】 修复HttpRequest.setFollowRedirects无效问题(issue#ILIKG@Gitee) -* 【core】 修复CharUtil.isEmoji问题 -* 【http】 修复HttpResponse.writeBody同步模式下写出失败问题 -* 【http】 修复Cookie机制导致的部分Cookie信息不能在请求时附带的问题 -* 【json】 修复JSONArray.toArray转换为原始类型导致的异常问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.1.3 - -### 新特性 -* 【all】 优化db的DsFactory、log的LogFactory、extra的TemplateUtil逻辑,减少异常栈嵌套 -* 【core】 Validator增加isMac、validateMac方法(感谢@【上海】阳仔) - -### Bug修复 -* 【core】 修复ArrayUtil.join前后fix失效问题(@【河北】理想主义) -* 【core】 修复DateRange最后一个元素逻辑问题(issue#ILE38@Gitee) -* 【cron】 修复调用CronUtil.stop()方法无法正常结束作业进程的问题(issue#ILFCZ@Gitee) -* 【db】 修复page方法在Oracle中丢失参数问题(issue#ILGXP@Gitee) -* 【extra】 修复QrCodeUtil.decode对复杂二维码解码失败问题(感谢@【成都】小朋友) - -------------------------------------------------------------------------------------------------------------- - -## 4.1.2 - -### 新特性 -* 【core】 MapUtil增加getDate方法(感谢@【帝都】宁静) -* 【json】 putByPath方法增加容错性,支持下标越界识别为追加(issue#IKNM6@Gitee) -* 【core】 增加FileUtil.getParent方法(pr#18@Gitee) -* 【core】 ImageUtil.pressText增加抗锯齿(pr#19@Gitee) -* 【core】 BeanUtil.getPropertyDescriptors去除class属性(issue#IKVKR@Gitee) -* 【json】 putByPath方法针对空的规则变更(issue#IKX2H@Gitee) -* 【captcha】 增加CodeGenerator,可自定义验证码文字生成策略(issue#IL3YH@Gitee) -* 【core】 增加CollUtil.list方法,更灵活的创建ArrayList和LinkedList -* 【core】 DateTime增加时区支持(issue#131@Github) -* 【extra】 QrCodeUtil二维码生成支持设置边距、颜色等自定义项(issue#135@Github) - -### Bug修复 -* 【core】 修复JSONUtil.formatJsonStr引号换行问题(issue#IKMMK@Gitee) -* 【core】 修复URLUtil.getDecodedPath可能导致的空指针问题(issue#IKLRD@Gitee) -* 【core】 修复PinyinUtil.getAllFirstLetter非汉字显示问题(issue#IKM0P@Gitee) -* 【json】 修复当Bean为私有类时无法实例化导致的JSON转换问题(感谢@【上海】风景) -* 【json】 修复Bean中有Object字段时toBean产生的问题(感谢@【上海】风景) -* 【core】 修复XmlUtil关闭XXE避免XXE攻击 -* 【poi】 修复Excel03SaxReader读取小数的问题(感谢@【深圳】rm -rf /) -* 【core】 修复CollUtil.findOne空参数导致的空指针问题(issue#133@Github) -* 【core】 修复JSONArray.addAll问题(pr#137@Github) -* 【core】 修复UnicodeUtil单独空格无法转换问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.1.1 - -### 新特性 -* 【poi】 ExcelWriter写出bean使用LinkedHashMap -* 【core】 UnicodeUtil新增:1、\u大小写不区分,2、\u后跟非16进制按照非Unicode符对待,直接输出(issue#IKJGU@Gitee) -* 【crypto】 增加Bcrypt实现(参照:jBCrypt) -* 【core】 XXXIterator修改为XXXIter,同时实现Iterator和Iterable接口 -* 【core】 Dict使用LinkedHashMap,Entity也是 - -### Bug修复 -* 【setting】 修复store方法无换行问题 -* 【core】 修复UnicodeUtil.toString方法不正确Unicode死循环问题(issue#IKJGU@Gitee) -* 【http】 修复HttpsURLConnectionOLDImpl导致的转换异常(issue#IKKGF@Gitee) -* 【crypto】 修复RSA分段加密解密的bug(感谢@【深圳】Demo) -* 【poi】 修复ExcelWriter写出文件无法覆盖问题(感谢@【宁波】mojie126) -* 【poi】 修复sax方式读取空行空指针问题(issue#124@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.1.0 - -### 新特性 -* 【extra】 模板工具改为模板门面,抽象各模板引擎 -* 【core】 修改Season为quarter(pr#114@Github) -* 【core】 CollUtil增加removeAny方法 -* 【core】 StrUtil增加emptyToDefault和blankToDefault(issue#115@Github) -* 【core】 优化排列组合算法(感谢@【青岛】LQ) -* 【core】 NumberUtil增加roundHalfEven(感谢@【青岛】LQ) -* 【http】 HttpRequest.form支持多文件上传(相同key)(issue#IJYWM@Gitee) -* 【db】 新增SqlLog,独立SQL日志打印配置 -* 【poi】 ExcelReader新增readAsText方法,ExcelWriter新增setHeaderOrFooter方法(设置页眉页脚) -* 【crypto】 删除DSA类(DSA算法用在Sign中),修改规则,RSA分段方式变为全局(issue#IKGKG@Gitee) -* 【core】 DateUtil添加range和rangeToList方法,增加DateRange类(issue#119@Github) -* 【core】 StrUtil增加concat方法,可选是否null转""(感谢@【帝都】宁静) - -### Bug修复 -* 【core】 修复StrUtil.replace方法第一个字符无法替换问题(issue#IJZR0@Gitee) -* 【core】 修复Season计算问题(pr#114@Github) -* 【core】 修复PinyinUtil获取拼音特殊字符转数字问题(issue#IJNWH@Gitee) -* 【core】 修复FileUtil.isAbsolutePath方法正则问题(issue#IJZUB@Gitee) -* 【extra】 修复ServletUtil.getMultipart方法的问题 -* 【http】 修复patch方法无效问题(issue#IK2Z8@Gitee) -* 【core】 修复DateUtil.parseTimeToday格式问题(issue#IK25B@Gitee) -* 【poi】 修复设置字体日期和小数无效问题(issue#IK488@Gitee) -* 【core】 修复NumberUtil.partValue的bug(pr#15@Gitee) -* 【poi】 调整了readBySax方式读取导致的部分问题 -* 【core】 修复CsvRow的get方法越界问题(issue#IK9CX@Gitee) -* 【core】 修复UnicodeUtil丢失末尾字符串的问题(issue#IKI6T@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.0.13 - -### 新特性 -* 【json】 JSONArray添加jsonIter方法可以实现foreach语法遍历JSONObject(issue#IJPIJ@Gitee) -* 【core】 强化FileTypeUtil中对PDF文件格式的识别兼容性(issue#IJO1K@Gitee) -* 【core】 修改BetweenFormater枚举规则,修复不足1天显示空问题 -* 【http】 由于JDK9移除了javax.activation导致的问题,修复移除相关包依赖(issue#109@Github) -* 【core】 改进Resource,增加getName方法,增加构造支持name -* 【core】 RandomUtil增加randomStringUpper方法(issue#IJVLS@Gitee) - -### Bug修复 -* 【core】 修复XmlUtil.toStr方法注释丢失问题(issue#IJPUA@Gitee) -* 【core】 修复ImageUtil.scale和createFont方法的bug(issue#IJOKE@Gitee) -* 【core】 修复StrUtil.format方法Map参数中值为null导致的空指针问题(issue#IJO31@Gitee) -* 【core】 修复ReUtil.getAllGroups丢失最后一个分组问题(issue#IJRJM@Gitee) -* 【json】 修复Bean中为Map导致的泛型类型不匹配问题(issue#IJRJM@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.0.12 - -### 新特性 -* 【core】 ClassScaner支持jar的嵌套 - -### Bug修复 -* 【setting】 修复Setting中size的bug -* 【cron】 修复Setting修改导致的定时任务读取错误问题(issue#IJMVN@Gitee) -* 【setting】 修复Props中autoLoad无效问题(issue#IJMOE@Gitee) -* 【cron】 修复表达式中年匹配位置的问题(issue#106@Gtihub) -* 【log】 修复log.info(null)空指针问题(issue#IJNRW@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.0.11 - -### 新特性 -* 【core】 Week.toChinese()添加可选参数,选择星期的前缀(比如是“星期”还是“周”) -* 【core】 PinyinUtil增加方法,汉字转拼音(pr#11@Gitee) -* 【core】 Convert增加toList方法 -* 【core】 CollUtil增加toList方法(感谢@【帝都】宁静) -* 【poi】 新增FormulaCellValue对象用于写出公式支持(感谢@【宁波】mojie126) - -### Bug修复 -* 【core】 修复NumberChineseFormater.format()方法无“元”字的问题(issue#IJ6MR@Gitee) -* 【core】 修复FileUtil.loopFile遍历根目录时空指针错误问题 -* 【poi】 修复ExcelReader遇到ERROR单元格时报错问题(感谢@夏夜神话) -* 【http】 修复HttpUtil.post传入json字符串导致的问题(issue#99@Github) -* 【json】 修复Unicode不可见字符转义导致的中文双引号等符号显示问题(issue#IJFBD@Gitee) -* 【core】 修复ReferenceUtil中SoftReference错误问题(pr#105@Github) -* 【db】 删除ActiveRsHandler(歧义),修复showSql属性报错问题(issue#IJII8@Gitee) -* 【setting】 大改Setting逻辑,使用GroupedMap代替分组拼接方式,解决了无分组情况下会包含分组的问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.0.10 - -### 新特性 -* 【poi】 ExcelWriter.merge方法加入重载,可选是否加入默认标题样式 -* 【poi】 ExcelSaxReader改进按照流读取工作簿的构造,使之对于mark不支持的流也可解析 -* 【cron】 添加updatePattern方法,可更新Task执行时间规则(感谢@【上海】嘿) -* 【cache】 添加get方法支持可选的是否更新lastAccess时间(issue#IISC4@Gitee) -* 【core】 StrUtil增加isNullOrUndefined、isEmptyOrUndefined、isBlankOrUndefined方法(issue#IIR44@Gitee) -* 【core】 isBlankChar方法迁移到CharUtil中 -* 【db】 增加NamedSql -* 【poi】 对于POI未引入或版本错误提供更加明确的提示 -* 【core】 增加UUIDConverter,支持UUID对象的自动转换 -* 【core】 IterUtil增加fieldValueList、fieldValueAsMap、join重载方法(issue#IIU4F@Gitee) -* 【core】 IoUtil增加checksum、toBuffered方法,StrUtil增加maxLength方法(参考osgl-tool) -* 【poi】 ExcelReader支持自定义sheet - -### Bug修复 -* 【poi】 修复ExcelWriter合并单元格后样式失效问题 -* 【http】 修复HttpUtil.download方法遇到特殊Disposition时处理异常问题(感谢@【深圳】Bomb) -* 【core】 修复StrUtil.toUnderlineCase方法中下划线转下划线导致的问题 -* 【core】 修复RandomUtil.randomEles方法计数错误问题(issue#98@Github) -* 【core】 修复NumberChineseFormater负数小数结果错误问题(pr#10@Gitee) -* 【captcha】修复验证码无法序列化的问题(issue#IJ2MI@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 4.0.9 - -### 新特性 -* 【core】 SecureUtil增加signParamsSha1方法(感谢@【帝都】宁静) -* 【core】 XmlUtil增加mapToXml和xmlToMap(感谢@【杭州】小宙子) -* 【captcha】修改逻辑:在创建验证码对象时生成一个验证码(感谢@【重庆】liuuuu) -* 【core】 CopiedIterator使用LinkedList替代ArrayList(issue#III8K@Gitee) -* 【poi】 ExcelWriter增加getOrCreateCell、createStyleForCell方法,便于自定义特殊单元格 -* 【core】 增加AnnotationUtil类 -* 【core】 IoUtil增加toMarkSupportStream方法 -* 【poi】 ExcelReader改进按照流读取工作簿的构造,使之对于mark不支持的流也可解析 -* 【core】 新增BytesResource和InputStreamResource -* 【core】 RandomUtil新增randomBigDecimal(感谢@【帝都】宁静) -* 【db】 Column对象添加comment字段 -* 【core】 Base64增加encode方法,参数为Inputstream和File,新增decodeToFile、decodeToStream(issue#IILZS@Gitee) -* 【core】 扩充XmlUtil部分方法 - -### Bug修复 -* 【core】修复StrUtil.replace问题(感谢@【上海】piaohao) -* 【mail】解决在javax.mail大于1.5版本时,附件名过长在国内邮箱导致的显示错误问题(添加splitlongparameters参数) -* 【core】修复ZipUtil.zip压缩目录时加入盘符问题(感谢@【深圳】Vmo ) -* 【core】修复PropertyComparator失效问题(感谢@【长沙】哼哼 ) -* 【cron】修复20/2此类表达式无效问题(感谢@【广州】杨小过 ) -* 【core】修复XmlUtil.toStr编码设置无效问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.0.8 - -### 新特性 -* 【core】新增PinyinComparator、CollUtil新增sortByPinyin(感谢@【帝都】宁静) -* 【json】JSONUtil增加xmlToJson方法 -* 【poi】 ExcelWriter增加setColumnWidth和setRowHeight方法 -* 【core】FileUtil.clean增加字符串重载(感谢@【帝都】宁静) -* 【core】ArrayUtil增加insert方法(感谢@【帝都】宁静) -* 【core】RandomUtil.randomDouble增加可选保留小数重载(感谢@【帝都】宁静) -* 【core】增加RandomUtil.randomDay随机天(感谢@【帝都】宁静) -* 【poi】 ExcelWriter增加setOrCreateSheet方法,从而支持多sheet生成 - -### Bug修复 -* 【json】修复JSONArray中addAll加入两次的bug(感谢@【天津】〓下页) -* 【core】修复BeanDesc中对static属性未忽略的问题(感谢@【深圳】枫林晓寒) -* 【http】解决无法移除默认头信息的问题 -* 【core】修复Base64在decode时针对urlSafe乱码问题(issue#89@Github) -* 【core】修复ReUtil.extractMulti(感谢@【杭州】徐承恩) -* 【core】修复DESede类中算法错误问题(issue#93@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.0.7 - -### 新特性 -* 【core】新加math包,并添加MathUtil工具类(排列组合迁入此) -* 【core】StrUtil增加move方法,字符串位移(感谢@【帝都】宁静) -* 【core】ArrayUtil的max和min采用可变参数(T[]除外)(感谢@【帝都】宁静) -* 【core】NumberUtil增加max和min方法,与ArrayUtil一致(感谢@【帝都】宁静) -* 【poi】 去除InternalExcelUtil,根据功能新增WorkbookUtil、RowUtil、CellUtil、ExcelPicUtil -* 【core】新增PinyinUtil(感谢@【帝都】宁静) -* 【core】StrUtil增加wrapAll、wrapAllIfMissing(感谢@【帝都】宁静) -* 【core】Singleton增加put方法 -* 【core】Convert增加convertByClassName方法 -* 【json】JSONUtil增加toList快捷方法 - -### Bug修复 -* 【core】修复排列组合结果错误问题(感谢@【帝都】宁静) -* 【poi】 修复StrUtil.unWrap传入null导致的越界问题(issue#II1VU@Gitee) -* 【core】修复ImageUtil.sliceByRowsAndCols方法计算错误(感谢@【唐山】小虫) -* 【core】修复StrUtil.replace问题(感谢@【霾都】QQ小冰) -* 【core】修复FileTypeUtil对jpg的识别范围(issue#91@Github) - -------------------------------------------------------------------------------------------------------------- - -## 4.0.6 - -### 新特性 -* 【poi】 ExcelReader增加getWriter、getOrCreateCell方法 -* 【core】NetUtil增加isInRange方法(感谢@【成都】小邓) -* 【core】新增BeanPath(仅支持部分JSONPath语法) -* 【core】CollUtil新增reverse、reverseNew方法 -* 【core】集合中新增排列(Arrangement)和组合(Combination)类(感谢@【北京】宁静) -* 【core】StrUtil新增splitToLong和splitToInt方法 -* 【core】MapUtil增加getXXX方法 -* 【core】扩充Dict构造 -* 【core】CollUtil新增sortByProperty方法 -* 【json】toBean支持下划线转驼峰 -* 【core】FileUtil新增更多方法,包括路径拼接 -* 【core】新增LineIterator、NullOutputStream两个类 - -### Bug修复 -* 【core】修复IdcardUtil中身份证15转18位年的问题(Issue#IHT1Q@Gitee) -* 【http】忽略Premature EOF错误(感谢@【南京】peckey) -* 【core】修复ArrayConvert中集合转原始类型数组导致的异常 - -------------------------------------------------------------------------------------------------------------- - -## 4.0.5 - -### 新特性 -* 【json】 toBean方法支持Map.class参数,消除歧义 -* 【core】FileWriter和FileUtil增加writeMap方法 -* 【core】新增CsvWriter和CsvUtil -* 【poi】 改进ExcelWriter.flush未指定文件时的报错信息 -* 【db】 在配置文件不存在时优化错误提示 -* 【core】BeanUtil.beanToMap方法支持自定义key -* 【core】增加ModifierUtil,修饰符工具类 -* 【http】下载文件时文件名首先从头信息中获取 -* 【poi】 ExcelReader增加getCell方法 -* 【db】 Oracle驱动变更 -* 【extra】扩充Sftp方法(感谢@【广西】Succy) -* 【core】ImageUtil增加binary方法,生成二值化图片(感谢@【天津】〓下页) - -### Bug修复 -* 【poi】 修复ExcelReader获取Workbook为空的问题 -* 【core】修复ImageUtil.scale的问题(感谢@【北京】千古不见一人闲) -* 【json】 修复JSON转字符串时值中双引号转义问题(感谢@【深圳】jae) - -------------------------------------------------------------------------------------------------------------- - -## 4.0.4 - -### 新特性 -* 【http】 HttpUtil.downloadFile增加超时重载(感谢@【深圳】富) -* 【setting】Setting增加构造重载(pr#8@Gitee) -* 【core】 IterUtil增加fieldValueMap方法(感谢@【苏州】陈华 万缕数据@【北京】宁静) - -### Bug修复 -* 【log】 修复StaticLog.warn打印级别错误问题(issue#IHMF9@Gitee) -* 【core】修复MapUtil.newHashMap中isOrder(感谢@【珠海】hzhhui) -* 【core】修复DateTime.season获取的问题(感谢@西湖断桥) -* 【cron】修复在秒匹配关闭时无法匹配的问题(感谢@【北京】宁静) - -------------------------------------------------------------------------------------------------------------- - -## 4.0.3 - -### 新特性 -* 【core】新增LocalPortGenerater,本地端口生成器 -* 【extra】新增Sftp类,用于SFTP支持 -* 【core】StrUtil增加replace(支持参数从某个位置开始)和replaceIgnoreCase方法(感谢@【贵阳】shadow ) -* 【core】Number.equals方法迁移到CharUtil(NumberUtil中依旧保留) -* 【extra】mail增加抄送和密送支持(感谢【成都】出错) -* 【poi】ExcelReader别名在返回List时也被支持(第一行) -* 【poi】ExcelReader增加getSheets和getSheetNames方法(感谢@【帝都】宁静) -* 【poi】ExcelReader增加readCellValue和readRow方法(感谢@【苏州】马克) -* 【db】全局数据源工厂独立,使用懒加载方式,消除歧义 -* 【log】全局日志工厂独立,懒加载方式,消除歧义 -* 【extra】MailUtil增加快捷方法支持抄送和密送参数 - -### Bug修复 -* 【core】修复获取子路径bug(issue#IHI5K@Gitee) -* 【poi】修复ExcelReader在读取文件后未关闭导致文件被占用问题(感谢@【昆明】-@_@) -* 【log】解决Tinylog实现显示类名和行行错误问题 -* 【extra】修复Mail构造在MailAccount传入null时读取错误的问题 - -------------------------------------------------------------------------------------------------------------- - -## 4.0.2 - -### 新特性 -* 【core】优化BeanDesc,适配更多Getter和Setter方法 -* 【extra】增加基于zxing的二维码生成和解码(zxing可选依赖) -* 【core】增加VersionComparator用于版本比较,同时添加StrUtil.compareVersion -* 【core】Convert支持Map、Bean之间的转换、enum,新增BeanConverter和CastBeanConverter -* 【extra】ServletUtil中增加获取body和上传文件支持 -* 【json】在json与bean互相转换时支持enum和字符串转换(感谢@【帝都】宁静) -* 【core】增加OptArrayTypeGetter接口 -* 【http】HttpUtil增加decodeParamMap方法,返回单值map(感谢@【帝都】宁静) -* 【poi】ExcelWriter增加writeCellValue方法 -* 【cron】去除CronUtil以及Scheduler中的isMatchYear方法(年的匹配通过表达式自动判断) -* 【extra】邮件Mail对象增加setUseGlobalSession方法,用于自定义是否使用单例会话 - -### Bug修复 -* 【setting】修复clear方法未清空group的问题,store方法未换行问题,set方法分组丢失问题(感谢@【广西】Succy) -* 【json】修复Map嵌套转JSONObject时判断失误导致的值错误(issue#@Gitee) -* 【core】修复betweenYear注释错误(感谢@【常州】在校学生) -* 【core】修复Convert.digitToChinese方法中角为0时显示问题(issue#IHHE1@Gitee) -* 【cron】修复在秒匹配模式下5位表达式执行异常问题,修复cron.setting文件不存在报错问题 -* 【extra】邮件配置中参数值转为String解决可能存在的bug - -------------------------------------------------------------------------------------------------------------- - -## 4.0.1 - -### 新特性 -* 新增CharUtil -* 新增ASCIIStrCache,对ASCII字符做String对应表,提升字符转字符串性能 -* 去除JschUtil中的同步修饰,改为锁 -* 新增MapUtil.sort -* SymmetricCrypto支持加密后转为Base64和从Base64解密 -* AsymmetricCrypto支持Hex和Base64加密解密 -* 新增SecureUtil.signParams方法用于参数签名(感谢@【帝都】宁静) -* 新增Loader和LazyLoader,抽象懒加载 -* 新增CsvReader,CSV读取 -* HttpRequest支持可选get请求下的url参数编码 -* ExcelReader增加read重载方法,ExcelUtil增加isEmpty(Sheet)方法(pr#5@Gitee) -* db模块针对IS NULL优化 - -### Bug修复 -* 修复db模块中数据库为下划线而Bean为驼峰导致的注入失败问题(感谢@【广西】Succy) -* 修复findLike的bug(感谢@cici) -* 修复ArrayUtil.join循环引用bug -* FileTypeUtil针对pdf格式做修改(issue#IHDNH@Gitee) -* 修复Http模块中get方法拼接参数问题 -* 修复db模块in方式查询错误问题 -* 修复CollUtil.disjunction计算差集修复一个集合为空的情况(感谢@【天津】〓下页) -* 修复Db模块中Number参数丢失问题(感谢@【山东】小灰灰) - -------------------------------------------------------------------------------------------------------------- - -## 4.0.0 - -### 新特性 -* 变更包名为cn.hutool.xxx -* 新增ObjecIdt类,用于实现MongoDB的ID生成策略 -* 验证码单独成为一个模块hutool-captcha -* 新增NamedThreadFactory -* 新增BufferUtil -* POI新增StyleUtil,StyleSet新增方法可设置背景、边框等样式 -* JDBC参数针对BigInteger处理 -* db模块支持显示和格式化显示SQL -* 调整日志优先级:ConsoleLog优先于JDKLog,Log4j2优先于Log4j -* db模块的SqlRunner中可自定义Wrapper -* ExcelReader增加read重载方法(pr#4@Gitee) -* Convert.convert增加Class的重载,解决返回值歧义(感谢@t-io) -* Http中使用byte[]存储body,减少转换 -* ExcelReader增加getWorkbook、getSheet方法 -* 新增StrBuilder -* 新增JschUtil -* 新增UnicodeUtil -* db模块的BeanListHandler和BeanHandler支持Map、Collection、Array等类型 -* NumberUtil加减乘支持多个值,解决float和double混合运算导致的坑 - -### Bug修复 -* 修复ExcelReader空行导致空指针问题(pr#4@Gitee) -* 修复BeanUtil.getProperty不能获取父类属性的问题 -* 修复BeanDesc类中boolean类型字段名为isXXX的情况无法注入问题 -* 解决类扫描后加载类中引用依赖导致的报错(感谢@【帝都】宁静) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5256e7d5b..3c2aa0da2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ ## 5.2.6 (2020-03-26) ### 新特性 +* 【extra 】 JschUtil增加execByShell方法(issue#I1CYES@Gitee) + ### Bug修复 * 【extra 】 修复SpringUtil使用devtools重启报错问题 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 55608f890..2a9baaba2 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 @@ -6,7 +6,13 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.net.LocalPortGenerater; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; -import com.jcraft.jsch.*; +import com.jcraft.jsch.Channel; +import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.ChannelSftp; +import com.jcraft.jsch.ChannelShell; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; import java.io.IOException; import java.io.InputStream; @@ -172,7 +178,7 @@ public class JschUtil { sshUser = "root"; } - if(null == jsch){ + if (null == jsch) { jsch = new JSch(); } @@ -343,7 +349,10 @@ public class JschUtil { } /** - * 执行Shell命令 + * 执行Shell命令(使用EXEC方式) + *

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

    * * @param session Session会话 * @param cmd 命令 @@ -375,6 +384,46 @@ public class JschUtil { } } + /** + * 执行Shell命令 + *

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

    + * + * @param session Session会话 + * @param cmd 命令 + * @param charset 发送和读取内容的编码 + * @return {@link ChannelExec} + * @since 5.2.5 + */ + public static String execByShell(Session session, String cmd, Charset charset) { + final ChannelShell shell = openShell(session); + // 开始连接 + shell.setPty(true); + OutputStream out = null; + InputStream in = null; + final StringBuilder result = StrUtil.builder(); + try { + out = shell.getOutputStream(); + in = shell.getInputStream(); + + out.write(StrUtil.bytes(cmd, charset)); + out.flush(); + + while (in.available() > 0) { + result.append(IoUtil.read(in, charset)); + } + } catch (IOException e) { + throw new IORuntimeException(e); + } finally { + IoUtil.close(out); + IoUtil.close(in); + close(shell); + } + return result.toString(); + } + /** * 关闭SSH连接会话 * From bcd773b85a5962ab1e5e05e6f469daac64164e65 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 29 Mar 2020 10:07:59 +0800 Subject: [PATCH 006/157] add method --- .../java/cn/hutool/core/lang/Console.java | 9 +- .../java/cn/hutool/core/util/StrUtil.java | 1025 ++++++++--------- .../java/cn/hutool/core/util/StrUtilTest.java | 4 +- 3 files changed, 514 insertions(+), 524 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Console.java b/hutool-core/src/main/java/cn/hutool/core/lang/Console.java index 0f1dfd8b5..a50f4edb2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Console.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Console.java @@ -1,13 +1,12 @@ package cn.hutool.core.lang; -import static java.lang.System.out; - -import java.util.Scanner; - import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; +import java.util.Scanner; + import static java.lang.System.err; +import static java.lang.System.out; /** * 命令行(控制台)工具方法类
    @@ -189,7 +188,7 @@ public class Console { * @since 5.2.5 */ public static String where() { - StackTraceElement stackTraceElement = new Throwable().getStackTrace()[1]; + final StackTraceElement stackTraceElement = new Throwable().getStackTrace()[1]; final String className = stackTraceElement.getClassName(); final String methodName = stackTraceElement.getMethodName(); final String fileName = stackTraceElement.getFileName(); diff --git a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java index dff3124b4..afe1ca6cf 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java @@ -1,14 +1,5 @@ package cn.hutool.core.util; -import java.io.StringReader; -import java.io.StringWriter; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.text.MessageFormat; -import java.util.*; -import java.util.Map.Entry; -import java.util.regex.Pattern; - import cn.hutool.core.comparator.VersionComparator; import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Assert; @@ -19,11 +10,24 @@ import cn.hutool.core.text.StrFormatter; import cn.hutool.core.text.StrSpliter; import cn.hutool.core.text.TextSimilarity; +import java.io.StringReader; +import java.io.StringWriter; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.regex.Pattern; + /** * 字符串工具类 - * - * @author xiaoleilu * + * @author xiaoleilu */ public class StrUtil { @@ -74,12 +78,13 @@ public class StrUtil { public static final String EMPTY_JSON = "{}"; // ------------------------------------------------------------------------ Blank + /** * 字符串是否为空白 空白的定义如下:
    * 1、为null
    * 2、为不可见字符(如空格)
    * 3、""
    - * + * * @param str 被检测的字符串 * @return 是否为空 */ @@ -105,7 +110,7 @@ public class StrUtil { * 1、为null
    * 2、为不可见字符(如空格)
    * 3、""
    - * + * * @param obj 对象 * @return 如果为字符串是否为空串 * @since 3.3.0 @@ -124,7 +129,7 @@ public class StrUtil { * 1、不为null
    * 2、不为不可见字符(如空格)
    * 3、不为""
    - * + * * @param str 被检测的字符串 * @return 是否为非空 */ @@ -134,7 +139,7 @@ public class StrUtil { /** * 是否包含空字符串 - * + * * @param strs 字符串列表 * @return 是否包含空字符串 */ @@ -153,7 +158,7 @@ public class StrUtil { /** * 给定所有字符串是否为空白 - * + * * @param strs 字符串 * @return 所有字符串是否为空白 */ @@ -171,11 +176,12 @@ public class StrUtil { } // ------------------------------------------------------------------------ Empty + /** * 字符串是否为空,空的定义如下:
    * 1、为null
    * 2、为""
    - * + * * @param str 被检测的字符串 * @return 是否为空 */ @@ -187,7 +193,7 @@ public class StrUtil { * 如果对象是字符串是否为空串空的定义如下:
    * 1、为null
    * 2、为""
    - * + * * @param obj 对象 * @return 如果为字符串是否为空串 * @since 3.3.0 @@ -205,21 +211,21 @@ public class StrUtil { * 字符串是否为非空白 空白的定义如下:
    * 1、不为null
    * 2、不为""
    - * + * * @param str 被检测的字符串 * @return 是否为非空 */ public static boolean isNotEmpty(CharSequence str) { return false == isEmpty(str); } - + /** * 当给定字符串为null时,转换为Empty - * + * * @param str 被检查的字符串 * @return 原字符串或者空串 - * @since 4.6.3 * @see #nullToEmpty(CharSequence) + * @since 4.6.3 */ public static String emptyIfNull(CharSequence str) { return nullToEmpty(str); @@ -227,7 +233,7 @@ public class StrUtil { /** * 当给定字符串为null时,转换为Empty - * + * * @param str 被转换的字符串 * @return 转换后的字符串 */ @@ -237,17 +243,16 @@ public class StrUtil { /** * 如果字符串是null,则返回指定默认字符串,否则返回字符串本身。 - * + * *
     	 * nullToDefault(null, "default")  = "default"
     	 * nullToDefault("", "default")    = ""
     	 * nullToDefault("  ", "default")  = "  "
     	 * nullToDefault("bat", "default") = "bat"
     	 * 
    - * - * @param str 要转换的字符串 + * + * @param str 要转换的字符串 * @param defaultStr 默认字符串 - * * @return 字符串本身或指定的默认字符串 */ public static String nullToDefault(CharSequence str, String defaultStr) { @@ -256,17 +261,16 @@ public class StrUtil { /** * 如果字符串是null或者"",则返回指定默认字符串,否则返回字符串本身。 - * + * *
     	 * emptyToDefault(null, "default")  = "default"
     	 * emptyToDefault("", "default")    = "default"
     	 * emptyToDefault("  ", "default")  = "  "
     	 * emptyToDefault("bat", "default") = "bat"
     	 * 
    - * - * @param str 要转换的字符串 + * + * @param str 要转换的字符串 * @param defaultStr 默认字符串 - * * @return 字符串本身或指定的默认字符串 * @since 4.1.0 */ @@ -276,17 +280,16 @@ public class StrUtil { /** * 如果字符串是null或者""或者空白,则返回指定默认字符串,否则返回字符串本身。 - * + * *
     	 * emptyToDefault(null, "default")  = "default"
     	 * emptyToDefault("", "default")    = "default"
     	 * emptyToDefault("  ", "default")  = "default"
     	 * emptyToDefault("bat", "default") = "bat"
     	 * 
    - * - * @param str 要转换的字符串 + * + * @param str 要转换的字符串 * @param defaultStr 默认字符串 - * * @return 字符串本身或指定的默认字符串 * @since 4.1.0 */ @@ -296,7 +299,7 @@ public class StrUtil { /** * 当给定字符串为空字符串时,转换为null - * + * * @param str 被转换的字符串 * @return 转换后的字符串 */ @@ -306,7 +309,7 @@ public class StrUtil { /** * 是否包含空字符串 - * + * * @param strs 字符串列表 * @return 是否包含空字符串 */ @@ -325,7 +328,7 @@ public class StrUtil { /** * 是否全部为空字符串 - * + * * @param strs 字符串列表 * @return 是否全部为空字符串 */ @@ -344,7 +347,7 @@ public class StrUtil { /** * 检查字符串是否为null、“null”、“undefined” - * + * * @param str 被检查的字符串 * @return 是否为null、“null”、“undefined” * @since 4.0.10 @@ -358,7 +361,7 @@ public class StrUtil { /** * 检查字符串是否为null、“”、“null”、“undefined” - * + * * @param str 被检查的字符串 * @return 是否为null、“”、“null”、“undefined” * @since 4.0.10 @@ -372,7 +375,7 @@ public class StrUtil { /** * 检查字符串是否为null、空白串、“null”、“undefined” - * + * * @param str 被检查的字符串 * @return 是否为null、空白串、“null”、“undefined” * @since 4.0.10 @@ -386,7 +389,7 @@ public class StrUtil { /** * 是否为“null”、“undefined”,不做空指针检查 - * + * * @param str 字符串 * @return 是否为“null”、“undefined” */ @@ -396,12 +399,13 @@ public class StrUtil { } // ------------------------------------------------------------------------ Trim + /** * 除去字符串头尾部的空白,如果字符串是null,依然返回null。 - * + * *

    * 注意,和String.trim不同,此方法使用NumberUtil.isBlankChar 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 - * + * *

     	 * trim(null)          = null
     	 * trim("")            = ""
    @@ -409,9 +413,8 @@ public class StrUtil {
     	 * trim("abc")         = "abc"
     	 * trim("    abc    ") = "abc"
     	 * 
    - * + * * @param str 要处理的字符串 - * * @return 除去头尾空白的字符串,如果原字串为null,则返回null */ public static String trim(CharSequence str) { @@ -420,7 +423,7 @@ public class StrUtil { /** * 给定字符串数组全部做去首尾空格 - * + * * @param strs 字符串数组 */ public static void trim(String[] strs) { @@ -477,10 +480,10 @@ public class StrUtil { /** * 除去字符串头部的空白,如果字符串是null,则返回null。 - * + * *

    * 注意,和String.trim不同,此方法使用CharUtil.isBlankChar 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 - * + * *

     	 * trimStart(null)         = null
     	 * trimStart("")           = ""
    @@ -489,9 +492,8 @@ public class StrUtil {
     	 * trimStart("abc  ")      = "abc  "
     	 * trimStart(" abc ")      = "abc "
     	 * 
    - * + * * @param str 要处理的字符串 - * * @return 除去空白的字符串,如果原字串为null或结果字符串为"",则返回 null */ public static String trimStart(CharSequence str) { @@ -500,10 +502,10 @@ public class StrUtil { /** * 除去字符串尾部的空白,如果字符串是null,则返回null。 - * + * *

    * 注意,和String.trim不同,此方法使用CharUtil.isBlankChar 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。 - * + * *

     	 * trimEnd(null)       = null
     	 * trimEnd("")         = ""
    @@ -512,9 +514,8 @@ public class StrUtil {
     	 * trimEnd("abc  ")    = "abc"
     	 * trimEnd(" abc ")    = " abc"
     	 * 
    - * + * * @param str 要处理的字符串 - * * @return 除去空白的字符串,如果原字串为null或结果字符串为"",则返回 null */ public static String trimEnd(CharSequence str) { @@ -523,10 +524,9 @@ public class StrUtil { /** * 除去字符串头尾部的空白符,如果字符串是null,依然返回null。 - * - * @param str 要处理的字符串 + * + * @param str 要处理的字符串 * @param mode -1表示trimStart,0表示trim全部, 1表示trimEnd - * * @return 除去指定字符后的的字符串,如果原字串为null,则返回null */ public static String trim(CharSequence str, int mode) { @@ -561,9 +561,9 @@ public class StrUtil { /** * 字符串是否以给定字符开始 - * + * * @param str 字符串 - * @param c 字符 + * @param c 字符 * @return 是否开始 */ public static boolean startWith(CharSequence str, char c) { @@ -573,9 +573,9 @@ public class StrUtil { /** * 是否以指定字符串开头
    * 如果给定的字符串和开头字符串都为null则返回true,否则任意一个值为null返回false - * - * @param str 被监测字符串 - * @param prefix 开头字符串 + * + * @param str 被监测字符串 + * @param prefix 开头字符串 * @param isIgnoreCase 是否忽略大小写 * @return 是否以指定字符串开头 */ @@ -593,8 +593,8 @@ public class StrUtil { /** * 是否以指定字符串开头 - * - * @param str 被监测字符串 + * + * @param str 被监测字符串 * @param prefix 开头字符串 * @return 是否以指定字符串开头 */ @@ -604,8 +604,8 @@ public class StrUtil { /** * 是否以指定字符串开头,忽略大小写 - * - * @param str 被监测字符串 + * + * @param str 被监测字符串 * @param prefix 开头字符串 * @return 是否以指定字符串开头 */ @@ -616,8 +616,8 @@ public class StrUtil { /** * 给定字符串是否以任何一个字符串开始
    * 给定字符串和数组为空都返回false - * - * @param str 给定字符串 + * + * @param str 给定字符串 * @param prefixes 需要检测的开始字符串 * @return 给定字符串是否以任何一个字符串开始 * @since 3.0.6 @@ -637,9 +637,9 @@ public class StrUtil { /** * 字符串是否以给定字符结尾 - * + * * @param str 字符串 - * @param c 字符 + * @param c 字符 * @return 是否结尾 */ public static boolean endWith(CharSequence str, char c) { @@ -649,9 +649,9 @@ public class StrUtil { /** * 是否以指定字符串结尾
    * 如果给定的字符串和开头字符串都为null则返回true,否则任意一个值为null返回false - * - * @param str 被监测字符串 - * @param suffix 结尾字符串 + * + * @param str 被监测字符串 + * @param suffix 结尾字符串 * @param isIgnoreCase 是否忽略大小写 * @return 是否以指定字符串结尾 */ @@ -669,8 +669,8 @@ public class StrUtil { /** * 是否以指定字符串结尾 - * - * @param str 被监测字符串 + * + * @param str 被监测字符串 * @param suffix 结尾字符串 * @return 是否以指定字符串结尾 */ @@ -680,8 +680,8 @@ public class StrUtil { /** * 是否以指定字符串结尾,忽略大小写 - * - * @param str 被监测字符串 + * + * @param str 被监测字符串 * @param suffix 结尾字符串 * @return 是否以指定字符串结尾 */ @@ -692,8 +692,8 @@ public class StrUtil { /** * 给定字符串是否以任何一个字符串结尾
    * 给定字符串和数组为空都返回false - * - * @param str 给定字符串 + * + * @param str 给定字符串 * @param suffixes 需要检测的结尾字符串 * @return 给定字符串是否以任何一个字符串结尾 * @since 3.0.6 @@ -713,8 +713,8 @@ public class StrUtil { /** * 指定字符是否在字符串中出现过 - * - * @param str 字符串 + * + * @param str 字符串 * @param searchChar 被查找的字符 * @return 是否包含 * @since 3.1.2 @@ -726,13 +726,13 @@ public class StrUtil { /** * 指定字符串是否在字符串中出现过 * - * @param str 字符串 + * @param str 字符串 * @param searchStr 被查找的字符串 * @return 是否包含 * @since 5.1.1 */ public static boolean contains(CharSequence str, CharSequence searchStr) { - if(null == str || null == searchStr){ + if (null == str || null == searchStr) { return false; } return str.toString().contains(searchStr); @@ -740,8 +740,8 @@ public class StrUtil { /** * 查找指定字符串是否包含指定字符串列表中的任意一个字符串 - * - * @param str 指定字符串 + * + * @param str 指定字符串 * @param testStrs 需要检查的字符串数组 * @return 是否包含任意一个字符串 * @since 3.2.0 @@ -752,8 +752,8 @@ public class StrUtil { /** * 查找指定字符串是否包含指定字符列表中的任意一个字符 - * - * @param str 指定字符串 + * + * @param str 指定字符串 * @param testChars 需要检查的字符数组 * @return 是否包含任意一个字符 * @since 4.1.11 @@ -772,8 +772,8 @@ public class StrUtil { /** * 检查指定字符串中是否只包含给定的字符 - * - * @param str 字符串 + * + * @param str 字符串 * @param testChars 检查的字符 * @return 字符串含有非检查的字符,返回false * @since 4.4.1 @@ -793,7 +793,7 @@ public class StrUtil { /** * 给定字符串是否包含空白符(空白符包括空格、制表符、全角空格和不间断空格)
    * 如果给定字符串为null或者"",则返回false - * + * * @param str 字符串 * @return 是否包含空白符 * @since 4.0.8 @@ -817,8 +817,8 @@ public class StrUtil { /** * 查找指定字符串是否包含指定字符串列表中的任意一个字符串,如果包含返回找到的第一个字符串 - * - * @param str 指定字符串 + * + * @param str 指定字符串 * @param testStrs 需要检查的字符串数组 * @return 被包含的第一个字符串 * @since 3.2.0 @@ -837,8 +837,8 @@ public class StrUtil { /** * 是否包含特定字符,忽略大小写,如果给定两个参数都为null,返回true - * - * @param str 被检测字符串 + * + * @param str 被检测字符串 * @param testStr 被测试是否包含的字符串 * @return 是否包含 */ @@ -853,8 +853,8 @@ public class StrUtil { /** * 查找指定字符串是否包含指定字符串列表中的任意一个字符串
    * 忽略大小写 - * - * @param str 指定字符串 + * + * @param str 指定字符串 * @param testStrs 需要检查的字符串数组 * @return 是否包含任意一个字符串 * @since 3.2.0 @@ -866,8 +866,8 @@ public class StrUtil { /** * 查找指定字符串是否包含指定字符串列表中的任意一个字符串,如果包含返回找到的第一个字符串
    * 忽略大小写 - * - * @param str 指定字符串 + * + * @param str 指定字符串 * @param testStrs 需要检查的字符串数组 * @return 被包含的第一个字符串 * @since 3.2.0 @@ -887,13 +887,13 @@ public class StrUtil { /** * 获得set或get或is方法对应的标准属性名
    * 例如:setName 返回 name - * + * *
     	 * getName =》name
     	 * setName =》name
     	 * isName  =》name
     	 * 
    - * + * * @param getOrSetMethodName Get或Set方法名 * @return 如果是set或get方法名,返回field, 否则null */ @@ -901,7 +901,7 @@ public class StrUtil { final String getOrSetMethodNameStr = getOrSetMethodName.toString(); if (getOrSetMethodNameStr.startsWith("get") || getOrSetMethodNameStr.startsWith("set")) { return removePreAndLowerFirst(getOrSetMethodName, 3); - } else if(getOrSetMethodNameStr.startsWith("is")) { + } else if (getOrSetMethodNameStr.startsWith("is")) { return removePreAndLowerFirst(getOrSetMethodName, 2); } return null; @@ -910,7 +910,7 @@ public class StrUtil { /** * 生成set方法名
    * 例如:name 返回 setName - * + * * @param fieldName 属性名 * @return setXxx */ @@ -920,7 +920,7 @@ public class StrUtil { /** * 生成get方法名 - * + * * @param fieldName 属性名 * @return getXxx */ @@ -931,8 +931,8 @@ public class StrUtil { /** * 移除字符串中所有给定字符串
    * 例:removeAll("aa-bb-cc-dd", "-") =》 aabbccdd - * - * @param str 字符串 + * + * @param str 字符串 * @param strToRemove 被移除的字符串 * @return 移除后的字符串 */ @@ -945,8 +945,8 @@ public class StrUtil { /** * 去除字符串中指定的多个字符,如有多个则全部去除 - * - * @param str 字符串 + * + * @param str 字符串 * @param chars 字符列表 * @return 去除后的字符 * @since 4.2.2 @@ -972,12 +972,12 @@ public class StrUtil { /** * 去除所有换行符,包括: - * + * *
     	 * 1. \r
     	 * 1. \n
     	 * 
    - * + * * @param str 字符串 * @return 处理后的字符串 * @since 4.2.2 @@ -989,8 +989,8 @@ public class StrUtil { /** * 去掉首部指定长度的字符串并将剩余字符串首字母小写
    * 例如:str=setName, preLength=3 =》 return name - * - * @param str 被处理的字符串 + * + * @param str 被处理的字符串 * @param preLength 去掉的长度 * @return 处理后的字符串,不符合规范返回null */ @@ -1012,8 +1012,8 @@ public class StrUtil { /** * 去掉首部指定长度的字符串并将剩余字符串首字母小写
    * 例如:str=setName, prefix=set =》 return name - * - * @param str 被处理的字符串 + * + * @param str 被处理的字符串 * @param prefix 前缀 * @return 处理后的字符串,不符合规范返回null */ @@ -1023,8 +1023,8 @@ public class StrUtil { /** * 原字符串首字母大写并在其首部添加指定字符串 例如:str=name, preString=get =》 return getName - * - * @param str 被处理的字符串 + * + * @param str 被处理的字符串 * @param preString 添加的首部 * @return 处理后的字符串 */ @@ -1038,7 +1038,7 @@ public class StrUtil { /** * 大写首字母
    * 例如:str = name, return Name - * + * * @param str 字符串 * @return 字符串 */ @@ -1058,7 +1058,7 @@ public class StrUtil { /** * 小写首字母
    * 例如:str = Name, return name - * + * * @param str 字符串 * @return 字符串 */ @@ -1077,8 +1077,8 @@ public class StrUtil { /** * 去掉指定前缀 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefix 前缀 * @return 切掉后的字符串,若前缀不是 preffix, 返回原字符串 */ @@ -1096,8 +1096,8 @@ public class StrUtil { /** * 忽略大小写去掉指定前缀 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefix 前缀 * @return 切掉后的字符串,若前缀不是 prefix, 返回原字符串 */ @@ -1115,8 +1115,8 @@ public class StrUtil { /** * 去掉指定后缀 - * - * @param str 字符串 + * + * @param str 字符串 * @param suffix 后缀 * @return 切掉后的字符串,若后缀不是 suffix, 返回原字符串 */ @@ -1134,8 +1134,8 @@ public class StrUtil { /** * 去掉指定后缀,并小写首字母 - * - * @param str 字符串 + * + * @param str 字符串 * @param suffix 后缀 * @return 切掉后的字符串,若后缀不是 suffix, 返回原字符串 */ @@ -1145,8 +1145,8 @@ public class StrUtil { /** * 忽略大小写去掉指定后缀 - * - * @param str 字符串 + * + * @param str 字符串 * @param suffix 后缀 * @return 切掉后的字符串,若后缀不是 suffix, 返回原字符串 */ @@ -1164,8 +1164,8 @@ public class StrUtil { /** * 去除两边的指定字符串 - * - * @param str 被处理的字符串 + * + * @param str 被处理的字符串 * @param prefixOrSuffix 前缀或后缀 * @return 处理后的字符串 * @since 3.1.2 @@ -1180,8 +1180,8 @@ public class StrUtil { /** * 去除两边的指定字符串 - * - * @param str 被处理的字符串 + * + * @param str 被处理的字符串 * @param prefix 前缀 * @param suffix 后缀 * @return 处理后的字符串 @@ -1208,8 +1208,8 @@ public class StrUtil { /** * 去除两边的指定字符串,忽略大小写 - * - * @param str 被处理的字符串 + * + * @param str 被处理的字符串 * @param prefixOrSuffix 前缀或后缀 * @return 处理后的字符串 * @since 3.1.2 @@ -1220,8 +1220,8 @@ public class StrUtil { /** * 去除两边的指定字符串,忽略大小写 - * - * @param str 被处理的字符串 + * + * @param str 被处理的字符串 * @param prefix 前缀 * @param suffix 后缀 * @return 处理后的字符串 @@ -1246,8 +1246,8 @@ public class StrUtil { /** * 如果给定字符串不是以prefix开头的,在开头补充 prefix - * - * @param str 字符串 + * + * @param str 字符串 * @param prefix 前缀 * @return 补充后的字符串 */ @@ -1266,8 +1266,8 @@ public class StrUtil { /** * 如果给定字符串不是以suffix结尾的,在尾部补充 suffix - * - * @param str 字符串 + * + * @param str 字符串 * @param suffix 后缀 * @return 补充后的字符串 */ @@ -1286,7 +1286,7 @@ public class StrUtil { /** * 清理空白字符 - * + * * @param str 被清理的字符串 * @return 清理后的字符串 */ @@ -1308,10 +1308,11 @@ public class StrUtil { } // ------------------------------------------------------------------------------ Split + /** * 切分字符串 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符 * @return 切分后的数组 */ @@ -1321,8 +1322,8 @@ public class StrUtil { /** * 切分字符串为long数组 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符 * @return 切分后long数组 * @since 4.0.6 @@ -1333,8 +1334,8 @@ public class StrUtil { /** * 切分字符串为long数组 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符串 * @return 切分后long数组 * @since 4.0.6 @@ -1345,8 +1346,8 @@ public class StrUtil { /** * 切分字符串为int数组 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符 * @return 切分后long数组 * @since 4.0.6 @@ -1357,8 +1358,8 @@ public class StrUtil { /** * 切分字符串为int数组 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符串 * @return 切分后long数组 * @since 4.0.6 @@ -1371,8 +1372,8 @@ public class StrUtil { * 切分字符串
    * a#b#c =》 [a,b,c]
    * a##b#c =》 [a,"",b,c] - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符 * @return 切分后的集合 */ @@ -1382,25 +1383,25 @@ public class StrUtil { /** * 切分字符串 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符 - * @param limit 限制分片数 + * @param limit 限制分片数 * @return 切分后的数组 */ public static String[] splitToArray(CharSequence str, char separator, int limit) { if (null == str) { - return new String[] {}; + return new String[]{}; } return StrSpliter.splitToArray(str.toString(), separator, limit, false, false); } /** * 切分字符串,不去除切分后每个元素两边的空白符,不去除空白项 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符 - * @param limit 限制分片数,-1不限制 + * @param limit 限制分片数,-1不限制 * @return 切分后的集合 */ public static List split(CharSequence str, char separator, int limit) { @@ -1409,8 +1410,8 @@ public class StrUtil { /** * 切分字符串,去除切分后每个元素两边的空白符,去除空白项 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符 * @return 切分后的集合 * @since 3.1.2 @@ -1421,8 +1422,8 @@ public class StrUtil { /** * 切分字符串,去除切分后每个元素两边的空白符,去除空白项 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符 * @return 切分后的集合 * @since 3.2.0 @@ -1433,10 +1434,10 @@ public class StrUtil { /** * 切分字符串,去除切分后每个元素两边的空白符,去除空白项 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符 - * @param limit 限制分片数,-1不限制 + * @param limit 限制分片数,-1不限制 * @return 切分后的集合 * @since 3.1.0 */ @@ -1446,10 +1447,10 @@ public class StrUtil { /** * 切分字符串,去除切分后每个元素两边的空白符,去除空白项 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符字符 - * @param limit 限制分片数,-1不限制 + * @param limit 限制分片数,-1不限制 * @return 切分后的集合 * @since 3.2.0 */ @@ -1459,10 +1460,10 @@ public class StrUtil { /** * 切分字符串,不限制分片数量 - * - * @param str 被切分的字符串 - * @param separator 分隔符字符 - * @param isTrim 是否去除切分字符串后每个元素两边的空格 + * + * @param str 被切分的字符串 + * @param separator 分隔符字符 + * @param isTrim 是否去除切分字符串后每个元素两边的空格 * @param ignoreEmpty 是否忽略空串 * @return 切分后的集合 * @since 3.0.8 @@ -1473,11 +1474,11 @@ public class StrUtil { /** * 切分字符串 - * - * @param str 被切分的字符串 - * @param separator 分隔符字符 - * @param limit 限制分片数,-1不限制 - * @param isTrim 是否去除切分字符串后每个元素两边的空格 + * + * @param str 被切分的字符串 + * @param separator 分隔符字符 + * @param limit 限制分片数,-1不限制 + * @param isTrim 是否去除切分字符串后每个元素两边的空格 * @param ignoreEmpty 是否忽略空串 * @return 切分后的集合 * @since 3.0.8 @@ -1491,11 +1492,11 @@ public class StrUtil { /** * 切分字符串 - * - * @param str 被切分的字符串 - * @param separator 分隔符字符 - * @param limit 限制分片数,-1不限制 - * @param isTrim 是否去除切分字符串后每个元素两边的空格 + * + * @param str 被切分的字符串 + * @param separator 分隔符字符 + * @param limit 限制分片数,-1不限制 + * @param isTrim 是否去除切分字符串后每个元素两边的空格 * @param ignoreEmpty 是否忽略空串 * @return 切分后的集合 * @since 3.2.0 @@ -1510,14 +1511,14 @@ public class StrUtil { /** * 切分字符串 - * - * @param str 被切分的字符串 + * + * @param str 被切分的字符串 * @param separator 分隔符 * @return 字符串 */ public static String[] split(CharSequence str, CharSequence separator) { if (str == null) { - return new String[] {}; + return new String[]{}; } final String separatorStr = (null == separator) ? null : separator.toString(); @@ -1526,7 +1527,7 @@ public class StrUtil { /** * 根据给定长度,将给定字符串截取为多个部分 - * + * * @param str 字符串 * @param len 每一个小节的长度 * @return 截取后的字符串数组 @@ -1534,7 +1535,7 @@ public class StrUtil { */ public static String[] split(CharSequence str, int len) { if (null == str) { - return new String[] {}; + return new String[]{}; } return StrSpliter.splitByLength(str.toString(), len); } @@ -1547,10 +1548,10 @@ public class StrUtil { * 如果经过修正的index中from大于to,则互换from和to example:
    * abcdefgh 2 3 =》 c
    * abcdefgh 2 -3 =》 cde
    - * - * @param str String + * + * @param str String * @param fromIndex 开始的index(包括) - * @param toIndex 结束的index(不包括) + * @param toIndex 结束的index(不包括) * @return 字串 */ public static String sub(CharSequence str, int fromIndex, int toIndex) { @@ -1593,9 +1594,9 @@ public class StrUtil { /** * 通过CodePoint截取字符串,可以截断Emoji * - * @param str String + * @param str String * @param fromIndex 开始的index(包括) - * @param toIndex 结束的index(不包括) + * @param toIndex 结束的index(不包括) * @return 字串 */ public static String subByCodePoint(CharSequence str, int fromIndex, int toIndex) { @@ -1620,8 +1621,8 @@ public class StrUtil { /** * 截取部分字符串,这里一个汉字的长度认为是2 * - * @param str 字符串 - * @param len 切割的位置 + * @param str 字符串 + * @param len 切割的位置 * @param suffix 切割后加上后缀 * @return 切割后的字符串 * @since 3.1.1 @@ -1651,7 +1652,7 @@ public class StrUtil { /** * 限制字符串长度,如果超过指定长度,截取指定长度并在末尾加"..." - * + * * @param string 字符串 * @param length 最大长度 * @return 切割后的剩余的前半部分字符串+"..." @@ -1670,8 +1671,8 @@ public class StrUtil { /** * 切割指定位置之前部分的字符串 - * - * @param string 字符串 + * + * @param string 字符串 * @param toIndex 切割到的位置(不包括) * @return 切割后的剩余的前半部分字符串 */ @@ -1681,8 +1682,8 @@ public class StrUtil { /** * 切割指定位置之后部分的字符串 - * - * @param string 字符串 + * + * @param string 字符串 * @param fromIndex 切割开始的位置(包括) * @return 切割后后剩余的后半部分字符串 */ @@ -1695,7 +1696,7 @@ public class StrUtil { /** * 切割指定长度的后部分的字符串 - * + * *
     	 * StrUtil.subSufByLength("abcde", 3)      =    "cde"
     	 * StrUtil.subSufByLength("abcde", 0)      =    ""
    @@ -1705,7 +1706,7 @@ public class StrUtil {
     	 * StrUtil.subSufByLength("abcde", 10)     =    "abcde"
     	 * StrUtil.subSufByLength(null, 3)               =    null
     	 * 
    - * + * * @param string 字符串 * @param length 切割长度 * @return 切割后后剩余的后半部分字符串 @@ -1724,10 +1725,10 @@ public class StrUtil { /** * 截取字符串,从指定位置开始,截取指定长度的字符串
    * author weibaohui - * - * @param input 原始字符串 + * + * @param input 原始字符串 * @param fromIndex 开始的index,包括 - * @param length 要截取的长度 + * @param length 要截取的长度 * @return 截取后的字符串 */ public static String subWithLength(String input, int fromIndex, int length) { @@ -1738,7 +1739,7 @@ public class StrUtil { * 截取分隔字符串之前的字符串,不包括分隔字符串
    * 如果给定的字符串为空串(null或"")或者分隔字符串为null,返回原字符串
    * 如果分隔字符串为空串"",则返回空串,如果分隔字符串未找到,返回原字符串,举例如下: - * + * *
     	 * StrUtil.subBefore(null, *)      = null
     	 * StrUtil.subBefore("", *)        = ""
    @@ -1749,9 +1750,9 @@ public class StrUtil {
     	 * StrUtil.subBefore("abc", "")    = ""
     	 * StrUtil.subBefore("abc", null)  = "abc"
     	 * 
    - * - * @param string 被查找的字符串 - * @param separator 分隔字符串(不包括) + * + * @param string 被查找的字符串 + * @param separator 分隔字符串(不包括) * @param isLastSeparator 是否查找最后一个分隔字符串(多次出现分隔字符串时选取最后一个),true为选取最后一个 * @return 切割后的字符串 * @since 3.1.1 @@ -1780,7 +1781,7 @@ public class StrUtil { * 截取分隔字符串之前的字符串,不包括分隔字符串
    * 如果给定的字符串为空串(null或"")或者分隔字符串为null,返回原字符串
    * 如果分隔字符串未找到,返回原字符串,举例如下: - * + * *
     	 * StrUtil.subBefore(null, *)      = null
     	 * StrUtil.subBefore("", *)        = ""
    @@ -1789,9 +1790,9 @@ public class StrUtil {
     	 * StrUtil.subBefore("abc", 'c')   = "ab"
     	 * StrUtil.subBefore("abc", 'd')   = "abc"
     	 * 
    - * - * @param string 被查找的字符串 - * @param separator 分隔字符串(不包括) + * + * @param string 被查找的字符串 + * @param separator 分隔字符串(不包括) * @param isLastSeparator 是否查找最后一个分隔字符串(多次出现分隔字符串时选取最后一个),true为选取最后一个 * @return 切割后的字符串 * @since 4.1.15 @@ -1828,8 +1829,8 @@ public class StrUtil { * StrUtil.subAfter("abc", "") = "abc" * * - * @param string 被查找的字符串 - * @param separator 分隔字符串(不包括) + * @param string 被查找的字符串 + * @param separator 分隔字符串(不包括) * @param isLastSeparator 是否查找最后一个分隔字符串(多次出现分隔字符串时选取最后一个),true为选取最后一个 * @return 切割后的字符串 * @since 3.1.1 @@ -1864,8 +1865,8 @@ public class StrUtil { * StrUtil.subAfter("abc", 'd') = "" * * - * @param string 被查找的字符串 - * @param separator 分隔字符串(不包括) + * @param string 被查找的字符串 + * @param separator 分隔字符串(不包括) * @param isLastSeparator 是否查找最后一个分隔字符串(多次出现分隔字符串时选取最后一个),true为选取最后一个 * @return 切割后的字符串 * @since 4.1.15 @@ -1884,9 +1885,9 @@ public class StrUtil { /** * 截取指定字符串中间部分,不包括标识字符串
    - * + *

    * 栗子: - * + * *

     	 * StrUtil.subBetween("wx[b]yz", "[", "]") = "b"
     	 * StrUtil.subBetween(null, *, *)          = null
    @@ -1899,10 +1900,10 @@ public class StrUtil {
     	 * StrUtil.subBetween("yabcz", "y", "z")   = "abc"
     	 * StrUtil.subBetween("yabczyabcz", "y", "z")   = "abc"
     	 * 
    - * - * @param str 被切割的字符串 + * + * @param str 被切割的字符串 * @param before 截取开始的字符串标识 - * @param after 截取到的字符串标识 + * @param after 截取到的字符串标识 * @return 截取后的字符串 * @since 3.1.1 */ @@ -1927,9 +1928,9 @@ public class StrUtil { /** * 截取指定字符串中间部分,不包括标识字符串
    - * + *

    * 栗子: - * + * *

     	 * StrUtil.subBetween(null, *)            = null
     	 * StrUtil.subBetween("", "")             = ""
    @@ -1938,8 +1939,8 @@ public class StrUtil {
     	 * StrUtil.subBetween("tagabctag", "")    = ""
     	 * StrUtil.subBetween("tagabctag", "tag") = "abc"
     	 * 
    - * - * @param str 被切割的字符串 + * + * @param str 被切割的字符串 * @param beforeAndAfter 截取开始和结束的字符串标识 * @return 截取后的字符串 * @since 3.1.1 @@ -1950,7 +1951,7 @@ public class StrUtil { /** * 截取指定字符串多段中间部分,不包括标识字符串
    - * + *

    * 栗子: * *

    @@ -1967,46 +1968,44 @@ public class StrUtil {
     	 * StrUtil.subBetweenAll("[yabc[zy]abcz]", "[", "]");   = ["zy"]           重叠时只截取内部,
     	 * 
    * - * @param str 被切割的字符串 - * @param regexBefore 截取开始的字符串标识 - * @param regexAfter 截取到的字符串标识 + * @param str 被切割的字符串 + * @param prefix 截取开始的字符串标识 + * @param suffix 截取到的字符串标识 * @return 截取后的字符串 * @author dahuoyzs * @since 5.2.5 */ - public static String[] subBetweenAll(CharSequence str, CharSequence regexBefore, CharSequence regexAfter) { - if (str == null || regexBefore == null || regexAfter == null || str.length() < 1 || regexBefore.length() < 1 || regexAfter.length() < 1) { + public static String[] subBetweenAll(CharSequence str, CharSequence prefix, CharSequence suffix) { + if(hasEmpty(str, prefix, suffix)) { return new String[0]; } - final String before = regexBefore.toString().replace("\\", ""); - final String after = regexAfter.toString().replace("\\", ""); - final Integer beforeNumber = StrUtil.count(str, before); - final Integer afterNumber = StrUtil.count(str, after); - if (beforeNumber<1||afterNumber<1){ + final int prefixCount = count(str, prefix); + final int suffixCount = count(str, suffix); + if (prefixCount < 1 || suffixCount < 1) { return new String[0]; } LinkedList betweenList = new LinkedList<>(); - if (beforeNumber.compareTo(afterNumber) > 0) { - String[] fragments = str.toString().split(regexAfter.toString()); + if (prefixCount > suffixCount) { + String[] fragments = split(str, suffix); for (int i = 0; i < fragments.length - 1; i++) { String fragment = fragments[i]; - if (fragment.contains(before)) { - int beforeIndex = StrUtil.lastIndexOf(fragment, before, 0, false); + if (fragment.contains(prefix)) { + int beforeIndex = StrUtil.lastIndexOf(fragment, prefix, 0, false); String between = fragment.substring(beforeIndex); - if (between.length()>0) + if (between.length() > 0) betweenList.add(between); } } } else { - String[] fragments = str.toString().split(regexBefore.toString()); + String[] fragments = split(str, prefix); for (int i = 1; i < fragments.length; i++) { String fragment = fragments[i]; - if (fragment.contains(after)) { - int afterIndex = StrUtil.indexOf(fragment, after, 0, false); + if (fragment.contains(suffix)) { + int afterIndex = StrUtil.indexOf(fragment, suffix, 0, false); String between = fragment.substring(0, afterIndex); - if (between.length()>0) + if (between.length() > 0) betweenList.add(between); } } @@ -2017,8 +2016,8 @@ public class StrUtil { /** * 给定字符串是否被字符包围 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefix 前缀 * @param suffix 后缀 * @return 是否包围,空串不包围 @@ -2037,8 +2036,8 @@ public class StrUtil { /** * 给定字符串是否被字符包围 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefix 前缀 * @param suffix 后缀 * @return 是否包围,空串不包围 @@ -2056,8 +2055,8 @@ public class StrUtil { /** * 重复某个字符 - * - * @param c 被重复的字符 + * + * @param c 被重复的字符 * @param count 重复的数目,如果小于等于0则返回"" * @return 重复字符字符串 */ @@ -2075,8 +2074,8 @@ public class StrUtil { /** * 重复某个字符串 - * - * @param str 被重复的字符 + * + * @param str 被重复的字符 * @param count 重复的数目 * @return 重复字符字符串 */ @@ -2111,8 +2110,8 @@ public class StrUtil { /** * 重复某个字符串到指定长度 - * - * @param str 被重复的字符 + * + * @param str 被重复的字符 * @param padLen 指定长度 * @return 重复字符字符串 * @since 4.3.2 @@ -2141,15 +2140,15 @@ public class StrUtil { /** * 重复某个字符串并通过分界符连接 - * + * *
     	 * StrUtil.repeatAndJoin("?", 5, ",")   = "?,?,?,?,?"
     	 * StrUtil.repeatAndJoin("?", 0, ",")   = ""
     	 * StrUtil.repeatAndJoin("?", 5, null) = "?????"
     	 * 
    - * - * @param str 被重复的字符串 - * @param count 数量 + * + * @param str 被重复的字符串 + * @param count 数量 * @param conjunction 分界符 * @return 连接后的字符串 * @since 4.0.1 @@ -2173,7 +2172,7 @@ public class StrUtil { /** * 比较两个字符串(大小写敏感)。 - * + * *
     	 * equals(null, null)   = true
     	 * equals(null, "abc")  = false
    @@ -2181,10 +2180,9 @@ public class StrUtil {
     	 * equals("abc", "abc") = true
     	 * equals("abc", "ABC") = false
     	 * 
    - * + * * @param str1 要比较的字符串1 * @param str2 要比较的字符串2 - * * @return 如果两个字符串相同,或者都是null,则返回true */ public static boolean equals(CharSequence str1, CharSequence str2) { @@ -2193,7 +2191,7 @@ public class StrUtil { /** * 比较两个字符串(大小写不敏感)。 - * + * *
     	 * equalsIgnoreCase(null, null)   = true
     	 * equalsIgnoreCase(null, "abc")  = false
    @@ -2201,10 +2199,9 @@ public class StrUtil {
     	 * equalsIgnoreCase("abc", "abc") = true
     	 * equalsIgnoreCase("abc", "ABC") = true
     	 * 
    - * + * * @param str1 要比较的字符串1 * @param str2 要比较的字符串2 - * * @return 如果两个字符串相同,或者都是null,则返回true */ public static boolean equalsIgnoreCase(CharSequence str1, CharSequence str2) { @@ -2213,9 +2210,9 @@ public class StrUtil { /** * 比较两个字符串是否相等。 - * - * @param str1 要比较的字符串1 - * @param str2 要比较的字符串2 + * + * @param str1 要比较的字符串1 + * @param str2 要比较的字符串2 * @param ignoreCase 是否忽略大小写 * @return 如果两个字符串相同,或者都是null,则返回true * @since 3.2.0 @@ -2240,7 +2237,7 @@ public class StrUtil { /** * 给定字符串是否与提供的中任一字符串相同(忽略大小写),相同则返回{@code true},没有相同的返回{@code false}
    * 如果参与比对的字符串列表为空,返回{@code false} - * + * * @param str1 给定需要检查的字符串 * @param strs 需要参与比对的字符串列表 * @return 是否相同 @@ -2253,7 +2250,7 @@ public class StrUtil { /** * 给定字符串是否与提供的中任一字符串相同,相同则返回{@code true},没有相同的返回{@code false}
    * 如果参与比对的字符串列表为空,返回{@code false} - * + * * @param str1 给定需要检查的字符串 * @param strs 需要参与比对的字符串列表 * @return 是否相同 @@ -2266,10 +2263,10 @@ public class StrUtil { /** * 给定字符串是否与提供的中任一字符串相同,相同则返回{@code true},没有相同的返回{@code false}
    * 如果参与比对的字符串列表为空,返回{@code false} - * - * @param str1 给定需要检查的字符串 + * + * @param str1 给定需要检查的字符串 * @param ignoreCase 是否忽略大小写 - * @param strs 需要参与比对的字符串列表 + * @param strs 需要参与比对的字符串列表 * @return 是否相同 * @since 4.3.2 */ @@ -2294,9 +2291,9 @@ public class StrUtil { * 通常使用:format("this is {} for {}", "a", "b") =》 this is a for b
    * 转义{}: format("this is \\{} for {}", "a", "b") =》 this is \{} for a
    * 转义\: format("this is \\\\{} for {}", "a", "b") =》 this is \a for b
    - * + * * @param template 文本模板,被替换的部分用 {} 表示 - * @param params 参数值 + * @param params 参数值 * @return 格式化后的文本 */ public static String format(CharSequence template, Object... params) { @@ -2313,8 +2310,8 @@ public class StrUtil { * 有序的格式化文本,使用{number}做为占位符
    * 例:
    * 通常使用:format("this is {0} for {1}", "a", "b") =》 this is a for b
    - * - * @param pattern 文本格式 + * + * @param pattern 文本格式 * @param arguments 参数 * @return 格式化后的文本 */ @@ -2325,9 +2322,9 @@ public class StrUtil { /** * 格式化文本,使用 {varName} 占位
    * map = {a: "aValue", b: "bValue"} format("{a} and {b}", map) ---=》 aValue and bValue - * + * * @param template 文本模板,被替换的部分用 {key} 表示 - * @param map 参数值对 + * @param map 参数值对 * @return 格式化后的文本 */ public static String format(CharSequence template, Map map) { @@ -2351,7 +2348,7 @@ public class StrUtil { /** * 编码字符串,编码为UTF-8 - * + * * @param str 字符串 * @return 编码后的字节码 */ @@ -2362,7 +2359,7 @@ public class StrUtil { /** * 编码字符串
    * 使用系统默认编码 - * + * * @param str 字符串 * @return 编码后的字节码 */ @@ -2372,8 +2369,8 @@ public class StrUtil { /** * 编码字符串 - * - * @param str 字符串 + * + * @param str 字符串 * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 * @return 编码后的字节码 */ @@ -2383,8 +2380,8 @@ public class StrUtil { /** * 编码字符串 - * - * @param str 字符串 + * + * @param str 字符串 * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 * @return 编码后的字节码 */ @@ -2402,7 +2399,7 @@ public class StrUtil { /** * 将对象转为字符串
    * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 - * + * * @param obj 对象 * @return 字符串 */ @@ -2413,8 +2410,8 @@ public class StrUtil { /** * 将对象转为字符串
    * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 - * - * @param obj 对象 + * + * @param obj 对象 * @param charsetName 字符集 * @return 字符串 */ @@ -2425,8 +2422,8 @@ public class StrUtil { /** * 将对象转为字符串
    * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 - * - * @param obj 对象 + * + * @param obj 对象 * @param charset 字符集 * @return 字符串 */ @@ -2452,8 +2449,8 @@ public class StrUtil { /** * 将byte数组转为字符串 - * - * @param bytes byte数组 + * + * @param bytes byte数组 * @param charset 字符集 * @return 字符串 */ @@ -2463,8 +2460,8 @@ public class StrUtil { /** * 解码字节码 - * - * @param data 字符串 + * + * @param data 字符串 * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 * @return 解码后的字符串 */ @@ -2481,8 +2478,8 @@ public class StrUtil { /** * 将Byte数组转为字符串 - * - * @param bytes byte数组 + * + * @param bytes byte数组 * @param charset 字符集 * @return 字符串 */ @@ -2492,8 +2489,8 @@ public class StrUtil { /** * 解码字节码 - * - * @param data 字符串 + * + * @param data 字符串 * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 * @return 解码后的字符串 */ @@ -2514,8 +2511,8 @@ public class StrUtil { /** * 将编码的byteBuffer数据转换为字符串 - * - * @param data 数据 + * + * @param data 数据 * @param charset 字符集,如果为空使用当前系统字符集 * @return 字符串 */ @@ -2529,8 +2526,8 @@ public class StrUtil { /** * 将编码的byteBuffer数据转换为字符串 - * - * @param data 数据 + * + * @param data 数据 * @param charset 字符集,如果为空使用当前系统字符集 * @return 字符串 */ @@ -2543,7 +2540,7 @@ public class StrUtil { /** * {@link CharSequence} 转为字符串,null安全 - * + * * @param cs {@link CharSequence} * @return 字符串 */ @@ -2553,7 +2550,7 @@ public class StrUtil { /** * 调用对象的toString方法,null会返回“null” - * + * * @param obj 对象 * @return 字符串 * @since 4.1.3 @@ -2564,8 +2561,8 @@ public class StrUtil { /** * 字符串转换为byteBuffer - * - * @param str 字符串 + * + * @param str 字符串 * @param charset 编码 * @return byteBuffer */ @@ -2575,12 +2572,11 @@ public class StrUtil { /** * 以 conjunction 为分隔符将多个对象转换为字符串 - * - * @see ArrayUtil#join(Object, CharSequence) - * + * * @param conjunction 分隔符 - * @param objs 数组 + * @param objs 数组 * @return 连接后的字符串 + * @see ArrayUtil#join(Object, CharSequence) */ public static String join(CharSequence conjunction, Object... objs) { return ArrayUtil.join(objs, conjunction); @@ -2589,7 +2585,7 @@ public class StrUtil { /** * 将驼峰式命名的字符串转换为下划线方式。如果转换前的驼峰式命名的字符串为空,则返回空字符串。
    * 例如: - * + * *
     	 * HelloWorld=》hello_world
     	 * Hello_World=》hello_world
    @@ -2606,7 +2602,7 @@ public class StrUtil {
     	/**
     	 * 将驼峰式命名的字符串转换为使用符号连接方式。如果转换前的驼峰式命名的字符串为空,则返回空字符串。
    * - * @param str 转换前的驼峰式命名的字符串,也可以为符号连接形式 + * @param str 转换前的驼峰式命名的字符串,也可以为符号连接形式 * @param symbol 连接符 * @return 转换后符号连接方式命名的字符串 * @since 4.0.10 @@ -2692,8 +2688,8 @@ public class StrUtil { /** * 包装指定字符串
    * 当前缀和后缀一致时使用此方法 - * - * @param str 被包装的字符串 + * + * @param str 被包装的字符串 * @param prefixAndSuffix 前缀和后缀 * @return 包装后的字符串 * @since 3.1.0 @@ -2704,8 +2700,8 @@ public class StrUtil { /** * 包装指定字符串 - * - * @param str 被包装的字符串 + * + * @param str 被包装的字符串 * @param prefix 前缀 * @param suffix 后缀 * @return 包装后的字符串 @@ -2716,9 +2712,9 @@ public class StrUtil { /** * 包装多个字符串 - * + * * @param prefixAndSuffix 前缀和后缀 - * @param strs 多个字符串 + * @param strs 多个字符串 * @return 包装的字符串数组 * @since 4.0.7 */ @@ -2728,10 +2724,10 @@ public class StrUtil { /** * 包装多个字符串 - * + * * @param prefix 前缀 * @param suffix 后缀 - * @param strs 多个字符串 + * @param strs 多个字符串 * @return 包装的字符串数组 * @since 4.0.7 */ @@ -2745,8 +2741,8 @@ public class StrUtil { /** * 包装指定字符串,如果前缀或后缀已经包含对应的字符串,则不再包装 - * - * @param str 被包装的字符串 + * + * @param str 被包装的字符串 * @param prefix 前缀 * @param suffix 后缀 * @return 包装后的字符串 @@ -2777,9 +2773,9 @@ public class StrUtil { /** * 包装多个字符串,如果已经包装,则不再包装 - * + * * @param prefixAndSuffix 前缀和后缀 - * @param strs 多个字符串 + * @param strs 多个字符串 * @return 包装的字符串数组 * @since 4.0.7 */ @@ -2789,10 +2785,10 @@ public class StrUtil { /** * 包装多个字符串,如果已经包装,则不再包装 - * + * * @param prefix 前缀 * @param suffix 后缀 - * @param strs 多个字符串 + * @param strs 多个字符串 * @return 包装的字符串数组 * @since 4.0.7 */ @@ -2806,8 +2802,8 @@ public class StrUtil { /** * 去掉字符包装,如果未被包装则返回原字符串 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefix 前置字符串 * @param suffix 后置字符串 * @return 去掉包装字符的字符串 @@ -2822,8 +2818,8 @@ public class StrUtil { /** * 去掉字符包装,如果未被包装则返回原字符串 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefix 前置字符 * @param suffix 后置字符 * @return 去掉包装字符的字符串 @@ -2841,8 +2837,8 @@ public class StrUtil { /** * 去掉字符包装,如果未被包装则返回原字符串 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefixAndSuffix 前置和后置字符 * @return 去掉包装字符的字符串 * @since 4.0.1 @@ -2853,8 +2849,8 @@ public class StrUtil { /** * 指定字符串是否被包装 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefix 前缀 * @param suffix 后缀 * @return 是否被包装 @@ -2869,8 +2865,8 @@ public class StrUtil { /** * 指定字符串是否被同一字符包装(前后都有这些字符串) - * - * @param str 字符串 + * + * @param str 字符串 * @param wrapper 包装字符串 * @return 是否被包装 */ @@ -2880,8 +2876,8 @@ public class StrUtil { /** * 指定字符串是否被同一字符包装(前后都有这些字符串) - * - * @param str 字符串 + * + * @param str 字符串 * @param wrapper 包装字符 * @return 是否被包装 */ @@ -2891,8 +2887,8 @@ public class StrUtil { /** * 指定字符串是否被包装 - * - * @param str 字符串 + * + * @param str 字符串 * @param prefixChar 前缀 * @param suffixChar 后缀 * @return 是否被包装 @@ -2907,16 +2903,16 @@ public class StrUtil { /** * 补充字符串以满足最小长度 - * + * *
     	 * StrUtil.padPre(null, *, *);//null
     	 * StrUtil.padPre("1", 3, "ABC");//"AB1"
     	 * StrUtil.padPre("123", 2, "ABC");//"12"
     	 * 
    - * - * @param str 字符串 + * + * @param str 字符串 * @param minLength 最小长度 - * @param padStr 补充的字符 + * @param padStr 补充的字符 * @return 补充后的字符串 */ public static String padPre(CharSequence str, int minLength, CharSequence padStr) { @@ -2935,16 +2931,16 @@ public class StrUtil { /** * 补充字符串以满足最小长度 - * + * *
     	 * StrUtil.padPre(null, *, *);//null
     	 * StrUtil.padPre("1", 3, '0');//"001"
     	 * StrUtil.padPre("123", 2, '0');//"12"
     	 * 
    - * - * @param str 字符串 + * + * @param str 字符串 * @param minLength 最小长度 - * @param padChar 补充的字符 + * @param padChar 补充的字符 * @return 补充后的字符串 */ public static String padPre(CharSequence str, int minLength, char padChar) { @@ -2963,16 +2959,16 @@ public class StrUtil { /** * 补充字符串以满足最小长度 - * + * *
     	 * StrUtil.padAfter(null, *, *);//null
     	 * StrUtil.padAfter("1", 3, '0');//"100"
     	 * StrUtil.padAfter("123", 2, '0');//"23"
     	 * 
    - * - * @param str 字符串,如果为null,按照空串处理 + * + * @param str 字符串,如果为null,按照空串处理 * @param minLength 最小长度 - * @param padChar 补充的字符 + * @param padChar 补充的字符 * @return 补充后的字符串 */ public static String padAfter(CharSequence str, int minLength, char padChar) { @@ -2991,16 +2987,16 @@ public class StrUtil { /** * 补充字符串以满足最小长度 - * + * *
     	 * StrUtil.padAfter(null, *, *);//null
     	 * StrUtil.padAfter("1", 3, "ABC");//"1AB"
     	 * StrUtil.padAfter("123", 2, "ABC");//"23"
     	 * 
    - * - * @param str 字符串,如果为null,按照空串处理 + * + * @param str 字符串,如果为null,按照空串处理 * @param minLength 最小长度 - * @param padStr 补充的字符 + * @param padStr 补充的字符 * @return 补充后的字符串 * @since 4.3.2 */ @@ -3020,7 +3016,7 @@ public class StrUtil { /** * 居中字符串,两边补充指定字符串,如果指定长度小于字符串,则返回原字符串 - * + * *
     	 * StrUtil.center(null, *)   = null
     	 * StrUtil.center("", 4)     = "    "
    @@ -3030,7 +3026,7 @@ public class StrUtil {
     	 * StrUtil.center("a", 4)    = " a  "
     	 * 
    * - * @param str 字符串 + * @param str 字符串 * @param size 指定长度 * @return 补充后的字符串 * @since 4.3.2 @@ -3041,7 +3037,7 @@ public class StrUtil { /** * 居中字符串,两边补充指定字符串,如果指定长度小于字符串,则返回原字符串 - * + * *
     	 * StrUtil.center(null, *, *)     = null
     	 * StrUtil.center("", 4, ' ')     = "    "
    @@ -3053,8 +3049,8 @@ public class StrUtil {
     	 * StrUtil.center("abc", 7, ' ')   = "  abc  "
     	 * 
    * - * @param str 字符串 - * @param size 指定长度 + * @param str 字符串 + * @param size 指定长度 * @param padChar 两边补充的字符 * @return 补充后的字符串 * @since 4.3.2 @@ -3075,7 +3071,7 @@ public class StrUtil { /** * 居中字符串,两边补充指定字符串,如果指定长度小于字符串,则返回原字符串 - * + * *
     	 * StrUtil.center(null, *, *)     = null
     	 * StrUtil.center("", 4, " ")     = "    "
    @@ -3088,8 +3084,8 @@ public class StrUtil {
     	 * StrUtil.center("abc", 7, "")   = "  abc  "
     	 * 
    * - * @param str 字符串 - * @param size 指定长度 + * @param str 字符串 + * @param size 指定长度 * @param padStr 两边补充的字符串 * @return 补充后的字符串 */ @@ -3112,7 +3108,7 @@ public class StrUtil { /** * 创建StringBuilder对象 - * + * * @return StringBuilder对象 */ public static StringBuilder builder() { @@ -3121,7 +3117,7 @@ public class StrUtil { /** * 创建StrBuilder对象 - * + * * @return StrBuilder对象 * @since 4.0.1 */ @@ -3131,7 +3127,7 @@ public class StrUtil { /** * 创建StringBuilder对象 - * + * * @param capacity 初始大小 * @return StringBuilder对象 */ @@ -3141,7 +3137,7 @@ public class StrUtil { /** * 创建StrBuilder对象 - * + * * @param capacity 初始大小 * @return StrBuilder对象 * @since 4.0.1 @@ -3152,7 +3148,7 @@ public class StrUtil { /** * 创建StringBuilder对象 - * + * * @param strs 初始字符串列表 * @return StringBuilder对象 */ @@ -3166,7 +3162,7 @@ public class StrUtil { /** * 创建StrBuilder对象 - * + * * @param strs 初始字符串列表 * @return StrBuilder对象 */ @@ -3176,7 +3172,7 @@ public class StrUtil { /** * 获得StringReader - * + * * @param str 字符串 * @return StringReader */ @@ -3189,7 +3185,7 @@ public class StrUtil { /** * 获得StringWriter - * + * * @return StringWriter */ public static StringWriter getWriter() { @@ -3210,7 +3206,7 @@ public class StrUtil { * StrUtil.count("abba", "xxx") = 0 *
    * - * @param content 被查找的字符串 + * @param content 被查找的字符串 * @param strForSearch 需要查找的字符串 * @return 查找到的个数 */ @@ -3232,8 +3228,8 @@ public class StrUtil { /** * 统计指定内容中包含指定字符的数量 - * - * @param content 内容 + * + * @param content 内容 * @param charForSearch 被统计的字符 * @return 包含数量 */ @@ -3253,8 +3249,8 @@ public class StrUtil { /** * 将字符串切分为N等份 - * - * @param str 字符串 + * + * @param str 字符串 * @param partLength 每等份的长度 * @return 切分后的数组 * @since 3.0.6 @@ -3265,7 +3261,7 @@ public class StrUtil { } int len = str.length(); if (len < partLength) { - return new String[] { str.toString() }; + return new String[]{str.toString()}; } int part = NumberUtil.count(len, partLength); final String[] array = new String[part]; @@ -3279,8 +3275,8 @@ public class StrUtil { /** * 将给定字符串,变成 "xxx...xxx" 形式的字符串 - * - * @param str 字符串 + * + * @param str 字符串 * @param maxLength 最大长度 * @return 截取后的字符串 */ @@ -3300,7 +3296,7 @@ public class StrUtil { /** * 比较两个字符串,用于排序 - * + * *
     	 * StrUtil.compare(null, null, *)     = 0
     	 * StrUtil.compare(null , "a", true)  < 0
    @@ -3313,9 +3309,9 @@ public class StrUtil {
     	 * StrUtil.compare("a", "B", *)       > 0
     	 * StrUtil.compare("ab", "abc", *)    < 0
     	 * 
    - * - * @param str1 字符串1 - * @param str2 字符串2 + * + * @param str1 字符串1 + * @param str2 字符串2 * @param nullIsLess {@code null} 值是否排在前(null是否小于非空值) * @return 排序值。负数:str1 < str2,正数:str1 > str2, 0:str1 == str2 */ @@ -3334,7 +3330,7 @@ public class StrUtil { /** * 比较两个字符串,用于排序,大小写不敏感 - * + * *
     	 * StrUtil.compareIgnoreCase(null, null, *)     = 0
     	 * StrUtil.compareIgnoreCase(null , "a", true)  < 0
    @@ -3349,9 +3345,9 @@ public class StrUtil {
     	 * StrUtil.compareIgnoreCase("A", "b", *)       < 0
     	 * StrUtil.compareIgnoreCase("ab", "abc", *)    < 0
     	 * 
    - * - * @param str1 字符串1 - * @param str2 字符串2 + * + * @param str1 字符串1 + * @param str2 字符串2 * @param nullIsLess {@code null} 值是否排在前(null是否小于非空值) * @return 排序值。负数:str1 < str2,正数:str1 > str2, 0:str1 == str2 */ @@ -3371,7 +3367,7 @@ public class StrUtil { /** * 比较两个版本
    * null版本排在最小:即: - * + * *
     	 * StrUtil.compareVersion(null, "v1") < 0
     	 * StrUtil.compareVersion("v1", "v1")  = 0
    @@ -3382,7 +3378,7 @@ public class StrUtil {
     	 * StrUtil.compareVersion("1.13.0", "1.12.1c") > 0
     	 * StrUtil.compareVersion("V0.0.20170102", "V0.0.20170101") > 0
     	 * 
    - * + * * @param version1 版本1 * @param version2 版本2 * @return 排序值。负数:version1 < version2,正数:version1 > version2, 0:version1 == version2 @@ -3394,8 +3390,8 @@ public class StrUtil { /** * 指定范围内查找指定字符 - * - * @param str 字符串 + * + * @param str 字符串 * @param searchChar 被查找的字符 * @return 位置 */ @@ -3405,10 +3401,10 @@ public class StrUtil { /** * 指定范围内查找指定字符 - * - * @param str 字符串 + * + * @param str 字符串 * @param searchChar 被查找的字符 - * @param start 起始位置,如果小于0,从0开始查找 + * @param start 起始位置,如果小于0,从0开始查找 * @return 位置 */ public static int indexOf(final CharSequence str, char searchChar, int start) { @@ -3421,11 +3417,11 @@ public class StrUtil { /** * 指定范围内查找指定字符 - * - * @param str 字符串 + * + * @param str 字符串 * @param searchChar 被查找的字符 - * @param start 起始位置,如果小于0,从0开始查找 - * @param end 终止位置,如果超过str.length()则默认查找到字符串末尾 + * @param start 起始位置,如果小于0,从0开始查找 + * @param end 终止位置,如果超过str.length()则默认查找到字符串末尾 * @return 位置 */ public static int indexOf(final CharSequence str, char searchChar, int start, int end) { @@ -3446,7 +3442,7 @@ public class StrUtil { /** * 指定范围内查找字符串,忽略大小写
    - * + * *
     	 * StrUtil.indexOfIgnoreCase(null, *, *)          = -1
     	 * StrUtil.indexOfIgnoreCase(*, null, *)          = -1
    @@ -3460,8 +3456,8 @@ public class StrUtil {
     	 * StrUtil.indexOfIgnoreCase("aabaabaa", "", 2)   = 2
     	 * StrUtil.indexOfIgnoreCase("abc", "", 9)        = -1
     	 * 
    - * - * @param str 字符串 + * + * @param str 字符串 * @param searchStr 需要查找位置的字符串 * @return 位置 * @since 3.2.1 @@ -3472,7 +3468,7 @@ public class StrUtil { /** * 指定范围内查找字符串 - * + * *
     	 * StrUtil.indexOfIgnoreCase(null, *, *)          = -1
     	 * StrUtil.indexOfIgnoreCase(*, null, *)          = -1
    @@ -3486,8 +3482,8 @@ public class StrUtil {
     	 * StrUtil.indexOfIgnoreCase("aabaabaa", "", 2)   = 2
     	 * StrUtil.indexOfIgnoreCase("abc", "", 9)        = -1
     	 * 
    - * - * @param str 字符串 + * + * @param str 字符串 * @param searchStr 需要查找位置的字符串 * @param fromIndex 起始位置 * @return 位置 @@ -3499,10 +3495,10 @@ public class StrUtil { /** * 指定范围内查找字符串 - * - * @param str 字符串 - * @param searchStr 需要查找位置的字符串 - * @param fromIndex 起始位置 + * + * @param str 字符串 + * @param searchStr 需要查找位置的字符串 + * @param fromIndex 起始位置 * @param ignoreCase 是否忽略大小写 * @return 位置 * @since 3.2.1 @@ -3538,8 +3534,8 @@ public class StrUtil { /** * 指定范围内查找字符串,忽略大小写 - * - * @param str 字符串 + * + * @param str 字符串 * @param searchStr 需要查找位置的字符串 * @return 位置 * @since 3.2.1 @@ -3551,8 +3547,8 @@ public class StrUtil { /** * 指定范围内查找字符串,忽略大小写
    * fromIndex 为搜索起始位置,从后往前计数 - * - * @param str 字符串 + * + * @param str 字符串 * @param searchStr 需要查找位置的字符串 * @param fromIndex 起始位置,从后往前计数 * @return 位置 @@ -3565,10 +3561,10 @@ public class StrUtil { /** * 指定范围内查找字符串
    * fromIndex 为搜索起始位置,从后往前计数 - * - * @param str 字符串 - * @param searchStr 需要查找位置的字符串 - * @param fromIndex 起始位置,从后往前计数 + * + * @param str 字符串 + * @param searchStr 需要查找位置的字符串 + * @param fromIndex 起始位置,从后往前计数 * @param ignoreCase 是否忽略大小写 * @return 位置 * @since 3.2.1 @@ -3605,9 +3601,9 @@ public class StrUtil { *

    * 如果 str=null 或 searchStr=null 或 ordinal≥0 则返回-1
    * 此方法来自:Apache-Commons-Lang - * + *

    * 例子(*代表任意字符): - * + * *

     	 * StrUtil.ordinalIndexOf(null, *, *)          = -1
     	 * StrUtil.ordinalIndexOf(*, null, *)          = -1
    @@ -3622,9 +3618,9 @@ public class StrUtil {
     	 * StrUtil.ordinalIndexOf("aabaabaa", "", 2)   = 0
     	 * 
    * - * @param str 被检查的字符串,可以为null + * @param str 被检查的字符串,可以为null * @param searchStr 被查找的字符串,可以为null - * @param ordinal 第几次出现的位置 + * @param ordinal 第几次出现的位置 * @return 查找到的位置 * @since 3.2.3 */ @@ -3648,14 +3644,14 @@ public class StrUtil { } // ------------------------------------------------------------------------------------------------------------------ Append and prepend + /** * 如果给定字符串不是以给定的一个或多个字符串为结尾,则在尾部添加结尾字符串
    * 不忽略大小写 * - * @param str 被检查的字符串 - * @param suffix 需要添加到结尾的字符串 + * @param str 被检查的字符串 + * @param suffix 需要添加到结尾的字符串 * @param suffixes 需要额外检查的结尾字符串,如果以这些中的一个为结尾,则不再添加 - * * @return 如果已经结尾,返回原字符串,否则返回添加结尾的字符串 * @since 3.0.7 */ @@ -3667,10 +3663,9 @@ public class StrUtil { * 如果给定字符串不是以给定的一个或多个字符串为结尾,则在尾部添加结尾字符串
    * 忽略大小写 * - * @param str 被检查的字符串 - * @param suffix 需要添加到结尾的字符串 + * @param str 被检查的字符串 + * @param suffix 需要添加到结尾的字符串 * @param suffixes 需要额外检查的结尾字符串,如果以这些中的一个为结尾,则不再添加 - * * @return 如果已经结尾,返回原字符串,否则返回添加结尾的字符串 * @since 3.0.7 */ @@ -3681,11 +3676,10 @@ public class StrUtil { /** * 如果给定字符串不是以给定的一个或多个字符串为结尾,则在尾部添加结尾字符串 * - * @param str 被检查的字符串 - * @param suffix 需要添加到结尾的字符串 + * @param str 被检查的字符串 + * @param suffix 需要添加到结尾的字符串 * @param ignoreCase 检查结尾时是否忽略大小写 - * @param suffixes 需要额外检查的结尾字符串,如果以这些中的一个为结尾,则不再添加 - * + * @param suffixes 需要额外检查的结尾字符串,如果以这些中的一个为结尾,则不再添加 * @return 如果已经结尾,返回原字符串,否则返回添加结尾的字符串 * @since 3.0.7 */ @@ -3707,10 +3701,9 @@ public class StrUtil { * 如果给定字符串不是以给定的一个或多个字符串为开头,则在首部添加起始字符串
    * 不忽略大小写 * - * @param str 被检查的字符串 - * @param prefix 需要添加到首部的字符串 + * @param str 被检查的字符串 + * @param prefix 需要添加到首部的字符串 * @param prefixes 需要额外检查的首部字符串,如果以这些中的一个为起始,则不再添加 - * * @return 如果已经结尾,返回原字符串,否则返回添加结尾的字符串 * @since 3.0.7 */ @@ -3722,10 +3715,9 @@ public class StrUtil { * 如果给定字符串不是以给定的一个或多个字符串为开头,则在首部添加起始字符串
    * 忽略大小写 * - * @param str 被检查的字符串 - * @param prefix 需要添加到首部的字符串 + * @param str 被检查的字符串 + * @param prefix 需要添加到首部的字符串 * @param prefixes 需要额外检查的首部字符串,如果以这些中的一个为起始,则不再添加 - * * @return 如果已经结尾,返回原字符串,否则返回添加结尾的字符串 * @since 3.0.7 */ @@ -3736,11 +3728,10 @@ public class StrUtil { /** * 如果给定字符串不是以给定的一个或多个字符串为开头,则在首部添加起始字符串 * - * @param str 被检查的字符串 - * @param prefix 需要添加到首部的字符串 + * @param str 被检查的字符串 + * @param prefix 需要添加到首部的字符串 * @param ignoreCase 检查结尾时是否忽略大小写 - * @param prefixes 需要额外检查的首部字符串,如果以这些中的一个为起始,则不再添加 - * + * @param prefixes 需要额外检查的首部字符串,如果以这些中的一个为起始,则不再添加 * @return 如果已经结尾,返回原字符串,否则返回添加结尾的字符串 * @since 3.0.7 */ @@ -3761,7 +3752,7 @@ public class StrUtil { /** * 反转字符串
    * 例如:abcd =》dcba - * + * * @param str 被反转的字符串 * @return 反转后的字符串 * @since 3.0.9 @@ -3773,10 +3764,10 @@ public class StrUtil { /** * 将已有字符串填充为规定长度,如果已有字符串超过这个长度则返回这个字符串
    * 字符填充于字符串前 - * - * @param str 被填充的字符串 + * + * @param str 被填充的字符串 * @param filledChar 填充的字符 - * @param len 填充长度 + * @param len 填充长度 * @return 填充后的字符串 * @since 3.1.2 */ @@ -3787,10 +3778,10 @@ public class StrUtil { /** * 将已有字符串填充为规定长度,如果已有字符串超过这个长度则返回这个字符串
    * 字符填充于字符串后 - * - * @param str 被填充的字符串 + * + * @param str 被填充的字符串 * @param filledChar 填充的字符 - * @param len 填充长度 + * @param len 填充长度 * @return 填充后的字符串 * @since 3.1.2 */ @@ -3800,11 +3791,11 @@ public class StrUtil { /** * 将已有字符串填充为规定长度,如果已有字符串超过这个长度则返回这个字符串 - * - * @param str 被填充的字符串 + * + * @param str 被填充的字符串 * @param filledChar 填充的字符 - * @param len 填充长度 - * @param isPre 是否填充在前 + * @param len 填充长度 + * @param isPre 是否填充在前 * @return 填充后的字符串 * @since 3.1.2 */ @@ -3821,12 +3812,12 @@ public class StrUtil { /** * 截取两个字符串的不同部分(长度一致),判断截取的子串是否相同
    * 任意一个字符串为null返回false - * - * @param str1 第一个字符串 - * @param start1 第一个字符串开始的位置 - * @param str2 第二个字符串 - * @param start2 第二个字符串开始的位置 - * @param length 截取长度 + * + * @param str1 第一个字符串 + * @param start1 第一个字符串开始的位置 + * @param str2 第二个字符串 + * @param start2 第二个字符串开始的位置 + * @param length 截取长度 * @param ignoreCase 是否忽略大小写 * @return 子串是否相同 * @since 3.2.1 @@ -3841,8 +3832,8 @@ public class StrUtil { /** * 字符串的每一个字符是否都与定义的匹配器匹配 - * - * @param value 字符串 + * + * @param value 字符串 * @param matcher 匹配器 * @return 是否全部匹配 * @since 3.2.3 @@ -3861,9 +3852,9 @@ public class StrUtil { /** * 替换字符串中的指定字符串,忽略大小写 - * - * @param str 字符串 - * @param searchStr 被查找的字符串 + * + * @param str 字符串 + * @param searchStr 被查找的字符串 * @param replacement 被替换的字符串 * @return 替换后的字符串 * @since 4.0.3 @@ -3874,9 +3865,9 @@ public class StrUtil { /** * 替换字符串中的指定字符串 - * - * @param str 字符串 - * @param searchStr 被查找的字符串 + * + * @param str 字符串 + * @param searchStr 被查找的字符串 * @param replacement 被替换的字符串 * @return 替换后的字符串 * @since 4.0.3 @@ -3887,11 +3878,11 @@ public class StrUtil { /** * 替换字符串中的指定字符串 - * - * @param str 字符串 - * @param searchStr 被查找的字符串 + * + * @param str 字符串 + * @param searchStr 被查找的字符串 * @param replacement 被替换的字符串 - * @param ignoreCase 是否忽略大小写 + * @param ignoreCase 是否忽略大小写 * @return 替换后的字符串 * @since 4.0.3 */ @@ -3901,12 +3892,12 @@ public class StrUtil { /** * 替换字符串中的指定字符串 - * - * @param str 字符串 - * @param fromIndex 开始位置(包括) - * @param searchStr 被查找的字符串 + * + * @param str 字符串 + * @param fromIndex 开始位置(包括) + * @param searchStr 被查找的字符串 * @param replacement 被替换的字符串 - * @param ignoreCase 是否忽略大小写 + * @param ignoreCase 是否忽略大小写 * @return 替换后的字符串 * @since 4.0.3 */ @@ -3948,10 +3939,10 @@ public class StrUtil { /** * 替换指定字符串的指定区间内字符为固定字符 - * - * @param str 字符串 + * + * @param str 字符串 * @param startInclude 开始位置(包含) - * @param endExclude 结束位置(不包含) + * @param endExclude 结束位置(不包含) * @param replacedChar 被替换的字符 * @return 替换后的字符串 * @since 3.2.1 @@ -3985,9 +3976,9 @@ public class StrUtil { /** * 替换所有正则匹配的文本,并使用自定义函数决定如何替换 - * - * @param str 要替换的字符串 - * @param pattern 用于匹配的正则式 + * + * @param str 要替换的字符串 + * @param pattern 用于匹配的正则式 * @param replaceFun 决定如何替换的函数 * @return 替换后的字符串 * @see ReUtil#replaceAll(CharSequence, Pattern, Func1) @@ -3999,9 +3990,9 @@ public class StrUtil { /** * 替换所有正则匹配的文本,并使用自定义函数决定如何替换 - * - * @param str 要替换的字符串 - * @param regex 用于匹配的正则式 + * + * @param str 要替换的字符串 + * @param regex 用于匹配的正则式 * @param replaceFun 决定如何替换的函数 * @return 替换后的字符串 * @see ReUtil#replaceAll(CharSequence, String, Func1) @@ -4014,9 +4005,9 @@ public class StrUtil { /** * 替换指定字符串的指定区间内字符为"*" * - * @param str 字符串 + * @param str 字符串 * @param startInclude 开始位置(包含) - * @param endExclude 结束位置(不包含) + * @param endExclude 结束位置(不包含) * @return 替换后的字符串 * @since 4.1.14 */ @@ -4027,9 +4018,9 @@ public class StrUtil { /** * 替换字符字符数组中所有的字符为replacedStr
    * 提供的chars为所有需要被替换的字符,例如:"\r\n",则"\r"和"\n"都会被替换,哪怕他们单独存在 - * - * @param str 被检查的字符串 - * @param chars 需要替换的字符列表,用一个字符串表示这个字符列表 + * + * @param str 被检查的字符串 + * @param chars 需要替换的字符列表,用一个字符串表示这个字符列表 * @param replacedStr 替换成的字符串 * @return 新字符串 * @since 3.2.2 @@ -4043,9 +4034,9 @@ public class StrUtil { /** * 替换字符字符数组中所有的字符为replacedStr - * - * @param str 被检查的字符串 - * @param chars 需要替换的字符列表 + * + * @param str 被检查的字符串 + * @param chars 需要替换的字符列表 * @param replacedStr 替换成的字符串 * @return 新字符串 * @since 3.2.2 @@ -4071,7 +4062,7 @@ public class StrUtil { /** * 计算两个字符串的相似度 - * + * * @param str1 字符串1 * @param str2 字符串2 * @return 相似度 @@ -4083,9 +4074,9 @@ public class StrUtil { /** * 计算连个字符串的相似度百分比 - * - * @param str1 字符串1 - * @param str2 字符串2 + * + * @param str1 字符串1 + * @param str2 字符串2 * @param scale 相似度 * @return 相似度百分比 * @since 3.2.3 @@ -4099,10 +4090,10 @@ public class StrUtil { * 如果字符串为null,返回false
    * 如果给定的位置大于字符串长度,返回false
    * 如果给定的位置小于0,返回false - * - * @param str 字符串 + * + * @param str 字符串 * @param position 位置 - * @param c 需要对比的字符 + * @param c 需要对比的字符 * @return 字符串指定位置的字符是否与给定字符相同 * @since 3.3.1 */ @@ -4116,7 +4107,7 @@ public class StrUtil { /** * 给定字符串数组的总长度
    * null字符长度定义为0 - * + * * @param strs 字符串数组 * @return 总长度 * @since 4.0.1 @@ -4133,11 +4124,11 @@ public class StrUtil { * 循环位移指定位置的字符串为指定距离
    * 当moveLength大于0向右位移,小于0向左位移,0不位移
    * 当moveLength大于字符串长度时采取循环位移策略,即位移到头后从头(尾)位移,例如长度为10,位移13则表示位移3 - * - * @param str 字符串 + * + * @param str 字符串 * @param startInclude 起始位置(包括) - * @param endExclude 结束位置(不包括) - * @param moveLength 移动距离,负数表示左移,正数为右移 + * @param endExclude 结束位置(不包括) + * @param moveLength 移动距离,负数表示左移,正数为右移 * @return 位移后的字符串 * @since 4.0.7 */ @@ -4171,10 +4162,10 @@ public class StrUtil { /** * 生成随机UUID - * + * * @return UUID字符串 - * @since 4.0.10 * @see IdUtil#randomUUID() + * @since 4.0.10 */ public static String uuid() { return IdUtil.randomUUID(); @@ -4182,9 +4173,9 @@ public class StrUtil { /** * 连接多个字符串为一个 - * + * * @param isNullToEmpty 是否null转为"" - * @param strs 字符串数组 + * @param strs 字符串数组 * @return 连接后的字符串 * @since 4.1.0 */ @@ -4198,12 +4189,12 @@ public class StrUtil { /** * 给定字符串中的字母是否全部为大写,判断依据如下: - * + * *
     	 * 1. 大写字母包括A-Z
     	 * 2. 其它非字母的Unicode符都算作大写
     	 * 
    - * + * * @param str 被检查的字符串 * @return 是否全部为大写 * @since 4.2.2 @@ -4223,12 +4214,12 @@ public class StrUtil { /** * 给定字符串中的字母是否全部为小写,判断依据如下: - * + * *
     	 * 1. 小写字母包括a-z
     	 * 2. 其它非字母的Unicode符都算作小写
     	 * 
    - * + * * @param str 被检查的字符串 * @return 是否全部为小写 * @since 4.2.2 @@ -4256,11 +4247,11 @@ public class StrUtil { public static int length(CharSequence cs) { return cs == null ? 0 : cs.length(); } - + /** * 给定字符串转为bytes后的byte数(byte长度) - * - * @param cs 字符串 + * + * @param cs 字符串 * @param charset 编码 * @return byte长度 * @since 4.5.2 diff --git a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java index f0fd31419..6e3a41fde 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java @@ -424,8 +424,8 @@ public class StrUtilTest { @Test public void subBetweenAllTest() { - Assert.assertArrayEquals(new String[]{"yz","abc"},StrUtil.subBetweenAll("saho[yz]fdsadp[abc]a","\\[","\\]")); - Assert.assertArrayEquals(new String[]{"abc"}, StrUtil.subBetweenAll("saho[yzfdsadp[abc]a]","\\[","\\]")); + Assert.assertArrayEquals(new String[]{"yz","abc"},StrUtil.subBetweenAll("saho[yz]fdsadp[abc]a","[","]")); + Assert.assertArrayEquals(new String[]{"abc"}, StrUtil.subBetweenAll("saho[yzfdsadp[abc]a]","[","]")); } } From 3eaf4702f52ff2c144abc2b29bb4cd38345fc9ea Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 29 Mar 2020 12:29:12 +0800 Subject: [PATCH 007/157] add method --- CHANGELOG.md | 1 + .../java/cn/hutool/core/util/StrUtil.java | 41 ++++--------------- .../java/cn/hutool/core/util/StrUtilTest.java | 5 +++ 3 files changed, 15 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c2aa0da2..9651cee1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### 新特性 * 【extra 】 JschUtil增加execByShell方法(issue#I1CYES@Gitee) +* 【core 】 StrUtil增加subBetweenAll方法,Console增加where和lineNumber方法(issue#812@Github) ### Bug修复 * 【extra 】 修复SpringUtil使用devtools重启报错问题 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java index afe1ca6cf..f3f36f253 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java @@ -1968,50 +1968,27 @@ public class StrUtil { * StrUtil.subBetweenAll("[yabc[zy]abcz]", "[", "]"); = ["zy"] 重叠时只截取内部, * * - * @param str 被切割的字符串 + * @param str 被切割的字符串 * @param prefix 截取开始的字符串标识 - * @param suffix 截取到的字符串标识 + * @param suffix 截取到的字符串标识 * @return 截取后的字符串 * @author dahuoyzs * @since 5.2.5 */ public static String[] subBetweenAll(CharSequence str, CharSequence prefix, CharSequence suffix) { - if(hasEmpty(str, prefix, suffix)) { + if (hasEmpty(str, prefix, suffix)) { return new String[0]; } - final int prefixCount = count(str, prefix); - final int suffixCount = count(str, suffix); - if (prefixCount < 1 || suffixCount < 1) { - return new String[0]; - } - - LinkedList betweenList = new LinkedList<>(); - if (prefixCount > suffixCount) { - String[] fragments = split(str, suffix); - for (int i = 0; i < fragments.length - 1; i++) { - String fragment = fragments[i]; - if (fragment.contains(prefix)) { - int beforeIndex = StrUtil.lastIndexOf(fragment, prefix, 0, false); - String between = fragment.substring(beforeIndex); - if (between.length() > 0) - betweenList.add(between); - } - } - } else { - String[] fragments = split(str, prefix); - for (int i = 1; i < fragments.length; i++) { - String fragment = fragments[i]; - if (fragment.contains(suffix)) { - int afterIndex = StrUtil.indexOf(fragment, suffix, 0, false); - String between = fragment.substring(0, afterIndex); - if (between.length() > 0) - betweenList.add(between); - } + final List result = new LinkedList<>(); + for (String fragment : split(str, prefix)) { + int suffixIndex = fragment.indexOf(suffix.toString()); + if (suffixIndex > 0) { + result.add(fragment.substring(0, suffixIndex)); } } - return betweenList.toArray(new String[0]); + return result.toArray(new String[0]); } /** diff --git a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java index 6e3a41fde..770ded054 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java @@ -426,6 +426,11 @@ public class StrUtilTest { public void subBetweenAllTest() { Assert.assertArrayEquals(new String[]{"yz","abc"},StrUtil.subBetweenAll("saho[yz]fdsadp[abc]a","[","]")); Assert.assertArrayEquals(new String[]{"abc"}, StrUtil.subBetweenAll("saho[yzfdsadp[abc]a]","[","]")); + Assert.assertArrayEquals(new String[]{"abc", "abc"}, StrUtil.subBetweenAll("yabczyabcz","y","z")); + Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll(null,"y","z")); + Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("","y","z")); + Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("abc",null,"z")); + Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("abc","y",null)); } } From 2783f17472ea8a37747dcd7093e838b5ef6798d7 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 30 Mar 2020 10:38:15 +0800 Subject: [PATCH 008/157] add setFreezePane --- .../main/java/cn/hutool/json/JSONUtil.java | 22 +++++++++++++++++ .../java/cn/hutool/poi/excel/ExcelWriter.java | 24 +++++++++++++++++++ .../hutool/poi/excel/test/ExcelWriteTest.java | 2 ++ 3 files changed, 48 insertions(+) diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 21d741b83..b7fa8d8e4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -48,6 +48,17 @@ public final class JSONUtil { return new JSONObject(); } + /** + * 创建JSONObject + * + * @param config JSON配置 + * @return JSONObject + * @since 5.2.5 + */ + public static JSONObject createObj(JSONConfig config) { + return new JSONObject(config); + } + /** * 创建 JSONArray * @@ -57,6 +68,17 @@ public final class JSONUtil { return new JSONArray(); } + /** + * 创建 JSONArray + * + * @param config JSON配置 + * @return JSONArray + * @since 5.2.5 + */ + public static JSONArray createArray(JSONConfig config) { + return new JSONArray(config); + } + /** * JSON字符串转JSONObject对象 * diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java index cd7e2ee0b..1f2a27cbb 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java @@ -470,6 +470,30 @@ public class ExcelWriter extends ExcelBase { return this; } + /** + * 设置窗口冻结,之前冻结的窗口会被覆盖,如果rowSplit为0表示取消冻结 + * + * @param rowSplit 冻结的行及行数,2表示前两行 + * @return this + * @since 5.2.5 + */ + public ExcelWriter setFreezePane(int rowSplit){ + return setFreezePane(0, rowSplit); + } + + /** + * 设置窗口冻结,之前冻结的窗口会被覆盖,如果colSplit和rowSplit为0表示取消冻结 + * + * @param colSplit 冻结的列及列数,2表示前两列 + * @param rowSplit 冻结的行及行数,2表示前两行 + * @return this + * @since 5.2.5 + */ + public ExcelWriter setFreezePane(int colSplit, int rowSplit){ + getSheet().createFreezePane(colSplit, rowSplit); + return this; + } + /** * 设置列宽(单位为一个字符的宽度,例如传入width为10,表示10个字符的宽度) * diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelWriteTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelWriteTest.java index 9bca94c59..abc3edb28 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelWriteTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelWriteTest.java @@ -90,6 +90,8 @@ public class ExcelWriteTest { // 一次性写出内容,使用默认样式 writer.write(rows); writer.autoSizeColumn(0, true); + //冻结前两行 + writer.setFreezePane(0, 2); // 关闭writer,释放内存 writer.close(); } From 174d80e865e4452db32df56bd56b171114b9c24c Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 30 Mar 2020 19:36:19 +0800 Subject: [PATCH 009/157] add methods --- CHANGELOG.md | 1 + .../cn/hutool/core/collection/CollUtil.java | 24 +++++++++ .../cn/hutool/core/collection/ListUtil.java | 25 ++++++++++ .../java/cn/hutool/core/map/TableMap.java | 50 +++++++++++++++++-- .../hutool/core/collection/ListUtilTest.java | 9 ++++ 5 files changed, 104 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9651cee1e..73dac269f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### 新特性 * 【extra 】 JschUtil增加execByShell方法(issue#I1CYES@Gitee) * 【core 】 StrUtil增加subBetweenAll方法,Console增加where和lineNumber方法(issue#812@Github) +* 【core 】 TableMap增加getKeys和getValues方法 ### Bug修复 * 【extra 】 修复SpringUtil使用devtools重启报错问题 diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index 3cb0ee020..186caf969 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -1310,6 +1310,30 @@ public class CollUtil { return count; } + /** + * 获取匹配规则定义中匹配到元素的所有位置
    + * 此方法对于某些无序集合的位置信息,以转换为数组后的位置为准。 + * + * @param 元素类型 + * @param collection 集合 + * @param matcher 匹配器,为空则全部匹配 + * @return 位置数组 + * @since 5.2.5 + */ + public static int[] indexOfAll(Collection collection, Matcher matcher){ + final List indexList = new ArrayList<>(); + if (null != collection) { + int index = 0; + for (T t : collection) { + if (null == matcher || matcher.match(t)) { + indexList.add(index); + } + index++; + } + } + return Convert.convert(int[].class, indexList); + } + // ---------------------------------------------------------------------- isEmpty /** diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java index d45d2c60d..5579dcec9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java @@ -2,7 +2,9 @@ package cn.hutool.core.collection; import cn.hutool.core.comparator.PinyinComparator; import cn.hutool.core.comparator.PropertyComparator; +import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Editor; +import cn.hutool.core.lang.Matcher; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.PageUtil; @@ -419,4 +421,27 @@ public class ListUtil { } return list2; } + + /** + * 获取匹配规则定义中匹配到元素的所有位置 + * + * @param 元素类型 + * @param list 列表 + * @param matcher 匹配器,为空则全部匹配 + * @return 位置数组 + * @since 5.2.5 + */ + public static int[] indexOfAll(List list, Matcher matcher){ + final List indexList = new ArrayList<>(); + if (null != list) { + int index = 0; + for (T t : list) { + if (null == matcher || matcher.match(t)) { + indexList.add(index); + } + index++; + } + } + return Convert.convert(int[].class, indexList); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java index 7b4be3645..b80ec2ed0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java @@ -1,13 +1,22 @@ package cn.hutool.core.map; -import java.io.Serializable; -import java.util.*; - import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; /** - * 无重复键的Map + * 可重复键的Map * * @author looly * @@ -73,6 +82,34 @@ public class TableMap implements Map, Serializable { return null; } + /** + * 获取指定key对应的所有值 + * + * @param key 键 + * @return 值列表 + * @since 5.2.5 + */ + public List getValues(K key){ + return CollUtil.getAny( + this.values, + ListUtil.indexOfAll(this.keys, (ele)-> ObjectUtil.equal(ele, key)) + ); + } + + /** + * 获取指定value对应的所有key + * + * @param value 值 + * @return 值列表 + * @since 5.2.5 + */ + public List getKeys(V value){ + return CollUtil.getAny( + this.keys, + ListUtil.indexOfAll(this.values, (ele)-> ObjectUtil.equal(ele, value)) + ); + } + @Override public V put(K key, V value) { keys.add(key); @@ -106,16 +143,19 @@ public class TableMap implements Map, Serializable { values.clear(); } + @SuppressWarnings("NullableProblems") @Override public Set keySet() { return new HashSet<>(keys); } + @SuppressWarnings("NullableProblems") @Override public Collection values() { - return new HashSet<>(values); + return Collections.unmodifiableList(this.values); } + @SuppressWarnings("NullableProblems") @Override public Set> entrySet() { HashSet> hashSet = new HashSet<>(); diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 36ce84875..61aa2855c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -15,4 +15,13 @@ public class ListUtilTest { Assert.assertEquals("edit2", filter.get(1)); Assert.assertEquals("edit3", filter.get(2)); } + + @Test + public void indexOfAll() { + List a = ListUtil.toLinkedList("1", "2", "3", "4", "3", "2", "1"); + final int[] indexArray = ListUtil.indexOfAll(a, "2"::equals); + Assert.assertArrayEquals(new int[]{1,5}, indexArray); + final int[] indexArray2 = ListUtil.indexOfAll(a, "1"::equals); + Assert.assertArrayEquals(new int[]{0,6}, indexArray2); + } } From ccccbf67aa24ffe4d17e9606b388d3df31484630 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 31 Mar 2020 11:06:39 +0800 Subject: [PATCH 010/157] fix code and add sh --- CHANGELOG.md | 3 +- bin/cobertura.sh | 3 + bin/simple_install.sh | 3 + .../main/java/cn/hutool/json/JSONArray.java | 17 +++- .../main/java/cn/hutool/json/JSONObject.java | 31 +++++-- .../java/cn/hutool/json/JSONObjectIter.java | 12 +-- .../java/cn/hutool/json/JSONObjectTest.java | 90 ++++++++++--------- .../java/cn/hutool/json/test/bean/UserA.java | 32 +------ 8 files changed, 101 insertions(+), 90 deletions(-) create mode 100644 bin/cobertura.sh create mode 100644 bin/simple_install.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 73dac269f..180f1db1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,13 @@ ------------------------------------------------------------------------------------------------------------- -## 5.2.6 (2020-03-26) +## 5.2.6 (2020-04-01) ### 新特性 * 【extra 】 JschUtil增加execByShell方法(issue#I1CYES@Gitee) * 【core 】 StrUtil增加subBetweenAll方法,Console增加where和lineNumber方法(issue#812@Github) * 【core 】 TableMap增加getKeys和getValues方法 +* 【json 】 JSONObject和JSONArray增加set方法,标识put弃用 ### Bug修复 * 【extra 】 修复SpringUtil使用devtools重启报错问题 diff --git a/bin/cobertura.sh b/bin/cobertura.sh new file mode 100644 index 000000000..1611b93f0 --- /dev/null +++ b/bin/cobertura.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +exec mvn -T 1 cobertura:cobertura diff --git a/bin/simple_install.sh b/bin/simple_install.sh new file mode 100644 index 000000000..d3b9281e2 --- /dev/null +++ b/bin/simple_install.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +exec mvn -T 1C clean install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index bd8bd5d85..5a17702f7 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -252,8 +252,21 @@ public class JSONArray implements JSON, JSONGetter, List, Rando * * @param value 值,可以是: Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the JSONNull.NULL。 * @return this. + * @see #set(Object) */ public JSONArray put(Object value) { + return set(value); + } + + /** + * Append an object value. This increases the array's length by one.
    + * 加入元素,数组长度+1,等同于 {@link JSONArray#add(Object)} + * + * @param value 值,可以是: Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the JSONNull.NULL。 + * @return this. + * @since 5.2.5 + */ + public JSONArray set(Object value) { this.add(value); return this; } @@ -284,7 +297,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando } JSONObject jo = new JSONObject(); for (int i = 0; i < names.size(); i += 1) { - jo.put(names.getStr(i), this.getObj(i)); + jo.set(names.getStr(i), this.getObj(i)); } return jo; } @@ -440,7 +453,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando while (index != this.size()) { this.add(JSONNull.NULL); } - this.put(element); + this.set(element); } } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index 4ec0ce078..655d31454 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -279,7 +279,7 @@ public class JSONObject implements JSON, JSONGetter, Map for (String name : names) { value = this.get(name); if (null != value) { - ja.put(value); + ja.set(value); } } return ja; @@ -332,14 +332,16 @@ public class JSONObject implements JSON, JSONGetter, Map } /** - * PUT 键值对到JSONObject中,如果值为null,将此键移除 + * PUT 键值对到JSONObject中,在忽略null模式下,如果值为null,将此键移除 * * @param key 键 * @param value 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. * @return this. * @throws JSONException 值是无穷数字抛出此异常 + * @deprecated 此方法存在歧义,原Map接口返回的是之前的值,重写后返回this了,未来版本此方法会修改,请使用{@link #set(String, Object)} */ @Override + @Deprecated public JSONObject put(String key, Object value) throws JSONException { if (null == key) { return this; @@ -356,6 +358,19 @@ public class JSONObject implements JSON, JSONGetter, Map return this; } + /** + * 设置键值对到JSONObject中,在忽略null模式下,如果值为null,将此键移除 + * + * @param key 键 + * @param value 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. + * @return this. + * @throws JSONException 值是无穷数字抛出此异常 + */ + public JSONObject set(String key, Object value) throws JSONException { + put(key, value); + return this; + } + /** * 一次性Put 键值对,如果key已经存在抛出异常,如果键值中有null值,忽略 * @@ -365,7 +380,7 @@ public class JSONObject implements JSON, JSONGetter, Map * @throws JSONException 值是无穷数字、键重复抛出异常 */ public JSONObject putOnce(String key, Object value) throws JSONException { - if (key != null && value != null) { + if (key != null) { if (rawHashMap.containsKey(key)) { throw new JSONException("Duplicate key \"{}\"", key); } @@ -409,11 +424,11 @@ public class JSONObject implements JSON, JSONGetter, Map InternalJSONUtil.testValidity(value); Object object = this.getObj(key); if (object == null) { - this.put(key, value instanceof JSONArray ? new JSONArray().put(value) : value); + this.put(key, value instanceof JSONArray ? new JSONArray().set(value) : value); } else if (object instanceof JSONArray) { - ((JSONArray) object).put(value); + ((JSONArray) object).set(value); } else { - this.put(key, new JSONArray().put(object).put(value)); + this.set(key, new JSONArray().set(object).set(value)); } return this; } @@ -430,9 +445,9 @@ public class JSONObject implements JSON, JSONGetter, Map InternalJSONUtil.testValidity(value); Object object = this.getObj(key); if (object == null) { - this.put(key, new JSONArray().put(value)); + this.set(key, new JSONArray().set(value)); } else if (object instanceof JSONArray) { - this.put(key, ((JSONArray) object).put(value)); + this.set(key, ((JSONArray) object).set(value)); } else { throw new JSONException("JSONObject [" + key + "] is not a JSONArray."); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObjectIter.java b/hutool-json/src/main/java/cn/hutool/json/JSONObjectIter.java index 660a8569a..fe27a2467 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObjectIter.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObjectIter.java @@ -10,10 +10,10 @@ import java.util.Iterator; */ public class JSONObjectIter implements Iterable { - Iterator iter; + Iterator iterator; - public JSONObjectIter(Iterator iter) { - this.iter = iter; + public JSONObjectIter(Iterator iterator) { + this.iterator = iterator; } @Override @@ -22,17 +22,17 @@ public class JSONObjectIter implements Iterable { @Override public boolean hasNext() { - return iter.hasNext(); + return iterator.hasNext(); } @Override public JSONObject next() { - return (JSONObject) iter.next(); + return (JSONObject) iterator.next(); } @Override public void remove() { - iter.remove(); + iterator.remove(); } }; } diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index cd870504f..08fc8908e 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -1,17 +1,7 @@ package cn.hutool.json; -import java.math.BigDecimal; -import java.util.Date; -import java.util.List; -import java.util.Objects; - import cn.hutool.core.annotation.Alias; import cn.hutool.core.collection.CollUtil; -import lombok.Data; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; @@ -30,6 +20,15 @@ import cn.hutool.json.test.bean.UserWithMap; import cn.hutool.json.test.bean.report.CaseReport; import cn.hutool.json.test.bean.report.StepReport; import cn.hutool.json.test.bean.report.SuiteReport; +import lombok.Data; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; +import java.util.Objects; /** * JSONObject单元测试 @@ -61,32 +60,33 @@ public class JSONObjectTest { @Test public void toStringTest3() { JSONObject json = Objects.requireNonNull(JSONUtil.createObj()// - .put("dateTime", DateUtil.parse("2019-05-02 22:12:01")))// + .set("dateTime", DateUtil.parse("2019-05-02 22:12:01")))// .setDateFormat(DatePattern.NORM_DATE_PATTERN); Assert.assertEquals("{\"dateTime\":\"2019-05-02\"}", json.toString()); } @Test public void toStringWithDateTest() { - JSONObject json = JSONUtil.createObj().put("date", DateUtil.parse("2019-05-08 19:18:21")); + JSONObject json = JSONUtil.createObj().set("date", DateUtil.parse("2019-05-08 19:18:21")); assert json != null; Assert.assertEquals("{\"date\":1557314301000}", json.toString()); - json = Objects.requireNonNull(JSONUtil.createObj().put("date", DateUtil.parse("2019-05-08 19:18:21"))).setDateFormat(DatePattern.NORM_DATE_PATTERN); + json = Objects.requireNonNull(JSONUtil.createObj().set("date", DateUtil.parse("2019-05-08 19:18:21"))).setDateFormat(DatePattern.NORM_DATE_PATTERN); Assert.assertEquals("{\"date\":\"2019-05-08\"}", json.toString()); } + @Test public void putAllTest() { - JSONObject json1 = JSONUtil.createObj(); - json1.put("a", "value1"); - json1.put("b", "value2"); - json1.put("c", "value3"); - json1.put("d", true); + JSONObject json1 = JSONUtil.createObj() + .set("a", "value1") + .set("b", "value2") + .set("c", "value3") + .set("d", true); - JSONObject json2 = JSONUtil.createObj(); - json2.put("a", "value21"); - json2.put("b", "value22"); + JSONObject json2 = JSONUtil.createObj() + .set("a", "value21") + .set("b", "value22"); // putAll操作会覆盖相同key的值,因此a,b两个key的值改变,c的值不变 json1.putAll(json2); @@ -134,13 +134,14 @@ public class JSONObjectTest { Console.log(json2); } - @SuppressWarnings("ConstantConditions") @Test public void toBeanTest() { - JSONObject subJson = JSONUtil.createObj().put("value1", "strValue1").put("value2", "234"); - JSONObject json = JSONUtil.createObj().put("strValue", "strTest").put("intValue", 123) + JSONObject subJson = JSONUtil.createObj().set("value1", "strValue1").set("value2", "234"); + JSONObject json = JSONUtil.createObj().set("strValue", "strTest").set("intValue", 123) // 测试空字符串转对象 - .put("doubleValue", "").put("beanValue", subJson).put("list", JSONUtil.createArray().put("a").put("b")).put("testEnum", "TYPE_A"); + .set("doubleValue", "") + .set("beanValue", subJson) + .set("list", JSONUtil.createArray().set("a").set("b")).set("testEnum", "TYPE_A"); TestBean bean = json.toBean(TestBean.class); Assert.assertEquals("a", bean.getList().get(0)); @@ -153,14 +154,13 @@ public class JSONObjectTest { Assert.assertEquals(TestEnum.TYPE_A, bean.getTestEnum()); } - @SuppressWarnings("ConstantConditions") @Test public void toBeanNullStrTest() { JSONObject json = JSONUtil.createObj()// - .put("strValue", "null")// - .put("intValue", 123)// - .put("beanValue", "null")// - .put("list", JSONUtil.createArray().put("a").put("b")); + .set("strValue", "null")// + .set("intValue", 123)// + .set("beanValue", "null")// + .set("list", JSONUtil.createArray().set("a").set("b")); TestBean bean = json.toBean(TestBean.class); // 当JSON中为字符串"null"时应被当作字符串处理 @@ -222,15 +222,14 @@ public class JSONObjectTest { /** * 在JSON转Bean过程中,Bean中字段如果为父类定义的泛型类型,则应正确转换,此方法用于测试这类情况 */ - @SuppressWarnings("ConstantConditions") @Test public void toBeanTest6() { JSONObject json = JSONUtil.createObj() - .put("targetUrl", "http://test.com") - .put("success", "true") - .put("result", JSONUtil.createObj() - .put("token", "tokenTest") - .put("userId", "测试用户1")); + .set("targetUrl", "http://test.com") + .set("success", "true") + .set("result", JSONUtil.createObj() + .set("token", "tokenTest") + .set("userId", "测试用户1")); TokenAuthWarp2 bean = json.toBean(TokenAuthWarp2.class); Assert.assertEquals("http://test.com", bean.getTargetUrl()); @@ -260,7 +259,8 @@ public class JSONObjectTest { userA.setDate(new Date()); userA.setSqs(CollectionUtil.newArrayList(new Seq(null), new Seq("seq2"))); - JSONObject json = JSONUtil.parseObj(userA, false); + JSONObject json = JSONUtil.parseObj(userA, false, true); + Assert.assertTrue(json.containsKey("a")); Assert.assertTrue(json.getJSONArray("sqs").getJSONObject(0).containsKey("seq")); } @@ -282,10 +282,11 @@ public class JSONObjectTest { Assert.assertEquals(bean.toString(), bean2.toString()); } - @SuppressWarnings("ConstantConditions") @Test public void parseBeanTest3() { - JSONObject json = JSONUtil.createObj().put("code", 22).put("data", "{\"jobId\": \"abc\", \"videoUrl\": \"http://a.com/a.mp4\"}"); + JSONObject json = JSONUtil.createObj() + .set("code", 22) + .set("data", "{\"jobId\": \"abc\", \"videoUrl\": \"http://a.com/a.mp4\"}"); JSONBean bean = json.toBean(JSONBean.class); Assert.assertEquals(22, bean.getCode()); @@ -322,10 +323,12 @@ public class JSONObjectTest { Assert.assertEquals(DateUtil.parse("2018-10-25"), bean.getDate()); } - @SuppressWarnings("ConstantConditions") @Test public void beanTransTest3() { - JSONObject userAJson = JSONUtil.createObj().put("a", "AValue").put("name", "nameValue").put("date", "08:00:00"); + JSONObject userAJson = JSONUtil.createObj() + .set("a", "AValue") + .set("name", "nameValue") + .set("date", "08:00:00"); UserA bean = JSONUtil.toBean(userAJson.toString(), UserA.class); Assert.assertEquals(DateUtil.today() + " 08:00:00", DateUtil.date(bean.getDate()).toString()); } @@ -377,10 +380,9 @@ public class JSONObjectTest { Assert.assertEquals("张三", jsonObject.getStr("name")); Assert.assertEquals(new Integer(35), jsonObject.getInt("age")); - @SuppressWarnings("ConstantConditions") JSONObject json = JSONUtil.createObj() - .put("name", "张三") - .put("age", 35); + .set("name", "张三") + .set("age", 35); final BeanWithAlias bean = JSONUtil.toBean(Objects.requireNonNull(json).toString(), BeanWithAlias.class); Assert.assertEquals("张三", bean.getValue1()); Assert.assertEquals(new Integer(35), bean.getValue2()); diff --git a/hutool-json/src/test/java/cn/hutool/json/test/bean/UserA.java b/hutool-json/src/test/java/cn/hutool/json/test/bean/UserA.java index fa8b5e626..748944654 100644 --- a/hutool-json/src/test/java/cn/hutool/json/test/bean/UserA.java +++ b/hutool-json/src/test/java/cn/hutool/json/test/bean/UserA.java @@ -1,40 +1,14 @@ package cn.hutool.json.test.bean; +import lombok.Data; + import java.util.Date; import java.util.List; +@Data public class UserA { private String name; private String a; private Date date; private List sqs; - - public String getName() { - return name; - } - public void setName(String name) { - this.name = name; - } - public String getA() { - return a; - } - public void setA(String a) { - this.a = a; - } - public Date getDate() { - return date; - } - public void setDate(Date date) { - this.date = date; - } - public List getSqs() { - return sqs; - } - public void setSqs(List sqs) { - this.sqs = sqs; - } - @Override - public String toString() { - return "UserA [name=" + name + ", a=" + a + ", date=" + date + ", sqs=" + sqs + "]"; - } } From bb3b3dd4ae5dd74e99e7824e8dc700216f77daf2 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 31 Mar 2020 11:16:35 +0800 Subject: [PATCH 011/157] update dependncy --- hutool-db/pom.xml | 6 +++--- hutool-extra/pom.xml | 7 ++++--- hutool-log/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-system/pom.xml | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index fa32455c3..9860ff4a6 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -21,9 +21,9 @@ 0.9.5.5 2.7.0 9.0.30 - 1.1.21 + 1.1.20 2.4.13 - 3.12.1 + 3.12.2 3.30.1 2.5.0 3.2.0 @@ -120,7 +120,7 @@ org.postgresql postgresql - 42.2.10.jre7 + 42.2.11.jre7 test diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 8298dc798..8f42d3959 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -19,7 +19,7 @@ 2.2 - 3.0.19.RELEASE + 3.1.2.RELEASE 1.3.0 2.3.30 4.8 @@ -30,6 +30,7 @@ 3.6 5.1.1 4.0.1 + 2.2.6.RELEASE @@ -210,13 +211,13 @@ org.springframework.boot spring-boot-starter - 2.2.5.RELEASE + ${spring-boot.version} true org.springframework.boot spring-boot-starter-test - 2.2.5.RELEASE + ${spring-boot.version} test diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index dc713a7b5..98f927ac6 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -21,7 +21,7 @@ 1.7.26 1.3.0-alpha5 1.2.17 - 2.13.0 + 2.13.1 1.2 1.3.6 3.4.1.Final diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 9833e6f62..05cf7bf27 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -16,7 +16,7 @@ Hutool 脚本执行封装 - 2.7.0 + 2.7.2 3.0.1 3.0.2 diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 487c2c2d5..a89067307 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -26,7 +26,7 @@ com.github.oshi oshi-core - 4.4.2 + 4.5.2 provided From 4a4cdd3744213fa9142df5f49c4d089ff283af9c Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 1 Apr 2020 08:29:33 +0800 Subject: [PATCH 012/157] add SimpleHttpServer --- CHANGELOG.md | 1 + .../main/java/cn/hutool/http/HttpUtil.java | 12 ++++ .../cn/hutool/http/server/SimpleServer.java | 67 +++++++++++++++++++ .../cn/hutool/http/server/package-info.java | 7 ++ .../cn/hutool/http/test/SimpleServerTest.java | 21 ++++++ 5 files changed, 108 insertions(+) create mode 100644 hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java create mode 100644 hutool-http/src/main/java/cn/hutool/http/server/package-info.java create mode 100644 hutool-http/src/test/java/cn/hutool/http/test/SimpleServerTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 180f1db1e..65b329433 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * 【core 】 StrUtil增加subBetweenAll方法,Console增加where和lineNumber方法(issue#812@Github) * 【core 】 TableMap增加getKeys和getValues方法 * 【json 】 JSONObject和JSONArray增加set方法,标识put弃用 +* 【http 】 增加SimpleHttpServer ### Bug修复 * 【extra 】 修复SpringUtil使用devtools重启报错问题 diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index 99db09344..1d244b7fa 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -14,6 +14,7 @@ import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; +import cn.hutool.http.server.SimpleServer; import java.io.File; import java.io.InputStream; @@ -774,6 +775,17 @@ public class HttpUtil { final ContentType contentType = ContentType.get(body); return (null == contentType) ? null : contentType.toString(); } + + /** + * 创建简易的Http服务器 + * + * @param port 端口 + * @return {@link SimpleServer} + * @since 5.2.6 + */ + public static SimpleServer createSimpleServer(int port){ + return new SimpleServer(port); + } // ----------------------------------------------------------------------------------------- Private method start /** diff --git a/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java b/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java new file mode 100644 index 000000000..f1fcc1f6e --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java @@ -0,0 +1,67 @@ +package cn.hutool.http.server; + +import cn.hutool.core.io.IORuntimeException; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.concurrent.Executor; + +/** + * 简易Http服务器,基于{@link HttpServer} + * + * @author looly + * @since 5.2.5 + */ +public class SimpleServer { + + HttpServer server; + + /** + * 构造 + * + * @param port 监听端口 + */ + public SimpleServer(int port) { + this(new InetSocketAddress(port)); + } + + /** + * 构造 + * + * @param hostname 监听地址 + * @param port 监听端口 + */ + public SimpleServer(String hostname, int port) { + this(new InetSocketAddress(hostname, port)); + } + + /** + * 构造 + * + * @param address 监听地址 + */ + public SimpleServer(InetSocketAddress address) { + try { + this.server = HttpServer.create(address, 0); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + public SimpleServer addHandler(String path, HttpHandler handler) { + this.server.createContext(path, handler); + return this; + } + + public SimpleServer setExecutor(Executor executor) { + this.server.setExecutor(executor); + return this; + } + + public SimpleServer start() { + this.server.start(); + return this; + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/server/package-info.java b/hutool-http/src/main/java/cn/hutool/http/server/package-info.java new file mode 100644 index 000000000..0b65d48e7 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/package-info.java @@ -0,0 +1,7 @@ +/** + * Http服务器封装 + * + * @author looly + * + */ +package cn.hutool.http.server; \ No newline at end of file diff --git a/hutool-http/src/test/java/cn/hutool/http/test/SimpleServerTest.java b/hutool-http/src/test/java/cn/hutool/http/test/SimpleServerTest.java new file mode 100644 index 000000000..43d83973f --- /dev/null +++ b/hutool-http/src/test/java/cn/hutool/http/test/SimpleServerTest.java @@ -0,0 +1,21 @@ +package cn.hutool.http.test; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.HttpStatus; +import cn.hutool.http.server.SimpleServer; + +import java.io.OutputStream; + +public class SimpleServerTest { + + public static void main(String[] args) { + final SimpleServer server = new SimpleServer(8888); + server.addHandler("/", httpExchange -> { + httpExchange.sendResponseHeaders(HttpStatus.HTTP_OK, 0); + final OutputStream out = httpExchange.getResponseBody(); + out.write(StrUtil.bytes("Hello Hutool Server!")); + IoUtil.close(out); + }).start(); + } +} From c7ce3719e8dbf5d888a67522ab9ac27fe0605967 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 1 Apr 2020 15:46:58 +0800 Subject: [PATCH 013/157] add createXXXScript --- CHANGELOG.md | 1 + .../cn/hutool/script/JavaScriptEngine.java | 2 +- .../java/cn/hutool/script/ScriptUtil.java | 105 +++++++++++++----- 3 files changed, 81 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65b329433..3e9258b2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * 【core 】 TableMap增加getKeys和getValues方法 * 【json 】 JSONObject和JSONArray增加set方法,标识put弃用 * 【http 】 增加SimpleHttpServer +* 【script 】 增加createXXXScript,区别单例 ### Bug修复 * 【extra 】 修复SpringUtil使用devtools重启报错问题 diff --git a/hutool-script/src/main/java/cn/hutool/script/JavaScriptEngine.java b/hutool-script/src/main/java/cn/hutool/script/JavaScriptEngine.java index c62283d0b..ca776c9d3 100644 --- a/hutool-script/src/main/java/cn/hutool/script/JavaScriptEngine.java +++ b/hutool-script/src/main/java/cn/hutool/script/JavaScriptEngine.java @@ -17,7 +17,7 @@ import java.io.Reader; public class JavaScriptEngine extends FullSupportScriptEngine { public JavaScriptEngine() { - super(ScriptUtil.getJsEngine()); + super(ScriptUtil.createJsEngine()); } /** diff --git a/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java b/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java index 991c425c6..637fba93c 100644 --- a/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java +++ b/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java @@ -22,29 +22,38 @@ public class ScriptUtil { private static SimpleCache cache = new SimpleCache<>(); /** - * 获得 {@link ScriptEngine} 实例 + * 获得单例的{@link ScriptEngine} 实例 * * @param nameOrExtOrMime 脚本名称 * @return {@link ScriptEngine} 实例 */ public static ScriptEngine getScript(String nameOrExtOrMime) { - return cache.get(nameOrExtOrMime, ()->{ - ScriptEngine engine = manager.getEngineByName(nameOrExtOrMime); - if (null == engine) { - engine = manager.getEngineByExtension(nameOrExtOrMime); - } - if (null == engine) { - engine = manager.getEngineByMimeType(nameOrExtOrMime); - } - if (null == engine) { - throw new NullPointerException(StrUtil.format("Script for [{}] not support !", nameOrExtOrMime)); - } - return engine; - }); + return cache.get(nameOrExtOrMime, ()-> createScript(nameOrExtOrMime)); } /** - * 获得 Javascript引擎 {@link JavaScriptEngine} + * 创建 {@link ScriptEngine} 实例 + * + * @param nameOrExtOrMime 脚本名称 + * @return {@link ScriptEngine} 实例 + * @since 5.2.6 + */ + public static ScriptEngine createScript(String nameOrExtOrMime) { + ScriptEngine engine = manager.getEngineByName(nameOrExtOrMime); + if (null == engine) { + engine = manager.getEngineByExtension(nameOrExtOrMime); + } + if (null == engine) { + engine = manager.getEngineByMimeType(nameOrExtOrMime); + } + if (null == engine) { + throw new NullPointerException(StrUtil.format("Script for [{}] not support !", nameOrExtOrMime)); + } + return engine; + } + + /** + * 获得非单例的 Javascript引擎 {@link JavaScriptEngine} * * @return {@link JavaScriptEngine} */ @@ -53,9 +62,9 @@ public class ScriptUtil { } /** - * 获得 JavaScript引擎 + * 获得单例的JavaScript引擎 * - * @return Python引擎 + * @return Javascript引擎 * @since 5.2.5 */ public static ScriptEngine getJsEngine() { @@ -63,7 +72,17 @@ public class ScriptUtil { } /** - * 获得 Python引擎
    + * 创建新的JavaScript引擎 + * + * @return Javascript引擎 + * @since 5.2.6 + */ + public static ScriptEngine createJsEngine() { + return createScript("js"); + } + + /** + * 获得单例的Python引擎
    * 需要引入org.python:jython * * @return Python引擎 @@ -75,7 +94,19 @@ public class ScriptUtil { } /** - * 获得Lua引擎
    + * 创建Python引擎
    + * 需要引入org.python:jython + * + * @return Python引擎 + * @since 5.2.6 + */ + public static ScriptEngine createPythonEngine() { + System.setProperty("python.import.site", "false"); + return createScript("python"); + } + + /** + * 获得单例的Lua引擎
    * 需要引入org.luaj:luaj-jse * * @return Lua引擎 @@ -86,7 +117,18 @@ public class ScriptUtil { } /** - * 获得Groovy引擎
    + * 创建Lua引擎
    + * 需要引入org.luaj:luaj-jse + * + * @return Lua引擎 + * @since 5.2.6 + */ + public static ScriptEngine createLuaEngine() { + return createScript("lua"); + } + + /** + * 获得单例的Groovy引擎
    * 需要引入org.codehaus.groovy:groovy-all * * @return Groovy引擎 @@ -97,10 +139,21 @@ public class ScriptUtil { } /** - * 执行脚本 + * 创建Groovy引擎
    + * 需要引入org.codehaus.groovy:groovy-all + * + * @return Groovy引擎 + * @since 5.2.6 + */ + public static ScriptEngine createGroovyEngine() { + return createScript("groovy"); + } + + /** + * 执行Javascript脚本 * * @param script 脚本内容 - * @return {@link CompiledScript} + * @return 执行结果 * @throws ScriptRuntimeException 脚本异常 * @since 3.2.0 */ @@ -117,7 +170,7 @@ public class ScriptUtil { * * @param script 脚本内容 * @param context 脚本上下文 - * @return {@link CompiledScript} + * @return 执行结果 * @throws ScriptRuntimeException 脚本异常 * @since 3.2.0 */ @@ -134,7 +187,7 @@ public class ScriptUtil { * * @param script 脚本内容 * @param bindings 绑定的参数 - * @return {@link CompiledScript} + * @return 执行结果 * @throws ScriptRuntimeException 脚本异常 * @since 3.2.0 */ @@ -147,7 +200,7 @@ public class ScriptUtil { } /** - * 编译脚本 + * 编译Javascript脚本 * * @param script 脚本内容 * @return {@link CompiledScript} @@ -163,7 +216,7 @@ public class ScriptUtil { } /** - * 编译脚本 + * 编译Javascript脚本 * * @param engine 引擎 * @param script 脚本内容 From b2201873ea1aab6cd32e88512396217f893fe5e4 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 2 Apr 2020 10:21:21 +0800 Subject: [PATCH 014/157] add Handler --- .../main/java/cn/hutool/core/io/FileUtil.java | 9 +- .../cn/hutool/core/io/file/FileReader.java | 27 +++--- .../main/java/cn/hutool/http/HttpUtil.java | 2 +- .../cn/hutool/http/server/SimpleServer.java | 37 +++++++- .../http/server/handler/HandlerUtil.java | 91 +++++++++++++++++++ .../http/server/handler/RootHandler.java | 45 +++++++++ .../hutool/http/server/SimpleServerTest.java | 13 +++ .../cn/hutool/http/test/SimpleServerTest.java | 21 ----- 8 files changed, 201 insertions(+), 44 deletions(-) create mode 100644 hutool-http/src/main/java/cn/hutool/http/server/handler/HandlerUtil.java create mode 100644 hutool-http/src/main/java/cn/hutool/http/server/handler/RootHandler.java create mode 100644 hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java delete mode 100644 hutool-http/src/test/java/cn/hutool/http/test/SimpleServerTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 852ee8aa2..9882ec1d4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -3254,10 +3254,10 @@ public class FileUtil { * * @param file 文件 * @param out 流 - * @return 目标文件 + * @return 写出的流byte数 * @throws IORuntimeException IO异常 */ - public static File writeToStream(File file, OutputStream out) throws IORuntimeException { + public static long writeToStream(File file, OutputStream out) throws IORuntimeException { return FileReader.create(file).writeToStream(out); } @@ -3266,10 +3266,11 @@ public class FileUtil { * * @param fullFilePath 文件绝对路径 * @param out 输出流 + * @return 写出的流byte数 * @throws IORuntimeException IO异常 */ - public static void writeToStream(String fullFilePath, OutputStream out) throws IORuntimeException { - writeToStream(touch(fullFilePath), out); + public static long writeToStream(String fullFilePath, OutputStream out) throws IORuntimeException { + return writeToStream(touch(fullFilePath), out); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java index b0a684241..93808ce89 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileReader.java @@ -1,5 +1,12 @@ package cn.hutool.core.io.file; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.LineHandler; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; + import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; @@ -11,13 +18,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.io.LineHandler; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; - /** * 文件读取器 * @@ -249,20 +249,15 @@ public class FileReader extends FileWrapper { * 将文件写入流中 * * @param out 流 - * @return File + * @return 写出的流byte数 * @throws IORuntimeException IO异常 */ - public File writeToStream(OutputStream out) throws IORuntimeException { - FileInputStream in = null; - try { - in = new FileInputStream(file); - IoUtil.copy(in, out); + public long writeToStream(OutputStream out) throws IORuntimeException { + try (FileInputStream in = new FileInputStream(this.file)){ + return IoUtil.copy(in, out); }catch (IOException e) { throw new IORuntimeException(e); - } finally { - IoUtil.close(in); } - return this.file; } // -------------------------------------------------------------------------- Interface start diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index 1d244b7fa..dca6c0daa 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -783,7 +783,7 @@ public class HttpUtil { * @return {@link SimpleServer} * @since 5.2.6 */ - public static SimpleServer createSimpleServer(int port){ + public static SimpleServer createServer(int port){ return new SimpleServer(port); } // ----------------------------------------------------------------------------------------- Private method start diff --git a/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java b/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java index f1fcc1f6e..90c587a69 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java @@ -50,18 +50,51 @@ public class SimpleServer { } } + /** + * 增加请求处理规则 + * + * @param path 路径 + * @param handler 处理器 + * @return this + */ public SimpleServer addHandler(String path, HttpHandler handler) { this.server.createContext(path, handler); return this; } + /** + * 设置自定义线程池 + * + * @param executor {@link Executor} + * @return this + */ public SimpleServer setExecutor(Executor executor) { this.server.setExecutor(executor); return this; } - public SimpleServer start() { + /** + * 获得原始HttpServer对象 + * + * @return {@link HttpServer} + */ + public HttpServer getRawServer(){ + return this.server; + } + + /** + * 获取服务器地址信息 + * + * @return {@link InetSocketAddress} + */ + public InetSocketAddress getAddress(){ + return this.server.getAddress(); + } + + /** + * 启动Http服务器,启动后会阻塞当前线程 + */ + public void start() { this.server.start(); - return this; } } diff --git a/hutool-http/src/main/java/cn/hutool/http/server/handler/HandlerUtil.java b/hutool-http/src/main/java/cn/hutool/http/server/handler/HandlerUtil.java new file mode 100644 index 000000000..a3fe4d0b8 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/handler/HandlerUtil.java @@ -0,0 +1,91 @@ +package cn.hutool.http.server.handler; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.http.Header; +import cn.hutool.http.HttpStatus; +import cn.hutool.http.HttpUtil; +import com.sun.net.httpserver.HttpExchange; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; + +/** + * 请求处理器相关工具类 + * + * @since 5.2.6 + */ +public class HandlerUtil { + + /** + * 返回404页面 + * + * @param httpExchange HttpExchange + * @param content 要发送的404页面内容 + * @throws IOException IO异常 + */ + public static void send404(HttpExchange httpExchange, String content) throws IOException { + if (null == httpExchange) { + return; + } + + if (null == content) { + content = "404 Not Found !"; + } + + httpExchange.sendResponseHeaders(HttpStatus.HTTP_NOT_FOUND, 0); + try (OutputStream out = httpExchange.getResponseBody()) { + IoUtil.writeUtf8(out, false, content); + } + } + + /** + * 返回文件 + * + * @param httpExchange HttpExchange + * @param file 要发送的文件 + * @throws IOException IO异常 + */ + public static void sendFile(HttpExchange httpExchange, File file) throws IOException { + if (ArrayUtil.hasNull(httpExchange, file)) { + return; + } + addHeader(httpExchange, + Header.CONTENT_TYPE.toString(), + HttpUtil.getMimeType(file.getName(), "text/html")); + httpExchange.sendResponseHeaders(HttpStatus.HTTP_OK, 0); + try (OutputStream out = httpExchange.getResponseBody()) { + FileUtil.writeToStream(file, out); + } + } + + /** + * 增加响应头信息 + * + * @param httpExchange HttpExchange + * @param header 头名 + * @param value 头值 + */ + public static void addHeader(HttpExchange httpExchange, String header, String value) { + if (ArrayUtil.hasEmpty(httpExchange, header)) { + return; + } + httpExchange.getResponseHeaders().add(header, value); + } + + /** + * 获取响应头信息 + * + * @param httpExchange HttpExchange + * @param header 头名 + * @return 值,不存在返回null + */ + public static String getHeader(HttpExchange httpExchange, String header) { + if (ArrayUtil.hasEmpty(httpExchange, header)) { + return null; + } + return httpExchange.getRequestHeaders().getFirst(header); + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/server/handler/RootHandler.java b/hutool-http/src/main/java/cn/hutool/http/server/handler/RootHandler.java new file mode 100644 index 000000000..8e10274ee --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/handler/RootHandler.java @@ -0,0 +1,45 @@ +package cn.hutool.http.server.handler; + +import cn.hutool.core.io.FileUtil; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +import java.io.File; +import java.io.IOException; +import java.net.URI; + +/** + * 默认的处理器,通过解析用户传入的path,找到网页根目录下对应文件后返回 + * + * @author looly + * @since 5.2.6 + */ +public class RootHandler implements HttpHandler { + + private final String rootDir; + + /** + * 构造 + * + * @param rootDir 网页根目录 + */ + public RootHandler(String rootDir) { + this.rootDir = rootDir; + } + + @Override + public void handle(HttpExchange httpExchange) throws IOException { + final URI uri = httpExchange.getRequestURI(); + File file = FileUtil.file(rootDir, uri.getPath()); + if (file.exists()) { + if (file.isDirectory()) { + //默认读取主页 + file = FileUtil.file(file, "index.html"); + } + HandlerUtil.sendFile(httpExchange, file); + } + + // 文件未找到 + HandlerUtil.send404(httpExchange, null); + } +} diff --git a/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java b/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java new file mode 100644 index 000000000..70a70a510 --- /dev/null +++ b/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java @@ -0,0 +1,13 @@ +package cn.hutool.http.server; + +import cn.hutool.http.HttpUtil; +import cn.hutool.http.server.handler.RootHandler; + +public class SimpleServerTest { + + public static void main(String[] args) { + HttpUtil.createServer(8888) + .addHandler("/", new RootHandler("D:\\test")) + .start(); + } +} diff --git a/hutool-http/src/test/java/cn/hutool/http/test/SimpleServerTest.java b/hutool-http/src/test/java/cn/hutool/http/test/SimpleServerTest.java deleted file mode 100644 index 43d83973f..000000000 --- a/hutool-http/src/test/java/cn/hutool/http/test/SimpleServerTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package cn.hutool.http.test; - -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.http.HttpStatus; -import cn.hutool.http.server.SimpleServer; - -import java.io.OutputStream; - -public class SimpleServerTest { - - public static void main(String[] args) { - final SimpleServer server = new SimpleServer(8888); - server.addHandler("/", httpExchange -> { - httpExchange.sendResponseHeaders(HttpStatus.HTTP_OK, 0); - final OutputStream out = httpExchange.getResponseBody(); - out.write(StrUtil.bytes("Hello Hutool Server!")); - IoUtil.close(out); - }).start(); - } -} From 3f6112fcfa7888f3da76d576bd7b7f4d718fa28b Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 2 Apr 2020 11:03:08 +0800 Subject: [PATCH 015/157] add null check --- CHANGELOG.md | 2 ++ .../src/main/java/cn/hutool/core/collection/CollUtil.java | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e9258b2f..a6c1dfaa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ * 【json 】 JSONObject和JSONArray增加set方法,标识put弃用 * 【http 】 增加SimpleHttpServer * 【script 】 增加createXXXScript,区别单例 +* 【core 】 修改FileUtil.writeFileToStream等方法返回值为long +* 【core 】 CollUtil.split增加空集合判定(issue#814@Github) ### Bug修复 * 【extra 】 修复SpringUtil使用devtools重启报错问题 diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index 186caf969..5a92c8fa6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -21,7 +21,6 @@ import cn.hutool.core.util.TypeUtil; import java.lang.reflect.Type; import java.util.AbstractCollection; -import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -824,7 +823,7 @@ public class CollUtil { /** * 创建Map
    - * 传入抽象Map{@link AbstractMap}和{@link Map}类将默认创建{@link HashMap} + * 传入AbstractMap和{@link Map}类将默认创建{@link HashMap} * * @param map键类型 * @param map值类型 @@ -923,6 +922,9 @@ public class CollUtil { */ public static List> split(Collection collection, int size) { final List> result = new ArrayList<>(); + if (CollUtil.isEmpty(collection)) { + return result; + } ArrayList subList = new ArrayList<>(size); for (T t : collection) { From 1218a5150914f4b77b53eeae9c9d289bc8642610 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 2 Apr 2020 11:59:58 +0800 Subject: [PATCH 016/157] add Request --- .../main/java/cn/hutool/core/io/IoUtil.java | 16 +- .../java/cn/hutool/core/util/CharsetUtil.java | 119 +++++++---- .../src/main/java/cn/hutool/http/Header.java | 11 +- .../main/java/cn/hutool/http/HttpUtil.java | 17 +- .../cn/hutool/http/server/HttpRequest.java | 199 ++++++++++++++++++ .../http/useragent/UserAgentParser.java | 4 + 6 files changed, 315 insertions(+), 51 deletions(-) create mode 100644 hutool-http/src/main/java/cn/hutool/http/server/HttpRequest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java index 72a073ca6..6d1d44dae 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java @@ -1,5 +1,12 @@ package cn.hutool.core.io; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.StrUtil; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; @@ -36,13 +43,6 @@ import java.util.zip.CRC32; import java.util.zip.CheckedInputStream; import java.util.zip.Checksum; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.exceptions.UtilException; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.HexUtil; -import cn.hutool.core.util.StrUtil; - /** * IO工具类
    * IO工具类只是辅助流的读写,并不负责关闭流。原因是流可能被多次读写,读写关闭后容易造成问题。 @@ -453,7 +453,7 @@ public class IoUtil { } /** - * 从流中读取内容,读到输出流中 + * 从流中读取内容,读到输出流中,读取完毕后并不关闭流 * * @param in 输入流 * @return 输出流 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CharsetUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CharsetUtil.java index 5d21c0b05..c19de5d01 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CharsetUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CharsetUtil.java @@ -1,65 +1,102 @@ package cn.hutool.core.util; +import cn.hutool.core.io.FileUtil; + import java.io.File; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; -import cn.hutool.core.io.FileUtil; - /** * 字符集工具类 - * @author xiaoleilu * + * @author xiaoleilu */ public class CharsetUtil { - - /** ISO-8859-1 */ + + /** + * ISO-8859-1 + */ public static final String ISO_8859_1 = "ISO-8859-1"; - /** UTF-8 */ + /** + * UTF-8 + */ public static final String UTF_8 = "UTF-8"; - /** GBK */ + /** + * GBK + */ public static final String GBK = "GBK"; - - /** ISO-8859-1 */ + + /** + * ISO-8859-1 + */ public static final Charset CHARSET_ISO_8859_1 = StandardCharsets.ISO_8859_1; - /** UTF-8 */ + /** + * UTF-8 + */ public static final Charset CHARSET_UTF_8 = StandardCharsets.UTF_8; - /** GBK */ + /** + * GBK + */ public static final Charset CHARSET_GBK; - static{ + static { //避免不支持GBK的系统中运行报错 issue#731 Charset _CHARSET_GBK = null; - try{ + try { _CHARSET_GBK = Charset.forName(GBK); - } catch (UnsupportedCharsetException e){ + } catch (UnsupportedCharsetException e) { //ignore } CHARSET_GBK = _CHARSET_GBK; } - + /** * 转换为Charset对象 + * * @param charsetName 字符集,为空则返回默认字符集 * @return Charset * @throws UnsupportedCharsetException 编码不支持 */ - public static Charset charset(String charsetName) throws UnsupportedCharsetException{ + public static Charset charset(String charsetName) throws UnsupportedCharsetException { return StrUtil.isBlank(charsetName) ? Charset.defaultCharset() : Charset.forName(charsetName); } - + + /** + * 解析字符串编码为Charset对象,解析失败返回默认编码 + * + * @param charsetName 字符集,为空则返回默认字符集 + * @param defaultCharset 解析失败使用的默认编码 + * @return Charset + * @since 5.2.6 + */ + public static Charset parse(String charsetName, Charset defaultCharset) throws UnsupportedCharsetException { + if (StrUtil.isBlank(charsetName)) { + return defaultCharset; + } + + Charset result; + try { + result = Charset.forName(charsetName); + } catch (UnsupportedCharsetException e) { + result = defaultCharset; + } + + return result; + } + /** * 转换字符串的字符集编码 - * @param source 字符串 - * @param srcCharset 源字符集,默认ISO-8859-1 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 * @param destCharset 目标字符集,默认UTF-8 * @return 转换后的字符集 */ public static String convert(String source, String srcCharset, String destCharset) { return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset)); } - + /** * 转换字符串的字符集编码
    * 当以错误的编码读取为字符串时,打印字符串将出现乱码。
    @@ -69,33 +106,33 @@ public class CharsetUtil { * 客户端 -》 GBK编码 -》 Servlet容器 -》 UTF-8解码 -》 乱码 * 乱码 -》 UTF-8编码 -》 GBK解码 -》 正确内容 * - * - * @param source 字符串 - * @param srcCharset 源字符集,默认ISO-8859-1 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 * @param destCharset 目标字符集,默认UTF-8 * @return 转换后的字符集 */ public static String convert(String source, Charset srcCharset, Charset destCharset) { - if(null == srcCharset) { + if (null == srcCharset) { srcCharset = StandardCharsets.ISO_8859_1; } - - if(null == destCharset) { + + if (null == destCharset) { destCharset = StandardCharsets.UTF_8; } - + if (StrUtil.isBlank(source) || srcCharset.equals(destCharset)) { return source; } return new String(source.getBytes(srcCharset), destCharset); } - + /** * 转换文件编码
    * 此方法用于转换文件编码,读取的文件实际编码必须与指定的srcCharset编码一致,否则导致乱码 - * - * @param file 文件 - * @param srcCharset 原文件的编码,必须与文件内容的编码保持一致 + * + * @param file 文件 + * @param srcCharset 原文件的编码,必须与文件内容的编码保持一致 * @param destCharset 转码后的编码 * @return 被转换编码的文件 * @since 3.1.0 @@ -104,41 +141,41 @@ public class CharsetUtil { final String str = FileUtil.readString(file, srcCharset); return FileUtil.writeString(str, file, destCharset); } - + /** * 系统字符集编码,如果是Windows,则默认为GBK编码,否则取 {@link CharsetUtil#defaultCharsetName()} - * - * @see CharsetUtil#defaultCharsetName() + * * @return 系统字符集编码 + * @see CharsetUtil#defaultCharsetName() * @since 3.1.2 */ public static String systemCharsetName() { return systemCharset().name(); } - + /** * 系统字符集编码,如果是Windows,则默认为GBK编码,否则取 {@link CharsetUtil#defaultCharsetName()} - * - * @see CharsetUtil#defaultCharsetName() + * * @return 系统字符集编码 + * @see CharsetUtil#defaultCharsetName() * @since 3.1.2 */ public static Charset systemCharset() { return FileUtil.isWindows() ? CHARSET_GBK : defaultCharset(); } - + /** * 系统默认字符集编码 - * + * * @return 系统字符集编码 */ public static String defaultCharsetName() { return defaultCharset().name(); } - + /** * 系统默认字符集编码 - * + * * @return 系统字符集编码 */ public static Charset defaultCharset() { diff --git a/hutool-http/src/main/java/cn/hutool/http/Header.java b/hutool-http/src/main/java/cn/hutool/http/Header.java index 3ff1ef0f3..a9385d37d 100644 --- a/hutool-http/src/main/java/cn/hutool/http/Header.java +++ b/hutool-http/src/main/java/cn/hutool/http/Header.java @@ -127,8 +127,17 @@ public enum Header { this.value = value; } + /** + * 获取值 + * + * @return 值 + */ + public String getValue(){ + return this.value; + } + @Override public String toString() { - return value; + return getValue(); } } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index dca6c0daa..264e9c233 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -677,7 +677,22 @@ public class HttpUtil { if (conn == null) { return null; } - return ReUtil.get(CHARSET_PATTERN, conn.getContentType(), 1); + return getCharset(conn.getContentType()); + } + + /** + * 从Http连接的头信息中获得字符集
    + * 从ContentType中获取 + * + * @param contentType Content-Type + * @return 字符集 + * @since 5.2.6 + */ + public static String getCharset(String contentType) { + if (StrUtil.isBlank(contentType)) { + return null; + } + return ReUtil.get(CHARSET_PATTERN, contentType, 1); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/server/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/server/HttpRequest.java new file mode 100644 index 000000000..e10b50507 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/HttpRequest.java @@ -0,0 +1,199 @@ +package cn.hutool.http.server; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.Header; +import cn.hutool.http.HttpUtil; +import cn.hutool.http.Method; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; + +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.Charset; + +/** + * Http请求对象,对{@link HttpExchange}封装 + * + * @author looly + * @since 5.2.6 + */ +public class HttpRequest { + + private final HttpExchange httpExchange; + + /** + * 构造 + * + * @param httpExchange {@link HttpExchange} + */ + public HttpRequest(HttpExchange httpExchange) { + this.httpExchange = httpExchange; + } + + /** + * 获得Http Method + * + * @return Http Method + */ + public String getMethod() { + return this.httpExchange.getRequestMethod(); + } + + /** + * 是否为GET请求 + * + * @return 是否为GET请求 + */ + public boolean isGetMethod() { + return Method.GET.name().equalsIgnoreCase(getMethod()); + } + + /** + * 是否为POST请求 + * + * @return 是否为POST请求 + */ + public boolean isPostMethod() { + return Method.POST.name().equalsIgnoreCase(getMethod()); + } + + /** + * 获得请求URI + * + * @return 请求URI + */ + public URI getURI() { + return this.httpExchange.getRequestURI(); + } + + /** + * 获得请求路径Path + * + * @return 请求路径 + */ + public String getPath() { + return getURI().getPath(); + } + + /** + * 获取请求参数 + * + * @return 参数字符串 + */ + public String getQuery() { + return getURI().getQuery(); + } + + /** + * 获得请求header中的信息 + * + * @return header值 + */ + public Headers getHeaders() { + return this.httpExchange.getRequestHeaders(); + } + + /** + * 获得请求header中的信息 + * + * @param headerKey 头信息的KEY + * @return header值 + */ + public String getHeader(String headerKey) { + return getHeaders().getFirst(headerKey); + } + + /** + * 获得请求header中的信息 + * + * @param headerKey 头信息的KEY + * @param charset 字符集 + * @return header值 + */ + public String getHeader(String headerKey, Charset charset) { + final String header = getHeader(headerKey); + if (null != header) { + return CharsetUtil.convert(header, CharsetUtil.CHARSET_ISO_8859_1, charset); + } + return null; + } + + /** + * 获得User-Agent + * + * @return User-Agent字符串 + */ + public String getUserAgentStr() { + return getHeader("User-Agent"); + } + + /** + * 获得User-Agent,未识别返回null + * + * @return User-Agent字符串,未识别返回null + */ + public UserAgent getUserAgent() { + return UserAgentUtil.parse(getUserAgentStr()); + } + + /** + * 获取请求体的流,流中可以读取请求内容,包括请求表单数据或文件上传数据 + * + * @return 流 + */ + public InputStream getBodyStream() { + return this.httpExchange.getRequestBody(); + } + + /** + * 获取请求体文本,可以是form表单、json、xml等任意内容
    + * 根据请求的Content-Type判断编码,判断失败使用UTF-8编码 + * + * @return 请求 + */ + public String getBody() { + final String contentType = getHeader(Header.CONTENT_TYPE.toString()); + final String charsetStr = HttpUtil.getCharset(contentType); + final Charset charset = CharsetUtil.parse(charsetStr, CharsetUtil.CHARSET_UTF_8); + + return getBody(charset); + } + + /** + * 获取请求体文本,可以是form表单、json、xml等任意内容 + * + * @param charset 编码 + * @return 请求 + */ + public String getBody(Charset charset) { + InputStream in = null; + try { + in = getBodyStream(); + return IoUtil.read(in, charset); + } finally { + IoUtil.close(in); + } + } + + /** + * 是否为Multipart类型表单,此类型表单用于文件上传 + * + * @return 是否为Multipart类型表单,此类型表单用于文件上传 + */ + public boolean isMultipart() { + if (false == isPostMethod()) { + return false; + } + + final String contentType = getHeader(Header.CONTENT_TYPE.toString()); + if (StrUtil.isBlank(contentType)) { + return false; + } + + return contentType.toLowerCase().startsWith("multipart/"); + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentParser.java b/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentParser.java index 6f95ddeed..99cc63938 100644 --- a/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentParser.java +++ b/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentParser.java @@ -1,6 +1,7 @@ package cn.hutool.http.useragent; import cn.hutool.core.util.ReUtil; +import cn.hutool.core.util.StrUtil; import java.util.regex.Pattern; @@ -19,6 +20,9 @@ public class UserAgentParser { * @return {@link UserAgent} */ public static UserAgent parse(String userAgentString) { + if(StrUtil.isBlank(userAgentString)){ + return null; + } final UserAgent userAgent = new UserAgent(); final Browser browser = parseBrowser(userAgentString); From 17be56a99c71bc9346d60789618da5e69fe22f34 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 2 Apr 2020 17:50:07 +0800 Subject: [PATCH 017/157] fix code --- CHANGELOG.md | 3 + .../cn/hutool/core/collection/CollUtil.java | 93 +++++- .../cn/hutool/core/collection/IterUtil.java | 312 ++++++++++-------- .../main/java/cn/hutool/core/net/NetUtil.java | 29 +- .../hutool/core/collection/CollUtilTest.java | 47 +++ .../hutool/core/collection/IterUtilTest.java | 17 +- .../java/cn/hutool/core/net/NetUtilTest.java | 16 + .../cn/hutool/extra/servlet/ServletUtil.java | 54 +-- .../cn/hutool/http/server/HttpRequest.java | 132 +++++++- 9 files changed, 498 insertions(+), 205 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6c1dfaa5..c9532ee0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ * 【script 】 增加createXXXScript,区别单例 * 【core 】 修改FileUtil.writeFileToStream等方法返回值为long * 【core 】 CollUtil.split增加空集合判定(issue#814@Github) +* 【core 】 NetUtil增加parseCookies方法 +* 【core 】 CollUtil增加toMap方法 +* 【core 】 CollUtil和IterUtil废弃一些方法 ### Bug修复 * 【extra 】 修复SpringUtil使用devtools重启报错问题 diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index 5a92c8fa6..adbae5435 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -9,6 +9,7 @@ import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Editor; import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.Matcher; +import cn.hutool.core.lang.func.Func1; import cn.hutool.core.lang.hash.Hash32; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; @@ -245,7 +246,7 @@ public class CollUtil { * * @param coll1 集合1 * @param coll2 集合2 - * @param 元素类型 + * @param 元素类型 * @return 单差集 */ public static Collection subtract(Collection coll1, Collection coll2) { @@ -327,10 +328,10 @@ public class CollUtil { * @param 集合元素类型 * @param collection 集合 * @return {@link Map} - * @see IterUtil#countMap(Iterable) + * @see IterUtil#countMap(Iterator) */ public static Map countMap(Iterable collection) { - return IterUtil.countMap(collection); + return IterUtil.countMap(null == collection ? null : collection.iterator()); } /** @@ -341,10 +342,13 @@ public class CollUtil { * @param iterable {@link Iterable} * @param conjunction 分隔符 * @return 连接后的字符串 - * @see IterUtil#join(Iterable, CharSequence) + * @see IterUtil#join(Iterator, CharSequence) */ public static String join(Iterable iterable, CharSequence conjunction) { - return IterUtil.join(iterable, conjunction); + if (null == iterable) { + return null; + } + return IterUtil.join(iterable.iterator(), conjunction); } /** @@ -355,8 +359,9 @@ public class CollUtil { * @param iterator 集合 * @param conjunction 分隔符 * @return 连接后的字符串 - * @see IterUtil#join(Iterator, CharSequence) + * @deprecated 请使用IterUtil#join(Iterator, CharSequence) */ + @Deprecated public static String join(Iterator iterator, CharSequence conjunction) { return IterUtil.join(iterator, conjunction); } @@ -643,8 +648,8 @@ public class CollUtil { * 新建一个List
    * 提供的参数为null时返回空{@link ArrayList} * - * @param 集合元素类型 - * @param isLinked 是否新建LinkedList + * @param 集合元素类型 + * @param isLinked 是否新建LinkedList * @param enumeration {@link Enumeration} * @return ArrayList对象 * @since 3.0.8 @@ -707,7 +712,7 @@ public class CollUtil { * 新建一个ArrayList
    * 提供的参数为null时返回空{@link ArrayList} * - * @param 集合元素类型 + * @param 集合元素类型 * @param iterator {@link Iterator} * @return ArrayList对象 * @since 3.0.8 @@ -720,7 +725,7 @@ public class CollUtil { * 新建一个ArrayList
    * 提供的参数为null时返回空{@link ArrayList} * - * @param 集合元素类型 + * @param 集合元素类型 * @param enumeration {@link Enumeration} * @return ArrayList对象 * @since 3.0.8 @@ -1204,6 +1209,36 @@ public class CollUtil { return Convert.toList(elementType, fieldValues); } + /** + * 字段值与列表值对应的Map,常用于元素对象中有唯一ID时需要按照这个ID查找对象的情况
    + * 例如:车牌号 =》车 + * + * @param 字段名对应值得类型,不确定请使用Object + * @param 对象类型 + * @param iterable 对象列表 + * @param fieldName 字段名(会通过反射获取其值) + * @return 某个字段值与对象对应Map + * @since 5.0.6 + */ + public static Map fieldValueMap(Iterable iterable, String fieldName) { + return IterUtil.fieldValueMap(null == iterable ? null : iterable.iterator(), fieldName); + } + + /** + * 两个字段值组成新的Map + * + * @param 字段名对应值得类型,不确定请使用Object + * @param 值类型,不确定使用Object + * @param iterable 对象列表 + * @param fieldNameForKey 做为键的字段名(会通过反射获取其值) + * @param fieldNameForValue 做为值的字段名(会通过反射获取其值) + * @return 某个字段值与对象对应Map + * @since 5.0.6 + */ + public static Map fieldValueAsMap(Iterable iterable, String fieldNameForKey, String fieldNameForValue) { + return IterUtil.fieldValueAsMap(null == iterable ? null : iterable.iterator(), fieldNameForKey, fieldNameForValue); + } + /** * 查找第一个匹配元素对象 * @@ -1316,13 +1351,13 @@ public class CollUtil { * 获取匹配规则定义中匹配到元素的所有位置
    * 此方法对于某些无序集合的位置信息,以转换为数组后的位置为准。 * - * @param 元素类型 + * @param 元素类型 * @param collection 集合 - * @param matcher 匹配器,为空则全部匹配 + * @param matcher 匹配器,为空则全部匹配 * @return 位置数组 * @since 5.2.5 */ - public static int[] indexOfAll(Collection collection, Matcher matcher){ + public static int[] indexOfAll(Collection collection, Matcher matcher) { final List indexList = new ArrayList<>(); if (null != collection) { int index = 0; @@ -1718,6 +1753,38 @@ public class CollUtil { return MapUtil.toMapList(listMap); } + /** + * 集合转换为Map,转换规则为:
    + * 按照keyFunc函数规则根据元素对象生成Key,元素作为值 + * + * @param Map键类型 + * @param Map值类型 + * @param values 数据列表 + * @param keyFunc 生成key的函数 + * @return 生成的map + * @since 5.2.6 + */ + public static Map toMap(Iterable values, Map map, Func1 keyFunc) { + return IterUtil.toMap(null == values ? null : values.iterator(), map, keyFunc); + } + + /** + * 集合转换为Map,转换规则为:
    + * 按照keyFunc函数规则根据元素对象生成Key,按照valueFunc函数规则根据元素对象生成value组成新的Map + * + * @param Map键类型 + * @param Map值类型 + * @param 元素类型 + * @param values 数据列表 + * @param map Map对象,转换后的键值对加入此Map,通过传入此对象自定义Map类型 + * @param keyFunc 生成key的函数 + * @return 生成的map + * @since 5.2.6 + */ + public static Map toMap(Iterable values, Map map, Func1 keyFunc, Func1 valueFunc) { + return IterUtil.toMap(null == values ? null : values.iterator(), map, keyFunc, valueFunc); + } + /** * 将指定对象全部加入到集合中
    * 提供的对象如果为集合类型,会自动转换为目标元素类型
    diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java index 9de7ce377..fd5957767 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java @@ -1,17 +1,24 @@ package cn.hutool.core.collection; +import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Filter; +import cn.hutool.core.lang.func.Func1; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; -import java.util.*; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; /** * {@link Iterable} 和 {@link Iterator} 相关工具类 - * + * * @author Looly * @since 3.1.0 */ @@ -19,7 +26,7 @@ public class IterUtil { /** * Iterable是否为空 - * + * * @param iterable Iterable对象 * @return 是否为空 */ @@ -29,7 +36,7 @@ public class IterUtil { /** * Iterator是否为空 - * + * * @param Iterator Iterator对象 * @return 是否为空 */ @@ -39,7 +46,7 @@ public class IterUtil { /** * Iterable是否为空 - * + * * @param iterable Iterable对象 * @return 是否为空 */ @@ -49,7 +56,7 @@ public class IterUtil { /** * Iterator是否为空 - * + * * @param Iterator Iterator对象 * @return 是否为空 */ @@ -59,7 +66,7 @@ public class IterUtil { /** * 是否包含{@code null}元素 - * + * * @param iter 被检查的{@link Iterable}对象,如果为{@code null} 返回true * @return 是否包含{@code null}元素 */ @@ -69,7 +76,7 @@ public class IterUtil { /** * 是否包含{@code null}元素 - * + * * @param iter 被检查的{@link Iterator}对象,如果为{@code null} 返回true * @return 是否包含{@code null}元素 */ @@ -88,7 +95,7 @@ public class IterUtil { /** * 是否全部元素为null - * + * * @param iter iter 被检查的{@link Iterable}对象,如果为{@code null} 返回true * @return 是否全部元素为null * @since 3.3.0 @@ -99,7 +106,7 @@ public class IterUtil { /** * 是否全部元素为null - * + * * @param iter iter 被检查的{@link Iterator}对象,如果为{@code null} 返回true * @return 是否全部元素为null * @since 3.3.0 @@ -124,11 +131,13 @@ public class IterUtil { * a: 1
    * b: 1
    * c: 3
    - * - * @param 集合元素类型 + * + * @param 集合元素类型 * @param iter {@link Iterable},如果为null返回一个空的Map * @return {@link Map} + * @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义,请使用CollUtil.countMap */ + @Deprecated public static Map countMap(Iterable iter) { return countMap(null == iter ? null : iter.iterator()); } @@ -140,8 +149,8 @@ public class IterUtil { * a: 1
    * b: 1
    * c: 3
    - * - * @param 集合元素类型 + * + * @param 集合元素类型 * @param iter {@link Iterator},如果为null返回一个空的Map * @return {@link Map} */ @@ -166,14 +175,16 @@ public class IterUtil { /** * 字段值与列表值对应的Map,常用于元素对象中有唯一ID时需要按照这个ID查找对象的情况
    * 例如:车牌号 =》车 - * - * @param 字段名对应值得类型,不确定请使用Object - * @param 对象类型 - * @param iter 对象列表 + * + * @param 字段名对应值得类型,不确定请使用Object + * @param 对象类型 + * @param iter 对象列表 * @param fieldName 字段名(会通过反射获取其值) * @return 某个字段值与对象对应Map * @since 4.0.4 + * @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义,请使用CollUtil.fieldValueMap */ + @Deprecated public static Map fieldValueMap(Iterable iter, String fieldName) { return fieldValueMap(null == iter ? null : iter.iterator(), fieldName); } @@ -181,71 +192,60 @@ public class IterUtil { /** * 字段值与列表值对应的Map,常用于元素对象中有唯一ID时需要按照这个ID查找对象的情况
    * 例如:车牌号 =》车 - * - * @param 字段名对应值得类型,不确定请使用Object - * @param 对象类型 - * @param iter 对象列表 + * + * @param 字段名对应值得类型,不确定请使用Object + * @param 对象类型 + * @param iter 对象列表 * @param fieldName 字段名(会通过反射获取其值) * @return 某个字段值与对象对应Map * @since 4.0.4 */ @SuppressWarnings("unchecked") public static Map fieldValueMap(Iterator iter, String fieldName) { - final Map result = new HashMap<>(); - if (null != iter) { - V value; - while (iter.hasNext()) { - value = iter.next(); - result.put((K) ReflectUtil.getFieldValue(value, fieldName), value); - } - } - return result; + return toMap(iter, new HashMap<>(), (value)->(K)ReflectUtil.getFieldValue(value, fieldName)); } - + /** * 两个字段值组成新的Map - * - * @param 字段名对应值得类型,不确定请使用Object - * @param 值类型,不确定使用Object - * @param iterable 对象列表 - * @param fieldNameForKey 做为键的字段名(会通过反射获取其值) + * + * @param 字段名对应值得类型,不确定请使用Object + * @param 值类型,不确定使用Object + * @param iterable 对象列表 + * @param fieldNameForKey 做为键的字段名(会通过反射获取其值) * @param fieldNameForValue 做为值的字段名(会通过反射获取其值) * @return 某个字段值与对象对应Map * @since 4.6.2 + * @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义,请使用CollUtil.fieldValueMap */ + @Deprecated public static Map fieldValueAsMap(Iterable iterable, String fieldNameForKey, String fieldNameForValue) { return fieldValueAsMap(null == iterable ? null : iterable.iterator(), fieldNameForKey, fieldNameForValue); } /** * 两个字段值组成新的Map - * - * @param 字段名对应值得类型,不确定请使用Object - * @param 值类型,不确定使用Object - * @param iter 对象列表 - * @param fieldNameForKey 做为键的字段名(会通过反射获取其值) + * + * @param 字段名对应值得类型,不确定请使用Object + * @param 值类型,不确定使用Object + * @param iter 对象列表 + * @param fieldNameForKey 做为键的字段名(会通过反射获取其值) * @param fieldNameForValue 做为值的字段名(会通过反射获取其值) * @return 某个字段值与对象对应Map * @since 4.0.10 */ @SuppressWarnings("unchecked") public static Map fieldValueAsMap(Iterator iter, String fieldNameForKey, String fieldNameForValue) { - final Map result = new HashMap<>(); - if (null != iter) { - Object value; - while (iter.hasNext()) { - value = iter.next(); - result.put((K) ReflectUtil.getFieldValue(value, fieldNameForKey), (V) ReflectUtil.getFieldValue(value, fieldNameForValue)); - } - } - return result; + return toMap(iter, new HashMap<>(), + (value)->(K)ReflectUtil.getFieldValue(value, fieldNameForKey), + (value)->(V)ReflectUtil.getFieldValue(value, fieldNameForValue) + ); } - + /** * 获取指定Bean列表中某个字段,生成新的列表 - * - * @param 对象类型 - * @param iterable 对象列表 + * + * @param 对象类型 + * @param iterable 对象列表 * @param fieldName 字段名(会通过反射获取其值) * @return 某个字段值与对象对应Map * @since 4.6.2 @@ -256,9 +256,9 @@ public class IterUtil { /** * 获取指定Bean列表中某个字段,生成新的列表 - * - * @param 对象类型 - * @param iter 对象列表 + * + * @param 对象类型 + * @param iter 对象列表 * @param fieldName 字段名(会通过反射获取其值) * @return 某个字段值与对象对应Map * @since 4.0.10 @@ -277,27 +277,29 @@ public class IterUtil { /** * 以 conjunction 为分隔符将集合转换为字符串 - * - * @param 集合元素类型 - * @param iterable {@link Iterable} + * + * @param 集合元素类型 + * @param iterable {@link Iterable} * @param conjunction 分隔符 * @return 连接后的字符串 + * @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义,请使用CollUtil.join */ + @Deprecated public static String join(Iterable iterable, CharSequence conjunction) { if (null == iterable) { return null; } return join(iterable.iterator(), conjunction); } - + /** * 以 conjunction 为分隔符将集合转换为字符串 - * - * @param 集合元素类型 - * @param iterable {@link Iterable} + * + * @param 集合元素类型 + * @param iterable {@link Iterable} * @param conjunction 分隔符 - * @param prefix 每个元素添加的前缀,null表示不添加 - * @param suffix 每个元素添加的后缀,null表示不添加 + * @param prefix 每个元素添加的前缀,null表示不添加 + * @param suffix 每个元素添加的后缀,null表示不添加 * @return 连接后的字符串 * @since 4.0.10 */ @@ -307,13 +309,13 @@ public class IterUtil { } return join(iterable.iterator(), conjunction, prefix, suffix); } - + /** * 以 conjunction 为分隔符将集合转换为字符串
    * 如果集合元素为数组、{@link Iterable}或{@link Iterator},则递归组合其为字符串 - * - * @param 集合元素类型 - * @param iterator 集合 + * + * @param 集合元素类型 + * @param iterator 集合 * @param conjunction 分隔符 * @return 连接后的字符串 */ @@ -324,12 +326,12 @@ public class IterUtil { /** * 以 conjunction 为分隔符将集合转换为字符串
    * 如果集合元素为数组、{@link Iterable}或{@link Iterator},则递归组合其为字符串 - * - * @param 集合元素类型 - * @param iterator 集合 + * + * @param 集合元素类型 + * @param iterator 集合 * @param conjunction 分隔符 - * @param prefix 每个元素添加的前缀,null表示不添加 - * @param suffix 每个元素添加的后缀,null表示不添加 + * @param prefix 每个元素添加的前缀,null表示不添加 + * @param suffix 每个元素添加的后缀,null表示不添加 * @return 连接后的字符串 * @since 4.0.10 */ @@ -364,9 +366,9 @@ public class IterUtil { /** * 将Entry集合转换为HashMap - * - * @param 键类型 - * @param 值类型 + * + * @param 键类型 + * @param 值类型 * @param entryIter entry集合 * @return Map */ @@ -379,15 +381,15 @@ public class IterUtil { } return map; } - + /** * 将键列表和值列表转换为Map
    * 以键为准,值与键位置需对应。如果键元素数多于值元素,多余部分值用null代替。
    * 如果值多于键,忽略多余的值。 - * - * @param 键类型 - * @param 值类型 - * @param keys 键列表 + * + * @param 键类型 + * @param 值类型 + * @param keys 键列表 * @param values 值列表 * @return 标题内容Map * @since 3.1.0 @@ -400,11 +402,11 @@ public class IterUtil { * 将键列表和值列表转换为Map
    * 以键为准,值与键位置需对应。如果键元素数多于值元素,多余部分值用null代替。
    * 如果值多于键,忽略多余的值。 - * - * @param 键类型 - * @param 值类型 - * @param keys 键列表 - * @param values 值列表 + * + * @param 键类型 + * @param 值类型 + * @param keys 键列表 + * @param values 值列表 * @param isOrder 是否有序 * @return 标题内容Map * @since 4.1.12 @@ -412,15 +414,15 @@ public class IterUtil { public static Map toMap(Iterable keys, Iterable values, boolean isOrder) { return toMap(null == keys ? null : keys.iterator(), null == values ? null : values.iterator(), isOrder); } - + /** * 将键列表和值列表转换为Map
    * 以键为准,值与键位置需对应。如果键元素数多于值元素,多余部分值用null代替。
    * 如果值多于键,忽略多余的值。 - * - * @param 键类型 - * @param 值类型 - * @param keys 键列表 + * + * @param 键类型 + * @param 值类型 + * @param keys 键列表 * @param values 值列表 * @return 标题内容Map * @since 3.1.0 @@ -433,11 +435,11 @@ public class IterUtil { * 将键列表和值列表转换为Map
    * 以键为准,值与键位置需对应。如果键元素数多于值元素,多余部分值用null代替。
    * 如果值多于键,忽略多余的值。 - * - * @param 键类型 - * @param 值类型 - * @param keys 键列表 - * @param values 值列表 + * + * @param 键类型 + * @param 值类型 + * @param keys 键列表 + * @param values 值列表 * @param isOrder 是否有序 * @return 标题内容Map * @since 4.1.12 @@ -455,14 +457,14 @@ public class IterUtil { /** * Iterator转List
    * 不判断,直接生成新的List - * - * @param 元素类型 + * + * @param 元素类型 * @param iter {@link Iterator} * @return List * @since 4.0.6 */ public static List toList(Iterable iter) { - if(null == iter) { + if (null == iter) { return null; } return toList(iter.iterator()); @@ -471,8 +473,8 @@ public class IterUtil { /** * Iterator转List
    * 不判断,直接生成新的List - * - * @param 元素类型 + * + * @param 元素类型 * @param iter {@link Iterator} * @return List * @since 4.0.6 @@ -489,9 +491,9 @@ public class IterUtil { * Enumeration转换为Iterator *

    * Adapt the specified Enumeration to the Iterator interface - * + * * @param 集合元素类型 - * @param e {@link Enumeration} + * @param e {@link Enumeration} * @return {@link Iterator} */ public static Iterator asIterator(Enumeration e) { @@ -500,8 +502,8 @@ public class IterUtil { /** * {@link Iterator} 转为 {@link Iterable} - * - * @param 元素类型 + * + * @param 元素类型 * @param iter {@link Iterator} * @return {@link Iterable} */ @@ -511,8 +513,8 @@ public class IterUtil { /** * 获取集合的第一个元素 - * - * @param 集合元素类型 + * + * @param 集合元素类型 * @param iterable {@link Iterable} * @return 第一个元素 */ @@ -525,8 +527,8 @@ public class IterUtil { /** * 获取集合的第一个元素 - * - * @param 集合元素类型 + * + * @param 集合元素类型 * @param iterator {@link Iterator} * @return 第一个元素 */ @@ -540,7 +542,7 @@ public class IterUtil { /** * 获得{@link Iterable}对象的元素类型(通过第一个非空元素判断)
    * 注意,此方法至少会调用多次next方法 - * + * * @param iterable {@link Iterable} * @return 元素类型,当列表为空或元素全部为null时,返回null */ @@ -555,7 +557,7 @@ public class IterUtil { /** * 获得{@link Iterator}对象的元素类型(通过第一个非空元素判断)
    * 注意,此方法至少会调用多次next方法 - * + * * @param iterator {@link Iterator} * @return 元素类型,当列表为空或元素全部为null时,返回null */ @@ -569,42 +571,42 @@ public class IterUtil { } return null; } - + /** * 过滤集合,此方法在原集合上直接修改
    * 通过实现Filter接口,完成元素的过滤,这个Filter实现可以实现以下功能: - * + * *

     	 * 1、过滤出需要的对象,{@link Filter#accept(Object)}方法返回false的对象将被使用{@link Iterator#remove()}方法移除
     	 * 
    - * - * @param 集合类型 - * @param 集合元素类型 - * @param iter 集合 + * + * @param 集合类型 + * @param 集合元素类型 + * @param iter 集合 * @param filter 过滤器接口 * @return 编辑后的集合 * @since 4.6.5 */ public static , E> T filter(T iter, Filter filter) { - if(null == iter) { + if (null == iter) { return null; } - + filter(iter.iterator(), filter); - + return iter; } - + /** * 过滤集合,此方法在原集合上直接修改
    * 通过实现Filter接口,完成元素的过滤,这个Filter实现可以实现以下功能: - * + * *
     	 * 1、过滤出需要的对象,{@link Filter#accept(Object)}方法返回false的对象将被使用{@link Iterator#remove()}方法移除
     	 * 
    - * - * @param 集合元素类型 - * @param iter 集合 + * + * @param 集合元素类型 + * @param iter 集合 * @param filter 过滤器接口 * @return 编辑后的集合 * @since 4.6.5 @@ -614,11 +616,61 @@ public class IterUtil { return iter; } - while(iter.hasNext()) { - if(false == filter.accept(iter.next())) { + while (iter.hasNext()) { + if (false == filter.accept(iter.next())) { iter.remove(); } } return iter; } + + /** + * Iterator转换为Map,转换规则为:
    + * 按照keyFunc函数规则根据元素对象生成Key,元素作为值 + * + * @param Map键类型 + * @param Map值类型 + * @param iterator 数据列表 + * @param map Map对象,转换后的键值对加入此Map,通过传入此对象自定义Map类型 + * @param keyFunc 生成key的函数 + * @return 生成的map + * @since 5.2.6 + */ + public static Map toMap(Iterator iterator, Map map, Func1 keyFunc) { + return toMap(iterator, map, keyFunc, (value) -> value); + } + + /** + * 集合转换为Map,转换规则为:
    + * 按照keyFunc函数规则根据元素对象生成Key,按照valueFunc函数规则根据元素对象生成value组成新的Map + * + * @param Map键类型 + * @param Map值类型 + * @param 元素类型 + * @param iterator 数据列表 + * @param map Map对象,转换后的键值对加入此Map,通过传入此对象自定义Map类型 + * @param keyFunc 生成key的函数 + * @return 生成的map + * @since 5.2.6 + */ + public static Map toMap(Iterator iterator, Map map, Func1 keyFunc, Func1 valueFunc) { + if (null == iterator) { + return map; + } + + if (null == map) { + map = MapUtil.newHashMap(true); + } + + E element; + while (iterator.hasNext()) { + element = iterator.next(); + try { + map.put(keyFunc.call(element), valueFunc.call(element)); + } catch (Exception e) { + throw new UtilException(e); + } + } + return map; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java index 36e503915..74a355455 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java @@ -12,6 +12,7 @@ import cn.hutool.core.util.StrUtil; import java.io.IOException; import java.io.OutputStream; import java.net.DatagramSocket; +import java.net.HttpCookie; import java.net.IDN; import java.net.Inet4Address; import java.net.Inet6Address; @@ -29,6 +30,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import java.util.TreeSet; @@ -647,7 +649,7 @@ public class NetUtil { if (ip != null && ip.indexOf(",") > 0) { final String[] ips = ip.trim().split(","); for (String subIp : ips) { - if (false == isUnknow(subIp)) { + if (false == isUnknown(subIp)) { ip = subIp; break; } @@ -662,8 +664,20 @@ public class NetUtil { * @param checkString 被检测的字符串 * @return 是否未知 * @since 4.4.1 + * @deprecated 拼写错误,请使用{@link #isUnknown(String)} */ public static boolean isUnknow(String checkString) { + return isUnknown(checkString); + } + + /** + * 检测给定字符串是否为未知,多用于检测HTTP请求相关
    + * + * @param checkString 被检测的字符串 + * @return 是否未知 + * @since 5.2.6 + */ + public static boolean isUnknown(String checkString) { return StrUtil.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); } @@ -692,6 +706,19 @@ public class NetUtil { } } + /** + * 解析Cookie信息 + * + * @param cookieStr Cookie字符串 + * @return cookie字符串 + * @since 5.2.6 + */ + public static List parseCookies(String cookieStr){ + if(StrUtil.isBlank(cookieStr)){ + return CollUtil.newArrayList(); + } + return HttpCookie.parse(cookieStr); + } // ----------------------------------------------------------------------------------------- Private method start /** diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java index d473f2f58..85427b99a 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java @@ -277,6 +277,32 @@ public class CollUtilTest { Assert.assertEquals("张三", list.get(2).getName()); } + @Test + public void fieldValueMapTest() { + List list = CollUtil.newArrayList(new TestBean("张三", 12, DateUtil.parse("2018-05-01")), // + new TestBean("李四", 13, DateUtil.parse("2018-03-01")), // + new TestBean("王五", 12, DateUtil.parse("2018-04-01"))// + ); + + final Map map = CollUtil.fieldValueMap(list, "name"); + Assert.assertEquals("李四", map.get("李四").getName()); + Assert.assertEquals("王五", map.get("王五").getName()); + Assert.assertEquals("张三", map.get("张三").getName()); + } + + @Test + public void fieldValueAsMapTest() { + List list = CollUtil.newArrayList(new TestBean("张三", 12, DateUtil.parse("2018-05-01")), // + new TestBean("李四", 13, DateUtil.parse("2018-03-01")), // + new TestBean("王五", 14, DateUtil.parse("2018-04-01"))// + ); + + final Map map = CollUtil.fieldValueAsMap(list, "name", "age"); + Assert.assertEquals(new Integer(12), map.get("张三")); + Assert.assertEquals(new Integer(13), map.get("李四")); + Assert.assertEquals(new Integer(14), map.get("王五")); + } + public static class TestBean { private String name; private int age; @@ -600,4 +626,25 @@ public class CollUtilTest { Assert.assertEquals(3, map.get("c").intValue()); Assert.assertEquals(4, map.get("d").intValue()); } + + @Test + public void toMapTest(){ + Collection keys = CollUtil.newArrayList("a", "b", "c", "d"); + final Map map = CollUtil.toMap(keys, new HashMap<>(), (value)->"key" + value); + Assert.assertEquals("a", map.get("keya")); + Assert.assertEquals("b", map.get("keyb")); + Assert.assertEquals("c", map.get("keyc")); + Assert.assertEquals("d", map.get("keyd")); + } + + @Test + public void countMapTest() { + ArrayList list = CollUtil.newArrayList("a", "b", "c", "c", "a", "b", "d"); + Map countMap = CollUtil.countMap(list); + + Assert.assertEquals(Integer.valueOf(2), countMap.get("a")); + Assert.assertEquals(Integer.valueOf(2), countMap.get("b")); + Assert.assertEquals(Integer.valueOf(2), countMap.get("c")); + Assert.assertEquals(Integer.valueOf(1), countMap.get("d")); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java index 11351f461..f5eabd172 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java @@ -1,11 +1,11 @@ package cn.hutool.core.collection; -import java.util.ArrayList; -import java.util.Map; - import org.junit.Assert; import org.junit.Test; +import java.util.ArrayList; +import java.util.Map; + /** * {@link IterUtil} 单元测试 * @author looly @@ -13,17 +13,6 @@ import org.junit.Test; */ public class IterUtilTest { - @Test - public void countMapTest() { - ArrayList list = CollUtil.newArrayList("a", "b", "c", "c", "a", "b", "d"); - Map countMap = IterUtil.countMap(list); - - Assert.assertEquals(Integer.valueOf(2), countMap.get("a")); - Assert.assertEquals(Integer.valueOf(2), countMap.get("b")); - Assert.assertEquals(Integer.valueOf(2), countMap.get("c")); - Assert.assertEquals(Integer.valueOf(1), countMap.get("d")); - } - @Test public void fieldValueMapTest() { ArrayList carList = CollUtil.newArrayList(new Car("123", "大众"), new Car("345", "奔驰"), new Car("567", "路虎")); diff --git a/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java index 32a16a7ad..0d6b8a87e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java @@ -6,7 +6,9 @@ import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; +import java.net.HttpCookie; import java.net.InetAddress; +import java.util.List; /** * NetUtil单元测试 @@ -57,4 +59,18 @@ public class NetUtilTest { public void isUsableLocalPortTest(){ Assert.assertTrue(NetUtil.isUsableLocalPort(80)); } + + @Test + public void parseCookiesTest(){ + String cookieStr = "cookieName=\"cookieValue\";Path=\"/\";Domain=\"cookiedomain.com\""; + final List httpCookies = NetUtil.parseCookies(cookieStr); + Assert.assertEquals(1, httpCookies.size()); + + final HttpCookie httpCookie = httpCookies.get(0); + Assert.assertEquals(0, httpCookie.getVersion()); + Assert.assertEquals("cookieName", httpCookie.getName()); + Assert.assertEquals("cookieValue", httpCookie.getValue()); + Assert.assertEquals("/", httpCookie.getPath()); + Assert.assertEquals("cookiedomain.com", httpCookie.getDomain()); + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java index 70afe80fb..f91530be4 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java @@ -3,11 +3,14 @@ package cn.hutool.extra.servlet; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.bean.copier.ValueProvider; +import cn.hutool.core.collection.ArrayIter; +import cn.hutool.core.collection.IterUtil; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.net.NetUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; @@ -227,13 +230,13 @@ public class ServletUtil { String ip; for (String header : headerNames) { ip = request.getHeader(header); - if (false == isUnknow(ip)) { - return getMultistageReverseProxyIp(ip); + if (false == NetUtil.isUnknown(ip)) { + return NetUtil.getMultistageReverseProxyIp(ip); } } ip = request.getRemoteAddr(); - return getMultistageReverseProxyIp(ip); + return NetUtil.getMultistageReverseProxyIp(ip); } /** @@ -415,14 +418,10 @@ public class ServletUtil { * @return Cookie map */ public static Map readCookieMap(HttpServletRequest httpServletRequest) { - final Map cookieMap = new CaseInsensitiveMap<>(); - final Cookie[] cookies = httpServletRequest.getCookies(); - if (null != cookies) { - for (Cookie cookie : cookies) { - cookieMap.put(cookie.getName(), cookie); - } - } - return cookieMap; + return IterUtil.toMap( + new ArrayIter<>(httpServletRequest.getCookies()), + new CaseInsensitiveMap<>(), + Cookie::getName); } /** @@ -614,37 +613,4 @@ public class ServletUtil { } } // --------------------------------------------------------- Response end - - // --------------------------------------------------------- Private methd start - /** - * 从多级反向代理中获得第一个非unknown IP地址 - * - * @param ip 获得的IP地址 - * @return 第一个非unknown IP地址 - */ - private static String getMultistageReverseProxyIp(String ip) { - // 多级反向代理检测 - if (ip != null && ip.indexOf(",") > 0) { - final String[] ips = ip.trim().split(","); - for (String subIp : ips) { - if (false == isUnknow(subIp)) { - ip = subIp; - break; - } - } - } - return ip; - } - - /** - * 检测给定字符串是否为未知,多用于检测HTTP请求相关
    - * - * @param checkString 被检测的字符串 - * @return 是否未知 - */ - private static boolean isUnknow(String checkString) { - return StrUtil.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); - } - // --------------------------------------------------------- Private methd end - } diff --git a/hutool-http/src/main/java/cn/hutool/http/server/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/server/HttpRequest.java index e10b50507..116b83560 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/HttpRequest.java @@ -1,6 +1,10 @@ package cn.hutool.http.server; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.Header; @@ -12,8 +16,11 @@ import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; import java.io.InputStream; +import java.net.HttpCookie; import java.net.URI; import java.nio.charset.Charset; +import java.util.Collection; +import java.util.Map; /** * Http请求对象,对{@link HttpExchange}封装 @@ -25,6 +32,8 @@ public class HttpRequest { private final HttpExchange httpExchange; + private Map cookieCache; + /** * 构造 * @@ -97,6 +106,16 @@ public class HttpRequest { return this.httpExchange.getRequestHeaders(); } + /** + * 获得请求header中的信息 + * + * @param headerKey 头信息的KEY + * @return header值 + */ + public String getHeader(Header headerKey) { + return getHeader(headerKey.toString()); + } + /** * 获得请求header中的信息 * @@ -122,13 +141,22 @@ public class HttpRequest { return null; } + /** + * 获取Content-Type头信息 + * + * @return Content-Type头信息 + */ + public String getContentType() { + return getHeader(Header.USER_AGENT); + } + /** * 获得User-Agent * * @return User-Agent字符串 */ public String getUserAgentStr() { - return getHeader("User-Agent"); + return getHeader(Header.USER_AGENT); } /** @@ -140,6 +168,49 @@ public class HttpRequest { return UserAgentUtil.parse(getUserAgentStr()); } + /** + * 获得Cookie信息字符串 + * + * @return cookie字符串 + */ + public String getCookiesStr() { + return getHeader(Header.COOKIE); + } + + /** + * 获得Cookie信息列表 + * + * @return Cookie信息列表 + */ + public Collection getCookies() { + return getCookieMap().values(); + } + + /** + * 获得Cookie信息Map,键为Cookie名,值为HttpCookie对象 + * + * @return Cookie信息Map + */ + public Map getCookieMap() { + if (null == this.cookieCache) { + cookieCache = CollUtil.toMap( + NetUtil.parseCookies(getCookiesStr()), + new CaseInsensitiveMap<>(), + HttpCookie::getName); + } + return cookieCache; + } + + /** + * 获得指定Cookie名对应的HttpCookie对象 + * + * @param cookieName Cookie名 + * @return HttpCookie对象 + */ + public HttpCookie getCookie(String cookieName) { + return getCookieMap().get(cookieName); + } + /** * 获取请求体的流,流中可以读取请求内容,包括请求表单数据或文件上传数据 * @@ -156,7 +227,7 @@ public class HttpRequest { * @return 请求 */ public String getBody() { - final String contentType = getHeader(Header.CONTENT_TYPE.toString()); + final String contentType = getContentType(); final String charsetStr = HttpUtil.getCharset(contentType); final Charset charset = CharsetUtil.parse(charsetStr, CharsetUtil.CHARSET_UTF_8); @@ -189,11 +260,66 @@ public class HttpRequest { return false; } - final String contentType = getHeader(Header.CONTENT_TYPE.toString()); + final String contentType = getContentType(); if (StrUtil.isBlank(contentType)) { return false; } return contentType.toLowerCase().startsWith("multipart/"); } + + /** + * 获取客户端IP + * + *

    + * 默认检测的Header: + * + *

    +	 * 1、X-Forwarded-For
    +	 * 2、X-Real-IP
    +	 * 3、Proxy-Client-IP
    +	 * 4、WL-Proxy-Client-IP
    +	 * 
    + * + *

    + * otherHeaderNames参数用于自定义检测的Header
    + * 需要注意的是,使用此方法获取的客户IP地址必须在Http服务器(例如Nginx)中配置头信息,否则容易造成IP伪造。 + *

    + * + * @param otherHeaderNames 其他自定义头文件,通常在Http服务器(例如Nginx)中配置 + * @return IP地址 + */ + public String getClientIP(String... otherHeaderNames) { + String[] headers = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"}; + if (ArrayUtil.isNotEmpty(otherHeaderNames)) { + headers = ArrayUtil.addAll(headers, otherHeaderNames); + } + + return getClientIPByHeader(headers); + } + + /** + * 获取客户端IP + * + *

    + * headerNames参数用于自定义检测的Header
    + * 需要注意的是,使用此方法获取的客户IP地址必须在Http服务器(例如Nginx)中配置头信息,否则容易造成IP伪造。 + *

    + * + * @param headerNames 自定义头,通常在Http服务器(例如Nginx)中配置 + * @return IP地址 + * @since 4.4.1 + */ + public String getClientIPByHeader(String... headerNames) { + String ip; + for (String header : headerNames) { + ip = getHeader(header); + if (false == NetUtil.isUnknown(ip)) { + return NetUtil.getMultistageReverseProxyIp(ip); + } + } + + ip = this.httpExchange.getRemoteAddress().getHostName(); + return NetUtil.getMultistageReverseProxyIp(ip); + } } From f2b44605d0ef14e75c3289b089687af064033a00 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 2 Apr 2020 18:21:22 +0800 Subject: [PATCH 018/157] add code --- .../cn/hutool/http/server/HttpServerBase.java | 32 +++++++ ...ttpRequest.java => HttpServerRequest.java} | 8 +- .../http/server/HttpServerResponse.java | 89 +++++++++++++++++++ .../cn/hutool/http/server/SimpleServer.java | 30 ++++++- .../cn/hutool/http/server/action/Action.java | 22 +++++ .../http/server/handler/ActionHandler.java | 32 +++++++ 6 files changed, 205 insertions(+), 8 deletions(-) create mode 100644 hutool-http/src/main/java/cn/hutool/http/server/HttpServerBase.java rename hutool-http/src/main/java/cn/hutool/http/server/{HttpRequest.java => HttpServerRequest.java} (93%) create mode 100644 hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java create mode 100644 hutool-http/src/main/java/cn/hutool/http/server/action/Action.java create mode 100644 hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java diff --git a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerBase.java b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerBase.java new file mode 100644 index 000000000..5cf8ecb81 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerBase.java @@ -0,0 +1,32 @@ +package cn.hutool.http.server; + +import com.sun.net.httpserver.HttpExchange; + +/** + * HttpServer公用对象,提供HttpExchange包装和公用方法 + * + * @author looly + * @since 5.2.6 + */ +public class HttpServerBase { + + final HttpExchange httpExchange; + + /** + * 构造 + * + * @param httpExchange {@link HttpExchange} + */ + public HttpServerBase(HttpExchange httpExchange) { + this.httpExchange = httpExchange; + } + + /** + * 获取{@link HttpExchange}对象 + * + * @return {@link HttpExchange}对象 + */ + public HttpExchange getHttpExchange() { + return this.httpExchange; + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/server/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java similarity index 93% rename from hutool-http/src/main/java/cn/hutool/http/server/HttpRequest.java rename to hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java index 116b83560..58062c6aa 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java @@ -28,9 +28,7 @@ import java.util.Map; * @author looly * @since 5.2.6 */ -public class HttpRequest { - - private final HttpExchange httpExchange; +public class HttpServerRequest extends HttpServerBase{ private Map cookieCache; @@ -39,8 +37,8 @@ public class HttpRequest { * * @param httpExchange {@link HttpExchange} */ - public HttpRequest(HttpExchange httpExchange) { - this.httpExchange = httpExchange; + public HttpServerRequest(HttpExchange httpExchange) { + super(httpExchange); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java new file mode 100644 index 000000000..23fb29c58 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java @@ -0,0 +1,89 @@ +package cn.hutool.http.server; + +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import com.sun.net.httpserver.HttpExchange; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Http响应对象,用于写出数据到客户端 + */ +public class HttpServerResponse extends HttpServerBase{ + + /** + * 构造 + * + * @param httpExchange {@link HttpExchange} + */ + public HttpServerResponse(HttpExchange httpExchange) { + super(httpExchange); + } + + /** + * 发送HTTP状态码 + * + * @param httpStatusCode HTTP状态码,见HttpStatus + * @return this + */ + public HttpServerResponse send(int httpStatusCode) { + return send(httpStatusCode, 0); + } + + /** + * 发送HTTP状态码 + * + * @param httpStatusCode HTTP状态码,见HttpStatus + * @param bodyLength 响应体长度,默认0 + * @return this + */ + public HttpServerResponse send(int httpStatusCode, long bodyLength) { + try { + this.httpExchange.sendResponseHeaders(httpStatusCode, bodyLength); + } catch (IOException e) { + throw new IORuntimeException(e); + } + return this; + } + + /** + * 获取响应数据流 + * + * @return 响应数据流 + */ + public OutputStream getOut() { + return this.httpExchange.getResponseBody(); + } + + /** + * 写出数据到客户端 + * + * @param data 数据 + * @return this + */ + public HttpServerResponse write(byte[] data) { + write(new ByteArrayInputStream(data)); + return this; + } + + /** + * 写出数据到客户端 + * + * @param in 数据流 + * @return this + */ + public HttpServerResponse write(InputStream in) { + OutputStream out = null; + try { + out = getOut(); + IoUtil.copy(in, out); + } finally { + IoUtil.close(out); + IoUtil.close(in); + } + return this; + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java b/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java index 90c587a69..21d62fd1f 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java @@ -1,6 +1,9 @@ package cn.hutool.http.server; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.http.server.action.Action; +import cn.hutool.http.server.handler.ActionHandler; +import cn.hutool.http.server.handler.RootHandler; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; @@ -53,7 +56,7 @@ public class SimpleServer { /** * 增加请求处理规则 * - * @param path 路径 + * @param path 路径 * @param handler 处理器 * @return this */ @@ -62,6 +65,27 @@ public class SimpleServer { return this; } + /** + * 设置根目录,默认的页面从root目录中读取解析返回 + * + * @param root 路径 + * @return this + */ + public SimpleServer setRoot(String root) { + return addHandler("/", new RootHandler(root)); + } + + /** + * 增加请求处理规则 + * + * @param path 路径 + * @param action 处理器 + * @return this + */ + public SimpleServer addAction(String path, Action action) { + return addHandler(path, new ActionHandler(action)); + } + /** * 设置自定义线程池 * @@ -78,7 +102,7 @@ public class SimpleServer { * * @return {@link HttpServer} */ - public HttpServer getRawServer(){ + public HttpServer getRawServer() { return this.server; } @@ -87,7 +111,7 @@ public class SimpleServer { * * @return {@link InetSocketAddress} */ - public InetSocketAddress getAddress(){ + public InetSocketAddress getAddress() { return this.server.getAddress(); } diff --git a/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java b/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java new file mode 100644 index 000000000..2ef304eef --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java @@ -0,0 +1,22 @@ +package cn.hutool.http.server.action; + +import cn.hutool.http.server.HttpServerRequest; +import cn.hutool.http.server.HttpServerResponse; + +/** + * 请求处理接口
    + * 当用户请求某个Path,则调用相应Action的doAction方法 + * + * @author Looly + * @since 5.2.6 + */ +public interface Action { + + /** + * 处理请求 + * + * @param request 请求对象 + * @param response 响应对象 + */ + void doAction(HttpServerRequest request, HttpServerResponse response); +} diff --git a/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java b/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java new file mode 100644 index 000000000..94a51d004 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java @@ -0,0 +1,32 @@ +package cn.hutool.http.server.handler; + +import cn.hutool.http.server.HttpServerRequest; +import cn.hutool.http.server.HttpServerResponse; +import cn.hutool.http.server.action.Action; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +/** + * Action处理器,用于将HttpHandler转换为Action形式 + * + * @author looly + * @since 5.2.6 + */ +public class ActionHandler implements HttpHandler { + + private final Action action; + + /** + * 构造 + * + * @param action Action + */ + public ActionHandler(Action action) { + this.action = action; + } + + @Override + public void handle(HttpExchange httpExchange) { + action.doAction(new HttpServerRequest(httpExchange), new HttpServerResponse(httpExchange)); + } +} From 3921a568dd8745fbe02f124e48bbd508275397e4 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 3 Apr 2020 12:13:56 +0800 Subject: [PATCH 019/157] add ValidateObjectInputStream --- CHANGELOG.md | 1 + .../cn/hutool/core/collection/CollUtil.java | 14 ++ .../cn/hutool/core/collection/ListUtil.java | 12 ++ .../main/java/cn/hutool/core/io/IoUtil.java | 24 ++- .../core/io/ValidateObjectInputStream.java | 53 ++++++ .../main/java/cn/hutool/core/map/MapUtil.java | 17 +- .../hutool/http/server/HttpServerRequest.java | 30 +++- .../http/server/HttpServerResponse.java | 165 +++++++++++++++++- 8 files changed, 299 insertions(+), 17 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/io/ValidateObjectInputStream.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c9532ee0a..80db6fe1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ * 【core 】 NetUtil增加parseCookies方法 * 【core 】 CollUtil增加toMap方法 * 【core 】 CollUtil和IterUtil废弃一些方法 +* 【core 】 添加ValidateObjectInputStream避免对象反序列化漏洞风险 ### Bug修复 * 【extra 】 修复SpringUtil使用devtools重启报错问题 diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index adbae5435..d5cf28afa 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -2348,7 +2348,9 @@ public class CollUtil { * @param Value类型 * @param map {@link Map} * @param kvConsumer {@link KVConsumer} 遍历的每条数据处理器 + * @deprecated JDK8+中使用map.forEach */ + @Deprecated public static void forEach(Map map, KVConsumer kvConsumer) { int index = 0; for (Entry entry : map.entrySet()) { @@ -2527,6 +2529,18 @@ public class CollUtil { return Collections.min(coll); } + /** + * 转为只读集合 + * + * @param 元素类型 + * @param c 集合 + * @return 只读集合 + * @since 5.2.6 + */ + public static Collection unmodifiable(Collection c) { + return Collections.unmodifiableCollection(c); + } + // ---------------------------------------------------------------------------------------------- Interface start /** diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java index 5579dcec9..104f7c9eb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java @@ -444,4 +444,16 @@ public class ListUtil { } return Convert.convert(int[].class, indexList); } + + /** + * 将对应List转换为不可修改的List + * + * @param list Map + * @param 元素类型 + * @return 不修改Map + * @since 5.2.6 + */ + public static List unmodifiable(List list) { + return Collections.unmodifiableList(list); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java index 6d1d44dae..06c2ea032 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java @@ -636,24 +636,38 @@ public class IoUtil { } /** - * 从流中读取内容,读到输出流中 + * 从流中读取对象,即对象的反序列化 * * @param 读取对象的类型 * @param in 输入流 * @return 输出流 * @throws IORuntimeException IO异常 * @throws UtilException ClassNotFoundException包装 + * @deprecated 由于存在对象反序列化漏洞风险,请使用{@link #readObj(InputStream, Class)} */ + @Deprecated public static T readObj(InputStream in) throws IORuntimeException, UtilException { + return readObj(in, null); + } + + /** + * 从流中读取对象,即对象的反序列化,读取后不关闭流 + * + * @param 读取对象的类型 + * @param in 输入流 + * @return 输出流 + * @throws IORuntimeException IO异常 + * @throws UtilException ClassNotFoundException包装 + */ + public static T readObj(InputStream in, Class clazz) throws IORuntimeException, UtilException { if (in == null) { throw new IllegalArgumentException("The InputStream must not be null"); } ObjectInputStream ois; try { - ois = new ObjectInputStream(in); - @SuppressWarnings("unchecked") // may fail with CCE if serialised form is incorrect - final T obj = (T) ois.readObject(); - return obj; + ois = new ValidateObjectInputStream(in, clazz); + //noinspection unchecked + return (T) ois.readObject(); } catch (IOException e) { throw new IORuntimeException(e); } catch (ClassNotFoundException e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/io/ValidateObjectInputStream.java b/hutool-core/src/main/java/cn/hutool/core/io/ValidateObjectInputStream.java new file mode 100644 index 000000000..ae077f412 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/ValidateObjectInputStream.java @@ -0,0 +1,53 @@ +package cn.hutool.core.io; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InvalidClassException; +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; + +/** + * 带有类验证的对象流,用于避免反序列化漏洞
    + * 详细见:https://xz.aliyun.com/t/41/ + * + * @author looly + * @since 5.2.6 + */ +public class ValidateObjectInputStream extends ObjectInputStream { + + private Class acceptClass; + + /** + * 构造 + * + * @param inputStream 流 + * @param acceptClass 接受的类 + * @throws IOException IO异常 + */ + public ValidateObjectInputStream(InputStream inputStream, Class acceptClass) throws IOException { + super(inputStream); + this.acceptClass = acceptClass; + } + + /** + * 接受反序列化的类,用于反序列化验证 + * + * @param acceptClass 接受反序列化的类 + */ + public void accept(Class acceptClass) { + this.acceptClass = acceptClass; + } + + /** + * 只允许反序列化SerialObject class + */ + @Override + protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { + if (null != this.acceptClass && false == desc.getName().equals(acceptClass.getName())) { + throw new InvalidClassException( + "Unauthorized deserialization attempt", + desc.getName()); + } + return super.resolveClass(desc); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java index 80a3523a6..39199103b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java @@ -560,7 +560,7 @@ public class MapUtil { public static String join(Map map, String separator, String keyValueSeparator, boolean isIgnoreNull, String... otherParams) { final StringBuilder strBuilder = StrUtil.builder(); boolean isFirst = true; - if(isNotEmpty(map)){ + if (isNotEmpty(map)) { for (Entry entry : map.entrySet()) { if (false == isIgnoreNull || entry.getKey() != null && entry.getValue() != null) { if (isFirst) { @@ -733,7 +733,7 @@ public class MapUtil { * @since 4.0.1 */ public static TreeMap sort(Map map, Comparator comparator) { - if(null == map){ + if (null == map) { return null; } @@ -777,6 +777,19 @@ public class MapUtil { return new MapWrapper<>(map); } + /** + * 将对应Map转换为不可修改的Map + * + * @param map Map + * @param 键类型 + * @param 值类型 + * @return 不修改Map + * @since 5.2.6 + */ + public static Map unmodifiable(Map map) { + return Collections.unmodifiableMap(map); + } + // ----------------------------------------------------------------------------------------------- builder /** diff --git a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java index 58062c6aa..1239b18cb 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java @@ -20,6 +20,7 @@ import java.net.HttpCookie; import java.net.URI; import java.nio.charset.Charset; import java.util.Collection; +import java.util.Collections; import java.util.Map; /** @@ -28,7 +29,7 @@ import java.util.Map; * @author looly * @since 5.2.6 */ -public class HttpServerRequest extends HttpServerBase{ +public class HttpServerRequest extends HttpServerBase { private Map cookieCache; @@ -148,6 +149,21 @@ public class HttpServerRequest extends HttpServerBase{ return getHeader(Header.USER_AGENT); } + /** + * 获取编码,获取失败默认使用UTF-8,获取规则如下: + * + *
    +	 *     1、从Content-Type头中获取编码,类似于:text/html;charset=utf-8
    +	 * 
    + * + * @return 编码,默认UTF-8 + */ + public Charset getCharset() { + final String contentType = getContentType(); + final String charsetStr = HttpUtil.getCharset(contentType); + return CharsetUtil.parse(charsetStr, CharsetUtil.CHARSET_UTF_8); + } + /** * 获得User-Agent * @@ -191,10 +207,10 @@ public class HttpServerRequest extends HttpServerBase{ */ public Map getCookieMap() { if (null == this.cookieCache) { - cookieCache = CollUtil.toMap( + cookieCache = Collections.unmodifiableMap(CollUtil.toMap( NetUtil.parseCookies(getCookiesStr()), new CaseInsensitiveMap<>(), - HttpCookie::getName); + HttpCookie::getName)); } return cookieCache; } @@ -220,16 +236,12 @@ public class HttpServerRequest extends HttpServerBase{ /** * 获取请求体文本,可以是form表单、json、xml等任意内容
    - * 根据请求的Content-Type判断编码,判断失败使用UTF-8编码 + * 使用{@link #getCharset()}判断编码,判断失败使用UTF-8编码 * * @return 请求 */ public String getBody() { - final String contentType = getContentType(); - final String charsetStr = HttpUtil.getCharset(contentType); - final Charset charset = CharsetUtil.parse(charsetStr, CharsetUtil.CHARSET_UTF_8); - - return getBody(charset); + return getBody(getCharset()); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java index 23fb29c58..879c30475 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java @@ -1,18 +1,33 @@ package cn.hutool.http.server; +import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; +import cn.hutool.http.Header; +import cn.hutool.http.HttpUtil; +import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; +import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; /** * Http响应对象,用于写出数据到客户端 */ -public class HttpServerResponse extends HttpServerBase{ +public class HttpServerResponse extends HttpServerBase { + + private Charset charset; /** * 构造 @@ -49,6 +64,111 @@ public class HttpServerResponse extends HttpServerBase{ return this; } + /** + * 获得所有响应头,获取后可以添加新的响应头 + * + * @return 响应头 + */ + public Headers getHeaders() { + return this.httpExchange.getResponseHeaders(); + } + + /** + * 添加响应头,如果已经存在,则追加 + * + * @param header 头key + * @param value 值 + * @return this + */ + public HttpServerResponse addHeader(String header, String value) { + getHeaders().add(header, value); + return this; + } + + /** + * 设置响应头,如果已经存在,则覆盖 + * + * @param header 头key + * @param value 值 + * @return this + */ + public HttpServerResponse setHeader(Header header, String value) { + return setHeader(header.getValue(), value); + } + + /** + * 设置响应头,如果已经存在,则覆盖 + * + * @param header 头key + * @param value 值 + * @return this + */ + public HttpServerResponse setHeader(String header, String value) { + getHeaders().set(header, value); + return this; + } + + /** + * 设置响应头,如果已经存在,则覆盖 + * + * @param header 头key + * @param value 值列表 + * @return this + */ + public HttpServerResponse setHeader(String header, List value) { + getHeaders().put(header, value); + return this; + } + + /** + * 设置所有响应头,如果已经存在,则覆盖 + * + * @param headers 响应头map + * @return this + */ + public HttpServerResponse setHeaders(Map> headers) { + getHeaders().putAll(headers); + return this; + } + + /** + * 设置Content-Type头,类似于:text/html;charset=utf-8
    + * 如果用户传入的信息无charset信息,自动根据charset补充,charset设置见{@link #setCharset(Charset)} + * + * @param contentType Content-Type头内容 + * @return this + */ + public HttpServerResponse setContentType(String contentType) { + if (null != contentType && null != this.charset) { + if (false == contentType.contains(";charset=")) { + contentType += ";charset=" + this.charset; + } + } + + return setHeader(Header.CONTENT_TYPE, contentType); + } + + /** + * 设置Content-Length头 + * + * @param contentLength Content-Length头内容 + * @return this + */ + public HttpServerResponse setContentLength(long contentLength) { + return setHeader(Header.CONTENT_LENGTH, String.valueOf(contentLength)); + } + + /** + * 设置响应的编码 + * + * @param charset 编码 + * @return this + */ + public HttpServerResponse setCharset(Charset charset) { + this.charset = charset; + return this; + } + /** * 获取响应数据流 * @@ -58,6 +178,15 @@ public class HttpServerResponse extends HttpServerBase{ return this.httpExchange.getResponseBody(); } + /** + * 获取响应数据流 + * + * @return 响应数据流 + */ + public OutputStream getWriter() { + return this.httpExchange.getResponseBody(); + } + /** * 写出数据到客户端 * @@ -86,4 +215,38 @@ public class HttpServerResponse extends HttpServerBase{ } return this; } + + /** + * 返回文件给客户端(文件下载) + * + * @param file 写出的文件对象 + * @since 5.2.6 + */ + public HttpServerResponse write(File file) { + final String fileName = file.getName(); + final String contentType = ObjectUtil.defaultIfNull(HttpUtil.getMimeType(fileName), "application/octet-stream"); + BufferedInputStream in = null; + try { + in = FileUtil.getInputStream(file); + write(in, contentType, fileName); + } finally { + IoUtil.close(in); + } + return this; + } + + /** + * 返回数据给客户端 + * + * @param in 需要返回客户端的内容 + * @param contentType 返回的类型 + * @param fileName 文件名 + * @since 5.2.6 + */ + public void write(InputStream in, String contentType, String fileName) { + final Charset charset = ObjectUtil.defaultIfNull(this.charset, CharsetUtil.CHARSET_UTF_8); + setHeader("Content-Disposition", StrUtil.format("attachment;filename={}", URLUtil.encode(fileName, charset))); + setContentType(contentType); + write(in); + } } From 6b13cb5263efdf65adee18744f8f07e9604fae71 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 4 Apr 2020 00:50:44 +0800 Subject: [PATCH 020/157] prepare 5.3.0 --- CHANGELOG.md | 3 + README.md | 10 +- bin/version.txt | 2 +- docs/js/version.js | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- .../java/cn/hutool/core/codec/Base64.java | 14 +- .../main/java/cn/hutool/core/map/BiMap.java | 71 ++++++++++ .../main/java/cn/hutool/core/map/MapUtil.java | 20 +++ .../java/cn/hutool/core/map/TableMap.java | 24 ++-- .../core/map/multi/CollectionValueMap.java | 12 +- .../hutool/core/map/multi/ListValueMap.java | 4 +- .../cn/hutool/core/map/multi/SetValueMap.java | 4 +- .../net}/multipart/MultipartFormData.java | 25 +--- .../MultipartRequestInputStream.java | 39 ++++-- .../core/net}/multipart/UploadFile.java | 49 ++++--- .../core/net}/multipart/UploadFileHeader.java | 2 +- .../core/net}/multipart/UploadSetting.java | 18 +-- .../core/net/multipart/package-info.java | 7 + .../java/cn/hutool/core/util/CharsetUtil.java | 13 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- .../cn/hutool/extra/servlet/ServletUtil.java | 6 +- .../extra/servlet/multipart/package-info.java | 7 - hutool-http/pom.xml | 2 +- .../main/java/cn/hutool/http/ContentType.java | 26 +++- .../main/java/cn/hutool/http/HttpUtil.java | 50 +++++-- .../cn/hutool/http/server/HttpServerBase.java | 5 + .../hutool/http/server/HttpServerRequest.java | 120 +++++++++++++---- .../http/server/HttpServerResponse.java | 126 ++++++++++++++++-- .../cn/hutool/http/server/SimpleServer.java | 7 +- .../hutool/http/server/action/RootAction.java | 62 +++++++++ .../http/server/handler/ActionHandler.java | 5 +- .../http/server/handler/HandlerUtil.java | 91 ------------- .../http/server/handler/RootHandler.java | 45 ------- .../hutool/http/server/SimpleServerTest.java | 14 +- .../cn/hutool/http/test/HttpUtilTest.java | 25 ++-- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 53 files changed, 619 insertions(+), 331 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/map/BiMap.java rename {hutool-extra/src/main/java/cn/hutool/extra/servlet => hutool-core/src/main/java/cn/hutool/core/net}/multipart/MultipartFormData.java (86%) rename {hutool-extra/src/main/java/cn/hutool/extra/servlet => hutool-core/src/main/java/cn/hutool/core/net}/multipart/MultipartRequestInputStream.java (84%) rename {hutool-extra/src/main/java/cn/hutool/extra/servlet => hutool-core/src/main/java/cn/hutool/core/net}/multipart/UploadFile.java (89%) rename {hutool-extra/src/main/java/cn/hutool/extra/servlet => hutool-core/src/main/java/cn/hutool/core/net}/multipart/UploadFileHeader.java (94%) rename {hutool-extra/src/main/java/cn/hutool/extra/servlet => hutool-core/src/main/java/cn/hutool/core/net}/multipart/UploadSetting.java (94%) create mode 100644 hutool-core/src/main/java/cn/hutool/core/net/multipart/package-info.java delete mode 100644 hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/package-info.java create mode 100644 hutool-http/src/main/java/cn/hutool/http/server/action/RootAction.java delete mode 100644 hutool-http/src/main/java/cn/hutool/http/server/handler/HandlerUtil.java delete mode 100644 hutool-http/src/main/java/cn/hutool/http/server/handler/RootHandler.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 80db6fe1e..52348afe1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,9 +18,12 @@ * 【core 】 CollUtil增加toMap方法 * 【core 】 CollUtil和IterUtil废弃一些方法 * 【core 】 添加ValidateObjectInputStream避免对象反序列化漏洞风险 +* 【core 】 添加BiMap +* 【all 】 cn.hutool.extra.servlet.multipart包迁移到cn.hutool.core.net下 ### Bug修复 * 【extra 】 修复SpringUtil使用devtools重启报错问题 +* 【http 】 修复HttpUtil.encodeParams针对无参数URL问题(issue#817@Github) ------------------------------------------------------------------------------------------------------------- diff --git a/README.md b/README.md index bf9a22421..1349f9835 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ -- 主页:https://hutool.cn/ | https://www.hutool.club/ --

    - -- QQ群③:555368316 -- + -- QQ群③:555368316 -- -- QQ群④:718802356 --

    @@ -116,21 +116,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.2.6 + 5.3.0 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.2.6' +compile 'cn.hutool:hutool-all:5.3.0' ``` ### 非Maven项目 点击以下任一链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.2.6/) -- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.2.6/) +- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.3.0/) +- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.0/) > 注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类获工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index a944d7e61..03f488b07 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.2.6 +5.3.0 diff --git a/docs/js/version.js b/docs/js/version.js index b5a3cc686..2f0178e29 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.2.6' \ No newline at end of file +var version = '5.3.0' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 0d5f10848..78044e36e 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 3c011fb9a..bb9458001 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index e7bf57f83..e885cda8f 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 403ddad61..67a2c345b 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 008e8704e..82467a4a7 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 0f9c30525..98c2d9dad 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 94bd039cc..68a16cf32 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-core 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 d796f882b..979b568c8 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 @@ -1,14 +1,14 @@ package cn.hutool.core.codec; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.CharsetUtil; + import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.CharsetUtil; - /** * Base64工具类,提供Base64的编码和解码方案
    * base64编码是用64(2的6次方)个ASCII字符来表示256(2的8次方)个ASCII字符,
    @@ -72,7 +72,7 @@ public class Base64 { * @return 被加密后的字符串 */ public static String encode(CharSequence source, String charset) { - return Base64Encoder.encode(source, CharsetUtil.charset(charset)); + return encode(source, CharsetUtil.charset(charset)); } /** @@ -84,7 +84,7 @@ public class Base64 { * @since 3.0.6 */ public static String encodeUrlSafe(CharSequence source, String charset) { - return Base64Encoder.encodeUrlSafe(source, CharsetUtil.charset(charset)); + return encodeUrlSafe(source, CharsetUtil.charset(charset)); } /** @@ -272,7 +272,7 @@ public class Base64 { * @return 被加密后的字符串 */ public static String decodeStr(CharSequence source, String charset) { - return Base64Decoder.decodeStr(source, CharsetUtil.charset(charset)); + return decodeStr(source, CharsetUtil.charset(charset)); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/map/BiMap.java b/hutool-core/src/main/java/cn/hutool/core/map/BiMap.java new file mode 100644 index 000000000..9680675a6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/BiMap.java @@ -0,0 +1,71 @@ +package cn.hutool.core.map; + +import java.util.Map; + +/** + * 双向Map
    + * 互换键值对不检查值是否有重复,如果有则后加入的元素替换先加入的元素
    + * 值的顺序在HashMap中不确定,所以谁覆盖谁也不确定,在有序的Map中按照先后顺序覆盖,保留最后的值
    + * 它与TableMap的区别是,BiMap维护两个Map实现高效的正向和反向查找 + * + * @param 键类型 + * @param 值类型 + * @since 5.2.6 + */ +public class BiMap extends MapWrapper { + + private Map inverse; + + /** + * 构造 + * + * @param raw 被包装的Map + */ + public BiMap(Map raw) { + super(raw); + } + + @Override + public V put(K key, V value) { + if (null != this.inverse) { + this.inverse.put(value, key); + } + return super.put(key, value); + } + + @Override + public void putAll(Map m) { + super.putAll(m); + if (null != this.inverse) { + m.forEach((key, value) -> this.inverse.put(value, key)); + } + } + + @Override + public void clear() { + super.clear(); + this.inverse = null; + } + + /** + * 获取反向Map + * + * @return 反向Map + */ + public Map getInverse() { + if (null == this.inverse) { + inverse = MapUtil.inverse(getRaw()); + } + return this.inverse; + } + + /** + * 根据值获得键 + * + * @param value 值 + * @return 键 + */ + public K getKey(V value) { + return getInverse().get(value); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java index 39199103b..256b3d346 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java @@ -681,11 +681,14 @@ public class MapUtil { /** * Map的键和值互换 + * 互换键值对不检查值是否有重复,如果有则后加入的元素替换先加入的元素
    + * 值的顺序在HashMap中不确定,所以谁覆盖谁也不确定,在有序的Map中按照先后顺序覆盖,保留最后的值 * * @param 键和值类型 * @param map Map对象,键值类型必须一致 * @return 互换后的Map * @since 3.2.2 + * @see #inverse(Map) */ public static Map reverse(Map map) { return filter(map, (Editor>) t -> new Entry() { @@ -707,6 +710,23 @@ public class MapUtil { }); } + /** + * Map的键和值互换
    + * 互换键值对不检查值是否有重复,如果有则后加入的元素替换先加入的元素
    + * 值的顺序在HashMap中不确定,所以谁覆盖谁也不确定,在有序的Map中按照先后顺序覆盖,保留最后的值 + * + * @param 键和值类型 + * @param 键和值类型 + * @param map Map对象,键值类型必须一致 + * @return 互换后的Map + * @since 5.2.6 + */ + public static Map inverse(Map map) { + final Map result = createMap(map.getClass()); + map.forEach((key, value) -> result.put(value, key)); + return result; + } + /** * 排序已有Map,Key有序的Map,使用默认Key排序方式(字母顺序) * diff --git a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java index b80ec2ed0..9190bec43 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java @@ -16,12 +16,13 @@ import java.util.Objects; import java.util.Set; /** - * 可重复键的Map - * - * @author looly + * 可重复键和值的Map
    + * 通过键值单独建立List方式,使键值对一一对应,实现正向和反向两种查找
    + * 无论是正向还是反向,都是遍历列表查找过程,相比标准的HashMap要慢,数据越多越慢 * * @param 键类型 * @param 值类型 + * @author looly */ public class TableMap implements Map, Serializable { private static final long serialVersionUID = 1L; @@ -31,7 +32,7 @@ public class TableMap implements Map, Serializable { /** * 构造 - * + * * @param size 初始容量 */ public TableMap(int size) { @@ -41,8 +42,8 @@ public class TableMap implements Map, Serializable { /** * 构造 - * - * @param keys 键列表 + * + * @param keys 键列表 * @param values 值列表 */ public TableMap(K[] keys, V[] values) { @@ -89,10 +90,10 @@ public class TableMap implements Map, Serializable { * @return 值列表 * @since 5.2.5 */ - public List getValues(K key){ + public List getValues(K key) { return CollUtil.getAny( this.values, - ListUtil.indexOfAll(this.keys, (ele)-> ObjectUtil.equal(ele, key)) + ListUtil.indexOfAll(this.keys, (ele) -> ObjectUtil.equal(ele, key)) ); } @@ -103,10 +104,10 @@ public class TableMap implements Map, Serializable { * @return 值列表 * @since 5.2.5 */ - public List getKeys(V value){ + public List getKeys(V value) { return CollUtil.getAny( this.keys, - ListUtil.indexOfAll(this.values, (ele)-> ObjectUtil.equal(ele, value)) + ListUtil.indexOfAll(this.values, (ele) -> ObjectUtil.equal(ele, value)) ); } @@ -189,12 +190,13 @@ public class TableMap implements Map, Serializable { public V setValue(V value) { throw new UnsupportedOperationException("setValue not supported."); } + @Override public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { - Map.Entry e = (Map.Entry)o; + Map.Entry e = (Map.Entry) o; return Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue()); } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/CollectionValueMap.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/CollectionValueMap.java index dab043fd2..64d1a47fe 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/multi/CollectionValueMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/CollectionValueMap.java @@ -1,12 +1,12 @@ package cn.hutool.core.map.multi; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapWrapper; + import java.util.Collection; import java.util.HashMap; import java.util.Map; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.map.MapWrapper; - /** * 值作为集合的Map实现,通过调用putValue可以在相同key时加入多个值,多个值用集合表示 * @@ -67,7 +67,7 @@ public abstract class CollectionValueMap extends MapWrapper>(initialCapacity, loadFactor)); + super(new HashMap<>(initialCapacity, loadFactor)); } // ------------------------------------------------------------------------- Constructor end @@ -81,7 +81,7 @@ public abstract class CollectionValueMap extends MapWrapper collection = this.get(key); if (null == collection) { - collection = createCollction(); + collection = createCollection(); this.put(key, collection); } collection.add(value); @@ -105,5 +105,5 @@ public abstract class CollectionValueMap extends MapWrapper createCollction(); + protected abstract Collection createCollection(); } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/ListValueMap.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/ListValueMap.java index 72b705bbd..b7e908353 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/multi/ListValueMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/ListValueMap.java @@ -62,7 +62,7 @@ public class ListValueMap extends CollectionValueMap { * @param loadFactor 加载因子 */ public ListValueMap(int initialCapacity, float loadFactor) { - super(new HashMap>(initialCapacity, loadFactor)); + super(new HashMap<>(initialCapacity, loadFactor)); } // ------------------------------------------------------------------------- Constructor end @@ -72,7 +72,7 @@ public class ListValueMap extends CollectionValueMap { } @Override - protected Collection createCollction() { + protected Collection createCollection() { return new ArrayList<>(DEFAULT_COLLCTION_INITIAL_CAPACITY); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/SetValueMap.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/SetValueMap.java index 52efdc4dc..a1e814bb4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/multi/SetValueMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/SetValueMap.java @@ -62,7 +62,7 @@ public class SetValueMap extends CollectionValueMap { * @param loadFactor 加载因子 */ public SetValueMap(int initialCapacity, float loadFactor) { - super(new HashMap>(initialCapacity, loadFactor)); + super(new HashMap<>(initialCapacity, loadFactor)); } // ------------------------------------------------------------------------- Constructor end @@ -72,7 +72,7 @@ public class SetValueMap extends CollectionValueMap { } @Override - protected Collection createCollction() { + protected Collection createCollection() { return new LinkedHashSet<>(DEFAULT_COLLCTION_INITIAL_CAPACITY); } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/MultipartFormData.java b/hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartFormData.java similarity index 86% rename from hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/MultipartFormData.java rename to hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartFormData.java index a7e32fe57..45f003980 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/MultipartFormData.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartFormData.java @@ -1,17 +1,16 @@ -package cn.hutool.extra.servlet.multipart; +package cn.hutool.core.net.multipart; + +import cn.hutool.core.util.ArrayUtil; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.Charset; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; -import javax.servlet.ServletRequest; - -import cn.hutool.core.util.ArrayUtil; - /** * HttpRequest解析器
    * 来自Jodd @@ -21,9 +20,9 @@ import cn.hutool.core.util.ArrayUtil; public class MultipartFormData { /** 请求参数 */ - private Map requestParameters = new HashMap(); + private Map requestParameters = new HashMap<>(); /** 请求文件 */ - private Map requestFiles = new HashMap(); + private Map requestFiles = new HashMap<>(); /** 是否解析完毕 */ private boolean loaded; @@ -48,16 +47,6 @@ public class MultipartFormData { } // --------------------------------------------------------------------- Constructor end - /** - * 解析上传文件和表单数据 - * - * @param request Http请求 - * @throws IOException IO异常 - */ - public void parseRequest(ServletRequest request) throws IOException { - parseRequestStream(request.getInputStream(), request.getCharacterEncoding()); - } - /** * 提取上传的文件和表单数据 * @@ -65,7 +54,7 @@ public class MultipartFormData { * @param charset 编码 * @throws IOException IO异常 */ - public void parseRequestStream(InputStream inputStream, String charset) throws IOException { + public void parseRequestStream(InputStream inputStream, Charset charset) throws IOException { setLoaded(); MultipartRequestInputStream input = new MultipartRequestInputStream(inputStream); diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/MultipartRequestInputStream.java b/hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartRequestInputStream.java similarity index 84% rename from hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/MultipartRequestInputStream.java rename to hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartRequestInputStream.java index 65b35b648..8dc5df6b0 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/MultipartRequestInputStream.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartRequestInputStream.java @@ -1,15 +1,16 @@ -package cn.hutool.extra.servlet.multipart; +package cn.hutool.core.net.multipart; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.Charset; /** * Http请求解析流,提供了专门针对带文件的form表单的解析
    * 来自Jodd - * + * * @author jodd.org */ public class MultipartRequestInputStream extends BufferedInputStream { @@ -20,7 +21,7 @@ public class MultipartRequestInputStream extends BufferedInputStream { /** * 读取byte字节流,在末尾抛出异常 - * + * * @return byte * @throws IOException 读取异常 */ @@ -34,7 +35,7 @@ public class MultipartRequestInputStream extends BufferedInputStream { /** * 跳过指定位数的 bytes. - * + * * @param i 跳过的byte数 * @throws IOException IO异常 */ @@ -47,12 +48,14 @@ public class MultipartRequestInputStream extends BufferedInputStream { // ---------------------------------------------------------------- boundary - /** part部分边界 */ + /** + * part部分边界 + */ protected byte[] boundary; /** * 输入流中读取边界 - * + * * @return 边界 * @throws IOException 读取异常 */ @@ -60,6 +63,7 @@ public class MultipartRequestInputStream extends BufferedInputStream { ByteArrayOutputStream boundaryOutput = new ByteArrayOutputStream(1024); byte b; // skip optional whitespaces + //noinspection StatementWithEmptyBody while ((b = readByte()) <= ' ') { } boundaryOutput.write(b); @@ -89,12 +93,12 @@ public class MultipartRequestInputStream extends BufferedInputStream { /** * 从流中读取文件头部信息, 如果达到末尾则返回null - * + * * @param encoding 字符集 * @return 头部信息, 如果达到末尾则返回null * @throws IOException 读取异常 */ - public UploadFileHeader readDataHeader(String encoding) throws IOException { + public UploadFileHeader readDataHeader(Charset encoding) throws IOException { String dataHeader = readDataHeaderString(encoding); if (dataHeader != null) { lastHeader = new UploadFileHeader(dataHeader); @@ -104,7 +108,14 @@ public class MultipartRequestInputStream extends BufferedInputStream { return lastHeader; } - protected String readDataHeaderString(String encoding) throws IOException { + /** + * 读取数据头信息字符串 + * + * @param charset 编码 + * @return 数据头信息字符串 + * @throws IOException IO异常 + */ + protected String readDataHeaderString(Charset charset) throws IOException { ByteArrayOutputStream data = new ByteArrayOutputStream(); byte b; while (true) { @@ -128,13 +139,13 @@ public class MultipartRequestInputStream extends BufferedInputStream { data.write(b); } skipBytes(3); - return encoding == null ? data.toString() : data.toString(encoding); + return charset == null ? data.toString() : data.toString(charset.name()); } // ---------------------------------------------------------------- copy /** * 全部字节流复制到out - * + * * @param out 输出流 * @return 复制的字节数 * @throws IOException 读取异常 @@ -154,8 +165,8 @@ public class MultipartRequestInputStream extends BufferedInputStream { /** * 复制字节流到out, 大于maxBytes或者文件末尾停止 - * - * @param out 输出流 + * + * @param out 输出流 * @param limit 最大字节数 * @return 复制的字节数 * @throws IOException 读取异常 @@ -178,7 +189,7 @@ public class MultipartRequestInputStream extends BufferedInputStream { /** * 跳过边界表示 - * + * * @return 跳过的字节数 * @throws IOException 读取异常 */ diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadFile.java b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFile.java similarity index 89% rename from hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadFile.java rename to hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFile.java index 43f4f3884..5e0386603 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadFile.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFile.java @@ -1,4 +1,8 @@ -package cn.hutool.extra.servlet.multipart; +package cn.hutool.core.net.multipart; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -10,27 +14,18 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.log.Log; -import cn.hutool.log.LogFactory; - /** * 上传的文件对象 - * + * * @author xiaoleilu - * */ public class UploadFile { - private static Log log = LogFactory.get(); - private static final String TMP_FILE_PREFIX = "hutool-"; private static final String TMP_FILE_SUFFIX = ".upload.tmp"; private UploadFileHeader header; private UploadSetting setting; - + private int size = -1; // 文件流(小文件位于内存中) @@ -40,8 +35,8 @@ public class UploadFile { /** * 构造 - * - * @param header 头部信息 + * + * @param header 头部信息 * @param setting 上传设置 */ public UploadFile(UploadFileHeader header, UploadSetting setting) { @@ -56,6 +51,7 @@ public class UploadFile { */ public void delete() { if (tempFile != null) { + //noinspection ResultOfMethodCallIgnored tempFile.delete(); } if (data != null) { @@ -66,17 +62,18 @@ public class UploadFile { /** * 将上传的文件写入指定的目标文件路径,自动创建文件
    * 写入后原临时文件会被删除 + * * @param destPath 目标文件路径 * @return 目标文件 * @throws IOException IO异常 */ public File write(String destPath) throws IOException { - if(data != null || tempFile != null) { + if (data != null || tempFile != null) { return write(FileUtil.touch(destPath)); } return null; } - + /** * 将上传的文件写入目标文件
    * 写入后原临时文件会被删除 @@ -87,7 +84,7 @@ public class UploadFile { */ public File write(File destination) throws IOException { assertValid(); - + if (destination.isDirectory() == true) { destination = new File(destination, this.header.getFileName()); } @@ -101,14 +98,14 @@ public class UploadFile { } return destination; } - + /** * @return 获得文件字节流 * @throws IOException IO异常 */ public byte[] getFileContent() throws IOException { assertValid(); - + if (data != null) { return data; } @@ -124,7 +121,7 @@ public class UploadFile { */ public InputStream getFileInputStream() throws IOException { assertValid(); - + if (data != null) { return new BufferedInputStream(new ByteArrayInputStream(data)); } @@ -174,9 +171,10 @@ public class UploadFile { } // ---------------------------------------------------------------- process + /** * 处理上传表单流,提取出文件 - * + * * @param input 上传表单的流 * @return 是否成功 * @throws IOException IO异常 @@ -184,7 +182,6 @@ public class UploadFile { protected boolean processStream(MultipartRequestInputStream input) throws IOException { if (!isAllowedExtension()) { // 非允许的扩展名 - log.debug("Forbidden uploaded file [{}]", this.getFileName()); size = input.skipToBoundary(); return false; } @@ -220,9 +217,9 @@ public class UploadFile { size += input.copy(out, maxFileSize - size + 1); // one more byte to detect larger files if (size > maxFileSize) { // 超出上传大小限制 + //noinspection ResultOfMethodCallIgnored tempFile.delete(); tempFile = null; - log.debug("Upload file [{}] too big, file size > [{}]", this.getFileName(), maxFileSize); input.skipToBoundary(); return false; } @@ -236,6 +233,7 @@ public class UploadFile { } // ---------------------------------------------------------------------------- Private method start + /** * @return 是否为允许的扩展名 */ @@ -257,13 +255,14 @@ public class UploadFile { // 未匹配到扩展名,如果为允许列表,返回false, 否则true return !isAllow; } - + /** * 断言是否文件流可用 + * * @throws IOException IO异常 */ private void assertValid() throws IOException { - if(! isUploaded()) { + if (!isUploaded()) { throw new IOException(StrUtil.format("File [{}] upload fail", getFileName())); } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadFileHeader.java b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFileHeader.java similarity index 94% rename from hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadFileHeader.java rename to hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFileHeader.java index b8b1a86ef..5c1770d9b 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadFileHeader.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFileHeader.java @@ -1,4 +1,4 @@ -package cn.hutool.extra.servlet.multipart; +package cn.hutool.core.net.multipart; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadSetting.java b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadSetting.java similarity index 94% rename from hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadSetting.java rename to hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadSetting.java index c75116c65..08e4dae05 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/UploadSetting.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadSetting.java @@ -1,4 +1,4 @@ -package cn.hutool.extra.servlet.multipart; +package cn.hutool.core.net.multipart; import cn.hutool.core.util.URLUtil; import cn.hutool.log.Log; @@ -8,7 +8,7 @@ import java.net.URL; /** * 上传文件设定文件 - * + * * @author xiaoleilu * */ @@ -42,7 +42,7 @@ public class UploadSetting { /** * 设定最大文件大小,-1表示无限制 - * + * * @param maxFileSize 最大文件大小 */ public void setMaxFileSize(int maxFileSize) { @@ -59,7 +59,7 @@ public class UploadSetting { /** * 设定文件保存到内存的边界
    * 如果文件大小小于这个边界,将保存于内存中,否则保存至临时目录中 - * + * * @param memoryThreshold 文件保存到内存的边界 */ public void setMemoryThreshold(int memoryThreshold) { @@ -75,7 +75,7 @@ public class UploadSetting { /** * 设定上传文件的临时目录,null表示使用系统临时目录 - * + * * @param tmpUploadPath 临时目录,绝对路径 */ public void setTmpUploadPath(String tmpUploadPath) { @@ -92,7 +92,7 @@ public class UploadSetting { /** * 设定文件扩展名限定里列表
    * 禁止列表还是允许列表取决于isAllowFileExts - * + * * @param fileExts 文件扩展名列表 */ public void setFileExts(String[] fileExts) { @@ -101,7 +101,7 @@ public class UploadSetting { /** * 是否允许文件扩展名
    - * + * * @return 若true表示只允许列表里的扩展名,否则是禁止列表里的扩展名 */ public boolean isAllowFileExts() { @@ -110,7 +110,7 @@ public class UploadSetting { /** * 设定是否允许扩展名 - * + * * @param isAllowFileExts 若true表示只允许列表里的扩展名,否则是禁止列表里的扩展名 */ public void setAllowFileExts(boolean isAllowFileExts) { @@ -128,7 +128,7 @@ public class UploadSetting { /** * 加载全局设定 - * + * * @param settingPath 设定文件路径,相对Classpath */ synchronized public void load(String settingPath) { diff --git a/hutool-core/src/main/java/cn/hutool/core/net/multipart/package-info.java b/hutool-core/src/main/java/cn/hutool/core/net/multipart/package-info.java new file mode 100644 index 000000000..1056d3767 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/net/multipart/package-info.java @@ -0,0 +1,7 @@ +/** + * 文件上传封装 + * + * @author looly + * + */ +package cn.hutool.core.net.multipart; \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CharsetUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CharsetUtil.java index c19de5d01..c6478af57 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CharsetUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CharsetUtil.java @@ -62,6 +62,17 @@ public class CharsetUtil { return StrUtil.isBlank(charsetName) ? Charset.defaultCharset() : Charset.forName(charsetName); } + /** + * 解析字符串编码为Charset对象,解析失败返回系统默认编码 + * + * @param charsetName 字符集,为空则返回默认字符集 + * @return Charset + * @since 5.2.6 + */ + public static Charset parse(String charsetName) { + return parse(charsetName, Charset.defaultCharset()); + } + /** * 解析字符串编码为Charset对象,解析失败返回默认编码 * @@ -70,7 +81,7 @@ public class CharsetUtil { * @return Charset * @since 5.2.6 */ - public static Charset parse(String charsetName, Charset defaultCharset) throws UnsupportedCharsetException { + public static Charset parse(String charsetName, Charset defaultCharset) { if (StrUtil.isBlank(charsetName)) { return defaultCharset; } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index d7e2134a8..549c8c6da 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index ccdd83b22..35777df68 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 9860ff4a6..c378809a7 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 82e7f9473..eaa01abd2 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 8f42d3959..0ab3cece5 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-extra diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java index f91530be4..7004f45f3 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java @@ -11,14 +11,14 @@ import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.map.CaseInsensitiveMap; import cn.hutool.core.net.NetUtil; +import cn.hutool.core.net.multipart.MultipartFormData; +import cn.hutool.core.net.multipart.UploadSetting; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import cn.hutool.extra.servlet.multipart.MultipartFormData; -import cn.hutool.extra.servlet.multipart.UploadSetting; import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; @@ -265,7 +265,7 @@ public class ServletUtil { public static MultipartFormData getMultipart(ServletRequest request, UploadSetting uploadSetting) throws IORuntimeException { final MultipartFormData formData = new MultipartFormData(uploadSetting); try { - formData.parseRequest(request); + formData.parseRequestStream(request.getInputStream(), request.getCharacterEncoding()); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/package-info.java deleted file mode 100644 index 0328d88f6..000000000 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/multipart/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * 基于Servlet的文件上传封装 - * - * @author looly - * - */ -package cn.hutool.extra.servlet.multipart; \ No newline at end of file diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0086696a9..c0925f434 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/ContentType.java b/hutool-http/src/main/java/cn/hutool/http/ContentType.java index 759cd7d92..b7398cd39 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ContentType.java +++ b/hutool-http/src/main/java/cn/hutool/http/ContentType.java @@ -28,10 +28,18 @@ public enum ContentType { * Rest请求XML编码 */ XML("application/xml"), + /** + * text/plain编码 + */ + TEXT_PLAIN("text/plain"), /** * Rest请求text/xml编码 */ - TEXT_XML("text/xml"); + TEXT_XML("text/xml"), + /** + * text/html编码 + */ + TEXT_HTML("text/html"); private String value; @@ -39,9 +47,19 @@ public enum ContentType { this.value = value; } + /** + * 获取value值 + * + * @return value值 + * @since 5.2.6 + */ + public String getValue() { + return value; + } + @Override public String toString() { - return value; + return getValue(); } /** @@ -62,7 +80,7 @@ public enum ContentType { * @since 4.1.5 */ public static boolean isDefault(String contentType) { - return null == contentType || isFormUrlEncoed(contentType); + return null == contentType || isFormUrlEncode(contentType); } /** @@ -71,7 +89,7 @@ public enum ContentType { * @param contentType 内容类型 * @return 是否为application/x-www-form-urlencoded */ - public static boolean isFormUrlEncoed(String contentType) { + public static boolean isFormUrlEncode(String contentType) { return StrUtil.startWithIgnoreCase(contentType, FORM_URLENCODED.toString()); } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index 264e9c233..07bb0b8af 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -2,6 +2,7 @@ package cn.hutool.http; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.collection.IterUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.FastByteArrayOutputStream; import cn.hutool.core.io.FileUtil; @@ -416,7 +417,7 @@ public class HttpUtil { if (value instanceof Iterable) { value = CollUtil.join((Iterable) value, ","); } else if (value instanceof Iterator) { - value = CollUtil.join((Iterator) value, ","); + value = IterUtil.join((Iterator) value, ","); } valueStr = Convert.toStr(value); if (StrUtil.isNotEmpty(key)) { @@ -435,30 +436,33 @@ public class HttpUtil { * *

    注意,此方法只能标准化整个URL,并不适合于单独编码参数值

    * - * @param paramsStr url参数,可以包含url本身 + * @param urlWithParams url和参数,可以包含url本身,也可以单独参数 * @param charset 编码 * @return 编码后的url和参数 * @since 4.0.1 */ - public static String encodeParams(String paramsStr, Charset charset) { - if (StrUtil.isBlank(paramsStr)) { + public static String encodeParams(String urlWithParams, Charset charset) { + if (StrUtil.isBlank(urlWithParams)) { return StrUtil.EMPTY; } String urlPart = null; // url部分,不包括问号 String paramPart; // 参数部分 - int pathEndPos = paramsStr.indexOf('?'); + final int pathEndPos = urlWithParams.indexOf('?'); if (pathEndPos > -1) { // url + 参数 - urlPart = StrUtil.subPre(paramsStr, pathEndPos); - paramPart = StrUtil.subSuf(paramsStr, pathEndPos + 1); + urlPart = StrUtil.subPre(urlWithParams, pathEndPos); + paramPart = StrUtil.subSuf(urlWithParams, pathEndPos + 1); if (StrUtil.isBlank(paramPart)) { // 无参数,返回url return urlPart; } - } else { - // 无URL - paramPart = paramsStr; + } else if(false == StrUtil.contains(urlWithParams, '=')){ + // 无参数的URL + return urlWithParams; + }else { + // 无URL的参数 + paramPart = urlWithParams; } paramPart = normalizeParams(paramPart, charset); @@ -534,6 +538,18 @@ public class HttpUtil { * @since 4.0.2 */ public static HashMap decodeParamMap(String paramsStr, String charset) { + return decodeParamMap(paramsStr, CharsetUtil.charset(charset)); + } + + /** + * 将URL参数解析为Map(也可以解析Post中的键值对参数) + * + * @param paramsStr 参数字符串(或者带参数的Path) + * @param charset 字符集 + * @return 参数Map + * @since 5.2.6 + */ + public static HashMap decodeParamMap(String paramsStr, Charset charset) { final Map> paramsMap = decodeParams(paramsStr, charset); final HashMap result = MapUtil.newHashMap(paramsMap.size()); List valueList; @@ -552,6 +568,18 @@ public class HttpUtil { * @return 参数Map */ public static Map> decodeParams(String paramsStr, String charset) { + return decodeParams(paramsStr, CharsetUtil.charset(charset)); + } + + /** + * 将URL参数解析为Map(也可以解析Post中的键值对参数) + * + * @param paramsStr 参数字符串(或者带参数的Path) + * @param charset 字符集 + * @return 参数Map + * @since 5.2.6 + */ + public static Map> decodeParams(String paramsStr, Charset charset) { if (StrUtil.isBlank(paramsStr)) { return Collections.emptyMap(); } @@ -811,7 +839,7 @@ public class HttpUtil { * @param value value * @param charset 编码 */ - private static void addParam(Map> params, String name, String value, String charset) { + private static void addParam(Map> params, String name, String value, Charset charset) { name = URLUtil.decode(name, charset); value = URLUtil.decode(value, charset); final List values = params.computeIfAbsent(name, k -> new ArrayList<>(1)); diff --git a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerBase.java b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerBase.java index 5cf8ecb81..ff146ff89 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerBase.java @@ -1,7 +1,10 @@ package cn.hutool.http.server; +import cn.hutool.core.util.CharsetUtil; import com.sun.net.httpserver.HttpExchange; +import java.nio.charset.Charset; + /** * HttpServer公用对象,提供HttpExchange包装和公用方法 * @@ -10,6 +13,8 @@ import com.sun.net.httpserver.HttpExchange; */ public class HttpServerBase { + final static Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + final HttpExchange httpExchange; /** diff --git a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java index 1239b18cb..f7b14d5fd 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java @@ -1,9 +1,13 @@ package cn.hutool.http.server; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.multi.ListValueMap; import cn.hutool.core.net.NetUtil; +import cn.hutool.core.net.multipart.MultipartFormData; +import cn.hutool.core.net.multipart.UploadSetting; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; @@ -15,6 +19,7 @@ import cn.hutool.http.useragent.UserAgentUtil; import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; +import java.io.IOException; import java.io.InputStream; import java.net.HttpCookie; import java.net.URI; @@ -32,6 +37,9 @@ import java.util.Map; public class HttpServerRequest extends HttpServerBase { private Map cookieCache; + private ListValueMap paramsCache; + private Charset charsetCache; + private byte[] bodyCache; /** * 构造 @@ -159,9 +167,13 @@ public class HttpServerRequest extends HttpServerBase { * @return 编码,默认UTF-8 */ public Charset getCharset() { - final String contentType = getContentType(); - final String charsetStr = HttpUtil.getCharset(contentType); - return CharsetUtil.parse(charsetStr, CharsetUtil.CHARSET_UTF_8); + if(null == this.charsetCache){ + final String contentType = getContentType(); + final String charsetStr = HttpUtil.getCharset(contentType); + this.charsetCache = CharsetUtil.parse(charsetStr, DEFAULT_CHARSET); + } + + return this.charsetCache; } /** @@ -226,12 +238,20 @@ public class HttpServerRequest extends HttpServerBase { } /** - * 获取请求体的流,流中可以读取请求内容,包括请求表单数据或文件上传数据 + * 是否为Multipart类型表单,此类型表单用于文件上传 * - * @return 流 + * @return 是否为Multipart类型表单,此类型表单用于文件上传 */ - public InputStream getBodyStream() { - return this.httpExchange.getRequestBody(); + public boolean isMultipart() { + if (false == isPostMethod()) { + return false; + } + + final String contentType = getContentType(); + if (StrUtil.isBlank(contentType)) { + return false; + } + return contentType.toLowerCase().startsWith("multipart/"); } /** @@ -251,31 +271,49 @@ public class HttpServerRequest extends HttpServerBase { * @return 请求 */ public String getBody(Charset charset) { - InputStream in = null; - try { - in = getBodyStream(); - return IoUtil.read(in, charset); - } finally { - IoUtil.close(in); - } + return StrUtil.str(getBodyBytes(), charset); } /** - * 是否为Multipart类型表单,此类型表单用于文件上传 + * 获取body的bytes数组 * - * @return 是否为Multipart类型表单,此类型表单用于文件上传 + * @return body的bytes数组 */ - public boolean isMultipart() { - if (false == isPostMethod()) { - return false; + public byte[] getBodyBytes(){ + if(null == this.bodyCache){ + this.bodyCache = IoUtil.readBytes(getBodyStream(), true); + } + return this.bodyCache; + } + + /** + * 获取请求体的流,流中可以读取请求内容,包括请求表单数据或文件上传数据 + * + * @return 流 + */ + public InputStream getBodyStream() { + return this.httpExchange.getRequestBody(); + } + + public ListValueMap getParams() { + if (null == this.paramsCache) { + this.paramsCache = new ListValueMap<>(); + final Charset charset = getCharset(); + + //解析URL中的参数 + final String query = getQuery(); + if(StrUtil.isNotBlank(query)){ + this.paramsCache.putAll(HttpUtil.decodeParams(query, charset)); + } + + // 解析body中的参数 + final String body = getBody(); + if(StrUtil.isNotBlank(body)){ + this.paramsCache.putAll(HttpUtil.decodeParams(body, charset)); + } } - final String contentType = getContentType(); - if (StrUtil.isBlank(contentType)) { - return false; - } - - return contentType.toLowerCase().startsWith("multipart/"); + return this.paramsCache; } /** @@ -332,4 +370,36 @@ public class HttpServerRequest extends HttpServerBase { ip = this.httpExchange.getRemoteAddress().getHostName(); return NetUtil.getMultistageReverseProxyIp(ip); } + + /** + * 获得MultiPart表单内容,多用于获得上传的文件 在同一次请求中,此方法只能被执行一次! + * + * @return MultipartFormData + * @throws IORuntimeException IO异常 + * @since 5.3.0 + */ + public MultipartFormData getMultipart() throws IORuntimeException { + return getMultipart(new UploadSetting()); + } + + /** + * 获得multipart/form-data 表单内容
    + * 包括文件和普通表单数据
    + * 在同一次请求中,此方法只能被执行一次! + * + * @param uploadSetting 上传文件的设定,包括最大文件大小、保存在内存的边界大小、临时目录、扩展名限定等 + * @return MultiPart表单 + * @throws IORuntimeException IO异常 + * @since 5.3.0 + */ + public MultipartFormData getMultipart(UploadSetting uploadSetting) throws IORuntimeException { + final MultipartFormData formData = new MultipartFormData(uploadSetting); + try { + formData.parseRequestStream(getBodyStream(), getCharset()); + } catch (IOException e) { + throw new IORuntimeException(e); + } + + return formData; + } } diff --git a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java index 879c30475..85966760a 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java @@ -3,12 +3,14 @@ package cn.hutool.http.server; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; +import cn.hutool.http.ContentType; import cn.hutool.http.Header; +import cn.hutool.http.HttpStatus; import cn.hutool.http.HttpUtil; + import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; @@ -18,6 +20,8 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; import java.nio.charset.Charset; import java.util.List; import java.util.Map; @@ -28,6 +32,10 @@ import java.util.Map; public class HttpServerResponse extends HttpServerBase { private Charset charset; + /** + * 是否已经发送了Http状态码,如果没有,提前写出状态码 + */ + private boolean isSendCode; /** * 构造 @@ -48,6 +56,38 @@ public class HttpServerResponse extends HttpServerBase { return send(httpStatusCode, 0); } + /** + * 发送成功状态码 + * + * @return this + */ + public HttpServerResponse sendOk() { + return send(HttpStatus.HTTP_OK); + } + + /** + * 发送404错误页 + * + * @param content 错误页页面内容,默认text/html类型 + * @return this + */ + public HttpServerResponse send404(String content) { + return sendError(HttpStatus.HTTP_NOT_FOUND, content); + } + + /** + * 发送错误页 + * + * @param errorCode HTTP错误状态码,见HttpStatus + * @param content 错误页页面内容,默认text/html类型 + * @return this + */ + public HttpServerResponse sendError(int errorCode, String content) { + send(errorCode); + setContentType(ContentType.TEXT_HTML.toString()); + return write(content); + } + /** * 发送HTTP状态码 * @@ -56,11 +96,17 @@ public class HttpServerResponse extends HttpServerBase { * @return this */ public HttpServerResponse send(int httpStatusCode, long bodyLength) { + if (this.isSendCode) { + throw new IORuntimeException("Http status code has been send!"); + } + try { this.httpExchange.sendResponseHeaders(httpStatusCode, bodyLength); } catch (IOException e) { throw new IORuntimeException(e); } + + this.isSendCode = true; return this; } @@ -70,6 +116,9 @@ public class HttpServerResponse extends HttpServerBase { * @return 响应头 */ public Headers getHeaders() { + if (false == this.isSendCode) { + sendOk(); + } return this.httpExchange.getResponseHeaders(); } @@ -141,7 +190,7 @@ public class HttpServerResponse extends HttpServerBase { public HttpServerResponse setContentType(String contentType) { if (null != contentType && null != this.charset) { if (false == contentType.contains(";charset=")) { - contentType += ";charset=" + this.charset; + contentType = ContentType.build(contentType, this.charset); } } @@ -169,12 +218,27 @@ public class HttpServerResponse extends HttpServerBase { return this; } + /** + * 设置属性 + * + * @param name 属性名 + * @param value 属性值 + * @return this + */ + public HttpServerResponse setAttr(String name, Object value) { + this.httpExchange.setAttribute(name, value); + return this; + } + /** * 获取响应数据流 * * @return 响应数据流 */ public OutputStream getOut() { + if (false == this.isSendCode) { + sendOk(); + } return this.httpExchange.getResponseBody(); } @@ -183,8 +247,43 @@ public class HttpServerResponse extends HttpServerBase { * * @return 响应数据流 */ - public OutputStream getWriter() { - return this.httpExchange.getResponseBody(); + public PrintWriter getWriter() { + final Charset charset = ObjectUtil.defaultIfNull(this.charset, DEFAULT_CHARSET); + return new PrintWriter(new OutputStreamWriter(getOut(), charset)); + } + + /** + * 写出数据到客户端 + * + * @param data 数据 + * @return this + */ + public HttpServerResponse write(String data, String contentType) { + setContentType(contentType); + return write(data); + } + + /** + * 写出数据到客户端 + * + * @param data 数据 + * @return this + */ + public HttpServerResponse write(String data) { + final Charset charset = ObjectUtil.defaultIfNull(this.charset, DEFAULT_CHARSET); + return write(StrUtil.bytes(data, charset)); + } + + /** + * 写出数据到客户端 + * + * @param data 数据 + * @param contentType 返回的类型 + * @return this + */ + public HttpServerResponse write(byte[] data, String contentType) { + setContentType(contentType); + return write(data); } /** @@ -194,8 +293,19 @@ public class HttpServerResponse extends HttpServerBase { * @return this */ public HttpServerResponse write(byte[] data) { - write(new ByteArrayInputStream(data)); - return this; + return write(new ByteArrayInputStream(data)); + } + + /** + * 返回数据给客户端 + * + * @param in 需要返回客户端的内容 + * @param contentType 返回的类型 + * @since 5.2.6 + */ + public HttpServerResponse write(InputStream in, String contentType) { + setContentType(contentType); + return write(in); } /** @@ -236,7 +346,7 @@ public class HttpServerResponse extends HttpServerBase { } /** - * 返回数据给客户端 + * 返回文件数据给客户端(文件下载) * * @param in 需要返回客户端的内容 * @param contentType 返回的类型 @@ -244,7 +354,7 @@ public class HttpServerResponse extends HttpServerBase { * @since 5.2.6 */ public void write(InputStream in, String contentType, String fileName) { - final Charset charset = ObjectUtil.defaultIfNull(this.charset, CharsetUtil.CHARSET_UTF_8); + final Charset charset = ObjectUtil.defaultIfNull(this.charset, DEFAULT_CHARSET); setHeader("Content-Disposition", StrUtil.format("attachment;filename={}", URLUtil.encode(fileName, charset))); setContentType(contentType); write(in); diff --git a/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java b/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java index 21d62fd1f..aa1377963 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/SimpleServer.java @@ -1,9 +1,10 @@ package cn.hutool.http.server; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.Console; import cn.hutool.http.server.action.Action; +import cn.hutool.http.server.action.RootAction; import cn.hutool.http.server.handler.ActionHandler; -import cn.hutool.http.server.handler.RootHandler; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; @@ -72,7 +73,7 @@ public class SimpleServer { * @return this */ public SimpleServer setRoot(String root) { - return addHandler("/", new RootHandler(root)); + return addAction("/", new RootAction(root)); } /** @@ -119,6 +120,8 @@ public class SimpleServer { * 启动Http服务器,启动后会阻塞当前线程 */ public void start() { + final InetSocketAddress address = getAddress(); + Console.log("Hutool Simple Http Server listen on 【{}:{}】", address.getHostName(), address.getPort()); this.server.start(); } } diff --git a/hutool-http/src/main/java/cn/hutool/http/server/action/RootAction.java b/hutool-http/src/main/java/cn/hutool/http/server/action/RootAction.java new file mode 100644 index 000000000..1444cdbb6 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/server/action/RootAction.java @@ -0,0 +1,62 @@ +package cn.hutool.http.server.action; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.http.server.HttpServerRequest; +import cn.hutool.http.server.HttpServerResponse; + +import java.io.File; +import java.util.List; + +/** + * 默认的处理器,通过解析用户传入的path,找到网页根目录下对应文件后返回 + * + * @author looly + * @since 5.2.6 + */ +public class RootAction implements Action { + + public static final String DEFAULT_INDEX_FILE_NAME = "index.html"; + + private final String rootDir; + private List indexFileNames; + + /** + * 构造 + * + * @param rootDir 网页根目录 + */ + public RootAction(String rootDir) { + this(rootDir, DEFAULT_INDEX_FILE_NAME); + } + + /** + * 构造 + * + * @param rootDir 网页根目录 + * @param indexFileNames 主页文件名列表 + */ + public RootAction(String rootDir, String... indexFileNames) { + this.rootDir = rootDir; + this.indexFileNames = CollUtil.toList(indexFileNames); + } + + @Override + public void doAction(HttpServerRequest request, HttpServerResponse response) { + final String path = request.getPath(); + File file = FileUtil.file(rootDir, path); + if (file.exists()) { + if (file.isDirectory()) { + for (String indexFileName : indexFileNames) { + //默认读取主页 + file = FileUtil.file(file, indexFileName); + if (file.exists() && file.isFile()) { + response.write(file); + } + } + } + } + + response.send404("404 Not Found !"); + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java b/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java index 94a51d004..e469ad3d0 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java @@ -27,6 +27,9 @@ public class ActionHandler implements HttpHandler { @Override public void handle(HttpExchange httpExchange) { - action.doAction(new HttpServerRequest(httpExchange), new HttpServerResponse(httpExchange)); + action.doAction( + new HttpServerRequest(httpExchange), + new HttpServerResponse(httpExchange) + ); } } diff --git a/hutool-http/src/main/java/cn/hutool/http/server/handler/HandlerUtil.java b/hutool-http/src/main/java/cn/hutool/http/server/handler/HandlerUtil.java deleted file mode 100644 index a3fe4d0b8..000000000 --- a/hutool-http/src/main/java/cn/hutool/http/server/handler/HandlerUtil.java +++ /dev/null @@ -1,91 +0,0 @@ -package cn.hutool.http.server.handler; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.http.Header; -import cn.hutool.http.HttpStatus; -import cn.hutool.http.HttpUtil; -import com.sun.net.httpserver.HttpExchange; - -import java.io.File; -import java.io.IOException; -import java.io.OutputStream; - -/** - * 请求处理器相关工具类 - * - * @since 5.2.6 - */ -public class HandlerUtil { - - /** - * 返回404页面 - * - * @param httpExchange HttpExchange - * @param content 要发送的404页面内容 - * @throws IOException IO异常 - */ - public static void send404(HttpExchange httpExchange, String content) throws IOException { - if (null == httpExchange) { - return; - } - - if (null == content) { - content = "404 Not Found !"; - } - - httpExchange.sendResponseHeaders(HttpStatus.HTTP_NOT_FOUND, 0); - try (OutputStream out = httpExchange.getResponseBody()) { - IoUtil.writeUtf8(out, false, content); - } - } - - /** - * 返回文件 - * - * @param httpExchange HttpExchange - * @param file 要发送的文件 - * @throws IOException IO异常 - */ - public static void sendFile(HttpExchange httpExchange, File file) throws IOException { - if (ArrayUtil.hasNull(httpExchange, file)) { - return; - } - addHeader(httpExchange, - Header.CONTENT_TYPE.toString(), - HttpUtil.getMimeType(file.getName(), "text/html")); - httpExchange.sendResponseHeaders(HttpStatus.HTTP_OK, 0); - try (OutputStream out = httpExchange.getResponseBody()) { - FileUtil.writeToStream(file, out); - } - } - - /** - * 增加响应头信息 - * - * @param httpExchange HttpExchange - * @param header 头名 - * @param value 头值 - */ - public static void addHeader(HttpExchange httpExchange, String header, String value) { - if (ArrayUtil.hasEmpty(httpExchange, header)) { - return; - } - httpExchange.getResponseHeaders().add(header, value); - } - - /** - * 获取响应头信息 - * - * @param httpExchange HttpExchange - * @param header 头名 - * @return 值,不存在返回null - */ - public static String getHeader(HttpExchange httpExchange, String header) { - if (ArrayUtil.hasEmpty(httpExchange, header)) { - return null; - } - return httpExchange.getRequestHeaders().getFirst(header); - } -} diff --git a/hutool-http/src/main/java/cn/hutool/http/server/handler/RootHandler.java b/hutool-http/src/main/java/cn/hutool/http/server/handler/RootHandler.java deleted file mode 100644 index 8e10274ee..000000000 --- a/hutool-http/src/main/java/cn/hutool/http/server/handler/RootHandler.java +++ /dev/null @@ -1,45 +0,0 @@ -package cn.hutool.http.server.handler; - -import cn.hutool.core.io.FileUtil; -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; - -import java.io.File; -import java.io.IOException; -import java.net.URI; - -/** - * 默认的处理器,通过解析用户传入的path,找到网页根目录下对应文件后返回 - * - * @author looly - * @since 5.2.6 - */ -public class RootHandler implements HttpHandler { - - private final String rootDir; - - /** - * 构造 - * - * @param rootDir 网页根目录 - */ - public RootHandler(String rootDir) { - this.rootDir = rootDir; - } - - @Override - public void handle(HttpExchange httpExchange) throws IOException { - final URI uri = httpExchange.getRequestURI(); - File file = FileUtil.file(rootDir, uri.getPath()); - if (file.exists()) { - if (file.isDirectory()) { - //默认读取主页 - file = FileUtil.file(file, "index.html"); - } - HandlerUtil.sendFile(httpExchange, file); - } - - // 文件未找到 - HandlerUtil.send404(httpExchange, null); - } -} diff --git a/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java b/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java index 70a70a510..afb3657ae 100644 --- a/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java @@ -1,13 +1,23 @@ package cn.hutool.http.server; +import cn.hutool.http.ContentType; import cn.hutool.http.HttpUtil; -import cn.hutool.http.server.handler.RootHandler; public class SimpleServerTest { public static void main(String[] args) { HttpUtil.createServer(8888) - .addHandler("/", new RootHandler("D:\\test")) + // 设置默认根目录, + .setRoot("d:/test") + // 返回JSON数据测试 + .addAction("/restTest", (request, response) -> + response.write("{\"id\": 1, \"msg\": \"OK\"}", ContentType.JSON.toString()) + ) + // 获取表单数据测试 + // http://localhost:8888/formTest?a=1&a=2&b=3 + .addAction("/formTest", (request, response) -> + response.write(request.getParams().toString(), ContentType.TEXT_PLAIN.toString()) + ) .start(); } } diff --git a/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java index 58ea75870..59afded99 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java @@ -1,13 +1,5 @@ package cn.hutool.http.test; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Console; import cn.hutool.core.util.CharsetUtil; @@ -15,6 +7,13 @@ import cn.hutool.core.util.ReUtil; import cn.hutool.http.Header; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; public class HttpUtilTest { @@ -164,6 +163,16 @@ public class HttpUtilTest { paramsStr = "a=bbb&c=你好&哈喽&"; encode = HttpUtil.encodeParams(paramsStr, CharsetUtil.CHARSET_UTF_8); Assert.assertEquals("a=bbb&c=%E4%BD%A0%E5%A5%BD&%E5%93%88%E5%96%BD=", encode); + + // URL原样输出 + paramsStr = "https://www.hutool.cn/"; + encode = HttpUtil.encodeParams(paramsStr, CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals(paramsStr, encode); + + // URL原样输出 + paramsStr = "https://www.hutool.cn/?"; + encode = HttpUtil.encodeParams(paramsStr, CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("https://www.hutool.cn/", encode); } @Test diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index af66a23a4..6b4058a92 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 98f927ac6..29dad09b7 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 379315af5..42eee9aa1 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 05cf7bf27..dc77c1722 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index eb777bc49..e1b72b92a 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 011ef7557..f87b3cab0 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index a89067307..0a7c45ab4 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index e443672b0..0c7c027be 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.2.6-SNAPSHOT + 5.3.0-SNAPSHOT hutool 提供丰富的Java工具方法 https://github.com/looly/hutool From 24a300e348be9b1dffe6927efffcdd946a169943 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 4 Apr 2020 01:34:54 +0800 Subject: [PATCH 021/157] fix code --- CHANGELOG.md | 1 + .../cn/hutool/cache/impl/AbstractCache.java | 1 - .../java/cn/hutool/captcha/CircleCaptcha.java | 15 +-- .../java/cn/hutool/core/bean/BeanDesc.java | 19 ++- .../core/bean/copier/ValueProvider.java | 4 +- .../convert/impl/GenericEnumConverter.java | 2 +- .../cn/hutool/core/io/file/FileCopier.java | 41 +++--- .../core/net/multipart/UploadSetting.java | 38 ------ .../asymmetric/AbstractAsymmetricCrypto.java | 13 +- .../src/main/java/cn/hutool/db/DbUtil.java | 37 +---- .../src/main/java/cn/hutool/db/Session.java | 18 ++- .../cn/hutool/db/handler/HandleHelper.java | 1 - .../cn/hutool/db/nosql/mongo/MongoDS.java | 11 -- .../cn/hutool/extra/servlet/ServletUtil.java | 2 +- .../cn/hutool/extra/spring/SpringUtil.java | 3 +- .../template/engine/beetl/BeetlUtil.java | 17 +-- .../template/engine/enjoy/EnjoyEngine.java | 3 +- .../SimpleStringTemplateLoader.java | 11 +- .../template/engine/rythm/RythmTemplate.java | 11 +- .../engine/velocity/VelocityUtil.java | 127 +++++++++--------- .../extra/tokenizer/AbstractResult.java | 1 - .../tokenizer/engine/jcseg/JcsegResult.java | 1 - .../extra/tokenizer/TokenizerUtilTest.java | 27 ++-- .../hutool/http/ssl/DefaultTrustManager.java | 4 +- .../cn/hutool/http/test/HttpRequestTest.java | 12 +- .../java/cn/hutool/json/InternalJSONUtil.java | 18 +-- .../java/cn/hutool/log/GlobalLogFactory.java | 35 ++--- .../main/java/cn/hutool/log/LogFactory.java | 48 +------ .../java/cn/hutool/poi/excel/ExcelReader.java | 31 ++--- .../java/cn/hutool/poi/excel/ExcelUtil.java | 9 +- .../poi/excel/sax/Excel07SaxReader.java | 4 - .../java/cn/hutool/setting/GroupedMap.java | 10 +- .../main/java/cn/hutool/setting/Setting.java | 30 ++--- .../java/cn/hutool/setting/SettingUtil.java | 1 - .../cn/hutool/setting/dialect/PropsUtil.java | 1 - .../cn/hutool/setting/test/PropsTest.java | 1 - 36 files changed, 228 insertions(+), 380 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52348afe1..9966a05f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ ### Bug修复 * 【extra 】 修复SpringUtil使用devtools重启报错问题 * 【http 】 修复HttpUtil.encodeParams针对无参数URL问题(issue#817@Github) +* 【extra 】 修复模板中无效引用的问题 ------------------------------------------------------------------------------------------------------------- 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 143afde03..464ab44b7 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 @@ -184,7 +184,6 @@ public abstract class AbstractCache implements Cache { // ---------------------------------------------------------------- get end - @SuppressWarnings("NullableProblems") @Override public Iterator iterator() { CacheObjIterator copiedIterator = (CacheObjIterator) this.cacheObjIterator(); diff --git a/hutool-captcha/src/main/java/cn/hutool/captcha/CircleCaptcha.java b/hutool-captcha/src/main/java/cn/hutool/captcha/CircleCaptcha.java index b8df607f0..193b2ae95 100644 --- a/hutool-captcha/src/main/java/cn/hutool/captcha/CircleCaptcha.java +++ b/hutool-captcha/src/main/java/cn/hutool/captcha/CircleCaptcha.java @@ -1,17 +1,16 @@ package cn.hutool.captcha; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Image; -import java.awt.image.BufferedImage; -import java.util.concurrent.ThreadLocalRandom; - import cn.hutool.core.img.GraphicsUtil; import cn.hutool.core.img.ImgUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.RandomUtil; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.util.concurrent.ThreadLocalRandom; + /** * 圆圈干扰验证码 * @@ -73,7 +72,7 @@ public class CircleCaptcha extends AbstractCaptcha { /** * 绘制字符串 * - * @param g {@link Graphics}画笔 + * @param g {@link Graphics2D}画笔 * @param code 验证码 */ private void drawString(Graphics2D g, String code) { diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java index 3e485b450..560805d5c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java @@ -1,14 +1,5 @@ package cn.hutool.core.bean; -import java.io.Serializable; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; - -import cn.hutool.core.annotation.Alias; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.CaseInsensitiveMap; import cn.hutool.core.util.BooleanUtil; @@ -18,6 +9,14 @@ import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.TypeUtil; +import java.io.Serializable; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + /** * Bean信息描述做为BeanInfo替代方案,此对象持有JavaBean中的setters和getters等相关信息描述
    * 查找Getter和Setter方法时会: @@ -322,7 +321,7 @@ public class BeanDesc implements Serializable{ } /** - * 获取字段名,如果存在{@link Alias}注解,读取注解的值作为名称 + * 获取字段名,如果存在Alias注解,读取注解的值作为名称 * * @return 字段名 */ diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/ValueProvider.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/ValueProvider.java index e2598bfa8..6debd127a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/ValueProvider.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/ValueProvider.java @@ -2,8 +2,6 @@ package cn.hutool.core.bean.copier; import java.lang.reflect.Type; -import cn.hutool.core.convert.Convert; - /** * 值提供者,用于提供Bean注入时参数对应值得抽象接口
    * 继承或匿名实例化此接口
    @@ -17,7 +15,7 @@ public interface ValueProvider{ /** * 获取值
    - * 返回值一般需要匹配被注入类型,如果不匹配会调用默认转换 {@link Convert#convert(Type, Object)}实现转换 + * 返回值一般需要匹配被注入类型,如果不匹配会调用默认转换 Convert#convert(Type, Object)实现转换 * * @param key Bean对象中参数名 * @param valueType 被注入的值得类型 diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/GenericEnumConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/GenericEnumConverter.java index 179717041..4e6f289d8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/GenericEnumConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/GenericEnumConverter.java @@ -25,9 +25,9 @@ public class GenericEnumConverter> extends AbstractConverter * 支持以下几种情况: @@ -209,25 +210,28 @@ public class FileCopier extends SrcToDestCopier{ //被过滤的目录跳过 return; } - + if (false == dest.exists()) { //目标为不存在路径,创建为目录 + //noinspection ResultOfMethodCallIgnored dest.mkdirs(); } else if (false == dest.isDirectory()) { throw new IORuntimeException(StrUtil.format("Src [{}] is a directory but dest [{}] is a file!", src.getPath(), dest.getPath())); } final String[] files = src.list(); - File srcFile; - File destFile; - for (String file : files) { - srcFile = new File(src, file); - destFile = this.isOnlyCopyFile ? dest : new File(dest, file); - // 递归复制 - if (srcFile.isDirectory()) { - internalCopyDirContent(srcFile, destFile); - } else { - internalCopyFile(srcFile, destFile); + if(ArrayUtil.isNotEmpty(files)){ + File srcFile; + File destFile; + for (String file : files) { + srcFile = new File(src, file); + destFile = this.isOnlyCopyFile ? dest : new File(dest, file); + // 递归复制 + if (srcFile.isDirectory()) { + internalCopyDirContent(srcFile, destFile); + } else { + internalCopyFile(srcFile, destFile); + } } } } @@ -263,6 +267,7 @@ public class FileCopier extends SrcToDestCopier{ } }else { //路径不存在则创建父目录 + //noinspection ResultOfMethodCallIgnored dest.getParentFile().mkdirs(); } @@ -275,7 +280,7 @@ public class FileCopier extends SrcToDestCopier{ } try { - Files.copy(src.toPath(), dest.toPath(), optionList.toArray(new CopyOption[optionList.size()])); + Files.copy(src.toPath(), dest.toPath(), optionList.toArray(new CopyOption[0])); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadSetting.java b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadSetting.java index 08e4dae05..872bb7010 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadSetting.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadSetting.java @@ -1,11 +1,5 @@ package cn.hutool.core.net.multipart; -import cn.hutool.core.util.URLUtil; -import cn.hutool.log.Log; -import cn.hutool.setting.Setting; - -import java.net.URL; - /** * 上传文件设定文件 * @@ -13,10 +7,6 @@ import java.net.URL; * */ public class UploadSetting { - private static final Log log = Log.get(); - - /** 默认的配置文件路径(相对ClassPath) */ - public final static String DEFAULT_SETTING_PATH = "config/upload.setting"; /** 最大文件大小,默认无限制 */ protected int maxFileSize = -1; @@ -117,32 +107,4 @@ public class UploadSetting { this.isAllowFileExts = isAllowFileExts; } // ---------------------------------------------------------------------- Setters and Getters end - - /** - * 加载全局设定
    - * 使用默认的配置文件classpath/config/upload.setting - */ - public void load() { - load(DEFAULT_SETTING_PATH); - } - - /** - * 加载全局设定 - * - * @param settingPath 设定文件路径,相对Classpath - */ - synchronized public void load(String settingPath) { - URL url = URLUtil.getURL(settingPath); - if (url == null) { - log.info("Can not find Upload setting file [{}], use default setting.", settingPath); - return; - } - Setting setting = new Setting(url, Setting.DEFAULT_CHARSET, true); - - maxFileSize = setting.getInt("file.size.max"); - memoryThreshold = setting.getInt("memory.threshold"); - tmpUploadPath = setting.getStr("tmp.upload.path"); - fileExts = setting.getStrings("file.exts"); - isAllowFileExts = setting.getBool("file.exts.allow"); - } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AbstractAsymmetricCrypto.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AbstractAsymmetricCrypto.java index a50df5ab7..5f56761ae 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AbstractAsymmetricCrypto.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AbstractAsymmetricCrypto.java @@ -1,10 +1,5 @@ package cn.hutool.crypto.asymmetric; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.security.PrivateKey; -import java.security.PublicKey; - import cn.hutool.core.codec.BCD; import cn.hutool.core.codec.Base64; import cn.hutool.core.io.IORuntimeException; @@ -12,9 +7,13 @@ import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.CryptoException; import cn.hutool.crypto.SecureUtil; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.security.PrivateKey; +import java.security.PublicKey; + /** * 抽象的非对称加密对象,包装了加密和解密为Hex和Base64的封装 * @@ -201,7 +200,6 @@ public abstract class AbstractAsymmetricCrypto * 会话通过共用Connection而可以实现JDBC事务
    @@ -246,7 +244,7 @@ public class Session extends AbstractDb implements Closeable { } /** - * 在事务中执行操作,通过实现{@link VoidFunc0}接口的call方法执行多条SQL语句从而完成事务 + * 在事务中执行操作,通过实现{@link VoidFunc1}接口的call方法执行多条SQL语句从而完成事务 * * @param func 函数抽象,在函数中执行多个SQL操作,多个操作会被合并为同一事务 * @throws SQLException SQL异常 @@ -264,7 +262,7 @@ public class Session extends AbstractDb implements Closeable { } /** - * 在事务中执行操作,通过实现{@link VoidFunc0}接口的call方法执行多条SQL语句从而完成事务 + * 在事务中执行操作,通过实现{@link VoidFunc1}接口的call方法执行多条SQL语句从而完成事务 * * @param func 函数抽象,在函数中执行多个SQL操作,多个操作会被合并为同一事务 * @since 3.2.3 diff --git a/hutool-db/src/main/java/cn/hutool/db/handler/HandleHelper.java b/hutool-db/src/main/java/cn/hutool/db/handler/HandleHelper.java index deba5ef76..e9762bebc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/handler/HandleHelper.java +++ b/hutool-db/src/main/java/cn/hutool/db/handler/HandleHelper.java @@ -148,7 +148,6 @@ public class HandleHelper { * @since 3.3.1 */ public static T handleRow(T row, int columnCount, ResultSetMetaData meta, ResultSet rs, boolean withMetaInfo) throws SQLException { - String columnLabel; int type; for (int i = 1; i <= columnCount; i++) { type = meta.getColumnType(i); diff --git a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java index da2f5a37c..38502b24b 100644 --- a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java +++ b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java @@ -353,17 +353,6 @@ public class MongoDS implements Closeable { log.debug("MongoDB connectionsPerHost: {}", connectionsPerHost); } - // multiplier for connectionsPerHost for # of threads that can block if connectionsPerHost is 10, and threadsAllowedToBlockForConnectionMultiplier is 5, then 50 threads can block more than - // that and an exception will be throw --int - Integer threadsAllowedToBlockForConnectionMultiplier = setting.getInt(group + "threadsAllowedToBlockForConnectionMultiplier"); - if (StrUtil.isBlank(group) == false && threadsAllowedToBlockForConnectionMultiplier == null) { - threadsAllowedToBlockForConnectionMultiplier = setting.getInt("threadsAllowedToBlockForConnectionMultiplier"); - } - if (threadsAllowedToBlockForConnectionMultiplier != null) { - builder.threadsAllowedToBlockForConnectionMultiplier(threadsAllowedToBlockForConnectionMultiplier); - log.debug("MongoDB threadsAllowedToBlockForConnectionMultiplier: {}", threadsAllowedToBlockForConnectionMultiplier); - } - // 被阻塞线程从连接池获取连接的最长等待时间(ms) --int Integer connectTimeout = setting.getInt(group + "connectTimeout"); if (StrUtil.isBlank(group) == false && connectTimeout == null) { diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java index 7004f45f3..b7bb02aa3 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java @@ -265,7 +265,7 @@ public class ServletUtil { public static MultipartFormData getMultipart(ServletRequest request, UploadSetting uploadSetting) throws IORuntimeException { final MultipartFormData formData = new MultipartFormData(uploadSetting); try { - formData.parseRequestStream(request.getInputStream(), request.getCharacterEncoding()); + formData.parseRequestStream(request.getInputStream(), CharsetUtil.charset(request.getCharacterEncoding())); } catch (IOException e) { throw new IORuntimeException(e); } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index 39f706d7a..4d67ce0a5 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -19,7 +19,6 @@ public class SpringUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; - @SuppressWarnings("NullableProblems") @Override public void setApplicationContext(ApplicationContext applicationContext) { SpringUtil.applicationContext = applicationContext; @@ -43,8 +42,8 @@ public class SpringUtil implements ApplicationContextAware { * @param name Bean名称 * @return Bean */ + @SuppressWarnings("unchecked") public static T getBean(String name) { - //noinspection unchecked return (T) applicationContext.getBean(name); } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/beetl/BeetlUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/beetl/BeetlUtil.java index 050da1b66..eb94ef293 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/beetl/BeetlUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/beetl/BeetlUtil.java @@ -1,10 +1,8 @@ package cn.hutool.extra.template.engine.beetl; -import java.io.IOException; -import java.io.Writer; -import java.nio.charset.Charset; -import java.util.Map; - +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.util.CharsetUtil; import org.beetl.core.Configuration; import org.beetl.core.GroupTemplate; import org.beetl.core.ResourceLoader; @@ -16,9 +14,10 @@ import org.beetl.core.resource.Matcher; import org.beetl.core.resource.StringTemplateResourceLoader; import org.beetl.core.resource.WebAppResourceLoader; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.util.CharsetUtil; +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.Map; /** * Beetl模板引擎工具类
    @@ -26,7 +25,9 @@ import cn.hutool.core.util.CharsetUtil; * 文档:http://ibeetl.com/guide/beetl.html * * @author Looly + * @deprecated 使用TemplateUtil替代 */ +@Deprecated public final class BeetlUtil { /** diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/enjoy/EnjoyEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/enjoy/EnjoyEngine.java index 27dd9d019..4d62d01d2 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/enjoy/EnjoyEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/enjoy/EnjoyEngine.java @@ -8,7 +8,6 @@ import cn.hutool.extra.template.TemplateConfig; import cn.hutool.extra.template.TemplateConfig.ResourceMode; import cn.hutool.extra.template.TemplateEngine; import com.jfinal.template.source.FileSourceFactory; -import org.beetl.core.GroupTemplate; /** * Enjoy库的引擎包装 @@ -80,7 +79,7 @@ public class EnjoyEngine implements TemplateEngine { * 创建引擎 * * @param config 模板配置 - * @return {@link GroupTemplate} + * @return {@link com.jfinal.template.Engine} */ private static com.jfinal.template.Engine createEngine(TemplateConfig config) { final com.jfinal.template.Engine engine = com.jfinal.template.Engine.create("Hutool-Enjoy-Engine-" + IdUtil.fastSimpleUUID()); diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/SimpleStringTemplateLoader.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/SimpleStringTemplateLoader.java index f1b7f9df1..f25d7b658 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/SimpleStringTemplateLoader.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/SimpleStringTemplateLoader.java @@ -1,11 +1,10 @@ package cn.hutool.extra.template.engine.freemarker; -import java.io.IOException; +import freemarker.cache.TemplateLoader; + import java.io.Reader; import java.io.StringReader; -import freemarker.cache.TemplateLoader; - /** * {@link TemplateLoader} 字符串实现形式
    * 用于直接获取字符串模板 @@ -16,7 +15,7 @@ import freemarker.cache.TemplateLoader; public class SimpleStringTemplateLoader implements TemplateLoader { @Override - public Object findTemplateSource(String name) throws IOException { + public Object findTemplateSource(String name) { return name; } @@ -26,12 +25,12 @@ public class SimpleStringTemplateLoader implements TemplateLoader { } @Override - public Reader getReader(Object templateSource, String encoding) throws IOException { + public Reader getReader(Object templateSource, String encoding) { return new StringReader((String) templateSource); } @Override - public void closeTemplateSource(Object templateSource) throws IOException { + public void closeTemplateSource(Object templateSource) { // ignore } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/rythm/RythmTemplate.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/rythm/RythmTemplate.java index bb7706000..1bab9b4ce 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/rythm/RythmTemplate.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/rythm/RythmTemplate.java @@ -1,15 +1,14 @@ package cn.hutool.extra.template.engine.rythm; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.TypeReference; +import cn.hutool.extra.template.AbstractTemplate; + import java.io.OutputStream; import java.io.Serializable; import java.io.Writer; import java.util.Map; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.lang.TypeReference; -import cn.hutool.extra.template.AbstractTemplate; -import cn.hutool.extra.template.engine.beetl.BeetlTemplate; - /** * Rythm模板包装 * @@ -25,7 +24,7 @@ public class RythmTemplate extends AbstractTemplate implements Serializable { * 包装Rythm模板 * * @param template Rythm的模板对象 {@link org.rythmengine.template.ITemplate} - * @return {@link BeetlTemplate} + * @return {@link RythmTemplate} */ public static RythmTemplate wrap(org.rythmengine.template.ITemplate template) { return (null == template) ? null : new RythmTemplate(template); diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityUtil.java index 02eaf43eb..d8d0e75a0 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityUtil.java @@ -1,5 +1,16 @@ package cn.hutool.extra.template.engine.velocity; +import cn.hutool.core.exceptions.NotInitedException; +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.IdUtil; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.apache.velocity.app.VelocityEngine; + import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; @@ -9,37 +20,30 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Properties; -import org.apache.velocity.Template; -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.Velocity; -import org.apache.velocity.app.VelocityEngine; - -import cn.hutool.core.exceptions.NotInitedException; -import cn.hutool.core.exceptions.UtilException; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.IdUtil; - /** * Velocity模板引擎工具类
    * 使用前必须初始化工具类 - * + * * @author xiaoleilu - * + * @deprecated 使用TemplateUtil替代 */ +@Deprecated public class VelocityUtil { - /** 是否初始化了默认引擎 */ + /** + * 是否初始化了默认引擎 + */ private static boolean isInited; - /** 全局上下文,当设定值时,对于每个模板都有效 */ + /** + * 全局上下文,当设定值时,对于每个模板都有效 + */ private static Map globalContext = new HashMap<>(); /** * 设置Velocity全局上下文
    * 当设定值时,对于每个模板都有效 - * - * @param name 名 + * + * @param name 名 * @param value 值 */ public void putGlobalContext(String name, Object value) { @@ -48,9 +52,9 @@ public class VelocityUtil { /** * 初始化Velocity全局属性 - * + * * @param templateDir 模板所在目录,绝对路径 - * @param charset 编码 + * @param charset 编码 */ synchronized public static void init(String templateDir, String charset) { Velocity.init(_newInitedProp(templateDir, charset)); @@ -61,9 +65,9 @@ public class VelocityUtil { /** * 初始化全局属性 - * - * @param templateDir 模板目录 - * @param charset 字符集编码 + * + * @param templateDir 模板目录 + * @param charset 字符集编码 * @param initedGlobalContext 初始的全局上下文 */ public static void init(String templateDir, String charset, Map initedGlobalContext) { @@ -73,9 +77,9 @@ public class VelocityUtil { /** * 新建Velocity模板引擎 - * + * * @param templateDir 模板所在目录,绝对路径 - * @param charset 编码 + * @param charset 编码 * @return VelocityEngine */ public static VelocityEngine newEngine(String templateDir, String charset) { @@ -89,11 +93,11 @@ public class VelocityUtil { /** * 获得指定模板填充后的内容 - * - * @param templateDir 模板所在目录,绝对路径 + * + * @param templateDir 模板所在目录,绝对路径 * @param templateFileName 模板名称 - * @param context 上下文(变量值的容器) - * @param charset 字符集 + * @param context 上下文(变量值的容器) + * @param charset 字符集 * @return 模板和内容匹配后的内容 */ public static String getContent(String templateDir, String templateFileName, VelocityContext context, String charset) { @@ -105,10 +109,10 @@ public class VelocityUtil { /** * 获得指定模板填充后的内容 - * - * @param ve 模板引擎 + * + * @param ve 模板引擎 * @param templateFileName 模板名称 - * @param context 上下文(变量值的容器) + * @param context 上下文(变量值的容器) * @return 模板和内容匹配后的内容 */ public static String getContent(VelocityEngine ve, String templateFileName, VelocityContext context) { @@ -119,9 +123,9 @@ public class VelocityUtil { /** * 获得指定模板填充后的内容,使用默认引擎 - * + * * @param templateFileName 模板文件 - * @param context 上下文(变量值的容器) + * @param context 上下文(变量值的容器) * @return 模板和内容匹配后的内容 */ public static String getContent(String templateFileName, VelocityContext context) { @@ -132,11 +136,11 @@ public class VelocityUtil { /** * 生成文件 - * - * @param ve 模板引擎 + * + * @param ve 模板引擎 * @param templateFileName 模板文件名 - * @param context 上下文 - * @param destPath 目标路径(绝对) + * @param context 上下文 + * @param destPath 目标路径(绝对) */ public static void toFile(VelocityEngine ve, String templateFileName, VelocityContext context, String destPath) { toFile(ve.getTemplate(templateFileName), context, destPath); @@ -144,10 +148,10 @@ public class VelocityUtil { /** * 生成文件,使用默认引擎 - * + * * @param templateFileName 模板文件名 - * @param context 模板上下文 - * @param destPath 目标路径(绝对) + * @param context 模板上下文 + * @param destPath 目标路径(绝对) */ public static void toFile(String templateFileName, VelocityContext context, String destPath) { assertInit(); @@ -157,9 +161,9 @@ public class VelocityUtil { /** * 生成文件 - * + * * @param template 模板 - * @param context 模板上下文 + * @param context 模板上下文 * @param destPath 目标路径(绝对) */ public static void toFile(Template template, VelocityContext context, String destPath) { @@ -177,11 +181,11 @@ public class VelocityUtil { /** * 生成内容写入流
    * 会自动关闭Writer - * - * @param ve 引擎 + * + * @param ve 引擎 * @param templateFileName 模板文件名 - * @param context 上下文 - * @param writer 流 + * @param context 上下文 + * @param writer 流 */ public static void toWriter(VelocityEngine ve, String templateFileName, VelocityContext context, Writer writer) { final Template template = ve.getTemplate(templateFileName); @@ -191,10 +195,10 @@ public class VelocityUtil { /** * 生成内容写入流
    * 会自动关闭Writer - * + * * @param templateFileName 模板文件名 - * @param context 上下文 - * @param writer 流 + * @param context 上下文 + * @param writer 流 */ public static void toWriter(String templateFileName, VelocityContext context, Writer writer) { assertInit(); @@ -206,10 +210,10 @@ public class VelocityUtil { /** * 生成内容写到响应内容中
    * 模板的变量来自于Request的Attribute对象 - * + * * @param templateFileName 模板文件 - * @param request 请求对象,用于获取模板中的变量值 - * @param response 响应对象 + * @param request 请求对象,用于获取模板中的变量值 + * @param response 响应对象 */ public static void toWriter(String templateFileName, javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) { final VelocityContext context = new VelocityContext(); @@ -229,9 +233,9 @@ public class VelocityUtil { /** * 融合模板和内容 - * + * * @param templateContent 模板的内容字符串 - * @param context 上下文 + * @param context 上下文 * @return 模板和内容匹配后的内容 */ public static String merge(String templateContent, VelocityContext context) { @@ -246,10 +250,10 @@ public class VelocityUtil { /** * 融合模板和内容并写入到Writer - * + * * @param template 模板 - * @param context 内容 - * @param writer Writer + * @param context 内容 + * @param writer Writer */ public static void merge(Template template, VelocityContext context, Writer writer) { if (template == null) { @@ -270,7 +274,7 @@ public class VelocityUtil { /** * 将Request中的数据转换为模板引擎
    * 取值包括Session和Request - * + * * @param context 内容 * @param request 请求对象 * @return VelocityContext @@ -289,7 +293,7 @@ public class VelocityUtil { /** * 将Session中的值放入模板上下文 - * + * * @param context 模板上下文 * @param session Session * @return VelocityContext @@ -309,9 +313,10 @@ public class VelocityUtil { } // -------------------------------------------------------------------------- Private method start + /** * 新建一个初始化后的属性对象 - * + * * @param templateDir 模板所在目录 * @return 初始化后的属性对象 */ diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/AbstractResult.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/AbstractResult.java index cbcd242d7..5d01acfc9 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/AbstractResult.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/AbstractResult.java @@ -49,7 +49,6 @@ public abstract class AbstractResult implements Result{ throw new UnsupportedOperationException("Jcseg result not allow to remove !"); } - @SuppressWarnings("NullableProblems") @Override public Iterator iterator() { return this; diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegResult.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegResult.java index 7fbf20dbb..b41c7f856 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegResult.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegResult.java @@ -63,7 +63,6 @@ public class JcsegResult implements Result{ throw new UnsupportedOperationException("Jcseg result not allow to remove !"); } - @SuppressWarnings("NullableProblems") @Override public Iterator iterator() { return this; diff --git a/hutool-extra/src/test/java/cn/hutool/extra/tokenizer/TokenizerUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/tokenizer/TokenizerUtilTest.java index 3b061cab5..195d2eec4 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/tokenizer/TokenizerUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/tokenizer/TokenizerUtilTest.java @@ -1,12 +1,6 @@ package cn.hutool.extra.tokenizer; -import java.util.Iterator; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - -import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.IterUtil; import cn.hutool.extra.tokenizer.engine.analysis.SmartcnEngine; import cn.hutool.extra.tokenizer.engine.hanlp.HanLPEngine; import cn.hutool.extra.tokenizer.engine.ikanalyzer.IKAnalyzerEngine; @@ -15,6 +9,11 @@ import cn.hutool.extra.tokenizer.engine.jieba.JiebaEngine; import cn.hutool.extra.tokenizer.engine.mmseg.MmsegEngine; import cn.hutool.extra.tokenizer.engine.mynlp.MynlpEngine; import cn.hutool.extra.tokenizer.engine.word.WordEngine; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.Iterator; /** * 模板引擎单元测试 @@ -38,7 +37,7 @@ public class TokenizerUtilTest { public void hanlpTest() { TokenizerEngine engine = new HanLPEngine(); Result result = engine.parse(text); - String resultStr = CollUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join((Iterator)result, " "); Assert.assertEquals("这 两 个 方法 的 区别 在于 返回 值", resultStr); } @@ -46,7 +45,7 @@ public class TokenizerUtilTest { public void ikAnalyzerTest() { TokenizerEngine engine = new IKAnalyzerEngine(); Result result = engine.parse(text); - String resultStr = CollUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join((Iterator)result, " "); Assert.assertEquals("这两个 方法 的 区别 在于 返回值", resultStr); } @@ -61,7 +60,7 @@ public class TokenizerUtilTest { public void jiebaTest() { TokenizerEngine engine = new JiebaEngine(); Result result = engine.parse(text); - String resultStr = CollUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join((Iterator)result, " "); Assert.assertEquals("这 两个 方法 的 区别 在于 返回值", resultStr); } @@ -76,7 +75,7 @@ public class TokenizerUtilTest { public void smartcnTest() { TokenizerEngine engine = new SmartcnEngine(); Result result = engine.parse(text); - String resultStr = CollUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join((Iterator)result, " "); Assert.assertEquals("这 两 个 方法 的 区别 在于 返回 值", resultStr); } @@ -84,7 +83,7 @@ public class TokenizerUtilTest { public void wordTest() { TokenizerEngine engine = new WordEngine(); Result result = engine.parse(text); - String resultStr = CollUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join((Iterator)result, " "); Assert.assertEquals("这两个 方法 的 区别 在于 返回值", resultStr); } @@ -94,12 +93,12 @@ public class TokenizerUtilTest { // 此单元测试需要JDK8,默认忽略 TokenizerEngine engine = new MynlpEngine(); Result result = engine.parse(text); - String resultStr = CollUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join((Iterator)result, " "); Assert.assertEquals("这 两个 方法 的 区别 在于 返回 值", resultStr); } private void checkResult(Result result) { - String resultStr = CollUtil.join((Iterator)result, " "); + String resultStr = IterUtil.join((Iterator)result, " "); Assert.assertEquals("这 两个 方法 的 区别 在于 返回 值", resultStr); } } diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/DefaultTrustManager.java b/hutool-http/src/main/java/cn/hutool/http/ssl/DefaultTrustManager.java index 0b848842e..008d1a025 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/DefaultTrustManager.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/DefaultTrustManager.java @@ -1,9 +1,7 @@ package cn.hutool.http.ssl; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; - import javax.net.ssl.X509TrustManager; +import java.security.cert.X509Certificate; /** * 证书管理 diff --git a/hutool-http/src/test/java/cn/hutool/http/test/HttpRequestTest.java b/hutool-http/src/test/java/cn/hutool/http/test/HttpRequestTest.java index 96836d5fb..34c9f60c2 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/HttpRequestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/HttpRequestTest.java @@ -1,11 +1,5 @@ package cn.hutool.http.test; -import java.util.List; -import java.util.Map; - -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.TimeInterval; import cn.hutool.core.lang.Console; @@ -13,6 +7,11 @@ import cn.hutool.core.util.CharsetUtil; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.http.ssl.SSLSocketFactoryBuilder; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.List; +import java.util.Map; /** * {@link HttpRequest}单元测试 @@ -37,6 +36,7 @@ public class HttpRequestTest { HttpResponse res = HttpRequest.get("https://www.oschina.net/").execute(); String body = res.body(); Console.log(res.getCookies()); + Console.log(body); } @Test diff --git a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java index 4c55a0ccd..693d1c264 100644 --- a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java @@ -1,5 +1,12 @@ package cn.hutool.json; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; + import java.io.IOException; import java.io.Writer; import java.time.temporal.TemporalAccessor; @@ -9,13 +16,6 @@ import java.util.Date; import java.util.Iterator; import java.util.Map; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.util.CharUtil; -import cn.hutool.core.util.NumberUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; - /** * 内部JSON工具类,仅用于JSON内部使用 * @@ -197,11 +197,11 @@ final class InternalJSONUtil { JSONObject nextTarget = target.getJSONObject(segment); if (nextTarget == null) { nextTarget = new JSONObject(); - target.put(segment, nextTarget); + target.set(segment, nextTarget); } target = nextTarget; } - target.put(path[last], value); + target.set(path[last], value); return jsonObject; } diff --git a/hutool-log/src/main/java/cn/hutool/log/GlobalLogFactory.java b/hutool-log/src/main/java/cn/hutool/log/GlobalLogFactory.java index 30712abb7..035ec7a4e 100644 --- a/hutool-log/src/main/java/cn/hutool/log/GlobalLogFactory.java +++ b/hutool-log/src/main/java/cn/hutool/log/GlobalLogFactory.java @@ -1,12 +1,5 @@ package cn.hutool.log; -import cn.hutool.log.dialect.commons.ApacheCommonsLogFactory; -import cn.hutool.log.dialect.console.ConsoleLogFactory; -import cn.hutool.log.dialect.jdk.JdkLogFactory; -import cn.hutool.log.dialect.log4j.Log4jLogFactory; -import cn.hutool.log.dialect.log4j2.Log4j2LogFactory; -import cn.hutool.log.dialect.slf4j.Slf4jLogFactory; - /** * 全局日志工厂类
    * 用于减少日志工厂创建,减少日志库探测 @@ -37,12 +30,12 @@ public class GlobalLogFactory { /** * 自定义日志实现 * - * @see Slf4jLogFactory - * @see Log4jLogFactory - * @see Log4j2LogFactory - * @see ApacheCommonsLogFactory - * @see JdkLogFactory - * @see ConsoleLogFactory + * @see cn.hutool.log.dialect.slf4j.Slf4jLogFactory + * @see cn.hutool.log.dialect.log4j.Log4jLogFactory + * @see cn.hutool.log.dialect.log4j2.Log4j2LogFactory + * @see cn.hutool.log.dialect.commons.ApacheCommonsLogFactory + * @see cn.hutool.log.dialect.jdk.JdkLogFactory + * @see cn.hutool.log.dialect.console.ConsoleLogFactory * * @param logFactoryClass 日志工厂类 * @return 自定义的日志工厂类 @@ -57,14 +50,14 @@ public class GlobalLogFactory { /** * 自定义日志实现 - * - * @see Slf4jLogFactory - * @see Log4jLogFactory - * @see Log4j2LogFactory - * @see ApacheCommonsLogFactory - * @see JdkLogFactory - * @see ConsoleLogFactory - * + * + * @see cn.hutool.log.dialect.slf4j.Slf4jLogFactory + * @see cn.hutool.log.dialect.log4j.Log4jLogFactory + * @see cn.hutool.log.dialect.log4j2.Log4j2LogFactory + * @see cn.hutool.log.dialect.commons.ApacheCommonsLogFactory + * @see cn.hutool.log.dialect.jdk.JdkLogFactory + * @see cn.hutool.log.dialect.console.ConsoleLogFactory + * * @param logFactory 日志工厂类对象 * @return 自定义的日志工厂类 */ diff --git a/hutool-log/src/main/java/cn/hutool/log/LogFactory.java b/hutool-log/src/main/java/cn/hutool/log/LogFactory.java index 80648d667..01b830ecb 100644 --- a/hutool-log/src/main/java/cn/hutool/log/LogFactory.java +++ b/hutool-log/src/main/java/cn/hutool/log/LogFactory.java @@ -3,14 +3,8 @@ package cn.hutool.log; import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.lang.caller.CallerUtil; import cn.hutool.core.util.ServiceLoaderUtil; -import cn.hutool.log.dialect.commons.ApacheCommonsLogFactory; import cn.hutool.log.dialect.console.ConsoleLogFactory; -import cn.hutool.log.dialect.jboss.JbossLogFactory; import cn.hutool.log.dialect.jdk.JdkLogFactory; -import cn.hutool.log.dialect.log4j.Log4jLogFactory; -import cn.hutool.log.dialect.log4j2.Log4j2LogFactory; -import cn.hutool.log.dialect.slf4j.Slf4jLogFactory; -import cn.hutool.log.dialect.tinylog.TinyLogFactory; import java.net.URL; import java.util.Map; @@ -20,14 +14,6 @@ import java.util.concurrent.ConcurrentHashMap; * 日志工厂类 * * @author Looly - * @see Slf4jLogFactory - * @see Log4j2LogFactory - * @see Log4jLogFactory - * @see ApacheCommonsLogFactory - * @see TinyLogFactory - * @see JbossLogFactory - * @see ConsoleLogFactory - * @see JdkLogFactory */ public abstract class LogFactory { @@ -131,14 +117,6 @@ public abstract class LogFactory { * * @param logFactoryClass 日志工厂类 * @return 自定义的日志工厂类 - * @see Slf4jLogFactory - * @see Log4j2LogFactory - * @see Log4jLogFactory - * @see ApacheCommonsLogFactory - * @see TinyLogFactory - * @see JbossLogFactory - * @see ConsoleLogFactory - * @see JdkLogFactory */ public static LogFactory setCurrentLogFactory(Class logFactoryClass) { return GlobalLogFactory.set(logFactoryClass); @@ -149,14 +127,6 @@ public abstract class LogFactory { * * @param logFactory 日志工厂类对象 * @return 自定义的日志工厂类 - * @see Slf4jLogFactory - * @see Log4j2LogFactory - * @see Log4jLogFactory - * @see ApacheCommonsLogFactory - * @see TinyLogFactory - * @see JbossLogFactory - * @see ConsoleLogFactory - * @see JdkLogFactory */ public static LogFactory setCurrentLogFactory(LogFactory logFactory) { return GlobalLogFactory.set(logFactory); @@ -195,14 +165,6 @@ public abstract class LogFactory { * 依次按照顺序检查日志库的jar是否被引入,如果未引入任何日志库,则检查ClassPath下的logging.properties,存在则使用JdkLogFactory,否则使用ConsoleLogFactory * * @return 日志实现类 - * @see Slf4jLogFactory - * @see Log4j2LogFactory - * @see Log4jLogFactory - * @see ApacheCommonsLogFactory - * @see TinyLogFactory - * @see JbossLogFactory - * @see ConsoleLogFactory - * @see JdkLogFactory */ public static LogFactory create() { final LogFactory factory = doCreate(); @@ -216,18 +178,10 @@ public abstract class LogFactory { * 依次按照顺序检查日志库的jar是否被引入,如果未引入任何日志库,则检查ClassPath下的logging.properties,存在则使用JdkLogFactory,否则使用ConsoleLogFactory * * @return 日志实现类 - * @see Slf4jLogFactory - * @see Log4j2LogFactory - * @see Log4jLogFactory - * @see ApacheCommonsLogFactory - * @see TinyLogFactory - * @see JbossLogFactory - * @see ConsoleLogFactory - * @see JdkLogFactory */ private static LogFactory doCreate() { final LogFactory factory = ServiceLoaderUtil.loadFirstAvailable(LogFactory.class); - if(null != factory){ + if (null != factory) { return factory; } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 97908780f..5c8cbb013 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -1,20 +1,5 @@ package cn.hutool.poi.excel; -import java.io.File; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.ss.extractor.ExcelExtractor; -import org.apache.poi.ss.usermodel.Row; -import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook; -import org.apache.poi.xssf.extractor.XSSFExcelExtractor; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; - import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.IterUtil; @@ -25,7 +10,20 @@ import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.CellEditor; import cn.hutool.poi.excel.cell.CellUtil; -import cn.hutool.poi.excel.editors.TrimEditor; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.extractor.ExcelExtractor; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.extractor.XSSFExcelExtractor; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * Excel读取器
    @@ -153,7 +151,6 @@ public class ExcelReader extends ExcelBase { * * @param cellEditor 单元格值处理接口 * @return this - * @see TrimEditor */ public ExcelReader setCellEditor(CellEditor cellEditor) { this.cellEditor = cellEditor; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelUtil.java index 03710ba52..1c8cddd27 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelUtil.java @@ -15,7 +15,6 @@ import cn.hutool.poi.excel.sax.handler.RowHandler; import java.io.BufferedInputStream; import java.io.File; import java.io.InputStream; -import java.io.OutputStream; /** * Excel工具类 @@ -348,7 +347,7 @@ public class ExcelUtil { // ------------------------------------------------------------------------------------------------ getWriter /** * 获得{@link ExcelWriter},默认写出到第一个sheet
    - * 不传入写出的Excel文件路径,只能调用{@link ExcelWriter#flush(OutputStream)}方法写出到流
    + * 不传入写出的Excel文件路径,只能调用ExcelWriter#flush(OutputStream)方法写出到流
    * 若写出到文件,还需调用{@link ExcelWriter#setDestFile(File)}方法自定义写出的文件,然后调用{@link ExcelWriter#flush()}方法写出到文件 * * @return {@link ExcelWriter} @@ -364,7 +363,7 @@ public class ExcelUtil { /** * 获得{@link ExcelWriter},默认写出到第一个sheet
    - * 不传入写出的Excel文件路径,只能调用{@link ExcelWriter#flush(OutputStream)}方法写出到流
    + * 不传入写出的Excel文件路径,只能调用ExcelWriter#flush(OutputStream)方法写出到流
    * 若写出到文件,还需调用{@link ExcelWriter#setDestFile(File)}方法自定义写出的文件,然后调用{@link ExcelWriter#flush()}方法写出到文件 * * @param isXlsx 是否为xlsx格式 @@ -455,7 +454,7 @@ public class ExcelUtil { // ------------------------------------------------------------------------------------------------ getBigWriter /** * 获得{@link BigExcelWriter},默认写出到第一个sheet
    - * 不传入写出的Excel文件路径,只能调用{@link BigExcelWriter#flush(OutputStream)}方法写出到流
    + * 不传入写出的Excel文件路径,只能调用ExcelWriter#flush(OutputStream)方法写出到流
    * 若写出到文件,还需调用{@link BigExcelWriter#setDestFile(File)}方法自定义写出的文件,然后调用{@link BigExcelWriter#flush()}方法写出到文件 * * @return {@link BigExcelWriter} @@ -471,7 +470,7 @@ public class ExcelUtil { /** * 获得{@link BigExcelWriter},默认写出到第一个sheet
    - * 不传入写出的Excel文件路径,只能调用{@link BigExcelWriter#flush(OutputStream)}方法写出到流
    + * 不传入写出的Excel文件路径,只能调用ExcelWriter#flush(OutputStream)方法写出到流
    * 若写出到文件,还需调用{@link BigExcelWriter#setDestFile(File)}方法自定义写出的文件,然后调用{@link BigExcelWriter#flush()}方法写出到文件 * * @param rowAccessWindowSize 在内存中的行数 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java index 95a044ad8..685841be1 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java @@ -41,10 +41,6 @@ public class Excel07SaxReader extends AbstractExcelSaxReader i * Cell中的行列号(Reference),行模式下此为行号属性名,列模式下为列号属性名 */ private static final String R_ATTR = "r"; - /** - * Cell类型 - */ - private static final String T_ELEMENT = "t"; /** * SST(SharedStringsTable) 的索引,样式index */ diff --git a/hutool-setting/src/main/java/cn/hutool/setting/GroupedMap.java b/hutool-setting/src/main/java/cn/hutool/setting/GroupedMap.java index 59f7f2cbc..4d5b944fe 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/GroupedMap.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/GroupedMap.java @@ -1,18 +1,18 @@ package cn.hutool.setting; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; + import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Set; import java.util.Map.Entry; +import java.util.Set; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.StrUtil; - /** * 基于分组的Map
    * 此对象方法线程安全 @@ -226,7 +226,6 @@ public class GroupedMap extends LinkedHashMap keySet() { readLock.lock(); @@ -277,7 +276,6 @@ public class GroupedMap extends LinkedHashMap>> entrySet() { readLock.lock(); diff --git a/hutool-setting/src/main/java/cn/hutool/setting/Setting.java b/hutool-setting/src/main/java/cn/hutool/setting/Setting.java index 9974619b8..b3990659d 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/Setting.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/Setting.java @@ -1,18 +1,5 @@ package cn.hutool.setting; -import java.io.File; -import java.net.URL; -import java.nio.charset.Charset; -import java.nio.file.Path; -import java.nio.file.WatchEvent; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.function.Consumer; - import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.IoUtil; @@ -31,6 +18,19 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.log.StaticLog; import cn.hutool.setting.dialect.Props; +import java.io.File; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.nio.file.WatchEvent; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.function.Consumer; + /** * 设置工具类。 用于支持设置(配置)文件
    * BasicSetting用于替换Properties类,提供功能更加强大的配置文件,同时对Properties文件向下兼容 @@ -638,7 +638,6 @@ public class Setting extends AbsSetting implements Map { * * @param m Map */ - @SuppressWarnings("NullableProblems") @Override public void putAll(Map m) { this.groupedMap.putAll(DEFAULT_GROUP, m); @@ -657,7 +656,6 @@ public class Setting extends AbsSetting implements Map { * * @return 默认分组(空分组)中的所有键列表 */ - @SuppressWarnings("NullableProblems") @Override public Set keySet() { return this.groupedMap.keySet(DEFAULT_GROUP); @@ -668,7 +666,6 @@ public class Setting extends AbsSetting implements Map { * * @return 默认分组(空分组)中的所有值列表 */ - @SuppressWarnings("NullableProblems") @Override public Collection values() { return this.groupedMap.values(DEFAULT_GROUP); @@ -679,7 +676,6 @@ public class Setting extends AbsSetting implements Map { * * @return 默认分组(空分组)中的所有键值对列表 */ - @SuppressWarnings("NullableProblems") @Override public Set> entrySet() { return this.groupedMap.entrySet(DEFAULT_GROUP); diff --git a/hutool-setting/src/main/java/cn/hutool/setting/SettingUtil.java b/hutool-setting/src/main/java/cn/hutool/setting/SettingUtil.java index f7a90358f..c986f8aa1 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/SettingUtil.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/SettingUtil.java @@ -56,7 +56,6 @@ public class SettingUtil { * @since 5.1.3 */ public static Setting getFirstFound(String... names) { - Setting setting; for (String name : names) { try { return get(name); diff --git a/hutool-setting/src/main/java/cn/hutool/setting/dialect/PropsUtil.java b/hutool-setting/src/main/java/cn/hutool/setting/dialect/PropsUtil.java index b1d1ab434..e3f99f34b 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/dialect/PropsUtil.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/dialect/PropsUtil.java @@ -57,7 +57,6 @@ public class PropsUtil { * @return 当前环境下配置文件 */ public static Props getFirstFound(String... names) { - Props props; for (String name : names) { try { return get(name); diff --git a/hutool-setting/src/test/java/cn/hutool/setting/test/PropsTest.java b/hutool-setting/src/test/java/cn/hutool/setting/test/PropsTest.java index 29faa349a..817bc4901 100644 --- a/hutool-setting/src/test/java/cn/hutool/setting/test/PropsTest.java +++ b/hutool-setting/src/test/java/cn/hutool/setting/test/PropsTest.java @@ -72,7 +72,6 @@ public class PropsTest { @Test public void toBeanWithNullPrefixTest(){ Props configProp = new Props(); - Boolean isInit = configProp.getBool("isInit"); configProp.setProperty("createTime", Objects.requireNonNull(DateUtil.parse("2020-01-01"))); configProp.setProperty("isInit", true); From cf5ff51a56bd7c35e56bf3229b047cc015d75224 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 4 Apr 2020 09:14:21 +0800 Subject: [PATCH 022/157] add test --- .../main/java/cn/hutool/core/io/FileUtil.java | 3 +- .../java/cn/hutool/core/io/FileUtilTest.java | 35 ++++++++++++++----- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 9882ec1d4..0390eff9f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -59,7 +59,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import java.util.jar.JarFile; import java.util.regex.Pattern; import java.util.zip.CRC32; @@ -273,7 +272,7 @@ public class FileUtil { * @param start 起始路径,必须为目录 * @param maxDepth 最大遍历深度,-1表示不限制深度 * @param visitor {@link FileVisitor} 接口,用于自定义在访问文件时,访问目录前后等节点做的操作 - * @see Files#walkFileTree(Path, Set, int, FileVisitor) + * @see Files#walkFileTree(Path, java.util.Set, int, FileVisitor) * @since 4.6.3 */ public static void walkFiles(Path start, int maxDepth, FileVisitor visitor) { 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 6154baab3..a5c240e0a 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 @@ -1,18 +1,17 @@ package cn.hutool.core.io; +import cn.hutool.core.io.file.LineSeparator; +import cn.hutool.core.lang.Console; +import cn.hutool.core.util.CharsetUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - -import cn.hutool.core.io.file.LineSeparator; -import cn.hutool.core.lang.Console; -import cn.hutool.core.util.CharsetUtil; - /** * {@link FileUtil} 单元测试类 * @@ -87,13 +86,31 @@ public class FileUtilTest { @Test @Ignore - public void copyFilesFromDir() { + public void copyFilesFromDirTest() { File srcFile = FileUtil.file("D:\\驱动"); File destFile = FileUtil.file("d:\\驱动备份"); FileUtil.copyFilesFromDir(srcFile, destFile, true); } + @Test + @Ignore + public void copyDirTest() { + File srcFile = FileUtil.file("D:\\test"); + File destFile = FileUtil.file("E:\\"); + + FileUtil.copy(srcFile, destFile, true); + } + + @Test + @Ignore + public void moveDirTest() { + File srcFile = FileUtil.file("E:\\test2"); + File destFile = FileUtil.file("D:\\"); + + FileUtil.move(srcFile, destFile, true); + } + @Test public void equlasTest() { // 源文件和目标文件都不存在 From d8baf834747c61502beb8b862e30f029c043ccbf Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 7 Apr 2020 11:19:46 +0800 Subject: [PATCH 023/157] fix bug --- CHANGELOG.md | 3 +- .../java/cn/hutool/core/util/StrUtil.java | 15 +++- .../java/cn/hutool/json/InternalJSONUtil.java | 2 +- .../main/java/cn/hutool/json/JSONArray.java | 11 ++- .../main/java/cn/hutool/json/JSONGetter.java | 78 ++++++++++--------- .../main/java/cn/hutool/json/JSONObject.java | 25 +++--- .../main/java/cn/hutool/json/JSONTokener.java | 28 ++++--- .../src/main/java/cn/hutool/json/XML.java | 2 +- .../main/java/cn/hutool/json/XMLTokener.java | 44 ++++++----- .../java/cn/hutool/json/JSONObjectTest.java | 5 +- .../cn/hutool/json/test/bean/UserWithMap.java | 11 +-- 11 files changed, 124 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9966a05f6..e72855ad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -## 5.2.6 (2020-04-01) +## 5.3.0 (2020-04-07) ### 新特性 * 【extra 】 JschUtil增加execByShell方法(issue#I1CYES@Gitee) @@ -25,6 +25,7 @@ * 【extra 】 修复SpringUtil使用devtools重启报错问题 * 【http 】 修复HttpUtil.encodeParams针对无参数URL问题(issue#817@Github) * 【extra 】 修复模板中无效引用的问题 +* 【extra 】 修复读取JSON文本配置未应用到子对象的问题(issue#818@Github) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java index f3f36f253..530d51d21 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java @@ -2385,8 +2385,12 @@ public class StrUtil { } /** - * 将对象转为字符串
    - * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * 将对象转为字符串 + * + *
    +	 * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组
    +	 * 2、对象数组会调用Arrays.toString方法
    +	 * 
     	 *
     	 * @param obj         对象
     	 * @param charsetName 字符集
    @@ -2397,8 +2401,11 @@ public class StrUtil {
     	}
     
     	/**
    -	 * 将对象转为字符串
    - * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * 将对象转为字符串 + *
    +	 * 	 1、Byte数组和ByteBuffer会被转换为对应字符串的数组
    +	 * 	 2、对象数组会调用Arrays.toString方法
    +	 * 
     	 *
     	 * @param obj     对象
     	 * @param charset 字符集
    diff --git a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java
    index 693d1c264..b3be2fee6 100644
    --- a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java
    +++ b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java
    @@ -196,7 +196,7 @@ final class InternalJSONUtil {
     			String segment = path[i];
     			JSONObject nextTarget = target.getJSONObject(segment);
     			if (nextTarget == null) {
    -				nextTarget = new JSONObject();
    +				nextTarget = new JSONObject(target.getConfig());
     				target.set(segment, nextTarget);
     			}
     			target = nextTarget;
    diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java
    index 5a17702f7..da5abb041 100644
    --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java
    +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java
    @@ -189,6 +189,11 @@ public class JSONArray implements JSON, JSONGetter, List, Rando
     	}
     	// -------------------------------------------------------------------------------------------------------------------- Constructor start
     
    +	@Override
    +	public JSONConfig getConfig() {
    +		return this.config;
    +	}
    +
     	/**
     	 * 设置转为字符串时的日期格式,默认为时间戳(null值)
     	 * 
    @@ -295,7 +300,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando
     		if (names == null || names.size() == 0 || this.size() == 0) {
     			return null;
     		}
    -		JSONObject jo = new JSONObject();
    +		final JSONObject jo = new JSONObject(this.config);
     		for (int i = 0; i < names.size(); i += 1) {
     			jo.set(names.getStr(i), this.getObj(i));
     		}
    @@ -591,6 +596,8 @@ public class JSONArray implements JSON, JSONGetter, List, Rando
     		} else if (source instanceof CharSequence) {
     			// JSON字符串
     			init((CharSequence) source);
    +		}else if (source instanceof JSONTokener) {
    +			init((JSONTokener) source);
     		} else {
     			Iterator iter;
     			if (source.getClass().isArray()) {// 数组
    @@ -615,7 +622,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando
     	 */
     	private void init(CharSequence source) {
     		if (null != source) {
    -			init(new JSONTokener(StrUtil.trim(source)));
    +			init(new JSONTokener(StrUtil.trim(source), this.config));
     		}
     	}
     
    diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java
    index 547e15c99..e05c03d75 100644
    --- a/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java
    +++ b/hutool-json/src/main/java/cn/hutool/json/JSONGetter.java
    @@ -5,12 +5,20 @@ import cn.hutool.core.getter.OptNullBasicTypeFromObjectGetter;
     
     /**
      * 用于JSON的Getter类,提供各种类型的Getter方法
    - * @author Looly
      *
      * @param  Key类型
    + * @author Looly
      */
    -public interface JSONGetter extends OptNullBasicTypeFromObjectGetter{
    -	
    +public interface JSONGetter extends OptNullBasicTypeFromObjectGetter {
    +
    +	/**
    +	 * 获取JSON配置
    +	 *
    +	 * @return {@link JSONConfig}
    +	 * @since 5.3.0
    +	 */
    +	JSONConfig getConfig();
    +
     	/**
     	 * key对应值是否为null或无此key
     	 *
    @@ -20,10 +28,10 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter{
     	default boolean isNull(K key) {
     		return JSONNull.NULL.equals(this.getObj(key));
     	}
    -	
    +
     	/**
     	 * 获取字符串类型值,并转义不可见字符,如'\n'换行符会被转义为字符串"\n"
    -	 * 
    +	 *
     	 * @param key 键
     	 * @return 字符串类型值
     	 * @since 4.2.2
    @@ -31,11 +39,11 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter{
     	default String getStrEscaped(K key) {
     		return getStrEscaped(key, null);
     	}
    -	
    +
     	/**
     	 * 获取字符串类型值,并转义不可见字符,如'\n'换行符会被转义为字符串"\n"
    -	 * 
    -	 * @param key 键
    +	 *
    +	 * @param key          键
     	 * @param defaultValue 默认值
     	 * @return 字符串类型值
     	 * @since 4.2.2
    @@ -43,51 +51,51 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter{
     	default String getStrEscaped(K key, String defaultValue) {
     		return JSONUtil.escape(getStr(key, defaultValue));
     	}
    -	
    +
     	/**
     	 * 获得JSONArray对象
    * 如果值为其它类型对象,尝试转换为{@link JSONArray}返回,否则抛出异常 - * + * * @param key KEY * @return JSONArray对象,如果值为null或者非JSONArray类型,返回null */ default JSONArray getJSONArray(K key) { final Object object = this.getObj(key); - if(null == object) { + if (null == object) { return null; } - - if(object instanceof JSONArray) { + + if (object instanceof JSONArray) { return (JSONArray) object; } - return new JSONArray(object); + return new JSONArray(object, getConfig()); } /** * 获得JSONObject对象
    * 如果值为其它类型对象,尝试转换为{@link JSONObject}返回,否则抛出异常 - * + * * @param key KEY * @return JSONArray对象,如果值为null或者非JSONObject类型,返回null */ default JSONObject getJSONObject(K key) { final Object object = this.getObj(key); - if(null == object) { + if (null == object) { return null; } - - if(object instanceof JSONObject) { + + if (object instanceof JSONObject) { return (JSONObject) object; } - return new JSONObject(object); + return new JSONObject(object, getConfig()); } - + /** * 从JSON中直接获取Bean对象
    * 先获取JSONObject对象,然后转为Bean对象 - * - * @param Bean类型 - * @param key KEY + * + * @param Bean类型 + * @param key KEY * @param beanType Bean类型 * @return Bean对象,如果值为null或者非JSONObject类型,返回null * @since 3.1.1 @@ -96,36 +104,36 @@ public interface JSONGetter extends OptNullBasicTypeFromObjectGetter{ final JSONObject obj = getJSONObject(key); return (null == obj) ? null : obj.toBean(beanType); } - + /** * 获取指定类型的对象
    * 转换失败或抛出异常 - * - * @param 获取的对象类型 - * @param key 键 + * + * @param 获取的对象类型 + * @param key 键 * @param type 获取对象类型 * @return 对象 * @throws ConvertException 转换异常 * @since 3.0.8 */ - default T get(K key, Class type) throws ConvertException{ + default T get(K key, Class type) throws ConvertException { return get(key, type, false); } - + /** * 获取指定类型的对象 - * - * @param 获取的对象类型 - * @param key 键 - * @param type 获取对象类型 + * + * @param 获取的对象类型 + * @param key 键 + * @param type 获取对象类型 * @param ignoreError 是否跳过转换失败的对象或值 * @return 对象 * @throws ConvertException 转换异常 * @since 3.0.8 */ - default T get(K key, Class type, boolean ignoreError) throws ConvertException{ + default T get(K key, Class type, boolean ignoreError) throws ConvertException { final Object value = this.getObj(key); - if(null == value){ + if (null == value) { return null; } return JSONConverter.jsonConvert(type, value, ignoreError); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index 655d31454..cd62146c3 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -7,6 +7,7 @@ import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.ObjectUtil; @@ -22,7 +23,6 @@ import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -103,14 +103,17 @@ public class JSONObject implements JSON, JSONGetter, Map * 构造 * * @param capacity 初始大小 - * @param config JSON配置项 + * @param config JSON配置项,null表示默认配置 * @since 4.1.19 */ public JSONObject(int capacity, JSONConfig config) { + if(null == config){ + config = JSONConfig.create(); + } if (config.isIgnoreCase()) { this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); } else { - this.rawHashMap = config.isOrder() ? new LinkedHashMap<>(capacity) : new HashMap<>(capacity); + this.rawHashMap = MapUtil.newHashMap(config.isOrder()); } this.config = config; } @@ -241,12 +244,7 @@ public class JSONObject implements JSON, JSONGetter, Map // -------------------------------------------------------------------------------------------------------------------- Constructor end - /** - * 获取JSON配置 - * - * @return {@link JSONConfig} - * @since 4.3.1 - */ + @Override public JSONConfig getConfig() { return this.config; } @@ -493,19 +491,16 @@ public class JSONObject implements JSON, JSONGetter, Map rawHashMap.clear(); } - @SuppressWarnings("NullableProblems") @Override public Set keySet() { return this.rawHashMap.keySet(); } - @SuppressWarnings("NullableProblems") @Override public Collection values() { return rawHashMap.values(); } - @SuppressWarnings("NullableProblems") @Override public Set> entrySet() { return rawHashMap.entrySet(); @@ -661,7 +656,7 @@ public class JSONObject implements JSON, JSONGetter, Map * * @param source JavaBean或者Map对象或者String */ - @SuppressWarnings({"rawtypes", "unchecked", "StatementWithEmptyBody"}) + @SuppressWarnings({"rawtypes", "unchecked"}) private void init(Object source) { if (null == source) { return; @@ -696,7 +691,7 @@ public class JSONObject implements JSON, JSONGetter, Map * @param source JSON字符串 */ private void init(CharSequence source) { - init(new JSONTokener(StrUtil.trim(source))); + init(new JSONTokener(StrUtil.trim(source), this.config)); } /** @@ -711,7 +706,7 @@ public class JSONObject implements JSON, JSONGetter, Map if (x.nextClean() != '{') { throw x.syntaxError("A JSONObject text must begin with '{'"); } - for (;;) { + while (true) { c = x.nextClean(); switch (c) { case 0: diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java index 0cca017a5..43ee692b1 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java @@ -1,5 +1,7 @@ package cn.hutool.json; +import cn.hutool.core.util.StrUtil; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -40,14 +42,20 @@ public class JSONTokener { */ private Reader reader; + /** + * JSON配置 + */ + private JSONConfig config; + // ------------------------------------------------------------------------------------ Constructor start /** * 从Reader中构建 * * @param reader Reader + * @param config JSON配置 */ - public JSONTokener(Reader reader) { + public JSONTokener(Reader reader, JSONConfig config) { this.reader = reader.markSupported() ? reader : new BufferedReader(reader); this.eof = false; this.usePrevious = false; @@ -61,18 +69,20 @@ public class JSONTokener { * 从InputStream中构建 * * @param inputStream InputStream + * @param config JSON配置 */ - public JSONTokener(InputStream inputStream) throws JSONException { - this(new InputStreamReader(inputStream)); + public JSONTokener(InputStream inputStream, JSONConfig config) throws JSONException { + this(new InputStreamReader(inputStream), config); } /** * 从字符串中构建 * - * @param s JSON字符串 + * @param s JSON字符串 + * @param config JSON配置 */ - public JSONTokener(String s) { - this(new StringReader(s)); + public JSONTokener(CharSequence s, JSONConfig config) { + this(new StringReader(StrUtil.str(s)), config); } // ------------------------------------------------------------------------------------ Constructor end @@ -318,10 +328,10 @@ public class JSONTokener { return this.nextString(c); case '{': this.back(); - return new JSONObject(this); + return new JSONObject(this, this.config); case '[': this.back(); - return new JSONArray(this); + return new JSONArray(this, this.config); } /* @@ -391,7 +401,7 @@ public class JSONTokener { * @return {@link JSONArray} */ public JSONArray toJSONArray() { - JSONArray jsonArray = new JSONArray(); + JSONArray jsonArray = new JSONArray(this.config); if (this.nextClean() != '[') { throw this.syntaxError("A JSONArray text must start with '['"); } diff --git a/hutool-json/src/main/java/cn/hutool/json/XML.java b/hutool-json/src/main/java/cn/hutool/json/XML.java index 08ccb810a..e2067ec55 100644 --- a/hutool-json/src/main/java/cn/hutool/json/XML.java +++ b/hutool-json/src/main/java/cn/hutool/json/XML.java @@ -229,7 +229,7 @@ public class XML { */ public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { JSONObject jo = new JSONObject(); - XMLTokener x = new XMLTokener(string); + XMLTokener x = new XMLTokener(string, jo.getConfig()); while (x.more() && x.skipPast("<")) { parse(x, jo, null, keepStrings); } diff --git a/hutool-json/src/main/java/cn/hutool/json/XMLTokener.java b/hutool-json/src/main/java/cn/hutool/json/XMLTokener.java index 79e79b29d..9ed2559f8 100644 --- a/hutool-json/src/main/java/cn/hutool/json/XMLTokener.java +++ b/hutool-json/src/main/java/cn/hutool/json/XMLTokener.java @@ -2,18 +2,19 @@ package cn.hutool.json; /** * XML分析器,继承自JSONTokener,提供XML的语法分析 - * + * * @author JSON.org */ public class XMLTokener extends JSONTokener { /** - * The table of entity values. It initially contains Character values for amp, apos, gt, lt, quot. + * The table of entity values. + * It initially contains Character values for amp, apos, gt, lt, quot. */ public static final java.util.HashMap entity; static { - entity = new java.util.HashMap(8); + entity = new java.util.HashMap<>(8); entity.put("amp", XML.AMP); entity.put("apos", XML.APOS); entity.put("gt", XML.GT); @@ -23,16 +24,17 @@ public class XMLTokener extends JSONTokener { /** * Construct an XMLTokener from a string. - * - * @param s A source string. + * + * @param s A source string. + * @param config JSON配置 */ - public XMLTokener(String s) { - super(s); + public XMLTokener(CharSequence s, JSONConfig config) { + super(s, config); } /** * Get the text in the CDATA block. - * + * * @return The string up to the ]]>. * @throws JSONException If the ]]> is not found. */ @@ -40,7 +42,7 @@ public class XMLTokener extends JSONTokener { char c; int i; StringBuilder sb = new StringBuilder(); - for (;;) { + for (; ; ) { c = next(); if (end()) { throw syntaxError("Unclosed CDATA"); @@ -55,7 +57,7 @@ public class XMLTokener extends JSONTokener { } /** - * Get the next XML outer token, trimming whitespace. + * Get the next XML outer token, trimming whitespace. * There are two kinds of tokens: the '>' character which begins a markup tag, and the content text between markup tags. * * @return A string, or a '>' Character, or null if there is no more source text. @@ -74,7 +76,7 @@ public class XMLTokener extends JSONTokener { return XML.LT; } sb = new StringBuilder(); - for (;;) { + for (; ; ) { if (c == '<' || c == 0) { back(); return sb.toString().trim(); @@ -90,14 +92,14 @@ public class XMLTokener extends JSONTokener { /** * Return the next entity. These entities are translated to Characters: & ' > < ". - * + * * @param ampersand An ampersand character. * @return A Character or an entity String if the entity is not recognized. * @throws JSONException If missing ';' in XML entity. */ public Object nextEntity(char ampersand) throws JSONException { StringBuilder sb = new StringBuilder(); - for (;;) { + for (; ; ) { char c = next(); if (Character.isLetterOrDigit(c) || c == '#') { sb.append(Character.toLowerCase(c)); @@ -114,7 +116,7 @@ public class XMLTokener extends JSONTokener { /** * Returns the next XML meta token. This is used for skipping over <!...> and <?...?> structures. - * + * * @return Syntax characters (< > / = ! ?) are returned as Character, and strings and names are returned as Boolean. We don't care what the values actually are. * @throws JSONException 字符串中属性未关闭或XML结构错误抛出此异常。If a string is not properly closed or if the XML is badly structured. */ @@ -142,7 +144,7 @@ public class XMLTokener extends JSONTokener { case '"': case '\'': q = c; - for (;;) { + for (; ; ) { c = next(); if (c == 0) { throw syntaxError("Unterminated string"); @@ -152,7 +154,7 @@ public class XMLTokener extends JSONTokener { } } default: - for (;;) { + for (; ; ) { c = next(); if (Character.isWhitespace(c)) { return Boolean.TRUE; @@ -177,7 +179,7 @@ public class XMLTokener extends JSONTokener { /** * Get the next XML Token. These tokens are found inside of angle brackets. It may be one of these characters: / > = ! ? or it may be a string wrapped in single quotes or double * quotes, or it may be a name. - * + * * @return a String or a Character. * @throws JSONException If the XML is not well formed. */ @@ -210,7 +212,7 @@ public class XMLTokener extends JSONTokener { case '\'': q = c; sb = new StringBuilder(); - for (;;) { + for (; ; ) { c = next(); if (c == 0) { throw syntaxError("Unterminated string"); @@ -229,7 +231,7 @@ public class XMLTokener extends JSONTokener { // Name sb = new StringBuilder(); - for (;;) { + for (; ; ) { sb.append(c); c = next(); if (Character.isWhitespace(c)) { @@ -258,7 +260,7 @@ public class XMLTokener extends JSONTokener { /** * Skip characters until past the requested string. If it is not found, we are left at the end of the source with a result of false. - * + * * @param to A string to skip past. * @return 是否成功skip * @throws JSONException JSON异常 @@ -286,7 +288,7 @@ public class XMLTokener extends JSONTokener { /* We will loop, possibly for all of the remaining characters. */ - for (;;) { + for (; ; ) { j = offset; b = true; diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 08fc8908e..b6cdf73ea 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -189,9 +189,8 @@ public class JSONObjectTest { public void toBeanTest3() { String jsonStr = "{'data':{'userName':'ak','password': null}}"; UserWithMap user = JSONUtil.toBean(JSONUtil.parseObj(jsonStr), UserWithMap.class); - String password = user.getData().get("password"); - Assert.assertTrue(user.getData().containsKey("password")); - Assert.assertNull(password); + // Bean默认忽略null + Assert.assertFalse(user.getData().containsKey("password")); } @Test diff --git a/hutool-json/src/test/java/cn/hutool/json/test/bean/UserWithMap.java b/hutool-json/src/test/java/cn/hutool/json/test/bean/UserWithMap.java index aa5965b6c..4ed56d226 100644 --- a/hutool-json/src/test/java/cn/hutool/json/test/bean/UserWithMap.java +++ b/hutool-json/src/test/java/cn/hutool/json/test/bean/UserWithMap.java @@ -1,15 +1,10 @@ package cn.hutool.json.test.bean; +import lombok.Data; + import java.util.Map; +@Data public class UserWithMap { private Map data; - - public Map getData() { - return data; - } - - public void setData(Map data) { - this.data = data; - } } From 91e9511738f32c4021e8bd5769dbd913c4fd17dd Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 7 Apr 2020 11:22:13 +0800 Subject: [PATCH 024/157] fix bug --- hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java index 530d51d21..e9b5b26b2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java @@ -2390,7 +2390,7 @@ public class StrUtil { *
     	 * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组
     	 * 2、对象数组会调用Arrays.toString方法
    -	 * 
    +	 * 
    * * @param obj 对象 * @param charsetName 字符集 @@ -2405,7 +2405,7 @@ public class StrUtil { *
     	 * 	 1、Byte数组和ByteBuffer会被转换为对应字符串的数组
     	 * 	 2、对象数组会调用Arrays.toString方法
    -	 * 
    +	 * 
    * * @param obj 对象 * @param charset 字符集 From b1e10cc76311bc3ef4db27e7fb3b3b931f2008f2 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 7 Apr 2020 11:26:21 +0800 Subject: [PATCH 025/157] fix comment --- .../cn/hutool/core/collection/CollUtil.java | 14 +- .../cn/hutool/core/collection/IterUtil.java | 19 +- .../main/java/cn/hutool/core/io/IoUtil.java | 277 +++++++++--------- 3 files changed, 162 insertions(+), 148 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index d5cf28afa..df01282fb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -1760,6 +1760,7 @@ public class CollUtil { * @param Map键类型 * @param Map值类型 * @param values 数据列表 + * @param map Map对象,转换后的键值对加入此Map,通过传入此对象自定义Map类型 * @param keyFunc 生成key的函数 * @return 生成的map * @since 5.2.6 @@ -1772,12 +1773,13 @@ public class CollUtil { * 集合转换为Map,转换规则为:
    * 按照keyFunc函数规则根据元素对象生成Key,按照valueFunc函数规则根据元素对象生成value组成新的Map * - * @param Map键类型 - * @param Map值类型 - * @param 元素类型 - * @param values 数据列表 - * @param map Map对象,转换后的键值对加入此Map,通过传入此对象自定义Map类型 - * @param keyFunc 生成key的函数 + * @param Map键类型 + * @param Map值类型 + * @param 元素类型 + * @param values 数据列表 + * @param map Map对象,转换后的键值对加入此Map,通过传入此对象自定义Map类型 + * @param keyFunc 生成key的函数 + * @param valueFunc 生成值的策略函数 * @return 生成的map * @since 5.2.6 */ diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java index fd5957767..79e71f37f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java @@ -202,7 +202,7 @@ public class IterUtil { */ @SuppressWarnings("unchecked") public static Map fieldValueMap(Iterator iter, String fieldName) { - return toMap(iter, new HashMap<>(), (value)->(K)ReflectUtil.getFieldValue(value, fieldName)); + return toMap(iter, new HashMap<>(), (value) -> (K) ReflectUtil.getFieldValue(value, fieldName)); } /** @@ -236,8 +236,8 @@ public class IterUtil { @SuppressWarnings("unchecked") public static Map fieldValueAsMap(Iterator iter, String fieldNameForKey, String fieldNameForValue) { return toMap(iter, new HashMap<>(), - (value)->(K)ReflectUtil.getFieldValue(value, fieldNameForKey), - (value)->(V)ReflectUtil.getFieldValue(value, fieldNameForValue) + (value) -> (K) ReflectUtil.getFieldValue(value, fieldNameForKey), + (value) -> (V) ReflectUtil.getFieldValue(value, fieldNameForValue) ); } @@ -644,12 +644,13 @@ public class IterUtil { * 集合转换为Map,转换规则为:
    * 按照keyFunc函数规则根据元素对象生成Key,按照valueFunc函数规则根据元素对象生成value组成新的Map * - * @param Map键类型 - * @param Map值类型 - * @param 元素类型 - * @param iterator 数据列表 - * @param map Map对象,转换后的键值对加入此Map,通过传入此对象自定义Map类型 - * @param keyFunc 生成key的函数 + * @param Map键类型 + * @param Map值类型 + * @param 元素类型 + * @param iterator 数据列表 + * @param map Map对象,转换后的键值对加入此Map,通过传入此对象自定义Map类型 + * @param keyFunc 生成key的函数 + * @param valueFunc 生成值的策略函数 * @return 生成的map * @since 5.2.6 */ diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java index 06c2ea032..0966d544a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java @@ -46,26 +46,34 @@ import java.util.zip.Checksum; /** * IO工具类
    * IO工具类只是辅助流的读写,并不负责关闭流。原因是流可能被多次读写,读写关闭后容易造成问题。 - * - * @author xiaoleilu * + * @author xiaoleilu */ public class IoUtil { - /** 默认缓存大小 8192*/ + /** + * 默认缓存大小 8192 + */ public static final int DEFAULT_BUFFER_SIZE = 2 << 12; - /** 默认中等缓存大小 16384*/ + /** + * 默认中等缓存大小 16384 + */ public static final int DEFAULT_MIDDLE_BUFFER_SIZE = 2 << 13; - /** 默认大缓存大小 32768*/ + /** + * 默认大缓存大小 32768 + */ public static final int DEFAULT_LARGE_BUFFER_SIZE = 2 << 14; - /** 数据流末尾 */ + /** + * 数据流末尾 + */ public static final int EOF = -1; // -------------------------------------------------------------------------------------- Copy start + /** * 将Reader中的内容复制到Writer中 使用默认缓存大小,拷贝后不关闭Reader - * + * * @param reader Reader * @param writer Writer * @return 拷贝的字节数 @@ -77,9 +85,9 @@ public class IoUtil { /** * 将Reader中的内容复制到Writer中,拷贝后不关闭Reader - * - * @param reader Reader - * @param writer Writer + * + * @param reader Reader + * @param writer Writer * @param bufferSize 缓存大小 * @return 传输的byte数 * @throws IORuntimeException IO异常 @@ -90,10 +98,10 @@ public class IoUtil { /** * 将Reader中的内容复制到Writer中,拷贝后不关闭Reader - * - * @param reader Reader - * @param writer Writer - * @param bufferSize 缓存大小 + * + * @param reader Reader + * @param writer Writer + * @param bufferSize 缓存大小 * @param streamProgress 进度处理器 * @return 传输的byte数 * @throws IORuntimeException IO异常 @@ -125,8 +133,8 @@ public class IoUtil { /** * 拷贝流,使用默认Buffer大小,拷贝后不关闭流 - * - * @param in 输入流 + * + * @param in 输入流 * @param out 输出流 * @return 传输的byte数 * @throws IORuntimeException IO异常 @@ -137,9 +145,9 @@ public class IoUtil { /** * 拷贝流,拷贝后不关闭流 - * - * @param in 输入流 - * @param out 输出流 + * + * @param in 输入流 + * @param out 输出流 * @param bufferSize 缓存大小 * @return 传输的byte数 * @throws IORuntimeException IO异常 @@ -150,10 +158,10 @@ public class IoUtil { /** * 拷贝流,拷贝后不关闭流 - * - * @param in 输入流 - * @param out 输出流 - * @param bufferSize 缓存大小 + * + * @param in 输入流 + * @param out 输出流 + * @param bufferSize 缓存大小 * @param streamProgress 进度条 * @return 传输的byte数 * @throws IORuntimeException IO异常 @@ -171,7 +179,7 @@ public class IoUtil { } long size = 0; try { - for (int readSize; (readSize = in.read(buffer)) != EOF;) { + for (int readSize; (readSize = in.read(buffer)) != EOF; ) { out.write(buffer, 0, readSize); size += readSize; out.flush(); @@ -191,10 +199,10 @@ public class IoUtil { /** * 拷贝流 thanks to: https://github.com/venusdrogon/feilong-io/blob/master/src/main/java/com/feilong/io/IOWriteUtil.java
    * 本方法不会关闭流 - * - * @param in 输入流 - * @param out 输出流 - * @param bufferSize 缓存大小 + * + * @param in 输入流 + * @param out 输出流 + * @param bufferSize 缓存大小 * @param streamProgress 进度条 * @return 传输的byte数 * @throws IORuntimeException IO异常 @@ -205,8 +213,8 @@ public class IoUtil { /** * 拷贝文件流,使用NIO - * - * @param in 输入 + * + * @param in 输入 * @param out 输出 * @return 拷贝的字节数 * @throws IORuntimeException IO异常 @@ -231,8 +239,8 @@ public class IoUtil { /** * 拷贝流,使用NIO,不会关闭流 - * - * @param in {@link ReadableByteChannel} + * + * @param in {@link ReadableByteChannel} * @param out {@link WritableByteChannel} * @return 拷贝的字节数 * @throws IORuntimeException IO异常 @@ -244,9 +252,9 @@ public class IoUtil { /** * 拷贝流,使用NIO,不会关闭流 - * - * @param in {@link ReadableByteChannel} - * @param out {@link WritableByteChannel} + * + * @param in {@link ReadableByteChannel} + * @param out {@link WritableByteChannel} * @param bufferSize 缓冲大小,如果小于等于0,使用默认 * @return 拷贝的字节数 * @throws IORuntimeException IO异常 @@ -258,10 +266,10 @@ public class IoUtil { /** * 拷贝流,使用NIO,不会关闭流 - * - * @param in {@link ReadableByteChannel} - * @param out {@link WritableByteChannel} - * @param bufferSize 缓冲大小,如果小于等于0,使用默认 + * + * @param in {@link ReadableByteChannel} + * @param out {@link WritableByteChannel} + * @param bufferSize 缓冲大小,如果小于等于0,使用默认 * @param streamProgress {@link StreamProgress}进度处理器 * @return 拷贝的字节数 * @throws IORuntimeException IO异常 @@ -296,6 +304,7 @@ public class IoUtil { // -------------------------------------------------------------------------------------- Copy end // -------------------------------------------------------------------------------------- getReader and getWriter start + /** * 获得一个文件读取器,默认使用UTF-8编码 * @@ -309,8 +318,8 @@ public class IoUtil { /** * 获得一个文件读取器 - * - * @param in 输入流 + * + * @param in 输入流 * @param charsetName 字符集名称 * @return BufferedReader对象 */ @@ -320,8 +329,8 @@ public class IoUtil { /** * 获得一个Reader - * - * @param in 输入流 + * + * @param in 输入流 * @param charset 字符集 * @return BufferedReader对象 */ @@ -343,7 +352,7 @@ public class IoUtil { /** * 获得{@link BufferedReader}
    * 如果是{@link BufferedReader}强转返回,否则新建。如果提供的Reader为null返回null - * + * * @param reader 普通Reader,如果为null返回null * @return {@link BufferedReader} or null * @since 3.0.9 @@ -359,8 +368,8 @@ public class IoUtil { /** * 获得{@link PushbackReader}
    * 如果是{@link PushbackReader}强转返回,否则新建 - * - * @param reader 普通Reader + * + * @param reader 普通Reader * @param pushBackSize 推后的byte数 * @return {@link PushbackReader} * @since 3.1.0 @@ -382,8 +391,8 @@ public class IoUtil { /** * 获得一个Writer - * - * @param out 输入流 + * + * @param out 输入流 * @param charsetName 字符集 * @return OutputStreamWriter对象 */ @@ -393,8 +402,8 @@ public class IoUtil { /** * 获得一个Writer - * - * @param out 输入流 + * + * @param out 输入流 * @param charset 字符集 * @return OutputStreamWriter对象 */ @@ -412,10 +421,11 @@ public class IoUtil { // -------------------------------------------------------------------------------------- getReader and getWriter end // -------------------------------------------------------------------------------------- read start + /** * 从流中读取内容 - * - * @param in 输入流 + * + * @param in 输入流 * @param charsetName 字符集 * @return 内容 * @throws IORuntimeException IO异常 @@ -427,8 +437,8 @@ public class IoUtil { /** * 从流中读取内容,读取完毕后并不关闭流 - * - * @param in 输入流,读取完毕后并不关闭流 + * + * @param in 输入流,读取完毕后并不关闭流 * @param charset 字符集 * @return 内容 * @throws IORuntimeException IO异常 @@ -440,7 +450,7 @@ public class IoUtil { /** * 从流中读取内容,读取完毕后并不关闭流 - * + * * @param channel 可读通道,读取完毕后并不关闭通道 * @param charset 字符集 * @return 内容 @@ -454,7 +464,7 @@ public class IoUtil { /** * 从流中读取内容,读到输出流中,读取完毕后并不关闭流 - * + * * @param in 输入流 * @return 输出流 * @throws IORuntimeException IO异常 @@ -467,7 +477,7 @@ public class IoUtil { /** * 从流中读取内容,读到输出流中 - * + * * @param channel 可读通道,读取完毕后并不关闭通道 * @return 输出流 * @throws IORuntimeException IO异常 @@ -480,7 +490,7 @@ public class IoUtil { /** * 从Reader中读取String,读取完毕后并不关闭Reader - * + * * @param reader Reader * @return String * @throws IORuntimeException IO异常 @@ -500,7 +510,7 @@ public class IoUtil { /** * 从FileChannel中读取UTF-8编码内容 - * + * * @param fileChannel 文件管道 * @return 内容 * @throws IORuntimeException IO异常 @@ -511,7 +521,7 @@ public class IoUtil { /** * 从FileChannel中读取内容,读取完毕后并不关闭Channel - * + * * @param fileChannel 文件管道 * @param charsetName 字符集 * @return 内容 @@ -523,9 +533,9 @@ public class IoUtil { /** * 从FileChannel中读取内容 - * + * * @param fileChannel 文件管道 - * @param charset 字符集 + * @param charset 字符集 * @return 内容 * @throws IORuntimeException IO异常 */ @@ -541,7 +551,7 @@ public class IoUtil { /** * 从流中读取bytes,读取完毕后关闭流 - * + * * @param in {@link InputStream} * @return bytes * @throws IORuntimeException IO异常 @@ -553,7 +563,7 @@ public class IoUtil { /** * 从流中读取bytes * - * @param in {@link InputStream} + * @param in {@link InputStream} * @param isCloseStream 是否关闭输入流 * @return bytes * @throws IORuntimeException IO异常 @@ -562,7 +572,7 @@ public class IoUtil { public static byte[] readBytes(InputStream in, boolean isCloseStream) throws IORuntimeException { final FastByteArrayOutputStream out = new FastByteArrayOutputStream(); copy(in, out); - if(isCloseStream){ + if (isCloseStream) { close(in); } return out.toByteArray(); @@ -570,8 +580,8 @@ public class IoUtil { /** * 读取指定长度的byte数组,不关闭流 - * - * @param in {@link InputStream},为null返回null + * + * @param in {@link InputStream},为null返回null * @param length 长度,小于等于0返回空byte数组 * @return bytes * @throws IORuntimeException IO异常 @@ -602,9 +612,9 @@ public class IoUtil { /** * 读取16进制字符串 - * - * @param in {@link InputStream} - * @param length 长度 + * + * @param in {@link InputStream} + * @param length 长度 * @param toLowerCase true 传换成小写格式 , false 传换成大写格式 * @return 16进制字符串 * @throws IORuntimeException IO异常 @@ -615,7 +625,7 @@ public class IoUtil { /** * 从流中读取前28个byte并转换为16进制,字母部分使用大写 - * + * * @param in {@link InputStream} * @return 16进制字符串 * @throws IORuntimeException IO异常 @@ -626,7 +636,7 @@ public class IoUtil { /** * 从流中读取前28个byte并转换为16进制,字母部分使用小写 - * + * * @param in {@link InputStream} * @return 16进制字符串 * @throws IORuntimeException IO异常 @@ -637,12 +647,12 @@ public class IoUtil { /** * 从流中读取对象,即对象的反序列化 - * + * * @param 读取对象的类型 - * @param in 输入流 + * @param in 输入流 * @return 输出流 * @throws IORuntimeException IO异常 - * @throws UtilException ClassNotFoundException包装 + * @throws UtilException ClassNotFoundException包装 * @deprecated 由于存在对象反序列化漏洞风险,请使用{@link #readObj(InputStream, Class)} */ @Deprecated @@ -653,11 +663,12 @@ public class IoUtil { /** * 从流中读取对象,即对象的反序列化,读取后不关闭流 * - * @param 读取对象的类型 - * @param in 输入流 + * @param 读取对象的类型 + * @param in 输入流 + * @param clazz 读取对象类型 * @return 输出流 * @throws IORuntimeException IO异常 - * @throws UtilException ClassNotFoundException包装 + * @throws UtilException ClassNotFoundException包装 */ public static T readObj(InputStream in, Class clazz) throws IORuntimeException, UtilException { if (in == null) { @@ -677,9 +688,9 @@ public class IoUtil { /** * 从流中读取内容,使用UTF-8编码 - * - * @param 集合类型 - * @param in 输入流 + * + * @param 集合类型 + * @param in 输入流 * @param collection 返回集合 * @return 内容 * @throws IORuntimeException IO异常 @@ -690,11 +701,11 @@ public class IoUtil { /** * 从流中读取内容 - * - * @param 集合类型 - * @param in 输入流 + * + * @param 集合类型 + * @param in 输入流 * @param charsetName 字符集 - * @param collection 返回集合 + * @param collection 返回集合 * @return 内容 * @throws IORuntimeException IO异常 */ @@ -704,10 +715,10 @@ public class IoUtil { /** * 从流中读取内容 - * - * @param 集合类型 - * @param in 输入流 - * @param charset 字符集 + * + * @param 集合类型 + * @param in 输入流 + * @param charset 字符集 * @param collection 返回集合 * @return 内容 * @throws IORuntimeException IO异常 @@ -718,9 +729,9 @@ public class IoUtil { /** * 从Reader中读取内容 - * - * @param 集合类型 - * @param reader {@link Reader} + * + * @param 集合类型 + * @param reader {@link Reader} * @param collection 返回集合 * @return 内容 * @throws IORuntimeException IO异常 @@ -732,8 +743,8 @@ public class IoUtil { /** * 按行读取UTF-8编码数据,针对每行的数据做处理 - * - * @param in {@link InputStream} + * + * @param in {@link InputStream} * @param lineHandler 行处理接口,实现handle方法用于编辑一行的数据后入到指定地方 * @throws IORuntimeException IO异常 * @since 3.1.1 @@ -744,9 +755,9 @@ public class IoUtil { /** * 按行读取数据,针对每行的数据做处理 - * - * @param in {@link InputStream} - * @param charset {@link Charset}编码 + * + * @param in {@link InputStream} + * @param charset {@link Charset}编码 * @param lineHandler 行处理接口,实现handle方法用于编辑一行的数据后入到指定地方 * @throws IORuntimeException IO异常 * @since 3.0.9 @@ -758,8 +769,8 @@ public class IoUtil { /** * 按行读取数据,针对每行的数据做处理
    * {@link Reader}自带编码定义,因此读取数据的编码跟随其编码。 - * - * @param reader {@link Reader} + * + * @param reader {@link Reader} * @param lineHandler 行处理接口,实现handle方法用于编辑一行的数据后入到指定地方 * @throws IORuntimeException IO异常 */ @@ -783,8 +794,8 @@ public class IoUtil { /** * String 转为流 - * - * @param content 内容 + * + * @param content 内容 * @param charsetName 编码 * @return 字节流 */ @@ -794,7 +805,7 @@ public class IoUtil { /** * String 转为流 - * + * * @param content 内容 * @param charset 编码 * @return 字节流 @@ -805,10 +816,10 @@ public class IoUtil { } return toStream(StrUtil.bytes(content, charset)); } - + /** * String 转为UTF-8编码的字节流流 - * + * * @param content 内容 * @return 字节流 * @since 4.5.1 @@ -847,7 +858,7 @@ public class IoUtil { /** * 转换为{@link BufferedInputStream} - * + * * @param in {@link InputStream} * @return {@link BufferedInputStream} * @since 4.0.10 @@ -858,7 +869,7 @@ public class IoUtil { /** * 转换为{@link BufferedOutputStream} - * + * * @param out {@link OutputStream} * @return {@link BufferedOutputStream} * @since 4.0.10 @@ -870,7 +881,7 @@ public class IoUtil { /** * 将{@link InputStream}转换为支持mark标记的流
    * 若原流支持mark标记,则返回原流,否则使用{@link BufferedInputStream} 包装之 - * + * * @param in 流 * @return {@link InputStream} * @since 4.0.9 @@ -888,8 +899,8 @@ public class IoUtil { /** * 转换为{@link PushbackInputStream}
    * 如果传入的输入流已经是{@link PushbackInputStream},强转返回,否则新建一个 - * - * @param in {@link InputStream} + * + * @param in {@link InputStream} * @param pushBackSize 推后的byte数 * @return {@link PushbackInputStream} * @since 3.1.0 @@ -900,10 +911,10 @@ public class IoUtil { /** * 将byte[]写到流中 - * - * @param out 输出流 + * + * @param out 输出流 * @param isCloseOut 写入完毕是否关闭输出流 - * @param content 写入的内容 + * @param content 写入的内容 * @throws IORuntimeException IO异常 */ public static void write(OutputStream out, boolean isCloseOut, byte[] content) throws IORuntimeException { @@ -920,10 +931,10 @@ public class IoUtil { /** * 将多部分内容写到流中,自动转换为UTF-8字符串 - * - * @param out 输出流 + * + * @param out 输出流 * @param isCloseOut 写入完毕是否关闭输出流 - * @param contents 写入的内容,调用toString()方法,不包括不会自动换行 + * @param contents 写入的内容,调用toString()方法,不包括不会自动换行 * @throws IORuntimeException IO异常 * @since 3.1.1 */ @@ -933,11 +944,11 @@ public class IoUtil { /** * 将多部分内容写到流中,自动转换为字符串 - * - * @param out 输出流 + * + * @param out 输出流 * @param charsetName 写出的内容的字符集 - * @param isCloseOut 写入完毕是否关闭输出流 - * @param contents 写入的内容,调用toString()方法,不包括不会自动换行 + * @param isCloseOut 写入完毕是否关闭输出流 + * @param contents 写入的内容,调用toString()方法,不包括不会自动换行 * @throws IORuntimeException IO异常 */ public static void write(OutputStream out, String charsetName, boolean isCloseOut, Object... contents) throws IORuntimeException { @@ -946,11 +957,11 @@ public class IoUtil { /** * 将多部分内容写到流中,自动转换为字符串 - * - * @param out 输出流 - * @param charset 写出的内容的字符集 + * + * @param out 输出流 + * @param charset 写出的内容的字符集 * @param isCloseOut 写入完毕是否关闭输出流 - * @param contents 写入的内容,调用toString()方法,不包括不会自动换行 + * @param contents 写入的内容,调用toString()方法,不包括不会自动换行 * @throws IORuntimeException IO异常 * @since 3.0.9 */ @@ -975,10 +986,10 @@ public class IoUtil { /** * 将多部分内容写到流中 - * - * @param out 输出流 + * + * @param out 输出流 * @param isCloseOut 写入完毕是否关闭输出流 - * @param contents 写入的内容 + * @param contents 写入的内容 * @throws IORuntimeException IO异常 */ public static void writeObjects(OutputStream out, boolean isCloseOut, Serializable... contents) throws IORuntimeException { @@ -1002,7 +1013,7 @@ public class IoUtil { /** * 从缓存中刷出数据 - * + * * @param flushable {@link Flushable} * @since 4.2.2 */ @@ -1019,7 +1030,7 @@ public class IoUtil { /** * 关闭
    * 关闭失败不会抛出异常 - * + * * @param closeable 被关闭的对象 */ public static void close(Closeable closeable) { @@ -1035,7 +1046,7 @@ public class IoUtil { /** * 关闭
    * 关闭失败不会抛出异常 - * + * * @param closeable 被关闭的对象 */ public static void close(AutoCloseable closeable) { @@ -1051,7 +1062,7 @@ public class IoUtil { /** * 尝试关闭指定对象
    * 判断对象如果实现了{@link AutoCloseable},则调用之 - * + * * @param obj 可关闭对象 * @since 4.3.2 */ @@ -1156,7 +1167,7 @@ public class IoUtil { /** * 计算流CRC32校验码,计算后关闭流 - * + * * @param in 文件,不能为目录 * @return CRC32值 * @throws IORuntimeException IO异常 @@ -1168,8 +1179,8 @@ public class IoUtil { /** * 计算流的校验码,计算后关闭流 - * - * @param in 流 + * + * @param in 流 * @param checksum {@link Checksum} * @return Checksum * @throws IORuntimeException IO异常 From 4210ebaa4ee3dc7245c21ee872b2ec140fe1a605 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 7 Apr 2020 12:22:48 +0800 Subject: [PATCH 026/157] igonre test --- hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java index 0d6b8a87e..475bd1a01 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java @@ -56,6 +56,7 @@ public class NetUtilTest { } @Test + @Ignore public void isUsableLocalPortTest(){ Assert.assertTrue(NetUtil.isUsableLocalPort(80)); } From ced42166d38d8cdf27e5582e63b5733ad2142c30 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 9 Apr 2020 16:35:44 +0800 Subject: [PATCH 027/157] XmlUtil.mapToXml support List --- CHANGELOG.md | 1 + .../java/cn/hutool/core/map/MapBuilder.java | 25 +++ .../java/cn/hutool/core/util/XmlUtil.java | 146 +++++++++++++----- .../java/cn/hutool/core/util/XmlUtilTest.java | 51 +++--- 4 files changed, 166 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e72855ad0..c71630bd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ * 【core 】 添加ValidateObjectInputStream避免对象反序列化漏洞风险 * 【core 】 添加BiMap * 【all 】 cn.hutool.extra.servlet.multipart包迁移到cn.hutool.core.net下 +* 【core 】 XmlUtil.mapToXml方法支持集合解析(issue#820@Github) ### Bug修复 * 【extra 】 修复SpringUtil使用devtools重启报错问题 diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index a278e5521..38779d1da 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -15,6 +15,31 @@ public class MapBuilder implements Serializable{ private Map map; + /** + * 创建Builder,默认HashMap实现 + * + * @param Key类型 + * @param Value类型 + * @return MapBuilder + * @since 5.3.0 + */ + public static MapBuilder create() { + return create(false); + } + + /** + * 创建Builder + * + * @param Key类型 + * @param Value类型 + * @param isLinked true创建LinkedHashMap,false创建HashMap + * @return MapBuilder + * @since 5.3.0 + */ + public static MapBuilder create(boolean isLinked) { + return create(MapUtil.newHashMap(isLinked)); + } + /** * 创建Builder * diff --git a/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java index 078879ad5..256dda90f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java @@ -17,7 +17,11 @@ import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.*; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.XPath; @@ -26,12 +30,20 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.beans.XMLDecoder; import java.beans.XMLEncoder; -import java.io.*; +import java.io.BufferedInputStream; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; /** * XML工具类
    @@ -519,6 +531,16 @@ public class XmlUtil { return (null == doc) ? null : doc.getDocumentElement(); } + /** + * 获取节点所在的Document + * @param node 节点 + * @return {@link Document} + * @since 5.3.0 + */ + public static Document getOwnerDocument(Node node){ + return (node instanceof Document) ? (Document) node : node.getOwnerDocument(); + } + /** * 去除XML文本中的无效字符 * @@ -956,7 +978,6 @@ public class XmlUtil { * @since 4.0.9 */ public static Document mapToXml(Map data, String rootName) { - return mapToXml(data, rootName, null); } @@ -973,7 +994,7 @@ public class XmlUtil { final Document doc = createXml(); final Element root = appendChild(doc, rootName, namespace); - mapToXml(doc, root, data); + appendMap(doc, root, data); return doc; } @@ -1025,59 +1046,106 @@ public class XmlUtil { * @since 5.0.4 */ public static Element appendChild(Node node, String tagName, String namespace) { - final Document doc = (node instanceof Document) ? (Document) node : node.getOwnerDocument(); + final Document doc = getOwnerDocument(node); final Element child = (null == namespace) ? doc.createElement(tagName) : doc.createElementNS(namespace, tagName); node.appendChild(child); return child; } + /** + * 创建文本子节点 + * + * @param node 节点 + * @param text 文本 + * @return 子节点 + * @since 5.3.0 + */ + public static Node appendText(Node node, CharSequence text){ + return appendText(getOwnerDocument(node), node, text); + } // ---------------------------------------------------------------------------------------- Private method start /** - * 将Map转换为XML格式的字符串 + * 追加数据子节点,可以是Map、集合、文本 + * + * @param doc {@link Document} + * @param node 节点 + * @param data 数据 + */ + @SuppressWarnings("rawtypes") + private static void append(Document doc, Node node, Object data){ + if (data instanceof Map) { + // 如果值依旧为map,递归继续 + appendMap(doc, node, (Map) data); + } else if (data instanceof Iterator) { + // 如果值依旧为map,递归继续 + appendIterator(doc, node, (Iterator) data); + }else if (data instanceof Iterable) { + // 如果值依旧为map,递归继续 + appendIterator(doc, node, ((Iterable)data).iterator()); + } else { + appendText(doc, node, data.toString()); + } + } + + /** + * 追加Map数据子节点 * * @param doc {@link Document} - * @param element 节点 + * @param node 当前节点 * @param data Map类型数据 * @since 4.0.8 */ - @SuppressWarnings("rawtypes") - private static void mapToXml(Document doc, Element element, Map data) { - Element filedEle; - Object key; - for (Entry entry : data.entrySet()) { - key = entry.getKey(); - if (null == key) { - continue; - } - // key作为标签名,无值的节点作为空节点创建 - filedEle = doc.createElement(key.toString()); - element.appendChild(filedEle); - // value作为标签内的值。 - final Object value = entry.getValue(); - if (null == value) { - continue; - } - if (value instanceof List) { - for (Object listEle : (List) value) { - if (listEle instanceof Map) { - // 如果值依旧为map,递归继续 - mapToXml(doc, filedEle, (Map) listEle); - } else { - // 创建文本节点 - filedEle.appendChild(doc.createTextNode(value.toString())); - } + @SuppressWarnings({"rawtypes", "unchecked"}) + private static void appendMap(Document doc, Node node, Map data) { + data.forEach((key, value)->{ + if(null != key){ + final Element child = appendChild(node, key.toString()); + if(null != value){ + append(doc, child, value); } - } else if (value instanceof Map) { - // 如果值依旧为map,递归继续 - mapToXml(doc, filedEle, (Map) value); - } else { - filedEle.appendChild(doc.createTextNode(value.toString())); + } + }); + } + /** + * 追加集合节点 + * + * @param doc {@link Document} + * @param node 节点 + * @param data 数据 + */ + @SuppressWarnings("rawtypes") + private static void appendIterator(Document doc, Node node, Iterator data){ + final Node parentNode = node.getParentNode(); + boolean isFirst = true; + Object eleData; + while(data.hasNext()){ + eleData = data.next(); + if(isFirst){ + append(doc, node, eleData); + isFirst = false; + } else{ + final Node cloneNode = node.cloneNode(false); + parentNode.appendChild(cloneNode); + append(doc, cloneNode, eleData); } } } + /** + * 追加文本节点 + * + * @param doc {@link Document} + * @param node 节点 + * @param text 文本内容 + * @return 增加的子节点,即Text节点 + * @since 5.3.0 + */ + private static Node appendText(Document doc, Node node, CharSequence text){ + return node.appendChild(doc.createTextNode(StrUtil.str(text))); + } + /** * 关闭XXE,避免漏洞攻击
    * see: https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#JAXP_DocumentBuilderFactory.2C_SAXParserFactory_and_DOM4J diff --git a/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java index 6ec91be1d..6ecc6282a 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java @@ -14,12 +14,11 @@ import java.util.Map; /** * {@link XmlUtil} 工具类 - * - * @author Looly * + * @author Looly */ public class XmlUtilTest { - + @Test public void parseTest() { String result = ""// @@ -84,7 +83,7 @@ public class XmlUtilTest { Assert.assertEquals("1490", map.get("remainpoint")); Assert.assertEquals("885", map.get("taskID")); Assert.assertEquals("1", map.get("successCounts")); - Assert.assertEquals("subText", ((Map)map.get("newNode")).get("sub")); + Assert.assertEquals("subText", ((Map) map.get("newNode")).get("sub")); } @Test @@ -106,17 +105,33 @@ public class XmlUtilTest { Document doc = XmlUtil.mapToXml(map, "user"); // Console.log(XmlUtil.toStr(doc, false)); Assert.assertEquals(""// - + ""// - + "张三"// - + "12"// - + ""// - + "<昵称>Looly"// - + "14"// - + ""// - + "", // + + ""// + + "张三"// + + "12"// + + ""// + + "<昵称>Looly"// + + "14"// + + ""// + + "", // XmlUtil.toStr(doc, false)); } - + + @Test + public void mapToXmlTest2() { + // 测试List + Map map = MapBuilder.create(new LinkedHashMap()) + .put("Town", CollUtil.newArrayList("town1", "town2")) + .build(); + + Document doc = XmlUtil.mapToXml(map, "City"); + Assert.assertEquals("" + + "" + + "town1" + + "town2" + + "", + XmlUtil.toStr(doc)); + } + @Test public void readTest() { Document doc = XmlUtil.readXML("test.xml"); @@ -127,9 +142,9 @@ public class XmlUtilTest { public void mapToXmlTestWithOmitXmlDeclaration() { Map map = MapBuilder.create(new LinkedHashMap()) - .put("name", "ddatsh") - .build(); - String xml=XmlUtil.mapToXmlStr(map,true); - Assert.assertEquals(xml,"ddatsh"); - } + .put("name", "ddatsh") + .build(); + String xml = XmlUtil.mapToXmlStr(map, true); + Assert.assertEquals("ddatsh", xml); + } } From 9b470616baf2ae1a3dc69219e829f0c1289841f8 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 9 Apr 2020 17:46:12 +0800 Subject: [PATCH 028/157] fix xml bug --- CHANGELOG.md | 1 + hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c71630bd6..b9a5945b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ * 【http 】 修复HttpUtil.encodeParams针对无参数URL问题(issue#817@Github) * 【extra 】 修复模板中无效引用的问题 * 【extra 】 修复读取JSON文本配置未应用到子对象的问题(issue#818@Github) +* 【extra 】 修复XmlUtil.createXml中namespace反向问题 ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java index 256dda90f..e336e7016 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java @@ -513,7 +513,7 @@ public class XmlUtil { */ public static Document createXml(String rootElementName, String namespace) { final Document doc = createXml(); - doc.appendChild(null == namespace ? doc.createElement(rootElementName) : doc.createElementNS(rootElementName, namespace)); + doc.appendChild(null == namespace ? doc.createElement(rootElementName) : doc.createElementNS(namespace, rootElementName)); return doc; } From 942521862dbf9caae80b05d0a6236304fc6d918b Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 10 Apr 2020 15:59:16 +0800 Subject: [PATCH 029/157] fix monitor --- CHANGELOG.md | 1 + .../java/cn/hutool/core/io/watch/WatchKind.java | 10 ++++++++++ .../cn/hutool/core/io/watch/WatchMonitor.java | 16 +++++----------- .../cn/hutool/core/io/watch/WatchServer.java | 6 ++++-- .../hutool/http/server/HttpServerResponse.java | 5 ++++- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9a5945b3..d36acb35f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ * 【extra 】 修复模板中无效引用的问题 * 【extra 】 修复读取JSON文本配置未应用到子对象的问题(issue#818@Github) * 【extra 】 修复XmlUtil.createXml中namespace反向问题 +* 【core 】 修复WatchMonitor默认无event问题 ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchKind.java b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchKind.java index e49dd7f0b..546dd09ba 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchKind.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchKind.java @@ -35,6 +35,16 @@ public enum WatchKind { */ DELETE(StandardWatchEventKinds.ENTRY_DELETE); + /** + * 全部事件 + */ + public static final WatchEvent.Kind[] ALL = {// + OVERFLOW.getValue(), //事件丢失 + MODIFY.getValue(), //修改 + CREATE.getValue(), //创建 + DELETE.getValue() //删除 + }; + private WatchEvent.Kind value; /** diff --git a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchMonitor.java b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchMonitor.java index 6cca21fcf..daaefe156 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchMonitor.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchMonitor.java @@ -15,7 +15,6 @@ import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardWatchEventKinds; import java.nio.file.WatchEvent; import java.nio.file.WatchService; @@ -35,28 +34,23 @@ public class WatchMonitor extends WatchServer { /** * 事件丢失 */ - public static final WatchEvent.Kind OVERFLOW = StandardWatchEventKinds.OVERFLOW; + public static final WatchEvent.Kind OVERFLOW = WatchKind.OVERFLOW.getValue(); /** * 修改事件 */ - public static final WatchEvent.Kind ENTRY_MODIFY = StandardWatchEventKinds.ENTRY_MODIFY; + public static final WatchEvent.Kind ENTRY_MODIFY = WatchKind.MODIFY.getValue(); /** * 创建事件 */ - public static final WatchEvent.Kind ENTRY_CREATE = StandardWatchEventKinds.ENTRY_CREATE; + public static final WatchEvent.Kind ENTRY_CREATE = WatchKind.CREATE.getValue(); /** * 删除事件 */ - public static final WatchEvent.Kind ENTRY_DELETE = StandardWatchEventKinds.ENTRY_DELETE; + public static final WatchEvent.Kind ENTRY_DELETE = WatchKind.DELETE.getValue(); /** * 全部事件 */ - public static final WatchEvent.Kind[] EVENTS_ALL = {// - OVERFLOW, //事件丢失 - ENTRY_MODIFY, //修改 - ENTRY_CREATE, //创建 - ENTRY_DELETE //删除 - }; + public static final WatchEvent.Kind[] EVENTS_ALL = WatchKind.ALL; /** * 监听路径,必须为目录 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchServer.java b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchServer.java index 57ee1f4af..31ffa0e8a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchServer.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchServer.java @@ -95,12 +95,14 @@ public class WatchServer extends Thread implements Closeable, Serializable { * @param maxDepth 递归下层目录的最大深度 */ public void registerPath(Path path, int maxDepth) { + final WatchEvent.Kind[] kinds = ArrayUtil.defaultIfEmpty(this.events, WatchKind.ALL); + try { final WatchKey key; if (ArrayUtil.isEmpty(this.modifiers)) { - key = path.register(this.watchService, this.events); + key = path.register(this.watchService, kinds); } else { - key = path.register(this.watchService, this.events, this.modifiers); + key = path.register(this.watchService, kinds, this.modifiers); } watchKeyPathMap.put(key, path); diff --git a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java index 85966760a..2b82b144d 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerResponse.java @@ -255,7 +255,8 @@ public class HttpServerResponse extends HttpServerBase { /** * 写出数据到客户端 * - * @param data 数据 + * @param data 数据 + * @param contentType Content-Type类型 * @return this */ public HttpServerResponse write(String data, String contentType) { @@ -301,6 +302,7 @@ public class HttpServerResponse extends HttpServerBase { * * @param in 需要返回客户端的内容 * @param contentType 返回的类型 + * @return this * @since 5.2.6 */ public HttpServerResponse write(InputStream in, String contentType) { @@ -330,6 +332,7 @@ public class HttpServerResponse extends HttpServerBase { * 返回文件给客户端(文件下载) * * @param file 写出的文件对象 + * @return this * @since 5.2.6 */ public HttpServerResponse write(File file) { From a967dd6c790c37d18da00c9c9ab17ea780eb27b8 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 10 Apr 2020 17:16:21 +0800 Subject: [PATCH 030/157] fix code --- .../cn/hutool/core/net/multipart/UploadFile.java | 16 +++++++--------- .../cn/hutool/http/server/action/Action.java | 4 +++- .../http/server/handler/ActionHandler.java | 4 +++- .../cn/hutool/http/server/SimpleServerTest.java | 12 ++++++++++++ 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFile.java b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFile.java index 5e0386603..bf0b548fd 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFile.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFile.java @@ -4,12 +4,9 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; -import java.io.BufferedInputStream; import java.io.BufferedOutputStream; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -20,11 +17,12 @@ import java.io.InputStream; * @author xiaoleilu */ public class UploadFile { + private static final String TMP_FILE_PREFIX = "hutool-"; private static final String TMP_FILE_SUFFIX = ".upload.tmp"; - private UploadFileHeader header; - private UploadSetting setting; + private final UploadFileHeader header; + private final UploadSetting setting; private int size = -1; @@ -69,7 +67,7 @@ public class UploadFile { */ public File write(String destPath) throws IOException { if (data != null || tempFile != null) { - return write(FileUtil.touch(destPath)); + return write(FileUtil.file(destPath)); } return null; } @@ -123,10 +121,10 @@ public class UploadFile { assertValid(); if (data != null) { - return new BufferedInputStream(new ByteArrayInputStream(data)); + return IoUtil.toBuffered(IoUtil.toStream(this.data)); } if (tempFile != null) { - return new BufferedInputStream(new FileInputStream(tempFile)); + return IoUtil.toBuffered(IoUtil.toStream(this.tempFile)); } return null; } @@ -190,7 +188,7 @@ public class UploadFile { // 处理内存文件 int memoryThreshold = setting.memoryThreshold; if (memoryThreshold > 0) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(memoryThreshold); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(memoryThreshold); int written = input.copy(baos, memoryThreshold); data = baos.toByteArray(); if (written <= memoryThreshold) { diff --git a/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java b/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java index 2ef304eef..5e3604b80 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java @@ -3,6 +3,8 @@ package cn.hutool.http.server.action; import cn.hutool.http.server.HttpServerRequest; import cn.hutool.http.server.HttpServerResponse; +import java.io.IOException; + /** * 请求处理接口
    * 当用户请求某个Path,则调用相应Action的doAction方法 @@ -18,5 +20,5 @@ public interface Action { * @param request 请求对象 * @param response 响应对象 */ - void doAction(HttpServerRequest request, HttpServerResponse response); + void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException; } diff --git a/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java b/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java index e469ad3d0..f102c8314 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java @@ -6,6 +6,8 @@ import cn.hutool.http.server.action.Action; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; +import java.io.IOException; + /** * Action处理器,用于将HttpHandler转换为Action形式 * @@ -26,7 +28,7 @@ public class ActionHandler implements HttpHandler { } @Override - public void handle(HttpExchange httpExchange) { + public void handle(HttpExchange httpExchange) throws IOException { action.doAction( new HttpServerRequest(httpExchange), new HttpServerResponse(httpExchange) diff --git a/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java b/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java index afb3657ae..ddf9082ab 100644 --- a/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java @@ -1,5 +1,7 @@ package cn.hutool.http.server; +import cn.hutool.core.lang.Console; +import cn.hutool.core.net.multipart.UploadFile; import cn.hutool.http.ContentType; import cn.hutool.http.HttpUtil; @@ -18,6 +20,16 @@ public class SimpleServerTest { .addAction("/formTest", (request, response) -> response.write(request.getParams().toString(), ContentType.TEXT_PLAIN.toString()) ) + // 文件上传测试 + // http://localhost:8888/formTest?a=1&a=2&b=3 + .addAction("/file", (request, response) -> { + final UploadFile file = request.getMultipart().getFile("file"); + // 传入目录,默认读取HTTP头中的文件名然后创建文件 + file.write("d:/test/"); + Console.log("Write file to: d:/test/"); + response.write(request.getParams().toString(), ContentType.TEXT_PLAIN.toString()); + } + ) .start(); } } From 47be0f4f79b68cf1d913b0682466f8a28a02d9ac Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 10 Apr 2020 17:56:10 +0800 Subject: [PATCH 031/157] change multipart --- .../java/cn/hutool/core/map/MapWrapper.java | 2 +- .../core/net/multipart/MultipartFormData.java | 91 +++++++++++++------ .../hutool/core/net/multipart/UploadFile.java | 2 +- .../hutool/http/server/HttpServerRequest.java | 25 +++-- 4 files changed, 80 insertions(+), 40 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java b/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java index 79cdd94f4..7da37c9a7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java @@ -24,7 +24,7 @@ public class MapWrapper implements Map, Iterable>, S /** 默认初始大小 */ protected static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 - private Map raw; + private final Map raw; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartFormData.java b/hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartFormData.java index 45f003980..b6162dc98 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartFormData.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/multipart/MultipartFormData.java @@ -1,13 +1,14 @@ package cn.hutool.core.net.multipart; -import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.map.multi.ListValueMap; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; -import java.util.Collections; -import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -20,14 +21,14 @@ import java.util.Set; public class MultipartFormData { /** 请求参数 */ - private Map requestParameters = new HashMap<>(); + private final ListValueMap requestParameters = new ListValueMap<>(); /** 请求文件 */ - private Map requestFiles = new HashMap<>(); + private final ListValueMap requestFiles = new ListValueMap<>(); + /** 上传选项 */ + private final UploadSetting setting; /** 是否解析完毕 */ private boolean loaded; - /** 上传选项 */ - private UploadSetting setting; // --------------------------------------------------------------------- Constructor start /** @@ -104,12 +105,9 @@ public class MultipartFormData { * @return null未找到,否则返回值 */ public String getParam(String paramName) { - if (requestParameters == null) { - return null; - } - String[] values = requestParameters.get(paramName); - if (ArrayUtil.isNotEmpty(values)) { - return values[0]; + final List values = getListParam(paramName); + if (CollUtil.isNotEmpty(values)) { + return values.get(0); } return null; } @@ -118,9 +116,6 @@ public class MultipartFormData { * @return 获得参数名集合 */ public Set getParamNames() { - if (requestParameters == null) { - return Collections.emptySet(); - } return requestParameters.keySet(); } @@ -131,9 +126,21 @@ public class MultipartFormData { * @return 数组表单值 */ public String[] getArrayParam(String paramName) { - if (requestParameters == null) { - return null; + final List listParam = getListParam(paramName); + if(null != listParam){ + return listParam.toArray(new String[0]); } + return null; + } + + /** + * 获得集合表单值 + * + * @param paramName 参数名 + * @return 数组表单值 + * @since 5.3.0 + */ + public List getListParam(String paramName) { return requestParameters.get(paramName); } @@ -143,7 +150,16 @@ public class MultipartFormData { * @return 所有属性的集合 */ public Map getParamMap() { - return requestParameters; + return Convert.toMap(String.class, String[].class, getParamListMap()); + } + + /** + * 获取所有属性的集合 + * + * @return 所有属性的集合 + */ + public ListValueMap getParamListMap() { + return this.requestParameters; } // --------------------------------------------------------------------------- Files parameters @@ -169,9 +185,22 @@ public class MultipartFormData { * @return 上传的文件列表 */ public UploadFile[] getFiles(String paramName) { - if (requestFiles == null) { - return null; + final List fileList = getFileList(paramName); + if(null != fileList){ + return fileList.toArray(new UploadFile[0]); } + return null; + } + + /** + * 获得某个属性名的所有文件
    + * 当表单中两个文件使用同一个name的时候 + * + * @param paramName 属性名 + * @return 上传的文件列表 + * @since 5.3.0 + */ + public List getFileList(String paramName) { return requestFiles.get(paramName); } @@ -181,9 +210,6 @@ public class MultipartFormData { * @return 上传的文件属性名集合 */ public Set getFileParamNames() { - if (requestFiles == null) { - return Collections.emptySet(); - } return requestFiles.keySet(); } @@ -193,6 +219,15 @@ public class MultipartFormData { * @return 文件映射 */ public Map getFileMap() { + return Convert.toMap(String.class, UploadFile[].class, getFileListValueMap()); + } + + /** + * 获取文件映射 + * + * @return 文件映射 + */ + public ListValueMap getFileListValueMap() { return this.requestFiles; } @@ -214,9 +249,7 @@ public class MultipartFormData { * @param uploadFile 文件 */ private void putFile(String name, UploadFile uploadFile) { - UploadFile[] fileUploads = requestFiles.get(name); - fileUploads = fileUploads == null ? new UploadFile[] { uploadFile } : ArrayUtil.append(fileUploads, uploadFile); - requestFiles.put(name, fileUploads); + this.requestFiles.putValue(name, uploadFile); } /** @@ -226,9 +259,7 @@ public class MultipartFormData { * @param value 参数值 */ private void putParameter(String name, String value) { - String[] params = requestParameters.get(name); - params = params == null ? new String[] { value } : ArrayUtil.append(params, value); - requestParameters.put(name, params); + this.requestParameters.putValue(name, value); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFile.java b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFile.java index bf0b548fd..4cc456934 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFile.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/multipart/UploadFile.java @@ -260,7 +260,7 @@ public class UploadFile { * @throws IOException IO异常 */ private void assertValid() throws IOException { - if (!isUploaded()) { + if (false == isUploaded()) { throw new IOException(StrUtil.format("File [{}] upload fail", getFileName())); } } diff --git a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java index f7b14d5fd..d62c96b67 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerRequest.java @@ -38,6 +38,7 @@ public class HttpServerRequest extends HttpServerBase { private Map cookieCache; private ListValueMap paramsCache; + private MultipartFormData multipartFormDataCache; private Charset charsetCache; private byte[] bodyCache; @@ -154,7 +155,7 @@ public class HttpServerRequest extends HttpServerBase { * @return Content-Type头信息 */ public String getContentType() { - return getHeader(Header.USER_AGENT); + return getHeader(Header.CONTENT_TYPE); } /** @@ -306,10 +307,15 @@ public class HttpServerRequest extends HttpServerBase { this.paramsCache.putAll(HttpUtil.decodeParams(query, charset)); } - // 解析body中的参数 - final String body = getBody(); - if(StrUtil.isNotBlank(body)){ - this.paramsCache.putAll(HttpUtil.decodeParams(body, charset)); + // 解析multipart中的参数 + if(isMultipart()){ + this.paramsCache.putAll(getMultipart().getParamListMap()); + } else{ + // 解析body中的参数 + final String body = getBody(); + if(StrUtil.isNotBlank(body)){ + this.paramsCache.putAll(HttpUtil.decodeParams(body, charset)); + } } } @@ -372,14 +378,17 @@ public class HttpServerRequest extends HttpServerBase { } /** - * 获得MultiPart表单内容,多用于获得上传的文件 在同一次请求中,此方法只能被执行一次! + * 获得MultiPart表单内容,多用于获得上传的文件 * * @return MultipartFormData * @throws IORuntimeException IO异常 * @since 5.3.0 */ public MultipartFormData getMultipart() throws IORuntimeException { - return getMultipart(new UploadSetting()); + if(null == this.multipartFormDataCache){ + this.multipartFormDataCache = parseMultipart(new UploadSetting()); + } + return this.multipartFormDataCache; } /** @@ -392,7 +401,7 @@ public class HttpServerRequest extends HttpServerBase { * @throws IORuntimeException IO异常 * @since 5.3.0 */ - public MultipartFormData getMultipart(UploadSetting uploadSetting) throws IORuntimeException { + public MultipartFormData parseMultipart(UploadSetting uploadSetting) throws IORuntimeException { final MultipartFormData formData = new MultipartFormData(uploadSetting); try { formData.parseRequestStream(getBodyStream(), getCharset()); From 73fd3b849f7544e477f03e1ffea7794b41a2546b Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 11 Apr 2020 10:38:07 +0800 Subject: [PATCH 032/157] fix code --- CHANGELOG.md | 1 + .../hutool/bloomfilter/BitSetBloomFilter.java | 16 ++--- .../java/cn/hutool/core/bean/BeanUtil.java | 18 +++++ .../cn/hutool/core/collection/CollUtil.java | 18 +++++ .../cn/hutool/core/collection/IterUtil.java | 2 + .../java/cn/hutool/core/date/ChineseDate.java | 14 ++-- .../core/io/FastByteArrayOutputStream.java | 5 +- .../java/cn/hutool/core/util/ArrayUtil.java | 2 +- .../java/cn/hutool/core/util/ZipUtil.java | 1 - .../java/cn/hutool/core/clone/CloneTest.java | 72 +++---------------- .../hutool/core/collection/CollUtilTest.java | 1 + .../hutool/core/collection/IterUtilTest.java | 12 ++-- .../core/convert/NumberWordFormatTest.java | 6 +- .../cn/hutool/db/ds/AbstractDSFactory.java | 15 ++-- .../main/java/cn/hutool/db/meta/MetaUtil.java | 3 +- .../hutool/extra/template/BeetlUtilTest.java | 8 +-- .../java/cn/hutool/http/test/RestTest.java | 16 +++-- .../main/java/cn/hutool/json/JSONArray.java | 8 +-- .../main/java/cn/hutool/json/JSONObject.java | 6 +- .../cn/hutool/json/CustomSerializeTest.java | 2 +- .../java/cn/hutool/json/JSONUtilTest.java | 24 ++++++- 21 files changed, 128 insertions(+), 122 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d36acb35f..5b35532a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ * 【core 】 添加BiMap * 【all 】 cn.hutool.extra.servlet.multipart包迁移到cn.hutool.core.net下 * 【core 】 XmlUtil.mapToXml方法支持集合解析(issue#820@Github) +* 【json 】 解析Object中对是否为bean单独判断,而不是直接解析 ### Bug修复 * 【extra 】 修复SpringUtil使用devtools重启报错问题 diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitSetBloomFilter.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitSetBloomFilter.java index 044edd16b..05846af9a 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitSetBloomFilter.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitSetBloomFilter.java @@ -1,13 +1,13 @@ package cn.hutool.bloomfilter; -import java.io.BufferedReader; -import java.io.IOException; -import java.util.BitSet; - import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.HashUtil; +import java.io.BufferedReader; +import java.io.IOException; +import java.util.BitSet; + /** * BloomFilter实现方式2,此方式使用BitSet存储。
    * Hash算法的使用使用固定顺序,只需指定个数即可 @@ -17,10 +17,10 @@ import cn.hutool.core.util.HashUtil; public class BitSetBloomFilter implements BloomFilter{ private static final long serialVersionUID = 1L; - private BitSet bitSet; - private int bitSetSize; - private int addedElements; - private int hashFunctionNumber; + private final BitSet bitSet; + private final int bitSetSize; + private final int addedElements; + private final int hashFunctionNumber; /** * 构造一个布隆过滤器,过滤器的容量为c * n 个bit. diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java index 4d007271d..e0c095705 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java @@ -42,6 +42,23 @@ import java.util.Map; */ public class BeanUtil { + /** + * 判断是否为可读的Bean对象,判定方法是: + * + *
    +	 *     1、是否存在只有无参数的getXXX方法或者isXXX方法
    +	 *     2、是否存在public类型的字段
    +	 * 
    + * + * @param clazz 待测试类 + * @return 是否为可读的Bean对象 + * @see #hasGetter(Class) + * @see #hasPublicField(Class) + */ + public static boolean isReadableBean(Class clazz) { + return hasGetter(clazz) || hasPublicField(clazz); + } + /** * 判断是否为Bean对象,判定方法是: * @@ -53,6 +70,7 @@ public class BeanUtil { * @param clazz 待测试类 * @return 是否为Bean对象 * @see #hasSetter(Class) + * @see #hasPublicField(Class) */ public static boolean isBean(Class clazz) { return hasSetter(clazz) || hasPublicField(clazz); diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index df01282fb..744a5df93 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -351,6 +351,24 @@ public class CollUtil { return IterUtil.join(iterable.iterator(), conjunction); } + /** + * 以 conjunction 为分隔符将集合转换为字符串 + * + * @param 集合元素类型 + * @param iterable {@link Iterable} + * @param conjunction 分隔符 + * @param prefix 每个元素添加的前缀,null表示不添加 + * @param suffix 每个元素添加的后缀,null表示不添加 + * @return 连接后的字符串 + * @since 5.3.0 + */ + public static String join(Iterable iterable, CharSequence conjunction, String prefix, String suffix) { + if (null == iterable) { + return null; + } + return IterUtil.join(iterable.iterator(), conjunction, prefix, suffix); + } + /** * 以 conjunction 为分隔符将集合转换为字符串
    * 如果集合元素为数组、{@link Iterable}或{@link Iterator},则递归组合其为字符串 diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java index 79e71f37f..aa2537d65 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java @@ -302,7 +302,9 @@ public class IterUtil { * @param suffix 每个元素添加的后缀,null表示不添加 * @return 连接后的字符串 * @since 4.0.10 + * @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义,请使用CollUtil.join */ + @Deprecated public static String join(Iterable iterable, CharSequence conjunction, String prefix, String suffix) { if (null == iterable) { return null; diff --git a/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java b/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java index 09655aa72..09622ddd3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java @@ -19,16 +19,16 @@ public class ChineseDate { private static final Date baseDate = DateUtil.parseDate("1900-01-31"); //农历年 - private int year; + private final int year; //农历月 - private int month; + private final int month; //农历日 - private int day; + private final int day; //是否闰年 private boolean leap; - private String[] chineseNumber = {"一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"}; - private String[] chineseNumberName = {"正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "腊"}; - private long[] lunarInfo = new long[]{0x04bd8, 0x04ae0, 0x0a570, + private final String[] chineseNumber = {"一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"}; + private final String[] chineseNumberName = {"正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "腊"}; + private final long[] lunarInfo = new long[]{0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, @@ -51,7 +51,7 @@ public class ChineseDate { 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0}; //农历节日 *表示放假日 - private String[] lFtv = new String[]{ + private final String[] lFtv = new String[]{ "0101 春节", "0102 大年初二", "0103 大年初三", "0104 大年初四", "0105 大年初五", "0106 大年初六", "0107 大年初七", "0105 路神生日", "0115 元宵节", "0202 龙抬头", "0219 观世音圣诞", "0404 寒食节", diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FastByteArrayOutputStream.java b/hutool-core/src/main/java/cn/hutool/core/io/FastByteArrayOutputStream.java index f1b498508..a27cace9d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FastByteArrayOutputStream.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FastByteArrayOutputStream.java @@ -1,11 +1,11 @@ package cn.hutool.core.io; +import cn.hutool.core.util.CharsetUtil; + import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; -import cn.hutool.core.util.CharsetUtil; - /** * 基于快速缓冲FastByteBuffer的OutputStream,随着数据的增长自动扩充缓冲区 *

    @@ -34,7 +34,6 @@ public class FastByteArrayOutputStream extends OutputStream { buffer = new FastByteBuffer(size); } - @SuppressWarnings("NullableProblems") @Override public void write(byte[] b, int off, int len) { buffer.append(b, off, len); diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java index 67cf26783..1c011f6f7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java @@ -2402,7 +2402,7 @@ public class ArrayUtil { if (ArrayUtil.isArray(item)) { sb.append(join(ArrayUtil.wrap(item), conjunction, prefix, suffix)); } else if (item instanceof Iterable) { - sb.append(IterUtil.join((Iterable) item, conjunction, prefix, suffix)); + sb.append(CollUtil.join((Iterable) item, conjunction, prefix, suffix)); } else if (item instanceof Iterator) { sb.append(IterUtil.join((Iterator) item, conjunction, prefix, suffix)); } else { 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 39b7335b2..36544e68d 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 @@ -938,7 +938,6 @@ public class ZipUtil { addDir(subPath, out); } // 压缩目录下的子文件或目录 - //noinspection ConstantConditions for (File childFile : files) { zip(childFile, srcRootDir, out, filter); } diff --git a/hutool-core/src/test/java/cn/hutool/core/clone/CloneTest.java b/hutool-core/src/test/java/cn/hutool/core/clone/CloneTest.java index e7f7f165d..569a32994 100644 --- a/hutool-core/src/test/java/cn/hutool/core/clone/CloneTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/clone/CloneTest.java @@ -1,5 +1,7 @@ package cn.hutool.core.clone; +import lombok.Data; +import lombok.EqualsAndHashCode; import org.junit.Assert; import org.junit.Test; @@ -17,7 +19,10 @@ public class CloneTest { Cat cat = new Cat(); Cat cat2 = cat.clone(); Assert.assertEquals(cat, cat2); - + } + + @Test + public void cloneTest2(){ //继承CloneSupport类 Dog dog = new Dog(); Dog dog2 = dog.clone(); @@ -30,7 +35,8 @@ public class CloneTest { * @author Looly * */ - private static class Cat implements Cloneable{ + @Data + static class Cat implements Cloneable{ private String name = "miaomiao"; private int age = 2; @@ -42,35 +48,6 @@ public class CloneTest { throw new CloneRuntimeException(e); } } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + age; - result = prime * result + ((name == null) ? 0 : name.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Cat other = (Cat) obj; - if (age != other.age) { - return false; - } - if (name == null) { - return other.name == null; - } else return name.equals(other.name); - } } /** @@ -78,37 +55,10 @@ public class CloneTest { * @author Looly * */ - private static class Dog extends CloneSupport{ + @EqualsAndHashCode(callSuper = false) + @Data + static class Dog extends CloneSupport{ private String name = "wangwang"; private int age = 3; - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + age; - result = prime * result + ((name == null) ? 0 : name.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Dog other = (Dog) obj; - if (age != other.age) { - return false; - } - if (name == null) { - return other.name == null; - } else return name.equals(other.name); - } } } diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java index 85427b99a..c6f6190fe 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java @@ -179,6 +179,7 @@ public class CollUtilTest { map.put("c", "3"); final String[] result = new String[1]; + //noinspection deprecation CollUtil.forEach(map, (key, value, index) -> { if (key.equals("a")) { result[0] = value; diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java index f5eabd172..a516536bf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java @@ -16,25 +16,25 @@ public class IterUtilTest { @Test public void fieldValueMapTest() { ArrayList carList = CollUtil.newArrayList(new Car("123", "大众"), new Car("345", "奔驰"), new Car("567", "路虎")); - Map carNameMap = IterUtil.fieldValueMap(carList, "carNumber"); - + Map carNameMap = IterUtil.fieldValueMap(carList.iterator(), "carNumber"); + Assert.assertEquals("大众", carNameMap.get("123").getCarName()); Assert.assertEquals("奔驰", carNameMap.get("345").getCarName()); Assert.assertEquals("路虎", carNameMap.get("567").getCarName()); } - + @Test public void joinTest() { ArrayList list = CollUtil.newArrayList("1", "2", "3", "4"); - String join = IterUtil.join(list, ":"); + String join = IterUtil.join(list.iterator(), ":"); Assert.assertEquals("1:2:3:4", join); ArrayList list1 = CollUtil.newArrayList(1, 2, 3, 4); - String join1 = IterUtil.join(list1, ":"); + String join1 = IterUtil.join(list1.iterator(), ":"); Assert.assertEquals("1:2:3:4", join1); ArrayList list2 = CollUtil.newArrayList("1", "2", "3", "4"); - String join2 = IterUtil.join(list2, ":", "\"", "\""); + String join2 = IterUtil.join(list2.iterator(), ":", "\"", "\""); Assert.assertEquals("\"1\":\"2\":\"3\":\"4\"", join2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/NumberWordFormatTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/NumberWordFormatTest.java index fdeb94091..8eafddea1 100644 --- a/hutool-core/src/test/java/cn/hutool/core/convert/NumberWordFormatTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/convert/NumberWordFormatTest.java @@ -3,16 +3,14 @@ package cn.hutool.core.convert; import org.junit.Assert; import org.junit.Test; -import cn.hutool.core.convert.NumberWordFormater; - public class NumberWordFormatTest { @Test public void formatTest() { - String format = NumberWordFormater.format(100.23); + String format = NumberWordFormatter.format(100.23); Assert.assertEquals("ONE HUNDRED AND CENTS TWENTY THREE ONLY", format); - String format2 = NumberWordFormater.format("2100.00"); + String format2 = NumberWordFormatter.format("2100.00"); Assert.assertEquals("TWO THOUSAND ONE HUNDRED AND CENTS ONLY", format2); } } diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/AbstractDSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/AbstractDSFactory.java index 387f0cc98..318a488d1 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/AbstractDSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/AbstractDSFactory.java @@ -1,11 +1,5 @@ package cn.hutool.db.ds; -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import javax.sql.DataSource; - import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.io.resource.NoResourceException; import cn.hutool.core.lang.Assert; @@ -15,6 +9,11 @@ import cn.hutool.db.DbUtil; import cn.hutool.db.dialect.DriverUtil; import cn.hutool.setting.Setting; +import javax.sql.DataSource; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + /** * 抽象数据源工厂
    * 此工厂抽象类用于实现数据源的缓存,当用户多次调用{@link #getDataSource(String)} 时,工厂只需创建一次即可。
    @@ -32,9 +31,9 @@ public abstract class AbstractDSFactory extends DSFactory { private static final String DEFAULT_DB_SETTING_PATH2 = "db.setting"; /** 数据库连接配置文件 */ - private Setting setting; + private final Setting setting; /** 数据源池 */ - private Map dsMap; + private final Map dsMap; /** * 构造 diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java index 43d50aaef..f8e917ab9 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/MetaUtil.java @@ -144,7 +144,7 @@ public class MetaUtil { } } } - return columnNames.toArray(new String[columnNames.size()]); + return columnNames.toArray(new String[0]); } catch (Exception e) { throw new DbRuntimeException("Get columns error!", e); } finally { @@ -172,7 +172,6 @@ public class MetaUtil { * @param tableName 表名 * @return Table对象 */ - @SuppressWarnings("resource") public static Table getTableMeta(DataSource ds, String tableName) { final Table table = Table.create(tableName); Connection conn = null; diff --git a/hutool-extra/src/test/java/cn/hutool/extra/template/BeetlUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/template/BeetlUtilTest.java index 342906c0c..c7d27f621 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/template/BeetlUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/template/BeetlUtilTest.java @@ -1,7 +1,7 @@ package cn.hutool.extra.template; -import java.io.IOException; - +import cn.hutool.core.lang.Dict; +import cn.hutool.extra.template.engine.beetl.BeetlUtil; import org.beetl.core.Configuration; import org.beetl.core.GroupTemplate; import org.beetl.core.Template; @@ -9,8 +9,7 @@ import org.beetl.core.resource.StringTemplateResourceLoader; import org.junit.Assert; import org.junit.Test; -import cn.hutool.core.lang.Dict; -import cn.hutool.extra.template.engine.beetl.BeetlUtil; +import java.io.IOException; /** * BeetlUtil单元测试 @@ -18,6 +17,7 @@ import cn.hutool.extra.template.engine.beetl.BeetlUtil; * @author looly * */ +@SuppressWarnings("deprecation") public class BeetlUtilTest { @Test diff --git a/hutool-http/src/test/java/cn/hutool/http/test/RestTest.java b/hutool-http/src/test/java/cn/hutool/http/test/RestTest.java index c6e735200..2730ec6d9 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/RestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/RestTest.java @@ -14,13 +14,14 @@ import org.junit.Test; * @author looly * */ -@SuppressWarnings("ConstantConditions") public class RestTest { @Test public void contentTypeTest() { HttpRequest request = HttpRequest.post("http://localhost:8090/rest/restTest/")// - .body(JSONUtil.createObj().put("aaa", "aaaValue").put("键2", "值2").toString()); + .body(JSONUtil.createObj() + .set("aaa", "aaaValue") + .set("键2", "值2").toString()); Assert.assertEquals("application/json;charset=UTF-8", request.header("Content-Type")); } @@ -28,7 +29,9 @@ public class RestTest { @Ignore public void postTest() { HttpRequest request = HttpRequest.post("http://localhost:8090/rest/restTest/")// - .body(JSONUtil.createObj().put("aaa", "aaaValue").put("键2", "值2").toString()); + .body(JSONUtil.createObj() + .set("aaa", "aaaValue") + .set("键2", "值2").toString()); Console.log(request.execute().body()); } @@ -36,7 +39,8 @@ public class RestTest { @Ignore public void postTest2() { String result = HttpUtil.post("http://localhost:8090/rest/restTest/", JSONUtil.createObj()// - .put("aaa", "aaaValue").put("键2", "值2").toString()); + .set("aaa", "aaaValue") + .set("键2", "值2").toString()); Console.log(result); } @@ -44,7 +48,9 @@ public class RestTest { @Ignore public void postTest3() { HttpRequest request = HttpRequest.post("http://211.162.39.204:8181/jeesite-simple/a/open/bizGwbnService/test")// - .body(JSONUtil.createObj().put("aaa", "aaaValue").put("键2", "值2").toString()); + .body(JSONUtil.createObj() + .set("aaa", "aaaValue") + .set("键2", "值2").toString()); Console.log(request.execute().body()); } } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index da5abb041..f19764969 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -3,6 +3,7 @@ package cn.hutool.json; import cn.hutool.core.bean.BeanPath; import cn.hutool.core.collection.ArrayIter; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; @@ -41,7 +42,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando /** 持有原始数据的List */ private final List rawList; /** 配置项 */ - private JSONConfig config; + private final JSONConfig config; // -------------------------------------------------------------------------------------------------------------------- Constructor start /** @@ -365,14 +366,13 @@ public class JSONArray implements JSON, JSONGetter, List, Rando return rawList.contains(o); } - @SuppressWarnings("NullableProblems") @Override public Object[] toArray() { return rawList.toArray(); } @Override - @SuppressWarnings({"unchecked", "NullableProblems"}) + @SuppressWarnings({"unchecked"}) public T[] toArray(T[] a) { return (T[]) JSONConverter.toArray(this, a.getClass().getComponentType()); } @@ -600,7 +600,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando init((JSONTokener) source); } else { Iterator iter; - if (source.getClass().isArray()) {// 数组 + if (ArrayUtil.isArray(source)) {// 数组 iter = new ArrayIter<>(source); } else if (source instanceof Iterator) {// Iterator iter = ((Iterator) source); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index cd62146c3..fdfd36098 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -46,7 +46,7 @@ public class JSONObject implements JSON, JSONGetter, Map /** JSON的KV持有Map */ private final Map rawHashMap; /** 配置项 */ - private JSONConfig config; + private final JSONConfig config; // -------------------------------------------------------------------------------------------------------------------- Constructor start /** @@ -677,9 +677,7 @@ public class JSONObject implements JSON, JSONGetter, Map } else if (source instanceof JSONTokener) { // JSONTokener init((JSONTokener) source); - } else if (source instanceof Number) { - // ignore Number - } else { + } else if(BeanUtil.isReadableBean(source.getClass())){ // 普通Bean this.populateMap(source); } diff --git a/hutool-json/src/test/java/cn/hutool/json/CustomSerializeTest.java b/hutool-json/src/test/java/cn/hutool/json/CustomSerializeTest.java index 18e46d590..2473bd8f3 100644 --- a/hutool-json/src/test/java/cn/hutool/json/CustomSerializeTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/CustomSerializeTest.java @@ -11,7 +11,7 @@ public class CustomSerializeTest { @Test public void serializeTest() { - JSONUtil.putSerializer(CustomBean.class, (JSONObjectSerializer) (json, bean) -> json.put("customName", bean.name)); + JSONUtil.putSerializer(CustomBean.class, (JSONObjectSerializer) (json, bean) -> json.set("customName", bean.name)); CustomBean customBean = new CustomBean(); customBean.name = "testName"; 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 d661ff278..bc02c17e5 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -25,6 +25,24 @@ public class JSONUtilTest { Console.log(jsonArray); } + /** + * 数字解析为JSONArray报错 + */ + @Test(expected = JSONException.class) + public void parseNumberTest(){ + JSONArray json = JSONUtil.parseArray(123L); + Console.log(json); + } + + /** + * 数字解析为JSONObject忽略 + */ + @Test + public void parseNumberTest2(){ + JSONObject json = JSONUtil.parseObj(123L); + Assert.assertEquals(new JSONObject(), json); + } + @Test public void toJsonStrTest() { UserA a1 = new UserA(); @@ -67,9 +85,9 @@ public class JSONUtilTest { public void toJsonStrTest3() { // 验证某个字段为JSON字符串时转义是否规范 JSONObject object = new JSONObject(true); - object.put("name", "123123"); - object.put("value", "\\"); - object.put("value2", " map = MapUtil.newHashMap(); map.put("user", object.toString()); From bc486cdac4ede533314c1a844ba53e0c876f6dc2 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 11 Apr 2020 13:08:46 +0800 Subject: [PATCH 033/157] fix code --- CHANGELOG.md | 2 + .../aop/aspects/TimeIntervalAspect.java | 2 +- .../aop/interceptor/JdkInterceptor.java | 4 +- .../hutool/bloomfilter/BitMapBloomFilter.java | 2 +- .../cn/hutool/bloomfilter/bitMap/IntMap.java | 2 +- .../cn/hutool/bloomfilter/bitMap/LongMap.java | 2 +- .../cn/hutool/cache/GlobalPruneTimer.java | 2 +- .../java/cn/hutool/cache/impl/NoCache.java | 16 +- .../java/cn/hutool/captcha/ShearCaptcha.java | 20 +- .../captcha/generator/MathGenerator.java | 2 +- .../java/cn/hutool/core/bean/BeanPath.java | 28 +- .../java/cn/hutool/core/bean/BeanUtil.java | 2 +- .../java/cn/hutool/core/bean/DynaBean.java | 12 +- .../copier/provider/MapValueProvider.java | 2 +- .../hutool/core/builder/CompareToBuilder.java | 14 +- .../cn/hutool/core/builder/EqualsBuilder.java | 19 +- .../core/collection/BoundedPriorityQueue.java | 3 +- .../cn/hutool/core/collection/CopiedIter.java | 2 +- .../core/comparator/ComparatorChain.java | 11 +- .../core/comparator/IndexedComparator.java | 6 +- .../core/convert/ConverterRegistry.java | 4 +- .../core/convert/impl/BooleanConverter.java | 7 +- .../core/convert/impl/CharacterConverter.java | 8 +- .../core/convert/impl/DateConverter.java | 2 +- .../core/convert/impl/EnumConverter.java | 2 +- .../convert/impl/GenericEnumConverter.java | 2 +- .../core/convert/impl/NumberConverter.java | 28 +- .../core/convert/impl/PrimitiveConverter.java | 2 +- .../core/convert/impl/ReferenceConverter.java | 12 +- .../impl/TemporalAccessorConverter.java | 2 +- .../java/cn/hutool/core/date/DateBetween.java | 4 +- .../java/cn/hutool/core/date/DateField.java | 2 +- .../java/cn/hutool/core/date/DateUnit.java | 2 +- .../main/java/cn/hutool/core/date/Month.java | 2 +- .../java/cn/hutool/core/date/Quarter.java | 2 +- .../java/cn/hutool/core/date/SystemClock.java | 2 +- .../cn/hutool/core/date/TimeInterval.java | 2 +- .../main/java/cn/hutool/core/date/Week.java | 2 +- .../core/date/format/FastDatePrinter.java | 6 +- .../src/main/java/cn/hutool/core/img/Img.java | 2 +- .../java/cn/hutool/core/img/ScaleType.java | 2 +- .../main/java/cn/hutool/core/io/FileUtil.java | 2 +- .../cn/hutool/core/io/file/FileAppender.java | 12 +- .../hutool/core/io/file/LineReadWatcher.java | 16 +- .../cn/hutool/core/io/file/LineSeparator.java | 2 +- .../java/cn/hutool/core/io/file/Tailer.java | 30 +- .../core/io/resource/InputStreamResource.java | 12 +- .../core/io/resource/MultiResource.java | 8 +- .../core/io/resource/StringResource.java | 14 +- .../hutool/core/io/resource/UrlResource.java | 16 +- .../cn/hutool/core/io/watch/WatchKind.java | 2 +- .../cn/hutool/core/io/watch/WatchServer.java | 2 +- .../cn/hutool/core/lang/ClassScanner.java | 1 - .../main/java/cn/hutool/core/lang/Pair.java | 8 +- .../java/cn/hutool/core/lang/PatternPool.java | 4 +- .../main/java/cn/hutool/core/lang/Range.java | 12 +- .../java/cn/hutool/core/lang/SimpleCache.java | 61 ++-- .../java/cn/hutool/core/lang/Singleton.java | 52 ++-- .../java/cn/hutool/core/lang/Snowflake.java | 6 +- .../main/java/cn/hutool/core/lang/UUID.java | 26 +- .../cn/hutool/core/lang/WeightRandom.java | 14 +- .../java/cn/hutool/core/lang/tree/Tree.java | 2 +- .../java/cn/hutool/core/map/MapBuilder.java | 2 +- .../java/cn/hutool/core/map/MapProxy.java | 16 +- .../java/cn/hutool/core/map/TableMap.java | 8 +- .../java/cn/hutool/core/map/TolerantMap.java | 290 +++++++++--------- .../main/java/cn/hutool/core/math/Money.java | 2 +- .../swing/clipboard/ClipboardMonitor.java | 9 +- .../core/swing/clipboard/ImageSelection.java | 5 +- .../java/cn/hutool/core/text/Simhash.java | 24 +- .../java/cn/hutool/core/text/StrSpliter.java | 28 +- .../java/cn/hutool/core/text/UnicodeUtil.java | 2 +- .../cn/hutool/core/text/csv/CsvParser.java | 20 +- .../text/escape/NumericEntityUnescaper.java | 2 +- .../core/text/replacer/ReplacerChain.java | 8 +- .../hutool/core/thread/ConcurrencyTester.java | 4 +- .../cn/hutool/core/thread/RejectPolicy.java | 2 +- .../hutool/core/thread/SemaphoreRunnable.java | 4 +- .../cn/hutool/core/thread/SyncFinisher.java | 14 +- .../cn/hutool/core/thread/lock/LockUtil.java | 2 +- .../cn/hutool/core/thread/lock/NoLock.java | 2 +- .../main/java/cn/hutool/core/util/IdUtil.java | 50 +-- .../java/cn/hutool/core/util/IdcardUtil.java | 156 +++++----- .../cn/hutool/core/util/ModifierUtil.java | 2 +- .../java/cn/hutool/core/util/ObjectUtil.java | 4 +- .../java/cn/hutool/core/util/RuntimeUtil.java | 97 ++++-- .../java/cn/hutool/core/util/TypeUtil.java | 6 +- .../core/convert/ConvertToCollectionTest.java | 13 +- .../cn/hutool/core/date/DateTimeTest.java | 6 +- .../java/cn/hutool/core/img/ImgUtilTest.java | 13 +- .../java/cn/hutool/core/lang/RangeTest.java | 28 +- .../cn/hutool/core/lang/SingletonTest.java | 30 ++ .../cn/hutool/core/math/CombinationTest.java | 6 +- .../core/swing/ClipboardMonitorTest.java | 35 +-- .../hutool/core/text/csv/CsvParserTest.java | 13 +- .../cn/hutool/core/text/csv/CsvUtilTest.java | 4 +- .../core/thread/ConcurrencyTesterTest.java | 12 +- .../cn/hutool/core/thread/ThreadUtilTest.java | 8 +- .../cn/hutool/core/util/EnumUtilTest.java | 14 +- .../java/cn/hutool/core/util/IdUtilTest.java | 44 ++- .../main/java/cn/hutool/cron/CronTimer.java | 12 +- .../main/java/cn/hutool/cron/Scheduler.java | 2 +- .../java/cn/hutool/cron/TaskExecutor.java | 4 +- .../java/cn/hutool/cron/TaskLauncher.java | 4 +- .../main/java/cn/hutool/cron/TaskTable.java | 18 +- .../cn/hutool/cron/pattern/CronPattern.java | 28 +- .../pattern/matcher/YearValueMatcher.java | 2 +- .../java/cn/hutool/cron/task/InvokeTask.java | 8 +- .../cn/hutool/cron/task/RunnableTask.java | 2 +- .../cron/demo/AddAndRemoveMainTest.java | 15 +- .../java/cn/hutool/cron/demo/TestJob.java | 2 +- .../crypto/asymmetric/SignAlgorithm.java | 2 +- .../java/cn/hutool/crypto/digest/BCrypt.java | 13 +- .../hutool/crypto/digest/DigestAlgorithm.java | 2 +- .../java/cn/hutool/crypto/digest/HMac.java | 17 +- .../hutool/crypto/digest/HmacAlgorithm.java | 2 +- .../crypto/symmetric/SymmetricAlgorithm.java | 2 +- .../crypto/symmetric/SymmetricCrypto.java | 2 +- .../cn/hutool/crypto/test/BCUtilTest.java | 5 - .../cn/hutool/db/ThreadLocalConnection.java | 5 +- .../cn/hutool/db/dialect/DialectFactory.java | 47 ++- .../cn/hutool/db/ds/DataSourceWrapper.java | 11 +- .../cn/hutool/db/ds/pooled/DbSetting.java | 2 +- .../hutool/db/ds/pooled/PooledConnection.java | 6 +- .../hutool/db/ds/pooled/PooledDataSource.java | 19 +- .../cn/hutool/db/handler/EntityHandler.java | 6 +- .../hutool/db/handler/EntityListHandler.java | 6 +- .../hutool/db/handler/EntitySetHandler.java | 6 +- .../hutool/db/handler/PageResultHandler.java | 10 +- .../main/java/cn/hutool/db/meta/JdbcType.java | 9 +- .../main/java/cn/hutool/db/meta/Table.java | 4 +- .../java/cn/hutool/db/meta/TableType.java | 2 +- .../hutool/db/nosql/mongo/MongoFactory.java | 40 ++- .../main/java/cn/hutool/db/sql/Condition.java | 123 ++++---- .../main/java/cn/hutool/db/sql/NamedSql.java | 8 +- .../java/cn/hutool/db/sql/SqlFormatter.java | 7 +- .../main/java/cn/hutool/db/sql/SqlUtil.java | 16 +- .../main/java/cn/hutool/db/sql/Wrapper.java | 10 +- .../db/transaction/TransactionLevel.java | 2 +- .../src/test/java/cn/hutool/db/CRUDTest.java | 2 +- .../test/java/cn/hutool/db/ConcurentTest.java | 30 +- .../test/java/cn/hutool/db/SessionTest.java | 11 +- .../java/cn/hutool/dfa/SensitiveUtil.java | 2 +- .../src/main/java/cn/hutool/dfa/WordTree.java | 12 +- .../hutool/extra/mail/InternalMailUtil.java | 24 +- .../main/java/cn/hutool/extra/mail/Mail.java | 27 +- .../cn/hutool/extra/mail/MailAccount.java | 2 +- .../java/cn/hutool/extra/mail/MailUtil.java | 16 +- .../extra/mail/UserPassAuthenticator.java | 4 +- .../qrcode/BufferedImageLuminanceSource.java | 1 + .../cn/hutool/extra/ssh/JschSessionPool.java | 2 +- .../template/engine/enjoy/EnjoyTemplate.java | 6 +- .../template/engine/rythm/RythmTemplate.java | 2 +- .../engine/thymeleaf/ThymeleafTemplate.java | 25 +- .../engine/velocity/VelocityTemplate.java | 17 +- .../engine/velocity/VelocityUtil.java | 2 +- .../tokenizer/engine/hanlp/HanLPEngine.java | 2 +- .../tokenizer/engine/hanlp/HanLPWord.java | 2 +- .../engine/ikanalyzer/IKAnalyzerEngine.java | 2 +- .../engine/ikanalyzer/IKAnalyzerResult.java | 11 +- .../engine/ikanalyzer/IKAnalyzerWord.java | 2 +- .../tokenizer/engine/jcseg/JcsegEngine.java | 15 +- .../tokenizer/engine/jcseg/JcsegResult.java | 2 +- .../tokenizer/engine/jieba/JiebaEngine.java | 4 +- .../tokenizer/engine/mmseg/MmsegEngine.java | 11 +- .../tokenizer/engine/mmseg/MmsegResult.java | 9 +- .../tokenizer/engine/mynlp/MynlpEngine.java | 2 +- .../tokenizer/engine/mynlp/MynlpResult.java | 11 +- .../tokenizer/engine/word/WordEngine.java | 2 +- .../tokenizer/engine/word/WordResult.java | 8 +- .../main/java/cn/hutool/http/ContentType.java | 2 +- .../src/main/java/cn/hutool/http/Header.java | 2 +- .../java/cn/hutool/http/HttpConnection.java | 4 +- .../java/cn/hutool/http/HttpInputStream.java | 9 +- .../java/cn/hutool/http/HttpResponse.java | 2 +- .../hutool/http/server/action/RootAction.java | 2 +- .../http/ssl/CustomProtocolsSSLFactory.java | 9 +- .../hutool/http/useragent/UserAgentInfo.java | 8 +- .../cn/hutool/http/webservice/SoapClient.java | 35 +-- .../hutool/http/webservice/SoapProtocol.java | 2 +- .../cn/hutool/http/test/DownloadTest.java | 2 +- .../main/java/cn/hutool/json/JSONNull.java | 8 +- .../main/java/cn/hutool/json/JSONTokener.java | 2 +- .../java/cn/hutool/json/JSONArrayTest.java | 18 +- .../cn/hutool/json/test/bean/ResultDto.java | 2 +- .../main/java/cn/hutool/log/LogFactory.java | 2 +- .../log/dialect/console/ConsoleLog.java | 2 +- .../cn/hutool/log/dialect/slf4j/Slf4jLog.java | 1 - .../hutool/log/dialect/tinylog/TinyLog.java | 4 +- .../java/cn/hutool/poi/excel/StyleSet.java | 7 +- .../poi/excel/sax/Excel03SaxReader.java | 6 +- .../poi/excel/sax/Excel07SaxReader.java | 2 +- .../main/java/cn/hutool/poi/word/PicType.java | 2 +- .../java/cn/hutool/poi/word/Word07Writer.java | 2 +- .../java/cn/hutool/script/ScriptUtil.java | 12 +- .../java/cn/hutool/setting/GroupedSet.java | 17 +- .../java/cn/hutool/setting/SettingLoader.java | 9 +- .../java/cn/hutool/setting/SettingUtil.java | 11 +- .../cn/hutool/setting/dialect/PropsUtil.java | 2 +- .../cn/hutool/setting/profile/Profile.java | 10 +- .../hutool/setting/test/SettingUtilTest.java | 4 +- .../java/cn/hutool/socket/SocketConfig.java | 7 +- .../java/cn/hutool/socket/nio/NioServer.java | 9 +- .../java/cn/hutool/socket/nio/Operation.java | 2 +- .../main/java/cn/hutool/system/HostInfo.java | 13 +- .../main/java/cn/hutool/system/JavaInfo.java | 128 +++----- .../java/cn/hutool/system/RuntimeInfo.java | 39 ++- 207 files changed, 1329 insertions(+), 1355 deletions(-) create mode 100644 hutool-core/src/test/java/cn/hutool/core/lang/SingletonTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b35532a2..743a0af62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ * 【all 】 cn.hutool.extra.servlet.multipart包迁移到cn.hutool.core.net下 * 【core 】 XmlUtil.mapToXml方法支持集合解析(issue#820@Github) * 【json 】 解析Object中对是否为bean单独判断,而不是直接解析 +* 【core 】 SimHash锁改为StampedLock +* 【core 】 Singleton改为SimpleCache实现 ### Bug修复 * 【extra 】 修复SpringUtil使用devtools重启报错问题 diff --git a/hutool-aop/src/main/java/cn/hutool/aop/aspects/TimeIntervalAspect.java b/hutool-aop/src/main/java/cn/hutool/aop/aspects/TimeIntervalAspect.java index 719ac3fb1..fd8da9b48 100644 --- a/hutool-aop/src/main/java/cn/hutool/aop/aspects/TimeIntervalAspect.java +++ b/hutool-aop/src/main/java/cn/hutool/aop/aspects/TimeIntervalAspect.java @@ -13,7 +13,7 @@ import java.lang.reflect.Method; public class TimeIntervalAspect extends SimpleAspect { private static final long serialVersionUID = 1L; - private TimeInterval interval = new TimeInterval(); + private final TimeInterval interval = new TimeInterval(); @Override public boolean before(Object target, Method method, Object[] args) { diff --git a/hutool-aop/src/main/java/cn/hutool/aop/interceptor/JdkInterceptor.java b/hutool-aop/src/main/java/cn/hutool/aop/interceptor/JdkInterceptor.java index 82a3a1eab..baa6d4b05 100644 --- a/hutool-aop/src/main/java/cn/hutool/aop/interceptor/JdkInterceptor.java +++ b/hutool-aop/src/main/java/cn/hutool/aop/interceptor/JdkInterceptor.java @@ -18,8 +18,8 @@ import java.lang.reflect.Method; public class JdkInterceptor implements InvocationHandler, Serializable { private static final long serialVersionUID = 1L; - private Object target; - private Aspect aspect; + private final Object target; + private final Aspect aspect; /** * 构造 diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java index 2a1096abb..589e2bfbb 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java @@ -55,7 +55,7 @@ public class BitMapBloomFilter implements BloomFilter{ */ @Override public boolean add(String str) { - boolean flag = true; + boolean flag = false; for (BloomFilter filter : filters) { flag |= filter.add(str); } diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/IntMap.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/IntMap.java index e5d310ca4..e6f83d540 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/IntMap.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/IntMap.java @@ -11,7 +11,7 @@ import java.io.Serializable; public class IntMap implements BitMap, Serializable { private static final long serialVersionUID = 1L; - private int[] ints; + private final int[] ints; /** * 构造 diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/LongMap.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/LongMap.java index 75d021db6..7ee584663 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/LongMap.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/LongMap.java @@ -11,7 +11,7 @@ import java.io.Serializable; public class LongMap implements BitMap, Serializable { private static final long serialVersionUID = 1L; - private long[] longs; + private final long[] longs; /** * 构造 diff --git a/hutool-cache/src/main/java/cn/hutool/cache/GlobalPruneTimer.java b/hutool-cache/src/main/java/cn/hutool/cache/GlobalPruneTimer.java index a7f95fcda..cff84d52e 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/GlobalPruneTimer.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/GlobalPruneTimer.java @@ -24,7 +24,7 @@ public enum GlobalPruneTimer { /** * 缓存任务计数 */ - private AtomicInteger cacheTaskNumber = new AtomicInteger(1); + private final AtomicInteger cacheTaskNumber = new AtomicInteger(1); /** * 定时器 diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/NoCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/NoCache.java index 0964109cd..55d6ec896 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/NoCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/NoCache.java @@ -1,10 +1,10 @@ package cn.hutool.cache.impl; -import java.util.Iterator; - import cn.hutool.cache.Cache; import cn.hutool.core.lang.func.Func0; +import java.util.Iterator; + /** * 无缓存实现,用于快速关闭缓存 * @@ -61,7 +61,17 @@ public class NoCache implements Cache { @Override public Iterator iterator() { - return null; + return new Iterator() { + @Override + public boolean hasNext() { + return false; + } + + @Override + public V next() { + return null; + } + }; } @Override diff --git a/hutool-captcha/src/main/java/cn/hutool/captcha/ShearCaptcha.java b/hutool-captcha/src/main/java/cn/hutool/captcha/ShearCaptcha.java index fa22ff72c..f99f577fc 100644 --- a/hutool-captcha/src/main/java/cn/hutool/captcha/ShearCaptcha.java +++ b/hutool-captcha/src/main/java/cn/hutool/captcha/ShearCaptcha.java @@ -1,16 +1,16 @@ package cn.hutool.captcha; +import cn.hutool.core.img.GraphicsUtil; +import cn.hutool.core.img.ImgUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; + import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; -import cn.hutool.core.img.GraphicsUtil; -import cn.hutool.core.img.ImgUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.RandomUtil; - /** * 扭曲干扰验证码 * @@ -110,18 +110,15 @@ public class ShearCaptcha extends AbstractCaptcha { int period = RandomUtil.randomInt(this.width); - boolean borderGap = true; int frames = 1; int phase = RandomUtil.randomInt(2); for (int i = 0; i < h1; i++) { double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames); g.copyArea(0, i, w1, 1, (int) d, 0); - if (borderGap) { - g.setColor(color); - g.drawLine((int) d, i, 0, i); - g.drawLine((int) d + w1, i, w1, i); - } + g.setColor(color); + g.drawLine((int) d, i, 0, i); + g.drawLine((int) d + w1, i, w1, i); } } @@ -162,6 +159,7 @@ public class ShearCaptcha extends AbstractCaptcha { * @param thickness 粗细 * @param c 颜色 */ + @SuppressWarnings("SameParameterValue") private void drawInterfere(Graphics g, int x1, int y1, int x2, int y2, int thickness, Color c) { // The thick line is in fact a filled polygon diff --git a/hutool-captcha/src/main/java/cn/hutool/captcha/generator/MathGenerator.java b/hutool-captcha/src/main/java/cn/hutool/captcha/generator/MathGenerator.java index 2beb52194..748706d75 100644 --- a/hutool-captcha/src/main/java/cn/hutool/captcha/generator/MathGenerator.java +++ b/hutool-captcha/src/main/java/cn/hutool/captcha/generator/MathGenerator.java @@ -16,7 +16,7 @@ public class MathGenerator implements CodeGenerator { private static final String operators = "+-*"; /** 参与计算数字最大长度 */ - private int numberLength; + private final int numberLength; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanPath.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanPath.java index ba7bead3e..47928ddcf 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanPath.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanPath.java @@ -1,13 +1,5 @@ package cn.hutool.core.bean; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.map.MapUtil; @@ -16,6 +8,14 @@ import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + /** * Bean路径表达式,用于获取多层嵌套Bean中的字段值或Bean对象
    * 根据给定的表达式,查找Bean中对应的属性值对象。 表达式分为两种: @@ -242,10 +242,6 @@ public class BeanPath implements Serializable{ } isNumStart = false; // 中括号结束加入下标 - if (builder.length() > 0) { - localPatternParts.add(unWrapIfPossible(builder)); - } - builder.reset(); } else { if (isNumStart) { // 非结束中括号情况下发现起始中括号报错(中括号未关闭) @@ -255,11 +251,11 @@ public class BeanPath implements Serializable{ isNumStart = true; } // 每一个边界符之前的表达式是一个完整的KEY,开始处理KEY - if (builder.length() > 0) { - localPatternParts.add(unWrapIfPossible(builder)); - } - builder.reset(); } + if (builder.length() > 0) { + localPatternParts.add(unWrapIfPossible(builder)); + } + builder.reset(); } else { // 非边界符号,追加字符 builder.append(c); diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java index e0c095705..c2f3257a7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java @@ -694,7 +694,7 @@ public class BeanUtil { */ public static T trimStrFields(T bean, String... ignoreFields) { if (bean == null) { - return bean; + return null; } final Field[] fields = ReflectUtil.getFields(bean.getClass()); diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/DynaBean.java b/hutool-core/src/main/java/cn/hutool/core/bean/DynaBean.java index 915e60e05..8df3da986 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/DynaBean.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/DynaBean.java @@ -1,14 +1,14 @@ package cn.hutool.core.bean; -import java.io.Serializable; -import java.lang.reflect.Method; -import java.util.Map; - import cn.hutool.core.clone.CloneSupport; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ReflectUtil; +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Map; + /** * 动态Bean,通过反射对Bean的相关方法做操作
    * 支持Map和普通Bean @@ -19,8 +19,8 @@ import cn.hutool.core.util.ReflectUtil; public class DynaBean extends CloneSupport implements Serializable{ private static final long serialVersionUID = 1L; - private Class beanClass; - private Object bean; + private final Class beanClass; + private final Object bean; /** * 创建一个{@link DynaBean} diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java index 2ab2a7e58..896cfab61 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java @@ -16,7 +16,7 @@ import java.util.Map; */ public class MapValueProvider implements ValueProvider { - private Map map; + private final Map map; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/builder/CompareToBuilder.java b/hutool-core/src/main/java/cn/hutool/core/builder/CompareToBuilder.java index 77cf72479..de0c6705b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/builder/CompareToBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/builder/CompareToBuilder.java @@ -1,13 +1,13 @@ package cn.hutool.core.builder; +import cn.hutool.core.util.ArrayUtil; + import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.Comparator; -import cn.hutool.core.util.ArrayUtil; - /** * 用于构建 {@link java.lang.Comparable#compareTo(Object)} 方法的辅助工具 * @@ -418,7 +418,7 @@ public class CompareToBuilder implements Builder { if (comparison != 0) { return this; } - comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0)); + comparison = (Long.compare(lhs, rhs)); return this; } @@ -434,7 +434,7 @@ public class CompareToBuilder implements Builder { if (comparison != 0) { return this; } - comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0)); + comparison = (Integer.compare(lhs, rhs)); return this; } @@ -450,7 +450,7 @@ public class CompareToBuilder implements Builder { if (comparison != 0) { return this; } - comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0)); + comparison = (Short.compare(lhs, rhs)); return this; } @@ -466,7 +466,7 @@ public class CompareToBuilder implements Builder { if (comparison != 0) { return this; } - comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0)); + comparison = (Character.compare(lhs, rhs)); return this; } @@ -482,7 +482,7 @@ public class CompareToBuilder implements Builder { if (comparison != 0) { return this; } - comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0)); + comparison = (Byte.compare(lhs, rhs)); return this; } diff --git a/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java b/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java index 07ad8c3d7..e03c08ce5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java @@ -1,5 +1,8 @@ package cn.hutool.core.builder; +import cn.hutool.core.lang.Pair; +import cn.hutool.core.util.ArrayUtil; + import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -7,9 +10,6 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; -import cn.hutool.core.lang.Pair; -import cn.hutool.core.util.ArrayUtil; - /** *

    {@link Object#equals(Object)} 方法的构建器

    * @@ -51,7 +51,7 @@ public class EqualsBuilder implements Builder { * * @since 3.0 */ - private static final ThreadLocal>> REGISTRY = new ThreadLocal>>(); + private static final ThreadLocal>> REGISTRY = new ThreadLocal<>(); /** *

    @@ -79,7 +79,7 @@ public class EqualsBuilder implements Builder { static Pair getRegisterPair(final Object lhs, final Object rhs) { final IDKey left = new IDKey(lhs); final IDKey right = new IDKey(rhs); - return new Pair(left, right); + return new Pair<>(left, right); } /** @@ -98,7 +98,7 @@ public class EqualsBuilder implements Builder { static boolean isRegistered(final Object lhs, final Object rhs) { final Set> registry = getRegistry(); final Pair pair = getRegisterPair(lhs, rhs); - final Pair swappedPair = new Pair(pair.getKey(), pair.getValue()); + final Pair swappedPair = new Pair<>(pair.getKey(), pair.getValue()); return registry != null && (registry.contains(pair) || registry.contains(swappedPair)); @@ -116,7 +116,7 @@ public class EqualsBuilder implements Builder { static void register(final Object lhs, final Object rhs) { synchronized (EqualsBuilder.class) { if (getRegistry() == null) { - REGISTRY.set(new HashSet>()); + REGISTRY.set(new HashSet<>()); } } @@ -845,7 +845,7 @@ public class EqualsBuilder implements Builder { */ @Override public Boolean build() { - return Boolean.valueOf(isEquals()); + return isEquals(); } /** @@ -854,7 +854,8 @@ public class EqualsBuilder implements Builder { * @param isEquals The value to set. * @since 2.1 */ - protected void setEquals(final boolean isEquals) { + @SuppressWarnings("SameParameterValue") + protected void setEquals(boolean isEquals) { this.isEquals = isEquals; } diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/BoundedPriorityQueue.java b/hutool-core/src/main/java/cn/hutool/core/collection/BoundedPriorityQueue.java index c62c93709..d83d1135c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/BoundedPriorityQueue.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/BoundedPriorityQueue.java @@ -2,7 +2,6 @@ package cn.hutool.core.collection; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.PriorityQueue; @@ -80,7 +79,7 @@ public class BoundedPriorityQueue extends PriorityQueue{ */ public ArrayList toList() { final ArrayList list = new ArrayList<>(this); - Collections.sort(list, comparator); + list.sort(comparator); return list; } diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CopiedIter.java b/hutool-core/src/main/java/cn/hutool/core/collection/CopiedIter.java index a9c836423..9b77361fe 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CopiedIter.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CopiedIter.java @@ -24,7 +24,7 @@ import java.util.List; public class CopiedIter implements Iterator, Iterable, Serializable { private static final long serialVersionUID = 1L; - private Iterator listIterator; + private final Iterator listIterator; public static CopiedIter copyOf(Iterator iterator){ return new CopiedIter<>(iterator); diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/ComparatorChain.java b/hutool-core/src/main/java/cn/hutool/core/comparator/ComparatorChain.java index 8dec1aa80..44fee28f0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/ComparatorChain.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/ComparatorChain.java @@ -1,5 +1,7 @@ package cn.hutool.core.comparator; +import cn.hutool.core.lang.Chain; + import java.io.Serializable; import java.util.ArrayList; import java.util.BitSet; @@ -8,8 +10,6 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; -import cn.hutool.core.lang.Chain; - /** * 比较器链。此链包装了多个比较器,最终比较结果按照比较器顺序综合多个比较器结果。
    * 按照比较器链的顺序分别比较,如果比较出相等则转向下一个比较器,否则直接返回
    @@ -24,7 +24,7 @@ public class ComparatorChain implements Chain, ComparatorChain< /** 比较器链. */ private final List> chain; /** 对应比较器位置是否反序. */ - private BitSet orderingBits; + private final BitSet orderingBits; /** 比较器是否被锁定。锁定的比较器链不能再添加新的比较器。比较器会在开始比较时开始加锁。 */ private boolean lock = false; @@ -249,8 +249,9 @@ public class ComparatorChain implements Chain, ComparatorChain< } if (object.getClass().equals(this.getClass())) { final ComparatorChain otherChain = (ComparatorChain) object; - return (Objects.equals(this.orderingBits, otherChain.orderingBits)) // - && (null == otherChain ? null == otherChain.chain : this.chain.equals(otherChain.chain)); + // + return Objects.equals(this.orderingBits, otherChain.orderingBits) + && this.chain.equals(otherChain.chain); } return false; } diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/IndexedComparator.java b/hutool-core/src/main/java/cn/hutool/core/comparator/IndexedComparator.java index 0b62eb2d5..adba14c20 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/IndexedComparator.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/IndexedComparator.java @@ -1,9 +1,9 @@ package cn.hutool.core.comparator; -import java.util.Comparator; - import cn.hutool.core.util.ArrayUtil; +import java.util.Comparator; + /** * 按照数组的顺序正序排列,数组的元素位置决定了对象的排序先后
    * 如果参与排序的元素并不在数组中,则排序在前 @@ -15,7 +15,7 @@ import cn.hutool.core.util.ArrayUtil; */ public class IndexedComparator implements Comparator { - private T[] array; + private final T[] array; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java b/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java index 7869a3741..5b6d2c700 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java @@ -93,7 +93,7 @@ public class ConverterRegistry implements Serializable{ /** 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载 */ private static class SingletonHolder { /** 静态初始化器,由JVM来保证线程安全 */ - private static ConverterRegistry instance = new ConverterRegistry(); + private static final ConverterRegistry INSTANCE = new ConverterRegistry(); } /** @@ -102,7 +102,7 @@ public class ConverterRegistry implements Serializable{ * @return {@link ConverterRegistry} */ public static ConverterRegistry getInstance() { - return SingletonHolder.instance; + return SingletonHolder.INSTANCE; } public ConverterRegistry() { diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/BooleanConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/BooleanConverter.java index a205369cd..44368e78f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/BooleanConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/BooleanConverter.java @@ -13,11 +13,8 @@ public class BooleanConverter extends AbstractConverter{ @Override protected Boolean convertInternal(Object value) { - if(boolean.class == value.getClass()){ - return Boolean.valueOf((boolean)value); - } - String valueStr = convertToStr(value); - return Boolean.valueOf(BooleanUtil.toBoolean(valueStr)); + //Object不可能出现Primitive类型,故忽略 + return BooleanUtil.toBoolean(convertToStr(value)); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/CharacterConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/CharacterConverter.java index a641aa7f1..8b3707cc9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/CharacterConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/CharacterConverter.java @@ -15,16 +15,12 @@ public class CharacterConverter extends AbstractConverter { @Override protected Character convertInternal(Object value) { - if (char.class == value.getClass()) { - return Character.valueOf((char) value); - } else if (value instanceof Boolean) { + if (value instanceof Boolean) { return BooleanUtil.toCharacter((Boolean) value); - } else if (boolean.class == value.getClass()) { - return BooleanUtil.toCharacter((boolean) value); } else { final String valueStr = convertToStr(value); if (StrUtil.isNotBlank(valueStr)) { - return Character.valueOf(valueStr.charAt(0)); + return valueStr.charAt(0); } } return null; diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/DateConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/DateConverter.java index 1ee9350c4..f9f0132ff 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/DateConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/DateConverter.java @@ -18,7 +18,7 @@ import java.util.Date; public class DateConverter extends AbstractConverter { private static final long serialVersionUID = 1L; - private Class targetType; + private final Class targetType; /** 日期格式化 */ private String format; diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/EnumConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/EnumConverter.java index 1b4d5618b..292dc04c4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/EnumConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/EnumConverter.java @@ -25,7 +25,7 @@ public class EnumConverter extends AbstractConverter { private static final Map, Map, Method>> VALUE_OF_METHOD_CACHE = new ConcurrentHashMap<>(); - private Class enumClass; + private final Class enumClass; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/GenericEnumConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/GenericEnumConverter.java index 4e6f289d8..7291f55e0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/GenericEnumConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/GenericEnumConverter.java @@ -14,7 +14,7 @@ import cn.hutool.core.convert.AbstractConverter; public class GenericEnumConverter> extends AbstractConverter { private static final long serialVersionUID = 1L; - private Class enumClass; + private final Class enumClass; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java index 50263664a..b102c0274 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java @@ -1,15 +1,15 @@ package cn.hutool.core.convert.impl; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - import cn.hutool.core.convert.AbstractConverter; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + /** * 数字转换器
    * 支持类型为:
    @@ -30,7 +30,7 @@ import cn.hutool.core.util.StrUtil; public class NumberConverter extends AbstractConverter { private static final long serialVersionUID = 1L; - private Class targetType; + private final Class targetType; public NumberConverter() { this.targetType = Number.class; @@ -50,7 +50,7 @@ public class NumberConverter extends AbstractConverter { final Class targetType = this.targetType; if (Byte.class == targetType) { if (value instanceof Number) { - return Byte.valueOf(((Number) value).byteValue()); + return ((Number) value).byteValue(); } else if(value instanceof Boolean) { return BooleanUtil.toByteObj((Boolean)value); } @@ -59,7 +59,7 @@ public class NumberConverter extends AbstractConverter { } else if (Short.class == targetType) { if (value instanceof Number) { - return Short.valueOf(((Number) value).shortValue()); + return ((Number) value).shortValue(); } else if(value instanceof Boolean) { return BooleanUtil.toShortObj((Boolean)value); } @@ -68,12 +68,12 @@ public class NumberConverter extends AbstractConverter { } else if (Integer.class == targetType) { if (value instanceof Number) { - return Integer.valueOf(((Number) value).intValue()); + return ((Number) value).intValue(); } else if(value instanceof Boolean) { return BooleanUtil.toInteger((Boolean)value); } final String valueStr = convertToStr(value); - return StrUtil.isBlank(valueStr) ? null : Integer.valueOf(NumberUtil.parseInt(valueStr)); + return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseInt(valueStr); } else if (AtomicInteger.class == targetType) { final AtomicInteger intValue = new AtomicInteger(); @@ -90,12 +90,12 @@ public class NumberConverter extends AbstractConverter { return intValue; } else if (Long.class == targetType) { if (value instanceof Number) { - return Long.valueOf(((Number) value).longValue()); + return ((Number) value).longValue(); } else if(value instanceof Boolean) { return BooleanUtil.toLongObj((Boolean)value); } final String valueStr = convertToStr(value); - return StrUtil.isBlank(valueStr) ? null : Long.valueOf(NumberUtil.parseLong(valueStr)); + return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseLong(valueStr); } else if (AtomicLong.class == targetType) { final AtomicLong longValue = new AtomicLong(); @@ -113,7 +113,7 @@ public class NumberConverter extends AbstractConverter { } else if (Float.class == targetType) { if (value instanceof Number) { - return Float.valueOf(((Number) value).floatValue()); + return ((Number) value).floatValue(); } else if(value instanceof Boolean) { return BooleanUtil.toFloatObj((Boolean)value); } @@ -122,7 +122,7 @@ public class NumberConverter extends AbstractConverter { } else if (Double.class == targetType) { if (value instanceof Number) { - return Double.valueOf(((Number) value).doubleValue()); + return ((Number) value).doubleValue(); } else if(value instanceof Boolean) { return BooleanUtil.toDoubleObj((Boolean)value); } diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/PrimitiveConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/PrimitiveConverter.java index ee3a95f31..a8dd0b987 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/PrimitiveConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/PrimitiveConverter.java @@ -29,7 +29,7 @@ import java.util.Date; public class PrimitiveConverter extends AbstractConverter { private static final long serialVersionUID = 1L; - private Class targetType; + private final Class targetType; /** * 构造
    diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/ReferenceConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/ReferenceConverter.java index 8dd269a9f..b9bbd42ce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/ReferenceConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/ReferenceConverter.java @@ -1,15 +1,15 @@ package cn.hutool.core.convert.impl; -import java.lang.ref.Reference; -import java.lang.ref.SoftReference; -import java.lang.ref.WeakReference; -import java.lang.reflect.Type; - import cn.hutool.core.convert.AbstractConverter; import cn.hutool.core.convert.ConverterRegistry; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.TypeUtil; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.lang.reflect.Type; + /** * {@link Reference}转换器 * @@ -20,7 +20,7 @@ import cn.hutool.core.util.TypeUtil; public class ReferenceConverter extends AbstractConverter { private static final long serialVersionUID = 1L; - private Class targetType; + private final Class targetType; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java index 8943fb888..af6331626 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java @@ -39,7 +39,7 @@ import java.util.Objects; public class TemporalAccessorConverter extends AbstractConverter { private static final long serialVersionUID = 1L; - private Class targetType; + private final Class targetType; /** * 日期格式化 */ diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateBetween.java b/hutool-core/src/main/java/cn/hutool/core/date/DateBetween.java index ff2a5ed84..47c01fe3f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateBetween.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateBetween.java @@ -16,9 +16,9 @@ public class DateBetween implements Serializable{ private static final long serialVersionUID = 1L; /** 开始日期 */ - private Date begin; + private final Date begin; /** 结束日期 */ - private Date end; + private final Date end; /** * 创建
    diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateField.java b/hutool-core/src/main/java/cn/hutool/core/date/DateField.java index d5b2b07f3..c8c572837 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateField.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateField.java @@ -103,7 +103,7 @@ public enum DateField { MILLISECOND(Calendar.MILLISECOND); // --------------------------------------------------------------- - private int value; + private final int value; DateField(int value) { this.value = value; diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateUnit.java b/hutool-core/src/main/java/cn/hutool/core/date/DateUnit.java index 13a36c71d..2f2a58544 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateUnit.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUnit.java @@ -19,7 +19,7 @@ public enum DateUnit { /**一周的毫秒数 */ WEEK(DAY.getMillis() * 7); - private long millis; + private final long millis; DateUnit(long millis){ this.millis = millis; } diff --git a/hutool-core/src/main/java/cn/hutool/core/date/Month.java b/hutool-core/src/main/java/cn/hutool/core/date/Month.java index 9d64b9e15..3c35608d0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/Month.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/Month.java @@ -53,7 +53,7 @@ public enum Month { UNDECIMBER(Calendar.UNDECIMBER); // --------------------------------------------------------------- - private int value; + private final int value; Month(int value) { this.value = value; diff --git a/hutool-core/src/main/java/cn/hutool/core/date/Quarter.java b/hutool-core/src/main/java/cn/hutool/core/date/Quarter.java index 0382e6160..d735011c5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/Quarter.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/Quarter.java @@ -23,7 +23,7 @@ public enum Quarter { Q4(4); // --------------------------------------------------------------- - private int value; + private final int value; Quarter(int value) { this.value = value; diff --git a/hutool-core/src/main/java/cn/hutool/core/date/SystemClock.java b/hutool-core/src/main/java/cn/hutool/core/date/SystemClock.java index d70842140..502acc67a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/SystemClock.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/SystemClock.java @@ -26,7 +26,7 @@ public class SystemClock { * 构造 * @param period 时钟更新间隔,单位毫秒 */ - private SystemClock(long period) { + public SystemClock(long period) { this.period = period; this.now = System.currentTimeMillis(); scheduleClockUpdating(); diff --git a/hutool-core/src/main/java/cn/hutool/core/date/TimeInterval.java b/hutool-core/src/main/java/cn/hutool/core/date/TimeInterval.java index 896c47de1..8edd5cfe8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/TimeInterval.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/TimeInterval.java @@ -12,7 +12,7 @@ public class TimeInterval implements Serializable { private static final long serialVersionUID = 1L; private long time; - private boolean isNano; + private final boolean isNano; /** * 构造,默认使用毫秒计数 diff --git a/hutool-core/src/main/java/cn/hutool/core/date/Week.java b/hutool-core/src/main/java/cn/hutool/core/date/Week.java index af1c8add1..e4282f43a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/Week.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/Week.java @@ -36,7 +36,7 @@ public enum Week { // --------------------------------------------------------------- /** 星期对应{@link Calendar} 中的Week值 */ - private int value; + private final int value; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/date/format/FastDatePrinter.java b/hutool-core/src/main/java/cn/hutool/core/date/format/FastDatePrinter.java index 9f6dd33d9..00db5b766 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/format/FastDatePrinter.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/format/FastDatePrinter.java @@ -1,5 +1,7 @@ package cn.hutool.core.date.format; +import cn.hutool.core.date.DateException; + import java.io.IOException; import java.io.ObjectInputStream; import java.text.DateFormatSymbols; @@ -12,8 +14,6 @@ import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import cn.hutool.core.date.DateException; - /** * {@link java.text.SimpleDateFormat} 的线程安全版本,用于将 {@link Date} 格式化输出
    * Thanks to Apache Commons Lang 3.5 @@ -48,7 +48,7 @@ class FastDatePrinter extends AbstractDateBasic implements DatePrinter { */ private void init() { final List rulesList = parsePattern(); - rules = rulesList.toArray(new Rule[rulesList.size()]); + rules = rulesList.toArray(new Rule[0]); int len = 0; for (int i = rules.length; --i >= 0;) { diff --git a/hutool-core/src/main/java/cn/hutool/core/img/Img.java b/hutool-core/src/main/java/cn/hutool/core/img/Img.java index 3233751c8..4438a634f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/Img.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/Img.java @@ -47,7 +47,7 @@ import java.nio.file.Path; public class Img implements Serializable { private static final long serialVersionUID = 1L; - private BufferedImage srcImage; + private final BufferedImage srcImage; private Image targetImage; /** * 目标图片文件格式,用于写出 diff --git a/hutool-core/src/main/java/cn/hutool/core/img/ScaleType.java b/hutool-core/src/main/java/cn/hutool/core/img/ScaleType.java index ed8eb8724..d96048879 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/ScaleType.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/ScaleType.java @@ -35,7 +35,7 @@ public enum ScaleType { this.value = value; } - private int value; + private final int value; public int getValue() { return this.value; diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 0390eff9f..a85e2ae19 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -82,7 +82,7 @@ public class FileUtil { /** * Windows下文件名中的无效字符 */ - private static Pattern FILE_NAME_INVALID_PATTERN_WIN = Pattern.compile("[\\\\/:*?\"<>|]"); + private static final Pattern FILE_NAME_INVALID_PATTERN_WIN = Pattern.compile("[\\\\/:*?\"<>|]"); /** * Class文件扩展名 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileAppender.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileAppender.java index 51a61a00f..1c0b087af 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/FileAppender.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileAppender.java @@ -1,5 +1,7 @@ package cn.hutool.core.io.file; +import cn.hutool.core.util.CharsetUtil; + import java.io.File; import java.io.PrintWriter; import java.io.Serializable; @@ -7,8 +9,6 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; -import cn.hutool.core.util.CharsetUtil; - /** * 文件追加器
    * 持有一个文件,在内存中积累一定量的数据后统一追加到文件
    @@ -21,12 +21,12 @@ import cn.hutool.core.util.CharsetUtil; public class FileAppender implements Serializable{ private static final long serialVersionUID = 1L; - private FileWriter writer; + private final FileWriter writer; /** 内存中持有的字符串数 */ - private int capacity; + private final int capacity; /** 追加内容是否为新行 */ - private boolean isNewLineMode; - private List list = new ArrayList<>(100); + private final boolean isNewLineMode; + private final List list = new ArrayList<>(100); /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/LineReadWatcher.java b/hutool-core/src/main/java/cn/hutool/core/io/file/LineReadWatcher.java index 18cc739b7..f0f83d05a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/LineReadWatcher.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/LineReadWatcher.java @@ -1,16 +1,16 @@ package cn.hutool.core.io.file; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.LineHandler; +import cn.hutool.core.io.watch.SimpleWatcher; + import java.io.IOException; import java.io.RandomAccessFile; import java.nio.charset.Charset; import java.nio.file.Path; import java.nio.file.WatchEvent; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.LineHandler; -import cn.hutool.core.io.watch.SimpleWatcher; - /** * 行处理的Watcher实现 * @@ -19,9 +19,9 @@ import cn.hutool.core.io.watch.SimpleWatcher; */ public class LineReadWatcher extends SimpleWatcher implements Runnable { - private RandomAccessFile randomAccessFile; - private Charset charset; - private LineHandler lineHandler; + private final RandomAccessFile randomAccessFile; + private final Charset charset; + private final LineHandler lineHandler; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/LineSeparator.java b/hutool-core/src/main/java/cn/hutool/core/io/file/LineSeparator.java index 23441aea3..514a5db0f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/LineSeparator.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/LineSeparator.java @@ -23,7 +23,7 @@ public enum LineSeparator { /** Windows系统换行符:"\r\n" */ WINDOWS("\r\n"); - private String value; + private final String value; LineSeparator(String lineSeparator) { this.value = lineSeparator; diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/Tailer.java b/hutool-core/src/main/java/cn/hutool/core/io/file/Tailer.java index f9403eac6..172a65439 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/Tailer.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/Tailer.java @@ -1,5 +1,14 @@ package cn.hutool.core.io.file; +import cn.hutool.core.date.DateUnit; +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.LineHandler; +import cn.hutool.core.lang.Console; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.CharsetUtil; + import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; @@ -12,15 +21,6 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import cn.hutool.core.date.DateUnit; -import cn.hutool.core.exceptions.UtilException; -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.LineHandler; -import cn.hutool.core.lang.Console; -import cn.hutool.core.util.CharUtil; -import cn.hutool.core.util.CharsetUtil; - /** * 文件内容跟随器,实现类似Linux下"tail -f"命令功能 * @@ -33,16 +33,16 @@ public class Tailer implements Serializable { public static final LineHandler CONSOLE_HANDLER = new ConsoleLineHandler(); /** 编码 */ - private Charset charset; + private final Charset charset; /** 行处理器 */ - private LineHandler lineHandler; + private final LineHandler lineHandler; /** 初始读取的行数 */ - private int initReadLine; + private final int initReadLine; /** 定时任务检查间隔时长 */ - private long period; + private final long period; - private RandomAccessFile randomAccessFile; - private ScheduledExecutorService executorService; + private final RandomAccessFile randomAccessFile; + private final ScheduledExecutorService executorService; /** * 构造,默认UTF-8编码 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java index 0955db2ce..10403c9eb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java @@ -1,15 +1,15 @@ package cn.hutool.core.io.resource; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.CharsetUtil; + import java.io.BufferedReader; import java.io.InputStream; import java.io.Serializable; import java.net.URL; import java.nio.charset.Charset; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.CharsetUtil; - /** * 基于{@link InputStream}的资源获取器
    * 注意:此对象中getUrl方法始终返回null @@ -20,8 +20,8 @@ import cn.hutool.core.util.CharsetUtil; public class InputStreamResource implements Resource, Serializable { private static final long serialVersionUID = 1L; - private InputStream in; - private String name; + private final InputStream in; + private final String name; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java index efa737bd7..3d46d64e3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java @@ -1,5 +1,8 @@ package cn.hutool.core.io.resource; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.IORuntimeException; + import java.io.BufferedReader; import java.io.InputStream; import java.io.Serializable; @@ -10,9 +13,6 @@ import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.List; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.io.IORuntimeException; - /** * 多资源组合资源
    * 此资源为一个利用游标自循环资源,只有调用{@link #next()} 方法才会获取下一个资源,使用完毕后调用{@link #reset()}方法重置游标 @@ -23,7 +23,7 @@ import cn.hutool.core.io.IORuntimeException; public class MultiResource implements Resource, Iterable, Iterator, Serializable { private static final long serialVersionUID = 1L; - private List resources; + private final List resources; private int cursor; /** diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java index 53a6d108a..e5c04f3bf 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java @@ -1,5 +1,9 @@ package cn.hutool.core.io.resource; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.CharsetUtil; + import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -8,10 +12,6 @@ import java.io.StringReader; import java.net.URL; import java.nio.charset.Charset; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.CharsetUtil; - /** * 字符串资源,字符串做为资源 * @@ -21,9 +21,9 @@ import cn.hutool.core.util.CharsetUtil; public class StringResource implements Resource, Serializable { private static final long serialVersionUID = 1L; - private String data; - private String name; - private Charset charset; + private final String data; + private final String name; + private final Charset charset; /** * 构造,使用UTF8编码 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java index 0707cd294..148a416f0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java @@ -1,12 +1,5 @@ package cn.hutool.core.io.resource; -import java.io.BufferedReader; -import java.io.File; -import java.io.InputStream; -import java.io.Serializable; -import java.net.URL; -import java.nio.charset.Charset; - import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; @@ -14,6 +7,13 @@ import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.URLUtil; +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStream; +import java.io.Serializable; +import java.net.URL; +import java.nio.charset.Charset; + /** * URL资源访问类 * @author Looly @@ -68,7 +68,7 @@ public class UrlResource implements Resource, Serializable{ @Override public InputStream getStream() throws NoResourceException{ if(null == this.url){ - throw new NoResourceException("Resource [{}] not exist!", this.url); + throw new NoResourceException("Resource URL is null!"); } return URLUtil.getStream(url); } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchKind.java b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchKind.java index 546dd09ba..a5b639291 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchKind.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchKind.java @@ -45,7 +45,7 @@ public enum WatchKind { DELETE.getValue() //删除 }; - private WatchEvent.Kind value; + private final WatchEvent.Kind value; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchServer.java b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchServer.java index 31ffa0e8a..342ca0cb8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchServer.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/watch/WatchServer.java @@ -51,7 +51,7 @@ public class WatchServer extends Thread implements Closeable, Serializable { /** * WatchKey 和 Path的对应表 */ - private Map watchKeyPathMap = new HashMap<>(); + private final Map watchKeyPathMap = new HashMap<>(); /** * 初始化
    diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/ClassScanner.java b/hutool-core/src/main/java/cn/hutool/core/lang/ClassScanner.java index b998f9c3c..4e099050c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/ClassScanner.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/ClassScanner.java @@ -2,7 +2,6 @@ package cn.hutool.core.lang; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.EnumerationIter; -import cn.hutool.core.convert.Convert; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.resource.ResourceUtil; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java b/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java index 2b6d28ca8..fa9510594 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java @@ -1,10 +1,10 @@ package cn.hutool.core.lang; +import cn.hutool.core.clone.CloneSupport; + import java.io.Serializable; import java.util.Objects; -import cn.hutool.core.clone.CloneSupport; - /** * 键值对对象,只能在构造时传入键值 * @@ -17,8 +17,8 @@ import cn.hutool.core.clone.CloneSupport; public class Pair extends CloneSupport> implements Serializable{ private static final long serialVersionUID = 1L; - private K key; - private V value; + private final K key; + private final V value; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java b/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java index 9778c61d3..c56a2e3df 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java @@ -183,8 +183,8 @@ public class PatternPool { * @author Looly */ private static class RegexWithFlag { - private String regex; - private int flag; + private final String regex; + private final int flag; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Range.java b/hutool-core/src/main/java/cn/hutool/core/lang/Range.java index af6d82bab..6057d5bb9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Range.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Range.java @@ -1,5 +1,7 @@ package cn.hutool.core.lang; +import cn.hutool.core.thread.lock.NoLock; + import java.io.Serializable; import java.util.Iterator; import java.util.NoSuchElementException; @@ -7,8 +9,6 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import cn.hutool.core.thread.lock.NoLock; - /** * 范围生成器。根据给定的初始值、结束值和步进生成一个步进列表生成器
    * 由于用户自行实现{@link Steper}来定义步进,因此Range本身无法判定边界(是否达到end),需在step实现边界判定逻辑。 @@ -27,19 +27,19 @@ public class Range implements Iterable, Iterator, Serializable { /** 锁保证线程安全 */ private Lock lock = new ReentrantLock(); /** 起始对象 */ - private T start; + private final T start; /** 结束对象 */ - private T end; + private final T end; /** 当前对象 */ private T current; /** 下一个对象 */ private T next; /** 步进 */ - private Steper steper; + private final Steper steper; /** 索引 */ private int index = 0; /** 是否包含第一个元素 */ - private boolean includeStart; + private final boolean includeStart; /** 是否包含最后一个元素 */ private boolean includeEnd; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java b/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java index 0c2cff152..07e31c668 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java @@ -8,24 +8,46 @@ import java.util.WeakHashMap; import java.util.concurrent.locks.StampedLock; /** - * 简单缓存,无超时实现,使用{@link WeakHashMap}实现缓存自动清理 - * @author Looly + * 简单缓存,无超时实现,默认使用{@link WeakHashMap}实现缓存自动清理 * * @param 键类型 * @param 值类型 + * @author Looly */ -public class SimpleCache implements Serializable{ +public class SimpleCache implements Serializable { private static final long serialVersionUID = 1L; - - /** 池 */ - private final Map cache = new WeakHashMap<>(); + /** + * 池 + */ + private final Map cache; // 乐观读写锁 - private final StampedLock lock = new StampedLock (); + private final StampedLock lock = new StampedLock(); + + /** + * 构造,默认使用{@link WeakHashMap}实现缓存自动清理 + */ + public SimpleCache() { + this(new WeakHashMap<>()); + } + + /** + * 构造 + *

    + * 通过自定义Map初始化,可以自定义缓存实现。
    + * 比如使用{@link WeakHashMap}则会自动清理key,使用HashMap则不会清理
    + * 同时,传入的Map对象也可以自带初始化的键值对,防止在get时创建 + *

    + * + * @param initMap 初始Map,用于定义Map类型 + */ + public SimpleCache(Map initMap) { + this.cache = initMap; + } /** * 从缓存池中查找值 - * + * * @param key 键 * @return 值 */ @@ -37,27 +59,27 @@ public class SimpleCache implements Serializable{ lock.unlockRead(stamp); } } - + /** * 从缓存中获得对象,当对象不在缓存中或已经过期返回Func0回调产生的对象 - * - * @param key 键 + * + * @param key 键 * @param supplier 如果不存在回调方法,用于生产值对象 * @return 值对象 */ public V get(K key, Func0 supplier) { - if(null == supplier){ + if (null == supplier) { return get(key); } long stamp = lock.readLock(); V v; - try{ + try { v = cache.get(key); if (null == v) { // 尝试转换独占写锁 long writeStamp = lock.tryConvertToWriteLock(stamp); - if(0 == writeStamp){ + if (0 == writeStamp) { // 转换失败,手动更新为写锁 lock.unlockRead(stamp); writeStamp = lock.writeLock(); @@ -65,7 +87,7 @@ public class SimpleCache implements Serializable{ stamp = writeStamp; v = cache.get(key); // 双重检查,防止在竞争锁的过程中已经有其它线程写入 - if(null == v) { + if (null == v) { try { v = supplier.call(); } catch (Exception e) { @@ -79,14 +101,15 @@ public class SimpleCache implements Serializable{ } return v; } - + /** * 放入缓存 - * @param key 键 + * + * @param key 键 * @param value 值 * @return 值 */ - public V put(K key, V value){ + public V put(K key, V value) { // 独占写锁 final long stamp = lock.writeLock(); try { @@ -99,7 +122,7 @@ public class SimpleCache implements Serializable{ /** * 移除缓存 - * + * * @param key 键 * @return 移除的值 */ diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java b/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java index ef9f23b86..c58d3b4d2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java @@ -1,22 +1,21 @@ package cn.hutool.core.lang; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; +import java.util.HashMap; + /** * 单例类
    * 提供单例对象的统一管理,当调用get方法时,如果对象池中存在此对象,返回此对象,否则创建新对象返回
    - * - * @author loolly * + * @author loolly */ public final class Singleton { - private static Map pool = new ConcurrentHashMap<>(); + + private static final SimpleCache POOL = new SimpleCache<>(new HashMap<>()); private Singleton() { } @@ -25,9 +24,9 @@ public final class Singleton { * 获得指定类的单例对象
    * 对象存在于池中返回,否则创建,每次调用此方法获得的对象为同一个对象
    * 注意:单例针对的是类和对象,因此get方法第一次调用时创建的对象始终唯一,也就是说就算参数变更,返回的依旧是第一次创建的对象 - * - * @param 单例对象类型 - * @param clazz 类 + * + * @param 单例对象类型 + * @param clazz 类 * @param params 构造方法参数 * @return 单例对象 */ @@ -35,28 +34,16 @@ public final class Singleton { public static T get(Class clazz, Object... params) { Assert.notNull(clazz, "Class must be not null !"); final String key = buildKey(clazz.getName(), params); - T obj = (T) pool.get(key); - - if (null == obj) { - synchronized (Singleton.class) { - obj = (T) pool.get(key); - if (null == obj) { - obj = (T) ReflectUtil.newInstance(clazz, params); - pool.put(key, obj); - } - } - } - - return obj; + return (T) POOL.get(key, () -> ReflectUtil.newInstance(clazz, params)); } /** * 获得指定类的单例对象
    * 对象存在于池中返回,否则创建,每次调用此方法获得的对象为同一个对象
    - * - * @param 单例对象类型 + * + * @param 单例对象类型 * @param className 类名 - * @param params 构造参数 + * @param params 构造参数 * @return 单例对象 */ public static T get(String className, Object... params) { @@ -67,23 +54,23 @@ public final class Singleton { /** * 将已有对象放入单例中,其Class做为键 - * + * * @param obj 对象 * @since 4.0.7 */ public static void put(Object obj) { Assert.notNull(obj, "Bean object must be not null !"); - pool.put(obj.getClass().getName(), obj); + POOL.put(obj.getClass().getName(), obj); } /** * 移除指定Singleton对象 - * + * * @param clazz 类 */ public static void remove(Class clazz) { if (null != clazz) { - pool.remove(clazz.getName()); + POOL.remove(clazz.getName()); } } @@ -91,15 +78,16 @@ public final class Singleton { * 清除所有Singleton对象 */ public static void destroy() { - pool.clear(); + POOL.clear(); } // ------------------------------------------------------------------------------------------- Private method start + /** * 构建key - * + * * @param className 类名 - * @param params 参数列表 + * @param params 参数列表 * @return key */ private static String buildKey(String className, Object... params) { diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java b/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java index 6b1baa8d5..b39ddb3bd 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java @@ -51,11 +51,11 @@ public class Snowflake implements Serializable { @SuppressWarnings({"PointlessBitwiseExpression", "FieldCanBeLocal"}) private final long sequenceMask = -1L ^ (-1L << sequenceBits);// 4095 - private long workerId; - private long dataCenterId; + private final long workerId; + private final long dataCenterId; + private final boolean useSystemClock; private long sequence = 0L; private long lastTimestamp = -1L; - private boolean useSystemClock; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/UUID.java b/hutool-core/src/main/java/cn/hutool/core/lang/UUID.java index 8c8805d07..40e993d87 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/UUID.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/UUID.java @@ -1,13 +1,13 @@ package cn.hutool.core.lang; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; + import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Random; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; - /** * 提供通用唯一识别码(universally unique identifier)(UUID)实现,UUID表示一个128位的值。
    * 此类拷贝自java.util.UUID,用于生成不带-的UUID字符串 @@ -165,15 +165,15 @@ public final class UUID implements java.io.Serializable, Comparable { components[i] = "0x" + components[i]; } - long mostSigBits = Long.decode(components[0]).longValue(); + long mostSigBits = Long.decode(components[0]); mostSigBits <<= 16; - mostSigBits |= Long.decode(components[1]).longValue(); + mostSigBits |= Long.decode(components[1]); mostSigBits <<= 16; - mostSigBits |= Long.decode(components[2]).longValue(); + mostSigBits |= Long.decode(components[2]); - long leastSigBits = Long.decode(components[3]).longValue(); + long leastSigBits = Long.decode(components[3]); leastSigBits <<= 48; - leastSigBits |= Long.decode(components[4]).longValue(); + leastSigBits |= Long.decode(components[4]); return new UUID(mostSigBits, leastSigBits); } @@ -412,11 +412,11 @@ public final class UUID implements java.io.Serializable, Comparable { public int compareTo(UUID val) { // The ordering is intentionally set up so that the UUIDs // can simply be numerically compared as two numbers - return (this.mostSigBits < val.mostSigBits ? -1 : // - (this.mostSigBits > val.mostSigBits ? 1 : // - (this.leastSigBits < val.leastSigBits ? -1 : // - (this.leastSigBits > val.leastSigBits ? 1 : // - 0)))); + int compare = Long.compare(this.mostSigBits, val.mostSigBits); + if(0 == compare){ + compare = Long.compare(this.leastSigBits, val.leastSigBits); + } + return compare; } // ------------------------------------------------------------------------------------------------------------------- Private method start diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/WeightRandom.java b/hutool-core/src/main/java/cn/hutool/core/lang/WeightRandom.java index 630c84a03..fd5f4dd0e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/WeightRandom.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/WeightRandom.java @@ -1,14 +1,14 @@ package cn.hutool.core.lang; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.RandomUtil; + import java.io.Serializable; import java.util.Random; import java.util.SortedMap; import java.util.TreeMap; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.RandomUtil; - /** * 权重随机算法实现
    *

    @@ -31,8 +31,8 @@ import cn.hutool.core.util.RandomUtil; public class WeightRandom implements Serializable { private static final long serialVersionUID = -8244697995702786499L; - private TreeMap weightMap; - private Random random; + private final TreeMap weightMap; + private final Random random; /** * 创建权重随机获取器 @@ -157,7 +157,7 @@ public class WeightRandom implements Serializable { /** 对象 */ private T obj; /** 权重 */ - private double weight; + private final double weight; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java index 38ab7d796..f5ab41fe4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/Tree.java @@ -16,7 +16,7 @@ import java.util.List; public class Tree extends LinkedHashMap implements Node { private static final long serialVersionUID = 1L; - private TreeNodeConfig treeNodeConfig; + private final TreeNodeConfig treeNodeConfig; private Tree parent; public Tree() { diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index 38779d1da..c2922e78e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -13,7 +13,7 @@ import java.util.Map; public class MapBuilder implements Serializable{ private static final long serialVersionUID = 1L; - private Map map; + private final Map map; /** * 创建Builder,默认HashMap实现 diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapProxy.java b/hutool-core/src/main/java/cn/hutool/core/map/MapProxy.java index 2d9875605..44604ee15 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapProxy.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapProxy.java @@ -1,5 +1,12 @@ package cn.hutool.core.map; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.getter.OptNullBasicTypeFromObjectGetter; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.StrUtil; + import java.io.Serializable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; @@ -8,13 +15,6 @@ import java.util.Collection; import java.util.Map; import java.util.Set; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.getter.OptNullBasicTypeFromObjectGetter; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.BooleanUtil; -import cn.hutool.core.util.ClassLoaderUtil; -import cn.hutool.core.util.StrUtil; - /** * Map代理,提供各种getXXX方法,并提供默认值支持 * @@ -123,7 +123,7 @@ public class MapProxy implements Map, OptNullBasicTypeFromObject final Class[] parameterTypes = method.getParameterTypes(); if (ArrayUtil.isEmpty(parameterTypes)) { final Class returnType = method.getReturnType(); - if (null != returnType && void.class != returnType) { + if (void.class != returnType) { // 匹配Getter final String methodName = method.getName(); String fieldName = null; diff --git a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java index 9190bec43..8fcfd9919 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java @@ -27,8 +27,8 @@ import java.util.Set; public class TableMap implements Map, Serializable { private static final long serialVersionUID = 1L; - private List keys; - private List values; + private final List keys; + private final List values; /** * 构造 @@ -168,8 +168,8 @@ public class TableMap implements Map, Serializable { private static class Entry implements Map.Entry { - private K key; - private V value; + private final K key; + private final V value; public Entry(K key, V value) { this.key = key; diff --git a/hutool-core/src/main/java/cn/hutool/core/map/TolerantMap.java b/hutool-core/src/main/java/cn/hutool/core/map/TolerantMap.java index 580f39599..d5e67a63d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/TolerantMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/TolerantMap.java @@ -19,185 +19,185 @@ import java.util.function.Function; */ public class TolerantMap extends AbstractMap implements Map, Cloneable, Serializable { - private static final long serialVersionUID = -4158133823263496197L; + private static final long serialVersionUID = -4158133823263496197L; - private transient Map map; + private transient Map map; - private transient V defaultValue; + private transient V defaultValue; - public TolerantMap(V defaultValue) { - this(new HashMap<>(), defaultValue); - } + public TolerantMap(V defaultValue) { + this(new HashMap<>(), defaultValue); + } - public TolerantMap(int initialCapacity, float loadFactor, V defaultValue) { - this(new HashMap<>(initialCapacity, loadFactor), defaultValue); - } + public TolerantMap(int initialCapacity, float loadFactor, V defaultValue) { + this(new HashMap<>(initialCapacity, loadFactor), defaultValue); + } - public TolerantMap(int initialCapacity, V defaultValue) { - this(new HashMap<>(initialCapacity), defaultValue); - } + public TolerantMap(int initialCapacity, V defaultValue) { + this(new HashMap<>(initialCapacity), defaultValue); + } - public TolerantMap(Map map, V defaultValue) { - this.map = map; - this.defaultValue = defaultValue; - } + public TolerantMap(Map map, V defaultValue) { + this.map = map; + this.defaultValue = defaultValue; + } - public static TolerantMap of(Map map, V defaultValue) { - return new TolerantMap<>(map, defaultValue); - } + public static TolerantMap of(Map map, V defaultValue) { + return new TolerantMap<>(map, defaultValue); + } - @Override - public int size() { - return map.size(); - } + @Override + public int size() { + return map.size(); + } - @Override - public boolean isEmpty() { - return map.isEmpty(); - } + @Override + public boolean isEmpty() { + return map.isEmpty(); + } - @Override - public boolean containsValue(Object value) { - return map.containsValue(value); - } + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } - @Override - public boolean containsKey(Object key) { - return map.containsKey(key); - } + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } - @Override - public V get(Object key) { - return getOrDefault(key, defaultValue); - } + @Override + public V get(Object key) { + return getOrDefault(key, defaultValue); + } - @Override - public V put(K key, V value) { - return map.put(key, value); - } + @Override + public V put(K key, V value) { + return map.put(key, value); + } - @Override - public V remove(Object key) { - return map.remove(key); - } + @Override + public V remove(Object key) { + return map.remove(key); + } - @Override - public void putAll(Map m) { - map.putAll(m); - } + @Override + public void putAll(Map m) { + map.putAll(m); + } - @Override - public void clear() { - map.clear(); - } + @Override + public void clear() { + map.clear(); + } - @Override - public Set keySet() { - return map.keySet(); - } + @Override + public Set keySet() { + return map.keySet(); + } - @Override - public Collection values() { - return map.values(); - } + @Override + public Collection values() { + return map.values(); + } - @Override - public Set> entrySet() { - return map.entrySet(); - } + @Override + public Set> entrySet() { + return map.entrySet(); + } - @Override - public V getOrDefault(Object key, V defaultValue) { - return map.getOrDefault(key, defaultValue); - } + @Override + public V getOrDefault(Object key, V defaultValue) { + return map.getOrDefault(key, defaultValue); + } - @Override - public void forEach(BiConsumer action) { - map.forEach(action); - } + @Override + public void forEach(BiConsumer action) { + map.forEach(action); + } - @Override - public void replaceAll(BiFunction function) { - map.replaceAll(function); - } + @Override + public void replaceAll(BiFunction function) { + map.replaceAll(function); + } - @Override - public V putIfAbsent(K key, V value) { - return map.putIfAbsent(key, value); - } + @Override + public V putIfAbsent(K key, V value) { + return map.putIfAbsent(key, value); + } - @Override - public boolean remove(Object key, Object value) { - return map.remove(key, value); - } + @Override + public boolean remove(Object key, Object value) { + return map.remove(key, value); + } - @Override - public boolean replace(K key, V oldValue, V newValue) { - return map.replace(key, oldValue, newValue); - } + @Override + public boolean replace(K key, V oldValue, V newValue) { + return map.replace(key, oldValue, newValue); + } - @Override - public V replace(K key, V value) { - return map.replace(key, value); - } + @Override + public V replace(K key, V value) { + return map.replace(key, value); + } - @Override - public V computeIfAbsent(K key, Function mappingFunction) { - return map.computeIfAbsent(key, mappingFunction); - } + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + return map.computeIfAbsent(key, mappingFunction); + } - @Override - public V computeIfPresent(K key, BiFunction remappingFunction) { - return map.computeIfPresent(key, remappingFunction); - } + @Override + public V computeIfPresent(K key, BiFunction remappingFunction) { + return map.computeIfPresent(key, remappingFunction); + } - @Override - public V compute(K key, BiFunction remappingFunction) { - return map.compute(key, remappingFunction); - } + @Override + public V compute(K key, BiFunction remappingFunction) { + return map.compute(key, remappingFunction); + } - @Override - public V merge(K key, V value, BiFunction remappingFunction) { - return map.merge(key, value, remappingFunction); - } + @Override + public V merge(K key, V value, BiFunction remappingFunction) { + return map.merge(key, value, remappingFunction); + } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - if (!super.equals(o)) { - return false; - } - TolerantMap that = (TolerantMap) o; - return map.equals(that.map) && Objects.equals(defaultValue, that.defaultValue); - } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + if (!super.equals(o)) { + return false; + } + TolerantMap that = (TolerantMap) o; + return map.equals(that.map) && Objects.equals(defaultValue, that.defaultValue); + } - @Override - public int hashCode() { - return Objects.hash(map, defaultValue); - } + @Override + public int hashCode() { + return Objects.hash(map, defaultValue); + } - @Override - public String toString() { - return "TolerantMap{" + "map=" + map + ", defaultValue=" + defaultValue + '}'; - } + @Override + public String toString() { + return "TolerantMap{" + "map=" + map + ", defaultValue=" + defaultValue + '}'; + } - private void writeObject(ObjectOutputStream s) throws IOException { - s.writeObject(ObjectUtil.serialize(map)); - s.writeObject(ObjectUtil.serialize(defaultValue)); - } + private void writeObject(ObjectOutputStream s) throws IOException { + s.writeObject(ObjectUtil.serialize(map)); + s.writeObject(ObjectUtil.serialize(defaultValue)); + } - private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { - map = ObjectUtil.deserialize((byte[]) s.readObject()); - defaultValue = ObjectUtil.deserialize((byte[]) s.readObject()); - } + private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { + map = ObjectUtil.deserialize((byte[]) s.readObject()); + defaultValue = ObjectUtil.deserialize((byte[]) s.readObject()); + } - @Override - protected Object clone() throws CloneNotSupportedException { - return super.clone(); - } + @Override + protected Object clone() throws CloneNotSupportedException { + return super.clone(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/math/Money.java b/hutool-core/src/main/java/cn/hutool/core/math/Money.java index 69d209e29..7845181c9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/math/Money.java +++ b/hutool-core/src/main/java/cn/hutool/core/math/Money.java @@ -77,7 +77,7 @@ public class Money implements Serializable, Comparable { /** * 币种。 */ - private Currency currency; + private final Currency currency; // 构造器 ==================================================== diff --git a/hutool-core/src/main/java/cn/hutool/core/swing/clipboard/ClipboardMonitor.java b/hutool-core/src/main/java/cn/hutool/core/swing/clipboard/ClipboardMonitor.java index b24d899b6..ee1f4d942 100644 --- a/hutool-core/src/main/java/cn/hutool/core/swing/clipboard/ClipboardMonitor.java +++ b/hutool-core/src/main/java/cn/hutool/core/swing/clipboard/ClipboardMonitor.java @@ -1,5 +1,8 @@ package cn.hutool.core.swing.clipboard; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.ObjectUtil; + import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.ClipboardOwner; import java.awt.datatransfer.Transferable; @@ -7,9 +10,6 @@ import java.io.Closeable; import java.util.LinkedHashSet; import java.util.Set; -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.ObjectUtil; - /** * 剪贴板监听 * @@ -189,6 +189,7 @@ public enum ClipboardMonitor implements ClipboardOwner, Runnable, Closeable { for (int i = 0; i < this.tryCount; i++) { if (this.delay > 0 && i > 0) { // 第一次获取不等待,只有从第二次获取时才开始等待 + //noinspection BusyWait Thread.sleep(this.delay); } @@ -201,7 +202,7 @@ public enum ClipboardMonitor implements ClipboardOwner, Runnable, Closeable { return newContents; } } - return newContents; + return null; } // ------------------------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/swing/clipboard/ImageSelection.java b/hutool-core/src/main/java/cn/hutool/core/swing/clipboard/ImageSelection.java index 21380df85..ea656307a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/swing/clipboard/ImageSelection.java +++ b/hutool-core/src/main/java/cn/hutool/core/swing/clipboard/ImageSelection.java @@ -4,7 +4,6 @@ import java.awt.Image; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; -import java.io.IOException; import java.io.Serializable; /** @@ -17,7 +16,7 @@ import java.io.Serializable; public class ImageSelection implements Transferable, Serializable { private static final long serialVersionUID = 1L; - private Image image; + private final Image image; /** * 构造 @@ -56,7 +55,7 @@ public class ImageSelection implements Transferable, Serializable { * @return 转换后的对象 */ @Override - public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException { if (false == DataFlavor.imageFlavor.equals(flavor)) { throw new UnsupportedFlavorException(flavor); } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/Simhash.java b/hutool-core/src/main/java/cn/hutool/core/text/Simhash.java index 74aa9fed4..a1fc9f191 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/Simhash.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/Simhash.java @@ -1,16 +1,14 @@ package cn.hutool.core.text; +import cn.hutool.core.lang.hash.MurmurHash; + import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; - -import cn.hutool.core.lang.hash.MurmurHash; +import java.util.concurrent.locks.StampedLock; /** *

    @@ -35,8 +33,8 @@ public class Simhash { private final int hammingThresh; /** 按照分段存储simhash,查找更快速 */ - private List>> storage; - private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final List>> storage; + private final StampedLock lock = new StampedLock(); /** * 构造 @@ -57,7 +55,7 @@ public class Simhash { this.hammingThresh = hammingThresh; this.storage = new ArrayList<>(fracCount); for (int i = 0; i < fracCount; i++) { - storage.add(new HashMap>()); + storage.add(new HashMap<>()); } } @@ -104,8 +102,7 @@ public class Simhash { String frac; Map> fracMap; - final ReadLock readLock = this.lock.readLock(); - readLock.lock(); + final long stamp = this.lock.readLock(); try { for (int i = 0; i < fracCount; i++) { frac = fracList.get(i); @@ -120,7 +117,7 @@ public class Simhash { } } } finally { - readLock.unlock(); + this.lock.unlockRead(stamp); } return false; } @@ -137,8 +134,7 @@ public class Simhash { String frac; Map> fracMap; - final WriteLock writeLock = this.lock.writeLock(); - writeLock.lock(); + final long stamp = this.lock.writeLock(); try { for (int i = 0; i < fracCount; i++) { frac = lFrac.get(i); @@ -152,7 +148,7 @@ public class Simhash { } } } finally { - writeLock.unlock(); + this.lock.unlockWrite(stamp); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrSpliter.java b/hutool-core/src/main/java/cn/hutool/core/text/StrSpliter.java index cccafb1fc..35aa70a8a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrSpliter.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrSpliter.java @@ -1,15 +1,15 @@ package cn.hutool.core.text; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import cn.hutool.core.lang.PatternPool; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * 字符串切分器 * @author Looly @@ -149,10 +149,10 @@ public class StrSpliter { */ public static List split(String str, char separator, int limit, boolean isTrim, boolean ignoreEmpty, boolean ignoreCase){ if(StrUtil.isEmpty(str)){ - return new ArrayList(0); + return new ArrayList<>(0); } if(limit == 1){ - return addToList(new ArrayList(1), str, isTrim, ignoreEmpty); + return addToList(new ArrayList<>(1), str, isTrim, ignoreEmpty); } final ArrayList list = new ArrayList<>(limit > 0 ? limit : 16); @@ -288,10 +288,10 @@ public class StrSpliter { */ public static List split(String str, String separator, int limit, boolean isTrim, boolean ignoreEmpty, boolean ignoreCase){ if(StrUtil.isEmpty(str)){ - return new ArrayList(0); + return new ArrayList<>(0); } if(limit == 1){ - return addToList(new ArrayList(1), str, isTrim, ignoreEmpty); + return addToList(new ArrayList<>(1), str, isTrim, ignoreEmpty); } if(StrUtil.isEmpty(separator)){//分隔符为空时按照空白符切分 @@ -350,10 +350,10 @@ public class StrSpliter { */ public static List split(String str, int limit){ if(StrUtil.isEmpty(str)){ - return new ArrayList(0); + return new ArrayList<>(0); } if(limit == 1){ - return addToList(new ArrayList(1), str, true, true); + return addToList(new ArrayList<>(1), str, true, true); } final ArrayList list = new ArrayList<>(); @@ -413,10 +413,10 @@ public class StrSpliter { */ public static List split(String str, Pattern separatorPattern, int limit, boolean isTrim, boolean ignoreEmpty){ if(StrUtil.isEmpty(str)){ - return new ArrayList(0); + return new ArrayList<>(0); } if(limit == 1){ - return addToList(new ArrayList(1), str, isTrim, ignoreEmpty); + return addToList(new ArrayList<>(1), str, isTrim, ignoreEmpty); } if(null == separatorPattern){//分隔符为空时按照空白符切分 @@ -507,7 +507,7 @@ public class StrSpliter { * @return Array */ private static String[] toArray(List list){ - return list.toArray(new String[list.size()]); + return list.toArray(new String[0]); } //---------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/UnicodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/UnicodeUtil.java index e2cb31f8b..7c9cd6d46 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/UnicodeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/UnicodeUtil.java @@ -43,7 +43,7 @@ public class UnicodeUtil { pos = i + 2; } } else { - pos = i;//非Unicode符,结束 + //非Unicode符,结束 break; } } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java index 09ed65a08..0ccd01a16 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java @@ -1,5 +1,12 @@ package cn.hutool.core.text.csv; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.text.StrBuilder; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; + import java.io.Closeable; import java.io.IOException; import java.io.Reader; @@ -11,13 +18,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.text.StrBuilder; -import cn.hutool.core.util.CharUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; - /** * CSV行解析器,参考:FastCSV * @@ -96,12 +96,8 @@ public final class CsvParser implements Closeable, Serializable { while (false == finished) { startingLineNo = ++lineNo; currentFields = readLine(); - if(null == currentFields) { - break; - } fieldCount = currentFields.size(); - // 末尾 - if (fieldCount == 0) { + if(fieldCount < 1){ break; } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/escape/NumericEntityUnescaper.java b/hutool-core/src/main/java/cn/hutool/core/text/escape/NumericEntityUnescaper.java index fa5e82780..b9d274241 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/escape/NumericEntityUnescaper.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/escape/NumericEntityUnescaper.java @@ -48,7 +48,7 @@ public class NumericEntityUnescaper extends StrReplacer { return 0; } out.append((char)entityValue); - return 2 + end - start + (isHex ? 1 : 0) + (isSemiNext ? 1 : 0); + return 2 + end - start + (isHex ? 1 : 0) + 1; } } return 0; diff --git a/hutool-core/src/main/java/cn/hutool/core/text/replacer/ReplacerChain.java b/hutool-core/src/main/java/cn/hutool/core/text/replacer/ReplacerChain.java index 9cac7ee26..4729948ac 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/replacer/ReplacerChain.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/replacer/ReplacerChain.java @@ -1,12 +1,12 @@ package cn.hutool.core.text.replacer; +import cn.hutool.core.lang.Chain; +import cn.hutool.core.text.StrBuilder; + import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import cn.hutool.core.lang.Chain; -import cn.hutool.core.text.StrBuilder; - /** * 字符串替换链,用于组合多个字符串替换逻辑 * @@ -16,7 +16,7 @@ import cn.hutool.core.text.StrBuilder; public class ReplacerChain extends StrReplacer implements Chain { private static final long serialVersionUID = 1L; - private List replacers = new LinkedList<>(); + private final List replacers = new LinkedList<>(); /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/ConcurrencyTester.java b/hutool-core/src/main/java/cn/hutool/core/thread/ConcurrencyTester.java index 1673054be..20b42f4ee 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/ConcurrencyTester.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/ConcurrencyTester.java @@ -17,8 +17,8 @@ import cn.hutool.core.date.TimeInterval; * @author kwer */ public class ConcurrencyTester { - private SyncFinisher sf; - private TimeInterval timeInterval; + private final SyncFinisher sf; + private final TimeInterval timeInterval; private long interval; public ConcurrencyTester(int threadSize) { diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/RejectPolicy.java b/hutool-core/src/main/java/cn/hutool/core/thread/RejectPolicy.java index 0babcb018..c95545985 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/RejectPolicy.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/RejectPolicy.java @@ -23,7 +23,7 @@ public enum RejectPolicy { /** 由主线程来直接执行 */ CALLER_RUNS(new ThreadPoolExecutor.CallerRunsPolicy()); - private RejectedExecutionHandler value; + private final RejectedExecutionHandler value; RejectPolicy(RejectedExecutionHandler handler) { this.value = handler; diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/SemaphoreRunnable.java b/hutool-core/src/main/java/cn/hutool/core/thread/SemaphoreRunnable.java index 9cdcfc304..22cac27b0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/SemaphoreRunnable.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/SemaphoreRunnable.java @@ -16,9 +16,9 @@ import java.util.concurrent.Semaphore; public class SemaphoreRunnable implements Runnable { /** 实际执行的逻辑 */ - private Runnable runnable; + private final Runnable runnable; /** 信号量 */ - private Semaphore semaphore; + private final Semaphore semaphore; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/SyncFinisher.java b/hutool-core/src/main/java/cn/hutool/core/thread/SyncFinisher.java index e4127f48f..8555a7dbc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/SyncFinisher.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/SyncFinisher.java @@ -1,13 +1,13 @@ package cn.hutool.core.thread; +import cn.hutool.core.exceptions.NotInitedException; +import cn.hutool.core.exceptions.UtilException; + import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; -import cn.hutool.core.exceptions.NotInitedException; -import cn.hutool.core.exceptions.UtilException; - /** * 线程同步结束器
    * 在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。 @@ -27,13 +27,13 @@ import cn.hutool.core.exceptions.UtilException; */ public class SyncFinisher { - private Set workers; - private int threadSize; - private ExecutorService executorService; + private final Set workers; + private final int threadSize; + private final ExecutorService executorService; private boolean isBeginAtSameTime; /** 启动同步器,用于保证所有worker线程同时开始 */ - private CountDownLatch beginLatch; + private final CountDownLatch beginLatch; /** 结束同步器,用于等待所有worker线程同时结束 */ private CountDownLatch endLatch; diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java b/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java index 051134330..12191a31f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/lock/LockUtil.java @@ -11,7 +11,7 @@ import java.util.concurrent.locks.StampedLock; */ public class LockUtil { - private static NoLock NO_LOCK = new NoLock(); + private static final NoLock NO_LOCK = new NoLock(); /** * 创建{@link StampedLock}锁 diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java b/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java index 912d21359..7f67b73cc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java @@ -38,7 +38,7 @@ public class NoLock implements Lock{ @SuppressWarnings("NullableProblems") @Override public Condition newCondition() { - return null; + throw new UnsupportedOperationException("NoLock`s newCondition method is unsupported"); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java index b7bca158f..bdb76fb32 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java @@ -7,23 +7,24 @@ import cn.hutool.core.lang.UUID; /** * ID生成器工具类,此工具类中主要封装: - * + * *

      * 1. 唯一性ID生成器:UUID、ObjectId(MongoDB)、Snowflake
      * 
    - * + * *

    * ID相关文章见:http://calvin1978.blogcn.com/articles/uuid.html - * + * * @author looly * @since 4.1.13 */ public class IdUtil { // ------------------------------------------------------------------- UUID + /** * 获取随机UUID - * + * * @return 随机UUID */ public static String randomUUID() { @@ -32,7 +33,7 @@ public class IdUtil { /** * 简化的UUID,去掉了横线 - * + * * @return 简化的UUID,去掉了横线 */ public static String simpleUUID() { @@ -41,7 +42,7 @@ public class IdUtil { /** * 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID - * + * * @return 随机UUID * @since 4.1.19 */ @@ -51,7 +52,7 @@ public class IdUtil { /** * 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID - * + * * @return 简化的UUID,去掉了横线 * @since 4.1.19 */ @@ -62,16 +63,16 @@ public class IdUtil { /** * 创建MongoDB ID生成策略实现
    * ObjectId由以下几部分组成: - * + * *

     	 * 1. Time 时间戳。
     	 * 2. Machine 所在主机的唯一标识符,一般是机器主机名的散列值。
     	 * 3. PID 进程ID。确保同一机器中不冲突
     	 * 4. INC 自增计数器。确保同一秒内产生objectId的唯一性。
     	 * 
    - * + *

    * 参考:http://blog.csdn.net/qxc1281/article/details/54021882 - * + * * @return ObjectId */ public static String objectId() { @@ -79,24 +80,27 @@ public class IdUtil { } /** - * 创建Twitter的Snowflake 算法生成器
    + * 创建Twitter的Snowflake 算法生成器。 + *

    + * 特别注意:此方法调用后会创建独立的{@link Snowflake}对象,每个独立的对象ID不互斥,会导致ID重复,请自行保证单例! + *

    * 分布式系统中,有一些需要使用全局唯一ID的场景,有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。 - * + * *

    * snowflake的结构如下(每部分用-分开):
    - * + * *

     	 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
     	 * 
    - * + *

    * 第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年)
    * 然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点)
    * 最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号) - * + * *

    * 参考:http://www.cnblogs.com/relucent/p/4955340.html - * - * @param workerId 终端ID + * + * @param workerId 终端ID * @param datacenterId 数据中心ID * @return {@link Snowflake} */ @@ -107,22 +111,22 @@ public class IdUtil { /** * 获取单例的Twitter的Snowflake 算法生成器对象
    * 分布式系统中,有一些需要使用全局唯一ID的场景,有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。 - * + * *

    * snowflake的结构如下(每部分用-分开):
    - * + * *

     	 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
     	 * 
    - * + *

    * 第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年)
    * 然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点)
    * 最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号) - * + * *

    * 参考:http://www.cnblogs.com/relucent/p/4955340.html - * - * @param workerId 终端ID + * + * @param workerId 终端ID * @param datacenterId 数据中心ID * @return {@link Snowflake} * @since 4.5.9 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java index e4fd48a7e..c29f6ff49 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java @@ -41,91 +41,91 @@ public class IdcardUtil { /** * 省市代码表 */ - private static Map cityCodes = new HashMap<>(); + private static final Map CITY_CODES = new HashMap<>(); /** * 台湾身份首字母对应数字 */ - private static Map twFirstCode = new HashMap<>(); + private static final Map TW_FIRST_CODE = new HashMap<>(); /** * 香港身份首字母对应数字 */ - private static Map hkFirstCode = new HashMap<>(); + private static final Map HK_FIRST_CODE = new HashMap<>(); static { - cityCodes.put("11", "北京"); - cityCodes.put("12", "天津"); - cityCodes.put("13", "河北"); - cityCodes.put("14", "山西"); - cityCodes.put("15", "内蒙古"); - cityCodes.put("21", "辽宁"); - cityCodes.put("22", "吉林"); - cityCodes.put("23", "黑龙江"); - cityCodes.put("31", "上海"); - cityCodes.put("32", "江苏"); - cityCodes.put("33", "浙江"); - cityCodes.put("34", "安徽"); - cityCodes.put("35", "福建"); - cityCodes.put("36", "江西"); - cityCodes.put("37", "山东"); - cityCodes.put("41", "河南"); - cityCodes.put("42", "湖北"); - cityCodes.put("43", "湖南"); - cityCodes.put("44", "广东"); - cityCodes.put("45", "广西"); - cityCodes.put("46", "海南"); - cityCodes.put("50", "重庆"); - cityCodes.put("51", "四川"); - cityCodes.put("52", "贵州"); - cityCodes.put("53", "云南"); - cityCodes.put("54", "西藏"); - cityCodes.put("61", "陕西"); - cityCodes.put("62", "甘肃"); - cityCodes.put("63", "青海"); - cityCodes.put("64", "宁夏"); - cityCodes.put("65", "新疆"); - cityCodes.put("71", "台湾"); - cityCodes.put("81", "香港"); - cityCodes.put("82", "澳门"); - cityCodes.put("91", "国外"); + CITY_CODES.put("11", "北京"); + CITY_CODES.put("12", "天津"); + CITY_CODES.put("13", "河北"); + CITY_CODES.put("14", "山西"); + CITY_CODES.put("15", "内蒙古"); + CITY_CODES.put("21", "辽宁"); + CITY_CODES.put("22", "吉林"); + CITY_CODES.put("23", "黑龙江"); + CITY_CODES.put("31", "上海"); + CITY_CODES.put("32", "江苏"); + CITY_CODES.put("33", "浙江"); + CITY_CODES.put("34", "安徽"); + CITY_CODES.put("35", "福建"); + CITY_CODES.put("36", "江西"); + CITY_CODES.put("37", "山东"); + CITY_CODES.put("41", "河南"); + CITY_CODES.put("42", "湖北"); + CITY_CODES.put("43", "湖南"); + CITY_CODES.put("44", "广东"); + CITY_CODES.put("45", "广西"); + CITY_CODES.put("46", "海南"); + CITY_CODES.put("50", "重庆"); + CITY_CODES.put("51", "四川"); + CITY_CODES.put("52", "贵州"); + CITY_CODES.put("53", "云南"); + CITY_CODES.put("54", "西藏"); + CITY_CODES.put("61", "陕西"); + CITY_CODES.put("62", "甘肃"); + CITY_CODES.put("63", "青海"); + CITY_CODES.put("64", "宁夏"); + CITY_CODES.put("65", "新疆"); + CITY_CODES.put("71", "台湾"); + CITY_CODES.put("81", "香港"); + CITY_CODES.put("82", "澳门"); + CITY_CODES.put("91", "国外"); - twFirstCode.put("A", 10); - twFirstCode.put("B", 11); - twFirstCode.put("C", 12); - twFirstCode.put("D", 13); - twFirstCode.put("E", 14); - twFirstCode.put("F", 15); - twFirstCode.put("G", 16); - twFirstCode.put("H", 17); - twFirstCode.put("J", 18); - twFirstCode.put("K", 19); - twFirstCode.put("L", 20); - twFirstCode.put("M", 21); - twFirstCode.put("N", 22); - twFirstCode.put("P", 23); - twFirstCode.put("Q", 24); - twFirstCode.put("R", 25); - twFirstCode.put("S", 26); - twFirstCode.put("T", 27); - twFirstCode.put("U", 28); - twFirstCode.put("V", 29); - twFirstCode.put("X", 30); - twFirstCode.put("Y", 31); - twFirstCode.put("W", 32); - twFirstCode.put("Z", 33); - twFirstCode.put("I", 34); - twFirstCode.put("O", 35); + TW_FIRST_CODE.put("A", 10); + TW_FIRST_CODE.put("B", 11); + TW_FIRST_CODE.put("C", 12); + TW_FIRST_CODE.put("D", 13); + TW_FIRST_CODE.put("E", 14); + TW_FIRST_CODE.put("F", 15); + TW_FIRST_CODE.put("G", 16); + TW_FIRST_CODE.put("H", 17); + TW_FIRST_CODE.put("J", 18); + TW_FIRST_CODE.put("K", 19); + TW_FIRST_CODE.put("L", 20); + TW_FIRST_CODE.put("M", 21); + TW_FIRST_CODE.put("N", 22); + TW_FIRST_CODE.put("P", 23); + TW_FIRST_CODE.put("Q", 24); + TW_FIRST_CODE.put("R", 25); + TW_FIRST_CODE.put("S", 26); + TW_FIRST_CODE.put("T", 27); + TW_FIRST_CODE.put("U", 28); + TW_FIRST_CODE.put("V", 29); + TW_FIRST_CODE.put("X", 30); + TW_FIRST_CODE.put("Y", 31); + TW_FIRST_CODE.put("W", 32); + TW_FIRST_CODE.put("Z", 33); + TW_FIRST_CODE.put("I", 34); + TW_FIRST_CODE.put("O", 35); //来自http://shenfenzheng.bajiu.cn/?rid=40 - hkFirstCode.put("A", 1);// 持证人拥有香港居留权 - hkFirstCode.put("B", 2);// 持证人所报称的出生日期或地点自首次登记以后,曾作出更改 - hkFirstCode.put("C", 3);// 持证人登记领证时在香港的居留受到入境事务处处长的限制 - hkFirstCode.put("N", 14);// 持证人所报的姓名自首次登记以后,曾作出更改 - hkFirstCode.put("O", 15);// 持证人报称在香港、澳门及中国以外其他地区或国家出生 - hkFirstCode.put("R", 18);// 持证人拥有香港入境权 - hkFirstCode.put("U", 21);// 持证人登记领证时在香港的居留不受入境事务处处长的限制 - hkFirstCode.put("W", 23);// 持证人报称在澳门地区出生 - hkFirstCode.put("X", 24);// 持证人报称在中国大陆出生 - hkFirstCode.put("Z", 26);// 持证人报称在香港出生 + HK_FIRST_CODE.put("A", 1);// 持证人拥有香港居留权 + HK_FIRST_CODE.put("B", 2);// 持证人所报称的出生日期或地点自首次登记以后,曾作出更改 + HK_FIRST_CODE.put("C", 3);// 持证人登记领证时在香港的居留受到入境事务处处长的限制 + HK_FIRST_CODE.put("N", 14);// 持证人所报的姓名自首次登记以后,曾作出更改 + HK_FIRST_CODE.put("O", 15);// 持证人报称在香港、澳门及中国以外其他地区或国家出生 + HK_FIRST_CODE.put("R", 18);// 持证人拥有香港入境权 + HK_FIRST_CODE.put("U", 21);// 持证人登记领证时在香港的居留不受入境事务处处长的限制 + HK_FIRST_CODE.put("W", 23);// 持证人报称在澳门地区出生 + HK_FIRST_CODE.put("X", 24);// 持证人报称在中国大陆出生 + HK_FIRST_CODE.put("Z", 26);// 持证人报称在香港出生 } /** @@ -248,7 +248,7 @@ public class IdcardUtil { if (ReUtil.isMatch(PatternPool.NUMBERS, idCard)) { // 省份 String proCode = idCard.substring(0, 2); - if (null == cityCodes.get(proCode)) { + if (null == CITY_CODES.get(proCode)) { return false; } @@ -314,7 +314,7 @@ public class IdcardUtil { return false; } String start = idCard.substring(0, 1); - Integer iStart = twFirstCode.get(start); + Integer iStart = TW_FIRST_CODE.get(start); if (null == iStart) { return false; } @@ -352,7 +352,7 @@ public class IdcardUtil { sum = 522 + (Character.toUpperCase(card.charAt(0)) - 55) * 8; } String start = idCard.substring(0, 1); - Integer iStart = hkFirstCode.get(start); + Integer iStart = HK_FIRST_CODE.get(start); if (null == iStart) { return false; } @@ -512,7 +512,7 @@ public class IdcardUtil { int len = idCard.length(); if (len == CHINA_ID_MIN_LENGTH || len == CHINA_ID_MAX_LENGTH) { String sProvinNum = idCard.substring(0, 2); - return cityCodes.get(sProvinNum); + return CITY_CODES.get(sProvinNum); } return null; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ModifierUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ModifierUtil.java index 26c03a29c..80c81addf 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ModifierUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ModifierUtil.java @@ -45,7 +45,7 @@ public class ModifierUtil { STRICT(Modifier.STRICT); /** 修饰符枚举对应的int修饰符值 */ - private int value; + private final int value; /** * 构造 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java index 69085986d..4ae7360ce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java @@ -569,7 +569,7 @@ public class ObjectUtil { } /** - * 是否存都为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素 + * 是否全都为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素 * * @param objs 被检查的对象,一个或者多个 * @return 是否都为空 @@ -579,7 +579,7 @@ public class ObjectUtil { } /** - * 是否存都不为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素 + * 是否全都不为{@code null}或空对象,通过{@link ObjectUtil#isEmpty(Object)} 判断元素 * * @param objs 被检查的对象,一个或者多个 * @return 是否都不为空 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java index a612534a1..f4d4f4c16 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RuntimeUtil.java @@ -1,5 +1,8 @@ package cn.hutool.core.util; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -7,12 +10,9 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; - /** * 系统运行时工具类,用于执行系统命令的工具 - * + * * @author Looly * @since 3.1.1 */ @@ -20,7 +20,7 @@ public class RuntimeUtil { /** * 执行系统命令,使用系统默认编码 - * + * * @param cmds 命令列表,每个元素代表一条命令 * @return 执行结果 * @throws IORuntimeException IO异常 @@ -31,9 +31,9 @@ public class RuntimeUtil { /** * 执行系统命令,使用系统默认编码 - * + * * @param charset 编码 - * @param cmds 命令列表,每个元素代表一条命令 + * @param cmds 命令列表,每个元素代表一条命令 * @return 执行结果 * @throws IORuntimeException IO异常 * @since 3.1.2 @@ -44,7 +44,7 @@ public class RuntimeUtil { /** * 执行系统命令,使用系统默认编码 - * + * * @param cmds 命令列表,每个元素代表一条命令 * @return 执行结果,按行区分 * @throws IORuntimeException IO异常 @@ -55,9 +55,9 @@ public class RuntimeUtil { /** * 执行系统命令,使用系统默认编码 - * + * * @param charset 编码 - * @param cmds 命令列表,每个元素代表一条命令 + * @param cmds 命令列表,每个元素代表一条命令 * @return 执行结果,按行区分 * @throws IORuntimeException IO异常 * @since 3.1.2 @@ -69,7 +69,7 @@ public class RuntimeUtil { /** * 执行命令
    * 命令带参数时参数可作为其中一个参数,也可以将命令和参数组合为一个字符串传入 - * + * * @param cmds 命令 * @return {@link Process} */ @@ -99,7 +99,7 @@ public class RuntimeUtil { /** * 执行命令
    * 命令带参数时参数可作为其中一个参数,也可以将命令和参数组合为一个字符串传入 - * + * * @param envp 环境变量参数,传入形式为key=value,null表示继承系统环境变量 * @param cmds 命令 * @return {@link Process} @@ -112,9 +112,9 @@ public class RuntimeUtil { /** * 执行命令
    * 命令带参数时参数可作为其中一个参数,也可以将命令和参数组合为一个字符串传入 - * + * * @param envp 环境变量参数,传入形式为key=value,null表示继承系统环境变量 - * @param dir 执行命令所在目录(用于相对路径命令执行),null表示使用当前进程执行的目录 + * @param dir 执行命令所在目录(用于相对路径命令执行),null表示使用当前进程执行的目录 * @param cmds 命令 * @return {@link Process} * @since 4.1.6 @@ -140,9 +140,10 @@ public class RuntimeUtil { } // -------------------------------------------------------------------------------------------------- result + /** * 获取命令执行结果,使用系统默认编码,获取后销毁进程 - * + * * @param process {@link Process} 进程 * @return 命令执行结果列表 */ @@ -152,7 +153,7 @@ public class RuntimeUtil { /** * 获取命令执行结果,使用系统默认编码,获取后销毁进程 - * + * * @param process {@link Process} 进程 * @param charset 编码 * @return 命令执行结果列表 @@ -162,7 +163,7 @@ public class RuntimeUtil { InputStream in = null; try { in = process.getInputStream(); - return IoUtil.readLines(in, charset, new ArrayList()); + return IoUtil.readLines(in, charset, new ArrayList<>()); } finally { IoUtil.close(in); destroy(process); @@ -171,7 +172,7 @@ public class RuntimeUtil { /** * 获取命令执行结果,使用系统默认编码,,获取后销毁进程 - * + * * @param process {@link Process} 进程 * @return 命令执行结果列表 * @since 3.1.2 @@ -182,7 +183,7 @@ public class RuntimeUtil { /** * 获取命令执行结果,获取后销毁进程 - * + * * @param process {@link Process} 进程 * @param charset 编码 * @return 命令执行结果列表 @@ -201,7 +202,7 @@ public class RuntimeUtil { /** * 获取命令执行异常结果,使用系统默认编码,,获取后销毁进程 - * + * * @param process {@link Process} 进程 * @return 命令执行结果列表 * @since 4.1.21 @@ -212,7 +213,7 @@ public class RuntimeUtil { /** * 获取命令执行异常结果,获取后销毁进程 - * + * * @param process {@link Process} 进程 * @param charset 编码 * @return 命令执行结果列表 @@ -231,7 +232,7 @@ public class RuntimeUtil { /** * 销毁进程 - * + * * @param process 进程 * @since 3.1.2 */ @@ -243,11 +244,61 @@ public class RuntimeUtil { /** * 增加一个JVM关闭后的钩子,用于在JVM关闭时执行某些操作 - * + * * @param hook 钩子 * @since 4.0.5 */ public static void addShutdownHook(Runnable hook) { Runtime.getRuntime().addShutdownHook((hook instanceof Thread) ? (Thread) hook : new Thread(hook)); } + + /** + * 获得JVM可用的处理器数量(一般为CPU核心数) + * + * @return 可用的处理器数量 + * @since 5.3.0 + */ + public static int getProcessorCount() { + return Runtime.getRuntime().availableProcessors(); + } + + /** + * 获得JVM中剩余的内存数,单位byte + * + * @return JVM中剩余的内存数,单位byte + * @since 5.3.0 + */ + public static long getFreeMemory() { + return Runtime.getRuntime().freeMemory(); + } + + /** + * 获得JVM已经从系统中获取到的总共的内存数,单位byte + * + * @return JVM中剩余的内存数,单位byte + * @since 5.3.0 + */ + public static long getTotalMemory() { + return Runtime.getRuntime().totalMemory(); + } + + /** + * 获得JVM中可以从系统中获取的最大的内存数,单位byte,以-Xmx参数为准 + * + * @return JVM中剩余的内存数,单位byte + * @since 5.3.0 + */ + public static long getMaxMemory() { + return Runtime.getRuntime().maxMemory(); + } + + /** + * 获得JVM最大可用内存,计算方法为:
    + * 最大内存-总内存+剩余内存 + * + * @return 最大可用内存 + */ + public final long getUsableMemory() { + return getMaxMemory() - getTotalMemory() + getFreeMemory(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java index 85cb22443..a4b3eb549 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java @@ -59,11 +59,7 @@ public class TypeUtil { if (null == field) { return null; } - Type type = field.getGenericType(); - if (null == type) { - type = field.getType(); - } - return type; + return field.getGenericType(); } /** diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToCollectionTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToCollectionTest.java index 778a3fbff..eb735c110 100644 --- a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToCollectionTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToCollectionTest.java @@ -1,5 +1,10 @@ package cn.hutool.core.convert; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; @@ -7,12 +12,6 @@ import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; -import org.junit.Assert; -import org.junit.Test; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.TypeReference; - /** * 转换为集合测试 * @@ -107,7 +106,7 @@ public class ConvertToCollectionTest { public void numberToListTest() { Integer i = 1; ArrayList list = Convert.convert(ArrayList.class, i); - Assert.assertTrue(i == list.get(0)); + Assert.assertSame(i, list.get(0)); BigDecimal b = BigDecimal.ONE; ArrayList list2 = Convert.convert(ArrayList.class, b); diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateTimeTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateTimeTest.java index f5f10b30d..5795b7897 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateTimeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateTimeTest.java @@ -67,12 +67,12 @@ public class DateTimeTest { // 默认情况下DateTime为可变对象 DateTime offsite = dateTime.offset(DateField.YEAR, 0); - Assert.assertTrue(offsite == dateTime); + Assert.assertSame(offsite, dateTime); // 设置为不可变对象后变动将返回新对象 dateTime.setMutable(false); offsite = dateTime.offset(DateField.YEAR, 0); - Assert.assertFalse(offsite == dateTime); + Assert.assertNotSame(offsite, dateTime); } @Test @@ -86,6 +86,7 @@ public class DateTimeTest { @Test public void monthTest() { + //noinspection ConstantConditions int month = DateUtil.parse("2017-07-01").month(); Assert.assertEquals(6, month); } @@ -93,6 +94,7 @@ public class DateTimeTest { @Test public void weekOfYearTest() { DateTime date = DateUtil.parse("2016-12-27"); + //noinspection ConstantConditions Assert.assertEquals(2016, date.year()); //跨年的周返回的总是1 Assert.assertEquals(1, date.weekOfYear()); diff --git a/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java index 7ab6944e0..132a28965 100644 --- a/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java @@ -1,5 +1,10 @@ package cn.hutool.core.img; +import cn.hutool.core.io.FileUtil; +import org.junit.Ignore; +import org.junit.Test; + +import javax.imageio.ImageIO; import java.awt.Color; import java.awt.Font; import java.awt.Image; @@ -7,14 +12,6 @@ import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.IOException; -import javax.imageio.ImageIO; - -import cn.hutool.core.lang.Console; -import org.junit.Ignore; -import org.junit.Test; - -import cn.hutool.core.io.FileUtil; - public class ImgUtilTest { @Test diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index b9e12e5ca..6a4c73264 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -1,12 +1,10 @@ package cn.hutool.core.lang; -import org.junit.Assert; -import org.junit.Test; - import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; -import cn.hutool.core.lang.Range; +import org.junit.Assert; +import org.junit.Test; /** * {@link Range} 单元测试 @@ -20,16 +18,11 @@ public class RangeTest { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-02"); - final Range range = new Range(start, end, new Range.Steper(){ - - @Override - public DateTime step(DateTime current, DateTime end, int index) { - if(current.isAfterOrEquals(end)) { - return null; - } - return current.offsetNew(DateField.DAY_OF_YEAR, 1); + final Range range = new Range<>(start, end, (current, end1, index) -> { + if (current.isAfterOrEquals(end1)) { + return null; } - + return current.offsetNew(DateField.DAY_OF_YEAR, 1); }); Assert.assertTrue(range.hasNext()); @@ -41,14 +34,7 @@ public class RangeTest { @Test public void intRangeTest() { - final Range range = new Range(1, 1, new Range.Steper(){ - - @Override - public Integer step(Integer current, Integer end, int index) { - return current >= end ? null : current +10; - } - - }); + final Range range = new Range<>(1, 1, (current, end, index) -> current >= end ? null : current + 10); Assert.assertTrue(range.hasNext()); Assert.assertEquals(Integer.valueOf(1), range.next()); diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/SingletonTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/SingletonTest.java new file mode 100644 index 000000000..52c2f564b --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/SingletonTest.java @@ -0,0 +1,30 @@ +package cn.hutool.core.lang; + +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.thread.ThreadUtil; +import lombok.Data; +import org.junit.Test; + +public class SingletonTest { + + @Test + public void getTest(){ + // 此测试中使用1000个线程获取单例对象,其间对象只被创建一次 + ThreadUtil.concurrencyTest(1000, ()-> Singleton.get(TestBean.class)); + } + + @Data + static class TestBean{ + private static volatile TestBean testSingleton; + + public TestBean(){ + if(null != testSingleton){ + throw new UtilException("单例测试中,对象被创建了两次!"); + } + testSingleton = this; + } + + private String name; + private String age; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/math/CombinationTest.java b/hutool-core/src/test/java/cn/hutool/core/math/CombinationTest.java index cd24b568f..7331635ba 100644 --- a/hutool-core/src/test/java/cn/hutool/core/math/CombinationTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/math/CombinationTest.java @@ -1,10 +1,10 @@ package cn.hutool.core.math; -import java.util.List; - import org.junit.Assert; import org.junit.Test; +import java.util.List; + /** * 组合单元测试 * @@ -49,6 +49,6 @@ public class CombinationTest { Assert.assertEquals(Combination.countAll(5), selectAll.size()); List list2 = combination.select(0); - Assert.assertTrue(1 == list2.size()); + Assert.assertEquals(1, list2.size()); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/swing/ClipboardMonitorTest.java b/hutool-core/src/test/java/cn/hutool/core/swing/ClipboardMonitorTest.java index 200534790..b19b1051b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/swing/ClipboardMonitorTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/swing/ClipboardMonitorTest.java @@ -1,42 +1,27 @@ package cn.hutool.core.swing; -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.Transferable; - +import cn.hutool.core.lang.Console; +import cn.hutool.core.swing.clipboard.ClipboardUtil; import org.junit.Ignore; import org.junit.Test; -import cn.hutool.core.lang.Console; -import cn.hutool.core.swing.clipboard.ClipboardListener; -import cn.hutool.core.swing.clipboard.ClipboardUtil; - public class ClipboardMonitorTest { @Test @Ignore public void monitorTest() { // 第一个监听 - ClipboardUtil.listen(new ClipboardListener() { - - @Override - public Transferable onChange(Clipboard clipboard, Transferable contents) { - Object object = ClipboardUtil.getStr(contents); - Console.log("1# {}", object); - return contents; - } - + ClipboardUtil.listen((clipboard, contents) -> { + Object object = ClipboardUtil.getStr(contents); + Console.log("1# {}", object); + return contents; }, false); // 第二个监听 - ClipboardUtil.listen(new ClipboardListener() { - - @Override - public Transferable onChange(Clipboard clipboard, Transferable contents) { - Object object = ClipboardUtil.getStr(contents); - Console.log("2# {}", object); - return contents; - } - + ClipboardUtil.listen((clipboard, contents) -> { + Object object = ClipboardUtil.getStr(contents); + Console.log("2# {}", object); + return contents; }); } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index bb93a6eb4..6ed052300 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -1,12 +1,11 @@ package cn.hutool.core.text.csv; -import java.io.StringReader; - -import org.junit.Assert; -import org.junit.Test; - import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.io.StringReader; public class CsvParserTest { @@ -15,6 +14,7 @@ public class CsvParserTest { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); CsvParser parser = new CsvParser(reader, null); CsvRow row = parser.nextRow(); + //noinspection ConstantConditions Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } @@ -24,6 +24,7 @@ public class CsvParserTest { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); CsvParser parser = new CsvParser(reader, null); CsvRow row = parser.nextRow(); + //noinspection ConstantConditions Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } @@ -33,6 +34,7 @@ public class CsvParserTest { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); CsvParser parser = new CsvParser(reader, null); CsvRow row = parser.nextRow(); + //noinspection ConstantConditions Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } @@ -42,6 +44,7 @@ public class CsvParserTest { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); CsvParser parser = new CsvParser(reader, null); CsvRow row = parser.nextRow(); + //noinspection ConstantConditions Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvUtilTest.java index cf04be8fd..43eea4a40 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvUtilTest.java @@ -24,9 +24,7 @@ public class CsvUtilTest { @Test public void readTest2() { CsvReader reader = CsvUtil.getReader(); - reader.read(FileUtil.getUtf8Reader("test.csv"), (csvRow)->{ - Assert.notEmpty(csvRow.getRawList()); - }); + reader.read(FileUtil.getUtf8Reader("test.csv"), (csvRow)-> Assert.notEmpty(csvRow.getRawList())); } @Test diff --git a/hutool-core/src/test/java/cn/hutool/core/thread/ConcurrencyTesterTest.java b/hutool-core/src/test/java/cn/hutool/core/thread/ConcurrencyTesterTest.java index 5999b97f6..fdd4279a5 100644 --- a/hutool-core/src/test/java/cn/hutool/core/thread/ConcurrencyTesterTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/thread/ConcurrencyTesterTest.java @@ -11,14 +11,10 @@ public class ConcurrencyTesterTest { @Test @Ignore public void concurrencyTesterTest() { - ConcurrencyTester tester = ThreadUtil.concurrencyTest(100, new Runnable() { - - @Override - public void run() { - long delay = RandomUtil.randomLong(100, 1000); - ThreadUtil.sleep(delay); - Console.log("{} test finished, delay: {}", Thread.currentThread().getName(), delay); - } + ConcurrencyTester tester = ThreadUtil.concurrencyTest(100, () -> { + long delay = RandomUtil.randomLong(100, 1000); + ThreadUtil.sleep(delay); + Console.log("{} test finished, delay: {}", Thread.currentThread().getName(), delay); }); Console.log(tester.getInterval()); } diff --git a/hutool-core/src/test/java/cn/hutool/core/thread/ThreadUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/thread/ThreadUtilTest.java index ef240d436..b5483e2b0 100644 --- a/hutool-core/src/test/java/cn/hutool/core/thread/ThreadUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/thread/ThreadUtilTest.java @@ -9,13 +9,7 @@ public class ThreadUtilTest { public void executeTest() { final boolean isValid = true; - ThreadUtil.execute(new Runnable() { - - @Override - public void run() { - Assert.assertTrue(isValid); - } - }); + ThreadUtil.execute(() -> Assert.assertTrue(isValid)); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/EnumUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/EnumUtilTest.java index 018aeaa55..d32e64c7e 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/EnumUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/EnumUtilTest.java @@ -1,12 +1,11 @@ package cn.hutool.core.util; -import java.util.List; -import java.util.Map; - +import cn.hutool.core.collection.CollUtil; import org.junit.Assert; import org.junit.Test; -import cn.hutool.core.collection.CollUtil; +import java.util.List; +import java.util.Map; /** * EnumUtil单元测试 @@ -49,17 +48,18 @@ public class EnumUtilTest { @Test public void getNameFieldMapTest() { Map enumMap = EnumUtil.getNameFieldMap(TestEnum.class, "type"); + assert enumMap != null; Assert.assertEquals("type1", enumMap.get("TEST1")); } public enum TestEnum{ TEST1("type1"), TEST2("type2"), TEST3("type3"); - + TestEnum(String type) { this.type = type; } - - private String type; + + private final String type; private String name; public String getType() { diff --git a/hutool-core/src/test/java/cn/hutool/core/util/IdUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/IdUtilTest.java index f52adca64..99b17bef2 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/IdUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/IdUtilTest.java @@ -1,13 +1,5 @@ package cn.hutool.core.util; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CountDownLatch; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.collection.ConcurrentHashSet; import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.TimeInterval; @@ -15,6 +7,13 @@ import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Console; import cn.hutool.core.lang.Snowflake; import cn.hutool.core.thread.ThreadUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; /** * {@link IdUtil} 单元测试 @@ -56,6 +55,7 @@ public class IdUtilTest { timer.restart(); for (int i = 0; i < 1000000; i++) { + //noinspection ResultOfMethodCallIgnored UUID.randomUUID().toString().replace("-", ""); } Console.log(timer.interval()); @@ -85,17 +85,13 @@ public class IdUtilTest { final int idCountPerThread = 10000; final CountDownLatch latch = new CountDownLatch(threadCount); for(int i =0; i < threadCount; i++) { - ThreadUtil.execute(new Runnable() { - - @Override - public void run() { - for(int i =0; i < idCountPerThread; i++) { - long id = snowflake.nextId(); - set.add(id); + ThreadUtil.execute(() -> { + for(int i1 = 0; i1 < idCountPerThread; i1++) { + long id = snowflake.nextId(); + set.add(id); // Console.log("Add new id: {}", id); - } - latch.countDown(); } + latch.countDown(); }); } @@ -118,17 +114,13 @@ public class IdUtilTest { final int idCountPerThread = 10000; final CountDownLatch latch = new CountDownLatch(threadCount); for(int i =0; i < threadCount; i++) { - ThreadUtil.execute(new Runnable() { - - @Override - public void run() { - for(int i =0; i < idCountPerThread; i++) { - long id = IdUtil.getSnowflake(1, 1).nextId(); - set.add(id); + ThreadUtil.execute(() -> { + for(int i1 = 0; i1 < idCountPerThread; i1++) { + long id = IdUtil.getSnowflake(1, 1).nextId(); + set.add(id); // Console.log("Add new id: {}", id); - } - latch.countDown(); } + latch.countDown(); }); } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/CronTimer.java b/hutool-cron/src/main/java/cn/hutool/cron/CronTimer.java index c9ef127d9..81a5ea27d 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/CronTimer.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/CronTimer.java @@ -19,13 +19,13 @@ public class CronTimer extends Thread implements Serializable { private static final Log log = LogFactory.get(); /** 定时单元:秒 */ - private long TIMER_UNIT_SECOND = DateUnit.SECOND.getMillis(); + private final long TIMER_UNIT_SECOND = DateUnit.SECOND.getMillis(); /** 定时单元:分 */ - private long TIMER_UNIT_MINUTE = DateUnit.MINUTE.getMillis(); + private final long TIMER_UNIT_MINUTE = DateUnit.MINUTE.getMillis(); /** 定时任务是否已经被强制关闭 */ - private boolean isStoped; - private Scheduler scheduler; + private boolean isStop; + private final Scheduler scheduler; /** * 构造 @@ -42,7 +42,7 @@ public class CronTimer extends Thread implements Serializable { long thisTime = System.currentTimeMillis(); long nextTime; long sleep; - while(false == isStoped){ + while(false == isStop){ //下一时间计算是按照上一个执行点开始时间计算的 nextTime = ((thisTime / timerUnit) + 1) * timerUnit; sleep = nextTime - System.currentTimeMillis(); @@ -62,7 +62,7 @@ public class CronTimer extends Thread implements Serializable { * 关闭定时器 */ synchronized public void stopTimer() { - this.isStoped = true; + this.isStop = true; ThreadUtil.interrupt(this, true); } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/Scheduler.java b/hutool-cron/src/main/java/cn/hutool/cron/Scheduler.java index a24928580..02959d57b 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/Scheduler.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/Scheduler.java @@ -55,7 +55,7 @@ import java.util.concurrent.locks.ReentrantLock; public class Scheduler implements Serializable { private static final long serialVersionUID = 1L; - private Lock lock = new ReentrantLock(); + private final Lock lock = new ReentrantLock(); /** 时区 */ private TimeZone timezone; diff --git a/hutool-cron/src/main/java/cn/hutool/cron/TaskExecutor.java b/hutool-cron/src/main/java/cn/hutool/cron/TaskExecutor.java index 0478abbe2..5365751f0 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/TaskExecutor.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/TaskExecutor.java @@ -11,8 +11,8 @@ import cn.hutool.cron.task.Task; */ public class TaskExecutor implements Runnable { - private Scheduler scheduler; - private Task task; + private final Scheduler scheduler; + private final Task task; /** * 获得任务对象 diff --git a/hutool-cron/src/main/java/cn/hutool/cron/TaskLauncher.java b/hutool-cron/src/main/java/cn/hutool/cron/TaskLauncher.java index 63c079e42..675ae8576 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/TaskLauncher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/TaskLauncher.java @@ -10,8 +10,8 @@ package cn.hutool.cron; */ public class TaskLauncher implements Runnable{ - private Scheduler scheduler; - private long millis; + private final Scheduler scheduler; + private final long millis; public TaskLauncher(Scheduler scheduler, long millis) { this.scheduler = scheduler; diff --git a/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java b/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java index d5e4b8aef..242c6e075 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/TaskTable.java @@ -1,5 +1,8 @@ package cn.hutool.cron; +import cn.hutool.cron.pattern.CronPattern; +import cn.hutool.cron.task.Task; + import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; @@ -9,9 +12,6 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import cn.hutool.cron.pattern.CronPattern; -import cn.hutool.cron.task.Task; - /** * 定时任务表
    * 任务表将ID、表达式、任务一一对应,定时任务执行过程中,会周期性检查定时任务表中的所有任务表达式匹配情况,从而执行其对应的任务
    @@ -22,14 +22,14 @@ import cn.hutool.cron.task.Task; public class TaskTable implements Serializable { private static final long serialVersionUID = 1L; - private ReadWriteLock lock = new ReentrantReadWriteLock(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); - private Scheduler scheduler; - private TimeZone timezone; + private final Scheduler scheduler; + private final TimeZone timezone; - private List ids = new ArrayList<>(); - private List patterns = new ArrayList<>(); - private List tasks = new ArrayList<>(); + private final List ids = new ArrayList<>(); + private final List patterns = new ArrayList<>(); + private final List tasks = new ArrayList<>(); private int size; /** diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java index a0e11407b..4fe7babcf 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java @@ -1,11 +1,5 @@ package cn.hutool.cron.pattern; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.TimeZone; - import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.cron.CronException; @@ -22,6 +16,12 @@ import cn.hutool.cron.pattern.parser.SecondValueParser; import cn.hutool.cron.pattern.parser.ValueParser; import cn.hutool.cron.pattern.parser.YearValueParser; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.TimeZone; + /** * 定时任务表达式
    * 表达式类似于Linux的crontab表达式,表达式使用空格分成5个部分,按顺序依次为: @@ -90,22 +90,22 @@ public class CronPattern { private static final ValueParser DAY_OF_WEEK_VALUE_PARSER = new DayOfWeekValueParser(); private static final ValueParser YEAR_VALUE_PARSER = new YearValueParser(); - private String pattern; + private final String pattern; /** 秒字段匹配列表 */ - private List secondMatchers = new ArrayList<>(); + private final List secondMatchers = new ArrayList<>(); /** 分字段匹配列表 */ - private List minuteMatchers = new ArrayList<>(); + private final List minuteMatchers = new ArrayList<>(); /** 时字段匹配列表 */ - private List hourMatchers = new ArrayList<>(); + private final List hourMatchers = new ArrayList<>(); /** 每月几号字段匹配列表 */ - private List dayOfMonthMatchers = new ArrayList<>(); + private final List dayOfMonthMatchers = new ArrayList<>(); /** 月字段匹配列表 */ - private List monthMatchers = new ArrayList<>(); + private final List monthMatchers = new ArrayList<>(); /** 星期字段匹配列表 */ - private List dayOfWeekMatchers = new ArrayList<>(); + private final List dayOfWeekMatchers = new ArrayList<>(); /** 年字段匹配列表 */ - private List yearMatchers = new ArrayList<>(); + private final List yearMatchers = new ArrayList<>(); /** 匹配器个数,取决于复合任务表达式中的单一表达式个数 */ private int matcherSize; diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/YearValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/YearValueMatcher.java index a22676b88..b1bd068f2 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/YearValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/YearValueMatcher.java @@ -10,7 +10,7 @@ import java.util.List; */ public class YearValueMatcher implements ValueMatcher{ - private List valueList; + private final List valueList; public YearValueMatcher(List intValueList) { this.valueList = intValueList; diff --git a/hutool-cron/src/main/java/cn/hutool/cron/task/InvokeTask.java b/hutool-cron/src/main/java/cn/hutool/cron/task/InvokeTask.java index b1285c719..3993aaf82 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/task/InvokeTask.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/task/InvokeTask.java @@ -1,7 +1,5 @@ package cn.hutool.cron.task; -import java.lang.reflect.Method; - import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.ClassUtil; @@ -9,6 +7,8 @@ import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.cron.CronException; +import java.lang.reflect.Method; + /** * 反射执行任务
    * 通过传入类名#方法名,通过反射执行相应的方法
    @@ -19,8 +19,8 @@ import cn.hutool.cron.CronException; */ public class InvokeTask implements Task{ - private Object obj; - private Method method; + private final Object obj; + private final Method method; /** * 构造 diff --git a/hutool-cron/src/main/java/cn/hutool/cron/task/RunnableTask.java b/hutool-cron/src/main/java/cn/hutool/cron/task/RunnableTask.java index 0f4fbe046..533166b05 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/task/RunnableTask.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/task/RunnableTask.java @@ -6,7 +6,7 @@ package cn.hutool.cron.task; * */ public class RunnableTask implements Task{ - private Runnable runnable; + private final Runnable runnable; public RunnableTask(Runnable runnable) { this.runnable = runnable; diff --git a/hutool-cron/src/test/java/cn/hutool/cron/demo/AddAndRemoveMainTest.java b/hutool-cron/src/test/java/cn/hutool/cron/demo/AddAndRemoveMainTest.java index a3fee6cbb..06575160f 100644 --- a/hutool-cron/src/test/java/cn/hutool/cron/demo/AddAndRemoveMainTest.java +++ b/hutool-cron/src/test/java/cn/hutool/cron/demo/AddAndRemoveMainTest.java @@ -10,23 +10,12 @@ public class AddAndRemoveMainTest { CronUtil.setMatchSecond(true); CronUtil.start(false); CronUtil.getScheduler().clear(); - String id = CronUtil.schedule("*/2 * * * * *", new Runnable() { - - @Override - public void run() { - Console.log("task running : 2s"); - } - }); + String id = CronUtil.schedule("*/2 * * * * *", (Runnable) () -> Console.log("task running : 2s")); ThreadUtil.sleep(3000); CronUtil.remove(id); Console.log("Task Removed"); - id = CronUtil.schedule("*/3 * * * * *", new Runnable() { - @Override - public void run() { - Console.log("New task add running : 3s"); - } - }); + CronUtil.schedule("*/3 * * * * *", (Runnable) () -> Console.log("New task add running : 3s")); Console.log("New Task added."); } } diff --git a/hutool-cron/src/test/java/cn/hutool/cron/demo/TestJob.java b/hutool-cron/src/test/java/cn/hutool/cron/demo/TestJob.java index a25fc5b43..810a52385 100644 --- a/hutool-cron/src/test/java/cn/hutool/cron/demo/TestJob.java +++ b/hutool-cron/src/test/java/cn/hutool/cron/demo/TestJob.java @@ -13,7 +13,7 @@ import cn.hutool.core.util.IdUtil; */ public class TestJob { - private String jobId = IdUtil.simpleUUID(); + private final String jobId = IdUtil.simpleUUID(); /** * 执行定时任务内容 diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SignAlgorithm.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SignAlgorithm.java index 44f4ba037..cde306de9 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SignAlgorithm.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SignAlgorithm.java @@ -33,7 +33,7 @@ public enum SignAlgorithm { SHA384withECDSA("SHA384withECDSA"), // SHA512withECDSA("SHA512withECDSA");// - private String value; + private final String value; /** * 构造 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 cb86a3313..7113bb467 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 @@ -1,9 +1,9 @@ package cn.hutool.crypto.digest; -import java.security.SecureRandom; - import cn.hutool.core.util.CharsetUtil; +import java.security.SecureRandom; + /** * BCrypt加密算法实现。由它加密的文件可在所有支持的操作系统和处理器上进行转移。它的口令必须是8至56个字符,并将在内部被转化为448位的密钥。 *

    @@ -177,7 +177,7 @@ public class BCrypt { * @return the decoded value of x */ private static byte char64(char x) { - if ((int) x < 0 || (int) x > index_64.length) + if ((int) x > index_64.length) return -1; return index_64[(int) x]; } @@ -191,6 +191,7 @@ public class BCrypt { * @return an array containing the decoded bytes * @throws IllegalArgumentException if maxolen is invalid */ + @SuppressWarnings("SameParameterValue") private static byte[] decodeBase64(String s, int maxolen) throws IllegalArgumentException { final StringBuilder rs = new StringBuilder(); int off = 0, slen = s.length(), olen = 0; @@ -285,8 +286,8 @@ public class BCrypt { * Initialise the Blowfish key schedule */ private void init_key() { - P = (int[]) P_orig.clone(); - S = (int[]) S_orig.clone(); + P = P_orig.clone(); + S = S_orig.clone(); } /** @@ -438,7 +439,7 @@ public class BCrypt { saltb = decodeBase64(real_salt, BCRYPT_SALT_LEN); bcrypt = new BCrypt(); - hashed = bcrypt.crypt(passwordb, saltb, rounds, (int[]) bf_crypt_ciphertext.clone()); + hashed = bcrypt.crypt(passwordb, saltb, rounds, bf_crypt_ciphertext.clone()); rs.append("$2"); if (minor >= 'a') diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigestAlgorithm.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigestAlgorithm.java index 5411371b1..b3560ee00 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigestAlgorithm.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigestAlgorithm.java @@ -14,7 +14,7 @@ public enum DigestAlgorithm { SHA384("SHA-384"), SHA512("SHA-512"); - private String value; + private final String value; /** * 构造 diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java index 7af08f6df..4daa0f7ab 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java @@ -1,13 +1,5 @@ package cn.hutool.crypto.digest; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.InputStream; -import java.io.Serializable; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.CharsetUtil; @@ -17,6 +9,13 @@ import cn.hutool.crypto.CryptoException; import cn.hutool.crypto.digest.mac.MacEngine; import cn.hutool.crypto.digest.mac.MacEngineFactory; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.io.Serializable; + /** * HMAC摘要算法
    * HMAC,全称为“Hash Message Authentication Code”,中文名“散列消息鉴别码”
    @@ -30,7 +29,7 @@ import cn.hutool.crypto.digest.mac.MacEngineFactory; public class HMac implements Serializable { private static final long serialVersionUID = 1L; - private MacEngine engine; + private final MacEngine engine; // ------------------------------------------------------------------------------------------- Constructor start /** diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HmacAlgorithm.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HmacAlgorithm.java index df53ebf65..08b361218 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HmacAlgorithm.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HmacAlgorithm.java @@ -15,7 +15,7 @@ public enum HmacAlgorithm { /** HmacSM3算法实现,需要BouncyCastle库支持 */ HmacSM3("HmacSM3"); - private String value; + private final String value; HmacAlgorithm(String value) { this.value = value; diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricAlgorithm.java b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricAlgorithm.java index c37f2e269..bedc93327 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricAlgorithm.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricAlgorithm.java @@ -22,7 +22,7 @@ public enum SymmetricAlgorithm { PBEWithSHA1AndDESede("PBEWithSHA1AndDESede"), PBEWithSHA1AndRC2_40("PBEWithSHA1AndRC2_40"); - private String value; + private final String value; /** * 构造 diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java index 77ec9314d..bd8fac9c1 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java @@ -48,7 +48,7 @@ public class SymmetricCrypto implements Serializable { * 是否0填充 */ private boolean isZeroPadding; - private Lock lock = new ReentrantLock(); + private final Lock lock = new ReentrantLock(); // ------------------------------------------------------------------ Constructor start diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java index 6afd56e99..1b9e99018 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/BCUtilTest.java @@ -8,11 +8,6 @@ import org.junit.Test; public class BCUtilTest { - @Test - public void decodeECPointTest(){ - - } - /** * 密钥生成来自:https://i.goto327.top/CryptTools/SM2.aspx?tdsourcetag=s_pctim_aiomsg */ diff --git a/hutool-db/src/main/java/cn/hutool/db/ThreadLocalConnection.java b/hutool-db/src/main/java/cn/hutool/db/ThreadLocalConnection.java index b8af20be4..60666ee69 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ThreadLocalConnection.java +++ b/hutool-db/src/main/java/cn/hutool/db/ThreadLocalConnection.java @@ -1,12 +1,11 @@ package cn.hutool.db; +import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; -import javax.sql.DataSource; - /** * 线程相关的数据库连接持有器
    * 此对象为单例类,用于存储线程相关的Connection对象。
    @@ -61,7 +60,7 @@ public enum ThreadLocalConnection { public static class GroupedConnection { /** 连接的Map,考虑到大部分情况是单数据库,故此处初始大小1 */ - private Map connMap = new HashMap<>(1, 1); + private final Map connMap = new HashMap<>(1, 1); /** * 获取连接,如果获取的连接为空或者已被关闭,重新创建连接 diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java index ac1711e01..05484d09a 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java @@ -1,11 +1,5 @@ package cn.hutool.db.dialect; -import java.sql.Connection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import javax.sql.DataSource; - import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.dialect.impl.AnsiSqlDialect; @@ -17,6 +11,11 @@ import cn.hutool.db.dialect.impl.SqlServer2012Dialect; import cn.hutool.db.dialect.impl.Sqlite3Dialect; import cn.hutool.log.StaticLog; +import javax.sql.DataSource; +import java.sql.Connection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + /** * 方言工厂类 * @@ -26,35 +25,35 @@ import cn.hutool.log.StaticLog; public class DialectFactory { /** JDBC 驱动 MySQL */ - public final static String DRIVER_MYSQL = "com.mysql.jdbc.Driver"; + public static final String DRIVER_MYSQL = "com.mysql.jdbc.Driver"; /** JDBC 驱动 MySQL,在6.X版本中变动驱动类名,且使用SPI机制 */ - public final static String DRIVER_MYSQL_V6 = "com.mysql.cj.jdbc.Driver"; + public static final String DRIVER_MYSQL_V6 = "com.mysql.cj.jdbc.Driver"; /** JDBC 驱动 Oracle */ - public final static String DRIVER_ORACLE = "oracle.jdbc.OracleDriver"; + public static final String DRIVER_ORACLE = "oracle.jdbc.OracleDriver"; /** JDBC 驱动 Oracle,旧版使用 */ - public final static String DRIVER_ORACLE_OLD = "oracle.jdbc.driver.OracleDriver"; + public static final String DRIVER_ORACLE_OLD = "oracle.jdbc.driver.OracleDriver"; /** JDBC 驱动 PostgreSQL */ - public final static String DRIVER_POSTGRESQL = "org.postgresql.Driver"; + public static final String DRIVER_POSTGRESQL = "org.postgresql.Driver"; /** JDBC 驱动 SQLLite3 */ - public final static String DRIVER_SQLLITE3 = "org.sqlite.JDBC"; + public static final String DRIVER_SQLLITE3 = "org.sqlite.JDBC"; /** JDBC 驱动 SQLServer */ - public final static String DRIVER_SQLSERVER = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; + public static final String DRIVER_SQLSERVER = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; /** JDBC 驱动 Hive */ - public final static String DRIVER_HIVE = "org.apache.hadoop.hive.jdbc.HiveDriver"; + public static final String DRIVER_HIVE = "org.apache.hadoop.hive.jdbc.HiveDriver"; /** JDBC 驱动 Hive2 */ - public final static String DRIVER_HIVE2 = "org.apache.hive.jdbc.HiveDriver"; + public static final String DRIVER_HIVE2 = "org.apache.hive.jdbc.HiveDriver"; /** JDBC 驱动 H2 */ - public final static String DRIVER_H2 = "org.h2.Driver"; + public static final String DRIVER_H2 = "org.h2.Driver"; /** JDBC 驱动 Derby */ - public final static String DRIVER_DERBY = "org.apache.derby.jdbc.ClientDriver"; + public static final String DRIVER_DERBY = "org.apache.derby.jdbc.ClientDriver"; /** JDBC 驱动 Derby嵌入式 */ - public final static String DRIVER_DERBY_EMBEDDED = "org.apache.derby.jdbc.EmbeddedDriver"; + public static final String DRIVER_DERBY_EMBEDDED = "org.apache.derby.jdbc.EmbeddedDriver"; /** JDBC 驱动 HSQLDB */ - public final static String DRIVER_HSQLDB = "org.hsqldb.jdbc.JDBCDriver"; + public static final String DRIVER_HSQLDB = "org.hsqldb.jdbc.JDBCDriver"; /** JDBC 驱动 达梦7 */ - public final static String DRIVER_DM7 = "dm.jdbc.driver.DmDriver"; + public static final String DRIVER_DM7 = "dm.jdbc.driver.DmDriver"; - private static Map dialectPool = new ConcurrentHashMap<>(); + private static final Map DIALECT_POOL = new ConcurrentHashMap<>(); private DialectFactory() { } @@ -150,15 +149,15 @@ public class DialectFactory { * @return {@link Dialect}方言 */ public static Dialect getDialect(DataSource ds) { - Dialect dialect = dialectPool.get(ds); + Dialect dialect = DIALECT_POOL.get(ds); if(null == dialect) { // 数据源作为锁的意义在于:不同数据源不会导致阻塞,相同数据源获取方言时可保证互斥 //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (ds) { - dialect = dialectPool.get(ds); + dialect = DIALECT_POOL.get(ds); if(null == dialect) { dialect = newDialect(ds); - dialectPool.put(ds, dialect); + DIALECT_POOL.put(ds, dialect); } } } diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/DataSourceWrapper.java b/hutool-db/src/main/java/cn/hutool/db/ds/DataSourceWrapper.java index 334e6a9c5..078c810a1 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/DataSourceWrapper.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/DataSourceWrapper.java @@ -1,5 +1,8 @@ package cn.hutool.db.ds; +import cn.hutool.core.io.IoUtil; + +import javax.sql.DataSource; import java.io.Closeable; import java.io.PrintWriter; import java.sql.Connection; @@ -7,10 +10,6 @@ import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; -import javax.sql.DataSource; - -import cn.hutool.core.io.IoUtil; - /** * {@link DataSource} 数据源实现包装,通过包装,提供基本功能外的额外功能和参数持有,包括: * @@ -23,8 +22,8 @@ import cn.hutool.core.io.IoUtil; */ public class DataSourceWrapper implements DataSource, Closeable, Cloneable { - private DataSource ds; - private String driver; + private final DataSource ds; + private final String driver; /** * 包装指定的DataSource diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/pooled/DbSetting.java b/hutool-db/src/main/java/cn/hutool/db/ds/pooled/DbSetting.java index 09b6485b0..698bee06e 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/pooled/DbSetting.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/pooled/DbSetting.java @@ -17,7 +17,7 @@ public class DbSetting { /** 默认的数据库连接配置文件路径 */ public final static String DEFAULT_DB_CONFIG_PATH = "config/db.setting"; - private Setting setting; + private final Setting setting; /** * 构造 diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledConnection.java b/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledConnection.java index 8c9f7063a..2c72b6905 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledConnection.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledConnection.java @@ -1,11 +1,11 @@ package cn.hutool.db.ds.pooled; +import cn.hutool.db.DbUtil; + import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; -import cn.hutool.db.DbUtil; - /** * 池化 * @author Looly @@ -13,7 +13,7 @@ import cn.hutool.db.DbUtil; */ public class PooledConnection extends ConnectionWraper{ - private PooledDataSource ds; + private final PooledDataSource ds; private boolean isClosed; public PooledConnection(PooledDataSource ds) throws SQLException { diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledDataSource.java b/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledDataSource.java index ee0d04e08..e03f44072 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledDataSource.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledDataSource.java @@ -1,11 +1,5 @@ package cn.hutool.db.ds.pooled; -import java.io.IOException; -import java.sql.Connection; -import java.sql.SQLException; -import java.util.LinkedList; -import java.util.Queue; - import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.thread.ThreadUtil; @@ -13,6 +7,11 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.simple.AbstractDataSource; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.LinkedList; +import java.util.Queue; + /** * 池化数据源 * @@ -24,7 +23,7 @@ public class PooledDataSource extends AbstractDataSource { private Queue freePool; private int activeCount; // 活跃连接数 - private DbConfig config; + private final DbConfig config; /** * 获得一个数据源 @@ -79,7 +78,7 @@ public class PooledDataSource extends AbstractDataSource { */ public PooledDataSource(DbConfig config) { this.config = config; - freePool = new LinkedList(); + freePool = new LinkedList<>(); int initialSize = config.getInitialSize(); try { while (initialSize-- > 0) { @@ -146,7 +145,7 @@ public class PooledDataSource extends AbstractDataSource { } @Override - synchronized public void close() throws IOException { + synchronized public void close() { if (CollectionUtil.isNotEmpty(this.freePool)) { for (PooledConnection pooledConnection : freePool) { pooledConnection.release(); @@ -157,7 +156,7 @@ public class PooledDataSource extends AbstractDataSource { } @Override - protected void finalize() throws Throwable { + protected void finalize() { IoUtil.close(this); } diff --git a/hutool-db/src/main/java/cn/hutool/db/handler/EntityHandler.java b/hutool-db/src/main/java/cn/hutool/db/handler/EntityHandler.java index c1a38f340..8f63880b0 100644 --- a/hutool-db/src/main/java/cn/hutool/db/handler/EntityHandler.java +++ b/hutool-db/src/main/java/cn/hutool/db/handler/EntityHandler.java @@ -1,11 +1,11 @@ package cn.hutool.db.handler; +import cn.hutool.db.Entity; + import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; -import cn.hutool.db.Entity; - /** * Entity对象处理器,只处理第一条数据 * @@ -16,7 +16,7 @@ public class EntityHandler implements RsHandler{ private static final long serialVersionUID = -8742432871908355992L; /** 是否大小写不敏感 */ - private boolean caseInsensitive; + private final boolean caseInsensitive; /** * 创建一个 EntityHandler对象 diff --git a/hutool-db/src/main/java/cn/hutool/db/handler/EntityListHandler.java b/hutool-db/src/main/java/cn/hutool/db/handler/EntityListHandler.java index b7029a76f..5501c06ca 100644 --- a/hutool-db/src/main/java/cn/hutool/db/handler/EntityListHandler.java +++ b/hutool-db/src/main/java/cn/hutool/db/handler/EntityListHandler.java @@ -1,12 +1,12 @@ package cn.hutool.db.handler; +import cn.hutool.db.Entity; + import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import cn.hutool.db.Entity; - /** * 结果集处理类 ,处理出的结果为Entity列表 * @author loolly @@ -16,7 +16,7 @@ public class EntityListHandler implements RsHandler>{ private static final long serialVersionUID = -2846240126316979895L; /** 是否大小写不敏感 */ - private boolean caseInsensitive; + private final boolean caseInsensitive; /** * 创建一个 EntityListHandler对象 diff --git a/hutool-db/src/main/java/cn/hutool/db/handler/EntitySetHandler.java b/hutool-db/src/main/java/cn/hutool/db/handler/EntitySetHandler.java index 787760b65..28e814579 100644 --- a/hutool-db/src/main/java/cn/hutool/db/handler/EntitySetHandler.java +++ b/hutool-db/src/main/java/cn/hutool/db/handler/EntitySetHandler.java @@ -1,11 +1,11 @@ package cn.hutool.db.handler; +import cn.hutool.db.Entity; + import java.sql.ResultSet; import java.sql.SQLException; import java.util.LinkedHashSet; -import cn.hutool.db.Entity; - /** * 结果集处理类 ,处理出的结果为Entity列表,结果不能重复(按照Entity对象去重) * @author loolly @@ -15,7 +15,7 @@ public class EntitySetHandler implements RsHandler>{ private static final long serialVersionUID = 8191723216703506736L; /** 是否大小写不敏感 */ - private boolean caseInsensitive; + private final boolean caseInsensitive; /** * 创建一个 EntityHandler对象 diff --git a/hutool-db/src/main/java/cn/hutool/db/handler/PageResultHandler.java b/hutool-db/src/main/java/cn/hutool/db/handler/PageResultHandler.java index d4b7d08cf..3d7c78af0 100644 --- a/hutool-db/src/main/java/cn/hutool/db/handler/PageResultHandler.java +++ b/hutool-db/src/main/java/cn/hutool/db/handler/PageResultHandler.java @@ -1,11 +1,11 @@ package cn.hutool.db.handler; -import java.sql.ResultSet; -import java.sql.SQLException; - import cn.hutool.db.Entity; import cn.hutool.db.PageResult; +import java.sql.ResultSet; +import java.sql.SQLException; + /** * 分页结果集处理类 ,处理出的结果为PageResult * @@ -14,11 +14,11 @@ import cn.hutool.db.PageResult; public class PageResultHandler implements RsHandler> { private static final long serialVersionUID = -1474161855834070108L; - private PageResult pageResult; + private final PageResult pageResult; /** * 是否大小写不敏感 */ - private boolean caseInsensitive; + private final boolean caseInsensitive; /** * 创建一个 EntityHandler对象
    diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/JdbcType.java b/hutool-db/src/main/java/cn/hutool/db/meta/JdbcType.java index e980ab615..969891170 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/JdbcType.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/JdbcType.java @@ -4,7 +4,10 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** + * JDBC中字段类型枚举 + * * @author Clinton Begin + * @see java.sql.Types */ public enum JdbcType { ARRAY(java.sql.Types.ARRAY), // @@ -60,10 +63,10 @@ public enum JdbcType { this.typeCode = code; } - private static Map codeMap = new ConcurrentHashMap<>(100, 1); + private static final Map CODE_MAP = new ConcurrentHashMap<>(100, 1); static { for (JdbcType type : JdbcType.values()) { - codeMap.put(type.typeCode, type); + CODE_MAP.put(type.typeCode, type); } } @@ -74,7 +77,7 @@ public enum JdbcType { * @return {@link JdbcType} */ public static JdbcType valueOf(int code) { - return codeMap.get(code); + return CODE_MAP.get(code); } } diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/Table.java b/hutool-db/src/main/java/cn/hutool/db/meta/Table.java index b5e5f4098..62a12f5e0 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/Table.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/Table.java @@ -21,8 +21,8 @@ public class Table implements Serializable, Cloneable { /** 注释 */ private String comment; /** 主键字段名列表 */ - private Set pkNames = new LinkedHashSet(); - private Map columns = new LinkedHashMap<>(); + private Set pkNames = new LinkedHashSet<>(); + private final Map columns = new LinkedHashMap<>(); public static Table create(String tableName) { return new Table(tableName); diff --git a/hutool-db/src/main/java/cn/hutool/db/meta/TableType.java b/hutool-db/src/main/java/cn/hutool/db/meta/TableType.java index 84d231325..99fc289a5 100644 --- a/hutool-db/src/main/java/cn/hutool/db/meta/TableType.java +++ b/hutool-db/src/main/java/cn/hutool/db/meta/TableType.java @@ -14,7 +14,7 @@ public enum TableType { ALIAS("ALIAS"), SYNONYM("SYNONYM"); - private String value; + private final String value; /** * 构造 diff --git a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory.java b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory.java index b616a866f..262c64bf7 100644 --- a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory.java @@ -1,13 +1,14 @@ package cn.hutool.db.nosql.mongo; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.RuntimeUtil; +import cn.hutool.setting.Setting; + import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.setting.Setting; - /** * MongoDB工厂类,用于创建 * @author looly @@ -19,16 +20,11 @@ public class MongoFactory { private final static String GROUP_SEPRATER = ","; /** 数据源池 */ - private static Map dsMap = new ConcurrentHashMap<>(); + private static final Map DS_MAP = new ConcurrentHashMap<>(); // JVM关闭前关闭MongoDB连接 static { - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - MongoFactory.closeAll(); - } - }); + RuntimeUtil.addShutdownHook(MongoFactory::closeAll); } // ------------------------------------------------------------------------ Get DS start @@ -41,11 +37,11 @@ public class MongoFactory { */ public static MongoDS getDS(String host, int port) { final String key = host + ":" + port; - MongoDS ds = dsMap.get(key); + MongoDS ds = DS_MAP.get(key); if (null == ds) { // 没有在池中加入之 ds = new MongoDS(host, port); - dsMap.put(key, ds); + DS_MAP.put(key, ds); } return ds; @@ -60,11 +56,11 @@ public class MongoFactory { */ public static MongoDS getDS(String... groups) { final String key = ArrayUtil.join(groups, GROUP_SEPRATER); - MongoDS ds = dsMap.get(key); + MongoDS ds = DS_MAP.get(key); if (null == ds) { // 没有在池中加入之 ds = new MongoDS(groups); - dsMap.put(key, ds); + DS_MAP.put(key, ds); } return ds; @@ -77,7 +73,7 @@ public class MongoFactory { * @return MongoDB连接 */ public static MongoDS getDS(Collection groups) { - return getDS(groups.toArray(new String[groups.size()])); + return getDS(groups.toArray(new String[0])); } /** @@ -89,11 +85,11 @@ public class MongoFactory { */ public static MongoDS getDS(Setting setting, String... groups) { final String key = setting.getSettingPath() + GROUP_SEPRATER + ArrayUtil.join(groups, GROUP_SEPRATER); - MongoDS ds = dsMap.get(key); + MongoDS ds = DS_MAP.get(key); if (null == ds) { // 没有在池中加入之 ds = new MongoDS(setting, groups); - dsMap.put(key, ds); + DS_MAP.put(key, ds); } return ds; @@ -107,7 +103,7 @@ public class MongoFactory { * @return MongoDB连接 */ public static MongoDS getDS(Setting setting, Collection groups) { - return getDS(setting, groups.toArray(new String[groups.size()])); + return getDS(setting, groups.toArray(new String[0])); } // ------------------------------------------------------------------------ Get DS ends @@ -115,11 +111,11 @@ public class MongoFactory { * 关闭全部连接 */ public static void closeAll() { - if(CollectionUtil.isNotEmpty(dsMap)){ - for(MongoDS ds : dsMap.values()) { + if(CollectionUtil.isNotEmpty(DS_MAP)){ + for(MongoDS ds : DS_MAP.values()) { ds.close(); } - dsMap.clear(); + DS_MAP.clear(); } } } diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/Condition.java b/hutool-db/src/main/java/cn/hutool/db/sql/Condition.java index d5471a29e..017a2113a 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/Condition.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/Condition.java @@ -1,37 +1,40 @@ package cn.hutool.db.sql; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - import cn.hutool.core.clone.CloneSupport; -import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.text.StrSpliter; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + /** * 条件对象
    - * - * @author Looly * + * @author Looly */ public class Condition extends CloneSupport { /** * SQL中 LIKE 语句查询方式
    - * - * @author Looly * + * @author Looly */ public enum LikeType { - /** 以给定值开头,拼接后的SQL "value%" */ + /** + * 以给定值开头,拼接后的SQL "value%" + */ StartWith, - /** 以给定值开头,拼接后的SQL "%value" */ + /** + * 以给定值开头,拼接后的SQL "%value" + */ EndWith, - /** 包含给定值,拼接后的SQL "%value%" */ + /** + * 包含给定值,拼接后的SQL "%value%" + */ Contains } @@ -44,21 +47,31 @@ public class Condition extends CloneSupport { private static final String VALUE_NULL = "NULL"; - /** 字段 */ + /** + * 字段 + */ private String field; - /** 运算符(大于号,小于号,等于号 like 等) */ + /** + * 运算符(大于号,小于号,等于号 like 等) + */ private String operator; - /** 值 */ + /** + * 值 + */ private Object value; - /** 是否使用条件值占位符 */ + /** + * 是否使用条件值占位符 + */ private boolean isPlaceHolder = true; - /** between firstValue and secondValue */ + /** + * between firstValue and secondValue + */ private Object secondValue; /** * 解析为Condition - * - * @param field 字段名 + * + * @param field 字段名 * @param expression 表达式或普通值 * @return Condition */ @@ -67,6 +80,7 @@ public class Condition extends CloneSupport { } // --------------------------------------------------------------- Constructor start + /** * 构造 */ @@ -75,7 +89,7 @@ public class Condition extends CloneSupport { /** * 构造 - * + * * @param isPlaceHolder 是否使用条件值占位符 */ public Condition(boolean isPlaceHolder) { @@ -84,7 +98,7 @@ public class Condition extends CloneSupport { /** * 构造,使用等于表达式(运算符是=) - * + * * @param field 字段 * @param value 值 */ @@ -95,10 +109,10 @@ public class Condition extends CloneSupport { /** * 构造 - * - * @param field 字段 + * + * @param field 字段 * @param operator 运算符(大于号,小于号,等于号 like 等) - * @param value 值 + * @param value 值 */ public Condition(String field, String operator, Object value) { this.field = field; @@ -108,9 +122,9 @@ public class Condition extends CloneSupport { /** * 构造 - * - * @param field 字段 - * @param value 值 + * + * @param field 字段 + * @param value 值 * @param likeType {@link LikeType} */ public Condition(String field, String value, LikeType likeType) { @@ -121,6 +135,7 @@ public class Condition extends CloneSupport { // --------------------------------------------------------------- Constructor end // --------------------------------------------------------------- Getters and Setters start + /** * @return 字段 */ @@ -130,7 +145,7 @@ public class Condition extends CloneSupport { /** * 设置字段名 - * + * * @param field 字段名 */ public void setField(String field) { @@ -140,7 +155,7 @@ public class Condition extends CloneSupport { /** * 获得运算符
    * 大于号,小于号,等于号 等 - * + * * @return 运算符 */ public String getOperator() { @@ -150,7 +165,7 @@ public class Condition extends CloneSupport { /** * 设置运算符
    * 大于号,小于号,等于号 等 - * + * * @param operator 运算符 */ public void setOperator(String operator) { @@ -159,7 +174,7 @@ public class Condition extends CloneSupport { /** * 获得值 - * + * * @return 值 */ public Object getValue() { @@ -168,7 +183,7 @@ public class Condition extends CloneSupport { /** * 设置值,不解析表达式 - * + * * @param value 值 */ public void setValue(Object value) { @@ -177,8 +192,8 @@ public class Condition extends CloneSupport { /** * 设置值 - * - * @param value 值 + * + * @param value 值 * @param isParse 是否解析值表达式 */ public void setValue(Object value, boolean isParse) { @@ -190,7 +205,7 @@ public class Condition extends CloneSupport { /** * 是否使用条件占位符 - * + * * @return 是否使用条件占位符 */ public boolean isPlaceHolder() { @@ -199,7 +214,7 @@ public class Condition extends CloneSupport { /** * 设置是否使用条件占位符 - * + * * @param isPlaceHolder 是否使用条件占位符 */ public void setPlaceHolder(boolean isPlaceHolder) { @@ -218,7 +233,7 @@ public class Condition extends CloneSupport { /** * 是否IN条件 - * + * * @return 是否IN条件 * @since 4.0.1 */ @@ -228,7 +243,7 @@ public class Condition extends CloneSupport { /** * 是否IS条件 - * + * * @return 是否IS条件 * @since 4.0.1 */ @@ -238,7 +253,7 @@ public class Condition extends CloneSupport { /** * 检查值是否为null,如果为null转换为 "IS NULL"形式 - * + * * @return this */ public Condition checkValueNull() { @@ -276,7 +291,7 @@ public class Condition extends CloneSupport { /** * 转换为条件字符串,并回填占位符对应的参数值 - * + * * @param paramValues 参数列表,用于回填占位符对应参数值 * @return 条件字符串 */ @@ -297,7 +312,7 @@ public class Condition extends CloneSupport { if (isPlaceHolder() && false == isOperatorIs()) { // 使用条件表达式占位符,条件表达式并不适用于 IS NULL conditionStrBuilder.append(" ?"); - if(null != paramValues) { + if (null != paramValues) { paramValues.add(this.value); } } else { @@ -310,19 +325,20 @@ public class Condition extends CloneSupport { } // ----------------------------------------------------------------------------------------------- Private method start + /** * 构建BETWEEN语句中的值部分
    * 开头必须加空格,类似:" ? AND ?" 或者 " 1 AND 2" - * + * * @param conditionStrBuilder 条件语句构建器 - * @param paramValues 参数集合,用于参数占位符对应参数回填 + * @param paramValues 参数集合,用于参数占位符对应参数回填 */ private void buildValuePartForBETWEEN(StringBuilder conditionStrBuilder, List paramValues) { // BETWEEN x AND y 的情况,两个参数 if (isPlaceHolder()) { // 使用条件表达式占位符 conditionStrBuilder.append(" ?"); - if(null != paramValues) { + if (null != paramValues) { paramValues.add(this.value); } } else { @@ -335,7 +351,7 @@ public class Condition extends CloneSupport { if (isPlaceHolder()) { // 使用条件表达式占位符 conditionStrBuilder.append(" ?"); - if(null != paramValues) { + if (null != paramValues) { paramValues.add(this.secondValue); } } else { @@ -347,9 +363,9 @@ public class Condition extends CloneSupport { /** * 构建IN语句中的值部分
    * 开头必须加空格,类似:" (?,?,?)" 或者 " (1,2,3,4)" - * + * * @param conditionStrBuilder 条件语句构建器 - * @param paramValues 参数集合,用于参数占位符对应参数回填 + * @param paramValues 参数集合,用于参数占位符对应参数回填 */ private void buildValuePartForIN(StringBuilder conditionStrBuilder, List paramValues) { conditionStrBuilder.append(" ("); @@ -361,12 +377,9 @@ public class Condition extends CloneSupport { valuesForIn = StrUtil.split((CharSequence) value, ','); } else { valuesForIn = Arrays.asList(Convert.convert(String[].class, value)); - if (null == valuesForIn) { - valuesForIn = CollUtil.newArrayList(Convert.toStr(value)); - } } conditionStrBuilder.append(StrUtil.repeatAndJoin("?", valuesForIn.size(), ",")); - if(null != paramValues) { + if (null != paramValues) { paramValues.addAll(valuesForIn); } } else { @@ -461,7 +474,7 @@ public class Condition extends CloneSupport { /** * 去掉包围在字符串两端的单引号或双引号 - * + * * @param value 值 * @return 去掉引号后的值 */ @@ -474,15 +487,15 @@ public class Condition extends CloneSupport { int from = 0; int to = value.length(); char startChar = value.charAt(0); - char endChar = value.charAt(to - 1); + char endChar = value.charAt(value.length() - 1); if (startChar == endChar) { if ('\'' == startChar || '"' == startChar) { from = 1; - to = to - 1; + to--; } } - if (from == 0 && to == value.length()) { + if (from == 0) { // 并不包含,返回原值 return value; } diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/NamedSql.java b/hutool-db/src/main/java/cn/hutool/db/sql/NamedSql.java index e58eea699..5899c29e5 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/NamedSql.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/NamedSql.java @@ -1,12 +1,12 @@ package cn.hutool.db.sql; +import cn.hutool.core.text.StrBuilder; +import cn.hutool.core.util.StrUtil; + import java.util.LinkedList; import java.util.List; import java.util.Map; -import cn.hutool.core.text.StrBuilder; -import cn.hutool.core.util.StrUtil; - /** * 使用命名占位符的SQL,例如:select * from table where field1=:name1
    * 支持的占位符格式为: @@ -22,7 +22,7 @@ import cn.hutool.core.util.StrUtil; public class NamedSql { private String sql; - private List params; + private final List params; /** * 构造 diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/SqlFormatter.java b/hutool-db/src/main/java/cn/hutool/db/sql/SqlFormatter.java index f7e93966b..0ba99bde8 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/SqlFormatter.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/SqlFormatter.java @@ -70,8 +70,8 @@ public class SqlFormatter { boolean afterInsert = false; int inFunction = 0; int parensSinceSelect = 0; - private LinkedList parenCounts = new LinkedList<>(); - private LinkedList afterByOrFromOrSelects = new LinkedList<>(); + private final LinkedList parenCounts = new LinkedList<>(); + private final LinkedList afterByOrFromOrSelects = new LinkedList<>(); int indent = 1; @@ -273,14 +273,13 @@ public class SqlFormatter { } if (this.inFunction > 0) { this.inFunction -= 1; - out(); } else { if (!this.afterByOrSetOrFromOrSelect) { this.indent -= 1; newline(); } - out(); } + out(); this.beginLine = false; } diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/SqlUtil.java b/hutool-db/src/main/java/cn/hutool/db/sql/SqlUtil.java index 471fc182c..1a8bfa03d 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/SqlUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/SqlUtil.java @@ -1,5 +1,12 @@ package cn.hutool.db.sql; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.DbRuntimeException; +import cn.hutool.db.Entity; +import cn.hutool.db.sql.Condition.LikeType; + import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; @@ -12,13 +19,6 @@ import java.sql.SQLException; import java.util.List; import java.util.Map.Entry; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.DbRuntimeException; -import cn.hutool.db.Entity; -import cn.hutool.db.sql.Condition.LikeType; - /** * SQL相关工具类,包括相关SQL语句拼接等 * @@ -105,7 +105,7 @@ public class SqlUtil { */ public static String buildLikeValue(String value, LikeType likeType, boolean withLikeKeyword) { if (null == value) { - return value; + return null; } StringBuilder likeValue = StrUtil.builder(withLikeKeyword ? "LIKE " : ""); diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/Wrapper.java b/hutool-db/src/main/java/cn/hutool/db/sql/Wrapper.java index 47418dfa1..525fcc5a2 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/Wrapper.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/Wrapper.java @@ -1,15 +1,15 @@ package cn.hutool.db.sql; -import java.util.Arrays; -import java.util.Collection; -import java.util.Map.Entry; - import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.lang.Editor; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.Entity; +import java.util.Arrays; +import java.util.Collection; +import java.util.Map.Entry; + /** * 包装器
    * 主要用于字段名的包装(在字段名的前后加字符,例如反引号来避免与数据库的关键字冲突) @@ -135,7 +135,7 @@ public class Wrapper { return fields; } - return Arrays.asList(wrap(fields.toArray(new String[fields.size()]))); + return Arrays.asList(wrap(fields.toArray(new String[0]))); } /** diff --git a/hutool-db/src/main/java/cn/hutool/db/transaction/TransactionLevel.java b/hutool-db/src/main/java/cn/hutool/db/transaction/TransactionLevel.java index 87e655103..a80f0def8 100644 --- a/hutool-db/src/main/java/cn/hutool/db/transaction/TransactionLevel.java +++ b/hutool-db/src/main/java/cn/hutool/db/transaction/TransactionLevel.java @@ -58,7 +58,7 @@ public enum TransactionLevel { SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE); /** 事务级别,对应Connection中的常量值 */ - private int level; + private final int level; TransactionLevel(int level) { this.level = level; diff --git a/hutool-db/src/test/java/cn/hutool/db/CRUDTest.java b/hutool-db/src/test/java/cn/hutool/db/CRUDTest.java index f916f6bc8..f86c90a67 100644 --- a/hutool-db/src/test/java/cn/hutool/db/CRUDTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/CRUDTest.java @@ -22,7 +22,7 @@ import java.util.List; */ public class CRUDTest { - private static Db db = Db.use("test"); + private static final Db db = Db.use("test"); @Test public void findIsNullTest() throws SQLException { diff --git a/hutool-db/src/test/java/cn/hutool/db/ConcurentTest.java b/hutool-db/src/test/java/cn/hutool/db/ConcurentTest.java index b590e6117..61932406c 100644 --- a/hutool-db/src/test/java/cn/hutool/db/ConcurentTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/ConcurentTest.java @@ -1,16 +1,15 @@ package cn.hutool.db; -import java.sql.SQLException; -import java.util.List; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.lang.Console; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.db.handler.EntityListHandler; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import java.sql.SQLException; +import java.util.List; /** * SqlRunner线程安全测试 @@ -31,17 +30,14 @@ public class ConcurentTest { @Test public void findTest() { for(int i = 0; i < 10000; i++) { - ThreadUtil.execute(new Runnable() { - @Override - public void run() { - List find; - try { - find = db.find(CollectionUtil.newArrayList("name AS name2"), Entity.create("user"), new EntityListHandler()); - } catch (SQLException e) { - throw new DbRuntimeException(e); - } - Console.log(find); + ThreadUtil.execute(() -> { + List find; + try { + find = db.find(CollectionUtil.newArrayList("name AS name2"), Entity.create("user"), new EntityListHandler()); + } catch (SQLException e) { + throw new DbRuntimeException(e); } + Console.log(find); }); } diff --git a/hutool-db/src/test/java/cn/hutool/db/SessionTest.java b/hutool-db/src/test/java/cn/hutool/db/SessionTest.java index 978996edc..66b77e708 100644 --- a/hutool-db/src/test/java/cn/hutool/db/SessionTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/SessionTest.java @@ -1,11 +1,9 @@ package cn.hutool.db; -import java.sql.SQLException; - import org.junit.Ignore; import org.junit.Test; -import cn.hutool.core.lang.func.VoidFunc1; +import java.sql.SQLException; /** * 事务性数据库操作单元测试 @@ -30,11 +28,6 @@ public class SessionTest { @Test @Ignore public void txTest() throws SQLException { - Session.create("test").tx(new VoidFunc1() { - @Override - public void call(Session session) throws SQLException { - session.update(Entity.create().set("age", 78), Entity.create("user").set("name", "unitTestUser")); - } - }); + Session.create("test").tx(session -> session.update(Entity.create().set("age", 78), Entity.create("user").set("name", "unitTestUser"))); } } diff --git a/hutool-dfa/src/main/java/cn/hutool/dfa/SensitiveUtil.java b/hutool-dfa/src/main/java/cn/hutool/dfa/SensitiveUtil.java index 8fbfa476e..c61f71b61 100644 --- a/hutool-dfa/src/main/java/cn/hutool/dfa/SensitiveUtil.java +++ b/hutool-dfa/src/main/java/cn/hutool/dfa/SensitiveUtil.java @@ -16,7 +16,7 @@ public final class SensitiveUtil { // private static final Log log = LogFactory.get(); public static final char DEFAULT_SEPARATOR = StrUtil.C_COMMA; - private static WordTree sensitiveTree = new WordTree(); + private static final WordTree sensitiveTree = new WordTree(); /** * @return 是否已经被初始化 diff --git a/hutool-dfa/src/main/java/cn/hutool/dfa/WordTree.java b/hutool-dfa/src/main/java/cn/hutool/dfa/WordTree.java index 69f76e393..0d715b338 100644 --- a/hutool-dfa/src/main/java/cn/hutool/dfa/WordTree.java +++ b/hutool-dfa/src/main/java/cn/hutool/dfa/WordTree.java @@ -1,5 +1,10 @@ package cn.hutool.dfa; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.Filter; +import cn.hutool.core.text.StrBuilder; +import cn.hutool.core.util.StrUtil; + import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -7,11 +12,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.lang.Filter; -import cn.hutool.core.text.StrBuilder; -import cn.hutool.core.util.StrUtil; - /** * DFA(Deterministic Finite Automaton 确定有穷自动机) * DFA单词树(以下简称单词树),常用于在某大段文字中快速查找某几个关键词是否存在。
    @@ -33,7 +33,7 @@ public class WordTree extends HashMap { /** * 敏感词字符末尾标识,用于标识单词末尾字符 */ - private Set endCharacterSet = new HashSet<>(); + private final Set endCharacterSet = new HashSet<>(); /** * 字符过滤规则,通过定义字符串过滤规则,过滤不需要的字符,当accept为false时,此字符不参与匹配 */ diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/InternalMailUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/InternalMailUtil.java index e0a80ae64..8290817ad 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/InternalMailUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/InternalMailUtil.java @@ -1,15 +1,15 @@ package cn.hutool.extra.mail; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; +import cn.hutool.core.util.ArrayUtil; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeUtility; - -import cn.hutool.core.util.ArrayUtil; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * 邮件内部工具类 @@ -30,15 +30,13 @@ public class InternalMailUtil { public static InternetAddress[] parseAddressFromStrs(String[] addrStrs, Charset charset) { final List resultList = new ArrayList<>(addrStrs.length); InternetAddress[] addrs; - for (int i = 0; i < addrStrs.length; i++) { - addrs = parseAddress(addrStrs[i], charset); - if(ArrayUtil.isNotEmpty(addrs)) { - for(int j = 0 ; j < addrs.length; j++) { - resultList.add(addrs[j]); - } + for (String addrStr : addrStrs) { + addrs = parseAddress(addrStr, charset); + if (ArrayUtil.isNotEmpty(addrs)) { + Collections.addAll(resultList, addrs); } } - return resultList.toArray(new InternetAddress[resultList.size()]); + return resultList.toArray(new InternetAddress[0]); } /** diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java index d3d2284a5..fa8a40b52 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java @@ -1,10 +1,11 @@ package cn.hutool.extra.mail; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.Date; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import javax.activation.DataHandler; import javax.activation.DataSource; @@ -19,13 +20,11 @@ import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import javax.mail.util.ByteArrayDataSource; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.Date; /** * 邮件发送客户端 @@ -38,7 +37,7 @@ public class Mail { /** * 邮箱帐户信息以及一些客户端配置信息 */ - private MailAccount mailAccount; + private final MailAccount mailAccount; /** * 收件人列表 */ @@ -70,7 +69,7 @@ public class Mail { /** * 正文、附件和图片的混合部分 */ - private Multipart multipart = new MimeMultipart(); + private final Multipart multipart = new MimeMultipart(); /** * 是否使用全局会话,默认为false */ diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java index c174a5e2e..8641c990d 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java @@ -467,7 +467,7 @@ public class MailAccount implements Serializable { if (this.starttlsEnable) { //STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。 - p.put(STARTTLS_ENABLE, String.valueOf(this.starttlsEnable)); + p.put(STARTTLS_ENABLE, "true"); if (null == this.sslEnable) { //为了兼容旧版本,当用户没有此项配置时,按照starttlsEnable开启状态时对待 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailUtil.java index adfd5bed0..1af4c9491 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailUtil.java @@ -1,5 +1,10 @@ package cn.hutool.extra.mail; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; + import java.io.File; import java.io.InputStream; import java.util.Collection; @@ -7,11 +12,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.StrUtil; - /** * 邮件工具类,基于javax.mail封装 * @@ -370,14 +370,14 @@ public class MailUtil { // 可选抄送人 if (CollUtil.isNotEmpty(ccs)) { - mail.setCcs(ccs.toArray(new String[ccs.size()])); + mail.setCcs(ccs.toArray(new String[0])); } // 可选密送人 if (CollUtil.isNotEmpty(bccs)) { - mail.setBccs(bccs.toArray(new String[bccs.size()])); + mail.setBccs(bccs.toArray(new String[0])); } - mail.setTos(tos.toArray(new String[tos.size()])); + mail.setTos(tos.toArray(new String[0])); mail.setTitle(subject); mail.setContent(content); mail.setHtml(isHtml); diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/UserPassAuthenticator.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/UserPassAuthenticator.java index 3cd25a2fe..6e0e5a19f 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/UserPassAuthenticator.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/UserPassAuthenticator.java @@ -11,8 +11,8 @@ import javax.mail.PasswordAuthentication; */ public class UserPassAuthenticator extends Authenticator { - private String user; - private String pass; + private final String user; + private final String pass; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/BufferedImageLuminanceSource.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/BufferedImageLuminanceSource.java index 50f2fcad3..c11f3e847 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/BufferedImageLuminanceSource.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/BufferedImageLuminanceSource.java @@ -99,6 +99,7 @@ public final class BufferedImageLuminanceSource extends LuminanceSource { return true; } + @SuppressWarnings("SuspiciousNameCombination") @Override public LuminanceSource rotateCounterClockwise() { diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschSessionPool.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschSessionPool.java index b89f65fa6..3466edff0 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschSessionPool.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschSessionPool.java @@ -20,7 +20,7 @@ public enum JschSessionPool { /** * SSH会话池,key:host,value:Session对象 */ - private Map sessionPool = new ConcurrentHashMap<>(); + private final Map sessionPool = new ConcurrentHashMap<>(); /** * 锁 */ diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/enjoy/EnjoyTemplate.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/enjoy/EnjoyTemplate.java index ab26ddf3d..db037f009 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/enjoy/EnjoyTemplate.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/enjoy/EnjoyTemplate.java @@ -1,12 +1,12 @@ package cn.hutool.extra.template.engine.enjoy; +import cn.hutool.extra.template.AbstractTemplate; + import java.io.OutputStream; import java.io.Serializable; import java.io.Writer; import java.util.Map; -import cn.hutool.extra.template.AbstractTemplate; - /** * Engoy模板实现 * @@ -16,7 +16,7 @@ import cn.hutool.extra.template.AbstractTemplate; public class EnjoyTemplate extends AbstractTemplate implements Serializable { private static final long serialVersionUID = 1L; - private com.jfinal.template.Template rawTemplate; + private final com.jfinal.template.Template rawTemplate; /** * 包装Enjoy模板 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/rythm/RythmTemplate.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/rythm/RythmTemplate.java index 1bab9b4ce..23505dec5 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/rythm/RythmTemplate.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/rythm/RythmTemplate.java @@ -18,7 +18,7 @@ import java.util.Map; public class RythmTemplate extends AbstractTemplate implements Serializable { private static final long serialVersionUID = -132774960373894911L; - private org.rythmengine.template.ITemplate rawTemplate; + private final org.rythmengine.template.ITemplate rawTemplate; /** * 包装Rythm模板 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/thymeleaf/ThymeleafTemplate.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/thymeleaf/ThymeleafTemplate.java index a95b9e6b3..bb23ec78c 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/thymeleaf/ThymeleafTemplate.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/thymeleaf/ThymeleafTemplate.java @@ -1,21 +1,20 @@ package cn.hutool.extra.template.engine.thymeleaf; -import java.io.OutputStream; -import java.io.Serializable; -import java.io.Writer; -import java.nio.charset.Charset; -import java.util.Locale; -import java.util.Map; - -import org.thymeleaf.TemplateEngine; -import org.thymeleaf.context.Context; - import cn.hutool.core.convert.Convert; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.TypeReference; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.extra.template.AbstractTemplate; +import org.thymeleaf.TemplateEngine; +import org.thymeleaf.context.Context; + +import java.io.OutputStream; +import java.io.Serializable; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.Locale; +import java.util.Map; /** * Thymeleaf模板实现 @@ -26,9 +25,9 @@ import cn.hutool.extra.template.AbstractTemplate; public class ThymeleafTemplate extends AbstractTemplate implements Serializable { private static final long serialVersionUID = 781284916568562509L; - private TemplateEngine engine; - private String template; - private Charset charset; + private final TemplateEngine engine; + private final String template; + private final Charset charset; /** * 包装Thymeleaf模板 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityTemplate.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityTemplate.java index 9b4d61f6d..5cee9c97c 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityTemplate.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityTemplate.java @@ -1,19 +1,18 @@ package cn.hutool.extra.template.engine.velocity; -import java.io.OutputStream; -import java.io.Serializable; -import java.io.Writer; -import java.util.Map; - -import org.apache.velocity.VelocityContext; -import org.apache.velocity.app.Velocity; - import cn.hutool.core.convert.Convert; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.TypeReference; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.template.AbstractTemplate; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; + +import java.io.OutputStream; +import java.io.Serializable; +import java.io.Writer; +import java.util.Map; /** * Velocity模板包装 @@ -24,7 +23,7 @@ import cn.hutool.extra.template.AbstractTemplate; public class VelocityTemplate extends AbstractTemplate implements Serializable { private static final long serialVersionUID = -132774960373894911L; - private org.apache.velocity.Template rawTemplate; + private final org.apache.velocity.Template rawTemplate; private String charset; /** diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityUtil.java index d8d0e75a0..6118a9670 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityUtil.java @@ -37,7 +37,7 @@ public class VelocityUtil { /** * 全局上下文,当设定值时,对于每个模板都有效 */ - private static Map globalContext = new HashMap<>(); + private static final Map globalContext = new HashMap<>(); /** * 设置Velocity全局上下文
    diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPEngine.java index 1616705d9..523b01ce6 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPEngine.java @@ -16,7 +16,7 @@ import cn.hutool.extra.tokenizer.Result; */ public class HanLPEngine implements TokenizerEngine { - private Segment seg; + private final Segment seg; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPWord.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPWord.java index b7060bda0..cf4408d1a 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPWord.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/hanlp/HanLPWord.java @@ -13,7 +13,7 @@ import cn.hutool.extra.tokenizer.Word; public class HanLPWord implements Word { private static final long serialVersionUID = 1L; - private Term term; + private final Term term; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerEngine.java index 7fba710f8..ff0429e95 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerEngine.java @@ -15,7 +15,7 @@ import cn.hutool.extra.tokenizer.Result; */ public class IKAnalyzerEngine implements TokenizerEngine { - private IKSegmenter seg; + private final IKSegmenter seg; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerResult.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerResult.java index 3c102233a..2c3a2edbf 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerResult.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerResult.java @@ -1,13 +1,12 @@ package cn.hutool.extra.tokenizer.engine.ikanalyzer; -import java.io.IOException; - -import org.wltea.analyzer.core.IKSegmenter; -import org.wltea.analyzer.core.Lexeme; - import cn.hutool.extra.tokenizer.AbstractResult; import cn.hutool.extra.tokenizer.TokenizerException; import cn.hutool.extra.tokenizer.Word; +import org.wltea.analyzer.core.IKSegmenter; +import org.wltea.analyzer.core.Lexeme; + +import java.io.IOException; /** * IKAnalyzer分词结果实现
    @@ -18,7 +17,7 @@ import cn.hutool.extra.tokenizer.Word; */ public class IKAnalyzerResult extends AbstractResult { - private IKSegmenter seg; + private final IKSegmenter seg; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerWord.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerWord.java index ab062f93c..e63fee809 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerWord.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/ikanalyzer/IKAnalyzerWord.java @@ -13,7 +13,7 @@ import cn.hutool.extra.tokenizer.Word; public class IKAnalyzerWord implements Word { private static final long serialVersionUID = 1L; - private Lexeme word; + private final Lexeme word; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegEngine.java index 50c1b1219..98981dd1c 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegEngine.java @@ -1,17 +1,16 @@ package cn.hutool.extra.tokenizer.engine.jcseg; -import java.io.IOException; -import java.io.StringReader; - +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.tokenizer.Result; +import cn.hutool.extra.tokenizer.TokenizerEngine; +import cn.hutool.extra.tokenizer.TokenizerException; import org.lionsoul.jcseg.ISegment; import org.lionsoul.jcseg.dic.ADictionary; import org.lionsoul.jcseg.dic.DictionaryFactory; import org.lionsoul.jcseg.segmenter.SegmenterConfig; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.tokenizer.TokenizerEngine; -import cn.hutool.extra.tokenizer.Result; -import cn.hutool.extra.tokenizer.TokenizerException; +import java.io.IOException; +import java.io.StringReader; /** * Jcseg分词引擎实现
    @@ -22,7 +21,7 @@ import cn.hutool.extra.tokenizer.TokenizerException; */ public class JcsegEngine implements TokenizerEngine { - private ISegment segment; + private final ISegment segment; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegResult.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegResult.java index b41c7f856..a0fcd86a9 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegResult.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jcseg/JcsegResult.java @@ -19,7 +19,7 @@ import java.util.NoSuchElementException; */ public class JcsegResult implements Result{ - private ISegment result; + private final ISegment result; private Word cachedWord; /** diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jieba/JiebaEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jieba/JiebaEngine.java index 6c7897194..59afb6fed 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jieba/JiebaEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/jieba/JiebaEngine.java @@ -16,8 +16,8 @@ import cn.hutool.extra.tokenizer.Result; */ public class JiebaEngine implements TokenizerEngine { - private JiebaSegmenter jiebaSegmenter; - private SegMode mode; + private final JiebaSegmenter jiebaSegmenter; + private final SegMode mode; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mmseg/MmsegEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mmseg/MmsegEngine.java index 8f1bcd1a1..ba9736013 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mmseg/MmsegEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mmseg/MmsegEngine.java @@ -1,14 +1,13 @@ package cn.hutool.extra.tokenizer.engine.mmseg; -import java.io.StringReader; - +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.tokenizer.Result; +import cn.hutool.extra.tokenizer.TokenizerEngine; import com.chenlb.mmseg4j.ComplexSeg; import com.chenlb.mmseg4j.Dictionary; import com.chenlb.mmseg4j.MMSeg; -import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.tokenizer.TokenizerEngine; -import cn.hutool.extra.tokenizer.Result; +import java.io.StringReader; /** * mmseg4j分词引擎实现
    @@ -19,7 +18,7 @@ import cn.hutool.extra.tokenizer.Result; */ public class MmsegEngine implements TokenizerEngine { - private MMSeg mmSeg; + private final MMSeg mmSeg; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mmseg/MmsegResult.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mmseg/MmsegResult.java index 6192ce4c8..d4e67aa3c 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mmseg/MmsegResult.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mmseg/MmsegResult.java @@ -1,12 +1,11 @@ package cn.hutool.extra.tokenizer.engine.mmseg; -import java.io.IOException; - -import com.chenlb.mmseg4j.MMSeg; - import cn.hutool.extra.tokenizer.AbstractResult; import cn.hutool.extra.tokenizer.TokenizerException; import cn.hutool.extra.tokenizer.Word; +import com.chenlb.mmseg4j.MMSeg; + +import java.io.IOException; /** * mmseg4j分词结果实现
    @@ -17,7 +16,7 @@ import cn.hutool.extra.tokenizer.Word; */ public class MmsegResult extends AbstractResult { - private MMSeg mmSeg; + private final MMSeg mmSeg; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mynlp/MynlpEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mynlp/MynlpEngine.java index 640a7defc..da1ef98db 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mynlp/MynlpEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mynlp/MynlpEngine.java @@ -17,7 +17,7 @@ import cn.hutool.extra.tokenizer.TokenizerEngine; */ public class MynlpEngine implements TokenizerEngine { - private Lexer lexer; + private final Lexer lexer; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mynlp/MynlpResult.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mynlp/MynlpResult.java index 5fc0f73ea..bbef6ea36 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mynlp/MynlpResult.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/mynlp/MynlpResult.java @@ -1,12 +1,11 @@ package cn.hutool.extra.tokenizer.engine.mynlp; -import java.util.Iterator; - -import com.mayabot.nlp.segment.Sentence; -import com.mayabot.nlp.segment.WordTerm; - import cn.hutool.extra.tokenizer.Result; import cn.hutool.extra.tokenizer.Word; +import com.mayabot.nlp.segment.Sentence; +import com.mayabot.nlp.segment.WordTerm; + +import java.util.Iterator; /** * MYNLP 中文NLP工具包分词结果实现
    @@ -17,7 +16,7 @@ import cn.hutool.extra.tokenizer.Word; */ public class MynlpResult implements Result { - private Iterator result; + private final Iterator result; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/word/WordEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/word/WordEngine.java index 8a1823c01..dcb954fdb 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/word/WordEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/word/WordEngine.java @@ -17,7 +17,7 @@ import cn.hutool.extra.tokenizer.TokenizerEngine; */ public class WordEngine implements TokenizerEngine { - private Segmentation segmentation; + private final Segmentation segmentation; /** * 构造 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/word/WordResult.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/word/WordResult.java index 49f680ea0..032a4a112 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/word/WordResult.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/word/WordResult.java @@ -1,11 +1,11 @@ package cn.hutool.extra.tokenizer.engine.word; -import java.util.Iterator; -import java.util.List; - import cn.hutool.extra.tokenizer.Result; import cn.hutool.extra.tokenizer.Word; +import java.util.Iterator; +import java.util.List; + /** * Word分词结果实现
    * 项目地址:https://github.com/ysc/word @@ -15,7 +15,7 @@ import cn.hutool.extra.tokenizer.Word; */ public class WordResult implements Result{ - private Iterator wordIter; + private final Iterator wordIter; /** * 构造 diff --git a/hutool-http/src/main/java/cn/hutool/http/ContentType.java b/hutool-http/src/main/java/cn/hutool/http/ContentType.java index b7398cd39..1eb1a719b 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ContentType.java +++ b/hutool-http/src/main/java/cn/hutool/http/ContentType.java @@ -41,7 +41,7 @@ public enum ContentType { */ TEXT_HTML("text/html"); - private String value; + private final String value; ContentType(String value) { this.value = value; diff --git a/hutool-http/src/main/java/cn/hutool/http/Header.java b/hutool-http/src/main/java/cn/hutool/http/Header.java index a9385d37d..1c90606c5 100644 --- a/hutool-http/src/main/java/cn/hutool/http/Header.java +++ b/hutool-http/src/main/java/cn/hutool/http/Header.java @@ -121,7 +121,7 @@ public enum Header { */ LOCATION("Location"); - private String value; + private final String value; Header(String value) { this.value = value; diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpConnection.java b/hutool-http/src/main/java/cn/hutool/http/HttpConnection.java index bd73c99b2..183bbee58 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpConnection.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpConnection.java @@ -33,8 +33,8 @@ import java.util.Map.Entry; */ public class HttpConnection { - private URL url; - private Proxy proxy; + private final URL url; + private final Proxy proxy; private HttpURLConnection conn; /** diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpInputStream.java b/hutool-http/src/main/java/cn/hutool/http/HttpInputStream.java index 09ca2fe44..9a51a9a62 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpInputStream.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpInputStream.java @@ -1,5 +1,7 @@ package cn.hutool.http; +import cn.hutool.core.util.StrUtil; + import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; import java.io.IOException; @@ -8,8 +10,6 @@ import java.util.zip.GZIPInputStream; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; -import cn.hutool.core.util.StrUtil; - /** * HTTP输入流,此流用于包装Http请求响应内容的流,用于解析各种压缩、分段的响应流内容 * @@ -79,11 +79,10 @@ public class HttpInputStream extends InputStream { try { this.in = (response.status < HttpStatus.HTTP_BAD_REQUEST) ? response.httpConnection.getInputStream() : response.httpConnection.getErrorStream(); } catch (IOException e) { - if (e instanceof FileNotFoundException) { - // 服务器无返回内容,忽略之 - } else { + if (false == (e instanceof FileNotFoundException)) { throw new HttpException(e); } + // 服务器无返回内容,忽略之 } // 在一些情况下,返回的流为null,此时提供状态码说明 diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java index f309b8669..dea5e40d7 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java @@ -43,7 +43,7 @@ public class HttpResponse extends HttpBase implements Closeable { /** 响应状态码 */ protected int status; /** 是否忽略读取Http响应体 */ - private boolean ignoreBody; + private final boolean ignoreBody; /** 从响应中获取的编码 */ private Charset charsetFromResponse; diff --git a/hutool-http/src/main/java/cn/hutool/http/server/action/RootAction.java b/hutool-http/src/main/java/cn/hutool/http/server/action/RootAction.java index 1444cdbb6..d7e8836f8 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/action/RootAction.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/action/RootAction.java @@ -19,7 +19,7 @@ public class RootAction implements Action { public static final String DEFAULT_INDEX_FILE_NAME = "index.html"; private final String rootDir; - private List indexFileNames; + private final List indexFileNames; /** * 构造 diff --git a/hutool-http/src/main/java/cn/hutool/http/ssl/CustomProtocolsSSLFactory.java b/hutool-http/src/main/java/cn/hutool/http/ssl/CustomProtocolsSSLFactory.java index 90ce12fb0..e1630541b 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ssl/CustomProtocolsSSLFactory.java +++ b/hutool-http/src/main/java/cn/hutool/http/ssl/CustomProtocolsSSLFactory.java @@ -2,15 +2,14 @@ package cn.hutool.http.ssl; import cn.hutool.core.util.ArrayUtil; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; - /** * 自定义支持协议类型的SSLSocketFactory * @@ -18,8 +17,8 @@ import javax.net.ssl.SSLSocketFactory; */ public class CustomProtocolsSSLFactory extends SSLSocketFactory { - private String[] protocols; - private SSLSocketFactory base; + private final String[] protocols; + private final SSLSocketFactory base; /** * 构造 diff --git a/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentInfo.java b/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentInfo.java index a16ecb60f..862c2183b 100644 --- a/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentInfo.java +++ b/hutool-http/src/main/java/cn/hutool/http/useragent/UserAgentInfo.java @@ -1,9 +1,9 @@ package cn.hutool.http.useragent; -import java.util.regex.Pattern; - import cn.hutool.core.util.ReUtil; +import java.util.regex.Pattern; + /** * User-agent信息 * @@ -15,9 +15,9 @@ public class UserAgentInfo { public static final String NameUnknown = "Unknown"; /** 信息名称 */ - private String name; + private final String name; /** 信息匹配模式 */ - private Pattern pattern; + private final Pattern pattern; /** * 构造 diff --git a/hutool-http/src/main/java/cn/hutool/http/webservice/SoapClient.java b/hutool-http/src/main/java/cn/hutool/http/webservice/SoapClient.java index 5fedf4589..6c5794f49 100644 --- a/hutool-http/src/main/java/cn/hutool/http/webservice/SoapClient.java +++ b/hutool-http/src/main/java/cn/hutool/http/webservice/SoapClient.java @@ -1,11 +1,15 @@ package cn.hutool.http.webservice; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.XmlUtil; +import cn.hutool.http.HttpGlobalConfig; +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; import javax.xml.XMLConstants; import javax.xml.namespace.QName; @@ -18,17 +22,12 @@ import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPHeaderElement; import javax.xml.soap.SOAPMessage; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.XmlUtil; -import cn.hutool.http.HttpGlobalConfig; -import cn.hutool.http.HttpRequest; -import cn.hutool.http.HttpResponse; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; /** * SOAP客户端 @@ -91,7 +90,7 @@ public class SoapClient { /** * 应用于方法上的命名空间URI */ - private String namespaceURI; + private final String namespaceURI; /** * 创建SOAP客户端,默认使用soap1.1版本协议 diff --git a/hutool-http/src/main/java/cn/hutool/http/webservice/SoapProtocol.java b/hutool-http/src/main/java/cn/hutool/http/webservice/SoapProtocol.java index 22900612d..8eee3c5d4 100644 --- a/hutool-http/src/main/java/cn/hutool/http/webservice/SoapProtocol.java +++ b/hutool-http/src/main/java/cn/hutool/http/webservice/SoapProtocol.java @@ -23,7 +23,7 @@ public enum SoapProtocol { this.value = value; } - private String value; + private final String value; /** * 获取版本值信息 diff --git a/hutool-http/src/test/java/cn/hutool/http/test/DownloadTest.java b/hutool-http/src/test/java/cn/hutool/http/test/DownloadTest.java index 8cf664711..229ce0857 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/DownloadTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/DownloadTest.java @@ -44,7 +44,7 @@ public class DownloadTest { // 带进度显示的文件下载 HttpUtil.downloadFile("http://mirrors.sohu.com/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-1810.iso", FileUtil.file("d:/"), new StreamProgress() { - long time = System.currentTimeMillis(); + final long time = System.currentTimeMillis(); @Override public void start() { diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 2d2c4d316..7efe4ace8 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -25,16 +25,12 @@ public class JSONNull implements Serializable{ * @param object An object to test for nullness. * @return true if the object parameter is the JSONObject.NULL object or null. */ + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") @Override public boolean equals(Object object) { - return object == null || (object instanceof JSONNull && object == this); + return object == null || (object == this); } - @Override - public int hashCode() { - return super.hashCode(); - } - /** * Get the "null" string value. *获得“null”字符串 diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java index 43ee692b1..30a940f25 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java @@ -40,7 +40,7 @@ public class JSONTokener { /** * 源 */ - private Reader reader; + private final Reader reader; /** * JSON配置 diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java index 9d5b70401..b6d0f0c51 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java @@ -1,14 +1,5 @@ package cn.hutool.json; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Console; @@ -17,6 +8,14 @@ import cn.hutool.core.util.CharsetUtil; import cn.hutool.json.test.bean.Exam; import cn.hutool.json.test.bean.JsonNode; import cn.hutool.json.test.bean.KeyBean; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * JSONArray单元测试 @@ -121,6 +120,7 @@ public class JSONArrayTest { String jsonStr = FileUtil.readString("exam_test.json", CharsetUtil.CHARSET_UTF_8); JSONArray array = JSONUtil.parseArray(jsonStr); + //noinspection SuspiciousToArrayCall Exam[] list = array.toArray(new Exam[0]); Assert.assertNotEquals(0, list.length); Assert.assertEquals(Exam.class, list[0].getClass()); diff --git a/hutool-json/src/test/java/cn/hutool/json/test/bean/ResultDto.java b/hutool-json/src/test/java/cn/hutool/json/test/bean/ResultDto.java index 8cc3662ae..40948f466 100644 --- a/hutool-json/src/test/java/cn/hutool/json/test/bean/ResultDto.java +++ b/hutool-json/src/test/java/cn/hutool/json/test/bean/ResultDto.java @@ -74,7 +74,7 @@ public class ResultDto implements Serializable { * @param message the message * @param result the result */ - ResultDto(int code, String message, T result) { + public ResultDto(int code, String message, T result) { super(); this.code(code).message(message).result(result); } diff --git a/hutool-log/src/main/java/cn/hutool/log/LogFactory.java b/hutool-log/src/main/java/cn/hutool/log/LogFactory.java index 01b830ecb..1730230a1 100644 --- a/hutool-log/src/main/java/cn/hutool/log/LogFactory.java +++ b/hutool-log/src/main/java/cn/hutool/log/LogFactory.java @@ -24,7 +24,7 @@ public abstract class LogFactory { /** * 日志对象缓存 */ - private Map logCache; + private final Map logCache; /** * 构造 diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java b/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java index e8f979bbc..4de619a00 100644 --- a/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java +++ b/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java @@ -19,7 +19,7 @@ public class ConsoleLog extends AbstractLog { private static final String logFormat = "[{date}] [{level}] {name}: {msg}"; private static Level currentLevel = Level.DEBUG; - private String name; + private final String name; //------------------------------------------------------------------------- Constructor diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/slf4j/Slf4jLog.java b/hutool-log/src/main/java/cn/hutool/log/dialect/slf4j/Slf4jLog.java index 99a02b3bb..708ec2417 100644 --- a/hutool-log/src/main/java/cn/hutool/log/dialect/slf4j/Slf4jLog.java +++ b/hutool-log/src/main/java/cn/hutool/log/dialect/slf4j/Slf4jLog.java @@ -161,7 +161,6 @@ public class Slf4jLog extends AbstractLog { * @param t 异常 * @param msgTemplate 消息模板 * @param arguments 参数 - * @return 是否支持 LocationAwareLogger对象,如果不支持需要日志方法调用被包装类的相应方法 */ private void locationAwareLog(LocationAwareLogger logger, String fqcn, int level_int, Throwable t, String msgTemplate, Object[] arguments) { // ((LocationAwareLogger)this.logger).log(null, fqcn, level_int, msgTemplate, arguments, t); diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog.java b/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog.java index 9c82d3a32..945527f43 100644 --- a/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog.java +++ b/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog.java @@ -20,8 +20,8 @@ public class TinyLog extends AbstractLog { /** 堆栈增加层数,因为封装因此多了两层,此值用于正确获取当前类名 */ private static final int DEPTH = 4; - private int level; - private String name; + private final int level; + private final String name; // ------------------------------------------------------------------------- Constructor public TinyLog(Class clazz) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java index 9a5e08587..cf36d5480 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/StyleSet.java @@ -1,7 +1,6 @@ package cn.hutool.poi.excel; -import java.io.Serializable; - +import cn.hutool.poi.excel.style.StyleUtil; import org.apache.poi.ss.usermodel.BorderStyle; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.FillPatternType; @@ -11,7 +10,7 @@ import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.VerticalAlignment; import org.apache.poi.ss.usermodel.Workbook; -import cn.hutool.poi.excel.style.StyleUtil; +import java.io.Serializable; /** * 样式集合,此样式集合汇集了整个工作簿的样式,用于减少样式的创建和冗余 @@ -23,7 +22,7 @@ public class StyleSet implements Serializable{ private static final long serialVersionUID = 1L; /** 工作簿引用 */ - private Workbook workbook; + private final Workbook workbook; /** 标题样式 */ protected CellStyle headCellStyle; /** 默认样式 */ diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel03SaxReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel03SaxReader.java index f3d97ccd3..d8d68103b 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel03SaxReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel03SaxReader.java @@ -45,7 +45,7 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i /** * 如果为公式,true表示输出公式计算后的结果值,false表示输出公式本身 */ - private boolean isOutputFormulaValues = true; + private final boolean isOutputFormulaValues = true; /** * 用于解析公式 @@ -66,7 +66,7 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i /** * Sheet边界记录,此Record中可以获得Sheet名 */ - private List boundSheetRecords = new ArrayList<>(); + private final List boundSheetRecords = new ArrayList<>(); private boolean isOutputNextStringRecord; @@ -80,7 +80,7 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i // 当前表索引 private int curRid = -1; - private RowHandler rowHandler; + private final RowHandler rowHandler; /** * 构造 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java index 685841be1..a8ec63665 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java @@ -57,7 +57,7 @@ public class Excel07SaxReader extends AbstractExcelSaxReader i // 当前列 private int curCell; // 上一次的内容 - private StringBuilder lastContent = new StringBuilder(64); + private final StringBuilder lastContent = new StringBuilder(64); // 单元数据类型 private CellDataType cellDataType; // 当前列坐标, 如A1,B5 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java index 41d44c485..6ac83eee9 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/PicType.java @@ -28,7 +28,7 @@ public enum PicType { this.value = value; } - private int value; + private final int value; /** * 获取图片类型对应值 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/word/Word07Writer.java b/hutool-poi/src/main/java/cn/hutool/poi/word/Word07Writer.java index 89492a2a2..0eadaba39 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/word/Word07Writer.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/word/Word07Writer.java @@ -28,7 +28,7 @@ import java.io.OutputStream; */ public class Word07Writer implements Closeable { - private XWPFDocument doc; + private final XWPFDocument doc; /** * 目标文件 */ diff --git a/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java b/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java index 637fba93c..acf952944 100644 --- a/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java +++ b/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java @@ -18,8 +18,8 @@ import javax.script.ScriptException; */ public class ScriptUtil { - private static final ScriptEngineManager manager = new ScriptEngineManager(); - private static SimpleCache cache = new SimpleCache<>(); + private static final ScriptEngineManager MANAGER = new ScriptEngineManager(); + private static final SimpleCache CACHE = new SimpleCache<>(); /** * 获得单例的{@link ScriptEngine} 实例 @@ -28,7 +28,7 @@ public class ScriptUtil { * @return {@link ScriptEngine} 实例 */ public static ScriptEngine getScript(String nameOrExtOrMime) { - return cache.get(nameOrExtOrMime, ()-> createScript(nameOrExtOrMime)); + return CACHE.get(nameOrExtOrMime, ()-> createScript(nameOrExtOrMime)); } /** @@ -39,12 +39,12 @@ public class ScriptUtil { * @since 5.2.6 */ public static ScriptEngine createScript(String nameOrExtOrMime) { - ScriptEngine engine = manager.getEngineByName(nameOrExtOrMime); + ScriptEngine engine = MANAGER.getEngineByName(nameOrExtOrMime); if (null == engine) { - engine = manager.getEngineByExtension(nameOrExtOrMime); + engine = MANAGER.getEngineByExtension(nameOrExtOrMime); } if (null == engine) { - engine = manager.getEngineByMimeType(nameOrExtOrMime); + engine = MANAGER.getEngineByMimeType(nameOrExtOrMime); } if (null == engine) { throw new NullPointerException(StrUtil.format("Script for [{}] not support !", nameOrExtOrMime)); diff --git a/hutool-setting/src/main/java/cn/hutool/setting/GroupedSet.java b/hutool-setting/src/main/java/cn/hutool/setting/GroupedSet.java index 13adfaf2e..59266129d 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/GroupedSet.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/GroupedSet.java @@ -1,5 +1,12 @@ package cn.hutool.setting; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; + import java.io.BufferedReader; import java.io.File; import java.io.IOException; @@ -13,13 +20,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Set; -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; - /** * 分组化的Set集合类
    * 在配置文件中可以用中括号分隔不同的分组,每个分组会放在独立的Set中,用group区别
    @@ -94,9 +94,6 @@ public class GroupedSet extends HashMap> { throw new RuntimeException("Null GroupSet file!"); } final URL url = URLUtil.getURL(configFile); - if (url == null) { - throw new RuntimeException(StrUtil.format("Can not find GroupSet file: [{}]", configFile.getAbsolutePath())); - } this.init(url, charset); } diff --git a/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java b/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java index 5a7f3eea3..542bceeaa 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/SettingLoader.java @@ -8,7 +8,6 @@ import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.log.Log; -import cn.hutool.log.LogFactory; import java.io.BufferedReader; import java.io.IOException; @@ -28,7 +27,7 @@ import java.util.Set; * */ public class SettingLoader { - private static Log log = LogFactory.get(); + private static final Log log = Log.get(); /** 注释符号(当有此符号在行首,表示此行为注释) */ private final static char COMMENT_FLAG_PRE = '#'; @@ -38,11 +37,11 @@ public class SettingLoader { private String varRegex = "\\$\\{(.*?)\\}"; /** 本设置对象的字符集 */ - private Charset charset; + private final Charset charset; /** 是否使用变量 */ - private boolean isUseVariable; + private final boolean isUseVariable; /** GroupedMap */ - private GroupedMap groupedMap; + private final GroupedMap groupedMap; /** * 构造 diff --git a/hutool-setting/src/main/java/cn/hutool/setting/SettingUtil.java b/hutool-setting/src/main/java/cn/hutool/setting/SettingUtil.java index c986f8aa1..44c7ba089 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/SettingUtil.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/SettingUtil.java @@ -17,8 +17,7 @@ public class SettingUtil { /** * 配置文件缓存 */ - private static Map settingMap = new ConcurrentHashMap<>(); - private static final Object lock = new Object(); + private static final Map SETTING_MAP = new ConcurrentHashMap<>(); /** * 获取当前环境下的配置文件
    @@ -28,10 +27,10 @@ public class SettingUtil { * @return 当前环境下配置文件 */ public static Setting get(String name) { - Setting setting = settingMap.get(name); + Setting setting = SETTING_MAP.get(name); if (null == setting) { - synchronized (lock) { - setting = settingMap.get(name); + synchronized (SettingUtil.class) { + setting = SETTING_MAP.get(name); if (null == setting) { String filePath = name; String extName = FileUtil.extName(filePath); @@ -39,7 +38,7 @@ public class SettingUtil { filePath = filePath + "." + Setting.EXT_NAME; } setting = new Setting(filePath, true); - settingMap.put(name, setting); + SETTING_MAP.put(name, setting); } } } diff --git a/hutool-setting/src/main/java/cn/hutool/setting/dialect/PropsUtil.java b/hutool-setting/src/main/java/cn/hutool/setting/dialect/PropsUtil.java index e3f99f34b..31cfed5ec 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/dialect/PropsUtil.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/dialect/PropsUtil.java @@ -19,7 +19,7 @@ public class PropsUtil { /** * 配置文件缓存 */ - private static Map propsMap = new ConcurrentHashMap<>(); + private static final Map propsMap = new ConcurrentHashMap<>(); private static final Object lock = new Object(); /** diff --git a/hutool-setting/src/main/java/cn/hutool/setting/profile/Profile.java b/hutool-setting/src/main/java/cn/hutool/setting/profile/Profile.java index 2505a26bc..a3e0c7495 100644 --- a/hutool-setting/src/main/java/cn/hutool/setting/profile/Profile.java +++ b/hutool-setting/src/main/java/cn/hutool/setting/profile/Profile.java @@ -1,14 +1,14 @@ package cn.hutool.setting.profile; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; +import cn.hutool.setting.Setting; + import java.io.Serializable; import java.nio.charset.Charset; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.StrUtil; -import cn.hutool.setting.Setting; - /** * Profile可以让我们定义一系列的配置信息,然后指定其激活条件。
    * 此类中我们规范一套规则如下:
    @@ -36,7 +36,7 @@ public class Profile implements Serializable { /** 是否使用变量 */ private boolean useVar; /** 配置文件缓存 */ - private Map settingMap = new ConcurrentHashMap<>(); + private final Map settingMap = new ConcurrentHashMap<>(); // -------------------------------------------------------------------------------- Constructor start /** diff --git a/hutool-setting/src/test/java/cn/hutool/setting/test/SettingUtilTest.java b/hutool-setting/src/test/java/cn/hutool/setting/test/SettingUtilTest.java index d714a7cfb..768f5fbc7 100644 --- a/hutool-setting/src/test/java/cn/hutool/setting/test/SettingUtilTest.java +++ b/hutool-setting/src/test/java/cn/hutool/setting/test/SettingUtilTest.java @@ -21,7 +21,9 @@ public class SettingUtilTest { @Test public void getFirstFoundTest() { - String driver = SettingUtil.getFirstFound("test2", "test").get("demo", "driver"); + //noinspection ConstantConditions + String driver = SettingUtil.getFirstFound("test2", "test") + .get("demo", "driver"); Assert.assertEquals("com.mysql.jdbc.Driver", driver); } } diff --git a/hutool-socket/src/main/java/cn/hutool/socket/SocketConfig.java b/hutool-socket/src/main/java/cn/hutool/socket/SocketConfig.java index 7a43611ee..6984fd519 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/SocketConfig.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/SocketConfig.java @@ -1,8 +1,9 @@ package cn.hutool.socket; -import java.io.Serializable; - import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.RuntimeUtil; + +import java.io.Serializable; /** * Socket通讯配置 @@ -14,7 +15,7 @@ public class SocketConfig implements Serializable{ private static final long serialVersionUID = 1L; /** CPU核心数 */ - private static int CPU_COUNT = Runtime.getRuntime().availableProcessors(); + private static final int CPU_COUNT = RuntimeUtil.getProcessorCount(); /** 共享线程池大小,此线程池用于接收和处理用户连接 */ private int threadPoolSize = CPU_COUNT; diff --git a/hutool-socket/src/main/java/cn/hutool/socket/nio/NioServer.java b/hutool-socket/src/main/java/cn/hutool/socket/nio/NioServer.java index c5c002e3c..6ab08524e 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/nio/NioServer.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/nio/NioServer.java @@ -1,5 +1,8 @@ package cn.hutool.socket.nio; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; + import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; @@ -11,9 +14,6 @@ import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.io.IoUtil; - /** * 基于NIO的Socket服务端实现 * @@ -130,7 +130,7 @@ public abstract class NioServer implements Closeable { } @Override - public void close() throws IOException { + public void close() { IoUtil.close(this.selector); IoUtil.close(this.serverSocketChannel); } @@ -166,6 +166,7 @@ public abstract class NioServer implements Closeable { try { channel.configureBlocking(false); // 注册通道 + //noinspection MagicConstant channel.register(selector, ops.getValue()); } catch (IOException e) { throw new IORuntimeException(e); diff --git a/hutool-socket/src/main/java/cn/hutool/socket/nio/Operation.java b/hutool-socket/src/main/java/cn/hutool/socket/nio/Operation.java index b3fb4a033..66d414bc6 100644 --- a/hutool-socket/src/main/java/cn/hutool/socket/nio/Operation.java +++ b/hutool-socket/src/main/java/cn/hutool/socket/nio/Operation.java @@ -18,7 +18,7 @@ public enum Operation { /** 接受连接操作 */ ACCEPT(SelectionKey.OP_ACCEPT); - private int value; + private final int value; /** * 构造 diff --git a/hutool-system/src/main/java/cn/hutool/system/HostInfo.java b/hutool-system/src/main/java/cn/hutool/system/HostInfo.java index d536251b6..6446a9161 100644 --- a/hutool-system/src/main/java/cn/hutool/system/HostInfo.java +++ b/hutool-system/src/main/java/cn/hutool/system/HostInfo.java @@ -1,10 +1,10 @@ package cn.hutool.system; +import cn.hutool.core.net.NetUtil; + import java.io.Serializable; import java.net.InetAddress; -import cn.hutool.core.net.NetUtil; - /** * 代表当前主机的信息。 */ @@ -16,8 +16,13 @@ public class HostInfo implements Serializable { public HostInfo() { final InetAddress localhost = NetUtil.getLocalhost(); - HOST_NAME = localhost.getHostName(); - HOST_ADDRESS = localhost.getHostAddress(); + if(null != localhost){ + HOST_NAME = localhost.getHostName(); + HOST_ADDRESS = localhost.getHostAddress(); + } else{ + HOST_NAME = null; + HOST_ADDRESS = null; + } } /** diff --git a/hutool-system/src/main/java/cn/hutool/system/JavaInfo.java b/hutool-system/src/main/java/cn/hutool/system/JavaInfo.java index 6776f672e..a74d625ab 100644 --- a/hutool-system/src/main/java/cn/hutool/system/JavaInfo.java +++ b/hutool-system/src/main/java/cn/hutool/system/JavaInfo.java @@ -1,5 +1,6 @@ package cn.hutool.system; +import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ReUtil; import java.io.Serializable; @@ -7,7 +8,7 @@ import java.io.Serializable; /** * 代表Java Implementation的信息。 */ -public class JavaInfo implements Serializable{ +public class JavaInfo implements Serializable { private static final long serialVersionUID = 1L; private final String JAVA_VERSION = SystemUtil.get("java.version", false); @@ -32,13 +33,11 @@ public class JavaInfo implements Serializable{ /** * 取得当前Java impl.的版本(取自系统属性:java.version)。 - * + * *

    * 例如Sun JDK 1.4.2:"1.4.2" - * - * + * * @return 属性值,如果不能取得(因为Java安全限制)或值不存在,则返回null。 - * * @since Java 1.1 */ public final String getVersion() { @@ -47,15 +46,15 @@ public class JavaInfo implements Serializable{ /** * 取得当前Java impl.的版本(取自系统属性:java.version)。 - * + * *

    * 例如: - * + * *

      *
    • JDK 1.2:1.2f
    • *
    • JDK 1.3.1:1.31f
    • *
    - * + * * @return 属性值,如果不能取得(因为Java安全限制)或值不存在,则返回0。 */ public final float getVersionFloat() { @@ -64,19 +63,17 @@ public class JavaInfo implements Serializable{ /** * 取得当前Java impl.的版本(取自系统属性:java.version),java10及其之后的版本返回值为4位。 - * + * *

    * 例如: - * + * *

      *
    • JDK 1.2:120
    • *
    • JDK 1.3.1:131
    • *
    • JDK 11.0.2:1102
    • *
    - * - * + * * @return 属性值,如果不能取得(因为Java安全限制)或值不存在,则返回0。 - * * @since Java 1.1 */ public final int getVersionInt() { @@ -85,7 +82,7 @@ public class JavaInfo implements Serializable{ /** * 取得当前Java impl.的版本的float值。 - * + * * @return Java版本的float值或0 */ private float getJavaVersionAsFloat() { @@ -95,14 +92,14 @@ public class JavaInfo implements Serializable{ String str = JAVA_VERSION; - str = ReUtil.get("^[0-9]{1,2}(\\.[0-9]{1,2})?", str,0); + str = ReUtil.get("^[0-9]{1,2}(\\.[0-9]{1,2})?", str, 0); - return Float.parseFloat(str); + return Float.parseFloat(str); } /** * 取得当前Java impl.的版本的int值。 - * + * * @return Java版本的int值或0 */ private int getJavaVersionAsInt() { @@ -110,22 +107,14 @@ public class JavaInfo implements Serializable{ return 0; } - String java_version = JAVA_VERSION; + String javaVersion = ReUtil.get("^[0-9]{1,2}(\\.[0-9]{1,2}){0,2}", JAVA_VERSION, 0); - java_version = ReUtil.get("^[0-9]{1,2}(\\.[0-9]{1,2}){0,2}", java_version,0); + String[] split = javaVersion.split("\\."); + String result = ArrayUtil.join(split, ""); - String[] split = java_version.split("\\."); - - String result = ""; - - for (int i = 0; i < split.length; i++) { - result = result + split[i]; - } - - //保证java10及其之后的版本返回的值为4位 - if (split[0].length()>1 && result.length()!=4){ - result = result + "0000"; - result = result.substring(0,4); + //保证java10及其之后的版本返回的值为4位 + if (split[0].length() > 1) { + result = (result + "0000").substring(0, 4); } return Integer.parseInt(result); @@ -133,13 +122,11 @@ public class JavaInfo implements Serializable{ /** * 取得当前Java impl.的厂商(取自系统属性:java.vendor)。 - * + * *

    * 例如Sun JDK 1.4.2:"Sun Microsystems Inc." - * - * + * * @return 属性值,如果不能取得(因为Java安全限制)或值不存在,则返回null。 - * * @since Java 1.1 */ public final String getVendor() { @@ -148,13 +135,11 @@ public class JavaInfo implements Serializable{ /** * 取得当前Java impl.的厂商网站的URL(取自系统属性:java.vendor.url)。 - * + * *

    * 例如Sun JDK 1.4.2:"http://java.sun.com/" - * - * + * * @return 属性值,如果不能取得(因为Java安全限制)或值不存在,则返回null。 - * * @since Java 1.1 */ public final String getVendorURL() { @@ -163,11 +148,10 @@ public class JavaInfo implements Serializable{ /** * 判断当前Java的版本。 - * + * *

    * 如果不能取得系统属性java.version(因为Java安全限制),则总是返回 false - * - * + * * @return 如果当前Java版本为1.1,则返回true */ public final boolean isJava1_1() { @@ -176,11 +160,10 @@ public class JavaInfo implements Serializable{ /** * 判断当前Java的版本。 - * + * *

    * 如果不能取得系统属性java.version(因为Java安全限制),则总是返回 false - * - * + * * @return 如果当前Java版本为1.2,则返回true */ public final boolean isJava1_2() { @@ -189,11 +172,10 @@ public class JavaInfo implements Serializable{ /** * 判断当前Java的版本。 - * + * *

    * 如果不能取得系统属性java.version(因为Java安全限制),则总是返回 false - * - * + * * @return 如果当前Java版本为1.3,则返回true */ public final boolean isJava1_3() { @@ -202,11 +184,10 @@ public class JavaInfo implements Serializable{ /** * 判断当前Java的版本。 - * + * *

    * 如果不能取得系统属性java.version(因为Java安全限制),则总是返回 false - * - * + * * @return 如果当前Java版本为1.4,则返回true */ public final boolean isJava1_4() { @@ -215,11 +196,10 @@ public class JavaInfo implements Serializable{ /** * 判断当前Java的版本。 - * + * *

    * 如果不能取得系统属性java.version(因为Java安全限制),则总是返回 false - * - * + * * @return 如果当前Java版本为1.5,则返回true */ public final boolean isJava1_5() { @@ -228,11 +208,10 @@ public class JavaInfo implements Serializable{ /** * 判断当前Java的版本。 - * + * *

    * 如果不能取得系统属性java.version(因为Java安全限制),则总是返回 false - * - * + * * @return 如果当前Java版本为1.6,则返回true */ public final boolean isJava1_6() { @@ -241,11 +220,10 @@ public class JavaInfo implements Serializable{ /** * 判断当前Java的版本。 - * + * *

    * 如果不能取得系统属性java.version(因为Java安全限制),则总是返回 false - * - * + * * @return 如果当前Java版本为1.7,则返回true */ public final boolean isJava1_7() { @@ -257,7 +235,6 @@ public class JavaInfo implements Serializable{ * *

    * 如果不能取得系统属性java.version(因为Java安全限制),则总是返回 false - * * * @return 如果当前Java版本为1.8,则返回true */ @@ -271,7 +248,6 @@ public class JavaInfo implements Serializable{ *

    * 如果不能取得系统属性java.version(因为Java安全限制),则总是返回 false * - * * @return 如果当前Java版本为9,则返回true */ public final boolean isJava9() { @@ -284,7 +260,6 @@ public class JavaInfo implements Serializable{ *

    * 如果不能取得系统属性java.version(因为Java安全限制),则总是返回 false * - * * @return 如果当前Java版本为10,则返回true */ public final boolean isJava10() { @@ -297,7 +272,6 @@ public class JavaInfo implements Serializable{ *

    * 如果不能取得系统属性java.version(因为Java安全限制),则总是返回 false * - * * @return 如果当前Java版本为11,则返回true */ public final boolean isJava11() { @@ -310,7 +284,6 @@ public class JavaInfo implements Serializable{ *

    * 如果不能取得系统属性java.version(因为Java安全限制),则总是返回 false * - * * @return 如果当前Java版本为12,则返回true */ public final boolean isJava12() { @@ -319,9 +292,8 @@ public class JavaInfo implements Serializable{ /** * 匹配当前Java的版本。 - * + * * @param versionPrefix Java版本前缀 - * * @return 如果版本匹配,则返回true */ private boolean getJavaVersionMatches(String versionPrefix) { @@ -334,19 +306,17 @@ public class JavaInfo implements Serializable{ /** * 判定当前Java的版本是否大于等于指定的版本号。 - * + * *

    * 例如: - * - * + * + * *

      *
    • 测试JDK 1.2:isJavaVersionAtLeast(1.2f)
    • *
    • 测试JDK 1.2.1:isJavaVersionAtLeast(1.31f)
    • *
    - * - * + * * @param requiredVersion 需要的版本 - * * @return 如果当前Java版本大于或等于指定的版本,则返回true */ public final boolean isJavaVersionAtLeast(float requiredVersion) { @@ -355,19 +325,17 @@ public class JavaInfo implements Serializable{ /** * 判定当前Java的版本是否大于等于指定的版本号。 - * + * *

    * 例如: - * - * + * + * *

      *
    • 测试JDK 1.2:isJavaVersionAtLeast(120)
    • *
    • 测试JDK 1.2.1:isJavaVersionAtLeast(131)
    • *
    - * - * + * * @param requiredVersion 需要的版本 - * * @return 如果当前Java版本大于或等于指定的版本,则返回true */ public final boolean isJavaVersionAtLeast(int requiredVersion) { @@ -376,7 +344,7 @@ public class JavaInfo implements Serializable{ /** * 将Java Implementation的信息转换成字符串。 - * + * * @return JVM impl.的字符串表示 */ @Override diff --git a/hutool-system/src/main/java/cn/hutool/system/RuntimeInfo.java b/hutool-system/src/main/java/cn/hutool/system/RuntimeInfo.java index ef6b380a3..328992fbc 100644 --- a/hutool-system/src/main/java/cn/hutool/system/RuntimeInfo.java +++ b/hutool-system/src/main/java/cn/hutool/system/RuntimeInfo.java @@ -1,59 +1,64 @@ package cn.hutool.system; -import java.io.Serializable; - import cn.hutool.core.io.FileUtil; +import java.io.Serializable; + /** * 运行时信息,包括内存总大小、已用大小、可用大小等 - * @author looly * + * @author looly */ -public class RuntimeInfo implements Serializable{ +public class RuntimeInfo implements Serializable { private static final long serialVersionUID = 1L; - - private Runtime currentRuntime = Runtime.getRuntime(); - + + private final Runtime currentRuntime = Runtime.getRuntime(); + /** * 获得运行时对象 + * * @return {@link Runtime} */ - public final Runtime getRuntime(){ + public final Runtime getRuntime() { return currentRuntime; } - + /** * 获得JVM最大内存 + * * @return 最大内存 */ - public final long getMaxMemory(){ + public final long getMaxMemory() { return currentRuntime.maxMemory(); } - + /** * 获得JVM已分配内存 + * * @return 已分配内存 */ - public final long getTotalMemory(){ + public final long getTotalMemory() { return currentRuntime.totalMemory(); } - + /** * 获得JVM已分配内存中的剩余空间 + * * @return 已分配内存中的剩余空间 */ - public final long getFreeMemory(){ + public final long getFreeMemory() { return currentRuntime.freeMemory(); } - + /** * 获得JVM最大可用内存 + * * @return 最大可用内存 */ - public final long getUsableMemory(){ + public final long getUsableMemory() { return currentRuntime.maxMemory() - currentRuntime.totalMemory() + currentRuntime.freeMemory(); } - + @Override public String toString() { StringBuilder builder = new StringBuilder(); From 4a574e324196dff7f008bfd869dd9629dba8dcbc Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 11 Apr 2020 13:53:08 +0800 Subject: [PATCH 034/157] add CalendarUtil --- CHANGELOG.md | 1 + .../cn/hutool/core/builder/EqualsBuilder.java | 1559 ++++++++--------- .../cn/hutool/core/date/CalendarUtil.java | 430 +++++ .../java/cn/hutool/core/date/DateUtil.java | 407 +---- .../cn/hutool/core/date/DateUtilTest.java | 18 +- 5 files changed, 1238 insertions(+), 1177 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 743a0af62..2b39123a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ * 【json 】 解析Object中对是否为bean单独判断,而不是直接解析 * 【core 】 SimHash锁改为StampedLock * 【core 】 Singleton改为SimpleCache实现 +* 【core 】 增加CalendarUtil,DateUtil相关方法全部迁移到此 ### Bug修复 * 【extra 】 修复SpringUtil使用devtools重启报错问题 diff --git a/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java b/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java index e03c08ce5..19a5299b1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java @@ -39,831 +39,828 @@ import java.util.Set; * return EqualsBuilder.reflectionEquals(this, obj); * } * - * */ public class EqualsBuilder implements Builder { private static final long serialVersionUID = 1L; - /** - *

    - * A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops. - *

    - * - * @since 3.0 - */ - private static final ThreadLocal>> REGISTRY = new ThreadLocal<>(); + /** + *

    + * A registry of objects used by reflection methods to detect cyclical object references and avoid infinite loops. + *

    + * + * @since 3.0 + */ + private static final ThreadLocal>> REGISTRY = new ThreadLocal<>(); - /** - *

    - * Returns the registry of object pairs being traversed by the reflection - * methods in the current thread. - *

    - * - * @return Set the registry of objects being traversed - * @since 3.0 - */ - static Set> getRegistry() { - return REGISTRY.get(); - } + /** + *

    + * Returns the registry of object pairs being traversed by the reflection + * methods in the current thread. + *

    + * + * @return Set the registry of objects being traversed + * @since 3.0 + */ + static Set> getRegistry() { + return REGISTRY.get(); + } - /** - *

    - * Converters value pair into a register pair. - *

    - * - * @param lhs this object - * @param rhs the other object - * - * @return the pair - */ - static Pair getRegisterPair(final Object lhs, final Object rhs) { - final IDKey left = new IDKey(lhs); - final IDKey right = new IDKey(rhs); - return new Pair<>(left, right); - } + /** + *

    + * Converters value pair into a register pair. + *

    + * + * @param lhs this object + * @param rhs the other object + * @return the pair + */ + static Pair getRegisterPair(final Object lhs, final Object rhs) { + final IDKey left = new IDKey(lhs); + final IDKey right = new IDKey(rhs); + return new Pair<>(left, right); + } - /** - *

    - * Returns true if the registry contains the given object pair. - * Used by the reflection methods to avoid infinite loops. - * Objects might be swapped therefore a check is needed if the object pair - * is registered in given or swapped order. - *

    - * - * @param lhs this object to lookup in registry - * @param rhs the other object to lookup on registry - * @return boolean true if the registry contains the given object. - * @since 3.0 - */ - static boolean isRegistered(final Object lhs, final Object rhs) { - final Set> registry = getRegistry(); - final Pair pair = getRegisterPair(lhs, rhs); - final Pair swappedPair = new Pair<>(pair.getKey(), pair.getValue()); + /** + *

    + * Returns true if the registry contains the given object pair. + * Used by the reflection methods to avoid infinite loops. + * Objects might be swapped therefore a check is needed if the object pair + * is registered in given or swapped order. + *

    + * + * @param lhs this object to lookup in registry + * @param rhs the other object to lookup on registry + * @return boolean true if the registry contains the given object. + * @since 3.0 + */ + static boolean isRegistered(final Object lhs, final Object rhs) { + final Set> registry = getRegistry(); + final Pair pair = getRegisterPair(lhs, rhs); + final Pair swappedPair = new Pair<>(pair.getKey(), pair.getValue()); - return registry != null - && (registry.contains(pair) || registry.contains(swappedPair)); - } + return registry != null + && (registry.contains(pair) || registry.contains(swappedPair)); + } - /** - *

    - * Registers the given object pair. - * Used by the reflection methods to avoid infinite loops. - *

    - * - * @param lhs this object to register - * @param rhs the other object to register - */ - static void register(final Object lhs, final Object rhs) { - synchronized (EqualsBuilder.class) { - if (getRegistry() == null) { - REGISTRY.set(new HashSet<>()); - } - } + /** + *

    + * Registers the given object pair. + * Used by the reflection methods to avoid infinite loops. + *

    + * + * @param lhs this object to register + * @param rhs the other object to register + */ + static void register(final Object lhs, final Object rhs) { + synchronized (EqualsBuilder.class) { + if (getRegistry() == null) { + REGISTRY.set(new HashSet<>()); + } + } - final Set> registry = getRegistry(); - final Pair pair = getRegisterPair(lhs, rhs); - registry.add(pair); - } + final Set> registry = getRegistry(); + final Pair pair = getRegisterPair(lhs, rhs); + registry.add(pair); + } - /** - *

    - * Unregisters the given object pair. - *

    - * - *

    - * Used by the reflection methods to avoid infinite loops. - * - * @param lhs this object to unregister - * @param rhs the other object to unregister - * @since 3.0 - */ - static void unregister(final Object lhs, final Object rhs) { - Set> registry = getRegistry(); - if (registry != null) { - final Pair pair = getRegisterPair(lhs, rhs); - registry.remove(pair); - synchronized (EqualsBuilder.class) { - //read again - registry = getRegistry(); - if (registry != null && registry.isEmpty()) { - REGISTRY.remove(); - } - } - } - } + /** + *

    + * Unregisters the given object pair. + *

    + * + *

    + * Used by the reflection methods to avoid infinite loops. + * + * @param lhs this object to unregister + * @param rhs the other object to unregister + * @since 3.0 + */ + static void unregister(final Object lhs, final Object rhs) { + Set> registry = getRegistry(); + if (registry != null) { + final Pair pair = getRegisterPair(lhs, rhs); + registry.remove(pair); + synchronized (EqualsBuilder.class) { + //read again + registry = getRegistry(); + if (registry != null && registry.isEmpty()) { + REGISTRY.remove(); + } + } + } + } - /** 是否equals,此值随着构建会变更,默认true */ - private boolean isEquals = true; + /** + * 是否equals,此值随着构建会变更,默认true + */ + private boolean isEquals = true; - /** - * 构造,初始状态值为true - */ - public EqualsBuilder() { - // do nothing for now. - } + /** + * 构造,初始状态值为true + */ + public EqualsBuilder() { + // do nothing for now. + } - //------------------------------------------------------------------------- + //------------------------------------------------------------------------- - /** - *

    反射检查两个对象是否equals,此方法检查对象及其父对象的属性(包括私有属性)是否equals

    - * - * @param lhs 此对象 - * @param rhs 另一个对象 - * @param excludeFields 排除的字段集合,如果有不参与计算equals的字段加入此集合即可 - * @return 两个对象是否equals,是返回true - */ - public static boolean reflectionEquals(final Object lhs, final Object rhs, final Collection excludeFields) { - return reflectionEquals(lhs, rhs, ArrayUtil.toArray(excludeFields, String.class)); - } + /** + *

    反射检查两个对象是否equals,此方法检查对象及其父对象的属性(包括私有属性)是否equals

    + * + * @param lhs 此对象 + * @param rhs 另一个对象 + * @param excludeFields 排除的字段集合,如果有不参与计算equals的字段加入此集合即可 + * @return 两个对象是否equals,是返回true + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final Collection excludeFields) { + return reflectionEquals(lhs, rhs, ArrayUtil.toArray(excludeFields, String.class)); + } - /** - *

    反射检查两个对象是否equals,此方法检查对象及其父对象的属性(包括私有属性)是否equals

    - * - * @param lhs 此对象 - * @param rhs 另一个对象 - * @param excludeFields 排除的字段集合,如果有不参与计算equals的字段加入此集合即可 - * @return 两个对象是否equals,是返回true - */ - public static boolean reflectionEquals(final Object lhs, final Object rhs, final String... excludeFields) { - return reflectionEquals(lhs, rhs, false, null, excludeFields); - } + /** + *

    反射检查两个对象是否equals,此方法检查对象及其父对象的属性(包括私有属性)是否equals

    + * + * @param lhs 此对象 + * @param rhs 另一个对象 + * @param excludeFields 排除的字段集合,如果有不参与计算equals的字段加入此集合即可 + * @return 两个对象是否equals,是返回true + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final String... excludeFields) { + return reflectionEquals(lhs, rhs, false, null, excludeFields); + } - /** - *

    This method uses reflection to determine if the two Objects - * are equal.

    - * - *

    It uses AccessibleObject.setAccessible to gain access to private - * fields. This means that it will throw a security exception if run under - * a security manager, if the permissions are not set up correctly. It is also - * not as efficient as testing explicitly. Non-primitive fields are compared using - * equals().

    - * - *

    If the TestTransients parameter is set to true, transient - * members will be tested, otherwise they are ignored, as they are likely - * derived fields, and not part of the value of the Object.

    - * - *

    Static fields will not be tested. Superclass fields will be included.

    - * - * @param lhs this object - * @param rhs the other object - * @param testTransients whether to include transient fields - * @return true if the two Objects have tested equals. - */ - public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients) { - return reflectionEquals(lhs, rhs, testTransients, null); - } + /** + *

    This method uses reflection to determine if the two Objects + * are equal.

    + * + *

    It uses AccessibleObject.setAccessible to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly. Non-primitive fields are compared using + * equals().

    + * + *

    If the TestTransients parameter is set to true, transient + * members will be tested, otherwise they are ignored, as they are likely + * derived fields, and not part of the value of the Object.

    + * + *

    Static fields will not be tested. Superclass fields will be included.

    + * + * @param lhs this object + * @param rhs the other object + * @param testTransients whether to include transient fields + * @return true if the two Objects have tested equals. + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients) { + return reflectionEquals(lhs, rhs, testTransients, null); + } - /** - *

    This method uses reflection to determine if the two Objects - * are equal.

    - * - *

    It uses AccessibleObject.setAccessible to gain access to private - * fields. This means that it will throw a security exception if run under - * a security manager, if the permissions are not set up correctly. It is also - * not as efficient as testing explicitly. Non-primitive fields are compared using - * equals().

    - * - *

    If the testTransients parameter is set to true, transient - * members will be tested, otherwise they are ignored, as they are likely - * derived fields, and not part of the value of the Object.

    - * - *

    Static fields will not be included. Superclass fields will be appended - * up to and including the specified superclass. A null superclass is treated - * as java.lang.Object.

    - * - * @param lhs this object - * @param rhs the other object - * @param testTransients whether to include transient fields - * @param reflectUpToClass the superclass to reflect up to (inclusive), - * may be null - * @param excludeFields array of field names to exclude from testing - * @return true if the two Objects have tested equals. - * @since 2.0 - */ - public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients, final Class reflectUpToClass, - final String... excludeFields) { - if (lhs == rhs) { - return true; - } - if (lhs == null || rhs == null) { - return false; - } - // Find the leaf class since there may be transients in the leaf - // class or in classes between the leaf and root. - // If we are not testing transients or a subclass has no ivars, - // then a subclass can test equals to a superclass. - final Class lhsClass = lhs.getClass(); - final Class rhsClass = rhs.getClass(); - Class testClass; - if (lhsClass.isInstance(rhs)) { - testClass = lhsClass; - if (!rhsClass.isInstance(lhs)) { - // rhsClass is a subclass of lhsClass - testClass = rhsClass; - } - } else if (rhsClass.isInstance(lhs)) { - testClass = rhsClass; - if (!lhsClass.isInstance(rhs)) { - // lhsClass is a subclass of rhsClass - testClass = lhsClass; - } - } else { - // The two classes are not related. - return false; - } - final EqualsBuilder equalsBuilder = new EqualsBuilder(); - try { - if (testClass.isArray()) { - equalsBuilder.append(lhs, rhs); - } else { - reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields); - while (testClass.getSuperclass() != null && testClass != reflectUpToClass) { - testClass = testClass.getSuperclass(); - reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields); - } - } - } catch (final IllegalArgumentException e) { - // In this case, we tried to test a subclass vs. a superclass and - // the subclass has ivars or the ivars are transient and - // we are testing transients. - // If a subclass has ivars that we are trying to test them, we get an - // exception and we know that the objects are not equal. - return false; - } - return equalsBuilder.isEquals(); - } + /** + *

    This method uses reflection to determine if the two Objects + * are equal.

    + * + *

    It uses AccessibleObject.setAccessible to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly. Non-primitive fields are compared using + * equals().

    + * + *

    If the testTransients parameter is set to true, transient + * members will be tested, otherwise they are ignored, as they are likely + * derived fields, and not part of the value of the Object.

    + * + *

    Static fields will not be included. Superclass fields will be appended + * up to and including the specified superclass. A null superclass is treated + * as java.lang.Object.

    + * + * @param lhs this object + * @param rhs the other object + * @param testTransients whether to include transient fields + * @param reflectUpToClass the superclass to reflect up to (inclusive), + * may be null + * @param excludeFields array of field names to exclude from testing + * @return true if the two Objects have tested equals. + * @since 2.0 + */ + public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients, final Class reflectUpToClass, + final String... excludeFields) { + if (lhs == rhs) { + return true; + } + if (lhs == null || rhs == null) { + return false; + } + // Find the leaf class since there may be transients in the leaf + // class or in classes between the leaf and root. + // If we are not testing transients or a subclass has no ivars, + // then a subclass can test equals to a superclass. + final Class lhsClass = lhs.getClass(); + final Class rhsClass = rhs.getClass(); + Class testClass; + if (lhsClass.isInstance(rhs)) { + testClass = lhsClass; + if (!rhsClass.isInstance(lhs)) { + // rhsClass is a subclass of lhsClass + testClass = rhsClass; + } + } else if (rhsClass.isInstance(lhs)) { + testClass = rhsClass; + if (!lhsClass.isInstance(rhs)) { + // lhsClass is a subclass of rhsClass + testClass = lhsClass; + } + } else { + // The two classes are not related. + return false; + } + final EqualsBuilder equalsBuilder = new EqualsBuilder(); + try { + if (testClass.isArray()) { + equalsBuilder.append(lhs, rhs); + } else { + reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields); + while (testClass.getSuperclass() != null && testClass != reflectUpToClass) { + testClass = testClass.getSuperclass(); + reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields); + } + } + } catch (final IllegalArgumentException e) { + // In this case, we tried to test a subclass vs. a superclass and + // the subclass has ivars or the ivars are transient and + // we are testing transients. + // If a subclass has ivars that we are trying to test them, we get an + // exception and we know that the objects are not equal. + return false; + } + return equalsBuilder.isEquals(); + } - /** - *

    Appends the fields and values defined by the given object of the - * given Class.

    - * - * @param lhs the left hand object - * @param rhs the right hand object - * @param clazz the class to append details of - * @param builder the builder to append to - * @param useTransients whether to test transient fields - * @param excludeFields array of field names to exclude from testing - */ - private static void reflectionAppend( - final Object lhs, - final Object rhs, - final Class clazz, - final EqualsBuilder builder, - final boolean useTransients, - final String[] excludeFields) { + /** + *

    Appends the fields and values defined by the given object of the + * given Class.

    + * + * @param lhs the left hand object + * @param rhs the right hand object + * @param clazz the class to append details of + * @param builder the builder to append to + * @param useTransients whether to test transient fields + * @param excludeFields array of field names to exclude from testing + */ + private static void reflectionAppend( + final Object lhs, + final Object rhs, + final Class clazz, + final EqualsBuilder builder, + final boolean useTransients, + final String[] excludeFields) { - if (isRegistered(lhs, rhs)) { - return; - } + if (isRegistered(lhs, rhs)) { + return; + } - try { - register(lhs, rhs); - final Field[] fields = clazz.getDeclaredFields(); - AccessibleObject.setAccessible(fields, true); - for (int i = 0; i < fields.length && builder.isEquals; i++) { - final Field f = fields[i]; - if (false == ArrayUtil.contains(excludeFields, f.getName()) - && (f.getName().indexOf('$') == -1) - && (useTransients || !Modifier.isTransient(f.getModifiers())) - && (!Modifier.isStatic(f.getModifiers()))) { - try { - builder.append(f.get(lhs), f.get(rhs)); - } catch (final IllegalAccessException e) { - //this can't happen. Would get a Security exception instead - //throw a runtime exception in case the impossible happens. - throw new InternalError("Unexpected IllegalAccessException"); - } - } - } - } finally { - unregister(lhs, rhs); - } - } + try { + register(lhs, rhs); + final Field[] fields = clazz.getDeclaredFields(); + AccessibleObject.setAccessible(fields, true); + for (int i = 0; i < fields.length && builder.isEquals; i++) { + final Field f = fields[i]; + if (false == ArrayUtil.contains(excludeFields, f.getName()) + && (f.getName().indexOf('$') == -1) + && (useTransients || !Modifier.isTransient(f.getModifiers())) + && (!Modifier.isStatic(f.getModifiers()))) { + try { + builder.append(f.get(lhs), f.get(rhs)); + } catch (final IllegalAccessException e) { + //this can't happen. Would get a Security exception instead + //throw a runtime exception in case the impossible happens. + throw new InternalError("Unexpected IllegalAccessException"); + } + } + } + } finally { + unregister(lhs, rhs); + } + } - //------------------------------------------------------------------------- + //------------------------------------------------------------------------- - /** - *

    Adds the result of super.equals() to this builder.

    - * - * @param superEquals the result of calling super.equals() - * @return EqualsBuilder - used to chain calls. - * @since 2.0 - */ - public EqualsBuilder appendSuper(final boolean superEquals) { - if (isEquals == false) { - return this; - } - isEquals = superEquals; - return this; - } + /** + *

    Adds the result of super.equals() to this builder.

    + * + * @param superEquals the result of calling super.equals() + * @return EqualsBuilder - used to chain calls. + * @since 2.0 + */ + public EqualsBuilder appendSuper(final boolean superEquals) { + if (isEquals == false) { + return this; + } + isEquals = superEquals; + return this; + } - //------------------------------------------------------------------------- + //------------------------------------------------------------------------- - /** - *

    Test if two Objects are equal using their - * equals method.

    - * - * @param lhs the left hand object - * @param rhs the right hand object - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final Object lhs, final Object rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - final Class lhsClass = lhs.getClass(); - if (!lhsClass.isArray()) { - // The simple case, not an array, just test the element - isEquals = lhs.equals(rhs); - } else if (lhs.getClass() != rhs.getClass()) { - // Here when we compare different dimensions, for example: a boolean[][] to a boolean[] - this.setEquals(false); - } - // 'Switch' on type of array, to dispatch to the correct handler - // This handles multi dimensional arrays of the same depth - else if (lhs instanceof long[]) { - append((long[]) lhs, (long[]) rhs); - } else if (lhs instanceof int[]) { - append((int[]) lhs, (int[]) rhs); - } else if (lhs instanceof short[]) { - append((short[]) lhs, (short[]) rhs); - } else if (lhs instanceof char[]) { - append((char[]) lhs, (char[]) rhs); - } else if (lhs instanceof byte[]) { - append((byte[]) lhs, (byte[]) rhs); - } else if (lhs instanceof double[]) { - append((double[]) lhs, (double[]) rhs); - } else if (lhs instanceof float[]) { - append((float[]) lhs, (float[]) rhs); - } else if (lhs instanceof boolean[]) { - append((boolean[]) lhs, (boolean[]) rhs); - } else { - // Not an array of primitives - append((Object[]) lhs, (Object[]) rhs); - } - return this; - } + /** + *

    Test if two Objects are equal using their + * equals method.

    + * + * @param lhs the left hand object + * @param rhs the right hand object + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final Object lhs, final Object rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + final Class lhsClass = lhs.getClass(); + if (!lhsClass.isArray()) { + // The simple case, not an array, just test the element + isEquals = lhs.equals(rhs); + } else if (lhs.getClass() != rhs.getClass()) { + // Here when we compare different dimensions, for example: a boolean[][] to a boolean[] + this.setEquals(false); + } + // 'Switch' on type of array, to dispatch to the correct handler + // This handles multi dimensional arrays of the same depth + else if (lhs instanceof long[]) { + append((long[]) lhs, (long[]) rhs); + } else if (lhs instanceof int[]) { + append((int[]) lhs, (int[]) rhs); + } else if (lhs instanceof short[]) { + append((short[]) lhs, (short[]) rhs); + } else if (lhs instanceof char[]) { + append((char[]) lhs, (char[]) rhs); + } else if (lhs instanceof byte[]) { + append((byte[]) lhs, (byte[]) rhs); + } else if (lhs instanceof double[]) { + append((double[]) lhs, (double[]) rhs); + } else if (lhs instanceof float[]) { + append((float[]) lhs, (float[]) rhs); + } else if (lhs instanceof boolean[]) { + append((boolean[]) lhs, (boolean[]) rhs); + } else { + // Not an array of primitives + append((Object[]) lhs, (Object[]) rhs); + } + return this; + } - /** - *

    - * Test if two long s are equal. - *

    - * - * @param lhs - * the left hand long - * @param rhs - * the right hand long - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final long lhs, final long rhs) { - if (isEquals == false) { - return this; - } - isEquals = (lhs == rhs); - return this; - } + /** + *

    + * Test if two long s are equal. + *

    + * + * @param lhs the left hand long + * @param rhs the right hand long + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final long lhs, final long rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } - /** - *

    Test if two ints are equal.

    - * - * @param lhs the left hand int - * @param rhs the right hand int - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final int lhs, final int rhs) { - if (isEquals == false) { - return this; - } - isEquals = (lhs == rhs); - return this; - } + /** + *

    Test if two ints are equal.

    + * + * @param lhs the left hand int + * @param rhs the right hand int + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final int lhs, final int rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } - /** - *

    Test if two shorts are equal.

    - * - * @param lhs the left hand short - * @param rhs the right hand short - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final short lhs, final short rhs) { - if (isEquals == false) { - return this; - } - isEquals = (lhs == rhs); - return this; - } + /** + *

    Test if two shorts are equal.

    + * + * @param lhs the left hand short + * @param rhs the right hand short + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final short lhs, final short rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } - /** - *

    Test if two chars are equal.

    - * - * @param lhs the left hand char - * @param rhs the right hand char - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final char lhs, final char rhs) { - if (isEquals == false) { - return this; - } - isEquals = (lhs == rhs); - return this; - } + /** + *

    Test if two chars are equal.

    + * + * @param lhs the left hand char + * @param rhs the right hand char + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final char lhs, final char rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } - /** - *

    Test if two bytes are equal.

    - * - * @param lhs the left hand byte - * @param rhs the right hand byte - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final byte lhs, final byte rhs) { - if (isEquals == false) { - return this; - } - isEquals = (lhs == rhs); - return this; - } + /** + *

    Test if two bytes are equal.

    + * + * @param lhs the left hand byte + * @param rhs the right hand byte + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final byte lhs, final byte rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } - /** - *

    Test if two doubles are equal by testing that the - * pattern of bits returned by doubleToLong are equal.

    - * - *

    This handles NaNs, Infinities, and -0.0.

    - * - *

    It is compatible with the hash code generated by - * HashCodeBuilder.

    - * - * @param lhs the left hand double - * @param rhs the right hand double - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final double lhs, final double rhs) { - if (isEquals == false) { - return this; - } - return append(Double.doubleToLongBits(lhs), Double.doubleToLongBits(rhs)); - } + /** + *

    Test if two doubles are equal by testing that the + * pattern of bits returned by doubleToLong are equal.

    + * + *

    This handles NaNs, Infinities, and -0.0.

    + * + *

    It is compatible with the hash code generated by + * HashCodeBuilder.

    + * + * @param lhs the left hand double + * @param rhs the right hand double + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final double lhs, final double rhs) { + if (isEquals == false) { + return this; + } + return append(Double.doubleToLongBits(lhs), Double.doubleToLongBits(rhs)); + } - /** - *

    Test if two floats are equal byt testing that the - * pattern of bits returned by doubleToLong are equal.

    - * - *

    This handles NaNs, Infinities, and -0.0.

    - * - *

    It is compatible with the hash code generated by - * HashCodeBuilder.

    - * - * @param lhs the left hand float - * @param rhs the right hand float - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final float lhs, final float rhs) { - if (isEquals == false) { - return this; - } - return append(Float.floatToIntBits(lhs), Float.floatToIntBits(rhs)); - } + /** + *

    Test if two floats are equal byt testing that the + * pattern of bits returned by doubleToLong are equal.

    + * + *

    This handles NaNs, Infinities, and -0.0.

    + * + *

    It is compatible with the hash code generated by + * HashCodeBuilder.

    + * + * @param lhs the left hand float + * @param rhs the right hand float + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final float lhs, final float rhs) { + if (isEquals == false) { + return this; + } + return append(Float.floatToIntBits(lhs), Float.floatToIntBits(rhs)); + } - /** - *

    Test if two booleanss are equal.

    - * - * @param lhs the left hand boolean - * @param rhs the right hand boolean - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final boolean lhs, final boolean rhs) { - if (isEquals == false) { - return this; - } - isEquals = (lhs == rhs); - return this; - } + /** + *

    Test if two booleanss are equal.

    + * + * @param lhs the left hand boolean + * @param rhs the right hand boolean + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final boolean lhs, final boolean rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } - /** - *

    Performs a deep comparison of two Object arrays.

    - * - *

    This also will be called for the top level of - * multi-dimensional, ragged, and multi-typed arrays.

    - * - * @param lhs the left hand Object[] - * @param rhs the right hand Object[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final Object[] lhs, final Object[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

    Performs a deep comparison of two Object arrays.

    + * + *

    This also will be called for the top level of + * multi-dimensional, ragged, and multi-typed arrays.

    + * + * @param lhs the left hand Object[] + * @param rhs the right hand Object[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final Object[] lhs, final Object[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

    Deep comparison of array of long. Length and all - * values are compared.

    - * - *

    The method {@link #append(long, long)} is used.

    - * - * @param lhs the left hand long[] - * @param rhs the right hand long[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final long[] lhs, final long[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

    Deep comparison of array of long. Length and all + * values are compared.

    + * + *

    The method {@link #append(long, long)} is used.

    + * + * @param lhs the left hand long[] + * @param rhs the right hand long[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final long[] lhs, final long[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

    Deep comparison of array of int. Length and all - * values are compared.

    - * - *

    The method {@link #append(int, int)} is used.

    - * - * @param lhs the left hand int[] - * @param rhs the right hand int[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final int[] lhs, final int[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

    Deep comparison of array of int. Length and all + * values are compared.

    + * + *

    The method {@link #append(int, int)} is used.

    + * + * @param lhs the left hand int[] + * @param rhs the right hand int[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final int[] lhs, final int[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

    Deep comparison of array of short. Length and all - * values are compared.

    - * - *

    The method {@link #append(short, short)} is used.

    - * - * @param lhs the left hand short[] - * @param rhs the right hand short[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final short[] lhs, final short[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

    Deep comparison of array of short. Length and all + * values are compared.

    + * + *

    The method {@link #append(short, short)} is used.

    + * + * @param lhs the left hand short[] + * @param rhs the right hand short[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final short[] lhs, final short[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

    Deep comparison of array of char. Length and all - * values are compared.

    - * - *

    The method {@link #append(char, char)} is used.

    - * - * @param lhs the left hand char[] - * @param rhs the right hand char[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final char[] lhs, final char[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

    Deep comparison of array of char. Length and all + * values are compared.

    + * + *

    The method {@link #append(char, char)} is used.

    + * + * @param lhs the left hand char[] + * @param rhs the right hand char[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final char[] lhs, final char[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

    Deep comparison of array of byte. Length and all - * values are compared.

    - * - *

    The method {@link #append(byte, byte)} is used.

    - * - * @param lhs the left hand byte[] - * @param rhs the right hand byte[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final byte[] lhs, final byte[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

    Deep comparison of array of byte. Length and all + * values are compared.

    + * + *

    The method {@link #append(byte, byte)} is used.

    + * + * @param lhs the left hand byte[] + * @param rhs the right hand byte[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final byte[] lhs, final byte[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

    Deep comparison of array of double. Length and all - * values are compared.

    - * - *

    The method {@link #append(double, double)} is used.

    - * - * @param lhs the left hand double[] - * @param rhs the right hand double[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final double[] lhs, final double[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

    Deep comparison of array of double. Length and all + * values are compared.

    + * + *

    The method {@link #append(double, double)} is used.

    + * + * @param lhs the left hand double[] + * @param rhs the right hand double[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final double[] lhs, final double[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

    Deep comparison of array of float. Length and all - * values are compared.

    - * - *

    The method {@link #append(float, float)} is used.

    - * - * @param lhs the left hand float[] - * @param rhs the right hand float[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final float[] lhs, final float[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

    Deep comparison of array of float. Length and all + * values are compared.

    + * + *

    The method {@link #append(float, float)} is used.

    + * + * @param lhs the left hand float[] + * @param rhs the right hand float[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final float[] lhs, final float[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

    Deep comparison of array of boolean. Length and all - * values are compared.

    - * - *

    The method {@link #append(boolean, boolean)} is used.

    - * - * @param lhs the left hand boolean[] - * @param rhs the right hand boolean[] - * @return EqualsBuilder - used to chain calls. - */ - public EqualsBuilder append(final boolean[] lhs, final boolean[] rhs) { - if (isEquals == false) { - return this; - } - if (lhs == rhs) { - return this; - } - if (lhs == null || rhs == null) { - this.setEquals(false); - return this; - } - if (lhs.length != rhs.length) { - this.setEquals(false); - return this; - } - for (int i = 0; i < lhs.length && isEquals; ++i) { - append(lhs[i], rhs[i]); - } - return this; - } + /** + *

    Deep comparison of array of boolean. Length and all + * values are compared.

    + * + *

    The method {@link #append(boolean, boolean)} is used.

    + * + * @param lhs the left hand boolean[] + * @param rhs the right hand boolean[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(final boolean[] lhs, final boolean[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } - /** - *

    Returns true if the fields that have been checked - * are all equal.

    - * - * @return boolean - */ - public boolean isEquals() { - return this.isEquals; - } + /** + *

    Returns true if the fields that have been checked + * are all equal.

    + * + * @return boolean + */ + public boolean isEquals() { + return this.isEquals; + } - /** - *

    Returns true if the fields that have been checked - * are all equal.

    - * - * @return true if all of the fields that have been checked - * are equal, false otherwise. - * - * @since 3.0 - */ - @Override - public Boolean build() { - return isEquals(); - } + /** + *

    Returns true if the fields that have been checked + * are all equal.

    + * + * @return true if all of the fields that have been checked + * are equal, false otherwise. + * @since 3.0 + */ + @Override + public Boolean build() { + return isEquals(); + } - /** - * Sets the isEquals value. - * - * @param isEquals The value to set. - * @since 2.1 - */ - @SuppressWarnings("SameParameterValue") - protected void setEquals(boolean isEquals) { - this.isEquals = isEquals; - } + /** + * Sets the isEquals value. + * + * @param isEquals The value to set. + * @since 2.1 + */ + protected void setEquals(boolean isEquals) { + this.isEquals = isEquals; + } - /** - * Reset the EqualsBuilder so you can use the same object again - * @since 2.5 - */ - public void reset() { - this.isEquals = true; - } + /** + * Reset the EqualsBuilder so you can use the same object again + * + * @since 2.5 + */ + public void reset() { + this.isEquals = true; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java new file mode 100644 index 000000000..72baae414 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java @@ -0,0 +1,430 @@ +package cn.hutool.core.date; + +import cn.hutool.core.comparator.CompareUtil; +import cn.hutool.core.util.StrUtil; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.Calendar; +import java.util.Date; +import java.util.LinkedHashSet; + +/** + * 针对{@link Calendar} 对象封装工具类 + * + * @author looly + * @since 5.3.0 + */ +public class CalendarUtil { + + /** + * 创建Calendar对象,时间为默认时区的当前时间 + * + * @return Calendar对象 + * @since 4.6.6 + */ + public static Calendar calendar() { + return Calendar.getInstance(); + } + + /** + * 转换为Calendar对象 + * + * @param date 日期对象 + * @return Calendar对象 + */ + public static Calendar calendar(Date date) { + if (date instanceof DateTime) { + return ((DateTime) date).toCalendar(); + } else { + return calendar(date.getTime()); + } + } + + /** + * 转换为Calendar对象 + * + * @param millis 时间戳 + * @return Calendar对象 + */ + public static Calendar calendar(long millis) { + final Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(millis); + return cal; + } + + /** + * 是否为上午 + * + * @param calendar {@link Calendar} + * @return 是否为上午 + */ + public static boolean isAM(Calendar calendar) { + return Calendar.AM == calendar.get(Calendar.AM_PM); + } + + /** + * 是否为下午 + * + * @param calendar {@link Calendar} + * @return 是否为下午 + */ + public static boolean isPM(Calendar calendar) { + return Calendar.PM == calendar.get(Calendar.AM_PM); + } + + /** + * 修改日期为某个时间字段起始时间 + * + * @param calendar {@link Calendar} + * @param dateField 时间字段 + * @return 原{@link Calendar} + */ + public static Calendar truncate(Calendar calendar, DateField dateField) { + return DateModifier.modify(calendar, dateField.getValue(), DateModifier.ModifyType.TRUNCATE); + } + + /** + * 修改日期为某个时间字段四舍五入时间 + * + * @param calendar {@link Calendar} + * @param dateField 时间字段 + * @return 原{@link Calendar} + */ + public static Calendar round(Calendar calendar, DateField dateField) { + return DateModifier.modify(calendar, dateField.getValue(), DateModifier.ModifyType.ROUND); + } + + /** + * 修改日期为某个时间字段结束时间 + * + * @param calendar {@link Calendar} + * @param dateField 时间字段 + * @return 原{@link Calendar} + */ + public static Calendar ceiling(Calendar calendar, DateField dateField) { + return DateModifier.modify(calendar, dateField.getValue(), DateModifier.ModifyType.CEILING); + } + + /** + * 获取秒级别的开始时间,即忽略毫秒部分 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + * @since 4.6.2 + */ + public static Calendar beginOfSecond(Calendar calendar) { + return truncate(calendar, DateField.SECOND); + } + + /** + * 获取秒级别的结束时间,即毫秒设置为999 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + * @since 4.6.2 + */ + public static Calendar endOfSecond(Calendar calendar) { + return ceiling(calendar, DateField.SECOND); + } + + /** + * 获取某天的开始时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + */ + public static Calendar beginOfDay(Calendar calendar) { + return truncate(calendar, DateField.DAY_OF_MONTH); + } + + /** + * 获取某天的结束时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + */ + public static Calendar endOfDay(Calendar calendar) { + return ceiling(calendar, DateField.DAY_OF_MONTH); + } + + /** + * 获取给定日期当前周的开始时间,周一定为一周的开始时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + */ + public static Calendar beginOfWeek(Calendar calendar) { + return beginOfWeek(calendar, true); + } + + /** + * 获取给定日期当前周的开始时间 + * + * @param calendar 日期 {@link Calendar} + * @param isMondayAsFirstDay 是否周一做为一周的第一天(false表示周日做为第一天) + * @return {@link Calendar} + * @since 3.1.2 + */ + public static Calendar beginOfWeek(Calendar calendar, boolean isMondayAsFirstDay) { + calendar.setFirstDayOfWeek(isMondayAsFirstDay ? Calendar.MONDAY : Calendar.SUNDAY); + // WEEK_OF_MONTH为上限的字段(不包括),实际调整的为DAY_OF_MONTH + return truncate(calendar, DateField.WEEK_OF_MONTH); + } + + /** + * 获取某周的结束时间,周日定为一周的结束 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + */ + public static Calendar endOfWeek(Calendar calendar) { + return endOfWeek(calendar, true); + } + + /** + * 获取某周的结束时间 + * + * @param calendar 日期 {@link Calendar} + * @param isSundayAsLastDay 是否周日做为一周的最后一天(false表示周六做为最后一天) + * @return {@link Calendar} + */ + public static Calendar endOfWeek(Calendar calendar, boolean isSundayAsLastDay) { + calendar.setFirstDayOfWeek(isSundayAsLastDay ? Calendar.MONDAY : Calendar.SUNDAY); + // WEEK_OF_MONTH为上限的字段(不包括),实际调整的为DAY_OF_MONTH + return ceiling(calendar, DateField.WEEK_OF_MONTH); + } + + /** + * 获取某月的开始时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + */ + public static Calendar beginOfMonth(Calendar calendar) { + return truncate(calendar, DateField.MONTH); + } + + /** + * 获取某月的结束时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + */ + public static Calendar endOfMonth(Calendar calendar) { + return ceiling(calendar, DateField.MONTH); + } + + /** + * 获取某季度的开始时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + * @since 4.1.0 + */ + public static Calendar beginOfQuarter(Calendar calendar) { + //noinspection MagicConstant + calendar.set(Calendar.MONTH, calendar.get(DateField.MONTH.getValue()) / 3 * 3); + calendar.set(Calendar.DAY_OF_MONTH, 1); + return beginOfDay(calendar); + } + + /** + * 获取某季度的结束时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + * @since 4.1.0 + */ + public static Calendar endOfQuarter(Calendar calendar) { + //noinspection MagicConstant + calendar.set(Calendar.MONTH, calendar.get(DateField.MONTH.getValue()) / 3 * 3 + 2); + calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH)); + return endOfDay(calendar); + } + + /** + * 获取某年的开始时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + */ + public static Calendar beginOfYear(Calendar calendar) { + return truncate(calendar, DateField.YEAR); + } + + /** + * 获取某年的结束时间 + * + * @param calendar 日期 {@link Calendar} + * @return {@link Calendar} + */ + public static Calendar endOfYear(Calendar calendar) { + return ceiling(calendar, DateField.YEAR); + } + + /** + * 比较两个日期是否为同一天 + * + * @param cal1 日期1 + * @param cal2 日期2 + * @return 是否为同一天 + */ + public static boolean isSameDay(Calendar cal1, Calendar cal2) { + if (cal1 == null || cal2 == null) { + throw new IllegalArgumentException("The date must not be null"); + } + return cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR) && // + cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && // + cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA); + } + + /** + * 获得指定日期区间内的年份和季节
    + * + * @param startDate 起始日期(包含) + * @param endDate 结束日期(包含) + * @return 季度列表 ,元素类似于 20132 + * @since 4.1.15 + */ + public static LinkedHashSet yearAndQuarter(long startDate, long endDate) { + LinkedHashSet quarters = new LinkedHashSet<>(); + final Calendar cal = calendar(startDate); + while (startDate <= endDate) { + // 如果开始时间超出结束时间,让结束时间为开始时间,处理完后结束循环 + quarters.add(yearAndQuarter(cal)); + + cal.add(Calendar.MONTH, 3); + startDate = cal.getTimeInMillis(); + } + + return quarters; + } + + /** + * 获得指定日期年份和季节
    + * 格式:[20131]表示2013年第一季度 + * + * @param cal 日期 + */ + public static String yearAndQuarter(Calendar cal) { + return StrUtil.builder().append(cal.get(Calendar.YEAR)).append(cal.get(Calendar.MONTH) / 3 + 1).toString(); + } + + /** + * 获取指定日期字段的最小值,例如分钟的最小值是0 + * + * @param calendar {@link Calendar} + * @param dateField {@link DateField} + * @return 字段最小值 + * @see Calendar#getActualMinimum(int) + * @since 4.5.7 + */ + public static int getBeginValue(Calendar calendar, int dateField) { + if (Calendar.DAY_OF_WEEK == dateField) { + return calendar.getFirstDayOfWeek(); + } + return calendar.getActualMinimum(dateField); + } + + /** + * 获取指定日期字段的最大值,例如分钟的最大值是59 + * + * @param calendar {@link Calendar} + * @param dateField {@link DateField} + * @return 字段最大值 + * @see Calendar#getActualMaximum(int) + * @since 4.5.7 + */ + public static int getEndValue(Calendar calendar, int dateField) { + if (Calendar.DAY_OF_WEEK == dateField) { + return (calendar.getFirstDayOfWeek() + 6) % 7; + } + return calendar.getActualMaximum(dateField); + } + + /** + * Calendar{@link Instant}对象 + * + * @param calendar Date对象 + * @return {@link Instant}对象 + * @since 5.0.5 + */ + public static Instant toInstant(Calendar calendar) { + return null == calendar ? null : calendar.toInstant(); + } + + /** + * {@link Calendar} 转换为 {@link LocalDateTime},使用系统默认时区 + * + * @param calendar {@link Calendar} + * @return {@link LocalDateTime} + * @since 5.0.5 + */ + public static LocalDateTime toLocalDateTime(Calendar calendar) { + return LocalDateTime.ofInstant(calendar.toInstant(), calendar.getTimeZone().toZoneId()); + } + + /** + * {@code null}安全的{@link Calendar}比较,{@code null}小于任何日期 + * + * @param calendar1 日期1 + * @param calendar2 日期2 + * @return 比较结果,如果calendar1 < calendar2,返回数小于0,calendar1==calendar2返回0,calendar1 > calendar2 大于0 + * @since 4.6.2 + */ + public static int compare(Calendar calendar1, Calendar calendar2) { + return CompareUtil.compare(calendar1, calendar2); + } + + /** + * 计算相对于dateToCompare的年龄,长用于计算指定生日在某年的年龄 + * + * @param birthday 生日 + * @param dateToCompare 需要对比的日期 + * @return 年龄 + */ + public static int age(Calendar birthday, Calendar dateToCompare) { + return age(birthday.getTimeInMillis(), dateToCompare.getTimeInMillis()); + } + + /** + * 计算相对于dateToCompare的年龄,长用于计算指定生日在某年的年龄 + * + * @param birthday 生日 + * @param dateToCompare 需要对比的日期 + * @return 年龄 + */ + protected static int age(long birthday, long dateToCompare) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(dateToCompare); + + if (cal.before(birthday)) { + throw new IllegalArgumentException("Birthday is after dateToCompare!"); + } + + final int year = cal.get(Calendar.YEAR); + final int month = cal.get(Calendar.MONTH); + final int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH); + final boolean isLastDayOfMonth = dayOfMonth == cal.getActualMaximum(Calendar.DAY_OF_MONTH); + + cal.setTimeInMillis(birthday); + int age = year - cal.get(Calendar.YEAR); + + final int monthBirth = cal.get(Calendar.MONTH); + if (month == monthBirth) { + + final int dayOfMonthBirth = cal.get(Calendar.DAY_OF_MONTH); + final boolean isLastDayOfMonthBirth = dayOfMonthBirth == cal.getActualMaximum(Calendar.DAY_OF_MONTH); + if ((false == isLastDayOfMonth || false == isLastDayOfMonthBirth) && dayOfMonth < dayOfMonthBirth) { + // 如果生日在当月,但是未达到生日当天的日期,年龄减一 + age--; + } + } else if (month < monthBirth) { + // 如果当前月份未达到生日的月份,年龄计算减一 + age--; + } + + return age; + } +} 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 f0de7226e..6bd5f0bfd 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 @@ -3,10 +3,10 @@ package cn.hutool.core.date; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.comparator.CompareUtil; import cn.hutool.core.convert.Convert; -import cn.hutool.core.date.DateModifier.ModifyType; import cn.hutool.core.date.format.DateParser; import cn.hutool.core.date.format.DatePrinter; import cn.hutool.core.date.format.FastDateFormat; +import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.PatternPool; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.ReUtil; @@ -39,7 +39,7 @@ import java.util.concurrent.TimeUnit; * * @author xiaoleilu */ -public class DateUtil { +public class DateUtil extends CalendarUtil { /** * java.util.Date EEE MMM zzz 缩写数组 @@ -129,42 +129,6 @@ public class DateUtil { return new DateTime(temporalAccessor); } - /** - * 创建Calendar对象,时间为默认时区的当前时间 - * - * @return Calendar对象 - * @since 4.6.6 - */ - public static Calendar calendar() { - return Calendar.getInstance(); - } - - /** - * 转换为Calendar对象 - * - * @param date 日期对象 - * @return Calendar对象 - */ - public static Calendar calendar(Date date) { - if (date instanceof DateTime) { - return ((DateTime) date).toCalendar(); - } else { - return calendar(date.getTime()); - } - } - - /** - * 转换为Calendar对象 - * - * @param millis 时间戳 - * @return Calendar对象 - */ - public static Calendar calendar(long millis) { - final Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(millis); - return cal; - } - /** * 当前时间的时间戳 * @@ -359,17 +323,6 @@ public class DateUtil { return DateTime.of(date).isAM(); } - /** - * 是否为上午 - * - * @param calendar {@link Calendar} - * @return 是否为上午 - * @since 4.5.7 - */ - public static boolean isAM(Calendar calendar) { - return Calendar.AM == calendar.get(Calendar.AM_PM); - } - /** * 是否为下午 * @@ -490,29 +443,6 @@ public class DateUtil { } return yearAndQuarter(startDate.getTime(), endDate.getTime()); } - - /** - * 获得指定日期区间内的年份和季节
    - * - * @param startDate 起始日期(包含) - * @param endDate 结束日期(包含) - * @return 季度列表 ,元素类似于 20132 - * @since 4.1.15 - */ - public static LinkedHashSet yearAndQuarter(long startDate, long endDate) { - LinkedHashSet quarters = new LinkedHashSet<>(); - final Calendar cal = calendar(startDate); - while (startDate <= endDate) { - // 如果开始时间超出结束时间,让结束时间为开始时间,处理完后结束循环 - quarters.add(yearAndQuarter(cal)); - - cal.add(Calendar.MONTH, 3); - startDate = cal.getTimeInMillis(); - } - - return quarters; - } - // ------------------------------------ Format start ---------------------------------------------- /** @@ -986,18 +916,6 @@ public class DateUtil { return new DateTime(truncate(calendar(date), dateField)); } - /** - * 修改日期为某个时间字段起始时间 - * - * @param calendar {@link Calendar} - * @param dateField 时间字段 - * @return 原{@link Calendar} - * @since 4.5.7 - */ - public static Calendar truncate(Calendar calendar, DateField dateField) { - return DateModifier.modify(calendar, dateField.getValue(), ModifyType.TRUNCATE); - } - /** * 修改日期为某个时间字段四舍五入时间 * @@ -1010,18 +928,6 @@ public class DateUtil { return new DateTime(round(calendar(date), dateField)); } - /** - * 修改日期为某个时间字段四舍五入时间 - * - * @param calendar {@link Calendar} - * @param dateField 时间字段 - * @return 原{@link Calendar} - * @since 4.5.7 - */ - public static Calendar round(Calendar calendar, DateField dateField) { - return DateModifier.modify(calendar, dateField.getValue(), ModifyType.ROUND); - } - /** * 修改日期为某个时间字段结束时间 * @@ -1034,18 +940,6 @@ public class DateUtil { return new DateTime(ceiling(calendar(date), dateField)); } - /** - * 修改日期为某个时间字段结束时间 - * - * @param calendar {@link Calendar} - * @param dateField 时间字段 - * @return 原{@link Calendar} - * @since 4.5.7 - */ - public static Calendar ceiling(Calendar calendar, DateField dateField) { - return DateModifier.modify(calendar, dateField.getValue(), ModifyType.CEILING); - } - /** * 获取秒级别的开始时间,即忽略毫秒部分 * @@ -1068,28 +962,6 @@ public class DateUtil { return new DateTime(endOfSecond(calendar(date))); } - /** - * 获取秒级别的开始时间,即忽略毫秒部分 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - * @since 4.6.2 - */ - public static Calendar beginOfSecond(Calendar calendar) { - return truncate(calendar, DateField.SECOND); - } - - /** - * 获取秒级别的结束时间,即毫秒设置为999 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - * @since 4.6.2 - */ - public static Calendar endOfSecond(Calendar calendar) { - return ceiling(calendar, DateField.SECOND); - } - /** * 获取某天的开始时间 * @@ -1110,26 +982,6 @@ public class DateUtil { return new DateTime(endOfDay(calendar(date))); } - /** - * 获取某天的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar beginOfDay(Calendar calendar) { - return truncate(calendar, DateField.DAY_OF_MONTH); - } - - /** - * 获取某天的结束时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar endOfDay(Calendar calendar) { - return ceiling(calendar, DateField.DAY_OF_MONTH); - } - /** * 获取某周的开始时间,周一定为一周的开始时间 * @@ -1150,54 +1002,6 @@ public class DateUtil { return new DateTime(endOfWeek(calendar(date))); } - /** - * 获取给定日期当前周的开始时间,周一定为一周的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar beginOfWeek(Calendar calendar) { - return beginOfWeek(calendar, true); - } - - /** - * 获取给定日期当前周的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @param isMondayAsFirstDay 是否周一做为一周的第一天(false表示周日做为第一天) - * @return {@link Calendar} - * @since 3.1.2 - */ - public static Calendar beginOfWeek(Calendar calendar, boolean isMondayAsFirstDay) { - calendar.setFirstDayOfWeek(isMondayAsFirstDay ? Calendar.MONDAY : Calendar.SUNDAY); - // WEEK_OF_MONTH为上限的字段(不包括),实际调整的为DAY_OF_MONTH - return truncate(calendar, DateField.WEEK_OF_MONTH); - } - - /** - * 获取某周的结束时间,周日定为一周的结束 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar endOfWeek(Calendar calendar) { - return endOfWeek(calendar, true); - } - - /** - * 获取某周的结束时间 - * - * @param calendar 日期 {@link Calendar} - * @param isSundayAsLastDay 是否周日做为一周的最后一天(false表示周六做为最后一天) - * @return {@link Calendar} - * @since 3.1.2 - */ - public static Calendar endOfWeek(Calendar calendar, boolean isSundayAsLastDay) { - calendar.setFirstDayOfWeek(isSundayAsLastDay ? Calendar.MONDAY : Calendar.SUNDAY); - // WEEK_OF_MONTH为上限的字段(不包括),实际调整的为DAY_OF_MONTH - return ceiling(calendar, DateField.WEEK_OF_MONTH); - } - /** * 获取某月的开始时间 * @@ -1218,26 +1022,6 @@ public class DateUtil { return new DateTime(endOfMonth(calendar(date))); } - /** - * 获取某月的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar beginOfMonth(Calendar calendar) { - return truncate(calendar, DateField.MONTH); - } - - /** - * 获取某月的结束时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar endOfMonth(Calendar calendar) { - return ceiling(calendar, DateField.MONTH); - } - /** * 获取某季度的开始时间 * @@ -1258,34 +1042,6 @@ public class DateUtil { return new DateTime(endOfQuarter(calendar(date))); } - /** - * 获取某季度的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - * @since 4.1.0 - */ - public static Calendar beginOfQuarter(Calendar calendar) { - //noinspection MagicConstant - calendar.set(Calendar.MONTH, calendar.get(DateField.MONTH.getValue()) / 3 * 3); - calendar.set(Calendar.DAY_OF_MONTH, 1); - return beginOfDay(calendar); - } - - /** - * 获取某季度的结束时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - * @since 4.1.0 - */ - public static Calendar endOfQuarter(Calendar calendar) { - //noinspection MagicConstant - calendar.set(Calendar.MONTH, calendar.get(DateField.MONTH.getValue()) / 3 * 3 + 2); - calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH)); - return endOfDay(calendar); - } - /** * 获取某年的开始时间 * @@ -1305,27 +1061,6 @@ public class DateUtil { public static DateTime endOfYear(Date date) { return new DateTime(endOfYear(calendar(date))); } - - /** - * 获取某年的开始时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar beginOfYear(Calendar calendar) { - return truncate(calendar, DateField.YEAR); - } - - /** - * 获取某年的结束时间 - * - * @param calendar 日期 {@link Calendar} - * @return {@link Calendar} - */ - public static Calendar endOfYear(Calendar calendar) { - return ceiling(calendar, DateField.YEAR); - } - // --------------------------------------------------- Offset for now /** @@ -1554,9 +1289,9 @@ public class DateUtil { /** * 计算指定指定时间区间内的周数 * - * @param beginDate 开始时间 - * @param endDate 结束时间 - * @param isReset 是否重置时间为起始时间 + * @param beginDate 开始时间 + * @param endDate 结束时间 + * @param isReset 是否重置时间为起始时间 * @return 周数 */ public static long betweenWeek(Date beginDate, Date endDate, boolean isReset) { @@ -1686,23 +1421,6 @@ public class DateUtil { return isSameDay(calendar(date1), calendar(date2)); } - /** - * 比较两个日期是否为同一天 - * - * @param cal1 日期1 - * @param cal2 日期2 - * @return 是否为同一天 - * @since 4.1.13 - */ - public static boolean isSameDay(Calendar cal1, Calendar cal2) { - if (cal1 == null || cal2 == null) { - throw new IllegalArgumentException("The date must not be null"); - } - return cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR) && // - cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && // - cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA); - } - /** * 计时,常用于记录某段代码的执行时间,单位:纳秒 * @@ -1863,41 +1581,16 @@ public class DateUtil { /** * 计算相对于dateToCompare的年龄,长用于计算指定生日在某年的年龄 * - * @param birthDay 生日 + * @param birthday 生日 * @param dateToCompare 需要对比的日期 * @return 年龄 */ - public static int age(Date birthDay, Date dateToCompare) { - Calendar cal = Calendar.getInstance(); - cal.setTime(dateToCompare); - - if (cal.before(birthDay)) { - throw new IllegalArgumentException(StrUtil.format("Birthday is after date {}!", formatDate(dateToCompare))); + public static int age(Date birthday, Date dateToCompare) { + Assert.notNull(birthday, "Birthday can not be null !"); + if (null == dateToCompare) { + dateToCompare = date(); } - - final int year = cal.get(Calendar.YEAR); - final int month = cal.get(Calendar.MONTH); - final int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH); - final boolean isLastDayOfMonth = dayOfMonth == cal.getActualMaximum(Calendar.DAY_OF_MONTH); - - cal.setTime(birthDay); - int age = year - cal.get(Calendar.YEAR); - - final int monthBirth = cal.get(Calendar.MONTH); - if (month == monthBirth) { - - final int dayOfMonthBirth = cal.get(Calendar.DAY_OF_MONTH); - final boolean isLastDayOfMonthBirth = dayOfMonthBirth == cal.getActualMaximum(Calendar.DAY_OF_MONTH); - if ((false == isLastDayOfMonth || false == isLastDayOfMonthBirth) && dayOfMonth < dayOfMonthBirth) { - // 如果生日在当月,但是未达到生日当天的日期,年龄减一 - age--; - } - } else if (month < monthBirth) { - // 如果当前月份未达到生日的月份,年龄计算减一 - age--; - } - - return age; + return age(birthday.getTime(), dateToCompare.getTime()); } /** @@ -2042,38 +1735,6 @@ public class DateUtil { return Zodiac.getChineseZodiac(year); } - /** - * 获取指定日期字段的最小值,例如分钟的最小值是0 - * - * @param calendar {@link Calendar} - * @param dateField {@link DateField} - * @return 字段最小值 - * @see Calendar#getActualMinimum(int) - * @since 4.5.7 - */ - public static int getBeginValue(Calendar calendar, int dateField) { - if (Calendar.DAY_OF_WEEK == dateField) { - return calendar.getFirstDayOfWeek(); - } - return calendar.getActualMinimum(dateField); - } - - /** - * 获取指定日期字段的最大值,例如分钟的最大值是59 - * - * @param calendar {@link Calendar} - * @param dateField {@link DateField} - * @return 字段最大值 - * @see Calendar#getActualMaximum(int) - * @since 4.5.7 - */ - public static int getEndValue(Calendar calendar, int dateField) { - if (Calendar.DAY_OF_WEEK == dateField) { - return (calendar.getFirstDayOfWeek() + 6) % 7; - } - return calendar.getActualMaximum(dateField); - } - /** * {@code null}安全的日期比较,{@code null}对象排在末尾 * @@ -2086,18 +1747,6 @@ public class DateUtil { return CompareUtil.compare(date1, date2); } - /** - * {@code null}安全的{@link Calendar}比较,{@code null}小于任何日期 - * - * @param calendar1 日期1 - * @param calendar2 日期2 - * @return 比较结果,如果calendar1 < calendar2,返回数小于0,calendar1==calendar2返回0,calendar1 > calendar2 大于0 - * @since 4.6.2 - */ - public static int compare(Calendar calendar1, Calendar calendar2) { - return CompareUtil.compare(calendar1, calendar2); - } - /** * 纳秒转毫秒 * @@ -2131,17 +1780,6 @@ public class DateUtil { return null == date ? null : date.toInstant(); } - /** - * Calendar{@link Instant}对象 - * - * @param calendar Date对象 - * @return {@link Instant}对象 - * @since 5.0.5 - */ - public static Instant toInstant(Calendar calendar) { - return null == calendar ? null : calendar.toInstant(); - } - /** * Date对象转换为{@link Instant}对象 * @@ -2189,21 +1827,10 @@ public class DateUtil { return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); } - /** - * {@link Calendar} 转换为 {@link LocalDateTime},使用系统默认时区 - * - * @param calendar {@link Calendar} - * @return {@link LocalDateTime} - * @since 5.0.5 - */ - public static LocalDateTime toLocalDateTime(Calendar calendar) { - return LocalDateTime.ofInstant(calendar.toInstant(), calendar.getTimeZone().toZoneId()); - } - /** * {@link Date} 转换为 {@link LocalDateTime},使用系统默认时区 * - * @param date {@link Calendar} + * @param date {@link Date} * @return {@link LocalDateTime} * @since 5.0.5 */ @@ -2214,16 +1841,6 @@ public class DateUtil { // ------------------------------------------------------------------------ Private method start - /** - * 获得指定日期年份和季节
    - * 格式:[20131]表示2013年第一季度 - * - * @param cal 日期 - */ - private static String yearAndQuarter(Calendar cal) { - return StrUtil.builder().append(cal.get(Calendar.YEAR)).append(cal.get(Calendar.MONTH) / 3 + 1).toString(); - } - /** * 标准化日期,默认处理以空格区分的日期时间格式,空格前为日期,空格后为时间:
    * 将以下字符替换为"-" diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index 3688a7f8b..39f9d550b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -329,28 +329,36 @@ public class DateUtilTest { Assert.assertEquals("20190321", ymd); } - @SuppressWarnings("ConstantConditions") @Test public void parseTest5() { // 测试时间解析 + //noinspection ConstantConditions String time = DateUtil.parse("22:12:12").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("22:12:12", time); + //noinspection ConstantConditions time = DateUtil.parse("2:12:12").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("02:12:12", time); + //noinspection ConstantConditions time = DateUtil.parse("2:2:12").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("02:02:12", time); + //noinspection ConstantConditions time = DateUtil.parse("2:2:1").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("02:02:01", time); + //noinspection ConstantConditions time = DateUtil.parse("22:2:1").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("22:02:01", time); + //noinspection ConstantConditions time = DateUtil.parse("2:22:1").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("02:22:01", time); // 测试两位时间解析 + //noinspection ConstantConditions time = DateUtil.parse("2:22").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("02:22:00", time); + //noinspection ConstantConditions time = DateUtil.parse("12:22").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("12:22:00", time); + //noinspection ConstantConditions time = DateUtil.parse("12:2").toString(DatePattern.NORM_TIME_FORMAT); Assert.assertEquals("12:02:00", time); @@ -688,6 +696,14 @@ public class DateUtilTest { Assert.assertEquals(18, age); } + @Test(expected = IllegalArgumentException.class) + public void ageTest2(){ + String d1 = "2019-02-29"; + String d2 = "2018-02-28"; + final int age = DateUtil.age(DateUtil.parseDate(d1), DateUtil.parseDate(d2)); + Assert.assertEquals(18, age); + } + @Test public void isExpiredTest(){ DateTime startDate = DateUtil.parse("2019-12-01 17:02:30"); From 660b64daf6755171f8bbf1c544a97d2f301978e1 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 11 Apr 2020 13:58:41 +0800 Subject: [PATCH 035/157] fix test --- .../src/main/java/cn/hutool/core/date/CalendarUtil.java | 8 ++++---- .../src/test/java/cn/hutool/core/date/DateUtilTest.java | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java index 72baae414..50d807829 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java @@ -396,13 +396,13 @@ public class CalendarUtil { * @return 年龄 */ protected static int age(long birthday, long dateToCompare) { - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(dateToCompare); - - if (cal.before(birthday)) { + if (birthday > dateToCompare) { throw new IllegalArgumentException("Birthday is after dateToCompare!"); } + final Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(dateToCompare); + final int year = cal.get(Calendar.YEAR); final int month = cal.get(Calendar.MONTH); final int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH); diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index 39f9d550b..6123d4b76 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -700,8 +700,7 @@ public class DateUtilTest { public void ageTest2(){ String d1 = "2019-02-29"; String d2 = "2018-02-28"; - final int age = DateUtil.age(DateUtil.parseDate(d1), DateUtil.parseDate(d2)); - Assert.assertEquals(18, age); + DateUtil.age(DateUtil.parseDate(d1), DateUtil.parseDate(d2)); } @Test From 97ad2fbf087984abd7a4369894daebbf20262216 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 11 Apr 2020 14:09:07 +0800 Subject: [PATCH 036/157] release 5.3.0 --- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 78044e36e..502870dc4 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index bb9458001..54ae0fd7b 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index e885cda8f..8cd280943 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 67a2c345b..341b23ff4 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 82467a4a7..658fa90f2 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 98c2d9dad..b02bd5f35 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 68a16cf32..214c7dea1 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 549c8c6da..ef1908380 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 35777df68..83fd87432 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index c378809a7..3337dce21 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index eaa01abd2..0bf640418 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 0ab3cece5..2a174db29 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index c0925f434..0e3e0200d 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 6b4058a92..73e9727dc 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 29dad09b7..ecd34defd 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 42eee9aa1..8a2b84774 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index dc77c1722..b72982b59 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index e1b72b92a..db89d6d2c 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index f87b3cab0..22a51124a 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 0a7c45ab4..10847fa3d 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool-system diff --git a/pom.xml b/pom.xml index 0c7c027be..e59aaeaa2 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.0-SNAPSHOT + 5.3.0 hutool 提供丰富的Java工具方法 https://github.com/looly/hutool From 2034be3b5a0d405617c37f2eeaf7d5b2745767b0 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 11 Apr 2020 14:30:43 +0800 Subject: [PATCH 037/157] prepare 5.3.1 --- CHANGELOG.md | 7 +++++++ README.md | 10 +++++----- bin/version.txt | 2 +- docs/js/version.js | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 25 files changed, 35 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b39123a7..863b31606 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ ------------------------------------------------------------------------------------------------------------- +## 5.3.1 (2020-04-11) + +### 新特性 +### Bug修复 + +------------------------------------------------------------------------------------------------------------- + ## 5.3.0 (2020-04-07) ### 新特性 diff --git a/README.md b/README.md index 1349f9835..f46d02f4e 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ -- 主页:https://hutool.cn/ | https://www.hutool.club/ --

    - -- QQ群③:555368316 -- + -- QQ群③:555368316 -- -- QQ群④:718802356 --

    @@ -116,21 +116,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.3.0 + 5.3.1 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.3.0' +compile 'cn.hutool:hutool-all:5.3.1' ``` ### 非Maven项目 点击以下任一链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.3.0/) -- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.0/) +- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.3.1/) +- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.1/) > 注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类获工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index 03f488b07..c7cb1311a 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.3.0 +5.3.1 diff --git a/docs/js/version.js b/docs/js/version.js index 2f0178e29..d8cb08e4e 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.3.0' \ No newline at end of file +var version = '5.3.1' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 502870dc4..5cb798123 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 54ae0fd7b..73ba81733 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index 8cd280943..c1741120c 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 341b23ff4..de2cf188c 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 658fa90f2..b084260d8 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index b02bd5f35..efbfa6c48 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 214c7dea1..7cb098ebf 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index ef1908380..2dd4aaaa3 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 83fd87432..467835571 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 3337dce21..a15e2d902 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 0bf640418..20320a89c 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2a174db29..53e1784db 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 0e3e0200d..08f736dd7 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 73e9727dc..f56839e2a 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index ecd34defd..44aa620b9 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 8a2b84774..392fb6c21 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index b72982b59..262fb4025 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index db89d6d2c..022a96b3c 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 22a51124a..b959f92b2 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 10847fa3d..2f0242d9f 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index e59aaeaa2..b4e2d832b 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.0 + 5.3.1-SNAPSHOT hutool 提供丰富的Java工具方法 https://github.com/looly/hutool From d15ec3c647a46193e0ea83f66bace612bf18eb99 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 11 Apr 2020 18:48:18 +0800 Subject: [PATCH 038/157] fix readme --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index f46d02f4e..7dd0f85da 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@

    - + - - + + @@ -37,10 +37,9 @@

    - -- 主页:https://hutool.cn/ | https://www.hutool.club/ -- + -- 主页:https://hutool.cn/ --

    - -- QQ群③:555368316 -- -- QQ群④:718802356 --

    @@ -77,13 +76,13 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 | -------------------|---------------------------------------------------------------------------------- | | hutool-aop | JDK动态代理封装,提供非IOC下的切面支持 | | hutool-bloomFilter | 布隆过滤,提供一些Hash算法的布隆过滤 | -| hutool-cache | 简单缓存实现 | +| hutool-cache | 简单缓存实现 | | hutool-core | 核心,包括Bean操作、日期、各种Util等 | | hutool-cron | 定时任务模块,提供类Crontab表达式的定时任务 | -| hutool-crypto | 加密解密模块,提供对称、非对称和摘要算法封装 | +| hutool-crypto | 加密解密模块,提供对称、非对称和摘要算法封装 | | hutool-db | JDBC封装后的数据操作,基于ActiveRecord思想 | | hutool-dfa | 基于DFA模型的多关键字查找 | -| hutool-extra | 扩展模块,对第三方封装(模板引擎、邮件、Servlet、二维码、Emoji、FTP、分词等) | +| hutool-extra | 扩展模块,对第三方封装(模板引擎、邮件、Servlet、二维码、Emoji、FTP、分词等) | | hutool-http | 基于HttpUrlConnection的Http客户端封装 | | hutool-log | 自动识别日志实现的日志门面 | | hutool-script | 脚本执行封装,例如Javascript | @@ -91,7 +90,7 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 | hutool-system | 系统参数调用封装(JVM信息等) | | hutool-json | JSON实现 | | hutool-captcha | 图片验证码实现 | -| hutool-poi | 针对POI中Excel的封装 | +| hutool-poi | 针对POI中Excel和Word的封装 | | hutool-socket | 基于Java的NIO和AIO的Socket封装 | 可以根据需求对每个模块单独引入,也可以通过引入`hutool-all`方式引入所有模块。 @@ -101,10 +100,11 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 ## 文档 [中文文档](https://www.hutool.cn/docs/) -[中文文档(备用)](https://www.hutool.club/docs/) [参考API](https://apidoc.gitee.com/loolly/hutool/) +[视频介绍](https://www.bilibili.com/video/BV1bQ4y1M7d9?p=2) + ------------------------------------------------------------------------------- ## 安装 From efedd36696708136e5ee4813c78df17c0e4753bb Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 13 Apr 2020 10:41:59 +0800 Subject: [PATCH 039/157] fix bug and add method --- CHANGELOG.md | 4 ++ .../cn/hutool/core/collection/CollUtil.java | 44 +++++++++++++-- .../cn/hutool/core/collection/IterUtil.java | 13 +++++ .../cn/hutool/core/collection/ListUtil.java | 23 +++++--- .../main/java/cn/hutool/core/map/MapUtil.java | 50 ++++++++++++++++- .../main/java/cn/hutool/core/net/NetUtil.java | 3 +- .../hutool/core/collection/CollUtilTest.java | 54 +++++++------------ .../src/test/java/cn/hutool/db/CRUDTest.java | 3 +- .../cn/hutool/extra/servlet/ServletUtil.java | 6 +++ .../java/cn/hutool/json/InternalJSONUtil.java | 19 ++++--- .../main/java/cn/hutool/json/JSONArray.java | 2 +- .../main/java/cn/hutool/json/JSONObject.java | 3 +- .../main/java/cn/hutool/json/JSONTokener.java | 3 +- .../java/cn/hutool/json/JSONArrayTest.java | 11 ++++ .../java/cn/hutool/json/JSONObjectTest.java | 6 +-- 15 files changed, 183 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 863b31606..4e0cee698 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,11 @@ ## 5.3.1 (2020-04-11) ### 新特性 +* 【core 】 ListUtil、MapUtil、CollUtil增加empty方法 + ### Bug修复 +* 【json 】 修复解析JSON字符串时配置无法传递问题 +* 【core 】 修复ServletUtil.readCookieMap空指针问题(issue#827@Github) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index 744a5df93..b80714da4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -38,8 +38,10 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.NavigableSet; import java.util.Objects; import java.util.Set; +import java.util.SortedSet; import java.util.Stack; import java.util.TreeMap; import java.util.TreeSet; @@ -2362,15 +2364,14 @@ public class CollUtil { } /** - * 循环遍历Map,使用{@link KVConsumer} 接受遍历的每条数据,并针对每条数据做处理 + * 循环遍历Map,使用{@link KVConsumer} 接受遍历的每条数据,并针对每条数据做处理
    + * 和JDK8中的map.forEach不同的是,此方法支持index * * @param Key类型 * @param Value类型 * @param map {@link Map} * @param kvConsumer {@link KVConsumer} 遍历的每条数据处理器 - * @deprecated JDK8+中使用map.forEach */ - @Deprecated public static void forEach(Map map, KVConsumer kvConsumer) { int index = 0; for (Entry entry : map.entrySet()) { @@ -2561,6 +2562,43 @@ public class CollUtil { return Collections.unmodifiableCollection(c); } + /** + * 根据给定的集合类型,返回对应的空集合,支持类型包括: + * * + *
    +	 *     1. NavigableSet
    +	 *     2. SortedSet
    +	 *     3. Set
    +	 *     4. List
    +	 * 
    + * + * @param 元素类型 + * @param 集合类型 + * @return 空集合 + * @since 5.3.1 + */ + @SuppressWarnings("unchecked") + public static > T empty(Class collectionClass) { + if (null == collectionClass) { + return (T) Collections.emptyList(); + } + + if (Set.class.isAssignableFrom(collectionClass)) { + if (NavigableSet.class == collectionClass) { + return (T) Collections.emptyNavigableSet(); + } else if (SortedSet.class == collectionClass) { + return (T) Collections.emptySortedSet(); + } else { + return (T) Collections.emptySet(); + } + } else if (List.class.isAssignableFrom(collectionClass)) { + return (T) Collections.emptyList(); + } + + // 不支持空集合的集合类型 + throw new IllegalArgumentException(StrUtil.format("[{}] is not support to get empty!", collectionClass)); + } + // ---------------------------------------------------------------------------------------------- Interface start /** diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java index aa2537d65..23675e37b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java @@ -9,6 +9,7 @@ import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import java.util.ArrayList; +import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; @@ -676,4 +677,16 @@ public class IterUtil { } return map; } + + /** + * 返回一个空Iterator + * + * @param 元素类型 + * @return 空Iterator + * @see Collections#emptyIterator() + * @since 5.3.1 + */ + public static Iterator empty() { + return Collections.emptyIterator(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java index 104f7c9eb..babe5f555 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java @@ -425,13 +425,13 @@ public class ListUtil { /** * 获取匹配规则定义中匹配到元素的所有位置 * - * @param 元素类型 - * @param list 列表 + * @param 元素类型 + * @param list 列表 * @param matcher 匹配器,为空则全部匹配 * @return 位置数组 * @since 5.2.5 */ - public static int[] indexOfAll(List list, Matcher matcher){ + public static int[] indexOfAll(List list, Matcher matcher) { final List indexList = new ArrayList<>(); if (null != list) { int index = 0; @@ -448,12 +448,23 @@ public class ListUtil { /** * 将对应List转换为不可修改的List * - * @param list Map - * @param 元素类型 - * @return 不修改Map + * @param list List + * @param 元素类型 + * @return 不可修改List * @since 5.2.6 */ public static List unmodifiable(List list) { return Collections.unmodifiableList(list); } + + /** + * 获取一个空List + * + * @param 元素类型 + * @return 空的List + * @since 5.2.6 + */ + public static List empty() { + return Collections.emptyList(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java index 256b3d346..c649a1177 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java @@ -22,7 +22,9 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.NavigableMap; import java.util.Set; +import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; @@ -687,8 +689,8 @@ public class MapUtil { * @param 键和值类型 * @param map Map对象,键值类型必须一致 * @return 互换后的Map - * @since 3.2.2 * @see #inverse(Map) + * @since 3.2.2 */ public static Map reverse(Map map) { return filter(map, (Editor>) t -> new Entry() { @@ -1067,4 +1069,50 @@ public class MapUtil { return map; } + + /** + * 返回一个空Map + * + * @param 键类型 + * @param 值类型 + * @return 空Map + * @see Collections#emptyMap() + * @since 5.3.1 + */ + public static Map empty() { + return Collections.emptyMap(); + } + + /** + * 根据传入的Map类型不同,返回对应类型的空Map,支持类型包括: + * + *
    +	 *     1. NavigableMap
    +	 *     2. SortedMap
    +	 *     3. Map
    +	 * 
    + * + * @param 键类型 + * @param 值类型 + * @param Map类型 + * @param mapClass Map类型,null返回默认的Map + * @return 空Map + * @since 5.3.1 + */ + @SuppressWarnings("unchecked") + public static > T empty(Class mapClass) { + if (null == mapClass) { + return (T) Collections.emptyMap(); + } + if (NavigableMap.class == mapClass) { + return (T) Collections.emptyNavigableMap(); + } else if (SortedMap.class == mapClass) { + return (T) Collections.emptySortedMap(); + } else if (Map.class == mapClass) { + return (T) Collections.emptyMap(); + } + + // 不支持空集合的集合类型 + throw new IllegalArgumentException(StrUtil.format("[{}] is not support to get empty!", mapClass)); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java index 74a355455..6709c719e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java @@ -28,6 +28,7 @@ import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.List; @@ -715,7 +716,7 @@ public class NetUtil { */ public static List parseCookies(String cookieStr){ if(StrUtil.isBlank(cookieStr)){ - return CollUtil.newArrayList(); + return Collections.emptyList(); } return HttpCookie.parse(cookieStr); } diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java index c6f6190fe..bb23a8678 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java @@ -5,11 +5,14 @@ import cn.hutool.core.lang.Dict; import cn.hutool.core.lang.Editor; import cn.hutool.core.lang.Filter; import cn.hutool.core.map.MapUtil; +import lombok.AllArgsConstructor; +import lombok.Data; import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; @@ -20,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.SortedSet; /** * 集合工具类单元测试 @@ -179,7 +183,6 @@ public class CollUtilTest { map.put("c", "3"); final String[] result = new String[1]; - //noinspection deprecation CollUtil.forEach(map, (key, value, index) -> { if (key.equals("a")) { result[0] = value; @@ -304,6 +307,20 @@ public class CollUtilTest { Assert.assertEquals(new Integer(14), map.get("王五")); } + @Test + public void emptyTest() { + final SortedSet emptySortedSet = CollUtil.empty(SortedSet.class); + Assert.assertEquals(Collections.emptySortedSet(), emptySortedSet); + + final Set emptySet = CollUtil.empty(Set.class); + Assert.assertEquals(Collections.emptySet(), emptySet); + + final List emptyList = CollUtil.empty(List.class); + Assert.assertEquals(Collections.emptyList(), emptyList); + } + + @Data + @AllArgsConstructor public static class TestBean { private String name; private int age; @@ -313,41 +330,6 @@ public class CollUtilTest { this.name = name; this.age = age; } - - public TestBean(String name, int age, Date createTime) { - this.name = name; - this.age = age; - this.createTime = createTime; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - public Date getCreateTime() { - return createTime; - } - - public void setCreateTime(Date createTime) { - this.createTime = createTime; - } - - @Override - public String toString() { - return "TestBeans [name=" + name + ", age=" + age + "]"; - } } @Test diff --git a/hutool-db/src/test/java/cn/hutool/db/CRUDTest.java b/hutool-db/src/test/java/cn/hutool/db/CRUDTest.java index f86c90a67..671c98069 100644 --- a/hutool-db/src/test/java/cn/hutool/db/CRUDTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/CRUDTest.java @@ -87,7 +87,8 @@ public class CRUDTest { @Test public void findInTest2() throws SQLException { - List results = db.findAll(Entity.create("user").set("id", new Condition("id", new long[]{1, 2, 3}))); + List results = db.findAll(Entity.create("user") + .set("id", new Condition("id", new long[]{1, 2, 3}))); Assert.assertEquals(2, results.size()); } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java index b7bb02aa3..1f2ffd4d1 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java @@ -10,6 +10,7 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.NetUtil; import cn.hutool.core.net.multipart.MultipartFormData; import cn.hutool.core.net.multipart.UploadSetting; @@ -418,6 +419,11 @@ public class ServletUtil { * @return Cookie map */ public static Map readCookieMap(HttpServletRequest httpServletRequest) { + final Cookie[] cookies = httpServletRequest.getCookies(); + if(ArrayUtil.isEmpty(cookies)){ + return MapUtil.empty(); + } + return IterUtil.toMap( new ArrayIter<>(httpServletRequest.getCookies()), new CaseInsensitiveMap<>(), diff --git a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java index b3be2fee6..0d5501c24 100644 --- a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java @@ -206,8 +206,13 @@ final class InternalJSONUtil { } /** - * 默认情况下是否忽略null值的策略选择
    - * JavaBean默认忽略null值,其它对象不忽略 + * 默认情况下是否忽略null值的策略选择,以下对象不忽略null值,其它对象忽略: + * + *
    +	 *     1. CharSequence
    +	 *     2. JSONTokener
    +	 *     3. Map
    +	 * 
    * * @param obj 需要检查的对象 * @return 是否忽略null值 @@ -234,13 +239,13 @@ final class InternalJSONUtil { //默认使用时间戳 long timeMillis; - if(dateObj instanceof TemporalAccessor){ - timeMillis = DateUtil.toInstant((TemporalAccessor)dateObj).toEpochMilli(); - } else if(dateObj instanceof Date){ + if (dateObj instanceof TemporalAccessor) { + timeMillis = DateUtil.toInstant((TemporalAccessor) dateObj).toEpochMilli(); + } else if (dateObj instanceof Date) { timeMillis = ((Date) dateObj).getTime(); - } else if(dateObj instanceof Calendar){ + } else if (dateObj instanceof Calendar) { timeMillis = ((Calendar) dateObj).getTimeInMillis(); - } else{ + } else { throw new UnsupportedOperationException("Unsupported Date type: " + dateObj.getClass()); } return String.valueOf(timeMillis); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index f19764969..48cae3772 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.*; +import static cn.hutool.json.JSONConverter.jsonConvert; /** * JSON数组
    diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index fdfd36098..ea5f6c000 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -124,7 +124,8 @@ public class JSONObject implements JSON, JSONGetter, Map *
  • value为Map,将键值对加入JSON对象
  • *
  • value为JSON字符串(CharSequence),使用JSONTokener解析
  • *
  • value为JSONTokener,直接解析
  • - *
  • value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  • + *
  • value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。 + * 例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  • * * * @param source JavaBean或者Map对象或者String diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java index 30a940f25..086c941d4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java @@ -45,7 +45,7 @@ public class JSONTokener { /** * JSON配置 */ - private JSONConfig config; + private final JSONConfig config; // ------------------------------------------------------------------------------------ Constructor start @@ -63,6 +63,7 @@ public class JSONTokener { this.index = 0; this.character = 1; this.line = 1; + this.config = config; } /** diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java index b6d0f0c51..f766bb2e3 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java @@ -45,6 +45,17 @@ public class JSONArrayTest { Assert.assertEquals(array.get(0), "value1"); } + @Test + public void parseWithNullTest() { + String jsonStr = "[{\"grep\":\"4.8\",\"result\":\"右\"},{\"grep\":\"4.8\",\"result\":null}]"; + JSONArray jsonArray = JSONUtil.parseArray(jsonStr); + Assert.assertFalse(jsonArray.getJSONObject(1).containsKey("result")); + + // 不忽略null,则null的键值对被保留 + jsonArray = new JSONArray(jsonStr, false); + Assert.assertTrue(jsonArray.getJSONObject(1).containsKey("result")); + } + @Test public void parseFileTest() { JSONArray array = JSONUtil.readJSONArray(FileUtil.file("exam_test.json"), CharsetUtil.CHARSET_UTF_8); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index b6cdf73ea..297e67bc0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -186,11 +186,11 @@ public class JSONObjectTest { } @Test - public void toBeanTest3() { + public void toBeanWithNullTest() { String jsonStr = "{'data':{'userName':'ak','password': null}}"; + Console.log(JSONUtil.parseObj(jsonStr)); UserWithMap user = JSONUtil.toBean(JSONUtil.parseObj(jsonStr), UserWithMap.class); - // Bean默认忽略null - Assert.assertFalse(user.getData().containsKey("password")); + Assert.assertTrue(user.getData().containsKey("password")); } @Test From b4568dd2e3e7e4afa0499dbfac3b253c7572cb7b Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 14 Apr 2020 15:19:25 +0800 Subject: [PATCH 040/157] fix alias --- CHANGELOG.md | 1 + .../main/java/cn/hutool/poi/excel/ExcelWriter.java | 7 ++++++- .../java/cn/hutool/poi/excel/test/ExcelWriteTest.java | 11 +++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e0cee698..2a06170fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### 新特性 * 【core 】 ListUtil、MapUtil、CollUtil增加empty方法 +* 【poi 】 调整别名策略,clearHeaderAlias和addHeaderAlias同时清除aliasComparator(issue#828@Github) ### Bug修复 * 【json 】 修复解析JSON字符串时配置无法传递问题 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java index 1f2a27cbb..3cec6ba4e 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java @@ -200,7 +200,6 @@ public class ExcelWriter extends ExcelBase { */ public ExcelWriter reset() { resetRow(); - this.aliasComparator = null; this.headLocationCache = null; return this; } @@ -426,6 +425,8 @@ public class ExcelWriter extends ExcelBase { */ public ExcelWriter setHeaderAlias(Map headerAlias) { this.headerAlias = headerAlias; + // 新增别名时清除比较器缓存 + this.aliasComparator = null; return this; } @@ -437,6 +438,8 @@ public class ExcelWriter extends ExcelBase { */ public ExcelWriter clearHeaderAlias() { this.headerAlias = null; + // 清空别名时清除比较器缓存 + this.aliasComparator = null; return this; } @@ -467,6 +470,8 @@ public class ExcelWriter extends ExcelBase { } this.headerAlias = headerAlias; headerAlias.put(name, alias); + // 新增别名时清除比较器缓存 + this.aliasComparator = null; return this; } diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelWriteTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelWriteTest.java index abc3edb28..c192efdcc 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelWriteTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelWriteTest.java @@ -450,15 +450,26 @@ public class ExcelWriteTest { rows.add(tempList); } ExcelWriter writer = ExcelUtil.getWriter("D:\\test\\multiSheet.xlsx", "正常数据"); + writer.addHeaderAlias("1", "row1"); + writer.addHeaderAlias("3", "row2"); + writer.setOnlyAlias(true); + writer.write(rows, true); writer.autoSizeColumnAll(); + //表2 writer.setSheet("当前重复数据"); + writer.clearHeaderAlias(); + writer.addHeaderAlias("3", "行3"); + writer.addHeaderAlias("1", "行1"); writer.write(rows, true); writer.autoSizeColumnAll(); + + //表3 writer.setSheet("历史重复数据"); writer.write(rows, true); writer.autoSizeColumnAll(); + writer.close(); } From 8102b313737189f457493cc7d6279556fa697ccc Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 14 Apr 2020 16:26:32 +0800 Subject: [PATCH 041/157] fix sm2 bug --- CHANGELOG.md | 1 + .../java/cn/hutool/crypto/asymmetric/SM2.java | 22 ------------------- .../java/cn/hutool/crypto/test/SM2Test.java | 2 -- .../cn/hutool/poi/excel/ExcelPicUtil.java | 15 ++++++------- 4 files changed, 8 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a06170fe..dcdfb9f07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ ### Bug修复 * 【json 】 修复解析JSON字符串时配置无法传递问题 * 【core 】 修复ServletUtil.readCookieMap空指针问题(issue#827@Github) +* 【crypto 】 修复SM2中检查密钥导致的问题(issue#I1EC47@Gitee) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index cf329334a..643aec6a1 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -182,7 +182,6 @@ public class SM2 extends AbstractAsymmetricCrypto { if (KeyType.PublicKey != keyType) { throw new IllegalArgumentException("Encrypt is only support by public key"); } - checkKey(keyType); return encrypt(data, new ParametersWithRandom(getCipherParameters(keyType))); } @@ -229,7 +228,6 @@ public class SM2 extends AbstractAsymmetricCrypto { if (KeyType.PrivateKey != keyType) { throw new IllegalArgumentException("Decrypt is only support by private key"); } - checkKey(keyType); return decrypt(data, getCipherParameters(keyType)); } @@ -450,26 +448,6 @@ public class SM2 extends AbstractAsymmetricCrypto { return null; } - /** - * 检查对应类型的Key是否存在 - * - * @param keyType key类型 - */ - private void checkKey(KeyType keyType) { - switch (keyType) { - case PublicKey: - if (null == this.publicKey) { - throw new NullPointerException("No public key provided"); - } - break; - case PrivateKey: - if (null == this.privateKey) { - throw new NullPointerException("No private key provided"); - } - break; - } - } - /** * 获取{@link SM2Engine},此对象为懒加载模式 * diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java index 7772c00c7..bef62d52c 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java @@ -171,9 +171,7 @@ public class SM2Test { String id = "31323334353637383132333435363738"; final SM2 sm2 = new SM2(d, x, y); - final String sign = sm2.signHex(data, id); - Assert.assertTrue(sm2.verifyHex(data, sign)); } } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelPicUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelPicUtil.java index a5000445f..c1dad24b2 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelPicUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelPicUtil.java @@ -1,9 +1,8 @@ package cn.hutool.poi.excel; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; import org.apache.poi.hssf.usermodel.HSSFClientAnchor; import org.apache.poi.hssf.usermodel.HSSFPicture; import org.apache.poi.hssf.usermodel.HSSFPictureData; @@ -20,9 +19,9 @@ import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker; -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.StrUtil; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** * Excel图片工具类 @@ -87,7 +86,7 @@ public class ExcelPicUtil { * @return 图片映射,键格式:行_列,值:{@link PictureData} */ private static Map getPicMapXlsx(XSSFWorkbook workbook, int sheetIndex) { - final Map sheetIndexPicMap = new HashMap(); + final Map sheetIndexPicMap = new HashMap<>(); final XSSFSheet sheet = workbook.getSheetAt(sheetIndex); XSSFDrawing drawing; for (POIXMLDocumentPart dr : sheet.getRelations()) { From ca7c407a1c975cf5d4ce9e9be883d252163ed454 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 16 Apr 2020 01:13:58 +0800 Subject: [PATCH 042/157] add UrlDecoder --- CHANGELOG.md | 4 + .../java/cn/hutool/core/map/TableMap.java | 33 +- .../java/cn/hutool/core/net/URLDecoder.java | 73 +++ .../java/cn/hutool/core/net/URLEncoder.java | 14 +- .../cn/hutool/core/net/url/UrlBuilder.java | 495 ++++++++++++++++++ .../java/cn/hutool/core/net/url/UrlPath.java | 172 ++++++ .../java/cn/hutool/core/net/url/UrlQuery.java | 245 +++++++++ .../cn/hutool/core/net/url/package-info.java | 7 + .../java/cn/hutool/core/util/CharUtil.java | 11 + .../java/cn/hutool/core/util/StrUtil.java | 2 +- .../java/cn/hutool/core/util/URLUtil.java | 181 ++++--- .../cn/hutool/core/bean/BeanUtilTest.java | 2 - .../cn/hutool/core/net/UrlBuilderTest.java | 173 ++++++ .../java/cn/hutool/crypto/symmetric/AES.java | 10 +- .../main/java/cn/hutool/http/HttpRequest.java | 43 +- .../java/cn/hutool/http/HttpResponse.java | 8 +- .../main/java/cn/hutool/http/HttpUtil.java | 144 +---- .../cn/hutool/http/test/HttpUtilTest.java | 14 +- 18 files changed, 1402 insertions(+), 229 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/net/url/package-info.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index dcdfb9f07..dc5cabd29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,15 @@ ### 新特性 * 【core 】 ListUtil、MapUtil、CollUtil增加empty方法 * 【poi 】 调整别名策略,clearHeaderAlias和addHeaderAlias同时清除aliasComparator(issue#828@Github) +* 【core 】 修改StrUtil.equals逻辑,改为contentEquals +* 【core 】 增加URLUtil.UrlDecoder ### Bug修复 * 【json 】 修复解析JSON字符串时配置无法传递问题 * 【core 】 修复ServletUtil.readCookieMap空指针问题(issue#827@Github) * 【crypto 】 修复SM2中检查密钥导致的问题(issue#I1EC47@Gitee) +* 【core 】 修复TableMap.isEmpty判断问题 +* 【http 】 修复编码后的URL传入导致二次编码的问题(issue#I1EIMN@Gitee) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java index 8fcfd9919..3671b5026 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java @@ -2,7 +2,6 @@ package cn.hutool.core.map; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; -import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import java.io.Serializable; @@ -10,6 +9,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -24,7 +25,7 @@ import java.util.Set; * @param 值类型 * @author looly */ -public class TableMap implements Map, Serializable { +public class TableMap implements Map, Iterable>, Serializable { private static final long serialVersionUID = 1L; private final List keys; @@ -58,7 +59,7 @@ public class TableMap implements Map, Serializable { @Override public boolean isEmpty() { - return ArrayUtil.isEmpty(keys); + return CollUtil.isEmpty(keys); } @Override @@ -159,13 +160,37 @@ public class TableMap implements Map, Serializable { @SuppressWarnings("NullableProblems") @Override public Set> entrySet() { - HashSet> hashSet = new HashSet<>(); + final Set> hashSet = new LinkedHashSet<>(); for (int i = 0; i < size(); i++) { hashSet.add(new Entry<>(keys.get(i), values.get(i))); } return hashSet; } + @Override + public Iterator> iterator() { + return new Iterator>() { + private final Iterator keysIter = keys.iterator(); + private final Iterator valuesIter = values.iterator(); + + @Override + public boolean hasNext() { + return keysIter.hasNext() && valuesIter.hasNext(); + } + + @Override + public Map.Entry next() { + return new Entry<>(keysIter.next(), valuesIter.next()); + } + + @Override + public void remove() { + keysIter.remove(); + valuesIter.remove(); + } + }; + } + private static class Entry implements Map.Entry { private final K key; diff --git a/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java b/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java new file mode 100644 index 000000000..9509fa72c --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/net/URLDecoder.java @@ -0,0 +1,73 @@ +package cn.hutool.core.net; + +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.StrUtil; + +import java.io.ByteArrayOutputStream; +import java.io.Serializable; +import java.nio.charset.Charset; + +/** + * URL解码,数据内容的类型是 application/x-www-form-urlencoded。 + * + *
    + * 1. 将%20转换为空格 ;
    + * 2. 将"%xy"转换为文本形式,xy是两位16进制的数值;
    + * 3. 跳过不符合规范的%形式,直接输出
    + * 
    + * + * @author looly + */ +public class URLDecoder implements Serializable { + private static final long serialVersionUID = 1L; + + private static final byte ESCAPE_CHAR = '%'; + + /** + * 解码 + * + * @param str 包含URL编码后的字符串 + * @param charset 编码 + * @return 解码后的字符串 + */ + public static String decode(String str, Charset charset) { + return StrUtil.str(decode(StrUtil.bytes(str, charset)), charset); + } + + /** + * 解码 + * + * @param bytes url编码的bytes + * @return 解码后的bytes + */ + public static byte[] decode(byte[] bytes) { + if (bytes == null) { + return null; + } + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(bytes.length); + int b; + for (int i = 0; i < bytes.length; i++) { + b = bytes[i]; + if (b == '+') { + buffer.write(CharUtil.SPACE); + } else if (b == ESCAPE_CHAR) { + if (i + 1 < bytes.length) { + final int u = CharUtil.digit16(bytes[i + 1]); + if (u >= 0 && i + 2 < bytes.length) { + final int l = CharUtil.digit16(bytes[i + 2]); + if (l >= 0) { + buffer.write((char) ((u << 4) + l)); + i += 2; + continue; + } + } + } + // 跳过不符合规范的%形式 + buffer.write(b); + } else { + buffer.write(b); + } + } + return buffer.toByteArray(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java b/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java index 096b0025d..c01365f00 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java @@ -1,5 +1,8 @@ package cn.hutool.core.net; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.HexUtil; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; @@ -7,9 +10,6 @@ import java.io.Serializable; import java.nio.charset.Charset; import java.util.BitSet; -import cn.hutool.core.util.CharUtil; -import cn.hutool.core.util.HexUtil; - /** * URL编码,数据内容的类型是 application/x-www-form-urlencoded。 * @@ -17,7 +17,6 @@ import cn.hutool.core.util.HexUtil; * 1.字符"a"-"z","A"-"Z","0"-"9",".","-","*",和"_" 都不会被编码; * 2.将空格转换为%20 ; * 3.将非文本内容转换成"%xy"的形式,xy是两位16进制的数值; - * 4.在每个 name=value 对之间放置 & 符号。 * * * @author looly, @@ -196,10 +195,8 @@ public class URLEncoder implements Serializable{ * @return 编码后的字符串 */ public String encode(String path, Charset charset) { - - int maxBytesPerChar = 10; final StringBuilder rewrittenPath = new StringBuilder(path.length()); - ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar); + ByteArrayOutputStream buf = new ByteArrayOutputStream(); OutputStreamWriter writer = new OutputStreamWriter(buf, charset); int c; @@ -221,9 +218,8 @@ public class URLEncoder implements Serializable{ } byte[] ba = buf.toByteArray(); - for (int j = 0; j < ba.length; j++) { + for (byte toEncode : ba) { // Converting each byte in the buffer - byte toEncode = ba[j]; rewrittenPath.append('%'); HexUtil.appendHex(rewrittenPath, toEncode, false); } diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java new file mode 100644 index 000000000..5fb76ff06 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -0,0 +1,495 @@ +package cn.hutool.core.net.url; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; + +import java.io.Serializable; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLStreamHandler; +import java.nio.charset.Charset; + +/** + * URL 生成器,格式形如: + *
    + * [scheme:]scheme-specific-part[#fragment]
    + * [scheme:][//authority][path][?query][#fragment]
    + * [scheme:][//host:port][path][?query][#fragment]
    + * 
    + * + * @author looly + * @see Uniform Resource Identifier + * @since 5.3.1 + */ +public final class UrlBuilder implements Serializable { + private static final long serialVersionUID = 1L; + private static final String DEFAULT_SCHEME = "http"; + + /** + * 协议,例如http + */ + private String scheme; + /** + * 主机,例如127.0.0.1 + */ + private String host; + /** + * 端口,默认-1 + */ + private int port = -1; + /** + * 路径,例如/aa/bb/cc + */ + private UrlPath path; + /** + * 查询语句,例如a=1&b=2 + */ + private UrlQuery query; + /** + * 标识符,例如#后边的部分 + */ + private String fragment; + + /** + * 编码,用于URLEncode和URLDecode + */ + private Charset charset; + + /** + * 使用URI构建UrlBuilder + * + * @param uri URI + * @param charset 编码,用于URLEncode和URLDecode + * @return UrlBuilder + */ + public static UrlBuilder of(URI uri, Charset charset) { + return of(uri.getScheme(), uri.getHost(), uri.getPort(), uri.getPath(), uri.getRawQuery(), uri.getFragment(), charset); + } + + /** + * 使用URL字符串构建UrlBuilder + * + * @param httpUrl URL字符串 + * @param charset 编码,用于URLEncode和URLDecode + * @return UrlBuilder + */ + public static UrlBuilder ofHttp(String httpUrl, Charset charset) { + Assert.notBlank(httpUrl, "Http url must be not blank!"); + + final int sepIndex = httpUrl.indexOf("://"); + if (sepIndex < 0) { + httpUrl = "http://" + httpUrl.trim(); + } + return of(httpUrl, charset); + } + + /** + * 使用URL字符串构建UrlBuilder + * + * @param url URL字符串 + * @param charset 编码,用于URLEncode和URLDecode + * @return UrlBuilder + */ + public static UrlBuilder of(String url, Charset charset) { + Assert.notBlank(url, "Url must be not blank!"); + return of(URLUtil.url(url.trim()), charset); + } + + /** + * 使用URL构建UrlBuilder + * + * @param url URL + * @param charset 编码,用于URLEncode和URLDecode + * @return UrlBuilder + */ + public static UrlBuilder of(URL url, Charset charset) { + return of(url.getProtocol(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef(), charset); + } + + /** + * 构建UrlBuilder + * + * @param scheme 协议,默认http + * @param host 主机,例如127.0.0.1 + * @param port 端口,-1表示默认端口 + * @param path 路径,例如/aa/bb/cc + * @param query 查询,例如a=1&b=2 + * @param fragment 标识符例如#后边的部分 + * @param charset 编码,用于URLEncode和URLDecode + * @return UrlBuilder + */ + public static UrlBuilder of(String scheme, String host, int port, String path, String query, String fragment, Charset charset) { + return of(scheme, host, port, UrlPath.of(path, charset), UrlQuery.of(query, charset), fragment, charset); + } + + /** + * 构建UrlBuilder + * + * @param scheme 协议,默认http + * @param host 主机,例如127.0.0.1 + * @param port 端口,-1表示默认端口 + * @param path 路径,例如/aa/bb/cc + * @param query 查询,例如a=1&b=2 + * @param fragment 标识符例如#后边的部分 + * @param charset 编码,用于URLEncode和URLDecode + * @return UrlBuilder + */ + public static UrlBuilder of(String scheme, String host, int port, UrlPath path, UrlQuery query, String fragment, Charset charset) { + return new UrlBuilder(scheme, host, port, path, query, fragment, charset); + } + + /** + * 创建空的UrlBuilder + * + * @return UrlBuilder + */ + public static UrlBuilder create() { + return new UrlBuilder(); + } + + /** + * 构造 + */ + public UrlBuilder() { + this.charset = CharsetUtil.CHARSET_UTF_8; + } + + /** + * 构造 + * + * @param scheme 协议,默认http + * @param host 主机,例如127.0.0.1 + * @param port 端口,-1表示默认端口 + * @param path 路径,例如/aa/bb/cc + * @param query 查询,例如a=1&b=2 + * @param fragment 标识符例如#后边的部分 + * @param charset 编码,用于URLEncode和URLDecode + */ + public UrlBuilder(String scheme, String host, int port, UrlPath path, UrlQuery query, String fragment, Charset charset) { + this.charset = charset; + this.scheme = scheme; + this.host = host; + this.port = port; + this.path = path; + this.query = query; + this.setFragment(fragment); + } + + /** + * 获取协议,例如http + * + * @return 协议,例如http + */ + public String getScheme() { + return scheme; + } + + /** + * 获取协议,例如http,如果用户未定义协议,使用默认的http协议 + * + * @return 协议,例如http + */ + public String getSchemeWithDefault() { + return StrUtil.emptyToDefault(this.scheme, DEFAULT_SCHEME); + } + + /** + * 设置协议,例如http + * + * @param scheme 协议,例如http + * @return this + */ + public UrlBuilder setScheme(String scheme) { + this.scheme = scheme; + return this; + } + + /** + * 获取 主机,例如127.0.0.1 + * + * @return 主机,例如127.0.0.1 + */ + public String getHost() { + return host; + } + + /** + * 设置主机,例如127.0.0.1 + * + * @param host 主机,例如127.0.0.1 + * @return this + */ + public UrlBuilder setHost(String host) { + this.host = host; + return this; + } + + /** + * 获取端口,默认-1 + * + * @return 端口,默认-1 + */ + public int getPort() { + return port; + } + + /** + * 设置端口,默认-1 + * + * @param port 端口,默认-1 + * @return this + */ + public UrlBuilder setPort(int port) { + this.port = port; + return this; + } + + /** + * 获得authority部分 + * + * @return authority部分 + */ + public String getAuthority() { + return (port < 0) ? host : host + ":" + port; + } + + /** + * 获取路径,例如/aa/bb/cc + * + * @return 路径,例如/aa/bb/cc + */ + public UrlPath getPath() { + return path; + } + + /** + * 获得路径,例如/aa/bb/cc + * + * @return 路径,例如/aa/bb/cc + */ + public String getPathStr() { + return null == this.path ? StrUtil.SLASH : this.path.build(charset); + } + + /** + * 设置路径,例如/aa/bb/cc,将覆盖之前所有的path相关设置 + * + * @param path 路径,例如/aa/bb/cc + * @return this + */ + public UrlBuilder setPath(UrlPath path) { + this.path = path; + return this; + } + + /** + * 增加路径节点 + * + * @param segment 路径节点 + * @return this + */ + public UrlBuilder addPath(String segment) { + if (StrUtil.isBlank(segment)) { + return this; + } + if (null == this.path) { + this.path = new UrlPath(); + } + this.path.add(segment); + return this; + } + + /** + * 追加path节点 + * + * @param segment path节点 + * @return this + */ + public UrlBuilder appendPath(CharSequence segment) { + if (StrUtil.isEmpty(segment)) { + return this; + } + + if (this.path == null) { + this.path = new UrlPath(); + } + this.path.add(segment); + return this; + } + + /** + * 获取查询语句,例如a=1&b=2 + * + * @return 查询语句,例如a=1&b=2 + */ + public UrlQuery getQuery() { + return query; + } + + /** + * 获取查询语句,例如a=1&b=2 + * + * @return 查询语句,例如a=1&b=2 + */ + public String getQueryStr() { + return null == this.query ? null : this.query.build(this.charset); + } + + /** + * 设置查询语句,例如a=1&b=2,将覆盖之前所有的query相关设置 + * + * @param query 查询语句,例如a=1&b=2 + * @return this + */ + public UrlBuilder setQuery(UrlQuery query) { + this.query = query; + return this; + } + + /** + * 添加查询项,支持重复键 + * + * @param key 键 + * @param value 值 + * @return this + */ + public UrlBuilder addQuery(String key, String value) { + if (StrUtil.isEmpty(key)) { + return this; + } + + if (this.query == null) { + this.query = new UrlQuery(); + } + this.query.add(key, value); + return this; + } + + /** + * 获取标识符,#后边的部分 + * + * @return 标识符,例如#后边的部分 + */ + public String getFragment() { + return fragment; + } + + /** + * 获取标识符,#后边的部分 + * + * @return 标识符,例如#后边的部分 + */ + public String getFragmentEncoded() { + return URLUtil.encodeAll(this.fragment, this.charset); + } + + /** + * 设置标识符,例如#后边的部分 + * + * @param fragment 标识符,例如#后边的部分 + * @return this + */ + public UrlBuilder setFragment(String fragment) { + if (StrUtil.isEmpty(fragment)) { + this.fragment = null; + } + this.fragment = StrUtil.removePrefix(fragment, "#"); + return this; + } + + /** + * 获取编码,用于URLEncode和URLDecode + * + * @return 编码 + */ + public Charset getCharset() { + return charset; + } + + /** + * 设置编码,用于URLEncode和URLDecode + * + * @param charset 编码 + * @return this + */ + public UrlBuilder setCharset(Charset charset) { + this.charset = charset; + return this; + } + + /** + * 创建URL字符串 + * + * @return URL字符串 + */ + public String build() { + return toURL().toString(); + } + + /** + * 转换为{@link URL} 对象 + * + * @return {@link URL} + */ + public URL toURL() { + return toURL(null); + } + + /** + * 转换为{@link URL} 对象 + * + * @param handler {@link URLStreamHandler},null表示默认 + * @return {@link URL} + */ + public URL toURL(URLStreamHandler handler) { + final StringBuilder fileBuilder = new StringBuilder(); + + // path + fileBuilder.append(StrUtil.blankToDefault(getPathStr(), StrUtil.SLASH)); + + // query + final String query = getQueryStr(); + if (StrUtil.isNotBlank(query)) { + fileBuilder.append('?').append(query); + } + + // fragment + if (StrUtil.isNotBlank(this.fragment)) { + fileBuilder.append('#').append(getFragmentEncoded()); + } + + try { + return new URL(getSchemeWithDefault(), host, port, fileBuilder.toString(), handler); + } catch (MalformedURLException e) { + return null; + } + } + + /** + * 转换为URI + * + * @return URI + */ + public URI toURI() { + try { + return new URI( + getSchemeWithDefault(), + getAuthority(), + getPathStr(), + getQueryStr(), + getFragmentEncoded()); + } catch (URISyntaxException e) { + return null; + } + } + + @Override + public String toString() { + return build(); + } + +} \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java new file mode 100644 index 000000000..01708765a --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java @@ -0,0 +1,172 @@ +package cn.hutool.core.net.url; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; + +import java.nio.charset.Charset; +import java.util.LinkedList; +import java.util.List; +import java.util.StringTokenizer; + +/** + * URL中Path部分的封装 + * + * @author looly + * @since 5.3.1 + */ +public class UrlPath { + private List segments; + private boolean withEngTag; + + /** + * 构建UrlPath + * + * @param pathStr 初始化的路径字符串 + * @param charset decode用的编码,null表示不做decode + * @return {@link UrlPath} + */ + public static UrlPath of(String pathStr, Charset charset) { + final UrlPath urlPath = new UrlPath(); + urlPath.parse(pathStr, charset); + return urlPath; + } + + /** + * 是否path的末尾加 / + * @param withEngTag 是否path的末尾加 / + * @return this + */ + public UrlPath setWithEndTag(boolean withEngTag){ + this.withEngTag = withEngTag; + return this; + } + + /** + * 获取path的节点列表 + * + * @return 节点列表 + */ + public List getSegments() { + return this.segments; + } + + /** + * 获得指定节点 + * + * @param index 节点位置 + * @return 节点,无节点或者越界返回null + */ + public String getSegment(int index) { + if (null == this.segments || index >= this.segments.size()) { + return null; + } + return this.segments.get(index); + } + + /** + * 添加到path最后面 + */ + public UrlPath add(CharSequence segment) { + add(segment, false); + return this; + } + + /** + * 添加到path最前面 + */ + public UrlPath addBefore(CharSequence segment) { + add(segment, true); + return this; + } + + /** + * 解析path + * + * @param path 路径,类似于aaa/bb/ccc + * @param charset decode编码,null表示不解码 + * @return this + */ + public UrlPath parse(String path, Charset charset) { + UrlPath urlPath = new UrlPath(); + + if (StrUtil.isNotEmpty(path)) { + path = path.trim(); + + final StringTokenizer tokenizer = new StringTokenizer(path, "/"); + while (tokenizer.hasMoreTokens()) { + add(URLUtil.decode(tokenizer.nextToken(), charset)); + } + } + + return urlPath; + } + + /** + * 构建path,前面带'/' + * + * @param charset encode编码,null表示不做encode + * @return 如果没有任何内容,则返回空字符串"" + */ + public String build(Charset charset) { + if (CollUtil.isEmpty(this.segments)) { + return StrUtil.EMPTY; + } + + final StringBuilder builder = new StringBuilder(); + for (String segment : segments) { + builder.append(CharUtil.SLASH).append(URLUtil.encodeAll(segment, charset)); + } + if(withEngTag || StrUtil.isEmpty(builder)){ + builder.append(CharUtil.SLASH); + } + return builder.toString(); + } + + @Override + public String toString() { + return build(null); + } + + /** + * 增加节点 + * + * @param segment 节点 + * @param before 是否在前面添加 + */ + private void add(CharSequence segment, boolean before) { + final String seg = fixSegment(segment); + if (null == seg) { + return; + } + + + if (this.segments == null) { + this.segments = new LinkedList<>(); + } + if (before) { + this.segments.add(0, seg); + } else { + this.segments.add(seg); + } + } + + /** + * 修正节点,包括去掉前后的/,去掉空白符 + * @param segment 节点 + * @return 修正后的节点 + */ + private static String fixSegment(CharSequence segment) { + if (StrUtil.isEmpty(segment) || "/".contentEquals(segment)) { + return null; + } + + String segmentStr = StrUtil.str(segment); + segmentStr = StrUtil.trim(segmentStr); + segmentStr = StrUtil.removePrefix(segmentStr, "/"); + segmentStr = StrUtil.removeSuffix(segmentStr, "/"); + segmentStr = StrUtil.trim(segmentStr); + return segmentStr; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java new file mode 100644 index 000000000..710cb9485 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -0,0 +1,245 @@ +package cn.hutool.core.net.url; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.IterUtil; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.map.TableMap; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; + +import java.nio.charset.Charset; +import java.util.Iterator; +import java.util.Map; + +/** + * URL中查询字符串部分的封装,类似于: + *
    + *   key1=v1&key2=&key3=v3
    + * 
    + * + * @author looly + * @since 5.3.1 + */ +public class UrlQuery { + + private final TableMap query; + + /** + * 构建UrlQuery + * + * @param queryMap 初始化的查询键值对 + * @return {@link UrlQuery} + */ + public static UrlQuery of(Map queryMap) { + return new UrlQuery(queryMap); + } + + /** + * 构建UrlQuery + * + * @param queryStr 初始化的查询字符串 + * @param charset decode用的编码,null表示不做decode + * @return {@link UrlQuery} + */ + public static UrlQuery of(String queryStr, Charset charset) { + final UrlQuery urlQuery = new UrlQuery(); + urlQuery.parse(queryStr, charset); + return urlQuery; + } + + /** + * 构造 + */ + public UrlQuery() { + this(null); + } + + /** + * 构造 + * + * @param queryMap 初始化的查询键值对 + */ + public UrlQuery(Map queryMap) { + if(MapUtil.isNotEmpty(queryMap)) { + query = new TableMap<>(queryMap.size()); + addAll(queryMap); + } else{ + query = new TableMap<>(MapUtil.DEFAULT_INITIAL_CAPACITY); + } + } + + /** + * 增加键值对 + * + * @param key 键 + * @param value 值,集合和数组转换为逗号分隔形式 + * @return this + */ + public UrlQuery add(CharSequence key, Object value) { + this.query.put(key, toStr(value)); + return this; + } + + /** + * 批量增加键值对 + * + * @param queryMap query中的键值对 + * @return this + */ + public UrlQuery addAll(Map queryMap) { + if(MapUtil.isNotEmpty(queryMap)) { + queryMap.forEach(this::add); + } + return this; + } + + /** + * 解析URL中的查询字符串 + * + * @param queryStr 查询字符串,类似于key1=v1&key2=&key3=v3 + * @param charset decode编码,null表示不做decode + * @return this + */ + public UrlQuery parse(String queryStr, Charset charset) { + if (StrUtil.isBlank(queryStr)) { + return this; + } + + // 去掉Path部分 + int pathEndPos = queryStr.indexOf('?'); + if (pathEndPos > -1) { + queryStr = StrUtil.subSuf(queryStr, pathEndPos + 1); + if (StrUtil.isBlank(queryStr)) { + return this; + } + } + + final int len = queryStr.length(); + String name = null; + int pos = 0; // 未处理字符开始位置 + int i; // 未处理字符结束位置 + char c; // 当前字符 + for (i = 0; i < len; i++) { + c = queryStr.charAt(i); + if (c == '=') { // 键值对的分界点 + if (null == name) { + // name可以是"" + name = queryStr.substring(pos, i); + } + pos = i + 1; + } else if (c == '&') { // 参数对的分界点 + if (null == name && pos != i) { + // 对于像&a&这类无参数值的字符串,我们将name为a的值设为"" + addParam(queryStr.substring(pos, i), StrUtil.EMPTY, charset); + } else if (name != null) { + addParam(name, queryStr.substring(pos, i), charset); + name = null; + } + pos = i + 1; + } + } + + // 处理结尾 + if (pos != i) { + if (name == null) { + addParam(queryStr.substring(pos, i), StrUtil.EMPTY, charset); + } else { + addParam(name, queryStr.substring(pos, i), charset); + } + } else if (name != null) { + addParam(name, StrUtil.EMPTY, charset); + } + return this; + } + + /** + * 获得查询的Map + * + * @return 查询的Map,只读 + */ + public Map getQueryMap(){ + return MapUtil.unmodifiable(this.query); + } + + /** + * 获取查询值 + * @param key 键 + * @return 值 + */ + public CharSequence get(CharSequence key){ + if(MapUtil.isEmpty(this.query)){ + return null; + } + return this.query.get(key); + } + + /** + * 构建URL查询字符串,即将key-value键值对转换为key1=v1&key2=&key3=v3形式 + * + * @param charset encode编码,null表示不做encode编码 + * @return URL查询字符串 + */ + public String build(Charset charset) { + if (MapUtil.isEmpty(this.query)) { + return StrUtil.EMPTY; + } + + final StringBuilder sb = new StringBuilder(); + boolean isFirst = true; + CharSequence key; + CharSequence value; + for (Map.Entry entry : this.query) { + if (isFirst) { + isFirst = false; + } else { + sb.append("&"); + } + key = entry.getKey(); + if (StrUtil.isNotEmpty(key)) { + sb.append(URLUtil.encodeAll(StrUtil.str(key), charset)).append("="); + value = entry.getValue(); + if (StrUtil.isNotEmpty(value)) { + sb.append(URLUtil.encodeAll(StrUtil.str(value), charset)); + } + } + } + return sb.toString(); + } + + @Override + public String toString() { + return build(null); + } + + /** + * 对象转换为字符串,用于URL的Query中 + * + * @param value 值 + * @return 字符串 + */ + private static String toStr(Object value) { + String result; + if (value instanceof Iterable) { + result = CollUtil.join((Iterable) value, ","); + } else if (value instanceof Iterator) { + result = IterUtil.join((Iterator) value, ","); + } else { + result = Convert.toStr(value); + } + return result; + } + + /** + * 将键值对加入到值为List类型的Map中 + * + * @param name key + * @param value value + * @param charset 编码 + */ + private void addParam(String name, String value, Charset charset) { + name = URLUtil.decode(name, charset); + value = URLUtil.decode(value, charset); + this.query.put(name, value); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/package-info.java b/hutool-core/src/main/java/cn/hutool/core/net/url/package-info.java new file mode 100644 index 000000000..1df7ac259 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/package-info.java @@ -0,0 +1,7 @@ +/** + * URL相关工具 + * + * @author looly + * @since 5.3.1 + */ +package cn.hutool.core.net.url; \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java index 4610c0877..f46e87e93 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java @@ -332,4 +332,15 @@ public class CharUtil { public static int getType(int c) { return Character.getType(c); } + + /** + * 获取给定字符的16进制数值 + * + * @param b 字符 + * @return 16进制字符 + * @since 5.3.1 + */ + public static int digit16(int b) { + return Character.digit(b, 16); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java index e9b5b26b2..d2e89f18a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java @@ -2207,7 +2207,7 @@ public class StrUtil { if (ignoreCase) { return str1.toString().equalsIgnoreCase(str2.toString()); } else { - return str1.equals(str2); + return str1.toString().contentEquals(str2); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 56e4ea049..8a75e508a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -6,7 +6,9 @@ import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.net.URLDecoder; import cn.hutool.core.net.URLEncoder; +import cn.hutool.core.net.url.UrlQuery; import java.io.BufferedReader; import java.io.File; @@ -18,44 +20,69 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.net.URLDecoder; import java.net.URLStreamHandler; import java.nio.charset.Charset; +import java.util.Map; import java.util.jar.JarFile; /** * 统一资源定位符相关工具类 * * @author xiaoleilu - * */ public class URLUtil { - /** 针对ClassPath路径的伪协议前缀(兼容Spring): "classpath:" */ + /** + * 针对ClassPath路径的伪协议前缀(兼容Spring): "classpath:" + */ public static final String CLASSPATH_URL_PREFIX = "classpath:"; - /** URL 前缀表示文件: "file:" */ + /** + * URL 前缀表示文件: "file:" + */ public static final String FILE_URL_PREFIX = "file:"; - /** URL 前缀表示jar: "jar:" */ + /** + * URL 前缀表示jar: "jar:" + */ public static final String JAR_URL_PREFIX = "jar:"; - /** URL 前缀表示war: "war:" */ + /** + * URL 前缀表示war: "war:" + */ public static final String WAR_URL_PREFIX = "war:"; - /** URL 协议表示文件: "file" */ + /** + * URL 协议表示文件: "file" + */ public static final String URL_PROTOCOL_FILE = "file"; - /** URL 协议表示Jar文件: "jar" */ + /** + * URL 协议表示Jar文件: "jar" + */ public static final String URL_PROTOCOL_JAR = "jar"; - /** URL 协议表示zip文件: "zip" */ + /** + * URL 协议表示zip文件: "zip" + */ public static final String URL_PROTOCOL_ZIP = "zip"; - /** URL 协议表示WebSphere文件: "wsjar" */ + /** + * URL 协议表示WebSphere文件: "wsjar" + */ public static final String URL_PROTOCOL_WSJAR = "wsjar"; - /** URL 协议表示JBoss zip文件: "vfszip" */ + /** + * URL 协议表示JBoss zip文件: "vfszip" + */ public static final String URL_PROTOCOL_VFSZIP = "vfszip"; - /** URL 协议表示JBoss文件: "vfsfile" */ + /** + * URL 协议表示JBoss文件: "vfsfile" + */ public static final String URL_PROTOCOL_VFSFILE = "vfsfile"; - /** URL 协议表示JBoss VFS资源: "vfs" */ + /** + * URL 协议表示JBoss VFS资源: "vfs" + */ public static final String URL_PROTOCOL_VFS = "vfs"; - /** Jar路径以及内部文件路径的分界符: "!/" */ + /** + * Jar路径以及内部文件路径的分界符: "!/" + */ public static final String JAR_URL_SEPARATOR = "!/"; - /** WAR路径及内部文件路径分界符 */ + /** + * WAR路径及内部文件路径分界符 + */ public static final String WAR_URL_SEPARATOR = "*/"; /** @@ -71,7 +98,7 @@ public class URLUtil { /** * 通过一个字符串形式的URL地址创建URL对象 * - * @param url URL + * @param url URL * @param handler {@link URLStreamHandler} * @return URL对象 * @since 4.1.1 @@ -111,7 +138,7 @@ public class URLUtil { /** * 将URL字符串转换为URL对象,并做必要验证 * - * @param urlStr URL字符串 + * @param urlStr URL字符串 * @param handler {@link URLStreamHandler} * @return URL * @since 4.1.9 @@ -167,7 +194,7 @@ public class URLUtil { /** * 获得URL * - * @param path 相对给定 class所在的路径 + * @param path 相对给定 class所在的路径 * @param clazz 指定class * @return URL * @see ResourceUtil#getResource(String, Class) @@ -181,7 +208,7 @@ public class URLUtil { * * @param file URL对应的文件对象 * @return URL - * @exception UtilException MalformedURLException + * @throws UtilException MalformedURLException */ public static URL getURL(File file) { Assert.notNull(file, "File is null !"); @@ -197,7 +224,7 @@ public class URLUtil { * * @param files URL对应的文件对象 * @return URL - * @exception UtilException MalformedURLException + * @throws UtilException MalformedURLException */ public static URL[] getURLs(File... files) { final URL[] urls = new URL[files.length]; @@ -219,8 +246,8 @@ public class URLUtil { * @return 域名的URI * @since 4.6.9 */ - public static URI getHost(URL url){ - if(null == url){ + public static URI getHost(URL url) { + if (null == url) { return null; } @@ -234,12 +261,26 @@ public class URLUtil { /** * 补全相对路径 * - * @param baseUrl 基准URL + * @param baseUrl 基准URL * @param relativePath 相对URL * @return 相对路径 - * @exception UtilException MalformedURLException + * @throws UtilException MalformedURLException + * @deprecated 拼写错误,请使用{@link #completeUrl(String, String)} */ + @Deprecated public static String complateUrl(String baseUrl, String relativePath) { + return completeUrl(baseUrl, relativePath); + } + + /** + * 补全相对路径 + * + * @param baseUrl 基准URL + * @param relativePath 相对URL + * @return 相对路径 + * @throws UtilException MalformedURLException + */ + public static String completeUrl(String baseUrl, String relativePath) { baseUrl = normalize(baseUrl, false); if (StrUtil.isBlank(baseUrl)) { return null; @@ -260,7 +301,7 @@ public class URLUtil { * * @param url URL * @return 编码后的URL - * @exception UtilException UnsupportedEncodingException + * @throws UtilException UnsupportedEncodingException */ public static String encodeAll(String url) { return encodeAll(url, CharsetUtil.CHARSET_UTF_8); @@ -270,12 +311,15 @@ public class URLUtil { * 编码URL
    * 将需要转换的内容(ASCII码形式之外的内容),用十六进制表示法转换出来,并在之前加上%开头。 * - * @param url URL - * @param charset 编码 + * @param url URL + * @param charset 编码,为null表示不编码 * @return 编码后的URL - * @exception UtilException UnsupportedEncodingException + * @throws UtilException UnsupportedEncodingException */ public static String encodeAll(String url, Charset charset) throws UtilException { + if (null == charset) { + return url; + } try { return java.net.URLEncoder.encode(url, charset.toString()); } catch (UnsupportedEncodingException e) { @@ -290,7 +334,7 @@ public class URLUtil { * * @param url URL * @return 编码后的URL - * @exception UtilException UnsupportedEncodingException + * @throws UtilException UnsupportedEncodingException * @since 3.1.2 */ public static String encode(String url) throws UtilException { @@ -304,7 +348,7 @@ public class URLUtil { * * @param url URL * @return 编码后的URL - * @exception UtilException UnsupportedEncodingException + * @throws UtilException UnsupportedEncodingException * @since 3.1.2 */ public static String encodeQuery(String url) throws UtilException { @@ -316,7 +360,7 @@ public class URLUtil { * 将需要转换的内容(ASCII码形式之外的内容),用十六进制表示法转换出来,并在之前加上%开头。
    * 此方法用于URL自动编码,类似于浏览器中键入地址自动编码,对于像类似于“/”的字符不再编码 * - * @param url 被编码内容 + * @param url 被编码内容 * @param charset 编码 * @return 编码后的字符 * @since 4.4.1 @@ -336,7 +380,7 @@ public class URLUtil { * 将需要转换的内容(ASCII码形式之外的内容),用十六进制表示法转换出来,并在之前加上%开头。
    * 此方法用于POST请求中的请求体自动编码,转义大部分特殊字符 * - * @param url 被编码内容 + * @param url 被编码内容 * @param charset 编码 * @return 编码后的字符 * @since 4.4.1 @@ -356,10 +400,10 @@ public class URLUtil { * 将需要转换的内容(ASCII码形式之外的内容),用十六进制表示法转换出来,并在之前加上%开头。
    * 此方法用于URL自动编码,类似于浏览器中键入地址自动编码,对于像类似于“/”的字符不再编码 * - * @param url URL + * @param url URL * @param charset 编码 * @return 编码后的URL - * @exception UtilException UnsupportedEncodingException + * @throws UtilException UnsupportedEncodingException */ public static String encode(String url, String charset) throws UtilException { if (StrUtil.isEmpty(url)) { @@ -373,10 +417,10 @@ public class URLUtil { * 将需要转换的内容(ASCII码形式之外的内容),用十六进制表示法转换出来,并在之前加上%开头。
    * 此方法用于POST请求中的请求体自动编码,转义大部分特殊字符 * - * @param url URL + * @param url URL * @param charset 编码 * @return 编码后的URL - * @exception UtilException UnsupportedEncodingException + * @throws UtilException UnsupportedEncodingException */ public static String encodeQuery(String url, String charset) throws UtilException { return encodeQuery(url, StrUtil.isBlank(charset) ? CharsetUtil.defaultCharset() : CharsetUtil.charset(charset)); @@ -388,7 +432,7 @@ public class URLUtil { * * @param url URL * @return 解码后的URL - * @exception UtilException UnsupportedEncodingException + * @throws UtilException UnsupportedEncodingException * @since 3.1.2 */ public static String decode(String url) throws UtilException { @@ -396,38 +440,32 @@ public class URLUtil { } /** - * 解码application/x-www-form-urlencoded字符 + * 解码application/x-www-form-urlencoded字符
    + * 将%开头的16进制表示的内容解码。 * * @param content 被解码内容 - * @param charset 编码 + * @param charset 编码,null表示不解码 * @return 编码后的字符 * @since 4.4.1 */ public static String decode(String content, Charset charset) { if (null == charset) { - charset = CharsetUtil.defaultCharset(); + return content; } - return decode(content, charset.name()); + return URLDecoder.decode(content, charset); } /** - * 解码URL
    + * 解码application/x-www-form-urlencoded字符
    * 将%开头的16进制表示的内容解码。 * - * @param url URL + * @param content URL * @param charset 编码 * @return 解码后的URL - * @exception UtilException UnsupportedEncodingException + * @throws UtilException UnsupportedEncodingException */ - public static String decode(String url, String charset) throws UtilException { - if (StrUtil.isEmpty(url)) { - return url; - } - try { - return URLDecoder.decode(url, charset); - } catch (UnsupportedEncodingException e) { - throw new UtilException(e, "Unsupported encoding: [{}]", charset); - } + public static String decode(String content, String charset) throws UtilException { + return decode(content, CharsetUtil.charset(charset)); } /** @@ -435,7 +473,7 @@ public class URLUtil { * * @param uriStr URI路径 * @return path - * @exception UtilException 包装URISyntaxException + * @throws UtilException 包装URISyntaxException */ public static String getPath(String uriStr) { URI uri; @@ -476,7 +514,7 @@ public class URLUtil { * * @param url URL * @return URI - * @exception UtilException 包装URISyntaxException + * @throws UtilException 包装URISyntaxException */ public static URI toURI(URL url) throws UtilException { return toURI(url, false); @@ -485,10 +523,10 @@ public class URLUtil { /** * 转URL为URI * - * @param url URL + * @param url URL * @param isEncode 是否编码参数中的特殊字符(默认UTF-8编码) * @return URI - * @exception UtilException 包装URISyntaxException + * @throws UtilException 包装URISyntaxException * @since 4.6.9 */ public static URI toURI(URL url, boolean isEncode) throws UtilException { @@ -504,7 +542,7 @@ public class URLUtil { * * @param location 字符串路径 * @return URI - * @exception UtilException 包装URISyntaxException + * @throws UtilException 包装URISyntaxException */ public static URI toURI(String location) throws UtilException { return toURI(location, false); @@ -516,11 +554,11 @@ public class URLUtil { * @param location 字符串路径 * @param isEncode 是否编码参数中的特殊字符(默认UTF-8编码) * @return URI - * @exception UtilException 包装URISyntaxException + * @throws UtilException 包装URISyntaxException * @since 4.6.9 */ public static URI toURI(String location, boolean isEncode) throws UtilException { - if(isEncode){ + if (isEncode) { location = encode(location); } try { @@ -590,7 +628,7 @@ public class URLUtil { /** * 获得Reader * - * @param url {@link URL} + * @param url {@link URL} * @param charset 编码 * @return {@link BufferedReader} * @since 3.2.1 @@ -636,7 +674,7 @@ public class URLUtil { * 1. 多个/替换为一个 * * - * @param url URL字符串 + * @param url URL字符串 * @param isEncodePath 是否对URL中path部分的中文和特殊字符做转义(不包括 http:, /和域名部分) * @return 标准化后的URL字符串 * @since 4.4.1 @@ -663,7 +701,7 @@ public class URLUtil { body = StrUtil.subPre(body, paramsSepIndex); } - if(StrUtil.isNotEmpty(body)){ + if (StrUtil.isNotEmpty(body)) { // 去除开头的\或者/ //noinspection ConstantConditions body = body.replaceAll("^[\\\\/]+", StrUtil.EMPTY); @@ -683,4 +721,21 @@ public class URLUtil { } return protocol + domain + StrUtil.nullToEmpty(path) + StrUtil.nullToEmpty(params); } + + /** + * 将Map形式的Form表单数据转换为Url参数形式
    + * paramMap中如果key为空(null和"")会被忽略,如果value为null,会被做为空白符("")
    + * 会自动url编码键和值 + * + *
    +	 * key1=v1&key2=&key3=v3
    +	 * 
    + * + * @param paramMap 表单数据 + * @param charset 编码,编码为null表示不编码 + * @return url参数 + */ + public static String buildQuery(Map paramMap, Charset charset) { + return UrlQuery.of(paramMap).build(charset); + } } \ No newline at end of file diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java index b1fa0d05d..360051230 100644 --- a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java @@ -4,7 +4,6 @@ import cn.hutool.core.annotation.Alias; import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.bean.copier.ValueProvider; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.lang.Console; import cn.hutool.core.map.MapUtil; import lombok.Getter; import lombok.Setter; @@ -154,7 +153,6 @@ public class BeanUtilTest { person.setSlow(true); Map map = BeanUtil.beanToMap(person); - Console.log(map); Assert.assertEquals("sub名字", map.get("aliasSubName")); } diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java new file mode 100644 index 000000000..777169541 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java @@ -0,0 +1,173 @@ +package cn.hutool.core.net; + +import cn.hutool.core.net.url.UrlBuilder; +import cn.hutool.core.util.CharsetUtil; +import org.junit.Assert; +import org.junit.Test; + +public class UrlBuilderTest { + + @Test + public void buildTest() { + String buildUrl = UrlBuilder.create().setHost("www.baidu.com").build(); + Assert.assertEquals("http://www.baidu.com/", buildUrl); + } + + @Test + public void testHost() { + String buildUrl = UrlBuilder.create() + .setScheme("https") + .setHost("www.baidu.com").build(); + Assert.assertEquals("https://www.baidu.com/", buildUrl); + } + + @Test + public void testHostPort() { + String buildUrl = UrlBuilder.create() + .setScheme("https") + .setHost("www.baidu.com") + .setPort(8080) + .build(); + Assert.assertEquals("https://www.baidu.com:8080/", buildUrl); + } + + @Test + public void testPathAndQuery() { + final String buildUrl = UrlBuilder.create() + .setScheme("https") + .setHost("www.baidu.com") + .addPath("/aaa").addPath("bbb") + .addQuery("ie", "UTF-8") + .addQuery("wd", "test") + .build(); + + Assert.assertEquals("https://www.baidu.com/aaa/bbb?ie=UTF-8&wd=test", buildUrl); + } + + @Test + public void testQueryWithChinese() { + final String buildUrl = UrlBuilder.create() + .setScheme("https") + .setHost("www.baidu.com") + .addPath("/aaa").addPath("bbb") + .addQuery("ie", "UTF-8") + .addQuery("wd", "测试") + .build(); + + Assert.assertEquals("https://www.baidu.com/aaa/bbb?ie=UTF-8&wd=%E6%B5%8B%E8%AF%95", buildUrl); + } + + @Test + public void testMultiQueryWithChinese() { + final String buildUrl = UrlBuilder.create() + .setScheme("https") + .setHost("www.baidu.com") + .addPath("/s") + .addQuery("ie", "UTF-8") + .addQuery("ie", "GBK") + .addQuery("wd", "测试") + .build(); + + Assert.assertEquals("https://www.baidu.com/s?ie=UTF-8&ie=GBK&wd=%E6%B5%8B%E8%AF%95", buildUrl); + } + + @Test + public void testFragment() { + String buildUrl = new UrlBuilder() + .setScheme("https") + .setHost("www.baidu.com") + .setFragment("abc").build(); + Assert.assertEquals("https://www.baidu.com/#abc", buildUrl); + } + + @Test + public void testChineseFragment() { + String buildUrl = new UrlBuilder() + .setScheme("https") + .setHost("www.baidu.com") + .setFragment("测试").build(); + Assert.assertEquals("https://www.baidu.com/#%E6%B5%8B%E8%AF%95", buildUrl); + } + + @Test + public void testChineseFragmentWithPath() { + String buildUrl = new UrlBuilder() + .setScheme("https") + .setHost("www.baidu.com") + .addPath("/s") + .setFragment("测试").build(); + Assert.assertEquals("https://www.baidu.com/s#%E6%B5%8B%E8%AF%95", buildUrl); + } + + @Test + public void testChineseFragmentWithPathAndQuery() { + String buildUrl = new UrlBuilder() + .setScheme("https") + .setHost("www.baidu.com") + .addPath("/s") + .addQuery("wd", "test") + .setFragment("测试").build(); + Assert.assertEquals("https://www.baidu.com/s?wd=test#%E6%B5%8B%E8%AF%95", buildUrl); + } + + @Test + public void ofTest() { + final UrlBuilder builder = UrlBuilder.of("http://www.baidu.com/aaa/bbb/?a=1&b=2#frag1", CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("http", builder.getScheme()); + Assert.assertEquals("www.baidu.com", builder.getHost()); + + Assert.assertEquals("aaa", builder.getPath().getSegment(0)); + Assert.assertEquals("bbb", builder.getPath().getSegment(1)); + + Assert.assertEquals("1", builder.getQuery().get("a")); + Assert.assertEquals("2", builder.getQuery().get("b")); + + Assert.assertEquals("frag1", builder.getFragment()); + } + + @Test + public void ofWithChineseTest() { + final UrlBuilder builder = UrlBuilder.ofHttp("www.baidu.com/aaa/bbb/?a=张三&b=%e6%9d%8e%e5%9b%9b#frag1", CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("http", builder.getScheme()); + Assert.assertEquals("www.baidu.com", builder.getHost()); + + Assert.assertEquals("aaa", builder.getPath().getSegment(0)); + Assert.assertEquals("bbb", builder.getPath().getSegment(1)); + + Assert.assertEquals("张三", builder.getQuery().get("a")); + Assert.assertEquals("李四", builder.getQuery().get("b")); + + Assert.assertEquals("frag1", builder.getFragment()); + } + + @Test + public void ofWithBlankTest() { + final UrlBuilder builder = UrlBuilder.ofHttp(" www.baidu.com/aaa/bbb/?a=张三&b=%e6%9d%8e%e5%9b%9b#frag1", CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("http", builder.getScheme()); + Assert.assertEquals("www.baidu.com", builder.getHost()); + + Assert.assertEquals("aaa", builder.getPath().getSegment(0)); + Assert.assertEquals("bbb", builder.getPath().getSegment(1)); + + Assert.assertEquals("张三", builder.getQuery().get("a")); + Assert.assertEquals("李四", builder.getQuery().get("b")); + + Assert.assertEquals("frag1", builder.getFragment()); + } + + @Test + public void ofSpecialTest() { + //测试不规范的或者无需解码的字符串是否成功解码 + final UrlBuilder builder = UrlBuilder.ofHttp(" www.baidu.com/aaa/bbb/?a=张三&b=%%e5%9b%9b#frag1", CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("http", builder.getScheme()); + Assert.assertEquals("www.baidu.com", builder.getHost()); + + Assert.assertEquals("aaa", builder.getPath().getSegment(0)); + Assert.assertEquals("bbb", builder.getPath().getSegment(1)); + + Assert.assertEquals("张三", builder.getQuery().get("a")); + Assert.assertEquals("%四", builder.getQuery().get("b")); + + Assert.assertEquals("frag1", builder.getFragment()); + } +} diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/AES.java b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/AES.java index 758bd7838..4b34b67e1 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/AES.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/AES.java @@ -1,14 +1,14 @@ package cn.hutool.crypto.symmetric; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; - import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.Mode; import cn.hutool.crypto.Padding; import cn.hutool.crypto.SecureUtil; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; + /** * AES加密算法实现
    * 高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法
    @@ -105,7 +105,7 @@ public class AES extends SymmetricCrypto { * @since 4.6.7 */ public AES(Mode mode, Padding padding, SecretKey key, byte[] iv) { - this(mode, padding, key, ArrayUtil.isEmpty(iv) ? ((IvParameterSpec) null) : new IvParameterSpec(iv)); + this(mode, padding, key, ArrayUtil.isEmpty(iv) ? null : new IvParameterSpec(iv)); } /** @@ -153,7 +153,7 @@ public class AES extends SymmetricCrypto { public AES(String mode, String padding, byte[] key, byte[] iv) { this(mode, padding,// SecureUtil.generateKey(SymmetricAlgorithm.AES.getValue(), key),// - ArrayUtil.isEmpty(iv) ? ((IvParameterSpec) null) : new IvParameterSpec(iv)); + ArrayUtil.isEmpty(iv) ? null : new IvParameterSpec(iv)); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index d8ffbdbfd..462df202f 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -12,11 +12,11 @@ import cn.hutool.core.io.resource.MultiResource; import cn.hutool.core.io.resource.Resource; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.URLUtil; import cn.hutool.http.cookie.GlobalCookieManager; import cn.hutool.http.ssl.SSLSocketFactoryBuilder; @@ -96,7 +96,7 @@ public class HttpRequest extends HttpBase { GlobalCookieManager.setCookieManager(null); } - private String url; + private UrlBuilder url; private URLStreamHandler urlHandler; private Method method = Method.GET; /** @@ -128,10 +128,6 @@ public class HttpRequest extends HttpBase { * 是否禁用缓存 */ private boolean isDisableCache; - /** - * 是否对url中的参数进行编码 - */ - private boolean encodeUrlParams; /** * 是否是REST请求模式 */ @@ -168,8 +164,7 @@ public class HttpRequest extends HttpBase { * @param url URL */ public HttpRequest(String url) { - Assert.notBlank(url, "Param [url] can not be blank !"); - this.url = URLUtil.normalize(url, true); + setUrl(url); // 给定一个默认头信息 this.header(GlobalHeaders.INSTANCE.headers); } @@ -265,7 +260,7 @@ public class HttpRequest extends HttpBase { * @since 4.1.8 */ public String getUrl() { - return url; + return url.toString(); } /** @@ -276,7 +271,19 @@ public class HttpRequest extends HttpBase { * @since 4.1.8 */ public HttpRequest setUrl(String url) { - this.url = url; + this.url = UrlBuilder.ofHttp(url, this.charset); + return this; + } + + /** + * 设置URL + * + * @param urlBuilder url字符串 + * @return this + * @since 5.3.1 + */ + public HttpRequest setUrl(UrlBuilder urlBuilder) { + this.url = urlBuilder; return this; } @@ -774,9 +781,10 @@ public class HttpRequest extends HttpBase { * @param isEncodeUrlParams 是否对URL中的参数进行编码 * @return this * @since 4.4.1 + * @deprecated 编码自动完成,无需设置 */ + @Deprecated public HttpRequest setEncodeUrlParams(boolean isEncodeUrlParams) { - this.encodeUrlParams = isEncodeUrlParams; return this; } @@ -925,10 +933,6 @@ public class HttpRequest extends HttpBase { public HttpResponse execute(boolean isAsync) { // 初始化URL urlWithParamIfGet(); - // 编码URL - if (this.encodeUrlParams) { - this.url = HttpUtil.encodeParams(this.url, this.charset); - } // 初始化 connection initConnection(); @@ -982,7 +986,8 @@ public class HttpRequest extends HttpBase { this.httpConnection.disconnectQuietly(); } - this.httpConnection = HttpConnection.create(URLUtil.toUrlForHttp(this.url, this.urlHandler), this.proxy)// + this.httpConnection = HttpConnection + .create(this.url.toURL(this.urlHandler), this.proxy)// .setMethod(this.method)// .setHttpsInfo(this.hostnameVerifier, this.ssf)// .setConnectTimeout(this.connectionTimeout)// @@ -1016,9 +1021,9 @@ public class HttpRequest extends HttpBase { if (Method.GET.equals(method) && false == this.isRest) { // 优先使用body形式的参数,不存在使用form if (ArrayUtil.isNotEmpty(this.bodyBytes)) { - this.url = HttpUtil.urlWithForm(this.url, StrUtil.str(this.bodyBytes, this.charset), this.charset, false); + this.url.getQuery().parse(StrUtil.str(this.bodyBytes, this.charset), this.charset); } else { - this.url = HttpUtil.urlWithForm(this.url, this.form, this.charset, false); + this.url.getQuery().addAll(this.form); } } } @@ -1047,7 +1052,7 @@ public class HttpRequest extends HttpBase { if (responseCode != HttpURLConnection.HTTP_OK) { if (responseCode == HttpURLConnection.HTTP_MOVED_TEMP || responseCode == HttpURLConnection.HTTP_MOVED_PERM || responseCode == HttpURLConnection.HTTP_SEE_OTHER) { - this.url = httpConnection.header(Header.LOCATION); + setUrl(httpConnection.header(Header.LOCATION)); if (redirectCount < this.maxRedirectCount) { redirectCount++; return execute(); diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java index dea5e40d7..8d5aa6bfa 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java @@ -471,11 +471,11 @@ public class HttpResponse extends HttpBase implements Closeable { */ private String getFileNameFromDisposition() { String fileName = null; - final String desposition = header(Header.CONTENT_DISPOSITION); - if (StrUtil.isNotBlank(desposition)) { - fileName = ReUtil.get("filename=\"(.*?)\"", desposition, 1); + final String disposition = header(Header.CONTENT_DISPOSITION); + if (StrUtil.isNotBlank(disposition)) { + fileName = ReUtil.get("filename=\"(.*?)\"", disposition, 1); if (StrUtil.isBlank(fileName)) { - fileName = StrUtil.subAfter(desposition, "filename=", true); + fileName = StrUtil.subAfter(disposition, "filename=", true); } } return fileName; diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index 07bb0b8af..c24bf6e6b 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -1,14 +1,12 @@ package cn.hutool.http; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.collection.IterUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.FastByteArrayOutputStream; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.StreamProgress; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.net.url.UrlQuery; import cn.hutool.core.text.StrBuilder; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; @@ -23,13 +21,9 @@ import java.io.OutputStream; import java.net.HttpURLConnection; import java.nio.charset.Charset; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.regex.Pattern; /** @@ -390,44 +384,11 @@ public class HttpUtil { * * * @param paramMap 表单数据 - * @param charset 编码 + * @param charset 编码,null表示不encode键值对 * @return url参数 */ public static String toParams(Map paramMap, Charset charset) { - if (CollectionUtil.isEmpty(paramMap)) { - return StrUtil.EMPTY; - } - if (null == charset) {// 默认编码为系统编码 - charset = CharsetUtil.CHARSET_UTF_8; - } - - final StringBuilder sb = new StringBuilder(); - boolean isFirst = true; - String key; - Object value; - String valueStr; - for (Entry item : paramMap.entrySet()) { - if (isFirst) { - isFirst = false; - } else { - sb.append("&"); - } - key = item.getKey(); - value = item.getValue(); - if (value instanceof Iterable) { - value = CollUtil.join((Iterable) value, ","); - } else if (value instanceof Iterator) { - value = IterUtil.join((Iterator) value, ","); - } - valueStr = Convert.toStr(value); - if (StrUtil.isNotEmpty(key)) { - sb.append(URLUtil.encodeAll(key, charset)).append("="); - if (StrUtil.isNotEmpty(valueStr)) { - sb.append(URLUtil.encodeAll(valueStr, charset)); - } - } - } - return sb.toString(); + return URLUtil.buildQuery(paramMap, charset); } /** @@ -437,7 +398,7 @@ public class HttpUtil { *

    注意,此方法只能标准化整个URL,并不适合于单独编码参数值

    * * @param urlWithParams url和参数,可以包含url本身,也可以单独参数 - * @param charset 编码 + * @param charset 编码 * @return 编码后的url和参数 * @since 4.0.1 */ @@ -457,10 +418,10 @@ public class HttpUtil { // 无参数,返回url return urlPart; } - } else if(false == StrUtil.contains(urlWithParams, '=')){ + } else if (false == StrUtil.contains(urlWithParams, '=')) { // 无参数的URL return urlWithParams; - }else { + } else { // 无URL的参数 paramPart = urlWithParams; } @@ -536,8 +497,10 @@ public class HttpUtil { * @param charset 字符集 * @return 参数Map * @since 4.0.2 + * @deprecated 请使用 {@link #decodeParamMap(String, Charset)} */ - public static HashMap decodeParamMap(String paramsStr, String charset) { + @Deprecated + public static Map decodeParamMap(String paramsStr, String charset) { return decodeParamMap(paramsStr, CharsetUtil.charset(charset)); } @@ -549,15 +512,12 @@ public class HttpUtil { * @return 参数Map * @since 5.2.6 */ - public static HashMap decodeParamMap(String paramsStr, Charset charset) { - final Map> paramsMap = decodeParams(paramsStr, charset); - final HashMap result = MapUtil.newHashMap(paramsMap.size()); - List valueList; - for (Entry> entry : paramsMap.entrySet()) { - valueList = entry.getValue(); - result.put(entry.getKey(), CollUtil.isEmpty(valueList) ? null : valueList.get(0)); + public static Map decodeParamMap(String paramsStr, Charset charset) { + final Map queryMap = UrlQuery.of(paramsStr, charset).getQueryMap(); + if (MapUtil.isEmpty(queryMap)) { + return MapUtil.empty(); } - return result; + return Convert.toMap(String.class, String.class, queryMap); } /** @@ -580,56 +540,17 @@ public class HttpUtil { * @since 5.2.6 */ public static Map> decodeParams(String paramsStr, Charset charset) { - if (StrUtil.isBlank(paramsStr)) { - return Collections.emptyMap(); + final Map queryMap = UrlQuery.of(paramsStr, charset).getQueryMap(); + if (MapUtil.isEmpty(queryMap)) { + return MapUtil.empty(); } - // 去掉Path部分 - int pathEndPos = paramsStr.indexOf('?'); - if (pathEndPos > -1) { - paramsStr = StrUtil.subSuf(paramsStr, pathEndPos + 1); - if (StrUtil.isBlank(paramsStr)) { - return Collections.emptyMap(); - } - } - - final int len = paramsStr.length(); final Map> params = new LinkedHashMap<>(); - String name = null; - int pos = 0; // 未处理字符开始位置 - int i; // 未处理字符结束位置 - char c; // 当前字符 - for (i = 0; i < len; i++) { - c = paramsStr.charAt(i); - if (c == '=') { // 键值对的分界点 - if (null == name) { - // name可以是"" - name = paramsStr.substring(pos, i); - } - pos = i + 1; - } else if (c == '&') { // 参数对的分界点 - if (null == name && pos != i) { - // 对于像&a&这类无参数值的字符串,我们将name为a的值设为"" - addParam(params, paramsStr.substring(pos, i), StrUtil.EMPTY, charset); - } else if (name != null) { - addParam(params, name, paramsStr.substring(pos, i), charset); - name = null; - } - pos = i + 1; - } - } - - // 处理结尾 - if (pos != i) { - if (name == null) { - addParam(params, paramsStr.substring(pos, i), StrUtil.EMPTY, charset); - } else { - addParam(params, name, paramsStr.substring(pos, i), charset); - } - } else if (name != null) { - addParam(params, name, StrUtil.EMPTY, charset); - } - + queryMap.forEach((key, value) -> { + final List values = params.computeIfAbsent(StrUtil.str(key), k -> new ArrayList<>(1)); + // 一般是一个参数 + values.add(StrUtil.str(value)); + }); return params; } @@ -826,26 +747,7 @@ public class HttpUtil { * @return {@link SimpleServer} * @since 5.2.6 */ - public static SimpleServer createServer(int port){ + public static SimpleServer createServer(int port) { return new SimpleServer(port); } - // ----------------------------------------------------------------------------------------- Private method start - - /** - * 将键值对加入到值为List类型的Map中 - * - * @param params 参数 - * @param name key - * @param value value - * @param charset 编码 - */ - private static void addParam(Map> params, String name, String value, Charset charset) { - name = URLUtil.decode(name, charset); - value = URLUtil.decode(value, charset); - final List values = params.computeIfAbsent(name, k -> new ArrayList<>(1)); - // 一般是一个参数 - values.add(value); - } - - // ----------------------------------------------------------------------------------------- Private method start end } diff --git a/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java index 59afded99..fda2003d1 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java @@ -11,6 +11,7 @@ import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; +import java.io.ByteArrayOutputStream; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -18,7 +19,7 @@ import java.util.Map; public class HttpUtilTest { @Test - @Ignore +// @Ignore public void postTest() { String result = HttpUtil.createPost("api.uhaozu.com/goods/description/1120448506").charset(CharsetUtil.UTF_8).execute().body(); Console.log(result); @@ -71,6 +72,17 @@ public class HttpUtilTest { Console.log(str); } + @Test + @Ignore + public void getTest5() { + String url2 = "http://storage.chancecloud.com.cn/20200413_%E7%B2%A4B12313_386.pdf"; + ByteArrayOutputStream os2 = new ByteArrayOutputStream(); + HttpUtil.download(url2, os2, false); + + url2 = "http://storage.chancecloud.com.cn/20200413_粤B12313_386.pdf"; + HttpUtil.download(url2, os2, false); + } + @Test @Ignore public void get12306Test() { From 01b815afab6e0fe20e907b95e675faa7cb2c0b1d Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 16 Apr 2020 01:15:37 +0800 Subject: [PATCH 043/157] add UrlDecoder --- .../cn/hutool/core/net/url/UrlBuilder.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 5fb76ff06..978b70bc5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -46,7 +46,7 @@ public final class UrlBuilder implements Serializable { */ private UrlPath path; /** - * 查询语句,例如a=1&b=2 + * 查询语句,例如a=1&b=2 */ private UrlQuery query; /** @@ -117,7 +117,7 @@ public final class UrlBuilder implements Serializable { * @param host 主机,例如127.0.0.1 * @param port 端口,-1表示默认端口 * @param path 路径,例如/aa/bb/cc - * @param query 查询,例如a=1&b=2 + * @param query 查询,例如a=1&b=2 * @param fragment 标识符例如#后边的部分 * @param charset 编码,用于URLEncode和URLDecode * @return UrlBuilder @@ -133,7 +133,7 @@ public final class UrlBuilder implements Serializable { * @param host 主机,例如127.0.0.1 * @param port 端口,-1表示默认端口 * @param path 路径,例如/aa/bb/cc - * @param query 查询,例如a=1&b=2 + * @param query 查询,例如a=1&b=2 * @param fragment 标识符例如#后边的部分 * @param charset 编码,用于URLEncode和URLDecode * @return UrlBuilder @@ -165,7 +165,7 @@ public final class UrlBuilder implements Serializable { * @param host 主机,例如127.0.0.1 * @param port 端口,-1表示默认端口 * @param path 路径,例如/aa/bb/cc - * @param query 查询,例如a=1&b=2 + * @param query 查询,例如a=1&b=2 * @param fragment 标识符例如#后边的部分 * @param charset 编码,用于URLEncode和URLDecode */ @@ -322,27 +322,27 @@ public final class UrlBuilder implements Serializable { } /** - * 获取查询语句,例如a=1&b=2 + * 获取查询语句,例如a=1&b=2 * - * @return 查询语句,例如a=1&b=2 + * @return 查询语句,例如a=1&b=2 */ public UrlQuery getQuery() { return query; } /** - * 获取查询语句,例如a=1&b=2 + * 获取查询语句,例如a=1&b=2 * - * @return 查询语句,例如a=1&b=2 + * @return 查询语句,例如a=1&b=2 */ public String getQueryStr() { return null == this.query ? null : this.query.build(this.charset); } /** - * 设置查询语句,例如a=1&b=2,将覆盖之前所有的query相关设置 + * 设置查询语句,例如a=1&b=2,将覆盖之前所有的query相关设置 * - * @param query 查询语句,例如a=1&b=2 + * @param query 查询语句,例如a=1&b=2 * @return this */ public UrlBuilder setQuery(UrlQuery query) { From 972234b9d417f78c97dfc00d4e35e63e9ae3bf53 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 16 Apr 2020 01:18:44 +0800 Subject: [PATCH 044/157] add UrlDecoder --- .../java/cn/hutool/core/collection/CollUtil.java | 5 +++-- .../main/java/cn/hutool/core/net/url/UrlPath.java | 12 ++++++++++-- .../java/cn/hutool/http/server/action/Action.java | 1 + 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index b80714da4..b2f3a74f6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -2572,8 +2572,9 @@ public class CollUtil { * 4. List * * - * @param 元素类型 - * @param 集合类型 + * @param 元素类型 + * @param 集合类型 + * @param collectionClass 集合类型 * @return 空集合 * @since 5.3.1 */ diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java index 01708765a..bf11b0837 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java @@ -35,10 +35,11 @@ public class UrlPath { /** * 是否path的末尾加 / + * * @param withEngTag 是否path的末尾加 / * @return this */ - public UrlPath setWithEndTag(boolean withEngTag){ + public UrlPath setWithEndTag(boolean withEngTag) { this.withEngTag = withEngTag; return this; } @@ -67,6 +68,9 @@ public class UrlPath { /** * 添加到path最后面 + * + * @param segment Path节点 + * @return this */ public UrlPath add(CharSequence segment) { add(segment, false); @@ -75,6 +79,9 @@ public class UrlPath { /** * 添加到path最前面 + * + * @param segment Path节点 + * @return this */ public UrlPath addBefore(CharSequence segment) { add(segment, true); @@ -118,7 +125,7 @@ public class UrlPath { for (String segment : segments) { builder.append(CharUtil.SLASH).append(URLUtil.encodeAll(segment, charset)); } - if(withEngTag || StrUtil.isEmpty(builder)){ + if (withEngTag || StrUtil.isEmpty(builder)) { builder.append(CharUtil.SLASH); } return builder.toString(); @@ -154,6 +161,7 @@ public class UrlPath { /** * 修正节点,包括去掉前后的/,去掉空白符 + * * @param segment 节点 * @return 修正后的节点 */ diff --git a/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java b/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java index 5e3604b80..a905fd5df 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java @@ -19,6 +19,7 @@ public interface Action { * * @param request 请求对象 * @param response 响应对象 + * @throws IOException IO异常 */ void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException; } From 3526b39d4f4ed107b78ba14ce062dc97367cbe94 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 16 Apr 2020 11:37:27 +0800 Subject: [PATCH 045/157] add UniversalNamespaceCache --- CHANGELOG.md | 1 + .../java/cn/hutool/core/util/XmlUtil.java | 203 +++++++++++++++--- .../java/cn/hutool/core/util/XmlUtilTest.java | 18 ++ 3 files changed, 191 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc5cabd29..faec6d294 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * 【poi 】 调整别名策略,clearHeaderAlias和addHeaderAlias同时清除aliasComparator(issue#828@Github) * 【core 】 修改StrUtil.equals逻辑,改为contentEquals * 【core 】 增加URLUtil.UrlDecoder +* 【core 】 增加XmlUtil.setNamespaceAware,getByPath支持UniversalNamespaceCache ### Bug修复 * 【json 】 修复解析JSON字符串时配置无法传递问题 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java index e336e7016..ff26635b5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java @@ -6,13 +6,17 @@ import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.BiMap; import cn.hutool.core.map.MapUtil; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -68,6 +72,11 @@ public class XmlUtil { */ private static String defaultDocumentBuilderFactory = "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"; + /** + * 是否打开命名空间支持 + */ + private static boolean namespaceAware = true; + /** * 禁用默认的DocumentBuilderFactory,禁用后如果有第三方的实现(如oracle的xdb包中的xmlparse),将会自动加载实现。 */ @@ -75,6 +84,16 @@ public class XmlUtil { defaultDocumentBuilderFactory = null; } + /** + * 设置是否打开命名空间支持,默认打开 + * + * @param isNamespaceAware 是否命名空间支持 + * @since 5.3.1 + */ + synchronized public static void setNamespaceAware(boolean isNamespaceAware) { + namespaceAware = isNamespaceAware; + } + // -------------------------------------------------------------------------------------- Read /** @@ -176,7 +195,7 @@ public class XmlUtil { throw new IllegalArgumentException("XML content string is empty !"); } xmlStr = cleanInvalid(xmlStr); - return readXML(new InputSource(StrUtil.getReader(xmlStr))); + return readXML(StrUtil.getReader(xmlStr)); } /** @@ -261,7 +280,7 @@ public class XmlUtil { * @since 3.0.9 */ public static String toStr(Document doc, String charset, boolean isPretty) { - return toStr(doc, charset, isPretty,false); + return toStr(doc, charset, isPretty, false); } /** @@ -421,7 +440,7 @@ public class XmlUtil { * @param omitXmlDeclaration 是否输出 xml Declaration * @since 5.1.2 */ - public static void transform(Source source, Result result, String charset, int indent,boolean omitXmlDeclaration) { + public static void transform(Source source, Result result, String charset, int indent, boolean omitXmlDeclaration) { final TransformerFactory factory = TransformerFactory.newInstance(); try { final Transformer xformer = factory.newTransformer(); @@ -432,7 +451,7 @@ public class XmlUtil { if (StrUtil.isNotBlank(charset)) { xformer.setOutputProperty(OutputKeys.ENCODING, charset); } - if (omitXmlDeclaration){ + if (omitXmlDeclaration) { xformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); } xformer.transform(source, result); @@ -487,7 +506,7 @@ public class XmlUtil { factory = DocumentBuilderFactory.newInstance(); } // 默认打开NamespaceAware,getElementsByTagNameNS可以使用命名空间 - factory.setNamespaceAware(true); + factory.setNamespaceAware(namespaceAware); return disableXXE(factory); } @@ -533,11 +552,12 @@ public class XmlUtil { /** * 获取节点所在的Document + * * @param node 节点 * @return {@link Document} * @since 5.3.0 */ - public static Document getOwnerDocument(Node node){ + public static Document getOwnerDocument(Node node) { return (node instanceof Document) ? (Document) node : node.getOwnerDocument(); } @@ -728,7 +748,31 @@ public class XmlUtil { * @since 3.2.0 */ public static Object getByXPath(String expression, Object source, QName returnType) { + NamespaceContext nsContext = null; + if (source instanceof Node) { + nsContext = new UniversalNamespaceCache((Node) source, false); + } + return getByXPath(expression, source, returnType, nsContext); + } + + /** + * 通过XPath方式读取XML节点等信息
    + * Xpath相关文章:
    + * https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html
    + * https://www.ibm.com/developerworks/cn/xml/x-nmspccontext/ + * + * @param expression XPath表达式 + * @param source 资源,可以是Docunent、Node节点等 + * @param returnType 返回类型,{@link javax.xml.xpath.XPathConstants} + * @param nsContext {@link NamespaceContext} + * @return 匹配返回类型的值 + * @since 5.3.1 + */ + public static Object getByXPath(String expression, Object source, QName returnType, NamespaceContext nsContext) { final XPath xPath = createXPath(); + if (null != nsContext) { + xPath.setNamespaceContext(nsContext); + } try { if (source instanceof InputSource) { return xPath.evaluate(expression, (InputSource) source, returnType); @@ -784,13 +828,13 @@ public class XmlUtil { /** * XML转Java Bean * - * @param bean类型 + * @param bean类型 * @param node XML节点 * @param bean bean类 * @return bean * @since 5.2.4 */ - public static T xmlToBean(Node node, Class bean){ + public static T xmlToBean(Node node, Class bean) { return BeanUtil.toBean(xmlToMap(node), bean); } @@ -853,7 +897,7 @@ public class XmlUtil { final Map map = xmlToMap(childEle); if (MapUtil.isNotEmpty(map)) { newValue = map; - } else{ + } else { newValue = childEle.getTextContent(); } } else { @@ -879,7 +923,7 @@ public class XmlUtil { /** * 将Map转换为XML格式的字符串 * - * @param data Map类型数据 + * @param data Map类型数据 * @return XML格式的字符串 * @since 5.1.2 */ @@ -895,8 +939,8 @@ public class XmlUtil { * @return XML格式的字符串 * @since 5.1.2 */ - public static String mapToXmlStr(Map data,boolean omitXmlDeclaration) { - return toStr(mapToXml(data, "xml"),CharsetUtil.UTF_8,false,omitXmlDeclaration); + public static String mapToXmlStr(Map data, boolean omitXmlDeclaration) { + return toStr(mapToXml(data, "xml"), CharsetUtil.UTF_8, false, omitXmlDeclaration); } /** @@ -965,7 +1009,7 @@ public class XmlUtil { * @return XML格式的字符串 * @since 5.1.2 */ - public static String mapToXmlStr(Map data, String rootName, String namespace, String charset,boolean isPretty, boolean omitXmlDeclaration) { + public static String mapToXmlStr(Map data, String rootName, String namespace, String charset, boolean isPretty, boolean omitXmlDeclaration) { return toStr(mapToXml(data, rootName, namespace), charset, isPretty, omitXmlDeclaration); } @@ -1007,7 +1051,7 @@ public class XmlUtil { * @since 5.2.4 */ public static Document beanToXml(Object bean, String namespace) { - if(null == bean){ + if (null == bean) { return null; } return mapToXml(BeanUtil.beanToMap(bean), bean.getClass().getSimpleName(), namespace); @@ -1060,7 +1104,7 @@ public class XmlUtil { * @return 子节点 * @since 5.3.0 */ - public static Node appendText(Node node, CharSequence text){ + public static Node appendText(Node node, CharSequence text) { return appendText(getOwnerDocument(node), node, text); } // ---------------------------------------------------------------------------------------- Private method start @@ -1068,21 +1112,21 @@ public class XmlUtil { /** * 追加数据子节点,可以是Map、集合、文本 * - * @param doc {@link Document} + * @param doc {@link Document} * @param node 节点 * @param data 数据 */ @SuppressWarnings("rawtypes") - private static void append(Document doc, Node node, Object data){ + private static void append(Document doc, Node node, Object data) { if (data instanceof Map) { // 如果值依旧为map,递归继续 appendMap(doc, node, (Map) data); } else if (data instanceof Iterator) { // 如果值依旧为map,递归继续 appendIterator(doc, node, (Iterator) data); - }else if (data instanceof Iterable) { + } else if (data instanceof Iterable) { // 如果值依旧为map,递归继续 - appendIterator(doc, node, ((Iterable)data).iterator()); + appendIterator(doc, node, ((Iterable) data).iterator()); } else { appendText(doc, node, data.toString()); } @@ -1091,17 +1135,17 @@ public class XmlUtil { /** * 追加Map数据子节点 * - * @param doc {@link Document} + * @param doc {@link Document} * @param node 当前节点 - * @param data Map类型数据 + * @param data Map类型数据 * @since 4.0.8 */ @SuppressWarnings({"rawtypes", "unchecked"}) private static void appendMap(Document doc, Node node, Map data) { - data.forEach((key, value)->{ - if(null != key){ + data.forEach((key, value) -> { + if (null != key) { final Element child = appendChild(node, key.toString()); - if(null != value){ + if (null != value) { append(doc, child, value); } } @@ -1111,21 +1155,21 @@ public class XmlUtil { /** * 追加集合节点 * - * @param doc {@link Document} + * @param doc {@link Document} * @param node 节点 * @param data 数据 */ @SuppressWarnings("rawtypes") - private static void appendIterator(Document doc, Node node, Iterator data){ + private static void appendIterator(Document doc, Node node, Iterator data) { final Node parentNode = node.getParentNode(); boolean isFirst = true; Object eleData; - while(data.hasNext()){ + while (data.hasNext()) { eleData = data.next(); - if(isFirst){ + if (isFirst) { append(doc, node, eleData); isFirst = false; - } else{ + } else { final Node cloneNode = node.cloneNode(false); parentNode.appendChild(cloneNode); append(doc, cloneNode, eleData); @@ -1136,13 +1180,13 @@ public class XmlUtil { /** * 追加文本节点 * - * @param doc {@link Document} + * @param doc {@link Document} * @param node 节点 * @param text 文本内容 * @return 增加的子节点,即Text节点 * @since 5.3.0 */ - private static Node appendText(Document doc, Node node, CharSequence text){ + private static Node appendText(Document doc, Node node, CharSequence text) { return node.appendChild(doc.createTextNode(StrUtil.str(text))); } @@ -1182,6 +1226,103 @@ public class XmlUtil { } return dbf; } + + /** + * 全局命名空间上下文
    + * 见:https://www.ibm.com/developerworks/cn/xml/x-nmspccontext/ + */ + public static class UniversalNamespaceCache implements NamespaceContext { + private static final String DEFAULT_NS = "DEFAULT"; + private final BiMap prefixUri = new BiMap<>(new HashMap<>()); + + /** + * This constructor parses the document and stores all namespaces it can + * find. If toplevelOnly is true, only namespaces in the root are used. + * + * @param node source Node + * @param toplevelOnly restriction of the search to enhance performance + */ + public UniversalNamespaceCache(Node node, boolean toplevelOnly) { + examineNode(node.getFirstChild(), toplevelOnly); + } + + /** + * A single node is read, the namespace attributes are extracted and stored. + * + * @param node to examine + * @param attributesOnly, if true no recursion happens + */ + private void examineNode(Node node, boolean attributesOnly) { + NamedNodeMap attributes = node.getAttributes(); + for (int i = 0; i < attributes.getLength(); i++) { + Node attribute = attributes.item(i); + storeAttribute(attribute); + } + + if (false == attributesOnly) { + NodeList childNodes = node.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node item = childNodes.item(i); + if (item.getNodeType() == Node.ELEMENT_NODE) + examineNode(item, false); + } + } + } + + /** + * This method looks at an attribute and stores it, if it is a namespace + * attribute. + * + * @param attribute to examine + */ + private void storeAttribute(Node attribute) { + // examine the attributes in namespace xmlns + if (attribute.getNamespaceURI() != null + && attribute.getNamespaceURI().equals( + XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) { + // Default namespace xmlns="uri goes here" + if (attribute.getNodeName().equals(XMLConstants.XMLNS_ATTRIBUTE)) { + prefixUri.put(DEFAULT_NS, attribute.getNodeValue()); + } else { + // The defined prefixes are stored here + prefixUri.put(attribute.getLocalName(), attribute.getNodeValue()); + } + } + + } + + /** + * This method is called by XPath. It returns the default namespace, if the + * prefix is null or "". + * + * @param prefix to search for + * @return uri + */ + @Override + public String getNamespaceURI(String prefix) { + if (prefix == null || prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) { + return prefixUri.get(DEFAULT_NS); + } else { + return prefixUri.get(prefix); + } + } + + /** + * This method is not needed in this context, but can be implemented in a + * similar way. + */ + @Override + public String getPrefix(String namespaceURI) { + return prefixUri.getInverse().get(namespaceURI); + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + // Not implemented + return null; + } + + } // ---------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java index 6ecc6282a..323619213 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java @@ -147,4 +147,22 @@ public class XmlUtilTest { String xml = XmlUtil.mapToXmlStr(map, true); Assert.assertEquals("ddatsh", xml); } + + @Test + public void getByPathTest(){ + String xmlStr = "\n" + + "\n" + + " \n" + + " \n" + + " 2020/04/15 21:01:21\n" + + " \n" + + " \n" + + "\n"; + + Document document = XmlUtil.readXML(xmlStr); + Object value = XmlUtil.getByXPath( + "//soap:Envelope/soap:Body/ns2:testResponse/return", + document,XPathConstants.STRING);// + Assert.assertEquals("2020/04/15 21:01:21", value); + } } From 94c824e03dfc27b868178e25fcc21acf8b11fac5 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 17 Apr 2020 09:54:40 +0800 Subject: [PATCH 046/157] add Spring cglib support --- CHANGELOG.md | 2 + hutool-aop/pom.xml | 8 +++ .../aop/interceptor/CglibInterceptor.java | 2 + .../interceptor/SpringCglibInterceptor.java | 65 +++++++++++++++++++ .../cn/hutool/aop/proxy/ProxyFactory.java | 48 ++++++++------ .../aop/proxy/SpringCglibProxyFactory.java | 25 +++++++ .../services/cn.hutool.aop.proxy.ProxyFactory | 3 + .../test/java/cn/hutool/aop/test/AopTest.java | 2 +- 8 files changed, 134 insertions(+), 21 deletions(-) create mode 100644 hutool-aop/src/main/java/cn/hutool/aop/interceptor/SpringCglibInterceptor.java create mode 100644 hutool-aop/src/main/java/cn/hutool/aop/proxy/SpringCglibProxyFactory.java create mode 100644 hutool-aop/src/main/resources/META-INF/services/cn.hutool.aop.proxy.ProxyFactory diff --git a/CHANGELOG.md b/CHANGELOG.md index faec6d294..3d19d7b27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ * 【core 】 修改StrUtil.equals逻辑,改为contentEquals * 【core 】 增加URLUtil.UrlDecoder * 【core 】 增加XmlUtil.setNamespaceAware,getByPath支持UniversalNamespaceCache +* 【core 】 增加XmlUtil.setNamespaceAware,getByPath支持UniversalNamespaceCache +* 【aop 】 增加Spring-cglib支持,改为SPI实现 ### Bug修复 * 【json 】 修复解析JSON字符串时配置无法传递问题 diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 73ba81733..e668a5e55 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -19,6 +19,7 @@ 3.3.0 + 5.2.5.RELEASE @@ -34,5 +35,12 @@ compile true + + org.springframework + spring-core + ${spring.version} + compile + true + diff --git a/hutool-aop/src/main/java/cn/hutool/aop/interceptor/CglibInterceptor.java b/hutool-aop/src/main/java/cn/hutool/aop/interceptor/CglibInterceptor.java index a157dd9b6..a4b7ddf02 100644 --- a/hutool-aop/src/main/java/cn/hutool/aop/interceptor/CglibInterceptor.java +++ b/hutool-aop/src/main/java/cn/hutool/aop/interceptor/CglibInterceptor.java @@ -1,6 +1,7 @@ package cn.hutool.aop.interceptor; import cn.hutool.aop.aspects.Aspect; +import cn.hutool.core.lang.Console; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; @@ -42,6 +43,7 @@ public class CglibInterceptor implements MethodInterceptor, Serializable { if (aspect.before(target, method, args)) { try { // result = proxy.invokeSuper(obj, args); + Console.log(target); result = proxy.invoke(target, args); } catch (InvocationTargetException e) { // 异常回调(只捕获业务代码导致的异常,而非反射导致的异常) diff --git a/hutool-aop/src/main/java/cn/hutool/aop/interceptor/SpringCglibInterceptor.java b/hutool-aop/src/main/java/cn/hutool/aop/interceptor/SpringCglibInterceptor.java new file mode 100644 index 000000000..8f3afa390 --- /dev/null +++ b/hutool-aop/src/main/java/cn/hutool/aop/interceptor/SpringCglibInterceptor.java @@ -0,0 +1,65 @@ +package cn.hutool.aop.interceptor; + +import cn.hutool.aop.aspects.Aspect; +import org.springframework.cglib.proxy.MethodInterceptor; +import org.springframework.cglib.proxy.MethodProxy; + +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Spring-cglib实现的动态代理切面 + * + * @author looly + */ +public class SpringCglibInterceptor implements MethodInterceptor, Serializable { + private static final long serialVersionUID = 1L; + + private final Object target; + private final Aspect aspect; + + /** + * 构造 + * + * @param target 被代理对象 + * @param aspect 切面实现 + */ + public SpringCglibInterceptor(Object target, Aspect aspect) { + this.target = target; + this.aspect = aspect; + } + + /** + * 获得目标对象 + * + * @return 目标对象 + */ + public Object getTarget() { + return this.target; + } + + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + final Object target = this.target; + Object result = null; + // 开始前回调 + if (aspect.before(target, method, args)) { + try { +// result = proxy.invokeSuper(obj, args); + result = proxy.invoke(target, args); + } catch (InvocationTargetException e) { + // 异常回调(只捕获业务代码导致的异常,而非反射导致的异常) + if (aspect.afterException(target, method, args, e.getTargetException())) { + throw e; + } + } + } + + // 结束执行回调 + if (aspect.after(target, method, args, result)) { + return result; + } + return null; + } +} diff --git a/hutool-aop/src/main/java/cn/hutool/aop/proxy/ProxyFactory.java b/hutool-aop/src/main/java/cn/hutool/aop/proxy/ProxyFactory.java index 24ac30871..8186ea668 100644 --- a/hutool-aop/src/main/java/cn/hutool/aop/proxy/ProxyFactory.java +++ b/hutool-aop/src/main/java/cn/hutool/aop/proxy/ProxyFactory.java @@ -1,46 +1,59 @@ package cn.hutool.aop.proxy; -import java.io.Serializable; - import cn.hutool.aop.aspects.Aspect; import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.ServiceLoaderUtil; + +import java.io.Serializable; /** * 代理工厂
    * 根据用户引入代理库的不同,产生不同的代理对象 - * - * @author looly * + * @author looly */ -public abstract class ProxyFactory implements Serializable{ +public abstract class ProxyFactory implements Serializable { private static final long serialVersionUID = 1L; /** * 创建代理 * - * @param 代理对象类型 + * @param 代理对象类型 + * @param target 被代理对象 + * @param aspectClass 切面实现类,自动实例化 + * @return 代理对象 + * @since 5.3.1 + */ + public T proxy(T target, Class aspectClass) { + return proxy(target, ReflectUtil.newInstanceIfPossible(aspectClass)); + } + + /** + * 创建代理 + * + * @param 代理对象类型 * @param target 被代理对象 * @param aspect 切面实现 * @return 代理对象 */ public abstract T proxy(T target, Aspect aspect); - + /** * 根据用户引入Cglib与否自动创建代理对象 - * - * @param 切面对象类型 - * @param target 目标对象 + * + * @param 切面对象类型 + * @param target 目标对象 * @param aspectClass 切面对象类 * @return 代理对象 */ - public static T createProxy(T target, Class aspectClass){ + public static T createProxy(T target, Class aspectClass) { return createProxy(target, ReflectUtil.newInstance(aspectClass)); } /** * 根据用户引入Cglib与否自动创建代理对象 - * - * @param 切面对象类型 + * + * @param 切面对象类型 * @param target 被代理对象 * @param aspect 切面实现 * @return 代理对象 @@ -51,15 +64,10 @@ public abstract class ProxyFactory implements Serializable{ /** * 根据用户引入Cglib与否创建代理工厂 - * + * * @return 代理工厂 */ public static ProxyFactory create() { - try { - return new CglibProxyFactory(); - } catch (NoClassDefFoundError e) { - // ignore - } - return new JdkProxyFactory(); + return ServiceLoaderUtil.loadFirstAvailable(ProxyFactory.class); } } diff --git a/hutool-aop/src/main/java/cn/hutool/aop/proxy/SpringCglibProxyFactory.java b/hutool-aop/src/main/java/cn/hutool/aop/proxy/SpringCglibProxyFactory.java new file mode 100644 index 000000000..46db19996 --- /dev/null +++ b/hutool-aop/src/main/java/cn/hutool/aop/proxy/SpringCglibProxyFactory.java @@ -0,0 +1,25 @@ +package cn.hutool.aop.proxy; + +import cn.hutool.aop.aspects.Aspect; +import cn.hutool.aop.interceptor.SpringCglibInterceptor; +import org.springframework.cglib.proxy.Enhancer; + +/** + * 基于Spring-cglib的切面代理工厂 + * + * @author looly + * + */ +public class SpringCglibProxyFactory extends ProxyFactory{ + private static final long serialVersionUID = 1L; + + @Override + @SuppressWarnings("unchecked") + public T proxy(T target, Aspect aspect) { + final Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(target.getClass()); + enhancer.setCallback(new SpringCglibInterceptor(target, aspect)); + return (T) enhancer.create(); + } + +} diff --git a/hutool-aop/src/main/resources/META-INF/services/cn.hutool.aop.proxy.ProxyFactory b/hutool-aop/src/main/resources/META-INF/services/cn.hutool.aop.proxy.ProxyFactory new file mode 100644 index 000000000..2bad43826 --- /dev/null +++ b/hutool-aop/src/main/resources/META-INF/services/cn.hutool.aop.proxy.ProxyFactory @@ -0,0 +1,3 @@ +cn.hutool.aop.proxy.CglibProxyFactory +cn.hutool.aop.proxy.SpringCglibProxyFactory +cn.hutool.aop.proxy.JdkProxyFactory \ No newline at end of file diff --git a/hutool-aop/src/test/java/cn/hutool/aop/test/AopTest.java b/hutool-aop/src/test/java/cn/hutool/aop/test/AopTest.java index eb036d1d4..55da3d3e7 100644 --- a/hutool-aop/src/test/java/cn/hutool/aop/test/AopTest.java +++ b/hutool-aop/src/test/java/cn/hutool/aop/test/AopTest.java @@ -23,7 +23,7 @@ public class AopTest { } @Test - public void aopByCglibTest() { + public void aopByAutoCglibTest() { Dog dog = ProxyUtil.proxy(new Dog(), TimeIntervalAspect.class); String result = dog.eat(); Assert.assertEquals("狗吃肉", result); From 7e8096a75c0a29268bb863b5025aecfd45393172 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 17 Apr 2020 15:54:43 +0800 Subject: [PATCH 047/157] fix json --- CHANGELOG.md | 1 + .../java/cn/hutool/json/InternalJSONUtil.java | 3 +- .../src/main/java/cn/hutool/json/JSON.java | 6 +- .../main/java/cn/hutool/json/JSONObject.java | 162 +++++++++++------- .../main/java/cn/hutool/json/JSONUtil.java | 76 +++++--- .../src/main/java/cn/hutool/json/XML.java | 138 +++++++++------ 6 files changed, 242 insertions(+), 144 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d19d7b27..ae65a09bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * 【core 】 增加XmlUtil.setNamespaceAware,getByPath支持UniversalNamespaceCache * 【core 】 增加XmlUtil.setNamespaceAware,getByPath支持UniversalNamespaceCache * 【aop 】 增加Spring-cglib支持,改为SPI实现 +* 【json 】 增加JSONUtil.parseXXX增加JSONConfig参数 ### Bug修复 * 【json 】 修复解析JSON字符串时配置无法传递问题 diff --git a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java index 0d5501c24..264a11dac 100644 --- a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java @@ -188,8 +188,7 @@ final class InternalJSONUtil { * @return JSONObject */ protected static JSONObject propertyPut(JSONObject jsonObject, Object key, Object value) { - String keyStr = Convert.toStr(key); - String[] path = StrUtil.split(keyStr, StrUtil.DOT); + final String[] path = StrUtil.split(Convert.toStr(key), StrUtil.DOT); int last = path.length - 1; JSONObject target = jsonObject; for (int i = 0; i < last; i += 1) { diff --git a/hutool-json/src/main/java/cn/hutool/json/JSON.java b/hutool-json/src/main/java/cn/hutool/json/JSON.java index cdbe26e6f..dbc518002 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSON.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSON.java @@ -1,13 +1,13 @@ package cn.hutool.json; +import cn.hutool.core.bean.BeanPath; +import cn.hutool.core.lang.TypeReference; + import java.io.Serializable; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Type; -import cn.hutool.core.bean.BeanPath; -import cn.hutool.core.lang.TypeReference; - /** * JSON接口 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index ea5f6c000..da07be15a 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -23,32 +23,41 @@ import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.Map; +import java.util.ResourceBundle; import java.util.Set; /** * JSON对象
    * 例:
    - * + * *
      * json = new JSONObject().put("JSON", "Hello, World!").toString();
      * 
    - * + * * @author looly */ public class JSONObject implements JSON, JSONGetter, Map { private static final long serialVersionUID = -330220388580734346L; - /** 默认初始大小 */ - public static final int DEFAULT_CAPACITY = 16; + /** + * 默认初始大小 + */ + public static final int DEFAULT_CAPACITY = MapUtil.DEFAULT_INITIAL_CAPACITY; - /** JSON的KV持有Map */ + /** + * JSON的KV持有Map + */ private final Map rawHashMap; - /** 配置项 */ + /** + * 配置项 + */ private final JSONConfig config; // -------------------------------------------------------------------------------------------------------------------- Constructor start + /** * 构造,初始容量为 {@link #DEFAULT_CAPACITY},KEY无序 */ @@ -58,7 +67,7 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 构造,初始容量为 {@link #DEFAULT_CAPACITY} - * + * * @param isOrder 是否有序 * @since 3.0.9 */ @@ -68,9 +77,9 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 构造 - * + * * @param capacity 初始大小 - * @param isOrder 是否有序 + * @param isOrder 是否有序 * @since 3.0.9 */ public JSONObject(int capacity, boolean isOrder) { @@ -79,19 +88,19 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 构造 - * - * @param capacity 初始大小 + * + * @param capacity 初始大小 * @param isIgnoreCase 是否忽略KEY大小写 - * @param isOrder 是否有序 + * @param isOrder 是否有序 * @since 3.3.1 */ public JSONObject(int capacity, boolean isIgnoreCase, boolean isOrder) { this(capacity, JSONConfig.create().setIgnoreCase(isIgnoreCase).setOrder(isOrder)); } - + /** * 构造 - * + * * @param config JSON配置项 * @since 4.6.5 */ @@ -101,13 +110,13 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 构造 - * + * * @param capacity 初始大小 - * @param config JSON配置项,null表示默认配置 + * @param config JSON配置项,null表示默认配置 * @since 4.1.19 */ public JSONObject(int capacity, JSONConfig config) { - if(null == config){ + if (null == config) { config = JSONConfig.create(); } if (config.isIgnoreCase()) { @@ -127,7 +136,7 @@ public class JSONObject implements JSON, JSONGetter, Map *
  • value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。 * 例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  • * - * + * * @param source JavaBean或者Map对象或者String */ public JSONObject(Object source) { @@ -142,8 +151,8 @@ public class JSONObject implements JSON, JSONGetter, Map *
  • value为JSONTokener,直接解析
  • *
  • value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  • * - * - * @param source JavaBean或者Map对象或者String + * + * @param source JavaBean或者Map对象或者String * @param ignoreNullValue 是否忽略空值 * @since 3.0.9 */ @@ -159,10 +168,10 @@ public class JSONObject implements JSON, JSONGetter, Map *
  • value为JSONTokener,直接解析
  • *
  • value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  • * - * - * @param source JavaBean或者Map对象或者String + * + * @param source JavaBean或者Map对象或者String * @param ignoreNullValue 是否忽略空值,如果source为JSON字符串,不忽略空值 - * @param isOrder 是否有序 + * @param isOrder 是否有序 * @since 4.2.2 */ public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { @@ -179,11 +188,11 @@ public class JSONObject implements JSON, JSONGetter, Map *
  • value为JSONTokener,直接解析
  • *
  • value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  • * - * + *

    * 如果给定值为Map,将键值对加入JSON对象;
    * 如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象
    * 例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三" - * + * * @param source JavaBean或者Map对象或者String * @param config JSON配置文件 * @since 4.2.2 @@ -195,16 +204,16 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 构建指定name列表对应的键值对为新的JSONObject,情况如下: - * + * *

     	 * 1. 若obj为Map,则获取name列表对应键值对
     	 * 2. 若obj为普通Bean,使用反射方式获取字段名和字段值
     	 * 
    - * + *

    * KEY或VALUE任意一个为null则不加入,字段不存在也不加入
    * 若names列表为空,则字段全部加入 * - * @param obj 包含需要字段的Bean对象或者Map对象 + * @param obj 包含需要字段的Bean对象或者Map对象 * @param names 需要构建JSONObject的字段名列表 */ public JSONObject(Object obj, String... names) { @@ -234,9 +243,9 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 从JSON字符串解析为JSON对象,对于排序单独配置参数 * - * @param source 以大括号 {} 包围的字符串,其中KEY和VALUE使用 : 分隔,每个键值对使用逗号分隔 + * @param source 以大括号 {} 包围的字符串,其中KEY和VALUE使用 : 分隔,每个键值对使用逗号分隔 * @param isOrder 是否有序 - * @exception JSONException JSON字符串语法错误 + * @throws JSONException JSON字符串语法错误 * @since 4.2.2 */ public JSONObject(CharSequence source, boolean isOrder) throws JSONException { @@ -252,7 +261,7 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 设置转为字符串时的日期格式,默认为时间戳(null值) - * + * * @param format 格式,null表示使用时间戳 * @return this * @since 4.1.19 @@ -333,7 +342,7 @@ public class JSONObject implements JSON, JSONGetter, Map /** * PUT 键值对到JSONObject中,在忽略null模式下,如果值为null,将此键移除 * - * @param key 键 + * @param key 键 * @param value 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. * @return this. * @throws JSONException 值是无穷数字抛出此异常 @@ -360,7 +369,7 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 设置键值对到JSONObject中,在忽略null模式下,如果值为null,将此键移除 * - * @param key 键 + * @param key 键 * @param value 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. * @return this. * @throws JSONException 值是无穷数字抛出此异常 @@ -373,7 +382,7 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 一次性Put 键值对,如果key已经存在抛出异常,如果键值中有null值,忽略 * - * @param key 键 + * @param key 键 * @param value 值对象,可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. * @return this. * @throws JSONException 值是无穷数字、键重复抛出异常 @@ -391,7 +400,7 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 在键和值都为非空的情况下put到JSONObject中 * - * @param key 键 + * @param key 键 * @param value 值对象,可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. * @return this. * @throws JSONException 值是无穷数字 @@ -414,7 +423,7 @@ public class JSONObject implements JSON, JSONGetter, Map * 积累值。类似于put,当key对应value已经存在时,与value组成新的JSONArray.
    * 如果只有一个值,此值就是value,如果多个值,则是添加到新的JSONArray中 * - * @param key 键 + * @param key 键 * @param value 被积累的值 * @return this. * @throws JSONException 如果给定键为null或者键对应的值存在且为非JSONArray @@ -435,7 +444,7 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 追加值,如果key无对应值,就添加一个JSONArray,其元素只有value,如果值已经是一个JSONArray,则添加到值JSONArray中。 * - * @param key 键 + * @param key 键 * @param value 值 * @return this. * @throws JSONException 如果给定键为null或者键对应的值存在且为非JSONArray @@ -559,12 +568,13 @@ public class JSONObject implements JSON, JSONGetter, Map } // ------------------------------------------------------------------------------------------------- Private method start + /** * 将JSON内容写入Writer - * - * @param writer writer + * + * @param writer writer * @param indentFactor 缩进因子,定义每一级别增加的缩进量 - * @param indent 本级别缩进量 + * @param indent 本级别缩进量 * @return Writer * @throws JSONException JSON相关异常 */ @@ -610,7 +620,7 @@ public class JSONObject implements JSON, JSONGetter, Map /** * Bean对象转Map - * + * * @param bean Bean对象 */ private void populateMap(Object bean) { @@ -654,7 +664,7 @@ public class JSONObject implements JSON, JSONGetter, Map *

  • value为JSONTokener,直接解析
  • *
  • value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  • * - * + * * @param source JavaBean或者Map对象或者String */ @SuppressWarnings({"rawtypes", "unchecked"}) @@ -662,9 +672,9 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == source) { return; } - + final JSONSerializer serializer = GlobalSerializeMapping.getSerializer(source.getClass()); - if(serializer instanceof JSONObjectSerializer) { + if (serializer instanceof JSONObjectSerializer) { // 自定义序列化 serializer.serialize(this, source); } else if (source instanceof Map) { @@ -678,7 +688,10 @@ public class JSONObject implements JSON, JSONGetter, Map } else if (source instanceof JSONTokener) { // JSONTokener init((JSONTokener) source); - } else if(BeanUtil.isReadableBean(source.getClass())){ + } else if (source instanceof ResourceBundle) { + // JSONTokener + init((ResourceBundle) source); + } else if (BeanUtil.isReadableBean(source.getClass())) { // 普通Bean this.populateMap(source); } @@ -686,16 +699,37 @@ public class JSONObject implements JSON, JSONGetter, Map /** * 初始化 - * + * + * @param bundle ResourceBundle + * @since 5.3.1 + */ + private void init(ResourceBundle bundle) { + Enumeration keys = bundle.getKeys(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + if (key != null) { + InternalJSONUtil.propertyPut(this, key, bundle.getString(key)); + } + } + } + + /** + * 初始化,可以判断字符串为JSON或者XML + * * @param source JSON字符串 */ private void init(CharSequence source) { + final String jsonStr = StrUtil.trim(source); + if (StrUtil.startWith(jsonStr, '<')) { + // 可能为XML + XML.toJSONObject(this, jsonStr, false); + } init(new JSONTokener(StrUtil.trim(source), this.config)); } /** * 初始化 - * + * * @param x JSONTokener */ private void init(JSONTokener x) { @@ -708,13 +742,13 @@ public class JSONObject implements JSON, JSONGetter, Map while (true) { c = x.nextClean(); switch (c) { - case 0: - throw x.syntaxError("A JSONObject text must end with '}'"); - case '}': - return; - default: - x.back(); - key = x.nextValue().toString(); + case 0: + throw x.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + default: + x.back(); + key = x.nextValue().toString(); } // The key is followed by ':'. @@ -728,17 +762,17 @@ public class JSONObject implements JSON, JSONGetter, Map // Pairs are separated by ','. switch (x.nextClean()) { - case ';': - case ',': - if (x.nextClean() == '}') { + case ';': + case ',': + if (x.nextClean() == '}') { + return; + } + x.back(); + break; + case '}': return; - } - x.back(); - break; - case '}': - return; - default: - throw x.syntaxError("Expected a ',' or '}'"); + default: + throw x.syntaxError("Expected a ',' or '}'"); } } } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index b7fa8d8e4..633f05ce9 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -23,9 +23,8 @@ import java.lang.reflect.Type; import java.nio.charset.Charset; import java.time.temporal.TemporalAccessor; import java.util.Calendar; -import java.util.Collection; import java.util.Date; -import java.util.Enumeration; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.ResourceBundle; @@ -100,6 +99,19 @@ public final class JSONUtil { return new JSONObject(obj); } + /** + * JSON字符串转JSONObject对象
    + * 此方法会忽略空值,但是对JSON字符串不影响 + * + * @param obj Bean对象或者Map + * @param config JSON配置 + * @return JSONObject + * @since 5.3.1 + */ + public static JSONObject parseObj(Object obj, JSONConfig config) { + return new JSONObject(obj, config); + } + /** * JSON字符串转JSONObject对象 * @@ -146,6 +158,18 @@ public final class JSONUtil { return new JSONArray(arrayOrCollection); } + /** + * JSON字符串转JSONArray + * + * @param arrayOrCollection 数组或集合对象 + * @param config JSON配置 + * @return JSONArray + * @since 5.3.1 + */ + public static JSONArray parseArray(Object arrayOrCollection, JSONConfig config) { + return new JSONArray(arrayOrCollection, config); + } + /** * JSON字符串转JSONArray * @@ -169,6 +193,22 @@ public final class JSONUtil { * @return JSON */ public static JSON parse(Object obj) { + return parse(obj, JSONConfig.create()); + } + + /** + * 转换对象为JSON
    + * 支持的对象:
    + * String: 转换为相应的对象
    + * Array、Iterable、Iterator:转换为JSONArray
    + * Bean对象:转为JSONObject + * + * @param obj 对象 + * @param config JSON配置 + * @return JSON + * @since 5.3.1 + */ + public static JSON parse(Object obj, JSONConfig config) { if (null == obj) { return null; } @@ -176,17 +216,13 @@ public final class JSONUtil { JSON json; if (obj instanceof JSON) { json = (JSON) obj; - } else if (obj instanceof String) { - String jsonStr = ((String) obj).trim(); - if (jsonStr.startsWith("[")) { - json = parseArray(jsonStr); - } else { - json = parseObj(jsonStr); - } - } else if (obj instanceof Collection || obj.getClass().isArray()) {// 列表 - json = new JSONArray(obj); + } else if (obj instanceof CharSequence) { + final String jsonStr = StrUtil.trim((CharSequence) obj); + json = StrUtil.startWith(jsonStr, '[') ? parseArray(jsonStr) : parseObj(jsonStr); + } else if (obj instanceof Iterable || obj instanceof Iterator || ArrayUtil.isArray(obj)) {// 列表 + json = new JSONArray(obj, config); } else {// 对象 - json = new JSONObject(obj); + json = new JSONObject(obj, config); } return json; @@ -206,8 +242,10 @@ public final class JSONUtil { * Map转化为JSONObject * * @param map {@link Map} - * @return JSONObject + * @return JSONObjec + * @deprecated 请直接使用 {@link #parseObj(Object)} */ + @Deprecated public static JSONObject parseFromMap(Map map) { return new JSONObject(map); } @@ -217,17 +255,11 @@ public final class JSONUtil { * * @param bundle ResourceBundle文件 * @return JSONObject + * @deprecated 请直接使用 {@link #parseObj(Object)} */ + @Deprecated public static JSONObject parseFromResourceBundle(ResourceBundle bundle) { - JSONObject jsonObject = new JSONObject(); - Enumeration keys = bundle.getKeys(); - while (keys.hasMoreElements()) { - String key = keys.nextElement(); - if (key != null) { - InternalJSONUtil.propertyPut(jsonObject, key, bundle.getString(key)); - } - } - return jsonObject; + return new JSONObject(bundle); } // -------------------------------------------------------------------- Pause end diff --git a/hutool-json/src/main/java/cn/hutool/json/XML.java b/hutool-json/src/main/java/cn/hutool/json/XML.java index e2067ec55..9687112dc 100644 --- a/hutool-json/src/main/java/cn/hutool/json/XML.java +++ b/hutool-json/src/main/java/cn/hutool/json/XML.java @@ -9,44 +9,109 @@ import java.util.Iterator; /** * 提供静态方法在XML和JSONObject之间转换 - * + * * @author JSON.org */ public class XML { - /** The Character '&'. */ + /** + * The Character '&'. + */ public static final Character AMP = CharUtil.AMP; - /** The Character '''. */ + /** + * The Character '''. + */ public static final Character APOS = CharUtil.SINGLE_QUOTE; - /** The Character '!'. */ + /** + * The Character '!'. + */ public static final Character BANG = '!'; - /** The Character '='. */ + /** + * The Character '='. + */ public static final Character EQ = '='; - /** The Character '>'. */ + /** + * The Character '>'. + */ public static final Character GT = '>'; - /** The Character '<'. */ + /** + * The Character '<'. + */ public static final Character LT = '<'; - /** The Character '?'. */ + /** + * The Character '?'. + */ public static final Character QUEST = '?'; - /** The Character '"'. */ + /** + * The Character '"'. + */ public static final Character QUOT = CharUtil.DOUBLE_QUOTES; - /** The Character '/'. */ + /** + * The Character '/'. + */ public static final Character SLASH = CharUtil.SLASH; + /** + * 转换XML为JSONObject + * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。 + * Content text may be placed in a "content" member. Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * + * @param string The source string. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static JSONObject toJSONObject(String string) throws JSONException { + return toJSONObject(string, false); + } + + /** + * 转换XML为JSONObject + * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。 + * Content text may be placed in a "content" member. Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * All values are converted as strings, for 1, 01, 29.0 will not be coerced to numbers but will instead be the exact value as seen in the XML document. + * + * @param string The source string. + * @param keepStrings If true, then values will not be coerced into boolean or numeric values and will instead be left as strings + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { + return toJSONObject(new JSONObject(), string, keepStrings); + } + + /** + * 转换XML为JSONObject + * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。 + * + * @param jo JSONObject + * @param string XML字符串 + * @param keepStrings If true, then values will not be coerced into boolean or numeric values and will instead be left as strings + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + * @since 5.3.1 + */ + public static JSONObject toJSONObject(JSONObject jo, String string, boolean keepStrings) throws JSONException { + XMLTokener x = new XMLTokener(string, jo.getConfig()); + while (x.more() && x.skipPast("<")) { + parse(x, jo, null, keepStrings); + } + return jo; + } + /** * Scan the content following the named tag, attaching it to the context. - * - * @param x The XMLTokener containing the source string. + * + * @param x The XMLTokener containing the source string. * @param context The JSONObject that will include the new material. - * @param name The tag name. + * @param name The tag name. * @return true if the close tag is processed. * @throws JSONException JSON异常 */ @@ -135,7 +200,7 @@ public class XML { tagName = (String) token; token = null; jsonobject = new JSONObject(); - for (;;) { + for (; ; ) { if (token == null) { token = x.nextToken(); } @@ -169,7 +234,7 @@ public class XML { } else if (token == GT) { // Content, between <...> and - for (;;) { + for (; ; ) { token = x.nextContent(); if (token == null) { if (tagName != null) { @@ -203,43 +268,10 @@ public class XML { } } - /** - * 转换XML为JSONObject - * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。 - * Content text may be placed in a "content" member. Comments, prologs, DTDs, and <[ [ ]]> are ignored. - * - * @param string The source string. - * @return A JSONObject containing the structured data from the XML string. - * @throws JSONException Thrown if there is an errors while parsing the string - */ - public static JSONObject toJSONObject(String string) throws JSONException { - return toJSONObject(string, false); - } - - /** - * 转换XML为JSONObject - * 转换过程中一些信息可能会丢失,JSON中无法区分节点和属性,相同的节点将被处理为JSONArray。 - * Content text may be placed in a "content" member. Comments, prologs, DTDs, and <[ [ ]]> are ignored. - * All values are converted as strings, for 1, 01, 29.0 will not be coerced to numbers but will instead be the exact value as seen in the XML document. - * - * @param string The source string. - * @param keepStrings If true, then values will not be coerced into boolean or numeric values and will instead be left as strings - * @return A JSONObject containing the structured data from the XML string. - * @throws JSONException Thrown if there is an errors while parsing the string - */ - public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { - JSONObject jo = new JSONObject(); - XMLTokener x = new XMLTokener(string, jo.getConfig()); - while (x.more() && x.skipPast("<")) { - parse(x, jo, null, keepStrings); - } - return jo; - } - /** * 转换JSONObject为XML * Convert a JSONObject into a well-formed, element-normal XML string. - * + * * @param object A JSONObject. * @return A string. * @throws JSONException Thrown if there is an error parsing the string @@ -251,17 +283,17 @@ public class XML { /** * 转换JSONObject为XML * Convert a JSONObject into a well-formed, element-normal XML string. - * - * @param object A JSONObject. + * + * @param object A JSONObject. * @param tagName The optional name of the enclosing tag. * @return A string. * @throws JSONException Thrown if there is an error parsing the string */ public static String toXml(Object object, String tagName) throws JSONException { - if(null == object) { + if (null == object) { return null; } - + StringBuilder sb = new StringBuilder(); JSONArray ja; JSONObject jo; From 07229be2dbeb70c11f88a66e04ee1a4ac7668e2d Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 17 Apr 2020 16:11:14 +0800 Subject: [PATCH 048/157] fix RandomUtil --- CHANGELOG.md | 1 + .../java/cn/hutool/core/util/RandomUtil.java | 20 +++++++++---------- .../java/cn/hutool/core/util/HexUtilTest.java | 3 --- .../cn/hutool/core/util/RandomUtilTest.java | 6 ++++++ 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae65a09bb..6219ce04f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * 【core 】 增加XmlUtil.setNamespaceAware,getByPath支持UniversalNamespaceCache * 【aop 】 增加Spring-cglib支持,改为SPI实现 * 【json 】 增加JSONUtil.parseXXX增加JSONConfig参数 +* 【core 】 RandomUtil.randomNumber改为返回char ### Bug修复 * 【json 】 修复解析JSON字符串时配置无法传递问题 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java index f15665bf7..e2b47675a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java @@ -1,5 +1,14 @@ package cn.hutool.core.util; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.lang.UUID; +import cn.hutool.core.lang.WeightRandom; +import cn.hutool.core.lang.WeightRandom.WeightObj; + import java.awt.Color; import java.math.BigDecimal; import java.math.RoundingMode; @@ -14,15 +23,6 @@ import java.util.Random; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DateField; -import cn.hutool.core.date.DateTime; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.exceptions.UtilException; -import cn.hutool.core.lang.UUID; -import cn.hutool.core.lang.WeightRandom; -import cn.hutool.core.lang.WeightRandom.WeightObj; - /** * 随机工具类 * @@ -487,7 +487,7 @@ public class RandomUtil { * @return 随机数字字符 * @since 3.1.2 */ - public static int randomNumber() { + public static char randomNumber() { return randomChar(BASE_NUMBER); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/HexUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/HexUtilTest.java index 99553f241..4193ba412 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/HexUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/HexUtilTest.java @@ -3,9 +3,6 @@ package cn.hutool.core.util; import org.junit.Assert; import org.junit.Test; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.HexUtil; - /** * HexUtil单元测试 * @author Looly diff --git a/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java index b37e10098..c17eac233 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java @@ -35,4 +35,10 @@ public class RandomUtilTest { public void randomBooleanTest() { Console.log(RandomUtil.randomBoolean()); } + + @Test + public void randomNumberTest() { + final char c = RandomUtil.randomNumber(); + Assert.assertTrue(c <= '9'); + } } From 2f9fa2c29a96f43bc01ac700f178ae0b425c9b03 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 17 Apr 2020 17:09:16 +0800 Subject: [PATCH 049/157] sm2 support encoding --- CHANGELOG.md | 1 + .../java/cn/hutool/crypto/asymmetric/SM2.java | 58 ++++++++++++++++--- .../java/cn/hutool/crypto/test/SM2Test.java | 19 ++++++ 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6219ce04f..f4be29d81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * 【aop 】 增加Spring-cglib支持,改为SPI实现 * 【json 】 增加JSONUtil.parseXXX增加JSONConfig参数 * 【core 】 RandomUtil.randomNumber改为返回char +* 【crypto 】 SM2支持设置Digest和DSAEncoding(issue#829@Github) ### Bug修复 * 【json 】 修复解析JSON字符串时配置无法传递问题 diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index 643aec6a1..cf91415c4 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -6,13 +6,18 @@ import cn.hutool.crypto.BCUtil; import cn.hutool.crypto.CryptoException; import cn.hutool.crypto.SecureUtil; import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.digests.SM3Digest; import org.bouncycastle.crypto.engines.SM2Engine; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.params.ParametersWithID; import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.signers.DSAEncoding; +import org.bouncycastle.crypto.signers.PlainDSAEncoding; import org.bouncycastle.crypto.signers.SM2Signer; +import org.bouncycastle.crypto.signers.StandardDSAEncoding; import java.security.PrivateKey; import java.security.PublicKey; @@ -35,10 +40,13 @@ public class SM2 extends AbstractAsymmetricCrypto { protected SM2Engine engine; protected SM2Signer signer; - private SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2; private ECPrivateKeyParameters privateKeyParams; private ECPublicKeyParameters publicKeyParams; + private DSAEncoding encoding = StandardDSAEncoding.INSTANCE; + private Digest digest = new SM3Digest(); + private SM2Engine.Mode mode = SM2Engine.Mode.C1C3C2; + // ------------------------------------------------------------------ Constructor start /** @@ -414,16 +422,51 @@ public class SM2 extends AbstractAsymmetricCrypto { } /** - * 设置加密类型 + * 设置DSA signatures的编码为PlainDSAEncoding + * + * @return this + * @since 5.3.1 + */ + public SM2 usePlainEncoding() { + return setEncoding(PlainDSAEncoding.INSTANCE); + } + + /** + * 设置DSA signatures的编码 + * + * @param encoding {@link DSAEncoding}实现 + * @return this + * @since 5.3.1 + */ + public SM2 setEncoding(DSAEncoding encoding) { + this.encoding = encoding; + this.signer = null; + return this; + } + + /** + * 设置Hash算法 + * + * @param digest {@link Digest}实现 + * @return this + * @since 5.3.1 + */ + public SM2 setDigest(Digest digest) { + this.digest = digest; + this.engine = null; + this.signer = null; + return this; + } + + /** + * 设置SM2模式,旧版是C1C2C3,新版本是C1C3C2 * * @param mode {@link SM2Engine.Mode} * @return this */ public SM2 setMode(SM2Engine.Mode mode) { this.mode = mode; - if (null != this.engine) { - this.engine = null; - } + this.engine = null; return this; } @@ -455,7 +498,7 @@ public class SM2 extends AbstractAsymmetricCrypto { */ private SM2Engine getEngine() { if (null == this.engine) { - this.engine = new SM2Engine(this.mode); + this.engine = new SM2Engine(this.digest, this.mode); } return this.engine; } @@ -467,10 +510,9 @@ public class SM2 extends AbstractAsymmetricCrypto { */ private SM2Signer getSigner() { if (null == this.signer) { - this.signer = new SM2Signer(); + this.signer = new SM2Signer(this.encoding, this.digest); } return this.signer; } - // ------------------------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java index bef62d52c..a5ed710d8 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SM2Test.java @@ -174,4 +174,23 @@ public class SM2Test { final String sign = sm2.signHex(data, id); Assert.assertTrue(sm2.verifyHex(data, sign)); } + + @Test + public void sm2PlainWithPointTest(){ + // 测试地址:https://i.goto327.top/CryptTools/SM2.aspx?tdsourcetag=s_pctim_aiomsg + + String d = "FAB8BBE670FAE338C9E9382B9FB6485225C11A3ECB84C938F10F20A93B6215F0"; + String x = "9EF573019D9A03B16B0BE44FC8A5B4E8E098F56034C97B312282DD0B4810AFC3"; + String y = "CC759673ED0FC9B9DC7E6FA38F0E2B121E02654BF37EA6B63FAF2A0D6013EADF"; + + String data = "434477813974bf58f94bcf760833c2b40f77a5fc360485b0b9ed1bd9682edb45"; + String id = "31323334353637383132333435363738"; + + final SM2 sm2 = new SM2(d, x, y); + // 生成的签名是64位 + sm2.usePlainEncoding(); + + String sign = "DCA0E80A7F46C93714B51C3EFC55A922BCEF7ECF0FE9E62B53BA6A7438B543A76C145A452CA9036F3CB70D7E6C67D4D9D7FE114E5367A2F6F5A4D39F2B10F3D6"; + Assert.assertTrue(sm2.verifyHex(data, sign)); + } } From 9c3bf7b6cac7f3bef6190b4de710f9c21e45de77 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 17 Apr 2020 17:33:22 +0800 Subject: [PATCH 050/157] add test --- .../src/test/java/cn/hutool/json/JSONObjectTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 297e67bc0..3615dcddb 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -124,6 +124,14 @@ public class JSONObjectTest { Assert.assertEquals("体”、“文", json.getStr("test")); } + @Test + public void parseStringTest4() { + String jsonStr = "{'msg':'这里还没有内容','data':{'cards':[]},'ok':0}"; + JSONObject json = new JSONObject(jsonStr); + Assert.assertEquals(new Integer(0), json.getInt("ok")); + Assert.assertEquals(new JSONArray(), json.getJSONObject("data").getJSONArray("cards")); + } + @Test @Ignore public void parseStringWithBomTest() { From 4d701be722cc3c0dc80392f3d2c8ca77027f1cdd Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 17 Apr 2020 18:02:45 +0800 Subject: [PATCH 051/157] fix toString --- hutool-json/src/main/java/cn/hutool/json/JSONArray.java | 6 +----- hutool-json/src/main/java/cn/hutool/json/JSONObject.java | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index 48cae3772..0afc46d02 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -520,11 +520,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando */ @Override public String toString() { - try { - return this.toJSONString(0); - } catch (Exception e) { - return null; - } + return this.toJSONString(0); } @Override diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index da07be15a..542dc5b38 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -551,11 +551,7 @@ public class JSONObject implements JSON, JSONGetter, Map */ @Override public String toString() { - try { - return this.toJSONString(0); - } catch (Exception e) { - return null; - } + return this.toJSONString(0); } @Override From 5b2aa48fba07b776483c7c385f9d83990bb920c0 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 17 Apr 2020 18:09:16 +0800 Subject: [PATCH 052/157] fix comment --- CHANGELOG.md | 2 +- .../src/main/java/cn/hutool/core/date/CalendarUtil.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4be29d81..e38f27d31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ * 【crypto 】 SM2支持设置Digest和DSAEncoding(issue#829@Github) ### Bug修复 -* 【json 】 修复解析JSON字符串时配置无法传递问题 +* 【json 】 修复解析JSON字符串时配置无法传递问题(issue#I1EIDN@Gitee) * 【core 】 修复ServletUtil.readCookieMap空指针问题(issue#827@Github) * 【crypto 】 修复SM2中检查密钥导致的问题(issue#I1EC47@Gitee) * 【core 】 修复TableMap.isEmpty判断问题 diff --git a/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java index 50d807829..8621975d2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java @@ -280,7 +280,7 @@ public class CalendarUtil { } /** - * 获得指定日期区间内的年份和季节
    + * 获得指定日期区间内的年份和季度
    * * @param startDate 起始日期(包含) * @param endDate 结束日期(包含) @@ -302,10 +302,11 @@ public class CalendarUtil { } /** - * 获得指定日期年份和季节
    + * 获得指定日期年份和季度
    * 格式:[20131]表示2013年第一季度 * * @param cal 日期 + * @return 年和季度,格式类似于20131 */ public static String yearAndQuarter(Calendar cal) { return StrUtil.builder().append(cal.get(Calendar.YEAR)).append(cal.get(Calendar.MONTH) / 3 + 1).toString(); From edd47d6c86c5e26a4f74dcdd4ae38290dd093ce6 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 17 Apr 2020 18:15:22 +0800 Subject: [PATCH 053/157] release 5.3.1 --- CHANGELOG.md | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 22 files changed, 22 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e38f27d31..24fec33c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -## 5.3.1 (2020-04-11) +## 5.3.1 (2020-04-17) ### 新特性 * 【core 】 ListUtil、MapUtil、CollUtil增加empty方法 diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 5cb798123..959e026f3 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index e668a5e55..376a7554d 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index c1741120c..adb49b89e 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index de2cf188c..22c6d620f 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index b084260d8..e156947d6 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index efbfa6c48..159a25a2a 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 7cb098ebf..bb9d5bc7e 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 2dd4aaaa3..421ddfc3e 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 467835571..59e45f969 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index a15e2d902..283e7e0c2 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 20320a89c..16ec227ec 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 53e1784db..dd0c31515 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 08f736dd7..005b58d28 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index f56839e2a..2e5c2a529 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 44aa620b9..23bfd77bf 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 392fb6c21..8311ed888 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 262fb4025..2b678edbd 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 022a96b3c..beb2ed497 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index b959f92b2..d706643fa 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 2f0242d9f..519e0a24d 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool-system diff --git a/pom.xml b/pom.xml index b4e2d832b..e8094d7e2 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.1-SNAPSHOT + 5.3.1 hutool 提供丰富的Java工具方法 https://github.com/looly/hutool From d3e547515a33f0a74966cb3c96b9af6bc0b3aaf5 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 17 Apr 2020 18:33:24 +0800 Subject: [PATCH 054/157] prepare 5.3.2 --- CHANGELOG.md | 6 ++++++ README.md | 8 ++++---- bin/version.txt | 2 +- docs/js/version.js | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 25 files changed, 33 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24fec33c3..4a9f3c963 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ ------------------------------------------------------------------------------------------------------------- +## 5.3.2 (2020-04-17) + +### 新特性 +### Bug修复 + +------------------------------------------------------------------------------------------------------------- ## 5.3.1 (2020-04-17) ### 新特性 diff --git a/README.md b/README.md index 7dd0f85da..87b48a8ab 100644 --- a/README.md +++ b/README.md @@ -116,21 +116,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.3.1 + 5.3.2 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.3.1' +compile 'cn.hutool:hutool-all:5.3.2' ``` ### 非Maven项目 点击以下任一链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.3.1/) -- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.1/) +- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.3.2/) +- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.2/) > 注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类获工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index c7cb1311a..84197c894 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.3.1 +5.3.2 diff --git a/docs/js/version.js b/docs/js/version.js index d8cb08e4e..fbb5134ec 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.3.1' \ No newline at end of file +var version = '5.3.2' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 959e026f3..a65431d7f 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 376a7554d..279efdf02 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index adb49b89e..f45ef3808 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 22c6d620f..a122db5c3 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index e156947d6..a55dc85df 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 159a25a2a..c2d5f03cd 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index bb9d5bc7e..b56708538 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 421ddfc3e..66ceeec21 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 59e45f969..5ad309c87 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 283e7e0c2..6bdf09299 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 16ec227ec..81c57a561 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index dd0c31515..71e77cd9d 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 005b58d28..5e1354725 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 2e5c2a529..336a82742 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 23bfd77bf..06e7c6ff8 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 8311ed888..119f0ca3d 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 2b678edbd..03f106b4e 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index beb2ed497..7567050e2 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index d706643fa..94a130f31 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 519e0a24d..c25148a91 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index e8094d7e2..0e001a660 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.1 + 5.3.2-SNAPSHOT hutool 提供丰富的Java工具方法 https://github.com/looly/hutool From eaf19384335215ffd72f8ccad86177d4ca79af84 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 17 Apr 2020 18:35:22 +0800 Subject: [PATCH 055/157] prepare 5.3.2 --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a9f3c963..b60d772a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,6 @@ * 【core 】 修改StrUtil.equals逻辑,改为contentEquals * 【core 】 增加URLUtil.UrlDecoder * 【core 】 增加XmlUtil.setNamespaceAware,getByPath支持UniversalNamespaceCache -* 【core 】 增加XmlUtil.setNamespaceAware,getByPath支持UniversalNamespaceCache * 【aop 】 增加Spring-cglib支持,改为SPI实现 * 【json 】 增加JSONUtil.parseXXX增加JSONConfig参数 * 【core 】 RandomUtil.randomNumber改为返回char From f4fc97f9de647e8218d968c5fc9c785afedf7de9 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 19 Apr 2020 09:38:17 +0800 Subject: [PATCH 056/157] add NetUtil.isopen --- CHANGELOG.md | 4 +++- .../main/java/cn/hutool/core/net/NetUtil.java | 21 +++++++++++++++++-- .../main/java/cn/hutool/db/PageResult.java | 6 +++--- .../java/cn/hutool/db/PageResultTest.java | 14 +++++++++++++ .../cn/hutool/http/server/DocServerTest.java | 17 +++++++++++++++ 5 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 hutool-db/src/test/java/cn/hutool/db/PageResultTest.java create mode 100644 hutool-http/src/test/java/cn/hutool/http/server/DocServerTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index b60d772a0..a4d3832cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,12 @@ ------------------------------------------------------------------------------------------------------------- -## 5.3.2 (2020-04-17) +## 5.3.2 (2020-04-19) ### 新特性 +* 【core 】 增加NetUtil.isOpen方法 ### Bug修复 +* 【db 】 修复PageResult.isLast计算问题 ------------------------------------------------------------------------------------------------------------- ## 5.3.1 (2020-04-17) diff --git a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java index 6709c719e..1c218677f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java @@ -714,12 +714,29 @@ public class NetUtil { * @return cookie字符串 * @since 5.2.6 */ - public static List parseCookies(String cookieStr){ - if(StrUtil.isBlank(cookieStr)){ + public static List parseCookies(String cookieStr) { + if (StrUtil.isBlank(cookieStr)) { return Collections.emptyList(); } return HttpCookie.parse(cookieStr); } + + /** + * 检查远程端口是否开启 + * + * @param address 远程地址 + * @param timeout 检测超时 + * @return 远程端口是否开启 + * @since 5.3.2 + */ + public static boolean isOpen(InetSocketAddress address, int timeout) { + try (Socket sc = new Socket()){ + sc.connect(address, timeout); + return true; + } catch (Exception e) { + return false; + } + } // ----------------------------------------------------------------------------------------- Private method start /** diff --git a/hutool-db/src/main/java/cn/hutool/db/PageResult.java b/hutool-db/src/main/java/cn/hutool/db/PageResult.java index 1be7b2cff..ffbec81fc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/PageResult.java +++ b/hutool-db/src/main/java/cn/hutool/db/PageResult.java @@ -1,9 +1,9 @@ package cn.hutool.db; -import java.util.ArrayList; - import cn.hutool.core.util.PageUtil; +import java.util.ArrayList; + /** * 分页数据结果集 * @@ -169,6 +169,6 @@ public class PageResult extends ArrayList { * @return 是否最后一页 */ public boolean isLast() { - return this.page >= this.totalPage; + return this.page >= (this.totalPage - 1); } } diff --git a/hutool-db/src/test/java/cn/hutool/db/PageResultTest.java b/hutool-db/src/test/java/cn/hutool/db/PageResultTest.java new file mode 100644 index 000000000..61f9a2876 --- /dev/null +++ b/hutool-db/src/test/java/cn/hutool/db/PageResultTest.java @@ -0,0 +1,14 @@ +package cn.hutool.db; + +import org.junit.Assert; +import org.junit.Test; + +public class PageResultTest { + + @Test + public void isLastTest(){ + // 每页2条,共10条,总共5页,第一页是0,最后一页应该是4 + final PageResult result = new PageResult<>(4, 2, 10); + Assert.assertTrue(result.isLast()); + } +} diff --git a/hutool-http/src/test/java/cn/hutool/http/server/DocServerTest.java b/hutool-http/src/test/java/cn/hutool/http/server/DocServerTest.java new file mode 100644 index 000000000..198af98c6 --- /dev/null +++ b/hutool-http/src/test/java/cn/hutool/http/server/DocServerTest.java @@ -0,0 +1,17 @@ +package cn.hutool.http.server; + +import cn.hutool.core.swing.DesktopUtil; +import cn.hutool.http.HttpUtil; + +public class DocServerTest { + + public static void main(String[] args) { + HttpUtil.createServer(80) + // 设置默认根目录, + .setRoot("D:\\workspace\\site\\hutool-site") + // 返回JSON数据测试 + .start(); + + DesktopUtil.browse("http://localhost/"); + } +} From 8e42898d4656f2efc9372b322e47b292a30536d8 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 19 Apr 2020 11:41:10 +0800 Subject: [PATCH 057/157] fix cron bug --- CHANGELOG.md | 3 + .../cn/hutool/core/thread/ThreadUtil.java | 60 +++++++++++++++---- .../main/java/cn/hutool/cron/CronTimer.java | 34 ++++++++--- .../java/cn/hutool/cron/demo/CronTest.java | 7 ++- 4 files changed, 84 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4d3832cc..8fe8c7ab5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,11 @@ ### 新特性 * 【core 】 增加NetUtil.isOpen方法 +* 【core 】 增加ThreadUtil.sleep和safeSleep的重载 + ### Bug修复 * 【db 】 修复PageResult.isLast计算问题 +* 【cron 】 修复更改系统时间后CronTimer被阻塞的问题(issue#838@Github) ------------------------------------------------------------------------------------------------------------- ## 5.3.1 (2020-04-17) diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java b/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java index 379e4ff8d..1093d9bcd 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java @@ -226,11 +226,23 @@ public class ThreadUtil { if (millis == null) { return true; } + return sleep(millis.longValue()); + } - try { - Thread.sleep(millis.longValue()); - } catch (InterruptedException e) { - return false; + /** + * 挂起当前线程 + * + * @param millis 挂起的毫秒数 + * @return 被中断返回false,否则true + * @since 5.3.2 + */ + public static boolean sleep(long millis) { + if (millis > 0) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + return false; + } } return true; } @@ -243,15 +255,36 @@ public class ThreadUtil { * @see ThreadUtil#sleep(Number) */ public static boolean safeSleep(Number millis) { - long millisLong = millis.longValue(); + if (millis == null) { + return true; + } + + return safeSleep(millis.longValue()); + } + + /** + * 考虑{@link Thread#sleep(long)}方法有可能时间不足给定毫秒数,此方法保证sleep时间不小于给定的毫秒数 + * + * @param millis 给定的sleep时间 + * @return 被中断返回false,否则true + * @see ThreadUtil#sleep(Number) + * @since 5.3.2 + */ + public static boolean safeSleep(long millis) { long done = 0; - while (done < millisLong) { - long before = System.currentTimeMillis(); - if (false == sleep(millisLong - done)) { + long before; + long spendTime; + while (done >= 0 && done < millis) { + before = System.currentTimeMillis(); + if (false == sleep(millis - done)) { return false; } - long after = System.currentTimeMillis(); - done += (after - before); + spendTime = System.currentTimeMillis() - before; + if (spendTime <= 0) { + // Sleep花费时间为0或者负数,说明系统时间被拨动 + break; + } + done += spendTime; } return true; } @@ -318,6 +351,13 @@ public class ThreadUtil { } } + /** + * 等待当前线程结束. 调用 {@link Thread#join()} 并忽略 {@link InterruptedException} + */ + public static void waitForDie() { + waitForDie(Thread.currentThread()); + } + /** * 等待线程结束. 调用 {@link Thread#join()} 并忽略 {@link InterruptedException} * diff --git a/hutool-cron/src/main/java/cn/hutool/cron/CronTimer.java b/hutool-cron/src/main/java/cn/hutool/cron/CronTimer.java index 81a5ea27d..84d7317a2 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/CronTimer.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/CronTimer.java @@ -44,16 +44,18 @@ public class CronTimer extends Thread implements Serializable { long sleep; while(false == isStop){ //下一时间计算是按照上一个执行点开始时间计算的 + //此处除以定时单位是为了清零单位以下部分,例如单位是分则秒和毫秒清零 nextTime = ((thisTime / timerUnit) + 1) * timerUnit; sleep = nextTime - System.currentTimeMillis(); - if (sleep > 0 && false == ThreadUtil.safeSleep(sleep)) { - //等待直到下一个时间点,如果被中断直接退出Timer - break; + if(isValidSleepMillis(sleep, timerUnit)){ + if (false == ThreadUtil.safeSleep(sleep)) { + //等待直到下一个时间点,如果被中断直接退出Timer + break; + } + //执行点,时间记录为执行开始的时间,而非结束时间 + thisTime = System.currentTimeMillis(); + spawnLauncher(thisTime); } - - //执行点,时间记录为执行开始的时间,而非结束时间 - thisTime = System.currentTimeMillis(); - spawnLauncher(thisTime); } log.debug("Hutool-cron timer stoped."); } @@ -73,4 +75,22 @@ public class CronTimer extends Thread implements Serializable { private void spawnLauncher(final long millis){ this.scheduler.taskLauncherManager.spawnLauncher(millis); } + + /** + * 检查是否为有效的sleep毫秒数,包括: + *
    +	 *     1. 是否>0,防止用户向未来调整时间
    +	 *     1. 是否<两倍的间隔单位,防止用户向历史调整时间
    +	 * 
    + * + * @param millis 毫秒数 + * @param timerUnit 定时单位,为秒或者分的毫秒值 + * @return 是否为有效的sleep毫秒数 + * @since 5.3.2 + */ + private static boolean isValidSleepMillis(long millis, long timerUnit){ + return millis > 0 && + // 防止用户向前调整时间导致的长时间sleep + millis < (2 * timerUnit); + } } diff --git a/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java b/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java index 72ad80c2a..76f0dad19 100644 --- a/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java +++ b/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java @@ -36,13 +36,14 @@ public class CronTest { } @Test - @Ignore +// @Ignore public void cronTest2() { // 支持秒级别定时任务 CronUtil.setMatchSecond(true); CronUtil.start(); - - ThreadUtil.sleep(30000); + + ThreadUtil.waitForDie(); + Console.log("Exit."); } @Test From c2c8854b33abcff4cd86e25263718931923faa68 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 19 Apr 2020 12:31:06 +0800 Subject: [PATCH 058/157] fix comment --- hutool-all/pom.xml | 2 +- hutool-all/src/main/java/cn/hutool/Hutool.java | 7 ++++--- pom.xml | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index a65431d7f..ab1bcdd4a 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -14,7 +14,7 @@ hutool-all ${project.artifactId} - 提供丰富的Java工具方法,此模块为Hutool所有模块的打包汇总,最终形式为一个jar包 + Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool diff --git a/hutool-all/src/main/java/cn/hutool/Hutool.java b/hutool-all/src/main/java/cn/hutool/Hutool.java index 94cd56878..18fe66962 100644 --- a/hutool-all/src/main/java/cn/hutool/Hutool.java +++ b/hutool-all/src/main/java/cn/hutool/Hutool.java @@ -18,13 +18,14 @@ package cn.hutool; /** *

    - * Hutool是Hu + tool的自造词,前者致敬我的“前任公司”,后者为工具之意,谐音“糊涂”,寓意追求“万事都作糊涂观,无所谓失,无所谓得”的境界。 + * Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 *

    * *

    - * Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。
    - * Hutool最初是我项目中“util”包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。 + * Hutool中的工具方法来自于每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当;
    *

    + * + *

    Hutool是项目中“util”包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可以最大限度的避免封装不完善带来的bug。

    * * @author Looly * diff --git a/pom.xml b/pom.xml index 0e001a660..0378e162e 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ hutool-parent 5.3.2-SNAPSHOT hutool - 提供丰富的Java工具方法 + Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool From ae13d262b068b76f50294c542b2436f628b1dfad Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 19 Apr 2020 12:39:32 +0800 Subject: [PATCH 059/157] fix comment --- hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java b/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java index 76f0dad19..2f166c2c9 100644 --- a/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java +++ b/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java @@ -36,7 +36,7 @@ public class CronTest { } @Test -// @Ignore + @Ignore public void cronTest2() { // 支持秒级别定时任务 CronUtil.setMatchSecond(true); From fee3eba35f7438c3889d564d900cf8d93d79086f Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 19 Apr 2020 16:27:19 +0800 Subject: [PATCH 060/157] change readme --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 87b48a8ab..230729626 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -

    +

    -

    +

    A set of tools that keep Java sweet.

    -

    +

    @@ -36,10 +36,10 @@ netlify

    -

    +

    -- 主页:https://hutool.cn/ --

    -

    +

    -- QQ群④:718802356 --

    From 22187269da4da73e4a409417e85997ca9fbbe73a Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 19 Apr 2020 16:28:48 +0800 Subject: [PATCH 061/157] change readme --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 230729626..87b48a8ab 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -

    +

    -

    +

    A set of tools that keep Java sweet.

    -

    +

    @@ -36,10 +36,10 @@ netlify

    -

    +

    -- 主页:https://hutool.cn/ --

    -

    +

    -- QQ群④:718802356 --

    From a300a8926e4ba30dd433af1165cc19bed99cc552 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 19 Apr 2020 22:36:16 +0800 Subject: [PATCH 062/157] add test --- .../cn/hutool/core/comparator/CompareUtil.java | 7 ++++++- .../java/cn/hutool/core/bean/BeanUtilTest.java | 10 ++++++++++ .../hutool/core/comparator/CompareUtilTest.java | 16 ++++++++++++++++ .../cn/hutool/http/server/action/Action.java | 1 + .../cn/hutool/http/server/BlankServerTest.java | 13 +++++++++++++ .../cn/hutool/http/server/DocServerTest.java | 1 - .../cn/hutool/http/server/SimpleServerTest.java | 10 +++++----- 7 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 hutool-core/src/test/java/cn/hutool/core/comparator/CompareUtilTest.java create mode 100644 hutool-http/src/test/java/cn/hutool/http/server/BlankServerTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java b/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java index 3795eec52..c592e7a30 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java @@ -2,6 +2,11 @@ package cn.hutool.core.comparator; import java.util.Comparator; +/** + * 比较工具类 + * + * @author looly + */ public class CompareUtil { /** @@ -22,7 +27,7 @@ public class CompareUtil { @SuppressWarnings({"rawtypes", "unchecked"}) public static int compare(T c1, T c2, Comparator comparator) { if (null == comparator) { - return compare((Comparable)c1, (Comparable)c2); + return compare((Comparable) c1, (Comparable) c2); } return comparator.compare(c1, c2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java index 360051230..16112e51d 100644 --- a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java @@ -156,6 +156,16 @@ public class BeanUtilTest { Assert.assertEquals("sub名字", map.get("aliasSubName")); } + @Test + public void mapToBeanWithAliasTest() { + Map map = MapUtil.newHashMap(); + map.put("aliasSubName", "sub名字"); + map.put("slow", true); + + final SubPersonWithAlias subPersonWithAlias = BeanUtil.mapToBean(map, SubPersonWithAlias.class, false); + Assert.assertEquals("sub名字", subPersonWithAlias.getSubName()); + } + @Test public void beanToMapWithLocalDateTimeTest() { final LocalDateTime now = LocalDateTime.now(); diff --git a/hutool-core/src/test/java/cn/hutool/core/comparator/CompareUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/comparator/CompareUtilTest.java new file mode 100644 index 000000000..b1378be80 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/comparator/CompareUtilTest.java @@ -0,0 +1,16 @@ +package cn.hutool.core.comparator; + +import org.junit.Assert; +import org.junit.Test; + +public class CompareUtilTest { + + @Test + public void compareTest(){ + int compare = CompareUtil.compare(null, "a", true); + Assert.assertTrue(compare > 0); + + compare = CompareUtil.compare(null, "a", false); + Assert.assertTrue(compare < 0); + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java b/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java index a905fd5df..73b69058e 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/action/Action.java @@ -12,6 +12,7 @@ import java.io.IOException; * @author Looly * @since 5.2.6 */ +@FunctionalInterface public interface Action { /** diff --git a/hutool-http/src/test/java/cn/hutool/http/server/BlankServerTest.java b/hutool-http/src/test/java/cn/hutool/http/server/BlankServerTest.java new file mode 100644 index 000000000..b5819778e --- /dev/null +++ b/hutool-http/src/test/java/cn/hutool/http/server/BlankServerTest.java @@ -0,0 +1,13 @@ +package cn.hutool.http.server; + +import cn.hutool.http.HttpUtil; + +public class BlankServerTest { + public static void main(String[] args) { + HttpUtil.createServer(8888) + .addAction("/", (req, res)->{ + res.write("Hello Hutool Server"); + }) + .start(); + } +} diff --git a/hutool-http/src/test/java/cn/hutool/http/server/DocServerTest.java b/hutool-http/src/test/java/cn/hutool/http/server/DocServerTest.java index 198af98c6..e5685e2bc 100644 --- a/hutool-http/src/test/java/cn/hutool/http/server/DocServerTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/server/DocServerTest.java @@ -9,7 +9,6 @@ public class DocServerTest { HttpUtil.createServer(80) // 设置默认根目录, .setRoot("D:\\workspace\\site\\hutool-site") - // 返回JSON数据测试 .start(); DesktopUtil.browse("http://localhost/"); diff --git a/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java b/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java index ddf9082ab..2c325eec1 100644 --- a/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java @@ -23,11 +23,11 @@ public class SimpleServerTest { // 文件上传测试 // http://localhost:8888/formTest?a=1&a=2&b=3 .addAction("/file", (request, response) -> { - final UploadFile file = request.getMultipart().getFile("file"); - // 传入目录,默认读取HTTP头中的文件名然后创建文件 - file.write("d:/test/"); - Console.log("Write file to: d:/test/"); - response.write(request.getParams().toString(), ContentType.TEXT_PLAIN.toString()); + final UploadFile file = request.getMultipart().getFile("file"); + // 传入目录,默认读取HTTP头中的文件名然后创建文件 + file.write("d:/test/"); + Console.log("Write file to: d:/test/"); + response.write(request.getParams().toString(), ContentType.TEXT_PLAIN.toString()); } ) .start(); From 81cb7fb660d5b37cf05e3fc6c56c55964bdd5dd7 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 21 Apr 2020 16:06:00 +0800 Subject: [PATCH 063/157] add test --- .../test/java/cn/hutool/core/collection/CollUtilTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java index bb23a8678..114265d91 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java @@ -33,6 +33,12 @@ import java.util.SortedSet; */ public class CollUtilTest { + @Test + public void isNotEmptyTest(){ + Assert.assertFalse(CollUtil.isNotEmpty((Collection) null)); + ; + } + @Test public void newHashSetTest() { Set set = CollUtil.newHashSet((String[]) null); From 355dc15aad169b09d72fb5c4a8aef0f082d2efae Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 21 Apr 2020 21:49:01 +0800 Subject: [PATCH 064/157] add toStrin --- CHANGELOG.md | 1 + hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fe8c7ab5..a8cbbb3c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### 新特性 * 【core 】 增加NetUtil.isOpen方法 * 【core 】 增加ThreadUtil.sleep和safeSleep的重载 +* 【core 】 Sftp类增加toString方法(issue#I1F2T4@Gitee) ### Bug修复 * 【db 】 修复PageResult.isLast计算问题 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java index 8b8712caa..2bc4d19c9 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java @@ -408,6 +408,15 @@ public class Sftp extends AbstractFtp { JschUtil.close(this.session); } + @Override + public String toString() { + return "Sftp{" + + "host='" + host + '\'' + + ", port=" + port + + ", user='" + user + '\'' + + '}'; + } + /** * JSch支持的三种文件传输模式 * From bacecd591f6d04c22458d191034255d6dc0f174e Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 22 Apr 2020 10:32:07 +0800 Subject: [PATCH 065/157] fix cha --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 87b48a8ab..c7e6b2cbc 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ compile 'cn.hutool:hutool-all:5.3.2' - [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.2/) > 注意 -> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类获工具方法可用。 +> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 > 如果你的项目使用JDK7,请使用Hutool 4.x版本 ### 编译安装 From 2b9fb67cd00aacc9d4aa3b835e4b378e93cab12d Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 23 Apr 2020 09:14:31 +0800 Subject: [PATCH 066/157] fix Page bug --- CHANGELOG.md | 1 + hutool-db/src/main/java/cn/hutool/db/Page.java | 5 +---- .../src/test/java/cn/hutool/db/PageTest.java | 17 +++++++++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 hutool-db/src/test/java/cn/hutool/db/PageTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index a8cbbb3c0..4c21fb561 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### Bug修复 * 【db 】 修复PageResult.isLast计算问题 * 【cron 】 修复更改系统时间后CronTimer被阻塞的问题(issue#838@Github) +* 【db 】 修复Page.addOrder无效问题(issue#838@Github) ------------------------------------------------------------------------------------------------------------- ## 5.3.1 (2020-04-17) diff --git a/hutool-db/src/main/java/cn/hutool/db/Page.java b/hutool-db/src/main/java/cn/hutool/db/Page.java index 39bc195a1..d53b6bc99 100644 --- a/hutool-db/src/main/java/cn/hutool/db/Page.java +++ b/hutool-db/src/main/java/cn/hutool/db/Page.java @@ -134,10 +134,7 @@ public class Page implements Serializable { * @param orders 排序 */ public void addOrder(Order... orders) { - if (null != this.orders) { - ArrayUtil.append(this.orders, orders); - } - this.orders = orders; + this.orders = ArrayUtil.append(this.orders, orders); } // ---------------------------------------------------------- Getters and Setters end diff --git a/hutool-db/src/test/java/cn/hutool/db/PageTest.java b/hutool-db/src/test/java/cn/hutool/db/PageTest.java new file mode 100644 index 000000000..7c7ca116e --- /dev/null +++ b/hutool-db/src/test/java/cn/hutool/db/PageTest.java @@ -0,0 +1,17 @@ +package cn.hutool.db; + +import cn.hutool.db.sql.Order; +import org.junit.Assert; +import org.junit.Test; + +public class PageTest { + + @Test + public void addOrderTest() { + Page page = new Page(); + page.addOrder(new Order("aaa")); + Assert.assertEquals(page.getOrders().length, 1); + page.addOrder(new Order("aaa")); + Assert.assertEquals(page.getOrders().length, 2); + } +} From 9f40666c955c3aa4440de753500a7901b5cefad3 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 23 Apr 2020 10:07:56 +0800 Subject: [PATCH 067/157] fic null point bug --- CHANGELOG.md | 3 +- .../hutool/core/bean/copier/BeanCopier.java | 5 +- .../core/bean/copier/ValueProvider.java | 2 +- .../copier/provider/BeanValueProvider.java | 10 ++-- .../copier/provider/MapValueProvider.java | 17 ++++++- .../java/cn/hutool/core/convert/Convert.java | 50 ++++++++++++++----- .../impl/TemporalAccessorConverter.java | 5 ++ .../java/cn/hutool/json/JSONConverter.java | 18 +++---- .../test/java/cn/hutool/json/IssueI1F8M2.java | 38 ++++++++++++++ 9 files changed, 116 insertions(+), 32 deletions(-) create mode 100644 hutool-json/src/test/java/cn/hutool/json/IssueI1F8M2.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c21fb561..23b69b9b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,8 @@ ### Bug修复 * 【db 】 修复PageResult.isLast计算问题 * 【cron 】 修复更改系统时间后CronTimer被阻塞的问题(issue#838@Github) -* 【db 】 修复Page.addOrder无效问题(issue#838@Github) +* 【db 】 修复Page.addOrder无效问题(issue#I1F9MZ@Gitee) +* 【json 】 修复JSONConvert转换日期空指针问题(issue#I1F8M2@Gitee) ------------------------------------------------------------------------------------------------------------- ## 5.3.1 (2020-04-17) diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java index 80fea3fd4..27ed86fbb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java @@ -130,7 +130,10 @@ public class BeanCopier implements Copier, Serializable { * @param bean Bean */ private void mapToBean(Map map, Object bean) { - valueProviderToBean(new MapValueProvider(map, this.copyOptions.ignoreCase), bean); + valueProviderToBean( + new MapValueProvider(map, this.copyOptions.ignoreCase, this.copyOptions.ignoreError), + bean + ); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/ValueProvider.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/ValueProvider.java index 6debd127a..d8663a6ce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/ValueProvider.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/ValueProvider.java @@ -18,7 +18,7 @@ public interface ValueProvider{ * 返回值一般需要匹配被注入类型,如果不匹配会调用默认转换 Convert#convert(Type, Object)实现转换 * * @param key Bean对象中参数名 - * @param valueType 被注入的值得类型 + * @param valueType 被注入的值的类型 * @return 对应参数名的值 */ Object value(T key, Type valueType); diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java index fbc410b2d..e28fe49fa 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java @@ -3,6 +3,7 @@ package cn.hutool.core.bean.copier.provider; import cn.hutool.core.bean.BeanDesc.PropDesc; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.copier.ValueProvider; +import cn.hutool.core.convert.Convert; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.util.StrUtil; @@ -42,20 +43,23 @@ public class BeanValueProvider implements ValueProvider { //boolean类型字段字段名支持两种方式 sourcePd = sourcePdMap.get(StrUtil.upperFirstAndAddPre(key, "is")); } - + + Object result = null; if (null != sourcePd) { final Method getter = sourcePd.getGetter(); if (null != getter) { try { - return getter.invoke(source); + result = getter.invoke(source); } catch (Exception e) { if (false == ignoreError) { throw new UtilException(e, "Inject [{}] error!", key); } } + + result = Convert.convertWithCheck(valueType,result, null, ignoreError); } } - return null; + return result; } @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java index 896cfab61..f2dc4a471 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java @@ -17,14 +17,26 @@ import java.util.Map; public class MapValueProvider implements ValueProvider { private final Map map; + private final boolean ignoreError; + + /** + * 构造 + * + * @param map Map + * @param ignoreCase 是否忽略key的大小写 + */ + public MapValueProvider(Map map, boolean ignoreCase) { + this(map, ignoreCase, false); + } /** * 构造 * * @param map Map * @param ignoreCase 是否忽略key的大小写 + * @since 5.3.2 */ - public MapValueProvider(Map map, boolean ignoreCase) { + public MapValueProvider(Map map, boolean ignoreCase, boolean ignoreError) { if(false == ignoreCase || map instanceof CaseInsensitiveMap) { //不忽略大小写或者提供的Map本身为CaseInsensitiveMap则无需转换 this.map = map; @@ -32,6 +44,7 @@ public class MapValueProvider implements ValueProvider { //转换为大小写不敏感的Map this.map = new CaseInsensitiveMap<>(map); } + this.ignoreError = ignoreError; } @Override @@ -42,7 +55,7 @@ public class MapValueProvider implements ValueProvider { value = map.get(StrUtil.toUnderlineCase(key)); } - return Convert.convert(valueType, value); + return Convert.convertWithCheck(valueType, value, null, this.ignoreError); } @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/Convert.java b/hutool-core/src/main/java/cn/hutool/core/convert/Convert.java index d4770dda7..d06fed1e8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/Convert.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/Convert.java @@ -1,14 +1,5 @@ package cn.hutool.core.convert; -import java.lang.reflect.Type; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.nio.charset.Charset; -import java.time.Instant; -import java.time.LocalDateTime; -import java.util.*; -import java.util.concurrent.TimeUnit; - import cn.hutool.core.convert.impl.CollectionConverter; import cn.hutool.core.convert.impl.EnumConverter; import cn.hutool.core.convert.impl.MapConverter; @@ -20,6 +11,21 @@ import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + /** * 类型转换器 * @@ -674,7 +680,7 @@ public class Convert { * @throws ConvertException 转换器不存在 */ public static T convert(Type type, Object value, T defaultValue) throws ConvertException { - return ConverterRegistry.getInstance().convert(type, value, defaultValue); + return convertWithCheck(type, value, defaultValue, false); } /** @@ -703,10 +709,30 @@ public class Convert { * @since 4.5.10 */ public static T convertQuietly(Type type, Object value, T defaultValue) { + return convertWithCheck(type, value, defaultValue, true); + } + + /** + * 转换值为指定类型,可选是否不抛异常转换
    + * 当转换失败时返回默认值 + * + * @param 目标类型 + * @param type 目标类型 + * @param value 值 + * @param defaultValue 默认值 + * @param quietly 是否静默转换,true不抛异常 + * @return 转换后的值 + * @since 5.3.2 + */ + public static T convertWithCheck(Type type, Object value, T defaultValue, boolean quietly) { + final ConverterRegistry registry = ConverterRegistry.getInstance(); try { - return convert(type, value, defaultValue); + return registry.convert(type, value, defaultValue); } catch (Exception e) { - return defaultValue; + if(quietly){ + return defaultValue; + } + throw e; } } diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java index af6331626..02bfe6a22 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/TemporalAccessorConverter.java @@ -4,6 +4,7 @@ import cn.hutool.core.convert.AbstractConverter; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import java.time.Instant; import java.time.LocalDate; @@ -107,6 +108,10 @@ public class TemporalAccessorConverter extends AbstractConverter { } if(value instanceof JSON) { - JSONDeserializer deserializer = GlobalSerializeMapping.getDeserializer(targetType); + final JSONDeserializer deserializer = GlobalSerializeMapping.getDeserializer(targetType); if(null != deserializer) { return (T) deserializer.deserialize((JSON)value); } } - Object targetValue; - try { - targetValue = Convert.convert(targetType, value); - } catch (ConvertException e) { - if (ignoreError) { - return null; - } - throw e; - } - + final T targetValue = ignoreError ? + Convert.convertQuietly(targetType, value): + Convert.convert(targetType, value); + if (null == targetValue && false == ignoreError) { if (StrUtil.isBlankIfStr(value)) { // 对于传入空字符串的情况,如果转换的目标对象是非字符串或非原始类型,转换器会返回false。 @@ -97,7 +91,7 @@ public class JSONConverter implements Converter { throw new ConvertException("Can not convert {} to type {}", value, ObjectUtil.defaultIfNull(TypeUtil.getClass(targetType), targetType)); } - return (T) targetValue; + return targetValue; } @Override diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI1F8M2.java b/hutool-json/src/test/java/cn/hutool/json/IssueI1F8M2.java new file mode 100644 index 000000000..5f928a337 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI1F8M2.java @@ -0,0 +1,38 @@ +package cn.hutool.json; + +import lombok.Data; +import org.junit.Assert; +import org.junit.Test; + +import java.time.LocalDateTime; + +/** + * https://gitee.com/loolly/dashboard/issues?id=I1F8M2 + */ +public class IssueI1F8M2 { + @Test + public void toBeanTest() { + String jsonStr = "{\"eventType\":\"fee\",\"fwdAlertingTime\":\"2020-04-22 16:34:13\",\"fwdAnswerTime\":\"\"}"; + Param param = JSONUtil.toBean(jsonStr, Param.class); + Assert.assertEquals("2020-04-22T16:34:13", param.getFwdAlertingTime().toString()); + Assert.assertNull(param.getFwdAnswerTime()); + } + + // Param类的字段 + @Data + static class Param { + /** + * fee表示话单事件 + */ + private String eventType; + /** + * 转接呼叫后振铃时间 + */ + private LocalDateTime fwdAlertingTime; + /** + * 转接呼叫后应答时间 + */ + private LocalDateTime fwdAnswerTime; + + } +} From bd4bd2d4c99d7f9d23c84e94f0cc4fcd88306efe Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 23 Apr 2020 10:11:25 +0800 Subject: [PATCH 068/157] fix comment --- .../cn/hutool/core/bean/copier/provider/MapValueProvider.java | 1 + 1 file changed, 1 insertion(+) diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java index f2dc4a471..97275f3b6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java @@ -34,6 +34,7 @@ public class MapValueProvider implements ValueProvider { * * @param map Map * @param ignoreCase 是否忽略key的大小写 + * @param ignoreError 是否忽略错误 * @since 5.3.2 */ public MapValueProvider(Map map, boolean ignoreCase, boolean ignoreError) { From 3f97a1e4958e784e00d495c24453c5a221cdb92a Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 23 Apr 2020 11:08:44 +0800 Subject: [PATCH 069/157] change size logic --- CHANGELOG.md | 3 ++- hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23b69b9b3..bbf394783 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,13 @@ ------------------------------------------------------------------------------------------------------------- -## 5.3.2 (2020-04-19) +## 5.3.2 (2020-04-23) ### 新特性 * 【core 】 增加NetUtil.isOpen方法 * 【core 】 增加ThreadUtil.sleep和safeSleep的重载 * 【core 】 Sftp类增加toString方法(issue#I1F2T4@Gitee) +* 【core 】 修改FileUtil.size逻辑,不存在的文件返回0 ### Bug修复 * 【db 】 修复PageResult.isLast计算问题 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index a85e2ae19..3cd5d63ea 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -589,13 +589,12 @@ public class FileUtil { * 当给定对象为文件时,直接调用 {@link File#length()}
    * 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回 * - * @param file 目录或文件 + * @param file 目录或文件,null或者文件不存在返回0 * @return 总大小,bytes长度 */ public static long size(File file) { - Assert.notNull(file, "file argument is null !"); - if (false == file.exists()) { - throw new IllegalArgumentException(StrUtil.format("File [{}] not exist !", file.getAbsolutePath())); + if (null == file || false == file.exists()) { + return 0; } if (file.isDirectory()) { From c19176c8e83546fdf044628ce6c99f30c9f7faac Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 23 Apr 2020 11:16:43 +0800 Subject: [PATCH 070/157] fix test --- .../cn/hutool/poi/excel/test/TestBean.java | 43 ++----------------- 1 file changed, 3 insertions(+), 40 deletions(-) diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/TestBean.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/TestBean.java index af68870bd..62c8405ba 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/TestBean.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/TestBean.java @@ -1,51 +1,14 @@ package cn.hutool.poi.excel.test; +import lombok.Data; + import java.util.Date; +@Data public class TestBean { private String name; private int age; private double score; private boolean isPass; private Date examDate; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public int getAge() { - return age; - } - - public void setAge(int age) { - this.age = age; - } - - public double getScore() { - return score; - } - - public void setScore(double score) { - this.score = score; - } - - public boolean isPass() { - return isPass; - } - - public void setPass(boolean isPass) { - this.isPass = isPass; - } - - public Date getExamDate() { - return examDate; - } - - public void setExamDate(Date examDate) { - this.examDate = examDate; - } } From 96c56d9e31badbdd501e7e74e33ea15205f54114 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 23 Apr 2020 11:40:27 +0800 Subject: [PATCH 071/157] fix xml null bug --- CHANGELOG.md | 1 + .../java/cn/hutool/core/util/XmlUtil.java | 32 +++++++++++-------- .../java/cn/hutool/core/util/XmlUtilTest.java | 9 ++++++ hutool-core/src/test/resources/test.xml | 4 +++ 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf394783..e616c389d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * 【cron 】 修复更改系统时间后CronTimer被阻塞的问题(issue#838@Github) * 【db 】 修复Page.addOrder无效问题(issue#I1F9MZ@Gitee) * 【json 】 修复JSONConvert转换日期空指针问题(issue#I1F8M2@Gitee) +* 【core 】 修复XML中带注释Xpath解析导致空指针问题(issue#I1F2WI@Gitee) ------------------------------------------------------------------------------------------------------------- ## 5.3.1 (2020-04-17) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java index ff26635b5..c7e6bd7bf 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java @@ -1253,18 +1253,23 @@ public class XmlUtil { * @param attributesOnly, if true no recursion happens */ private void examineNode(Node node, boolean attributesOnly) { - NamedNodeMap attributes = node.getAttributes(); - for (int i = 0; i < attributes.getLength(); i++) { - Node attribute = attributes.item(i); - storeAttribute(attribute); + final NamedNodeMap attributes = node.getAttributes(); + if(null != attributes){ + for (int i = 0; i < attributes.getLength(); i++) { + Node attribute = attributes.item(i); + storeAttribute(attribute); + } } if (false == attributesOnly) { - NodeList childNodes = node.getChildNodes(); - for (int i = 0; i < childNodes.getLength(); i++) { - Node item = childNodes.item(i); - if (item.getNodeType() == Node.ELEMENT_NODE) - examineNode(item, false); + final NodeList childNodes = node.getChildNodes(); + if(null != childNodes){ + Node item; + for (int i = 0; i < childNodes.getLength(); i++) { + item = childNodes.item(i); + if (item.getNodeType() == Node.ELEMENT_NODE) + examineNode(item, false); + } } } } @@ -1276,12 +1281,13 @@ public class XmlUtil { * @param attribute to examine */ private void storeAttribute(Node attribute) { + if(null == attribute){ + return; + } // examine the attributes in namespace xmlns - if (attribute.getNamespaceURI() != null - && attribute.getNamespaceURI().equals( - XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) { + if (XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(attribute.getNamespaceURI())) { // Default namespace xmlns="uri goes here" - if (attribute.getNodeName().equals(XMLConstants.XMLNS_ATTRIBUTE)) { + if (XMLConstants.XMLNS_ATTRIBUTE.equals(attribute.getNodeName())) { prefixUri.put(DEFAULT_NS, attribute.getNodeValue()); } else { // The defined prefixes are stored here diff --git a/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java index 323619213..0838cca6b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.map.MapBuilder; import cn.hutool.core.map.MapUtil; import org.junit.Assert; @@ -64,6 +65,14 @@ public class XmlUtilTest { Assert.assertEquals("ok", value); } + @Test + public void xpathTest2() { + String result = ResourceUtil.readUtf8Str("test.xml"); + Document docResult = XmlUtil.parseXml(result); + Object value = XmlUtil.getByXPath("//returnsms/message", docResult, XPathConstants.STRING); + Assert.assertEquals("ok", value); + } + @Test public void xmlToMapTest() { String xml = ""// diff --git a/hutool-core/src/test/resources/test.xml b/hutool-core/src/test/resources/test.xml index d831b2820..dc1eb8f6d 100644 --- a/hutool-core/src/test/resources/test.xml +++ b/hutool-core/src/test/resources/test.xml @@ -1,4 +1,8 @@ + + Success(成功) ok From 74ba64c0c003c07a48baaa4fd6ed56159441826d Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 23 Apr 2020 11:46:14 +0800 Subject: [PATCH 072/157] fix test --- .../java/cn/hutool/core/date/DateUtilTest.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index 6123d4b76..d769e06b9 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -364,11 +364,11 @@ public class DateUtilTest { } - @SuppressWarnings("ConstantConditions") @Test public void parseTest6() { String str = "Tue Jun 4 16:25:15 +0800 2019"; DateTime dateTime = DateUtil.parse(str); + assert dateTime != null; Assert.assertEquals("2019-06-04 16:25:15", dateTime.toString()); } @@ -379,12 +379,12 @@ public class DateUtilTest { Assert.assertEquals("2019-06-01 19:45:43", dateTime.toString()); } - @SuppressWarnings("ConstantConditions") @Test public void parseAndOffsetTest() { // 检查UTC时间偏移是否准确 String str = "2019-09-17T13:26:17.948Z"; DateTime dateTime = DateUtil.parse(str); + assert dateTime != null; Assert.assertEquals("2019-09-17 13:26:17", dateTime.toString()); DateTime offset = DateUtil.offsetHour(dateTime, 8); @@ -467,7 +467,6 @@ public class DateUtilTest { Assert.assertEquals(dt1, dt2); } - @SuppressWarnings("ConstantConditions") @Test public void parseUTCTest() { String dateStr1 = "2018-09-13T05:34:31Z"; @@ -497,11 +496,13 @@ public class DateUtilTest { dateStr1 = "2018-09-13T13:34:34+0800"; dt = DateUtil.parse(dateStr1); + assert dt != null; dateStr = dt.toString(TimeZone.getTimeZone("GMT+8:00")); Assert.assertEquals("2018-09-13 13:34:34", dateStr); dateStr1 = "2018-09-13T13:34:35+08:00"; dt = DateUtil.parse(dateStr1); + assert dt != null; dateStr = dt.toString(TimeZone.getTimeZone("GMT+8:00")); Assert.assertEquals("2018-09-13 13:34:35", dateStr); @@ -519,11 +520,13 @@ public class DateUtilTest { dateStr1 = "2018-09-13T13:34:38.999+0800"; dt = DateUtil.parse(dateStr1); + assert dt != null; dateStr = dt.toString(simpleDateFormat); Assert.assertEquals("2018-09-13 13:34:38.999", dateStr); dateStr1 = "2018-09-13T13:34:39.999+08:00"; dt = DateUtil.parse(dateStr1); + assert dt != null; dateStr = dt.toString(simpleDateFormat); Assert.assertEquals("2018-09-13 13:34:39.999", dateStr); } @@ -563,6 +566,13 @@ public class DateUtilTest { Assert.assertEquals("2019-05-16 17:57:18", Objects.requireNonNull(time).toString()); } + @Test + public void parseISOTest() { + String dateStr = "2020-04-23T02:31:00.000Z"; + DateTime time = DateUtil.parse(dateStr); + Assert.assertEquals("2020-04-23 02:31:00", Objects.requireNonNull(time).toString()); + } + @Test public void endOfYearTest() { DateTime date = DateUtil.date(); From 368a5e4d1c3ab25319a0c15b6d637d748ca18903 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 23 Apr 2020 12:01:53 +0800 Subject: [PATCH 073/157] fix FileUtil.remove bug --- CHANGELOG.md | 2 ++ hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java | 5 ++++- hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java | 5 ++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e616c389d..fc495b155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * 【core 】 增加ThreadUtil.sleep和safeSleep的重载 * 【core 】 Sftp类增加toString方法(issue#I1F2T4@Gitee) * 【core 】 修改FileUtil.size逻辑,不存在的文件返回0 +* 【extra 】 Sftp.ls遇到文件不存在返回空集合,而非抛异常(issue#844@Github) ### Bug修复 * 【db 】 修复PageResult.isLast计算问题 @@ -17,6 +18,7 @@ * 【db 】 修复Page.addOrder无效问题(issue#I1F9MZ@Gitee) * 【json 】 修复JSONConvert转换日期空指针问题(issue#I1F8M2@Gitee) * 【core 】 修复XML中带注释Xpath解析导致空指针问题(issue#I1F2WI@Gitee) +* 【core 】 修复FileUtil.rename原文件无扩展名多点的问题(issue#839@Github) ------------------------------------------------------------------------------------------------------------- ## 5.3.1 (2020-04-17) diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 3cd5d63ea..b798a8f4b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -1175,7 +1175,10 @@ public class FileUtil { */ public static File rename(File file, String newName, boolean isRetainExt, boolean isOverride) { if (isRetainExt) { - newName = newName.concat(".").concat(FileUtil.extName(file)); + final String extName = FileUtil.extName(file); + if(StrUtil.isNotBlank(extName)){ + newName = newName.concat(".").concat(extName); + } } final Path path = file.toPath(); final CopyOption[] options = isOverride ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{}; diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java index 2bc4d19c9..c16cfeb21 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java @@ -238,7 +238,10 @@ public class Sftp extends AbstractFtp { return LsEntrySelector.CONTINUE; }); } catch (SftpException e) { - throw new JschRuntimeException(e); + if(false == StrUtil.startWithIgnoreCase(e.getMessage(), "No such file")){ + throw new JschRuntimeException(e); + } + // 文件不存在忽略 } return fileNames; } From e8c0a75e7f19c6e2967487181a2f73b3f122f8f6 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 23 Apr 2020 12:07:41 +0800 Subject: [PATCH 074/157] fix null bug --- CHANGELOG.md | 1 + hutool-db/src/main/java/cn/hutool/db/DbUtil.java | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc495b155..ef7fba7b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ * 【json 】 修复JSONConvert转换日期空指针问题(issue#I1F8M2@Gitee) * 【core 】 修复XML中带注释Xpath解析导致空指针问题(issue#I1F2WI@Gitee) * 【core 】 修复FileUtil.rename原文件无扩展名多点的问题(issue#839@Github) +* 【db 】 修复DbUtil.close可能存在的空指针问题(issue#847@Github) ------------------------------------------------------------------------------------------------------------- ## 5.3.1 (2020-04-17) diff --git a/hutool-db/src/main/java/cn/hutool/db/DbUtil.java b/hutool-db/src/main/java/cn/hutool/db/DbUtil.java index e01ac8b44..1de03ef36 100644 --- a/hutool-db/src/main/java/cn/hutool/db/DbUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/DbUtil.java @@ -152,10 +152,12 @@ public final class DbUtil { */ public static void close(Object... objsToClose) { for (Object obj : objsToClose) { - if (obj instanceof AutoCloseable) { - IoUtil.close((AutoCloseable) obj); - } else { - log.warn("Object {} not a ResultSet or Statement or PreparedStatement or Connection!", obj.getClass().getName()); + if(null != obj){ + if (obj instanceof AutoCloseable) { + IoUtil.close((AutoCloseable) obj); + } else { + log.warn("Object {} not a ResultSet or Statement or PreparedStatement or Connection!", obj.getClass().getName()); + } } } } From 01ac0f94dcaf1b9bba6701d28e09f22a0ebc3ec0 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 23 Apr 2020 13:50:59 +0800 Subject: [PATCH 075/157] add h2 and derby test --- hutool-db/pom.xml | 6 +++ .../cn/hutool/db/dialect/DialectFactory.java | 9 +--- .../cn/hutool/db/dialect/impl/H2Dialect.java | 6 +-- .../src/test/java/cn/hutool/db/DerbyTest.java | 50 +++++++++++++++++++ .../src/test/java/cn/hutool/db/H2Test.java | 42 ++++++++++++++++ .../test/java/cn/hutool/db/HsqldbTest.java | 21 ++++---- .../src/test/resources/config/db.setting | 12 ++++- 7 files changed, 125 insertions(+), 21 deletions(-) create mode 100644 hutool-db/src/test/java/cn/hutool/db/DerbyTest.java create mode 100644 hutool-db/src/test/java/cn/hutool/db/H2Test.java diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 6bdf09299..2312adac4 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -135,5 +135,11 @@ 1.7.26 test + + com.h2database + h2 + 1.4.200 + test +
    diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java index 05484d09a..8995d0f39 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java @@ -45,9 +45,7 @@ public class DialectFactory { /** JDBC 驱动 H2 */ public static final String DRIVER_H2 = "org.h2.Driver"; /** JDBC 驱动 Derby */ - public static final String DRIVER_DERBY = "org.apache.derby.jdbc.ClientDriver"; - /** JDBC 驱动 Derby嵌入式 */ - public static final String DRIVER_DERBY_EMBEDDED = "org.apache.derby.jdbc.EmbeddedDriver"; + public static final String DRIVER_DERBY = "org.apache.derby.jdbc.AutoloadedDriver"; /** JDBC 驱动 HSQLDB */ public static final String DRIVER_HSQLDB = "org.hsqldb.jdbc.JDBCDriver"; /** JDBC 驱动 达梦7 */ @@ -126,12 +124,9 @@ public class DialectFactory { driver = DRIVER_HIVE; } else if (nameContainsProductInfo.contains("h2")) { driver = DRIVER_H2; - } else if (nameContainsProductInfo.startsWith("jdbc:derby://")) { - // Derby数据库网络连接方式 - driver = DRIVER_DERBY; } else if (nameContainsProductInfo.contains("derby")) { // 嵌入式Derby数据库 - driver = DRIVER_DERBY_EMBEDDED; + driver = DRIVER_DERBY; } else if (nameContainsProductInfo.contains("hsqldb")) { // HSQLDB driver = DRIVER_HSQLDB; diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/H2Dialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/H2Dialect.java index 0d86f061a..404814c45 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/H2Dialect.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/H2Dialect.java @@ -3,19 +3,17 @@ package cn.hutool.db.dialect.impl; import cn.hutool.db.Page; import cn.hutool.db.dialect.DialectName; import cn.hutool.db.sql.SqlBuilder; -import cn.hutool.db.sql.Wrapper; /** * H2数据库方言 - * - * @author loolly * + * @author loolly */ public class H2Dialect extends AnsiSqlDialect { private static final long serialVersionUID = 1490520247974768214L; public H2Dialect() { - wrapper = new Wrapper('"', '"'); +// wrapper = new Wrapper('"'); } @Override diff --git a/hutool-db/src/test/java/cn/hutool/db/DerbyTest.java b/hutool-db/src/test/java/cn/hutool/db/DerbyTest.java new file mode 100644 index 000000000..19549cb14 --- /dev/null +++ b/hutool-db/src/test/java/cn/hutool/db/DerbyTest.java @@ -0,0 +1,50 @@ +package cn.hutool.db; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; + +import java.sql.SQLException; +import java.util.List; + +/** + * Derby数据库单元测试 + * + * @author looly + * + */ +public class DerbyTest { + + private static final String DS_GROUP_NAME = "derby"; + + @BeforeClass + public static void init() throws SQLException { + Db db = Db.use(DS_GROUP_NAME); + try{ + db.execute("CREATE TABLE test(a INTEGER, b BIGINT)"); + }catch (SQLException e){ + // 数据库已存在 + return; + } + + db.insert(Entity.create("test").set("a", 1).set("b", 11)); + db.insert(Entity.create("test").set("a", 2).set("b", 21)); + db.insert(Entity.create("test").set("a", 3).set("b", 31)); + db.insert(Entity.create("test").set("a", 4).set("b", 41)); + } + + @Test + @Ignore + public void queryTest() throws SQLException { + List query = Db.use(DS_GROUP_NAME).query("select * from test"); + Assert.assertEquals(4, query.size()); + } + + @Test + @Ignore + public void findTest() throws SQLException { + List query = Db.use(DS_GROUP_NAME).find(Entity.create("test")); + Assert.assertEquals(4, query.size()); + } +} diff --git a/hutool-db/src/test/java/cn/hutool/db/H2Test.java b/hutool-db/src/test/java/cn/hutool/db/H2Test.java new file mode 100644 index 000000000..86263c783 --- /dev/null +++ b/hutool-db/src/test/java/cn/hutool/db/H2Test.java @@ -0,0 +1,42 @@ +package cn.hutool.db; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.sql.SQLException; +import java.util.List; + +/** + * H2数据库单元测试 + * + * @author looly + * + */ +public class H2Test { + + private static final String DS_GROUP_NAME = "h2"; + + @BeforeClass + public static void init() throws SQLException { + Db db = Db.use(DS_GROUP_NAME); + db.execute("CREATE TABLE test(a INTEGER, b BIGINT)"); + + db.insert(Entity.create("test").set("a", 1).set("b", 11)); + db.insert(Entity.create("test").set("a", 2).set("b", 21)); + db.insert(Entity.create("test").set("a", 3).set("b", 31)); + db.insert(Entity.create("test").set("a", 4).set("b", 41)); + } + + @Test + public void queryTest() throws SQLException { + List query = Db.use(DS_GROUP_NAME).query("select * from test"); + Assert.assertEquals(4, query.size()); + } + + @Test + public void findTest() throws SQLException { + List query = Db.use(DS_GROUP_NAME).find(Entity.create("test")); + Assert.assertEquals(4, query.size()); + } +} diff --git a/hutool-db/src/test/java/cn/hutool/db/HsqldbTest.java b/hutool-db/src/test/java/cn/hutool/db/HsqldbTest.java index e5c607c06..54669ce1e 100644 --- a/hutool-db/src/test/java/cn/hutool/db/HsqldbTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/HsqldbTest.java @@ -1,15 +1,12 @@ package cn.hutool.db; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + import java.sql.SQLException; import java.util.List; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import cn.hutool.db.Db; -import cn.hutool.db.Entity; - /** * HSQLDB数据库单元测试 * @@ -20,8 +17,8 @@ public class HsqldbTest { private static final String DS_GROUP_NAME = "hsqldb"; - @Before - public void init() throws SQLException { + @BeforeClass + public static void init() throws SQLException { Db db = Db.use(DS_GROUP_NAME); db.execute("CREATE TABLE test(a INTEGER, b BIGINT)"); db.insert(Entity.create("test").set("a", 1).set("b", 11)); @@ -35,4 +32,10 @@ public class HsqldbTest { List query = Db.use(DS_GROUP_NAME).query("select * from test"); Assert.assertEquals(4, query.size()); } + + @Test + public void findTest() throws SQLException { + List query = Db.use(DS_GROUP_NAME).find(Entity.create("test")); + Assert.assertEquals(4, query.size()); + } } diff --git a/hutool-db/src/test/resources/config/db.setting b/hutool-db/src/test/resources/config/db.setting index 4d764d5a3..b3ef7cc0f 100644 --- a/hutool-db/src/test/resources/config/db.setting +++ b/hutool-db/src/test/resources/config/db.setting @@ -28,7 +28,17 @@ url = jdbc:sqlite:test.db [hsqldb] url = jdbc:hsqldb:mem:mem_hutool user = SA -pass = +pass = + +# 测试用HSQLDB数据库 +[h2] +url = jdbc:h2:mem:h2_hutool +user = sa +pass = + +# 测试用HSQLDB数据库 +[derby] +url = jdbc:derby:.derby/test_db;create=true # 测试用Oracle数据库 [orcl] From 03827c771105a2a71a29b2139018796861b3ebb7 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 23 Apr 2020 14:22:34 +0800 Subject: [PATCH 076/157] add toString for HttpRequest --- CHANGELOG.md | 1 + .../main/java/cn/hutool/http/HttpBase.java | 15 +++++---- .../main/java/cn/hutool/http/HttpRequest.java | 32 +++++++++++++++---- .../cn/hutool/http/test/HttpRequestTest.java | 10 ++---- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef7fba7b8..fda23e977 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * 【core 】 Sftp类增加toString方法(issue#I1F2T4@Gitee) * 【core 】 修改FileUtil.size逻辑,不存在的文件返回0 * 【extra 】 Sftp.ls遇到文件不存在返回空集合,而非抛异常(issue#844@Github) +* 【http 】 改进HttpRequest.toString()格式,添加url ### Bug修复 * 【db 】 修复PageResult.isLast计算问题 diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index 128690fca..083441d31 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -1,5 +1,11 @@ package cn.hutool.http; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; + import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; @@ -8,11 +14,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.map.CaseInsensitiveMap; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; - /** * http基类 * @author Looly @@ -293,7 +294,9 @@ public abstract class HttpBase { StringBuilder sb = StrUtil.builder(); sb.append("Request Headers: ").append(StrUtil.CRLF); for (Entry> entry : this.headers.entrySet()) { - sb.append(" ").append(entry).append(StrUtil.CRLF); + sb.append(" ").append( + entry.getKey()).append(": ").append(CollUtil.join(entry.getValue(), ",")) + .append(StrUtil.CRLF); } sb.append("Request Body: ").append(StrUtil.CRLF); diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 462df202f..7eded0776 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -933,10 +933,8 @@ public class HttpRequest extends HttpBase { public HttpResponse execute(boolean isAsync) { // 初始化URL urlWithParamIfGet(); - // 初始化 connection initConnection(); - // 发送请求 send(); @@ -975,6 +973,15 @@ public class HttpRequest extends HttpBase { header(Header.AUTHORIZATION, content, true); return this; } + + @Override + public String toString() { + StringBuilder sb = StrUtil.builder(); + sb.append("Request Url: ").append(this.url).append(StrUtil.CRLF); + sb.append(super.toString()); + return sb.toString(); + } + // ---------------------------------------------------------------- Private method start /** @@ -1102,12 +1109,23 @@ public class HttpRequest extends HttpBase { } // Write的时候会优先使用body中的内容,write时自动关闭OutputStream - if (ArrayUtil.isNotEmpty(this.bodyBytes)) { - IoUtil.write(this.httpConnection.getOutputStream(), true, this.bodyBytes); - } else { - final String content = HttpUtil.toParams(this.form, this.charset); - IoUtil.write(this.httpConnection.getOutputStream(), this.charset, true, content); + byte[] content; + if(ArrayUtil.isNotEmpty(this.bodyBytes)){ + content = this.bodyBytes; + } else{ + content = StrUtil.bytes(getFormUrlEncoded(), this.charset); } + IoUtil.write(this.httpConnection.getOutputStream(), true, content); + } + + /** + * 获取编码后的表单数据,无表单数据返回"" + * + * @return 编码后的表单数据,无表单数据返回"" + * @since 5.3.2 + */ + private String getFormUrlEncoded() { + return HttpUtil.toParams(this.form, this.charset); } /** diff --git a/hutool-http/src/test/java/cn/hutool/http/test/HttpRequestTest.java b/hutool-http/src/test/java/cn/hutool/http/test/HttpRequestTest.java index 34c9f60c2..a725b169e 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/HttpRequestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/HttpRequestTest.java @@ -41,15 +41,11 @@ public class HttpRequestTest { @Test @Ignore - public void getWithParamsTest() { + public void toStringTest() { String url = "http://gc.ditu.aliyun.com/geocoding?ccc=你好"; - HttpRequest request = HttpRequest.get(url).setEncodeUrlParams(true).body("a=乌海"); - String body = request.execute().body(); - Console.log(body); - -// String body2 = HttpUtil.get(url); -// Console.log(body2); + HttpRequest request = HttpRequest.get(url).body("a=乌海"); + Console.log(request.toString()); } @Test From d6125c1bf794835f2a4bda3a7667b5458b827bd7 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 23 Apr 2020 14:31:42 +0800 Subject: [PATCH 077/157] release 5.3.2 --- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index ab1bcdd4a..dba0569b7 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 279efdf02..d7b0d7987 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index f45ef3808..b70284a7d 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index a122db5c3..2cdfe81f0 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index a55dc85df..e27e282e3 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index c2d5f03cd..dd87820dd 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index b56708538..3ccfc611c 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 66ceeec21..75cc163b5 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 5ad309c87..2a828da0a 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 2312adac4..0614813c7 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 81c57a561..a455ac630 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 71e77cd9d..3001c9464 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 5e1354725..ecfcfd317 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 336a82742..9efdd70fa 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 06e7c6ff8..d357f2a8e 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 119f0ca3d..91086186e 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 03f106b4e..546bac394 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 7567050e2..084fa2bba 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 94a130f31..c496e7652 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index c25148a91..f813cc482 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool-system diff --git a/pom.xml b/pom.xml index 0378e162e..839dd8d69 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.2-SNAPSHOT + 5.3.2 hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool From 669d19eab8d50ebe0df9b0c15da84d0e3ed7e8e2 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 23 Apr 2020 15:20:05 +0800 Subject: [PATCH 078/157] prepare 5.3.3 --- CHANGELOG.md | 7 +++++++ README.md | 8 ++++---- bin/version.txt | 2 +- docs/js/version.js | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 25 files changed, 34 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fda23e977..55892c53b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ ------------------------------------------------------------------------------------------------------------- +## 5.3.3 (2020-04-23) + +### 新特性 +### Bug修复 + +------------------------------------------------------------------------------------------------------------- + ## 5.3.2 (2020-04-23) ### 新特性 diff --git a/README.md b/README.md index c7e6b2cbc..2197e4ccd 100644 --- a/README.md +++ b/README.md @@ -116,21 +116,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.3.2 + 5.3.3 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.3.2' +compile 'cn.hutool:hutool-all:5.3.3' ``` ### 非Maven项目 点击以下任一链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.3.2/) -- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.2/) +- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.3.3/) +- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.3/) > 注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index 84197c894..74664af74 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.3.2 +5.3.3 diff --git a/docs/js/version.js b/docs/js/version.js index fbb5134ec..148a5aacd 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.3.2' \ No newline at end of file +var version = '5.3.3' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index dba0569b7..9c443ad64 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index d7b0d7987..406bce953 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index b70284a7d..78c924dc0 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 2cdfe81f0..263934240 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index e27e282e3..1b455ecf3 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index dd87820dd..eabf9fc58 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 3ccfc611c..7234dad0b 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 75cc163b5..2244d0bbb 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 2a828da0a..86aeea71a 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 0614813c7..b6c77f6c4 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index a455ac630..fc686849b 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 3001c9464..fd00e96b8 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index ecfcfd317..6d12b74df 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 9efdd70fa..f6cd3c24b 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index d357f2a8e..ae6bb36d6 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 91086186e..abe2821d3 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 546bac394..63db46a14 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 084fa2bba..95f30366a 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index c496e7652..4500b4851 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index f813cc482..6da67a273 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 839dd8d69..9679f8698 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.2 + 5.3.3-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool From 2bcad6031dcfb6a6f074d99e68c8b38eeabb46e3 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 25 Apr 2020 09:31:39 +0800 Subject: [PATCH 079/157] fix slash escape bug --- CHANGELOG.md | 3 ++- .../java/cn/hutool/json/InternalJSONUtil.java | 2 +- .../src/main/java/cn/hutool/json/JSONUtil.java | 17 +++++++++-------- .../java/cn/hutool/json/JSONObjectTest.java | 9 +++++++++ .../test/java/cn/hutool/json/JSONUtilTest.java | 6 +++--- 5 files changed, 24 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55892c53b..393208b5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,11 @@ ------------------------------------------------------------------------------------------------------------- -## 5.3.3 (2020-04-23) +## 5.3.3 (2020-04-25) ### 新特性 ### Bug修复 +* 【json 】 修复JSON转字符串时中的/会被转义,修复此bug的单元测试 + String jsonStr = "{\"a\":\"
    aaa
    \"}"; + JSONObject json = new JSONObject(jsonStr); + Assert.assertEquals("
    aaa
    ", json.get("a")); + Assert.assertEquals(jsonStr, json.toString()); + } + @Test public void toBeanTest() { JSONObject subJson = JSONUtil.createObj().set("value1", "strValue1").set("value2", "234"); 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 bc02c17e5..c6ccf2e85 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -93,11 +93,11 @@ public class JSONUtilTest { map.put("user", object.toString()); JSONObject json = JSONUtil.parseObj(map); - Assert.assertEquals("{\"name\":\"123123\",\"value\":\"\\\\\",\"value2\":\"<\\/\"}", json.get("user")); - Assert.assertEquals("{\"user\":\"{\\\"name\\\":\\\"123123\\\",\\\"value\\\":\\\"\\\\\\\\\\\",\\\"value2\\\":\\\"<\\\\/\\\"}\"}", json.toString()); + Assert.assertEquals("{\"name\":\"123123\",\"value\":\"\\\\\",\"value2\":\" Date: Sat, 25 Apr 2020 18:50:48 +0800 Subject: [PATCH 080/157] fix URL --- CHANGELOG.md | 5 +- .../main/java/cn/hutool/core/img/ImgUtil.java | 60 +++++++++++--- .../java/cn/hutool/core/map/TableMap.java | 8 ++ .../java/cn/hutool/core/net/url/UrlQuery.java | 78 ++++++++++--------- .../cn/hutool/core/net/UrlBuilderTest.java | 18 +++++ .../main/java/cn/hutool/http/HttpRequest.java | 14 +++- .../hutool/http/server/SimpleServerTest.java | 4 + .../cn/hutool/http/test/HttpUtilTest.java | 9 +++ 8 files changed, 147 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 393208b5b..5a5654dac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,11 @@ ## 5.3.3 (2020-04-25) ### 新特性 +* 【core 】 ImgUtil.createImage支持背景透明(issue#851@Github) +* 【json 】 更改JSON转字符串时" implements Map, Iterable>, Ser }; } + @Override + public String toString() { + return "TableMap{" + + "keys=" + keys + + ", values=" + values + + '}'; + } + private static class Entry implements Map.Entry { private final K key; diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index 710cb9485..c84fa3f0a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -61,10 +61,10 @@ public class UrlQuery { * @param queryMap 初始化的查询键值对 */ public UrlQuery(Map queryMap) { - if(MapUtil.isNotEmpty(queryMap)) { + if (MapUtil.isNotEmpty(queryMap)) { query = new TableMap<>(queryMap.size()); addAll(queryMap); - } else{ + } else { query = new TableMap<>(MapUtil.DEFAULT_INITIAL_CAPACITY); } } @@ -88,7 +88,7 @@ public class UrlQuery { * @return this */ public UrlQuery addAll(Map queryMap) { - if(MapUtil.isNotEmpty(queryMap)) { + if (MapUtil.isNotEmpty(queryMap)) { queryMap.forEach(this::add); } return this; @@ -122,34 +122,31 @@ public class UrlQuery { char c; // 当前字符 for (i = 0; i < len; i++) { c = queryStr.charAt(i); - if (c == '=') { // 键值对的分界点 - if (null == name) { - // name可以是"" - name = queryStr.substring(pos, i); - } - pos = i + 1; - } else if (c == '&') { // 参数对的分界点 - if (null == name && pos != i) { - // 对于像&a&这类无参数值的字符串,我们将name为a的值设为"" - addParam(queryStr.substring(pos, i), StrUtil.EMPTY, charset); - } else if (name != null) { + switch (c) { + case '='://键和值的分界符 + if (null == name) { + // name可以是"" + name = queryStr.substring(pos, i); + // 开始位置从分节符后开始 + pos = i + 1; + } + // 当=不作为分界符时,按照普通字符对待 + break; + case '&'://键值对之间的分界符 addParam(name, queryStr.substring(pos, i), charset); name = null; - } - pos = i + 1; + if ("amp;".equals(queryStr.substring(i + 1, i + 5))) { + // issue#850@Github,"&"转义为"&" + i+=4; + } + // 开始位置从分节符后开始 + pos = i + 1; + break; } } // 处理结尾 - if (pos != i) { - if (name == null) { - addParam(queryStr.substring(pos, i), StrUtil.EMPTY, charset); - } else { - addParam(name, queryStr.substring(pos, i), charset); - } - } else if (name != null) { - addParam(name, StrUtil.EMPTY, charset); - } + addParam(name, queryStr.substring(pos, i), charset); return this; } @@ -158,17 +155,18 @@ public class UrlQuery { * * @return 查询的Map,只读 */ - public Map getQueryMap(){ + public Map getQueryMap() { return MapUtil.unmodifiable(this.query); } /** * 获取查询值 + * * @param key 键 * @return 值 */ - public CharSequence get(CharSequence key){ - if(MapUtil.isEmpty(this.query)){ + public CharSequence get(CharSequence key) { + if (MapUtil.isEmpty(this.query)) { return null; } return this.query.get(key); @@ -231,15 +229,25 @@ public class UrlQuery { } /** - * 将键值对加入到值为List类型的Map中 + * 将键值对加入到值为List类型的Map中,,情况如下: + *
    +	 *     1、key和value都不为null,类似于 "a=1"或者"=1",直接put
    +	 *     2、key不为null,value为null,类似于 "a=",值传""
    +	 *     3、key为null,value不为null,类似于 "1"
    +	 *     4、key和value都为null,忽略之,比如&&
    +	 * 
    * - * @param name key - * @param value value + * @param key key,为null则value作为key + * @param value value,为null且key不为null时传入"" * @param charset 编码 */ - private void addParam(String name, String value, Charset charset) { - name = URLUtil.decode(name, charset); - value = URLUtil.decode(value, charset); - this.query.put(name, value); + private void addParam(String key, String value, Charset charset) { + if (null != key) { + final String actualKey = URLUtil.decode(key, charset); + this.query.put(actualKey, StrUtil.nullToEmpty(URLUtil.decode(value, charset))); + } else if (null != value) { + // name为空,value作为name,value赋值"" + this.query.put(URLUtil.decode(value, charset), StrUtil.EMPTY); + } } } diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java index 777169541..4ff8f6b8c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java @@ -170,4 +170,22 @@ public class UrlBuilderTest { Assert.assertEquals("frag1", builder.getFragment()); } + + @Test + public void weixinUrlTest(){ + String urlStr = "https://mp.weixin.qq.com/s?" + + "__biz=MzI5NjkyNTIxMg==" + + "&mid=100000465" + + "&idx=1" + + "&sn=1044c0d19723f74f04f4c1da34eefa35" + + "&chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7"; + final UrlBuilder builder = UrlBuilder.ofHttp(urlStr, CharsetUtil.CHARSET_UTF_8); + // 原URL中的&替换为&,value中的=被编码为%3D + Assert.assertEquals("https://mp.weixin.qq.com/s?" + + "__biz=MzI5NjkyNTIxMg%3D%3D" + + "&mid=100000465&idx=1" + + "&sn=1044c0d19723f74f04f4c1da34eefa35" + + "&chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7", + builder.toString()); + } } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 7eded0776..189b53112 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -14,6 +14,7 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; @@ -159,12 +160,21 @@ public class HttpRequest extends HttpBase { private SSLSocketFactory ssf; /** - * 构造 + * 构造,URL编码默认使用UTF-8 * * @param url URL */ public HttpRequest(String url) { - setUrl(url); + this(UrlBuilder.ofHttp(url, CharsetUtil.CHARSET_UTF_8)); + } + + /** + * 构造 + * + * @param url {@link UrlBuilder} + */ + public HttpRequest(UrlBuilder url) { + this.url = url; // 给定一个默认头信息 this.header(GlobalHeaders.INSTANCE.headers); } diff --git a/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java b/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java index 2c325eec1..de61f7f70 100644 --- a/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java @@ -11,6 +11,10 @@ public class SimpleServerTest { HttpUtil.createServer(8888) // 设置默认根目录, .setRoot("d:/test") + // get数据测试,返回请求的PATH + .addAction("/get", (request, response) -> + response.write(request.getURI().toString(), ContentType.TEXT_PLAIN.toString()) + ) // 返回JSON数据测试 .addAction("/restTest", (request, response) -> response.write("{\"id\": 1, \"msg\": \"OK\"}", ContentType.JSON.toString()) diff --git a/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java index fda2003d1..7a66972c3 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java @@ -295,4 +295,13 @@ public class HttpUtilTest { String mimeType = HttpUtil.getMimeType("aaa.aaa"); Assert.assertNull(mimeType); } + + @Test + @Ignore + public void getWeixinTest(){ + // 测试特殊URL,即URL中有&情况是否请求正常 + String url = "https://mp.weixin.qq.com/s?__biz=MzI5NjkyNTIxMg==&mid=100000465&idx=1&sn=1044c0d19723f74f04f4c1da34eefa35&chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7"; + final String s = HttpUtil.get(url); + Console.log(s); + } } From 63732834acb885d493ff1627569ff52827c1a129 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 25 Apr 2020 19:23:42 +0800 Subject: [PATCH 081/157] CronPattern support L --- CHANGELOG.md | 3 ++- .../cron/pattern/parser/SimpleValueParser.java | 13 ++++++++++++- .../cn/hutool/cron/pattern/CronPatternTest.java | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a5654dac..608c7c33f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,9 +8,10 @@ ### 新特性 * 【core 】 ImgUtil.createImage支持背景透明(issue#851@Github) * 【json 】 更改JSON转字符串时" max){ this.min = max; @@ -26,6 +32,11 @@ public class SimpleValueParser implements ValueParser { @Override public int parse(String value) throws CronException { + if("L".equalsIgnoreCase(value)){ + // L表示最大值 + return max; + } + int i; try { i = Integer.parseInt(value); diff --git a/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternTest.java b/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternTest.java index e1d57107c..3785330c0 100644 --- a/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternTest.java +++ b/hutool-cron/src/test/java/cn/hutool/cron/pattern/CronPatternTest.java @@ -125,6 +125,22 @@ public class CronPatternTest { assertMatch(pattern, "2017-02-19 04:20:33"); } + @Test + public void lastTest() { + // 每月最后一天的任意时间 + CronPattern pattern = new CronPattern("* * * L * ?"); + assertMatch(pattern, "2017-07-31 04:20:00"); + assertMatch(pattern, "2017-02-28 04:20:00"); + + // 最后一个月的任意时间 + pattern = new CronPattern("* * * * L ?"); + assertMatch(pattern, "2017-12-02 04:20:00"); + + // 任意天的最后时间 + pattern = new CronPattern("L L L * * ?"); + assertMatch(pattern, "2017-12-02 23:59:59"); + } + @Test(expected = CronException.class) public void rangeYearTest() { // year的范围是1970~2099年,超出报错 From cf1f208aa02d24c7169b1ad033c1ffa74935e0de Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 27 Apr 2020 13:45:54 +0800 Subject: [PATCH 082/157] add PinyinUtil --- CHANGELOG.md | 2 + .../java/cn/hutool/core/lang/SimpleCache.java | 8 +- hutool-extra/pom.xml | 6 + .../java/cn/hutool/extra/ftp/AbstractFtp.java | 40 +++-- .../main/java/cn/hutool/extra/ftp/Ftp.java | 169 ++++++++++-------- .../java/cn/hutool/extra/ftp/FtpConfig.java | 134 ++++++++++++++ .../cn/hutool/extra/pinyin/PinyinUtil.java | 73 ++++++++ .../cn/hutool/extra/pinyin/package-info.java | 7 + .../cn/hutool/extra/ssh/JschSessionPool.java | 54 ++---- .../java/cn/hutool/extra/ssh/JschUtil.java | 46 ++++- .../main/java/cn/hutool/extra/ssh/Sftp.java | 53 ++++-- .../hutool/extra/pinyin/PinyinUtilTest.java | 19 ++ 12 files changed, 467 insertions(+), 144 deletions(-) create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/ftp/FtpConfig.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/pinyin/package-info.java create mode 100644 hutool-extra/src/test/java/cn/hutool/extra/pinyin/PinyinUtilTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 608c7c33f..8968e2af4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ * 【core 】 ImgUtil.createImage支持背景透明(issue#851@Github) * 【json 】 更改JSON转字符串时" 值类型 * @author Looly */ -public class SimpleCache implements Serializable { +public class SimpleCache implements Iterable>, Serializable { private static final long serialVersionUID = 1L; /** @@ -148,4 +149,9 @@ public class SimpleCache implements Serializable { lock.unlockWrite(stamp); } } + + @Override + public Iterator> iterator() { + return this.cache.entrySet().iterator(); + } } diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index fd00e96b8..d368455aa 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -214,6 +214,12 @@ ${spring-boot.version} true + + io.github.biezhi + TinyPinyin + 2.0.3.RELEASE + true + org.springframework.boot spring-boot-starter-test diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java index ff605b0a2..6b8397557 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java @@ -1,15 +1,15 @@ package cn.hutool.extra.ftp; -import java.io.Closeable; -import java.io.File; -import java.nio.charset.Charset; -import java.util.List; - import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; +import java.io.Closeable; +import java.io.File; +import java.nio.charset.Charset; +import java.util.List; + /** * 抽象FTP类,用于定义通用的FTP方法 * @@ -19,14 +19,18 @@ import cn.hutool.core.util.StrUtil; public abstract class AbstractFtp implements Closeable { public static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8 ; - - protected String host; - protected int port; - - protected String user; - protected String password; - - protected Charset charset; + + protected FtpConfig ftpConfig; + + /** + * 构造 + * + * @param config FTP配置 + * @since 5.3.3 + */ + protected AbstractFtp(FtpConfig config){ + this.ftpConfig = config; + } /** * 如果连接超时的话,重新进行连接 @@ -119,12 +123,12 @@ public abstract class AbstractFtp implements Closeable { //首位为空,表示以/开头 this.cd(StrUtil.SLASH); } - for (int i = 0; i < dirs.length; i++) { - if (StrUtil.isNotEmpty(dirs[i])) { - if (false == cd(dirs[i])) { + for (String s : dirs) { + if (StrUtil.isNotEmpty(s)) { + if (false == cd(s)) { //目录不存在时创建 - mkdir(dirs[i]); - cd(dirs[i]); + mkdir(s); + cd(s); } } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index 9e3bb1be6..e1ec874e6 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -13,6 +13,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.SocketException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; @@ -20,23 +21,27 @@ import java.util.List; /** * FTP客户端封装
    * 此客户端基于Apache-Commons-Net - * + * * @author looly * @since 4.1.8 */ public class Ftp extends AbstractFtp { - /** 默认端口 */ + /** + * 默认端口 + */ public static final int DEFAULT_PORT = 21; private FTPClient client; private FtpMode mode; - /** 执行完操作是否返回当前目录 */ + /** + * 执行完操作是否返回当前目录 + */ private boolean backToPwd; /** * 构造,匿名登录 - * + * * @param host 域名或IP */ public Ftp(String host) { @@ -45,7 +50,7 @@ public class Ftp extends AbstractFtp { /** * 构造,匿名登录 - * + * * @param host 域名或IP * @param port 端口 */ @@ -55,10 +60,10 @@ public class Ftp extends AbstractFtp { /** * 构造 - * - * @param host 域名或IP - * @param port 端口 - * @param user 用户名 + * + * @param host 域名或IP + * @param port 端口 + * @param user 用户名 * @param password 密码 */ public Ftp(String host, int port, String user, String password) { @@ -67,12 +72,12 @@ public class Ftp extends AbstractFtp { /** * 构造 - * - * @param host 域名或IP - * @param port 端口 - * @param user 用户名 + * + * @param host 域名或IP + * @param port 端口 + * @param user 用户名 * @param password 密码 - * @param charset 编码 + * @param charset 编码 */ public Ftp(String host, int port, String user, String password, Charset charset) { this(host, port, user, password, charset, null); @@ -81,19 +86,25 @@ public class Ftp extends AbstractFtp { /** * 构造 * - * @param host 域名或IP - * @param port 端口 - * @param user 用户名 + * @param host 域名或IP + * @param port 端口 + * @param user 用户名 * @param password 密码 - * @param charset 编码 - * @param mode 模式 + * @param charset 编码 + * @param mode 模式 */ public Ftp(String host, int port, String user, String password, Charset charset, FtpMode mode) { - this.host = host; - this.port = port; - this.user = user; - this.password = password; - this.charset = charset; + this(new FtpConfig(host, port, user, password, charset), mode); + } + + /** + * 构造 + * + * @param config FTP配置 + * @param mode 模式 + */ + public Ftp(FtpConfig config, FtpMode mode) { + super(config); this.mode = mode; this.init(); } @@ -104,15 +115,15 @@ public class Ftp extends AbstractFtp { * @return this */ public Ftp init() { - return this.init(this.host, this.port, this.user, this.password, this.mode); + return this.init(this.ftpConfig, this.mode); } /** * 初始化连接 * - * @param host 域名或IP - * @param port 端口 - * @param user 用户名 + * @param host 域名或IP + * @param port 端口 + * @param user 用户名 * @param password 密码 * @return this */ @@ -122,22 +133,39 @@ public class Ftp extends AbstractFtp { /** * 初始化连接 - * - * @param host 域名或IP - * @param port 端口 - * @param user 用户名 + * + * @param host 域名或IP + * @param port 端口 + * @param user 用户名 * @param password 密码 - * @param mode 模式 + * @param mode 模式 * @return this */ public Ftp init(String host, int port, String user, String password, FtpMode mode) { + return init(new FtpConfig(host, port, user, password, this.ftpConfig.getCharset()), mode); + } + + /** + * 初始化连接 + * + * @param config FTP配置 + * @param mode 模式 + * @return this + */ + public Ftp init(FtpConfig config, FtpMode mode) { final FTPClient client = new FTPClient(); - client.setControlEncoding(this.charset.toString()); + client.setControlEncoding(config.getCharset().toString()); + client.setConnectTimeout((int) config.getConnectionTimeout()); + try { + client.setSoTimeout((int)config.getSoTimeout()); + } catch (SocketException e) { + //ignore + } try { // 连接ftp服务器 - client.connect(host, port); + client.connect(config.getHost(), config.getPort()); // 登录ftp服务器 - client.login(user, password); + client.login(config.getUser(), config.getPassword()); } catch (IOException e) { throw new FtpException(e); } @@ -148,7 +176,7 @@ public class Ftp extends AbstractFtp { } catch (IOException e) { // ignore } - throw new FtpException("Login failed for user [{}], reply code is: [{}]", user, replyCode); + throw new FtpException("Login failed for user [{}], reply code is: [{}]", config.getUser(), replyCode); } this.client = client; if (mode != null) { @@ -159,7 +187,7 @@ public class Ftp extends AbstractFtp { /** * 设置FTP连接模式,可选主动和被动模式 - * + * * @param mode 模式枚举 * @return this * @since 4.1.19 @@ -167,19 +195,19 @@ public class Ftp extends AbstractFtp { public Ftp setMode(FtpMode mode) { this.mode = mode; switch (mode) { - case Active: - this.client.enterLocalActiveMode(); - break; - case Passive: - this.client.enterLocalPassiveMode(); - break; + case Active: + this.client.enterLocalActiveMode(); + break; + case Passive: + this.client.enterLocalPassiveMode(); + break; } return this; } /** * 设置执行完操作是否返回当前目录 - * + * * @param backToPwd 执行完操作是否返回当前目录 * @return this * @since 4.6.0 @@ -191,7 +219,7 @@ public class Ftp extends AbstractFtp { /** * 如果连接超时的话,重新进行连接 经测试,当连接超时时,client.isConnected()仍然返回ture,无法判断是否连接超时 因此,通过发送pwd命令的方式,检查连接是否超时 - * + * * @return this */ @Override @@ -211,7 +239,7 @@ public class Ftp extends AbstractFtp { /** * 改变目录 - * + * * @param directory 目录 * @return 是否成功 */ @@ -230,7 +258,7 @@ public class Ftp extends AbstractFtp { /** * 远程当前目录 - * + * * @return 远程当前目录 * @since 4.1.14 */ @@ -256,7 +284,7 @@ public class Ftp extends AbstractFtp { /** * 遍历某个目录下所有文件和目录,不会递归遍历 - * + * * @param path 目录 * @return 文件或目录列表 */ @@ -291,7 +319,7 @@ public class Ftp extends AbstractFtp { /** * 判断ftp服务器文件是否存在 - * + * * @param path 文件路径 * @return 是否存在 */ @@ -356,15 +384,15 @@ public class Ftp extends AbstractFtp { /** * 上传文件到指定目录,可选: - * + * *
     	 * 1. path为null或""上传到当前路径
     	 * 2. path为相对路径则相对于当前路径的子路径
     	 * 3. path为绝对路径则上传到此路径
     	 * 
    - * + * * @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径 - * @param file 文件 + * @param file 文件 * @return 是否上传成功 */ @Override @@ -375,15 +403,15 @@ public class Ftp extends AbstractFtp { /** * 上传文件到指定目录,可选: - * + * *
     	 * 1. path为null或""上传到当前路径
     	 * 2. path为相对路径则相对于当前路径的子路径
     	 * 3. path为绝对路径则上传到此路径
     	 * 
    - * - * @param file 文件 - * @param path 服务端路径,可以为{@code null} 或者相对路径或绝对路径 + * + * @param file 文件 + * @param path 服务端路径,可以为{@code null} 或者相对路径或绝对路径 * @param fileName 自定义在服务端保存的文件名 * @return 是否上传成功 */ @@ -397,16 +425,15 @@ public class Ftp extends AbstractFtp { /** * 上传文件到指定目录,可选: - * + * *
     	 * 1. path为null或""上传到当前路径
     	 * 2. path为相对路径则相对于当前路径的子路径
     	 * 3. path为绝对路径则上传到此路径
     	 * 
    - * - * - * @param path 服务端路径,可以为{@code null} 或者相对路径或绝对路径 - * @param fileName 文件名 + * + * @param path 服务端路径,可以为{@code null} 或者相对路径或绝对路径 + * @param fileName 文件名 * @param fileStream 文件流 * @return 是否上传成功 */ @@ -443,8 +470,8 @@ public class Ftp extends AbstractFtp { /** * 下载文件 - * - * @param path 文件路径 + * + * @param path 文件路径 * @param outFile 输出文件或目录 */ @Override @@ -456,10 +483,10 @@ public class Ftp extends AbstractFtp { /** * 下载文件 - * - * @param path 文件路径 + * + * @param path 文件路径 * @param fileName 文件名 - * @param outFile 输出文件或目录 + * @param outFile 输出文件或目录 */ public void download(String path, String fileName, File outFile) { if (outFile.isDirectory()) { @@ -477,10 +504,10 @@ public class Ftp extends AbstractFtp { /** * 下载文件到输出流 - * - * @param path 文件路径 + * + * @param path 文件路径 * @param fileName 文件名 - * @param out 输出位置 + * @param out 输出位置 */ public void download(String path, String fileName, OutputStream out) { String pwd = null; @@ -503,7 +530,7 @@ public class Ftp extends AbstractFtp { /** * 获取FTPClient客户端对象 - * + * * @return {@link FTPClient} */ public FTPClient getClient() { diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/FtpConfig.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/FtpConfig.java new file mode 100644 index 000000000..f87d494d0 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/FtpConfig.java @@ -0,0 +1,134 @@ +package cn.hutool.extra.ftp; + +import java.io.Serializable; +import java.nio.charset.Charset; + +/** + * FTP配置项,提供FTP各种参数信息 + * + * @author looly + */ +public class FtpConfig implements Serializable { + private static final long serialVersionUID = 1L; + + public static FtpConfig create(){ + return new FtpConfig(); + } + + /** + * 主机 + */ + private String host; + /** + * 端口 + */ + private int port; + /** + * 用户名 + */ + private String user; + /** + * 密码 + */ + private String password; + /** + * 编码 + */ + private Charset charset; + + /** + * 连接超时时长,单位毫秒 + */ + private long connectionTimeout; + + /** + * Socket连接超时时长,单位毫秒 + */ + private long soTimeout; + + /** + * 构造 + */ + public FtpConfig() { + } + + /** + * 构造 + * + * @param host 主机 + * @param port 端口 + * @param user 用户名 + * @param password 密码 + * @param charset 编码 + */ + public FtpConfig(String host, int port, String user, String password, Charset charset) { + this.host = host; + this.port = port; + this.user = user; + this.password = password; + this.charset = charset; + } + + public String getHost() { + return host; + } + + public FtpConfig setHost(String host) { + this.host = host; + return this; + } + + public int getPort() { + return port; + } + + public FtpConfig setPort(int port) { + this.port = port; + return this; + } + + public String getUser() { + return user; + } + + public FtpConfig setUser(String user) { + this.user = user; + return this; + } + + public String getPassword() { + return password; + } + + public FtpConfig setPassword(String password) { + this.password = password; + return this; + } + + public Charset getCharset() { + return charset; + } + + public FtpConfig setCharset(Charset charset) { + this.charset = charset; + return this; + } + + public long getConnectionTimeout() { + return connectionTimeout; + } + + public FtpConfig setConnectionTimeout(long connectionTimeout) { + this.connectionTimeout = connectionTimeout; + return this; + } + + public long getSoTimeout() { + return soTimeout; + } + + public FtpConfig setSoTimeout(long soTimeout) { + this.soTimeout = soTimeout; + return this; + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java new file mode 100644 index 000000000..1f565688a --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java @@ -0,0 +1,73 @@ +package cn.hutool.extra.pinyin; + +import com.github.promeg.pinyinhelper.Pinyin; + +/** + * 拼音工具类,封装了TinyPinyin + * + *

    + * TinyPinyin(https://github.com/promeG/TinyPinyin)提供者未提交Maven中央库,
    + * 因此使用 + * https://github.com/biezhi/TinyPinyin打包的版本 + *

    + * + *

    + * 引入: + *

    + * <dependency>
    + *     <groupId>io.github.biezhi</groupId>
    + *     <artifactId>TinyPinyin</artifactId>
    + *     <version>2.0.3.RELEASE</version>
    + * </dependency>
    + * 
    + *

    + * + * @author looly + */ +public class PinyinUtil { + + /** + * 自定义拼音全局配置,例如加入自定义字典等 + * + * @param config 配置,通过Pinyin.newConfig().with(dict)添加字典 + */ + public static void init(Pinyin.Config config) { + Pinyin.init(config); + } + + /** + * 如果c为汉字,则返回大写拼音;如果c不是汉字,则返回String.valueOf(c) + * + * @param c 任意字符,汉族返回拼音,非汉字原样返回 + * @param isToUpperCase 是否转换为大写 + * @return 汉族返回拼音,非汉字原样返回 + */ + public static String toPinyin(char c, boolean isToUpperCase) { + final String pinyin = Pinyin.toPinyin(c); + return isToUpperCase ? pinyin : pinyin.toLowerCase(); + } + + /** + * 将输入字符串转为拼音,每个字之间的拼音使用空格分隔 + * + * @param str 任意字符,汉族返回拼音,非汉字原样返回 + * @param isToUpperCase 是否转换为大写 + * @return 汉族返回拼音,非汉字原样返回 + */ + public static String toPinyin(String str, boolean isToUpperCase) { + return toPinyin(str, " ", isToUpperCase); + } + + /** + * 将输入字符串转为拼音,以字符为单位插入分隔符 + * + * @param str 任意字符,汉族返回拼音,非汉字原样返回 + * @param separator 每个字拼音之间的分隔符 + * @param isToUpperCase 是否转换为大写 + * @return 汉族返回拼音,非汉字原样返回 + */ + public static String toPinyin(String str, String separator, boolean isToUpperCase) { + final String pinyin = Pinyin.toPinyin(str, separator); + return isToUpperCase ? pinyin : pinyin.toLowerCase(); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/package-info.java new file mode 100644 index 000000000..1e1bb8603 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/package-info.java @@ -0,0 +1,7 @@ +/** + * 拼音工具封装,基于TinyPinyin + * + * @author looly + * + */ +package cn.hutool.extra.pinyin; \ No newline at end of file diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschSessionPool.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschSessionPool.java index 3466edff0..aa2703da0 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschSessionPool.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/JschSessionPool.java @@ -1,13 +1,12 @@ package cn.hutool.extra.ssh; +import cn.hutool.core.lang.SimpleCache; import cn.hutool.core.util.StrUtil; import com.jcraft.jsch.Session; -import java.util.Collection; +import java.util.HashMap; import java.util.Iterator; -import java.util.Map; import java.util.Map.Entry; -import java.util.concurrent.ConcurrentHashMap; /** * Jsch会话池 @@ -20,11 +19,7 @@ public enum JschSessionPool { /** * SSH会话池,key:host,value:Session对象 */ - private final Map sessionPool = new ConcurrentHashMap<>(); - /** - * 锁 - */ - private static final Object lock = new Object(); + private final SimpleCache cache = new SimpleCache<>(new HashMap<>()); /** * 获取Session,不存在返回null @@ -33,7 +28,7 @@ public enum JschSessionPool { * @return Session */ public Session get(String key) { - return sessionPool.get(key); + return cache.get(key); } /** @@ -47,17 +42,7 @@ public enum JschSessionPool { */ public Session getSession(String sshHost, int sshPort, String sshUser, String sshPass) { final String key = StrUtil.format("{}@{}:{}", sshUser, sshHost, sshPort); - Session session = get(key); - if (null == session || false == session.isConnected()) { - synchronized (lock) { - session = get(key); - if (null == session || false == session.isConnected()) { - session = JschUtil.openSession(sshHost, sshPort, sshUser, sshPass); - put(key, session); - } - } - } - return session; + return this.cache.get(key, ()-> JschUtil.openSession(sshHost, sshPort, sshUser, sshPass)); } /** @@ -72,17 +57,7 @@ public enum JschSessionPool { */ public Session getSession(String sshHost, int sshPort, String sshUser, String prvkey, byte[] passphrase) { final String key = StrUtil.format("{}@{}:{}", sshUser, sshHost, sshPort); - Session session = get(key); - if (null == session || false == session.isConnected()) { - synchronized (lock) { - session = get(key); - if (null == session || false == session.isConnected()) { - session = JschUtil.openSession(sshHost, sshPort, sshUser, prvkey, passphrase); - put(key, session); - } - } - } - return session; + return this.cache.get(key, ()->JschUtil.openSession(sshHost, sshPort, sshUser, prvkey, passphrase)); } /** @@ -92,7 +67,7 @@ public enum JschSessionPool { * @param session Session */ public void put(String key, Session session) { - this.sessionPool.put(key, session); + this.cache.put(key, session); } /** @@ -101,11 +76,11 @@ public enum JschSessionPool { * @param key 主机,格式为user@host:port */ public void close(String key) { - Session session = sessionPool.get(key); + Session session = get(key); if (session != null && session.isConnected()) { session.disconnect(); } - sessionPool.remove(key); + this.cache.remove(key); } /** @@ -116,7 +91,7 @@ public enum JschSessionPool { */ public void remove(Session session) { if (null != session) { - final Iterator> iterator = this.sessionPool.entrySet().iterator(); + final Iterator> iterator = this.cache.iterator(); Entry entry; while (iterator.hasNext()) { entry = iterator.next(); @@ -132,12 +107,13 @@ public enum JschSessionPool { * 关闭所有SSH连接会话 */ public void closeAll() { - Collection sessions = sessionPool.values(); - for (Session session : sessions) { - if (session.isConnected()) { + Session session; + for (Entry entry : this.cache) { + session = entry.getValue(); + if (session != null && session.isConnected()) { session.disconnect(); } } - sessionPool.clear(); + cache.clear(); } } 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 2a9baaba2..62211cab1 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 @@ -85,9 +85,24 @@ public class JschUtil { * @return SSH会话 */ public static Session openSession(String sshHost, int sshPort, String sshUser, String sshPass) { + return openSession(sshHost, sshPort, sshUser, sshPass, 0); + } + + /** + * 打开一个新的SSH会话 + * + * @param sshHost 主机 + * @param sshPort 端口 + * @param sshUser 用户名 + * @param sshPass 密码 + * @param timeout Socket连接超时时长,单位毫秒 + * @return SSH会话 + * @since 5.3.3 + */ + public static Session openSession(String sshHost, int sshPort, String sshUser, String sshPass, int timeout) { final Session session = createSession(sshHost, sshPort, sshUser, sshPass); try { - session.connect(); + session.connect(timeout); } catch (JSchException e) { throw new JschRuntimeException(e); } @@ -257,7 +272,19 @@ public class JschUtil { * @since 4.0.3 */ public static ChannelSftp openSftp(Session session) { - return (ChannelSftp) openChannel(session, ChannelType.SFTP); + return openSftp(session, 0); + } + + /** + * 打开SFTP连接 + * + * @param session Session会话 + * @param timeout 连接超时时长,单位毫秒 + * @return {@link ChannelSftp} + * @since 5.3.3 + */ + public static ChannelSftp openSftp(Session session, int timeout) { + return (ChannelSftp) openChannel(session, ChannelType.SFTP, timeout); } /** @@ -305,9 +332,22 @@ public class JschUtil { * @since 4.5.2 */ public static Channel openChannel(Session session, ChannelType channelType) { + return openChannel(session, channelType, 0); + } + + /** + * 打开Channel连接 + * + * @param session Session会话 + * @param channelType 通道类型,可以是shell或sftp等,见{@link ChannelType} + * @param timeout 连接超时时长,单位毫秒 + * @return {@link Channel} + * @since 5.3.3 + */ + public static Channel openChannel(Session session, ChannelType channelType, int timeout) { final Channel channel = createChannel(session, channelType); try { - channel.connect(); + channel.connect(Math.max(timeout, 0)); } catch (JSchException e) { throw new JschRuntimeException(e); } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java index c16cfeb21..d63ea63f9 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java @@ -4,6 +4,7 @@ import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Filter; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.ftp.AbstractFtp; +import cn.hutool.extra.ftp.FtpConfig; import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.ChannelSftp.LsEntry; import com.jcraft.jsch.ChannelSftp.LsEntrySelector; @@ -59,7 +60,18 @@ public class Sftp extends AbstractFtp { * @since 4.1.14 */ public Sftp(String sshHost, int sshPort, String sshUser, String sshPass, Charset charset) { - init(sshHost, sshPort, sshUser, sshPass, charset); + this(new FtpConfig(sshHost, sshPort, sshUser, sshPass, charset)); + } + + /** + * 构造 + * + * @param config FTP配置 + * @since 5.3.3 + */ + public Sftp(FtpConfig config) { + super(config); + init(config); } /** @@ -79,6 +91,7 @@ public class Sftp extends AbstractFtp { * @since 4.1.14 */ public Sftp(Session session, Charset charset) { + super(FtpConfig.create().setCharset(charset)); init(session, charset); } @@ -89,6 +102,7 @@ public class Sftp extends AbstractFtp { * @param charset 编码 */ public Sftp(ChannelSftp channel, Charset charset) { + super(FtpConfig.create().setCharset(charset)); init(channel, charset); } // ---------------------------------------------------------------------------------------- Constructor end @@ -103,13 +117,28 @@ public class Sftp extends AbstractFtp { * @param charset 编码 */ public void init(String sshHost, int sshPort, String sshUser, String sshPass, Charset charset) { - this.host = sshHost; - this.port = sshPort; - this.user = sshUser; - this.password = sshPass; init(JschUtil.getSession(sshHost, sshPort, sshUser, sshPass), charset); } + /** + * 初始化 + * + * @since 5.3.3 + */ + public void init() { + init(this.ftpConfig); + } + + /** + * 初始化 + * + * @param config FTP配置 + * @since 5.3.3 + */ + public void init(FtpConfig config) { + init(config.getHost(), config.getPort(), config.getUser(), config.getPassword(), config.getCharset()); + } + /** * 初始化 * @@ -118,7 +147,7 @@ public class Sftp extends AbstractFtp { */ public void init(Session session, Charset charset) { this.session = session; - init(JschUtil.openSftp(session), charset); + init(JschUtil.openSftp(session, (int)this.ftpConfig.getConnectionTimeout()), charset); } /** @@ -128,7 +157,7 @@ public class Sftp extends AbstractFtp { * @param charset 编码 */ public void init(ChannelSftp channel, Charset charset) { - this.charset = charset; + this.ftpConfig.setCharset(charset); try { channel.setFilenameEncoding(charset.toString()); } catch (SftpException e) { @@ -139,8 +168,8 @@ public class Sftp extends AbstractFtp { @Override public Sftp reconnectIfTimeout() { - if (false == this.cd("/") && StrUtil.isNotBlank(this.host)) { - init(this.host, this.port, this.user, this.password, this.charset); + if (false == this.cd("/") && StrUtil.isNotBlank(this.ftpConfig.getHost())) { + init(this.ftpConfig); } return this; } @@ -414,9 +443,9 @@ public class Sftp extends AbstractFtp { @Override public String toString() { return "Sftp{" + - "host='" + host + '\'' + - ", port=" + port + - ", user='" + user + '\'' + + "host='" + this.ftpConfig.getHost() + '\'' + + ", port=" + this.ftpConfig.getPort() + + ", user='" + this.ftpConfig.getUser() + '\'' + '}'; } diff --git a/hutool-extra/src/test/java/cn/hutool/extra/pinyin/PinyinUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/pinyin/PinyinUtilTest.java new file mode 100644 index 000000000..167f3e43b --- /dev/null +++ b/hutool-extra/src/test/java/cn/hutool/extra/pinyin/PinyinUtilTest.java @@ -0,0 +1,19 @@ +package cn.hutool.extra.pinyin; + +import org.junit.Assert; +import org.junit.Test; + +public class PinyinUtilTest { + + @Test + public void toPinyinTest(){ + final String pinyin = PinyinUtil.toPinyin("你好", false); + Assert.assertEquals("ni hao", pinyin); + } + + @Test + public void toPinyinUpperCaseTest(){ + final String pinyin = PinyinUtil.toPinyin("你好怡", true); + Assert.assertEquals("NI HAO YI", pinyin); + } +} From a0a3e4905fa1d7450e589e8eb08da48651e33530 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 27 Apr 2020 23:38:16 +0800 Subject: [PATCH 083/157] add test --- .../src/test/java/cn/hutool/http/test/HttpUtilTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java index 7a66972c3..d2bef0174 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java @@ -125,6 +125,14 @@ public class HttpUtilTest { Assert.assertEquals("?#@!$%^&=dsssss555555", map.get("c").get(0)); } + @Test + public void decodeParamMapTest() { + // 参数值存在分界标记等号时 + Map paramMap = HttpUtil.decodeParamMap("https://www.xxx.com/api.action?aa=123&f_token=NzBkMjQxNDM1MDVlMDliZTk1OTU3ZDI1OTI0NTBiOWQ=", CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals("123",paramMap.get("aa")); + Assert.assertEquals("NzBkMjQxNDM1MDVlMDliZTk1OTU3ZDI1OTI0NTBiOWQ=",paramMap.get("f_token")); + } + @Test public void toParamsTest() { String paramsStr = "uuuu=0&a=b&c=3Ddsssss555555"; From 000b68c0c2b65e30bfe5e17332ce3d941f30729d Mon Sep 17 00:00:00 2001 From: wb-ycl473317 Date: Tue, 28 Apr 2020 10:39:48 +0800 Subject: [PATCH 084/157] BitMapBloomFilter update --- .../src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java index 589e2bfbb..a4cc97d67 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java @@ -26,7 +26,7 @@ public class BitMapBloomFilter implements BloomFilter{ * @param m M值决定BitMap的大小 */ public BitMapBloomFilter(int m) { - int mNum =NumberUtil.div(String.valueOf(m), String.valueOf(5)).intValue(); + long mNum =NumberUtil.div(String.valueOf(m), String.valueOf(5)).longValue(); long size = mNum * 1024 * 1024 * 8; filters = new BloomFilter[]{ From c3cc8381abcedf3c8ea3c9f53e90a0998bf4f5ac Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 29 Apr 2020 16:20:21 +0800 Subject: [PATCH 085/157] add methods --- CHANGELOG.md | 3 +- .../cn/hutool/extra/spring/SpringUtil.java | 55 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8968e2af4..970001c6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -## 5.3.3 (2020-04-25) +## 5.3.3 (2020-04-29) ### 新特性 * 【core 】 ImgUtil.createImage支持背景透明(issue#851@Github) @@ -11,6 +11,7 @@ * 【cron 】 表达式的所有段支持L关键字(issue#849@Github) * 【extra 】 增加PinyinUtil,封装TinyPinyin * 【extra 】 Ftp和Sftp增加FtpConfig,提供超时等更多可选参数 +* 【extra 】 SpringUtil增加getActiveProfiles、getBeansOfType、getBeanNamesForType方法(issue#I1FXF3@Gitee) ### Bug修复 * 【core 】 修复URLBuilder中请求参数有`&`导致的问题(issue#850@Github) diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index 4d67ce0a5..b7372342b 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -1,9 +1,12 @@ package cn.hutool.extra.spring; +import cn.hutool.core.util.ArrayUtil; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; +import java.util.Map; + /** * Spring(Spring boot)工具封装,包括: * @@ -70,6 +73,58 @@ public class SpringUtil implements ApplicationContextAware { return applicationContext.getBean(name, clazz); } + /** + * 获取指定类型对应的所有Bean,包括子类 + * + * @param Bean类型 + * @param type 类、接口,null表示获取所有bean + * @return 类型对应的bean,key是bean注册的name,value是Bean + * @since 5.3.3 + */ + public static Map getBeansOfType(Class type){ + return applicationContext.getBeansOfType(type); + } + + /** + * 获取指定类型对应的Bean名称,包括子类 + * @param type 类、接口,null表示获取所有bean名称 + * @return bean名称 + * @since 5.3.3 + */ + public static String[] getBeanNamesForType(Class type){ + return applicationContext.getBeanNamesForType(type); + } + + /** + * 获取配置文件配置项的值 + * + * @param key 配置项key + * @since 5.3.3 + */ + public static String getProperty(String key) { + return applicationContext.getEnvironment().getProperty(key); + } + + /** + * 获取当前的环境配置,无配置返回null + * + * @return 当前的环境配置 + * @since 5.3.3 + */ + public static String[] getActiveProfiles(){ + return applicationContext.getEnvironment().getActiveProfiles(); + } + + /** + * 获取当前的环境配置,当有多个环境配置时,只获取第一个 + * + * @return 当前的环境配置 + * @since 5.3.3 + */ + public static String getActiveProfile(){ + final String[] activeProfiles = getActiveProfiles(); + return ArrayUtil.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; + } } From ea96eecd4b80aa3466911f19375d221cd153711c Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 30 Apr 2020 09:11:22 +0800 Subject: [PATCH 086/157] add opt --- CHANGELOG.md | 2 + .../hutool/bloomfilter/BitMapBloomFilter.java | 25 ++-- .../main/java/cn/hutool/core/io/IoUtil.java | 13 ++ .../java/cn/hutool/crypto/digest/HMac.java | 28 +++- .../crypto/digest/mac/BCHMacEngine.java | 18 ++- .../crypto/digest/mac/DefaultHMacEngine.java | 28 ++-- .../hutool/crypto/digest/mac/MacEngine.java | 18 ++- .../crypto/digest/mac/MacEngineFactory.java | 6 +- .../cn/hutool/crypto/digest/opt/HOTP.java | 124 ++++++++++++++++++ .../crypto/digest/opt/package-info.java | 14 ++ 10 files changed, 241 insertions(+), 35 deletions(-) create mode 100644 hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java create mode 100644 hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/package-info.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 970001c6b..fd91bdd0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ * 【extra 】 增加PinyinUtil,封装TinyPinyin * 【extra 】 Ftp和Sftp增加FtpConfig,提供超时等更多可选参数 * 【extra 】 SpringUtil增加getActiveProfiles、getBeansOfType、getBeanNamesForType方法(issue#I1FXF3@Gitee) +* 【bloomFilter】 避免布隆过滤器数字溢出(pr#119@Gitee) +* 【core 】 增加IoUtil.writeObj(issue#I1FZIE) ### Bug修复 * 【core 】 修复URLBuilder中请求参数有`&`导致的问题(issue#850@Github) diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java index a4cc97d67..b1da492d6 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BitMapBloomFilter.java @@ -13,35 +13,36 @@ import cn.hutool.core.util.NumberUtil; * 2.散列hash映射到数组的bit位置
    * 3.验证
    * 此实现方式可以指定Hash算法 - * + * * @author Ansj */ -public class BitMapBloomFilter implements BloomFilter{ +public class BitMapBloomFilter implements BloomFilter { private static final long serialVersionUID = 1L; private BloomFilter[] filters; /** * 构造,使用默认的5个过滤器 + * * @param m M值决定BitMap的大小 */ public BitMapBloomFilter(int m) { - long mNum =NumberUtil.div(String.valueOf(m), String.valueOf(5)).longValue(); + long mNum = NumberUtil.div(String.valueOf(m), String.valueOf(5)).longValue(); long size = mNum * 1024 * 1024 * 8; - + filters = new BloomFilter[]{ - new DefaultFilter(size), - new ELFFilter(size), - new JSFilter(size), - new PJWFilter(size), - new SDBMFilter(size) + new DefaultFilter(size), + new ELFFilter(size), + new JSFilter(size), + new PJWFilter(size), + new SDBMFilter(size) }; } /** * 使用自定的多个过滤器建立BloomFilter - * - * @param m M值决定BitMap的大小 + * + * @param m M值决定BitMap的大小 * @param filters Bloom过滤器列表 */ public BitMapBloomFilter(int m, BloomFilter... filters) { @@ -51,6 +52,7 @@ public class BitMapBloomFilter implements BloomFilter{ /** * 增加字符串到Filter映射中 + * * @param str 字符串 */ @Override @@ -64,6 +66,7 @@ public class BitMapBloomFilter implements BloomFilter{ /** * 是否可能包含此字符串,此处存在误判 + * * @param str 字符串 * @return 是否存在 */ diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java index 0966d544a..cda8b5efe 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java @@ -984,6 +984,19 @@ public class IoUtil { } } + /** + * 将多部分内容写到流中 + * + * @param out 输出流 + * @param isCloseOut 写入完毕是否关闭输出流 + * @param obj 写入的对象内容 + * @throws IORuntimeException IO异常 + * @since 5.3.3 + */ + public static void writeObj(OutputStream out, boolean isCloseOut, Serializable obj) throws IORuntimeException { + writeObjects(out, isCloseOut, obj); + } + /** * 将多部分内容写到流中 * diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java index 4daa0f7ab..363c5057a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java @@ -9,12 +9,12 @@ import cn.hutool.crypto.CryptoException; import cn.hutool.crypto.digest.mac.MacEngine; import cn.hutool.crypto.digest.mac.MacEngineFactory; -import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import java.io.Serializable; +import java.security.Key; /** * HMAC摘要算法
    @@ -37,7 +37,7 @@ public class HMac implements Serializable { * @param algorithm 算法 {@link HmacAlgorithm} */ public HMac(HmacAlgorithm algorithm) { - this(algorithm, (SecretKey)null); + this(algorithm, (Key)null); } /** @@ -54,7 +54,7 @@ public class HMac implements Serializable { * @param algorithm 算法 {@link HmacAlgorithm} * @param key 密钥 */ - public HMac(HmacAlgorithm algorithm, SecretKey key) { + public HMac(HmacAlgorithm algorithm, Key key) { this(algorithm.getValue(), key); } @@ -74,7 +74,7 @@ public class HMac implements Serializable { * @param key 密钥 * @since 4.5.13 */ - public HMac(String algorithm, SecretKey key) { + public HMac(String algorithm, Key key) { this(MacEngineFactory.createEngine(algorithm, key)); } @@ -223,5 +223,23 @@ public class HMac implements Serializable { public String digestHex(InputStream data, int bufferLength) { return HexUtil.encodeHexStr(digest(data, bufferLength)); } - + + /** + * 获取MAC算法块长度 + * @return MAC算法块长度 + * @since 5.3.3 + */ + public int getMacLength(){ + return this.engine.getMacLength(); + } + + /** + * 获取算法 + * + * @return 算法 + * @since 5.3.3 + */ + public String getAlgorithm() { + return this.engine.getAlgorithm(); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCHMacEngine.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCHMacEngine.java index e0db7ee49..9f61e5c0a 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCHMacEngine.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/BCHMacEngine.java @@ -1,16 +1,15 @@ package cn.hutool.crypto.digest.mac; -import java.io.IOException; -import java.io.InputStream; - +import cn.hutool.core.io.IoUtil; +import cn.hutool.crypto.CryptoException; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.Mac; import org.bouncycastle.crypto.macs.HMac; import org.bouncycastle.crypto.params.KeyParameter; -import cn.hutool.core.io.IoUtil; -import cn.hutool.crypto.CryptoException; +import java.io.IOException; +import java.io.InputStream; /** * BouncyCastle的HMAC算法实现引擎,使用{@link Mac} 实现摘要
    @@ -94,4 +93,13 @@ public class BCHMacEngine implements MacEngine { return mac; } + @Override + public int getMacLength() { + return mac.getMacSize(); + } + + @Override + public String getAlgorithm() { + return this.mac.getAlgorithmName(); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/DefaultHMacEngine.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/DefaultHMacEngine.java index 6136347e4..2ea6aef98 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/DefaultHMacEngine.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/DefaultHMacEngine.java @@ -1,16 +1,16 @@ package cn.hutool.crypto.digest.mac; -import java.io.IOException; -import java.io.InputStream; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - import cn.hutool.core.io.IoUtil; import cn.hutool.crypto.CryptoException; import cn.hutool.crypto.SecureUtil; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.io.InputStream; +import java.security.Key; + /** * 默认的HMAC算法实现引擎,使用{@link Mac} 实现摘要
    * 当引入BouncyCastle库时自动使用其作为Provider @@ -39,7 +39,7 @@ public class DefaultHMacEngine implements MacEngine { * @param key 密钥 * @since 4.5.13 */ - public DefaultHMacEngine(String algorithm, SecretKey key) { + public DefaultHMacEngine(String algorithm, Key key) { init(algorithm, key); } // ------------------------------------------------------------------------------------------- Constructor end @@ -61,7 +61,7 @@ public class DefaultHMacEngine implements MacEngine { * @return this * @throws CryptoException Cause by IOException */ - public DefaultHMacEngine init(String algorithm, SecretKey key){ + public DefaultHMacEngine init(String algorithm, Key key){ try { mac = SecureUtil.createMac(algorithm); if(null == key){ @@ -106,4 +106,14 @@ public class DefaultHMacEngine implements MacEngine { public Mac getMac() { return mac; } + + @Override + public int getMacLength() { + return mac.getMacLength(); + } + + @Override + public String getAlgorithm() { + return this.mac.getAlgorithm(); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngine.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngine.java index 85fb8ec95..04d4d7f42 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngine.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngine.java @@ -1,9 +1,9 @@ package cn.hutool.crypto.digest.mac; -import java.io.InputStream; - import cn.hutool.core.io.IoUtil; +import java.io.InputStream; + /** * MAC(Message Authentication Code)算法引擎 * @@ -20,4 +20,18 @@ public interface MacEngine { * @return 摘要bytes */ byte[] digest(InputStream data, int bufferLength); + + /** + * 获取MAC算法块大小 + * + * @return MAC算法块大小 + */ + int getMacLength(); + + /** + * 获取当前算法 + * + * @return 算法 + */ + String getAlgorithm(); } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngineFactory.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngineFactory.java index 9288b6e72..49d0516dd 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngineFactory.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/mac/MacEngineFactory.java @@ -1,10 +1,10 @@ package cn.hutool.crypto.digest.mac; -import javax.crypto.SecretKey; - import cn.hutool.crypto.SmUtil; import cn.hutool.crypto.digest.HmacAlgorithm; +import java.security.Key; + /** * {@link MacEngine} 实现工厂类 * @@ -19,7 +19,7 @@ public class MacEngineFactory { * @param key 密钥 * @return {@link MacEngine} */ - public static MacEngine createEngine(String algorithm, SecretKey key) { + public static MacEngine createEngine(String algorithm, Key key) { if(algorithm.equalsIgnoreCase(HmacAlgorithm.HmacSM3.getValue())) { // HmacSM3算法是BC库实现的 return SmUtil.createHmacSm3Engine(key.getEncoded()); diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java new file mode 100644 index 000000000..fe69e2006 --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java @@ -0,0 +1,124 @@ +package cn.hutool.crypto.digest.opt; + +import cn.hutool.crypto.digest.HMac; +import cn.hutool.crypto.digest.HmacAlgorithm; + +/** + *

    HMAC-based one-time passwords (HOTP) 一次性密码生成器, + * 规范见:RFC 4226.

    + * + *

    参考:https://github.com/jchambers/java-otp

    + * + * @author Looly + */ +public class HOTP { + + /** + * 数子量级 + */ + private static final int[] MOD_DIVISORS = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000}; + /** + * 默认密码长度. + */ + public static final int DEFAULT_PASSWORD_LENGTH = 6; + + /** + * 默认HMAC算法. + */ + public static final HmacAlgorithm HOTP_HMAC_ALGORITHM = HmacAlgorithm.HmacSHA1; + + private final HMac mac; + private final int passwordLength; + private final int modDivisor; + + private final byte[] buffer; + + /** + * 构造,使用默认密码长度和默认HMAC算法 + */ + public HOTP(byte[] key) { + this(DEFAULT_PASSWORD_LENGTH, key); + } + + /** + * 构造,使用默认HMAC算法 + * + * @param passwordLength 密码长度,可以是6,7,8 + * @param key 共享密码,RFC 4226要求最少128位 + */ + public HOTP(int passwordLength, byte[] key) { + this(passwordLength, HOTP_HMAC_ALGORITHM, key); + } + + /** + * 构造 + * + * @param passwordLength 密码长度,可以是6,7,8 + * @param algorithm HMAC算法枚举 + * @param key 共享密码,RFC 4226要求最少128位 + */ + public HOTP(int passwordLength, HmacAlgorithm algorithm, byte[] key) { + this.mac = new HMac(algorithm, key); + + this.modDivisor = MOD_DIVISORS[passwordLength]; + this.passwordLength = passwordLength; + this.buffer = new byte[this.mac.getMacLength()]; + } + + /** + * 生成一次性密码 + * + * @param counter 事件计数的值,8 字节的整数,称为移动因子(moving factor), + * 可以是基于计次的动移动因子,也可以是计时移动因子 + * @return 一次性密码的int值 + */ + public synchronized int generateOneTimePassword(final long counter) { + // C 的整数值需要用二进制的字符串表达,比如某个事件计数为 3, + // 则C是 "11"(此处省略了前面的二进制的数字0) + this.buffer[0] = (byte) ((counter & 0xff00000000000000L) >>> 56); + this.buffer[1] = (byte) ((counter & 0x00ff000000000000L) >>> 48); + this.buffer[2] = (byte) ((counter & 0x0000ff0000000000L) >>> 40); + this.buffer[3] = (byte) ((counter & 0x000000ff00000000L) >>> 32); + this.buffer[4] = (byte) ((counter & 0x00000000ff000000L) >>> 24); + this.buffer[5] = (byte) ((counter & 0x0000000000ff0000L) >>> 16); + this.buffer[6] = (byte) ((counter & 0x000000000000ff00L) >>> 8); + this.buffer[7] = (byte) (counter & 0x00000000000000ffL); + + final byte[] digest = this.mac.digest(this.buffer); + + return truncate(digest); + } + + /** + * 截断 + * + * @param digest HMAC的hash值 + * @return 截断值 + */ + private int truncate(byte[] digest) { + final int offset = digest[digest.length - 1] & 0x0f; + return ((digest[offset] & 0x7f) << 24 | + (digest[offset + 1] & 0xff) << 16 | + (digest[offset + 2] & 0xff) << 8 | + (digest[offset + 3] & 0xff)) % + this.modDivisor; + } + + /** + * 获取密码长度,可以是6,7,8 + * + * @return 密码长度,可以是6,7,8 + */ + public int getPasswordLength() { + return this.passwordLength; + } + + /** + * 获取HMAC算法 + * + * @return HMAC算法 + */ + public String getAlgorithm() { + return this.mac.getAlgorithm(); + } +} \ No newline at end of file diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/package-info.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/package-info.java new file mode 100644 index 000000000..e496dd688 --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/package-info.java @@ -0,0 +1,14 @@ +/** + * OTP 是 One-Time Password的简写,表示一次性密码。 + *

    + * 计算OTP串的公式: + *

    + * OTP(K,C) = Truncate(HMAC-SHA-1(K,C))
    + * K:表示秘钥串
    + * C:是一个数字,表示随机数
    + * Truncate:是一个函数,就是怎么截取加密后的串,并取加密后串的哪些字段组成一个数字。
    + * 
    + * + * @author looly + */ +package cn.hutool.crypto.digest.opt; \ No newline at end of file From c697de539f6d2b2a767a2a74fe41f26499b47f11 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 30 Apr 2020 12:01:35 +0800 Subject: [PATCH 087/157] add TOPT --- .../cn/hutool/crypto/digest/opt/HOTP.java | 6 +- .../cn/hutool/crypto/digest/opt/TOPT.java | 86 +++++++++++++++++++ 2 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/TOPT.java diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java index fe69e2006..4060d0727 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java @@ -34,14 +34,14 @@ public class HOTP { private final byte[] buffer; /** - * 构造,使用默认密码长度和默认HMAC算法 + * 构造,使用默认密码长度和默认HMAC算法(HmacSHA1) */ public HOTP(byte[] key) { this(DEFAULT_PASSWORD_LENGTH, key); } /** - * 构造,使用默认HMAC算法 + * 构造,使用默认HMAC算法(HmacSHA1) * * @param passwordLength 密码长度,可以是6,7,8 * @param key 共享密码,RFC 4226要求最少128位 @@ -72,7 +72,7 @@ public class HOTP { * 可以是基于计次的动移动因子,也可以是计时移动因子 * @return 一次性密码的int值 */ - public synchronized int generateOneTimePassword(final long counter) { + public synchronized int generate(final long counter) { // C 的整数值需要用二进制的字符串表达,比如某个事件计数为 3, // 则C是 "11"(此处省略了前面的二进制的数字0) this.buffer[0] = (byte) ((counter & 0xff00000000000000L) >>> 56); diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/TOPT.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/TOPT.java new file mode 100644 index 000000000..84ec7b5f5 --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/TOPT.java @@ -0,0 +1,86 @@ +package cn.hutool.crypto.digest.opt; + +import cn.hutool.crypto.digest.HmacAlgorithm; + +import java.time.Duration; +import java.time.Instant; + +/** + *

    time-based one-time passwords (TOTP) 一次性密码生成器, + * 规范见:RFC 6238.

    + * + *

    参考:https://github.com/jchambers/java-otp

    + * + * @author Looly + */ +public class TOPT extends HOTP { + + /** + * 默认步进 (30秒). + */ + public static final Duration DEFAULT_TIME_STEP = Duration.ofSeconds(30); + + private final Duration timeStep; + + /** + * 构造,使用默认HMAC算法(HmacSHA1) + * + * @param key 共享密码,RFC 4226要求最少128位 + */ + public TOPT(byte[] key) { + this(DEFAULT_TIME_STEP, key); + } + + /** + * 构造,使用默认HMAC算法(HmacSHA1) + * + * @param timeStep 日期步进,用于生成移动因子(moving factor) + * @param key 共享密码,RFC 4226要求最少128位 + */ + public TOPT(Duration timeStep, byte[] key) { + this(timeStep, DEFAULT_PASSWORD_LENGTH, key); + } + + /** + * 构造,使用默认HMAC算法(HmacSHA1) + * + * @param timeStep 日期步进,用于生成移动因子(moving factor) + * @param passwordLength 密码长度,可以是6,7,8 + * @param key 共享密码,RFC 4226要求最少128位 + */ + public TOPT(Duration timeStep, int passwordLength, byte[] key) { + this(timeStep, passwordLength, HOTP_HMAC_ALGORITHM, key); + } + + /** + * 构造 + * + * @param timeStep 日期步进,用于生成移动因子(moving factor) + * @param passwordLength 密码长度,可以是6,7,8 + * @param algorithm HMAC算法枚举 + * @param key 共享密码,RFC 4226要求最少128位 + */ + public TOPT(Duration timeStep, int passwordLength, HmacAlgorithm algorithm, byte[] key) { + super(passwordLength, algorithm, key); + this.timeStep = timeStep; + } + + /** + * 使用给定的时间戳生成一次性密码. + * + * @param timestamp 用于生成密码的时间戳 + * @return 一次性密码的int形式 + */ + public int generate(Instant timestamp) { + return this.generate(timestamp.toEpochMilli() / this.timeStep.toMillis()); + } + + /** + * 获取步进 + * + * @return 步进 + */ + public Duration getTimeStep() { + return this.timeStep; + } +} From be65a142b46c3348c721378bc24adc579c0de898 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 1 May 2020 22:20:27 +0800 Subject: [PATCH 088/157] fix code --- CHANGELOG.md | 2 + .../cn/hutool/core/io/FastStringWriter.java | 91 +++++++++++++++++++ .../main/java/cn/hutool/core/io/FileUtil.java | 2 +- .../cn/hutool/core/io/checksum/CRC16.java | 2 +- .../core/io/resource/BytesResource.java | 12 +-- .../core/io/resource/InputStreamResource.java | 6 -- .../core/io/resource/MultiResource.java | 4 +- .../cn/hutool/core/io/resource/Resource.java | 9 +- .../hutool/core/io/resource/ResourceUtil.java | 5 +- .../core/io/resource/StringResource.java | 5 - .../hutool/core/io/resource/UrlResource.java | 6 -- .../cn/hutool/core/net/url/UrlBuilder.java | 2 +- .../java/cn/hutool/core/net/url/UrlQuery.java | 2 +- .../java/cn/hutool/core/util/BooleanUtil.java | 2 +- .../core/io/resource/ResourceUtilTest.java | 13 +++ .../cn/hutool/core/net/UrlBuilderTest.java | 56 ++++++------ .../java/cn/hutool/db/sql/SqlBuilder.java | 17 ++-- .../java/cn/hutool/db/sql/SqlBuilderTest.java | 12 +++ 18 files changed, 173 insertions(+), 75 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/io/FastStringWriter.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index fd91bdd0e..185840e1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,11 @@ * 【extra 】 SpringUtil增加getActiveProfiles、getBeansOfType、getBeanNamesForType方法(issue#I1FXF3@Gitee) * 【bloomFilter】 避免布隆过滤器数字溢出(pr#119@Gitee) * 【core 】 增加IoUtil.writeObj(issue#I1FZIE) +* 【core 】 增加FastStringWriter ### Bug修复 * 【core 】 修复URLBuilder中请求参数有`&`导致的问题(issue#850@Github) +* 【db 】 修复SqlBuilder中orderBy无效问题(issue#856@Github) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FastStringWriter.java b/hutool-core/src/main/java/cn/hutool/core/io/FastStringWriter.java new file mode 100644 index 000000000..b5b225c63 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/FastStringWriter.java @@ -0,0 +1,91 @@ +package cn.hutool.core.io; + +import cn.hutool.core.text.StrBuilder; + +import java.io.Writer; + +/** + * 借助{@link StrBuilder} 提供快读的字符串写出,相比jdk的StringWriter非线程安全,速度更快。 + * + * @author looly + * @since 5.3.3 + */ +public final class FastStringWriter extends Writer { + + private final StrBuilder builder; + + /** + * 构造 + */ + public FastStringWriter() { + this(StrBuilder.DEFAULT_CAPACITY); + } + + /** + * 构造 + * + * @param initialSize 初始容量 + */ + public FastStringWriter(int initialSize) { + super(); + if (initialSize < 0) { + initialSize = StrBuilder.DEFAULT_CAPACITY; + } + this.builder = new StrBuilder(initialSize); + } + + + @Override + public void write(final int c) { + this.builder.append((char) c); + } + + + @Override + public void write(final String str) { + this.builder.append(str); + } + + + @Override + public void write(final String str, final int off, final int len) { + this.builder.append(str, off, off + len); + } + + + @Override + public void write(final char[] cbuf) { + this.builder.append(cbuf, 0, cbuf.length); + } + + + @Override + public void write(final char[] cbuf, final int off, final int len) { + if ((off < 0) || (off > cbuf.length) || (len < 0) || + ((off + len) > cbuf.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + this.builder.append(cbuf, off, len); + } + + + @Override + public void flush() { + // Nothing to be flushed + } + + + @Override + public void close() { + // Nothing to be closed + } + + + @Override + public String toString() { + return this.builder.toString(); + } + +} \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index b798a8f4b..05da50528 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -3578,6 +3578,6 @@ public class FileUtil { * @param charset 编码 */ public static void tail(File file, Charset charset) { - FileUtil.tail(file, charset, Tailer.CONSOLE_HANDLER); + tail(file, charset, Tailer.CONSOLE_HANDLER); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/checksum/CRC16.java b/hutool-core/src/main/java/cn/hutool/core/io/checksum/CRC16.java index 3fc31c1ad..2017418c5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/checksum/CRC16.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/checksum/CRC16.java @@ -66,7 +66,7 @@ public class CRC16 implements Checksum, Serializable { @Override public void update(byte[] b, int off, int len) { for (int i = off; i < off + len; i++) - update((int) b[i]); + update(b[i]); } @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/BytesResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/BytesResource.java index c350d9943..f3607ba4b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/BytesResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/BytesResource.java @@ -1,5 +1,8 @@ package cn.hutool.core.io.resource; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.util.StrUtil; + import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -8,10 +11,6 @@ import java.io.StringReader; import java.net.URL; import java.nio.charset.Charset; -import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.StrUtil; - /** * 基于byte[]的资源获取器
    * 注意:此对象中getUrl方法始终返回null @@ -70,11 +69,6 @@ public class BytesResource implements Resource, Serializable { return StrUtil.str(this.bytes, charset); } - @Override - public String readUtf8Str() throws IORuntimeException { - return readStr(CharsetUtil.CHARSET_UTF_8); - } - @Override public byte[] readBytes() throws IORuntimeException { return this.bytes; diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java index 10403c9eb..e861329f2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/InputStreamResource.java @@ -2,7 +2,6 @@ package cn.hutool.core.io.resource; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.CharsetUtil; import java.io.BufferedReader; import java.io.InputStream; @@ -74,11 +73,6 @@ public class InputStreamResource implements Resource, Serializable { } } - @Override - public String readUtf8Str() throws IORuntimeException { - return readStr(CharsetUtil.CHARSET_UTF_8); - } - @Override public byte[] readBytes() throws IORuntimeException { InputStream in = null; diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java index 3d46d64e3..3e6f98c75 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java @@ -94,7 +94,7 @@ public class MultiResource implements Resource, Iterable, Iterator= resources.size()) { throw new ConcurrentModificationException(); } @@ -110,7 +110,7 @@ public class MultiResource implements Resource, Iterable, Iterator * 资源可以是文件、URL、ClassPath中的文件亦或者jar包中的文件 @@ -60,7 +61,9 @@ public interface Resource { * @return 读取资源内容 * @throws IORuntimeException 包装IOException */ - String readUtf8Str() throws IORuntimeException; + default String readUtf8Str() throws IORuntimeException{ + return readStr(CharsetUtil.CHARSET_UTF_8); + } /** * 读取资源内容,读取完毕后会关闭流
    diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java index e2e793622..bc7e8c47a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java @@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; @@ -18,7 +17,7 @@ import java.util.Enumeration; import java.util.List; /** - * ClassPath资源工具类 + * Resource资源工具类 * * @author Looly * @@ -33,7 +32,7 @@ public class ResourceUtil { * @since 3.1.1 */ public static String readUtf8Str(String resource) { - return readStr(resource, CharsetUtil.CHARSET_UTF_8); + return getResourceObj(resource).readUtf8Str(); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java index e5c04f3bf..0f45a4972 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/StringResource.java @@ -82,11 +82,6 @@ public class StringResource implements Resource, Serializable { return this.data; } - @Override - public String readUtf8Str() throws IORuntimeException { - return this.data; - } - @Override public byte[] readBytes() throws IORuntimeException { return this.data.getBytes(this.charset); diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java index 148a416f0..995a5923f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java @@ -3,7 +3,6 @@ package cn.hutool.core.io.resource; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.URLUtil; @@ -96,11 +95,6 @@ public class UrlResource implements Resource, Serializable{ } } - @Override - public String readUtf8Str() throws IORuntimeException{ - return readStr(CharsetUtil.CHARSET_UTF_8); - } - @Override public byte[] readBytes() throws IORuntimeException{ InputStream in = null; diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 978b70bc5..95549f74f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -71,7 +71,7 @@ public final class UrlBuilder implements Serializable { } /** - * 使用URL字符串构建UrlBuilder + * 使用URL字符串构建UrlBuilder,当传入的URL没有协议时,按照http协议对待。 * * @param httpUrl URL字符串 * @param charset 编码,用于URLEncode和URLDecode diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java index c84fa3f0a..74e4119b0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java @@ -135,7 +135,7 @@ public class UrlQuery { case '&'://键值对之间的分界符 addParam(name, queryStr.substring(pos, i), charset); name = null; - if ("amp;".equals(queryStr.substring(i + 1, i + 5))) { + if (i+4 < len && "amp;".equals(queryStr.substring(i + 1, i + 5))) { // issue#850@Github,"&"转义为"&" i+=4; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/BooleanUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/BooleanUtil.java index 6a0f84ad9..8cba53eb2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/BooleanUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/BooleanUtil.java @@ -11,7 +11,7 @@ import cn.hutool.core.convert.Convert; public class BooleanUtil { /** 表示为真的字符串 */ - private static final String[] TRUE_ARRAY = { "true", "yes", "y", "t", "ok", "1", "on", "是", "对", "真", }; + private static final String[] TRUE_ARRAY = { "true", "yes", "y", "t", "ok", "1", "on", "是", "对", "真", "對", "√"}; /** * 取相反值 diff --git a/hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java new file mode 100644 index 000000000..36b2d72bc --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/io/resource/ResourceUtilTest.java @@ -0,0 +1,13 @@ +package cn.hutool.core.io.resource; + +import org.junit.Assert; +import org.junit.Test; + +public class ResourceUtilTest { + + @Test + public void readXmlTest(){ + final String str = ResourceUtil.readUtf8Str("test.xml"); + Assert.assertNotNull(str); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java index 4ff8f6b8c..a09f24d35 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java @@ -9,112 +9,112 @@ public class UrlBuilderTest { @Test public void buildTest() { - String buildUrl = UrlBuilder.create().setHost("www.baidu.com").build(); - Assert.assertEquals("http://www.baidu.com/", buildUrl); + String buildUrl = UrlBuilder.create().setHost("www.hutool.cn").build(); + Assert.assertEquals("http://www.hutool.cn/", buildUrl); } @Test public void testHost() { String buildUrl = UrlBuilder.create() .setScheme("https") - .setHost("www.baidu.com").build(); - Assert.assertEquals("https://www.baidu.com/", buildUrl); + .setHost("www.hutool.cn").build(); + Assert.assertEquals("https://www.hutool.cn/", buildUrl); } @Test public void testHostPort() { String buildUrl = UrlBuilder.create() .setScheme("https") - .setHost("www.baidu.com") + .setHost("www.hutool.cn") .setPort(8080) .build(); - Assert.assertEquals("https://www.baidu.com:8080/", buildUrl); + Assert.assertEquals("https://www.hutool.cn:8080/", buildUrl); } @Test public void testPathAndQuery() { final String buildUrl = UrlBuilder.create() .setScheme("https") - .setHost("www.baidu.com") + .setHost("www.hutool.cn") .addPath("/aaa").addPath("bbb") .addQuery("ie", "UTF-8") .addQuery("wd", "test") .build(); - Assert.assertEquals("https://www.baidu.com/aaa/bbb?ie=UTF-8&wd=test", buildUrl); + Assert.assertEquals("https://www.hutool.cn/aaa/bbb?ie=UTF-8&wd=test", buildUrl); } @Test public void testQueryWithChinese() { final String buildUrl = UrlBuilder.create() .setScheme("https") - .setHost("www.baidu.com") + .setHost("www.hutool.cn") .addPath("/aaa").addPath("bbb") .addQuery("ie", "UTF-8") .addQuery("wd", "测试") .build(); - Assert.assertEquals("https://www.baidu.com/aaa/bbb?ie=UTF-8&wd=%E6%B5%8B%E8%AF%95", buildUrl); + Assert.assertEquals("https://www.hutool.cn/aaa/bbb?ie=UTF-8&wd=%E6%B5%8B%E8%AF%95", buildUrl); } @Test public void testMultiQueryWithChinese() { final String buildUrl = UrlBuilder.create() .setScheme("https") - .setHost("www.baidu.com") + .setHost("www.hutool.cn") .addPath("/s") .addQuery("ie", "UTF-8") .addQuery("ie", "GBK") .addQuery("wd", "测试") .build(); - Assert.assertEquals("https://www.baidu.com/s?ie=UTF-8&ie=GBK&wd=%E6%B5%8B%E8%AF%95", buildUrl); + Assert.assertEquals("https://www.hutool.cn/s?ie=UTF-8&ie=GBK&wd=%E6%B5%8B%E8%AF%95", buildUrl); } @Test public void testFragment() { String buildUrl = new UrlBuilder() .setScheme("https") - .setHost("www.baidu.com") + .setHost("www.hutool.cn") .setFragment("abc").build(); - Assert.assertEquals("https://www.baidu.com/#abc", buildUrl); + Assert.assertEquals("https://www.hutool.cn/#abc", buildUrl); } @Test public void testChineseFragment() { String buildUrl = new UrlBuilder() .setScheme("https") - .setHost("www.baidu.com") + .setHost("www.hutool.cn") .setFragment("测试").build(); - Assert.assertEquals("https://www.baidu.com/#%E6%B5%8B%E8%AF%95", buildUrl); + Assert.assertEquals("https://www.hutool.cn/#%E6%B5%8B%E8%AF%95", buildUrl); } @Test public void testChineseFragmentWithPath() { String buildUrl = new UrlBuilder() .setScheme("https") - .setHost("www.baidu.com") + .setHost("www.hutool.cn") .addPath("/s") .setFragment("测试").build(); - Assert.assertEquals("https://www.baidu.com/s#%E6%B5%8B%E8%AF%95", buildUrl); + Assert.assertEquals("https://www.hutool.cn/s#%E6%B5%8B%E8%AF%95", buildUrl); } @Test public void testChineseFragmentWithPathAndQuery() { String buildUrl = new UrlBuilder() .setScheme("https") - .setHost("www.baidu.com") + .setHost("www.hutool.cn") .addPath("/s") .addQuery("wd", "test") .setFragment("测试").build(); - Assert.assertEquals("https://www.baidu.com/s?wd=test#%E6%B5%8B%E8%AF%95", buildUrl); + Assert.assertEquals("https://www.hutool.cn/s?wd=test#%E6%B5%8B%E8%AF%95", buildUrl); } @Test public void ofTest() { - final UrlBuilder builder = UrlBuilder.of("http://www.baidu.com/aaa/bbb/?a=1&b=2#frag1", CharsetUtil.CHARSET_UTF_8); + final UrlBuilder builder = UrlBuilder.of("http://www.hutool.cn/aaa/bbb/?a=1&b=2#frag1", CharsetUtil.CHARSET_UTF_8); Assert.assertEquals("http", builder.getScheme()); - Assert.assertEquals("www.baidu.com", builder.getHost()); + Assert.assertEquals("www.hutool.cn", builder.getHost()); Assert.assertEquals("aaa", builder.getPath().getSegment(0)); Assert.assertEquals("bbb", builder.getPath().getSegment(1)); @@ -127,9 +127,9 @@ public class UrlBuilderTest { @Test public void ofWithChineseTest() { - final UrlBuilder builder = UrlBuilder.ofHttp("www.baidu.com/aaa/bbb/?a=张三&b=%e6%9d%8e%e5%9b%9b#frag1", CharsetUtil.CHARSET_UTF_8); + final UrlBuilder builder = UrlBuilder.ofHttp("www.hutool.cn/aaa/bbb/?a=张三&b=%e6%9d%8e%e5%9b%9b#frag1", CharsetUtil.CHARSET_UTF_8); Assert.assertEquals("http", builder.getScheme()); - Assert.assertEquals("www.baidu.com", builder.getHost()); + Assert.assertEquals("www.hutool.cn", builder.getHost()); Assert.assertEquals("aaa", builder.getPath().getSegment(0)); Assert.assertEquals("bbb", builder.getPath().getSegment(1)); @@ -142,9 +142,9 @@ public class UrlBuilderTest { @Test public void ofWithBlankTest() { - final UrlBuilder builder = UrlBuilder.ofHttp(" www.baidu.com/aaa/bbb/?a=张三&b=%e6%9d%8e%e5%9b%9b#frag1", CharsetUtil.CHARSET_UTF_8); + final UrlBuilder builder = UrlBuilder.ofHttp(" www.hutool.cn/aaa/bbb/?a=张三&b=%e6%9d%8e%e5%9b%9b#frag1", CharsetUtil.CHARSET_UTF_8); Assert.assertEquals("http", builder.getScheme()); - Assert.assertEquals("www.baidu.com", builder.getHost()); + Assert.assertEquals("www.hutool.cn", builder.getHost()); Assert.assertEquals("aaa", builder.getPath().getSegment(0)); Assert.assertEquals("bbb", builder.getPath().getSegment(1)); @@ -158,9 +158,9 @@ public class UrlBuilderTest { @Test public void ofSpecialTest() { //测试不规范的或者无需解码的字符串是否成功解码 - final UrlBuilder builder = UrlBuilder.ofHttp(" www.baidu.com/aaa/bbb/?a=张三&b=%%e5%9b%9b#frag1", CharsetUtil.CHARSET_UTF_8); + final UrlBuilder builder = UrlBuilder.ofHttp(" www.hutool.cn/aaa/bbb/?a=张三&b=%%e5%9b%9b#frag1", CharsetUtil.CHARSET_UTF_8); Assert.assertEquals("http", builder.getScheme()); - Assert.assertEquals("www.baidu.com", builder.getHost()); + Assert.assertEquals("www.hutool.cn", builder.getHost()); Assert.assertEquals("aaa", builder.getPath().getSegment(0)); Assert.assertEquals("bbb", builder.getPath().getSegment(1)); diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java b/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java index f5ca1de29..f1775ddcf 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java @@ -1,11 +1,5 @@ package cn.hutool.db.sql; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map.Entry; - import cn.hutool.core.builder.Builder; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ArrayUtil; @@ -15,6 +9,12 @@ import cn.hutool.db.DbRuntimeException; import cn.hutool.db.Entity; import cn.hutool.db.dialect.DialectName; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map.Entry; + /** * SQL构建器
    * 首先拼接SQL语句,值使用 ? 占位
    @@ -404,12 +404,13 @@ public class SqlBuilder implements Builder{ } sql.append(" ORDER BY "); - String field = null; + String field; boolean isFirst = true; for (Order order : orders) { + field = order.getField(); if (null != wrapper) { // 包装字段名 - field = wrapper.wrap(order.getField()); + field = wrapper.wrap(field); } if (StrUtil.isBlank(field)) { continue; diff --git a/hutool-db/src/test/java/cn/hutool/db/sql/SqlBuilderTest.java b/hutool-db/src/test/java/cn/hutool/db/sql/SqlBuilderTest.java index 6f3027359..2ae41a39a 100644 --- a/hutool-db/src/test/java/cn/hutool/db/sql/SqlBuilderTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/sql/SqlBuilderTest.java @@ -19,4 +19,16 @@ public class SqlBuilderTest { SqlBuilder builder4 = SqlBuilder.create().select().from("user").where(LogicalOperator.AND, new Condition("name", "is not null")); Assert.assertEquals("SELECT * FROM user WHERE name IS NOT NULL", builder4.build()); } + + @Test + public void orderByTest(){ + SqlBuilder builder = SqlBuilder.create().select("id", "username").from("user") + .join("role", SqlBuilder.Join.INNER) + .on("user.id = role.user_id") + .where(new Condition("age", ">=", 18), + new Condition("username", "abc", Condition.LikeType.Contains) + ).orderBy(new Order("id")); + + Assert.assertEquals("SELECT id,username FROM user INNER JOIN role ON user.id = role.user_id WHERE age >= ? AND username LIKE ? ORDER BY id", builder.build()); + } } From 70c0f4caccbfa79f8dc272376c1fc3e16e53546e Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 1 May 2020 23:01:55 +0800 Subject: [PATCH 089/157] add test --- .../java/cn/hutool/core/map/TableMap.java | 17 +- .../java/cn/hutool/core/map/TolerantMap.java | 191 +++++------------- .../hutool/core/convert/MapConvertTest.java | 15 +- .../java/cn/hutool/core/map/BiMapTest.java | 22 ++ .../java/cn/hutool/core/map/TableMapTest.java | 20 ++ 5 files changed, 109 insertions(+), 156 deletions(-) create mode 100644 hutool-core/src/test/java/cn/hutool/core/map/BiMapTest.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/map/TableMapTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java index 7ce096b42..76dac40fc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java @@ -84,6 +84,20 @@ public class TableMap implements Map, Iterable>, Ser return null; } + /** + * 根据value获得对应的key,只返回找到的第一个value对应的key值 + * @param value 值 + * @return 键 + * @since 5.3.3 + */ + public K getKey(V value){ + final int index = values.indexOf(value); + if (index > -1 && index < keys.size()) { + return keys.get(index); + } + return null; + } + /** * 获取指定key对应的所有值 * @@ -145,19 +159,16 @@ public class TableMap implements Map, Iterable>, Ser values.clear(); } - @SuppressWarnings("NullableProblems") @Override public Set keySet() { return new HashSet<>(keys); } - @SuppressWarnings("NullableProblems") @Override public Collection values() { return Collections.unmodifiableList(this.values); } - @SuppressWarnings("NullableProblems") @Override public Set> entrySet() { final Set> hashSet = new LinkedHashSet<>(); diff --git a/hutool-core/src/main/java/cn/hutool/core/map/TolerantMap.java b/hutool-core/src/main/java/cn/hutool/core/map/TolerantMap.java index d5e67a63d..9cb5bc7ce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/TolerantMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/TolerantMap.java @@ -1,166 +1,80 @@ package cn.hutool.core.map; -import cn.hutool.core.util.ObjectUtil; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Function; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; /** * 一个可以提供默认值的Map * - * @author pantao - * @since 2020/1/3 + * @param 键类型 + * @param 值类型 + * @author pantao, looly */ -public class TolerantMap extends AbstractMap implements Map, Cloneable, Serializable { - +public class TolerantMap extends MapWrapper { private static final long serialVersionUID = -4158133823263496197L; - private transient Map map; - - private transient V defaultValue; + private final transient V defaultValue; + /** + * 构造 + * + * @param defaultValue 默认值 + */ public TolerantMap(V defaultValue) { this(new HashMap<>(), defaultValue); } + /** + * 构造 + * + * @param initialCapacity 初始容量 + * @param loadFactor 增长因子 + * @param defaultValue 默认值 + */ public TolerantMap(int initialCapacity, float loadFactor, V defaultValue) { this(new HashMap<>(initialCapacity, loadFactor), defaultValue); } + /** + * 构造 + * + * @param initialCapacity 初始容量 + * @param defaultValue 默认值 + */ public TolerantMap(int initialCapacity, V defaultValue) { this(new HashMap<>(initialCapacity), defaultValue); } + /** + * 构造 + * + * @param map Map实现 + * @param defaultValue 默认值 + */ public TolerantMap(Map map, V defaultValue) { - this.map = map; + super(map); this.defaultValue = defaultValue; } + /** + * 构建TolerantMap + * + * @param map map实现 + * @param defaultValue 默认值 + * @param 键类型 + * @param 值类型 + * @return TolerantMap + */ public static TolerantMap of(Map map, V defaultValue) { return new TolerantMap<>(map, defaultValue); } - @Override - public int size() { - return map.size(); - } - - @Override - public boolean isEmpty() { - return map.isEmpty(); - } - - @Override - public boolean containsValue(Object value) { - return map.containsValue(value); - } - - @Override - public boolean containsKey(Object key) { - return map.containsKey(key); - } - @Override public V get(Object key) { return getOrDefault(key, defaultValue); } - @Override - public V put(K key, V value) { - return map.put(key, value); - } - - @Override - public V remove(Object key) { - return map.remove(key); - } - - @Override - public void putAll(Map m) { - map.putAll(m); - } - - @Override - public void clear() { - map.clear(); - } - - @Override - public Set keySet() { - return map.keySet(); - } - - @Override - public Collection values() { - return map.values(); - } - - @Override - public Set> entrySet() { - return map.entrySet(); - } - - @Override - public V getOrDefault(Object key, V defaultValue) { - return map.getOrDefault(key, defaultValue); - } - - @Override - public void forEach(BiConsumer action) { - map.forEach(action); - } - - @Override - public void replaceAll(BiFunction function) { - map.replaceAll(function); - } - - @Override - public V putIfAbsent(K key, V value) { - return map.putIfAbsent(key, value); - } - - @Override - public boolean remove(Object key, Object value) { - return map.remove(key, value); - } - - @Override - public boolean replace(K key, V oldValue, V newValue) { - return map.replace(key, oldValue, newValue); - } - - @Override - public V replace(K key, V value) { - return map.replace(key, value); - } - - @Override - public V computeIfAbsent(K key, Function mappingFunction) { - return map.computeIfAbsent(key, mappingFunction); - } - - @Override - public V computeIfPresent(K key, BiFunction remappingFunction) { - return map.computeIfPresent(key, remappingFunction); - } - - @Override - public V compute(K key, BiFunction remappingFunction) { - return map.compute(key, remappingFunction); - } - - @Override - public V merge(K key, V value, BiFunction remappingFunction) { - return map.merge(key, value, remappingFunction); - } - @Override public boolean equals(Object o) { if (this == o) { @@ -173,31 +87,16 @@ public class TolerantMap extends AbstractMap implements Map, C return false; } TolerantMap that = (TolerantMap) o; - return map.equals(that.map) && Objects.equals(defaultValue, that.defaultValue); + return getRaw().equals(that.getRaw()) && Objects.equals(defaultValue, that.defaultValue); } @Override public int hashCode() { - return Objects.hash(map, defaultValue); + return Objects.hash(getRaw(), defaultValue); } @Override public String toString() { - return "TolerantMap{" + "map=" + map + ", defaultValue=" + defaultValue + '}'; - } - - private void writeObject(ObjectOutputStream s) throws IOException { - s.writeObject(ObjectUtil.serialize(map)); - s.writeObject(ObjectUtil.serialize(defaultValue)); - } - - private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { - map = ObjectUtil.deserialize((byte[]) s.readObject()); - defaultValue = ObjectUtil.deserialize((byte[]) s.readObject()); - } - - @Override - protected Object clone() throws CloneNotSupportedException { - return super.clone(); + return "TolerantMap{" + "map=" + getRaw() + ", defaultValue=" + defaultValue + '}'; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/MapConvertTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/MapConvertTest.java index 36b409627..01cd00b2d 100644 --- a/hutool-core/src/test/java/cn/hutool/core/convert/MapConvertTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/convert/MapConvertTest.java @@ -1,15 +1,13 @@ package cn.hutool.core.convert; +import cn.hutool.core.map.MapBuilder; +import org.junit.Assert; +import org.junit.Test; + import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import org.junit.Assert; -import org.junit.Test; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.map.MapBuilder; - /** * Map转换单元测试 * @@ -31,7 +29,10 @@ public class MapConvertTest { @Test public void mapToMapTest() { - Map srcMap = MapBuilder.create(new HashMap()).put("name", "AAA").put("age", 45).map(); + Map srcMap = MapBuilder + .create(new HashMap()) + .put("name", "AAA") + .put("age", 45).map(); LinkedHashMap map = Convert.convert(LinkedHashMap.class, srcMap); Assert.assertEquals("AAA", map.get("name")); diff --git a/hutool-core/src/test/java/cn/hutool/core/map/BiMapTest.java b/hutool-core/src/test/java/cn/hutool/core/map/BiMapTest.java new file mode 100644 index 000000000..308f9e4ee --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/map/BiMapTest.java @@ -0,0 +1,22 @@ +package cn.hutool.core.map; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class BiMapTest { + + @Test + public void getTest(){ + BiMap biMap = new BiMap<>(new HashMap<>()); + biMap.put("aaa", 111); + biMap.put("bbb", 222); + + Assert.assertEquals(new Integer(111), biMap.get("aaa")); + Assert.assertEquals(new Integer(222), biMap.get("bbb")); + + Assert.assertEquals("aaa", biMap.getKey(111)); + Assert.assertEquals("bbb", biMap.getKey(222)); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/map/TableMapTest.java b/hutool-core/src/test/java/cn/hutool/core/map/TableMapTest.java new file mode 100644 index 000000000..9fc3bf72b --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/map/TableMapTest.java @@ -0,0 +1,20 @@ +package cn.hutool.core.map; + +import org.junit.Assert; +import org.junit.Test; + +public class TableMapTest { + + @Test + public void getTest(){ + TableMap tableMap = new TableMap<>(16); + tableMap.put("aaa", 111); + tableMap.put("bbb", 222); + + Assert.assertEquals(new Integer(111), tableMap.get("aaa")); + Assert.assertEquals(new Integer(222), tableMap.get("bbb")); + + Assert.assertEquals("aaa", tableMap.getKey(111)); + Assert.assertEquals("bbb", tableMap.getKey(222)); + } +} From a4c8ebc572103b6f8c9cadf8e5356cdd761421ae Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 3 May 2020 08:44:04 +0800 Subject: [PATCH 090/157] fix url bug --- .../src/main/java/cn/hutool/core/net/url/UrlPath.java | 5 +++++ .../test/java/cn/hutool/core/net/UrlBuilderTest.java | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java index bf11b0837..31a1b715f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java @@ -101,6 +101,11 @@ public class UrlPath { if (StrUtil.isNotEmpty(path)) { path = path.trim(); + // 原URL中以/结尾,则这个规则需保留,issue#I1G44J@Gitee + if(StrUtil.endWith(path, CharUtil.SLASH)){ + this.withEngTag = true; + } + final StringTokenizer tokenizer = new StringTokenizer(path, "/"); while (tokenizer.hasMoreTokens()) { add(URLUtil.decode(tokenizer.nextToken(), charset)); diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java index a09f24d35..c5f2f1a68 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java @@ -1,5 +1,6 @@ package cn.hutool.core.net; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.CharsetUtil; import org.junit.Assert; @@ -188,4 +189,13 @@ public class UrlBuilderTest { "&chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7", builder.toString()); } + + @Test + public void endWithSlashTest(){ + // 原URL中以/结尾,则这个规则需保留,issue#I1G44J@Gitee + final String today = DateUtil.date().toString("yyyyMMdd"); + final String getWorkDayUrl = "https://tool.bitefu.net/jiari/?info=1&d=" + today; + final UrlBuilder builder = UrlBuilder.ofHttp(getWorkDayUrl, CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals(getWorkDayUrl, builder.toString()); + } } From 0321ce1120851e0b0f20b65d8cab9511219bf2b4 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 3 May 2020 09:10:06 +0800 Subject: [PATCH 091/157] fix test --- CHANGELOG.md | 1 + .../java/cn/hutool/core/map/MapWrapper.java | 99 +++++++++++++++++-- .../java/cn/hutool/core/map/TolerantMap.java | 9 +- .../cn/hutool/core/map/TolerantMapTest.java | 48 ++++----- 4 files changed, 121 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 185840e1d..62e912323 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ ### Bug修复 * 【core 】 修复URLBuilder中请求参数有`&`导致的问题(issue#850@Github) +* 【core 】 修复URLBuilder中路径以`/`结尾导致的问题(issue#I1G44J@Gitee) * 【db 】 修复SqlBuilder中orderBy无效问题(issue#856@Github) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java b/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java index 7da37c9a7..dfb58c0f6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java @@ -4,42 +4,50 @@ import java.io.Serializable; import java.util.Collection; import java.util.Iterator; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; /** * Map包装类,通过包装一个已有Map实现特定功能。例如自定义Key的规则或Value规则 - * - * @author looly * * @param 键类型 * @param 值类型 * @author looly + * @author looly * @since 4.3.3 */ public class MapWrapper implements Map, Iterable>, Serializable, Cloneable { private static final long serialVersionUID = -7524578042008586382L; - - /** 默认增长因子 */ + + /** + * 默认增长因子 + */ protected static final float DEFAULT_LOAD_FACTOR = 0.75f; - /** 默认初始大小 */ + /** + * 默认初始大小 + */ protected static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 private final Map raw; /** * 构造 - * + * * @param raw 被包装的Map */ public MapWrapper(Map raw) { this.raw = raw; } - + /** * 获取原始的Map + * * @return Map */ - public Map getRaw(){ + public Map getRaw() { return this.raw; } @@ -113,8 +121,83 @@ public class MapWrapper implements Map, Iterable>, S return this.entrySet().iterator(); } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + MapWrapper that = (MapWrapper) o; + return Objects.equals(raw, that.raw); + } + + @Override + public int hashCode() { + return Objects.hash(raw); + } + @Override public String toString() { return raw.toString(); } + + + @Override + public void forEach(BiConsumer action) { + raw.forEach(action); + } + + @Override + public void replaceAll(BiFunction function) { + raw.replaceAll(function); + } + + @Override + public V putIfAbsent(K key, V value) { + return raw.putIfAbsent(key, value); + } + + @Override + public boolean remove(Object key, Object value) { + return raw.remove(key, value); + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + return raw.replace(key, oldValue, newValue); + } + + @Override + public V replace(K key, V value) { + return raw.replace(key, value); + } + + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + return raw.computeIfAbsent(key, mappingFunction); + } + + //---------------------------------------------------------------------------- Override default methods start + @Override + public V getOrDefault(Object key, V defaultValue) { + return raw.getOrDefault(key, defaultValue); + } + + @Override + public V computeIfPresent(K key, BiFunction remappingFunction) { + return raw.computeIfPresent(key, remappingFunction); + } + + @Override + public V compute(K key, BiFunction remappingFunction) { + return raw.compute(key, remappingFunction); + } + + @Override + public V merge(K key, V value, BiFunction remappingFunction) { + return raw.merge(key, value, remappingFunction); + } + //---------------------------------------------------------------------------- Override default methods end } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/TolerantMap.java b/hutool-core/src/main/java/cn/hutool/core/map/TolerantMap.java index 9cb5bc7ce..9f190c52e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/TolerantMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/TolerantMap.java @@ -14,7 +14,7 @@ import java.util.Objects; public class TolerantMap extends MapWrapper { private static final long serialVersionUID = -4158133823263496197L; - private final transient V defaultValue; + private final V defaultValue; /** * 构造 @@ -83,11 +83,12 @@ public class TolerantMap extends MapWrapper { if (o == null || getClass() != o.getClass()) { return false; } - if (!super.equals(o)) { + if (false == super.equals(o)) { return false; } - TolerantMap that = (TolerantMap) o; - return getRaw().equals(that.getRaw()) && Objects.equals(defaultValue, that.defaultValue); + final TolerantMap that = (TolerantMap) o; + return getRaw().equals(that.getRaw()) + && Objects.equals(defaultValue, that.defaultValue); } @Override diff --git a/hutool-core/src/test/java/cn/hutool/core/map/TolerantMapTest.java b/hutool-core/src/test/java/cn/hutool/core/map/TolerantMapTest.java index dba466118..b6ed7cbe2 100644 --- a/hutool-core/src/test/java/cn/hutool/core/map/TolerantMapTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/map/TolerantMapTest.java @@ -9,32 +9,32 @@ import java.util.HashMap; public class TolerantMapTest { - private final TolerantMap map = TolerantMap.of(new HashMap<>(), "default"); + private final TolerantMap map = TolerantMap.of(new HashMap<>(), "default"); - @Before - public void before() { - map.put("monday", "星期一"); - map.put("tuesday", "星期二"); - } + @Before + public void before() { + map.put("monday", "星期一"); + map.put("tuesday", "星期二"); + } - @Test - public void testSerialize() { - byte[] bytes = ObjectUtil.serialize(map); - TolerantMap serializedMap = ObjectUtil.deserialize(bytes); - assert serializedMap != map; - assert map.equals(serializedMap); - } + @Test + public void testSerialize() { + byte[] bytes = ObjectUtil.serialize(map); + TolerantMap serializedMap = ObjectUtil.deserialize(bytes); + assert serializedMap != map; + assert map.equals(serializedMap); + } - @Test - public void testClone() { - TolerantMap clonedMap = ObjectUtil.clone(map); - assert clonedMap != map; - assert map.equals(clonedMap); - } + @Test + public void testClone() { + TolerantMap clonedMap = ObjectUtil.clone(map); + assert clonedMap != map; + assert map.equals(clonedMap); + } - @Test - public void testGet() { - assert "星期二".equals(map.get("tuesday")); - assert "default".equals(map.get(RandomUtil.randomString(6))); - } + @Test + public void testGet() { + assert "星期二".equals(map.get("tuesday")); + assert "default".equals(map.get(RandomUtil.randomString(6))); + } } From fc68293aca5bcd2ed10a7b7102bf83732a6b691b Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 3 May 2020 09:19:52 +0800 Subject: [PATCH 092/157] fix test --- .../src/main/java/cn/hutool/extra/spring/SpringUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index b7372342b..5a3b46c13 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -99,6 +99,7 @@ public class SpringUtil implements ApplicationContextAware { * 获取配置文件配置项的值 * * @param key 配置项key + * @return 属性值 * @since 5.3.3 */ public static String getProperty(String key) { From c00ab6e12630b941323e4db004405c504ba0dcba Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 5 May 2020 13:14:42 +0800 Subject: [PATCH 093/157] add method --- CHANGELOG.md | 1 + .../src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java | 1 - hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62e912323..cf4b18644 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * 【bloomFilter】 避免布隆过滤器数字溢出(pr#119@Gitee) * 【core 】 增加IoUtil.writeObj(issue#I1FZIE) * 【core 】 增加FastStringWriter +* 【core 】 增加NumberUtil.ceilDiv方法(pr#858@Github) ### Bug修复 * 【core 】 修复URLBuilder中请求参数有`&`导致的问题(issue#850@Github) diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java index 1f565688a..afec2546e 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java @@ -20,7 +20,6 @@ import com.github.promeg.pinyinhelper.Pinyin; * <version>2.0.3.RELEASE</version> * </dependency> * - *

    * * @author looly */ diff --git a/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java index d2bef0174..40ea4a3e1 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/HttpUtilTest.java @@ -19,7 +19,7 @@ import java.util.Map; public class HttpUtilTest { @Test -// @Ignore + @Ignore public void postTest() { String result = HttpUtil.createPost("api.uhaozu.com/goods/description/1120448506").charset(CharsetUtil.UTF_8).execute().body(); Console.log(result); From 4aee10da40decd74e99694da7eda4210ad1f0af2 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 5 May 2020 13:25:17 +0800 Subject: [PATCH 094/157] add method --- .../main/java/cn/hutool/json/JSONUtil.java | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 8bc816859..4ab90c4a0 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -331,6 +331,18 @@ public final class JSONUtil { return json.toJSONString(0); } + /** + * 转为JSON字符串,并写出到write + * + * @param json JSON + * @since 5.3.3 + */ + public static void toJsonStr(JSON json, Writer writer) { + if (null != json) { + json.write(writer); + } + } + /** * 转为JSON字符串 * @@ -354,12 +366,25 @@ public final class JSONUtil { if (null == obj) { return null; } - if (obj instanceof String) { - return (String) obj; + if (obj instanceof CharSequence) { + return StrUtil.str((CharSequence) obj); } return toJsonStr(parse(obj)); } + /** + * 转换为JSON字符串并写出到writer + * + * @param obj 被转为JSON的对象 + * @param writer Writer + * @since 5.3.3 + */ + public static void toJsonStr(Object obj, Writer writer) { + if (null != obj) { + toJsonStr(parse(obj), writer); + } + } + /** * 转换为格式化后的JSON字符串 * From 3d11e2baa270ce065a45622c67e154d737335875 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 5 May 2020 16:52:03 +0800 Subject: [PATCH 095/157] add idcard validate --- CHANGELOG.md | 1 + .../src/main/java/cn/hutool/core/util/IdcardUtil.java | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf4b18644..a58e9a9e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * 【core 】 增加IoUtil.writeObj(issue#I1FZIE) * 【core 】 增加FastStringWriter * 【core 】 增加NumberUtil.ceilDiv方法(pr#858@Github) +* 【core 】 IdcardUtil增加省份校验(issue#859@Github) ### Bug修复 * 【core 】 修复URLBuilder中请求参数有`&`导致的问题(issue#850@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java index c29f6ff49..e2f4ad944 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java @@ -218,6 +218,12 @@ public class IdcardUtil { return false; } + // 省份 + final String proCode = idCard.substring(0, 2); + if (null == CITY_CODES.get(proCode)) { + return false; + } + //校验生日 if (false == Validator.isBirthday(idCard.substring(6, 14))) { return false; From 33b2a3dc51303f5f028aff156491a9e9f24c4dd6 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 5 May 2020 17:07:01 +0800 Subject: [PATCH 096/157] add idcard validate --- .../cn/hutool/extra/pinyin/PinyinUtil.java | 35 +++++++++++++------ .../hutool/extra/pinyin/PinyinUtilTest.java | 15 +++++--- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java index afec2546e..5e394e6fa 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java @@ -37,11 +37,11 @@ public class PinyinUtil { /** * 如果c为汉字,则返回大写拼音;如果c不是汉字,则返回String.valueOf(c) * - * @param c 任意字符,汉族返回拼音,非汉字原样返回 + * @param c 任意字符,汉字返回拼音,非汉字原样返回 * @param isToUpperCase 是否转换为大写 - * @return 汉族返回拼音,非汉字原样返回 + * @return 汉字返回拼音,非汉字原样返回 */ - public static String toPinyin(char c, boolean isToUpperCase) { + public static String getPinyin(char c, boolean isToUpperCase) { final String pinyin = Pinyin.toPinyin(c); return isToUpperCase ? pinyin : pinyin.toLowerCase(); } @@ -49,24 +49,39 @@ public class PinyinUtil { /** * 将输入字符串转为拼音,每个字之间的拼音使用空格分隔 * - * @param str 任意字符,汉族返回拼音,非汉字原样返回 + * @param str 任意字符,汉字返回拼音,非汉字原样返回 * @param isToUpperCase 是否转换为大写 - * @return 汉族返回拼音,非汉字原样返回 + * @return 汉字返回拼音,非汉字原样返回 */ - public static String toPinyin(String str, boolean isToUpperCase) { - return toPinyin(str, " ", isToUpperCase); + public static String getPinyin(String str, boolean isToUpperCase) { + return getPinyin(str, " ", isToUpperCase); } /** * 将输入字符串转为拼音,以字符为单位插入分隔符 * - * @param str 任意字符,汉族返回拼音,非汉字原样返回 + * @param str 任意字符,汉字返回拼音,非汉字原样返回 * @param separator 每个字拼音之间的分隔符 * @param isToUpperCase 是否转换为大写 - * @return 汉族返回拼音,非汉字原样返回 + * @return 汉字返回拼音,非汉字原样返回 */ - public static String toPinyin(String str, String separator, boolean isToUpperCase) { + public static String getPinyin(String str, String separator, boolean isToUpperCase) { final String pinyin = Pinyin.toPinyin(str, separator); return isToUpperCase ? pinyin : pinyin.toLowerCase(); } + + /** + * 将输入字符串转为拼音首字母,其它字符原样返回 + * + * @param str 任意字符,汉字返回拼音,非汉字原样返回 + * @param isToUpperCase 是否转换为大写 + * @return 汉字返回拼音,非汉字原样返回 + */ + public static char[] getFirstLetter(String str, boolean isToUpperCase) { + final char[] result = new char[str.length()]; + for(int i=0; i < result.length; i++){ + result[i] = getPinyin(str.charAt(i), isToUpperCase).charAt(0); + } + return result; + } } diff --git a/hutool-extra/src/test/java/cn/hutool/extra/pinyin/PinyinUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/pinyin/PinyinUtilTest.java index 167f3e43b..a8e72e830 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/pinyin/PinyinUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/pinyin/PinyinUtilTest.java @@ -1,19 +1,26 @@ package cn.hutool.extra.pinyin; +import cn.hutool.core.util.ArrayUtil; import org.junit.Assert; import org.junit.Test; public class PinyinUtilTest { @Test - public void toPinyinTest(){ - final String pinyin = PinyinUtil.toPinyin("你好", false); + public void getPinyinTest(){ + final String pinyin = PinyinUtil.getPinyin("你好", false); Assert.assertEquals("ni hao", pinyin); } @Test - public void toPinyinUpperCaseTest(){ - final String pinyin = PinyinUtil.toPinyin("你好怡", true); + public void getPinyinUpperCaseTest(){ + final String pinyin = PinyinUtil.getPinyin("你好怡", true); Assert.assertEquals("NI HAO YI", pinyin); } + + @Test + public void getFirstLetterTest(){ + final char[] result = PinyinUtil.getFirstLetter("H是第一个", false); + Assert.assertEquals("h, s, d, y, g", ArrayUtil.join(result, ", ")); + } } From dc2460106f095308dd48e99568a1e8ccc537c7a8 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 5 May 2020 23:11:53 +0800 Subject: [PATCH 097/157] add method for extra --- CHANGELOG.md | 1 + .../java/cn/hutool/core/lang/Singleton.java | 44 ++++++++- .../java/cn/hutool/core/util/ArrayUtil.java | 54 ++++++++++ hutool-extra/pom.xml | 12 +++ .../cn/hutool/extra/pinyin/PinyinEngine.java | 53 ++++++++++ .../hutool/extra/pinyin/PinyinException.java | 33 +++++++ .../cn/hutool/extra/pinyin/PinyinUtil.java | 88 ++++++++--------- .../extra/pinyin/engine/PinyinFactory.java | 52 ++++++++++ .../pinyin/engine/jpinyin/JPinyinEngine.java | 64 ++++++++++++ .../pinyin/engine/jpinyin/package-info.java | 20 ++++ .../extra/pinyin/engine/package-info.java | 7 ++ .../engine/pinyin4j/Pinyin4jEngine.java | 92 ++++++++++++++++++ .../pinyin/engine/pinyin4j/package-info.java | 20 ++++ .../engine/tinypinyin/TinyPinyinEngine.java | 56 +++++++++++ .../engine/tinypinyin/package-info.java | 22 +++++ .../hutool/extra/template/TemplateUtil.java | 2 +- .../template/engine/TemplateFactory.java | 23 +++++ .../tokenizer/engine/TokenizerFactory.java | 13 +++ .../cn.hutool.extra.pinyin.PinyinEngine | 3 + .../hutool/extra/pinyin/PinyinUtilTest.java | 11 +-- .../main/java/cn/hutool/log/LogFactory.java | 14 +-- .../packages/access-bridge-64.pkc | Bin 0 -> 124 bytes .../.jython_cache/packages/ant-1.10.7.pkc | Bin 0 -> 13444 bytes .../packages/ant-antlr-1.10.7.pkc | Bin 0 -> 120 bytes .../packages/ant-junit-1.10.7.pkc | Bin 0 -> 615 bytes .../packages/ant-launcher-1.10.7.pkc | Bin 0 -> 150 bytes .../packages/apiguardian-api-1.1.0.pkc | Bin 0 -> 126 bytes .../.jython_cache/packages/charsets.pkc | Bin 0 -> 1447 bytes .../.jython_cache/packages/cldrdata.pkc | Bin 0 -> 30717 bytes .../.jython_cache/packages/deploy.pkc | Bin 0 -> 9914 bytes .../.jython_cache/packages/dnsns.pkc | Bin 0 -> 122 bytes .../.jython_cache/packages/groovy-3.0.2.pkc | Bin 0 -> 36733 bytes .../packages/groovy-ant-3.0.2.pkc | Bin 0 -> 428 bytes .../packages/groovy-astbuilder-3.0.2.pkc | Bin 0 -> 149 bytes .../packages/groovy-cli-picocli-3.0.2.pkc | Bin 0 -> 141 bytes .../packages/groovy-console-3.0.2.pkc | Bin 0 -> 2035 bytes .../packages/groovy-datetime-3.0.2.pkc | Bin 0 -> 172 bytes .../packages/groovy-docgenerator-3.0.2.pkc | Bin 0 -> 142 bytes .../packages/groovy-groovydoc-3.0.2.pkc | Bin 0 -> 1324 bytes .../packages/groovy-groovysh-3.0.2.pkc | Bin 0 -> 3068 bytes .../packages/groovy-jmx-3.0.2.pkc | Bin 0 -> 514 bytes .../packages/groovy-json-3.0.2.pkc | Bin 0 -> 856 bytes .../packages/groovy-jsr223-3.0.2.pkc | Bin 0 -> 223 bytes .../packages/groovy-macro-3.0.2.pkc | Bin 0 -> 641 bytes .../packages/groovy-nio-3.0.2.pkc | Bin 0 -> 232 bytes .../packages/groovy-servlet-3.0.2.pkc | Bin 0 -> 184 bytes .../packages/groovy-sql-3.0.2.pkc | Bin 0 -> 473 bytes .../packages/groovy-swing-3.0.2.pkc | Bin 0 -> 3207 bytes .../packages/groovy-templates-3.0.2.pkc | Bin 0 -> 517 bytes .../packages/groovy-test-3.0.2.pkc | Bin 0 -> 801 bytes .../packages/groovy-test-junit5-3.0.2.pkc | Bin 0 -> 154 bytes .../packages/groovy-testng-3.0.2.pkc | Bin 0 -> 173 bytes .../packages/groovy-xml-3.0.2.pkc | Bin 0 -> 1124 bytes .../packages/hamcrest-core-1.3.pkc | Bin 0 -> 662 bytes .../.jython_cache/packages/idea_rt.pkc | Bin 0 -> 896 bytes .../.jython_cache/packages/jaccess.pkc | Bin 0 -> 522 bytes .../packages/javaparser-core-3.15.13.pkc | Bin 0 -> 9908 bytes .../.jython_cache/packages/javaws.pkc | Bin 0 -> 3161 bytes hutool-script/.jython_cache/packages/jce.pkc | Bin 0 -> 1212 bytes .../packages/jcommander-1.72.pkc | Bin 0 -> 1098 bytes hutool-script/.jython_cache/packages/jfr.pkc | Bin 0 -> 2523 bytes .../.jython_cache/packages/jfxrt.pkc | Bin 0 -> 50868 bytes .../.jython_cache/packages/jfxswt.pkc | Bin 0 -> 146 bytes .../.jython_cache/packages/jline-2.14.6.pkc | Bin 0 -> 1146 bytes hutool-script/.jython_cache/packages/jsse.pkc | Bin 0 -> 1561 bytes .../.jython_cache/packages/junit-4.13.pkc | Bin 0 -> 4080 bytes .../packages/junit-jupiter-api-5.6.0.pkc | Bin 0 -> 2448 bytes .../packages/junit-jupiter-engine-5.6.0.pkc | Bin 0 -> 2343 bytes .../packages/junit-platform-commons-1.6.0.pkc | Bin 0 -> 891 bytes .../packages/junit-platform-engine-1.6.0.pkc | Bin 0 -> 2102 bytes .../junit-platform-launcher-1.6.0.pkc | Bin 0 -> 1404 bytes .../.jython_cache/packages/junit-rt.pkc | Bin 0 -> 589 bytes .../packages/junit-vintage-engine-5.6.0.pkc | Bin 0 -> 800 bytes .../.jython_cache/packages/junit5-rt.pkc | Bin 0 -> 201 bytes .../.jython_cache/packages/jython-2.7.2.pkc | Bin 0 -> 142290 bytes .../.jython_cache/packages/localedata.pkc | Bin 0 -> 10019 bytes .../.jython_cache/packages/lombok-1.18.12.pkc | Bin 0 -> 888 bytes .../.jython_cache/packages/luaj-jse-3.0.1.pkc | Bin 0 -> 1402 bytes .../packages/management-agent.pkc | Bin 0 -> 59 bytes .../.jython_cache/packages/nashorn.pkc | Bin 0 -> 8510 bytes .../packages/opentest4j-1.2.0.pkc | Bin 0 -> 233 bytes .../.jython_cache/packages/packages.idx | Bin 0 -> 11540 bytes .../.jython_cache/packages/picocli-4.1.4.pkc | Bin 0 -> 135 bytes .../.jython_cache/packages/plugin.pkc | Bin 0 -> 9613 bytes .../.jython_cache/packages/qdox-1.12.1.pkc | Bin 0 -> 1825 bytes .../.jython_cache/packages/resources.pkc | Bin 0 -> 52 bytes hutool-script/.jython_cache/packages/rt.pkc | Bin 0 -> 236602 bytes .../.jython_cache/packages/sunec.pkc | Bin 0 -> 218 bytes .../packages/sunjce_provider.pkc | Bin 0 -> 1433 bytes .../.jython_cache/packages/sunmscapi.pkc | Bin 0 -> 187 bytes .../.jython_cache/packages/sunpkcs11.pkc | Bin 0 -> 1235 bytes .../.jython_cache/packages/testng-6.14.3.pkc | Bin 0 -> 6494 bytes .../.jython_cache/packages/tools.pkc | Bin 0 -> 44815 bytes .../.jython_cache/packages/zipfs.pkc | Bin 0 -> 248 bytes 94 files changed, 616 insertions(+), 68 deletions(-) create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinEngine.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinException.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/PinyinFactory.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/jpinyin/JPinyinEngine.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/jpinyin/package-info.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/package-info.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/pinyin4j/Pinyin4jEngine.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/pinyin4j/package-info.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/tinypinyin/TinyPinyinEngine.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/tinypinyin/package-info.java create mode 100644 hutool-extra/src/main/resources/META-INF/services/cn.hutool.extra.pinyin.PinyinEngine create mode 100644 hutool-script/.jython_cache/packages/access-bridge-64.pkc create mode 100644 hutool-script/.jython_cache/packages/ant-1.10.7.pkc create mode 100644 hutool-script/.jython_cache/packages/ant-antlr-1.10.7.pkc create mode 100644 hutool-script/.jython_cache/packages/ant-junit-1.10.7.pkc create mode 100644 hutool-script/.jython_cache/packages/ant-launcher-1.10.7.pkc create mode 100644 hutool-script/.jython_cache/packages/apiguardian-api-1.1.0.pkc create mode 100644 hutool-script/.jython_cache/packages/charsets.pkc create mode 100644 hutool-script/.jython_cache/packages/cldrdata.pkc create mode 100644 hutool-script/.jython_cache/packages/deploy.pkc create mode 100644 hutool-script/.jython_cache/packages/dnsns.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-ant-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-astbuilder-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-cli-picocli-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-console-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-datetime-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-docgenerator-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-groovydoc-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-groovysh-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-jmx-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-json-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-jsr223-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-macro-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-nio-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-servlet-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-sql-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-swing-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-templates-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-test-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-test-junit5-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-testng-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/groovy-xml-3.0.2.pkc create mode 100644 hutool-script/.jython_cache/packages/hamcrest-core-1.3.pkc create mode 100644 hutool-script/.jython_cache/packages/idea_rt.pkc create mode 100644 hutool-script/.jython_cache/packages/jaccess.pkc create mode 100644 hutool-script/.jython_cache/packages/javaparser-core-3.15.13.pkc create mode 100644 hutool-script/.jython_cache/packages/javaws.pkc create mode 100644 hutool-script/.jython_cache/packages/jce.pkc create mode 100644 hutool-script/.jython_cache/packages/jcommander-1.72.pkc create mode 100644 hutool-script/.jython_cache/packages/jfr.pkc create mode 100644 hutool-script/.jython_cache/packages/jfxrt.pkc create mode 100644 hutool-script/.jython_cache/packages/jfxswt.pkc create mode 100644 hutool-script/.jython_cache/packages/jline-2.14.6.pkc create mode 100644 hutool-script/.jython_cache/packages/jsse.pkc create mode 100644 hutool-script/.jython_cache/packages/junit-4.13.pkc create mode 100644 hutool-script/.jython_cache/packages/junit-jupiter-api-5.6.0.pkc create mode 100644 hutool-script/.jython_cache/packages/junit-jupiter-engine-5.6.0.pkc create mode 100644 hutool-script/.jython_cache/packages/junit-platform-commons-1.6.0.pkc create mode 100644 hutool-script/.jython_cache/packages/junit-platform-engine-1.6.0.pkc create mode 100644 hutool-script/.jython_cache/packages/junit-platform-launcher-1.6.0.pkc create mode 100644 hutool-script/.jython_cache/packages/junit-rt.pkc create mode 100644 hutool-script/.jython_cache/packages/junit-vintage-engine-5.6.0.pkc create mode 100644 hutool-script/.jython_cache/packages/junit5-rt.pkc create mode 100644 hutool-script/.jython_cache/packages/jython-2.7.2.pkc create mode 100644 hutool-script/.jython_cache/packages/localedata.pkc create mode 100644 hutool-script/.jython_cache/packages/lombok-1.18.12.pkc create mode 100644 hutool-script/.jython_cache/packages/luaj-jse-3.0.1.pkc create mode 100644 hutool-script/.jython_cache/packages/management-agent.pkc create mode 100644 hutool-script/.jython_cache/packages/nashorn.pkc create mode 100644 hutool-script/.jython_cache/packages/opentest4j-1.2.0.pkc create mode 100644 hutool-script/.jython_cache/packages/packages.idx create mode 100644 hutool-script/.jython_cache/packages/picocli-4.1.4.pkc create mode 100644 hutool-script/.jython_cache/packages/plugin.pkc create mode 100644 hutool-script/.jython_cache/packages/qdox-1.12.1.pkc create mode 100644 hutool-script/.jython_cache/packages/resources.pkc create mode 100644 hutool-script/.jython_cache/packages/rt.pkc create mode 100644 hutool-script/.jython_cache/packages/sunec.pkc create mode 100644 hutool-script/.jython_cache/packages/sunjce_provider.pkc create mode 100644 hutool-script/.jython_cache/packages/sunmscapi.pkc create mode 100644 hutool-script/.jython_cache/packages/sunpkcs11.pkc create mode 100644 hutool-script/.jython_cache/packages/testng-6.14.3.pkc create mode 100644 hutool-script/.jython_cache/packages/tools.pkc create mode 100644 hutool-script/.jython_cache/packages/zipfs.pkc diff --git a/CHANGELOG.md b/CHANGELOG.md index a58e9a9e4..8b1226517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ * 【core 】 增加FastStringWriter * 【core 】 增加NumberUtil.ceilDiv方法(pr#858@Github) * 【core 】 IdcardUtil增加省份校验(issue#859@Github) +* 【extra 】 TemplateFactory和TokenizerFactory增加单例的get方法 ### Bug修复 * 【core 】 修复URLBuilder中请求参数有`&`导致的问题(issue#850@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java b/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java index c58d3b4d2..96ab8616f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java @@ -1,5 +1,6 @@ package cn.hutool.core.lang; +import cn.hutool.core.lang.func.Func0; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ReflectUtil; @@ -30,11 +31,26 @@ public final class Singleton { * @param params 构造方法参数 * @return 单例对象 */ - @SuppressWarnings("unchecked") public static T get(Class clazz, Object... params) { Assert.notNull(clazz, "Class must be not null !"); final String key = buildKey(clazz.getName(), params); - return (T) POOL.get(key, () -> ReflectUtil.newInstance(clazz, params)); + return get(key, () -> ReflectUtil.newInstance(clazz, params)); + } + + /** + * 获得指定类的单例对象
    + * 对象存在于池中返回,否则创建,每次调用此方法获得的对象为同一个对象
    + * 注意:单例针对的是类和对象,因此get方法第一次调用时创建的对象始终唯一,也就是说就算参数变更,返回的依旧是第一次创建的对象 + * + * @param 单例对象类型 + * @param key 自定义键 + * @param supplier 单例对象的创建函数 + * @return 单例对象 + * @since 5.3.3 + */ + @SuppressWarnings("unchecked") + public static T get(String key, Func0 supplier) { + return (T) POOL.get(key, supplier::call); } /** @@ -60,7 +76,18 @@ public final class Singleton { */ public static void put(Object obj) { Assert.notNull(obj, "Bean object must be not null !"); - POOL.put(obj.getClass().getName(), obj); + put(obj.getClass().getName(), obj); + } + + /** + * 将已有对象放入单例中,其Class做为键 + * + * @param key 键 + * @param obj 对象 + * @since 5.3.3 + */ + public static void put(String key, Object obj) { + POOL.put(key, obj); } /** @@ -70,10 +97,19 @@ public final class Singleton { */ public static void remove(Class clazz) { if (null != clazz) { - POOL.remove(clazz.getName()); + remove(clazz.getName()); } } + /** + * 移除指定Singleton对象 + * + * @param key 键 + */ + public static void remove(String key) { + POOL.remove(key); + } + /** * 清除所有Singleton对象 */ diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java index 1c011f6f7..4be5ea355 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java @@ -838,6 +838,27 @@ public class ArrayUtil { return list.toArray(Arrays.copyOf(array, list.size())); } + /** + * 编辑数组
    + * 编辑过程通过传入的Editor实现来返回需要的元素内容,这个Editor实现可以实现以下功能: + * + *
    +	 * 1、修改元素对象,返回集合中为修改后的对象
    +	 * 
    + * + * 注意:此方法会修改原数组! + * + * @param 数组元素类型 + * @param array 数组 + * @param editor 编辑器接口 + * @since 5.3.3 + */ + public static void edit(T[] array, Editor editor) { + for(int i = 0; i < array.length; i++){ + array[i] = editor.edit(array[i]); + } + } + /** * 过滤
    * 过滤过程通过传入的Filter实现来过滤返回需要的元素内容,这个Filter实现可以实现以下功能: @@ -2412,6 +2433,39 @@ public class ArrayUtil { return sb.toString(); } + /** + * 以 conjunction 为分隔符将数组转换为字符串 + * + * @param 被处理的集合 + * @param array 数组 + * @param conjunction 分隔符 + * @param editor 每个元素的编辑器,null表示不编辑 + * @return 连接后的字符串 + * @since 5.3.3 + */ + public static String join(T[] array, CharSequence conjunction, Editor editor) { + if (null == array) { + return null; + } + + final StringBuilder sb = new StringBuilder(); + boolean isFirst = true; + for (T item : array) { + if (isFirst) { + isFirst = false; + } else { + sb.append(conjunction); + } + if(null != editor){ + item = editor.edit(item); + } + if(null != item){ + sb.append(StrUtil.toString(item)); + } + } + return sb.toString(); + } + /** * 以 conjunction 为分隔符将数组转换为字符串 * diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index d368455aa..8ff91aa0a 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -220,6 +220,18 @@ 2.0.3.RELEASE true
    + + com.belerweb + pinyin4j + 2.5.1 + true + + + com.github.stuxuhai + jpinyin + 1.1.8 + true + org.springframework.boot spring-boot-starter-test diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinEngine.java new file mode 100644 index 000000000..6df9a8446 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinEngine.java @@ -0,0 +1,53 @@ +package cn.hutool.extra.pinyin; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; + +/** + * 拼音引擎接口,具体的拼音实现通过实现此接口,完成具体实现功能 + * + * @author looly + * @since 5.3.3 + */ +public interface PinyinEngine { + + /** + * 如果c为汉字,则返回大写拼音;如果c不是汉字,则返回String.valueOf(c) + * + * @param c 任意字符,汉字返回拼音,非汉字原样返回 + * @return 汉字返回拼音,非汉字原样返回 + */ + String getPinyin(char c); + + /** + * 获取字符串对应的完整拼音,非中文返回原字符 + * + * @param str 字符串 + * @param separator 拼音之间的分隔符 + * @return 拼音 + */ + String getPinyin(String str, String separator); + + /** + * 将输入字符串转为拼音首字母,其它字符原样返回 + * + * @param c 任意字符,汉字返回拼音,非汉字原样返回 + * @return 汉字返回拼音,非汉字原样返回 + */ + default char getFirstLetter(char c) { + return getPinyin(c).charAt(0); + } + + /** + * 将输入字符串转为拼音首字母,其它字符原样返回 + * + * @param str 任意字符,汉字返回拼音,非汉字原样返回 + * @param separator 分隔符 + * @return 汉字返回拼音,非汉字原样返回 + */ + default String getFirstLetter(String str, String separator) { + final String splitSeparator = StrUtil.isEmpty(separator) ? "#" : separator; + final String[] split = StrUtil.split(getPinyin(str, splitSeparator), splitSeparator); + return ArrayUtil.join(split, separator, (s)->String.valueOf(s.charAt(0))); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinException.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinException.java new file mode 100644 index 000000000..ff1f5063c --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinException.java @@ -0,0 +1,33 @@ +package cn.hutool.extra.pinyin; + +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.util.StrUtil; + +/** + * 模板异常 + * + * @author xiaoleilu + */ +public class PinyinException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public PinyinException(Throwable e) { + super(ExceptionUtil.getMessage(e), e); + } + + public PinyinException(String message) { + super(message); + } + + public PinyinException(String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params)); + } + + public PinyinException(String message, Throwable throwable) { + super(message, throwable); + } + + public PinyinException(Throwable throwable, String messageTemplate, Object... params) { + super(StrUtil.format(messageTemplate, params), throwable); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java index 5e394e6fa..9e59bdc45 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/PinyinUtil.java @@ -1,87 +1,85 @@ package cn.hutool.extra.pinyin; -import com.github.promeg.pinyinhelper.Pinyin; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.pinyin.engine.PinyinFactory; /** - * 拼音工具类,封装了TinyPinyin - * - *

    - * TinyPinyin(https://github.com/promeG/TinyPinyin)提供者未提交Maven中央库,
    - * 因此使用 - * https://github.com/biezhi/TinyPinyin打包的版本 - *

    - * - *

    - * 引入: - *

    - * <dependency>
    - *     <groupId>io.github.biezhi</groupId>
    - *     <artifactId>TinyPinyin</artifactId>
    - *     <version>2.0.3.RELEASE</version>
    - * </dependency>
    - * 
    + * 拼音工具类,封装了TinyPinyin、JPinyin、Pinyin4j,通过SPI自动识别。 * * @author looly */ public class PinyinUtil { + private static final String CHINESE_REGEX = "[\\u4e00-\\u9fa5]"; + /** - * 自定义拼音全局配置,例如加入自定义字典等 + * 获得全局单例的拼音引擎 * - * @param config 配置,通过Pinyin.newConfig().with(dict)添加字典 + * @return 全局单例的拼音引擎 */ - public static void init(Pinyin.Config config) { - Pinyin.init(config); + public static PinyinEngine getEngine(){ + return PinyinFactory.get(); } /** * 如果c为汉字,则返回大写拼音;如果c不是汉字,则返回String.valueOf(c) * - * @param c 任意字符,汉字返回拼音,非汉字原样返回 - * @param isToUpperCase 是否转换为大写 + * @param c 任意字符,汉字返回拼音,非汉字原样返回 * @return 汉字返回拼音,非汉字原样返回 */ - public static String getPinyin(char c, boolean isToUpperCase) { - final String pinyin = Pinyin.toPinyin(c); - return isToUpperCase ? pinyin : pinyin.toLowerCase(); + public static String getPinyin(char c) { + return getEngine().getPinyin(c); } /** * 将输入字符串转为拼音,每个字之间的拼音使用空格分隔 * - * @param str 任意字符,汉字返回拼音,非汉字原样返回 - * @param isToUpperCase 是否转换为大写 + * @param str 任意字符,汉字返回拼音,非汉字原样返回 * @return 汉字返回拼音,非汉字原样返回 */ - public static String getPinyin(String str, boolean isToUpperCase) { - return getPinyin(str, " ", isToUpperCase); + public static String getPinyin(String str) { + return getPinyin(str, StrUtil.SPACE); } /** * 将输入字符串转为拼音,以字符为单位插入分隔符 * - * @param str 任意字符,汉字返回拼音,非汉字原样返回 - * @param separator 每个字拼音之间的分隔符 - * @param isToUpperCase 是否转换为大写 + * @param str 任意字符,汉字返回拼音,非汉字原样返回 + * @param separator 每个字拼音之间的分隔符 * @return 汉字返回拼音,非汉字原样返回 */ - public static String getPinyin(String str, String separator, boolean isToUpperCase) { - final String pinyin = Pinyin.toPinyin(str, separator); - return isToUpperCase ? pinyin : pinyin.toLowerCase(); + public static String getPinyin(String str, String separator) { + return getEngine().getPinyin(str, separator); } /** * 将输入字符串转为拼音首字母,其它字符原样返回 * - * @param str 任意字符,汉字返回拼音,非汉字原样返回 - * @param isToUpperCase 是否转换为大写 + * @param c 任意字符,汉字返回拼音,非汉字原样返回 * @return 汉字返回拼音,非汉字原样返回 */ - public static char[] getFirstLetter(String str, boolean isToUpperCase) { - final char[] result = new char[str.length()]; - for(int i=0; i < result.length; i++){ - result[i] = getPinyin(str.charAt(i), isToUpperCase).charAt(0); - } - return result; + public static char getFirstLetter(char c) { + return getEngine().getFirstLetter(c); + } + + /** + * 将输入字符串转为拼音首字母,其它字符原样返回 + * + * @param str 任意字符,汉字返回拼音,非汉字原样返回 + * @param separator 分隔符 + * @return 汉字返回拼音,非汉字原样返回 + */ + public static String getFirstLetter(String str, String separator) { + return getEngine().getFirstLetter(str, separator); + } + + /** + * 是否为中文字符 + * + * @param c 字符 + * @return 是否为中文字符 + */ + public static boolean isChinese(char c) { + return '〇' == c || String.valueOf(c).matches(CHINESE_REGEX); } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/PinyinFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/PinyinFactory.java new file mode 100644 index 000000000..09d2228ba --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/PinyinFactory.java @@ -0,0 +1,52 @@ +package cn.hutool.extra.pinyin.engine; + +import cn.hutool.core.lang.Singleton; +import cn.hutool.core.util.ServiceLoaderUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.pinyin.PinyinEngine; +import cn.hutool.extra.template.TemplateException; +import cn.hutool.log.StaticLog; + +/** + * 简单拼音引擎工厂,用于根据用户引入的拼音库jar,自动创建对应的拼音引擎对象 + * + * @author looly + */ +public class PinyinFactory { + + /** + * 获得单例的PinyinEngine + * + * @return 单例的PinyinEngine + */ + public static PinyinEngine get(){ + return Singleton.get(PinyinEngine.class.getName(), PinyinFactory::create); + } + + /** + * 根据用户引入的拼音引擎jar,自动创建对应的拼音引擎对象
    + * 推荐创建的引擎单例使用,此方法每次调用会返回新的引擎 + * + * @return {@link PinyinEngine} + */ + public static PinyinEngine create() { + final PinyinEngine engine = doCreate(); + StaticLog.debug("Use [{}] Engine As Default.", StrUtil.removeSuffix(engine.getClass().getSimpleName(), "Engine")); + return engine; + } + + /** + * 根据用户引入的拼音引擎jar,自动创建对应的拼音引擎对象
    + * 推荐创建的引擎单例使用,此方法每次调用会返回新的引擎 + * + * @return {@link PinyinEngine} + */ + private static PinyinEngine doCreate() { + final PinyinEngine engine = ServiceLoaderUtil.loadFirstAvailable(PinyinEngine.class); + if(null != engine){ + return engine; + } + + throw new TemplateException("No template found ! Please add one of pinyin jar to your project !"); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/jpinyin/JPinyinEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/jpinyin/JPinyinEngine.java new file mode 100644 index 000000000..d17cfc1d0 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/jpinyin/JPinyinEngine.java @@ -0,0 +1,64 @@ +package cn.hutool.extra.pinyin.engine.jpinyin; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.extra.pinyin.PinyinEngine; +import com.github.stuxuhai.jpinyin.PinyinException; +import com.github.stuxuhai.jpinyin.PinyinFormat; +import com.github.stuxuhai.jpinyin.PinyinHelper; + +/** + * 封装了Jpinyin的引擎。 + * + *

    + * jpinyin(github库作者已删除)封装。 + *

    + * + *

    + * 引入: + *

    + * <dependency>
    + *     <groupId>com.github.stuxuhai</groupId>
    + *     <artifactId>jpinyin</artifactId>
    + *     <version>1.1.8</version>
    + * </dependency>
    + * 
    + * + * @author looly + */ +public class JPinyinEngine implements PinyinEngine { + + //设置汉子拼音输出的格式 + PinyinFormat format; + + public JPinyinEngine(){ + this(null); + } + + public JPinyinEngine(PinyinFormat format){ + init(format); + } + + public void init(PinyinFormat format){ + if(null == format){ + // 不加声调 + format = PinyinFormat.WITHOUT_TONE; + } + this.format = format; + } + + + @Override + public String getPinyin(char c) { + String[] results = PinyinHelper.convertToPinyinArray(c, format); + return ArrayUtil.isEmpty(results) ? String.valueOf(c) : results[0]; + } + + @Override + public String getPinyin(String str, String separator) { + try { + return PinyinHelper.convertToPinyinString(str, separator, format); + } catch (PinyinException e) { + throw new cn.hutool.extra.pinyin.PinyinException(e); + } + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/jpinyin/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/jpinyin/package-info.java new file mode 100644 index 000000000..308bffd4c --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/jpinyin/package-info.java @@ -0,0 +1,20 @@ +/** + * 封装了Jpinyin的引擎。 + * + *

    + * jpinyin(github库作者已删除)封装。 + *

    + * + *

    + * 引入: + *

    + * <dependency>
    + *     <groupId>com.github.stuxuhai</groupId>
    + *     <artifactId>jpinyin</artifactId>
    + *     <version>1.1.8</version>
    + * </dependency>
    + * 
    + * + * @author looly + */ +package cn.hutool.extra.pinyin.engine.jpinyin; \ No newline at end of file diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/package-info.java new file mode 100644 index 000000000..e87d6e71a --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/package-info.java @@ -0,0 +1,7 @@ +/** + * 拼音具体实现 + * + * @author looly + * + */ +package cn.hutool.extra.pinyin.engine; \ No newline at end of file diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/pinyin4j/Pinyin4jEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/pinyin4j/Pinyin4jEngine.java new file mode 100644 index 000000000..3de08d659 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/pinyin4j/Pinyin4jEngine.java @@ -0,0 +1,92 @@ +package cn.hutool.extra.pinyin.engine.pinyin4j; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.extra.pinyin.PinyinEngine; +import cn.hutool.extra.pinyin.PinyinException; +import net.sourceforge.pinyin4j.PinyinHelper; +import net.sourceforge.pinyin4j.format.HanyuPinyinCaseType; +import net.sourceforge.pinyin4j.format.HanyuPinyinOutputFormat; +import net.sourceforge.pinyin4j.format.HanyuPinyinToneType; +import net.sourceforge.pinyin4j.format.HanyuPinyinVCharType; +import net.sourceforge.pinyin4j.format.exception.BadHanyuPinyinOutputFormatCombination; + +/** + * 封装了Pinyin4j的引擎。 + * + *

    + * pinyin4j(http://sourceforge.net/projects/pinyin4j)封装。 + *

    + * + *

    + * 引入: + *

    + * <dependency>
    + *     <groupId>com.belerweb</groupId>
    + *     <artifactId>pinyin4j</artifactId>
    + *     <version>2.5.1</version>
    + * </dependency>
    + * 
    + * + * @author looly + */ +public class Pinyin4jEngine implements PinyinEngine { + + //设置汉子拼音输出的格式 + HanyuPinyinOutputFormat format; + + /** + * 构造 + */ + public Pinyin4jEngine() { + this(null); + } + + /** + * 构造 + * + * @param format 格式 + */ + public Pinyin4jEngine(HanyuPinyinOutputFormat format) { + init(format); + } + + /** + * 初始化 + * + * @param format 格式 + */ + public void init(HanyuPinyinOutputFormat format) { + if (null == format) { + format = new HanyuPinyinOutputFormat(); + // 小写 + format.setCaseType(HanyuPinyinCaseType.LOWERCASE); + // 不加声调 + format.setToneType(HanyuPinyinToneType.WITHOUT_TONE); + // 'ü' 使用 "v" 代替 + format.setVCharType(HanyuPinyinVCharType.WITH_V); + } + this.format = format; + } + + @Override + public String getPinyin(char c) { + String result; + try { + String[] results = PinyinHelper.toHanyuPinyinStringArray(c, format); + result = ArrayUtil.isEmpty(results) ? String.valueOf(c) : results[0]; + } catch (BadHanyuPinyinOutputFormatCombination e) { + result = String.valueOf(c); + } + return result; + } + + @Override + public String getPinyin(String str, String separator) { + try { + return PinyinHelper.toHanYuPinyinString(str, format, separator, true); + } catch (BadHanyuPinyinOutputFormatCombination e) { + throw new PinyinException(e); + } + } + +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/pinyin4j/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/pinyin4j/package-info.java new file mode 100644 index 000000000..baf1df160 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/pinyin4j/package-info.java @@ -0,0 +1,20 @@ +/** + * 封装了Pinyin4j的引擎。 + * + *

    + * pinyin4j(http://sourceforge.net/projects/pinyin4j)封装。 + *

    + * + *

    + * 引入: + *

    + * <dependency>
    + *     <groupId>com.belerweb</groupId>
    + *     <artifactId>pinyin4j</artifactId>
    + *     <version>2.5.1</version>
    + * </dependency>
    + * 
    + * + * @author looly + */ +package cn.hutool.extra.pinyin.engine.pinyin4j; \ No newline at end of file diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/tinypinyin/TinyPinyinEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/tinypinyin/TinyPinyinEngine.java new file mode 100644 index 000000000..8d1850951 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/tinypinyin/TinyPinyinEngine.java @@ -0,0 +1,56 @@ +package cn.hutool.extra.pinyin.engine.tinypinyin; + +import cn.hutool.extra.pinyin.PinyinEngine; +import com.github.promeg.pinyinhelper.Pinyin; + +/** + * 封装了TinyPinyin的引擎。 + * + *

    + * TinyPinyin(https://github.com/promeG/TinyPinyin)提供者未提交Maven中央库,
    + * 因此使用 + * https://github.com/biezhi/TinyPinyin打包的版本 + *

    + * + *

    + * 引入: + *

    + * <dependency>
    + *     <groupId>io.github.biezhi</groupId>
    + *     <artifactId>TinyPinyin</artifactId>
    + *     <version>2.0.3.RELEASE</version>
    + * </dependency>
    + * 
    + * + * @author looly + */ +public class TinyPinyinEngine implements PinyinEngine { + + /** + * 构造 + */ + public TinyPinyinEngine(){ + } + + /** + * 构造 + * @param config 配置 + */ + public TinyPinyinEngine(Pinyin.Config config){ + Pinyin.init(config); + } + + @Override + public String getPinyin(char c) { + if(false == Pinyin.isChinese(c)){ + return String.valueOf(c); + } + return Pinyin.toPinyin(c).toLowerCase(); + } + + @Override + public String getPinyin(String str, String separator) { + return Pinyin.toPinyin(str, separator).toLowerCase(); + } + +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/tinypinyin/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/tinypinyin/package-info.java new file mode 100644 index 000000000..01b947636 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/tinypinyin/package-info.java @@ -0,0 +1,22 @@ +/** + * 封装了TinyPinyin的引擎。 + * + *

    + * TinyPinyin(https://github.com/promeG/TinyPinyin)提供者未提交Maven中央库,
    + * 因此使用 + * https://github.com/biezhi/TinyPinyin打包的版本 + *

    + * + *

    + * 引入: + *

    + * <dependency>
    + *     <groupId>io.github.biezhi</groupId>
    + *     <artifactId>TinyPinyin</artifactId>
    + *     <version>2.0.3.RELEASE</version>
    + * </dependency>
    + * 
    + * + * @author looly + */ +package cn.hutool.extra.pinyin.engine.tinypinyin; \ No newline at end of file diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/TemplateUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/template/TemplateUtil.java index bb0cc8354..d5311a987 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/TemplateUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/TemplateUtil.java @@ -18,7 +18,7 @@ public class TemplateUtil { * @since 4.1.11 */ public static TemplateEngine createEngine() { - return createEngine(new TemplateConfig()); + return TemplateFactory.create(); } /** diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/TemplateFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/TemplateFactory.java index f04207c92..9c7e4ed25 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/TemplateFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/TemplateFactory.java @@ -1,5 +1,6 @@ package cn.hutool.extra.template.engine; +import cn.hutool.core.lang.Singleton; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.ServiceLoaderUtil; import cn.hutool.core.util.StrUtil; @@ -14,6 +15,28 @@ import cn.hutool.log.StaticLog; * @author looly */ public class TemplateFactory { + + /** + * 根据用户引入的模板引擎jar,自动创建对应的模板引擎对象
    + * 获得的是单例的TemplateEngine + * + * @return 单例的TemplateEngine + */ + public static TemplateEngine get(){ + return Singleton.get(TemplateEngine.class.getName(), TemplateFactory::create); + } + + /** + * 根据用户引入的模板引擎jar,自动创建对应的模板引擎对象
    + * 推荐创建的引擎单例使用,此方法每次调用会返回新的引擎 + * + * @return {@link TemplateEngine} + * @since 5.3.3 + */ + public static TemplateEngine create() { + return create(new TemplateConfig()); + } + /** * 根据用户引入的模板引擎jar,自动创建对应的模板引擎对象
    * 推荐创建的引擎单例使用,此方法每次调用会返回新的引擎 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/TokenizerFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/TokenizerFactory.java index b8c800f12..9c2a059aa 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/TokenizerFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/tokenizer/engine/TokenizerFactory.java @@ -1,5 +1,6 @@ package cn.hutool.extra.tokenizer.engine; +import cn.hutool.core.lang.Singleton; import cn.hutool.core.util.ServiceLoaderUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.tokenizer.TokenizerEngine; @@ -13,6 +14,18 @@ import cn.hutool.log.StaticLog; * */ public class TokenizerFactory { + + /** + * 根据用户引入的模板引擎jar,自动创建对应的分词引擎对象
    + * 获得的是单例的TokenizerEngine + * + * @return 单例的TokenizerEngine + * @since 5.3.3 + */ + public static TokenizerEngine get(){ + return Singleton.get(TokenizerEngine.class.getName(), TokenizerFactory::create); + } + /** * 根据用户引入的分词引擎jar,自动创建对应的分词引擎对象 * diff --git a/hutool-extra/src/main/resources/META-INF/services/cn.hutool.extra.pinyin.PinyinEngine b/hutool-extra/src/main/resources/META-INF/services/cn.hutool.extra.pinyin.PinyinEngine new file mode 100644 index 000000000..5526a0a33 --- /dev/null +++ b/hutool-extra/src/main/resources/META-INF/services/cn.hutool.extra.pinyin.PinyinEngine @@ -0,0 +1,3 @@ +cn.hutool.extra.pinyin.engine.tinypinyin.TinyPinyinEngine +cn.hutool.extra.pinyin.engine.jpinyin.JPinyinEngine +cn.hutool.extra.pinyin.engine.pinyin4j.Pinyin4jEngine \ No newline at end of file diff --git a/hutool-extra/src/test/java/cn/hutool/extra/pinyin/PinyinUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/pinyin/PinyinUtilTest.java index a8e72e830..ef2164245 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/pinyin/PinyinUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/pinyin/PinyinUtilTest.java @@ -1,6 +1,5 @@ package cn.hutool.extra.pinyin; -import cn.hutool.core.util.ArrayUtil; import org.junit.Assert; import org.junit.Test; @@ -8,19 +7,19 @@ public class PinyinUtilTest { @Test public void getPinyinTest(){ - final String pinyin = PinyinUtil.getPinyin("你好", false); + final String pinyin = PinyinUtil.getPinyin("你好", " "); Assert.assertEquals("ni hao", pinyin); } @Test public void getPinyinUpperCaseTest(){ - final String pinyin = PinyinUtil.getPinyin("你好怡", true); - Assert.assertEquals("NI HAO YI", pinyin); + final String pinyin = PinyinUtil.getPinyin("你好怡", " "); + Assert.assertEquals("ni hao yi", pinyin); } @Test public void getFirstLetterTest(){ - final char[] result = PinyinUtil.getFirstLetter("H是第一个", false); - Assert.assertEquals("h, s, d, y, g", ArrayUtil.join(result, ", ")); + final String result = PinyinUtil.getFirstLetter("H是第一个", ", "); + Assert.assertEquals("h, s, d, y, g", result); } } diff --git a/hutool-log/src/main/java/cn/hutool/log/LogFactory.java b/hutool-log/src/main/java/cn/hutool/log/LogFactory.java index 1730230a1..b374fa4d6 100644 --- a/hutool-log/src/main/java/cn/hutool/log/LogFactory.java +++ b/hutool-log/src/main/java/cn/hutool/log/LogFactory.java @@ -53,12 +53,7 @@ public abstract class LogFactory { * @return 日志对象 */ public Log getLog(String name) { - Log log = logCache.get(name); - if (null == log) { - log = createLog(name); - logCache.put(name, log); - } - return log; + return logCache.computeIfAbsent(name, o -> createLog((String)o)); } /** @@ -68,12 +63,7 @@ public abstract class LogFactory { * @return 日志对象 */ public Log getLog(Class clazz) { - Log log = logCache.get(clazz); - if (null == log) { - log = createLog(clazz); - logCache.put(clazz, log); - } - return log; + return logCache.computeIfAbsent(clazz, o -> createLog((Class)o)); } /** diff --git a/hutool-script/.jython_cache/packages/access-bridge-64.pkc b/hutool-script/.jython_cache/packages/access-bridge-64.pkc new file mode 100644 index 0000000000000000000000000000000000000000..0e514aee62106ee23ba976a14ef54e695be6fed4 GIT binary patch literal 124 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 literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/ant-antlr-1.10.7.pkc b/hutool-script/.jython_cache/packages/ant-antlr-1.10.7.pkc new file mode 100644 index 0000000000000000000000000000000000000000..df43bbfbff50104ed4a9e80ac0456b57857bb053 GIT binary patch literal 120 zcmZRucd?4eHHt4vEy$0_FG`O|EJ#ewNR3I%D*<6$AkHa@G1N0O&@)E}=t2bavJ#6J z7#Iu1D*kV0Py_1LgXq>P$(i6EpL4e88rjK4N4D}544A4b&!J>LuiA4+y pjCnt#-Ul);Fz`6!=BJeAr0QnorR6gS0}a+gG8n{WV0H}f1OVZZDt!O| literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/charsets.pkc b/hutool-script/.jython_cache/packages/charsets.pkc new file mode 100644 index 0000000000000000000000000000000000000000..0b5b3700c0c16ac001ae88d163c1b8c186b697f8 GIT binary patch literal 1447 zcmZWp&2HL25T=Kyw?0E}Z6ynNcm1>5Q-eVXP!bdqg#(hcz$zFV8yVAtXY0##c4i%j zdYSK=@%-)VdOAP)KNlnYM=!SP9qY=jpK+fpwzXLl`Es$^==xxqLwBp|PUqA1@Aq$C zJ6{gRvRmd=cXjBRx8~}hmp_M|71`}LP0|Yr$$Un7S{1s^4_?1IE(_B;HRgg2%2l;C zwKsT7vg8h7G)rI2^o0*N_Nb#_Z{myC z17N-%5%SQX(}NbBpK4K}A7h-5wFs4jE$SU`1H;OYd5;v?!u zL=-0^Qklj3^+&lgO9?VhV`SMKN?y9F2;4QE+E)MT)8`L7sGPN?QUKt(quSPP&&QmKIityohfMPrFzqQ*WgXIexqpy zow#^-i$F6eLzOX1OB`m?nqhV=mELHWULTvPZyK{|td`9)AO5>DMIW-tY?6l1<^19+ zn3Uj`hW7Vrlfi2})BiFsp1lWD8@==87` z1v&Qf@_DHnYrd(C*;#UHmbH1cY`8LoF4vYmR4){bB-hrKo?ez!VcBe>bHro4%MTk% zA9uCRODLW)9)<)FfdsJg@EtNh83!QkNKnEsnLuh|;2l^AuK=Z6QWrd%PvMihOt|A| zGWOE^WnUQBN9}4#VVs^7lzavx;{LC`Da^BOH0-p#IWMu<(?sw0FdkY!b`5?od{Cf% StGgyTpY4v-uTAF)7uY|Nk&-h2 literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/cldrdata.pkc b/hutool-script/.jython_cache/packages/cldrdata.pkc new file mode 100644 index 0000000000000000000000000000000000000000..bd6e66e5d6147ca8ac7e77e6577e5d19c2cb6ae7 GIT binary patch literal 30717 zcmaJ~%W`B%Qq8hqh1tvk%y^pd8Utg|)J)F|7|aTlq*kd~QdgH$)l^#hHTxw~J9Ts=DgsUDXT*FYf;9FaP-M;1Al(>Sc$Y zUp95S-ZZy$`_c-&oGb=E{$kxMtL_AK0?5ATrkgFa=(HXjA6JWdHL9Av@U#x+7oQz( znxh0N%cIYdA8y90N&f?V6spK)S|e7L24cnHkKY$6uym#@orUu&=}TG# zDJ-28#JgefZVE*oxah-{sX3)-*5koXBCRpn)v)iU)Go+gSh;rR)sWgX+E>R$dDn#3 z@0529No3k6@0vmf8|`2dUcc84Hr&BRJJ|TT**z}@za|v~@`G-Y3gQ>)$m;s6c^dqJ z+MPayqe~t66E!$}@x3vu^N#`kHm&_%$zafX;8^ zi)I}S2c)Q9SCCh$`rU?eo$IrfJ7~3o)}QDPn-xuTqg|`8s3ug{Xjj#qr!W48*!4UO zud7wt>s##Q?D5xMGB5Y|@|09<`yznu~!jc^F0x#`oo53?YSCCAXi^bBi+6a(F zW!2nSzFJPCO(5Y|+XP5$S~Y*(*F5iQUy<)BnS*y#c=J{ycvn&1AenJ!#GO{7>Vc+} z(f+`Bah&ej&AIy>7L7lrDFP&%staeu{8_9%+VduS3%{O~ntilq_0gVp%R==(_{Tz?e7RO_15Ow_abbm6Qg)h#9 zy<=XFL9%X*XFe0!$6H|nkZ`On5(zpq)-~+}Bxj|o~5T=Ee zdXqvp%#c#YatwjyP5-r2+T*b}7*jL|2sd#yuLg^qbQ|8z~lD3r&!aRN{^)3U!DU(uWxdpmO<#>~h>!W() z<3^_xG%m5a5UnnB5Vr}Er(+XLAc`5U6$7(lR}8}tL!JT{gFo+P>GjKkZ`HTAOMLYR?Q#qLOHSsr@FVDET}<{!jVO3&}}m4Ha+fcYu-PD_S>A-l^nkT z3CAB~vIC@X8_ff^hTIu|-9oIZAbl=A4E~hG{s6I4=aZ6Js((M(qzB{^_U}RRw@h}Y z-nmT(j-&a6=KE>7f!Lo}XB$|npY{DZ<7WY+j|S~av3t~u5FqJU6hLazs=0!|PHaMg zm{!}P<}>jb_P8#D2V7mqDeDqCauo+VtypnDy5k+GK^j%zL=QMd+%-s+JQ{I3=vt9{ z*QSz3A-@5=fascVlk8skoV(K{wA-ENkZjQv7hP?6mo3WM zgZX+pF|$Qs72}I9vy!f(V$(4$e3+iumLGY%g)`wfTVIg9T4Y;g|6^X*iO&}+S&e{% zFVzJANFuaqe)n_!sNFt1XGTZQG8;$F;gveZDCEw7&0Lo2%{;tU2YEB6!GmPwLnD3% zjhwG8OYNIx60YFNh1q-m~h=CwoUPBJCY2Aw@$LqS5FRAOGs{kO|s?Zjp z=GhC9zXa|2!{bS|C`__p^F7TQqkXA75B{C3+~zqPHPtX2$(|7S@l&%1@8J}Z$ey>$ zpyBkjyM|a{_!=!=q;Jn(+*g$@?m)toswzP0+N!x+s23{HML7P*A6hTMnXXPXyRRTc zqC59BYg@49#O7@d+7~}B{ENcEU$1`S|42ghDjdP@j)i8BoD2YZ^(rZ)3!asdQwD=% zH*b)r2_QYF0d&$fU4OYPgM){w<2_5qd!`avC6`0!!**fPyoHDe^d|kPOL6Sb)}@@o z7$jbTLFrIUJ*CI1ltXXR9?cv|MZFk9k2mQc-lrdAQ3TS~`|I%btLyWBNPhSr4huT5 zdjYBbt=_HGE3pEoXPQQq0hp;;g6WZpl}kJ5RZHt8M>(jQT;~BZF>76upkj$RL0uD= zmNSVZn66-`n_LtEGofi+@Hlqxz)mLn8KXvKl>o+bu|)m+OE8;&X-Q1Kq$K8Ff`P*c zhUu1KS1AT=Cgq^)2?ow37`T*T-xRDThh{Byp;@`L{h`u$Hz3;;?m`Z9?S~K548Q8K zfL}zk`$kBdRxB3pzEEMES`U5_*Qg6_z+&@6>x{#_HUY7T+e%0=6H$Wc#hA%0)1FC5>zbGpGYKeWB2dg^pqL3kF_S`qrA+DNkdy;I z6-#3L@V&-&EeFPv3E$iY|^%D_l@@V6?_-7*d40o_A4D0n|Qxsw9CeEHTY+;E8{BJQ35*G`8=*f z;kWf-g+~!4tzqUrdUx3~Z>RJbM!W1;a`+%fS3QSMD~ZMdl1AfLF_&x2TDGChI$X*f zD4=(0@fr>p0LlD7yZ$G(6d15<58!( z-jf)Ob_=h-^jLL`b_*{ytmj6%5*zB9jv)?-)o2g-+y^9!2aUKLG@HRc(L`!CsY-0Z zJK1ugRC0LNu+>UPtHA?9=kCCU6Y&^_lkiGUyoP*f1Cl#HBW?%Hqbk>9c&FRJhK)Vy zxN;rO7Ni75yM>n%wh~6WBONYfq{C%;4LOvE$imtCk}vZ?y5k&4AivT6IM;(y(xu@# ziPi;_JfIn}c^Q`G!)h?)E?^PFf(>ydqYK`UjYL9P4N;wf9UW>|^$>PU-L)Y*P9UX& z!qwHVQHeLf;(f>y8S=q3>bR0=YmrP__i=);XAbB}4S&m)1 z-0~c53u|os$ZM?8e&_4Kf09jU*L|24O^$Uql^r%p+3lN@x=yChS`~ZSIxG9OdBxCv z?25Lfk7!=}K92?QXLg@rc8SKg!!LF0JN%s-H?+<6bSl%%_LXMZPLEqV{5?$q{4hc( z*>}m`&c<#xDxDj8ceg9%%l=lQ7;dX{J9f)E0+2i<(TJ~H--#R3eix4PG@|8gDoF02 zEo@sKD?RxFV%yeObF7d*#BT7BRiq0Sss&won$~tI(7s;WZ(H)PabXra&28xdt!)Q? z!(+tJl3Z}KnfMUY^S}E+c0=t$x+p$`Ydv#mIcyK4NUQMtV#OOikpA!W~Wy*&ezth$}< zygeC&dui4lI)$)zCtWmAr_<%Sk+Gp-#3*d zr<;Zwy+`ag(iSAkt1F}dSl0ZE%4q*fIPPUt*jC-@v-sO5i$!HO;+CJqqmD1=wK8HI zZqz^Pc=HKT7*TM#x|LiyA!|kQx($Wg5#H;L;C>O_s&{bDI|7hALNwxXf6eO+NY`$g zfr{5-qg^9t5BfR89Y}fr50GYXF%Q6fE1TYXT=3|&@Sg1q>iEp3i0uuUXVNN(cIYi? z-?Fa;maQdLgG2U~+_H%bDuX|Z9(q}Z6(;*EECv1Kh|%@#8E>vHd&wUU%xG8kRW8|qvd65kR1H$DSL$l)nj+EkZTX)GHI0M+K)N%_UhneE0+Pmg0wmEFAN}6* z<2-0hB&4lA_?#SzhgOaIU^rsiI5m6KShROrFIHC+VO_1pzFt~Fm00{nySB`dlf;FM z_P=m0cBh*6;g&35_U5XBZM65SEI}aVlEvQ+NIw4X3^wPO!&l(vKh-j$seA-nEu{xE z;&RMhamO4xyz0(P#rtaTC$R=qq))hsLx5+2-_47bXI&GHoAX?{DXG>S5VyVqG%YwRiSNnqT+;3D+kyTda^j zf1d~c%KX`d5+A9$>_UkR=gIw?0R0Ap#!zu=akcG4*@{&pbgw*#baw`y+pSS{iw z=U7c5o7As1KHn@sLCiO!e|*S$XPNf~zjJ(X@@{LgEt}qD-BstS{cHIgdsmCW|B@cA z7U4*&3?L1826UX3x{{zWl)Bcyq18gvcGn^4_O5xw&{(`2p(!S9m~27td9E5-R)UzF z#ViHb0`gk6LhP$<%T}xiAZ0YW)Bl_x?8PxF8<5n60!SZyc&MDyb;|}5f8ilJOl|-; z))SM~qz)1ax%{+zHgRvo{5F=wG{DSkpkd^THAf#F}~m~MRG)>L?p*Pv|{kPPO{p;Z~jo! zAC0}Njz?p(D>$Uc{f{3S4RC9TvK;{f@bnpgQ_leWcLw09GXO_jfR6&eBj*wy3+J)e zEwO)kzuh7APZ8q@4E$_^sgj)D-|G>?t_8BAC=~12z6ehejK$|QA5!c+{M;m2;VPO_ zg8|7baVX|$r6qTW_27`~A*YpyFpRYwq&tBp8%? z{LUY_^GE93?lcRXuOw~>Po6dTl0h05D374`k8N|Bg}Y~-|Ru}Sl>E!eXyJ?F%mW%kAwxW(5q&;_3}gJiRUM*2k|GvL9m z;>DEpCZ2%YVnHPybi5DYLi49A_;3`YjJI$_j|i?IX>;OdAn8&AFqTR%nt5jpxnoBtOgx}{WK5QM-%(M-xVWvh z&EO)oSmXsD#%CYsL}j)yL@5+5cLparC>9gKN8EMB-c$2-2T1A^0wjhOpJ5}oFJ@mU z)(_!tRJ62G@Z!0*TA9iE61!Z^OpZ*cma2`cy(CI4g&88elC8^fk?P!%4_HAYXF$@IB8PW7Sr+JyY?;}SEqO-(p(ggaTGiY z%l;C37R78Z-HaSHgu6sK*T=~C78+p~u{8qe&RBcz!ZQ{~>YoFo?=C)*!4n2CGf&}C zpW-h$as;F>vha=#L00kifpovm-hJ>w3nU~D`2o`R8O`IdhTJ6)@i{c0!Xq;z*@}zq zb8h99KV-X#i-L4N(_XBw7=fg53;^jfiw|88wZ&pw4KpXhLuzm}6t?X{zp~%Q%X=w% z*s7K(+=t=u6b5vqnYd_9HSJyC;5_H+EvpIr8$g+CBT#lt`OhiluX;9LTU4+PLQ@N}L_Aen&xv8c=STX>hK6Ar-3ye=zw%(i9{B$od)U76?^)^sstn* zDL@+O;l)gLq?|2k{~SGAboF8u^O=s5k)#HRWawwRcNE|o1?bEdZ6G-hSLw6PFoZZ# z1~|PtT0vqNE`eiM29Vr`0r-pzAgzx8I##5h;S!vWew((3OB`}2F^oV{0lx9Iz+NP? z763_IYk;H$#fQw6O(9+$jCM5^?s%-RAl>D`@+ZXY8ttmLz02kg1Cm-wfb?O9=iIr? zk3HGv4HOW|y2-0{@nt_!A;#ak&y1LxB1mc&7v1ML%oc5EO+l{0NCo^_3_#2X#%`Gva$B%yeI6B%)a3w3ZCbUy%^Z{M8BHt^qx%VzWwSm;ozF7e^J7%x z6ZP9$sMwHyP(#n>yhkZzxSh`juZhF+`Sx7yOO3Xg^T`QFnOB9_N_%R+VgQoz0U(LN zs=1wu2`iNqV&AL8CpR@A-QU+YOC_zXSF|Tm7XHNuK`an|Y0~Fa67N&7C_&PG8 z_)`U<#2>Lg?4Wgb`==gU<>Gxo{*RJxeg3HV*5`%X!br>$Gi#rxCnq@j(@$m=kaT4P zNX#n!S1A6ADDEDlJFD!!LAiS%eFQI=ae&mlRVx@wegbfK)|#`fNYWGUzmV1^mXf6l U@}-14MZD==WKza2Bu;bk{|EpmR{#J2 literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/deploy.pkc b/hutool-script/.jython_cache/packages/deploy.pkc new file mode 100644 index 0000000000000000000000000000000000000000..880926b2be269df56fe5884ae02ecfe578116ba7 GIT binary patch literal 9914 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 literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/dnsns.pkc b/hutool-script/.jython_cache/packages/dnsns.pkc new file mode 100644 index 0000000000000000000000000000000000000000..1e271703213324e46d288a4d96a126be491d2135 GIT binary patch literal 122 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 D&hsK% literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/groovy-ant-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-ant-3.0.2.pkc new file mode 100644 index 0000000000000000000000000000000000000000..7930855e22edf4f2a6ba1ba00dee9aadcddb98ed GIT binary patch literal 428 zcmb7A!A`?43=LPf!$*)sXxumft2&S>gjV2G31z7lGSav>E~0$>&*+tdNp9xy5kZfhJh8Nw%$`ayxKf+80uFln&nokN(z}KDgHIU4mhdbRs?m{Xr|Cj=eW+ZWvyw%=YO;kS9>^y#3BX;#zL`*|7#dzfd=X&79=KTr0PM8(*v5L2Q`R6!m+r-31VnSQDRl_x$yXo9&y(;S83D?SL3DXzJ z^W^jZJSoBCn%O8i`gB}8`W8K@w3Avb@#4YLmczEyYX>&oF5l911NLzbn~0Hx8CXyp zT#yYynNGPC23t8ZSc&h#m%<6uHg0=Ft%mjtX3R?*gUZR!7SG@YU+0x*fe@nYXeogQ z<&It&Az`#&4w`#9Rp4}1642r(Xw_`{t70VmT&M=v^}JR|y)oKiBeCN_EpRDd9d|*2 zWe9n@cB{2!7P@5;I+8hep&5k|SI9ZJNnaqMCmi`+j42kI$HNcT6idVN(4+B~S*F}f z+og*u4l|uufIwdpWx5t>-hfp;(!wDuD+_%^H*qk5q(-Xkh0IASm>HVjI}x)vy%M+> zSxqU2eq_v<8j7T5_EMTJchv^m`^n!m>xp`4)^bf;dfWfK?Xj~7RL`!f7XkHlSv_x^PLgJIlE!uuGy F-vQ&*4R!zk literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/groovy-datetime-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-datetime-3.0.2.pkc new file mode 100644 index 0000000000000000000000000000000000000000..faa763afcbe20ff4634ada1994e2b29bdc297a11 GIT binary patch literal 172 zcmZSZbFqraHHt4vEy$0_FG`O|&QD3rNGvUmNiWLJFRO%7x+#ezsU?}YsWHZS26{&5 zqPk#Fy{yC{1_s7Lu}T&j234S8dWi*z$r-775To>9X6U6>l%(brXXfV>GibX24GsYs S>8W|CMTsT(MKQ*D y26{%=WOTtYdRd7@3=E8gVwD`M401q2^%4sblQUBFAV%sTo5jH6lAr7jGZ_FwH8L## literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/groovy-groovydoc-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-groovydoc-3.0.2.pkc new file mode 100644 index 0000000000000000000000000000000000000000..471cf6578ebae7a02d8757e052f9d102b0bc1c31 GIT binary patch literal 1324 zcma)6O>fgc5M4NML*fFQI3PHo)Iy~vZlO(4Q6r)>&`TsV-AtV&>m4(@u9~00oxh62 z_+uU02~tn?d!BhSJ8#x%cJ^_Z9sgj=X-S+eHLY+BgSfJUVcd4C&zpA zQAH;|ft7mn>&5oTAN5+~639WXF{#s=ho~fh$a?kkb5-Pw^tWKFdbytgE?8F2>NrXK zZoCP^>ZA;fy+-*;v{NMYfsIQSnB^7%diA`m7r6^}OX|%7#2)`1GsE`Og7?zzHA9{w zk1Cu)SkKW@!5XLFAe9f3`+)4g))iyg=-gQRT7S@hxk+&yAtx!xXqZ;B10EOQ!s8cCB5mi*?ArId1Yj{ zSeF^!$iP7dxUo2~SjtJ2Q7^gMCgSKxjXQ2cBKpwaJqKz&&b~(F)SEY%_djJq{mcLW literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/groovy-groovysh-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-groovysh-3.0.2.pkc new file mode 100644 index 0000000000000000000000000000000000000000..17b66a2eb707d8b292cb7154f829a2bce7019970 GIT binary patch literal 3068 zcmeHJ&5qMB5KfOAxFW;_A$|lX8=(aWiCecVOT})f8U!3Fq1j}b7#us;PPf@d<4t%Q z#z~xR+ilf700MD}XFMLyd^6v~-l+d-njL;tn9HeD$uyENrcmf&=KU= ztG7`3w8}ulBgif*3vFc9BYPc|!oXr_|C(`JH9oE~ShpL-C=8o1RLir&ESJh`xEzWZ zTfD{cT&j58%@*|t<}86Tj$Zx(|=KQxL(231!jMMiNi z&wT*J>^*C7d3SfcucrMf(~YP!5Eab;1`OQJy{S^BB^ma!3AyfaRvpq zaoFPM2_{S%17(92U0fRe2{YVQyXd z_AlMwJ+&~pIpD}^q~to#HjrPyUjkw;{*v7Wf35Bc{#x}m{?gu6{3Z4E_`AZ@4R({e zYw}kr@9`esSJdmg?Aktcy;}+IK_zHt^el+HRm1r`j$kL9J>&P8&N;MueHpo(LChSf ztCn>7V+Ch!ZCTu|iS|;pp6UX)%~rBgzG_*W6Ytt>aakm-H6yT2B*e~UTu>%Xm|#Q` zcq!SLQ;qK`sSxI^Wr*!9VG6N4cZb5XUDqRWM}PmH75mSMd)r5D0K#zf!n?ULd%QZ|vScS%o8{5h>M8ts|BcZG_B_oN8>sIZn@-+0{wrtud_Rs$8 z&x%+spOl+@66C0mGL_;EzI9osj1a44e5QL>tH<~uo&{jqgLtn=h{^9=6a9(XK^3>{ z@wPN}2Q>M4b@ekdnQh6i_FpKq*BQ`#%jckdQs+qgqEkj{4)7wqA}hj{VO1|4!lX_i zXM;XqvOUepZe;*Bh_>kRg*D)Lf!;&sB$%E!!whGnYFkI#X_Fv7F6iN#DH!~4>>&Uo0U}!UU)UQG(O4+i*t5z%^X9;5 literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/groovy-json-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-json-3.0.2.pkc new file mode 100644 index 0000000000000000000000000000000000000000..2df0e66629e90b525560f12fbe755dea8c72b853 GIT binary patch literal 856 zcmZ`%!BX2W5H;s==E@HkPX}&2(Zr+!6AFVvPo816%903VNvxzy+-pCmf79=&v@&2q zIaqr8v~PFytYTJwX}jY)LTg$>t5%pLt|7Q~MaFFUVLN(oMz<%`=jwRO95GWp02RgF z(?P%gr}&h0RT30`YrKb`{6!4Dp+*94s7%o$NVUX_YgD)bk9=^p6==OQnxnU(Ic^8Z zRq!@=PPqnh1DoNZx4BfK0@>kcz*7azNx{N}dBog>5z87+2%Xfc@r;p-%$XBl@zzV_ zio@ts1s22_tI>TcsXdYgwfFlg5%io~PQYO*Hw-%IJvIV#bfcW63VG%B(>2f}+?Q#8 z$w?TW0&0OjK!une84$5kV*-g3Q&~sE^2Z91Y5tutp?hakB qqwYIJHl(V=vb5eg$rn1QIoarU3`riD9ILQ@>GsDQlpTPFF-=B|L@_)KT{bchoU?|9}tVWmp&m}J&?{O~R4OoMY77ax^V-ck}U z%GBKAJb^OaB|SK^%@PRumYk431H;caExod4!o#kWsW4I@J$R*^IQdty;WWe@|jV#?37?Vm|G+9o)0!RhX~p&|4V_o4Lu*3|fEZ zd0Fe_G1CpC-F3)KJ)GK7g7-L A5C8xG literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/groovy-nio-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-nio-3.0.2.pkc new file mode 100644 index 0000000000000000000000000000000000000000..cd2e527921a433168fcc5655147985a3975c4290 GIT binary patch literal 232 zcmZQ@aj}ZYHHt4vEy$0_FG`O|&QD3rNGvUmNiWLJFRO%7x_O!TF~)iZdPYbBx?lml zti&P)2F60MO8#mFX`m5$FeCIJM(7ol=9OgTrZUL-W#+qsP4!JJ$;eMB)(J1lEJ;kt yNexIW$zV_b=}jz1OwLG!=>*!Ums(Mhnpd2epI6Ml3pBtL!Ij0X0cIT!vgrU|K~q8i literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/groovy-servlet-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-servlet-3.0.2.pkc new file mode 100644 index 0000000000000000000000000000000000000000..622b329986864a3dc671c05374ebe5a7ab999a95 GIT binary patch literal 184 zcmZSZaj}ZYHHt4vEy$0_FG`O|&QD3rNGvUmNiWLJFRO%7y2YtQWjU!OF~)iZdPZo1 zx?n-Qti&P)2F60MO8##Qd=TAwP;CqWj!DHOMTyBJ9wj9O!4R`_+`%@$cu?9YGcP4G aFI@-9bxtfvP0uf?)Coz=Eyw|KpyB{CYD8QB literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/groovy-sql-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-sql-3.0.2.pkc new file mode 100644 index 0000000000000000000000000000000000000000..b17277838d66261352911f58295764e3ffe5e1ee GIT binary patch literal 473 zcmZWl!A^rf5T#cW4_-X`0d!;P&68FdL!v2>q`i=s5hk!^*@f9b;jjEe7b?)!!)ABh zdo%OidO>iXl&g2b$|Q#JBsCcp(AXp=W4^k-YiVnh+=*MUIs%phAU=USZ&6=&S3lm3 z8zG>Av_NqfAY7-wmQh=2wDm6IT5aa?Vpc6)*1%~Ybsjl4K$yi0j3sK`6I2zFKMMX4 z0WieK{-&)!XPE7PBz!3JpNAB0XdA`7;H;jUoD&h5wi`k7eHh?}dfRXW1SPVw6YBYW zLQMN!k)iiC2AGHwx%(2|oh@ZZ>ux8lWJ5i&FOcxKmRt-g?ZXo&HBEE5nIPrRWp5vK H=30LNmxr@A literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/groovy-swing-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-swing-3.0.2.pkc new file mode 100644 index 0000000000000000000000000000000000000000..35f3668f7b93b02668d06d625c716d2bf3c0efd4 GIT binary patch literal 3207 zcmeHJO>g5i5Y@KV0!4$xUJA58&sw9{-g;WwapElEHi*2*#Xvzzvog07simmIzVxs5 zuXIRiC{jso1$ybhu=r+%9L~oZX6y4`cg?#$G_-1`biFH81qar5yIL#ta2o#Jc8^@v zyI*EM&)y}_TL;ZPGo59xzPve|eayb|mNREE<4r3vwG^uBH7r4hRmF|c`GDMltU!a# z&*2m6g_)OzE16&O&fII(wxI3j2AvZ5mhA;xvytI}zS_!dWIyNW-#}Fq-%cV>q7u)0-4vDR{ za5T@{<2}XB2bEkSpGZ1}fFvP$Phvn}rM;%toXW4ThK^y|5QIA>dKh6Bh6&~ak%I2x zPkeIdOAaNWv5VowyzdF7nq{#du+fNHQ;P8-TO*> z^%q6*qQ?|ddDgXtnwfA-E4X3ikPjM|z~hM)AKQu<+eFlTh3a(~Y=@TIYF^i%>4?<> z*5?F^UD))dXJROn6Is2qULKgN!Fjc1x(13=P6pQbc>B~v7p^UDKuR3YS=w<+Z-+#uo@;PybM2Cl0NEkUb=kMhZZ!faaeYFrQu_4u-<}pP{4!qvljHXTh=tNvKLNrTh`-UTzKsDF z{s5?B^y%p}0FEaU`;o#)X+$o;xK?^9xxx9uGz7sg4wUGdaFZoc7lt3>64a)0)OMp_ zRPg*8tv$^qKrgE_K-i9x>-vbiixV7W&NK7w@1WJm_=6SB`^mbcr#I!Q*qU7Gi z$-PUGn{ITEO{yM;)3dXI>^(s=_PLXN8Ti;geunpvyM|8}J((Qjm28druiv1u5fta|R!w@kC37|IQGi_eAqwRmhv6P`T_{ eBy0c3?|JsxS0QWtOT17TiEC2U#y&ls*Zc=YA<57H literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/groovy-templates-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-templates-3.0.2.pkc new file mode 100644 index 0000000000000000000000000000000000000000..2b473a64fd9d237b2fcb9d1627461a5ecd7251d8 GIT binary patch literal 517 zcmZXRO-{ow5QWW}T^F1pjZoRKB26Pysi2BPf<+``lBsLzpD?ydav5%i#4&KwB!qV5 z@za~{J=tWvxXtD46`|6(MwS=4ge`R@XQ=hQ>5i!dSqW;v)nOQ% zYl;T`4R3#|;jNAeS53Tnt;-&n@pUYl=f%%WBh++^HBpW zc=NQ($9wbM?1;(uDolqj1UZICai}qZr7CO~6XI&qJOc*H;ibHg!)|6!W#qdeAr3zK zo8xOpt^2 z#*nHk@}_Zy1l|nM!p*uYa5cf3m&gfH^-g~Ys`!y`y_o~6kx|xq0aK#63l6w=Ey3cb zk@0J;w_|SW#jQbn`^GpIL6Inp~g7pO|D%;C#)Io)JjFDYszwy tuJO7^Oa$cZtJSC-(Us?{+V>MDX6vDF!Pj3N{z0+(la9Xr{;S(_pg-W>EyVx; literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/groovy-test-junit5-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-test-junit5-3.0.2.pkc new file mode 100644 index 0000000000000000000000000000000000000000..9e882305ccda160180ff21d73b3c120c4fbdf92b GIT binary patch literal 154 zcmZPwaj}ZYHHt4vEy$0_FG`O|&QD3rNGvUmNiWLJFRO%7x+ST_CAwLqd6^}qF~)iZ zdPZ0zbiopOS&2mq42*?hm7*35A`nybAUgF5a!S)P^B7dz!Df4f0*wnQ&C5$I@<`1o MNG;L+_f&c&j literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/groovy-testng-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-testng-3.0.2.pkc new file mode 100644 index 0000000000000000000000000000000000000000..98602cab227960d1341d702d596ebd8fff7caf9b GIT binary patch literal 173 zcmZSZatgY+N<=|N0j;0XbmY_k9mmvyw>*mBs+(C{JrDz5L1#KNHwxp<%$JG8){d-+| z`k*8u8M=1TeY|%(K1Z?n{8MJ8-yBpnvu>MhYz~DCKHEBLANSqod}nm_QG5{7QQ*7@ zi0{%d_TuN+ezImqZIvK7`!l~yE`riIIDS>4!acv18?ERGq z%=FSW{naUA#opHUdpa}h5K6CX>DfzSay7We-E^Ub`JQtBO#apQUhP)meSXgnomvM3 z-dmnuD@`Lp{!FMo^s=Dm>!9Q}D~ZJ&_@HrkiL+nQ!9w~XkriLsUR+m_-<1zm-KY+K hrXwZ&2aQ9|2uk$)4k}IW+>ldi*klAj`2YGYe*u(prXTU*^sEv%}Wr)@||{n!=%{{bg2Ihxu7_@e`Rb61{*E;>ffg?{DIi zyQ8T)QijkO&|(!k2Yt#Bkaco}mBxb979aW+m&%mrWQMw$qc5CVE34ZQ2$b2u zi`Nb`r&HqzOo7av`a4U*+36vZLBnpzaee6YAzYZ!KPjq1m=QP%K)3c|NAP!@)z(qb zQE*Pm8gYC$rTDr5kGnD2AuWe^8jpobHKGYGBcNU;ohRGui^gbJdj{216gIP?oGaKE p>v@u?X9)$rS}&If)a{w&ln(#cdvx^c$->l!mM87MI#VVPeggm~`Y!+g literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/idea_rt.pkc b/hutool-script/.jython_cache/packages/idea_rt.pkc new file mode 100644 index 0000000000000000000000000000000000000000..d6c1cf30c5d2628bf062092d88485eaac9dc2d0f GIT binary patch literal 896 zcmZ`&&5qMB5H?3100aj_E(j!|2r4eiiMC0#sakE-uE)P>RFK5&2mkslpIERnsuYTscWw# zZKJG`B#n+b@7|v!o#ewz$@xrH2wyEFORz#b`u6+J+h5{oY3f9_D9Z#lSo%;Xo_t0} zsdS}Tquq|ABh+Zci~rIY3pD*x^|PyK%)~lz&b`wXyXZ#E2Q1S z>PmFZ?A=#ZQphnzU0kQAWF2f57r3aAxmFjR7^@(QP#?Ul{i%f>KuN}K#q(pv2aT`8 zyqM2OYP_=0G~6@D-m7I=WJ;sNs|PU>u2QmuyO}-iHce3fr6b-vTtVDnz`Mj-V^>bh zyK_gD$!41dOV&04VzJuA1CC?W+m21;6&Bnl1a|He=RS<~995v*=HBP*2HNvdRlK!l z$86Yc_>!!MhOmZmiq3e;E0`j1e%v~}4jVtIt#+NoSr4eZ$=0Ae@Td#4d@6B@*WeY6 zk#0$O?2Gp}KRoFGBT?+p6#n9%)Fl4Uqx@uSZS!AfUziUd-*Y;!jyzL7&{zyWVD n1%f#IjFU(lzQyp85%)Vi^<!`Qch%gY%d?~TS*5mOt3~M(6O`n*w zCZdm*rDjr4A8=xIAhYe6M>bzzg?ddUv4@LAA9pQ&Yq`HvGOswGA_rD!lsbguZ`P1; VFLF>j*y1&rcPln+0~%KhjNc2^#z6o8 literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/javaparser-core-3.15.13.pkc b/hutool-script/.jython_cache/packages/javaparser-core-3.15.13.pkc new file mode 100644 index 0000000000000000000000000000000000000000..6144999e656ae2a92e525168745ff541e2134cea GIT binary patch literal 9908 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 GVdB3z9Of(l literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/javaws.pkc b/hutool-script/.jython_cache/packages/javaws.pkc new file mode 100644 index 0000000000000000000000000000000000000000..3b0fdce014d57d02cd54268db89f15117d30b697 GIT binary patch literal 3161 zcmZ`+&5j~B5boJaq$rBC$sx*VkIBJ8yK{)5oQ45r2Lv;SUS{`zl$h=U%((5A?Peg) zk+;bkHnP*?XSvJuKyzXW&FoxMo)BeOrJjtejmL5^ZNaV%~8RIv+d@HlovfX zQWZs4$A5o)w~T&DWH!)+K+w^0CHD%nj@F|>8<{P``Fg28?MUVb&i7ZowL=%L#xSxn&e<1 zbj;lse3vJ<)2a}LW#EwcxV${4g-8w_tUm8(Vx+ndPmIx5i8YSQ*;Y|?aoj=G3QVEI zNT%pYnmp6JSAZeNK^f1w8cBhsdS|Ub%R(ja#56-Cq&#aUH9kmXlESn4H#+>wVt(hm zLhx&C7}qy4SeOI)$mS7xWhQuj z9;&?#F(?x!98uX^CX{y`@D;37zMxEyN3@J$4eUUHNI-j_bk1ic+WsMkyk;wsI?54o z@`JeO$960R1)3S%*qU(hb2<8=F*UU0U^7HXo8Rvv?86X!SBXUzz4>pL zGtcU(5R4UsrlK5Qpw)SVHMGSHCAU7I(Syg)T5~jKH4(5h->=r;ri!M_GDNoQ28S95f<2yMcVIMsUYnlI^`r@JoVD*y$q7SmCE zG0vIeTw#65Y?xriYHN~ngB98(RByx7m`nLwC!mzAJllIs3o+u_b^N5| zraoK>Un(X{Q$ozd9v@}fG!|8Za^q8QSfotaY$DSP`=fIjSpYUKNL?@slNTm73TXBk zj>@B-tIYRf56%hrHu7e3OHmZ?bW&oN<|)n!E5C&;a0J*^iET5-(rXi+tS_YK)7+bV z0BUI?Yc7YhO#cQ&7Ce2B#>O9;Sdn4!*3>@#L@BB44C!m0oLS@Q9YuR8h3I#@;HF6} zeJW0N-MGgcMc2Z7UgJ=HVv@VkZP26}2Kdezd))0fwnqQ18eZcPvKNs0`1WN* zZ##)su*j`HVdt?k?4#Y7+~jhp^2FdJwB_ntwf6X;aTf`b%!NL%#yQshAx;#4AYu5s zF>vFAJZ_KJVEknMM7JD>zfkG!EruGBLNRlRHdu*v62RHGUW3P_#GUQp`iqS>q-84U zFz@XS^%=IYp$g003#zsa5^}^YYtgrM4?PcXEzGa}YL1uQ3?XIihc3}QUw46O0Cr|a zZ`Z&b$B#5(OWxUv1H&B}gTb8}-JkUP`&T9<4Noa;VFg7O6w*x^*?=$df5OICKZ l;nIe+K#wwRgB+kLU88rMY!qMy_z%FyL~{2x;hV6J{SV;|uY>>q literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/jce.pkc b/hutool-script/.jython_cache/packages/jce.pkc new file mode 100644 index 0000000000000000000000000000000000000000..7509523314e26b2d2ea18c8f94f3f7db815bbbe7 GIT binary patch literal 1212 zcmZuwO>dh(5T!j=s(PxFOM7Ug9uP_*RjPWb4K%hZVq~J8ELG!Wu$L?g+FdL=$NsJU zoc@u_f@2`$!pEDBee>q6^C|egUcesKd3t~4ee*8=Twh+TbA@ZcH|w0CmxFSgSNXp$ zA7baDuBE4u)ry;=$E86fL9`zkFx1Z5JE0@c6iARMG|roxx*t5Ij+H52>+pa@Wq2tg zWIK>t7fHq4xe&MoF%>1dPxu3d2Zl|S+jjYd$DhDVsg5?==hY6COW+MkR6w|UTP8Kz z#gL|4ZoBdbn5~#XItsO2=CZ0yViZE*Cc9FZX`N-Lx(;Uor(Po~wV7lDo$jm9U ztWDRf&(7A22k*|H+{soU7AQ@VD;v2mjTPU6L5k01R@#Zrc`q~^iRIqbLY&l^gp+_| zxPwleAAYlxi4ZZWf@@7{aa*hdw7XzPA~Y+1ik5=2V+=~u8t`CR75Y*xK>bF=GfoM; z`UoU!3F>&+ai1SK0{oKv!K`aep#kMyv`0IXb*NxmfkX Yoc-JoAd;huf&RVupU7isuY>)21QnmiMgRZ+ literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/jcommander-1.72.pkc b/hutool-script/.jython_cache/packages/jcommander-1.72.pkc new file mode 100644 index 0000000000000000000000000000000000000000..f519fa6abeddea30975616050c46026248669249 GIT binary patch literal 1098 zcmZ`&%Wm5+5TyU1w<5@;M^;FDNQ2&ND+ZJ}u3;f5P$($Y($-->kpdq^&`79N@1Fj^02KGOAvZU;oG_ z7aJYYBA+(;8`lC#majlTi3Sy&+x@`#OEPR#@%osUt!603umR99BwW8oik7GYz-CCNacH=wvsi3N` z=m~N;bdqS-Emsq|lz#Yw9d2VKH8X67i7>cDMJ@)PW6rAtaFV)vXzAK)?|Kf8*Xovk zA3xl5F$<=RrcM=_E-mNH322H&lP`lci=%R?RypLe(@^E4P|q316Ip$*g}f7w9xbMz z(RI>_Y09=S=BxxBaMBG1!V|MsP!pw~4yZ;f{qoa_Gt+3ZZKs*g$HvmS5k0;Ww)Ci( zB0yY1keUcIlcG?lwU22}6cFc)BC3CqVVWcZJk(tB25UOhVfaZl^SJD=8z G9{dLhq@kt& literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/jfr.pkc b/hutool-script/.jython_cache/packages/jfr.pkc new file mode 100644 index 0000000000000000000000000000000000000000..9f59f040cbc7d25dabc50d50c2907f3a4a1ba1dc GIT binary patch literal 2523 zcmaJ@&5k2A5cco@EaHGbfDqCiK(fLvC&Y!!BomQQvNKA;&mj_WI+di8Zo9GFVY834 zZ-96r-hy(K`!C6Gn2f7jE|?>1w&( z*xp`QA#V2neDl?l_`+C~WYF7NlN?gtqy6;(j-b60FBUs%{!*(97H9@meD{CmJzrUS ztF+q~n~QG;MmulI#2d>#ezU0u+i&SvBR;+2+Z&TP@evvil%GLiT#AlP;>*P3y({)f zmSW)Ir~4599K28U*4>q9wgxLlS-Rb!m&8~U2V~k&qA_d5C)fSoJYpmsT`x`r$f5Fz z7o}A`HJWaf!;@Bdnvk$C2hf$~SCy3@Nk4>5s?%_W<+0uvUh#=;sB(-LD(%_-SRYlE zuFw43zfGVZhjE{7YqW37yi9g&xvTkmI5IYI^TLRx$EDqkd!L%d;)#EJ;}RrvjICXF5=uu^$=WQ(*mg{TiO{o zlN}K~P6e`oSL_a>80(_+a;k~~*0op#9M~ggO0P|Rn84zwDUc|nUNS-}b;v!3*GZweVeicZ3P zp)hQjV?|(!Um(46SLi7TBqPu1&N%x?F5b(`Wr0uBUee_ zf<@Z1s)mup3MQ5+KP@J4MOGWltE+>!6KHY4k|_RuBF; z6nXbK9C`OyBxIiHTG>~@Hhh%n_iG;PH{Cc49zynErS{e zM+dIXSyO<{SD>``yZv|?DN#|iokkZyAVtME5*i$$$gRC09I0!*M3!}xK|&N~(>sic zwkGP`X`JUJjfT$76fkxxzP>&wd%4nT3!yT_ZFicH^YgjON-l{y#yT%o#{_UX|Gi7G zb+y_u^`X#Rkp%eNQCes)=#1i)n&7U=`3!sJ+t1jkv#QreVSsHWG#)w-b=*<$`|I3G a_$I50y>;{XM7-C>)EXUttHx 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 literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/jfxswt.pkc b/hutool-script/.jython_cache/packages/jfxswt.pkc new file mode 100644 index 0000000000000000000000000000000000000000..bdec129c4a9d835345ce5edbd83df7c2b6b2283a GIT binary patch literal 146 zcmZQjcd?4`N-Rr^$x6vK)U(hth&M7YjL9lWjmgPOipff=C@wG2%StR_U|`JJtsx)4 zAdm%AomQcjnwylGq6d^^@O3UNF3HagDN4*MPD?G)LFGD?X6B>-McpEt6Z6Uvi*?9w9Y%TO~D=zpdcg_uxn2d@Y<{E0D1l4;P15A$y+( z2a(a%D4;V*6*xflFDy)HbXVw=0^}i`awQxLq;=2#9(kool!80jCr9sSwK4Vy=;}U2 zwzZWrhf>G(BrJ{*Vybn7Zlr>dNKTORTkK^%4f}fyYvGl<1x%zCs>R2%$I9WAaU&vU z@KF0LKG)W#sX+&;Du^)oR6Ea%SrN}irl%bs`qVWpETmqUy{+$Mvz}q>qDg_w5>eCa zOIVOH^;!e|syq-Yz@ARMqwzr`?1RBLSbGcBc#MGPr6NI{=2JM3OB6I{pYnpbZ1J*# zvU&FLEWt+X-B!y0y>>DI_Bj+vEk(5Bzel}}e&kiciOH)Bh1;;#jVaiD*=`>WmI@z+ zBIS#4Ay;n0`%PR6 z5E**1*}g0V*L_cO4b{RQ(C1aH<60*zkF*cYqkb5xdRuU$|# F{{byJuvh>9 literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/jsse.pkc b/hutool-script/.jython_cache/packages/jsse.pkc new file mode 100644 index 0000000000000000000000000000000000000000..82ca3c432793c94a1e6dc9e15d27406f89b7e33b GIT binary patch literal 1561 zcmZ`(!EVz)5H*4mCobGLzyZNwWwl6^fKwAEC`lWr;+ArVgf{ln*~H$pX4fVDHa-q! z*Rj1x+e@@FyPmu^Z{7s&M_-pSe$SVibbA_p4o`l6Iyqf#5SF=EEjL<2xZxNC&o+NQ zes~+a(v=J~Bozu%hg#<^{*7Q&Wo*J_s@HrAQyCisUi6tcXw5U|6F(}me{%u20#rK6 z1xRxN^>~-8xunMzxH_9aUIMz~6{=m$=+~%3)v+0~*Z+WjU2)N1a0IgBMq3O+63^CuD5MH4uXat-~;^4GgQhJ3zlmB+515Q0-WA ziYsBjKVJ%MSkvB4BBX?pz*jkpLD0ET8E)#PPskI z8Ch$V(4cp-kS-^3H=nHhCy7@g~>M zzq7^|-gGpH@4SGXN=zoUg;Ul!Zjfh+y1XttvHDAy+Lm>1tRK68d}@tEYU_jih(s+) zrAatD{GMSur#3K-jNXECzOUrru)0tEIraN|Bezohkv-2FBXVH`X!b}IoAN8!(!uM4 zl~|&>7b#%yJg(%?xW=@MJB=6JsncB0VU6E5WJ%T4yI%2*pnfAYX1Q}K}&rA literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/junit-4.13.pkc b/hutool-script/.jython_cache/packages/junit-4.13.pkc new file mode 100644 index 0000000000000000000000000000000000000000..c8d36f03904a81410934db60763960354d5b8186 GIT binary patch literal 4080 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 literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/junit-platform-engine-1.6.0.pkc b/hutool-script/.jython_cache/packages/junit-platform-engine-1.6.0.pkc new file mode 100644 index 0000000000000000000000000000000000000000..2c1246bdf4f7fca2d987849b95f1662d9ce87147 GIT binary patch literal 2102 zcma)8OK%e~5O%r3na7C(2as@9fDi``C{>b*R&9y8sSr{m5JH}G7b-6Wbhw--@&@xHK2H(@Cn0cR%A!!(CYh2GJ|!=Q z8YX-NdC$wGW|k{CWST9&f;QyE-cU!c_uQdurww`Q3-#)GK?^EA55ls;c9YX$RCbP zY#+v%Q&s8=ym`c?^jdPP?=_gy~_(_rHJ;97An4P5=lakA6 z8t@{o_TrG_aok70{q;-4t}4XKaR9Uq?ndD{*Mhj?JyJur zruc*cY{AhZxJGCe)qYoK1Z$jpkgjpe{?zTmgyHk56*QK3ZMiVy?E_{J_m-&L@7_fe z$RqckFeZAoF@R1{J**x2hDUg;~5i=zEs^hViW>IO-1)8FvYF2 zFkQF#$`uMLq%pUDhA$zGiXuVG&lHzKrNq~bgr18$0V(WGFFCT#mfdscwz_rgQsqFu z6(C*Ak7vhsOv7lV)g8ML&{NorXXL}3(ZlAt@z~h8%H}jh9>Pq(@~zTaHKs^(0ydkb z+a)$E>!Uvik-gKQF2)6u$TFI`5!Hwu=SyzzbKsP&q7gImdqPChJ7|FKOw6kwe)ZA0 a2>tu6-0={5yZ;5+qAPiP<&Oht_4*%f_$%xH literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/junit-platform-launcher-1.6.0.pkc b/hutool-script/.jython_cache/packages/junit-platform-launcher-1.6.0.pkc new file mode 100644 index 0000000000000000000000000000000000000000..d19a5da0e9c0a208c7c8d2a9a9c48c9a82d86922 GIT binary patch literal 1404 zcmaJ>&2AGh5O(ie;lhOr2P9|tIdI^V(j=%-P^u(=kb=;3cd~KowYUDMx;y|c!0Yff zJOg)TcWslTX)e}yW<2xFH#276M(?!5Sx?EWdp2gto_gIxe;YiqA z-=b;eXuWj2*G`0VX!tZBNy3>{)`4<1Y0~?tunyJMqhy)mjYsSFg|;p!Lxv@kQ6iV| z9p>H%tx~WhY0nE(POJsRZm$;-6nlQ8v`hvlmgy;$kW+27HcqJWlM|A#>yk<@Q<80@ zh9g`9H7XGq*$w&_o$uX4wh>BVq^+@UbSf^Dyf+qOW3(Be&5fujf0&Q0?CsQi1}O^Y z%=;ZYEud~k>0HsNK*kQHx_e;0C0?rwFi;_RT0ZkG*A)_AXpX?0Ywh>~&8^6Bs(~2m z`qItz97~!6(;Tqx!~E~+3_{hR&9QEY+UawmX|d}^%pCNKaoNjbgCXO*e`0I#qg2P zW{;DJrk4JIg@gQ>&+2x|h_EPFzV$9#$kq8E8@||h`TeVC_CXTU+QaA{rN?> KO)rTbX#N2X)*^KP literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/junit-rt.pkc b/hutool-script/.jython_cache/packages/junit-rt.pkc new file mode 100644 index 0000000000000000000000000000000000000000..871581e6cc5d944b843d01d99626c0ee059f675e GIT binary patch literal 589 zcmaJ;T~ER=6fM8OnD{u~aZwmXeUi^)M9i|m+g{3g!3$jr?VZY>^xydIx+w%=cx&%D zJs-Dh-hWCXqnC!~-UubwbPUup9FKw>dVQVeg+khSLxF@^d9B;KN5AC< z=|&by_h9AlRN}lo-5By$+%VSIetloZ>@w52kG1H^ccA05$pH8wAp<;bh64B$EX9RV zV4CAuNf>Ff29aze5}nM5oBb%MFYMyTrLt(VPNmd7k#|mADMJ@ToYN z6cyOzl<~9k{NA(Kl&970n@fg{D&n@fi5^4MqYt))RL!Pp2EIiP)w}$C{(5I?Hn#E` z%f=kswLf39#+bw6mYV2bhJH)tr7p?GCHX{~C)!+M*AWME^v&a4ghjIg+W=>eS9@6+keaaz#O7fYy$3F#CbkM>slEUSaaEf$tBVO^oG)02k-Zimbswt%_G5+cNq;sG6?j(8fJ(_xy;WblV>hV*xUi26Uv I@CCtq00qoQzyJUM literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/jython-2.7.2.pkc b/hutool-script/.jython_cache/packages/jython-2.7.2.pkc new file mode 100644 index 0000000000000000000000000000000000000000..9c7f230a026bd27c7dfca7ae9729d502e06c9535 GIT binary patch literal 142290 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 literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/localedata.pkc b/hutool-script/.jython_cache/packages/localedata.pkc new file mode 100644 index 0000000000000000000000000000000000000000..4a1bb0849cad8d1b52da14e00a30fb3ea9a44c5f GIT binary patch literal 10019 zcmZ`@5g_y(9<2*IO z?cGyT+*RfMUGZagS8e8bRV?x@KYq%aEPM6zuiyXfx7kONe}CQN`z?&fVG9YE^yE$Nu&*>@EFc`l2c z=WfLQV~z#Yz6ezNB2ev%JDKp>UreagCYzC0tBpVz3U_fs*cjq><;P83?Bfnp)9;-a zYjkO7h0;6utlVH$WS z)xbj;8c@l-9w!U{$y!0PRRhg7G@z2ZxlI@Vl4YRYWOsb1YC2RZxvRIK{dxn1yGn%( z=$6rkCi^=ZeP}{dmE@*@ zObowgn8u>R0KN@-Ak+6nk^M}*FA8zOP`DFyeNp)1z}`c5s-JG?&JYP&5p}Bc3SA>lrj17<;TlR8MWgqy$D5w}y$qhe*TfM0KLhaHF(3Zt@ zm;EuQ_SRj9x9;5KEP;oKR&=UP4oju`tLof@_Y&rquC?r1ORlxG)g2A zNS3ejR(zeevd!nM8-65TF~>?|(Z2eJ>}twoA(A_}2~&fzen1~%D25XmF^oni#;J}N z4Fv-wZ?HdL-XNF+CFn5N-DXp8CPf-ka+xaYzGmC7sZ+lVT2vaq6}Ol66SCr5w@@aii~Cw{){} zI;5Cm9n!MZ3#FuEsgfH{6Y&6&W#B1S15bGvdzIXq>x2OySq65|BV|XA6qVe&tAqg{ zS1CjjdqcNZ-nhvi3?3BI)aMYkEEiCs3eAAJ zHrTBy`4ZPY0Ec2%iWwBHVgS`{U_d2b_}T^%B{1rB<}py}^X^}-e6=4Is)rh1d_|hX zSL8PERVANh+t)+F<=KVuYeiKfN;#2zF~{zsp0jkQ@xmnmiA78@_b*De5!pnwC$g{i z*oS=$7#c`aeyF;>XSBXV6SQ7OX!dP^=glIFKV6;sCkp-SpC5^Ybjcqu$qW=KMP5o@ z9ZF#Kx^O9&W?dV&yO$Igd^~1=uaj5B;VU7V8xNVMAXs?lkmMg$tYDI1 z|D@s(cstqAK0KPl1-y6u!uK7XL;>Xf$z((JzuA%18WkTYiEQh?Qxr6NzbzvIYDol? zO71)r0U%ks0BXLhfXKcsw;nX~^#G;8jBai`Ny1=?{TmNvr@!Ryv{PbOMpnxm|hCFr5b| zNoU(+|KYRY+auHfKwHCiCBmR!Oc0`d--8usdaxpf*mqsfzTkn80?iCN{O}_6L%P9% zUV>yF4T!)4m3%M4wsPo7XhdJs0NN6P0hN4*!Zv{ZhLwHvxn9$ij-DB3zpgx)8|lzF z3wK1KUxvO7Z!WZa7^>O$u1^Sc==De_z`ut4&LahSPuJQjPY}0gHLz>X$7U0Rb<&zon~5B~QjD4A@r_nh|HbId%8+-3$r_+8<$T4($$e0cfeo0?>5}PM1o) zM`te}(VRnj6)A78Yk>FOfmzVXb*u6OVyuf7;n~D! z900MYeslKxVmvN-YW6H+G|otd1J@Xh1KmhFq7~5ri1%Odr4E<# z1Rj$2ALvB3*3*atk2%)&1+3zKX>)l}k&fM-A&hjMXw-4w2_qe-!f?@mXVJ5bq5+I( z0K$RUI~D1G73mJNqr?#O6~_?F7dyWzJ^2-SlAyfu=2b&61Tn=>V4hBN(?K`T#vF8~ zALF?rjVs2De~8C{|BCEhypMID#iDT&-LSB6sfP!;x0mtUAB=HROLrR21tS;vGSdSO zyC3-Q9QZoPDp*hce+&pyhBp+RL=Dr7A-szcTq;Q7zYUrqUyZw>$-7N6t{*nev&-}@ ifg<>rs^pnx`(v8mpwT~)uQi%ZfdQ3#|H(EmZ~q6Y@uTzr literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/lombok-1.18.12.pkc b/hutool-script/.jython_cache/packages/lombok-1.18.12.pkc new file mode 100644 index 0000000000000000000000000000000000000000..82932f5a78efab5f1c7949b1afa345f8beac989c GIT binary patch literal 888 zcmZXS&2AGx49BNJY5^4qf#AX`Od}=YfH9DH?H@q%5xs2r~_4aDUouNR%^_xi7T{Xus; z>W;k?IHmTgAK!ofQc4{TOU@$Uk+DlwPur_Kyb0)Rk2rt1Qio@?(am?D9(5%I(WZr( z4o5lKphwnnfMD3p9jE9$yHk^))8}BUoWzIQ9*%COT!=BfSR1^BgB zkGt3kHjwt93hKc>Wp4d#qqC=Ft#eWv{iQML`7VmpC3`PC)ctqI;t34(s3vs3WJm^0KTXd48XctO;WcUM1Z z;dghc5S>LL{Q<9{ahToBC++>j*gO(AomhnR_M&in?RB!1*X!7tZ z)EQqwNEY}W&y2@2$8!)LIv=NH^Sj5&O`Ts%O@dWk&Q<}Z=Z$xb_lNCS-Zoa?g*f?f z8vp$kLOiDvl75Ppl@hNgOM_u!Q#Km`{RX{w+rc>`69`{(OzX%z$T(QgG8cLLhuJx1LikZL%44va-9UPwaK4U z&T0ddsO^GnnY#>@RCU4VmS`v-0^>U{3FVEimSC}%)G;GE9OcNn=;Dr^X9C(NFosG7 ze%#xtK6YDez!Vr1ze4tWMOfz>FNR2s=7+g@%oshhS9SI4Yc^vcIXve!3HLd zN|W`TdvI)&R{>iPaG+-l`)&`05SM#+6ZT+aAkLjHIgt8L7&6x@N<$GBobJ+l*hxlt z#%pvmW9;eE$bnUuokPs*vYlF4xAz~% zH23Y?5v@dLMnornpD51gnfQCpKugE-CU@I*-leE*=B0zPv|ww^4xz)u+|zVpkra9` z*OZMwt!e1_B+E0=JM^^Wy6Z_|M}EW%Xi6UCnEZ6dDXIG7?Kkwt?k55b%IhkMCXq4D zuxl6843l%b?*R*c$L19YH{v#mH;?F7uB@~ntzSL}yOYYQ7996mBa+RC{$U2V-Aj=) Ocfk+M|1BhRa{dSB2L3q! literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/management-agent.pkc b/hutool-script/.jython_cache/packages/management-agent.pkc new file mode 100644 index 0000000000000000000000000000000000000000..260e406b99900cfb46e00c030bca397e3ae39abd GIT binary patch literal 59 zcmZQDbg_!@N-Rr^$x6vK)U(hth&M7YjL9lWjmgPOipfpPOH5DAP0cIO1=4vXdRd7@ M3=E7}yEWtk05W6~7XSbN literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/nashorn.pkc b/hutool-script/.jython_cache/packages/nashorn.pkc new file mode 100644 index 0000000000000000000000000000000000000000..e979d6f8b97b051fcf1601f256b72169b633e07d GIT binary patch literal 8510 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 MpM~;pArW5E7q|XZxc~qF literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/packages.idx b/hutool-script/.jython_cache/packages/packages.idx new file mode 100644 index 0000000000000000000000000000000000000000..8bd2536a7cfa1e720053d11c72bb411f119c2f8b GIT binary patch literal 11540 zcmcIqNsrt_6s|}R1p)*DSvfFl3Y*Pz&q@>pK^8F+mMnxNI1C=Qr`sO4?Xlf6-GM6{ zazt`2jCKMKtkfcCE|vJI0WSo5?-xcNt&7 z0atOcaxV+g!m&BnlIMC2-+_Nwb?bg^qNb}JAzRk$k7t(1V}QO$F~izw#h$k(m#UK$ zC(L{&3YML4C1V^;pl|>%E;{`2iHA;-qa|&4NzHG#t=y?+NwVG%7b{4H{4w{Bd}KPz%uF+68Z#jZ@+bH9A0k7|Vb3Y@6v!IBDQj^a=Zx1Y z7`!-~Zt*eWa@cH}28RL7-k5Q@i>y?&{dcl_E@F!i741#zWVTKatM$|?<-To-K+;CDHOYjqbkkr|AQPDx(LIScD}*Y^D2sZIG(-) zN5_NE^*lf4`v4^#NI;#&>9K5Nxp}eN3LxWilz6vrd(T*6f1~6y%y%$I!5=pG;s{Tk`FYaH zUSVsjp$0tc>O;8C`H{F~>$q+BK znbdB{h;LAWnfM%J-=}7FY=N`ZHU`$x?Yx;yOj-Td-WWKPKB%1v6Uk4 zxqsXqP8^iD!hpsUGgs^)E1o;vCr^Sd2B+0tgO=+B_r)kFpF-p$pvr{`Z$z)nV%`T3Cndw`y0W?OFZ+Ep7V1xS*6TOoT+0 zhU;uUG9vS$os4kPMHocss z7=bG_Q~dWCsQ~_9Hwdv04hx#F$}PCYyu?G<_&vzFuB@S?sJXnTmet(CWjey~&k|85 zGG^$_H-flshF&(L1vJdit7A)Bc6Od6&yG}Elz49BE5)HW01Z>A4no`LwiWakeUBLs zWy^6mMFB#b-cX@A9UQH->WzYJCugmewD}J>dT>PSo;rQFXQa+x>^v$(f+|yOJv?5g z9sVJ&a55<6AZSQH>RNiF@2a7FFM61W=o2}dF8VNtmO+A@er8GNdqtEqmy=aE(_|Mg z$vjv^JW^%SM?~ky#3)?OFTMEDmG64a&SXIC*FL7FQ2lzxFs?O@QF=W*PhWFm z_Ojc}(it-hXzm;(28v3mi>ws2f4S%AV!$a2oY+EO_A|FgO1(z?jmF%?u&-d=M}(Tf zw%f-o1tHlsV<*kNyNzv3RdLTg!e7z;+Us`y%H4v zUGqII-Ru%_%6hMmXU|YZpf@rNWJ$4o{kNVvV!-k({3bo!fzK9x2G{i4^`pOW>t$+DoohesO`sg}`G)Y`$X>)W+JX!n+uR`NOB&;&|zH1{Yv=BaJ P+~7?ClnPDYX{P-jI8kW6 literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/picocli-4.1.4.pkc b/hutool-script/.jython_cache/packages/picocli-4.1.4.pkc new file mode 100644 index 0000000000000000000000000000000000000000..eea530c9deef2e88b523c461e641a12a4c8ea28a GIT binary patch literal 135 zcmZQDcd?4eHHt4vEy$0_%uCCUDacIDPtM7N(d<+=86cl2jca NBR4TG#V0c_6#$~aEKmRd literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/plugin.pkc b/hutool-script/.jython_cache/packages/plugin.pkc new file mode 100644 index 0000000000000000000000000000000000000000..c951ef61708e81a5533e48ccfcff49222d34f246 GIT binary patch literal 9613 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 literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/qdox-1.12.1.pkc b/hutool-script/.jython_cache/packages/qdox-1.12.1.pkc new file mode 100644 index 0000000000000000000000000000000000000000..dc6d639b20466451565a9e0a9192d5e848ea0cf4 GIT binary patch literal 1825 zcmZ`)O>fgc5Z!X%02hz|3DE;cz{yy&M}#URO@dk_G)>ZSiG(KhB-v)O>+G&m=kM{$ zm|e%srg1O!%=66b?0h`4pRFI`<>_CInHnc*Ikt0^&*t`C>4h1uQdN|XllbH`KJoIm zQjYIHGj_Ckw|RcXzL0Aiy2j2mPPxX!D!sV}u|Unfw<=(mfRw1C4i|XK4JRWfTY`r9 zbjpiTVV~*)XP^!0m@hNIUSDkZ5B?6{e8 zky6+oc)Lh2vs_8`w$_)OwQvf#u$Qoga{|f(SZlq}xXfo8AT!P+QWvIb5WM_5` z#)hhr#!^NB)Z%NNVgR&PIS3O1MIv&;*LW31?r;?80H=ZBFO5LWIS8=DM{|&Ybr+2} zFm>Q#J2m48h{oQ9yCwKrg;&{vdujH#%*~L`!jpT{WuA#mkj#D{(hf4)i%R2)vR!1F z(xo;9f%;UcN5@^~sKc`cxCAcUc<%Z%ob##e;xxRD#|E61IyB5Dev$%cw8?P45jRgv z!;Lgl2~2Av--5`)Iq-Uy@6AuO@$EGk(}c=dg$yvswHuP%owlFY=lWUSQUNVSo>;@? z&XHCy^1J5*KVeSjs)Xh$bz%>Rti05RWC1gD;0R{^MRlm&x1c#pgxCKN7bKulS10=Z z6v_DAJDOIj+604c_uJBPOYPfk@P(vZ=hnrp`5HnGiyJi&WM3=VP736&*X0vU3mZ{7 zj&g`j1X{Yr0nj1RAR3MWi^{h8bU~veZIj5CChCE$cG4pn^P^j`m@lw5EXx{*EfhGT zqI+mt>aDC{iH`2wQA3sM1gR^_2_ZelUKKC8YDex-wIT}pz&_RwMM;3U<$}Gr(zGL( d_8@Z|ro8FT@l^Bfa>*@w+2~)m9|W-;{s*N-Rr^$x6vK)U(hth&M7YjL9lWjmgPOiYZDh&Mz%WPA%5UN-SbvV9eUB HAs+w$tvV2r literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/rt.pkc b/hutool-script/.jython_cache/packages/rt.pkc new file mode 100644 index 0000000000000000000000000000000000000000..a5f2bae314803af26bbf50b07717153454d0f3be GIT binary patch literal 236602 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 literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/sunec.pkc b/hutool-script/.jython_cache/packages/sunec.pkc new file mode 100644 index 0000000000000000000000000000000000000000..86041143a2897d3d81f5102f7d267e9c481b462b GIT binary patch literal 218 zcmYj}OA5j;6h-3-T!K@P64a46Qrr3kDRdf;*LacCHp**K>^fYIn=|Uf;W_7W#VDDp zJ+-9D^K~L;a(tSOC#qzq$`lF@ry6geWl1bVuY3&$ndk>48)(nQbrM=U(>U`*W%L2v zuD(}MU^Xo|{0U;J;lNF(L1$!m0}Cd1)}%x1qTJgwj$+R(lGWlE We$HR`h15DW_}nJ>GB+1X&NJTAjZ9qt literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/sunjce_provider.pkc b/hutool-script/.jython_cache/packages/sunjce_provider.pkc new file mode 100644 index 0000000000000000000000000000000000000000..cf73916e43941508627149e6790db9e9c7ca996a GIT binary patch literal 1433 zcmZux!H%0S5KRyLh8}zE!HSlsTU9+RAtYp1C{~h`N-?y)H@+Fhy6ceVIy3|@u`=rkgNy#&ogaPS4p5U^A3KZ8;k$EBiyo%;{6{Pd{ zS?FWBxDTs*r$k-AB4&}OkDy2xGs+b&L4#_D3!3a_#p_yBZ>$z%kqjvwYLOtwl4l5e zA*(1IQX0(1tME+$lu&7cV30$H*E1ZYZ^D?4j;1mC6tQ52#D`Xq{Z&)Q`|3>RPmATO z7p3RL+UhZi$u^qZz;{Qy7$J}PY{;%?Qr@5{3^E0J)VOWu27IKj*VZ+TnLQnBXat7{ z+lEsG<^OSvij)g=1wE?vm;s&`4gkoOgW zxUxST$i3L>>Q&kTmiDlmP6fxQYnEL~{FNw?bvAH%_5NS8O>h^?et2p8^l;RkpHtTU nq3k?x&S)W%Vi}-Csd^nIOm3LQ1rArq3R$2p=2D*RQ(Nu>{ILX* literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/sunmscapi.pkc b/hutool-script/.jython_cache/packages/sunmscapi.pkc new file mode 100644 index 0000000000000000000000000000000000000000..25f66dd2826580803d0bd3cb9d00692d344027d9 GIT binary patch literal 187 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

    JGyceLS z%QxZd-%qs`kDYshr~5)$kM)Q1gHSdwDg8SJ+U&%L7D($|36z6U0qSxBA`j6-Xk!w4 z11>*%RW~z@AJBsB-dVnIELe$n?{tyy-d__EJyuJlusGiHEz$*UW5A?^o1Ab^6N>(o zaAmC@F9>^LS=z}o`@1{%q!6WbIdMz_Fs3?gJlwP{GzGu5sY5Cesl2eKmFiaH=GIUn zm@~?yhNtx}r25RtuOt=3Iq zF^Jszb6z1b7Kw}%gqe|OW*P4;K2xNlwf<$~npTMq*fE>oI0HhIg=6ESUt@I*d6Oo7 z@5o@#@s$S!A3JmPgkZNCt!;!l{DtGm`f^17?z_)Tat|D#yptaisEdN zRWnvB)}ji*f|`==TBr~tfJ=A>HK76DZ25BWj-Ms#&8ETB0pM>SqK&jRgB{a-^1t!> zKpm=OzqP+7O))vX7$WQ-f8Id1d87%EH8f@(D*&6EFt7YbTUs^N*@FuJ8;VK6?AVSe z4;p70EMw7%IXL@;G-?&Ti~e_SmVO`S4CbL(RtO^%=|=9~)q4Qbm{#Sqe)VR&EQ=N) zh*TSI@PrLtKBhyazR7k2mX&=L(+%F$pVz>PlsmlTXMU0}*I4x%AQ z)2n^ZGX^({GNe%vOewf_t(<&f9}=BA+|C*hSQ7^lFs0c9x_At5uNO-w za&pp4FN_5g@ngS9AbJSt^U*2)uvyoZzXB69h}*HUww#8y=G ziOSc$gEna&6T4CVGrlWvx74S;13Dhwf%J_>9l$B0ET(8L-((c2Ffb1ddEmqSLa{V0 z@6Cb^>wzy*(!Q(ZNN6F2GyySTAc`R>vc~6#-~c14z!P7k!$GV;M^Z1Ql<4{LK{xU| zz4;+Y>cfS>@jGyZ-l-xfErM6U3_UQ2$@eVA{eW*)K*S}&c5&}1-7uWXIEhrYU_OyY zTG~4Xxx9h)9f}%>0Q9&Ai!!Ih`w&u1ynh^O?mgqrX+b)bYg*H%NfCxYd7@o7Dv z`6(joX#X(r0{2us;ZLgQ5>9lHcr=c5BT^^|&560QgnsqI|I@uG>v+G~yOt3xuE=oE(S0_-xuxW8AVK7`=o#0^4*$v1B z!fz6AUCGJA{0a#fR-g0#{I@5y+)$$UDj97mqlEMagpz2YRawA=g%K~h0{6uMTOPs5 z+Q2(SGgHljt|2MTd8>_`(a@`fB18$G;`hxQ$=;Pu>_PLAx8fajaWFigc&`)t%+MPH!B4j~L zJ4<}u*E4t!EC&3dTZDP{5n?PpI`j;{k!>q;kwNMhV8w-{7i%$3&TfRI+N2GSCf(Kx zV*IRpU>{Wcus8XBVl^- z)Y31CTABb(lNHEu{Gtr~c7n6Pr1Z&O7VgMp!rBCL;*nvoC`z6|SHklZMG&(V#A4cs zldRIX1YqLOku*n5IZr?#H%+TUHb2sHX-5pUhXO`Y<`9Yl0P$yMM)C`41}2LDS!a-1 zNF0G*1$x>M-$^zWlcJ~7n81w1sp6RBQ4+Jo%nh2VxeU3WG^S~tmi_v;u9x{y&lMms z4uFNQiZm^TiX%aMFo|4KFs33^HwW-5OG0}E1ausPDnHVlO8VTsWZ@R*IU+#?Fm zwB6)KdVTznaoi)ke$IdNgd+x9mip)n@r_F_`W3& z1WONp7SCVELdsbPERVPYt({Dn&6CbL0^V`bJS?n6fn%^VN9dS+_rl;2uR0U=dB*d+IRaOIiyEryth<9D_y_?k3zJA9Z0nEy&f*G%|P?~s9kDco_>Ywh8ZDEDN1 zC@#vx>1y&#IVzeo40Y1kQ+4H#A1{?jDbvEl>%;4ZO+^?^aZjxOa3S{ju!4~r{SoV; z+}fXv`)o=60~EJI245$%3eOEDEYfHWU`tin-_3fV~|gS`tiaLyT~E+8UoWxZ@ea z1SU+Q{|bFYSW`no5)t zj$m^vKY3vbr*UGm@g^iuv+tU3HYYHOLY9Cu zq$!d^O+~uKZyy90gRpjjpk?47^hEi@ON5I`X1@+RTk;3|U>_D!PvR+PamV}oc$FAg zyTW=7iuiy*%ODs@qqKvePvb^vfP2I9s7YCC5^Bx>DIkaJ1lN^2n#1}YFa zSY{~@j4<;dD1iO;F~}t=w(9Tgf!hWG1;iy-+3_u2NDWB4L!;j;y|AT%WR-o#5e2Ml zaK%9Hy9h0)NHv8^s_L>&gH09ZO>sLfvd?Y0)*}0>9=;WV{$0EDf=F8&VmDue7M6FV z-K#f52bVMgSV;rC)aR#S0^wQV^;ZzoWxRQC#K?+75p-8^5tg9Qe{M3o_uRSzrl>Y z)DLbJC%Feh>J9;7pAR@9RZfc84YWG9;FCjz3jD?xdRA?2Fe=PADBU8GCG1J*a2=v7 zUHn||Qbrr42Ui$O?K@H&N?>jb9}>#@BN&>0z;PwWC76M@_WbxoCmzIhYfL3*cmhM+ zi*gb&xOaI{gLSdZ;TY{6lRB297;niaMmxY9>he_Bz{Lqp)!51nLjSH-IhL!lJ%ivC zT(e|3A#$F!(P-%F0EqBcv@@VlV(ncoK;tV}c!h$ks{Z({(wwO(LX>$48rzDNq3J%+ z+jyLnUzGkm2@sRbt6yskM@Bv9ZqunqGcc$nfiyr_N$5t^W)@sZ)TpFDzx&#pTH{ zPz|PM5XpRu?_$G|3|FN#=C>C#^`d6Pyg^f9D07m`pjI&%iiLfm7(#{ngr&7G%j~ z=;9;vyqC12Jb}}Rs-5t~4A6fPUI5CwT5nrMIKwZxx{e)6S8^OfxfnXy=1bUgj zOCwlNK@&X5gLK7LvT|lSuGAsDzJ`5z>q3mKxt;w`aUvga>1>z#yXYYhb2E!=K7W0*2b6$8n<*Ei z^n+pA*i}zREw>GXFUrJDP!nIplPdsA8CB}fy&6-LYgKc@Bcb&_#t$X0?JWJ=J9Z!z zYXpkgm0SNgZZeto|1Zw>?IK86IaHsx9pxxpfW%!JK-ieVu@b9wiC5{t!_C(Eu4!6m0K6D(CGq?4xWAB2wK;(cx4v3i6hu{dfobN*$ ztpYrg8VNTDdEp0^BYjKwfbWAqV} z8srFt_UmBjul29pDbq?nyZ9CabMh|xPG0Pc-$5L8U`5zyRN=pR$uR~F7u+$pFY~rU z{3Rp{xu;FC*jHUWsn@0`HFtgzZuDC@*NrQv9BmKe(~}{K#{oNK7WvC7jD_9tyd1ta zA?%rT%+IN(ul@b032Hs2SS|2O1G^N*GHpe-D1^25_#TDUv5eGExX6vYLV>Ub{xl$) zDIW%t7&nLkqrxFTAMQh<5ZbG~ScV-O(qV7|qV6S@?Uhl)#r@n~RcMq&SNe>i^q!{M z;IRhB7t5who2J<(cH(Mg57RWE8t(6m4qN&>_K2f(PbG-O#pt<_c_mg@+08!BU8 z{ch>#Ht;qU3&~~|<&vl5!TvE_{()+(=Q&oxtb(c=T+#*wB@a-f1uAFr4Jv70B2FQg zm=;-vErdYq;+O>DUL>tGBq`-Q)-9{QTR&)-=U6Q11RP+=%T2dWYn?dV+W|#=s}D9e zP4wyUFg;BWc?%ubb8_>jg51oz>@OXu2alM@a2mB`YA))~77!vgz2o9IsY`6DCfxg) z=P?GaI!_s<@xUKl>d!@j|<_grV%;==Lz>_=4$KCSTK?k}cs(tjk8$&cbZ;9& zjym9^pS}(_l8|dzN4c6z#1jsLdj^jNo0P-qaYD7b;X)&Z|2w}+f~e4_lndOYQvtCr z$=T(%!@TS&tza?wArp%{R$q!jyuRk>m{zT&|8!YF-2xj5!Z0v{6P(Jj%UGq$7@(hI z<6y+`^7U1@()W_A3npThlvW{-KjvZ=ARUOmoPaUH;|Pj}LC{@P+!&~W*lu84ACYSqUGOAN1~ zuoWhF{e3mK_39+`$@Lqmvu*$bVxTuYIw0ML>DqW?K&osk@i)Nv0FO?Pxc}In9|B+> z;mmJXFOnhJMZ|GvFF0nbn^q@iM=348$;!;6gf5AqaQY%fE#=z-VQ7U75DnFan03-^j;A(y&P zfK_ZtyWNN^{WdV~b+{tLXK=h)jQ780$grdAPZ2H{KGGcRoePv3nPOOis zli2=5B-tpt3~XRfJmhr$Yaz_k*ko-Xk-vjbC7=7TFW2zi(dQ-kX%j!S6W3^yQHi69 z-&yJLS`{;<6+VjsfBYvt+K=ok-^U(R1>3}le1A`({$tiBBtR13Bi{#y#1!ZIrelXn zqGyMk2^L8n_MDItx;-m#;X?p%QbXp1CjmkZx-@UKL11wJoh{0<<^)=NgxF^)17MhN zGY2j>5Lf@JV#bpn`ydCQYESBsW#6Z?7|JCsC*%ZQLXKB^(e%$gU*pn8d`)c;3V*X~ z>i&Q!DkV4Ixsu3f&`VfGYTfVj3Mtw}vAbmz1!M_R)~L8)!y6b4Cp5%Fpr zqu;j^)njoEr~jEqp57M(f6C6=vnl)S3;l1ELoRO;)9b*Z`wAX_V3rj<-783jmRD%= zm6!;_E_FbxsufsU4snwF^SO5K(@*35b4b! zOlXk8H^tIHNN-aSn2EI*hZnB@FVkzPzuZyu54sp-PZHCE7`s~m5yA#TR zlc@YeR@h>r>?#pMm9I>jhA5!x2>=ff6 LMLK=9qpAN3Yz>wQ literal 0 HcmV?d00001 diff --git a/hutool-script/.jython_cache/packages/zipfs.pkc b/hutool-script/.jython_cache/packages/zipfs.pkc new file mode 100644 index 0000000000000000000000000000000000000000..b61230cd13af1aa708c5d5ae782c2b42b1d65ae6 GIT binary patch literal 248 zcmZXOK?=e^3`NHycmlT~GN>zYrL+jTDimGFfJ|$OsqKtp(rUew2k>&XMMZHH{`*P( z7XyE$*1FUxJ)FjJDo6XtXsl8Km0_gd%Ib#a#K}~X5S{eV?**b8+g!TBNP||kwz#h~ zEir?zatwJuw!{O_aEE6f31YTXg-oFH1~rQdX2PgoaDQZraCyV9zJ!lxpZ}`wsFg`< Y;|7|K^Ll#r)xopIu{PXhZ5T861QXO-!~g&Q literal 0 HcmV?d00001 From db74be205c66f9b07e8594d909668af079f66a10 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 5 May 2020 23:12:55 +0800 Subject: [PATCH 098/157] add method for extra --- .../packages/access-bridge-64.pkc | Bin 124 -> 0 bytes .../.jython_cache/packages/ant-1.10.7.pkc | Bin 13444 -> 0 bytes .../packages/ant-antlr-1.10.7.pkc | Bin 120 -> 0 bytes .../packages/ant-junit-1.10.7.pkc | Bin 615 -> 0 bytes .../packages/ant-launcher-1.10.7.pkc | Bin 150 -> 0 bytes .../packages/apiguardian-api-1.1.0.pkc | Bin 126 -> 0 bytes .../.jython_cache/packages/charsets.pkc | Bin 1447 -> 0 bytes .../.jython_cache/packages/cldrdata.pkc | Bin 30717 -> 0 bytes .../.jython_cache/packages/deploy.pkc | Bin 9914 -> 0 bytes hutool-script/.jython_cache/packages/dnsns.pkc | Bin 122 -> 0 bytes .../.jython_cache/packages/groovy-3.0.2.pkc | Bin 36733 -> 0 bytes .../packages/groovy-ant-3.0.2.pkc | Bin 428 -> 0 bytes .../packages/groovy-astbuilder-3.0.2.pkc | Bin 149 -> 0 bytes .../packages/groovy-cli-picocli-3.0.2.pkc | Bin 141 -> 0 bytes .../packages/groovy-console-3.0.2.pkc | Bin 2035 -> 0 bytes .../packages/groovy-datetime-3.0.2.pkc | Bin 172 -> 0 bytes .../packages/groovy-docgenerator-3.0.2.pkc | Bin 142 -> 0 bytes .../packages/groovy-groovydoc-3.0.2.pkc | Bin 1324 -> 0 bytes .../packages/groovy-groovysh-3.0.2.pkc | Bin 3068 -> 0 bytes .../packages/groovy-jmx-3.0.2.pkc | Bin 514 -> 0 bytes .../packages/groovy-json-3.0.2.pkc | Bin 856 -> 0 bytes .../packages/groovy-jsr223-3.0.2.pkc | Bin 223 -> 0 bytes .../packages/groovy-macro-3.0.2.pkc | Bin 641 -> 0 bytes .../packages/groovy-nio-3.0.2.pkc | Bin 232 -> 0 bytes .../packages/groovy-servlet-3.0.2.pkc | Bin 184 -> 0 bytes .../packages/groovy-sql-3.0.2.pkc | Bin 473 -> 0 bytes .../packages/groovy-swing-3.0.2.pkc | Bin 3207 -> 0 bytes .../packages/groovy-templates-3.0.2.pkc | Bin 517 -> 0 bytes .../packages/groovy-test-3.0.2.pkc | Bin 801 -> 0 bytes .../packages/groovy-test-junit5-3.0.2.pkc | Bin 154 -> 0 bytes .../packages/groovy-testng-3.0.2.pkc | Bin 173 -> 0 bytes .../packages/groovy-xml-3.0.2.pkc | Bin 1124 -> 0 bytes .../packages/hamcrest-core-1.3.pkc | Bin 662 -> 0 bytes .../.jython_cache/packages/idea_rt.pkc | Bin 896 -> 0 bytes .../.jython_cache/packages/jaccess.pkc | Bin 522 -> 0 bytes .../packages/javaparser-core-3.15.13.pkc | Bin 9908 -> 0 bytes .../.jython_cache/packages/javaws.pkc | Bin 3161 -> 0 bytes hutool-script/.jython_cache/packages/jce.pkc | Bin 1212 -> 0 bytes .../.jython_cache/packages/jcommander-1.72.pkc | Bin 1098 -> 0 bytes hutool-script/.jython_cache/packages/jfr.pkc | Bin 2523 -> 0 bytes hutool-script/.jython_cache/packages/jfxrt.pkc | Bin 50868 -> 0 bytes .../.jython_cache/packages/jfxswt.pkc | Bin 146 -> 0 bytes .../.jython_cache/packages/jline-2.14.6.pkc | Bin 1146 -> 0 bytes hutool-script/.jython_cache/packages/jsse.pkc | Bin 1561 -> 0 bytes .../.jython_cache/packages/junit-4.13.pkc | Bin 4080 -> 0 bytes .../packages/junit-jupiter-api-5.6.0.pkc | Bin 2448 -> 0 bytes .../packages/junit-jupiter-engine-5.6.0.pkc | Bin 2343 -> 0 bytes .../packages/junit-platform-commons-1.6.0.pkc | Bin 891 -> 0 bytes .../packages/junit-platform-engine-1.6.0.pkc | Bin 2102 -> 0 bytes .../packages/junit-platform-launcher-1.6.0.pkc | Bin 1404 -> 0 bytes .../.jython_cache/packages/junit-rt.pkc | Bin 589 -> 0 bytes .../packages/junit-vintage-engine-5.6.0.pkc | Bin 800 -> 0 bytes .../.jython_cache/packages/junit5-rt.pkc | Bin 201 -> 0 bytes .../.jython_cache/packages/jython-2.7.2.pkc | Bin 142290 -> 0 bytes .../.jython_cache/packages/localedata.pkc | Bin 10019 -> 0 bytes .../.jython_cache/packages/lombok-1.18.12.pkc | Bin 888 -> 0 bytes .../.jython_cache/packages/luaj-jse-3.0.1.pkc | Bin 1402 -> 0 bytes .../packages/management-agent.pkc | Bin 59 -> 0 bytes .../.jython_cache/packages/nashorn.pkc | Bin 8510 -> 0 bytes .../packages/opentest4j-1.2.0.pkc | Bin 233 -> 0 bytes .../.jython_cache/packages/packages.idx | Bin 11540 -> 0 bytes .../.jython_cache/packages/picocli-4.1.4.pkc | Bin 135 -> 0 bytes .../.jython_cache/packages/plugin.pkc | Bin 9613 -> 0 bytes .../.jython_cache/packages/qdox-1.12.1.pkc | Bin 1825 -> 0 bytes .../.jython_cache/packages/resources.pkc | Bin 52 -> 0 bytes hutool-script/.jython_cache/packages/rt.pkc | Bin 236602 -> 0 bytes hutool-script/.jython_cache/packages/sunec.pkc | Bin 218 -> 0 bytes .../.jython_cache/packages/sunjce_provider.pkc | Bin 1433 -> 0 bytes .../.jython_cache/packages/sunmscapi.pkc | Bin 187 -> 0 bytes .../.jython_cache/packages/sunpkcs11.pkc | Bin 1235 -> 0 bytes .../.jython_cache/packages/testng-6.14.3.pkc | Bin 6494 -> 0 bytes hutool-script/.jython_cache/packages/tools.pkc | Bin 44815 -> 0 bytes hutool-script/.jython_cache/packages/zipfs.pkc | Bin 248 -> 0 bytes 73 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 hutool-script/.jython_cache/packages/access-bridge-64.pkc delete mode 100644 hutool-script/.jython_cache/packages/ant-1.10.7.pkc delete mode 100644 hutool-script/.jython_cache/packages/ant-antlr-1.10.7.pkc delete mode 100644 hutool-script/.jython_cache/packages/ant-junit-1.10.7.pkc delete mode 100644 hutool-script/.jython_cache/packages/ant-launcher-1.10.7.pkc delete mode 100644 hutool-script/.jython_cache/packages/apiguardian-api-1.1.0.pkc delete mode 100644 hutool-script/.jython_cache/packages/charsets.pkc delete mode 100644 hutool-script/.jython_cache/packages/cldrdata.pkc delete mode 100644 hutool-script/.jython_cache/packages/deploy.pkc delete mode 100644 hutool-script/.jython_cache/packages/dnsns.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-ant-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-astbuilder-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-cli-picocli-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-console-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-datetime-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-docgenerator-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-groovydoc-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-groovysh-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-jmx-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-json-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-jsr223-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-macro-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-nio-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-servlet-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-sql-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-swing-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-templates-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-test-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-test-junit5-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-testng-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/groovy-xml-3.0.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/hamcrest-core-1.3.pkc delete mode 100644 hutool-script/.jython_cache/packages/idea_rt.pkc delete mode 100644 hutool-script/.jython_cache/packages/jaccess.pkc delete mode 100644 hutool-script/.jython_cache/packages/javaparser-core-3.15.13.pkc delete mode 100644 hutool-script/.jython_cache/packages/javaws.pkc delete mode 100644 hutool-script/.jython_cache/packages/jce.pkc delete mode 100644 hutool-script/.jython_cache/packages/jcommander-1.72.pkc delete mode 100644 hutool-script/.jython_cache/packages/jfr.pkc delete mode 100644 hutool-script/.jython_cache/packages/jfxrt.pkc delete mode 100644 hutool-script/.jython_cache/packages/jfxswt.pkc delete mode 100644 hutool-script/.jython_cache/packages/jline-2.14.6.pkc delete mode 100644 hutool-script/.jython_cache/packages/jsse.pkc delete mode 100644 hutool-script/.jython_cache/packages/junit-4.13.pkc delete mode 100644 hutool-script/.jython_cache/packages/junit-jupiter-api-5.6.0.pkc delete mode 100644 hutool-script/.jython_cache/packages/junit-jupiter-engine-5.6.0.pkc delete mode 100644 hutool-script/.jython_cache/packages/junit-platform-commons-1.6.0.pkc delete mode 100644 hutool-script/.jython_cache/packages/junit-platform-engine-1.6.0.pkc delete mode 100644 hutool-script/.jython_cache/packages/junit-platform-launcher-1.6.0.pkc delete mode 100644 hutool-script/.jython_cache/packages/junit-rt.pkc delete mode 100644 hutool-script/.jython_cache/packages/junit-vintage-engine-5.6.0.pkc delete mode 100644 hutool-script/.jython_cache/packages/junit5-rt.pkc delete mode 100644 hutool-script/.jython_cache/packages/jython-2.7.2.pkc delete mode 100644 hutool-script/.jython_cache/packages/localedata.pkc delete mode 100644 hutool-script/.jython_cache/packages/lombok-1.18.12.pkc delete mode 100644 hutool-script/.jython_cache/packages/luaj-jse-3.0.1.pkc delete mode 100644 hutool-script/.jython_cache/packages/management-agent.pkc delete mode 100644 hutool-script/.jython_cache/packages/nashorn.pkc delete mode 100644 hutool-script/.jython_cache/packages/opentest4j-1.2.0.pkc delete mode 100644 hutool-script/.jython_cache/packages/packages.idx delete mode 100644 hutool-script/.jython_cache/packages/picocli-4.1.4.pkc delete mode 100644 hutool-script/.jython_cache/packages/plugin.pkc delete mode 100644 hutool-script/.jython_cache/packages/qdox-1.12.1.pkc delete mode 100644 hutool-script/.jython_cache/packages/resources.pkc delete mode 100644 hutool-script/.jython_cache/packages/rt.pkc delete mode 100644 hutool-script/.jython_cache/packages/sunec.pkc delete mode 100644 hutool-script/.jython_cache/packages/sunjce_provider.pkc delete mode 100644 hutool-script/.jython_cache/packages/sunmscapi.pkc delete mode 100644 hutool-script/.jython_cache/packages/sunpkcs11.pkc delete mode 100644 hutool-script/.jython_cache/packages/testng-6.14.3.pkc delete mode 100644 hutool-script/.jython_cache/packages/tools.pkc delete mode 100644 hutool-script/.jython_cache/packages/zipfs.pkc diff --git a/hutool-script/.jython_cache/packages/access-bridge-64.pkc b/hutool-script/.jython_cache/packages/access-bridge-64.pkc deleted file mode 100644 index 0e514aee62106ee23ba976a14ef54e695be6fed4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124 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 diff --git a/hutool-script/.jython_cache/packages/ant-antlr-1.10.7.pkc b/hutool-script/.jython_cache/packages/ant-antlr-1.10.7.pkc deleted file mode 100644 index df43bbfbff50104ed4a9e80ac0456b57857bb053..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 120 zcmZRucd?4eHHt4vEy$0_FG`O|EJ#ewNR3I%D*<6$AkHa@G1N0O&@)E}=t2bavJ#6J z7#Iu1D*kV0Py_1LgXq>P$(i6EpL4e88rjK4N4D}544A4b&!J>LuiA4+y pjCnt#-Ul);Fz`6!=BJeAr0QnorR6gS0}a+gG8n{WV0H}f1OVZZDt!O| diff --git a/hutool-script/.jython_cache/packages/charsets.pkc b/hutool-script/.jython_cache/packages/charsets.pkc deleted file mode 100644 index 0b5b3700c0c16ac001ae88d163c1b8c186b697f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1447 zcmZWp&2HL25T=Kyw?0E}Z6ynNcm1>5Q-eVXP!bdqg#(hcz$zFV8yVAtXY0##c4i%j zdYSK=@%-)VdOAP)KNlnYM=!SP9qY=jpK+fpwzXLl`Es$^==xxqLwBp|PUqA1@Aq$C zJ6{gRvRmd=cXjBRx8~}hmp_M|71`}LP0|Yr$$Un7S{1s^4_?1IE(_B;HRgg2%2l;C zwKsT7vg8h7G)rI2^o0*N_Nb#_Z{myC z17N-%5%SQX(}NbBpK4K}A7h-5wFs4jE$SU`1H;OYd5;v?!u zL=-0^Qklj3^+&lgO9?VhV`SMKN?y9F2;4QE+E)MT)8`L7sGPN?QUKt(quSPP&&QmKIityohfMPrFzqQ*WgXIexqpy zow#^-i$F6eLzOX1OB`m?nqhV=mELHWULTvPZyK{|td`9)AO5>DMIW-tY?6l1<^19+ zn3Uj`hW7Vrlfi2})BiFsp1lWD8@==87` z1v&Qf@_DHnYrd(C*;#UHmbH1cY`8LoF4vYmR4){bB-hrKo?ez!VcBe>bHro4%MTk% zA9uCRODLW)9)<)FfdsJg@EtNh83!QkNKnEsnLuh|;2l^AuK=Z6QWrd%PvMihOt|A| zGWOE^WnUQBN9}4#VVs^7lzavx;{LC`Da^BOH0-p#IWMu<(?sw0FdkY!b`5?od{Cf% StGgyTpY4v-uTAF)7uY|Nk&-h2 diff --git a/hutool-script/.jython_cache/packages/cldrdata.pkc b/hutool-script/.jython_cache/packages/cldrdata.pkc deleted file mode 100644 index bd6e66e5d6147ca8ac7e77e6577e5d19c2cb6ae7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30717 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 U@}-14MZD==WKza2Bu;bk{|EpmR{#J2 diff --git a/hutool-script/.jython_cache/packages/deploy.pkc b/hutool-script/.jython_cache/packages/deploy.pkc deleted file mode 100644 index 880926b2be269df56fe5884ae02ecfe578116ba7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9914 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 diff --git a/hutool-script/.jython_cache/packages/dnsns.pkc b/hutool-script/.jython_cache/packages/dnsns.pkc deleted file mode 100644 index 1e271703213324e46d288a4d96a126be491d2135..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122 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 D&hsK% diff --git a/hutool-script/.jython_cache/packages/groovy-ant-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-ant-3.0.2.pkc deleted file mode 100644 index 7930855e22edf4f2a6ba1ba00dee9aadcddb98ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 428 zcmb7A!A`?43=LPf!$*)sXxumft2&S>gjV2G31z7lGSav>E~0$>&*+tdNp9xy5kZfhJh8Nw%$`ayxKf+80uFln&nokN(z}KDgHIU4mhdbRs?m{Xr|Cj=eW+ZWvyw%=YO;kS9>^y#3BX;#zL`*|7#dzfd=X&79=KTr0PM8(*v5L2Q`R6!m+r-31VnSQDRl_x$yXo9&y(;S83D?SL3DXzJ z^W^jZJSoBCn%O8i`gB}8`W8K@w3Avb@#4YLmczEyYX>&oF5l911NLzbn~0Hx8CXyp zT#yYynNGPC23t8ZSc&h#m%<6uHg0=Ft%mjtX3R?*gUZR!7SG@YU+0x*fe@nYXeogQ z<&It&Az`#&4w`#9Rp4}1642r(Xw_`{t70VmT&M=v^}JR|y)oKiBeCN_EpRDd9d|*2 zWe9n@cB{2!7P@5;I+8hep&5k|SI9ZJNnaqMCmi`+j42kI$HNcT6idVN(4+B~S*F}f z+og*u4l|uufIwdpWx5t>-hfp;(!wDuD+_%^H*qk5q(-Xkh0IASm>HVjI}x)vy%M+> zSxqU2eq_v<8j7T5_EMTJchv^m`^n!m>xp`4)^bf;dfWfK?Xj~7RL`!f7XkHlSv_x^PLgJIlE!uuGy F-vQ&*4R!zk diff --git a/hutool-script/.jython_cache/packages/groovy-datetime-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-datetime-3.0.2.pkc deleted file mode 100644 index faa763afcbe20ff4634ada1994e2b29bdc297a11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 172 zcmZSZbFqraHHt4vEy$0_FG`O|&QD3rNGvUmNiWLJFRO%7x+#ezsU?}YsWHZS26{&5 zqPk#Fy{yC{1_s7Lu}T&j234S8dWi*z$r-775To>9X6U6>l%(brXXfV>GibX24GsYs S>8W|CMTsT(MKQ*D y26{%=WOTtYdRd7@3=E8gVwD`M401q2^%4sblQUBFAV%sTo5jH6lAr7jGZ_FwH8L## diff --git a/hutool-script/.jython_cache/packages/groovy-groovydoc-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-groovydoc-3.0.2.pkc deleted file mode 100644 index 471cf6578ebae7a02d8757e052f9d102b0bc1c31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1324 zcma)6O>fgc5M4NML*fFQI3PHo)Iy~vZlO(4Q6r)>&`TsV-AtV&>m4(@u9~00oxh62 z_+uU02~tn?d!BhSJ8#x%cJ^_Z9sgj=X-S+eHLY+BgSfJUVcd4C&zpA zQAH;|ft7mn>&5oTAN5+~639WXF{#s=ho~fh$a?kkb5-Pw^tWKFdbytgE?8F2>NrXK zZoCP^>ZA;fy+-*;v{NMYfsIQSnB^7%diA`m7r6^}OX|%7#2)`1GsE`Og7?zzHA9{w zk1Cu)SkKW@!5XLFAe9f3`+)4g))iyg=-gQRT7S@hxk+&yAtx!xXqZ;B10EOQ!s8cCB5mi*?ArId1Yj{ zSeF^!$iP7dxUo2~SjtJ2Q7^gMCgSKxjXQ2cBKpwaJqKz&&b~(F)SEY%_djJq{mcLW diff --git a/hutool-script/.jython_cache/packages/groovy-groovysh-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-groovysh-3.0.2.pkc deleted file mode 100644 index 17b66a2eb707d8b292cb7154f829a2bce7019970..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3068 zcmeHJ&5qMB5KfOAxFW;_A$|lX8=(aWiCecVOT})f8U!3Fq1j}b7#us;PPf@d<4t%Q z#z~xR+ilf700MD}XFMLyd^6v~-l+d-njL;tn9HeD$uyENrcmf&=KU= ztG7`3w8}ulBgif*3vFc9BYPc|!oXr_|C(`JH9oE~ShpL-C=8o1RLir&ESJh`xEzWZ zTfD{cT&j58%@*|t<}86Tj$Zx(|=KQxL(231!jMMiNi z&wT*J>^*C7d3SfcucrMf(~YP!5Eab;1`OQJy{S^BB^ma!3AyfaRvpq zaoFPM2_{S%17(92U0fRe2{YVQyXd z_AlMwJ+&~pIpD}^q~to#HjrPyUjkw;{*v7Wf35Bc{#x}m{?gu6{3Z4E_`AZ@4R({e zYw}kr@9`esSJdmg?Aktcy;}+IK_zHt^el+HRm1r`j$kL9J>&P8&N;MueHpo(LChSf ztCn>7V+Ch!ZCTu|iS|;pp6UX)%~rBgzG_*W6Ytt>aakm-H6yT2B*e~UTu>%Xm|#Q` zcq!SLQ;qK`sSxI^Wr*!9VG6N4cZb5XUDqRWM}PmH75mSMd)r5D0K#zf!n?ULd%QZ|vScS%o8{5h>M8ts|BcZG_B_oN8>sIZn@-+0{wrtud_Rs$8 z&x%+spOl+@66C0mGL_;EzI9osj1a44e5QL>tH<~uo&{jqgLtn=h{^9=6a9(XK^3>{ z@wPN}2Q>M4b@ekdnQh6i_FpKq*BQ`#%jckdQs+qgqEkj{4)7wqA}hj{VO1|4!lX_i zXM;XqvOUepZe;*Bh_>kRg*D)Lf!;&sB$%E!!whGnYFkI#X_Fv7F6iN#DH!~4>>&Uo0U}!UU)UQG(O4+i*t5z%^X9;5 diff --git a/hutool-script/.jython_cache/packages/groovy-json-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-json-3.0.2.pkc deleted file mode 100644 index 2df0e66629e90b525560f12fbe755dea8c72b853..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 856 zcmZ`%!BX2W5H;s==E@HkPX}&2(Zr+!6AFVvPo816%903VNvxzy+-pCmf79=&v@&2q zIaqr8v~PFytYTJwX}jY)LTg$>t5%pLt|7Q~MaFFUVLN(oMz<%`=jwRO95GWp02RgF z(?P%gr}&h0RT30`YrKb`{6!4Dp+*94s7%o$NVUX_YgD)bk9=^p6==OQnxnU(Ic^8Z zRq!@=PPqnh1DoNZx4BfK0@>kcz*7azNx{N}dBog>5z87+2%Xfc@r;p-%$XBl@zzV_ zio@ts1s22_tI>TcsXdYgwfFlg5%io~PQYO*Hw-%IJvIV#bfcW63VG%B(>2f}+?Q#8 z$w?TW0&0OjK!une84$5kV*-g3Q&~sE^2Z91Y5tutp?hakB qqwYIJHl(V=vb5eg$rn1QIoarU3`riD9ILQ@>GsDQlpTPFF-=B|L@_)KT{bchoU?|9}tVWmp&m}J&?{O~R4OoMY77ax^V-ck}U z%GBKAJb^OaB|SK^%@PRumYk431H;caExod4!o#kWsW4I@J$R*^IQdty;WWe@|jV#?37?Vm|G+9o)0!RhX~p&|4V_o4Lu*3|fEZ zd0Fe_G1CpC-F3)KJ)GK7g7-L A5C8xG diff --git a/hutool-script/.jython_cache/packages/groovy-nio-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-nio-3.0.2.pkc deleted file mode 100644 index cd2e527921a433168fcc5655147985a3975c4290..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 232 zcmZQ@aj}ZYHHt4vEy$0_FG`O|&QD3rNGvUmNiWLJFRO%7x_O!TF~)iZdPYbBx?lml zti&P)2F60MO8#mFX`m5$FeCIJM(7ol=9OgTrZUL-W#+qsP4!JJ$;eMB)(J1lEJ;kt yNexIW$zV_b=}jz1OwLG!=>*!Ums(Mhnpd2epI6Ml3pBtL!Ij0X0cIT!vgrU|K~q8i diff --git a/hutool-script/.jython_cache/packages/groovy-servlet-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-servlet-3.0.2.pkc deleted file mode 100644 index 622b329986864a3dc671c05374ebe5a7ab999a95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 184 zcmZSZaj}ZYHHt4vEy$0_FG`O|&QD3rNGvUmNiWLJFRO%7y2YtQWjU!OF~)iZdPZo1 zx?n-Qti&P)2F60MO8##Qd=TAwP;CqWj!DHOMTyBJ9wj9O!4R`_+`%@$cu?9YGcP4G aFI@-9bxtfvP0uf?)Coz=Eyw|KpyB{CYD8QB diff --git a/hutool-script/.jython_cache/packages/groovy-sql-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-sql-3.0.2.pkc deleted file mode 100644 index b17277838d66261352911f58295764e3ffe5e1ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 473 zcmZWl!A^rf5T#cW4_-X`0d!;P&68FdL!v2>q`i=s5hk!^*@f9b;jjEe7b?)!!)ABh zdo%OidO>iXl&g2b$|Q#JBsCcp(AXp=W4^k-YiVnh+=*MUIs%phAU=USZ&6=&S3lm3 z8zG>Av_NqfAY7-wmQh=2wDm6IT5aa?Vpc6)*1%~Ybsjl4K$yi0j3sK`6I2zFKMMX4 z0WieK{-&)!XPE7PBz!3JpNAB0XdA`7;H;jUoD&h5wi`k7eHh?}dfRXW1SPVw6YBYW zLQMN!k)iiC2AGHwx%(2|oh@ZZ>ux8lWJ5i&FOcxKmRt-g?ZXo&HBEE5nIPrRWp5vK H=30LNmxr@A diff --git a/hutool-script/.jython_cache/packages/groovy-swing-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-swing-3.0.2.pkc deleted file mode 100644 index 35f3668f7b93b02668d06d625c716d2bf3c0efd4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3207 zcmeHJO>g5i5Y@KV0!4$xUJA58&sw9{-g;WwapElEHi*2*#Xvzzvog07simmIzVxs5 zuXIRiC{jso1$ybhu=r+%9L~oZX6y4`cg?#$G_-1`biFH81qar5yIL#ta2o#Jc8^@v zyI*EM&)y}_TL;ZPGo59xzPve|eayb|mNREE<4r3vwG^uBH7r4hRmF|c`GDMltU!a# z&*2m6g_)OzE16&O&fII(wxI3j2AvZ5mhA;xvytI}zS_!dWIyNW-#}Fq-%cV>q7u)0-4vDR{ za5T@{<2}XB2bEkSpGZ1}fFvP$Phvn}rM;%toXW4ThK^y|5QIA>dKh6Bh6&~ak%I2x zPkeIdOAaNWv5VowyzdF7nq{#du+fNHQ;P8-TO*> z^%q6*qQ?|ddDgXtnwfA-E4X3ikPjM|z~hM)AKQu<+eFlTh3a(~Y=@TIYF^i%>4?<> z*5?F^UD))dXJROn6Is2qULKgN!Fjc1x(13=P6pQbc>B~v7p^UDKuR3YS=w<+Z-+#uo@;PybM2Cl0NEkUb=kMhZZ!faaeYFrQu_4u-<}pP{4!qvljHXTh=tNvKLNrTh`-UTzKsDF z{s5?B^y%p}0FEaU`;o#)X+$o;xK?^9xxx9uGz7sg4wUGdaFZoc7lt3>64a)0)OMp_ zRPg*8tv$^qKrgE_K-i9x>-vbiixV7W&NK7w@1WJm_=6SB`^mbcr#I!Q*qU7Gi z$-PUGn{ITEO{yM;)3dXI>^(s=_PLXN8Ti;geunpvyM|8}J((Qjm28druiv1u5fta|R!w@kC37|IQGi_eAqwRmhv6P`T_{ eBy0c3?|JsxS0QWtOT17TiEC2U#y&ls*Zc=YA<57H diff --git a/hutool-script/.jython_cache/packages/groovy-templates-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-templates-3.0.2.pkc deleted file mode 100644 index 2b473a64fd9d237b2fcb9d1627461a5ecd7251d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 517 zcmZXRO-{ow5QWW}T^F1pjZoRKB26Pysi2BPf<+``lBsLzpD?ydav5%i#4&KwB!qV5 z@za~{J=tWvxXtD46`|6(MwS=4ge`R@XQ=hQ>5i!dSqW;v)nOQ% zYl;T`4R3#|;jNAeS53Tnt;-&n@pUYl=f%%WBh++^HBpW zc=NQ($9wbM?1;(uDolqj1UZICai}qZr7CO~6XI&qJOc*H;ibHg!)|6!W#qdeAr3zK zo8xOpt^2 z#*nHk@}_Zy1l|nM!p*uYa5cf3m&gfH^-g~Ys`!y`y_o~6kx|xq0aK#63l6w=Ey3cb zk@0J;w_|SW#jQbn`^GpIL6Inp~g7pO|D%;C#)Io)JjFDYszwy tuJO7^Oa$cZtJSC-(Us?{+V>MDX6vDF!Pj3N{z0+(la9Xr{;S(_pg-W>EyVx; diff --git a/hutool-script/.jython_cache/packages/groovy-test-junit5-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-test-junit5-3.0.2.pkc deleted file mode 100644 index 9e882305ccda160180ff21d73b3c120c4fbdf92b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 154 zcmZPwaj}ZYHHt4vEy$0_FG`O|&QD3rNGvUmNiWLJFRO%7x+ST_CAwLqd6^}qF~)iZ zdPZ0zbiopOS&2mq42*?hm7*35A`nybAUgF5a!S)P^B7dz!Df4f0*wnQ&C5$I@<`1o MNG;L+_f&c&j diff --git a/hutool-script/.jython_cache/packages/groovy-testng-3.0.2.pkc b/hutool-script/.jython_cache/packages/groovy-testng-3.0.2.pkc deleted file mode 100644 index 98602cab227960d1341d702d596ebd8fff7caf9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmZSZatgY+N<=|N0j;0XbmY_k9mmvyw>*mBs+(C{JrDz5L1#KNHwxp<%$JG8){d-+| z`k*8u8M=1TeY|%(K1Z?n{8MJ8-yBpnvu>MhYz~DCKHEBLANSqod}nm_QG5{7QQ*7@ zi0{%d_TuN+ezImqZIvK7`!l~yE`riIIDS>4!acv18?ERGq z%=FSW{naUA#opHUdpa}h5K6CX>DfzSay7We-E^Ub`JQtBO#apQUhP)meSXgnomvM3 z-dmnuD@`Lp{!FMo^s=Dm>!9Q}D~ZJ&_@HrkiL+nQ!9w~XkriLsUR+m_-<1zm-KY+K hrXwZ&2aQ9|2uk$)4k}IW+>ldi*klAj`2YGYe*u(prXTU*^sEv%}Wr)@||{n!=%{{bg2Ihxu7_@e`Rb61{*E;>ffg?{DIi zyQ8T)QijkO&|(!k2Yt#Bkaco}mBxb979aW+m&%mrWQMw$qc5CVE34ZQ2$b2u zi`Nb`r&HqzOo7av`a4U*+36vZLBnpzaee6YAzYZ!KPjq1m=QP%K)3c|NAP!@)z(qb zQE*Pm8gYC$rTDr5kGnD2AuWe^8jpobHKGYGBcNU;ohRGui^gbJdj{216gIP?oGaKE p>v@u?X9)$rS}&If)a{w&ln(#cdvx^c$->l!mM87MI#VVPeggm~`Y!+g diff --git a/hutool-script/.jython_cache/packages/idea_rt.pkc b/hutool-script/.jython_cache/packages/idea_rt.pkc deleted file mode 100644 index d6c1cf30c5d2628bf062092d88485eaac9dc2d0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 896 zcmZ`&&5qMB5H?3100aj_E(j!|2r4eiiMC0#sakE-uE)P>RFK5&2mkslpIERnsuYTscWw# zZKJG`B#n+b@7|v!o#ewz$@xrH2wyEFORz#b`u6+J+h5{oY3f9_D9Z#lSo%;Xo_t0} zsdS}Tquq|ABh+Zci~rIY3pD*x^|PyK%)~lz&b`wXyXZ#E2Q1S z>PmFZ?A=#ZQphnzU0kQAWF2f57r3aAxmFjR7^@(QP#?Ul{i%f>KuN}K#q(pv2aT`8 zyqM2OYP_=0G~6@D-m7I=WJ;sNs|PU>u2QmuyO}-iHce3fr6b-vTtVDnz`Mj-V^>bh zyK_gD$!41dOV&04VzJuA1CC?W+m21;6&Bnl1a|He=RS<~995v*=HBP*2HNvdRlK!l z$86Yc_>!!MhOmZmiq3e;E0`j1e%v~}4jVtIt#+NoSr4eZ$=0Ae@Td#4d@6B@*WeY6 zk#0$O?2Gp}KRoFGBT?+p6#n9%)Fl4Uqx@uSZS!AfUziUd-*Y;!jyzL7&{zyWVD n1%f#IjFU(lzQyp85%)Vi^<!`Qch%gY%d?~TS*5mOt3~M(6O`n*w zCZdm*rDjr4A8=xIAhYe6M>bzzg?ddUv4@LAA9pQ&Yq`HvGOswGA_rD!lsbguZ`P1; VFLF>j*y1&rcPln+0~%KhjNc2^#z6o8 diff --git a/hutool-script/.jython_cache/packages/javaparser-core-3.15.13.pkc b/hutool-script/.jython_cache/packages/javaparser-core-3.15.13.pkc deleted file mode 100644 index 6144999e656ae2a92e525168745ff541e2134cea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9908 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 GVdB3z9Of(l diff --git a/hutool-script/.jython_cache/packages/javaws.pkc b/hutool-script/.jython_cache/packages/javaws.pkc deleted file mode 100644 index 3b0fdce014d57d02cd54268db89f15117d30b697..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3161 zcmZ`+&5j~B5boJaq$rBC$sx*VkIBJ8yK{)5oQ45r2Lv;SUS{`zl$h=U%((5A?Peg) zk+;bkHnP*?XSvJuKyzXW&FoxMo)BeOrJjtejmL5^ZNaV%~8RIv+d@HlovfX zQWZs4$A5o)w~T&DWH!)+K+w^0CHD%nj@F|>8<{P``Fg28?MUVb&i7ZowL=%L#xSxn&e<1 zbj;lse3vJ<)2a}LW#EwcxV${4g-8w_tUm8(Vx+ndPmIx5i8YSQ*;Y|?aoj=G3QVEI zNT%pYnmp6JSAZeNK^f1w8cBhsdS|Ub%R(ja#56-Cq&#aUH9kmXlESn4H#+>wVt(hm zLhx&C7}qy4SeOI)$mS7xWhQuj z9;&?#F(?x!98uX^CX{y`@D;37zMxEyN3@J$4eUUHNI-j_bk1ic+WsMkyk;wsI?54o z@`JeO$960R1)3S%*qU(hb2<8=F*UU0U^7HXo8Rvv?86X!SBXUzz4>pL zGtcU(5R4UsrlK5Qpw)SVHMGSHCAU7I(Syg)T5~jKH4(5h->=r;ri!M_GDNoQ28S95f<2yMcVIMsUYnlI^`r@JoVD*y$q7SmCE zG0vIeTw#65Y?xriYHN~ngB98(RByx7m`nLwC!mzAJllIs3o+u_b^N5| zraoK>Un(X{Q$ozd9v@}fG!|8Za^q8QSfotaY$DSP`=fIjSpYUKNL?@slNTm73TXBk zj>@B-tIYRf56%hrHu7e3OHmZ?bW&oN<|)n!E5C&;a0J*^iET5-(rXi+tS_YK)7+bV z0BUI?Yc7YhO#cQ&7Ce2B#>O9;Sdn4!*3>@#L@BB44C!m0oLS@Q9YuR8h3I#@;HF6} zeJW0N-MGgcMc2Z7UgJ=HVv@VkZP26}2Kdezd))0fwnqQ18eZcPvKNs0`1WN* zZ##)su*j`HVdt?k?4#Y7+~jhp^2FdJwB_ntwf6X;aTf`b%!NL%#yQshAx;#4AYu5s zF>vFAJZ_KJVEknMM7JD>zfkG!EruGBLNRlRHdu*v62RHGUW3P_#GUQp`iqS>q-84U zFz@XS^%=IYp$g003#zsa5^}^YYtgrM4?PcXEzGa}YL1uQ3?XIihc3}QUw46O0Cr|a zZ`Z&b$B#5(OWxUv1H&B}gTb8}-JkUP`&T9<4Noa;VFg7O6w*x^*?=$df5OICKZ l;nIe+K#wwRgB+kLU88rMY!qMy_z%FyL~{2x;hV6J{SV;|uY>>q diff --git a/hutool-script/.jython_cache/packages/jce.pkc b/hutool-script/.jython_cache/packages/jce.pkc deleted file mode 100644 index 7509523314e26b2d2ea18c8f94f3f7db815bbbe7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1212 zcmZuwO>dh(5T!j=s(PxFOM7Ug9uP_*RjPWb4K%hZVq~J8ELG!Wu$L?g+FdL=$NsJU zoc@u_f@2`$!pEDBee>q6^C|egUcesKd3t~4ee*8=Twh+TbA@ZcH|w0CmxFSgSNXp$ zA7baDuBE4u)ry;=$E86fL9`zkFx1Z5JE0@c6iARMG|roxx*t5Ij+H52>+pa@Wq2tg zWIK>t7fHq4xe&MoF%>1dPxu3d2Zl|S+jjYd$DhDVsg5?==hY6COW+MkR6w|UTP8Kz z#gL|4ZoBdbn5~#XItsO2=CZ0yViZE*Cc9FZX`N-Lx(;Uor(Po~wV7lDo$jm9U ztWDRf&(7A22k*|H+{soU7AQ@VD;v2mjTPU6L5k01R@#Zrc`q~^iRIqbLY&l^gp+_| zxPwleAAYlxi4ZZWf@@7{aa*hdw7XzPA~Y+1ik5=2V+=~u8t`CR75Y*xK>bF=GfoM; z`UoU!3F>&+ai1SK0{oKv!K`aep#kMyv`0IXb*NxmfkX Yoc-JoAd;huf&RVupU7isuY>)21QnmiMgRZ+ diff --git a/hutool-script/.jython_cache/packages/jcommander-1.72.pkc b/hutool-script/.jython_cache/packages/jcommander-1.72.pkc deleted file mode 100644 index f519fa6abeddea30975616050c46026248669249..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1098 zcmZ`&%Wm5+5TyU1w<5@;M^;FDNQ2&ND+ZJ}u3;f5P$($Y($-->kpdq^&`79N@1Fj^02KGOAvZU;oG_ z7aJYYBA+(;8`lC#majlTi3Sy&+x@`#OEPR#@%osUt!603umR99BwW8oik7GYz-CCNacH=wvsi3N` z=m~N;bdqS-Emsq|lz#Yw9d2VKH8X67i7>cDMJ@)PW6rAtaFV)vXzAK)?|Kf8*Xovk zA3xl5F$<=RrcM=_E-mNH322H&lP`lci=%R?RypLe(@^E4P|q316Ip$*g}f7w9xbMz z(RI>_Y09=S=BxxBaMBG1!V|MsP!pw~4yZ;f{qoa_Gt+3ZZKs*g$HvmS5k0;Ww)Ci( zB0yY1keUcIlcG?lwU22}6cFc)BC3CqVVWcZJk(tB25UOhVfaZl^SJD=8z G9{dLhq@kt& diff --git a/hutool-script/.jython_cache/packages/jfr.pkc b/hutool-script/.jython_cache/packages/jfr.pkc deleted file mode 100644 index 9f59f040cbc7d25dabc50d50c2907f3a4a1ba1dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2523 zcmaJ@&5k2A5cco@EaHGbfDqCiK(fLvC&Y!!BomQQvNKA;&mj_WI+di8Zo9GFVY834 zZ-96r-hy(K`!C6Gn2f7jE|?>1w&( z*xp`QA#V2neDl?l_`+C~WYF7NlN?gtqy6;(j-b60FBUs%{!*(97H9@meD{CmJzrUS ztF+q~n~QG;MmulI#2d>#ezU0u+i&SvBR;+2+Z&TP@evvil%GLiT#AlP;>*P3y({)f zmSW)Ir~4599K28U*4>q9wgxLlS-Rb!m&8~U2V~k&qA_d5C)fSoJYpmsT`x`r$f5Fz z7o}A`HJWaf!;@Bdnvk$C2hf$~SCy3@Nk4>5s?%_W<+0uvUh#=;sB(-LD(%_-SRYlE zuFw43zfGVZhjE{7YqW37yi9g&xvTkmI5IYI^TLRx$EDqkd!L%d;)#EJ;}RrvjICXF5=uu^$=WQ(*mg{TiO{o zlN}K~P6e`oSL_a>80(_+a;k~~*0op#9M~ggO0P|Rn84zwDUc|nUNS-}b;v!3*GZweVeicZ3P zp)hQjV?|(!Um(46SLi7TBqPu1&N%x?F5b(`Wr0uBUee_ zf<@Z1s)mup3MQ5+KP@J4MOGWltE+>!6KHY4k|_RuBF; z6nXbK9C`OyBxIiHTG>~@Hhh%n_iG;PH{Cc49zynErS{e zM+dIXSyO<{SD>``yZv|?DN#|iokkZyAVtME5*i$$$gRC09I0!*M3!}xK|&N~(>sic zwkGP`X`JUJjfT$76fkxxzP>&wd%4nT3!yT_ZFicH^YgjON-l{y#yT%o#{_UX|Gi7G zb+y_u^`X#Rkp%eNQCes)=#1i)n&7U=`3!sJ+t1jkv#QreVSsHWG#)w-b=*<$`|I3G a_$I50y>;{XM7-C>)EXUttHx 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 diff --git a/hutool-script/.jython_cache/packages/jfxswt.pkc b/hutool-script/.jython_cache/packages/jfxswt.pkc deleted file mode 100644 index bdec129c4a9d835345ce5edbd83df7c2b6b2283a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 146 zcmZQjcd?4`N-Rr^$x6vK)U(hth&M7YjL9lWjmgPOipff=C@wG2%StR_U|`JJtsx)4 zAdm%AomQcjnwylGq6d^^@O3UNF3HagDN4*MPD?G)LFGD?X6B>-McpEt6Z6Uvi*?9w9Y%TO~D=zpdcg_uxn2d@Y<{E0D1l4;P15A$y+( z2a(a%D4;V*6*xflFDy)HbXVw=0^}i`awQxLq;=2#9(kool!80jCr9sSwK4Vy=;}U2 zwzZWrhf>G(BrJ{*Vybn7Zlr>dNKTORTkK^%4f}fyYvGl<1x%zCs>R2%$I9WAaU&vU z@KF0LKG)W#sX+&;Du^)oR6Ea%SrN}irl%bs`qVWpETmqUy{+$Mvz}q>qDg_w5>eCa zOIVOH^;!e|syq-Yz@ARMqwzr`?1RBLSbGcBc#MGPr6NI{=2JM3OB6I{pYnpbZ1J*# zvU&FLEWt+X-B!y0y>>DI_Bj+vEk(5Bzel}}e&kiciOH)Bh1;;#jVaiD*=`>WmI@z+ zBIS#4Ay;n0`%PR6 z5E**1*}g0V*L_cO4b{RQ(C1aH<60*zkF*cYqkb5xdRuU$|# F{{byJuvh>9 diff --git a/hutool-script/.jython_cache/packages/jsse.pkc b/hutool-script/.jython_cache/packages/jsse.pkc deleted file mode 100644 index 82ca3c432793c94a1e6dc9e15d27406f89b7e33b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1561 zcmZ`(!EVz)5H*4mCobGLzyZNwWwl6^fKwAEC`lWr;+ArVgf{ln*~H$pX4fVDHa-q! z*Rj1x+e@@FyPmu^Z{7s&M_-pSe$SVibbA_p4o`l6Iyqf#5SF=EEjL<2xZxNC&o+NQ zes~+a(v=J~Bozu%hg#<^{*7Q&Wo*J_s@HrAQyCisUi6tcXw5U|6F(}me{%u20#rK6 z1xRxN^>~-8xunMzxH_9aUIMz~6{=m$=+~%3)v+0~*Z+WjU2)N1a0IgBMq3O+63^CuD5MH4uXat-~;^4GgQhJ3zlmB+515Q0-WA ziYsBjKVJ%MSkvB4BBX?pz*jkpLD0ET8E)#PPskI z8Ch$V(4cp-kS-^3H=nHhCy7@g~>M zzq7^|-gGpH@4SGXN=zoUg;Ul!Zjfh+y1XttvHDAy+Lm>1tRK68d}@tEYU_jih(s+) zrAatD{GMSur#3K-jNXECzOUrru)0tEIraN|Bezohkv-2FBXVH`X!b}IoAN8!(!uM4 zl~|&>7b#%yJg(%?xW=@MJB=6JsncB0VU6E5WJ%T4yI%2*pnfAYX1Q}K}&rA diff --git a/hutool-script/.jython_cache/packages/junit-4.13.pkc b/hutool-script/.jython_cache/packages/junit-4.13.pkc deleted file mode 100644 index c8d36f03904a81410934db60763960354d5b8186..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4080 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 diff --git a/hutool-script/.jython_cache/packages/junit-platform-engine-1.6.0.pkc b/hutool-script/.jython_cache/packages/junit-platform-engine-1.6.0.pkc deleted file mode 100644 index 2c1246bdf4f7fca2d987849b95f1662d9ce87147..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2102 zcma)8OK%e~5O%r3na7C(2as@9fDi``C{>b*R&9y8sSr{m5JH}G7b-6Wbhw--@&@xHK2H(@Cn0cR%A!!(CYh2GJ|!=Q z8YX-NdC$wGW|k{CWST9&f;QyE-cU!c_uQdurww`Q3-#)GK?^EA55ls;c9YX$RCbP zY#+v%Q&s8=ym`c?^jdPP?=_gy~_(_rHJ;97An4P5=lakA6 z8t@{o_TrG_aok70{q;-4t}4XKaR9Uq?ndD{*Mhj?JyJur zruc*cY{AhZxJGCe)qYoK1Z$jpkgjpe{?zTmgyHk56*QK3ZMiVy?E_{J_m-&L@7_fe z$RqckFeZAoF@R1{J**x2hDUg;~5i=zEs^hViW>IO-1)8FvYF2 zFkQF#$`uMLq%pUDhA$zGiXuVG&lHzKrNq~bgr18$0V(WGFFCT#mfdscwz_rgQsqFu z6(C*Ak7vhsOv7lV)g8ML&{NorXXL}3(ZlAt@z~h8%H}jh9>Pq(@~zTaHKs^(0ydkb z+a)$E>!Uvik-gKQF2)6u$TFI`5!Hwu=SyzzbKsP&q7gImdqPChJ7|FKOw6kwe)ZA0 a2>tu6-0={5yZ;5+qAPiP<&Oht_4*%f_$%xH diff --git a/hutool-script/.jython_cache/packages/junit-platform-launcher-1.6.0.pkc b/hutool-script/.jython_cache/packages/junit-platform-launcher-1.6.0.pkc deleted file mode 100644 index d19a5da0e9c0a208c7c8d2a9a9c48c9a82d86922..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1404 zcmaJ>&2AGh5O(ie;lhOr2P9|tIdI^V(j=%-P^u(=kb=;3cd~KowYUDMx;y|c!0Yff zJOg)TcWslTX)e}yW<2xFH#276M(?!5Sx?EWdp2gto_gIxe;YiqA z-=b;eXuWj2*G`0VX!tZBNy3>{)`4<1Y0~?tunyJMqhy)mjYsSFg|;p!Lxv@kQ6iV| z9p>H%tx~WhY0nE(POJsRZm$;-6nlQ8v`hvlmgy;$kW+27HcqJWlM|A#>yk<@Q<80@ zh9g`9H7XGq*$w&_o$uX4wh>BVq^+@UbSf^Dyf+qOW3(Be&5fujf0&Q0?CsQi1}O^Y z%=;ZYEud~k>0HsNK*kQHx_e;0C0?rwFi;_RT0ZkG*A)_AXpX?0Ywh>~&8^6Bs(~2m z`qItz97~!6(;Tqx!~E~+3_{hR&9QEY+UawmX|d}^%pCNKaoNjbgCXO*e`0I#qg2P zW{;DJrk4JIg@gQ>&+2x|h_EPFzV$9#$kq8E8@||h`TeVC_CXTU+QaA{rN?> KO)rTbX#N2X)*^KP diff --git a/hutool-script/.jython_cache/packages/junit-rt.pkc b/hutool-script/.jython_cache/packages/junit-rt.pkc deleted file mode 100644 index 871581e6cc5d944b843d01d99626c0ee059f675e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 589 zcmaJ;T~ER=6fM8OnD{u~aZwmXeUi^)M9i|m+g{3g!3$jr?VZY>^xydIx+w%=cx&%D zJs-Dh-hWCXqnC!~-UubwbPUup9FKw>dVQVeg+khSLxF@^d9B;KN5AC< z=|&by_h9AlRN}lo-5By$+%VSIetloZ>@w52kG1H^ccA05$pH8wAp<;bh64B$EX9RV zV4CAuNf>Ff29aze5}nM5oBb%MFYMyTrLt(VPNmd7k#|mADMJ@ToYN z6cyOzl<~9k{NA(Kl&970n@fg{D&n@fi5^4MqYt))RL!Pp2EIiP)w}$C{(5I?Hn#E` z%f=kswLf39#+bw6mYV2bhJH)tr7p?GCHX{~C)!+M*AWME^v&a4ghjIg+W=>eS9@6+keaaz#O7fYy$3F#CbkM>slEUSaaEf$tBVO^oG)02k-Zimbswt%_G5+cNq;sG6?j(8fJ(_xy;WblV>hV*xUi26Uv I@CCtq00qoQzyJUM diff --git a/hutool-script/.jython_cache/packages/jython-2.7.2.pkc b/hutool-script/.jython_cache/packages/jython-2.7.2.pkc deleted file mode 100644 index 9c7f230a026bd27c7dfca7ae9729d502e06c9535..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 142290 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 diff --git a/hutool-script/.jython_cache/packages/localedata.pkc b/hutool-script/.jython_cache/packages/localedata.pkc deleted file mode 100644 index 4a1bb0849cad8d1b52da14e00a30fb3ea9a44c5f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10019 zcmZ`@5g_y(9<2*IO z?cGyT+*RfMUGZagS8e8bRV?x@KYq%aEPM6zuiyXfx7kONe}CQN`z?&fVG9YE^yE$Nu&*>@EFc`l2c z=WfLQV~z#Yz6ezNB2ev%JDKp>UreagCYzC0tBpVz3U_fs*cjq><;P83?Bfnp)9;-a zYjkO7h0;6utlVH$WS z)xbj;8c@l-9w!U{$y!0PRRhg7G@z2ZxlI@Vl4YRYWOsb1YC2RZxvRIK{dxn1yGn%( z=$6rkCi^=ZeP}{dmE@*@ zObowgn8u>R0KN@-Ak+6nk^M}*FA8zOP`DFyeNp)1z}`c5s-JG?&JYP&5p}Bc3SA>lrj17<;TlR8MWgqy$D5w}y$qhe*TfM0KLhaHF(3Zt@ zm;EuQ_SRj9x9;5KEP;oKR&=UP4oju`tLof@_Y&rquC?r1ORlxG)g2A zNS3ejR(zeevd!nM8-65TF~>?|(Z2eJ>}twoA(A_}2~&fzen1~%D25XmF^oni#;J}N z4Fv-wZ?HdL-XNF+CFn5N-DXp8CPf-ka+xaYzGmC7sZ+lVT2vaq6}Ol66SCr5w@@aii~Cw{){} zI;5Cm9n!MZ3#FuEsgfH{6Y&6&W#B1S15bGvdzIXq>x2OySq65|BV|XA6qVe&tAqg{ zS1CjjdqcNZ-nhvi3?3BI)aMYkEEiCs3eAAJ zHrTBy`4ZPY0Ec2%iWwBHVgS`{U_d2b_}T^%B{1rB<}py}^X^}-e6=4Is)rh1d_|hX zSL8PERVANh+t)+F<=KVuYeiKfN;#2zF~{zsp0jkQ@xmnmiA78@_b*De5!pnwC$g{i z*oS=$7#c`aeyF;>XSBXV6SQ7OX!dP^=glIFKV6;sCkp-SpC5^Ybjcqu$qW=KMP5o@ z9ZF#Kx^O9&W?dV&yO$Igd^~1=uaj5B;VU7V8xNVMAXs?lkmMg$tYDI1 z|D@s(cstqAK0KPl1-y6u!uK7XL;>Xf$z((JzuA%18WkTYiEQh?Qxr6NzbzvIYDol? zO71)r0U%ks0BXLhfXKcsw;nX~^#G;8jBai`Ny1=?{TmNvr@!Ryv{PbOMpnxm|hCFr5b| zNoU(+|KYRY+auHfKwHCiCBmR!Oc0`d--8usdaxpf*mqsfzTkn80?iCN{O}_6L%P9% zUV>yF4T!)4m3%M4wsPo7XhdJs0NN6P0hN4*!Zv{ZhLwHvxn9$ij-DB3zpgx)8|lzF z3wK1KUxvO7Z!WZa7^>O$u1^Sc==De_z`ut4&LahSPuJQjPY}0gHLz>X$7U0Rb<&zon~5B~QjD4A@r_nh|HbId%8+-3$r_+8<$T4($$e0cfeo0?>5}PM1o) zM`te}(VRnj6)A78Yk>FOfmzVXb*u6OVyuf7;n~D! z900MYeslKxVmvN-YW6H+G|otd1J@Xh1KmhFq7~5ri1%Odr4E<# z1Rj$2ALvB3*3*atk2%)&1+3zKX>)l}k&fM-A&hjMXw-4w2_qe-!f?@mXVJ5bq5+I( z0K$RUI~D1G73mJNqr?#O6~_?F7dyWzJ^2-SlAyfu=2b&61Tn=>V4hBN(?K`T#vF8~ zALF?rjVs2De~8C{|BCEhypMID#iDT&-LSB6sfP!;x0mtUAB=HROLrR21tS;vGSdSO zyC3-Q9QZoPDp*hce+&pyhBp+RL=Dr7A-szcTq;Q7zYUrqUyZw>$-7N6t{*nev&-}@ ifg<>rs^pnx`(v8mpwT~)uQi%ZfdQ3#|H(EmZ~q6Y@uTzr diff --git a/hutool-script/.jython_cache/packages/lombok-1.18.12.pkc b/hutool-script/.jython_cache/packages/lombok-1.18.12.pkc deleted file mode 100644 index 82932f5a78efab5f1c7949b1afa345f8beac989c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 888 zcmZXS&2AGx49BNJY5^4qf#AX`Od}=YfH9DH?H@q%5xs2r~_4aDUouNR%^_xi7T{Xus; z>W;k?IHmTgAK!ofQc4{TOU@$Uk+DlwPur_Kyb0)Rk2rt1Qio@?(am?D9(5%I(WZr( z4o5lKphwnnfMD3p9jE9$yHk^))8}BUoWzIQ9*%COT!=BfSR1^BgB zkGt3kHjwt93hKc>Wp4d#qqC=Ft#eWv{iQML`7VmpC3`PC)ctqI;t34(s3vs3WJm^0KTXd48XctO;WcUM1Z z;dghc5S>LL{Q<9{ahToBC++>j*gO(AomhnR_M&in?RB!1*X!7tZ z)EQqwNEY}W&y2@2$8!)LIv=NH^Sj5&O`Ts%O@dWk&Q<}Z=Z$xb_lNCS-Zoa?g*f?f z8vp$kLOiDvl75Ppl@hNgOM_u!Q#Km`{RX{w+rc>`69`{(OzX%z$T(QgG8cLLhuJx1LikZL%44va-9UPwaK4U z&T0ddsO^GnnY#>@RCU4VmS`v-0^>U{3FVEimSC}%)G;GE9OcNn=;Dr^X9C(NFosG7 ze%#xtK6YDez!Vr1ze4tWMOfz>FNR2s=7+g@%oshhS9SI4Yc^vcIXve!3HLd zN|W`TdvI)&R{>iPaG+-l`)&`05SM#+6ZT+aAkLjHIgt8L7&6x@N<$GBobJ+l*hxlt z#%pvmW9;eE$bnUuokPs*vYlF4xAz~% zH23Y?5v@dLMnornpD51gnfQCpKugE-CU@I*-leE*=B0zPv|ww^4xz)u+|zVpkra9` z*OZMwt!e1_B+E0=JM^^Wy6Z_|M}EW%Xi6UCnEZ6dDXIG7?Kkwt?k55b%IhkMCXq4D zuxl6843l%b?*R*c$L19YH{v#mH;?F7uB@~ntzSL}yOYYQ7996mBa+RC{$U2V-Aj=) Ocfk+M|1BhRa{dSB2L3q! diff --git a/hutool-script/.jython_cache/packages/management-agent.pkc b/hutool-script/.jython_cache/packages/management-agent.pkc deleted file mode 100644 index 260e406b99900cfb46e00c030bca397e3ae39abd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59 zcmZQDbg_!@N-Rr^$x6vK)U(hth&M7YjL9lWjmgPOipfpPOH5DAP0cIO1=4vXdRd7@ M3=E7}yEWtk05W6~7XSbN diff --git a/hutool-script/.jython_cache/packages/nashorn.pkc b/hutool-script/.jython_cache/packages/nashorn.pkc deleted file mode 100644 index e979d6f8b97b051fcf1601f256b72169b633e07d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8510 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 MpM~;pArW5E7q|XZxc~qF diff --git a/hutool-script/.jython_cache/packages/packages.idx b/hutool-script/.jython_cache/packages/packages.idx deleted file mode 100644 index 8bd2536a7cfa1e720053d11c72bb411f119c2f8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11540 zcmcIqNsrt_6s|}R1p)*DSvfFl3Y*Pz&q@>pK^8F+mMnxNI1C=Qr`sO4?Xlf6-GM6{ zazt`2jCKMKtkfcCE|vJI0WSo5?-xcNt&7 z0atOcaxV+g!m&BnlIMC2-+_Nwb?bg^qNb}JAzRk$k7t(1V}QO$F~izw#h$k(m#UK$ zC(L{&3YML4C1V^;pl|>%E;{`2iHA;-qa|&4NzHG#t=y?+NwVG%7b{4H{4w{Bd}KPz%uF+68Z#jZ@+bH9A0k7|Vb3Y@6v!IBDQj^a=Zx1Y z7`!-~Zt*eWa@cH}28RL7-k5Q@i>y?&{dcl_E@F!i741#zWVTKatM$|?<-To-K+;CDHOYjqbkkr|AQPDx(LIScD}*Y^D2sZIG(-) zN5_NE^*lf4`v4^#NI;#&>9K5Nxp}eN3LxWilz6vrd(T*6f1~6y%y%$I!5=pG;s{Tk`FYaH zUSVsjp$0tc>O;8C`H{F~>$q+BK znbdB{h;LAWnfM%J-=}7FY=N`ZHU`$x?Yx;yOj-Td-WWKPKB%1v6Uk4 zxqsXqP8^iD!hpsUGgs^)E1o;vCr^Sd2B+0tgO=+B_r)kFpF-p$pvr{`Z$z)nV%`T3Cndw`y0W?OFZ+Ep7V1xS*6TOoT+0 zhU;uUG9vS$os4kPMHocss z7=bG_Q~dWCsQ~_9Hwdv04hx#F$}PCYyu?G<_&vzFuB@S?sJXnTmet(CWjey~&k|85 zGG^$_H-flshF&(L1vJdit7A)Bc6Od6&yG}Elz49BE5)HW01Z>A4no`LwiWakeUBLs zWy^6mMFB#b-cX@A9UQH->WzYJCugmewD}J>dT>PSo;rQFXQa+x>^v$(f+|yOJv?5g z9sVJ&a55<6AZSQH>RNiF@2a7FFM61W=o2}dF8VNtmO+A@er8GNdqtEqmy=aE(_|Mg z$vjv^JW^%SM?~ky#3)?OFTMEDmG64a&SXIC*FL7FQ2lzxFs?O@QF=W*PhWFm z_Ojc}(it-hXzm;(28v3mi>ws2f4S%AV!$a2oY+EO_A|FgO1(z?jmF%?u&-d=M}(Tf zw%f-o1tHlsV<*kNyNzv3RdLTg!e7z;+Us`y%H4v zUGqII-Ru%_%6hMmXU|YZpf@rNWJ$4o{kNVvV!-k({3bo!fzK9x2G{i4^`pOW>t$+DoohesO`sg}`G)Y`$X>)W+JX!n+uR`NOB&;&|zH1{Yv=BaJ P+~7?ClnPDYX{P-jI8kW6 diff --git a/hutool-script/.jython_cache/packages/picocli-4.1.4.pkc b/hutool-script/.jython_cache/packages/picocli-4.1.4.pkc deleted file mode 100644 index eea530c9deef2e88b523c461e641a12a4c8ea28a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 135 zcmZQDcd?4eHHt4vEy$0_%uCCUDacIDPtM7N(d<+=86cl2jca NBR4TG#V0c_6#$~aEKmRd diff --git a/hutool-script/.jython_cache/packages/plugin.pkc b/hutool-script/.jython_cache/packages/plugin.pkc deleted file mode 100644 index c951ef61708e81a5533e48ccfcff49222d34f246..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9613 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 diff --git a/hutool-script/.jython_cache/packages/qdox-1.12.1.pkc b/hutool-script/.jython_cache/packages/qdox-1.12.1.pkc deleted file mode 100644 index dc6d639b20466451565a9e0a9192d5e848ea0cf4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1825 zcmZ`)O>fgc5Z!X%02hz|3DE;cz{yy&M}#URO@dk_G)>ZSiG(KhB-v)O>+G&m=kM{$ zm|e%srg1O!%=66b?0h`4pRFI`<>_CInHnc*Ikt0^&*t`C>4h1uQdN|XllbH`KJoIm zQjYIHGj_Ckw|RcXzL0Aiy2j2mPPxX!D!sV}u|Unfw<=(mfRw1C4i|XK4JRWfTY`r9 zbjpiTVV~*)XP^!0m@hNIUSDkZ5B?6{e8 zky6+oc)Lh2vs_8`w$_)OwQvf#u$Qoga{|f(SZlq}xXfo8AT!P+QWvIb5WM_5` z#)hhr#!^NB)Z%NNVgR&PIS3O1MIv&;*LW31?r;?80H=ZBFO5LWIS8=DM{|&Ybr+2} zFm>Q#J2m48h{oQ9yCwKrg;&{vdujH#%*~L`!jpT{WuA#mkj#D{(hf4)i%R2)vR!1F z(xo;9f%;UcN5@^~sKc`cxCAcUc<%Z%ob##e;xxRD#|E61IyB5Dev$%cw8?P45jRgv z!;Lgl2~2Av--5`)Iq-Uy@6AuO@$EGk(}c=dg$yvswHuP%owlFY=lWUSQUNVSo>;@? z&XHCy^1J5*KVeSjs)Xh$bz%>Rti05RWC1gD;0R{^MRlm&x1c#pgxCKN7bKulS10=Z z6v_DAJDOIj+604c_uJBPOYPfk@P(vZ=hnrp`5HnGiyJi&WM3=VP736&*X0vU3mZ{7 zj&g`j1X{Yr0nj1RAR3MWi^{h8bU~veZIj5CChCE$cG4pn^P^j`m@lw5EXx{*EfhGT zqI+mt>aDC{iH`2wQA3sM1gR^_2_ZelUKKC8YDex-wIT}pz&_RwMM;3U<$}Gr(zGL( d_8@Z|ro8FT@l^Bfa>*@w+2~)m9|W-;{s*N-Rr^$x6vK)U(hth&M7YjL9lWjmgPOiYZDh&Mz%WPA%5UN-SbvV9eUB HAs+w$tvV2r diff --git a/hutool-script/.jython_cache/packages/rt.pkc b/hutool-script/.jython_cache/packages/rt.pkc deleted file mode 100644 index a5f2bae314803af26bbf50b07717153454d0f3be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 236602 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 diff --git a/hutool-script/.jython_cache/packages/sunec.pkc b/hutool-script/.jython_cache/packages/sunec.pkc deleted file mode 100644 index 86041143a2897d3d81f5102f7d267e9c481b462b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 218 zcmYj}OA5j;6h-3-T!K@P64a46Qrr3kDRdf;*LacCHp**K>^fYIn=|Uf;W_7W#VDDp zJ+-9D^K~L;a(tSOC#qzq$`lF@ry6geWl1bVuY3&$ndk>48)(nQbrM=U(>U`*W%L2v zuD(}MU^Xo|{0U;J;lNF(L1$!m0}Cd1)}%x1qTJgwj$+R(lGWlE We$HR`h15DW_}nJ>GB+1X&NJTAjZ9qt diff --git a/hutool-script/.jython_cache/packages/sunjce_provider.pkc b/hutool-script/.jython_cache/packages/sunjce_provider.pkc deleted file mode 100644 index cf73916e43941508627149e6790db9e9c7ca996a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1433 zcmZux!H%0S5KRyLh8}zE!HSlsTU9+RAtYp1C{~h`N-?y)H@+Fhy6ceVIy3|@u`=rkgNy#&ogaPS4p5U^A3KZ8;k$EBiyo%;{6{Pd{ zS?FWBxDTs*r$k-AB4&}OkDy2xGs+b&L4#_D3!3a_#p_yBZ>$z%kqjvwYLOtwl4l5e zA*(1IQX0(1tME+$lu&7cV30$H*E1ZYZ^D?4j;1mC6tQ52#D`Xq{Z&)Q`|3>RPmATO z7p3RL+UhZi$u^qZz;{Qy7$J}PY{;%?Qr@5{3^E0J)VOWu27IKj*VZ+TnLQnBXat7{ z+lEsG<^OSvij)g=1wE?vm;s&`4gkoOgW zxUxST$i3L>>Q&kTmiDlmP6fxQYnEL~{FNw?bvAH%_5NS8O>h^?et2p8^l;RkpHtTU nq3k?x&S)W%Vi}-Csd^nIOm3LQ1rArq3R$2p=2D*RQ(Nu>{ILX* diff --git a/hutool-script/.jython_cache/packages/sunmscapi.pkc b/hutool-script/.jython_cache/packages/sunmscapi.pkc deleted file mode 100644 index 25f66dd2826580803d0bd3cb9d00692d344027d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 187 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

    JGyceLS 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 LMLK=9qpAN3Yz>wQ diff --git a/hutool-script/.jython_cache/packages/zipfs.pkc b/hutool-script/.jython_cache/packages/zipfs.pkc deleted file mode 100644 index b61230cd13af1aa708c5d5ae782c2b42b1d65ae6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 248 zcmZXOK?=e^3`NHycmlT~GN>zYrL+jTDimGFfJ|$OsqKtp(rUew2k>&XMMZHH{`*P( z7XyE$*1FUxJ)FjJDo6XtXsl8Km0_gd%Ib#a#K}~X5S{eV?**b8+g!TBNP||kwz#h~ zEir?zatwJuw!{O_aEE6f31YTXg-oFH1~rQdX2PgoaDQZraCyV9zJ!lxpZ}`wsFg`< Y;|7|K^Ll#r)xopIu{PXhZ5T861QXO-!~g&Q From 2c80766697c7f97fa82fc661cb3ca1b058bb1a8b Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 5 May 2020 23:13:51 +0800 Subject: [PATCH 099/157] add method for extra --- .gitignore | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 08aca5ccb..a93f9229b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,11 +7,11 @@ target/ dependency-reduced-pom.xml pom.xml.versionsBackup -.factorypath +.factorypath # Gradle .gradle/ -build/ +build/ #IDEA # idea ignore @@ -26,7 +26,8 @@ build/ *.diff *.patch *.tmp +.jython_cache/ # system ignore .DS_Store -Thumbs.db +Thumbs.db From 513fb1c861e182db889f67ac64e43138520ebcf0 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 5 May 2020 23:24:00 +0800 Subject: [PATCH 100/157] release 5.3.3 --- CHANGELOG.md | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 22 files changed, 22 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b1226517..e7cc441df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -## 5.3.3 (2020-04-29) +## 5.3.3 (2020-05-05) ### 新特性 * 【core 】 ImgUtil.createImage支持背景透明(issue#851@Github) diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 9c443ad64..6d80973b3 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 406bce953..4b17ea164 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index 78c924dc0..ab944d872 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 263934240..ee2b36f92 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 1b455ecf3..8ab79e0a4 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index eabf9fc58..8107c9ffe 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 7234dad0b..6f30bd822 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 2244d0bbb..992f433ad 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 86aeea71a..b9d6a945e 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index b6c77f6c4..7a8660073 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index fc686849b..c64885d1f 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 8ff91aa0a..ffbd00753 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 6d12b74df..077b480d3 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index f6cd3c24b..558cbe909 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index ae6bb36d6..e65c15d9b 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index abe2821d3..305b86836 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 63db46a14..37d9285fc 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 95f30366a..ce9564c4a 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 4500b4851..37cae4b97 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 6da67a273..3628b25da 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool-system diff --git a/pom.xml b/pom.xml index 9679f8698..3814798e7 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.3-SNAPSHOT + 5.3.3 hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool From bf65fa3c5faf22ebbb0d86d5ef61498c838d3f88 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 5 May 2020 23:49:41 +0800 Subject: [PATCH 101/157] fix bug --- CHANGELOG.md | 1 + .../src/main/java/cn/hutool/core/util/StrUtil.java | 7 +++++-- .../test/java/cn/hutool/core/util/StrUtilTest.java | 13 +++++++++++++ .../main/java/cn/hutool/crypto/digest/opt/HOTP.java | 2 ++ .../src/main/java/cn/hutool/json/JSONUtil.java | 1 + 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7cc441df..3082bb5ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ * 【core 】 修复URLBuilder中请求参数有`&`导致的问题(issue#850@Github) * 【core 】 修复URLBuilder中路径以`/`结尾导致的问题(issue#I1G44J@Gitee) * 【db 】 修复SqlBuilder中orderBy无效问题(issue#856@Github) +* 【core 】 修复StrUtil.subBetweenAll错误问题(issue#861@Github) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java index d2e89f18a..f7f8abc4a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java @@ -1510,7 +1510,7 @@ public class StrUtil { } /** - * 切分字符串 + * 切分字符串,如果分隔符不存在则返回原字符串 * * @param str 被切分的字符串 * @param separator 分隔符 @@ -1976,11 +1976,14 @@ public class StrUtil { * @since 5.2.5 */ public static String[] subBetweenAll(CharSequence str, CharSequence prefix, CharSequence suffix) { - if (hasEmpty(str, prefix, suffix)) { + if (hasEmpty(str, prefix, suffix) || + // 不包含起始字符串,则肯定没有子串 + false == contains(str, prefix)) { return new String[0]; } final List result = new LinkedList<>(); + final String[] split = split(str, prefix); for (String fragment : split(str, prefix)) { int suffixIndex = fragment.indexOf(suffix.toString()); if (suffixIndex > 0) { diff --git a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java index 770ded054..b893a7487 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java @@ -432,5 +432,18 @@ public class StrUtilTest { Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("abc",null,"z")); Assert.assertArrayEquals(new String[0], StrUtil.subBetweenAll("abc","y",null)); } + + @Test + public void subBetweenAllTest2() { + //issue#861@Github,起始不匹配的时候,应该直接空 + String src1 = "/* \n* hutool */ asdas /* \n* hutool */"; + String src2 = "/ * hutool */ asdas / * hutool */"; + + String[] results1 = StrUtil.subBetweenAll(src1,"/**","*/"); + Assert.assertEquals(0, results1.length); + + String[] results2 = StrUtil.subBetweenAll(src2,"/*","*/"); + Assert.assertEquals(0, results2.length); + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java index 4060d0727..cc3598aa5 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/opt/HOTP.java @@ -35,6 +35,8 @@ public class HOTP { /** * 构造,使用默认密码长度和默认HMAC算法(HmacSHA1) + * + * @param key 共享密码,RFC 4226要求最少128位 */ public HOTP(byte[] key) { this(DEFAULT_PASSWORD_LENGTH, key); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 4ab90c4a0..3bdbc3608 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -335,6 +335,7 @@ public final class JSONUtil { * 转为JSON字符串,并写出到write * * @param json JSON + * @param writer Writer * @since 5.3.3 */ public static void toJsonStr(JSON json, Writer writer) { From fdf735fa05b2bba32655838784b54d3b2e69ca09 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 6 May 2020 08:24:09 +0800 Subject: [PATCH 102/157] prepare 5.3.4 --- CHANGELOG.md | 8 ++++++++ README.md | 8 ++++---- bin/version.txt | 2 +- docs/js/version.js | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 25 files changed, 35 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3082bb5ef..4839b2267 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ ------------------------------------------------------------------------------------------------------------- +## 5.3.4 (2020-05-06) + +### 新特性 + +### Bug修复 + +------------------------------------------------------------------------------------------------------------- + ## 5.3.3 (2020-05-05) ### 新特性 diff --git a/README.md b/README.md index 2197e4ccd..c8a3daf1c 100644 --- a/README.md +++ b/README.md @@ -116,21 +116,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.3.3 + 5.3.4 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.3.3' +compile 'cn.hutool:hutool-all:5.3.4' ``` ### 非Maven项目 点击以下任一链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.3.3/) -- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.3/) +- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.3.4/) +- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.4/) > 注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index 74664af74..e0a61e6a8 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.3.3 +5.3.4 diff --git a/docs/js/version.js b/docs/js/version.js index 148a5aacd..7cef9f0c5 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.3.3' \ No newline at end of file +var version = '5.3.4' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 6d80973b3..235e99894 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 4b17ea164..ff0aa7af8 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index ab944d872..8b2b18e92 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index ee2b36f92..3a49d6e53 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 8ab79e0a4..e9aa94481 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 8107c9ffe..4de346566 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 6f30bd822..b60094660 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 992f433ad..2dbdf52c4 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index b9d6a945e..55e9a4182 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 7a8660073..df5c3aad8 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index c64885d1f..38e6a8db9 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index ffbd00753..0d478bfbc 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 077b480d3..9bd602671 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 558cbe909..26b8210c0 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index e65c15d9b..793e7f20a 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 305b86836..769cbee2f 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 37d9285fc..54a327e23 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index ce9564c4a..99172b2bf 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 37cae4b97..683b3774f 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 3628b25da..cfc795e7f 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 3814798e7..19b055e3e 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.3 + 5.3.4-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool From 7480d710cb546c1511233cfcef8f5bb9f826f93d Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 6 May 2020 08:38:38 +0800 Subject: [PATCH 103/157] fix method --- hutool-http/src/main/java/cn/hutool/http/HttpRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 189b53112..a23d367be 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -878,7 +878,7 @@ public class HttpRequest extends HttpBase { public HttpRequest setSSLProtocol(String protocol) { Assert.notBlank(protocol, "protocol must be not blank!"); try { - this.ssf = SSLSocketFactoryBuilder.create().setProtocol(protocol).build(); + setSSLSocketFactory(SSLSocketFactoryBuilder.create().setProtocol(protocol).build()); } catch (Exception e) { throw new HttpException(e); } From 5b3f989f7f1e1686eae385ed097a5dfbe5f29b91 Mon Sep 17 00:00:00 2001 From: zpzhaoa <57425586@qq.com> Date: Wed, 6 May 2020 17:08:23 +0800 Subject: [PATCH 104/157] fix NPE in TreeUtil getNode --- .../src/main/java/cn/hutool/core/lang/tree/TreeUtil.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java index 3ec88e31b..26b0160f0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java @@ -134,6 +134,11 @@ public class TreeUtil { return node; } + //fix NPE + if(null == node.getChildren()) { + return null; + } + // 查找子节点 Tree childNode; for (Tree child : node.getChildren()) { From d544dde3c028ecfc2426f01f004223887490ceae Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 7 May 2020 15:52:16 +0800 Subject: [PATCH 105/157] fix ftp bug --- CHANGELOG.md | 1 + hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4839b2267..5633ce1cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### 新特性 ### Bug修复 +* 【extra 】 修复Ftp设置超时问题 ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index e1ec874e6..3cdb4d991 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -13,7 +13,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.SocketException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; @@ -156,14 +155,10 @@ public class Ftp extends AbstractFtp { final FTPClient client = new FTPClient(); client.setControlEncoding(config.getCharset().toString()); client.setConnectTimeout((int) config.getConnectionTimeout()); - try { - client.setSoTimeout((int)config.getSoTimeout()); - } catch (SocketException e) { - //ignore - } try { // 连接ftp服务器 client.connect(config.getHost(), config.getPort()); + client.setSoTimeout((int)config.getSoTimeout()); // 登录ftp服务器 client.login(config.getUser(), config.getPassword()); } catch (IOException e) { From 987d9e35f5c51300cda17249463e511b4e68a1ad Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 7 May 2020 16:00:30 +0800 Subject: [PATCH 106/157] fix npe --- .../cn/hutool/core/lang/tree/TreeUtil.java | 6 +-- .../hutool/core/lang/tree/TreeSearchTest.java | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeSearchTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java index 26b0160f0..6432a7360 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeUtil.java @@ -134,14 +134,14 @@ public class TreeUtil { return node; } - //fix NPE - if(null == node.getChildren()) { + final List> children = node.getChildren(); + if(null == children) { return null; } // 查找子节点 Tree childNode; - for (Tree child : node.getChildren()) { + for (Tree child : children) { childNode = child.getNode(id); if (null != childNode) { return childNode; diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeSearchTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeSearchTest.java new file mode 100644 index 000000000..67e19ac04 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/tree/TreeSearchTest.java @@ -0,0 +1,37 @@ +package cn.hutool.core.lang.tree; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class TreeSearchTest { + static List> all_menu=new ArrayList<>(); + static { + /* + * root + * /module-A + * /module-A-menu-1 + * /module-B + * /module-B-menu-1 + * /module-B-menu-2 + */ + all_menu.add(new TreeNode<>(1L, 0L, "root", 0L)); + all_menu.add(new TreeNode<>(2L,1L,"module-A",0L)); + all_menu.add(new TreeNode<>(3L,1L,"module-B",0L)); + all_menu.add(new TreeNode<>(4L,2L,"module-A-menu-1",0L)); + all_menu.add(new TreeNode<>(5L,3L,"module-B-menu-1",0L)); + all_menu.add(new TreeNode<>(6L,3L,"module-B-menu-2",0L)); + } + + @Test + public void searchNode() { + List> treeItems=TreeUtil.build(all_menu, 0L); + + Tree tree=treeItems.get(0); + Tree searchResult=tree.getNode(3L); + + Assert.assertEquals("module-B", searchResult.getName()); + } +} From 8289e6a8da47db744f00b200a6d2e3f57acb1180 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 7 May 2020 17:37:02 +0800 Subject: [PATCH 107/157] fix code --- CHANGELOG.md | 2 ++ .../java/cn/hutool/cache/impl/TimedCache.java | 2 +- .../java/cn/hutool/core/util/URLUtil.java | 28 +++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5633ce1cf..d58197505 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,11 @@ ## 5.3.4 (2020-05-06) ### 新特性 +* 【core 】 增加URLUtil.getContentLength方法(issue#I1GB1Z@Gitee) ### Bug修复 * 【extra 】 修复Ftp设置超时问题 +* 【extra 】 修复TreeUtil根据id查找子节点时的NPE问题(pr#120@Gitee) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java index 03add0533..292ea326f 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java @@ -28,7 +28,7 @@ public class TimedCache extends AbstractCache { * @param timeout 超时(过期)时长,单位毫秒 */ public TimedCache(long timeout) { - this(timeout, new HashMap>()); + this(timeout, new HashMap<>()); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 8a75e508a..62359ef8a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -15,11 +15,13 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.net.URLConnection; import java.net.URLStreamHandler; import java.nio.charset.Charset; import java.util.Map; @@ -738,4 +740,30 @@ public class URLUtil { public static String buildQuery(Map paramMap, Charset charset) { return UrlQuery.of(paramMap).build(charset); } + + /** + * 获取指定URL对应资源的内容长度,对于Http,其长度使用Content-Length头决定。 + * + * @param url URL + * @return 内容长度,未知返回-1 + * @throws IORuntimeException IO异常 + * @since 5.3.4 + */ + public static long getContentLength(URL url) throws IORuntimeException{ + if(null == url){ + return -1; + } + + URLConnection conn = null; + try { + conn = url.openConnection(); + return conn.getContentLengthLong(); + } catch (IOException e) { + throw new IORuntimeException(e); + } finally { + if (conn instanceof HttpURLConnection) { + ((HttpURLConnection)conn).disconnect(); + } + } + } } \ No newline at end of file From c1edd9b40191ed1042eb67730ef896985934ec62 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 9 May 2020 21:50:15 +0800 Subject: [PATCH 108/157] fix copyProperties bug --- CHANGELOG.md | 6 ++-- .../java/cn/hutool/core/bean/BeanDesc.java | 2 +- .../java/cn/hutool/core/util/ArrayUtil.java | 36 ++++++++++++++++--- .../java/cn/hutool/core/util/ReflectUtil.java | 2 +- .../cn/hutool/core/bean/BeanUtilTest.java | 34 ++++++++++++++++-- .../cn/hutool/core/util/ArrayUtilTest.java | 13 +++++++ 6 files changed, 82 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d58197505..191631139 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,12 @@ ### 新特性 * 【core 】 增加URLUtil.getContentLength方法(issue#I1GB1Z@Gitee) +* 【extra 】 增加PinyinUtil(issue#I1GMIV@Gitee) ### Bug修复 -* 【extra 】 修复Ftp设置超时问题 -* 【extra 】 修复TreeUtil根据id查找子节点时的NPE问题(pr#120@Gitee) +* 【extra 】 修复Ftp设置超时问题(issue#I1GMTQ@Gitee) +* 【core 】 修复TreeUtil根据id查找子节点时的NPE问题(pr#120@Gitee) +* 【core 】 修复BeanUtil.copyProperties中Alias注解无效问题(issue#I1GK3M@Gitee) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java index 560805d5c..fb41e24ff 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanDesc.java @@ -141,7 +141,7 @@ public class BeanDesc implements Serializable{ for (Field field : ReflectUtil.getFields(this.beanClass)) { if(false == ModifierUtil.isStatic(field)) { //只针对非static属性 - this.propMap.put(field.getName(), createProp(field)); + this.propMap.put(ReflectUtil.getFieldName(field), createProp(field)); } } return this; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java index 4be5ea355..57422dd04 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java @@ -3,6 +3,7 @@ package cn.hutool.core.util; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.IterUtil; +import cn.hutool.core.comparator.CompareUtil; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Editor; import cn.hutool.core.lang.Filter; @@ -13,6 +14,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; @@ -3393,19 +3395,32 @@ public class ArrayUtil { // ------------------------------------------------------------------------------------------------------------ min and max /** * 取最小值 - * + * * @param 元素类型 * @param numberArray 数字数组 * @return 最小值 * @since 3.0.9 */ public static > T min(T[] numberArray) { + return min(numberArray, null); + } + + /** + * 取最小值 + * + * @param 元素类型 + * @param numberArray 数字数组 + * @param comparator 比较器,null按照默认比较 + * @return 最小值 + * @since 5.3.4 + */ + public static > T min(T[] numberArray, Comparator comparator) { if (isEmpty(numberArray)) { throw new IllegalArgumentException("Number array must not empty !"); } T min = numberArray[0]; for (T t : numberArray) { - if (ObjectUtil.compare(min, t) > 0) { + if (CompareUtil.compare(min, t, comparator) > 0) { min = t; } } @@ -3554,19 +3569,32 @@ public class ArrayUtil { /** * 取最大值 - * + * * @param 元素类型 * @param numberArray 数字数组 * @return 最大值 * @since 3.0.9 */ public static > T max(T[] numberArray) { + return max(numberArray, null); + } + + /** + * 取最大值 + * + * @param 元素类型 + * @param numberArray 数字数组 + * @param comparator 比较器,null表示默认比较器 + * @return 最大值 + * @since 5.3.4 + */ + public static > T max(T[] numberArray, Comparator comparator) { if (isEmpty(numberArray)) { throw new IllegalArgumentException("Number array must not empty !"); } T max = numberArray[0]; for (int i = 1; i < numberArray.length; i++) { - if (ObjectUtil.compare(max, numberArray[i]) < 0) { + if (CompareUtil.compare(max, numberArray[i], comparator) < 0) { max = numberArray[i]; } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java index f805ff586..17a5f8c2f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java @@ -149,7 +149,7 @@ public class ReflectUtil { final Field[] fields = getFields(beanClass); if (ArrayUtil.isNotEmpty(fields)) { for (Field field : fields) { - if ((name.equals(field.getName()))) { + if ((name.equals(getFieldName(field)))) { return field; } } diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java index 16112e51d..2297e75fe 100644 --- a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java @@ -5,12 +5,14 @@ import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.bean.copier.ValueProvider; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; +import lombok.Data; import lombok.Getter; import lombok.Setter; import org.junit.Assert; import org.junit.Test; import java.beans.PropertyDescriptor; +import java.io.Serializable; import java.lang.reflect.Type; import java.time.LocalDate; import java.time.LocalDateTime; @@ -213,7 +215,7 @@ public class BeanUtilTest { } @Test - public void copyProperties() { + public void copyPropertiesTest() { SubPerson person = new SubPerson(); person.setAge(14); person.setOpenid("11213232"); @@ -331,9 +333,9 @@ public class BeanUtilTest { } @Test - public void beanToBeanTest(){ + public void beanToBeanTest() { // 修复对象无getter方法导致报错的问题 - Page page1=new Page(); + Page page1 = new Page(); BeanUtil.toBean(page1, Page.class); } @@ -349,4 +351,30 @@ public class BeanUtilTest { return this; } } + + @Test + public void copyBeanToBeanTest() { + // 测试在copyProperties方法中alias是否有效 + Food info = new Food(); + info.setBookID("0"); + info.setCode("123"); + HllFoodEntity entity = new HllFoodEntity(); + BeanUtil.copyProperties(info, entity); + Assert.assertEquals(info.getBookID(), entity.getBookId()); + Assert.assertEquals(info.getCode(), entity.getCode2()); + } + + @Data + public static class Food { + @Alias("bookId") + private String bookID; + private String code; + } + + @Data + public static class HllFoodEntity implements Serializable { + private String bookId; + @Alias("code") + private String code2; + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index c613c01a9..7b6132d99 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -6,7 +6,9 @@ import cn.hutool.core.lang.Filter; import org.junit.Assert; import org.junit.Test; +import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Comparator; import java.util.Map; import java.util.Objects; @@ -161,6 +163,17 @@ public class ArrayUtilTest { double maxDouble = ArrayUtil.max(1D, 2.4D, 13.0D, 4.55D, 5D); Assert.assertEquals(13.0, maxDouble, 2); + + BigDecimal one = new BigDecimal("1.00"); + BigDecimal two = new BigDecimal("2.0"); + BigDecimal three = new BigDecimal("3"); + BigDecimal[] bigDecimals = {two,one,three}; + + BigDecimal minAccuracy = ArrayUtil.min(bigDecimals, Comparator.comparingInt(BigDecimal::scale)); + Assert.assertEquals(minAccuracy,three); + + BigDecimal maxAccuracy = ArrayUtil.max(bigDecimals,Comparator.comparingInt(BigDecimal::scale)); + Assert.assertEquals(maxAccuracy,one); } @Test From bd4a0a075f45e7d654ecdc8e59fa10e9c6ec035c Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 10 May 2020 00:06:11 +0800 Subject: [PATCH 109/157] fix bug --- CHANGELOG.md | 2 ++ .../cn/hutool/core/collection/CollUtil.java | 10 ++++++- .../java/cn/hutool/core/date/DateUtil.java | 17 +++++++++-- .../java/cn/hutool/core/util/XmlUtil.java | 17 ++++++++++- .../hutool/core/collection/CollUtilTest.java | 6 ++-- .../cn/hutool/core/date/DateUtilTest.java | 9 ++++++ .../java/cn/hutool/core/util/XmlUtilTest.java | 30 +++++++++++++++++++ 7 files changed, 85 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 191631139..268f22d9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ * 【extra 】 修复Ftp设置超时问题(issue#I1GMTQ@Gitee) * 【core 】 修复TreeUtil根据id查找子节点时的NPE问题(pr#120@Gitee) * 【core 】 修复BeanUtil.copyProperties中Alias注解无效问题(issue#I1GK3M@Gitee) +* 【core 】 修复CollUtil.containsAll空集合判断问题(issue#I1G9DE@Gitee) +* 【core 】 修复XmlUtil.xmlToBean失败问题(issue#865@Github) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index b2f3a74f6..3efe98bfa 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -307,7 +307,15 @@ public class CollUtil { * @since 4.5.12 */ public static boolean containsAll(Collection coll1, Collection coll2) { - if (isEmpty(coll1) || isEmpty(coll2) || coll1.size() < coll2.size()) { + if(isEmpty(coll1)){ + return isEmpty(coll2); + } + + if(isEmpty(coll2)){ + return true; + } + + if (coll1.size() < coll2.size()) { return false; } 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 6bd5f0bfd..ae35f4c3f 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 @@ -714,7 +714,14 @@ public class DateUtil extends CalendarUtil { } /** - * 格式yyyy-MM-dd HH:mm:ss + * 解析日期时间字符串,格式支持: + * + *

    +	 * yyyy-MM-dd HH:mm:ss
    +	 * yyyy/MM/dd HH:mm:ss
    +	 * yyyy.MM.dd HH:mm:ss
    +	 * yyyy年MM月dd日 HH:mm:ss
    +	 * 
    * * @param dateString 标准形式的时间字符串 * @return 日期对象 @@ -725,7 +732,13 @@ public class DateUtil extends CalendarUtil { } /** - * 解析格式为yyyy-MM-dd的日期,忽略时分秒 + * 解析日期字符串,忽略时分秒,支持的格式包括: + *
    +	 * yyyy-MM-dd
    +	 * yyyy/MM/dd
    +	 * yyyy.MM.dd
    +	 * yyyy年MM月dd日
    +	 * 
    * * @param dateString 标准形式的日期字符串 * @return 日期对象 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java index c7e6bd7bf..366f81763 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java @@ -835,7 +835,11 @@ public class XmlUtil { * @since 5.2.4 */ public static T xmlToBean(Node node, Class bean) { - return BeanUtil.toBean(xmlToMap(node), bean); + final Map map = xmlToMap(node); + if(null != map && map.size() == 1){ + return BeanUtil.toBean(map.get(bean.getSimpleName()), bean); + } + return BeanUtil.toBean(map, bean); } /** @@ -1042,6 +1046,17 @@ public class XmlUtil { return doc; } + /** + * 将Bean转换为XML + * + * @param bean Bean对象 + * @return XML + * @since 5.3.4 + */ + public static Document beanToXml(Object bean) { + return beanToXml(bean, null); + } + /** * 将Bean转换为XML * diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java index 114265d91..c3b98bd01 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java @@ -36,7 +36,6 @@ public class CollUtilTest { @Test public void isNotEmptyTest(){ Assert.assertFalse(CollUtil.isNotEmpty((Collection) null)); - ; } @Test @@ -589,8 +588,11 @@ public class CollUtilTest { public void containsAllTest() { ArrayList list1 = CollUtil.newArrayList(1, 2, 3, 4, 5); ArrayList list2 = CollUtil.newArrayList(5, 3, 1); - Assert.assertTrue(CollUtil.containsAll(list1, list2)); + + ArrayList list3 = CollUtil.newArrayList(1); + ArrayList list4 = CollUtil.newArrayList(); + Assert.assertTrue(CollUtil.containsAll(list3, list4)); } @Test diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index d769e06b9..73af5b1f6 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.BetweenFormater.Level; import cn.hutool.core.date.format.FastDateFormat; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.text.SimpleDateFormat; @@ -379,6 +380,14 @@ public class DateUtilTest { Assert.assertEquals("2019-06-01 19:45:43", dateTime.toString()); } + @Test + @Ignore + public void parseTest8() { + String str = "2020-04-24 9:00:00"; + DateTime dateTime = DateUtil.parse(str); + Assert.assertEquals("2019-06-01 19:45:43", dateTime.toString()); + } + @Test public void parseAndOffsetTest() { // 检查UTC时间偏移是否准确 diff --git a/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java index 0838cca6b..44bf1afbf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.map.MapBuilder; import cn.hutool.core.map.MapUtil; +import lombok.Data; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -174,4 +175,33 @@ public class XmlUtilTest { document,XPathConstants.STRING);// Assert.assertEquals("2020/04/15 21:01:21", value); } + + @Test + public void xmlToBeanTest(){ + final TestBean testBean = new TestBean(); + testBean.setReqCode("1111"); + testBean.setAccountName("账户名称"); + testBean.setOperator("cz"); + testBean.setProjectCode("123"); + testBean.setBankCode("00001"); + + final Document doc = XmlUtil.beanToXml(testBean); + Assert.assertEquals(TestBean.class.getSimpleName(), doc.getDocumentElement().getTagName()); + + final TestBean testBean2 = XmlUtil.xmlToBean(doc, TestBean.class); + Assert.assertEquals(testBean.getReqCode(), testBean2.getReqCode()); + Assert.assertEquals(testBean.getAccountName(), testBean2.getAccountName()); + Assert.assertEquals(testBean.getOperator(), testBean2.getOperator()); + Assert.assertEquals(testBean.getProjectCode(), testBean2.getProjectCode()); + Assert.assertEquals(testBean.getBankCode(), testBean2.getBankCode()); + } + + @Data + public static class TestBean{ + private String ReqCode; + private String AccountName; + private String Operator; + private String ProjectCode; + private String BankCode; + } } From c2d70a4613954fd13484b2ba849dae891c539f6e Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 10 May 2020 00:14:29 +0800 Subject: [PATCH 110/157] release 5.3.4 --- CHANGELOG.md | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 22 files changed, 22 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 268f22d9d..716aa0aa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -## 5.3.4 (2020-05-06) +## 5.3.4 (2020-05-10) ### 新特性 * 【core 】 增加URLUtil.getContentLength方法(issue#I1GB1Z@Gitee) diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 235e99894..a98c998e9 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index ff0aa7af8..0a1ad1370 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index 8b2b18e92..6c3cc0d55 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 3a49d6e53..c3eb5360a 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index e9aa94481..3a5431ceb 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 4de346566..c93e3ed74 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index b60094660..36e8b22fb 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 2dbdf52c4..a4623f943 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 55e9a4182..102d38582 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index df5c3aad8..defbc4169 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 38e6a8db9..624852bd5 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 0d478bfbc..3367e87d4 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 9bd602671..4ab2ce180 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 26b8210c0..b5849343d 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 793e7f20a..742f7d056 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 769cbee2f..cb59ee5cd 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 54a327e23..c352046ad 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 99172b2bf..eb0f5ae89 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 683b3774f..3477a9086 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index cfc795e7f..374a589cd 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool-system diff --git a/pom.xml b/pom.xml index 19b055e3e..feb549c86 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.4-SNAPSHOT + 5.3.4 hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool From 58fab104c765643c0e37a651965b495c645dd9a7 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 10 May 2020 08:50:33 +0800 Subject: [PATCH 111/157] prepare 5.3.5 --- CHANGELOG.md | 7 +++++++ README.md | 8 ++++---- bin/version.txt | 2 +- docs/js/version.js | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 25 files changed, 34 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 716aa0aa5..eda3d77df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ ------------------------------------------------------------------------------------------------------------- +## 5.3.5 (2020-05-10) + +### 新特性 +### Bug修复 + +------------------------------------------------------------------------------------------------------------- + ## 5.3.4 (2020-05-10) ### 新特性 diff --git a/README.md b/README.md index c8a3daf1c..ecec3bb8b 100644 --- a/README.md +++ b/README.md @@ -116,21 +116,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.3.4 + 5.3.5 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.3.4' +compile 'cn.hutool:hutool-all:5.3.5' ``` ### 非Maven项目 点击以下任一链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.3.4/) -- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.4/) +- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.3.5/) +- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.5/) > 注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index e0a61e6a8..e61ecd12d 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.3.4 +5.3.5 diff --git a/docs/js/version.js b/docs/js/version.js index 7cef9f0c5..fd0d2385c 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.3.4' \ No newline at end of file +var version = '5.3.5' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index a98c998e9..9d80bdaf5 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 0a1ad1370..9d3c57ef0 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index 6c3cc0d55..0855fd857 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index c3eb5360a..8e7683f7f 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 3a5431ceb..d62a8cebd 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index c93e3ed74..2f539f943 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 36e8b22fb..985e0b937 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index a4623f943..d5ab802af 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 102d38582..ab276d73d 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index defbc4169..17de4984a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 624852bd5..1ca335845 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 3367e87d4..83a4e5abc 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 4ab2ce180..891cfd36c 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index b5849343d..6981ac28c 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 742f7d056..2b4b8f653 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index cb59ee5cd..df4fde6dc 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index c352046ad..bec25453d 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index eb0f5ae89..f21d12d87 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 3477a9086..60e917d72 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 374a589cd..73c08115c 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index feb549c86..658ec56c8 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.4 + 5.3.5-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool From a2303b11db39fa3578adc95d7c56d6606bbf02c5 Mon Sep 17 00:00:00 2001 From: chenzz Date: Mon, 11 May 2020 11:09:01 +0800 Subject: [PATCH 112/157] =?UTF-8?q?add:=20=E9=80=92=E5=BD=92=E4=B8=8B?= =?UTF-8?q?=E8=BD=BDFTP=E6=9C=8D=E5=8A=A1=E5=99=A8=E4=B8=8A=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=88=B0=E6=9C=AC=E5=9C=B0(=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=99=AE=E9=80=9Aftp=E5=92=8Csftp)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/hutool/extra/ftp/AbstractFtp.java | 8 ++ .../main/java/cn/hutool/extra/ftp/Ftp.java | 29 +++++++ .../main/java/cn/hutool/extra/ssh/Sftp.java | 84 +++++++++++++------ .../java/cn/hutool/extra/ftp/FtpTest.java | 22 +++++ 4 files changed, 117 insertions(+), 26 deletions(-) diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java index 6b8397557..3e21149a4 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java @@ -154,6 +154,14 @@ public abstract class AbstractFtp implements Closeable { */ public abstract void download(String path, File outFile); + /** + * 递归下载FTP服务器上文件到本地(文件目录和服务器同步), 服务器上有新文件会覆盖本地文件 + * + * @param sourcePath ftp服务器目录 + * @param destinationPath 本地目录 + */ + public abstract void recursiveDownloadFolder(String sourcePath, String destinationPath) throws Exception; + // ---------------------------------------------------------------------------------------------------------------------------------------- Private method start /** * 是否包含指定字符串,忽略大小写 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index 3cdb4d991..ed7b7841e 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -476,6 +476,35 @@ public class Ftp extends AbstractFtp { download(dir, fileName, outFile); } + /** + * 递归下载FTP服务器上文件到本地(文件目录和服务器同步) + * + * @param sourcePath ftp服务器目录 + * @param destinationPath 本地目录 + */ + @Override + public void recursiveDownloadFolder(String sourcePath, String destinationPath) { + String pathSeparator = "/"; + FTPFile[] lsFiles = lsFiles(sourcePath); + + for (FTPFile ftpFile : lsFiles) { + String sourcePathPathFile = sourcePath + pathSeparator + ftpFile.getName(); + String destinationPathFile = destinationPath + pathSeparator + ftpFile.getName(); + + if (!ftpFile.isDirectory()) { + // 本地不存在文件或者ftp上文件有修改则下载 + if (!FileUtil.exist(destinationPathFile) + || (ftpFile.getTimestamp().getTimeInMillis() > FileUtil.lastModifiedTime(destinationPathFile).getTime())) { + // Download file from source (source filename, destination filename). + download(sourcePathPathFile, FileUtil.file(destinationPathFile)); + } + } else if (!(".".equals(ftpFile.getName()) || "..".equals(ftpFile.getName()))) { + FileUtil.mkdir(destinationPathFile); + recursiveDownloadFolder(sourcePathPathFile, destinationPathFile); + } + } + } + /** * 下载文件 * diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java index d63ea63f9..5ed1f8f63 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java @@ -22,12 +22,12 @@ import java.util.Vector; * SFTP是Secure File Transfer Protocol的缩写,安全文件传送协议。可以为传输文件提供一种安全的加密方法。
    * SFTP 为 SSH的一部份,是一种传输文件到服务器的安全方式。SFTP是使用加密传输认证信息和传输的数据,所以,使用SFTP是非常安全的。
    * 但是,由于这种传输方式使用了加密/解密技术,所以传输效率比普通的FTP要低得多,如果您对网络安全性要求更高时,可以使用SFTP代替FTP。
    - * + * *

    * 此类为基于jsch的SFTP实现
    * 参考:https://www.cnblogs.com/longyg/archive/2012/06/25/2556576.html *

    - * + * * @author looly * @since 4.0.2 */ @@ -39,7 +39,7 @@ public class Sftp extends AbstractFtp { // ---------------------------------------------------------------------------------------- Constructor start /** * 构造 - * + * * @param sshHost 远程主机 * @param sshPort 远程主机端口 * @param sshUser 远程主机用户名 @@ -51,7 +51,7 @@ public class Sftp extends AbstractFtp { /** * 构造 - * + * * @param sshHost 远程主机 * @param sshPort 远程主机端口 * @param sshUser 远程主机用户名 @@ -76,7 +76,7 @@ public class Sftp extends AbstractFtp { /** * 构造 - * + * * @param session {@link Session} */ public Sftp(Session session) { @@ -85,7 +85,7 @@ public class Sftp extends AbstractFtp { /** * 构造 - * + * * @param session {@link Session} * @param charset 编码 * @since 4.1.14 @@ -97,7 +97,7 @@ public class Sftp extends AbstractFtp { /** * 构造 - * + * * @param channel {@link ChannelSftp} * @param charset 编码 */ @@ -109,7 +109,7 @@ public class Sftp extends AbstractFtp { /** * 构造 - * + * * @param sshHost 远程主机 * @param sshPort 远程主机端口 * @param sshUser 远程主机用户名 @@ -141,7 +141,7 @@ public class Sftp extends AbstractFtp { /** * 初始化 - * + * * @param session {@link Session} * @param charset 编码 */ @@ -152,7 +152,7 @@ public class Sftp extends AbstractFtp { /** * 初始化 - * + * * @param channel {@link ChannelSftp} * @param charset 编码 */ @@ -176,7 +176,7 @@ public class Sftp extends AbstractFtp { /** * 获取SFTP通道客户端 - * + * * @return 通道客户端 * @since 4.1.14 */ @@ -186,7 +186,7 @@ public class Sftp extends AbstractFtp { /** * 远程当前目录 - * + * * @return 远程当前目录 */ @Override @@ -200,7 +200,7 @@ public class Sftp extends AbstractFtp { /** * 获取HOME路径 - * + * * @return HOME路径 * @since 4.0.5 */ @@ -214,7 +214,7 @@ public class Sftp extends AbstractFtp { /** * 遍历某个目录下所有文件或目录,不会递归遍历 - * + * * @param path 遍历某个目录下所有文件或目录 * @return 目录或文件名列表 * @since 4.0.5 @@ -226,7 +226,7 @@ public class Sftp extends AbstractFtp { /** * 遍历某个目录下所有目录,不会递归遍历 - * + * * @param path 遍历某个目录下所有目录 * @return 目录名列表 * @since 4.0.5 @@ -237,7 +237,7 @@ public class Sftp extends AbstractFtp { /** * 遍历某个目录下所有文件,不会递归遍历 - * + * * @param path 遍历某个目录下所有文件 * @return 文件名列表 * @since 4.0.5 @@ -248,7 +248,7 @@ public class Sftp extends AbstractFtp { /** * 遍历某个目录下所有文件或目录,不会递归遍历 - * + * * @param path 遍历某个目录下所有文件或目录 * @param filter 文件或目录过滤器,可以实现过滤器返回自己需要的文件或目录名列表 * @return 目录或文件名列表 @@ -287,7 +287,7 @@ public class Sftp extends AbstractFtp { /** * 打开指定目录,如果指定路径非目录或不存在返回false - * + * * @param directory directory * @return 是否打开目录 */ @@ -307,7 +307,7 @@ public class Sftp extends AbstractFtp { /** * 删除文件 - * + * * @param filePath 要删除的文件绝对路径 */ @Override @@ -322,7 +322,7 @@ public class Sftp extends AbstractFtp { /** * 删除文件夹及其文件夹下的所有文件 - * + * * @param dirPath 文件夹路径 * @return boolean 是否删除成功 */ @@ -373,7 +373,7 @@ public class Sftp extends AbstractFtp { /** * 将本地文件上传到目标服务器,目标文件名为destPath,若destPath为目录,则目标文件名将与srcFilePath文件名相同。覆盖模式 - * + * * @param srcFilePath 本地文件路径 * @param destPath 目标路径, * @return this @@ -384,7 +384,7 @@ public class Sftp extends AbstractFtp { /** * 将本地文件上传到目标服务器,目标文件名为destPath,若destPath为目录,则目标文件名将与srcFilePath文件名相同。 - * + * * @param srcFilePath 本地文件路径 * @param destPath 目标路径, * @param mode {@link Mode} 模式 @@ -393,10 +393,10 @@ public class Sftp extends AbstractFtp { public Sftp put(String srcFilePath, String destPath, Mode mode) { return put(srcFilePath, destPath, null, mode); } - + /** * 将本地文件上传到目标服务器,目标文件名为destPath,若destPath为目录,则目标文件名将与srcFilePath文件名相同。 - * + * * @param srcFilePath 本地文件路径 * @param destPath 目标路径, * @param monitor 上传进度监控,通过实现此接口完成进度显示 @@ -418,9 +418,41 @@ public class Sftp extends AbstractFtp { get(src, FileUtil.getAbsolutePath(destFile)); } + /** + * 递归下载FTP服务器上文件到本地(文件目录和服务器同步) + * + * @param sourcePath ftp服务器目录 + * @param destinationPath 本地目录 + */ + @Override + public void recursiveDownloadFolder(String sourcePath, String destinationPath) throws Exception { + String pathSeparator = "/"; + Vector fileAndFolderList = channel.ls(sourcePath); + + //Iterate through list of folder content + for (ChannelSftp.LsEntry item : fileAndFolderList) { + + String sourcePathPathFile = sourcePath + pathSeparator + item.getFilename(); + String destinationPathFile = destinationPath + pathSeparator + item.getFilename(); + + if (!item.getAttrs().isDir()) { + // 本地不存在文件或者ftp上文件有修改则下载 + if (!FileUtil.exist(destinationPathFile) + || (item.getAttrs().getMTime() > (FileUtil.lastModifiedTime(destinationPathFile).getTime() / 1000))) { + // Download file from source (source filename, destination filename). + channel.get(sourcePathPathFile, destinationPathFile); + } + } else if (!(".".equals(item.getFilename()) || "..".equals(item.getFilename()))) { + FileUtil.mkdir(destinationPathFile); + recursiveDownloadFolder(sourcePathPathFile, destinationPathFile); + } + } + + } + /** * 获取远程文件 - * + * * @param src 远程文件路径 * @param dest 目标文件路径 * @return this @@ -451,7 +483,7 @@ public class Sftp extends AbstractFtp { /** * JSch支持的三种文件传输模式 - * + * * @author looly * */ diff --git a/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java b/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java index ede110773..f2609ebe3 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java @@ -2,6 +2,7 @@ package cn.hutool.extra.ftp; import java.util.List; +import cn.hutool.extra.ssh.Sftp; import org.junit.Ignore; import org.junit.Test; @@ -59,4 +60,25 @@ public class FtpTest { IoUtil.close(ftp); } + + @Test + @Ignore + public void recursiveDownloadFolder() throws Exception { + Ftp ftp = new Ftp("looly.centos"); + ftp.recursiveDownloadFolder("/","d:/test/download"); + + IoUtil.close(ftp); + } + + @Test + @Ignore + public void recursiveDownloadFolderSftp() throws Exception { + Sftp ftp = new Sftp("127.0.0.1", 22, "test", "test"); + + ftp.cd("/file/aaa"); + Console.log(ftp.pwd()); + ftp.recursiveDownloadFolder("/","d:/test/download"); + + IoUtil.close(ftp); + } } From fa936f47424836d385cf0f6a3f963c166c9821eb Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 11 May 2020 23:18:02 +0800 Subject: [PATCH 113/157] add recursiveDownloadFolder --- CHANGELOG.md | 4 +- .../cn/hutool/core/collection/CollUtil.java | 26 +++- .../main/java/cn/hutool/core/io/FileUtil.java | 2 +- .../java/cn/hutool/extra/ftp/AbstractFtp.java | 54 +++++---- .../main/java/cn/hutool/extra/ftp/Ftp.java | 67 ++++++++--- .../main/java/cn/hutool/extra/ssh/Sftp.java | 113 ++++++++++++------ .../java/cn/hutool/extra/ftp/FtpTest.java | 19 ++- 7 files changed, 186 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eda3d77df..633b3c474 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,11 @@ ------------------------------------------------------------------------------------------------------------- -## 5.3.5 (2020-05-10) +## 5.3.5 (2020-05-11) ### 新特性 +* 【core 】 增加CollUtil.map方法 +* 【extra 】 增加Sftp.lsEntries方法,Ftp和Sftp增加recursiveDownloadFolder(pr#121@Gitee) ### Bug修复 ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index 3efe98bfa..a4ccaf888 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -49,6 +49,7 @@ import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.LinkedBlockingDeque; +import java.util.function.Function; /** * 集合相关工具类 @@ -1170,16 +1171,31 @@ public class CollUtil { * @param ignoreNull 是否忽略空值 * @return 抽取后的新列表 * @since 4.5.7 + * @see #map(Iterable, Function, boolean) */ public static List extract(Iterable collection, Editor editor, boolean ignoreNull) { - final List fieldValueList = new ArrayList<>(); + return map(collection, editor::edit, ignoreNull); + } + + /** + * 通过func自定义一个规则,此规则将原集合中的元素转换成新的元素,生成新的列表返回
    + * 例如提供的是一个Bean列表,通过Function接口实现获取某个字段值,返回这个字段值组成的新列表 + * + * @param collection 原集合 + * @param func 编辑函数 + * @param ignoreNull 是否忽略空值 + * @return 抽取后的新列表 + * @since 5.3.5 + */ + public static List map(Iterable collection, Function func, boolean ignoreNull) { + final List fieldValueList = new ArrayList<>(); if (null == collection) { return fieldValueList; } - Object value; - for (Object bean : collection) { - value = editor.edit(bean); + R value; + for (T bean : collection) { + value = func.apply(bean); if (null == value && ignoreNull) { continue; } @@ -1212,7 +1228,7 @@ public class CollUtil { * @since 4.5.7 */ public static List getFieldValues(Iterable collection, final String fieldName, boolean ignoreNull) { - return extract(collection, bean -> { + return map(collection, bean -> { if (bean instanceof Map) { return ((Map) bean).get(fieldName); } else { diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 05da50528..2b30df3a2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -567,7 +567,7 @@ public class FileUtil { * @return 最后修改时间 */ public static Date lastModifiedTime(File file) { - if (!exist(file)) { + if (false == exist(file)) { return null; } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java index 3e21149a4..2b45d1ed9 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/AbstractFtp.java @@ -12,13 +12,13 @@ import java.util.List; /** * 抽象FTP类,用于定义通用的FTP方法 - * + * * @author looly * @since 4.1.14 */ public abstract class AbstractFtp implements Closeable { - - public static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8 ; + + public static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; protected FtpConfig ftpConfig; @@ -28,21 +28,21 @@ public abstract class AbstractFtp implements Closeable { * @param config FTP配置 * @since 5.3.3 */ - protected AbstractFtp(FtpConfig config){ + protected AbstractFtp(FtpConfig config) { this.ftpConfig = config; } /** * 如果连接超时的话,重新进行连接 - * @since 4.5.2 - * + * * @return this + * @since 4.5.2 */ public abstract AbstractFtp reconnectIfTimeout(); - + /** * 打开指定目录 - * + * * @param directory directory * @return 是否打开目录 */ @@ -50,7 +50,7 @@ public abstract class AbstractFtp implements Closeable { /** * 打开上级目录 - * + * * @return 是否打开目录 * @since 4.0.5 */ @@ -60,14 +60,14 @@ public abstract class AbstractFtp implements Closeable { /** * 远程当前目录(工作目录) - * + * * @return 远程当前目录 */ public abstract String pwd(); /** * 在当前远程目录(工作目录)下创建新的目录 - * + * * @param dir 目录名 * @return 是否创建成功 */ @@ -75,7 +75,7 @@ public abstract class AbstractFtp implements Closeable { /** * 文件或目录是否存在 - * + * * @param path 目录 * @return 是否存在 */ @@ -88,7 +88,7 @@ public abstract class AbstractFtp implements Closeable { /** * 遍历某个目录下所有文件和目录,不会递归遍历 - * + * * @param path 需要遍历的目录 * @return 文件和目录列表 */ @@ -96,7 +96,7 @@ public abstract class AbstractFtp implements Closeable { /** * 删除指定目录下的指定文件 - * + * * @param path 目录路径 * @return 是否存在 */ @@ -104,7 +104,7 @@ public abstract class AbstractFtp implements Closeable { /** * 删除文件夹及其文件夹下的所有文件 - * + * * @param dirPath 文件夹路径 * @return boolean 是否删除成功 */ @@ -112,14 +112,14 @@ public abstract class AbstractFtp implements Closeable { /** * 创建指定文件夹及其父目录,从根目录开始创建,创建完成后回到默认的工作目录 - * + * * @param dir 文件夹路径,绝对路径 */ public void mkDirs(String dir) { final String[] dirs = StrUtil.trim(dir).split("[\\\\/]+"); final String now = pwd(); - if(dirs.length > 0 && StrUtil.isEmpty(dirs[0])) { + if (dirs.length > 0 && StrUtil.isEmpty(dirs[0])) { //首位为空,表示以/开头 this.cd(StrUtil.SLASH); } @@ -139,17 +139,17 @@ public abstract class AbstractFtp implements Closeable { /** * 将本地文件上传到目标服务器,目标文件名为destPath,若destPath为目录,则目标文件名将与file文件名相同。 * 覆盖模式 - * + * * @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径 - * @param file 需要上传的文件 + * @param file 需要上传的文件 * @return 是否成功 */ public abstract boolean upload(String destPath, File file); /** * 下载文件 - * - * @param path 文件路径 + * + * @param path 文件路径 * @param outFile 输出文件或目录 */ public abstract void download(String path, File outFile); @@ -157,16 +157,18 @@ public abstract class AbstractFtp implements Closeable { /** * 递归下载FTP服务器上文件到本地(文件目录和服务器同步), 服务器上有新文件会覆盖本地文件 * - * @param sourcePath ftp服务器目录 - * @param destinationPath 本地目录 + * @param sourcePath ftp服务器目录 + * @param destDir 本地目录 + * @since 5.3.5 */ - public abstract void recursiveDownloadFolder(String sourcePath, String destinationPath) throws Exception; + public abstract void recursiveDownloadFolder(String sourcePath, File destDir); // ---------------------------------------------------------------------------------------------------------------------------------------- Private method start + /** * 是否包含指定字符串,忽略大小写 - * - * @param names 文件或目录名列表 + * + * @param names 文件或目录名列表 * @param nameToFind 要查找的文件或目录名 * @return 是否包含 */ diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index ed7b7841e..7f64b16b0 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -1,7 +1,9 @@ package cn.hutool.extra.ftp; +import cn.hutool.core.collection.ListUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Filter; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; @@ -158,7 +160,7 @@ public class Ftp extends AbstractFtp { try { // 连接ftp服务器 client.connect(config.getHost(), config.getPort()); - client.setSoTimeout((int)config.getSoTimeout()); + client.setSoTimeout((int) config.getSoTimeout()); // 登录ftp服务器 client.login(config.getUser(), config.getPassword()); } catch (IOException e) { @@ -277,6 +279,34 @@ public class Ftp extends AbstractFtp { return fileNames; } + /** + * 遍历某个目录下所有文件和目录,不会递归遍历
    + * 此方法自动过滤"."和".."两种目录 + * + * @param path 目录 + * @param filter 过滤器,null表示不过滤,默认去掉"."和".."两种目录 + * @return 文件或目录列表 + * @since 5.3.5 + */ + public List lsFiles(String path, Filter filter) { + final FTPFile[] ftpFiles = lsFiles(path); + if (ArrayUtil.isEmpty(ftpFiles)) { + return ListUtil.empty(); + } + + final List result = new ArrayList<>(ftpFiles.length - 2); + String fileName; + for (FTPFile ftpFile : ftpFiles) { + fileName = ftpFile.getName(); + if (false == StrUtil.equals(".", fileName) && false == StrUtil.equals("..", fileName)) { + if (null == filter || filter.accept(ftpFile)) { + result.add(ftpFile); + } + } + } + return result; + } + /** * 遍历某个目录下所有文件和目录,不会递归遍历 * @@ -479,28 +509,29 @@ public class Ftp extends AbstractFtp { /** * 递归下载FTP服务器上文件到本地(文件目录和服务器同步) * - * @param sourcePath ftp服务器目录 - * @param destinationPath 本地目录 + * @param sourcePath ftp服务器目录 + * @param destDir 本地目录 */ @Override - public void recursiveDownloadFolder(String sourcePath, String destinationPath) { - String pathSeparator = "/"; - FTPFile[] lsFiles = lsFiles(sourcePath); + public void recursiveDownloadFolder(String sourcePath, File destDir) { + String fileName; + String srcFile; + File destFile; + for (FTPFile ftpFile : lsFiles(sourcePath, null)) { + fileName = ftpFile.getName(); + srcFile = StrUtil.format("{}/{}", sourcePath, fileName); + destFile = FileUtil.file(destDir, fileName); - for (FTPFile ftpFile : lsFiles) { - String sourcePathPathFile = sourcePath + pathSeparator + ftpFile.getName(); - String destinationPathFile = destinationPath + pathSeparator + ftpFile.getName(); - - if (!ftpFile.isDirectory()) { + if (false == ftpFile.isDirectory()) { // 本地不存在文件或者ftp上文件有修改则下载 - if (!FileUtil.exist(destinationPathFile) - || (ftpFile.getTimestamp().getTimeInMillis() > FileUtil.lastModifiedTime(destinationPathFile).getTime())) { - // Download file from source (source filename, destination filename). - download(sourcePathPathFile, FileUtil.file(destinationPathFile)); + if (false == FileUtil.exist(destFile) + || (ftpFile.getTimestamp().getTimeInMillis() > destFile.lastModified())) { + download(srcFile, destFile); } - } else if (!(".".equals(ftpFile.getName()) || "..".equals(ftpFile.getName()))) { - FileUtil.mkdir(destinationPathFile); - recursiveDownloadFolder(sourcePathPathFile, destinationPathFile); + } else { + // 服务端依旧是目录,继续递归 + FileUtil.mkdir(destFile); + recursiveDownloadFolder(srcFile, destFile); } } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java index 5ed1f8f63..6ca943627 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java @@ -1,5 +1,7 @@ package cn.hutool.extra.ssh; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.ListUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Filter; import cn.hutool.core.util.StrUtil; @@ -37,6 +39,7 @@ public class Sftp extends AbstractFtp { private ChannelSftp channel; // ---------------------------------------------------------------------------------------- Constructor start + /** * 构造 * @@ -147,7 +150,7 @@ public class Sftp extends AbstractFtp { */ public void init(Session session, Charset charset) { this.session = session; - init(JschUtil.openSftp(session, (int)this.ftpConfig.getConnectionTimeout()), charset); + init(JschUtil.openSftp(session, (int) this.ftpConfig.getConnectionTimeout()), charset); } /** @@ -247,32 +250,62 @@ public class Sftp extends AbstractFtp { } /** - * 遍历某个目录下所有文件或目录,不会递归遍历 + * 遍历某个目录下所有文件或目录,不会递归遍历
    + * 此方法自动过滤"."和".."两种目录 * - * @param path 遍历某个目录下所有文件或目录 + * @param path 遍历某个目录下所有文件或目录 * @param filter 文件或目录过滤器,可以实现过滤器返回自己需要的文件或目录名列表 * @return 目录或文件名列表 * @since 4.0.5 */ public List ls(String path, final Filter filter) { - final List fileNames = new ArrayList<>(); + final List entries = lsEntries(path, filter); + if (CollUtil.isEmpty(entries)) { + return ListUtil.empty(); + } + return CollUtil.map(entries, LsEntry::getFilename, true); + } + + /** + * 遍历某个目录下所有文件或目录,生成LsEntry列表,不会递归遍历
    + * 此方法自动过滤"."和".."两种目录 + * + * @param path 遍历某个目录下所有文件或目录 + * @return 目录或文件名列表 + * @since 5.3.5 + */ + public List lsEntries(String path) { + return lsEntries(path, null); + } + + /** + * 遍历某个目录下所有文件或目录,生成LsEntry列表,不会递归遍历
    + * 此方法自动过滤"."和".."两种目录 + * + * @param path 遍历某个目录下所有文件或目录 + * @param filter 文件或目录过滤器,可以实现过滤器返回自己需要的文件或目录名列表 + * @return 目录或文件名列表 + * @since 5.3.5 + */ + public List lsEntries(String path, Filter filter) { + final List entryList = new ArrayList<>(); try { channel.ls(path, entry -> { - String fileName = entry.getFilename(); + final String fileName = entry.getFilename(); if (false == StrUtil.equals(".", fileName) && false == StrUtil.equals("..", fileName)) { if (null == filter || filter.accept(entry)) { - fileNames.add(entry.getFilename()); + entryList.add(entry); } } return LsEntrySelector.CONTINUE; }); } catch (SftpException e) { - if(false == StrUtil.startWithIgnoreCase(e.getMessage(), "No such file")){ + if (false == StrUtil.startWithIgnoreCase(e.getMessage(), "No such file")) { throw new JschRuntimeException(e); } // 文件不存在忽略 } - return fileNames; + return entryList; } @Override @@ -375,7 +408,7 @@ public class Sftp extends AbstractFtp { * 将本地文件上传到目标服务器,目标文件名为destPath,若destPath为目录,则目标文件名将与srcFilePath文件名相同。覆盖模式 * * @param srcFilePath 本地文件路径 - * @param destPath 目标路径, + * @param destPath 目标路径, * @return this */ public Sftp put(String srcFilePath, String destPath) { @@ -386,8 +419,8 @@ public class Sftp extends AbstractFtp { * 将本地文件上传到目标服务器,目标文件名为destPath,若destPath为目录,则目标文件名将与srcFilePath文件名相同。 * * @param srcFilePath 本地文件路径 - * @param destPath 目标路径, - * @param mode {@link Mode} 模式 + * @param destPath 目标路径, + * @param mode {@link Mode} 模式 * @return this */ public Sftp put(String srcFilePath, String destPath, Mode mode) { @@ -398,9 +431,9 @@ public class Sftp extends AbstractFtp { * 将本地文件上传到目标服务器,目标文件名为destPath,若destPath为目录,则目标文件名将与srcFilePath文件名相同。 * * @param srcFilePath 本地文件路径 - * @param destPath 目标路径, - * @param monitor 上传进度监控,通过实现此接口完成进度显示 - * @param mode {@link Mode} 模式 + * @param destPath 目标路径, + * @param monitor 上传进度监控,通过实现此接口完成进度显示 + * @param mode {@link Mode} 模式 * @return this * @since 4.6.5 */ @@ -421,30 +454,29 @@ public class Sftp extends AbstractFtp { /** * 递归下载FTP服务器上文件到本地(文件目录和服务器同步) * - * @param sourcePath ftp服务器目录 - * @param destinationPath 本地目录 + * @param sourcePath ftp服务器目录,必须为目录 + * @param destDir 本地目录 */ @Override - public void recursiveDownloadFolder(String sourcePath, String destinationPath) throws Exception { - String pathSeparator = "/"; - Vector fileAndFolderList = channel.ls(sourcePath); + public void recursiveDownloadFolder(String sourcePath, File destDir) throws JschRuntimeException { + String fileName; + String srcFile; + File destFile; + for (LsEntry item : lsEntries(sourcePath)) { + fileName = item.getFilename(); + srcFile = StrUtil.format("{}/{}", sourcePath, fileName); + destFile = FileUtil.file(destDir, fileName); - //Iterate through list of folder content - for (ChannelSftp.LsEntry item : fileAndFolderList) { - - String sourcePathPathFile = sourcePath + pathSeparator + item.getFilename(); - String destinationPathFile = destinationPath + pathSeparator + item.getFilename(); - - if (!item.getAttrs().isDir()) { + if (false == item.getAttrs().isDir()) { // 本地不存在文件或者ftp上文件有修改则下载 - if (!FileUtil.exist(destinationPathFile) - || (item.getAttrs().getMTime() > (FileUtil.lastModifiedTime(destinationPathFile).getTime() / 1000))) { - // Download file from source (source filename, destination filename). - channel.get(sourcePathPathFile, destinationPathFile); + if (false == FileUtil.exist(destFile) + || (item.getAttrs().getMTime() > (destFile.lastModified() / 1000))) { + download(srcFile, destFile); } - } else if (!(".".equals(item.getFilename()) || "..".equals(item.getFilename()))) { - FileUtil.mkdir(destinationPathFile); - recursiveDownloadFolder(sourcePathPathFile, destinationPathFile); + } else { + // 服务端依旧是目录,继续递归 + FileUtil.mkdir(destFile); + recursiveDownloadFolder(srcFile, destFile); } } @@ -453,7 +485,7 @@ public class Sftp extends AbstractFtp { /** * 获取远程文件 * - * @param src 远程文件路径 + * @param src 远程文件路径 * @param dest 目标文件路径 * @return this */ @@ -485,14 +517,19 @@ public class Sftp extends AbstractFtp { * JSch支持的三种文件传输模式 * * @author looly - * */ public enum Mode { - /** 完全覆盖模式,这是JSch的默认文件传输模式,即如果目标文件已经存在,传输的文件将完全覆盖目标文件,产生新的文件。 */ + /** + * 完全覆盖模式,这是JSch的默认文件传输模式,即如果目标文件已经存在,传输的文件将完全覆盖目标文件,产生新的文件。 + */ OVERWRITE, - /** 恢复模式,如果文件已经传输一部分,这时由于网络或其他任何原因导致文件传输中断,如果下一次传输相同的文件,则会从上一次中断的地方续传。 */ + /** + * 恢复模式,如果文件已经传输一部分,这时由于网络或其他任何原因导致文件传输中断,如果下一次传输相同的文件,则会从上一次中断的地方续传。 + */ RESUME, - /** 追加模式,如果目标文件已存在,传输的文件将在目标文件后追加。 */ + /** + * 追加模式,如果目标文件已存在,传输的文件将在目标文件后追加。 + */ APPEND } } diff --git a/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java b/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java index f2609ebe3..34a1e19d3 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java @@ -1,14 +1,13 @@ package cn.hutool.extra.ftp; -import java.util.List; - -import cn.hutool.extra.ssh.Sftp; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Console; +import cn.hutool.extra.ssh.Sftp; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.List; public class FtpTest { @@ -63,21 +62,21 @@ public class FtpTest { @Test @Ignore - public void recursiveDownloadFolder() throws Exception { + public void recursiveDownloadFolder() { Ftp ftp = new Ftp("looly.centos"); - ftp.recursiveDownloadFolder("/","d:/test/download"); + ftp.recursiveDownloadFolder("/",FileUtil.file("d:/test/download")); IoUtil.close(ftp); } @Test @Ignore - public void recursiveDownloadFolderSftp() throws Exception { + public void recursiveDownloadFolderSftp() { Sftp ftp = new Sftp("127.0.0.1", 22, "test", "test"); ftp.cd("/file/aaa"); Console.log(ftp.pwd()); - ftp.recursiveDownloadFolder("/","d:/test/download"); + ftp.recursiveDownloadFolder("/",FileUtil.file("d:/test/download")); IoUtil.close(ftp); } From 0a9b9a6fce343c434056694588f9603d71ece070 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 12 May 2020 00:37:42 +0800 Subject: [PATCH 114/157] add method and test --- CHANGELOG.md | 1 + .../java/cn/hutool/core/util/StrUtilTest.java | 3 +++ .../java/cn/hutool/json/Issue867Test.java | 26 +++++++++++++++++++ .../java/cn/hutool/system/oshi/OshiUtil.java | 12 +++++++++ 4 files changed, 42 insertions(+) create mode 100644 hutool-json/src/test/java/cn/hutool/json/Issue867Test.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 633b3c474..b1f9cf71b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### 新特性 * 【core 】 增加CollUtil.map方法 * 【extra 】 增加Sftp.lsEntries方法,Ftp和Sftp增加recursiveDownloadFolder(pr#121@Gitee) +* 【system 】 OshiUtil增加getNetworkIFs方法 ### Bug修复 ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java index b893a7487..f18c345c0 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java @@ -346,6 +346,9 @@ public class StrUtilTest { String str1 = "TableTestOfDay"; String result1 = StrUtil.toCamelCase(str1); Assert.assertEquals("TableTestOfDay", result1); + + String abc1d = StrUtil.toCamelCase("abc_1d"); + Assert.assertEquals("abc1d", abc1d); } @Test diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue867Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue867Test.java new file mode 100644 index 000000000..86058bef2 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue867Test.java @@ -0,0 +1,26 @@ +package cn.hutool.json; + +import cn.hutool.core.annotation.Alias; +import lombok.Data; +import org.junit.Assert; +import org.junit.Test; + +public class Issue867Test { + + @Test + public void toBeanTest(){ + String json = "{\"abc_1d\":\"123\",\"abc_d\":\"456\",\"abc_de\":\"789\"}"; + Test02 bean = JSONUtil.toBean(JSONUtil.parseObj(json),Test02.class); + Assert.assertEquals("123", bean.getAbc1d()); + Assert.assertEquals("456", bean.getAbcD()); + Assert.assertEquals("789", bean.getAbcDe()); + } + + @Data + static class Test02 { + @Alias("abc_1d") + private String abc1d; + private String abcD; + private String abcDe; + } +} diff --git a/hutool-system/src/main/java/cn/hutool/system/oshi/OshiUtil.java b/hutool-system/src/main/java/cn/hutool/system/oshi/OshiUtil.java index b44a11101..4aeff7ec3 100644 --- a/hutool-system/src/main/java/cn/hutool/system/oshi/OshiUtil.java +++ b/hutool-system/src/main/java/cn/hutool/system/oshi/OshiUtil.java @@ -6,6 +6,7 @@ import oshi.hardware.ComputerSystem; import oshi.hardware.GlobalMemory; import oshi.hardware.HWDiskStore; import oshi.hardware.HardwareAbstractionLayer; +import oshi.hardware.NetworkIF; import oshi.hardware.Sensors; import oshi.software.os.OperatingSystem; @@ -98,4 +99,15 @@ public class OshiUtil { public static HWDiskStore[] getDiskStores() { return hardware.getDiskStores(); } + + /** + * 获取网络相关信息,可能多块网卡 + * @return 网络相关信息 + * @since 5.3.5 + */ + public static NetworkIF[] getNetworkIFs(){ + return hardware.getNetworkIFs(); + } + + } From c047fd5a239a804e42dbe8f8fd77ccc68809d320 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 13 May 2020 04:58:40 +0800 Subject: [PATCH 115/157] add method and test --- CHANGELOG.md | 3 +- .../copier/provider/MapValueProvider.java | 2 +- .../cn/hutool/core/collection/CollUtil.java | 68 +++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1f9cf71b..c84e852e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,13 @@ ------------------------------------------------------------------------------------------------------------- -## 5.3.5 (2020-05-11) +## 5.3.5 (2020-05-13) ### 新特性 * 【core 】 增加CollUtil.map方法 * 【extra 】 增加Sftp.lsEntries方法,Ftp和Sftp增加recursiveDownloadFolder(pr#121@Gitee) * 【system 】 OshiUtil增加getNetworkIFs方法 +* 【core 】 CollUtil增加unionDistinct、unionAll方法(pr#122@Gitee) ### Bug修复 ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java index 97275f3b6..2007776b6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/MapValueProvider.java @@ -61,10 +61,10 @@ public class MapValueProvider implements ValueProvider { @Override public boolean containsKey(String key) { - //检查下划线模式 if(map.containsKey(key)) { return true; }else { + //检查下划线模式 return map.containsKey(StrUtil.toUnderlineCase(key)); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index a4ccaf888..ec3c5437a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -144,6 +144,74 @@ public class CollUtil { return union; } + /** + * 多个集合的非重复并集,类似于SQL中的“UNION DISTINCT”
    + * 针对一个集合中存在多个相同元素的情况,只保留一个
    + * 例如:集合1:[a, b, c, c, c],集合2:[a, b, c, c]
    + * 结果:[a, b, c],此结果中只保留了一个c + * + * @param 集合元素类型 + * @param coll1 集合1 + * @param coll2 集合2 + * @param otherColls 其它集合 + * @return 并集的集合,返回 {@link LinkedHashSet} + */ + @SafeVarargs + public static Set unionDistinct(Collection coll1, Collection coll2, Collection... otherColls) { + final Set result; + if(isEmpty(coll1)){ + result = new LinkedHashSet<>(); + } else { + result = new LinkedHashSet<>(coll1); + } + + if(isNotEmpty(coll2)){ + result.addAll(coll2); + } + + if(ArrayUtil.isNotEmpty(otherColls)){ + for (Collection otherColl : otherColls) { + result.addAll(otherColl); + } + } + + return result; + } + + /** + * 多个集合的完全并集,类似于SQL中的“UNION ALL”
    + * 针对一个集合中存在多个相同元素的情况,保留全部元素
    + * 例如:集合1:[a, b, c, c, c],集合2:[a, b, c, c]
    + * 结果:[a, b, c, c, c, a, b, c, c] + * + * @param 集合元素类型 + * @param coll1 集合1 + * @param coll2 集合2 + * @param otherColls 其它集合 + * @return 并集的集合,返回 {@link ArrayList} + */ + @SafeVarargs + public static List unionAll(Collection coll1, Collection coll2, Collection... otherColls) { + final List result; + if(isEmpty(coll1)){ + result = new ArrayList<>(); + } else { + result = new ArrayList<>(coll1); + } + + if(isNotEmpty(coll2)){ + result.addAll(coll2); + } + + if(ArrayUtil.isNotEmpty(otherColls)){ + for (Collection otherColl : otherColls) { + result.addAll(otherColl); + } + } + + return result; + } + /** * 两个集合的交集
    * 针对一个集合中存在多个相同元素的情况,计算两个集合中此元素的个数,保留最少的个数
    From e2428714a0ce7945b5c9fd485299c98659ac8d08 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 13 May 2020 06:24:53 +0800 Subject: [PATCH 116/157] add method and test --- CHANGELOG.md | 3 + .../cn/hutool/core/collection/CollUtil.java | 2 +- .../main/java/cn/hutool/core/io/IoUtil.java | 43 +++++++++-- .../core/io/ValidateObjectInputStream.java | 72 +++++++++++++++---- 4 files changed, 100 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c84e852e5..49d4b4c03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,10 @@ * 【extra 】 增加Sftp.lsEntries方法,Ftp和Sftp增加recursiveDownloadFolder(pr#121@Gitee) * 【system 】 OshiUtil增加getNetworkIFs方法 * 【core 】 CollUtil增加unionDistinct、unionAll方法(pr#122@Gitee) +* 【core 】 增加IoUtil.readObj重载,通过ValidateObjectInputStream由用户自定义安全检查。 + ### Bug修复 +* 【core 】 修复IoUtil.readObj中反序列化安全检查导致的一些问题,去掉安全检查。 ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index ec3c5437a..c3ae49c56 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -293,7 +293,7 @@ public class CollUtil { return coll1; } - final ArrayList result = new ArrayList<>(); + final List result = new ArrayList<>(); final Map map1 = countMap(coll1); final Map map2 = countMap(coll2); final Set elts = newHashSet(coll2); diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java index cda8b5efe..2896c0102 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java @@ -20,7 +20,6 @@ import java.io.Flushable; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; @@ -648,14 +647,16 @@ public class IoUtil { /** * 从流中读取对象,即对象的反序列化 * + *

    + * 注意!!! 此方法不会检查反序列化安全,可能存在反序列化漏洞风险!!! + *

    + * * @param 读取对象的类型 * @param in 输入流 * @return 输出流 * @throws IORuntimeException IO异常 * @throws UtilException ClassNotFoundException包装 - * @deprecated 由于存在对象反序列化漏洞风险,请使用{@link #readObj(InputStream, Class)} */ - @Deprecated public static T readObj(InputStream in) throws IORuntimeException, UtilException { return readObj(in, null); } @@ -663,6 +664,10 @@ public class IoUtil { /** * 从流中读取对象,即对象的反序列化,读取后不关闭流 * + *

    + * 注意!!! 此方法不会检查反序列化安全,可能存在反序列化漏洞风险!!! + *

    + * * @param 读取对象的类型 * @param in 输入流 * @param clazz 读取对象类型 @@ -671,14 +676,38 @@ public class IoUtil { * @throws UtilException ClassNotFoundException包装 */ public static T readObj(InputStream in, Class clazz) throws IORuntimeException, UtilException { + try { + return readObj((in instanceof ValidateObjectInputStream) ? + (ValidateObjectInputStream) in : new ValidateObjectInputStream(in), + clazz); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 从流中读取对象,即对象的反序列化,读取后不关闭流 + * + *

    + * 此方法使用了{@link ValidateObjectInputStream}中的黑白名单方式过滤类,用于避免反序列化漏洞
    + * 通过构造{@link ValidateObjectInputStream},调用{@link ValidateObjectInputStream#accept(Class[])} + * 或者{@link ValidateObjectInputStream#refuse(Class[])}方法添加可以被序列化的类或者禁止序列化的类。 + *

    + * + * @param 读取对象的类型 + * @param in 输入流,使用{@link ValidateObjectInputStream}中的黑白名单方式过滤类,用于避免反序列化漏洞 + * @param clazz 读取对象类型 + * @return 输出流 + * @throws IORuntimeException IO异常 + * @throws UtilException ClassNotFoundException包装 + */ + public static T readObj(ValidateObjectInputStream in, Class clazz) throws IORuntimeException, UtilException { if (in == null) { throw new IllegalArgumentException("The InputStream must not be null"); } - ObjectInputStream ois; try { - ois = new ValidateObjectInputStream(in, clazz); //noinspection unchecked - return (T) ois.readObject(); + return (T) in.readObject(); } catch (IOException e) { throw new IORuntimeException(e); } catch (ClassNotFoundException e) { @@ -989,7 +1018,7 @@ public class IoUtil { * * @param out 输出流 * @param isCloseOut 写入完毕是否关闭输出流 - * @param obj 写入的对象内容 + * @param obj 写入的对象内容 * @throws IORuntimeException IO异常 * @since 5.3.3 */ diff --git a/hutool-core/src/main/java/cn/hutool/core/io/ValidateObjectInputStream.java b/hutool-core/src/main/java/cn/hutool/core/io/ValidateObjectInputStream.java index ae077f412..91c1a130e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/ValidateObjectInputStream.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/ValidateObjectInputStream.java @@ -1,10 +1,14 @@ package cn.hutool.core.io; +import cn.hutool.core.collection.CollUtil; + import java.io.IOException; import java.io.InputStream; import java.io.InvalidClassException; import java.io.ObjectInputStream; import java.io.ObjectStreamClass; +import java.util.HashSet; +import java.util.Set; /** * 带有类验证的对象流,用于避免反序列化漏洞
    @@ -15,27 +19,48 @@ import java.io.ObjectStreamClass; */ public class ValidateObjectInputStream extends ObjectInputStream { - private Class acceptClass; + private Set whiteClassSet; + private Set blackClassSet; /** * 构造 * * @param inputStream 流 - * @param acceptClass 接受的类 + * @param acceptClasses 白名单的类 * @throws IOException IO异常 */ - public ValidateObjectInputStream(InputStream inputStream, Class acceptClass) throws IOException { + public ValidateObjectInputStream(InputStream inputStream, Class... acceptClasses) throws IOException { super(inputStream); - this.acceptClass = acceptClass; + accept(acceptClasses); + } + + /** + * 禁止反序列化的类,用于反序列化验证 + * + * @param refuseClasses 禁止反序列化的类 + * @since 5.3.5 + */ + public void refuse(Class... refuseClasses) { + if(null == this.blackClassSet){ + this.blackClassSet = new HashSet<>(); + } + for (Class acceptClass : refuseClasses) { + this.blackClassSet.add(acceptClass.getName()); + } } /** * 接受反序列化的类,用于反序列化验证 * - * @param acceptClass 接受反序列化的类 + * @param acceptClasses 接受反序列化的类 */ - public void accept(Class acceptClass) { - this.acceptClass = acceptClass; + public void accept(Class... acceptClasses) { + if(null == this.whiteClassSet){ + this.whiteClassSet = new HashSet<>(); + } + for (Class acceptClass : acceptClasses) { + this.whiteClassSet.add(acceptClass.getName()); + } } /** @@ -43,11 +68,34 @@ public class ValidateObjectInputStream extends ObjectInputStream { */ @Override protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { - if (null != this.acceptClass && false == desc.getName().equals(acceptClass.getName())) { - throw new InvalidClassException( - "Unauthorized deserialization attempt", - desc.getName()); - } + validateClassName(desc.getName()); return super.resolveClass(desc); } + + /** + * 验证反序列化的类是否合法 + * @param className 类名 + * @throws InvalidClassException 非法类 + */ + private void validateClassName(String className) throws InvalidClassException { + // 黑名单 + if(CollUtil.isNotEmpty(this.blackClassSet)){ + if(this.blackClassSet.contains(className)){ + throw new InvalidClassException("Unauthorized deserialization attempt by black list", className); + } + } + + if(CollUtil.isEmpty(this.whiteClassSet)){ + return; + } + if(className.startsWith("java.")){ + // java中的类默认在白名单中 + return; + } + if(this.whiteClassSet.contains(className)){ + return; + } + + throw new InvalidClassException("Unauthorized deserialization attempt", className); + } } From 1abab026dd981c808e38139ba2e7f13371be9b1f Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 13 May 2020 14:18:12 +0800 Subject: [PATCH 117/157] fix code --- .../main/java/cn/hutool/poi/excel/ExcelReader.java | 2 ++ .../cn/hutool/poi/excel/test/ExcelReadTest.java | 14 +++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java index 5c8cbb013..38dc24c43 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelReader.java @@ -5,6 +5,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.IterUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Console; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; @@ -239,6 +240,7 @@ public class ExcelReader extends ExcelBase { List rowList; for (int i = startRowIndex; i <= endRowIndex; i++) { rowList = readRow(i); + Console.log("### {}: {}", i, rowList); if (CollUtil.isNotEmpty(rowList) || false == ignoreEmptyRow) { if (null == rowList) { rowList = new ArrayList<>(0); diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java index 2f3816802..9fe934b08 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java @@ -1,17 +1,16 @@ package cn.hutool.poi.excel.test; -import java.util.List; -import java.util.Map; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.lang.Console; import cn.hutool.core.map.MapUtil; import cn.hutool.poi.excel.ExcelReader; import cn.hutool.poi.excel.ExcelUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.List; +import java.util.Map; /** * Excel读取单元测试 @@ -208,4 +207,5 @@ public class ExcelReadTest { Assert.assertEquals(11L, read.get(1).get(2)); Assert.assertEquals(11L, read.get(2).get(2)); } + } From 76a47b3c54084b05519da4a02a3ab576329637eb Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 13 May 2020 14:43:29 +0800 Subject: [PATCH 118/157] fix bug --- .../cn/hutool/core/collection/CollUtil.java | 33 +++++++++++++++++++ .../hutool/http/server/action/RootAction.java | 2 ++ 2 files changed, 35 insertions(+) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index c3ae49c56..07a2e0d55 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -326,6 +326,39 @@ public class CollUtil { return result; } + /** + * 计算集合的单差集,即只返回【集合1】中有,但是【集合2】中没有的元素,例如: + * + *
    +	 *     subtractToList([1,2,3,4],[2,3,4,5]) -》 [1]
    +	 * 
    + * + * @param coll1 集合1 + * @param coll2 集合2 + * @param 元素类型 + * @return 单差集 + * @since 5.3.5 + */ + public static List subtractToList(Collection coll1, Collection coll2) { + + if (isEmpty(coll1)) { + return ListUtil.empty(); + } + if (isEmpty(coll2)) { + return ListUtil.list(true, coll2); + } + + //将被交数用链表储存,防止因为频繁扩容影响性能 + final List result = new LinkedList<>(); + Set set = new HashSet<>(coll2); + for (T t : coll1) { + if (false == set.contains(t)) { + result.add(t); + } + } + return result; + } + /** * 判断指定集合是否包含指定值,如果集合为空(null或者空),返回{@code false},否则找到元素返回{@code true} * diff --git a/hutool-http/src/main/java/cn/hutool/http/server/action/RootAction.java b/hutool-http/src/main/java/cn/hutool/http/server/action/RootAction.java index d7e8836f8..9b4c66803 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/action/RootAction.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/action/RootAction.java @@ -54,6 +54,8 @@ public class RootAction implements Action { response.write(file); } } + } else{ + response.write(file); } } From d8ca033080ba9b8f95f5f3ec4d6224a4359b79d9 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 13 May 2020 14:44:29 +0800 Subject: [PATCH 119/157] fix bug --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49d4b4c03..87c7ec4c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Bug修复 * 【core 】 修复IoUtil.readObj中反序列化安全检查导致的一些问题,去掉安全检查。 +* 【http 】 修复SimpleServer文件访问404问题(issue#I1GZI3@Gitee) ------------------------------------------------------------------------------------------------------------- From 3da83e0227347b0bc67da00f6cfa1878990fe552 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 13 May 2020 21:22:54 +0800 Subject: [PATCH 120/157] release 5.3.5 --- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 9d80bdaf5..6a7a43095 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 9d3c57ef0..54bedd837 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index 0855fd857..ec8eb0e01 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 8e7683f7f..08ed345ad 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index d62a8cebd..a52182798 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 2f539f943..05d38c746 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 985e0b937..f27560e39 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index d5ab802af..0a3fc5461 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index ab276d73d..4e4ba7a29 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 17de4984a..fb31b1b7a 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 1ca335845..572508a7e 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 83a4e5abc..cf28049e0 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 891cfd36c..e2b453335 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 6981ac28c..4b503adda 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 2b4b8f653..ab3e29f44 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index df4fde6dc..9f027d4e4 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index bec25453d..0c3137514 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index f21d12d87..9951eb07b 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 60e917d72..f9b26d6e6 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 73c08115c..e17235fd3 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool-system diff --git a/pom.xml b/pom.xml index 658ec56c8..07f3f7988 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.5-SNAPSHOT + 5.3.5 hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool From d0fe78ae662f7bad023fe22bb770989669b0d5c5 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 14 May 2020 01:56:03 +0800 Subject: [PATCH 121/157] add body --- CHANGELOG.md | 2 + .../hutool/core/bean/copier/BeanCopier.java | 2 +- .../core/convert/impl/BeanConverter.java | 4 +- .../main/java/cn/hutool/core/io/IoUtil.java | 2 +- .../cn/hutool/core/io/resource/Resource.java | 16 ++ .../java/cn/hutool/core/util/ObjectUtil.java | 28 +-- .../main/java/cn/hutool/http/ContentType.java | 4 + .../main/java/cn/hutool/http/HttpRequest.java | 188 +++++++----------- .../cn/hutool/http/body/MultipartBody.java | 154 ++++++++++++++ .../java/cn/hutool/http/body/RequestBody.java | 16 ++ .../cn/hutool/http/body/package-info.java | 7 + .../hutool/http/server/SimpleServerTest.java | 10 +- .../java/cn/hutool/http/test/UploadTest.java | 20 +- .../test/java/cn/hutool/json/IssueI1H2VN.java | 40 ++++ 14 files changed, 337 insertions(+), 156 deletions(-) create mode 100644 hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java create mode 100644 hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java create mode 100644 hutool-http/src/main/java/cn/hutool/http/body/package-info.java create mode 100644 hutool-json/src/test/java/cn/hutool/json/IssueI1H2VN.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 87c7ec4c9..9cfccb9e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,12 @@ * 【system 】 OshiUtil增加getNetworkIFs方法 * 【core 】 CollUtil增加unionDistinct、unionAll方法(pr#122@Gitee) * 【core 】 增加IoUtil.readObj重载,通过ValidateObjectInputStream由用户自定义安全检查。 +* 【http 】 改造HttpRequest中文件上传部分,增加MultipartBody类 ### Bug修复 * 【core 】 修复IoUtil.readObj中反序列化安全检查导致的一些问题,去掉安全检查。 * 【http 】 修复SimpleServer文件访问404问题(issue#I1GZI3@Gitee) +* 【core 】 修复BeanCopier中循环引用逻辑问题(issue#I1H2VN@Gitee) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java index 27ed86fbb..10fb83b65 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java @@ -267,7 +267,7 @@ public class BeanCopier implements Copier, Serializable { if (null == value && copyOptions.ignoreNullValue) { continue;// 当允许跳过空时,跳过 } - if (bean.equals(value)) { + if (bean == value) { continue;// 值不能为bean本身,防止循环引用 } diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/BeanConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/BeanConverter.java index cb78ec6e4..72513ef59 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/BeanConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/BeanConverter.java @@ -65,7 +65,9 @@ public class BeanConverter extends AbstractConverter { @Override protected T convertInternal(Object value) { - if(value instanceof Map || value instanceof ValueProvider || BeanUtil.isBean(value.getClass())) { + if(value instanceof Map || + value instanceof ValueProvider || + BeanUtil.isBean(value.getClass())) { if(value instanceof Map && this.beanClass.isInterface()) { // 将Map动态代理为Bean return MapProxy.create((Map)value).toProxyBean(this.beanClass); diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java index 2896c0102..9d368316c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java @@ -1001,9 +1001,9 @@ public class IoUtil { for (Object content : contents) { if (content != null) { osw.write(Convert.toStr(content, StrUtil.EMPTY)); - osw.flush(); } } + osw.flush(); } catch (IOException e) { throw new IORuntimeException(e); } finally { diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java index 22b2d702f..98989a29e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java @@ -1,11 +1,13 @@ package cn.hutool.core.io.resource; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.CharsetUtil; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.net.URL; import java.nio.charset.Charset; @@ -36,6 +38,20 @@ public interface Resource { * @return {@link InputStream} */ InputStream getStream(); + + /** + * 将资源内容写出到流,不关闭输出流,但是关闭资源流 + * @param out 输出流 + * @throws IORuntimeException IO异常 + * @since 5.3.5 + */ + default void writeTo(OutputStream out) throws IORuntimeException{ + try (InputStream in = getStream()) { + IoUtil.copy(in, out); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } /** * 获得Reader diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java index 4ae7360ce..6b2a8261a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java @@ -397,18 +397,8 @@ public class ObjectUtil { if (false == (obj instanceof Serializable)) { return null; } - - FastByteArrayOutputStream byteOut = new FastByteArrayOutputStream(); - ObjectOutputStream oos = null; - try { - oos = new ObjectOutputStream(byteOut); - oos.writeObject(obj); - oos.flush(); - } catch (Exception e) { - throw new UtilException(e); - } finally { - IoUtil.close(oos); - } + final FastByteArrayOutputStream byteOut = new FastByteArrayOutputStream(); + IoUtil.writeObjects(byteOut, false, (Serializable) obj); return byteOut.toByteArray(); } @@ -416,20 +406,16 @@ public class ObjectUtil { * 反序列化
    * 对象必须实现Serializable接口 * + *

    + * 注意!!! 此方法不会检查反序列化安全,可能存在反序列化漏洞风险!!! + *

    + * * @param 对象类型 * @param bytes 反序列化的字节码 * @return 反序列化后的对象 */ - @SuppressWarnings("unchecked") public static T deserialize(byte[] bytes) { - ObjectInputStream ois; - try { - ByteArrayInputStream bais = new ByteArrayInputStream(bytes); - ois = new ObjectInputStream(bais); - return (T) ois.readObject(); - } catch (Exception e) { - throw new UtilException(e); - } + return IoUtil.readObj(new ByteArrayInputStream(bytes)); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ContentType.java b/hutool-http/src/main/java/cn/hutool/http/ContentType.java index 1eb1a719b..b60259b71 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ContentType.java +++ b/hutool-http/src/main/java/cn/hutool/http/ContentType.java @@ -43,6 +43,10 @@ public enum ContentType { private final String value; + /** + * 构造 + * @param value ContentType值 + */ ContentType(String value) { this.value = value; } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index a23d367be..2637a30c5 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -1,14 +1,13 @@ package cn.hutool.http; import cn.hutool.core.codec.Base64; -import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.resource.BytesResource; import cn.hutool.core.io.resource.FileResource; import cn.hutool.core.io.resource.MultiFileResource; -import cn.hutool.core.io.resource.MultiResource; import cn.hutool.core.io.resource.Resource; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; @@ -16,8 +15,8 @@ import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.http.body.MultipartBody; import cn.hutool.http.cookie.GlobalCookieManager; import cn.hutool.http.ssl.SSLSocketFactoryBuilder; @@ -25,18 +24,15 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSocketFactory; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.net.CookieManager; import java.net.HttpCookie; import java.net.HttpURLConnection; import java.net.Proxy; import java.net.URLStreamHandler; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; /** * http请求类
    @@ -46,12 +42,7 @@ import java.util.Map.Entry; */ public class HttpRequest extends HttpBase { - private static final String BOUNDARY = "--------------------Hutool_" + RandomUtil.randomString(16); - private static final byte[] BOUNDARY_END = StrUtil.format("--{}--\r\n", BOUNDARY).getBytes(); - private static final String CONTENT_DISPOSITION_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n\r\n"; - private static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n"; - - private static final String CONTENT_TYPE_MULTIPART_PREFIX = "multipart/form-data; boundary="; + private static final String CONTENT_TYPE_MULTIPART_PREFIX = ContentType.MULTIPART.getValue() + "; boundary="; private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n\r\n"; /** @@ -113,9 +104,9 @@ public class HttpRequest extends HttpBase { */ private Map form; /** - * 文件表单对象,用于文件上传 + * 是否为Multipart表单 */ - private Map fileForm; + private boolean isMultiPart; /** * Cookie */ @@ -492,17 +483,17 @@ public class HttpRequest extends HttpBase { if (value instanceof File) { // 文件上传 return this.form(name, (File) value); - } else if (value instanceof Resource) { - // 自定义流上传 - return this.form(name, (Resource) value); - } else if (this.form == null) { - this.form = new LinkedHashMap<>(); } + if(value instanceof Resource){ + return form(name, (Resource)value); + } + + // 普通值 String strValue; if (value instanceof List) { // 列表对象 - strValue = CollectionUtil.join((List) value, ","); + strValue = CollUtil.join((List) value, ","); } else if (ArrayUtil.isArray(value)) { if (File.class == ArrayUtil.getComponentType(value)) { // 多文件 @@ -515,8 +506,7 @@ public class HttpRequest extends HttpBase { strValue = Convert.toStr(value, null); } - form.put(name, strValue); - return this; + return putToForm(name, strValue); } /** @@ -531,8 +521,7 @@ public class HttpRequest extends HttpBase { form(name, value); for (int i = 0; i < parameters.length; i += 2) { - name = parameters[i].toString(); - form(name, parameters[i + 1]); + form(parameters[i].toString(), parameters[i + 1]); } return this; } @@ -545,9 +534,7 @@ public class HttpRequest extends HttpBase { */ public HttpRequest form(Map formMap) { if (MapUtil.isNotEmpty(formMap)) { - for (Map.Entry entry : formMap.entrySet()) { - form(entry.getKey(), entry.getValue()); - } + formMap.forEach(this::form); } return this; } @@ -557,10 +544,13 @@ public class HttpRequest extends HttpBase { * 一旦有文件加入,表单变为multipart/form-data * * @param name 名 - * @param files 需要上传的文件 + * @param files 需要上传的文件,为空跳过 * @return this */ public HttpRequest form(String name, File... files) { + if(ArrayUtil.isEmpty(files)){ + return this; + } if (1 == files.length) { final File file = files[0]; return form(name, file, file.getName()); @@ -628,11 +618,8 @@ public class HttpRequest extends HttpBase { keepAlive(true); } - if (null == this.fileForm) { - fileForm = new HashMap<>(); - } - // 文件对象 - this.fileForm.put(name, resource); + this.isMultiPart = true; + return putToForm(name, resource); } return this; } @@ -653,7 +640,13 @@ public class HttpRequest extends HttpBase { * @since 3.3.0 */ public Map fileForm() { - return this.fileForm; + final Map result = MapUtil.newHashMap(); + this.form.forEach((key, value)->{ + if(value instanceof Resource){ + result.put(key, (Resource)value); + } + }); + return result; } // ---------------------------------------------------------------- Form end @@ -1091,10 +1084,10 @@ public class HttpRequest extends HttpBase { || Method.PUT.equals(this.method) // || Method.DELETE.equals(this.method) // || this.isRest) { - if (CollectionUtil.isEmpty(this.fileForm)) { - sendFormUrlEncoded();// 普通表单 - } else { + if (isMultipart()) { sendMultipart(); // 文件上传表单 + } else { + sendFormUrlEncoded();// 普通表单 } } else { this.httpConnection.connect(); @@ -1148,94 +1141,15 @@ public class HttpRequest extends HttpBase { setMultipart();// 设置表单类型为Multipart try (OutputStream out = this.httpConnection.getOutputStream()) { - writeFileForm(out); - writeForm(out); - formEnd(out); + MultipartBody.create(this.form, this.charset).write(out); } } - // 普通字符串数据 - - /** - * 发送普通表单内容 - * - * @param out 输出流 - */ - private void writeForm(OutputStream out) { - if (CollectionUtil.isNotEmpty(this.form)) { - StringBuilder builder = StrUtil.builder(); - for (Entry entry : this.form.entrySet()) { - builder.append("--").append(BOUNDARY).append(StrUtil.CRLF); - builder.append(StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, entry.getKey())); - builder.append(entry.getValue()).append(StrUtil.CRLF); - } - IoUtil.write(out, this.charset, false, builder); - } - } - - /** - * 发送文件对象表单 - * - * @param out 输出流 - */ - private void writeFileForm(OutputStream out) { - for (Entry entry : this.fileForm.entrySet()) { - appendPart(entry.getKey(), entry.getValue(), out); - } - } - - /** - * 添加Multipart表单的数据项 - * - * @param formFieldName 表单名 - * @param resource 资源,可以是文件等 - * @param out Http流 - * @since 4.1.0 - */ - private void appendPart(String formFieldName, Resource resource, OutputStream out) { - if (resource instanceof MultiResource) { - // 多资源 - for (Resource subResource : (MultiResource) resource) { - appendPart(formFieldName, subResource, out); - } - } else { - // 普通资源 - final StringBuilder builder = StrUtil.builder().append("--").append(BOUNDARY).append(StrUtil.CRLF); - final String fileName = resource.getName(); - builder.append(StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, ObjectUtil.defaultIfNull(fileName, formFieldName))); - // 根据name的扩展名指定互联网媒体类型,默认二进制流数据 - builder.append(StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, HttpUtil.getMimeType(fileName, "application/octet-stream"))); - IoUtil.write(out, this.charset, false, builder); - InputStream in = null; - try { - in = resource.getStream(); - IoUtil.copy(in, out); - } finally { - IoUtil.close(in); - } - IoUtil.write(out, this.charset, false, StrUtil.CRLF); - } - - } - - // 添加结尾数据 - - /** - * 上传表单结束 - * - * @param out 输出流 - * @throws IOException IO异常 - */ - private void formEnd(OutputStream out) throws IOException { - out.write(BOUNDARY_END); - out.flush(); - } - /** * 设置表单类型为Multipart(文件上传) */ private void setMultipart() { - this.httpConnection.header(Header.CONTENT_TYPE, CONTENT_TYPE_MULTIPART_PREFIX + BOUNDARY, true); + this.httpConnection.header(Header.CONTENT_TYPE, MultipartBody.getContentType(), true); } /** @@ -1251,6 +1165,44 @@ public class HttpRequest extends HttpBase { || Method.OPTIONS == this.method // || Method.TRACE == this.method; } + + /** + * 判断是否为multipart/form-data表单,条件如下: + * + *
    +	 *     1. 存在资源对象(fileForm非空)
    +	 *     2. 用户自定义头为multipart/form-data开头
    +	 * 
    + * @return 是否为multipart/form-data表单 + * @since 5.3.5 + */ + private boolean isMultipart(){ + if(this.isMultiPart){ + return true; + } + + final String contentType = header(Header.CONTENT_TYPE); + return StrUtil.isNotEmpty(contentType) && + contentType.startsWith(ContentType.MULTIPART.getValue()); + } + + /** + * 将参数加入到form中,如果form为空,新建之。 + * + * @param name 表单属性名 + * @param value 属性值 + * @return this + */ + private HttpRequest putToForm(String name, Object value){ + if(null == name || null == value){ + return this; + } + if(null == this.form){ + this.form = new LinkedHashMap<>(); + } + this.form.put(name, value); + return this; + } // ---------------------------------------------------------------- Private method end } diff --git a/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java b/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java new file mode 100644 index 000000000..05374c044 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java @@ -0,0 +1,154 @@ +package cn.hutool.http.body; + +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.resource.MultiResource; +import cn.hutool.core.io.resource.Resource; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.ContentType; +import cn.hutool.http.HttpUtil; + +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.Map; + +/** + * Multipart/form-data数据的请求体封装 + * + * @author looly + * @since 5.3.5 + */ +public class MultipartBody implements RequestBody{ + + private static final String BOUNDARY = "--------------------Hutool_" + RandomUtil.randomString(16); + private static final String BOUNDARY_END = StrUtil.format("--{}--\r\n", BOUNDARY); + private static final String CONTENT_DISPOSITION_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n\r\n"; + private static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n"; + + private static final String CONTENT_TYPE_MULTIPART_PREFIX = ContentType.MULTIPART.getValue() + "; boundary="; + private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n\r\n"; + + /** + * 存储表单数据 + */ + private final Map form; + /** + * 编码 + */ + private final Charset charset; + + /** + * 根据已有表单内容,构建MultipartBody + * @param form 表单 + * @param charset 编码 + * @return MultipartBody + */ + public static MultipartBody create(Map form, Charset charset){ + return new MultipartBody(form, charset); + } + + /** + * 获取Multipart的Content-Type类型 + * + * @return Multipart的Content-Type类型 + */ + public static String getContentType(){ + return CONTENT_TYPE_MULTIPART_PREFIX + BOUNDARY; + } + + /** + * 构造 + * + * @param form 表单 + * @param charset 编码 + */ + public MultipartBody(Map form, Charset charset) { + this.form = form; + this.charset = charset; + } + + /** + * 写出Multiparty数据,不关闭流 + * + * @param out out流 + */ + @Override + public void write(OutputStream out) { + writeForm(out); + formEnd(out); + } + + // 普通字符串数据 + + /** + * 发送文件对象表单 + * + * @param out 输出流 + */ + private void writeForm(OutputStream out) { + if (MapUtil.isNotEmpty(this.form)) { + for (Map.Entry entry : this.form.entrySet()) { + appendPart(entry.getKey(), entry.getValue(), out); + } + } + } + + /** + * 添加Multipart表单的数据项 + * + * @param formFieldName 表单名 + * @param value 值,可以是普通值、资源(如文件等) + * @param out Http流 + * @throws IORuntimeException IO异常 + */ + private void appendPart(String formFieldName, Object value, OutputStream out) throws IORuntimeException { + // 多资源 + if (value instanceof MultiResource) { + for (Resource subResource : (MultiResource) value) { + appendPart(formFieldName, subResource, out); + } + return; + } + + write(out, "--", BOUNDARY, StrUtil.CRLF); + + if(value instanceof Resource){ + // 文件资源(二进制资源) + final Resource resource = (Resource)value; + final String fileName = resource.getName(); + write(out, StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, ObjectUtil.defaultIfNull(fileName, formFieldName))); + // 根据name的扩展名指定互联网媒体类型,默认二进制流数据 + write(out, StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, HttpUtil.getMimeType(fileName, "application/octet-stream"))); + resource.writeTo(out); + } else{ + // 普通数据 + write(out, StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName)); + write(out, value); + } + + write(out, StrUtil.CRLF); + } + + /** + * 上传表单结束 + * + * @param out 输出流 + * @throws IORuntimeException IO异常 + */ + private void formEnd(OutputStream out) throws IORuntimeException { + write(out, BOUNDARY_END); + } + + /** + * 写出对象 + * + * @param out 输出流 + * @param objs 写出的对象(转换为字符串) + */ + private void write(OutputStream out, Object... objs) { + IoUtil.write(out, this.charset, false, objs); + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java b/hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java new file mode 100644 index 000000000..574a4ed6f --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java @@ -0,0 +1,16 @@ +package cn.hutool.http.body; + +import java.io.OutputStream; + +/** + * 定义请求体接口 + */ +public interface RequestBody { + + /** + * 写出数据,不关闭流 + * + * @param out out流 + */ + void write(OutputStream out); +} diff --git a/hutool-http/src/main/java/cn/hutool/http/body/package-info.java b/hutool-http/src/main/java/cn/hutool/http/body/package-info.java new file mode 100644 index 000000000..3e15dbfa0 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/body/package-info.java @@ -0,0 +1,7 @@ +/** + * 请求体封装实现 + * + * @author looly + * + */ +package cn.hutool.http.body; \ No newline at end of file diff --git a/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java b/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java index de61f7f70..f76021b18 100644 --- a/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java @@ -27,11 +27,13 @@ public class SimpleServerTest { // 文件上传测试 // http://localhost:8888/formTest?a=1&a=2&b=3 .addAction("/file", (request, response) -> { - final UploadFile file = request.getMultipart().getFile("file"); + final UploadFile[] files = request.getMultipart().getFiles("file"); // 传入目录,默认读取HTTP头中的文件名然后创建文件 - file.write("d:/test/"); - Console.log("Write file to: d:/test/"); - response.write(request.getParams().toString(), ContentType.TEXT_PLAIN.toString()); + for (UploadFile file : files) { + file.write("d:/test/"); + Console.log("Write file: d:/test/" + file.getFileName()); + } + response.write(request.getMultipart().getParamMap().toString(), ContentType.TEXT_PLAIN.toString()); } ) .start(); diff --git a/hutool-http/src/test/java/cn/hutool/http/test/UploadTest.java b/hutool-http/src/test/java/cn/hutool/http/test/UploadTest.java index 8fdd17c30..6a8e151bd 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/UploadTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/UploadTest.java @@ -1,15 +1,15 @@ package cn.hutool.http.test; -import java.io.File; -import java.util.HashMap; - -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Console; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.File; +import java.util.HashMap; /** * 文件上传单元测试 @@ -24,16 +24,16 @@ public class UploadTest { @Test @Ignore public void uploadFilesTest() { - File file = FileUtil.file("e:\\face.jpg"); - File file2 = FileUtil.file("e:\\face2.jpg"); + File file = FileUtil.file("d:\\图片1.JPG"); + File file2 = FileUtil.file("d:\\图片3.png"); // 方法一:自定义构建表单 HttpRequest request = HttpRequest// - .post("http://localhost:8090/file/upload")// + .post("http://localhost:8888/file")// .form("file", file2, file)// .form("fileType", "图片"); HttpResponse response = request.execute(); - System.out.println(response.body()); + Console.log(response.body()); } @Test diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI1H2VN.java b/hutool-json/src/test/java/cn/hutool/json/IssueI1H2VN.java new file mode 100644 index 000000000..5004d2fac --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI1H2VN.java @@ -0,0 +1,40 @@ +package cn.hutool.json; + +import lombok.Data; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +/** + * 测试同一对象作为对象的字段是否会有null的问题, + * 此问题原来出在BeanCopier中,判断循环引用使用了equals,并不严谨。 + * 修复后使用==判断循环引用。 + */ +public class IssueI1H2VN { + + @Test + public void toBeanTest() { + String jsonStr = "{'conditionsVo':[{'column':'StockNo','value':'abc','type':'='},{'column':'CheckIncoming','value':'1','type':'='}]," + + "'queryVo':{'conditionsVo':[{'column':'StockNo','value':'abc','type':'='},{'column':'CheckIncoming','value':'1','type':'='}],'queryVo':null}}"; + QueryVo vo = JSONUtil.toBean(jsonStr, QueryVo.class); + Assert.assertEquals(2, vo.getConditionsVo().size()); + final QueryVo subVo = vo.getQueryVo(); + Assert.assertNotNull(subVo); + Assert.assertEquals(2, subVo.getConditionsVo().size()); + Assert.assertNull(subVo.getQueryVo()); + } + + @Data + public static class ConditionVo { + private String column; + private String value; + private String type; + } + + @Data + public static class QueryVo { + private List conditionsVo; + private QueryVo queryVo; + } +} From a4bc10801cb9fe3714872b6165e0bb748bb2f04f Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 14 May 2020 01:59:11 +0800 Subject: [PATCH 122/157] fix comment --- .../cn/hutool/core/collection/CollUtil.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index 07a2e0d55..778e66f5c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -159,17 +159,17 @@ public class CollUtil { @SafeVarargs public static Set unionDistinct(Collection coll1, Collection coll2, Collection... otherColls) { final Set result; - if(isEmpty(coll1)){ + if (isEmpty(coll1)) { result = new LinkedHashSet<>(); } else { result = new LinkedHashSet<>(coll1); } - if(isNotEmpty(coll2)){ + if (isNotEmpty(coll2)) { result.addAll(coll2); } - if(ArrayUtil.isNotEmpty(otherColls)){ + if (ArrayUtil.isNotEmpty(otherColls)) { for (Collection otherColl : otherColls) { result.addAll(otherColl); } @@ -193,17 +193,17 @@ public class CollUtil { @SafeVarargs public static List unionAll(Collection coll1, Collection coll2, Collection... otherColls) { final List result; - if(isEmpty(coll1)){ + if (isEmpty(coll1)) { result = new ArrayList<>(); } else { result = new ArrayList<>(coll1); } - if(isNotEmpty(coll2)){ + if (isNotEmpty(coll2)) { result.addAll(coll2); } - if(ArrayUtil.isNotEmpty(otherColls)){ + if (ArrayUtil.isNotEmpty(otherColls)) { for (Collection otherColl : otherColls) { result.addAll(otherColl); } @@ -409,11 +409,11 @@ public class CollUtil { * @since 4.5.12 */ public static boolean containsAll(Collection coll1, Collection coll2) { - if(isEmpty(coll1)){ + if (isEmpty(coll1)) { return isEmpty(coll2); } - if(isEmpty(coll2)){ + if (isEmpty(coll2)) { return true; } @@ -1271,8 +1271,8 @@ public class CollUtil { * @param editor 编辑器 * @param ignoreNull 是否忽略空值 * @return 抽取后的新列表 - * @since 4.5.7 * @see #map(Iterable, Function, boolean) + * @since 4.5.7 */ public static List extract(Iterable collection, Editor editor, boolean ignoreNull) { return map(collection, editor::edit, ignoreNull); @@ -1282,6 +1282,8 @@ public class CollUtil { * 通过func自定义一个规则,此规则将原集合中的元素转换成新的元素,生成新的列表返回
    * 例如提供的是一个Bean列表,通过Function接口实现获取某个字段值,返回这个字段值组成的新列表 * + * @param 集合元素类型 + * @param 返回集合元素类型 * @param collection 原集合 * @param func 编辑函数 * @param ignoreNull 是否忽略空值 From 5e381a10071812d30ee55de17e66dcc3536ff75f Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 14 May 2020 02:19:43 +0800 Subject: [PATCH 123/157] prepare 5.3.6 --- CHANGELOG.md | 8 ++++++++ README.md | 8 ++++---- bin/version.txt | 2 +- docs/js/version.js | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 25 files changed, 35 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cfccb9e5..2c64a8067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ ------------------------------------------------------------------------------------------------------------- +## 5.3.6 (2020-05-14) + +### 新特性 + +### Bug修复 + +------------------------------------------------------------------------------------------------------------- + ## 5.3.5 (2020-05-13) ### 新特性 diff --git a/README.md b/README.md index ecec3bb8b..e15401016 100644 --- a/README.md +++ b/README.md @@ -116,21 +116,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.3.5 + 5.3.6 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.3.5' +compile 'cn.hutool:hutool-all:5.3.6' ``` ### 非Maven项目 点击以下任一链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.3.5/) -- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.5/) +- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.3.6/) +- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.6/) > 注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index e61ecd12d..9a97057db 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.3.5 +5.3.6 diff --git a/docs/js/version.js b/docs/js/version.js index fd0d2385c..71491d0e4 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.3.5' \ No newline at end of file +var version = '5.3.6' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 6a7a43095..5521bd7e1 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 54bedd837..b13f5e5eb 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index ec8eb0e01..731b60d5d 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 08ed345ad..8da905e90 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index a52182798..58066ac06 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 05d38c746..ac2b49d38 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index f27560e39..53810980b 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 0a3fc5461..6ba0bb8a2 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 4e4ba7a29..1a6c2d7b9 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index fb31b1b7a..c76e32eea 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 572508a7e..f8f84ebbd 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index cf28049e0..7ef57f39b 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index e2b453335..592d1e0ab 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 4b503adda..03ec5d02b 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index ab3e29f44..f107727be 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 9f027d4e4..8917a404d 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 0c3137514..56df02915 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 9951eb07b..b8b8aeb00 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index f9b26d6e6..cf8d45849 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index e17235fd3..003558f0a 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 07f3f7988..45982c59a 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.3.5 + 5.3.6-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool From 6fad5aa90612da223b4e9690cc5ae77afd9ebb25 Mon Sep 17 00:00:00 2001 From: zzzzbw Date: Sun, 17 May 2020 11:07:15 +0800 Subject: [PATCH 124/157] =?UTF-8?q?NumberConverter=20Long=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E5=A2=9E=E5=8A=A0=E6=97=A5=E6=9C=9F=E8=BD=AC=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/core/convert/impl/NumberConverter.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java index b102c0274..8ec0b8886 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java @@ -1,12 +1,16 @@ package cn.hutool.core.convert.impl; import cn.hutool.core.convert.AbstractConverter; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; import java.math.BigDecimal; import java.math.BigInteger; +import java.time.temporal.TemporalAccessor; +import java.util.Calendar; +import java.util.Date; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -93,6 +97,12 @@ public class NumberConverter extends AbstractConverter { return ((Number) value).longValue(); } else if(value instanceof Boolean) { return BooleanUtil.toLongObj((Boolean)value); + } else if (value instanceof Date) { + return ((Date) value).getTime(); + } else if (value instanceof Calendar) { + return ((Calendar) value).getTimeInMillis(); + } else if (value instanceof TemporalAccessor) { + return DateUtil.toInstant((TemporalAccessor) value).toEpochMilli(); } final String valueStr = convertToStr(value); return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseLong(valueStr); From 34d646bd88b9a4b20d26af3503788d955078bbe3 Mon Sep 17 00:00:00 2001 From: zz <358714485@qq.com> Date: Sun, 17 May 2020 18:50:15 +0800 Subject: [PATCH 125/157] fix annotation in StrUtil and SymmetricCrypto --- hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java | 4 ++-- .../main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java index f7f8abc4a..3bcb3bc7f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java @@ -2953,7 +2953,7 @@ public class StrUtil { * StrUtil.padAfter("123", 2, '0');//"23" * * - * @param str 字符串,如果为null,按照空串处理 + * @param str 字符串,如果为null,直接返回null * @param minLength 最小长度 * @param padChar 补充的字符 * @return 补充后的字符串 @@ -2981,7 +2981,7 @@ public class StrUtil { * StrUtil.padAfter("123", 2, "ABC");//"23" * * - * @param str 字符串,如果为null,按照空串处理 + * @param str 字符串,如果为null,直接返回null * @param minLength 最小长度 * @param padStr 补充的字符 * @return 补充后的字符串 diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java index bd8fac9c1..27261779d 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java @@ -436,7 +436,7 @@ public class SymmetricCrypto implements Serializable { } /** - * 解密Hex表示的字符串,默认UTF-8编码 + * 解密Hex(16进制)或Base64表示的字符串,默认UTF-8编码 * * @param data 被解密的String * @return 解密后的String From 200dc6c5eac2c6693e50a31d2c87519d01821810 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 17 May 2020 19:19:24 +0800 Subject: [PATCH 126/157] add date to long support for Convert --- CHANGELOG.md | 3 +- .../core/convert/impl/NumberConverter.java | 48 ++++++++----------- .../core/convert/ConvertToNumberTest.java | 26 ++++++++++ .../extra/pinyin/engine/PinyinFactory.java | 2 +- 4 files changed, 50 insertions(+), 29 deletions(-) create mode 100644 hutool-core/src/test/java/cn/hutool/core/convert/ConvertToNumberTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c64a8067..32f3a5fe9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,10 @@ ------------------------------------------------------------------------------------------------------------- -## 5.3.6 (2020-05-14) +## 5.3.6 (2020-05-17) ### 新特性 +* 【core 】 NumberConverter Long类型增加日期转换(pr#872@Github) ### Bug修复 diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java index 8ec0b8886..040035e8e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java @@ -21,7 +21,9 @@ import java.util.concurrent.atomic.AtomicLong; *
  • java.lang.Byte
  • *
  • java.lang.Short
  • *
  • java.lang.Integer
  • + *
  • java.util.concurrent.atomic.AtomicInteger
  • *
  • java.lang.Long
  • + *
  • java.util.concurrent.atomic.AtomicLong
  • *
  • java.lang.Float
  • *
  • java.lang.Double
  • *
  • java.math.BigDecimal
  • @@ -51,7 +53,10 @@ public class NumberConverter extends AbstractConverter { @Override protected Number convertInternal(Object value) { - final Class targetType = this.targetType; + return convertInternal(value, this.targetType); + } + + private Number convertInternal(Object value, Class targetType) { if (Byte.class == targetType) { if (value instanceof Number) { return ((Number) value).byteValue(); @@ -60,7 +65,7 @@ public class NumberConverter extends AbstractConverter { } final String valueStr = convertToStr(value); return StrUtil.isBlank(valueStr) ? null : Byte.valueOf(valueStr); - + } else if (Short.class == targetType) { if (value instanceof Number) { return ((Number) value).shortValue(); @@ -78,20 +83,15 @@ public class NumberConverter extends AbstractConverter { } final String valueStr = convertToStr(value); return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseInt(valueStr); - + } else if (AtomicInteger.class == targetType) { - final AtomicInteger intValue = new AtomicInteger(); - if (value instanceof Number) { - intValue.set(((Number) value).intValue()); - } else if(value instanceof Boolean) { - intValue.set(BooleanUtil.toInt((Boolean) value)); + final Number number = convertInternal(value, Integer.class); + if(null != number){ + final AtomicInteger intValue = new AtomicInteger(); + intValue.set(number.intValue()); + return intValue; } - final String valueStr = convertToStr(value); - if (StrUtil.isBlank(valueStr)) { - return null; - } - intValue.set(NumberUtil.parseInt(valueStr)); - return intValue; + return null; } else if (Long.class == targetType) { if (value instanceof Number) { return ((Number) value).longValue(); @@ -108,19 +108,13 @@ public class NumberConverter extends AbstractConverter { return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseLong(valueStr); } else if (AtomicLong.class == targetType) { - final AtomicLong longValue = new AtomicLong(); - if (value instanceof Number) { - longValue.set(((Number) value).longValue()); - } else if(value instanceof Boolean) { - longValue.set(BooleanUtil.toLong((Boolean) value)); + final Number number = convertInternal(value, Long.class); + if(null != number){ + final AtomicLong longValue = new AtomicLong(); + longValue.set(number.longValue()); + return longValue; } - final String valueStr = convertToStr(value); - if (StrUtil.isBlank(valueStr)) { - return null; - } - longValue.set(NumberUtil.parseLong(valueStr)); - return longValue; - + return null; } else if (Float.class == targetType) { if (value instanceof Number) { return ((Number) value).floatValue(); @@ -144,7 +138,7 @@ public class NumberConverter extends AbstractConverter { } else if (BigInteger.class == targetType) { return toBigInteger(value); - + }else if(Number.class == targetType){ if (value instanceof Number) { return (Number)value; diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToNumberTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToNumberTest.java new file mode 100644 index 000000000..ed2b09b95 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToNumberTest.java @@ -0,0 +1,26 @@ +package cn.hutool.core.convert; + +import cn.hutool.core.date.DateTime; +import cn.hutool.core.date.DateUtil; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.atomic.AtomicLong; + +public class ConvertToNumberTest { + @Test + public void dateToLongTest(){ + final DateTime date = DateUtil.parse("2020-05-17 12:32:00"); + final Long dateLong = Convert.toLong(date); + assert date != null; + Assert.assertEquals(date.getTime(), dateLong.longValue()); + } + + @Test + public void dateToAtomicLongTest(){ + final DateTime date = DateUtil.parse("2020-05-17 12:32:00"); + final AtomicLong dateLong = Convert.convert(AtomicLong.class, date); + assert date != null; + Assert.assertEquals(date.getTime(), dateLong.longValue()); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/PinyinFactory.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/PinyinFactory.java index 09d2228ba..cef4a5fd0 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/PinyinFactory.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/PinyinFactory.java @@ -47,6 +47,6 @@ public class PinyinFactory { return engine; } - throw new TemplateException("No template found ! Please add one of pinyin jar to your project !"); + throw new TemplateException("No pinyin jar found ! Please add one of it to your project !"); } } From a0b1a336540ef27e7dba57aa97aed1cd4a3e63a1 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 17 May 2020 19:26:40 +0800 Subject: [PATCH 127/157] add date to long support for Convert --- .../core/convert/impl/NumberConverter.java | 59 ++++++++++--------- .../core/convert/ConvertToNumberTest.java | 8 +++ 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java index 040035e8e..97fca587f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java @@ -29,9 +29,8 @@ import java.util.concurrent.atomic.AtomicLong; *
  • java.math.BigDecimal
  • *
  • java.math.BigInteger
  • * - * - * @author Looly * + * @author Looly */ public class NumberConverter extends AbstractConverter { private static final long serialVersionUID = 1L; @@ -44,7 +43,7 @@ public class NumberConverter extends AbstractConverter { /** * 构造
    - * + * * @param clazz 需要转换的数字类型,默认 {@link Number} */ public NumberConverter(Class clazz) { @@ -60,8 +59,8 @@ public class NumberConverter extends AbstractConverter { if (Byte.class == targetType) { if (value instanceof Number) { return ((Number) value).byteValue(); - } else if(value instanceof Boolean) { - return BooleanUtil.toByteObj((Boolean)value); + } else if (value instanceof Boolean) { + return BooleanUtil.toByteObj((Boolean) value); } final String valueStr = convertToStr(value); return StrUtil.isBlank(valueStr) ? null : Byte.valueOf(valueStr); @@ -69,8 +68,8 @@ public class NumberConverter extends AbstractConverter { } else if (Short.class == targetType) { if (value instanceof Number) { return ((Number) value).shortValue(); - } else if(value instanceof Boolean) { - return BooleanUtil.toShortObj((Boolean)value); + } else if (value instanceof Boolean) { + return BooleanUtil.toShortObj((Boolean) value); } final String valueStr = convertToStr(value); return StrUtil.isBlank(valueStr) ? null : Short.valueOf(valueStr); @@ -78,15 +77,21 @@ public class NumberConverter extends AbstractConverter { } else if (Integer.class == targetType) { if (value instanceof Number) { return ((Number) value).intValue(); - } else if(value instanceof Boolean) { - return BooleanUtil.toInteger((Boolean)value); + } else if (value instanceof Boolean) { + return BooleanUtil.toInteger((Boolean) value); + } else if (value instanceof Date) { + return (int)((Date) value).getTime(); + } else if (value instanceof Calendar) { + return (int)((Calendar) value).getTimeInMillis(); + } else if (value instanceof TemporalAccessor) { + return (int)DateUtil.toInstant((TemporalAccessor) value).toEpochMilli(); } final String valueStr = convertToStr(value); return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseInt(valueStr); } else if (AtomicInteger.class == targetType) { final Number number = convertInternal(value, Integer.class); - if(null != number){ + if (null != number) { final AtomicInteger intValue = new AtomicInteger(); intValue.set(number.intValue()); return intValue; @@ -95,8 +100,8 @@ public class NumberConverter extends AbstractConverter { } else if (Long.class == targetType) { if (value instanceof Number) { return ((Number) value).longValue(); - } else if(value instanceof Boolean) { - return BooleanUtil.toLongObj((Boolean)value); + } else if (value instanceof Boolean) { + return BooleanUtil.toLongObj((Boolean) value); } else if (value instanceof Date) { return ((Date) value).getTime(); } else if (value instanceof Calendar) { @@ -109,7 +114,7 @@ public class NumberConverter extends AbstractConverter { } else if (AtomicLong.class == targetType) { final Number number = convertInternal(value, Long.class); - if(null != number){ + if (null != number) { final AtomicLong longValue = new AtomicLong(); longValue.set(number.longValue()); return longValue; @@ -118,8 +123,8 @@ public class NumberConverter extends AbstractConverter { } else if (Float.class == targetType) { if (value instanceof Number) { return ((Number) value).floatValue(); - } else if(value instanceof Boolean) { - return BooleanUtil.toFloatObj((Boolean)value); + } else if (value instanceof Boolean) { + return BooleanUtil.toFloatObj((Boolean) value); } final String valueStr = convertToStr(value); return StrUtil.isBlank(valueStr) ? null : Float.valueOf(valueStr); @@ -127,8 +132,8 @@ public class NumberConverter extends AbstractConverter { } else if (Double.class == targetType) { if (value instanceof Number) { return ((Number) value).doubleValue(); - } else if(value instanceof Boolean) { - return BooleanUtil.toDoubleObj((Boolean)value); + } else if (value instanceof Boolean) { + return BooleanUtil.toDoubleObj((Boolean) value); } final String valueStr = convertToStr(value); return StrUtil.isBlank(valueStr) ? null : Double.valueOf(valueStr); @@ -139,11 +144,11 @@ public class NumberConverter extends AbstractConverter { } else if (BigInteger.class == targetType) { return toBigInteger(value); - }else if(Number.class == targetType){ + } else if (Number.class == targetType) { if (value instanceof Number) { - return (Number)value; - } else if(value instanceof Boolean) { - return BooleanUtil.toInteger((Boolean)value); + return (Number) value; + } else if (value instanceof Boolean) { + return BooleanUtil.toInteger((Boolean) value); } final String valueStr = convertToStr(value); return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseNumber(valueStr); @@ -156,7 +161,7 @@ public class NumberConverter extends AbstractConverter { * 转换为BigDecimal
    * 如果给定的值为空,或者转换失败,返回默认值
    * 转换失败不会报错 - * + * * @param value 被转换的值 * @return 结果 */ @@ -167,8 +172,8 @@ public class NumberConverter extends AbstractConverter { return new BigDecimal((Integer) value); } else if (value instanceof BigInteger) { return new BigDecimal((BigInteger) value); - } else if(value instanceof Boolean) { - return new BigDecimal((boolean)value ? 1 : 0); + } else if (value instanceof Boolean) { + return new BigDecimal((boolean) value ? 1 : 0); } //对于Double类型,先要转换为String,避免精度问题 @@ -183,15 +188,15 @@ public class NumberConverter extends AbstractConverter { * 转换为BigInteger
    * 如果给定的值为空,或者转换失败,返回默认值
    * 转换失败不会报错 - * + * * @param value 被转换的值 * @return 结果 */ private BigInteger toBigInteger(Object value) { if (value instanceof Long) { return BigInteger.valueOf((Long) value); - } else if(value instanceof Boolean) { - return BigInteger.valueOf((boolean)value ? 1 : 0); + } else if (value instanceof Boolean) { + return BigInteger.valueOf((boolean) value ? 1 : 0); } final String valueStr = convertToStr(value); if (StrUtil.isBlank(valueStr)) { diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToNumberTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToNumberTest.java index ed2b09b95..f8b30cf26 100644 --- a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToNumberTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToNumberTest.java @@ -16,6 +16,14 @@ public class ConvertToNumberTest { Assert.assertEquals(date.getTime(), dateLong.longValue()); } + @Test + public void dateToIntTest(){ + final DateTime date = DateUtil.parse("2020-05-17 12:32:00"); + final Integer dateInt = Convert.toInt(date); + assert date != null; + Assert.assertEquals((int)date.getTime(), dateInt.intValue()); + } + @Test public void dateToAtomicLongTest(){ final DateTime date = DateUtil.parse("2020-05-17 12:32:00"); From 9440512d54c6fa97715d3dc53eea1044e110a2bd Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 17 May 2020 19:29:17 +0800 Subject: [PATCH 128/157] fix commnet --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32f3a5fe9..18249e244 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### 新特性 * 【core 】 NumberConverter Long类型增加日期转换(pr#872@Github) +* 【all 】 StrUtil and SymmetricCrypto注释修正(pr#873@Github) ### Bug修复 From a3eca85e12db8b5a2d80f1511b6923c27f376463 Mon Sep 17 00:00:00 2001 From: jptx1234 Date: Sun, 17 May 2020 23:29:43 +0800 Subject: [PATCH 129/157] =?UTF-8?q?Snowflake=20=E5=BE=AA=E7=8E=AF=E7=AD=89?= =?UTF-8?q?=E5=BE=85=E4=B8=8B=E4=B8=80=E4=B8=AA=E6=97=B6=E9=97=B4=E6=97=B6?= =?UTF-8?q?=E5=8A=A0=E5=85=A5=E5=AF=B9=E6=97=B6=E9=92=9F=E5=80=92=E9=80=80?= =?UTF-8?q?=E7=9A=84=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Snowflake 在循环等待下一个时间时,判断时间未发生变化的方式由<=改为==,避免了时钟突然倒退导致的长时间循环,同时加入时钟倒退检测,避免生成重复ID --- .../src/main/java/cn/hutool/core/lang/Snowflake.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java b/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java index b39ddb3bd..44908d7d0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java @@ -177,9 +177,15 @@ public class Snowflake implements Serializable { */ private long tilNextMillis(long lastTimestamp) { long timestamp = genTime(); - while (timestamp <= lastTimestamp) { + // 循环直到操作系统时间戳变化 + while (timestamp == lastTimestamp) { timestamp = genTime(); } + if (timestamp < lastTimestamp) { + // 如果发现新的时间戳比上次记录的时间戳数值小,说明操作系统时间发生了倒退,报错 + throw new IllegalStateException( + StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp)); + } return timestamp; } From 6b3be8468c291f4065beb2760f2855a10302a41c Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 18 May 2020 09:38:20 +0800 Subject: [PATCH 130/157] add read bean for csv --- CHANGELOG.md | 1 + .../hutool/core/io/resource/ResourceUtil.java | 30 ++++++--- .../hutool/core/text/csv/CsvBaseReader.java | 58 ++++++++++++++---- .../java/cn/hutool/core/text/csv/CsvRow.java | 18 +++++- .../hutool/core/text/csv/CsvReaderTest.java | 61 ++++++++++++++++++- hutool-core/src/test/resources/test_bean.csv | 4 ++ 6 files changed, 148 insertions(+), 24 deletions(-) create mode 100644 hutool-core/src/test/resources/test_bean.csv diff --git a/CHANGELOG.md b/CHANGELOG.md index 18249e244..c69bf49a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### 新特性 * 【core 】 NumberConverter Long类型增加日期转换(pr#872@Github) * 【all 】 StrUtil and SymmetricCrypto注释修正(pr#873@Github) +* 【core 】 CsvReader支持返回Bean(issue#869@Github) ### Bug修复 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java index bc7e8c47a..b8b9844d1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/ResourceUtil.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.EnumerationIter; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ClassLoaderUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; @@ -61,41 +62,52 @@ public class ResourceUtil { /** * 从ClassPath资源中获取{@link InputStream} * - * @param resurce ClassPath资源 + * @param resource ClassPath资源 * @return {@link InputStream} * @throws NoResourceException 资源不存在异常 * @since 3.1.2 */ - public static InputStream getStream(String resurce) throws NoResourceException { - return getResourceObj(resurce).getStream(); + public static InputStream getStream(String resource) throws NoResourceException { + return getResourceObj(resource).getStream(); } /** * 从ClassPath资源中获取{@link InputStream},当资源不存在时返回null * - * @param resurce ClassPath资源 + * @param resource ClassPath资源 * @return {@link InputStream} * @since 4.0.3 */ - public static InputStream getStreamSafe(String resurce) { + public static InputStream getStreamSafe(String resource) { try { - return getResourceObj(resurce).getStream(); + return getResourceObj(resource).getStream(); } catch (NoResourceException e) { // ignore } return null; } + /** + * 从ClassPath资源中获取{@link BufferedReader} + * + * @param resource ClassPath资源 + * @return {@link InputStream} + * @since 5.3.6 + */ + public static BufferedReader getUtf8Reader(String resource) { + return getReader(resource, CharsetUtil.CHARSET_UTF_8); + } + /** * 从ClassPath资源中获取{@link BufferedReader} * - * @param resurce ClassPath资源 + * @param resource ClassPath资源 * @param charset 编码 * @return {@link InputStream} * @since 3.1.2 */ - public static BufferedReader getReader(String resurce, Charset charset) { - return getResourceObj(resurce).getReader(charset); + public static BufferedReader getReader(String resource, Charset charset) { + return getResourceObj(resource).getReader(charset); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvBaseReader.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvBaseReader.java index d65bb56ee..f41987037 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvBaseReader.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvBaseReader.java @@ -14,6 +14,7 @@ import java.nio.charset.Charset; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -143,17 +144,6 @@ public class CsvBaseReader implements Serializable { return read(FileUtil.getReader(path, charset)); } - /** - * 从Reader中读取CSV数据,读取后关闭Reader - * - * @param reader Reader - * @param rowHandler 行处理器,用于一行一行的处理数据 - * @throws IORuntimeException IO异常 - */ - public void read(Reader reader, CsvRowHandler rowHandler) throws IORuntimeException { - read(parse(reader), rowHandler); - } - /** * 从Reader中读取CSV数据,读取后关闭Reader * @@ -170,6 +160,52 @@ public class CsvBaseReader implements Serializable { return new CsvData(header, rows); } + /** + * 从Reader中读取CSV数据,结果为Map,读取后关闭Reader。
    + * 此方法默认识别首行为标题行。 + * + * @param reader Reader + * @return {@link CsvData},包含数据列表和行信息 + * @throws IORuntimeException IO异常 + */ + public List> readMapList(Reader reader) throws IORuntimeException { + // 此方法必须包含标题 + this.config.setContainsHeader(true); + + final List> result = new ArrayList<>(); + read(reader, (row) -> result.add(row.getFieldMap())); + return result; + } + + /** + * 从Reader中读取CSV数据并转换为Bean列表,读取后关闭Reader。
    + * 此方法默认识别首行为标题行。 + * + * @param Bean类型 + * @param reader Reader + * @param clazz Bean类型 + * @return Bean列表 + */ + public List read(Reader reader, Class clazz) { + // 此方法必须包含标题 + this.config.setContainsHeader(true); + + final List result = new ArrayList<>(); + read(reader, (row) -> result.add(row.toBean(clazz))); + return result; + } + + /** + * 从Reader中读取CSV数据,读取后关闭Reader + * + * @param reader Reader + * @param rowHandler 行处理器,用于一行一行的处理数据 + * @throws IORuntimeException IO异常 + */ + public void read(Reader reader, CsvRowHandler rowHandler) throws IORuntimeException { + read(parse(reader), rowHandler); + } + //--------------------------------------------------------------------------------------------- Private method start /** diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvRow.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvRow.java index 0f76e3987..aaeedee39 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvRow.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvRow.java @@ -1,5 +1,7 @@ package cn.hutool.core.text.csv; +import cn.hutool.core.bean.BeanUtil; + import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; @@ -74,7 +76,7 @@ public final class CsvRow implements List { /** * 获取标题与字段值对应的Map * - * @return an unmodifiable map of header names and field values of this row + * @return 标题与字段值对应的Map * @throws IllegalStateException CSV文件无标题行抛出此异常 */ public Map getFieldMap() { @@ -82,7 +84,7 @@ public final class CsvRow implements List { throw new IllegalStateException("No header available"); } - final Map fieldMap = new LinkedHashMap<>(headerMap.size()); + final Map fieldMap = new LinkedHashMap<>(headerMap.size(), 1); String key; Integer col; String val; @@ -96,6 +98,18 @@ public final class CsvRow implements List { return fieldMap; } + /** + * 一行数据转换为Bean对象 + * + * @param Bean类型 + * @param clazz bean类 + * @return Bean + * @since 5.3.6 + */ + public T toBean(Class clazz){ + return BeanUtil.mapToBean(getFieldMap(), clazz, true); + } + /** * 获取字段格式 * diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvReaderTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvReaderTest.java index 59d9e61fe..0db3d8142 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvReaderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvReaderTest.java @@ -1,10 +1,14 @@ package cn.hutool.core.text.csv; +import cn.hutool.core.annotation.Alias; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.util.CharsetUtil; +import lombok.Data; import org.junit.Assert; import org.junit.Test; -import cn.hutool.core.io.resource.ResourceUtil; -import cn.hutool.core.util.CharsetUtil; +import java.util.List; +import java.util.Map; public class CsvReaderTest { @@ -14,4 +18,57 @@ public class CsvReaderTest { CsvData data = reader.read(ResourceUtil.getReader("test.csv", CharsetUtil.CHARSET_UTF_8)); Assert.assertEquals("关注\"对象\"", data.getRow(0).get(2)); } + + @Test + public void readMapListTest(){ + final CsvReader reader = CsvUtil.getReader(); + final List> result = reader.readMapList( + ResourceUtil.getUtf8Reader("test_bean.csv")); + + Assert.assertEquals("张三", result.get(0).get("姓名")); + Assert.assertEquals("男", result.get(0).get("gender")); + Assert.assertEquals("无", result.get(0).get("focus")); + Assert.assertEquals("33", result.get(0).get("age")); + + Assert.assertEquals("李四", result.get(1).get("姓名")); + Assert.assertEquals("男", result.get(1).get("gender")); + Assert.assertEquals("好对象", result.get(1).get("focus")); + Assert.assertEquals("23", result.get(1).get("age")); + + Assert.assertEquals("王妹妹", result.get(2).get("姓名")); + Assert.assertEquals("女", result.get(2).get("gender")); + Assert.assertEquals("特别关注", result.get(2).get("focus")); + Assert.assertEquals("22", result.get(2).get("age")); + } + + @Test + public void readBeanListTest(){ + final CsvReader reader = CsvUtil.getReader(); + final List result = reader.read( + ResourceUtil.getUtf8Reader("test_bean.csv"), TestBean.class); + + Assert.assertEquals("张三", result.get(0).getName()); + Assert.assertEquals("男", result.get(0).getGender()); + Assert.assertEquals("无", result.get(0).getFocus()); + Assert.assertEquals(Integer.valueOf(33), result.get(0).getAge()); + + Assert.assertEquals("李四", result.get(1).getName()); + Assert.assertEquals("男", result.get(1).getGender()); + Assert.assertEquals("好对象", result.get(1).getFocus()); + Assert.assertEquals(Integer.valueOf(23), result.get(1).getAge()); + + Assert.assertEquals("王妹妹", result.get(2).getName()); + Assert.assertEquals("女", result.get(2).getGender()); + Assert.assertEquals("特别关注", result.get(2).getFocus()); + Assert.assertEquals(Integer.valueOf(22), result.get(2).getAge()); + } + + @Data + private static class TestBean{ + @Alias("姓名") + private String name; + private String gender; + private String focus; + private Integer age; + } } diff --git a/hutool-core/src/test/resources/test_bean.csv b/hutool-core/src/test/resources/test_bean.csv new file mode 100644 index 000000000..40d6862d8 --- /dev/null +++ b/hutool-core/src/test/resources/test_bean.csv @@ -0,0 +1,4 @@ +姓名,gender,focus,age +张三,男,无,33 +李四,男,好对象,23 +王妹妹,女,特别关注,22 \ No newline at end of file From 3c6b7fdc82735d5624467dc7217841c3356dd430 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 18 May 2020 09:46:26 +0800 Subject: [PATCH 131/157] fix snowflake --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c69bf49a7..e7055c2ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * 【core 】 NumberConverter Long类型增加日期转换(pr#872@Github) * 【all 】 StrUtil and SymmetricCrypto注释修正(pr#873@Github) * 【core 】 CsvReader支持返回Bean(issue#869@Github) +* 【core 】 Snowflake循环等待下一个时间时避免长时间循环,加入对时钟倒退的判断(pr#874@Github) ### Bug修复 From 5d5480af459dce65246451f92dd2c523f092e619 Mon Sep 17 00:00:00 2001 From: wb-zj268791 Date: Tue, 19 May 2020 14:42:02 +0800 Subject: [PATCH 132/157] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20QRCode=20=20base64?= =?UTF-8?q?=20=E7=BC=96=E7=A0=81=E5=BD=A2=E5=BC=8F=E8=BF=94=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/extra/qrcode/QrCodeUtil.java | 60 ++++++++++++++++++- .../hutool/extra/qrcode/QrCodeUtilTest.java | 16 +++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java index 73c5927fa..df6c28fff 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java @@ -19,12 +19,18 @@ import com.google.zxing.common.HybridBinarizer; import java.awt.Image; import java.awt.Rectangle; import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.text.MessageFormat; +import java.util.Base64; import java.util.HashMap; +import javax.imageio.ImageIO; + /** * 基于Zxing的二维码工具类 * @@ -34,9 +40,59 @@ import java.util.HashMap; */ public class QrCodeUtil { + /** + * 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示 + * @param content 内容 + * @param qrConfig 二维码配置,包括长、宽、边距、颜色等 + * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} + * @param logoBase64 logo 图片的 base64 编码 + * @return 图片 Base64 编码字符串 + */ + public static String generateAsBase64(String content, QrConfig qrConfig,String imageType,String logoBase64){ + byte[] decode; + try { + decode = Base64.getDecoder().decode(logoBase64); + }catch (Exception e){ + throw new QrCodeException(e); + } + return generateAsBase64(content,qrConfig,imageType,decode); + } + + /** + * 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示 + * @param content 内容 + * @param qrConfig 二维码配置,包括长、宽、边距、颜色等 + * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} + * @param logo logo 图片的byte[] + * @return 图片 Base64 编码字符串 + */ + public static String generateAsBase64(String content, QrConfig qrConfig,String imageType,byte [] logo){ + BufferedImage img; + try { + img = ImageIO.read(new ByteArrayInputStream(logo)); + } catch (IOException e) { + throw new QrCodeException(e); + } + qrConfig.setImg(img); + return generateAsBase64(content,qrConfig, imageType); + } + + /** + * 生成 Base64 编码格式的二维码,以 String 形式表示 + * @param content 内容 + * @param qrConfig 二维码配置,包括长、宽、边距、颜色等 + * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} + * @return 图片 Base64 编码字符串 + */ + public static String generateAsBase64(String content,QrConfig qrConfig,String imageType){ + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + generate(content,qrConfig,imageType,bos); + byte[] encode = Base64.getEncoder().encode(bos.toByteArray()); + return MessageFormat.format("data:image/{0};base64,{1}",imageType,new String(encode)); + } /** * 生成PNG格式的二维码图片,以byte[]形式表示 - * + * * @param content 内容 * @param width 宽度 * @param height 高度 @@ -51,7 +107,7 @@ public class QrCodeUtil { /** * 生成PNG格式的二维码图片,以byte[]形式表示 - * + * * @param content 内容 * @param config 二维码配置,包括长、宽、边距、颜色等 * @return 图片的byte[] diff --git a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java index cc32c3d8c..fe29dfeed 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java @@ -1,7 +1,9 @@ package cn.hutool.extra.qrcode; import java.awt.Color; +import java.io.File; +import cn.hutool.core.codec.Base64; import org.junit.Ignore; import org.junit.Test; @@ -51,4 +53,18 @@ public class QrCodeUtilTest { String decode = QrCodeUtil.decode(FileUtil.file("e:/pic/qr.png")); Console.log(decode); } + + @Test + public void generateAsBase64Test(){ + String base64 = QrCodeUtil.generateAsBase64("http://hutool.cn/", new QrConfig(400, 400), "png"); + System.out.println(base64); + + byte[] bytes = FileUtil.readBytes( + new File("e:/pic/qr.png")); + String encode = Base64.encode(bytes); + String base641 = QrCodeUtil.generateAsBase64("http://hutool.cn/", new QrConfig(400, 400), "png", encode); + System.out.println(base641); + + } + } From 0d4da3bec49ecef4bacf6c2fb170d44aac3f66ae Mon Sep 17 00:00:00 2001 From: wb-zj268791 Date: Tue, 19 May 2020 14:42:55 +0800 Subject: [PATCH 133/157] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20QRCode=20=20base64?= =?UTF-8?q?=20=E7=BC=96=E7=A0=81=E5=BD=A2=E5=BC=8F=E8=BF=94=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java index fe29dfeed..0552d6c61 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java @@ -55,6 +55,7 @@ public class QrCodeUtilTest { } @Test + @Ignore public void generateAsBase64Test(){ String base64 = QrCodeUtil.generateAsBase64("http://hutool.cn/", new QrConfig(400, 400), "png"); System.out.println(base64); From addf09488def4eb3fc39056fe529ce76502ca713 Mon Sep 17 00:00:00 2001 From: helixcs Date: Tue, 19 May 2020 15:04:21 +0800 Subject: [PATCH 134/157] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20QRCode=20=20base64?= =?UTF-8?q?=20=E7=BC=96=E7=A0=81=E5=BD=A2=E5=BC=8F=E8=BF=94=E5=9B=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/extra/qrcode/QrCodeUtil.java | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java index df6c28fff..735f43766 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java @@ -42,31 +42,33 @@ public class QrCodeUtil { /** * 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示 - * @param content 内容 - * @param qrConfig 二维码配置,包括长、宽、边距、颜色等 - * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} + * + * @param content 内容 + * @param qrConfig 二维码配置,包括长、宽、边距、颜色等 + * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} * @param logoBase64 logo 图片的 base64 编码 - * @return 图片 Base64 编码字符串 + * @return 图片 Base64 编码字符串 */ - public static String generateAsBase64(String content, QrConfig qrConfig,String imageType,String logoBase64){ + public static String generateAsBase64(String content, QrConfig qrConfig, String imageType, String logoBase64) { byte[] decode; try { decode = Base64.getDecoder().decode(logoBase64); - }catch (Exception e){ + } catch (Exception e) { throw new QrCodeException(e); } - return generateAsBase64(content,qrConfig,imageType,decode); + return generateAsBase64(content, qrConfig, imageType, decode); } /** * 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示 - * @param content 内容 - * @param qrConfig 二维码配置,包括长、宽、边距、颜色等 - * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} - * @param logo logo 图片的byte[] - * @return 图片 Base64 编码字符串 + * + * @param content 内容 + * @param qrConfig 二维码配置,包括长、宽、边距、颜色等 + * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} + * @param logo logo 图片的byte[] + * @return 图片 Base64 编码字符串 */ - public static String generateAsBase64(String content, QrConfig qrConfig,String imageType,byte [] logo){ + public static String generateAsBase64(String content, QrConfig qrConfig, String imageType, byte[] logo) { BufferedImage img; try { img = ImageIO.read(new ByteArrayInputStream(logo)); @@ -74,21 +76,22 @@ public class QrCodeUtil { throw new QrCodeException(e); } qrConfig.setImg(img); - return generateAsBase64(content,qrConfig, imageType); + return generateAsBase64(content, qrConfig, imageType); } /** * 生成 Base64 编码格式的二维码,以 String 形式表示 - * @param content 内容 - * @param qrConfig 二维码配置,包括长、宽、边距、颜色等 + * + * @param content 内容 + * @param qrConfig 二维码配置,包括长、宽、边距、颜色等 * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} * @return 图片 Base64 编码字符串 */ - public static String generateAsBase64(String content,QrConfig qrConfig,String imageType){ + public static String generateAsBase64(String content, QrConfig qrConfig, String imageType) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); - generate(content,qrConfig,imageType,bos); + generate(content, qrConfig, imageType, bos); byte[] encode = Base64.getEncoder().encode(bos.toByteArray()); - return MessageFormat.format("data:image/{0};base64,{1}",imageType,new String(encode)); + return MessageFormat.format("data:image/{0};base64,{1}", imageType, new String(encode)); } /** * 生成PNG格式的二维码图片,以byte[]形式表示 From d0644331bca20c5634a5ef94309011965c279cae Mon Sep 17 00:00:00 2001 From: easepan Date: Tue, 19 May 2020 15:07:48 +0800 Subject: [PATCH 135/157] feat: convert list to map --- .../cn/hutool/core/collection/IterUtil.java | 78 +++++++++++++++++++ .../hutool/core/collection/IterUtilTest.java | 28 ++++++- 2 files changed, 104 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java index 23675e37b..e7bf48690 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java @@ -5,6 +5,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.func.Func1; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; @@ -16,6 +17,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.function.Function; /** * {@link Iterable} 和 {@link Iterator} 相关工具类 @@ -457,6 +459,82 @@ public class IterUtil { return resultMap; } + /** + * 将列表转成值为List的HashMap + * + * @param iterable 值列表 + * @param keyMapper Map的键映射 + * @param 键类型 + * @param 值类型 + * @return HashMap + */ + public static Map> toListMap(Iterable iterable, Function keyMapper) { + return toListMap(iterable, keyMapper, v -> v); + } + + /** + * 将列表转成值为List的HashMap + * + * @param iterable 值列表 + * @param keyMapper Map的键映射 + * @param valueMapper Map中List的值映射 + * @param 列表值类型 + * @param 键类型 + * @param 值类型 + * @return HashMap + */ + public static Map> toListMap(Iterable iterable, Function keyMapper, Function valueMapper) { + final HashMap> resultMap = MapUtil.newHashMap(); + + if (ObjectUtil.isNull(iterable)) { + return resultMap; + } + + for (T value : iterable) { + resultMap.computeIfAbsent(keyMapper.apply(value), k -> new ArrayList<>()).add(valueMapper.apply(value)); + } + + return resultMap; + } + + /** + * 将列表转成HashMap + * + * @param iterable 值列表 + * @param keyMapper Map的键映射 + * @param 键类型 + * @param 值类型 + * @return HashMap + */ + public static Map toMap(Iterable iterable, Function keyMapper) { + return toMap(iterable, keyMapper, v -> v); + } + + /** + * 将列表转成HashMap + * + * @param iterable 值列表 + * @param keyMapper Map的键映射 + * @param valueMapper Map的值映射 + * @param 列表值类型 + * @param 键类型 + * @param 值类型 + * @return HashMap + */ + public static Map toMap(Iterable iterable, Function keyMapper, Function valueMapper) { + final HashMap resultMap = MapUtil.newHashMap(); + + if (ObjectUtil.isNull(iterable)) { + return resultMap; + } + + for (T value : iterable) { + resultMap.put(keyMapper.apply(value), valueMapper.apply(value)); + } + + return resultMap; + } + /** * Iterator转List
    * 不判断,直接生成新的List diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java index a516536bf..0ab5d784a 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/IterUtilTest.java @@ -3,8 +3,7 @@ package cn.hutool.core.collection; import org.junit.Assert; import org.junit.Test; -import java.util.ArrayList; -import java.util.Map; +import java.util.*; /** * {@link IterUtil} 单元测试 @@ -38,6 +37,31 @@ public class IterUtilTest { Assert.assertEquals("\"1\":\"2\":\"3\":\"4\"", join2); } + @Test + public void testToListMap() { + Map> expectedMap = new HashMap<>(); + expectedMap.put("a", Collections.singletonList("and")); + expectedMap.put("b", Arrays.asList("brave", "back")); + + Map> testMap = IterUtil.toListMap(Arrays.asList("and", "brave", "back"), + v -> v.substring(0, 1)); + Assert.assertEquals(testMap, expectedMap); + } + + @Test + public void testToMap() { + Map expectedMap = new HashMap<>(); + + Car bmw = new Car("123", "bmw"); + expectedMap.put("123", bmw); + + Car benz = new Car("456", "benz"); + expectedMap.put("456", benz); + + Map testMap = IterUtil.toMap(Arrays.asList(bmw, benz), Car::getCarNumber); + Assert.assertEquals(expectedMap, testMap); + } + public static class Car { private String carNumber; private String carName; From 5dbc49ad26ba43f7db8fedd6ce0366e56b76184b Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 19 May 2020 16:15:22 +0800 Subject: [PATCH 136/157] fix commmet --- .../java/cn/hutool/core/date/DateTime.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java index 1a0758155..d0626f382 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java @@ -1,5 +1,12 @@ package cn.hutool.core.date; +import cn.hutool.core.date.format.DateParser; +import cn.hutool.core.date.format.DatePrinter; +import cn.hutool.core.date.format.FastDateFormat; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; + import java.sql.Timestamp; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -13,13 +20,6 @@ import java.util.Date; import java.util.Locale; import java.util.TimeZone; -import cn.hutool.core.date.format.DateParser; -import cn.hutool.core.date.format.DatePrinter; -import cn.hutool.core.date.format.FastDateFormat; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; - /** * 包装java.util.Date * @@ -833,10 +833,10 @@ public class DateTime extends Date { // -------------------------------------------------------------------- toString start /** - * 转为"yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串
    + * 转为"yyyy-MM-dd HH:mm:ss" 格式字符串
    * 如果时区被设置,会转换为其时区对应的时间,否则转换为当前地点对应的时区 * - * @return "yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串 + * @return "yyyy-MM-dd HH:mm:ss" 格式字符串 */ @Override public String toString() { @@ -844,10 +844,10 @@ public class DateTime extends Date { } /** - * 转为"yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串
    + * 转为"yyyy-MM-dd HH:mm:ss" 格式字符串
    * 时区使用当前地区的默认时区 * - * @return "yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串 + * @return "yyyy-MM-dd HH:mm:ss" 格式字符串 * @since 4.1.14 */ public String toStringDefaultTimeZone() { @@ -855,11 +855,11 @@ public class DateTime extends Date { } /** - * 转为"yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串
    + * 转为"yyyy-MM-dd HH:mm:ss" 格式字符串
    * 如果时区不为{@code null},会转换为其时区对应的时间,否则转换为当前时间对应的时区 * * @param timeZone 时区 - * @return "yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串 + * @return "yyyy-MM-dd HH:mm:ss" 格式字符串 * @since 4.1.14 */ public String toString(TimeZone timeZone) { @@ -872,9 +872,9 @@ public class DateTime extends Date { } /** - * 转为"yyyy-MM-dd " 格式字符串 + * 转为"yyyy-MM-dd" 格式字符串 * - * @return "yyyy-MM-dd " 格式字符串 + * @return "yyyy-MM-dd" 格式字符串 * @since 4.0.0 */ public String toDateStr() { From 4b537016705abed3ab4a58daf6f00afa0c1eabc5 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 19 May 2020 17:17:17 +0800 Subject: [PATCH 137/157] add methods --- CHANGELOG.md | 4 +- .../main/java/cn/hutool/core/img/ImgUtil.java | 15 ++ .../java/cn/hutool/core/util/URLUtil.java | 72 +++++++- .../cn/hutool/extra/qrcode/QrCodeUtil.java | 158 +++++++++--------- .../hutool/extra/qrcode/QrCodeUtilTest.java | 14 +- 5 files changed, 170 insertions(+), 93 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7055c2ca..d442f4958 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,15 @@ ------------------------------------------------------------------------------------------------------------- -## 5.3.6 (2020-05-17) +## 5.3.6 (2020-05-19) ### 新特性 * 【core 】 NumberConverter Long类型增加日期转换(pr#872@Github) * 【all 】 StrUtil and SymmetricCrypto注释修正(pr#873@Github) * 【core 】 CsvReader支持返回Bean(issue#869@Github) * 【core 】 Snowflake循环等待下一个时间时避免长时间循环,加入对时钟倒退的判断(pr#874@Github) +* 【extra 】 新增 QRCode base64 编码形式返回(pr#878@Github) +* 【core 】 ImgUtil增加toBase64DateUri,URLUtil增加getDataUri方法 ### Bug修复 diff --git a/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java b/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java index 21bbcdb61..74d0faaa1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java @@ -13,6 +13,7 @@ import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; import javax.imageio.IIOImage; import javax.imageio.ImageIO; @@ -1261,6 +1262,20 @@ public class ImgUtil { return IoUtil.toStream(toBytes(image, imageType)); } + /** + * 将图片对象转换为Base64的Data URI形式,格式为:data:image/[imageType];base64,[data] + * + * @param image 图片对象 + * @param imageType 图片类型 + * @return Base64的字符串表现形式 + * @since 5.3.6 + */ + public static String toBase64DateUri(Image image, String imageType) { + return URLUtil.getDataUri( + "image/" + imageType, "base64", + toBase64(image, imageType)); + } + /** * 将图片对象转换为Base64形式 * diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 62359ef8a..989c89acb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -28,7 +28,16 @@ import java.util.Map; import java.util.jar.JarFile; /** - * 统一资源定位符相关工具类 + * URL(Uniform Resource Locator)统一资源定位符相关工具类 + * + *

    + * 统一资源定位符,描述了一台特定服务器上某资源的特定位置。 + *

    + * URL组成: + *
    + *   协议://主机名[:端口]/ 路径/[:参数] [?查询]#Fragment
    + *   protocol :// hostname[:port] / path / [:parameters][?query]#fragment
    + * 
    * * @author xiaoleilu */ @@ -749,11 +758,11 @@ public class URLUtil { * @throws IORuntimeException IO异常 * @since 5.3.4 */ - public static long getContentLength(URL url) throws IORuntimeException{ - if(null == url){ + public static long getContentLength(URL url) throws IORuntimeException { + if (null == url) { return -1; } - + URLConnection conn = null; try { conn = url.openConnection(); @@ -762,8 +771,61 @@ public class URLUtil { throw new IORuntimeException(e); } finally { if (conn instanceof HttpURLConnection) { - ((HttpURLConnection)conn).disconnect(); + ((HttpURLConnection) conn).disconnect(); } } } + + /** + * Data URI Scheme封装。data URI scheme 允许我们使用内联(inline-code)的方式在网页中包含数据,
    + * 目的是将一些小的数据,直接嵌入到网页中,从而不用再从外部文件载入。常用于将图片嵌入网页。 + * + *

    + * Data URI的格式规范: + *

    +	 *     data:[][;charset=][;],
    +	 * 
    + * + * @param mimeType 可选项(null表示无),数据类型(image/png、text/plain等) + * @param encoding 数据编码方式(US-ASCII,BASE64等) + * @param data 编码后的数据 + * @return Data URI字符串 + * @since 5.3.6 + */ + public static String getDataUri(String mimeType, String encoding, String data){ + return getDataUri(mimeType, null, encoding, data); + } + + /** + * Data URI Scheme封装。data URI scheme 允许我们使用内联(inline-code)的方式在网页中包含数据,
    + * 目的是将一些小的数据,直接嵌入到网页中,从而不用再从外部文件载入。常用于将图片嵌入网页。 + * + *

    + * Data URI的格式规范: + *

    +	 *     data:[][;charset=][;],
    +	 * 
    + * + * @param mimeType 可选项(null表示无),数据类型(image/png、text/plain等) + * @param charset 可选项(null表示无),源文本的字符集编码方式 + * @param encoding 数据编码方式(US-ASCII,BASE64等) + * @param data 编码后的数据 + * @return Data URI字符串 + * @since 5.3.6 + */ + public static String getDataUri(String mimeType, Charset charset, String encoding, String data){ + final StringBuilder builder = StrUtil.builder("data:"); + if(StrUtil.isNotBlank(mimeType)){ + builder.append(mimeType); + } + if(null != charset){ + builder.append(";charset=").append(charset.name()); + } + if(StrUtil.isNotBlank(encoding)){ + builder.append(';').append(encoding); + } + builder.append(',').append(data); + + return builder.toString(); + } } \ No newline at end of file diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java index 735f43766..f0cee3c74 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java @@ -1,5 +1,6 @@ package cn.hutool.extra.qrcode; +import cn.hutool.core.codec.Base64; import cn.hutool.core.img.Img; import cn.hutool.core.img.ImgUtil; import cn.hutool.core.util.CharsetUtil; @@ -19,24 +20,17 @@ import com.google.zxing.common.HybridBinarizer; import java.awt.Image; import java.awt.Rectangle; import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.text.MessageFormat; -import java.util.Base64; import java.util.HashMap; -import javax.imageio.ImageIO; - /** * 基于Zxing的二维码工具类 - * + * * @author looly * @since 4.0.2 - * */ public class QrCodeUtil { @@ -50,13 +44,7 @@ public class QrCodeUtil { * @return 图片 Base64 编码字符串 */ public static String generateAsBase64(String content, QrConfig qrConfig, String imageType, String logoBase64) { - byte[] decode; - try { - decode = Base64.getDecoder().decode(logoBase64); - } catch (Exception e) { - throw new QrCodeException(e); - } - return generateAsBase64(content, qrConfig, imageType, decode); + return generateAsBase64(content, qrConfig, imageType, Base64.decode(logoBase64)); } /** @@ -69,36 +57,46 @@ public class QrCodeUtil { * @return 图片 Base64 编码字符串 */ public static String generateAsBase64(String content, QrConfig qrConfig, String imageType, byte[] logo) { - BufferedImage img; - try { - img = ImageIO.read(new ByteArrayInputStream(logo)); - } catch (IOException e) { - throw new QrCodeException(e); - } - qrConfig.setImg(img); + return generateAsBase64(content, qrConfig, imageType, ImgUtil.toImage(logo)); + } + + /** + * 生成代 logo 图片的 Base64 编码格式的二维码,以 String 形式表示 + * + * @param content 内容 + * @param qrConfig 二维码配置,包括长、宽、边距、颜色等 + * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} + * @param logo logo 图片的byte[] + * @return 图片 Base64 编码字符串 + */ + public static String generateAsBase64(String content, QrConfig qrConfig, String imageType, Image logo) { + qrConfig.setImg(logo); return generateAsBase64(content, qrConfig, imageType); } /** * 生成 Base64 编码格式的二维码,以 String 形式表示 * + *

    + * 输出格式为: data:image/[type];base64,[data] + *

    + * * @param content 内容 * @param qrConfig 二维码配置,包括长、宽、边距、颜色等 * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} * @return 图片 Base64 编码字符串 */ public static String generateAsBase64(String content, QrConfig qrConfig, String imageType) { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - generate(content, qrConfig, imageType, bos); - byte[] encode = Base64.getEncoder().encode(bos.toByteArray()); - return MessageFormat.format("data:image/{0};base64,{1}", imageType, new String(encode)); + final BufferedImage img = generate(content, qrConfig); + return ImgUtil.toBase64DateUri(img, imageType); } + /** * 生成PNG格式的二维码图片,以byte[]形式表示 * * @param content 内容 - * @param width 宽度 - * @param height 高度 + * @param width 宽度 + * @param height 高度 * @return 图片的byte[] * @since 4.0.10 */ @@ -112,7 +110,7 @@ public class QrCodeUtil { * 生成PNG格式的二维码图片,以byte[]形式表示 * * @param content 内容 - * @param config 二维码配置,包括长、宽、边距、颜色等 + * @param config 二维码配置,包括长、宽、边距、颜色等 * @return 图片的byte[] * @since 4.1.2 */ @@ -124,10 +122,10 @@ public class QrCodeUtil { /** * 生成二维码到文件,二维码图片格式取决于文件的扩展名 - * - * @param content 文本内容 - * @param width 宽度 - * @param height 高度 + * + * @param content 文本内容 + * @param width 宽度 + * @param height 高度 * @param targetFile 目标文件,扩展名决定输出格式 * @return 目标文件 */ @@ -139,9 +137,9 @@ public class QrCodeUtil { /** * 生成二维码到文件,二维码图片格式取决于文件的扩展名 - * - * @param content 文本内容 - * @param config 二维码配置,包括长、宽、边距、颜色等 + * + * @param content 文本内容 + * @param config 二维码配置,包括长、宽、边距、颜色等 * @param targetFile 目标文件,扩展名决定输出格式 * @return 目标文件 * @since 4.1.2 @@ -154,12 +152,12 @@ public class QrCodeUtil { /** * 生成二维码到输出流 - * - * @param content 文本内容 - * @param width 宽度 - * @param height 高度 + * + * @param content 文本内容 + * @param width 宽度 + * @param height 高度 * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} - * @param out 目标流 + * @param out 目标流 */ public static void generate(String content, int width, int height, String imageType, OutputStream out) { final BufferedImage image = generate(content, width, height); @@ -168,11 +166,11 @@ public class QrCodeUtil { /** * 生成二维码到输出流 - * - * @param content 文本内容 - * @param config 二维码配置,包括长、宽、边距、颜色等 + * + * @param content 文本内容 + * @param config 二维码配置,包括长、宽、边距、颜色等 * @param imageType 图片类型(图片扩展名),见{@link ImgUtil} - * @param out 目标流 + * @param out 目标流 * @since 4.1.2 */ public static void generate(String content, QrConfig config, String imageType, OutputStream out) { @@ -182,10 +180,10 @@ public class QrCodeUtil { /** * 生成二维码图片 - * + * * @param content 文本内容 - * @param width 宽度 - * @param height 高度 + * @param width 宽度 + * @param height 高度 * @return 二维码图片(黑白) */ public static BufferedImage generate(String content, int width, int height) { @@ -194,11 +192,11 @@ public class QrCodeUtil { /** * 生成二维码或条形码图片 - * + * * @param content 文本内容 - * @param format 格式,可选二维码或者条形码 - * @param width 宽度 - * @param height 高度 + * @param format 格式,可选二维码或者条形码 + * @param width 宽度 + * @param height 高度 * @return 二维码图片(黑白) */ public static BufferedImage generate(String content, BarcodeFormat format, int width, int height) { @@ -207,9 +205,9 @@ public class QrCodeUtil { /** * 生成二维码图片 - * + * * @param content 文本内容 - * @param config 二维码配置,包括长、宽、边距、颜色等 + * @param config 二维码配置,包括长、宽、边距、颜色等 * @return 二维码图片(黑白) * @since 4.1.2 */ @@ -220,10 +218,10 @@ public class QrCodeUtil { /** * 生成二维码或条形码图片
    * 只有二维码时QrConfig中的图片才有效 - * + * * @param content 文本内容 - * @param format 格式,可选二维码、条形码等 - * @param config 二维码配置,包括长、宽、边距、颜色等 + * @param format 格式,可选二维码、条形码等 + * @param config 二维码配置,包括长、宽、边距、颜色等 * @return 二维码图片(黑白) * @since 4.1.14 */ @@ -256,12 +254,13 @@ public class QrCodeUtil { } // ------------------------------------------------------------------------------------------------------------------- encode + /** * 将文本内容编码为二维码 - * + * * @param content 文本内容 - * @param width 宽度 - * @param height 高度 + * @param width 宽度 + * @param height 高度 * @return {@link BitMatrix} */ public static BitMatrix encode(String content, int width, int height) { @@ -270,9 +269,9 @@ public class QrCodeUtil { /** * 将文本内容编码为二维码 - * + * * @param content 文本内容 - * @param config 二维码配置,包括长、宽、边距、颜色等 + * @param config 二维码配置,包括长、宽、边距、颜色等 * @return {@link BitMatrix} * @since 4.1.2 */ @@ -282,11 +281,11 @@ public class QrCodeUtil { /** * 将文本内容编码为条形码或二维码 - * + * * @param content 文本内容 - * @param format 格式枚举 - * @param width 宽度 - * @param height 高度 + * @param format 格式枚举 + * @param width 宽度 + * @param height 高度 * @return {@link BitMatrix} */ public static BitMatrix encode(String content, BarcodeFormat format, int width, int height) { @@ -295,10 +294,10 @@ public class QrCodeUtil { /** * 将文本内容编码为条形码或二维码 - * + * * @param content 文本内容 - * @param format 格式枚举 - * @param config 二维码配置,包括长、宽、边距、颜色等 + * @param format 格式枚举 + * @param config 二维码配置,包括长、宽、边距、颜色等 * @return {@link BitMatrix} * @since 4.1.2 */ @@ -319,9 +318,10 @@ public class QrCodeUtil { } // ------------------------------------------------------------------------------------------------------------------- decode + /** * 解码二维码图片为文本 - * + * * @param qrCodeInputstream 二维码输入流 * @return 解码文本 */ @@ -331,7 +331,7 @@ public class QrCodeUtil { /** * 解码二维码图片为文本 - * + * * @param qrCodeFile 二维码文件 * @return 解码文本 */ @@ -341,7 +341,7 @@ public class QrCodeUtil { /** * 将二维码图片解码为文本 - * + * * @param image {@link Image} 二维码图片 * @return 解码后的文本 */ @@ -351,9 +351,9 @@ public class QrCodeUtil { /** * 将二维码图片解码为文本 - * - * @param image {@link Image} 二维码图片 - * @param isTryHarder 是否优化精度 + * + * @param image {@link Image} 二维码图片 + * @param isTryHarder 是否优化精度 * @param isPureBarcode 是否使用复杂模式,扫描带logo的二维码设为true * @return 解码后的文本 * @since 4.3.1 @@ -389,8 +389,8 @@ public class QrCodeUtil { /** * BitMatrix转BufferedImage - * - * @param matrix BitMatrix + * + * @param matrix BitMatrix * @param foreColor 前景色 * @param backColor 背景色(null表示透明背景) * @return BufferedImage @@ -402,9 +402,9 @@ public class QrCodeUtil { BufferedImage image = new BufferedImage(width, height, null == backColor ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { - if(matrix.get(x, y)) { + if (matrix.get(x, y)) { image.setRGB(x, y, foreColor); - } else if(null != backColor){ + } else if (null != backColor) { image.setRGB(x, y, backColor); } } diff --git a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java index 0552d6c61..f8600f8ff 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java @@ -1,16 +1,14 @@ package cn.hutool.extra.qrcode; -import java.awt.Color; -import java.io.File; - import cn.hutool.core.codec.Base64; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Console; +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import org.junit.Ignore; import org.junit.Test; -import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.lang.Console; +import java.awt.Color; +import java.io.File; /** * 二维码工具类单元测试 @@ -61,7 +59,7 @@ public class QrCodeUtilTest { System.out.println(base64); byte[] bytes = FileUtil.readBytes( - new File("e:/pic/qr.png")); + new File("d:/test/qr.png")); String encode = Base64.encode(bytes); String base641 = QrCodeUtil.generateAsBase64("http://hutool.cn/", new QrConfig(400, 400), "png", encode); System.out.println(base641); From 008855fafbc6c63f64fdb113413d595c718bb179 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 19 May 2020 17:31:15 +0800 Subject: [PATCH 138/157] add methods --- CHANGELOG.md | 1 + .../cn/hutool/core/collection/IterUtil.java | 44 ++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d442f4958..a6d9cb00b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * 【core 】 Snowflake循环等待下一个时间时避免长时间循环,加入对时钟倒退的判断(pr#874@Github) * 【extra 】 新增 QRCode base64 编码形式返回(pr#878@Github) * 【core 】 ImgUtil增加toBase64DateUri,URLUtil增加getDataUri方法 +* 【core 】 IterUtil添加List转Map的工具方法(pr#123@Gitee) ### Bug修复 diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java index e7bf48690..53b15b249 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java @@ -467,6 +467,7 @@ public class IterUtil { * @param 键类型 * @param 值类型 * @return HashMap + * @since 5.3.6 */ public static Map> toListMap(Iterable iterable, Function keyMapper) { return toListMap(iterable, keyMapper, v -> v); @@ -482,10 +483,29 @@ public class IterUtil { * @param 键类型 * @param 值类型 * @return HashMap + * @since 5.3.6 */ public static Map> toListMap(Iterable iterable, Function keyMapper, Function valueMapper) { - final HashMap> resultMap = MapUtil.newHashMap(); + return toListMap(MapUtil.newHashMap(), iterable, keyMapper, valueMapper); + } + /** + * 将列表转成值为List的HashMap + * + * @param resultMap 结果Map,可自定义结果Map类型 + * @param iterable 值列表 + * @param keyMapper Map的键映射 + * @param valueMapper Map中List的值映射 + * @param 列表值类型 + * @param 键类型 + * @param 值类型 + * @return HashMap + * @since 5.3.6 + */ + public static Map> toListMap(Map> resultMap, Iterable iterable, Function keyMapper, Function valueMapper) { + if (null == resultMap) { + resultMap = MapUtil.newHashMap(); + } if (ObjectUtil.isNull(iterable)) { return resultMap; } @@ -505,6 +525,7 @@ public class IterUtil { * @param 键类型 * @param 值类型 * @return HashMap + * @since 5.3.6 */ public static Map toMap(Iterable iterable, Function keyMapper) { return toMap(iterable, keyMapper, v -> v); @@ -520,10 +541,29 @@ public class IterUtil { * @param 键类型 * @param 值类型 * @return HashMap + * @since 5.3.6 */ public static Map toMap(Iterable iterable, Function keyMapper, Function valueMapper) { - final HashMap resultMap = MapUtil.newHashMap(); + return toMap(MapUtil.newHashMap(), iterable, keyMapper, valueMapper); + } + /** + * 将列表转成Map + * + * @param resultMap 结果Map,通过传入map对象决定结果的Map类型 + * @param iterable 值列表 + * @param keyMapper Map的键映射 + * @param valueMapper Map的值映射 + * @param 列表值类型 + * @param 键类型 + * @param 值类型 + * @return HashMap + * @since 5.3.6 + */ + public static Map toMap(Map resultMap, Iterable iterable, Function keyMapper, Function valueMapper) { + if (null == resultMap) { + resultMap = MapUtil.newHashMap(); + } if (ObjectUtil.isNull(iterable)) { return resultMap; } From f7bf950073e43fa1d0415702c8677292ab5d038f Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 19 May 2020 18:31:30 +0800 Subject: [PATCH 139/157] fix dead lock bug --- CHANGELOG.md | 1 + .../java/cn/hutool/core/lang/SimpleCache.java | 47 +++++++------------ .../java/cn/hutool/core/lang/func/Func.java | 15 ++++++ .../java/cn/hutool/core/lang/func/Func0.java | 14 ++++++ .../java/cn/hutool/core/lang/func/Func1.java | 15 ++++++ .../cn/hutool/core/lang/func/VoidFunc.java | 14 ++++++ .../cn/hutool/core/lang/func/VoidFunc0.java | 13 +++++ .../cn/hutool/core/lang/func/VoidFunc1.java | 14 ++++++ .../cn/hutool/core/lang/SingletonTest.java | 21 +++++++++ 9 files changed, 125 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6d9cb00b..73b88515c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * 【core 】 IterUtil添加List转Map的工具方法(pr#123@Gitee) ### Bug修复 +* 【core 】 修复SimpleCache死锁问题(issue#I1HOKB@Gitee) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java b/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java index 1f4ea3a75..c8149f759 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java @@ -6,7 +6,7 @@ import java.io.Serializable; import java.util.Iterator; import java.util.Map; import java.util.WeakHashMap; -import java.util.concurrent.locks.StampedLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; /** * 简单缓存,无超时实现,默认使用{@link WeakHashMap}实现缓存自动清理 @@ -23,7 +23,7 @@ public class SimpleCache implements Iterable>, Serializabl */ private final Map cache; // 乐观读写锁 - private final StampedLock lock = new StampedLock(); + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); /** * 构造,默认使用{@link WeakHashMap}实现缓存自动清理 @@ -53,11 +53,11 @@ public class SimpleCache implements Iterable>, Serializabl * @return 值 */ public V get(K key) { - long stamp = lock.readLock(); + lock.readLock().lock(); try { return cache.get(key); } finally { - lock.unlockRead(stamp); + lock.readLock().unlock(); } } @@ -69,23 +69,11 @@ public class SimpleCache implements Iterable>, Serializabl * @return 值对象 */ public V get(K key, Func0 supplier) { - if (null == supplier) { - return get(key); - } + V v = get(key); - long stamp = lock.readLock(); - V v; - try { - v = cache.get(key); - if (null == v) { - // 尝试转换独占写锁 - long writeStamp = lock.tryConvertToWriteLock(stamp); - if (0 == writeStamp) { - // 转换失败,手动更新为写锁 - lock.unlockRead(stamp); - writeStamp = lock.writeLock(); - } - stamp = writeStamp; + if(null == v && null != supplier){ + lock.writeLock().lock(); + try{ v = cache.get(key); // 双重检查,防止在竞争锁的过程中已经有其它线程写入 if (null == v) { @@ -96,10 +84,11 @@ public class SimpleCache implements Iterable>, Serializabl } cache.put(key, v); } + } finally{ + lock.writeLock().unlock(); } - } finally { - lock.unlock(stamp); } + return v; } @@ -112,11 +101,11 @@ public class SimpleCache implements Iterable>, Serializabl */ public V put(K key, V value) { // 独占写锁 - final long stamp = lock.writeLock(); + lock.writeLock().lock(); try { cache.put(key, value); } finally { - lock.unlockWrite(stamp); + lock.writeLock().unlock(); } return value; } @@ -129,11 +118,11 @@ public class SimpleCache implements Iterable>, Serializabl */ public V remove(K key) { // 独占写锁 - final long stamp = lock.writeLock(); + lock.writeLock().lock(); try { return cache.remove(key); } finally { - lock.unlockWrite(stamp); + lock.writeLock().unlock(); } } @@ -142,11 +131,11 @@ public class SimpleCache implements Iterable>, Serializabl */ public void clear() { // 独占写锁 - final long stamp = lock.writeLock(); + lock.writeLock().lock(); try { this.cache.clear(); } finally { - lock.unlockWrite(stamp); + lock.writeLock().unlock(); } } @@ -154,4 +143,4 @@ public class SimpleCache implements Iterable>, Serializabl public Iterator> iterator() { return this.cache.entrySet().iterator(); } -} +} \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Func.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Func.java index 8a91da6f1..a22f28ba6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/func/Func.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Func.java @@ -23,4 +23,19 @@ public interface Func { */ @SuppressWarnings("unchecked") R call(P... parameters) throws Exception; + + /** + * 执行函数,异常包装为RuntimeException + * + * @param parameters 参数列表 + * @return 函数执行结果 + */ + @SuppressWarnings("unchecked") + default R callWithRuntimeException(P... parameters){ + try { + return call(parameters); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Func0.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Func0.java index c0cbe71d8..bdc8db679 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/func/Func0.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Func0.java @@ -20,4 +20,18 @@ public interface Func0 { * @throws Exception 自定义异常 */ R call() throws Exception; + + /** + * 执行函数,异常包装为RuntimeException + * + * @return 函数执行结果 + * @since 5.3.6 + */ + default R callWithRuntimeException(){ + try { + return call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Func1.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Func1.java index 3306e82fd..1967f08d1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/func/Func1.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Func1.java @@ -23,4 +23,19 @@ public interface Func1 { * @throws Exception 自定义异常 */ R call(P parameter) throws Exception; + + /** + * 执行函数,异常包装为RuntimeException + * + * @param parameter 参数 + * @return 函数执行结果 + * @since 5.3.6 + */ + default R callWithRuntimeException(P parameter){ + try { + return call(parameter); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/VoidFunc.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/VoidFunc.java index 4034b79ff..931599f20 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/func/VoidFunc.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/VoidFunc.java @@ -22,4 +22,18 @@ public interface VoidFunc

    { */ @SuppressWarnings("unchecked") void call(P... parameters) throws Exception; + + /** + * 执行函数,异常包装为RuntimeException + * + * @param parameters 参数列表 + */ + @SuppressWarnings("unchecked") + default void callWithRuntimeException(P... parameters){ + try { + call(parameters); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/VoidFunc0.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/VoidFunc0.java index fd80af43b..a2a8ee54e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/func/VoidFunc0.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/VoidFunc0.java @@ -19,4 +19,17 @@ public interface VoidFunc0 { * @throws Exception 自定义异常 */ void call() throws Exception; + + /** + * 执行函数,异常包装为RuntimeException + * + * @since 5.3.6 + */ + default void callWithRuntimeException(){ + try { + call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/VoidFunc1.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/VoidFunc1.java index ac32f634b..4cade382b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/func/VoidFunc1.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/VoidFunc1.java @@ -20,4 +20,18 @@ public interface VoidFunc1

    { * @throws Exception 自定义异常 */ void call(P parameter) throws Exception; + + /** + * 执行函数,异常包装为RuntimeException + * + * @param parameter 参数 + * @since 5.3.6 + */ + default void callWithRuntimeException(P parameter){ + try { + call(parameter); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/SingletonTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/SingletonTest.java index 52c2f564b..1a9d0a434 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/SingletonTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/SingletonTest.java @@ -3,6 +3,7 @@ package cn.hutool.core.lang; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.thread.ThreadUtil; import lombok.Data; +import org.junit.Assert; import org.junit.Test; public class SingletonTest { @@ -27,4 +28,24 @@ public class SingletonTest { private String name; private String age; } + + /** + * 测试单例构建属性锁死问题 + * C构建单例时候,同时构建B,此时在SimpleCache中会有写锁竞争(写入C时获取了写锁,此时要写入B,也要获取写锁) + */ + @Test(timeout = 1000L) + public void reentrantTest(){ + final C c = Singleton.get(C.class); + Assert.assertEquals("aaa", c.getB().getA()); + } + + @Data + static class B{ + private String a = "aaa"; + } + + @Data + static class C{ + private B b = Singleton.get(B.class); + } } From ceaac0c412d3d6097bd8eb00ed801fcbb3e08463 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 21 May 2020 16:43:14 +0800 Subject: [PATCH 140/157] change return not null --- CHANGELOG.md | 1 + .../cn/hutool/core/bean/copier/provider/BeanValueProvider.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73b88515c..92ffc9924 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * 【extra 】 新增 QRCode base64 编码形式返回(pr#878@Github) * 【core 】 ImgUtil增加toBase64DateUri,URLUtil增加getDataUri方法 * 【core 】 IterUtil添加List转Map的工具方法(pr#123@Gitee) +* 【core 】 BeanValuePovider转换失败时,返回原数据,而非null ### Bug修复 * 【core 】 修复SimpleCache死锁问题(issue#I1HOKB@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java index e28fe49fa..ffc745f5e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java @@ -56,7 +56,8 @@ public class BeanValueProvider implements ValueProvider { } } - result = Convert.convertWithCheck(valueType,result, null, ignoreError); + // 尝试将结果转换为目标类型,如果转换失败,返回原类型。 + result = Convert.convertWithCheck(valueType,result, result, ignoreError); } } return result; From b9e468d6c79fa833d6cea80a4c49d327948fca8b Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 21 May 2020 16:44:50 +0800 Subject: [PATCH 141/157] change return not null --- hutool-http/src/main/java/cn/hutool/http/HttpUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index c24bf6e6b..a9944159f 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -353,7 +353,7 @@ public class HttpUtil { } /** - * 将Map形式的Form表单数据转换为Url参数形式,不做编码 + * 将Map形式的Form表单数据转换为Url参数形式,会自动url编码键和值 * * @param paramMap 表单数据 * @return url参数 From 9ff2d650bb43de6e88f57bafefea874100eb505f Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 25 May 2020 11:36:20 +0800 Subject: [PATCH 142/157] add toBean support Map --- CHANGELOG.md | 3 +- .../java/cn/hutool/core/bean/BeanUtil.java | 2 +- .../java/cn/hutool/core/util/ReflectUtil.java | 20 +++++++ .../java/cn/hutool/core/util/StrUtil.java | 56 +++++++++---------- .../cn/hutool/core/bean/BeanUtilTest.java | 16 ++++++ .../core/convert/ConvertToBeanTest.java | 18 ++++-- 6 files changed, 79 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92ffc9924..f383a7b00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -## 5.3.6 (2020-05-19) +## 5.3.6 (2020-05-25) ### 新特性 * 【core 】 NumberConverter Long类型增加日期转换(pr#872@Github) @@ -14,6 +14,7 @@ * 【core 】 ImgUtil增加toBase64DateUri,URLUtil增加getDataUri方法 * 【core 】 IterUtil添加List转Map的工具方法(pr#123@Gitee) * 【core 】 BeanValuePovider转换失败时,返回原数据,而非null +* 【core 】 支持BeanUtil.toBean(object, Map.class)转换(issue#I1I4HC@Gitee) ### Bug修复 * 【core 】 修复SimpleCache死锁问题(issue#I1HOKB@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java index c2f3257a7..45e97563d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java @@ -476,7 +476,7 @@ public class BeanUtil { * @since 5.2.4 */ public static T toBean(Object source, Class clazz, CopyOptions options) { - final T target = ReflectUtil.newInstance(clazz); + final T target = ReflectUtil.newInstanceIfPossible(clazz); copyProperties(source, target, options); return target; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java index 17a5f8c2f..222eb936e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java @@ -13,6 +13,7 @@ import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -769,13 +770,32 @@ public class ReflectUtil { /** * 尝试遍历并调用此类的所有构造方法,直到构造成功并返回 + *

    + * 对于某些特殊的接口,按照其默认实现实例化,例如: + *

    +	 *     Map       -》 HashMap
    +	 *     Collction -》 ArrayList
    +	 *     List      -》 ArrayList
    +	 *     Set       -》 HashSet
    +	 * 
    * * @param 对象类型 * @param beanClass 被构造的类 * @return 构造后的对象 */ + @SuppressWarnings("unchecked") public static T newInstanceIfPossible(Class beanClass) { Assert.notNull(beanClass); + + // 某些特殊接口的实例化按照默认实现进行 + if (beanClass.isAssignableFrom(AbstractMap.class)) { + beanClass = (Class) HashMap.class; + } else if (beanClass.isAssignableFrom(List.class)) { + beanClass = (Class) ArrayList.class; + } else if (beanClass.isAssignableFrom(Set.class)) { + beanClass = (Class) HashSet.class; + } + try { return newInstance(beanClass); } catch (Exception e) { diff --git a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java index 3bcb3bc7f..0d0bc15b1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java @@ -1741,14 +1741,14 @@ public class StrUtil { * 如果分隔字符串为空串"",则返回空串,如果分隔字符串未找到,返回原字符串,举例如下: * *
    -	 * StrUtil.subBefore(null, *)      = null
    -	 * StrUtil.subBefore("", *)        = ""
    -	 * StrUtil.subBefore("abc", "a")   = ""
    -	 * StrUtil.subBefore("abcba", "b") = "a"
    -	 * StrUtil.subBefore("abc", "c")   = "ab"
    -	 * StrUtil.subBefore("abc", "d")   = "abc"
    -	 * StrUtil.subBefore("abc", "")    = ""
    -	 * StrUtil.subBefore("abc", null)  = "abc"
    +	 * StrUtil.subBefore(null, *, false)      = null
    +	 * StrUtil.subBefore("", *, false)        = ""
    +	 * StrUtil.subBefore("abc", "a", false)   = ""
    +	 * StrUtil.subBefore("abcba", "b", false) = "a"
    +	 * StrUtil.subBefore("abc", "c", false)   = "ab"
    +	 * StrUtil.subBefore("abc", "d", false)   = "abc"
    +	 * StrUtil.subBefore("abc", "", false)    = ""
    +	 * StrUtil.subBefore("abc", null, false)  = "abc"
     	 * 
    * * @param string 被查找的字符串 @@ -1783,12 +1783,12 @@ public class StrUtil { * 如果分隔字符串未找到,返回原字符串,举例如下: * *
    -	 * StrUtil.subBefore(null, *)      = null
    -	 * StrUtil.subBefore("", *)        = ""
    -	 * StrUtil.subBefore("abc", 'a')   = ""
    -	 * StrUtil.subBefore("abcba", 'b') = "a"
    -	 * StrUtil.subBefore("abc", 'c')   = "ab"
    -	 * StrUtil.subBefore("abc", 'd')   = "abc"
    +	 * StrUtil.subBefore(null, *, false)      = null
    +	 * StrUtil.subBefore("", *, false)        = ""
    +	 * StrUtil.subBefore("abc", 'a', false)   = ""
    +	 * StrUtil.subBefore("abcba", 'b', false) = "a"
    +	 * StrUtil.subBefore("abc", 'c', false)   = "ab"
    +	 * StrUtil.subBefore("abc", 'd', false)   = "abc"
     	 * 
    * * @param string 被查找的字符串 @@ -1819,14 +1819,14 @@ public class StrUtil { * 如果分隔字符串为空串(null或""),则返回空串,如果分隔字符串未找到,返回空串,举例如下: * *
    -	 * StrUtil.subAfter(null, *)      = null
    -	 * StrUtil.subAfter("", *)        = ""
    -	 * StrUtil.subAfter(*, null)      = ""
    -	 * StrUtil.subAfter("abc", "a")   = "bc"
    -	 * StrUtil.subAfter("abcba", "b") = "cba"
    -	 * StrUtil.subAfter("abc", "c")   = ""
    -	 * StrUtil.subAfter("abc", "d")   = ""
    -	 * StrUtil.subAfter("abc", "")    = "abc"
    +	 * StrUtil.subAfter(null, *, false)      = null
    +	 * StrUtil.subAfter("", *, false)        = ""
    +	 * StrUtil.subAfter(*, null, false)      = ""
    +	 * StrUtil.subAfter("abc", "a", false)   = "bc"
    +	 * StrUtil.subAfter("abcba", "b", false) = "cba"
    +	 * StrUtil.subAfter("abc", "c", false)   = ""
    +	 * StrUtil.subAfter("abc", "d", false)   = ""
    +	 * StrUtil.subAfter("abc", "", false)    = "abc"
     	 * 
    * * @param string 被查找的字符串 @@ -1857,12 +1857,12 @@ public class StrUtil { * 如果分隔字符串为空串(null或""),则返回空串,如果分隔字符串未找到,返回空串,举例如下: * *
    -	 * StrUtil.subAfter(null, *)      = null
    -	 * StrUtil.subAfter("", *)        = ""
    -	 * StrUtil.subAfter("abc", 'a')   = "bc"
    -	 * StrUtil.subAfter("abcba", 'b') = "cba"
    -	 * StrUtil.subAfter("abc", 'c')   = ""
    -	 * StrUtil.subAfter("abc", 'd')   = ""
    +	 * StrUtil.subAfter(null, *, false)      = null
    +	 * StrUtil.subAfter("", *, false)        = ""
    +	 * StrUtil.subAfter("abc", 'a', false)   = "bc"
    +	 * StrUtil.subAfter("abcba", 'b', false) = "cba"
    +	 * StrUtil.subAfter("abc", 'c', false)   = ""
    +	 * StrUtil.subAfter("abc", 'd', false)   = ""
     	 * 
    * * @param string 被查找的字符串 diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java index 2297e75fe..1ccaa3977 100644 --- a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java @@ -75,6 +75,22 @@ public class BeanUtilTest { Assert.assertEquals(person.getOpenid(), "DFDFSDFWERWER"); } + @Test + public void toBeanTest(){ + SubPerson person = new SubPerson(); + person.setAge(14); + person.setOpenid("11213232"); + person.setName("测试A11"); + person.setSubName("sub名字"); + + final Map map = BeanUtil.toBean(person, Map.class); + Assert.assertEquals("测试A11", map.get("name")); + Assert.assertEquals(14, map.get("age")); + Assert.assertEquals("11213232", map.get("openid")); + // static属性应被忽略 + Assert.assertFalse(map.containsKey("SUBNAME")); + } + @Test public void mapToBeanIgnoreCaseTest() { HashMap map = CollUtil.newHashMap(); diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToBeanTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToBeanTest.java index b9ed303d9..166dc6dde 100644 --- a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToBeanTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertToBeanTest.java @@ -1,15 +1,15 @@ package cn.hutool.core.convert; +import cn.hutool.core.bean.BeanUtilTest.SubPerson; +import cn.hutool.core.lang.Console; +import cn.hutool.core.lang.TypeReference; +import org.junit.Assert; +import org.junit.Test; + import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import cn.hutool.core.lang.Console; -import org.junit.Assert; -import org.junit.Test; - -import cn.hutool.core.bean.BeanUtilTest.SubPerson; - /** * 类型转换工具单元测试
    * 转换为数组 @@ -45,6 +45,12 @@ public class ConvertToBeanTest { Assert.assertEquals("测试A11", map.get("name")); Assert.assertEquals("14", map.get("age")); Assert.assertEquals("11213232", map.get("openid")); + + final LinkedHashMap map2 = Convert.convert( + new TypeReference>() {}, person); + Assert.assertEquals("测试A11", map2.get("name")); + Assert.assertEquals("14", map2.get("age")); + Assert.assertEquals("11213232", map2.get("openid")); } @Test From 688eefc02b04c65f72bb53908f5739a65f08be8c Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 25 May 2020 11:39:40 +0800 Subject: [PATCH 143/157] add test --- .../src/test/java/cn/hutool/core/util/ArrayUtilTest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 7b6132d99..0f41afde3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -42,6 +42,11 @@ public class ArrayUtilTest { isEmpty = ArrayUtil.isEmpty(d); //noinspection ConstantConditions Assert.assertTrue(isEmpty); + + // Object数组 + Object[] e = new Object[]{"1", "2", 3, 4D}; + final boolean empty = ArrayUtil.isEmpty(e); + Assert.assertFalse(empty); } @Test From b90602746df7001ee9d805a5c779b4233d6c72bb Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 25 May 2020 11:55:55 +0800 Subject: [PATCH 144/157] fix img --- hutool-core/src/main/java/cn/hutool/core/img/Img.java | 1 - .../src/test/java/cn/hutool/core/img/ImgUtilTest.java | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/img/Img.java b/hutool-core/src/main/java/cn/hutool/core/img/Img.java index 4438a634f..0f2ae2911 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/Img.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/Img.java @@ -482,7 +482,6 @@ public class Img implements Serializable { public Img pressImage(Image pressImg, int x, int y, float alpha) { final int pressImgWidth = pressImg.getWidth(null); final int pressImgHeight = pressImg.getHeight(null); - return pressImage(pressImg, new Rectangle(x, y, pressImgWidth, pressImgHeight), alpha); } diff --git a/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java index 132a28965..f54308740 100644 --- a/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java @@ -60,7 +60,10 @@ public class ImgUtilTest { @Test @Ignore public void pressImgTest() { - ImgUtil.pressImage(FileUtil.file("d:/picTest/1.jpg"), FileUtil.file("d:/picTest/dest.jpg"), ImgUtil.read(FileUtil.file("d:/picTest/1432613.jpg")), 0, 0, 0.1f); + ImgUtil.pressImage( + FileUtil.file("d:/test/617180969474805871.jpg"), + FileUtil.file("d:/test/dest.png"), + ImgUtil.read(FileUtil.file("d:/test/vbbb.png")), 0, 0, 0.9f); } @Test From 4451a8451d296e210242836065f85188b013b966 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 25 May 2020 16:04:33 +0800 Subject: [PATCH 145/157] add test --- .../java/cn/hutool/core/collection/CollUtil.java | 14 ++++++++++++++ .../src/main/java/cn/hutool/core/map/MapUtil.java | 13 +++++++++++++ .../test/java/cn/hutool/db/nosql/RedisDSTest.java | 15 +++++++++++++++ hutool-db/src/test/resources/config/redis.setting | 2 +- 4 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 hutool-db/src/test/java/cn/hutool/db/nosql/RedisDSTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index 778e66f5c..de399b88b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -2727,6 +2727,20 @@ public class CollUtil { throw new IllegalArgumentException(StrUtil.format("[{}] is not support to get empty!", collectionClass)); } + /** + * 清除一个或多个集合内的元素,每个集合调用clear()方法 + * + * @param collections 一个或多个集合 + * @since 5.3.6 + */ + public static void clear(Collection... collections) { + for (Collection collection : collections) { + if (isNotEmpty(collection)) { + collection.clear(); + } + } + } + // ---------------------------------------------------------------------------------------------- Interface start /** diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java index c649a1177..07d432faa 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java @@ -1115,4 +1115,17 @@ public class MapUtil { // 不支持空集合的集合类型 throw new IllegalArgumentException(StrUtil.format("[{}] is not support to get empty!", mapClass)); } + + /** + * 清除一个或多个Map集合内的元素,每个Map调用clear()方法 + * + * @param maps 一个或多个Map + */ + public static void clear(Map... maps) { + for (Map map : maps) { + if (isNotEmpty(map)) { + map.clear(); + } + } + } } diff --git a/hutool-db/src/test/java/cn/hutool/db/nosql/RedisDSTest.java b/hutool-db/src/test/java/cn/hutool/db/nosql/RedisDSTest.java new file mode 100644 index 000000000..ec83d5555 --- /dev/null +++ b/hutool-db/src/test/java/cn/hutool/db/nosql/RedisDSTest.java @@ -0,0 +1,15 @@ +package cn.hutool.db.nosql; + +import cn.hutool.db.nosql.redis.RedisDS; +import org.junit.Ignore; +import org.junit.Test; +import redis.clients.jedis.Jedis; + +public class RedisDSTest { + + @Test + @Ignore + public void redisDSTest(){ + final Jedis jedis = RedisDS.create().getJedis(); + } +} diff --git a/hutool-db/src/test/resources/config/redis.setting b/hutool-db/src/test/resources/config/redis.setting index d9dec37cb..033299766 100644 --- a/hutool-db/src/test/resources/config/redis.setting +++ b/hutool-db/src/test/resources/config/redis.setting @@ -18,7 +18,7 @@ connectionTimeout = 2000 # 读取超时,默认timeout soTimeout = 2000 # 密码,默认无 -password = +#password = # 数据库序号,默认0 database = 0 # 客户端名,默认"Hutool" From 7c803950ef7a44fbbe59cdbc51327aa1037331dc Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 25 May 2020 16:15:54 +0800 Subject: [PATCH 146/157] add method --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f383a7b00..5e40ce6b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * 【core 】 IterUtil添加List转Map的工具方法(pr#123@Gitee) * 【core 】 BeanValuePovider转换失败时,返回原数据,而非null * 【core 】 支持BeanUtil.toBean(object, Map.class)转换(issue#I1I4HC@Gitee) +* 【core 】 MapUtil和CollUtil增加clear方法(issue#I1I4HC@Gitee) ### Bug修复 * 【core 】 修复SimpleCache死锁问题(issue#I1HOKB@Gitee) From 73b2f75de26c8e3f3c6bf88cc870f09129dea598 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 26 May 2020 00:15:59 +0800 Subject: [PATCH 147/157] add FontUtil --- CHANGELOG.md | 1 + .../java/cn/hutool/core/img/FontUtil.java | 123 ++++++++++++++++ .../java/cn/hutool/core/img/GraphicsUtil.java | 136 +++++++++++++++--- .../src/main/java/cn/hutool/core/img/Img.java | 48 ++++--- .../main/java/cn/hutool/core/img/ImgUtil.java | 45 +++--- 5 files changed, 290 insertions(+), 63 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/img/FontUtil.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e40ce6b5..322aa28bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * 【core 】 BeanValuePovider转换失败时,返回原数据,而非null * 【core 】 支持BeanUtil.toBean(object, Map.class)转换(issue#I1I4HC@Gitee) * 【core 】 MapUtil和CollUtil增加clear方法(issue#I1I4HC@Gitee) +* 【core 】 增加FontUtil,可定义pressText是否从中间(issue#I1HSWU@Gitee) ### Bug修复 * 【core 】 修复SimpleCache死锁问题(issue#I1HOKB@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/img/FontUtil.java b/hutool-core/src/main/java/cn/hutool/core/img/FontUtil.java new file mode 100644 index 000000000..6c3f52ab0 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/img/FontUtil.java @@ -0,0 +1,123 @@ +package cn.hutool.core.img; + +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.io.IORuntimeException; +import sun.font.FontDesignMetrics; + +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontFormatException; +import java.awt.FontMetrics; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +/** + * AWT中字体相关工具类 + * + * @author looly + * @since 5.3.6 + */ +public class FontUtil { + + /** + * 创建默认字体 + * + * @return 默认字体 + */ + public static Font createFont() { + return new Font(null); + } + + /** + * 创建SansSerif字体 + * + * @param size 字体大小 + * @return 字体 + */ + public static Font createSansSerifFont(int size) { + return createFont(Font.SANS_SERIF, size); + } + + /** + * 创建指定名称的字体 + * + * @param name 字体名称 + * @param size 字体大小 + * @return 字体 + */ + public static Font createFont(String name, int size) { + return new Font(name, Font.PLAIN, size); + } + + /** + * 根据文件创建字体
    + * 首先尝试创建{@link Font#TRUETYPE_FONT}字体,此类字体无效则创建{@link Font#TYPE1_FONT} + * + * @param fontFile 字体文件 + * @return {@link Font} + */ + public static Font createFont(File fontFile) { + try { + return Font.createFont(Font.TRUETYPE_FONT, fontFile); + } catch (FontFormatException e) { + // True Type字体无效时使用Type1字体 + try { + return Font.createFont(Font.TYPE1_FONT, fontFile); + } catch (Exception e1) { + throw new UtilException(e); + } + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 根据文件创建字体
    + * 首先尝试创建{@link Font#TRUETYPE_FONT}字体,此类字体无效则创建{@link Font#TYPE1_FONT} + * + * @param fontStream 字体流 + * @return {@link Font} + */ + public static Font createFont(InputStream fontStream) { + try { + return Font.createFont(Font.TRUETYPE_FONT, fontStream); + } catch (FontFormatException e) { + // True Type字体无效时使用Type1字体 + try { + return Font.createFont(Font.TYPE1_FONT, fontStream); + } catch (Exception e1) { + throw new UtilException(e1); + } + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 获得字体对应字符串的长宽信息 + * + * @param font 字体 + * @param str 字符串 + * @return 长宽信息 + */ + public static Dimension getDimension(Font font, String str) { + final FontMetrics metrics = FontDesignMetrics.getMetrics(font); + return getDimension(FontDesignMetrics.getMetrics(font), str); + } + + /** + * 获得字体对应字符串的长宽信息 + * + * @param metrics {@link FontMetrics} + * @param str 字符串 + * @return 长宽信息 + */ + public static Dimension getDimension(FontMetrics metrics, String str) { + final int width = metrics.stringWidth(str); + final int height = metrics.getAscent() - metrics.getLeading() - metrics.getDescent(); + + return new Dimension(width, height); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/img/GraphicsUtil.java b/hutool-core/src/main/java/cn/hutool/core/img/GraphicsUtil.java index f1ce6a002..107c87e45 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/GraphicsUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/GraphicsUtil.java @@ -1,16 +1,23 @@ package cn.hutool.core.img; +import cn.hutool.core.util.ObjectUtil; + +import java.awt.AlphaComposite; import java.awt.Color; +import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.Point; +import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.image.BufferedImage; /** * {@link Graphics}相关工具类 - * + * * @author looly * @since 4.5.2 */ @@ -18,7 +25,7 @@ public class GraphicsUtil { /** * 创建{@link Graphics2D} - * + * * @param image {@link BufferedImage} * @param color {@link Color}背景颜色以及当前画笔颜色,{@code null}表示不设置背景色 * @return {@link Graphics2D} @@ -26,8 +33,8 @@ public class GraphicsUtil { */ public static Graphics2D createGraphics(BufferedImage image, Color color) { final Graphics2D g = image.createGraphics(); - - if(null != color) { + + if (null != color) { // 填充背景 g.setColor(color); g.fillRect(0, 0, image.getWidth(), image.getHeight()); @@ -39,8 +46,8 @@ public class GraphicsUtil { /** * 获取文字居中高度的Y坐标(距离上边距距离)
    * 此方法依赖FontMetrics,如果获取失败,默认为背景高度的1/3 - * - * @param g {@link Graphics2D}画笔 + * + * @param g {@link Graphics2D}画笔 * @param backgroundHeight 背景高度 * @return 最小高度,-1表示无法获取 * @since 4.5.17 @@ -64,11 +71,11 @@ public class GraphicsUtil { /** * 绘制字符串,使用随机颜色,默认抗锯齿 - * - * @param g {@link Graphics}画笔 - * @param str 字符串 - * @param font 字体 - * @param width 字符串总宽度 + * + * @param g {@link Graphics}画笔 + * @param str 字符串 + * @param font 字体 + * @param width 字符串总宽度 * @param height 字符串背景高度 * @return 画笔对象 * @since 4.5.10 @@ -79,12 +86,12 @@ public class GraphicsUtil { /** * 绘制字符串,默认抗锯齿 - * - * @param g {@link Graphics}画笔 - * @param str 字符串 - * @param font 字体 - * @param color 字体颜色,{@code null} 表示使用随机颜色(每个字符单独随机) - * @param width 字符串背景的宽度 + * + * @param g {@link Graphics}画笔 + * @param str 字符串 + * @param font 字体 + * @param color 字体颜色,{@code null} 表示使用随机颜色(每个字符单独随机) + * @param width 字符串背景的宽度 * @param height 字符串背景的高度 * @return 画笔对象 * @since 4.5.10 @@ -98,7 +105,7 @@ public class GraphicsUtil { g.setFont(font); // 文字高度(必须在设置字体后调用) - int midY = GraphicsUtil.getCenterY(g, height); + int midY = getCenterY(g, height); if (null != color) { g.setColor(color); } @@ -115,4 +122,97 @@ public class GraphicsUtil { return g; } + /** + * 绘制字符串,默认抗锯齿。
    + * 此方法定义一个矩形区域和坐标,文字基于这个区域中间偏移x,y绘制。 + * + * @param g {@link Graphics}画笔 + * @param str 字符串 + * @param font 字体,字体大小决定了在背景中绘制的大小 + * @param color 字体颜色,{@code null} 表示使用黑色 + * @param rectangle 字符串绘制坐标和大小,此对象定义了绘制字符串的区域大小和偏移位置 + * @return 画笔对象 + * @since 4.5.10 + */ + public static Graphics drawString(Graphics g, String str, Font font, Color color, Rectangle rectangle) { + // 背景长宽 + final int backgroundWidth = rectangle.width; + final int backgroundHeight = rectangle.height; + + //获取字符串本身的长宽 + Dimension dimension; + try { + dimension = FontUtil.getDimension(g.getFontMetrics(font), str); + } catch (Exception e) { + // 此处报告bug某些情况下会抛出IndexOutOfBoundsException,在此做容错处理 + dimension = new Dimension(backgroundWidth / 3, backgroundHeight / 3); + } + + rectangle.setSize(dimension.width, dimension.height); + final Point point = ImgUtil.getPointBaseCentre(rectangle, backgroundWidth, backgroundHeight); + + return drawString(g, str, font, color, point); + } + + /** + * 绘制字符串,默认抗锯齿 + * + * @param g {@link Graphics}画笔 + * @param str 字符串 + * @param font 字体,字体大小决定了在背景中绘制的大小 + * @param color 字体颜色,{@code null} 表示使用黑色 + * @param point 绘制字符串的位置坐标 + * @return 画笔对象 + * @since 5.3.6 + */ + public static Graphics drawString(Graphics g, String str, Font font, Color color, Point point) { + // 抗锯齿 + if (g instanceof Graphics2D) { + ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } + + g.setFont(font); + g.setColor(ObjectUtil.defaultIfNull(color, Color.BLACK)); + g.drawString(str, point.x, point.y); + + return g; + } + + /** + * 绘制图片 + * + * @param g 画笔 + * @param img 要绘制的图片 + * @param point 绘制的位置,基于左上角 + * @return 画笔对象 + */ + public static Graphics drawImg(Graphics g, Image img, Point point) { + return drawImg(g, img, + new Rectangle(point.x, point.y, img.getWidth(null), img.getHeight(null))); + } + + /** + * 绘制图片 + * + * @param g 画笔 + * @param img 要绘制的图片 + * @param rectangle 矩形对象,表示矩形区域的x,y,width,height,,基于左上角 + * @return 画笔对象 + */ + public static Graphics drawImg(Graphics g, Image img, Rectangle rectangle) { + g.drawImage(img, rectangle.x, rectangle.y, rectangle.width, rectangle.height, null); // 绘制切割后的图 + return g; + } + + /** + * 设置画笔透明度 + * + * @param g 画笔 + * @param alpha 透明度:alpha 必须是范围 [0.0, 1.0] 之内(包含边界值)的一个浮点数字 + * @return 画笔 + */ + public static Graphics2D setAlpha(Graphics2D g, float alpha){ + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha)); + return g; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/img/Img.java b/hutool-core/src/main/java/cn/hutool/core/img/Img.java index 0f2ae2911..74ae47517 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/Img.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/Img.java @@ -15,9 +15,9 @@ import javax.imageio.stream.ImageOutputStream; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Font; -import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Image; +import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Toolkit; @@ -145,13 +145,13 @@ public class Img implements Serializable { /** * 构造 * - * @param srcImage 来源图片 + * @param srcImage 来源图片 * @param targetImageType 目标图片类型 * @since 5.0.7 */ public Img(BufferedImage srcImage, String targetImageType) { this.srcImage = srcImage; - if(null == targetImageType){ + if (null == targetImageType) { targetImageType = ImgUtil.IMAGE_TYPE_JPG; } this.targetImageType = targetImageType; @@ -312,7 +312,7 @@ public class Img implements Serializable { Graphics2D g = image.createGraphics(); // 设置背景 - if(null != fixedColor){ + if (null != fixedColor) { g.setBackground(fixedColor); g.clearRect(0, 0, width, height); } @@ -450,20 +450,22 @@ public class Img implements Serializable { if (null == font) { // 默认字体 - font = new Font("Courier", Font.PLAIN, (int) (targetImage.getHeight() * 0.75)); + font = FontUtil.createSansSerifFont((int) (targetImage.getHeight() * 0.75)); } - - // 抗锯齿 - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g.setColor(color); - g.setFont(font); // 透明度 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha)); - // 在指定坐标绘制水印文字 - final FontMetrics metrics = g.getFontMetrics(font); - final int textLength = metrics.stringWidth(pressText); - final int textHeight = metrics.getAscent() - metrics.getLeading() - metrics.getDescent(); - g.drawString(pressText, Math.abs(targetImage.getWidth() - textLength) / 2 + x, Math.abs(targetImage.getHeight() + textHeight) / 2 + y); + + // 绘制 + if (positionBaseCentre) { + // 基于中心绘制 + GraphicsUtil.drawString(g, pressText, font, color, + new Rectangle(x, y, targetImage.getWidth(), targetImage.getHeight())); + } else { + // 基于左上角绘制 + GraphicsUtil.drawString(g, pressText, font, color, + new Point(x, y)); + } + g.dispose(); this.targetImage = targetImage; @@ -497,7 +499,6 @@ public class Img implements Serializable { public Img pressImage(Image pressImg, Rectangle rectangle, float alpha) { final Image targetImg = getValidSrcImg(); - fixRectangle(rectangle, targetImg.getWidth(null), targetImg.getHeight(null)); this.targetImage = draw(ImgUtil.toBufferedImage(targetImg), pressImg, rectangle, alpha); return this; } @@ -619,12 +620,21 @@ public class Img implements Serializable { * @param backgroundImg 背景图片 * @param img 要绘制的图片 * @param rectangle 矩形对象,表示矩形区域的x,y,width,height,x,y从背景图片中心计算 + * @param alpha 透明度:alpha 必须是范围 [0.0, 1.0] 之内(包含边界值)的一个浮点数字 * @return 绘制后的背景 */ - private static BufferedImage draw(BufferedImage backgroundImg, Image img, Rectangle rectangle, float alpha) { + private BufferedImage draw(BufferedImage backgroundImg, Image img, Rectangle rectangle, float alpha) { final Graphics2D g = backgroundImg.createGraphics(); - g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha)); - g.drawImage(img, rectangle.x, rectangle.y, rectangle.width, rectangle.height, null); // 绘制切割后的图 + GraphicsUtil.setAlpha(g, alpha); + + Point point; + if (positionBaseCentre) { + point = ImgUtil.getPointBaseCentre(rectangle, backgroundImg.getWidth(), backgroundImg.getHeight()); + } else { + point = new Point(rectangle.x, rectangle.y); + } + GraphicsUtil.drawImg(g, img, point); + g.dispose(); return backgroundImg; } diff --git a/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java b/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java index 74d0faaa1..ca5c47fbd 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java @@ -2,7 +2,6 @@ package cn.hutool.core.img; import cn.hutool.core.codec.Base64; import cn.hutool.core.convert.Convert; -import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; @@ -25,10 +24,10 @@ import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; import java.awt.Color; import java.awt.Font; -import java.awt.FontFormatException; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; +import java.awt.Point; import java.awt.Rectangle; import java.awt.font.FontRenderContext; import java.awt.geom.AffineTransform; @@ -1377,18 +1376,7 @@ public class ImgUtil { * @since 3.0.9 */ public static Font createFont(File fontFile) { - try { - return Font.createFont(Font.TRUETYPE_FONT, fontFile); - } catch (FontFormatException e) { - // True Type字体无效时使用Type1字体 - try { - return Font.createFont(Font.TYPE1_FONT, fontFile); - } catch (Exception e1) { - throw new UtilException(e); - } - } catch (IOException e) { - throw new IORuntimeException(e); - } + return FontUtil.createFont(fontFile); } /** @@ -1400,18 +1388,7 @@ public class ImgUtil { * @since 3.0.9 */ public static Font createFont(InputStream fontStream) { - try { - return Font.createFont(Font.TRUETYPE_FONT, fontStream); - } catch (FontFormatException e) { - // True Type字体无效时使用Type1字体 - try { - return Font.createFont(Font.TYPE1_FONT, fontStream); - } catch (Exception e1) { - throw new UtilException(e1); - } - } catch (IOException e) { - throw new IORuntimeException(e); - } + return FontUtil.createFont(fontStream); } /** @@ -1917,4 +1894,20 @@ public class ImgUtil { } return new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)); } + + /** + * 获得修正后的矩形坐标位置,变为以背景中心为基准坐标(即x,y == 0,0时,处于背景正中) + * + * @param rectangle 矩形 + * @param backgroundWidth 参考宽(背景宽) + * @param backgroundHeight 参考高(背景高) + * @return 修正后的{@link Point} + * @since 5.3.6 + */ + public static Point getPointBaseCentre(Rectangle rectangle, int backgroundWidth, int backgroundHeight) { + return new Point( + rectangle.x + (Math.abs(backgroundWidth - rectangle.width) / 2), // + rectangle.y + (Math.abs(backgroundHeight - rectangle.height) / 2)// + ); + } } From ac314bb14a3a2d9256c1486213ed707fc44bc82b Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 26 May 2020 00:28:07 +0800 Subject: [PATCH 148/157] add test --- .../java/cn/hutool/core/img/FontUtilTest.java | 15 +++++++++++++++ .../test/java/cn/hutool/core/img/ImgTest.java | 16 ++++++++++++++++ .../java/cn/hutool/core/img/ImgUtilTest.java | 8 ++++---- 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 hutool-core/src/test/java/cn/hutool/core/img/FontUtilTest.java diff --git a/hutool-core/src/test/java/cn/hutool/core/img/FontUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/img/FontUtilTest.java new file mode 100644 index 000000000..c9f1cad3e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/img/FontUtilTest.java @@ -0,0 +1,15 @@ +package cn.hutool.core.img; + +import org.junit.Assert; +import org.junit.Test; + +import java.awt.Font; + +public class FontUtilTest { + + @Test + public void createFontTest(){ + final Font font = FontUtil.createFont(); + Assert.assertNotNull(font); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/img/ImgTest.java b/hutool-core/src/test/java/cn/hutool/core/img/ImgTest.java index e35cc04ac..8548e7c4b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/img/ImgTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/img/ImgTest.java @@ -4,6 +4,9 @@ import cn.hutool.core.io.FileUtil; import org.junit.Ignore; import org.junit.Test; +import java.awt.Color; +import java.awt.Font; + public class ImgTest { @Test @@ -23,4 +26,17 @@ public class ImgTest { public void roundTest() { Img.from(FileUtil.file("e:/pic/face.jpg")).round(0.5).write(FileUtil.file("e:/pic/face_round.png")); } + + @Test + @Ignore + public void pressTextTest() { + Img.from(FileUtil.file("d:/test/617180969474805871.jpg")) + .setPositionBaseCentre(false) + .pressText("版权所有", Color.RED, // + new Font("黑体", Font.BOLD, 100), // + 0, // + 100, // + 1f) + .write(FileUtil.file("d:/test/test2_result.png")); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java index f54308740..f1702ac31 100644 --- a/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java @@ -70,13 +70,13 @@ public class ImgUtilTest { @Ignore public void pressTextTest() { ImgUtil.pressText(// - FileUtil.file("e:/pic/face.jpg"), // - FileUtil.file("e:/pic/test2_result.png"), // - "版权所有", Color.WHITE, // + FileUtil.file("d:/test/617180969474805871.jpg"), // + FileUtil.file("d:/test/test2_result.png"), // + "版权所有", Color.RED, // new Font("黑体", Font.BOLD, 100), // 0, // 0, // - 0.8f); + 1f); } @Test From 0543cec440806fbf38ffe4fb4e4f99c3994e018b Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 26 May 2020 00:53:59 +0800 Subject: [PATCH 149/157] add header support --- CHANGELOG.md | 1 + .../main/java/cn/hutool/http/HttpBase.java | 2 +- .../cn/hutool/http/webservice/SoapClient.java | 58 ++++++++++++++----- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 322aa28bd..7a5013547 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ * 【core 】 支持BeanUtil.toBean(object, Map.class)转换(issue#I1I4HC@Gitee) * 【core 】 MapUtil和CollUtil增加clear方法(issue#I1I4HC@Gitee) * 【core 】 增加FontUtil,可定义pressText是否从中间(issue#I1HSWU@Gitee) +* 【core 】 SoapClient支持自定义请求头(issue#I1I0AO@Gitee) ### Bug修复 * 【core 】 修复SimpleCache死锁问题(issue#I1HOKB@Gitee) diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java index 083441d31..7e9aa63ca 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpBase.java @@ -271,7 +271,7 @@ public abstract class HttpBase { */ public T charset(String charset) { if(StrUtil.isNotBlank(charset)){ - this.charset = Charset.forName(charset); + charset(Charset.forName(charset)); } return (T) this; } diff --git a/hutool-http/src/main/java/cn/hutool/http/webservice/SoapClient.java b/hutool-http/src/main/java/cn/hutool/http/webservice/SoapClient.java index 6c5794f49..d2dfc74b6 100644 --- a/hutool-http/src/main/java/cn/hutool/http/webservice/SoapClient.java +++ b/hutool-http/src/main/java/cn/hutool/http/webservice/SoapClient.java @@ -3,10 +3,10 @@ package cn.hutool.http.webservice; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.XmlUtil; +import cn.hutool.http.HttpBase; import cn.hutool.http.HttpGlobalConfig; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; @@ -51,7 +51,7 @@ import java.util.Map.Entry; * @author looly * @since 4.5.4 */ -public class SoapClient { +public class SoapClient extends HttpBase { /** * XML消息体的Content-Type @@ -62,10 +62,7 @@ public class SoapClient { * 请求的URL地址 */ private String url; - /** - * 编码 - */ - private Charset charset = CharsetUtil.CHARSET_UTF_8; + /** * 默认连接超时 */ @@ -203,11 +200,17 @@ public class SoapClient { * * @param charset 编码 * @return this + * @see #charset(Charset) */ public SoapClient setCharset(Charset charset) { - this.charset = charset; + return this.charset(charset); + } + + @Override + public SoapClient charset(Charset charset) { + super.charset(charset); try { - this.message.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, this.charset.toString()); + this.message.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, this.charset()); this.message.setProperty(SOAPMessage.WRITE_XML_DECLARATION, "true"); } catch (SOAPException e) { // ignore @@ -228,17 +231,45 @@ public class SoapClient { } /** - * 设置头信息 + * 设置SOAP头信息 + * + * @param name 头信息标签名 + * @return this + * @deprecated 为了和Http Hrader区分,请使用{@link #setSOAPHeader(QName)} + */ + @Deprecated + public SoapClient setHeader(QName name) { + return setSOAPHeader(name, null, null, null, null); + } + + /** + * 设置SOAP头信息 * * @param name 头信息标签名 * @return this */ - public SoapClient setHeader(QName name) { - return setHeader(name, null, null, null, null); + public SoapClient setSOAPHeader(QName name) { + return setSOAPHeader(name, null, null, null, null); } /** - * 设置头信息 + * 设置SOAP头信息 + * + * @param name 头信息标签名 + * @param actorURI 中间的消息接收者 + * @param roleUri Role的URI + * @param mustUnderstand 标题项对于要对其进行处理的接收者来说是强制的还是可选的 + * @param relay relay属性 + * @return this + * @deprecated 为了和Http Hrader区分,请使用{@link #setSOAPHeader(QName, String, String, Boolean, Boolean)} + */ + @Deprecated + public SoapClient setHeader(QName name, String actorURI, String roleUri, Boolean mustUnderstand, Boolean relay) { + return setSOAPHeader(name, actorURI, roleUri, mustUnderstand, relay); + } + + /** + * 设置SOAP头信息 * * @param name 头信息标签名 * @param actorURI 中间的消息接收者 @@ -247,7 +278,7 @@ public class SoapClient { * @param relay relay属性 * @return this */ - public SoapClient setHeader(QName name, String actorURI, String roleUri, Boolean mustUnderstand, Boolean relay) { + public SoapClient setSOAPHeader(QName name, String actorURI, String roleUri, Boolean mustUnderstand, Boolean relay) { SOAPHeader header; SOAPHeaderElement ele; try { @@ -548,6 +579,7 @@ public class SoapClient { .setConnectionTimeout(this.connectionTimeout) .setReadTimeout(this.readTimeout) .contentType(getXmlContentType())// + .header(this.headers()) .body(getMsgStr(false))// .executeAsync(); } From 40f97b386490e4c6e84b878a0380a5ac9d796d54 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 26 May 2020 01:09:26 +0800 Subject: [PATCH 150/157] add methods --- CHANGELOG.md | 3 +- .../java/cn/hutool/script/ScriptUtil.java | 33 ++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a5013547..81759acf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,8 @@ * 【core 】 支持BeanUtil.toBean(object, Map.class)转换(issue#I1I4HC@Gitee) * 【core 】 MapUtil和CollUtil增加clear方法(issue#I1I4HC@Gitee) * 【core 】 增加FontUtil,可定义pressText是否从中间(issue#I1HSWU@Gitee) -* 【core 】 SoapClient支持自定义请求头(issue#I1I0AO@Gitee) +* 【http 】 SoapClient支持自定义请求头(issue#I1I0AO@Gitee) +* 【script 】 ScriptUtil增加evalInvocable和invoke方法(issue#I1HHCP@Gitee) ### Bug修复 * 【core 】 修复SimpleCache死锁问题(issue#I1HOKB@Gitee) diff --git a/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java b/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java index acf952944..7c3061dc1 100644 --- a/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java +++ b/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java @@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil; import javax.script.Bindings; import javax.script.Compilable; import javax.script.CompiledScript; +import javax.script.Invocable; import javax.script.ScriptContext; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; @@ -28,7 +29,7 @@ public class ScriptUtil { * @return {@link ScriptEngine} 实例 */ public static ScriptEngine getScript(String nameOrExtOrMime) { - return CACHE.get(nameOrExtOrMime, ()-> createScript(nameOrExtOrMime)); + return CACHE.get(nameOrExtOrMime, () -> createScript(nameOrExtOrMime)); } /** @@ -149,6 +150,18 @@ public class ScriptUtil { return createScript("groovy"); } + /** + * 执行Javascript脚本,返回Invocable + * + * @param script 脚本内容 + * @return 执行结果 + * @throws ScriptRuntimeException 脚本异常 + * @since 5.3.6 + */ + public static Invocable evalInvocable(String script) throws ScriptRuntimeException { + return (Invocable) eval(script); + } + /** * 执行Javascript脚本 * @@ -199,6 +212,24 @@ public class ScriptUtil { } } + /** + * 执行JS脚本中的指定方法 + * + * @param script js脚本 + * @param func 方法名 + * @param args 方法参数 + * @return 结果 + * @since 5.3.6 + */ + public static Object invoke(String script, String func, Object... args) { + final Invocable eval = evalInvocable(script); + try { + return eval.invokeFunction(func, args); + } catch (ScriptException | NoSuchMethodException e) { + throw new ScriptRuntimeException(e); + } + } + /** * 编译Javascript脚本 * From e9400766a9d063db66deabe2cae41dbdae68ac26 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 26 May 2020 01:39:00 +0800 Subject: [PATCH 151/157] pr#124 --- CHANGELOG.md | 2 + .../cn/hutool/core/img/BackgroundRemoval.java | 354 ++++++++++++++++++ .../main/java/cn/hutool/core/img/ImgUtil.java | 117 +++++- .../java/cn/hutool/core/img/ImgUtilTest.java | 23 ++ hutool-system/pom.xml | 2 +- .../java/cn/hutool/system/oshi/CpuInfo.java | 131 +++++++ .../java/cn/hutool/system/oshi/OshiUtil.java | 68 +++- .../test/java/cn/hutool/system/OshiTest.java | 10 +- 8 files changed, 694 insertions(+), 13 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/img/BackgroundRemoval.java create mode 100644 hutool-system/src/main/java/cn/hutool/system/oshi/CpuInfo.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 81759acf7..2fe9acd26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ * 【core 】 增加FontUtil,可定义pressText是否从中间(issue#I1HSWU@Gitee) * 【http 】 SoapClient支持自定义请求头(issue#I1I0AO@Gitee) * 【script 】 ScriptUtil增加evalInvocable和invoke方法(issue#I1HHCP@Gitee) +* 【core 】 ImgUtil增加去除背景色的方法(pr#124@Gitee) +* 【system 】 OshiUtil增加获取CPU使用率的方法(pr#124@Gitee) ### Bug修复 * 【core 】 修复SimpleCache死锁问题(issue#I1HOKB@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/img/BackgroundRemoval.java b/hutool-core/src/main/java/cn/hutool/core/img/BackgroundRemoval.java new file mode 100644 index 000000000..0988d32f0 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/img/BackgroundRemoval.java @@ -0,0 +1,354 @@ +package cn.hutool.core.img; + +import cn.hutool.core.io.FileTypeUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; + +import javax.imageio.ImageIO; +import javax.swing.ImageIcon; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + *

    图片背景识别处理、背景替换、背景设置为矢量图

    + *

    根据一定规则算出图片背景色的RGB值,进行替换

    + *

    2020-05-21 16:36

    + * + * @author Dai Yuanchuan + **/ +public class BackgroundRemoval { + + /** + * 目前暂时支持的图片类型数组 + * 其他格式的不保证结果 + */ + public static String[] IMAGES_TYPE = {"jpg", "png"}; + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param inputPath 要处理图片的路径 + * @param outputPath 输出图片的路径 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的范围在0~255之间] + * @return 返回处理结果 true:图片处理完成 false:图片处理失败 + */ + public static boolean backgroundRemoval(String inputPath, String outputPath, int tolerance) { + return backgroundRemoval(new File(inputPath), new File(outputPath), tolerance); + } + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param input 需要进行操作的图片 + * @param output 最后输出的文件 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间] + * @return 返回处理结果 true:图片处理完成 false:图片处理失败 + */ + public static boolean backgroundRemoval(File input, File output, int tolerance) { + return backgroundRemoval(input, output, null, tolerance); + } + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param input 需要进行操作的图片 + * @param output 最后输出的文件 + * @param override 指定替换成的背景颜色 为null时背景为透明 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间] + * @return 返回处理结果 true:图片处理完成 false:图片处理失败 + */ + public static boolean backgroundRemoval(File input, File output, Color override, int tolerance) { + if (fileTypeValidation(input, IMAGES_TYPE)) { + return false; + } + try { + // 获取图片左上、中上、右上、右中、右下、下中、左下、左中、8个像素点rgb的16进制值 + BufferedImage bufferedImage = ImageIO.read(input); + // 图片输出的格式为 png + return ImageIO.write(backgroundRemoval(bufferedImage, override, tolerance), "png", output); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param bufferedImage 需要进行处理的图片流 + * @param override 指定替换成的背景颜色 为null时背景为透明 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间] + * @return 返回处理好的图片流 + */ + public static BufferedImage backgroundRemoval(BufferedImage bufferedImage, Color override, int tolerance) { + // 容差值 最大255 最小0 + tolerance = Math.min(255, Math.max(tolerance, 0)); + // 绘制icon + ImageIcon imageIcon = new ImageIcon(bufferedImage); + BufferedImage image = new BufferedImage(imageIcon.getIconWidth(), imageIcon.getIconHeight(), + BufferedImage.TYPE_4BYTE_ABGR); + // 绘图工具 + Graphics graphics = image.getGraphics(); + graphics.drawImage(imageIcon.getImage(), 0, 0, imageIcon.getImageObserver()); + // 需要删除的RGB元素 + String[] removeRgb = getRemoveRgb(bufferedImage); + // 获取图片的大概主色调 + String mainColor = getMainColor(bufferedImage); + int alpha = 0; + for (int y = image.getMinY(); y < image.getHeight(); y++) { + for (int x = image.getMinX(); x < image.getWidth(); x++) { + // 获取像素的16进制 + int rgb = image.getRGB(x, y); + String hex = ImgUtil.toHex((rgb & 0xff0000) >> 16, (rgb & 0xff00) >> 8, (rgb & 0xff)); + boolean isTrue = ArrayUtil.contains(removeRgb, hex) || + areColorsWithinTolerance(hexToRgb(mainColor), new Color(Integer.parseInt(hex.substring(1), 16)), tolerance); + if (isTrue) { + rgb = override == null ? ((alpha + 1) << 24) | (rgb & 0x00ffffff) : override.getRGB(); + } + image.setRGB(x, y, rgb); + } + } + graphics.drawImage(image, 0, 0, imageIcon.getImageObserver()); + return image; + } + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param outputStream 需要进行处理的图片字节数组流 + * @param override 指定替换成的背景颜色 为null时背景为透明 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间] + * @return 返回处理好的图片流 + */ + public static BufferedImage backgroundRemoval(ByteArrayOutputStream outputStream, Color override, int tolerance) { + try { + return backgroundRemoval(ImageIO.read(new ByteArrayInputStream(outputStream.toByteArray())), override, tolerance); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * 获取要删除的 RGB 元素 + * 分别获取图片左上、中上、右上、右中、右下、下中、左下、左中、8个像素点rgb的16进制值 + * + * @param image 图片流 + * @return String数组 包含 各个位置的rgb数值 + */ + private static String[] getRemoveRgb(BufferedImage image) { + // 获取图片流的宽和高 + int width = image.getWidth() - 1; + int height = image.getHeight() - 1; + // 左上 + int leftUpPixel = image.getRGB(1, 1); + String leftUp = ImgUtil.toHex((leftUpPixel & 0xff0000) >> 16, (leftUpPixel & 0xff00) >> 8, (leftUpPixel & 0xff)); + // 上中 + int upMiddlePixel = image.getRGB(width / 2, 1); + String upMiddle = ImgUtil.toHex((upMiddlePixel & 0xff0000) >> 16, (upMiddlePixel & 0xff00) >> 8, (upMiddlePixel & 0xff)); + // 右上 + int rightUpPixel = image.getRGB(width, 1); + String rightUp = ImgUtil.toHex((rightUpPixel & 0xff0000) >> 16, (rightUpPixel & 0xff00) >> 8, (rightUpPixel & 0xff)); + // 右中 + int rightMiddlePixel = image.getRGB(width, height / 2); + String rightMiddle = ImgUtil.toHex((rightMiddlePixel & 0xff0000) >> 16, (rightMiddlePixel & 0xff00) >> 8, (rightMiddlePixel & 0xff)); + // 右下 + int lowerRightPixel = image.getRGB(width, height); + String lowerRight = ImgUtil.toHex((lowerRightPixel & 0xff0000) >> 16, (lowerRightPixel & 0xff00) >> 8, (lowerRightPixel & 0xff)); + // 下中 + int lowerMiddlePixel = image.getRGB(width / 2, height); + String lowerMiddle = ImgUtil.toHex((lowerMiddlePixel & 0xff0000) >> 16, (lowerMiddlePixel & 0xff00) >> 8, (lowerMiddlePixel & 0xff)); + // 左下 + int leftLowerPixel = image.getRGB(1, height); + String leftLower = ImgUtil.toHex((leftLowerPixel & 0xff0000) >> 16, (leftLowerPixel & 0xff00) >> 8, (leftLowerPixel & 0xff)); + // 左中 + int leftMiddlePixel = image.getRGB(1, height / 2); + String leftMiddle = ImgUtil.toHex((leftMiddlePixel & 0xff0000) >> 16, (leftMiddlePixel & 0xff00) >> 8, (leftMiddlePixel & 0xff)); + // 需要删除的RGB元素 + return new String[]{leftUp, upMiddle, rightUp, rightMiddle, lowerRight, lowerMiddle, leftLower, leftMiddle}; + } + + /** + * 十六进制颜色码转RGB颜色值 + * + * @param hex 十六进制颜色码 + * @return 返回 RGB颜色值 + */ + public static Color hexToRgb(String hex) { + return new Color(Integer.parseInt(hex.substring(1), 16)); + } + + + /** + * 判断颜色是否在容差范围内 + * 对比两个颜色的相似度,判断这个相似度是否小于 tolerance 容差值 + * + * @param color1 颜色1 + * @param color2 颜色2 + * @param tolerance 容差值 + * @return 返回true:两个颜色在容差值之内 false: 不在 + */ + public static boolean areColorsWithinTolerance(Color color1, Color color2, int tolerance) { + return areColorsWithinTolerance(color1, color2, new Color(tolerance, tolerance, tolerance)); + } + + /** + * 判断颜色是否在容差范围内 + * 对比两个颜色的相似度,判断这个相似度是否小于 tolerance 容差值 + * + * @param color1 颜色1 + * @param color2 颜色2 + * @param tolerance 容差色值 + * @return 返回true:两个颜色在容差值之内 false: 不在 + */ + public static boolean areColorsWithinTolerance(Color color1, Color color2, Color tolerance) { + return (color1.getRed() - color2.getRed() < tolerance.getRed() && color1 + .getRed() - color2.getRed() > -tolerance.getRed()) + && (color1.getBlue() - color2.getBlue() < tolerance + .getBlue() && color1.getBlue() - color2.getBlue() > -tolerance + .getBlue()) + && (color1.getGreen() - color2.getGreen() < tolerance + .getGreen() && color1.getGreen() + - color2.getGreen() > -tolerance.getGreen()); + } + + /** + * 获取图片大概的主题色 + * 循环所有的像素点,取出出现次数最多的一个像素点的RGB值 + * + * @param input 图片文件路径 + * @return 返回一个图片的大概的色值 一个16进制的颜色码 + */ + public static String getMainColor(String input) { + return getMainColor(new File(input)); + } + + /** + * 获取图片大概的主题色 + * 循环所有的像素点,取出出现次数最多的一个像素点的RGB值 + * + * @param input 图片文件 + * @return 返回一个图片的大概的色值 一个16进制的颜色码 + */ + public static String getMainColor(File input) { + try { + return getMainColor(ImageIO.read(input)); + } catch (IOException e) { + e.printStackTrace(); + } + return ""; + } + + /** + * 获取图片大概的主题色 + * 循环所有的像素点,取出出现次数最多的一个像素点的RGB值 + * + * @param bufferedImage 图片流 + * @return 返回一个图片的大概的色值 一个16进制的颜色码 + */ + public static String getMainColor(BufferedImage bufferedImage) { + if (bufferedImage == null) { + throw new IllegalArgumentException("图片流是空的"); + } + + // 存储图片的所有RGB元素 + List list = new ArrayList<>(); + for (int y = bufferedImage.getMinY(); y < bufferedImage.getHeight(); y++) { + for (int x = bufferedImage.getMinX(); x < bufferedImage.getWidth(); x++) { + int pixel = bufferedImage.getRGB(x, y); + list.add(((pixel & 0xff0000) >> 16) + "-" + ((pixel & 0xff00) >> 8) + "-" + (pixel & 0xff)); + } + } + + Map map = new HashMap<>(list.size()); + for (String string : list) { + Integer integer = map.get(string); + if (integer == null) { + integer = 1; + } else { + integer++; + } + map.put(string, integer); + } + String max = ""; + long num = 0; + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + Integer temp = entry.getValue(); + if (StrUtil.isBlank(max) || temp > num) { + max = key; + num = temp; + } + } + String[] strings = max.split("-"); + // rgb 的数量只有3个 + int rgbLength = 3; + if (strings.length == rgbLength) { + return ImgUtil.toHex(Integer.parseInt(strings[0]), Integer.parseInt(strings[1]), + Integer.parseInt(strings[2])); + } + return ""; + } + + // -------------------------------------------------------------------------- private + + /** + * 文件类型验证 + * 根据给定文件类型数据,验证给定文件类型. + * + * @param input 需要进行验证的文件 + * @param imagesType 文件包含的类型数组 + * @return 返回布尔值 false:给定文件的文件类型在文件数组中 true:给定文件的文件类型 不在给定数组中。 + */ + private static boolean fileTypeValidation(File input, String[] imagesType) { + if (!input.exists()) { + throw new IllegalArgumentException("给定文件为空"); + } + // 获取图片类型 + String type = FileTypeUtil.getType(input); + // 类型对比 + if (!ArrayUtil.contains(imagesType, type)) { + throw new IllegalArgumentException(StrUtil.format("文件类型{}不支持", type)); + } + return false; + } +} \ No newline at end of file diff --git a/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java b/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java index ca5c47fbd..8caa47738 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java @@ -1765,13 +1765,23 @@ public class ImgUtil { * @since 4.1.14 */ public static String toHex(Color color) { - String R = Integer.toHexString(color.getRed()); - R = R.length() < 2 ? ('0' + R) : R; - String G = Integer.toHexString(color.getGreen()); - G = G.length() < 2 ? ('0' + G) : G; - String B = Integer.toHexString(color.getBlue()); - B = B.length() < 2 ? ('0' + B) : B; - return '#' + R + G + B; + return toHex(color.getRed(), color.getGreen(), color.getBlue()); + } + + /** + * RGB颜色值转换成十六进制颜色码 + * + * @param r 红(R) + * @param g 绿(G) + * @param b 蓝(B) + * @return 返回字符串形式的 十六进制颜色码 如 + */ + public static String toHex(int r, int g, int b) { + // rgb 小于 255 + if(r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255){ + throw new IllegalArgumentException("RGB must be 0~255!"); + } + return String.format("#%02X%02X%02X", r, g, b); } /** @@ -1910,4 +1920,97 @@ public class ImgUtil { rectangle.y + (Math.abs(backgroundHeight - rectangle.height) / 2)// ); } + + // ------------------------------------------------------------------------------------------------------ 背景图换算 + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param inputPath 要处理图片的路径 + * @param outputPath 输出图片的路径 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的范围在0~255之间] + * @return 返回处理结果 true:图片处理完成 false:图片处理失败 + */ + public static boolean backgroundRemoval(String inputPath, String outputPath, int tolerance) { + return BackgroundRemoval.backgroundRemoval(inputPath, outputPath, tolerance); + } + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param input 需要进行操作的图片 + * @param output 最后输出的文件 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间] + * @return 返回处理结果 true:图片处理完成 false:图片处理失败 + */ + public static boolean backgroundRemoval(File input, File output, int tolerance) { + return BackgroundRemoval.backgroundRemoval(input, output, tolerance); + } + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param input 需要进行操作的图片 + * @param output 最后输出的文件 + * @param override 指定替换成的背景颜色 为null时背景为透明 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间] + * @return 返回处理结果 true:图片处理完成 false:图片处理失败 + */ + public static boolean backgroundRemoval(File input, File output, Color override, int tolerance) { + return BackgroundRemoval.backgroundRemoval(input, output, override, tolerance); + } + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param bufferedImage 需要进行处理的图片流 + * @param override 指定替换成的背景颜色 为null时背景为透明 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间] + * @return 返回处理好的图片流 + */ + public static BufferedImage backgroundRemoval(BufferedImage bufferedImage, Color override, int tolerance) { + return BackgroundRemoval.backgroundRemoval(bufferedImage, override, tolerance); + } + + /** + * 背景移除 + * 图片去底工具 + * 将 "纯色背景的图片" 还原成 "透明背景的图片" + * 将纯色背景的图片转成矢量图 + * 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色 + * 再加入一定的容差值,然后将所有像素点与该颜色进行比较 + * 发现相同则将颜色不透明度设置为0,使颜色完全透明. + * + * @param outputStream 需要进行处理的图片字节数组流 + * @param override 指定替换成的背景颜色 为null时背景为透明 + * @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间] + * @return 返回处理好的图片流 + */ + public static BufferedImage backgroundRemoval(ByteArrayOutputStream outputStream, Color override, int tolerance) { + return BackgroundRemoval.backgroundRemoval(outputStream, override, tolerance); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java index f1702ac31..401010512 100644 --- a/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java @@ -1,6 +1,7 @@ package cn.hutool.core.img; import cn.hutool.core.io.FileUtil; +import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -10,6 +11,7 @@ import java.awt.Font; import java.awt.Image; import java.awt.Rectangle; import java.awt.image.BufferedImage; +import java.io.File; import java.io.IOException; public class ImgUtilTest { @@ -109,4 +111,25 @@ public class ImgUtilTest { BufferedImage image = ImgUtil.copyImage(ImgUtil.read("f:/pic/test.png"), BufferedImage.TYPE_INT_RGB); ImgUtil.write(image, FileUtil.file("f:/pic/test_dest.jpg")); } + + @Test + public void toHexTest(){ + final String s = ImgUtil.toHex(Color.RED); + Assert.assertEquals("#FF0000", s); + } + + @Test + @Ignore + public void backgroundRemovalTest() { + // 图片 背景 换成 透明的 + ImgUtil.backgroundRemoval( + "d:/test/617180969474805871.jpg", + "d:/test/2.jpg", 10); + + // 图片 背景 换成 红色的 + ImgUtil.backgroundRemoval(new File( + "d:/test/617180969474805871.jpg"), + new File("d:/test/3.jpg"), + new Color(200, 0, 0), 10); + } } diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 003558f0a..ef4bb4a1c 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -26,7 +26,7 @@ com.github.oshi oshi-core - 4.5.2 + 5.1.0 provided diff --git a/hutool-system/src/main/java/cn/hutool/system/oshi/CpuInfo.java b/hutool-system/src/main/java/cn/hutool/system/oshi/CpuInfo.java new file mode 100644 index 000000000..f2141ef4c --- /dev/null +++ b/hutool-system/src/main/java/cn/hutool/system/oshi/CpuInfo.java @@ -0,0 +1,131 @@ +package cn.hutool.system.oshi; + +import java.text.DecimalFormat; + +/** + *

    + *

    2020-05-21 14:19

    + * + * @author Dai Yuanchuan + **/ +public class CpuInfo { + + /** + * cpu核心数 + */ + private Integer cpuNum; + + /** + * CPU总的使用率 + */ + private double toTal; + + /** + * CPU系统使用率 + */ + private double sys; + + /** + * CPU用户使用率 + */ + private double used; + + /** + * CPU当前等待率 + */ + private double wait; + + /** + * CPU当前空闲率 + */ + private double free; + + /** + * CPU型号信息 + */ + private String cpuModel; + + public CpuInfo() { + } + + public CpuInfo(Integer cpuNum, double toTal, double sys, double used, double wait, double free, String cpuModel) { + this.cpuNum = cpuNum; + this.toTal = toTal; + this.sys = sys; + this.used = used; + this.wait = wait; + this.free = free; + this.cpuModel = cpuModel; + } + + public Integer getCpuNum() { + return cpuNum; + } + + public void setCpuNum(Integer cpuNum) { + this.cpuNum = cpuNum; + } + + public double getToTal() { + return toTal; + } + + public void setToTal(double toTal) { + this.toTal = toTal; + } + + public double getSys() { + return sys; + } + + public void setSys(double sys) { + this.sys = sys; + } + + public double getUsed() { + return used; + } + + public void setUsed(double used) { + this.used = used; + } + + public double getWait() { + return wait; + } + + public void setWait(double wait) { + this.wait = wait; + } + + public double getFree() { + return free; + } + + public void setFree(double free) { + this.free = free; + } + + public String getCpuModel() { + return cpuModel; + } + + public void setCpuModel(String cpuModel) { + this.cpuModel = cpuModel; + } + + @Override + public String toString() { + DecimalFormat format = new DecimalFormat("#.00"); + return "CpuInfo{" + + "cpu核心数=" + cpuNum + + ", CPU总的使用率=" + toTal + + ", CPU系统使用率=" + sys + + ", CPU用户使用率=" + used + + ", CPU当前等待率=" + wait + + ", CPU当前空闲率=" + free + + ", CPU利用率=" + Double.parseDouble(format.format((100 - getFree()))) + + ", CPU型号信息='" + cpuModel + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/hutool-system/src/main/java/cn/hutool/system/oshi/OshiUtil.java b/hutool-system/src/main/java/cn/hutool/system/oshi/OshiUtil.java index 4aeff7ec3..5497b328e 100644 --- a/hutool-system/src/main/java/cn/hutool/system/oshi/OshiUtil.java +++ b/hutool-system/src/main/java/cn/hutool/system/oshi/OshiUtil.java @@ -9,6 +9,10 @@ import oshi.hardware.HardwareAbstractionLayer; import oshi.hardware.NetworkIF; import oshi.hardware.Sensors; import oshi.software.os.OperatingSystem; +import oshi.util.Util; + +import java.text.DecimalFormat; +import java.util.List; /** * Oshi库封装的工具类,通过此工具类,可获取系统、硬件相关信息 @@ -95,19 +99,77 @@ public class OshiUtil { * 获取磁盘相关信息,可能有多个磁盘(包括可移动磁盘等) * * @return 磁盘相关信息 + * @since 5.3.6 */ - public static HWDiskStore[] getDiskStores() { + public static List getDiskStores() { return hardware.getDiskStores(); } /** * 获取网络相关信息,可能多块网卡 * @return 网络相关信息 - * @since 5.3.5 + * @since 5.3.6 */ - public static NetworkIF[] getNetworkIFs(){ + public static List getNetworkIFs(){ return hardware.getNetworkIFs(); } + // ------------------------------------------------------------------ cpu + /** + * 获取系统CPU 系统使用率、用户使用率、利用率等等 相关信息 + * + * @return 系统 CPU 使用率 等信息 + */ + public static CpuInfo getCpuInfo() { + return getCpuInfo(1000); + } + + /** + * 获取系统CPU 系统使用率、用户使用率、利用率等等 相关信息 + * + * @param waitingTime 设置等待时间 + * @return 系统 CPU 使用率 等信息 + */ + public static CpuInfo getCpuInfo(long waitingTime) { + return getCpuInfo(OshiUtil.getProcessor(), waitingTime); + } + + /** + * 获取系统CPU 系统使用率、用户使用率、利用率等等 相关信息 + * + * @param processor {@link CentralProcessor} + * @param waitingTime 设置等待时间 + * @return 系统 CPU 使用率 等信息 + */ + private static CpuInfo getCpuInfo(CentralProcessor processor, long waitingTime) { + CpuInfo cpuInfo = new CpuInfo(); + // CPU信息 + long[] prevTicks = processor.getSystemCpuLoadTicks(); + // 这里必须要设置延迟 + Util.sleep(waitingTime); + long[] ticks = processor.getSystemCpuLoadTicks(); + long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()]; + long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()]; + long softIrq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()]; + long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()]; + long cSys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()]; + long user = ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()]; + long ioWait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()]; + long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()]; + long totalCpu = Math.max(user + nice + cSys + idle + ioWait + irq + softIrq + steal, 0); + final DecimalFormat format = new DecimalFormat("#.00"); + cpuInfo.setCpuNum(processor.getLogicalProcessorCount()); + cpuInfo.setToTal(totalCpu); + cpuInfo.setSys(Double.parseDouble(format.format(cSys <= 0 ? 0 : (100d * cSys / totalCpu)))); + cpuInfo.setUsed(Double.parseDouble(format.format(user <= 0 ? 0 : (100d * user / totalCpu)))); + if (totalCpu == 0) { + cpuInfo.setWait(0); + } else { + cpuInfo.setWait(Double.parseDouble(format.format(100d * ioWait / totalCpu))); + } + cpuInfo.setFree(Double.parseDouble(format.format(idle <= 0 ? 0 : (100d * idle / totalCpu)))); + cpuInfo.setCpuModel(processor.toString()); + return cpuInfo; + } } diff --git a/hutool-system/src/test/java/cn/hutool/system/OshiTest.java b/hutool-system/src/test/java/cn/hutool/system/OshiTest.java index ebc638156..7bfee6a89 100644 --- a/hutool-system/src/test/java/cn/hutool/system/OshiTest.java +++ b/hutool-system/src/test/java/cn/hutool/system/OshiTest.java @@ -1,10 +1,10 @@ package cn.hutool.system; +import cn.hutool.system.oshi.CpuInfo; +import cn.hutool.system.oshi.OshiUtil; import org.junit.Assert; import org.junit.Test; -import cn.hutool.system.oshi.OshiUtil; - public class OshiTest { @Test @@ -12,4 +12,10 @@ public class OshiTest { long total = OshiUtil.getMemory().getTotal(); Assert.assertTrue(total > 0); } + + @Test + public void getCupInfo() { + CpuInfo cpuInfo = OshiUtil.getCpuInfo(); + Assert.assertNotNull(cpuInfo); + } } From df58ad5eff9d1a896e34348e7720fe7c46e7b690 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 26 May 2020 10:22:59 +0800 Subject: [PATCH 152/157] fix bean --- .../java/cn/hutool/core/bean/BeanUtil.java | 2 +- .../copier/provider/BeanValueProvider.java | 5 ++++- .../core/convert/AbstractConverter.java | 3 ++- .../java/cn/hutool/core/util/URLUtil.java | 20 +++++++++---------- .../cn/hutool/core/bean/BeanUtilTest.java | 1 + 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java index 45e97563d..17608fc02 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java @@ -614,7 +614,7 @@ public class BeanUtil { * @return 目标对象 */ public static T copyProperties(Object source, Class tClass) { - T target = ReflectUtil.newInstance(tClass); + T target = ReflectUtil.newInstanceIfPossible(tClass); copyProperties(source, target, CopyOptions.create()); return target; } diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java index ffc745f5e..38bab46ed 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/provider/BeanValueProvider.java @@ -57,7 +57,10 @@ public class BeanValueProvider implements ValueProvider { } // 尝试将结果转换为目标类型,如果转换失败,返回原类型。 - result = Convert.convertWithCheck(valueType,result, result, ignoreError); + final Object convertValue = Convert.convertWithCheck(valueType,result, null, ignoreError); + if(null != convertValue){ + result = convertValue; + } } } return result; diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/AbstractConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/AbstractConverter.java index 11aa91d66..73aa9435e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/AbstractConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/AbstractConverter.java @@ -58,7 +58,8 @@ public abstract class AbstractConverter implements Converter, Serializable T result = convertInternal(value); return ((null == result) ? defaultValue : result); } else { - throw new IllegalArgumentException(StrUtil.format("Default value [{}] is not the instance of [{}]", defaultValue, targetType)); + throw new IllegalArgumentException( + StrUtil.format("Default value [{}]({}) is not the instance of [{}]", defaultValue, defaultValue.getClass(), targetType)); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 989c89acb..e2ebfb6b5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -783,16 +783,16 @@ public class URLUtil { *

    * Data URI的格式规范: *

    -	 *     data:[][;charset=][;],
    +	 *     data:[<mime type>][;charset=<charset>][;<encoding>],<encoded data>
     	 * 
    * * @param mimeType 可选项(null表示无),数据类型(image/png、text/plain等) * @param encoding 数据编码方式(US-ASCII,BASE64等) - * @param data 编码后的数据 + * @param data 编码后的数据 * @return Data URI字符串 * @since 5.3.6 */ - public static String getDataUri(String mimeType, String encoding, String data){ + public static String getDataUri(String mimeType, String encoding, String data) { return getDataUri(mimeType, null, encoding, data); } @@ -803,25 +803,25 @@ public class URLUtil { *

    * Data URI的格式规范: *

    -	 *     data:[][;charset=][;],
    +	 *     data:[<mime type>][;charset=<charset>][;<encoding>],<encoded data>
     	 * 
    * * @param mimeType 可选项(null表示无),数据类型(image/png、text/plain等) - * @param charset 可选项(null表示无),源文本的字符集编码方式 + * @param charset 可选项(null表示无),源文本的字符集编码方式 * @param encoding 数据编码方式(US-ASCII,BASE64等) - * @param data 编码后的数据 + * @param data 编码后的数据 * @return Data URI字符串 * @since 5.3.6 */ - public static String getDataUri(String mimeType, Charset charset, String encoding, String data){ + public static String getDataUri(String mimeType, Charset charset, String encoding, String data) { final StringBuilder builder = StrUtil.builder("data:"); - if(StrUtil.isNotBlank(mimeType)){ + if (StrUtil.isNotBlank(mimeType)) { builder.append(mimeType); } - if(null != charset){ + if (null != charset) { builder.append(";charset=").append(charset.name()); } - if(StrUtil.isNotBlank(encoding)){ + if (StrUtil.isNotBlank(encoding)) { builder.append(';').append(encoding); } builder.append(',').append(data); diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java index 1ccaa3977..6e458bece 100644 --- a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java @@ -237,6 +237,7 @@ public class BeanUtilTest { person.setOpenid("11213232"); person.setName("测试A11"); person.setSubName("sub名字"); + SubPerson person1 = BeanUtil.copyProperties(person, SubPerson.class); Assert.assertEquals(14, person1.getAge()); Assert.assertEquals("11213232", person1.getOpenid()); From 7c4c6c7af169d01c7e6318e332608936e7d43682 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 26 May 2020 15:58:22 +0800 Subject: [PATCH 153/157] fix bug --- CHANGELOG.md | 1 + .../hutool/core/thread/SemaphoreRunnable.java | 22 ++++++++++++++----- .../cn/hutool/core/thread/ThreadUtilTest.java | 1 - 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fe9acd26..00ff2872a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ ### Bug修复 * 【core 】 修复SimpleCache死锁问题(issue#I1HOKB@Gitee) +* 【core 】 修复SemaphoreRunnable释放问题(issue#I1HLQQ@Gitee) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/SemaphoreRunnable.java b/hutool-core/src/main/java/cn/hutool/core/thread/SemaphoreRunnable.java index 22cac27b0..c81fd06da 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/SemaphoreRunnable.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/SemaphoreRunnable.java @@ -31,16 +31,28 @@ public class SemaphoreRunnable implements Runnable { this.semaphore = semaphore; } + /** + * 获得信号量 + * + * @return {@link Semaphore} + * @since 5.3.6 + */ + public Semaphore getSemaphore(){ + return this.semaphore; + } + @Override public void run() { if (null != this.semaphore) { - try { + try{ semaphore.acquire(); - this.runnable.run(); - } catch (InterruptedException e) { + try { + this.runnable.run(); + } finally { + semaphore.release(); + } + }catch (InterruptedException e) { Thread.currentThread().interrupt(); - } finally { - semaphore.release(); } } } diff --git a/hutool-core/src/test/java/cn/hutool/core/thread/ThreadUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/thread/ThreadUtilTest.java index b5483e2b0..94e898611 100644 --- a/hutool-core/src/test/java/cn/hutool/core/thread/ThreadUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/thread/ThreadUtilTest.java @@ -10,6 +10,5 @@ public class ThreadUtilTest { final boolean isValid = true; ThreadUtil.execute(() -> Assert.assertTrue(isValid)); - } } From 44fe8904a24f0f764b7a1e398117754ca7aabbf1 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 26 May 2020 18:21:42 +0800 Subject: [PATCH 154/157] remove EC --- CHANGELOG.md | 1 + .../main/java/cn/hutool/crypto/KeyUtil.java | 85 ++++++++++++++----- .../asymmetric/AsymmetricAlgorithm.java | 4 +- .../java/cn/hutool/crypto/asymmetric/SM2.java | 5 +- .../cn/hutool/crypto/test/KeyUtilTest.java | 20 +++-- 5 files changed, 85 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00ff2872a..321be47da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ * 【script 】 ScriptUtil增加evalInvocable和invoke方法(issue#I1HHCP@Gitee) * 【core 】 ImgUtil增加去除背景色的方法(pr#124@Gitee) * 【system 】 OshiUtil增加获取CPU使用率的方法(pr#124@Gitee) +* 【crypto 】 AsymmetricAlgorithm去除EC(issue#887@Github) ### Bug修复 * 【core 】 修复SimpleCache死锁问题(issue#I1HOKB@Gitee) diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java index aad2a8a73..67e19b29b 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java @@ -1,7 +1,26 @@ package cn.hutool.crypto; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.asymmetric.AsymmetricAlgorithm; +import cn.hutool.crypto.symmetric.SymmetricAlgorithm; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; +import javax.crypto.spec.DESedeKeySpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; import java.io.File; import java.io.InputStream; +import java.math.BigInteger; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; @@ -17,32 +36,15 @@ import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; +import java.security.interfaces.RSAPrivateCrtKey; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.ECGenParameterSpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.RSAPublicKeySpec; import java.security.spec.X509EncodedKeySpec; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.DESKeySpec; -import javax.crypto.spec.DESedeKeySpec; -import javax.crypto.spec.PBEKeySpec; -import javax.crypto.spec.SecretKeySpec; - -import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.CharUtil; -import cn.hutool.core.util.CharsetUtil; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.crypto.asymmetric.AsymmetricAlgorithm; -import cn.hutool.crypto.symmetric.SymmetricAlgorithm; - /** * 密钥工具类 * @@ -906,4 +908,49 @@ public class KeyUtil { public static PublicKey decodeECPoint(byte[] encodeByte, String curveName) { return BCUtil.decodeECPoint(encodeByte, curveName); } + + /** + * 通过RSA私钥生成RSA公钥 + * + * @param privateKey RSA私钥 + * @return RSA公钥,null表示私钥不被支持 + * @since 5.3.6 + */ + public static PublicKey getRSAPublicKey(PrivateKey privateKey){ + if(privateKey instanceof RSAPrivateCrtKey){ + final RSAPrivateCrtKey privk = (RSAPrivateCrtKey)privateKey; + return getRSAPublicKey(privk.getModulus(), privk.getPublicExponent()); + } + return null; + } + + /** + * 获得RSA公钥对象 + * + * @param modulus Modulus + * @param publicExponent Public Exponent + * @return 公钥 + * @since 5.3.6 + */ + public static PublicKey getRSAPublicKey(String modulus, String publicExponent){ + return getRSAPublicKey( + new BigInteger(modulus, 16), new BigInteger(publicExponent, 16)); + } + + /** + * 获得RSA公钥对象 + * + * @param modulus Modulus + * @param publicExponent Public Exponent + * @return 公钥 + * @since 5.3.6 + */ + public static PublicKey getRSAPublicKey(BigInteger modulus, BigInteger publicExponent){ + final RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(modulus, publicExponent); + try { + return getKeyFactory("RSA").generatePublic(publicKeySpec); + } catch (InvalidKeySpecException e) { + throw new CryptoException(e); + } + } } diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AsymmetricAlgorithm.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AsymmetricAlgorithm.java index 2cadaf9c5..bcaf0585d 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AsymmetricAlgorithm.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/AsymmetricAlgorithm.java @@ -13,9 +13,7 @@ public enum AsymmetricAlgorithm { /** RSA算法,此算法用了默认补位方式为RSA/ECB/PKCS1Padding */ RSA_ECB_PKCS1("RSA/ECB/PKCS1Padding"), /** RSA算法,此算法用了RSA/None/NoPadding */ - RSA_None("RSA/None/NoPadding"), - /** EC(Elliptic Curve)算法 */ - EC("EC"); + RSA_None("RSA/None/NoPadding"); private final String value; diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java index cf91415c4..b33f49614 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/asymmetric/SM2.java @@ -4,6 +4,7 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.util.HexUtil; import cn.hutool.crypto.BCUtil; import cn.hutool.crypto.CryptoException; +import cn.hutool.crypto.KeyUtil; import cn.hutool.crypto.SecureUtil; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.Digest; @@ -78,8 +79,8 @@ public class SM2 extends AbstractAsymmetricCrypto { */ public SM2(byte[] privateKey, byte[] publicKey) { this(// - SecureUtil.generatePrivateKey(ALGORITHM_SM2, privateKey), // - SecureUtil.generatePublicKey(ALGORITHM_SM2, publicKey)// + KeyUtil.generatePrivateKey(ALGORITHM_SM2, privateKey), // + KeyUtil.generatePublicKey(ALGORITHM_SM2, publicKey)// ); } diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java index 5ca62af65..b038fd67d 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/KeyUtilTest.java @@ -1,14 +1,15 @@ package cn.hutool.crypto.test; -import java.security.KeyPair; - -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.crypto.CryptoException; import cn.hutool.crypto.GlobalBouncyCastleProvider; import cn.hutool.crypto.KeyUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; public class KeyUtilTest { @@ -23,4 +24,11 @@ public class KeyUtilTest { Assert.assertNotNull(pair); } + @Test + public void getRSAPublicKeyTest(){ + final KeyPair keyPair = KeyUtil.generateKeyPair("RSA"); + final PrivateKey aPrivate = keyPair.getPrivate(); + final PublicKey rsaPublicKey = KeyUtil.getRSAPublicKey(aPrivate); + Assert.assertEquals(rsaPublicKey, keyPair.getPublic()); + } } From 6b6366e1ef7423c94c4f584241818bae190ae583 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 28 May 2020 01:16:42 +0800 Subject: [PATCH 155/157] add default method --- CHANGELOG.md | 3 + .../cn/hutool/cache/GlobalPruneTimer.java | 2 +- .../src/main/java/cn/hutool/core/img/Img.java | 2 +- .../test/java/cn/hutool/core/img/ImgTest.java | 7 + .../java/cn/hutool/core/img/ImgUtilTest.java | 3 +- .../hutool/poi/excel/sax/AttributeName.java | 45 ++++ .../cn/hutool/poi/excel/sax/CellDataType.java | 2 +- .../cn/hutool/poi/excel/sax/ElementName.java | 28 +++ .../poi/excel/sax/Excel03SaxReader.java | 52 +++-- .../poi/excel/sax/Excel07SaxReader.java | 206 ++++++++++-------- .../poi/excel/sax/handler/RowHandler.java | 20 +- .../poi/excel/test/ExcelSaxReadTest.java | 41 +++- 12 files changed, 298 insertions(+), 113 deletions(-) create mode 100644 hutool-poi/src/main/java/cn/hutool/poi/excel/sax/AttributeName.java create mode 100644 hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ElementName.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 321be47da..6fc54589b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,10 +22,13 @@ * 【core 】 ImgUtil增加去除背景色的方法(pr#124@Gitee) * 【system 】 OshiUtil增加获取CPU使用率的方法(pr#124@Gitee) * 【crypto 】 AsymmetricAlgorithm去除EC(issue#887@Github) +* 【cache 】 超时缓存使用的线程池大小默认为1(issue#890@Github) +* 【poi 】 ExcelSaxReader支持handleCell方法 ### Bug修复 * 【core 】 修复SimpleCache死锁问题(issue#I1HOKB@Gitee) * 【core 】 修复SemaphoreRunnable释放问题(issue#I1HLQQ@Gitee) +* 【poi 】 修复Sax方式读取Excel行号错误问题(issue#882@Gitee) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-cache/src/main/java/cn/hutool/cache/GlobalPruneTimer.java b/hutool-cache/src/main/java/cn/hutool/cache/GlobalPruneTimer.java index cff84d52e..dea22235e 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/GlobalPruneTimer.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/GlobalPruneTimer.java @@ -56,7 +56,7 @@ public enum GlobalPruneTimer { if (null != pruneTimer) { shutdownNow(); } - this.pruneTimer = new ScheduledThreadPoolExecutor(16, r -> ThreadUtil.newThread(r, StrUtil.format("Pure-Timer-{}", cacheTaskNumber.getAndIncrement()))); + this.pruneTimer = new ScheduledThreadPoolExecutor(1, r -> ThreadUtil.newThread(r, StrUtil.format("Pure-Timer-{}", cacheTaskNumber.getAndIncrement()))); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/img/Img.java b/hutool-core/src/main/java/cn/hutool/core/img/Img.java index 74ae47517..9f6c6112d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/Img.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/Img.java @@ -554,7 +554,7 @@ public class Img implements Serializable { * @return 处理过的图片 */ public Image getImg() { - return this.targetImage; + return null == this.targetImage ? this.srcImage : this.targetImage; } /** diff --git a/hutool-core/src/test/java/cn/hutool/core/img/ImgTest.java b/hutool-core/src/test/java/cn/hutool/core/img/ImgTest.java index 8548e7c4b..6e2036e22 100644 --- a/hutool-core/src/test/java/cn/hutool/core/img/ImgTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/img/ImgTest.java @@ -20,6 +20,13 @@ public class ImgTest { public void compressTest() { Img.from(FileUtil.file("f:/test/4347273249269e3fb272341acc42d4e.jpg")).setQuality(0.8).write(FileUtil.file("f:/test/test_dest.jpg")); } + + @Test + @Ignore + public void writeTest() { + final Img from = Img.from(FileUtil.file("d:/test/81898311-001d6100-95eb-11ea-83c2-a14d7b1010bd.png")); + ImgUtil.write(from.getImg(), FileUtil.file("d:/test/dest.jpg")); + } @Test @Ignore diff --git a/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java index 401010512..b27be1f37 100644 --- a/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/img/ImgUtilTest.java @@ -102,7 +102,8 @@ public class ImgUtilTest { @Test @Ignore public void compressTest() { - ImgUtil.compress(FileUtil.file("e:/pic/1111.png"), FileUtil.file("e:/pic/1111_target.jpg"), 0.8f); + ImgUtil.compress(FileUtil.file("d:/test/dest.png"), + FileUtil.file("d:/test/1111_target.jpg"), 0.1f); } @Test diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/AttributeName.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/AttributeName.java new file mode 100644 index 000000000..9a51f77eb --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/AttributeName.java @@ -0,0 +1,45 @@ +package cn.hutool.poi.excel.sax; + +import org.xml.sax.Attributes; + +/** + * Excel的XML中属性名枚举 + * + * @author looly + * @since 5.3.6 + */ +public enum AttributeName { + + /** + * 行列号属性,行标签下此为行号属性名,cell标签下下为列号属性名 + */ + r, + /** + * ST(StylesTable) 的索引,样式index,用于获取行或单元格样式 + */ + s, + /** + * Type类型,单元格类型属性,见{@link CellDataType} + */ + t; + + /** + * 是否匹配给定属性 + * + * @param attributeName 属性 + * @return 是否匹配 + */ + public boolean match(String attributeName) { + return this.name().equals(attributeName); + } + + /** + * 从属性里列表中获取对应属性值 + * + * @param attributes 属性列表 + * @return 属性值 + */ + public String getValue(Attributes attributes){ + return attributes.getValue(name()); + } +} diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/CellDataType.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/CellDataType.java index 06273dcff..44baadb39 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/CellDataType.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/CellDataType.java @@ -15,7 +15,7 @@ public enum CellDataType { FORMULA("str"), /** 富文本类型 */ INLINESTR("inlineStr"), - /** 字符串类型 */ + /** 共享字符串索引类型 */ SSTINDEX("s"), /** 数字类型 */ NUMBER(""), diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ElementName.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ElementName.java new file mode 100644 index 000000000..08614fc8e --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ElementName.java @@ -0,0 +1,28 @@ +package cn.hutool.poi.excel.sax; + +/** + * 标签名枚举 + * + * @author looly + * @since 5.3.6 + */ +public enum ElementName { + /** + * 行标签名,表示一行 + */ + row, + /** + * 单元格标签名,表示一个单元格 + */ + c; + + /** + * 给定标签名是否匹配当前标签 + * + * @param elementName 标签名 + * @return 是否匹配 + */ + public boolean match(String elementName){ + return this.name().equals(elementName); + } +} diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel03SaxReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel03SaxReader.java index d8d68103b..da8d6152b 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel03SaxReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel03SaxReader.java @@ -18,6 +18,7 @@ import org.apache.poi.hssf.record.BOFRecord; import org.apache.poi.hssf.record.BlankRecord; import org.apache.poi.hssf.record.BoolErrRecord; import org.apache.poi.hssf.record.BoundSheetRecord; +import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.hssf.record.FormulaRecord; import org.apache.poi.hssf.record.LabelRecord; import org.apache.poi.hssf.record.LabelSSTRecord; @@ -77,7 +78,7 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i * 自定义需要处理的sheet编号,如果-1表示处理所有sheet */ private int rid = -1; - // 当前表索引 + // 当前rid索引 private int curRid = -1; private final RowHandler rowHandler; @@ -193,7 +194,7 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i if (record instanceof MissingCellDummyRecord) { // 空值的操作 MissingCellDummyRecord mc = (MissingCellDummyRecord) record; - addToRowCellList(mc.getColumn(), StrUtil.EMPTY); + addToRowCellList(mc); } else if (record instanceof LastCellOfRowDummyRecord) { // 行结束 processLastCell((LastCellOfRowDummyRecord) record); @@ -208,17 +209,40 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i // ---------------------------------------------------------------------------------------------- Private method start /** - * 将单元格数据加入到行列表中 - * @param index 加入位置 - * @param value 值 + * 将空数据加入到行列表中 + * + * @param record MissingCellDummyRecord */ - private void addToRowCellList(int index, Object value){ - while(index > this.rowCellList.size()){ + private void addToRowCellList(MissingCellDummyRecord record) { + addToRowCellList(record.getRow(), record.getColumn(), StrUtil.EMPTY); + } + + /** + * 将单元格数据加入到行列表中 + * + * @param record 单元格 + * @param value 值 + */ + private void addToRowCellList(CellValueRecordInterface record, Object value) { + addToRowCellList(record.getRow(), record.getColumn(), value); + } + + /** + * 将单元格数据加入到行列表中 + * + * @param row 行号 + * @param column 单元格 + * @param value 值 + */ + private void addToRowCellList(int row, int column, Object value) { + while (column > this.rowCellList.size()) { // 对于中间无数据的单元格补齐空白 this.rowCellList.add(StrUtil.EMPTY); + this.rowHandler.handleCell(this.curRid, row, rowCellList.size() - 1, value, null); } - this.rowCellList.add(index, value); + this.rowCellList.add(column, value); + this.rowHandler.handleCell(this.curRid, row, column, value, null); } /** @@ -232,12 +256,12 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i switch (record.getSid()) { case BlankRecord.sid: // 空白记录 - addToRowCellList(((BlankRecord) record).getColumn(), StrUtil.EMPTY); + addToRowCellList(((BlankRecord) record), StrUtil.EMPTY); break; case BoolErrRecord.sid: // 布尔类型 final BoolErrRecord berec = (BoolErrRecord) record; - addToRowCellList(berec.getColumn(), berec.getBooleanValue()); + addToRowCellList(berec, berec.getBooleanValue()); break; case FormulaRecord.sid: // 公式类型 @@ -253,7 +277,7 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i } else { value = StrUtil.wrap(HSSFFormulaParser.toFormulaString(stubWorkbook, formulaRec.getParsedExpression()), "\""); } - addToRowCellList(formulaRec.getColumn(), value); + addToRowCellList(formulaRec, value); break; case StringRecord.sid: // 单元格中公式的字符串 @@ -266,7 +290,7 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i case LabelRecord.sid: final LabelRecord lrec = (LabelRecord) record; value = lrec.getValue(); - addToRowCellList(lrec.getColumn(), value); + addToRowCellList(lrec, value); break; case LabelSSTRecord.sid: // 字符串类型 @@ -274,7 +298,7 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i if (null != sstRecord) { value = sstRecord.getString(lsrec.getSSTIndex()).toString(); } - addToRowCellList(lsrec.getColumn(), ObjectUtil.defaultIfNull(value, StrUtil.EMPTY)); + addToRowCellList(lsrec, ObjectUtil.defaultIfNull(value, StrUtil.EMPTY)); break; case NumberRecord.sid: // 数字类型 final NumberRecord numrec = (NumberRecord) record; @@ -296,7 +320,7 @@ public class Excel03SaxReader extends AbstractExcelSaxReader i } } // 向容器加入列值 - addToRowCellList(numrec.getColumn(), value); + addToRowCellList(numrec, value); break; default: break; diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java index a8ec63665..8557a08ac 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java @@ -1,6 +1,7 @@ package cn.hutool.poi.excel.sax; import cn.hutool.core.io.IoUtil; +import cn.hutool.core.text.StrBuilder; import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.sax.handler.RowHandler; import cn.hutool.poi.exceptions.POIException; @@ -29,52 +30,39 @@ import java.util.List; */ public class Excel07SaxReader extends AbstractExcelSaxReader implements ContentHandler { - /** - * Cell单元格元素 - */ - private static final String C_ELEMENT = "c"; - /** - * 行元素 - */ - private static final String ROW_ELEMENT = "row"; - /** - * Cell中的行列号(Reference),行模式下此为行号属性名,列模式下为列号属性名 - */ - private static final String R_ATTR = "r"; - /** - * SST(SharedStringsTable) 的索引,样式index - */ - private static final String S_ATTR_VALUE = "s"; - // 列中属性值 - private static final String T_ATTR_VALUE = "t"; // sheet r:Id前缀 private static final String RID_PREFIX = "rId"; + // 单元格的格式表,对应style.xml + private StylesTable stylesTable; // excel 2007 的共享字符串表,对应sharedString.xml private SharedStringsTable sharedStringsTable; - // 当前行 - private int curRow; + // sheet的索引 + private int sheetIndex; + + // 当前非空行 + private int index; // 当前列 private int curCell; - // 上一次的内容 - private final StringBuilder lastContent = new StringBuilder(64); // 单元数据类型 private CellDataType cellDataType; + // 当前行号,从0开始 + private long rowNumber; // 当前列坐标, 如A1,B5 private String curCoordinate; // 前一个列的坐标 private String preCoordinate; // 行的最大列坐标 private String maxCellCoordinate; - // 单元格的格式表,对应style.xml - private StylesTable stylesTable; + // 单元格样式 + private XSSFCellStyle xssfCellStyle; // 单元格存储的格式化字符串,nmtFmt的formatCode属性的值 private String numFmtString; - // sheet的索引 - private int sheetIndex; + // 上一次的内容 + private final StrBuilder lastContent = StrUtil.strBuilder(); // 存储每行的列元素 - List rowCellList = new ArrayList<>(); + private List rowCellList = new ArrayList<>(); /** * 行处理器 @@ -136,9 +124,9 @@ public class Excel07SaxReader extends AbstractExcelSaxReader i final XSSFReader xssfReader = new XSSFReader(opcPackage); // 获取共享样式表 - try{ + try { stylesTable = xssfReader.getStylesTable(); - } catch (Exception e){ + } catch (Exception e) { //ignore } // 获取共享字符串表 @@ -155,7 +143,7 @@ public class Excel07SaxReader extends AbstractExcelSaxReader i final Iterator sheetInputStreams = xssfReader.getSheetsData(); while (sheetInputStreams.hasNext()) { // 重新读取一个sheet时行归零 - curRow = 0; + index = 0; this.sheetIndex++; sheetInputStream = sheetInputStreams.next(); ExcelSaxUtil.readFrom(sheetInputStream, this); @@ -178,24 +166,11 @@ public class Excel07SaxReader extends AbstractExcelSaxReader i */ @Override public void startElement(String uri, String localName, String qName, Attributes attributes) { - // 单元格元素 - if (C_ELEMENT.equals(localName)) { - // 获取当前列坐标 - String tempCurCoordinate = attributes.getValue(R_ATTR); - // 前一列为null,则将其设置为"@",A为第一列,ascii码为65,前一列即为@,ascii码64 - if (preCoordinate == null) { - preCoordinate = String.valueOf(ExcelSaxUtil.CELL_FILL_CHAR); - } else { - // 存在,则前一列要设置为上一列的坐标 - preCoordinate = curCoordinate; - } - // 重置当前列 - curCoordinate = tempCurCoordinate; - // 设置单元格类型 - setCellType(attributes); + if (ElementName.row.match(localName)) {// 行开始 + startRow(attributes); + } else if (ElementName.c.match(localName)) {// 单元格元素 + startCell(attributes); } - - lastContent.setLength(0); } /** @@ -203,42 +178,10 @@ public class Excel07SaxReader extends AbstractExcelSaxReader i */ @Override public void endElement(String uri, String localName, String qName) { - final String contentStr = StrUtil.trim(lastContent); - -// if (T_ELEMENT.equals(qName)) { -// // type标签 -// // rowCellList.add(curCell++, contentStr); -// } else - if (C_ELEMENT.equals(localName)) { - // cell标签 - Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); - // 补全单元格之间的空格 - fillBlankCell(preCoordinate, curCoordinate, false); - rowCellList.add(curCell++, value); - } else if (ROW_ELEMENT.equals(localName)) { - // 如果是row标签,说明已经到了一行的结尾 - // 最大列坐标以第一行的为准 - if (curRow == 0) { - maxCellCoordinate = curCoordinate; - } - - // 补全一行尾部可能缺失的单元格 - if (maxCellCoordinate != null) { - fillBlankCell(curCoordinate, maxCellCoordinate, true); - } - - rowHandler.handle(sheetIndex, curRow, rowCellList); - - // 一行结束 - // 新建一个新列,之前的列抛弃(可能被回收或rowHandler处理) - rowCellList = new ArrayList<>(curCell + 1); - // 行数增加 - curRow++; - // 当前列置0 - curCell = 0; - // 置空当前列坐标和前一列坐标 - curCoordinate = null; - preCoordinate = null; + if (ElementName.c.match(localName)) { // 单元格结束 + endCell(); + } else if (ElementName.row.match(localName)) {// 行结束 + endRow(); } } @@ -297,6 +240,89 @@ public class Excel07SaxReader extends AbstractExcelSaxReader i // --------------------------------------------------------------------------------------- Pass method end // --------------------------------------------------------------------------------------- Private method start + + /** + * 行开始 + * + * @param attributes 属性列表 + */ + private void startRow(Attributes attributes) { + this.rowNumber = Long.parseLong(AttributeName.r.getValue(attributes)) - 1; + } + + /** + * 单元格开始 + * + * @param attributes 属性列表 + */ + private void startCell(Attributes attributes) { + // 获取当前列坐标 + final String tempCurCoordinate = AttributeName.r.getValue(attributes); + // 前一列为null,则将其设置为"@",A为第一列,ascii码为65,前一列即为@,ascii码64 + if (preCoordinate == null) { + preCoordinate = String.valueOf(ExcelSaxUtil.CELL_FILL_CHAR); + } else { + // 存在,则前一列要设置为上一列的坐标 + preCoordinate = curCoordinate; + } + // 重置当前列 + curCoordinate = tempCurCoordinate; + // 设置单元格类型 + setCellType(attributes); + + // 清空之前的数据 + lastContent.reset(); + } + + /** + * 一个单元格结尾 + */ + private void endCell() { + final String contentStr = StrUtil.trim(lastContent); + final Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString); + // 补全单元格之间的空格 + fillBlankCell(preCoordinate, curCoordinate, false); + addCellValue(curCell++, value); + } + + /** + * 一行结尾 + */ + private void endRow() { + // 最大列坐标以第一个非空行的为准 + if (index == 0) { + maxCellCoordinate = curCoordinate; + } + + // 补全一行尾部可能缺失的单元格 + if (maxCellCoordinate != null) { + fillBlankCell(curCoordinate, maxCellCoordinate, true); + } + + rowHandler.handle(sheetIndex, rowNumber, rowCellList); + + // 一行结束 + // 新建一个新列,之前的列抛弃(可能被回收或rowHandler处理) + rowCellList = new ArrayList<>(curCell + 1); + // 行数增加 + index++; + // 当前列置0 + curCell = 0; + // 置空当前列坐标和前一列坐标 + curCoordinate = null; + preCoordinate = null; + } + + /** + * 在一行中的指定列增加值 + * @param index 位置 + * @param value 值 + */ + private void addCellValue(int index, Object value){ + this.rowCellList.add(index, value); + this.rowHandler.handleCell(this.sheetIndex, this.rowNumber, index, value, this.xssfCellStyle); + } + /** * 填充空白单元格,如果前一个单元格大于后一个,不需要填充
    * @@ -311,7 +337,7 @@ public class Excel07SaxReader extends AbstractExcelSaxReader i len++; } while (len-- > 0) { - rowCellList.add(curCell++, ""); + addCellValue(curCell++, ""); } } } @@ -319,19 +345,19 @@ public class Excel07SaxReader extends AbstractExcelSaxReader i /** * 设置单元格的类型 * - * @param attribute 属性 + * @param attributes 属性 */ - private void setCellType(Attributes attribute) { + private void setCellType(Attributes attributes) { // numFmtString的值 numFmtString = ""; - this.cellDataType = CellDataType.of(attribute.getValue(T_ATTR_VALUE)); + this.cellDataType = CellDataType.of(AttributeName.t.getValue(attributes)); // 获取单元格的xf索引,对应style.xml中cellXfs的子元素xf - if(null != this.stylesTable){ - final String xfIndexStr = attribute.getValue(S_ATTR_VALUE); + if (null != this.stylesTable) { + final String xfIndexStr = AttributeName.s.getValue(attributes); if (null != xfIndexStr) { int xfIndex = Integer.parseInt(xfIndexStr); - final XSSFCellStyle xssfCellStyle = stylesTable.getStyleAt(xfIndex); + this.xssfCellStyle = stylesTable.getStyleAt(xfIndex); numFmtString = xssfCellStyle.getDataFormatString(); // 单元格存储格式的索引,对应style.xml中的numFmts元素的子元素索引 int numFmtIndex = xssfCellStyle.getDataFormat(); diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/RowHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/RowHandler.java index f76f534e7..aff168c36 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/RowHandler.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/handler/RowHandler.java @@ -1,5 +1,7 @@ package cn.hutool.poi.excel.sax.handler; +import org.apache.poi.ss.usermodel.CellStyle; + import java.util.List; /** @@ -9,12 +11,24 @@ import java.util.List; */ @FunctionalInterface public interface RowHandler { - + + /** + * 处理一个单元格的数据 + * @param sheetIndex 当前Sheet序号 + * @param rowIndex 当前行号 + * @param cellIndex 当前列号 + * @param value 单元格的值 + * @param xssfCellStyle 单元格样式 + */ + default void handleCell(int sheetIndex, long rowIndex, int cellIndex, Object value, CellStyle xssfCellStyle){ + //pass + } + /** * 处理一行数据 * @param sheetIndex 当前Sheet序号 - * @param rowIndex 当前行号 + * @param rowIndex 当前行号,从0开始计数 * @param rowList 行数据列表 */ - void handle(int sheetIndex, int rowIndex, List rowList); + void handle(int sheetIndex, long rowIndex, List rowList); } diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelSaxReadTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelSaxReadTest.java index fb3393fcd..e3764c82c 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelSaxReadTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelSaxReadTest.java @@ -7,15 +7,17 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.ExcelUtil; import cn.hutool.poi.excel.sax.Excel03SaxReader; import cn.hutool.poi.excel.sax.handler.RowHandler; +import org.apache.poi.ss.usermodel.CellStyle; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; +import java.util.List; + /** * Excel sax方式读取 - * - * @author looly * + * @author looly */ public class ExcelSaxReadTest { @@ -64,4 +66,39 @@ public class ExcelSaxReadTest { } }; } + + @Test + @Ignore + public void handle07CellTest() { + ExcelUtil.readBySax("d:/test/test.xlsx", -1, new RowHandler() { + + @Override + public void handleCell(int sheetIndex, long rowIndex, int cellIndex, Object value, CellStyle xssfCellStyle) { + Console.log("{} {} {}", rowIndex, cellIndex, value); + } + + @Override + public void handle(int sheetIndex, long rowIndex, List rowList) { + + } + } + ); + } + + @Test + @Ignore + public void handle03CellTest() { + ExcelUtil.readBySax("d:/test/test.xls", -1, new RowHandler() { + + @Override + public void handleCell(int sheetIndex, long rowIndex, int cellIndex, Object value, CellStyle xssfCellStyle) { + Console.log("{} {} {}", rowIndex, cellIndex, value); + } + + @Override + public void handle(int sheetIndex, long rowIndex, List rowList) { + } + } + ); + } } From 8bcb1daf9907ba1556b54b31599991e1cb0a5c59 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 28 May 2020 01:30:18 +0800 Subject: [PATCH 156/157] support back --- CHANGELOG.md | 1 + .../src/main/java/cn/hutool/core/lang/Snowflake.java | 12 +++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fc54589b..271661c8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ * 【crypto 】 AsymmetricAlgorithm去除EC(issue#887@Github) * 【cache 】 超时缓存使用的线程池大小默认为1(issue#890@Github) * 【poi 】 ExcelSaxReader支持handleCell方法 +* 【core 】 Snowflake容忍2秒内的时间回拨(issue#I1IGDX@Gitee) ### Bug修复 * 【core 】 修复SimpleCache死锁问题(issue#I1HOKB@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java b/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java index 44908d7d0..ba031fec5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java @@ -141,10 +141,16 @@ public class Snowflake implements Serializable { public synchronized long nextId() { long timestamp = genTime(); if (timestamp < lastTimestamp) { - // 如果服务器时间有问题(时钟后退) 报错。 - throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp)); + if(lastTimestamp - timestamp < 2000){ + // 容忍2秒内的回拨,避免NTP校时造成的异常 + timestamp = lastTimestamp; + } else{ + // 如果服务器时间有问题(时钟后退) 报错。 + throw new IllegalStateException(StrUtil.format("Clock moved backwards. Refusing to generate id for {}ms", lastTimestamp - timestamp)); + } } - if (lastTimestamp == timestamp) { + + if (timestamp == lastTimestamp) { sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); From ff327057e43fecbcb2926172ca255df3f016c35f Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 28 May 2020 01:33:59 +0800 Subject: [PATCH 157/157] del sun class --- .../src/main/java/cn/hutool/core/img/FontUtil.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/img/FontUtil.java b/hutool-core/src/main/java/cn/hutool/core/img/FontUtil.java index 6c3f52ab0..c6b2502d7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/FontUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/FontUtil.java @@ -2,7 +2,6 @@ package cn.hutool.core.img; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.io.IORuntimeException; -import sun.font.FontDesignMetrics; import java.awt.Dimension; import java.awt.Font; @@ -94,18 +93,6 @@ public class FontUtil { } } - /** - * 获得字体对应字符串的长宽信息 - * - * @param font 字体 - * @param str 字符串 - * @return 长宽信息 - */ - public static Dimension getDimension(Font font, String str) { - final FontMetrics metrics = FontDesignMetrics.getMetrics(font); - return getDimension(FontDesignMetrics.getMetrics(font), str); - } - /** * 获得字体对应字符串的长宽信息 *